import { Router } from 'express'; import { authRequired } from '../../Application/Services/AuthMiddleware'; import { container } from '../../Application/Services/DIContainer'; import { ErrorResponseService } from '../../Application/Services/ErrorResponseService'; import { ValidationMiddleware } from '../../Application/Services/ValidationMiddleware'; import { GeneralSearchService } from '../../Application/Search/Generalsearch'; import { logRequest, logError, logAuth, logWarning } from '../../Application/Services/Logger'; const userRouter = Router(); // Create search service that isn't in the container yet const searchService = new GeneralSearchService(container.userRepository, container.organizationRepository, container.deckRepository); // Login endpoint userRouter.post('/login', ValidationMiddleware.combine([ ValidationMiddleware.validateRequiredFields(['username', 'password']), ValidationMiddleware.validateStringLength({ username: { min: 3, max: 50 }, password: { min: 6, max: 100 } }) ]), async (req, res) => { try { logRequest('Login endpoint accessed', req, res, { username: req.body.username }); const { username, password } = req.body; const result = await container.loginCommandHandler.execute({ username, password }, res); if (result) { logAuth('User login successful', undefined, { username: result.user.username }, req, res); res.json(result); } else { throw new Error(`Login failed: ${result}`); } } catch (error) { logError('Login endpoint error', error as Error, req, res); if (error instanceof Error) { if (error.message.includes('Invalid username')) { return ErrorResponseService.sendUnauthorized(res, 'Invalid username or password'); } if (error.message.includes('Invalid password')) { return ErrorResponseService.sendUnauthorized(res, 'Invalid username or password'); } if (error.message.includes('not verified')) { return ErrorResponseService.sendUnauthorized(res, 'Please verify your email address'); } if (error.message.includes('restriction')) { return ErrorResponseService.sendUnauthorized(res, 'Please verify your email address'); } if (error.message.includes('deactivated')) { return ErrorResponseService.sendUnauthorized(res, 'Account has been deactivated'); } } return ErrorResponseService.sendInternalServerError(res); } }); // Create user endpoint userRouter.post('/create', ValidationMiddleware.combine([ ValidationMiddleware.validateRequiredFields(['username', 'email', 'password']), ValidationMiddleware.validateEmailFormat(['email']), ValidationMiddleware.validateStringLength({ username: { min: 3, max: 50 }, password: { min: 6, max: 100 } }) ]), async (req, res) => { try { logRequest('Create user endpoint accessed', req, res, { username: req.body.username, email: req.body.email }); const acceptLanguage = req.header('Accept-Language') || 'en'; const language : 'hu' | 'de' | 'en' = acceptLanguage.toLowerCase().startsWith('hu') ? 'hu' : acceptLanguage.toLowerCase().startsWith('de') ? 'de' : 'en'; const result = await container.createUserCommandHandler.execute({ ...req.body, language }); logRequest('User created successfully', req, res, { username: result.username }); res.status(201).json(result); } catch (error) { // Don't log here since CreateUserCommandHandler already logs system errors // Only log validation/user input errors at router level if (error instanceof Error) { if (error.message.includes('already exists')) { return ErrorResponseService.sendConflict(res, error.message); } if (error.message.includes('validation')) { return ErrorResponseService.sendBadRequest(res, error.message); } // Log unexpected errors that weren't handled by the command handler if (!error.message.includes('Failed to create user')) { logError('Unexpected create user endpoint error', error as Error, req, res); } } return ErrorResponseService.sendInternalServerError(res); } }); // Get user profile (current user) userRouter.get('/profile', authRequired, async (req, res) => { try { const userId = (req as any).user.userId; logRequest('Get user profile endpoint accessed', req, res, { userId }); const result = await container.getUserByIdQueryHandler.execute({ id: userId }); if (!result) { logWarning('User profile not found', { userId }, req, res); return ErrorResponseService.sendNotFound(res, 'User not found'); } logRequest('User profile retrieved successfully', req, res, { userId, username: result.username }); res.json(result); } catch (error) { logError('Get user profile endpoint error', error as Error, req, res); return ErrorResponseService.sendInternalServerError(res); } }); // Update user profile (current user) userRouter.patch('/profile', authRequired, async (req, res) => { try { const userId = (req as any).user.userId; logRequest('Update user profile endpoint accessed', req, res, { userId, fieldsToUpdate: Object.keys(req.body) }); const result = await container.updateUserCommandHandler.execute({ id: userId, ...req.body }); if (!result) { return ErrorResponseService.sendNotFound(res, 'User not found'); } logRequest('User profile updated successfully', req, res, { userId, username: result.username }); res.json(result); } catch (error) { logError('Update user profile endpoint error', error as Error, req, res); if (error instanceof Error) { if (error.message.includes('already exists')) { return ErrorResponseService.sendConflict(res, error.message); } if (error.message.includes('validation')) { return ErrorResponseService.sendBadRequest(res, error.message); } } return ErrorResponseService.sendInternalServerError(res); } }); //Soft delete user (current user) userRouter.delete('/profile', authRequired, async (req, res) => { try { const userId = (req as any).user.userId; const result = await container.deleteUserCommandHandler.execute({ id: userId, soft: true }); logRequest('User soft deleted successfully', req, res, { userId }); res.json({ success: result }); } catch (error) { logError('Soft delete user endpoint error', error as Error, req, res); return ErrorResponseService.sendInternalServerError(res); } }); //logout user (current user) userRouter.post('/logout', authRequired, async (req, res) => { try { const userId = (req as any).user.userId; await container.logoutCommandHandler.execute(userId, res, req); logRequest('User logged out successfully', req, res, { userId }); res.json({ success: true }); } catch (error) { logError('Logout user endpoint error', error as Error, req, res); return ErrorResponseService.sendInternalServerError(res); } }); // Refresh token endpoint userRouter.post('/refresh-token', async (req, res) => { try { logRequest('Token refresh endpoint accessed', req, res); const jwtService = container.jwtService; const newTokenPair = jwtService.attemptTokenRefresh(req, res); if (newTokenPair) { logRequest('Token refresh successful', req, res); res.json({ success: true, message: 'Tokens refreshed successfully', accessToken: newTokenPair.accessToken, refreshToken: newTokenPair.refreshToken }); } else { logWarning('Token refresh failed - invalid or missing refresh token', undefined, req, res); return ErrorResponseService.sendUnauthorized(res, 'Invalid or expired refresh token'); } } catch (error) { logError('Refresh token endpoint error', error as Error, req, res); return ErrorResponseService.sendInternalServerError(res); } }); // Email verification endpoint userRouter.post('/verify-email/:token', async (req, res) => { try { const { token } = req.params; logRequest('Email verification endpoint accessed', req, res, { tokenPrefix: token.substring(0, 8) + '...' }); if (!token) { return ErrorResponseService.sendBadRequest(res, 'Verification token is required'); } const result = await container.verifyEmailCommandHandler.execute({ token }); if (result) { logAuth('Email verification successful', undefined, { tokenPrefix: token.substring(0, 8) + '...' }, req, res); res.json({ success: true, message: 'Email verified successfully' }); } else { throw new Error('Email verification failed'); } } catch (error) { logError('Email verification endpoint error', error as Error, req, res); if (error instanceof Error) { if (error.message.includes('Invalid') || error.message.includes('expired')) { return ErrorResponseService.sendBadRequest(res, 'Invalid or expired verification token'); } } return ErrorResponseService.sendInternalServerError(res); } }); // Forgot password request endpoint userRouter.post('/forgot-password', ValidationMiddleware.combine([ ValidationMiddleware.validateRequiredFields(['email']), ValidationMiddleware.validateEmailFormat(['email']) ]), async (req, res) => { try { const { email } = req.body; const acceptLanguage = req.header('Accept-Language') || 'en'; const language: 'hu' | 'de' | 'en' = acceptLanguage.toLowerCase().startsWith('hu') ? 'hu' : acceptLanguage.toLowerCase().startsWith('de') ? 'de' : 'en'; logRequest('Forgot password endpoint accessed', req, res, { email }); const result = await container.requestPasswordResetCommandHandler.execute({ language, email }); if (result) { logAuth('Password reset request successful', undefined, { email }, req, res); res.json({ success: true, message: 'If an account with this email exists, a password reset link has been sent' }); } else { throw new Error('Password reset request failed'); } } catch (error) { logError('Forgot password endpoint error', error as Error, req, res); // Always return success for security (don't reveal if email exists) res.json({ success: true, message: 'If an account with this email exists, a password reset link has been sent' }); } }); // Reset password endpoint userRouter.post('/reset-password', ValidationMiddleware.combine([ ValidationMiddleware.validateRequiredFields(['token', 'newPassword']), ValidationMiddleware.validateStringLength({ newPassword: { min: 6, max: 100 } }) ]), async (req, res) => { try { const { token, newPassword } = req.body; logRequest('Reset password endpoint accessed', req, res, { tokenPrefix: token.substring(0, 8) + '...' }); const result = await container.resetPasswordCommandHandler.execute({ token, newPassword }); if (result) { logAuth('Password reset successful', undefined, { tokenPrefix: token.substring(0, 8) + '...' }, req, res); res.json({ success: true, message: 'Password reset successfully' }); } else { throw new Error('Password reset failed'); } } catch (error) { logError('Reset password endpoint error', error as Error, req, res); if (error instanceof Error) { if (error.message.includes('Invalid') || error.message.includes('expired')) { return ErrorResponseService.sendBadRequest(res, 'Invalid or expired reset token'); } if (error.message.includes('Password validation')) { return ErrorResponseService.sendBadRequest(res, error.message); } } return ErrorResponseService.sendInternalServerError(res); } }); export default userRouter;