/** * @ai-summary Fastify route handlers for onboarding API * @ai-context HTTP request/response handling for onboarding workflow */ import { FastifyRequest, FastifyReply } from 'fastify'; import { OnboardingService } from '../domain/onboarding.service'; import { UserPreferencesRepository } from '../../../core/user-preferences/data/user-preferences.repository'; import { UserProfileRepository } from '../../user-profile/data/user-profile.repository'; import { pool } from '../../../core/config/database'; import { logger } from '../../../core/logging/logger'; import { SavePreferencesInput } from './onboarding.validation'; interface AuthenticatedRequest extends FastifyRequest { user: { sub: string; [key: string]: unknown; }; } export class OnboardingController { private onboardingService: OnboardingService; constructor() { const userPreferencesRepo = new UserPreferencesRepository(pool); const userProfileRepo = new UserProfileRepository(pool); this.onboardingService = new OnboardingService(userPreferencesRepo, userProfileRepo); } /** * POST /api/onboarding/preferences * Save user preferences during onboarding */ async savePreferences( request: FastifyRequest<{ Body: SavePreferencesInput }>, reply: FastifyReply ) { try { const auth0Sub = (request as AuthenticatedRequest).user.sub; const preferences = await this.onboardingService.savePreferences( auth0Sub, request.body ); return reply.code(200).send({ success: true, preferences, }); } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; logger.error('Error in savePreferences controller', { error, userId: request.userContext?.userId, }); if (errorMessage === 'User profile not found') { return reply.code(404).send({ error: 'Not Found', message: 'User profile not found', }); } return reply.code(500).send({ error: 'Internal server error', message: 'Failed to save preferences', }); } } /** * POST /api/onboarding/complete * Mark onboarding as complete */ async completeOnboarding(request: FastifyRequest, reply: FastifyReply) { try { const auth0Sub = (request as AuthenticatedRequest).user.sub; const completedAt = await this.onboardingService.completeOnboarding(auth0Sub); return reply.code(200).send({ success: true, completedAt: completedAt.toISOString(), }); } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; logger.error('Error in completeOnboarding controller', { error, userId: request.userContext?.userId, }); if (errorMessage === 'User profile not found') { return reply.code(404).send({ error: 'Not Found', message: 'User profile not found', }); } return reply.code(500).send({ error: 'Internal server error', message: 'Failed to complete onboarding', }); } } /** * GET /api/onboarding/status * Get current onboarding status */ async getStatus(request: FastifyRequest, reply: FastifyReply) { try { const auth0Sub = (request as AuthenticatedRequest).user.sub; const status = await this.onboardingService.getOnboardingStatus(auth0Sub); return reply.code(200).send({ preferencesSet: status.preferencesSet, onboardingCompleted: status.onboardingCompleted, onboardingCompletedAt: status.onboardingCompletedAt ? status.onboardingCompletedAt.toISOString() : null, }); } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; logger.error('Error in getStatus controller', { error, userId: request.userContext?.userId, }); if (errorMessage === 'User profile not found') { return reply.code(404).send({ error: 'Not Found', message: 'User profile not found', }); } return reply.code(500).send({ error: 'Internal server error', message: 'Failed to get onboarding status', }); } } }