import { Router, type IRouter } from "express"; import { db } from "@workspace/db"; import { aiProvidersTable, type AiProvider } from "@workspace/db"; import { eq } from "drizzle-orm"; import { ListProvidersResponse, CreateProviderBody, UpdateProviderParams, UpdateProviderBody, UpdateProviderResponse, DeleteProviderParams, TestProviderParams, TestProviderResponse, TestProviderConnectionBody, } from "@workspace/api-zod"; import { callProvider } from "../lib/aiAnalysis"; const router: IRouter = Router(); function maskToken(token: string | null): string { if (!token) return ""; if (token.length <= 8) return "••••"; return `${token.slice(0, 3)}…${token.slice(-4)}`; } function serializeProvider(p: AiProvider) { return { id: p.id, name: p.name, apiType: p.apiType, baseUrl: p.baseUrl, model: p.model, enabled: p.enabled, hasToken: !!p.apiToken, tokenPreview: maskToken(p.apiToken), createdAt: p.createdAt.toISOString(), }; } router.get("/providers", async (_req, res) => { const rows = await db.select().from(aiProvidersTable).orderBy(aiProvidersTable.id); res.json(ListProvidersResponse.parse(rows.map(serializeProvider))); }); router.post("/providers", async (req, res) => { const parsed = CreateProviderBody.safeParse(req.body); if (!parsed.success) return res .status(400) .json({ message: "Ungültige Eingabe", details: parsed.error.issues }); const d = parsed.data; const [created] = await db .insert(aiProvidersTable) .values({ name: d.name, apiType: d.apiType, baseUrl: d.baseUrl, model: d.model, apiToken: d.apiToken ?? null, enabled: d.enabled ?? true, }) .returning(); return res .status(201) .json(UpdateProviderResponse.parse(serializeProvider(created))); }); router.patch("/providers/:id", async (req, res) => { const params = UpdateProviderParams.safeParse(req.params); if (!params.success) return res.status(400).json({ message: "Ungültige ID" }); const parsed = UpdateProviderBody.safeParse(req.body); if (!parsed.success) return res .status(400) .json({ message: "Ungültige Eingabe", details: parsed.error.issues }); const d = parsed.data; const update: Partial = {}; if (d.name !== undefined) update.name = d.name; if (d.apiType !== undefined) update.apiType = d.apiType; if (d.baseUrl !== undefined) update.baseUrl = d.baseUrl; if (d.model !== undefined) update.model = d.model; if (d.enabled !== undefined) update.enabled = d.enabled; if (d.apiToken !== undefined && d.apiToken !== "") update.apiToken = d.apiToken; const [updated] = await db .update(aiProvidersTable) .set(update) .where(eq(aiProvidersTable.id, params.data.id)) .returning(); if (!updated) return res.status(404).json({ message: "Provider nicht gefunden" }); return res.json(UpdateProviderResponse.parse(serializeProvider(updated))); }); router.delete("/providers/:id", async (req, res) => { const params = DeleteProviderParams.safeParse(req.params); if (!params.success) return res.status(400).json({ message: "Ungültige ID" }); await db .delete(aiProvidersTable) .where(eq(aiProvidersTable.id, params.data.id)); return res.status(204).send(); }); router.post("/providers/:id/test", async (req, res) => { const params = TestProviderParams.safeParse(req.params); if (!params.success) return res.status(400).json({ message: "Ungültige ID" }); const [provider] = await db .select() .from(aiProvidersTable) .where(eq(aiProvidersTable.id, params.data.id)); if (!provider) return res.status(404).json({ message: "Provider nicht gefunden" }); if (!provider.apiToken) { return res.json( TestProviderResponse.parse({ ok: false, message: "Kein API-Token hinterlegt.", }), ); } try { const reply = await callProvider( provider, "Du bist ein Verbindungstest.", 'Antworte mit dem einzelnen Wort "OK".', ); return res.json( TestProviderResponse.parse({ ok: true, message: `Verbindung erfolgreich. Antwort: ${reply.trim().slice(0, 80) || "(leer)"}`, }), ); } catch (err) { return res.json( TestProviderResponse.parse({ ok: false, message: err instanceof Error ? err.message : "Verbindung fehlgeschlagen.", }), ); } }); router.post("/providers/test-connection", async (req, res) => { const parsed = TestProviderConnectionBody.safeParse(req.body); if (!parsed.success) return res .status(400) .json({ message: "Ungültige Eingabe", details: parsed.error.issues }); const d = parsed.data; let token: string | null = d.apiToken && d.apiToken !== "" ? d.apiToken : null; if (!token && d.providerId !== undefined && d.providerId !== null) { const [existing] = await db .select() .from(aiProvidersTable) .where(eq(aiProvidersTable.id, d.providerId)); if (existing?.apiToken) token = existing.apiToken; } if (!token) { return res.json( TestProviderResponse.parse({ ok: false, message: "Kein API-Token angegeben.", }), ); } const provider: AiProvider = { id: d.providerId ?? 0, name: "", apiType: d.apiType, baseUrl: d.baseUrl, model: d.model, apiToken: token, enabled: true, createdAt: new Date(), }; try { const reply = await callProvider( provider, "Du bist ein Verbindungstest.", 'Antworte mit dem einzelnen Wort "OK".', ); return res.json( TestProviderResponse.parse({ ok: true, message: `Verbindung erfolgreich. Antwort: ${reply.trim().slice(0, 80) || "(leer)"}`, }), ); } catch (err) { return res.json( TestProviderResponse.parse({ ok: false, message: err instanceof Error ? err.message : "Verbindung fehlgeschlagen.", }), ); } }); export default router;