import type { FastifyInstance } from "fastify"; import { BookmarkCreateSchema, BookmarkReorderSchema, BookmarkUpdateSchema } from "@launchpad/shared"; import * as bookmarkRepo from "../db/repositories/bookmarks.js"; import * as categoryRepo from "../db/repositories/categories.js"; import { probeHttp } from "../scanner/http.js"; export async function bookmarkRoutes(app: FastifyInstance): Promise { app.get("/api/bookmarks", async () => { return bookmarkRepo.listBookmarks(); }); app.get("/api/bookmarks/:id", async (request, reply) => { const { id } = request.params as { id: string }; const bookmark = bookmarkRepo.getBookmark(id); if (!bookmark) { return reply.code(404).send({ error: "Lesezeichen nicht gefunden" }); } return bookmark; }); app.post("/api/bookmarks", async (request, reply) => { const parsed = BookmarkCreateSchema.safeParse(request.body); if (!parsed.success) { return reply.code(400).send({ error: "Ungültige Eingabe", issues: parsed.error.issues }); } let displayName = parsed.data.displayName; let favicon: string | null = null; let description = parsed.data.description; // Titel/Favicon/Beschreibung automatisch ziehen, falls nicht angegeben // (derselbe Mechanismus wie beim Netzwerk-Scanner, siehe // apps/backend/src/scanner/http.ts). try { const probe = await probeHttp(parsed.data.url, 5000); if (!displayName) { displayName = probe.title ?? bookmarkRepo.extractHostname(parsed.data.url); } if (!description) { description = probe.description; } favicon = probe.faviconUrl ?? null; } catch { if (!displayName) { displayName = bookmarkRepo.extractHostname(parsed.data.url); } } if (parsed.data.category) { categoryRepo.ensureCategory(parsed.data.category); } const bookmark = bookmarkRepo.createBookmark( { ...parsed.data, description }, { displayName, favicon } ); return reply.code(201).send(bookmark); }); // Muss vor der /:id-Route stehen, damit "reorder" nicht als ID interpretiert wird. app.patch("/api/bookmarks/reorder", async (request, reply) => { const parsed = BookmarkReorderSchema.safeParse(request.body); if (!parsed.success) { return reply.code(400).send({ error: "Ungültige Eingabe", issues: parsed.error.issues }); } return bookmarkRepo.reorderBookmarks(parsed.data); }); app.patch("/api/bookmarks/:id", async (request, reply) => { const { id } = request.params as { id: string }; const parsed = BookmarkUpdateSchema.safeParse(request.body); if (!parsed.success) { return reply.code(400).send({ error: "Ungültige Eingabe", issues: parsed.error.issues }); } if (parsed.data.category) { categoryRepo.ensureCategory(parsed.data.category); } const bookmark = bookmarkRepo.updateBookmark(id, parsed.data); if (!bookmark) { return reply.code(404).send({ error: "Lesezeichen nicht gefunden" }); } return bookmark; }); app.delete("/api/bookmarks/:id", async (request, reply) => { const { id } = request.params as { id: string }; const deleted = bookmarkRepo.deleteBookmark(id); if (!deleted) { return reply.code(404).send({ error: "Lesezeichen nicht gefunden" }); } return reply.code(204).send(); }); }