/** * @ai-summary Fastify route handlers for user profile API * @ai-context HTTP request/response handling with user authentication */ import { FastifyRequest, FastifyReply } from 'fastify'; import { UserProfileService } from '../domain/user-profile.service'; import { UserProfileRepository } from '../data/user-profile.repository'; import { AdminRepository } from '../../admin/data/admin.repository'; import { pool } from '../../../core/config/database'; import { logger } from '../../../core/logging/logger'; import { UpdateProfileInput, updateProfileSchema, RequestDeletionInput, requestDeletionSchema, } from './user-profile.validation'; export class UserProfileController { private userProfileService: UserProfileService; private userProfileRepository: UserProfileRepository; constructor() { this.userProfileRepository = new UserProfileRepository(pool); const adminRepository = new AdminRepository(pool); this.userProfileService = new UserProfileService(this.userProfileRepository); this.userProfileService.setAdminRepository(adminRepository); } /** * GET /api/user/profile - Get current user's profile */ async getProfile(request: FastifyRequest, reply: FastifyReply) { try { const userId = request.userContext?.userId; if (!userId) { return reply.code(401).send({ error: 'Unauthorized', message: 'User context missing', }); } // Get profile by UUID (auth plugin ensures profile exists during authentication) const profile = await this.userProfileRepository.getById(userId); if (!profile) { return reply.code(404).send({ error: 'Not Found', message: 'User profile not found', }); } return reply.code(200).send(profile); } catch (error: any) { logger.error('Error getting user profile', { error: error.message, userId: request.userContext?.userId, }); return reply.code(500).send({ error: 'Internal server error', message: 'Failed to retrieve user profile', }); } } /** * PUT /api/user/profile - Update user profile */ async updateProfile( request: FastifyRequest<{ Body: UpdateProfileInput }>, reply: FastifyReply ) { try { const userId = request.userContext?.userId; if (!userId) { return reply.code(401).send({ error: 'Unauthorized', message: 'User context missing', }); } // Validate request body const validation = updateProfileSchema.safeParse(request.body); if (!validation.success) { return reply.code(400).send({ error: 'Bad Request', message: 'Invalid request body', details: validation.error.errors, }); } const updates = validation.data; // Update profile by UUID const profile = await this.userProfileService.updateProfile( userId, updates ); return reply.code(200).send(profile); } catch (error: any) { logger.error('Error updating user profile', { error: error.message, userId: request.userContext?.userId, }); if (error.message.includes('not found')) { return reply.code(404).send({ error: 'Not Found', message: 'User profile not found', }); } if (error.message.includes('At least one field')) { return reply.code(400).send({ error: 'Bad Request', message: error.message, }); } return reply.code(500).send({ error: 'Internal server error', message: 'Failed to update user profile', }); } } /** * POST /api/user/delete - Request account deletion */ async requestDeletion( request: FastifyRequest<{ Body: RequestDeletionInput }>, reply: FastifyReply ) { try { const userId = request.userContext?.userId; if (!userId) { return reply.code(401).send({ error: 'Unauthorized', message: 'User context missing', }); } // Validate request body const validation = requestDeletionSchema.safeParse(request.body); if (!validation.success) { return reply.code(400).send({ error: 'Bad Request', message: 'Invalid request body', details: validation.error.errors, }); } const { confirmationText } = validation.data; // Request deletion by UUID const profile = await this.userProfileService.requestDeletion( userId, confirmationText ); const deletionStatus = this.userProfileService.getDeletionStatus(profile); return reply.code(200).send({ message: 'Account deletion requested successfully', deletionStatus, }); } catch (error: any) { logger.error('Error requesting account deletion', { error: error.message, userId: request.userContext?.userId, }); if (error.message.includes('Invalid confirmation')) { return reply.code(400).send({ error: 'Bad Request', message: 'Confirmation text must be exactly "DELETE"', }); } if (error.message.includes('already requested')) { return reply.code(400).send({ error: 'Bad Request', message: 'Account deletion already requested', }); } if (error.message.includes('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 request account deletion', }); } } /** * POST /api/user/cancel-deletion - Cancel account deletion */ async cancelDeletion(request: FastifyRequest, reply: FastifyReply) { try { const userId = request.userContext?.userId; if (!userId) { return reply.code(401).send({ error: 'Unauthorized', message: 'User context missing', }); } // Cancel deletion by UUID const profile = await this.userProfileService.cancelDeletion(userId); return reply.code(200).send({ message: 'Account deletion canceled successfully', profile, }); } catch (error: any) { logger.error('Error canceling account deletion', { error: error.message, userId: request.userContext?.userId, }); if (error.message.includes('no deletion request')) { return reply.code(400).send({ error: 'Bad Request', message: 'No deletion request pending', }); } if (error.message.includes('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 cancel account deletion', }); } } /** * GET /api/user/deletion-status - Get deletion status */ async getDeletionStatus(request: FastifyRequest, reply: FastifyReply) { try { const userId = request.userContext?.userId; if (!userId) { return reply.code(401).send({ error: 'Unauthorized', message: 'User context missing', }); } // Get profile by UUID (auth plugin ensures profile exists) const profile = await this.userProfileRepository.getById(userId); if (!profile) { return reply.code(404).send({ error: 'Not Found', message: 'User profile not found', }); } const deletionStatus = this.userProfileService.getDeletionStatus(profile); return reply.code(200).send(deletionStatus); } catch (error: any) { logger.error('Error getting deletion status', { error: error.message, userId: request.userContext?.userId, }); return reply.code(500).send({ error: 'Internal server error', message: 'Failed to get deletion status', }); } } }