feat: add request create/list API

This commit is contained in:
Jonathan
2026-07-10 16:47:54 +02:00
parent ab4db641b0
commit 15bb68bc49
4 changed files with 114 additions and 0 deletions
+38
View File
@@ -0,0 +1,38 @@
import { describe, it, expect } from "vitest";
import { POST, GET } from "./route";
function postReq(body: unknown) {
return new Request("http://localhost/api/requests", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify(body),
});
}
describe("requests API", () => {
it("creates a request with an initial job", async () => {
const res = await POST(postReq({ artist: "Radiohead", album: "In Rainbows" }));
expect(res.status).toBe(201);
const body = await res.json();
expect(body.artist).toBe("Radiohead");
expect(body.status).toBe("pending");
expect(body.job.state).toBe("requested");
expect(body.job.currentStage).toBe("intake");
});
it("rejects a request missing fields", async () => {
const res = await POST(postReq({ artist: "Radiohead" }));
expect(res.status).toBe(400);
});
it("lists requests newest first with job state", async () => {
await POST(postReq({ artist: "A", album: "1" }));
await POST(postReq({ artist: "B", album: "2" }));
const res = await GET();
expect(res.status).toBe(200);
const body = await res.json();
expect(body.requests).toHaveLength(2);
expect(body.requests[0].artist).toBe("B");
expect(body.requests[0].job.state).toBe("requested");
});
});
+53
View File
@@ -0,0 +1,53 @@
import { prisma } from "@/lib/db";
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 });
}
const created = await prisma.request.create({
data: {
artist: artist.trim(),
album: album.trim(),
job: { create: {} },
},
include: { job: true },
});
return Response.json(
{
id: created.id,
artist: created.artist,
album: created.album,
status: created.status,
job: { id: created.job!.id, state: created.job!.state, currentStage: created.job!.currentStage },
},
{ status: 201 },
);
}
export async function GET() {
const requests = await prisma.request.findMany({
orderBy: { createdAt: "desc" },
include: { job: true },
});
return Response.json({
requests: requests.map((r) => ({
id: r.id,
artist: r.artist,
album: r.album,
status: r.status,
createdAt: r.createdAt,
job: r.job ? { state: r.job.state, currentStage: r.job.currentStage } : null,
})),
});
}
+8
View File
@@ -0,0 +1,8 @@
import { beforeEach } from "vitest";
import { prisma } from "@/lib/db";
beforeEach(async () => {
// Job has a cascade FK to Request; delete Job first.
await prisma.job.deleteMany();
await prisma.request.deleteMany();
});
+15
View File
@@ -0,0 +1,15 @@
import { defineConfig } from "vitest/config";
import { fileURLToPath } from "node:url";
export default defineConfig({
resolve: {
alias: {
"@": fileURLToPath(new URL("./src", import.meta.url)),
},
},
test: {
environment: "node",
setupFiles: ["./src/test/setup.ts"],
fileParallelism: false,
},
});