generated from Dicken/dickendock
Lesezeichen, Import/Export, Favoriten-Sortierung im Frontend, Favicon-Fix, sortierbare Spalten, Kategorie-Dropdown
This commit is contained in:
86
apps/backend/src/routes/bookmarks.ts
Normal file
86
apps/backend/src/routes/bookmarks.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
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;
|
||||
|
||||
// Titel/Favicon automatisch ziehen, falls kein Name angegeben wurde oder
|
||||
// schlicht um ein Favicon zu bekommen (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);
|
||||
}
|
||||
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, { 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();
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user