conflict fixed

This commit is contained in:
2025-12-11 09:58:29 +03:00
9 changed files with 138 additions and 0 deletions
@@ -0,0 +1,11 @@
-- CreateTable
CREATE TABLE "ContactMessage" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"email" TEXT NOT NULL,
"subject" TEXT,
"message" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "ContactMessage_pkey" PRIMARY KEY ("id")
);
+9
View File
@@ -135,3 +135,12 @@ model File {
@@index([folder, originalName])
}
model ContactMessage {
id String @id @default(uuid())
name String
email String
subject String?
message String
createdAt DateTime @default(now())
}
+2
View File
@@ -9,6 +9,7 @@ import { ItemModule } from './item/item.module';
import { FileModule } from './file/file.module';
import { RedisModule } from './redis/redis.module';
import { CacheModule } from './cache/cache.module';
import { ContactModule } from './contact/contact.module';
@Module({
imports: [
@@ -21,6 +22,7 @@ import { CacheModule } from './cache/cache.module';
FileModule,
RedisModule,
CacheModule,
ContactModule,
],
})
export class AppModule {}
+21
View File
@@ -0,0 +1,21 @@
import { Body, Controller, Get, Post, UseGuards } from '@nestjs/common';
import { ContactService } from './contact.service';
import { AllowAnonymous, AuthGuard } from '@thallesp/nestjs-better-auth';
import { CreateContactDto } from './dto/createContant.dto';
@Controller('contact')
@UseGuards(AuthGuard)
export class ContactController {
constructor(private readonly contactService: ContactService) {}
@Post()
@AllowAnonymous()
async create(@Body() data: CreateContactDto) {
return this.contactService.create(data);
}
@Get()
async findAll() {
return this.contactService.findAll();
}
}
+11
View File
@@ -0,0 +1,11 @@
import { Module } from '@nestjs/common';
import { ContactService } from './contact.service';
import { ContactController } from './contact.controller';
import { PrismaModule } from 'src/prisma/prisma.module';
@Module({
imports: [PrismaModule],
providers: [ContactService],
controllers: [ContactController],
})
export class ContactModule {}
+44
View File
@@ -0,0 +1,44 @@
import { Injectable } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
import { Prisma } from '@prisma/client';
import { emailService } from '../shared/services/email.service';
import { CreateContactDto } from './dto/createContant.dto';
@Injectable()
export class ContactService {
constructor(private prisma: PrismaService) {}
async create(data: CreateContactDto) {
const message = await this.prisma.contactMessage.create({
data: {
name: data.name,
email: data.email,
subject: data.subject,
message: data.message,
},
});
// Send email notification (fire and forget, or await if critical)
// We'll catch errors so it doesn't fail the request if email fails
try {
await emailService.sendContactMessageNotification({
name: message.name,
email: message.email,
subject: message.subject || undefined,
message: message.message,
});
} catch (error) {
console.error('Failed to send contact notification email:', error);
}
return message;
}
async findAll() {
return this.prisma.contactMessage.findMany({
orderBy: {
createdAt: 'desc',
},
});
}
}
+22
View File
@@ -0,0 +1,22 @@
import { IsString, IsEmail, IsNotEmpty, IsOptional, MaxLength } from 'class-validator';
export class CreateContactDto {
@IsString()
@IsNotEmpty()
@MaxLength(100)
name: string;
@IsEmail()
@IsNotEmpty()
email: string;
@IsString()
@IsOptional()
@MaxLength(20)
subject?: string;
@IsString()
@IsNotEmpty()
@MaxLength(1000)
message: string;
}
+3
View File
@@ -47,5 +47,8 @@ export const config = {
user: process.env.EMAIL_USER,
password: process.env.EMAIL_PASSWORD,
from: process.env.EMAIL_FROM || '"Zemenu" <no-reply@zemenu.com>',
contactNotificationEmails: process.env.CONTACT_NOTIFICATION_EMAILS
? process.env.CONTACT_NOTIFICATION_EMAILS.split(',').map(email => email.trim())
: [process.env.EMAIL_FROM || 'no-reply@zemenu.com'],
},
};
+15
View File
@@ -52,6 +52,21 @@ export class EmailService {
await this.sendEmail(to, 'Reset your password', html);
}
async sendContactMessageNotification(data: { name: string; email: string; subject?: string; message: string }) {
const content = `
<p>You have received a new contact message from <strong>${data.name}</strong> (${data.email}).</p>
<p><strong>Subject:</strong> ${data.subject || 'No Subject'}</p>
<hr style="border: 0; border-top: 1px solid #eee; margin: 20px 0;">
<p style="white-space: pre-wrap;">${data.message}</p>
`;
const html = this.getHtmlTemplate('New Contact Message', content);
// Send to the configured notification emails
const recipients = config.email.contactNotificationEmails;
await this.sendEmail(recipients.join(','), `New Contact Message: ${data.subject || 'No Subject'}`, html);
}
private getHtmlTemplate(title: string, content: string): string {
const logoUrl = 'https://res.cloudinary.com/dmxoohiwo/image/upload/v1764315105/logo_fpczsd.png';
const currentYear = new Date().getFullYear();