← 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 });
  }
}