import { type NextRequest } from 'next/server'; import { requireAdminApi } from '@/lib/auth/admin-guard'; import { getSupabaseAdmin } from '@/lib/supabase/admin'; import { withAdminRetry } from '@/lib/admin/retry'; import { logAdminAction } from '@/lib/auth/audit'; import { isUuid } from '@/lib/admin/validators'; import { jsonNoStore } from '@/lib/admin/response'; export const runtime = 'nodejs'; export const dynamic = 'force-dynamic'; export async function POST( req: NextRequest, { params }: { params: { id: string } }, ) { const auth = await requireAdminApi(); if (!auth.ok) return auth.response; const { id } = params; if (!isUuid(id)) { return jsonNoStore({ error: 'invalid user id' }, { status: 400 }); } if (id === auth.user.id) { return jsonNoStore( { error: 'you cannot change your own role' }, { status: 400 }, ); } let body: { role?: unknown }; try { body = (await req.json()) as { role?: unknown }; } catch { return jsonNoStore({ error: 'invalid json' }, { status: 400 }); } if (body.role !== 'admin' && body.role !== 'user') { return jsonNoStore( { error: "role must be 'admin' or 'user'" }, { status: 400 }, ); } const role = body.role; const admin = getSupabaseAdmin(); // Merge with existing app_metadata so we don't clobber other keys. Retry the // read on transient empty-body responses so a burst-load flake isn't // misreported as a 404; a genuine not-found still falls through below. const { data: existing, error: getErr } = await withAdminRetry(() => admin.auth.admin.getUserById(id), ); if (getErr || !existing.user) { return jsonNoStore({ error: 'user not found' }, { status: 404 }); } const merged = { ...(existing.user.app_metadata ?? {}), role }; // `updateUserById` is idempotent here (same app_metadata), so retrying a // transient empty-body write is safe. const { error } = await withAdminRetry(() => admin.auth.admin.updateUserById(id, { app_metadata: merged, }), ); if (error) { console.error('admin user.role failed', error); return jsonNoStore({ error: 'internal error' }, { status: 500 }); } await logAdminAction(auth.user, { action: 'user.role', target_type: 'user', target_id: id, details: { role }, }); return jsonNoStore({ ok: true, role }); }