refactor: rename web/ to api/ to better reflect its purpose
Some checks failed
Deploy to Production / test (push) Failing after 30s
Some checks failed
Deploy to Production / test (push) Failing after 30s
The web/ folder contains the REST API, WebSocket server, and OAuth routes — not a web frontend. Renaming to api/ clarifies this distinction since the actual web frontend lives in panel/. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
217
api/src/routes/moderation.routes.ts
Normal file
217
api/src/routes/moderation.routes.ts
Normal file
@@ -0,0 +1,217 @@
|
||||
/**
|
||||
* @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<Response | null> {
|
||||
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<string, any>;
|
||||
|
||||
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<string, any>;
|
||||
|
||||
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
|
||||
};
|
||||
Reference in New Issue
Block a user