feat(web): Last.fm client lib + top-artists/albums + mb release-group routes

This commit is contained in:
Jonathan
2026-07-13 19:01:04 +02:00
parent 5eb4fbfd55
commit ecdad88973
8 changed files with 502 additions and 0 deletions
@@ -0,0 +1,56 @@
import { prisma } from "@/lib/db";
import { decryptSecret } from "@/lib/crypto";
import { topAlbums, type LfmPeriod } from "@/lib/lastfm";
const PERIODS: LfmPeriod[] = ["overall", "7day", "1month", "3month", "6month", "12month"];
const LIMIT = 50;
async function getCreds(): Promise<{ username: string; apiKey: string } | null> {
const rows = await prisma.config.findMany({ where: { key: { in: ["lastfm.username", "lastfm.api_key"] } } });
const byKey = new Map(rows.map((r) => [r.key, r]));
const username = byKey.get("lastfm.username")?.value ?? "";
const keyRow = byKey.get("lastfm.api_key");
const apiKey = keyRow ? (keyRow.secret ? decryptSecret(keyRow.value) : keyRow.value) : "";
if (!username || !apiKey) return null;
return { username, apiKey };
}
function albumKey(artist: string, album: string): string {
return `${artist} ${album}`.trim().toLowerCase();
}
export async function GET(request: Request) {
const creds = await getCreds();
if (!creds) return Response.json({ error: "Last.fm not configured" }, { status: 400 });
const url = new URL(request.url);
const periodParam = url.searchParams.get("period") ?? "overall";
const period: LfmPeriod = (PERIODS as string[]).includes(periodParam) ? (periodParam as LfmPeriod) : "overall";
const page = Number(url.searchParams.get("page")) || 1;
let result;
try {
result = await topAlbums(creds.username, creds.apiKey, { period, page, limit: LIMIT });
} catch (e) {
return Response.json({ error: e instanceof Error ? e.message : "Last.fm request failed" }, { status: 502 });
}
const [libraryItems, monitoredReleases] = await Promise.all([
prisma.libraryItem.findMany({ select: { artist: true, album: true } }),
prisma.monitoredRelease.findMany({
where: { OR: [{ monitored: true }, { currentQualityClass: { not: null } }] },
select: { artistName: true, album: true },
}),
]);
const ownedKeys = new Set(libraryItems.map((r) => albumKey(r.artist, r.album)));
const wantedKeys = new Set(monitoredReleases.map((r) => albumKey(r.artistName, r.album)));
const albums = result.items.map((a) => {
const key = albumKey(a.artist, a.name);
const owned = ownedKeys.has(key);
const wanted = wantedKeys.has(key);
return { ...a, inLibrary: owned || wanted, owned, wanted };
});
return Response.json({ albums, totalPages: result.totalPages, page: result.page });
}