spb/note-spboucher-ai
Public
1import { z } from "zod";2import { handler, parseBody } from "@/lib/api";3import { db, schema } from "@/db";4import { and, eq } from "drizzle-orm";5import { audit } from "@/lib/audit";6const KEY = "compare.votes";7async function votes(): Promise<Record<string, { model: string; at: string }>> { const [row] = await db.select().from(schema.settings).where(eq(schema.settings.key, KEY)); return (row?.value as any) ?? {}; }8/** GET → { votes: { [comparisonId]: { model, at } }, tally: { [model]: wins } } */9export const GET = handler(async () => {10 const v = await votes(); const tally: Record<string, number> = {}; for (const x of Object.values(v)) tally[x.model] = (tally[x.model] ?? 0) + 1;11 return Response.json({ votes: v, tally });12});13/** POST { comparisonId, model | null } → stores "best answer" vote for a saved comparison in settings `compare.votes`. */14export const POST = handler(async ({ req, ws, user }) => {15 const b = await parseBody(req, z.object({ comparisonId: z.string().uuid(), model: z.string().max(120).nullable() }));16 const [c] = await db.select({ id: schema.aiComparisons.id, models: schema.aiComparisons.models }).from(schema.aiComparisons).where(and(eq(schema.aiComparisons.id, b.comparisonId), eq(schema.aiComparisons.workspaceId, ws.id)));17 if (!c) return Response.json({ error: "Comparison not found" }, { status: 404 });18 if (b.model && !c.models.includes(b.model)) return Response.json({ error: "Model not part of this comparison" }, { status: 400 });19 const v = await votes(); if (b.model) v[c.id] = { model: b.model, at: new Date().toISOString() }; else delete v[c.id];20 await db.insert(schema.settings).values({ key: KEY, value: v }).onConflictDoUpdate({ target: schema.settings.key, set: { value: v, updatedAt: new Date() } });21 await audit(user.id, "ai.compare_vote", { type: "comparison", id: c.id }, { model: b.model });22 return Response.json({ ok: true, votes: v });23});24