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>
131 lines
4.4 KiB
TypeScript
131 lines
4.4 KiB
TypeScript
/**
|
|
* @fileoverview Lootdrop management endpoints for Aurora API.
|
|
* Provides endpoints for viewing, spawning, and canceling lootdrops.
|
|
*/
|
|
|
|
import type { RouteContext, RouteModule } from "./types";
|
|
import {
|
|
jsonResponse,
|
|
errorResponse,
|
|
parseStringIdFromPath,
|
|
withErrorHandling
|
|
} from "./utils";
|
|
|
|
/**
|
|
* Lootdrops routes handler.
|
|
*
|
|
* Endpoints:
|
|
* - GET /api/lootdrops - List lootdrops
|
|
* - POST /api/lootdrops - Spawn a lootdrop
|
|
* - DELETE /api/lootdrops/:messageId - Cancel/delete a lootdrop
|
|
*/
|
|
async function handler(ctx: RouteContext): Promise<Response | null> {
|
|
const { pathname, method, req, url } = ctx;
|
|
|
|
// Only handle requests to /api/lootdrops*
|
|
if (!pathname.startsWith("/api/lootdrops")) {
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* @route GET /api/lootdrops
|
|
* @description Returns recent lootdrops, sorted by newest first.
|
|
*
|
|
* @query limit - Max results (default: 50)
|
|
* @response 200 - `{ lootdrops: Lootdrop[] }`
|
|
* @response 500 - Error fetching lootdrops
|
|
*/
|
|
if (pathname === "/api/lootdrops" && method === "GET") {
|
|
return withErrorHandling(async () => {
|
|
const { lootdrops } = await import("@shared/db/schema");
|
|
const { DrizzleClient } = await import("@shared/db/DrizzleClient");
|
|
const { desc } = await import("drizzle-orm");
|
|
|
|
const limit = url.searchParams.get("limit") ? parseInt(url.searchParams.get("limit")!) : 50;
|
|
|
|
const result = await DrizzleClient.select()
|
|
.from(lootdrops)
|
|
.orderBy(desc(lootdrops.createdAt))
|
|
.limit(limit);
|
|
|
|
return jsonResponse({ lootdrops: result });
|
|
}, "fetch lootdrops");
|
|
}
|
|
|
|
/**
|
|
* @route POST /api/lootdrops
|
|
* @description Spawns a new lootdrop in a Discord channel.
|
|
* Requires a valid text channel ID where the bot has permissions.
|
|
*
|
|
* @body {
|
|
* channelId: string (required) - Discord channel ID to spawn in,
|
|
* amount?: number - Reward amount (random if not specified),
|
|
* currency?: string - Currency type
|
|
* }
|
|
* @response 201 - `{ success: true }`
|
|
* @response 400 - Invalid channel or missing channelId
|
|
* @response 500 - Error spawning lootdrop
|
|
*
|
|
* @example
|
|
* // Request
|
|
* POST /api/lootdrops
|
|
* { "channelId": "1234567890", "amount": 100, "currency": "Gold" }
|
|
*/
|
|
if (pathname === "/api/lootdrops" && method === "POST") {
|
|
return withErrorHandling(async () => {
|
|
const { lootdropService } = await import("@shared/modules/economy/lootdrop.service");
|
|
const { AuroraClient } = await import("../../../bot/lib/BotClient");
|
|
const { TextChannel } = await import("discord.js");
|
|
|
|
const data = await req.json() as Record<string, any>;
|
|
|
|
if (!data.channelId) {
|
|
return errorResponse("Missing required field: channelId", 400);
|
|
}
|
|
|
|
const channel = await AuroraClient.channels.fetch(data.channelId);
|
|
|
|
if (!channel || !(channel instanceof TextChannel)) {
|
|
return errorResponse("Invalid channel. Must be a TextChannel.", 400);
|
|
}
|
|
|
|
await lootdropService.spawnLootdrop(channel, data.amount, data.currency);
|
|
|
|
return jsonResponse({ success: true }, 201);
|
|
}, "spawn lootdrop");
|
|
}
|
|
|
|
/**
|
|
* @route DELETE /api/lootdrops/:messageId
|
|
* @description Cancels and deletes an active lootdrop.
|
|
* The lootdrop is identified by its Discord message ID.
|
|
*
|
|
* @param messageId - Discord message ID of the lootdrop
|
|
* @response 204 - Lootdrop deleted (no content)
|
|
* @response 404 - Lootdrop not found
|
|
* @response 500 - Error deleting lootdrop
|
|
*/
|
|
if (pathname.match(/^\/api\/lootdrops\/[^\/]+$/) && method === "DELETE") {
|
|
const messageId = parseStringIdFromPath(pathname);
|
|
if (!messageId) return null;
|
|
|
|
return withErrorHandling(async () => {
|
|
const { lootdropService } = await import("@shared/modules/economy/lootdrop.service");
|
|
const success = await lootdropService.deleteLootdrop(messageId);
|
|
|
|
if (!success) {
|
|
return errorResponse("Lootdrop not found", 404);
|
|
}
|
|
|
|
return new Response(null, { status: 204 });
|
|
}, "delete lootdrop");
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
export const lootdropsRoutes: RouteModule = {
|
|
name: "lootdrops",
|
|
handler
|
|
};
|