855a0f15fe
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>
85 lines
3.0 KiB
TypeScript
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;
|
|
}
|
|
}
|