import { corsHeaders, handleCorsPreflightRequest } from '../_shared/cors.ts' import { requireManager } from '../_shared/admin-auth.ts' import { createServiceRoleClient } from '../_shared/quota.ts' import { verifyCurrentAdminActor } from '../_shared/admin-current-role.ts' import { AdminPublicError, adminErrorResponse, adminJsonResponse, parseIsoDate, parsePagination, parsePositiveInteger, requireUuid, sanitizeAuditRecord, validateQueryKeys, } from '../_shared/admin-contract.ts' const AUDIT_COLUMNS = 'id, admin_id, action, target_type, target_id, before_data, after_data, memo, created_at' function targetTypeFilter(value: string | null): string | undefined { if (value === null || value === '') return undefined if (value !== 'subscription' && value !== 'profile') { throw new AdminPublicError(400, 'invalid_target_type') } return value } async function readAuditLog(req: Request): Promise { const admin = await requireManager(req) const url = new URL(req.url) validateQueryKeys(url, ['id', 'page', 'limit', 'target_type', 'admin_id', 'target_id', 'from', 'to']) const serviceClient = createServiceRoleClient() await verifyCurrentAdminActor(serviceClient, admin) const logId = parsePositiveInteger(url.searchParams.get('id'), 'invalid_audit_id') if (logId !== undefined) { const { data: log, error: logError } = await serviceClient .from('audit_log') .select(AUDIT_COLUMNS) .eq('id', logId) .maybeSingle() if (logError) throw new AdminPublicError(500, 'admin_audit_read_failed') if (!log) throw new AdminPublicError(404, 'audit_log_not_found') if (typeof log.admin_id !== 'string') throw new AdminPublicError(502, 'invalid_admin_read_response') const { data: admin, error: adminError } = await serviceClient .from('profiles') .select('id, name, avatar_url') .eq('id', log.admin_id) .maybeSingle() if (adminError) throw new AdminPublicError(500, 'admin_audit_read_failed') return adminJsonResponse({ log: sanitizeAuditRecord(log), admin: admin ?? null }, corsHeaders) } const { page, limit, from, to } = parsePagination(url) const targetType = targetTypeFilter(url.searchParams.get('target_type')) const adminIdValue = url.searchParams.get('admin_id') const targetIdValue = url.searchParams.get('target_id') const adminId = adminIdValue ? requireUuid(adminIdValue, 'invalid_admin_id') : undefined const targetId = targetIdValue ? requireUuid(targetIdValue, 'invalid_target_id') : undefined const fromDate = parseIsoDate(url.searchParams.get('from'), 'invalid_from_date') const toDate = parseIsoDate(url.searchParams.get('to'), 'invalid_to_date') if (fromDate && toDate && fromDate > toDate) throw new AdminPublicError(400, 'invalid_date_range') let query = serviceClient.from('audit_log').select(AUDIT_COLUMNS, { count: 'exact' }) if (targetType) query = query.eq('target_type', targetType) if (adminId) query = query.eq('admin_id', adminId) if (targetId) query = query.eq('target_id', targetId) if (fromDate) query = query.gte('created_at', `${fromDate}T00:00:00.000Z`) if (toDate) query = query.lte('created_at', `${toDate}T23:59:59.999Z`) const { data, count, error } = await query .order('created_at', { ascending: false }) .range(from, to) if (error) throw new AdminPublicError(500, 'admin_audit_read_failed') const logs = (data ?? []) as Array> const adminIds = [...new Set(logs .map((log) => log.admin_id) .filter((value): value is string => typeof value === 'string'))] const adminNames = new Map() if (adminIds.length > 0) { const { data: admins, error: adminsError } = await serviceClient .from('profiles') .select('id, name') .in('id', adminIds) if (adminsError) throw new AdminPublicError(500, 'admin_audit_read_failed') for (const admin of (admins ?? []) as Array<{ id: string; name: string | null }>) { adminNames.set(admin.id, admin.name) } } return adminJsonResponse({ logs: logs.map((log) => ({ ...sanitizeAuditRecord(log), admin_name: typeof log.admin_id === 'string' ? adminNames.get(log.admin_id) ?? null : null, })), total: count ?? 0, page, limit, }, corsHeaders) } export async function handleAdminAuditLog(req: Request): Promise { const preflight = handleCorsPreflightRequest(req) if (preflight) return preflight try { if (req.method !== 'GET') { return adminJsonResponse({ error: 'method_not_allowed' }, corsHeaders, 405) } return await readAuditLog(req) } catch (error) { console.error('admin-audit-log request failed', error) return adminErrorResponse(error, corsHeaders) } } if (import.meta.main) Deno.serve(handleAdminAuditLog)