skillguard/artifacts/api-server/src/routes/providers.ts

145 lines
4.4 KiB
TypeScript
Raw Normal View History

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,
} 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<typeof aiProvidersTable.$inferInsert> = {};
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.",
}),
);
}
});
export default router;