import type { Member } from '~/utils/types'; export default defineEventHandler(async (event) => { console.log('[api/admin/cleanup-accounts.post] ========================='); console.log('[api/admin/cleanup-accounts.post] POST /api/admin/cleanup-accounts - Account cleanup for expired members'); try { // Validate session and require admin privileges const sessionManager = createSessionManager(); const cookieHeader = getCookie(event, 'monacousa-session') ? getHeader(event, 'cookie') : undefined; const session = sessionManager.getSession(cookieHeader); if (!session?.user) { throw createError({ statusCode: 401, statusMessage: 'Authentication required' }); } // Require admin privileges for account cleanup if (session.user.tier !== 'admin') { throw createError({ statusCode: 403, statusMessage: 'Admin privileges required' }); } console.log('[api/admin/cleanup-accounts.post] Authorized admin:', session.user.email); // Get cleanup options from request body (optional) const body = await readBody(event).catch(() => ({})); const dryRun = body?.dryRun === true; const monthsOverdue = body?.monthsOverdue || 3; console.log('[api/admin/cleanup-accounts.post] Cleanup options:', { dryRun, monthsOverdue }); // Calculate cutoff date (default: 3 months ago) const cutoffDate = new Date(); cutoffDate.setMonth(cutoffDate.getMonth() - monthsOverdue); console.log('[api/admin/cleanup-accounts.post] Cutoff date:', cutoffDate.toISOString()); // Find members registered before cutoff date with unpaid dues const { getMembers } = await import('~/server/utils/nocodb'); const membersResult = await getMembers(); const allMembers = membersResult.list || []; const expiredMembers = allMembers.filter((member: Member) => { // Must have a registration date if (!member.registration_date) return false; // Must be registered before cutoff date const registrationDate = new Date(member.registration_date); if (registrationDate >= cutoffDate) return false; // Must have unpaid dues if (member.current_year_dues_paid === 'true') return false; // Must have a Keycloak ID (portal account) if (!member.keycloak_id) return false; return true; }); console.log('[api/admin/cleanup-accounts.post] Found', expiredMembers.length, 'expired members for cleanup'); const deletedAccounts = []; const failedDeletions = []; if (!dryRun && expiredMembers.length > 0) { const { createKeycloakAdminClient } = await import('~/server/utils/keycloak-admin'); const { deleteMember } = await import('~/server/utils/nocodb'); const keycloakAdmin = createKeycloakAdminClient(); for (const member of expiredMembers) { try { console.log('[api/admin/cleanup-accounts.post] Processing cleanup for:', member.email); // Delete from Keycloak first if (member.keycloak_id) { try { await keycloakAdmin.deleteUser(member.keycloak_id); console.log('[api/admin/cleanup-accounts.post] Deleted Keycloak user:', member.keycloak_id); } catch (keycloakError: any) { console.warn('[api/admin/cleanup-accounts.post] Failed to delete Keycloak user:', keycloakError.message); // Continue with member deletion even if Keycloak deletion fails } } // Delete member record await deleteMember(member.Id); console.log('[api/admin/cleanup-accounts.post] Deleted member record:', member.Id); deletedAccounts.push({ id: member.Id, email: member.email, name: `${member.first_name} ${member.last_name}`, registrationDate: member.registration_date, keycloakId: member.keycloak_id }); } catch (error: any) { console.error('[api/admin/cleanup-accounts.post] Failed to delete account for', member.email, ':', error); failedDeletions.push({ id: member.Id, email: member.email, name: `${member.first_name} ${member.last_name}`, error: error.message }); } } } const result = { success: true, dryRun, monthsOverdue, cutoffDate: cutoffDate.toISOString(), totalExpiredMembers: expiredMembers.length, deletedCount: deletedAccounts.length, failedCount: failedDeletions.length, message: dryRun ? `Found ${expiredMembers.length} expired accounts that would be deleted (dry run)` : `Cleaned up ${deletedAccounts.length} expired accounts${failedDeletions.length > 0 ? ` (${failedDeletions.length} failed)` : ''}`, data: { expiredMembers: expiredMembers.map(m => ({ id: m.Id, email: m.email, name: `${m.first_name} ${m.last_name}`, registrationDate: m.registration_date, daysSinceRegistration: Math.floor((Date.now() - new Date(m.registration_date || '').getTime()) / (1000 * 60 * 60 * 24)), hasKeycloakAccount: !!m.keycloak_id })), deleted: deletedAccounts, failed: failedDeletions } }; console.log('[api/admin/cleanup-accounts.post] ✅ Account cleanup completed'); console.log('[api/admin/cleanup-accounts.post] Summary:', { found: expiredMembers.length, deleted: deletedAccounts.length, failed: failedDeletions.length, dryRun }); return result; } catch (error: any) { console.error('[api/admin/cleanup-accounts.post] ❌ Account cleanup failed:', error); // If it's already an HTTP error, re-throw it if (error.statusCode) { throw error; } // Otherwise, wrap it in a generic error throw createError({ statusCode: 500, statusMessage: error.message || 'Account cleanup failed' }); } });