import { moderationCases } from "@/db/schema"; import { eq, and, desc } from "drizzle-orm"; import { DrizzleClient } from "@/lib/DrizzleClient"; import type { CreateCaseOptions, ClearCaseOptions, SearchCasesFilter, CaseType } from "./moderation.types"; export class ModerationService { /** * Generate the next sequential case ID */ private static async getNextCaseId(): Promise { const latestCase = await DrizzleClient.query.moderationCases.findFirst({ orderBy: [desc(moderationCases.id)], }); if (!latestCase) { return "CASE-0001"; } // Extract number from case ID (e.g., "CASE-0042" -> 42) const match = latestCase.caseId.match(/CASE-(\d+)/); if (!match) { return "CASE-0001"; } const nextNumber = parseInt(match[1], 10) + 1; return `CASE-${nextNumber.toString().padStart(4, '0')}`; } /** * Create a new moderation case */ static async createCase(options: CreateCaseOptions) { const caseId = await this.getNextCaseId(); const [newCase] = await DrizzleClient.insert(moderationCases).values({ caseId, type: options.type, userId: BigInt(options.userId), username: options.username, moderatorId: BigInt(options.moderatorId), moderatorName: options.moderatorName, reason: options.reason, metadata: options.metadata || {}, active: options.type === 'warn' ? true : false, // Only warnings are "active" by default }).returning(); return newCase; } /** * Get a case by its case ID */ static async getCaseById(caseId: string) { return await DrizzleClient.query.moderationCases.findFirst({ where: eq(moderationCases.caseId, caseId), }); } /** * Get all cases for a specific user */ static async getUserCases(userId: string, activeOnly: boolean = false) { const conditions = [eq(moderationCases.userId, BigInt(userId))]; if (activeOnly) { conditions.push(eq(moderationCases.active, true)); } return await DrizzleClient.query.moderationCases.findMany({ where: and(...conditions), orderBy: [desc(moderationCases.createdAt)], }); } /** * Get active warnings for a user */ static async getUserWarnings(userId: string) { return await DrizzleClient.query.moderationCases.findMany({ where: and( eq(moderationCases.userId, BigInt(userId)), eq(moderationCases.type, 'warn'), eq(moderationCases.active, true) ), orderBy: [desc(moderationCases.createdAt)], }); } /** * Get all notes for a user */ static async getUserNotes(userId: string) { return await DrizzleClient.query.moderationCases.findMany({ where: and( eq(moderationCases.userId, BigInt(userId)), eq(moderationCases.type, 'note') ), orderBy: [desc(moderationCases.createdAt)], }); } /** * Clear/resolve a warning */ static async clearCase(options: ClearCaseOptions) { const [updatedCase] = await DrizzleClient.update(moderationCases) .set({ active: false, resolvedAt: new Date(), resolvedBy: BigInt(options.clearedBy), resolvedReason: options.reason || 'Manually cleared', }) .where(eq(moderationCases.caseId, options.caseId)) .returning(); return updatedCase; } /** * Search cases with various filters */ static async searchCases(filter: SearchCasesFilter) { const conditions = []; if (filter.userId) { conditions.push(eq(moderationCases.userId, BigInt(filter.userId))); } if (filter.moderatorId) { conditions.push(eq(moderationCases.moderatorId, BigInt(filter.moderatorId))); } if (filter.type) { conditions.push(eq(moderationCases.type, filter.type)); } if (filter.active !== undefined) { conditions.push(eq(moderationCases.active, filter.active)); } const whereClause = conditions.length > 0 ? and(...conditions) : undefined; return await DrizzleClient.query.moderationCases.findMany({ where: whereClause, orderBy: [desc(moderationCases.createdAt)], limit: filter.limit || 50, offset: filter.offset || 0, }); } /** * Get total count of active warnings for a user (useful for auto-timeout) */ static async getActiveWarningCount(userId: string): Promise { const warnings = await this.getUserWarnings(userId); return warnings.length; } }