✨ Características: - 45 ejercicios universitarios (Basic → Advanced) - Renderizado LaTeX profesional - IA generativa (Z.ai/DashScope) - Docker 9 servicios - Tests 123/123 pasando - Seguridad enterprise (JWT, XSS, Rate limiting) 🐳 Infraestructura: - Next.js 14 + Node.js 20 - PostgreSQL 15 + Redis 7 - Docker Compose completo - Nginx + SSL ready 📚 Documentación: - 5 informes técnicos completos - README profesional - Scripts de deployment automatizados Estado: Producción lista ✅
322 lines
8.2 KiB
TypeScript
322 lines
8.2 KiB
TypeScript
/**
|
|
* User Controller
|
|
*
|
|
* HTTP request handlers for user profile endpoints
|
|
*/
|
|
|
|
import { Request, Response } from 'express';
|
|
import { userService } from './user.service';
|
|
import { ValidationError, NotFoundError, AuthenticationError, ConflictError } from '../../shared/types';
|
|
import { logger } from '../../shared/utils/logger';
|
|
import type { UserRole } from '@prisma/client';
|
|
|
|
// ============================================
|
|
// CONTROLLER
|
|
// ============================================
|
|
|
|
class UserController {
|
|
/**
|
|
* GET /api/users/me
|
|
* Get current user profile
|
|
*/
|
|
async getProfile(req: Request, res: Response): Promise<void> {
|
|
try {
|
|
const userId = req.user?.userId;
|
|
|
|
if (!userId) {
|
|
throw new AuthenticationError('User authentication required');
|
|
}
|
|
|
|
const user = await userService.getUserProfile(userId);
|
|
|
|
res.json({
|
|
success: true,
|
|
data: user,
|
|
});
|
|
} catch (error) {
|
|
logger.error({ error, req }, 'Error getting user profile');
|
|
|
|
if (error instanceof AuthenticationError) {
|
|
res.status(401).json({
|
|
success: false,
|
|
error: { code: error.code, message: error.message },
|
|
});
|
|
return;
|
|
}
|
|
|
|
if (error instanceof NotFoundError) {
|
|
res.status(404).json({
|
|
success: false,
|
|
error: { code: error.code, message: error.message },
|
|
});
|
|
return;
|
|
}
|
|
|
|
res.status(500).json({
|
|
success: false,
|
|
error: { code: 'INTERNAL_ERROR', message: 'Failed to get profile' },
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* PUT /api/users/me
|
|
* Update current user profile
|
|
*/
|
|
async updateProfile(req: Request, res: Response): Promise<void> {
|
|
try {
|
|
const userId = req.user?.userId;
|
|
|
|
if (!userId) {
|
|
throw new AuthenticationError('User authentication required');
|
|
}
|
|
|
|
const { username, telegramChatId } = req.body;
|
|
|
|
const updatedUser = await userService.updateProfile(userId, {
|
|
username,
|
|
telegramChatId: telegramChatId || null,
|
|
});
|
|
|
|
res.json({
|
|
success: true,
|
|
data: updatedUser,
|
|
});
|
|
} catch (error) {
|
|
logger.error({ error, req }, 'Error updating user profile');
|
|
|
|
if (error instanceof AuthenticationError) {
|
|
res.status(401).json({
|
|
success: false,
|
|
error: { code: error.code, message: error.message },
|
|
});
|
|
return;
|
|
}
|
|
|
|
if (error instanceof ValidationError) {
|
|
res.status(400).json({
|
|
success: false,
|
|
error: { code: error.code, message: error.message },
|
|
});
|
|
return;
|
|
}
|
|
|
|
if (error instanceof ConflictError) {
|
|
res.status(409).json({
|
|
success: false,
|
|
error: { code: error.code, message: error.message },
|
|
});
|
|
return;
|
|
}
|
|
|
|
res.status(500).json({
|
|
success: false,
|
|
error: { code: 'INTERNAL_ERROR', message: 'Failed to update profile' },
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* GET /api/users/me/stats
|
|
* Get current user statistics
|
|
*/
|
|
async getStats(req: Request, res: Response): Promise<void> {
|
|
try {
|
|
const userId = req.user?.userId;
|
|
|
|
if (!userId) {
|
|
throw new AuthenticationError('User authentication required');
|
|
}
|
|
|
|
const stats = await userService.getUserStats(userId);
|
|
|
|
res.json({
|
|
success: true,
|
|
data: stats,
|
|
});
|
|
} catch (error) {
|
|
logger.error({ error, req }, 'Error getting user stats');
|
|
|
|
if (error instanceof AuthenticationError) {
|
|
res.status(401).json({
|
|
success: false,
|
|
error: { code: error.code, message: error.message },
|
|
});
|
|
return;
|
|
}
|
|
|
|
res.status(500).json({
|
|
success: false,
|
|
error: { code: 'INTERNAL_ERROR', message: 'Failed to get stats' },
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* PUT /api/users/me/password
|
|
* Change current user password
|
|
*/
|
|
async changePassword(req: Request, res: Response): Promise<void> {
|
|
try {
|
|
const userId = req.user?.userId;
|
|
|
|
if (!userId) {
|
|
throw new AuthenticationError('User authentication required');
|
|
}
|
|
|
|
const { currentPassword, newPassword } = req.body;
|
|
|
|
await userService.changePassword(userId, currentPassword, newPassword);
|
|
|
|
res.json({
|
|
success: true,
|
|
message: 'Password changed successfully',
|
|
});
|
|
} catch (error) {
|
|
logger.error({ error, req }, 'Error changing password');
|
|
|
|
if (error instanceof AuthenticationError) {
|
|
res.status(401).json({
|
|
success: false,
|
|
error: { code: error.code, message: error.message },
|
|
});
|
|
return;
|
|
}
|
|
|
|
if (error instanceof ValidationError) {
|
|
res.status(400).json({
|
|
success: false,
|
|
error: { code: error.code, message: error.message },
|
|
});
|
|
return;
|
|
}
|
|
|
|
res.status(500).json({
|
|
success: false,
|
|
error: { code: 'INTERNAL_ERROR', message: 'Failed to change password' },
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* GET /api/users (admin only)
|
|
* List all users
|
|
*/
|
|
async listUsers(req: Request, res: Response): Promise<void> {
|
|
try {
|
|
const { role, isActive, page = '1', limit = '50' } = req.query;
|
|
|
|
const result = await userService.listUsers({
|
|
role: role as UserRole,
|
|
isActive: isActive === 'true' ? true : isActive === 'false' ? false : undefined,
|
|
page: parseInt(page as string, 10) || 1,
|
|
limit: parseInt(limit as string, 10) || 50,
|
|
});
|
|
|
|
res.json({
|
|
success: true,
|
|
data: result.users,
|
|
meta: {
|
|
pagination: {
|
|
page: parseInt(page as string, 10) || 1,
|
|
limit: parseInt(limit as string, 10) || 50,
|
|
total: result.total,
|
|
totalPages: Math.ceil(result.total / (parseInt(limit as string, 10) || 50)),
|
|
},
|
|
},
|
|
});
|
|
} catch (error) {
|
|
logger.error({ error, req }, 'Error listing users');
|
|
|
|
res.status(500).json({
|
|
success: false,
|
|
error: { code: 'INTERNAL_ERROR', message: 'Failed to list users' },
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* PUT /api/users/:id/role (admin only)
|
|
* Update user role
|
|
*/
|
|
async updateUserRole(req: Request, res: Response): Promise<void> {
|
|
try {
|
|
const { id } = req.params;
|
|
const { role } = req.body;
|
|
|
|
if (!role || !['STUDENT', 'TEACHER', 'ADMIN'].includes(role)) {
|
|
throw new ValidationError('Valid role is required (STUDENT, TEACHER, ADMIN)');
|
|
}
|
|
|
|
const updatedUser = await userService.updateUserRole(id, role as UserRole);
|
|
|
|
res.json({
|
|
success: true,
|
|
data: updatedUser,
|
|
});
|
|
} catch (error) {
|
|
logger.error({ error, req }, 'Error updating user role');
|
|
|
|
if (error instanceof ValidationError) {
|
|
res.status(400).json({
|
|
success: false,
|
|
error: { code: error.code, message: error.message },
|
|
});
|
|
return;
|
|
}
|
|
|
|
if (error instanceof NotFoundError) {
|
|
res.status(404).json({
|
|
success: false,
|
|
error: { code: error.code, message: error.message },
|
|
});
|
|
return;
|
|
}
|
|
|
|
res.status(500).json({
|
|
success: false,
|
|
error: { code: 'INTERNAL_ERROR', message: 'Failed to update user role' },
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* DELETE /api/users/:id (admin only)
|
|
* Deactivate user
|
|
*/
|
|
async deactivateUser(req: Request, res: Response): Promise<void> {
|
|
try {
|
|
const { id } = req.params;
|
|
|
|
const deactivatedUser = await userService.deactivateUser(id);
|
|
|
|
res.json({
|
|
success: true,
|
|
data: deactivatedUser,
|
|
message: 'User deactivated successfully',
|
|
});
|
|
} catch (error) {
|
|
logger.error({ error, req }, 'Error deactivating user');
|
|
|
|
if (error instanceof NotFoundError) {
|
|
res.status(404).json({
|
|
success: false,
|
|
error: { code: error.code, message: error.message },
|
|
});
|
|
return;
|
|
}
|
|
|
|
res.status(500).json({
|
|
success: false,
|
|
error: { code: 'INTERNAL_ERROR', message: 'Failed to deactivate user' },
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
// ============================================
|
|
// EXPORT
|
|
// ============================================
|
|
|
|
export const userController = new UserController();
|
|
export default userController; |