diff --git a/web/src/app/api/discover/config/route.test.ts b/web/src/app/api/discover/config/route.test.ts new file mode 100644 index 0000000..ce85771 --- /dev/null +++ b/web/src/app/api/discover/config/route.test.ts @@ -0,0 +1,29 @@ +import { describe, it, expect } from "vitest"; +import { prisma } from "@/lib/db"; +import { GET, PATCH } from "./route"; + +function patch(body: unknown) { + return PATCH(new Request("http://localhost/api/discover/config", { + method: "PATCH", headers: { "content-type": "application/json" }, body: JSON.stringify(body), + })); +} + +describe("discover config API", () => { + it("GET returns defaults when nothing is stored", async () => { + const body = await (await GET()).json(); + expect(body).toMatchObject({ + "discover.enabled": "false", "discover.intervalHours": "168", + "discover.maxSeeds": "50", "discover.similarPerSeed": "20", + "discover.albumsPerArtist": "1", "discover.minScore": "0", + }); + }); + + it("PATCH upserts known keys and ignores unknown ones", async () => { + const res = await patch({ "discover.enabled": "true", "discover.maxSeeds": 25, "nope": "x" }); + expect((await res.json()).updated).toBe(2); + const body = await (await GET()).json(); + expect(body["discover.enabled"]).toBe("true"); + expect(body["discover.maxSeeds"]).toBe("25"); + expect(await prisma.config.findUnique({ where: { key: "nope" } })).toBeNull(); + }); +}); diff --git a/web/src/app/api/discover/config/route.ts b/web/src/app/api/discover/config/route.ts new file mode 100644 index 0000000..8dfbfd6 --- /dev/null +++ b/web/src/app/api/discover/config/route.ts @@ -0,0 +1,38 @@ +import { prisma } from "@/lib/db"; + +const DEFAULTS: Record = { + "discover.enabled": "false", + "discover.intervalHours": "168", + "discover.maxSeeds": "50", + "discover.similarPerSeed": "20", + "discover.albumsPerArtist": "1", + "discover.minScore": "0", +}; + +export async function GET() { + const rows = await prisma.config.findMany({ where: { key: { in: Object.keys(DEFAULTS) } } }); + const stored = Object.fromEntries(rows.map((r) => [r.key, r.value])); + return Response.json( + Object.fromEntries(Object.entries(DEFAULTS).map(([k, d]) => [k, stored[k] ?? d])), + ); +} + +export async function PATCH(request: Request) { + let body: unknown; + try { + body = await request.json(); + } catch { + return Response.json({ error: "invalid JSON" }, { status: 400 }); + } + const entries = Object.entries((body ?? {}) as Record).filter( + ([k]) => k in DEFAULTS, + ); + for (const [key, value] of entries) { + await prisma.config.upsert({ + where: { key }, + create: { key, value: String(value), secret: false }, + update: { value: String(value) }, + }); + } + return Response.json({ updated: entries.length }); +}