generated from Dicken/dickendock
94 lines
3.6 KiB
TypeScript
94 lines
3.6 KiB
TypeScript
import type { FastifyInstance } from "fastify";
|
|
import { ServiceCreateSchema, ServiceReorderSchema, ServiceUpdateSchema } from "@launchpad/shared";
|
|
import * as deviceRepo from "../db/repositories/devices.js";
|
|
import * as serviceRepo from "../db/repositories/services.js";
|
|
import { suggestIconsForServiceName } from "../scanner/iconDb.js";
|
|
|
|
interface ServiceListQuery {
|
|
deviceId?: string;
|
|
category?: string;
|
|
favorite?: string;
|
|
}
|
|
|
|
export async function serviceRoutes(app: FastifyInstance): Promise<void> {
|
|
app.get("/api/services", async (request) => {
|
|
const query = request.query as ServiceListQuery;
|
|
return serviceRepo.listServices({
|
|
deviceId: query.deviceId,
|
|
category: query.category,
|
|
favorite: query.favorite === undefined ? undefined : query.favorite === "true",
|
|
});
|
|
});
|
|
|
|
app.get("/api/services/:id", async (request, reply) => {
|
|
const { id } = request.params as { id: string };
|
|
const service = serviceRepo.getService(id);
|
|
if (!service) {
|
|
return reply.code(404).send({ error: "Dienst nicht gefunden" });
|
|
}
|
|
return service;
|
|
});
|
|
|
|
app.post("/api/services", async (request, reply) => {
|
|
const parsed = ServiceCreateSchema.safeParse(request.body);
|
|
if (!parsed.success) {
|
|
return reply.code(400).send({ error: "Ungültige Eingabe", issues: parsed.error.issues });
|
|
}
|
|
|
|
const device = deviceRepo.getDevice(parsed.data.deviceId);
|
|
if (!device) {
|
|
return reply.code(400).send({ error: `Gerät ${parsed.data.deviceId} existiert nicht` });
|
|
}
|
|
|
|
const service = serviceRepo.createService(parsed.data);
|
|
return reply.code(201).send(service);
|
|
});
|
|
|
|
// Muss vor der /:id-Route stehen, damit "reorder" nicht als ID interpretiert wird.
|
|
app.patch("/api/services/reorder", async (request, reply) => {
|
|
const parsed = ServiceReorderSchema.safeParse(request.body);
|
|
if (!parsed.success) {
|
|
return reply.code(400).send({ error: "Ungültige Eingabe", issues: parsed.error.issues });
|
|
}
|
|
return serviceRepo.reorderServices(parsed.data);
|
|
});
|
|
|
|
app.patch("/api/services/:id", async (request, reply) => {
|
|
const { id } = request.params as { id: string };
|
|
const parsed = ServiceUpdateSchema.safeParse(request.body);
|
|
if (!parsed.success) {
|
|
return reply.code(400).send({ error: "Ungültige Eingabe", issues: parsed.error.issues });
|
|
}
|
|
const service = serviceRepo.updateService(id, parsed.data);
|
|
if (!service) {
|
|
return reply.code(404).send({ error: "Dienst nicht gefunden" });
|
|
}
|
|
return service;
|
|
});
|
|
|
|
app.delete("/api/services/:id", async (request, reply) => {
|
|
const { id } = request.params as { id: string };
|
|
const deleted = serviceRepo.deleteService(id);
|
|
if (!deleted) {
|
|
return reply.code(404).send({ error: "Dienst nicht gefunden" });
|
|
}
|
|
return reply.code(204).send();
|
|
});
|
|
|
|
// Liefert für jeden Dienst OHNE Favicon eine Liste möglicher Treffer aus
|
|
// der externen Icon-Datenbank (siehe scanner/iconDb.ts), damit der Nutzer
|
|
// selbst auswählen kann statt dass automatisch geraten wird (mehrdeutige
|
|
// Namen wie "LXC Adguard" könnten sonst falsch zugeordnet werden).
|
|
app.get("/api/services/favicon-suggestions", async () => {
|
|
const services = serviceRepo.listServices().filter((s) => !s.favicon);
|
|
const suggestions = [];
|
|
for (const service of services) {
|
|
const candidates = await suggestIconsForServiceName(service.displayName);
|
|
if (candidates.length > 0) {
|
|
suggestions.push({ serviceId: service.id, serviceName: service.displayName, candidates });
|
|
}
|
|
}
|
|
return { checked: services.length, suggestions };
|
|
});
|
|
}
|