import { Router } from "express"; import { requireAuth } from "../middleware/auth"; import { getCourtlistenerCaseOpinions } from "../lib/courtlistener"; import { createServerSupabase } from "../lib/supabase"; import { getUserModelSettings } from "../lib/userSettings"; export const caseLawRouter = Router(); caseLawRouter.use(requireAuth); const isDev = process.env.NODE_ENV !== "production"; const devLog = (...args: Parameters) => { if (isDev) console.log(...args); }; const sidepanelOpinionFetches = new Map>(); function cleanClusterId(value: unknown): number | null { const numeric = typeof value === "number" ? value : typeof value === "string" ? Number.parseInt(value, 10) : NaN; return Number.isFinite(numeric) && numeric > 0 ? Math.floor(numeric) : null; } caseLawRouter.post("/case-opinions", async (req, res) => { const body = req.body && typeof req.body === "object" && !Array.isArray(req.body) ? (req.body as Record) : {}; const clusterId = cleanClusterId(body.clusterId ?? body.cluster_id); if (!clusterId) { return res.status(400).json({ detail: "cluster_id is required", }); } try { const userId = String(res.locals.userId ?? ""); const settings = await getUserModelSettings(userId); devLog("[case-law/case-opinions] loading sidepanel opinions", { clusterId, }); const db = createServerSupabase(); const fetchKey = `${userId}:${clusterId}`; let fetchPromise = sidepanelOpinionFetches.get(fetchKey); if (fetchPromise) { devLog("[case-law/case-opinions] joining in-flight fetch", { clusterId, }); } else { fetchPromise = getCourtlistenerCaseOpinions({ clusterId, db, includeFullText: true, maxChars: 50000, apiToken: settings.api_keys.courtlistener, }).finally(() => { sidepanelOpinionFetches.delete(fetchKey); }); sidepanelOpinionFetches.set(fetchKey, fetchPromise); } const fetched = await fetchPromise; const fetchedRecord = fetched && typeof fetched === "object" && !Array.isArray(fetched) ? (fetched as Record) : {}; const opinions = Array.isArray(fetchedRecord.opinions) ? fetchedRecord.opinions : []; devLog("[case-law/case-opinions] returning sidepanel opinions", { clusterId, opinionCount: opinions.length, }); return res.json({ opinions }); } catch (err) { const message = err instanceof Error ? err.message : "Failed to fetch case opinions"; return res.status(502).json({ detail: message }); } });