fix: search-now dedupes against an active job (match worker enqueue invariant)

The worker's enqueue_due refuses to enqueue when a non-terminal job
already exists for a release. The search-now route enqueued
unconditionally, so two clicks (or a click racing the worker sweep)
could create two concurrent pipelines for the same album, both
importing and colliding on LibraryItem's @@unique([artist,album]).

Mirror the worker's guard: no-op if a job in a non-terminal state
(anything other than imported/needs_attention) already exists for the
release.
This commit is contained in:
Jonathan
2026-07-11 14:19:31 +02:00
parent 7087b2780e
commit 9f895cfc10
2 changed files with 23 additions and 0 deletions
@@ -23,4 +23,17 @@ describe("POST /api/releases/[id]/search", () => {
it("404s an unknown release", async () => { it("404s an unknown release", async () => {
expect((await POST(new Request("http://localhost/x", { method: "POST" }), ctx("nope"))).status).toBe(404); expect((await POST(new Request("http://localhost/x", { method: "POST" }), ctx("nope"))).status).toBe(404);
}); });
it("does not enqueue a duplicate while a non-terminal job is active", async () => {
const r = await prisma.monitoredRelease.create({
data: { artistMbid: "a1", artistName: "John Mayer", rgMbid: "rg-dup", album: "Continuum", secondaryTypes: [], monitored: true },
});
const first = await POST(new Request("http://localhost/x", { method: "POST" }), ctx(r.id));
expect((await first.json()).enqueued).toBe(true);
// the first job is in a non-terminal state ('requested'); a second search must no-op
const second = await POST(new Request("http://localhost/x", { method: "POST" }), ctx(r.id));
expect(second.status).toBe(202);
expect((await second.json()).enqueued).toBe(false);
expect(await prisma.request.count({ where: { monitoredReleaseId: r.id } })).toBe(1);
});
}); });
@@ -5,6 +5,16 @@ export async function POST(_request: Request, { params }: { params: Promise<{ id
const release = await prisma.monitoredRelease.findUnique({ where: { id } }); const release = await prisma.monitoredRelease.findUnique({ where: { id } });
if (!release) return Response.json({ error: "not found" }, { status: 404 }); if (!release) return Response.json({ error: "not found" }, { status: 404 });
const activeJob = await prisma.job.findFirst({
where: {
request: { monitoredReleaseId: release.id },
state: { notIn: ["imported", "needs_attention"] },
},
});
if (activeJob) {
return Response.json({ enqueued: false }, { status: 202 });
}
await prisma.request.create({ await prisma.request.create({
data: { data: {
artist: release.artistName, artist: release.artistName,