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 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
7b0a6738dd
commit
6963e297b0
@@ -0,0 +1,56 @@
|
||||
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();
|
||||
},
|
||||
}),
|
||||
];
|
||||
Reference in New Issue
Block a user