feat: Add contact form submission and message management functionality.

This commit is contained in:
pradeepkumar
2026-03-20 12:42:16 +05:30
parent 2914e74947
commit 8e9d623f24
6 changed files with 149 additions and 0 deletions

View File

@@ -743,3 +743,20 @@ model UserReport {
@@index([status]) @@index([status])
@@map("user_reports") @@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")
}

View File

@@ -21,6 +21,7 @@ import { NotificationsModule } from './notifications';
import { TestimonialsModule } from './testimonials'; import { TestimonialsModule } from './testimonials';
import { SupportChatModule } from './support-chat'; import { SupportChatModule } from './support-chat';
import { UserReportsModule } from './user-reports'; import { UserReportsModule } from './user-reports';
import { ContactModule } from './contact';
import { StripeModule } from './stripe'; import { StripeModule } from './stripe';
import { AppController } from './app.controller'; import { AppController } from './app.controller';
import { AppService } from './app.service'; import { AppService } from './app.service';
@@ -66,6 +67,7 @@ import { AppService } from './app.service';
TestimonialsModule, TestimonialsModule,
SupportChatModule, SupportChatModule,
UserReportsModule, UserReportsModule,
ContactModule,
StripeModule, StripeModule,
], ],
controllers: [AppController], controllers: [AppController],

View 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);
}
}

View 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 {}

View 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
View File

@@ -0,0 +1,2 @@
export { ContactModule } from './contact.module';
export { ContactService } from './contact.service';