export default defineEventHandler(async (event) => { console.log('[api/members/[id]/create-portal-account.post] ========================='); console.log('[api/members/[id]/create-portal-account.post] POST /api/members/:id/create-portal-account - Create portal account for member'); try { // Validate session and require board/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 board or admin privileges if (session.user.tier !== 'admin' && session.user.tier !== 'board') { throw createError({ statusCode: 403, statusMessage: 'Board or Admin privileges required' }); } console.log('[api/members/[id]/create-portal-account.post] Authorized user:', session.user.email, 'tier:', session.user.tier); // Get member ID from route parameter const memberId = getRouterParam(event, 'id'); if (!memberId) { throw createError({ statusCode: 400, statusMessage: 'Member ID is required' }); } console.log('[api/members/[id]/create-portal-account.post] Processing member ID:', memberId); // 1. Get member data const { getMemberById } = await import('~/server/utils/nocodb'); const member = await getMemberById(memberId); if (!member) { throw createError({ statusCode: 404, statusMessage: 'Member not found' }); } console.log('[api/members/[id]/create-portal-account.post] Found member:', member.email); // 2. Check if member already has portal account if (member.keycloak_id) { console.log('[api/members/[id]/create-portal-account.post] Member already has portal account:', member.keycloak_id); throw createError({ statusCode: 409, statusMessage: 'Member already has a portal account' }); } // 3. Validate member data if (!member.email || !member.first_name || !member.last_name) { throw createError({ statusCode: 400, statusMessage: 'Member must have email, first name, and last name to create portal account' }); } // 4. Check if user already exists in Keycloak (by email) const { createKeycloakAdminClient } = await import('~/server/utils/keycloak-admin'); const keycloakAdmin = createKeycloakAdminClient(); console.log('[api/members/[id]/create-portal-account.post] Checking for existing Keycloak user...'); const existingUsers = await keycloakAdmin.findUserByEmail(member.email); if (existingUsers.length > 0) { console.log('[api/members/[id]/create-portal-account.post] User already exists in Keycloak'); throw createError({ statusCode: 409, statusMessage: 'A user with this email already exists in the system' }); } // 5. Determine membership tier based on member data const membershipTier = determineMembershipTier(member); console.log('[api/members/[id]/create-portal-account.post] Determined membership tier:', membershipTier); // 6. Prepare membership data for Keycloak sync const membershipData = { membershipStatus: member.membership_status || 'Active', duesStatus: member.current_year_dues_paid === 'true' ? 'paid' as const : 'unpaid' as const, memberSince: member.member_since || new Date().getFullYear().toString(), nationality: member.nationality || '', phone: member.phone || '', address: member.address || '', registrationDate: member.registration_date || new Date().toISOString(), paymentDueDate: member.payment_due_date || '', membershipTier, nocodbMemberId: memberId }; // 7. Create Keycloak user with role-based registration console.log('[api/members/[id]/create-portal-account.post] Creating Keycloak user with role-based system...'); const keycloakId = await keycloakAdmin.createUserWithRoleRegistration({ email: member.email, firstName: member.first_name, lastName: member.last_name, membershipTier, membershipData }); console.log('[api/members/[id]/create-portal-account.post] Created Keycloak user with ID:', keycloakId); // 8. Update member record with keycloak_id console.log('[api/members/[id]/create-portal-account.post] Updating member record with keycloak_id...'); const { updateMember } = await import('~/server/utils/nocodb'); await updateMember(memberId, { keycloak_id: keycloakId }); // 9. Send welcome/verification email using our custom email system console.log('[api/members/[id]/create-portal-account.post] Sending welcome/verification email...'); try { const { getEmailService } = await import('~/server/utils/email'); const { generateEmailVerificationToken } = await import('~/server/utils/email-tokens'); const emailService = getEmailService(); const verificationToken = await generateEmailVerificationToken(keycloakId, member.email); const config = useRuntimeConfig(); const verificationLink = `${config.public.domain}/api/auth/verify-email?token=${verificationToken}`; await emailService.sendWelcomeEmail(member.email, { firstName: member.first_name, lastName: member.last_name, verificationLink, memberId: memberId }); console.log('[api/members/[id]/create-portal-account.post] Welcome email sent successfully'); } catch (emailError: any) { console.error('[api/members/[id]/create-portal-account.post] Failed to send welcome email:', emailError.message); // Don't fail the account creation if email fails - user can resend verification email later } console.log('[api/members/[id]/create-portal-account.post] ✅ Portal account creation successful'); return { success: true, message: 'Portal account created successfully. The member will receive an email to verify their account and set their password.', data: { keycloak_id: keycloakId, member_id: memberId, email: member.email, name: `${member.first_name} ${member.last_name}` } }; } catch (error: any) { console.error('[api/members/[id]/create-portal-account.post] ❌ Portal account creation 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 || 'Failed to create portal account' }); } }); /** * Determine membership tier based on member data * This function analyzes member information to assign appropriate portal roles */ function determineMembershipTier(member: any): 'user' | 'board' | 'admin' { // Check for explicit tier indicators in member data // This could be based on membership type, special flags, or other criteria // For now, default all members to 'user' tier // In the future, you might want to check specific fields like: // - member.membership_type // - member.is_board_member // - member.is_admin // - specific email domains for admins // - etc. // Example logic (uncomment and modify as needed): /* if (member.email && member.email.includes('@admin.monacousa.org')) { return 'admin'; } if (member.membership_type === 'Board' || member.is_board_member === 'true') { return 'board'; } */ // Default to user tier for all members return 'user'; }