Backend Complete: Interface Refactoring & Service Container Enhancements
Repository Interface Optimization: - Created IBaseRepository.ts and IPaginatedRepository.ts - Refactored all 7 repository interfaces to extend base interfaces - Eliminated ~200 lines of redundant code (70% reduction) - Improved type safety and maintainability Dependency Injection Improvements: - Added EmailService and GameTokenService to DIContainer - Updated CreateUserCommandHandler constructor for DI - Updated RequestPasswordResetCommandHandler constructor for DI - Enhanced testability and service consistency Environment Configuration: - Created comprehensive .env.example with 40+ variables - Organized into 12 logical sections (Database, Security, Email, etc.) - Added security guidelines and best practices - Documented all backend environment requirements Documentation: - Added comprehensive codebase review - Created refactoring summary report - Added frontend implementation guide Impact: Improved code quality, reduced maintenance overhead, enhanced developer experience
This commit is contained in:
@@ -0,0 +1,313 @@
|
||||
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', result.user.id, { 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 result = await container.createUserCommandHandler.execute(req.body);
|
||||
|
||||
logRequest('User created successfully', req, res, {
|
||||
userId: result.id,
|
||||
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);
|
||||
}
|
||||
});
|
||||
|
||||
// Email verification endpoint
|
||||
userRouter.get('/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;
|
||||
|
||||
logRequest('Forgot password endpoint accessed', req, res, { email });
|
||||
|
||||
const result = await container.requestPasswordResetCommandHandler.execute({ 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;
|
||||
Reference in New Issue
Block a user