import type { FastifyInstance } from "fastify"; import { prisma } from "../database/client"; import { requireAdmin } from "../auth/routes"; import { AppError } from "../security/errors"; import type { Config } from "../config"; import { createOmdbClientFromDb } from "../settings/omdb"; const SITE_SETTINGS_ID = "public_site"; type PublicSiteConfig = { installPageEnabled: boolean; }; function parseConfig(raw: string | null | undefined): Partial { if (!raw) return {}; try { return JSON.parse(raw) as Partial; } catch { return {}; } } async function readSiteConfig(): Promise { const row = await prisma.integrationSetting.findUnique({ where: { id: SITE_SETTINGS_ID } }); if (!row) { return { installPageEnabled: true }; } const parsed = parseConfig(row.configJson); return { installPageEnabled: parsed.installPageEnabled ?? row.enabled ?? true, }; } async function writeSiteConfig(cfg: PublicSiteConfig) { await prisma.integrationSetting.upsert({ where: { id: SITE_SETTINGS_ID }, create: { id: SITE_SETTINGS_ID, enabled: cfg.installPageEnabled, configJson: JSON.stringify(cfg), }, update: { enabled: cfg.installPageEnabled, configJson: JSON.stringify(cfg), }, }); } export function registerSiteRoutes(app: FastifyInstance, config?: Config) { app.get("/api/v1/public/site", async () => { const cfg = await readSiteConfig(); return { installPageEnabled: cfg.installPageEnabled, productName: "Vonas Media Cluster", shortName: "VMC", docsRepo: "https://git.vonas.nl/jos/stremio.git", }; }); /** OMDb Poster API proxy — key blijft server-side. */ app.get("/api/v1/public/omdb-poster/:imdbId", async (request, reply) => { if (!config) { throw new AppError("CONFIG", "OMDb poster proxy niet beschikbaar", 503); } const { imdbId } = request.params as { imdbId: string }; const query = request.query as { h?: string }; const heightRaw = Number.parseInt(query.h ?? "600", 10); const height = Number.isFinite(heightRaw) ? Math.min(Math.max(heightRaw, 100), 1000) : 600; if (!/^tt\d+$/i.test(imdbId)) { throw new AppError("INVALID_REQUEST", "Ongeldige IMDb-id", 400); } const client = await createOmdbClientFromDb(config.SESSION_SECRET, { envFallback: config.OMDB_API_KEY, publicBase: config.PUBLIC_URL, }); if (!client) { throw new AppError("NOT_CONFIGURED", "OMDb niet geconfigureerd", 404); } const poster = await client.fetchPosterBytes(imdbId, height); if (!poster) { throw new AppError("NOT_FOUND", "Poster niet gevonden", 404); } return reply .header("Content-Type", poster.contentType) .header("Cache-Control", "public, max-age=86400") .send(Buffer.from(poster.body)); }); app.get("/api/v1/admin/site/install-page", { preHandler: requireAdmin }, async () => { return readSiteConfig(); }); app.put("/api/v1/admin/site/install-page", { preHandler: requireAdmin }, async (request) => { const body = (request.body || {}) as { enabled?: boolean }; if (typeof body.enabled !== "boolean") { throw new AppError("INVALID_REQUEST", "enabled (boolean) verplicht", 400); } const cfg = { installPageEnabled: body.enabled }; await writeSiteConfig(cfg); return cfg; }); }