From dde1bf522f39f0e0290c9a86330e2f5bae977a03 Mon Sep 17 00:00:00 2001 From: Mathis HERRIOT <197931332+0x485254@users.noreply.github.com> Date: Thu, 8 Jan 2026 15:26:39 +0100 Subject: [PATCH] feat: implement ReportsModule with service, controller, and endpoints Added ReportsModule to manage user reports. Includes service methods for creating, retrieving, and updating report statuses, as well as controller endpoints for handling these operations. Integrated with authentication, role-based access control, and database logic. --- backend/src/reports/dto/create-report.dto.ts | 25 +++++++++ .../reports/dto/update-report-status.dto.ts | 13 +++++ backend/src/reports/reports.controller.ts | 54 +++++++++++++++++++ backend/src/reports/reports.module.ts | 13 +++++ backend/src/reports/reports.service.ts | 44 +++++++++++++++ 5 files changed, 149 insertions(+) create mode 100644 backend/src/reports/dto/create-report.dto.ts create mode 100644 backend/src/reports/dto/update-report-status.dto.ts create mode 100644 backend/src/reports/reports.controller.ts create mode 100644 backend/src/reports/reports.module.ts create mode 100644 backend/src/reports/reports.service.ts diff --git a/backend/src/reports/dto/create-report.dto.ts b/backend/src/reports/dto/create-report.dto.ts new file mode 100644 index 0000000..4a5284d --- /dev/null +++ b/backend/src/reports/dto/create-report.dto.ts @@ -0,0 +1,25 @@ +import { IsEnum, IsOptional, IsString, IsUUID } from "class-validator"; + +export enum ReportReason { + INAPPROPRIATE = "inappropriate", + SPAM = "spam", + COPYRIGHT = "copyright", + OTHER = "other", +} + +export class CreateReportDto { + @IsOptional() + @IsUUID() + contentId?: string; + + @IsOptional() + @IsUUID() + tagId?: string; + + @IsEnum(ReportReason) + reason!: "inappropriate" | "spam" | "copyright" | "other"; + + @IsOptional() + @IsString() + description?: string; +} diff --git a/backend/src/reports/dto/update-report-status.dto.ts b/backend/src/reports/dto/update-report-status.dto.ts new file mode 100644 index 0000000..a6e50f9 --- /dev/null +++ b/backend/src/reports/dto/update-report-status.dto.ts @@ -0,0 +1,13 @@ +import { IsEnum } from "class-validator"; + +export enum ReportStatus { + PENDING = "pending", + REVIEWED = "reviewed", + RESOLVED = "resolved", + DISMISSED = "dismissed", +} + +export class UpdateReportStatusDto { + @IsEnum(ReportStatus) + status!: "pending" | "reviewed" | "resolved" | "dismissed"; +} diff --git a/backend/src/reports/reports.controller.ts b/backend/src/reports/reports.controller.ts new file mode 100644 index 0000000..74cd121 --- /dev/null +++ b/backend/src/reports/reports.controller.ts @@ -0,0 +1,54 @@ +import { + Body, + Controller, + DefaultValuePipe, + Get, + Param, + ParseIntPipe, + Patch, + Post, + Query, + Req, + UseGuards, +} from "@nestjs/common"; +import { Roles } from "../auth/decorators/roles.decorator"; +import { AuthGuard } from "../auth/guards/auth.guard"; +import { RolesGuard } from "../auth/guards/roles.guard"; +import type { AuthenticatedRequest } from "../common/interfaces/request.interface"; +import { CreateReportDto } from "./dto/create-report.dto"; +import { UpdateReportStatusDto } from "./dto/update-report-status.dto"; +import { ReportsService } from "./reports.service"; + +@Controller("reports") +export class ReportsController { + constructor(private readonly reportsService: ReportsService) {} + + @Post() + @UseGuards(AuthGuard) + create( + @Req() req: AuthenticatedRequest, + @Body() createReportDto: CreateReportDto, + ) { + return this.reportsService.create(req.user.sub, createReportDto); + } + + @Get() + @UseGuards(AuthGuard, RolesGuard) + @Roles("admin", "moderator") + findAll( + @Query("limit", new DefaultValuePipe(10), ParseIntPipe) limit: number, + @Query("offset", new DefaultValuePipe(0), ParseIntPipe) offset: number, + ) { + return this.reportsService.findAll(limit, offset); + } + + @Patch(":id/status") + @UseGuards(AuthGuard, RolesGuard) + @Roles("admin", "moderator") + updateStatus( + @Param("id") id: string, + @Body() updateReportStatusDto: UpdateReportStatusDto, + ) { + return this.reportsService.updateStatus(id, updateReportStatusDto.status); + } +} diff --git a/backend/src/reports/reports.module.ts b/backend/src/reports/reports.module.ts new file mode 100644 index 0000000..5e43023 --- /dev/null +++ b/backend/src/reports/reports.module.ts @@ -0,0 +1,13 @@ +import { Module } from "@nestjs/common"; +import { AuthModule } from "../auth/auth.module"; +import { CryptoModule } from "../crypto/crypto.module"; +import { DatabaseModule } from "../database/database.module"; +import { ReportsController } from "./reports.controller"; +import { ReportsService } from "./reports.service"; + +@Module({ + imports: [DatabaseModule, AuthModule, CryptoModule], + controllers: [ReportsController], + providers: [ReportsService], +}) +export class ReportsModule {} diff --git a/backend/src/reports/reports.service.ts b/backend/src/reports/reports.service.ts new file mode 100644 index 0000000..3b9ea77 --- /dev/null +++ b/backend/src/reports/reports.service.ts @@ -0,0 +1,44 @@ +import { Injectable } from "@nestjs/common"; +import { desc, eq } from "drizzle-orm"; +import { DatabaseService } from "../database/database.service"; +import { reports } from "../database/schemas"; +import { CreateReportDto } from "./dto/create-report.dto"; + +@Injectable() +export class ReportsService { + constructor(private readonly databaseService: DatabaseService) {} + + async create(reporterId: string, data: CreateReportDto) { + const [newReport] = await this.databaseService.db + .insert(reports) + .values({ + reporterId, + contentId: data.contentId, + tagId: data.tagId, + reason: data.reason, + description: data.description, + }) + .returning(); + return newReport; + } + + async findAll(limit: number, offset: number) { + return await this.databaseService.db + .select() + .from(reports) + .orderBy(desc(reports.createdAt)) + .limit(limit) + .offset(offset); + } + + async updateStatus( + id: string, + status: "pending" | "reviewed" | "resolved" | "dismissed", + ) { + return await this.databaseService.db + .update(reports) + .set({ status, updatedAt: new Date() }) + .where(eq(reports.id, id)) + .returning(); + } +}