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
112 lines
3.1 KiB
Go
112 lines
3.1 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/services"
|
|
)
|
|
|
|
type AuthHandler struct {
|
|
authService *services.AuthService
|
|
}
|
|
|
|
func NewAuthHandler(authService *services.AuthService) *AuthHandler {
|
|
return &AuthHandler{authService: authService}
|
|
}
|
|
|
|
// Login godoc
|
|
// @Summary Iniciar sesión
|
|
// @Description Autentica usuario y devuelve tokens JWT
|
|
// @Tags auth
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param login body models.LoginRequest true "Credenciales"
|
|
// @Success 200 {object} models.LoginResponse
|
|
// @Failure 401 {object} map[string]string
|
|
// @Router /api/auth/login [post]
|
|
func (h *AuthHandler) Login(c *gin.Context) {
|
|
var req models.LoginRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
resp, err := h.authService.Login(c.Request.Context(), &req)
|
|
if err != nil {
|
|
switch err {
|
|
case services.ErrInvalidCredentials:
|
|
c.JSON(http.StatusUnauthorized, gin.H{"error": "Credenciales inválidas"})
|
|
case services.ErrUserInactive:
|
|
c.JSON(http.StatusForbidden, gin.H{"error": "Usuario inactivo"})
|
|
default:
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Error interno"})
|
|
}
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, resp)
|
|
}
|
|
|
|
// RefreshToken godoc
|
|
// @Summary Renovar token de acceso
|
|
// @Description Renueva el token de acceso usando el refresh token
|
|
// @Tags auth
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param refresh body models.RefreshRequest true "Refresh token"
|
|
// @Success 200 {object} models.LoginResponse
|
|
// @Failure 401 {object} map[string]string
|
|
// @Router /api/auth/refresh [post]
|
|
func (h *AuthHandler) RefreshToken(c *gin.Context) {
|
|
var req models.RefreshRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
resp, err := h.authService.RefreshToken(c.Request.Context(), req.RefreshToken)
|
|
if err != nil {
|
|
switch err {
|
|
case services.ErrInvalidToken, services.ErrTokenExpired:
|
|
c.JSON(http.StatusUnauthorized, gin.H{"error": "Token inválido o expirado"})
|
|
case services.ErrUserNotFound:
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "Usuario no encontrado"})
|
|
case services.ErrUserInactive:
|
|
c.JSON(http.StatusForbidden, gin.H{"error": "Usuario inactivo"})
|
|
default:
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Error interno"})
|
|
}
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, resp)
|
|
}
|
|
|
|
// Logout godoc
|
|
// @Summary Cerrar sesión
|
|
// @Description Cierra la sesión del usuario
|
|
// @Tags auth
|
|
// @Produce json
|
|
// @Security BearerAuth
|
|
// @Success 200 {object} map[string]string
|
|
// @Router /api/auth/logout [post]
|
|
func (h *AuthHandler) Logout(c *gin.Context) {
|
|
userID, exists := c.Get("user_id")
|
|
if !exists {
|
|
c.JSON(http.StatusUnauthorized, gin.H{"error": "No autorizado"})
|
|
return
|
|
}
|
|
|
|
err := h.authService.Logout(c.Request.Context(), userID.(uuid.UUID))
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Error al cerrar sesión"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{"message": "Sesión cerrada exitosamente"})
|
|
}
|