mc-dashboard/app/api/mods/updates/route.ts

88 lines
2.5 KiB
TypeScript
Raw Permalink Normal View History

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<UpdateInfo[]> {
let metadata: Record<string, ModMetadataEntry>;
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 }
);
}
}