← back to Norma
app/api/admin/permissions/route.ts
83 lines
/**
* /api/admin/permissions
* GET — list all feature × role rows
* PUT — replace allowed_roles for one or many feature_keys
*
* Admin-only. Edits invalidate the in-process permission cache.
*/
import { NextRequest, NextResponse } from 'next/server';
import { query } from '@/lib/db';
import { requireRole } from '@/lib/require-role';
import { ALL_ROLES, type UserRole } from '@/lib/auth';
import { clearPermissionsCache } from '@/lib/permissions';
const ASSIGNABLE_ROLES: UserRole[] = ALL_ROLES.filter((r) => r !== 'admin');
export async function GET(request: NextRequest) {
const session = requireRole(request, 'admin');
if (session instanceof NextResponse) return session;
try {
const { rows } = await query(
`SELECT feature_key, label, category, allowed_roles, description, updated_at
FROM role_permissions
ORDER BY category, feature_key`,
);
return NextResponse.json({ permissions: rows, assignableRoles: ASSIGNABLE_ROLES });
} catch (err) {
console.error('[admin/permissions] GET error:', (err as Error).message);
return NextResponse.json({ error: 'Failed to load permissions' }, { status: 500 });
}
}
interface UpdateBody {
updates: Array<{ feature_key: string; allowed_roles: string[] }>;
}
export async function PUT(request: NextRequest) {
const session = requireRole(request, 'admin');
if (session instanceof NextResponse) return session;
try {
const body = (await request.json()) as UpdateBody;
const updates = Array.isArray(body?.updates) ? body.updates : [];
if (updates.length === 0) {
return NextResponse.json({ error: 'No updates supplied' }, { status: 400 });
}
if (updates.length > 200) {
return NextResponse.json({ error: 'Too many updates in one call' }, { status: 400 });
}
// Look up admin's id for updated_by audit
const me = await query<{ id: string }>(
'SELECT id FROM tier_credentials WHERE username = $1',
[session.username],
);
const updatedBy = me.rows[0]?.id || null;
let changed = 0;
for (const u of updates) {
if (typeof u.feature_key !== 'string' || !Array.isArray(u.allowed_roles)) continue;
// Filter out 'admin' (never gated) and any unknown roles
const clean = u.allowed_roles.filter(
(r): r is UserRole => ASSIGNABLE_ROLES.includes(r as UserRole),
);
const res = await query(
`UPDATE role_permissions
SET allowed_roles = $1, updated_by = $2, updated_at = NOW()
WHERE feature_key = $3`,
[clean, updatedBy, u.feature_key],
);
changed += res.rowCount || 0;
}
clearPermissionsCache();
return NextResponse.json({ success: true, changed });
} catch (err) {
console.error('[admin/permissions] PUT error:', (err as Error).message);
return NextResponse.json({ error: 'Failed to update permissions' }, { status: 500 });
}
}