/** * @copyright NHCarrigan * @license Naomi's Public License * @author Naomi Carrigan */ import { ReportStatus as PrismaReportStatus, ReportReason as PrismaReportReason, } from "@prisma/client"; import type { CreateReportDto, ProfileReportWithUsers, ReportStatus, UpdateReportDto, } from "@library/shared-types"; import { ReportReason } from "@library/shared-types"; import { prisma } from "../lib/prisma.js"; export class ReportService { private prisma = prisma; /** * Convert Prisma ReportReason to shared-types ReportReason */ private toPrismaReportReason(reason: ReportReason): PrismaReportReason { return reason as unknown as PrismaReportReason; } /** * Convert Prisma ReportStatus to shared-types ReportStatus */ private toPrismaReportStatus(status: ReportStatus): PrismaReportStatus { return status as unknown as PrismaReportStatus; } /** * Convert Prisma enum back to shared-types enum */ private fromPrismaReportReason(reason: PrismaReportReason): ReportReason { return reason as unknown as ReportReason; } /** * Convert Prisma enum back to shared-types enum */ private fromPrismaReportStatus(status: PrismaReportStatus): ReportStatus { return status as unknown as ReportStatus; } /** * Create a new profile report. * * @param reporterId - The ID of the user making the report * @param createDto - The report details * @returns The created report * @throws Error if user already has a pending report for this profile */ async createReport( reporterId: string, createDto: CreateReportDto, ): Promise { // Check if user already has a pending report for this profile const existingReport = await this.prisma.profileReport.findFirst({ where: { reporterId, reportedUserId: createDto.reportedUserId, status: PrismaReportStatus.PENDING, }, }); if (existingReport) { throw new Error( "You already have a pending report for this profile. Please wait for it to be reviewed.", ); } // Check if user has reached the limit of pending reports (5 max) const pendingReportsCount = await this.prisma.profileReport.count({ where: { reporterId, status: PrismaReportStatus.PENDING, }, }); if (pendingReportsCount >= 5) { throw new Error( "You have reached the maximum number of pending reports (5). Please wait for your existing reports to be reviewed.", ); } const report = await this.prisma.profileReport.create({ data: { reporterId, reportedUserId: createDto.reportedUserId, reason: this.toPrismaReportReason(createDto.reason), details: createDto.details, }, include: { reportedUser: { select: { id: true, username: true, displayName: true, avatar: true, }, }, reporter: { select: { id: true, username: true, displayName: true, avatar: true, }, }, }, }); return { id: report.id, reportedUserId: report.reportedUserId, reporterId: report.reporterId, reason: this.fromPrismaReportReason(report.reason), details: report.details, status: this.fromPrismaReportStatus(report.status), reviewedBy: report.reviewedBy ?? undefined, reviewNotes: report.reviewNotes ?? undefined, createdAt: report.createdAt, updatedAt: report.updatedAt, reportedUser: report.reportedUser, reporter: report.reporter, }; } /** * Get all reports (admin only). Optionally filter by status. * * @param status - Optional status filter * @returns All reports matching the filter */ async getAllReports( status?: ReportStatus, ): Promise { const reports = await this.prisma.profileReport.findMany({ where: status ? { status: this.toPrismaReportStatus(status) } : undefined, include: { reportedUser: { select: { id: true, username: true, displayName: true, avatar: true, }, }, reporter: { select: { id: true, username: true, displayName: true, avatar: true, }, }, reviewer: { select: { id: true, username: true, displayName: true, }, }, }, orderBy: { createdAt: "desc", }, }); return reports.map((report) => ({ id: report.id, reportedUserId: report.reportedUserId, reporterId: report.reporterId, reason: this.fromPrismaReportReason(report.reason), details: report.details, status: this.fromPrismaReportStatus(report.status), reviewedBy: report.reviewedBy ?? undefined, reviewNotes: report.reviewNotes ?? undefined, createdAt: report.createdAt, updatedAt: report.updatedAt, reportedUser: report.reportedUser, reporter: report.reporter, reviewer: report.reviewer ?? undefined, })); } /** * Get a single report by ID (admin only). * * @param id - The report ID * @returns The report or null */ async getReportById(id: string): Promise { const report = await this.prisma.profileReport.findUnique({ where: { id }, include: { reportedUser: { select: { id: true, username: true, displayName: true, avatar: true, }, }, reporter: { select: { id: true, username: true, displayName: true, avatar: true, }, }, reviewer: { select: { id: true, username: true, displayName: true, }, }, }, }); if (!report) { return null; } return { id: report.id, reportedUserId: report.reportedUserId, reporterId: report.reporterId, reason: this.fromPrismaReportReason(report.reason), details: report.details, status: this.fromPrismaReportStatus(report.status), reviewedBy: report.reviewedBy ?? undefined, reviewNotes: report.reviewNotes ?? undefined, createdAt: report.createdAt, updatedAt: report.updatedAt, reportedUser: report.reportedUser, reporter: report.reporter, reviewer: report.reviewer ?? undefined, }; } /** * Update a report's status and review notes (admin only). * * @param id - The report ID * @param reviewerId - The ID of the admin reviewing the report * @param updateDto - The update details * @returns The updated report */ async updateReport( id: string, reviewerId: string, updateDto: UpdateReportDto, ): Promise { const report = await this.prisma.profileReport.update({ where: { id }, data: { status: this.toPrismaReportStatus(updateDto.status), reviewNotes: updateDto.reviewNotes, reviewedBy: reviewerId, }, include: { reportedUser: { select: { id: true, username: true, displayName: true, avatar: true, }, }, reporter: { select: { id: true, username: true, displayName: true, avatar: true, }, }, reviewer: { select: { id: true, username: true, displayName: true, }, }, }, }); return { id: report.id, reportedUserId: report.reportedUserId, reporterId: report.reporterId, reason: this.fromPrismaReportReason(report.reason), details: report.details, status: this.fromPrismaReportStatus(report.status), reviewedBy: report.reviewedBy ?? undefined, reviewNotes: report.reviewNotes ?? undefined, createdAt: report.createdAt, updatedAt: report.updatedAt, reportedUser: report.reportedUser, reporter: report.reporter, reviewer: report.reviewer ?? undefined, }; } }