Files
discord-rpg-concept/api/src/routes/classes.routes.ts
syntaxbullet 04e5851387 refactor: rename web/ to api/ to better reflect its purpose
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>
2026-02-14 11:37:40 +01:00

156 lines
4.9 KiB
TypeScript

/**
* @fileoverview Class management endpoints for Aurora API.
* Provides CRUD operations for player classes/guilds.
*/
import type { RouteContext, RouteModule } from "./types";
import {
jsonResponse,
errorResponse,
parseBody,
parseStringIdFromPath,
withErrorHandling
} from "./utils";
import { CreateClassSchema, UpdateClassSchema } from "./schemas";
/**
* Classes routes handler.
*
* Endpoints:
* - GET /api/classes - List all classes
* - POST /api/classes - Create a new class
* - PUT /api/classes/:id - Update a class
* - DELETE /api/classes/:id - Delete a class
*/
async function handler(ctx: RouteContext): Promise<Response | null> {
const { pathname, method, req } = ctx;
// Only handle requests to /api/classes*
if (!pathname.startsWith("/api/classes")) {
return null;
}
const { classService } = await import("@shared/modules/class/class.service");
/**
* @route GET /api/classes
* @description Returns all classes/guilds in the system.
*
* @response 200 - `{ classes: Class[] }`
* @response 500 - Error fetching classes
*
* @example
* // Response
* {
* "classes": [
* { "id": "1", "name": "Warrior", "balance": "5000", "roleId": "123456789" }
* ]
* }
*/
if (pathname === "/api/classes" && method === "GET") {
return withErrorHandling(async () => {
const classes = await classService.getAllClasses();
return jsonResponse({ classes });
}, "fetch classes");
}
/**
* @route POST /api/classes
* @description Creates a new class/guild.
*
* @body {
* id: string | number (required) - Unique class identifier,
* name: string (required) - Class display name,
* balance?: string | number - Initial class balance (default: 0),
* roleId?: string - Associated Discord role ID
* }
* @response 201 - `{ success: true, class: Class }`
* @response 400 - Missing required fields
* @response 500 - Error creating class
*
* @example
* // Request
* POST /api/classes
* { "id": "2", "name": "Mage", "balance": "0", "roleId": "987654321" }
*/
if (pathname === "/api/classes" && method === "POST") {
return withErrorHandling(async () => {
const data = await req.json() as Record<string, any>;
if (!data.id || !data.name || typeof data.name !== 'string') {
return errorResponse("Missing required fields: id and name are required", 400);
}
const newClass = await classService.createClass({
id: BigInt(data.id),
name: data.name,
balance: data.balance ? BigInt(data.balance) : 0n,
roleId: data.roleId || null,
});
return jsonResponse({ success: true, class: newClass }, 201);
}, "create class");
}
/**
* @route PUT /api/classes/:id
* @description Updates an existing class.
*
* @param id - Class ID
* @body {
* name?: string - Updated class name,
* balance?: string | number - Updated balance,
* roleId?: string - Updated Discord role ID
* }
* @response 200 - `{ success: true, class: Class }`
* @response 404 - Class not found
* @response 500 - Error updating class
*/
if (pathname.match(/^\/api\/classes\/\d+$/) && method === "PUT") {
const id = parseStringIdFromPath(pathname);
if (!id) return null;
return withErrorHandling(async () => {
const data = await req.json() as Record<string, any>;
const updateData: any = {};
if (data.name !== undefined) updateData.name = data.name;
if (data.balance !== undefined) updateData.balance = BigInt(data.balance);
if (data.roleId !== undefined) updateData.roleId = data.roleId;
const updatedClass = await classService.updateClass(BigInt(id), updateData);
if (!updatedClass) {
return errorResponse("Class not found", 404);
}
return jsonResponse({ success: true, class: updatedClass });
}, "update class");
}
/**
* @route DELETE /api/classes/:id
* @description Deletes a class. Users assigned to this class will need to be reassigned.
*
* @param id - Class ID
* @response 204 - Class deleted (no content)
* @response 500 - Error deleting class
*/
if (pathname.match(/^\/api\/classes\/\d+$/) && method === "DELETE") {
const id = parseStringIdFromPath(pathname);
if (!id) return null;
return withErrorHandling(async () => {
await classService.deleteClass(BigInt(id));
return new Response(null, { status: 204 });
}, "delete class");
}
return null;
}
export const classesRoutes: RouteModule = {
name: "classes",
handler
};