This commit is contained in:
Stefan ATZLESBERGER
2026-04-16 15:44:10 +02:00
parent 3bdc8cc646
commit 14ed445328
30 changed files with 1562 additions and 1167 deletions
+4 -206
View File
@@ -1,217 +1,15 @@
import { serve } from "bun";
import index from "./index.html";
import { getDb } from "./server/db";
import { createSnippet, deleteSnippet, getSnippetById, listSnippets, updateSnippet } from "./server/snippets";
import {
createTagTreeNode,
deleteTagTreeNode,
listTagTreeNodes,
suggestTagTreeItems,
updateTagTreeNode,
} from "./server/tagTree";
import { snippetRoutes } from "./server/routes/snippetsHandlers";
import { tagTreeRoutes } from "./server/routes/tagTreeHandlers";
getDb();
async function readJson(req: Request): Promise<unknown> {
try {
return await req.json();
} catch {
return null;
}
}
const server = serve({
routes: {
"/api/snippets": {
async GET(req) {
const url = new URL(req.url);
const q = url.searchParams.get("q");
const sort = url.searchParams.get("sort");
return Response.json(listSnippets(q, sort));
},
async POST(req) {
const body = await readJson(req);
if (!body || typeof body !== "object") {
return Response.json({ error: "Invalid JSON body" }, { status: 400 });
}
const o = body as Record<string, unknown>;
const tags = Array.isArray(o.tags)
? o.tags.filter((t): t is string => typeof t === "string")
: [];
const snippet = createSnippet({
title:
o.title === null
? null
: typeof o.title === "string"
? o.title
: undefined,
body: typeof o.body === "string" ? o.body : "",
tags,
});
return Response.json(snippet, { status: 201 });
},
},
"/api/tag-suggestions": {
async GET(req) {
const url = new URL(req.url);
const q = url.searchParams.get("q") ?? "";
const database = getDb();
return Response.json({ items: suggestTagTreeItems(database, q) });
},
},
"/api/tag-tree": {
async GET() {
return Response.json(listTagTreeNodes());
},
async POST(req) {
const body = await readJson(req);
if (!body || typeof body !== "object") {
return Response.json({ error: "Invalid JSON body" }, { status: 400 });
}
const o = body as Record<string, unknown>;
try {
const node = createTagTreeNode({
name: typeof o.name === "string" ? o.name : "",
parent_id:
o.parent_id === null || o.parent_id === undefined
? null
: typeof o.parent_id === "number"
? o.parent_id
: Number(o.parent_id),
color: o.color === null || o.color === undefined ? null : String(o.color),
label_prefix:
o.label_prefix === null || o.label_prefix === undefined
? null
: typeof o.label_prefix === "string"
? o.label_prefix
: String(o.label_prefix),
});
return Response.json(node, { status: 201 });
} catch (e) {
const msg = e instanceof Error ? e.message : "Fehler";
return Response.json({ error: msg }, { status: 400 });
}
},
},
"/api/tag-tree/:id": {
async PATCH(req) {
const id = Number(req.params.id);
if (!Number.isFinite(id)) {
return Response.json({ error: "Invalid id" }, { status: 400 });
}
const body = await readJson(req);
if (!body || typeof body !== "object") {
return Response.json({ error: "Invalid JSON body" }, { status: 400 });
}
const o = body as Record<string, unknown>;
const patch: {
name?: string;
parent_id?: number | null;
color?: string | null;
label_prefix?: string | null;
} = {};
if ("name" in o && typeof o.name === "string") patch.name = o.name;
if ("parent_id" in o) {
patch.parent_id =
o.parent_id === null ? null : typeof o.parent_id === "number" ? o.parent_id : Number(o.parent_id);
}
if ("color" in o) {
patch.color = o.color === null || o.color === undefined ? null : String(o.color);
}
if ("label_prefix" in o) {
patch.label_prefix =
o.label_prefix === null || o.label_prefix === undefined
? null
: typeof o.label_prefix === "string"
? o.label_prefix
: String(o.label_prefix);
}
try {
const node = updateTagTreeNode(id, patch);
if (!node) {
return Response.json({ error: "Not found" }, { status: 404 });
}
return Response.json(node);
} catch (e) {
const msg = e instanceof Error ? e.message : "Fehler";
return Response.json({ error: msg }, { status: 400 });
}
},
async DELETE(req) {
const id = Number(req.params.id);
if (!Number.isFinite(id)) {
return Response.json({ error: "Invalid id" }, { status: 400 });
}
const ok = deleteTagTreeNode(id);
if (!ok) {
return Response.json({ error: "Not found" }, { status: 404 });
}
return new Response(null, { status: 204 });
},
},
"/api/snippets/:id": {
async GET(req) {
const id = Number(req.params.id);
if (!Number.isFinite(id)) {
return Response.json({ error: "Invalid id" }, { status: 400 });
}
const snippet = getSnippetById(id);
if (!snippet) {
return Response.json({ error: "Not found" }, { status: 404 });
}
return Response.json(snippet);
},
async PATCH(req) {
const id = Number(req.params.id);
if (!Number.isFinite(id)) {
return Response.json({ error: "Invalid id" }, { status: 400 });
}
const body = await readJson(req);
if (!body || typeof body !== "object") {
return Response.json({ error: "Invalid JSON body" }, { status: 400 });
}
const o = body as Record<string, unknown>;
const patch: {
title?: string | null;
body?: string;
tags?: string[];
sort_order?: number;
} = {};
if ("title" in o) {
patch.title = o.title === null ? null : String(o.title);
}
if ("body" in o && typeof o.body === "string") {
patch.body = o.body;
}
if ("tags" in o && Array.isArray(o.tags)) {
patch.tags = o.tags.filter((t): t is string => typeof t === "string");
}
if ("sort_order" in o && typeof o.sort_order === "number" && Number.isFinite(o.sort_order)) {
patch.sort_order = o.sort_order;
}
const snippet = updateSnippet(id, patch);
if (!snippet) {
return Response.json({ error: "Not found" }, { status: 404 });
}
return Response.json(snippet);
},
async DELETE(req) {
const id = Number(req.params.id);
if (!Number.isFinite(id)) {
return Response.json({ error: "Invalid id" }, { status: 400 });
}
const ok = deleteSnippet(id);
if (!ok) {
return Response.json({ error: "Not found" }, { status: 404 });
}
return new Response(null, { status: 204 });
},
},
...snippetRoutes,
...tagTreeRoutes,
"/*": index,
},