Files
Lyra/web/src/app/api/artists/[id]/route.ts
T
Jonathan 855a0f15fe feat(library): ignore a release (do-not-monitor)
Add MonitoredRelease.ignored (migration add_release_ignored). The monitor's
enqueue_due skips ignored releases, and they drop off the Wanted list —
independent of `monitored`, so auto-monitor-future can't silently re-grab
an album the user chose to ignore.

- Worker enqueue_due: AND NOT mr.ignored.
- /api/releases/[id] PATCH now accepts `ignored`; /api/wanted filters it out;
  artist detail exposes `ignored`.
- UI: Ignore button on Wanted rows; discography shows an "Ignored" badge with
  an Ignore/Un-ignore toggle (monitor + search disabled while ignored).

Worker + web tests added.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 21:03:54 +02:00

85 lines
3.0 KiB
TypeScript

import { prisma } from "@/lib/db";
import { Prisma } from "@prisma/client";
import { isCoreRelease } from "@/lib/release-filter";
export async function GET(request: Request, { params }: { params: Promise<{ id: string }> }) {
const { id } = await params;
// ?all=true returns every release type (the detail page filters client-side via tabs);
// otherwise honour the artist's showAllTypes flag (studio-only default) as before.
const all = new URL(request.url).searchParams.get("all") === "true";
const artist = await prisma.watchedArtist.findUnique({
where: { id },
include: { releases: { orderBy: [{ firstReleaseDate: "desc" }, { album: "asc" }] } },
});
if (!artist) return Response.json({ error: "not found" }, { status: 404 });
const visibleReleases = all || artist.showAllTypes ? artist.releases : artist.releases.filter(isCoreRelease);
return Response.json({
id: artist.id,
mbid: artist.mbid,
name: artist.name,
autoMonitorFuture: artist.autoMonitorFuture,
showAllTypes: artist.showAllTypes,
releases: visibleReleases.map((r) => ({
id: r.id,
rgMbid: r.rgMbid,
album: r.album,
primaryType: r.primaryType,
secondaryTypes: r.secondaryTypes,
firstReleaseDate: r.firstReleaseDate,
monitored: r.monitored,
ignored: r.ignored,
state: r.state,
currentQualityClass: r.currentQualityClass,
})),
});
}
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 { autoMonitorFuture, showAllTypes } = (body ?? {}) as {
autoMonitorFuture?: unknown;
showAllTypes?: unknown;
};
const data: { autoMonitorFuture?: boolean; showAllTypes?: boolean } = {};
if (typeof autoMonitorFuture === "boolean") data.autoMonitorFuture = autoMonitorFuture;
if (typeof showAllTypes === "boolean") data.showAllTypes = showAllTypes;
if (Object.keys(data).length === 0) {
return Response.json(
{ error: "autoMonitorFuture or showAllTypes (boolean) is required" },
{ status: 400 },
);
}
try {
const updated = await prisma.watchedArtist.update({ where: { id }, data });
return Response.json({
id: updated.id,
autoMonitorFuture: updated.autoMonitorFuture,
showAllTypes: updated.showAllTypes,
});
} catch (e) {
if (e instanceof Prisma.PrismaClientKnownRequestError && e.code === "P2025") {
return Response.json({ error: "not found" }, { status: 404 });
}
throw e;
}
}
export async function DELETE(_request: Request, { params }: { params: Promise<{ id: string }> }) {
const { id } = await params;
try {
await prisma.watchedArtist.delete({ where: { id } });
return new Response(null, { status: 204 });
} catch (e) {
if (e instanceof Prisma.PrismaClientKnownRequestError && e.code === "P2025") {
return Response.json({ error: "not found" }, { status: 404 });
}
throw e;
}
}