feat: implement user reporting system with dedicated module, service, controller, DTOs, and Prisma schema updates.
This commit is contained in:
@@ -20,6 +20,7 @@ import { CmsModule } from './cms';
|
||||
import { NotificationsModule } from './notifications';
|
||||
import { TestimonialsModule } from './testimonials';
|
||||
import { SupportChatModule } from './support-chat';
|
||||
import { UserReportsModule } from './user-reports';
|
||||
import { StripeModule } from './stripe';
|
||||
import { AppController } from './app.controller';
|
||||
import { AppService } from './app.service';
|
||||
@@ -64,6 +65,7 @@ import { AppService } from './app.service';
|
||||
NotificationsModule,
|
||||
TestimonialsModule,
|
||||
SupportChatModule,
|
||||
UserReportsModule,
|
||||
StripeModule,
|
||||
],
|
||||
controllers: [AppController],
|
||||
|
||||
24
src/user-reports/dto/create-report.dto.ts
Normal file
24
src/user-reports/dto/create-report.dto.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
||||
import { IsString, IsNotEmpty, IsOptional, IsUUID } from 'class-validator';
|
||||
|
||||
export class CreateReportDto {
|
||||
@ApiProperty({ description: 'ID of the user being reported' })
|
||||
@IsUUID()
|
||||
@IsNotEmpty()
|
||||
reportedUserId: string;
|
||||
|
||||
@ApiPropertyOptional({ description: 'Conversation ID where the issue occurred' })
|
||||
@IsUUID()
|
||||
@IsOptional()
|
||||
conversationId?: string;
|
||||
|
||||
@ApiProperty({ description: 'Reason for the report', example: 'Spam' })
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
reason: string;
|
||||
|
||||
@ApiPropertyOptional({ description: 'Additional details about the report' })
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
description?: string;
|
||||
}
|
||||
2
src/user-reports/dto/index.ts
Normal file
2
src/user-reports/dto/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export { CreateReportDto } from './create-report.dto';
|
||||
export { UpdateReportDto } from './update-report.dto';
|
||||
14
src/user-reports/dto/update-report.dto.ts
Normal file
14
src/user-reports/dto/update-report.dto.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
||||
import { IsString, IsOptional, IsEnum } from 'class-validator';
|
||||
import { ReportStatus } from '@prisma/client';
|
||||
|
||||
export class UpdateReportDto {
|
||||
@ApiProperty({ enum: ReportStatus, description: 'New status for the report' })
|
||||
@IsEnum(ReportStatus)
|
||||
status: ReportStatus;
|
||||
|
||||
@ApiPropertyOptional({ description: 'Admin notes about the report' })
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
adminNotes?: string;
|
||||
}
|
||||
3
src/user-reports/index.ts
Normal file
3
src/user-reports/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export { UserReportsModule } from './user-reports.module';
|
||||
export { UserReportsService } from './user-reports.service';
|
||||
export { UserReportsController } from './user-reports.controller';
|
||||
87
src/user-reports/user-reports.controller.ts
Normal file
87
src/user-reports/user-reports.controller.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
import {
|
||||
Controller,
|
||||
Get,
|
||||
Post,
|
||||
Patch,
|
||||
Body,
|
||||
Param,
|
||||
Query,
|
||||
UseGuards,
|
||||
HttpCode,
|
||||
HttpStatus,
|
||||
} from '@nestjs/common';
|
||||
import { ApiTags, ApiOperation, ApiResponse, ApiBearerAuth, ApiQuery, ApiParam } from '@nestjs/swagger';
|
||||
import { UserReportsService } from './user-reports.service';
|
||||
import { CreateReportDto, UpdateReportDto } from './dto';
|
||||
import { JwtAuthGuard } from '../auth/guards';
|
||||
import { CurrentUser } from '../auth/decorators';
|
||||
import { Roles } from '../auth/decorators/roles.decorator';
|
||||
import { UserRole, ReportStatus } from '@prisma/client';
|
||||
|
||||
@ApiTags('User Reports')
|
||||
@Controller('user-reports')
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@ApiBearerAuth('JWT-auth')
|
||||
export class UserReportsController {
|
||||
constructor(private readonly userReportsService: UserReportsService) {}
|
||||
|
||||
// ==========================================
|
||||
// USER: SUBMIT A REPORT
|
||||
// ==========================================
|
||||
@Post()
|
||||
@HttpCode(HttpStatus.CREATED)
|
||||
@ApiOperation({ summary: 'Submit a report against a user' })
|
||||
async createReport(
|
||||
@CurrentUser('id') reporterId: string,
|
||||
@Body() dto: CreateReportDto,
|
||||
) {
|
||||
return this.userReportsService.createReport(reporterId, dto);
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// ADMIN: GET ALL REPORTS
|
||||
// ==========================================
|
||||
@Get('admin/all')
|
||||
@Roles(UserRole.ADMIN)
|
||||
@ApiOperation({ summary: 'Admin: Get all user reports' })
|
||||
@ApiQuery({ name: 'status', required: false, enum: ReportStatus })
|
||||
async getAllReports(@Query('status') status?: ReportStatus) {
|
||||
return this.userReportsService.getAllReports(status);
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// ADMIN: GET REPORT COUNTS
|
||||
// ==========================================
|
||||
@Get('admin/counts')
|
||||
@Roles(UserRole.ADMIN)
|
||||
@ApiOperation({ summary: 'Admin: Get report counts by status' })
|
||||
async getReportCounts() {
|
||||
return this.userReportsService.getReportCounts();
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// ADMIN: GET SINGLE REPORT
|
||||
// ==========================================
|
||||
@Get('admin/:id')
|
||||
@Roles(UserRole.ADMIN)
|
||||
@ApiOperation({ summary: 'Admin: Get a specific report' })
|
||||
@ApiParam({ name: 'id', description: 'Report ID' })
|
||||
async getReport(@Param('id') id: string) {
|
||||
return this.userReportsService.getReportById(id);
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// ADMIN: UPDATE REPORT STATUS
|
||||
// ==========================================
|
||||
@Patch('admin/:id')
|
||||
@Roles(UserRole.ADMIN)
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@ApiOperation({ summary: 'Admin: Update report status and notes' })
|
||||
@ApiParam({ name: 'id', description: 'Report ID' })
|
||||
async updateReport(
|
||||
@Param('id') id: string,
|
||||
@Body() dto: UpdateReportDto,
|
||||
) {
|
||||
return this.userReportsService.updateReport(id, dto.status, dto.adminNotes);
|
||||
}
|
||||
}
|
||||
12
src/user-reports/user-reports.module.ts
Normal file
12
src/user-reports/user-reports.module.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { UserReportsController } from './user-reports.controller';
|
||||
import { UserReportsService } from './user-reports.service';
|
||||
import { PrismaModule } from '../prisma/prisma.module';
|
||||
|
||||
@Module({
|
||||
imports: [PrismaModule],
|
||||
controllers: [UserReportsController],
|
||||
providers: [UserReportsService],
|
||||
exports: [UserReportsService],
|
||||
})
|
||||
export class UserReportsModule {}
|
||||
104
src/user-reports/user-reports.service.ts
Normal file
104
src/user-reports/user-reports.service.ts
Normal file
@@ -0,0 +1,104 @@
|
||||
import { Injectable, NotFoundException, BadRequestException } from '@nestjs/common';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
import { ReportStatus } from '@prisma/client';
|
||||
import { CreateReportDto } from './dto';
|
||||
|
||||
@Injectable()
|
||||
export class UserReportsService {
|
||||
constructor(private readonly prisma: PrismaService) {}
|
||||
|
||||
private readonly reportInclude = {
|
||||
reporter: {
|
||||
select: {
|
||||
id: true,
|
||||
email: true,
|
||||
userProfile: { select: { firstName: true, lastName: true } },
|
||||
agentProfile: { select: { firstName: true, lastName: true } },
|
||||
},
|
||||
},
|
||||
reportedUser: {
|
||||
select: {
|
||||
id: true,
|
||||
email: true,
|
||||
userProfile: { select: { firstName: true, lastName: true } },
|
||||
agentProfile: { select: { firstName: true, lastName: true } },
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
async createReport(reporterId: string, dto: CreateReportDto) {
|
||||
if (reporterId === dto.reportedUserId) {
|
||||
throw new BadRequestException('You cannot report yourself');
|
||||
}
|
||||
|
||||
// Check if user already reported this user recently (within 24h)
|
||||
const recentReport = await this.prisma.userReport.findFirst({
|
||||
where: {
|
||||
reporterId,
|
||||
reportedUserId: dto.reportedUserId,
|
||||
createdAt: { gte: new Date(Date.now() - 24 * 60 * 60 * 1000) },
|
||||
},
|
||||
});
|
||||
|
||||
if (recentReport) {
|
||||
throw new BadRequestException('You have already reported this user recently');
|
||||
}
|
||||
|
||||
return this.prisma.userReport.create({
|
||||
data: {
|
||||
reporterId,
|
||||
reportedUserId: dto.reportedUserId,
|
||||
conversationId: dto.conversationId,
|
||||
reason: dto.reason,
|
||||
description: dto.description,
|
||||
},
|
||||
include: this.reportInclude,
|
||||
});
|
||||
}
|
||||
|
||||
async getAllReports(status?: ReportStatus) {
|
||||
const where = status ? { status } : {};
|
||||
return this.prisma.userReport.findMany({
|
||||
where,
|
||||
include: this.reportInclude,
|
||||
orderBy: { createdAt: 'desc' },
|
||||
});
|
||||
}
|
||||
|
||||
async getReportById(id: string) {
|
||||
const report = await this.prisma.userReport.findUnique({
|
||||
where: { id },
|
||||
include: this.reportInclude,
|
||||
});
|
||||
|
||||
if (!report) {
|
||||
throw new NotFoundException('Report not found');
|
||||
}
|
||||
|
||||
return report;
|
||||
}
|
||||
|
||||
async updateReport(id: string, status: ReportStatus, adminNotes?: string) {
|
||||
const report = await this.prisma.userReport.findUnique({ where: { id } });
|
||||
if (!report) {
|
||||
throw new NotFoundException('Report not found');
|
||||
}
|
||||
|
||||
return this.prisma.userReport.update({
|
||||
where: { id },
|
||||
data: { status, adminNotes },
|
||||
include: this.reportInclude,
|
||||
});
|
||||
}
|
||||
|
||||
async getReportCounts() {
|
||||
const [pending, reviewed, resolved, dismissed, total] = await Promise.all([
|
||||
this.prisma.userReport.count({ where: { status: ReportStatus.PENDING } }),
|
||||
this.prisma.userReport.count({ where: { status: ReportStatus.REVIEWED } }),
|
||||
this.prisma.userReport.count({ where: { status: ReportStatus.RESOLVED } }),
|
||||
this.prisma.userReport.count({ where: { status: ReportStatus.DISMISSED } }),
|
||||
this.prisma.userReport.count(),
|
||||
]);
|
||||
return { pending, reviewed, resolved, dismissed, total };
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user