164 lines
6.0 KiB
TypeScript
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'
|
|
});
|
|
}
|
|
});
|