215 lines
8.1 KiB
TypeScript
215 lines
8.1 KiB
TypeScript
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] Attempting to send welcome/verification email...');
|
|
let emailSent = false;
|
|
let emailError: string | null = null;
|
|
|
|
try {
|
|
const { getEmailService } = await import('~/server/utils/email');
|
|
const { generateEmailVerificationToken } = await import('~/server/utils/email-tokens');
|
|
|
|
const emailService = await 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
|
|
});
|
|
|
|
emailSent = true;
|
|
console.log('[api/members/[id]/create-portal-account.post] Welcome email sent successfully');
|
|
} 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);
|
|
|
|
// 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: emailSent
|
|
? 'Portal account created successfully. The member will receive an email to verify their account and set their password.'
|
|
: `Portal account created successfully. Email sending failed: ${emailError || 'Unknown error'}. The member can use "Forgot Password" to access their account.`,
|
|
data: {
|
|
keycloak_id: keycloakId,
|
|
member_id: memberId,
|
|
email: member.email,
|
|
name: `${member.first_name} ${member.last_name}`,
|
|
email_sent: emailSent,
|
|
email_error: emailError
|
|
}
|
|
};
|
|
|
|
} 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';
|
|
}
|