/** * @fileoverview Bot settings endpoints for Aurora API. * Provides endpoints for reading and updating bot configuration, * as well as fetching Discord metadata. */ import type { RouteContext, RouteModule } from "./types"; import { jsonResponse, errorResponse, withErrorHandling } from "./utils"; /** * JSON replacer for BigInt serialization. */ function jsonReplacer(_key: string, value: unknown): unknown { return typeof value === "bigint" ? value.toString() : value; } /** * Settings routes handler. * * Endpoints: * - GET /api/settings - Get current bot configuration * - POST /api/settings - Update bot configuration (partial merge) * - GET /api/settings/meta - Get Discord metadata (roles, channels, commands) */ async function handler(ctx: RouteContext): Promise { const { pathname, method, req } = ctx; // Only handle requests to /api/settings* if (!pathname.startsWith("/api/settings")) { return null; } /** * @route GET /api/settings * @description Returns the current bot configuration. * Configuration includes economy settings, leveling settings, * command toggles, and other system settings. * @response 200 - Full configuration object * @response 500 - Error fetching settings * * @example * // Response * { * "economy": { "dailyReward": 100, "streakBonus": 10 }, * "leveling": { "xpPerMessage": 15, "levelUpChannel": "123456789" }, * "commands": { "disabled": [], "channelLocks": {} } * } */ if (pathname === "/api/settings" && method === "GET") { return withErrorHandling(async () => { const { config } = await import("@shared/lib/config"); return new Response(JSON.stringify(config, jsonReplacer), { headers: { "Content-Type": "application/json" } }); }, "fetch settings"); } /** * @route POST /api/settings * @description Updates bot configuration with partial merge. * Only the provided fields will be updated; other settings remain unchanged. * After updating, commands are automatically reloaded. * * @body Partial configuration object * @response 200 - `{ success: true }` * @response 400 - Validation error * @response 500 - Error saving settings * * @example * // Request - Only update economy daily reward * POST /api/settings * { "economy": { "dailyReward": 150 } } */ if (pathname === "/api/settings" && method === "POST") { try { const partialConfig = await req.json(); const { saveConfig, config: currentConfig } = await import("@shared/lib/config"); const { deepMerge } = await import("@shared/lib/utils"); // Merge partial update into current config const mergedConfig = deepMerge(currentConfig, partialConfig); // saveConfig throws if validation fails saveConfig(mergedConfig); const { systemEvents, EVENTS } = await import("@shared/lib/events"); systemEvents.emit(EVENTS.ACTIONS.RELOAD_COMMANDS); return jsonResponse({ success: true }); } catch (error) { // Return 400 for validation errors const message = error instanceof Error ? error.message : String(error); return errorResponse("Failed to save settings", 400, message); } } /** * @route GET /api/settings/meta * @description Returns Discord server metadata for settings UI. * Provides lists of roles, channels, and registered commands. * * @response 200 - `{ roles: Role[], channels: Channel[], commands: Command[] }` * @response 500 - Error fetching metadata * * @example * // Response * { * "roles": [ * { "id": "123456789", "name": "Admin", "color": "#FF0000" } * ], * "channels": [ * { "id": "987654321", "name": "general", "type": 0 } * ], * "commands": [ * { "name": "daily", "category": "economy" } * ] * } */ if (pathname === "/api/settings/meta" && method === "GET") { return withErrorHandling(async () => { const { AuroraClient } = await import("../../../bot/lib/BotClient"); const { env } = await import("@shared/lib/env"); if (!env.DISCORD_GUILD_ID) { return jsonResponse({ roles: [], channels: [], commands: [] }); } const guild = AuroraClient.guilds.cache.get(env.DISCORD_GUILD_ID); if (!guild) { return jsonResponse({ roles: [], channels: [], commands: [] }); } // Map roles and channels to a simplified format const roles = guild.roles.cache .sort((a, b) => b.position - a.position) .map(r => ({ id: r.id, name: r.name, color: r.hexColor })); const channels = guild.channels.cache .map(c => ({ id: c.id, name: c.name, type: c.type })); const commands = Array.from(AuroraClient.knownCommands.entries()) .map(([name, category]) => ({ name, category })) .sort((a, b) => { if (a.category !== b.category) return a.category.localeCompare(b.category); return a.name.localeCompare(b.name); }); return jsonResponse({ roles, channels, commands }); }, "fetch settings meta"); } return null; } export const settingsRoutes: RouteModule = { name: "settings", handler };