feat: wanted list API — list + standalone add
This commit is contained in:
@@ -0,0 +1,54 @@
|
|||||||
|
import { describe, it, expect, vi, afterEach } from "vitest";
|
||||||
|
|
||||||
|
vi.mock("@/lib/musicbrainz", () => ({ searchReleaseGroup: vi.fn() }));
|
||||||
|
import { searchReleaseGroup } from "@/lib/musicbrainz";
|
||||||
|
import { prisma } from "@/lib/db";
|
||||||
|
import { GET, POST } from "./route";
|
||||||
|
|
||||||
|
const mockSearch = vi.mocked(searchReleaseGroup);
|
||||||
|
afterEach(() => mockSearch.mockReset());
|
||||||
|
|
||||||
|
function postReq(body: unknown) {
|
||||||
|
return new Request("http://localhost/api/wanted", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify(body) });
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("wanted API", () => {
|
||||||
|
it("lists monitored, non-fulfilled releases only", async () => {
|
||||||
|
await prisma.monitoredRelease.createMany({
|
||||||
|
data: [
|
||||||
|
{ artistMbid: "a1", artistName: "A", rgMbid: "rg1", album: "Wanted", secondaryTypes: [], monitored: true, state: "wanted" },
|
||||||
|
{ artistMbid: "a1", artistName: "A", rgMbid: "rg2", album: "Done", secondaryTypes: [], monitored: true, state: "fulfilled" },
|
||||||
|
{ artistMbid: "a1", artistName: "A", rgMbid: "rg3", album: "Dormant", secondaryTypes: [], monitored: false, state: "wanted" },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
const res = await GET();
|
||||||
|
expect(res.status).toBe(200);
|
||||||
|
const wanted = (await res.json()).wanted;
|
||||||
|
expect(wanted.map((w: any) => w.album)).toEqual(["Wanted"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("adds a standalone wanted release from artist+album", async () => {
|
||||||
|
mockSearch.mockResolvedValue({ rgMbid: "rg9", title: "Continuum", primaryType: "Album", secondaryTypes: [], firstReleaseDate: "2006-09-12", artistMbid: "a1", artistName: "John Mayer" });
|
||||||
|
const res = await POST(postReq({ artist: "John Mayer", album: "Continuum" }));
|
||||||
|
expect(res.status).toBe(201);
|
||||||
|
const row = await prisma.monitoredRelease.findUnique({ where: { rgMbid: "rg9" } });
|
||||||
|
expect(row).toMatchObject({ artistName: "John Mayer", album: "Continuum", monitored: true, watchedArtistId: null });
|
||||||
|
});
|
||||||
|
|
||||||
|
it("re-monitors an existing release-group (200) instead of duplicating", async () => {
|
||||||
|
await prisma.monitoredRelease.create({ data: { artistMbid: "a1", artistName: "John Mayer", rgMbid: "rg9", album: "Continuum", secondaryTypes: [], monitored: false } });
|
||||||
|
mockSearch.mockResolvedValue({ rgMbid: "rg9", title: "Continuum", primaryType: "Album", secondaryTypes: [], firstReleaseDate: "2006", artistMbid: "a1", artistName: "John Mayer" });
|
||||||
|
const res = await POST(postReq({ artist: "John Mayer", album: "Continuum" }));
|
||||||
|
expect(res.status).toBe(200);
|
||||||
|
expect((await prisma.monitoredRelease.findUnique({ where: { rgMbid: "rg9" } }))!.monitored).toBe(true);
|
||||||
|
expect(await prisma.monitoredRelease.count({ where: { rgMbid: "rg9" } })).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("400s a bad body, 404s no MB match, 502s on MB failure", async () => {
|
||||||
|
expect((await POST(postReq({ artist: "X" }))).status).toBe(400);
|
||||||
|
mockSearch.mockResolvedValue(null);
|
||||||
|
expect((await POST(postReq({ artist: "X", album: "Y" }))).status).toBe(404);
|
||||||
|
mockSearch.mockRejectedValue(new Error("boom"));
|
||||||
|
expect((await POST(postReq({ artist: "X", album: "Y" }))).status).toBe(502);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
import { prisma } from "@/lib/db";
|
||||||
|
import { searchReleaseGroup } from "@/lib/musicbrainz";
|
||||||
|
|
||||||
|
export async function GET() {
|
||||||
|
const items = await prisma.monitoredRelease.findMany({
|
||||||
|
where: { monitored: true, state: { not: "fulfilled" } },
|
||||||
|
orderBy: [{ lastSearchedAt: { sort: "asc", nulls: "first" } }, { createdAt: "asc" }],
|
||||||
|
});
|
||||||
|
return Response.json({
|
||||||
|
wanted: items.map((r) => ({
|
||||||
|
id: r.id,
|
||||||
|
artistName: r.artistName,
|
||||||
|
album: r.album,
|
||||||
|
state: r.state,
|
||||||
|
currentQualityClass: r.currentQualityClass,
|
||||||
|
lastSearchedAt: r.lastSearchedAt,
|
||||||
|
watchedArtistId: r.watchedArtistId,
|
||||||
|
})),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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 });
|
||||||
|
}
|
||||||
|
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 });
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user