monacousa-portal/server/api/admin/cleanup-accounts.post.ts

164 lines
6.0 KiB
TypeScript

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'
});
}
});