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>
80 lines
2.9 KiB
TypeScript
80 lines
2.9 KiB
TypeScript
import { Prisma } from "@prisma/client";
|
|
import { prisma } from "@/lib/db";
|
|
import { searchReleaseGroup } from "@/lib/musicbrainz";
|
|
|
|
export async function GET() {
|
|
const items = await prisma.monitoredRelease.findMany({
|
|
where: { monitored: true, ignored: false, state: { not: "fulfilled" } },
|
|
orderBy: [{ lastSearchedAt: { sort: "asc", nulls: "first" } }, { createdAt: "asc" }],
|
|
});
|
|
return Response.json({
|
|
wanted: items.map((r) => ({
|
|
id: r.id,
|
|
artistName: r.artistName,
|
|
artistMbid: r.artistMbid,
|
|
album: r.album,
|
|
state: r.state,
|
|
currentQualityClass: r.currentQualityClass,
|
|
lastSearchedAt: r.lastSearchedAt,
|
|
watchedArtistId: r.watchedArtistId,
|
|
rgMbid: r.rgMbid,
|
|
primaryType: r.primaryType,
|
|
firstReleaseDate: r.firstReleaseDate,
|
|
})),
|
|
});
|
|
}
|
|
|
|
export async function POST(request: Request) {
|
|
let body: unknown;
|
|
try {
|
|
body = await request.json();
|
|
} catch {
|
|
return Response.json({ error: "invalid JSON" }, { status: 400 });
|
|
}
|
|
const { artist, album } = (body ?? {}) as { artist?: unknown; album?: unknown };
|
|
if (typeof artist !== "string" || !artist.trim() || typeof album !== "string" || !album.trim()) {
|
|
return Response.json({ error: "artist and album are required" }, { status: 400 });
|
|
}
|
|
|
|
let match;
|
|
try {
|
|
match = await searchReleaseGroup(artist.trim(), album.trim());
|
|
} catch {
|
|
return Response.json({ error: "MusicBrainz unavailable" }, { status: 502 });
|
|
}
|
|
if (!match) {
|
|
return Response.json({ error: "no matching release-group found" }, { status: 404 });
|
|
}
|
|
|
|
const existing = await prisma.monitoredRelease.findUnique({ where: { rgMbid: match.rgMbid } });
|
|
if (existing) {
|
|
const updated = await prisma.monitoredRelease.update({ where: { rgMbid: match.rgMbid }, data: { monitored: true } });
|
|
return Response.json({ id: updated.id, album: updated.album, monitored: updated.monitored }, { status: 200 });
|
|
}
|
|
try {
|
|
const created = await prisma.monitoredRelease.create({
|
|
data: {
|
|
artistMbid: match.artistMbid,
|
|
artistName: match.artistName,
|
|
rgMbid: match.rgMbid,
|
|
album: match.title,
|
|
primaryType: match.primaryType,
|
|
secondaryTypes: match.secondaryTypes,
|
|
firstReleaseDate: match.firstReleaseDate,
|
|
monitored: true,
|
|
},
|
|
});
|
|
return Response.json({ id: created.id, album: created.album, monitored: created.monitored }, { status: 201 });
|
|
} catch (e) {
|
|
if (e instanceof Prisma.PrismaClientKnownRequestError && e.code === "P2002") {
|
|
// Concurrent Want for the same release-group won the create; converge to the update path.
|
|
const updated = await prisma.monitoredRelease.update({
|
|
where: { rgMbid: match.rgMbid },
|
|
data: { monitored: true },
|
|
});
|
|
return Response.json({ id: updated.id, album: updated.album, monitored: updated.monitored }, { status: 200 });
|
|
}
|
|
throw e;
|
|
}
|
|
}
|