From 3f624c33959eaf126c46ec1edb963986987aef70 Mon Sep 17 00:00:00 2001 From: Jonathan Date: Wed, 15 Jul 2026 12:20:16 +0200 Subject: [PATCH] feat(web): DELETE /api/requests/[id] to remove a queued item Co-Authored-By: Claude Opus 4.8 (1M context) --- web/src/app/api/requests/[id]/route.test.ts | 37 +++++++++++++++++++++ web/src/app/api/requests/[id]/route.ts | 16 +++++++++ 2 files changed, 53 insertions(+) create mode 100644 web/src/app/api/requests/[id]/route.test.ts create mode 100644 web/src/app/api/requests/[id]/route.ts diff --git a/web/src/app/api/requests/[id]/route.test.ts b/web/src/app/api/requests/[id]/route.test.ts new file mode 100644 index 0000000..5e94661 --- /dev/null +++ b/web/src/app/api/requests/[id]/route.test.ts @@ -0,0 +1,37 @@ +import { describe, it, expect } from "vitest"; +import { DELETE } from "./route"; +import { prisma } from "@/lib/db"; +import type { Prisma } from "@prisma/client"; + +function del(id: string) { + return new Request(`http://localhost/api/requests/${id}`, { method: "DELETE" }); +} + +async function seed(jobData: Prisma.JobCreateWithoutRequestInput) { + return prisma.request.create({ + data: { artist: "A", album: "B", status: "pending", job: { create: jobData } }, + include: { job: true }, + }); +} + +describe("remove request API", () => { + it("deletes a queued request and cascades its job", async () => { + const created = await seed({ state: "requested" }); + const res = await DELETE(del(created.id), { params: Promise.resolve({ id: created.id }) }); + expect(res.status).toBe(200); + expect(await prisma.request.findUnique({ where: { id: created.id } })).toBeNull(); + expect(await prisma.job.findUnique({ where: { id: created.job!.id } })).toBeNull(); + }); + + it("refuses to remove an in-flight job", async () => { + const created = await seed({ state: "downloading" }); + const res = await DELETE(del(created.id), { params: Promise.resolve({ id: created.id }) }); + expect(res.status).toBe(400); + expect(await prisma.request.findUnique({ where: { id: created.id } })).not.toBeNull(); + }); + + it("404s for a missing request", async () => { + const res = await DELETE(del("nope"), { params: Promise.resolve({ id: "nope" }) }); + expect(res.status).toBe(404); + }); +}); diff --git a/web/src/app/api/requests/[id]/route.ts b/web/src/app/api/requests/[id]/route.ts new file mode 100644 index 0000000..5bb15d3 --- /dev/null +++ b/web/src/app/api/requests/[id]/route.ts @@ -0,0 +1,16 @@ +import { prisma } from "@/lib/db"; + +export async function DELETE(_request: Request, { params }: { params: Promise<{ id: string }> }) { + const { id } = await params; + + const req = await prisma.request.findUnique({ where: { id }, include: { job: true } }); + if (!req) { + return Response.json({ error: "not found" }, { status: 404 }); + } + if (req.job && req.job.state !== "requested") { + return Response.json({ error: "only queued items can be removed" }, { status: 400 }); + } + + await prisma.request.delete({ where: { id } }); // cascades Job + Candidates + return Response.json({ ok: true }); +}