From f750b815d260e3f5691900f9ed563a7ba9de7cca Mon Sep 17 00:00:00 2001 From: Jonathan Date: Sun, 12 Jul 2026 00:29:28 +0200 Subject: [PATCH] feat(discovery): GET /api/discover feed Co-Authored-By: Claude Opus 4.8 (1M context) --- web/src/app/api/discover/route.test.ts | 24 ++++++++++++++++++++++++ web/src/app/api/discover/route.ts | 25 +++++++++++++++++++++++++ 2 files changed, 49 insertions(+) create mode 100644 web/src/app/api/discover/route.test.ts create mode 100644 web/src/app/api/discover/route.ts diff --git a/web/src/app/api/discover/route.test.ts b/web/src/app/api/discover/route.test.ts new file mode 100644 index 0000000..98012a3 --- /dev/null +++ b/web/src/app/api/discover/route.test.ts @@ -0,0 +1,24 @@ +import { describe, it, expect } from "vitest"; +import { prisma } from "@/lib/db"; +import { GET } from "./route"; + +describe("discover feed API", () => { + it("returns pending artists and albums split, highest score first, excluding non-pending", async () => { + await prisma.discoverySuggestion.createMany({ + data: [ + { kind: "artist", artistMbid: "a1", artistName: "Low", score: 0.2, seedCount: 1, + sources: ["listenbrainz"], secondaryTypes: [], dedupeKey: "artist:a1:" }, + { kind: "artist", artistMbid: "a2", artistName: "High", score: 0.9, seedCount: 2, + sources: ["listenbrainz"], secondaryTypes: [], dedupeKey: "artist:a2:" }, + { kind: "album", artistMbid: "a3", artistName: "Band", rgMbid: "rg1", album: "Rec", + score: 0.5, seedCount: 1, sources: ["listenbrainz"], secondaryTypes: [], dedupeKey: "album:a3:rg1" }, + { kind: "artist", artistMbid: "a4", artistName: "Gone", score: 5, seedCount: 1, + sources: ["listenbrainz"], secondaryTypes: [], status: "dismissed", dedupeKey: "artist:a4:" }, + ], + }); + const body = await (await GET()).json(); + expect(body.artists.map((a: any) => a.artistName)).toEqual(["High", "Low"]); // dismissed excluded + expect(body.albums.map((a: any) => a.album)).toEqual(["Rec"]); + expect(body.artists[0]).toMatchObject({ artistMbid: "a2", seedCount: 2, sources: ["listenbrainz"] }); + }); +}); diff --git a/web/src/app/api/discover/route.ts b/web/src/app/api/discover/route.ts new file mode 100644 index 0000000..b8bd503 --- /dev/null +++ b/web/src/app/api/discover/route.ts @@ -0,0 +1,25 @@ +import { prisma } from "@/lib/db"; + +export async function GET() { + const rows = await prisma.discoverySuggestion.findMany({ + where: { status: "pending" }, + orderBy: [{ score: "desc" }, { createdAt: "asc" }], + }); + const map = (r: (typeof rows)[number]) => ({ + id: r.id, + artistMbid: r.artistMbid, + artistName: r.artistName, + rgMbid: r.rgMbid, + album: r.album, + primaryType: r.primaryType, + secondaryTypes: r.secondaryTypes, + firstReleaseDate: r.firstReleaseDate, + score: r.score, + seedCount: r.seedCount, + sources: r.sources, + }); + return Response.json({ + artists: rows.filter((r) => r.kind === "artist").map(map), + albums: rows.filter((r) => r.kind === "album").map(map), + }); +}