| import { Router, Response } from 'express'; |
| import { supabase } from '../../db/client'; |
| import { logger } from '../../utils/logger'; |
| import { authenticateUser, AuthenticatedRequest } from '../middleware/auth'; |
|
|
| const router = Router(); |
|
|
| const EMAIL_PATTERN = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; |
| const ALLOWED_ROLES = new Set(['user', 'admin']); |
|
|
| async function isAdmin(userId: string): Promise<boolean> { |
| const { data, error } = await supabase |
| .from('user_profiles') |
| .select('role') |
| .eq('id', userId) |
| .single(); |
|
|
| if (error) { |
| logger.warn('Failed to verify admin for user management', { |
| userId, |
| error: error.message, |
| }); |
| return false; |
| } |
|
|
| return data?.role === 'admin'; |
| } |
|
|
| router.post( |
| '/api/admin/users', |
| authenticateUser, |
| async (req, res: Response): Promise<void> => { |
| const authReq = req as AuthenticatedRequest; |
|
|
| try { |
| if (!(await isAdmin(authReq.userId))) { |
| res.status(403).json({ error: 'Admin access required' }); |
| return; |
| } |
|
|
| const email = String(authReq.body.email || '').trim().toLowerCase(); |
| const password = String(authReq.body.password || ''); |
| const displayName = String(authReq.body.displayName || '').trim(); |
| const role = String(authReq.body.role || 'user').trim().toLowerCase(); |
| const ticketBalance = Number(authReq.body.ticketBalance ?? 0); |
|
|
| if (!EMAIL_PATTERN.test(email)) { |
| res.status(400).json({ error: 'A valid email address is required' }); |
| return; |
| } |
| if (password.length < 8 || password.length > 128) { |
| res.status(400).json({ error: 'Password must contain 8 to 128 characters' }); |
| return; |
| } |
| if (displayName.length > 80) { |
| res.status(400).json({ error: 'Display name must not exceed 80 characters' }); |
| return; |
| } |
| if (!ALLOWED_ROLES.has(role)) { |
| res.status(400).json({ error: 'Role must be user or admin' }); |
| return; |
| } |
| if (!Number.isInteger(ticketBalance) || ticketBalance < 0 || ticketBalance > 1_000_000) { |
| res.status(400).json({ error: 'Ticket balance must be an integer from 0 to 1,000,000' }); |
| return; |
| } |
|
|
| const { data: created, error: createError } = await supabase.auth.admin.createUser({ |
| email, |
| password, |
| email_confirm: true, |
| app_metadata: { |
| dashboard_provisioned: true, |
| }, |
| user_metadata: { |
| display_name: displayName || email.split('@')[0], |
| }, |
| }); |
|
|
| if (createError || !created.user) { |
| const message = createError?.message || 'Supabase did not return the created user'; |
| const status = /already|registered|exists/i.test(message) ? 409 : 400; |
| res.status(status).json({ error: message }); |
| return; |
| } |
|
|
| const { data: profile, error: profileError } = await supabase |
| .from('user_profiles') |
| .update({ |
| display_name: displayName || email.split('@')[0], |
| role, |
| ticket_balance: ticketBalance, |
| updated_at: new Date().toISOString(), |
| }) |
| .eq('id', created.user.id) |
| .select('id, email, display_name, role, ticket_balance, created_at') |
| .single(); |
|
|
| if (profileError || !profile) { |
| await supabase.auth.admin.deleteUser(created.user.id).catch(() => undefined); |
| throw profileError || new Error('User profile was not created'); |
| } |
|
|
| logger.info('Dashboard account created by admin', { |
| adminUserId: authReq.userId, |
| createdUserId: created.user.id, |
| role, |
| ticketBalance, |
| }); |
|
|
| res.status(201).json({ user: profile }); |
| } catch (error: unknown) { |
| const message = error instanceof Error ? error.message : String(error); |
| logger.error('Admin user creation failed', { |
| adminUserId: authReq.userId, |
| error: message, |
| }); |
| res.status(500).json({ error: 'Failed to create dashboard account' }); |
| } |
| }, |
| ); |
|
|
| export default router; |
|
|