Files
STTracker/apps/server/src/routes/works.ts
T
Lucas WintherandClaude Opus 5.5 6963e297b0 feat(server): REST API, auth, sources, changes, export
createApp() serves /api/v1 from a typed route table (zod bodies and queries), with
session-cookie and Bearer app-token auth, a CSRF header for cookie writes, login/register
rate limiting, body caps, JSON error mapping, OpenAPI generation and a 404 /ingest mount.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-30 21:15:01 +01:00

57 lines
2.0 KiB
TypeScript

import { acceptSuggestion, listSuggestions, rejectSuggestion, resolveVisibleWork } from "@sttracker/core/entries";
import { NotFoundError } from "@sttracker/core/errors";
import { getWork, searchWorks } from "@sttracker/core/works";
import { z } from "zod";
import { noContent, route } from "../http.ts";
import { mediaType, qInt } from "./schemas.ts";
export const workRoutes = [
route({
method: "GET",
path: "works/search",
summary: "Search works visible to you by title.",
auth: "any:read",
query: z.object({ q: z.string().min(1).max(500), mediaType: mediaType.optional(), limit: qInt.optional() }),
handler: async ({ deps, user, query }) => searchWorks(deps.db, user.id, query.q, { mediaType: query.mediaType, limit: query.limit }),
}),
route({
method: "GET",
path: "works/:id",
summary: "One work (follows merges and your aliases).",
auth: "any:read",
handler: async ({ deps, user, params }) => {
const visible = await resolveVisibleWork(deps.db.k, user.id, params.id!);
const work = await getWork(deps.db, visible.id);
if (!work) throw new NotFoundError("work");
return work;
},
}),
route({
method: "GET",
path: "works/:id/suggestions",
summary: "Pending match suggestions for a work.",
auth: "any:read",
handler: async ({ deps, user, params }) => listSuggestions(deps.db, user.id, params.id!),
}),
route({
method: "POST",
path: "suggestions/:id/accept",
summary: "Accept a match suggestion (moves your entry to the candidate work).",
auth: "any:write",
handler: async ({ deps, user, params }) => {
await acceptSuggestion(deps.db, user.id, params.id!);
return noContent();
},
}),
route({
method: "POST",
path: "suggestions/:id/reject",
summary: "Reject a match suggestion.",
auth: "any:write",
handler: async ({ deps, user, params }) => {
await rejectSuggestion(deps.db, user.id, params.id!);
return noContent();
},
}),
];