feat: Implement a new API routing system by adding dedicated route files for users, transactions, assets, items, quests, and other game entities, and integrating them into the server.
All checks were successful
Deploy to Production / test (push) Successful in 44s
All checks were successful
Deploy to Production / test (push) Successful in 44s
This commit is contained in:
207
web/src/routes/quests.routes.ts
Normal file
207
web/src/routes/quests.routes.ts
Normal file
@@ -0,0 +1,207 @@
|
||||
/**
|
||||
* @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<Response | null> {
|
||||
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
|
||||
};
|
||||
Reference in New Issue
Block a user