Features: - React 18 + TypeScript frontend with Vite - Go + Gin backend API - PostgreSQL database - JWT authentication with refresh tokens - User management (admin panel) - Docker containerization - Progress tracking system - 4 economic modules structure Fixed: - Login with username or email - User creation without required email - Database nullable timestamps - API response field naming
231 lines
5.9 KiB
Go
231 lines
5.9 KiB
Go
package handlers
|
|
|
|
import (
|
|
"net/http"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/google/uuid"
|
|
|
|
"github.com/ren/econ/backend/internal/models"
|
|
"github.com/ren/econ/backend/internal/repository"
|
|
"github.com/ren/econ/backend/internal/services"
|
|
)
|
|
|
|
type UsersHandler struct {
|
|
userRepo *repository.UserRepository
|
|
progresoRepo *repository.ProgresoRepository
|
|
authService *services.AuthService
|
|
}
|
|
|
|
func NewUsersHandler(userRepo *repository.UserRepository, progresoRepo *repository.ProgresoRepository, authService *services.AuthService) *UsersHandler {
|
|
return &UsersHandler{
|
|
userRepo: userRepo,
|
|
progresoRepo: progresoRepo,
|
|
authService: authService,
|
|
}
|
|
}
|
|
|
|
// ListUsers godoc
|
|
// @Summary Listar usuarios
|
|
// @Description Lista todos los usuarios (solo admin)
|
|
// @Tags admin
|
|
// @Produce json
|
|
// @Security BearerAuth
|
|
// @Success 200 {array} models.Usuario
|
|
// @Router /api/admin/usuarios [get]
|
|
func (h *UsersHandler) ListUsers(c *gin.Context) {
|
|
users, err := h.userRepo.List(c.Request.Context())
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Error al listar usuarios"})
|
|
return
|
|
}
|
|
|
|
if users == nil {
|
|
users = []models.Usuario{}
|
|
}
|
|
|
|
c.JSON(http.StatusOK, users)
|
|
}
|
|
|
|
// CreateUser godoc
|
|
// @Summary Crear usuario
|
|
// @Description Crea un nuevo usuario (solo admin)
|
|
// @Tags admin
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param usuario body models.UsuarioCreate true "Usuario a crear"
|
|
// @Security BearerAuth
|
|
// @Success 201 {object} models.Usuario
|
|
// @Router /api/admin/usuarios [post]
|
|
func (h *UsersHandler) CreateUser(c *gin.Context) {
|
|
var req models.UsuarioCreate
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
// Hash password if provided
|
|
passwordHash := req.Password
|
|
if passwordHash != "" {
|
|
hash, err := h.authService.HashPassword(passwordHash)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Error al hashear password"})
|
|
return
|
|
}
|
|
passwordHash = hash
|
|
}
|
|
|
|
user := &models.Usuario{
|
|
Username: req.Username,
|
|
Email: req.Email,
|
|
PasswordHash: passwordHash,
|
|
Nombre: req.Nombre,
|
|
Rol: req.Rol,
|
|
}
|
|
|
|
// Si no se proporciona email, generar uno automáticamente basado en el username
|
|
if user.Email == "" {
|
|
user.Email = req.Username + "@econ.local"
|
|
}
|
|
|
|
if user.Rol == "" {
|
|
user.Rol = "estudiante"
|
|
}
|
|
|
|
err := h.userRepo.Create(c.Request.Context(), user)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Error al crear usuario: " + err.Error()})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusCreated, user)
|
|
}
|
|
|
|
// GetUser godoc
|
|
// @Summary Obtener usuario
|
|
// @Description Obtiene un usuario por ID (solo admin)
|
|
// @Tags admin
|
|
// @Produce json
|
|
// @Security BearerAuth
|
|
// @Param id path string true "ID del usuario"
|
|
// @Success 200 {object} models.Usuario
|
|
// @Router /api/admin/usuarios/{id} [get]
|
|
func (h *UsersHandler) GetUser(c *gin.Context) {
|
|
id, err := uuid.Parse(c.Param("id"))
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "ID inválido"})
|
|
return
|
|
}
|
|
|
|
user, err := h.userRepo.GetByID(c.Request.Context(), id)
|
|
if err != nil {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "Usuario no encontrado"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, user)
|
|
}
|
|
|
|
// UpdateUser godoc
|
|
// @Summary Actualizar usuario
|
|
// @Description Actualiza un usuario (solo admin)
|
|
// @Tags admin
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param id path string true "ID del usuario"
|
|
// @Param usuario body models.UsuarioUpdate true "Datos a actualizar"
|
|
// @Security BearerAuth
|
|
// @Success 200 {object} models.Usuario
|
|
// @Router /api/admin/usuarios/{id} [put]
|
|
func (h *UsersHandler) UpdateUser(c *gin.Context) {
|
|
id, err := uuid.Parse(c.Param("id"))
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "ID inválido"})
|
|
return
|
|
}
|
|
|
|
var req models.UsuarioUpdate
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
user, err := h.userRepo.GetByID(c.Request.Context(), id)
|
|
if err != nil {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "Usuario no encontrado"})
|
|
return
|
|
}
|
|
|
|
if req.Email != "" {
|
|
user.Email = req.Email
|
|
}
|
|
if req.Nombre != "" {
|
|
user.Nombre = req.Nombre
|
|
}
|
|
if req.Activo != nil {
|
|
user.Activo = *req.Activo
|
|
}
|
|
|
|
err = h.userRepo.Update(c.Request.Context(), user)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Error al actualizar usuario"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, user)
|
|
}
|
|
|
|
// DeleteUser godoc
|
|
// @Summary Eliminar usuario
|
|
// @Description Desactiva un usuario (solo admin)
|
|
// @Tags admin
|
|
// @Produce json
|
|
// @Security BearerAuth
|
|
// @Param id path string true "ID del usuario"
|
|
// @Success 200 {object} map[string]string
|
|
// @Router /api/admin/usuarios/{id} [delete]
|
|
func (h *UsersHandler) DeleteUser(c *gin.Context) {
|
|
id, err := uuid.Parse(c.Param("id"))
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "ID inválido"})
|
|
return
|
|
}
|
|
|
|
err = h.userRepo.Delete(c.Request.Context(), id)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Error al eliminar usuario"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{"message": "Usuario desactivado exitosamente"})
|
|
}
|
|
|
|
// GetUserProgreso godoc
|
|
// @Summary Ver progreso de usuario
|
|
// @Description Obtiene el progreso de un usuario (solo admin)
|
|
// @Tags admin
|
|
// @Produce json
|
|
// @Security BearerAuth
|
|
// @Param id path string true "ID del usuario"
|
|
// @Success 200 {array} models.Progreso
|
|
// @Router /api/admin/usuarios/{id}/progreso [get]
|
|
func (h *UsersHandler) GetUserProgreso(c *gin.Context) {
|
|
id, err := uuid.Parse(c.Param("id"))
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "ID inválido"})
|
|
return
|
|
}
|
|
|
|
progresos, err := h.progresoRepo.GetByUsuarioID(c.Request.Context(), id)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Error al obtener progreso"})
|
|
return
|
|
}
|
|
|
|
if progresos == nil {
|
|
progresos = []models.Progreso{}
|
|
}
|
|
|
|
c.JSON(http.StatusOK, progresos)
|
|
}
|