feat: Add contact form submission and message management functionality.
This commit is contained in:
@@ -743,3 +743,20 @@ model UserReport {
|
||||
@@index([status])
|
||||
@@map("user_reports")
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// CONTACT MESSAGES
|
||||
// ============================================
|
||||
model ContactMessage {
|
||||
id String @id @default(uuid())
|
||||
name String
|
||||
email String
|
||||
phone String?
|
||||
message String
|
||||
isRead Boolean @default(false)
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
@@index([isRead])
|
||||
@@index([createdAt])
|
||||
@@map("contact_messages")
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import { NotificationsModule } from './notifications';
|
||||
import { TestimonialsModule } from './testimonials';
|
||||
import { SupportChatModule } from './support-chat';
|
||||
import { UserReportsModule } from './user-reports';
|
||||
import { ContactModule } from './contact';
|
||||
import { StripeModule } from './stripe';
|
||||
import { AppController } from './app.controller';
|
||||
import { AppService } from './app.service';
|
||||
@@ -66,6 +67,7 @@ import { AppService } from './app.service';
|
||||
TestimonialsModule,
|
||||
SupportChatModule,
|
||||
UserReportsModule,
|
||||
ContactModule,
|
||||
StripeModule,
|
||||
],
|
||||
controllers: [AppController],
|
||||
|
||||
74
src/contact/contact.controller.ts
Normal file
74
src/contact/contact.controller.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
import {
|
||||
Controller, Get, Post, Patch, Delete,
|
||||
Body, Param, Query, UseGuards, HttpCode, HttpStatus,
|
||||
} from '@nestjs/common';
|
||||
import { ApiTags, ApiOperation, ApiBearerAuth, ApiQuery } from '@nestjs/swagger';
|
||||
import { ContactService } from './contact.service';
|
||||
import { JwtAuthGuard } from '../auth/guards';
|
||||
import { Public } from '../auth/decorators';
|
||||
import { Roles } from '../auth/decorators/roles.decorator';
|
||||
import { UserRole } from '@prisma/client';
|
||||
import { IsString, IsEmail, IsOptional, IsNotEmpty } from 'class-validator';
|
||||
|
||||
class CreateContactMessageDto {
|
||||
@IsString() @IsNotEmpty() name: string;
|
||||
@IsEmail() email: string;
|
||||
@IsString() @IsOptional() phone?: string;
|
||||
@IsString() @IsNotEmpty() message: string;
|
||||
}
|
||||
|
||||
@ApiTags('Contact')
|
||||
@Controller('contact')
|
||||
export class ContactController {
|
||||
constructor(private readonly contactService: ContactService) {}
|
||||
|
||||
// Public - anyone can submit a contact form
|
||||
@Post()
|
||||
@Public()
|
||||
@HttpCode(HttpStatus.CREATED)
|
||||
@ApiOperation({ summary: 'Submit a contact form message' })
|
||||
async createMessage(@Body() dto: CreateContactMessageDto) {
|
||||
return this.contactService.createMessage(dto);
|
||||
}
|
||||
|
||||
// Admin only
|
||||
@Get('admin/all')
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Roles(UserRole.ADMIN)
|
||||
@ApiBearerAuth('JWT-auth')
|
||||
@ApiOperation({ summary: 'Admin: Get all contact messages' })
|
||||
@ApiQuery({ name: 'isRead', required: false, type: Boolean })
|
||||
async getAllMessages(@Query('isRead') isRead?: string) {
|
||||
const readFilter = isRead === 'true' ? true : isRead === 'false' ? false : undefined;
|
||||
return this.contactService.getAllMessages(readFilter);
|
||||
}
|
||||
|
||||
@Get('admin/counts')
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Roles(UserRole.ADMIN)
|
||||
@ApiBearerAuth('JWT-auth')
|
||||
@ApiOperation({ summary: 'Admin: Get contact message counts' })
|
||||
async getCounts() {
|
||||
return this.contactService.getCounts();
|
||||
}
|
||||
|
||||
@Patch('admin/:id/read')
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Roles(UserRole.ADMIN)
|
||||
@ApiBearerAuth('JWT-auth')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@ApiOperation({ summary: 'Admin: Mark a contact message as read' })
|
||||
async markAsRead(@Param('id') id: string) {
|
||||
return this.contactService.markAsRead(id);
|
||||
}
|
||||
|
||||
@Delete('admin/:id')
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Roles(UserRole.ADMIN)
|
||||
@ApiBearerAuth('JWT-auth')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@ApiOperation({ summary: 'Admin: Delete a contact message' })
|
||||
async deleteMessage(@Param('id') id: string) {
|
||||
return this.contactService.deleteMessage(id);
|
||||
}
|
||||
}
|
||||
12
src/contact/contact.module.ts
Normal file
12
src/contact/contact.module.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { ContactController } from './contact.controller';
|
||||
import { ContactService } from './contact.service';
|
||||
import { PrismaModule } from '../prisma/prisma.module';
|
||||
|
||||
@Module({
|
||||
imports: [PrismaModule],
|
||||
controllers: [ContactController],
|
||||
providers: [ContactService],
|
||||
exports: [ContactService],
|
||||
})
|
||||
export class ContactModule {}
|
||||
42
src/contact/contact.service.ts
Normal file
42
src/contact/contact.service.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import { Injectable, NotFoundException } from '@nestjs/common';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
|
||||
@Injectable()
|
||||
export class ContactService {
|
||||
constructor(private readonly prisma: PrismaService) {}
|
||||
|
||||
async createMessage(data: { name: string; email: string; phone?: string; message: string }) {
|
||||
return this.prisma.contactMessage.create({ data });
|
||||
}
|
||||
|
||||
async getAllMessages(isRead?: boolean) {
|
||||
const where = isRead !== undefined ? { isRead } : {};
|
||||
return this.prisma.contactMessage.findMany({
|
||||
where,
|
||||
orderBy: { createdAt: 'desc' },
|
||||
});
|
||||
}
|
||||
|
||||
async markAsRead(id: string) {
|
||||
const msg = await this.prisma.contactMessage.findUnique({ where: { id } });
|
||||
if (!msg) throw new NotFoundException('Contact message not found');
|
||||
return this.prisma.contactMessage.update({
|
||||
where: { id },
|
||||
data: { isRead: true },
|
||||
});
|
||||
}
|
||||
|
||||
async deleteMessage(id: string) {
|
||||
const msg = await this.prisma.contactMessage.findUnique({ where: { id } });
|
||||
if (!msg) throw new NotFoundException('Contact message not found');
|
||||
return this.prisma.contactMessage.delete({ where: { id } });
|
||||
}
|
||||
|
||||
async getCounts() {
|
||||
const [total, unread] = await Promise.all([
|
||||
this.prisma.contactMessage.count(),
|
||||
this.prisma.contactMessage.count({ where: { isRead: false } }),
|
||||
]);
|
||||
return { total, unread };
|
||||
}
|
||||
}
|
||||
2
src/contact/index.ts
Normal file
2
src/contact/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export { ContactModule } from './contact.module';
|
||||
export { ContactService } from './contact.service';
|
||||
Reference in New Issue
Block a user