/** * @fileoverview Moderation case management endpoints for Aurora API. * Provides endpoints for viewing, creating, and resolving moderation cases. */ import type { RouteContext, RouteModule } from "./types"; import { jsonResponse, errorResponse, parseBody, withErrorHandling } from "./utils"; import { CreateCaseSchema, ClearCaseSchema, CaseIdPattern } from "./schemas"; /** * Moderation routes handler. * * Endpoints: * - GET /api/moderation - List cases with filters * - GET /api/moderation/:caseId - Get single case * - POST /api/moderation - Create new case * - PUT /api/moderation/:caseId/clear - Clear/resolve case */ async function handler(ctx: RouteContext): Promise { const { pathname, method, req, url } = ctx; // Only handle requests to /api/moderation* if (!pathname.startsWith("/api/moderation")) { return null; } const { ModerationService } = await import("@shared/modules/moderation/moderation.service"); /** * @route GET /api/moderation * @description Returns moderation cases with optional filtering. * * @query userId - Filter by target user ID * @query moderatorId - Filter by moderator ID * @query type - Filter by case type (warn, timeout, kick, ban, note, prune) * @query active - Filter by active status (true/false) * @query limit - Max results (default: 50) * @query offset - Pagination offset (default: 0) * * @response 200 - `{ cases: ModerationCase[] }` * @response 500 - Error fetching cases * * @example * // Request * GET /api/moderation?type=warn&active=true&limit=10 * * // Response * { * "cases": [ * { * "id": "1", * "caseId": "CASE-0001", * "type": "warn", * "userId": "123456789", * "username": "User1", * "moderatorId": "987654321", * "moderatorName": "Mod1", * "reason": "Spam", * "active": true, * "createdAt": "2024-01-15T12:00:00Z" * } * ] * } */ if (pathname === "/api/moderation" && method === "GET") { return withErrorHandling(async () => { const filter: any = {}; if (url.searchParams.get("userId")) filter.userId = url.searchParams.get("userId"); if (url.searchParams.get("moderatorId")) filter.moderatorId = url.searchParams.get("moderatorId"); if (url.searchParams.get("type")) filter.type = url.searchParams.get("type"); const activeParam = url.searchParams.get("active"); if (activeParam !== null) filter.active = activeParam === "true"; filter.limit = url.searchParams.get("limit") ? parseInt(url.searchParams.get("limit")!) : 50; filter.offset = url.searchParams.get("offset") ? parseInt(url.searchParams.get("offset")!) : 0; const cases = await ModerationService.searchCases(filter); return jsonResponse({ cases }); }, "fetch moderation cases"); } /** * @route GET /api/moderation/:caseId * @description Returns a single moderation case by case ID. * Case IDs follow the format CASE-XXXX (e.g., CASE-0001). * * @param caseId - Case ID in CASE-XXXX format * @response 200 - Full case object * @response 404 - Case not found * @response 500 - Error fetching case */ if (pathname.match(/^\/api\/moderation\/CASE-\d+$/i) && method === "GET") { const caseId = pathname.split("/").pop()!.toUpperCase(); return withErrorHandling(async () => { const moderationCase = await ModerationService.getCaseById(caseId); if (!moderationCase) { return errorResponse("Case not found", 404); } return jsonResponse(moderationCase); }, "fetch moderation case"); } /** * @route POST /api/moderation * @description Creates a new moderation case. * * @body { * type: "warn" | "timeout" | "kick" | "ban" | "note" | "prune" (required), * userId: string (required) - Target user's Discord ID, * username: string (required) - Target user's username, * moderatorId: string (required) - Moderator's Discord ID, * moderatorName: string (required) - Moderator's username, * reason: string (required) - Reason for the action, * metadata?: object - Additional case metadata (e.g., duration) * } * @response 201 - `{ success: true, case: ModerationCase }` * @response 400 - Missing required fields * @response 500 - Error creating case * * @example * // Request * POST /api/moderation * { * "type": "warn", * "userId": "123456789", * "username": "User1", * "moderatorId": "987654321", * "moderatorName": "Mod1", * "reason": "Rule violation", * "metadata": { "duration": "24h" } * } */ if (pathname === "/api/moderation" && method === "POST") { return withErrorHandling(async () => { const data = await req.json() as Record; if (!data.type || !data.userId || !data.username || !data.moderatorId || !data.moderatorName || !data.reason) { return errorResponse( "Missing required fields: type, userId, username, moderatorId, moderatorName, reason", 400 ); } const newCase = await ModerationService.createCase({ type: data.type, userId: data.userId, username: data.username, moderatorId: data.moderatorId, moderatorName: data.moderatorName, reason: data.reason, metadata: data.metadata || {}, }); return jsonResponse({ success: true, case: newCase }, 201); }, "create moderation case"); } /** * @route PUT /api/moderation/:caseId/clear * @description Clears/resolves a moderation case. * Sets the case as inactive and records who cleared it. * * @param caseId - Case ID in CASE-XXXX format * @body { * clearedBy: string (required) - Discord ID of user clearing the case, * clearedByName: string (required) - Username of user clearing the case, * reason?: string - Reason for clearing (default: "Cleared via API") * } * @response 200 - `{ success: true, case: ModerationCase }` * @response 400 - Missing required fields * @response 404 - Case not found * @response 500 - Error clearing case * * @example * // Request * PUT /api/moderation/CASE-0001/clear * { "clearedBy": "987654321", "clearedByName": "Admin1", "reason": "Appeal accepted" } */ if (pathname.match(/^\/api\/moderation\/CASE-\d+\/clear$/i) && method === "PUT") { const caseId = (pathname.split("/")[3] || "").toUpperCase(); return withErrorHandling(async () => { const data = await req.json() as Record; if (!data.clearedBy || !data.clearedByName) { return errorResponse("Missing required fields: clearedBy, clearedByName", 400); } const updatedCase = await ModerationService.clearCase({ caseId, clearedBy: data.clearedBy, clearedByName: data.clearedByName, reason: data.reason || "Cleared via API", }); if (!updatedCase) { return errorResponse("Case not found", 404); } return jsonResponse({ success: true, case: updatedCase }); }, "clear moderation case"); } return null; } export const moderationRoutes: RouteModule = { name: "moderation", handler };