generated from Dicken/dickendock
94 lines
3.3 KiB
TypeScript
94 lines
3.3 KiB
TypeScript
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<void> {
|
|
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();
|
|
});
|
|
}
|