import "./env.js"; import Fastify from "fastify"; import cors from "@fastify/cors"; import { ensureSchema } from "./db/client.js"; import { healthRoutes } from "./routes/health.js"; import { deviceRoutes } from "./routes/devices.js"; import { serviceRoutes } from "./routes/services.js"; import { categoryRoutes } from "./routes/categories.js"; import { scanRoutes } from "./routes/scan.js"; import { logRoutes } from "./routes/logs.js"; import { pluginRoutes } from "./routes/plugins.js"; import { resetRoutes } from "./routes/reset.js"; import { transferRoutes } from "./routes/transfer.js"; import { bookmarkRoutes } from "./routes/bookmarks.js"; import { recentVisitsRoutes } from "./routes/recentVisits.js"; import { settingsRoutes } from "./routes/settings.js"; import { readLaterRoutes } from "./routes/readLater.js"; import { faviconProxyRoutes } from "./routes/faviconProxy.js"; import { iconsRoutes } from "./routes/icons.js"; import { apiRoutes } from "./routes/apis.js"; import { loadPlugins } from "./plugins/loader.js"; import { startLiveStatusHeartbeat } from "./liveStatus.js"; import * as serviceRepo from "./db/repositories/services.js"; import * as bookmarkRepo from "./db/repositories/bookmarks.js"; import * as categoryRepo from "./db/repositories/categories.js"; const PORT = Number(process.env.PORT ?? 3001); const HOST = process.env.HOST ?? "0.0.0.0"; async function main() { const app = Fastify({ logger: { level: process.env.LOG_LEVEL ?? "info", transport: process.env.NODE_ENV !== "production" ? { target: "pino-pretty", options: { colorize: true } } : undefined, }, }); await app.register(cors, { origin: process.env.CORS_ORIGIN ?? true, }); ensureSchema(); // Kategorien, die bereits auf Diensten stehen (z. B. aus Scans vor diesem // Fix, oder von Plugins), aber noch nicht in der categories-Tabelle sind, // hier nachtragen. Damit zeigt Admin -> Kategorien immer das, was auch in // der Suche als Kategorie auftaucht. const existingCategoryNames = Array.from( new Set( [ ...serviceRepo.listServices().map((s) => s.category), ...bookmarkRepo.listBookmarks().map((b) => b.category), ].filter((c): c is string => !!c) ) ); const syncedCount = categoryRepo.syncCategoriesFromServiceValues(existingCategoryNames); if (syncedCount > 0) { app.log.info(`${syncedCount} Kategorie(n) aus bestehenden Diensten nachgetragen`); } const plugins = await loadPlugins(); app.log.info(`${plugins.length} Plugin(s) geladen`); await app.register(healthRoutes); await app.register(deviceRoutes); await app.register(serviceRoutes); await app.register(categoryRoutes); await app.register(scanRoutes); await app.register(logRoutes); await app.register(pluginRoutes); await app.register(resetRoutes); await app.register(transferRoutes); await app.register(bookmarkRoutes); await app.register(recentVisitsRoutes); await app.register(settingsRoutes); await app.register(readLaterRoutes); await app.register(faviconProxyRoutes); await app.register(iconsRoutes); await app.register(apiRoutes); app.get("/", async () => { return { name: "LaunchPad API", status: "running" }; }); try { await app.listen({ port: PORT, host: HOST }); app.log.info(`LaunchPad backend läuft auf http://${HOST}:${PORT}`); startLiveStatusHeartbeat(); } catch (err) { app.log.error(err); process.exit(1); } } main();