/** * @ai-summary Admin user data access layer * @ai-context Provides parameterized SQL queries for admin user operations */ import { Pool } from 'pg'; import { AdminUser, AdminAuditLog } from '../domain/admin.types'; import { logger } from '../../../core/logging/logger'; export class AdminRepository { constructor(private pool: Pool) {} async getAdminById(id: string): Promise { const query = ` SELECT id, user_profile_id, email, role, created_at, created_by, revoked_at, updated_at FROM admin_users WHERE id = $1 LIMIT 1 `; try { const result = await this.pool.query(query, [id]); if (result.rows.length === 0) { return null; } return this.mapRowToAdminUser(result.rows[0]); } catch (error) { logger.error('Error fetching admin by id', { error, id }); throw error; } } async getAdminByUserProfileId(userProfileId: string): Promise { const query = ` SELECT id, user_profile_id, email, role, created_at, created_by, revoked_at, updated_at FROM admin_users WHERE user_profile_id = $1 LIMIT 1 `; try { const result = await this.pool.query(query, [userProfileId]); if (result.rows.length === 0) { return null; } return this.mapRowToAdminUser(result.rows[0]); } catch (error) { logger.error('Error fetching admin by user_profile_id', { error, userProfileId }); throw error; } } async getAdminByEmail(email: string): Promise { const query = ` SELECT id, user_profile_id, email, role, created_at, created_by, revoked_at, updated_at FROM admin_users WHERE LOWER(email) = LOWER($1) LIMIT 1 `; try { const result = await this.pool.query(query, [email]); if (result.rows.length === 0) { return null; } return this.mapRowToAdminUser(result.rows[0]); } catch (error) { logger.error('Error fetching admin by email', { error, email }); throw error; } } async getAllAdmins(): Promise { const query = ` SELECT id, user_profile_id, email, role, created_at, created_by, revoked_at, updated_at FROM admin_users ORDER BY created_at DESC `; try { const result = await this.pool.query(query); return result.rows.map(row => this.mapRowToAdminUser(row)); } catch (error) { logger.error('Error fetching all admins', { error }); throw error; } } async getActiveAdmins(): Promise { const query = ` SELECT id, user_profile_id, email, role, created_at, created_by, revoked_at, updated_at FROM admin_users WHERE revoked_at IS NULL ORDER BY created_at DESC `; try { const result = await this.pool.query(query); return result.rows.map(row => this.mapRowToAdminUser(row)); } catch (error) { logger.error('Error fetching active admins', { error }); throw error; } } async createAdmin(userProfileId: string, email: string, role: string, createdBy: string): Promise { const query = ` INSERT INTO admin_users (user_profile_id, email, role, created_by) VALUES ($1, $2, $3, $4) RETURNING id, user_profile_id, email, role, created_at, created_by, revoked_at, updated_at `; try { const result = await this.pool.query(query, [userProfileId, email, role, createdBy]); if (result.rows.length === 0) { throw new Error('Failed to create admin user'); } return this.mapRowToAdminUser(result.rows[0]); } catch (error) { logger.error('Error creating admin', { error, userProfileId, email }); throw error; } } async revokeAdmin(id: string): Promise { const query = ` UPDATE admin_users SET revoked_at = CURRENT_TIMESTAMP WHERE id = $1 RETURNING id, user_profile_id, email, role, created_at, created_by, revoked_at, updated_at `; try { const result = await this.pool.query(query, [id]); if (result.rows.length === 0) { throw new Error('Admin user not found'); } return this.mapRowToAdminUser(result.rows[0]); } catch (error) { logger.error('Error revoking admin', { error, id }); throw error; } } async reinstateAdmin(id: string): Promise { const query = ` UPDATE admin_users SET revoked_at = NULL WHERE id = $1 RETURNING id, user_profile_id, email, role, created_at, created_by, revoked_at, updated_at `; try { const result = await this.pool.query(query, [id]); if (result.rows.length === 0) { throw new Error('Admin user not found'); } return this.mapRowToAdminUser(result.rows[0]); } catch (error) { logger.error('Error reinstating admin', { error, id }); throw error; } } async logAuditAction( actorAdminId: string, action: string, targetAdminId?: string, resourceType?: string, resourceId?: string, context?: Record ): Promise { const query = ` INSERT INTO admin_audit_logs (actor_admin_id, target_admin_id, action, resource_type, resource_id, context) VALUES ($1, $2, $3, $4, $5, $6) RETURNING id, actor_admin_id, target_admin_id, action, resource_type, resource_id, context, created_at `; try { const result = await this.pool.query(query, [ actorAdminId, targetAdminId || null, action, resourceType || null, resourceId || null, context ? JSON.stringify(context) : null, ]); if (result.rows.length === 0) { throw new Error('Failed to create audit log'); } return this.mapRowToAuditLog(result.rows[0]); } catch (error) { logger.error('Error logging audit action', { error, actorAdminId, action }); throw error; } } async getAuditLogs(limit: number = 100, offset: number = 0): Promise<{ logs: AdminAuditLog[]; total: number }> { const countQuery = 'SELECT COUNT(*) as total FROM admin_audit_logs'; const query = ` SELECT id, actor_admin_id, target_admin_id, action, resource_type, resource_id, context, created_at FROM admin_audit_logs ORDER BY created_at DESC LIMIT $1 OFFSET $2 `; try { const [countResult, dataResult] = await Promise.all([ this.pool.query(countQuery), this.pool.query(query, [limit, offset]), ]); const total = parseInt(countResult.rows[0].total, 10); const logs = dataResult.rows.map(row => this.mapRowToAuditLog(row)); return { logs, total }; } catch (error) { logger.error('Error fetching audit logs', { error }); throw error; } } private mapRowToAdminUser(row: any): AdminUser { return { id: row.id, userProfileId: row.user_profile_id, email: row.email, role: row.role, createdAt: new Date(row.created_at), createdBy: row.created_by, revokedAt: row.revoked_at ? new Date(row.revoked_at) : null, updatedAt: new Date(row.updated_at), }; } private mapRowToAuditLog(row: any): AdminAuditLog { return { id: row.id, actorAdminId: row.actor_admin_id, targetAdminId: row.target_admin_id, action: row.action, resourceType: row.resource_type, resourceId: row.resource_id, // JSONB columns are automatically parsed by pg driver - no JSON.parse needed context: row.context || undefined, createdAt: new Date(row.created_at), }; } }