/** * @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 { 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; 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 };