← back to Norma
app/api/registry/route.ts
75 lines
// GET /api/registry — returns the integration registry + set/missing status
// for each env var. Values themselves NEVER leave the server.
//
// Admin-gated: requires the norma-auth cookie with admin role.
import { NextResponse } from 'next/server';
import { cookies } from 'next/headers';
import { INTEGRATIONS, CATEGORY_ORDER, statusFor } from '@/lib/api-registry';
export const runtime = 'nodejs';
export const dynamic = 'force-dynamic';
interface SessionPayload {
role?: string;
user?: string;
}
async function readSession(): Promise<SessionPayload | null> {
const jar = await cookies();
const raw = jar.get('norma-auth')?.value;
if (!raw) return null;
try {
// The cookie is JWT-style or signed-JSON depending on lib/auth.ts; we
// only need the payload to check the role. Tolerant decode.
const parts = raw.split('.');
const body = parts.length >= 2 ? parts[1] : parts[0];
const json = Buffer.from(body, 'base64url').toString('utf8');
return JSON.parse(json) as SessionPayload;
} catch {
return null;
}
}
export async function GET() {
const session = await readSession();
if (!session || session.role !== 'admin') {
return NextResponse.json({ error: 'admin only' }, { status: 403 });
}
const entries = INTEGRATIONS.map(e => ({
id: e.id,
name: e.name,
category: e.category,
purpose: e.purpose,
required: e.required,
envVars: e.envVars,
status: statusFor(e),
signupUrl: e.signupUrl,
docsUrl: e.docsUrl,
scopes: e.scopes,
pricing: e.pricing,
getSteps: e.getSteps,
// Which env vars are set (boolean only — no values).
envVarStatus: Object.fromEntries(
e.envVars.map(v => [v, !!process.env[v] && String(process.env[v]).length > 0]),
),
}));
const setCount = entries.filter(e => e.status === 'set').length;
const partialCount = entries.filter(e => e.status === 'partial').length;
const missingCount = entries.filter(e => e.status === 'missing').length;
return NextResponse.json({
categories: CATEGORY_ORDER,
entries,
summary: {
total: entries.length,
set: setCount,
partial: partialCount,
missing: missingCount,
requiredMissing: entries.filter(e => e.required && e.status !== 'set').length,
},
});
}