From c3d2295ed17702958836e36a63ca7d4805e7a1d5 Mon Sep 17 00:00:00 2001 From: Yuri Tatishchev Date: Thu, 5 Feb 2026 14:37:44 -0800 Subject: [PATCH] mal api route --- .../api/mal/animelist/[username]/+server.ts | 72 +++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 src/routes/api/mal/animelist/[username]/+server.ts diff --git a/src/routes/api/mal/animelist/[username]/+server.ts b/src/routes/api/mal/animelist/[username]/+server.ts new file mode 100644 index 0000000..e2d26a4 --- /dev/null +++ b/src/routes/api/mal/animelist/[username]/+server.ts @@ -0,0 +1,72 @@ +import { error } from "@sveltejs/kit"; +import { env } from "$env/dynamic/private"; +import type { RequestHandler } from "./$types"; + +const UPSTREAM_ORIGIN = "https://api.myanimelist.net/v2"; + +function pickForwardHeader(request: Request, headerName: string) { + const v = request.headers.get(headerName); + return v ?? undefined; +} + +export const GET: RequestHandler = async ({ params, request, fetch, url }) => { + const username = params.username; + + if (!username) { + throw error(400, "Missing username"); + } + + if (!env.MAL_CLIENT_ID) { + throw error(500, "MAL_CLIENT_ID is not configured"); + } + + // Forward query params as-is (status/sort/limit/offset/etc). + // Note: url.searchParams is already decoded/encoded appropriately by URL. + const upstreamUrl = new URL( + `${UPSTREAM_ORIGIN}/users/${encodeURIComponent(username)}/animelist`, + ); + + upstreamUrl.search = url.search; + + const ifNoneMatch = pickForwardHeader(request, "if-none-match"); + const ifModifiedSince = pickForwardHeader(request, "if-modified-since"); + + const upstreamRes = await fetch(upstreamUrl, { + method: "GET", + headers: { + "X-MAL-CLIENT-ID": env.MAL_CLIENT_ID, + ...(ifNoneMatch ? { "if-none-match": ifNoneMatch } : {}), + ...(ifModifiedSince ? { "if-modified-since": ifModifiedSince } : {}), + }, + }); + + // Map common statuses cleanly + if (upstreamRes.status === 401 || upstreamRes.status === 403) { + throw error(upstreamRes.status, "MAL auth failed"); + } + if (upstreamRes.status === 404) { + throw error(404, "User not found"); + } + if (!upstreamRes.ok && upstreamRes.status !== 304) { + throw error(upstreamRes.status, `Upstream error (${upstreamRes.status})`); + } + + // Pass through caching/content headers that are useful and safe + const headers = new Headers(); + + const copy = (h: string) => { + const v = upstreamRes.headers.get(h); + if (v) headers.set(h, v); + }; + + copy("content-type"); + copy("cache-control"); + copy("etag"); + copy("last-modified"); + + return new Response(upstreamRes.body, { + status: upstreamRes.status, + statusText: upstreamRes.statusText, + headers, + }); +};