import { problemDetails } from "@minecraft-account-manager/contracts"; import { authorizeAdminApi } from "@/lib/auth/admin-api-auth"; import { problemInstance, problemResponse } from "@/lib/problem-response"; import { createSuggestionsClient, SuggestionsError } from "./suggestions"; type Client = ReturnType; let runtime: { token: string; guildId: string; forumId: string; client: Client } | undefined; function getClient() { const token = process.env.DISCORD_BOT_TOKEN?.trim() ?? ""; const guildId = process.env.DISCORD_GUILD_ID?.trim() ?? ""; const forumId = process.env.DISCORD_SUGGESTIONS_FORUM_ID?.trim() ?? ""; if (!runtime || runtime.token !== token || runtime.guildId !== guildId || runtime.forumId !== forumId) { runtime = { token, guildId, forumId, client: createSuggestionsClient({ token, guildId, forumId }) }; } return runtime.client; } export async function suggestionsApi(request: Request, operation: (client: Client) => Promise) { const authorization = await authorizeAdminApi(request); if (authorization.response) return authorization.response; try { return Response.json(await operation(getClient()), { headers: { "cache-control": "no-store" } }); } catch (error) { const safe = error instanceof SuggestionsError ? error : new SuggestionsError(503, "discord-unavailable", "Discord suggestions are unavailable."); const titles: Record = { 400: "Invalid request", 401: "Authentication required", 403: "Administrator role required", 404: "Suggestion not found", 405: "Method not allowed", 503: "Suggestions unavailable" }; const response = problemResponse(problemDetails(`urn:error:${safe.code}`, titles[safe.status] ?? "Suggestions unavailable", safe.status, safe.message, problemInstance(request))); if (safe.retryAfter) response.headers.set("retry-after", String(safe.retryAfter)); return response; } } export function suggestionQuery(request: Request, list = false) { const params = new URL(request.url).searchParams; const allowed = list ? ["limit", "cursor", "status"] : ["limit", "cursor"]; for (const key of params.keys()) { if (!allowed.includes(key) || params.getAll(key).length !== 1 || !params.get(key)) throw new SuggestionsError(400, "invalid-request", "Unsupported or repeated query parameter."); } const rawLimit = params.get("limit"); if (rawLimit !== null && !/^\d{1,3}$/.test(rawLimit)) throw new SuggestionsError(400, "invalid-request", "Limit must be between 1 and 100."); return { limit: rawLimit === null ? undefined : Number(rawLimit), cursor: params.get("cursor") ?? undefined, ...(list ? { status: params.get("status") ?? undefined } : {}) }; } export function suggestionsReadOnly(request: Request) { return suggestionsApi(request, async () => { throw new SuggestionsError(405, "method-not-allowed", "Suggestions are read-only. Use GET."); }).then((response) => { if (response.status === 405) response.headers.set("allow", "GET, HEAD"); return response; }); }