/** * @fileoverview Quest management endpoints for Aurora API. * Provides CRUD operations for game quests. */ import type { RouteContext, RouteModule } from "./types"; import { jsonResponse, errorResponse, parseIdFromPath, withErrorHandling } from "./utils"; import { CreateQuestSchema, UpdateQuestSchema } from "@shared/modules/quest/quest.types"; /** * Quest routes handler. * * Endpoints: * - GET /api/quests - List all quests * - POST /api/quests - Create a new quest * - PUT /api/quests/:id - Update an existing quest * - DELETE /api/quests/:id - Delete a quest */ async function handler(ctx: RouteContext): Promise { const { pathname, method, req } = ctx; // Only handle requests to /api/quests* if (!pathname.startsWith("/api/quests")) { return null; } const { questService } = await import("@shared/modules/quest/quest.service"); /** * @route GET /api/quests * @description Returns all quests in the system. * @response 200 - `{ success: true, data: Quest[] }` * @response 500 - Error fetching quests * * @example * // Response * { * "success": true, * "data": [ * { * "id": 1, * "name": "Daily Login", * "description": "Login once to claim", * "triggerEvent": "login", * "requirements": { "target": 1 }, * "rewards": { "xp": 50, "balance": 100 } * } * ] * } */ if (pathname === "/api/quests" && method === "GET") { return withErrorHandling(async () => { const quests = await questService.getAllQuests(); return jsonResponse({ success: true, data: quests.map(q => ({ id: q.id, name: q.name, description: q.description, triggerEvent: q.triggerEvent, requirements: q.requirements, rewards: q.rewards, })), }); }, "fetch quests"); } /** * @route POST /api/quests * @description Creates a new quest. * * @body { * name: string, * description?: string, * triggerEvent: string, * target: number, * xpReward: number, * balanceReward: number * } * @response 200 - `{ success: true, quest: Quest }` * @response 400 - Validation error * @response 500 - Error creating quest * * @example * // Request * POST /api/quests * { * "name": "Win 5 Battles", * "description": "Defeat 5 enemies in combat", * "triggerEvent": "battle_win", * "target": 5, * "xpReward": 200, * "balanceReward": 500 * } */ if (pathname === "/api/quests" && method === "POST") { return withErrorHandling(async () => { const rawData = await req.json(); const parseResult = CreateQuestSchema.safeParse(rawData); if (!parseResult.success) { return Response.json({ error: "Invalid payload", issues: parseResult.error.issues.map(i => ({ path: i.path, message: i.message })) }, { status: 400 }); } const data = parseResult.data; const result = await questService.createQuest({ name: data.name, description: data.description || "", triggerEvent: data.triggerEvent, requirements: { target: data.target }, rewards: { xp: data.xpReward, balance: data.balanceReward } }); return jsonResponse({ success: true, quest: result[0] }); }, "create quest"); } /** * @route PUT /api/quests/:id * @description Updates an existing quest by ID. * * @param id - Quest ID (numeric) * @body Partial quest fields to update * @response 200 - `{ success: true, quest: Quest }` * @response 400 - Invalid quest ID or validation error * @response 404 - Quest not found * @response 500 - Error updating quest */ if (pathname.match(/^\/api\/quests\/\d+$/) && method === "PUT") { const id = parseIdFromPath(pathname); if (!id) { return errorResponse("Invalid quest ID", 400); } return withErrorHandling(async () => { const rawData = await req.json(); const parseResult = UpdateQuestSchema.safeParse(rawData); if (!parseResult.success) { return Response.json({ error: "Invalid payload", issues: parseResult.error.issues.map(i => ({ path: i.path, message: i.message })) }, { status: 400 }); } const data = parseResult.data; const result = await questService.updateQuest(id, { ...(data.name !== undefined && { name: data.name }), ...(data.description !== undefined && { description: data.description }), ...(data.triggerEvent !== undefined && { triggerEvent: data.triggerEvent }), ...(data.target !== undefined && { requirements: { target: data.target } }), ...((data.xpReward !== undefined || data.balanceReward !== undefined) && { rewards: { xp: data.xpReward ?? 0, balance: data.balanceReward ?? 0 } }) }); if (!result || result.length === 0) { return errorResponse("Quest not found", 404); } return jsonResponse({ success: true, quest: result[0] }); }, "update quest"); } /** * @route DELETE /api/quests/:id * @description Deletes a quest by ID. * * @param id - Quest ID (numeric) * @response 200 - `{ success: true, deleted: number }` * @response 400 - Invalid quest ID * @response 404 - Quest not found * @response 500 - Error deleting quest */ if (pathname.match(/^\/api\/quests\/\d+$/) && method === "DELETE") { const id = parseIdFromPath(pathname); if (!id) { return errorResponse("Invalid quest ID", 400); } return withErrorHandling(async () => { const result = await questService.deleteQuest(id); if (!result || result.length === 0) { return errorResponse("Quest not found", 404); } return jsonResponse({ success: true, deleted: (result[0] as { id: number }).id }); }, "delete quest"); } return null; } export const questsRoutes: RouteModule = { name: "quests", handler };