import { NextResponse } from "next/server"; import { readFileSync } from "fs"; import { auth } from "@/lib/auth"; import { MOD_METADATA_FILE } from "@/lib/constants"; import { memoAsync } from "@/lib/cache"; export const dynamic = "force-dynamic"; const API = "https://api.modrinth.com/v2"; const GAME_VERSION = "1.20.1"; const LOADER = "forge"; const UA = "HurkiCorgiMC/1.0 (minecraft.hurkicorgi.com)"; type ModMetadataEntry = { projectId: string; side: string }; type UpdateInfo = { filename: string; projectId: string; latestFilename: string; latestVersionId: string; dateModified: string; hasUpdate: boolean; }; async function checkUpdates(): Promise { let metadata: Record; try { metadata = JSON.parse(readFileSync(MOD_METADATA_FILE, "utf8")); } catch { return []; } const entries = Object.entries(metadata).filter(([, m]) => m?.projectId); if (entries.length === 0) return []; const results = await Promise.all( entries.map(async ([filename, entry]) => { try { const url = `${API}/project/${entry.projectId}/version?game_versions=["${GAME_VERSION}"]&loaders=["${LOADER}"]`; const res = await fetch(url, { headers: { "User-Agent": UA }, signal: AbortSignal.timeout(10_000), }); if (!res.ok) return null; const versions: Array<{ id: string; date_published: string; files: Array<{ filename: string }>; }> = await res.json(); const latest = versions[0]; if (!latest || !latest.files?.[0]) return null; const latestFilename = latest.files[0].filename; return { filename, projectId: entry.projectId, latestFilename, latestVersionId: latest.id, dateModified: latest.date_published, hasUpdate: latestFilename !== filename, } satisfies UpdateInfo; } catch { return null; } }) ); return results.filter((r): r is UpdateInfo => r !== null && r.hasUpdate); } export async function GET() { const session = await auth(); if (!session) { return NextResponse.json({ error: "Unauthorized" }, { status: 403 }); } try { const updates = await memoAsync("mods:updates", 30 * 60 * 1000, checkUpdates); return NextResponse.json(updates, { headers: { "Cache-Control": "private, max-age=300" }, }); } catch (e) { return NextResponse.json( { error: (e as Error).message }, { status: 500 } ); } }