2025-08-08 19:40:13 +02:00
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 ) ;
2025-08-08 22:51:14 +02:00
// 8. Update member record with keycloak_id
2025-08-08 19:40:13 +02:00
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 } ) ;
2025-08-08 22:51:14 +02:00
// 9. Send welcome/verification email using our custom email system
2025-08-09 16:13:52 +02:00
console . log ( '[api/members/[id]/create-portal-account.post] Attempting to send welcome/verification email...' ) ;
let emailSent = false ;
2025-08-09 16:55:59 +02:00
let emailError : string | null = null ;
2025-08-08 22:51:14 +02:00
try {
const { getEmailService } = await import ( '~/server/utils/email' ) ;
const { generateEmailVerificationToken } = await import ( '~/server/utils/email-tokens' ) ;
2025-08-09 16:13:52 +02:00
const emailService = await getEmailService ( ) ;
2025-08-08 22:51:14 +02:00
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
} ) ;
2025-08-09 16:13:52 +02:00
emailSent = true ;
2025-08-08 22:51:14 +02:00
console . log ( '[api/members/[id]/create-portal-account.post] Welcome email sent successfully' ) ;
2025-08-09 16:55:59 +02:00
} catch ( error : any ) {
emailError = error . message || 'Unknown email error' ;
console . error ( '[api/members/[id]/create-portal-account.post] Failed to send welcome email:' , emailError ) ;
// Log the full error for debugging
console . error ( '[api/members/[id]/create-portal-account.post] Full email error:' , error ) ;
2025-08-08 22:51:14 +02:00
// Don't fail the account creation if email fails - user can resend verification email later
}
2025-08-08 19:40:13 +02:00
console . log ( '[api/members/[id]/create-portal-account.post] ✅ Portal account creation successful' ) ;
return {
success : true ,
2025-08-09 16:13:52 +02:00
message : emailSent
? 'Portal account created successfully. The member will receive an email to verify their account and set their password.'
2025-08-09 16:55:59 +02:00
: ` Portal account created successfully. Email sending failed: ${ emailError || 'Unknown error' } . The member can use "Forgot Password" to access their account. ` ,
2025-08-08 19:40:13 +02:00
data : {
keycloak_id : keycloakId ,
member_id : memberId ,
email : member.email ,
2025-08-09 16:13:52 +02:00
name : ` ${ member . first_name } ${ member . last_name } ` ,
2025-08-09 16:55:59 +02:00
email_sent : emailSent ,
email_error : emailError
2025-08-08 19:40:13 +02:00
}
} ;
} 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' ;
}