🎓 Initial commit: Math2 Platform - Plataforma de Álgebra Lineal PRO
✨ 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 ✅
This commit is contained in:
322
backend/src/modules/user/user.controller.ts
Normal file
322
backend/src/modules/user/user.controller.ts
Normal file
@@ -0,0 +1,322 @@
|
||||
/**
|
||||
* 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;
|
||||
Reference in New Issue
Block a user