← back to Norma
app/api/settings/tier-credentials/route.ts
105 lines
import { NextRequest, NextResponse } from 'next/server';
import { query } from '@/lib/db';
import { hashPassword, ALL_ROLES, type UserRole } from '@/lib/auth';
import { requireRole } from '@/lib/require-role';
/**
* GET /api/settings/tier-credentials
* List all tier_credentials with org name join.
*/
export async function GET(request: NextRequest) {
const result = requireRole(request, 'admin');
if (result instanceof NextResponse) return result;
try {
const { rows } = await query(
`SELECT tc.id, tc.username, tc.role, tc.org_id, tc.display_name,
tc.created_at, tc.updated_at,
na.org_name
FROM tier_credentials tc
LEFT JOIN nonprofit_accounts na ON tc.org_id = na.id
ORDER BY tc.created_at ASC`
);
return NextResponse.json({ credentials: rows });
} catch (err) {
console.error('[tier-credentials] GET error:', (err as Error).message);
return NextResponse.json({ error: 'Failed to fetch credentials' }, { status: 500 });
}
}
/**
* POST /api/settings/tier-credentials
* Create a new tier credential.
* Body: { username, password, role, org_id?, display_name? }
*/
export async function POST(request: NextRequest) {
const session = requireRole(request, 'admin');
if (session instanceof NextResponse) return session;
try {
const body = await request.json();
const {
username,
password,
role,
org_id,
display_name,
} = body;
if (!username || !password || !role) {
return NextResponse.json(
{ error: 'username, password, and role are required' },
{ status: 400 }
);
}
if (!ALL_ROLES.includes(role as UserRole)) {
return NextResponse.json(
{ error: `role must be one of ${ALL_ROLES.join(', ')}` },
{ status: 400 }
);
}
if (username.includes(':')) {
return NextResponse.json(
{ error: 'Username cannot contain colons' },
{ status: 400 }
);
}
// Check uniqueness
const existing = await query(
'SELECT id FROM tier_credentials WHERE username = $1',
[username]
);
if (existing.rows.length > 0) {
return NextResponse.json(
{ error: 'Username already exists' },
{ status: 409 }
);
}
const hashed = await hashPassword(password);
const { rows } = await query(
`INSERT INTO tier_credentials
(username, password_hash, role, org_id, display_name)
VALUES ($1, $2, $3, $4, $5)
RETURNING id, username, role, org_id, display_name,
created_at, updated_at`,
[
username,
hashed,
role,
org_id || null,
display_name || null,
]
);
return NextResponse.json({ credential: rows[0] }, { status: 201 });
} catch (err) {
console.error('[tier-credentials] POST error:', (err as Error).message);
return NextResponse.json({ error: 'Failed to create credential' }, { status: 500 });
}
}