From 6615ecf3a722e20a7bf14123d29f6bcb5ab1a28a Mon Sep 17 00:00:00 2001 From: Jonathan Date: Sat, 11 Jul 2026 14:00:49 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20releases=20API=20=E2=80=94=20monitor=20?= =?UTF-8?q?toggle=20+=20search-now=20enqueue?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/src/app/api/releases/[id]/route.test.ts | 30 +++++++++++++++++++ web/src/app/api/releases/[id]/route.ts | 25 ++++++++++++++++ .../api/releases/[id]/search/route.test.ts | 26 ++++++++++++++++ web/src/app/api/releases/[id]/search/route.ts | 18 +++++++++++ 4 files changed, 99 insertions(+) create mode 100644 web/src/app/api/releases/[id]/route.test.ts create mode 100644 web/src/app/api/releases/[id]/route.ts create mode 100644 web/src/app/api/releases/[id]/search/route.test.ts create mode 100644 web/src/app/api/releases/[id]/search/route.ts diff --git a/web/src/app/api/releases/[id]/route.test.ts b/web/src/app/api/releases/[id]/route.test.ts new file mode 100644 index 0000000..c118d21 --- /dev/null +++ b/web/src/app/api/releases/[id]/route.test.ts @@ -0,0 +1,30 @@ +import { describe, it, expect } from "vitest"; +import { prisma } from "@/lib/db"; +import { PATCH } from "./route"; + +const ctx = (id: string) => ({ params: Promise.resolve({ id }) }); + +async function seedRelease(monitored = false) { + return prisma.monitoredRelease.create({ + data: { artistMbid: "a1", artistName: "John Mayer", rgMbid: "rg1", album: "Continuum", secondaryTypes: [], monitored }, + }); +} +function patchReq(body: unknown) { + return new Request("http://localhost/x", { method: "PATCH", headers: { "content-type": "application/json" }, body: JSON.stringify(body) }); +} + +describe("PATCH /api/releases/[id]", () => { + it("toggles monitored", async () => { + const r = await seedRelease(false); + const res = await PATCH(patchReq({ monitored: true }), ctx(r.id)); + expect(res.status).toBe(200); + expect((await res.json()).monitored).toBe(true); + expect((await prisma.monitoredRelease.findUnique({ where: { id: r.id } }))!.monitored).toBe(true); + }); + + it("400s a non-boolean and 404s an unknown release", async () => { + const r = await seedRelease(); + expect((await PATCH(patchReq({ monitored: 1 }), ctx(r.id))).status).toBe(400); + expect((await PATCH(patchReq({ monitored: true }), ctx("nope"))).status).toBe(404); + }); +}); diff --git a/web/src/app/api/releases/[id]/route.ts b/web/src/app/api/releases/[id]/route.ts new file mode 100644 index 0000000..6e4f9ae --- /dev/null +++ b/web/src/app/api/releases/[id]/route.ts @@ -0,0 +1,25 @@ +import { prisma } from "@/lib/db"; +import { Prisma } from "@prisma/client"; + +export async function PATCH(request: Request, { params }: { params: Promise<{ id: string }> }) { + const { id } = await params; + let body: unknown; + try { + body = await request.json(); + } catch { + return Response.json({ error: "invalid JSON" }, { status: 400 }); + } + const { monitored } = (body ?? {}) as { monitored?: unknown }; + if (typeof monitored !== "boolean") { + return Response.json({ error: "monitored (boolean) is required" }, { status: 400 }); + } + try { + const updated = await prisma.monitoredRelease.update({ where: { id }, data: { monitored } }); + return Response.json({ id: updated.id, monitored: updated.monitored }); + } catch (e) { + if (e instanceof Prisma.PrismaClientKnownRequestError && e.code === "P2025") { + return Response.json({ error: "not found" }, { status: 404 }); + } + throw e; + } +} diff --git a/web/src/app/api/releases/[id]/search/route.test.ts b/web/src/app/api/releases/[id]/search/route.test.ts new file mode 100644 index 0000000..4861a54 --- /dev/null +++ b/web/src/app/api/releases/[id]/search/route.test.ts @@ -0,0 +1,26 @@ +import { describe, it, expect } from "vitest"; +import { prisma } from "@/lib/db"; +import { POST } from "./route"; + +const ctx = (id: string) => ({ params: Promise.resolve({ id }) }); + +describe("POST /api/releases/[id]/search", () => { + it("enqueues a Request+Job linked to the release and stamps lastSearchedAt", async () => { + const r = await prisma.monitoredRelease.create({ + data: { artistMbid: "a1", artistName: "John Mayer", rgMbid: "rg1", album: "Continuum", secondaryTypes: [], monitored: true }, + }); + const res = await POST(new Request("http://localhost/x", { method: "POST" }), ctx(r.id)); + expect(res.status).toBe(202); + expect((await res.json()).enqueued).toBe(true); + + const reqs = await prisma.request.findMany({ where: { monitoredReleaseId: r.id }, include: { job: true } }); + expect(reqs).toHaveLength(1); + expect(reqs[0]).toMatchObject({ artist: "John Mayer", album: "Continuum" }); + expect(reqs[0].job!.state).toBe("requested"); + expect((await prisma.monitoredRelease.findUnique({ where: { id: r.id } }))!.lastSearchedAt).not.toBeNull(); + }); + + it("404s an unknown release", async () => { + expect((await POST(new Request("http://localhost/x", { method: "POST" }), ctx("nope"))).status).toBe(404); + }); +}); diff --git a/web/src/app/api/releases/[id]/search/route.ts b/web/src/app/api/releases/[id]/search/route.ts new file mode 100644 index 0000000..3e8e07f --- /dev/null +++ b/web/src/app/api/releases/[id]/search/route.ts @@ -0,0 +1,18 @@ +import { prisma } from "@/lib/db"; + +export async function POST(_request: Request, { params }: { params: Promise<{ id: string }> }) { + const { id } = await params; + const release = await prisma.monitoredRelease.findUnique({ where: { id } }); + if (!release) return Response.json({ error: "not found" }, { status: 404 }); + + await prisma.request.create({ + data: { + artist: release.artistName, + album: release.album, + monitoredReleaseId: release.id, + job: { create: {} }, + }, + }); + await prisma.monitoredRelease.update({ where: { id }, data: { lastSearchedAt: new Date() } }); + return Response.json({ enqueued: true }, { status: 202 }); +}