Files
tm_back/internal/handler/user.go
T
2025-07-27 16:20:21 +03:30

381 lines
12 KiB
Go

package handler
import (
"github.com/go-playground/validator/v10"
"github.com/google/uuid"
"github.com/labstack/echo/v4"
"tm/internal/domain"
"tm/pkg/logger"
"tm/pkg/response"
)
// UserAuthHandler handles authentication related HTTP requests for panel users
type UserAuthHandler struct {
userAuthService domain.UserAuthService
validator *validator.Validate
logger logger.Logger
}
// NewUserAuthHandler creates a new panel user authentication handler
func NewUserAuthHandler(
userAuthService domain.UserAuthService,
validator *validator.Validate,
logger logger.Logger,
) *UserAuthHandler {
return &UserAuthHandler{
userAuthService: userAuthService,
validator: validator,
logger: logger,
}
}
// Login handles panel user authentication
// @Summary Panel user login
// @Description Authenticate panel user and return tokens
// @Tags user-auth
// @Accept json
// @Produce json
// @Param request body domain.LoginRequest true "Login request"
// @Success 200 {object} response.APIResponse{data=domain.UserAuthResponse}
// @Failure 400 {object} response.APIResponse
// @Failure 401 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Router /api/admin/auth/login [post]
func (h *UserAuthHandler) Login(c echo.Context) error {
var req domain.LoginRequest
// Bind request body
if err := c.Bind(&req); err != nil {
h.logger.Warn("Failed to bind panel user login request", map[string]interface{}{
"error": err.Error(),
})
return response.BadRequest(c, "Invalid request format", err.Error())
}
// Validate request
if err := h.validator.Struct(&req); err != nil {
h.logger.Warn("Panel user login request validation failed", map[string]interface{}{
"error": err.Error(),
"email": req.Email,
})
return response.ValidationError(c, "Validation failed", err.Error())
}
// Call service
authResponse, err := h.userAuthService.Login(c.Request().Context(), req)
if err != nil {
h.logger.Warn("Panel user login failed", map[string]interface{}{
"error": err.Error(),
"email": req.Email,
})
if err.Error() == "invalid credentials" {
return response.Unauthorized(c, "Invalid email or password")
}
return response.InternalServerError(c, "Login failed")
}
h.logger.Info("Panel user logged in successfully", map[string]interface{}{
"user_id": authResponse.User.ID.String(),
"email": authResponse.User.Email,
"role": authResponse.User.Role,
})
return response.Success(c, authResponse, "Login successful")
}
// RefreshToken handles token refresh for panel users
// @Summary Refresh panel user access token
// @Description Generate new access token using refresh token
// @Tags user-auth
// @Accept json
// @Produce json
// @Param request body RefreshTokenRequest true "Refresh token request"
// @Success 200 {object} response.APIResponse{data=domain.UserAuthResponse}
// @Failure 400 {object} response.APIResponse
// @Failure 401 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Router /api/admin/auth/refresh [post]
func (h *UserAuthHandler) RefreshToken(c echo.Context) error {
var req struct {
RefreshToken string `json:"refresh_token" validate:"required"`
}
// Bind request body
if err := c.Bind(&req); err != nil {
return response.BadRequest(c, "Invalid request format", err.Error())
}
// Validate request
if err := h.validator.Struct(&req); err != nil {
return response.ValidationError(c, "Validation failed", err.Error())
}
// Call service
authResponse, err := h.userAuthService.RefreshToken(c.Request().Context(), req.RefreshToken)
if err != nil {
h.logger.Warn("Panel user token refresh failed", map[string]interface{}{
"error": err.Error(),
})
if err.Error() == "invalid refresh token" {
return response.Unauthorized(c, "Invalid refresh token")
}
return response.InternalServerError(c, "Token refresh failed")
}
return response.Success(c, authResponse, "Token refreshed successfully")
}
// ChangePassword handles password change for panel users
// @Summary Change panel user password
// @Description Change authenticated panel user's password
// @Tags user-auth
// @Accept json
// @Produce json
// @Security ApiKeyAuth
// @Param request body ChangePasswordRequest true "Change password request"
// @Success 200 {object} response.APIResponse
// @Failure 400 {object} response.APIResponse
// @Failure 401 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Router /api/admin/auth/change-password [post]
func (h *UserAuthHandler) ChangePassword(c echo.Context) error {
var req struct {
OldPassword string `json:"old_password" validate:"required"`
NewPassword string `json:"new_password" validate:"required,min=8"`
}
// Bind request body
if err := c.Bind(&req); err != nil {
return response.BadRequest(c, "Invalid request format", err.Error())
}
// Validate request
if err := h.validator.Struct(&req); err != nil {
return response.ValidationError(c, "Validation failed", err.Error())
}
// Get user from context (set by auth middleware)
user, ok := c.Get("user").(*domain.User)
if !ok {
return response.Unauthorized(c, "Authentication required")
}
// Call service
err := h.userAuthService.ChangePassword(c.Request().Context(), user.ID, req.OldPassword, req.NewPassword)
if err != nil {
h.logger.Error("Panel user password change failed", map[string]interface{}{
"error": err.Error(),
"user_id": user.ID.String(),
})
if err.Error() == "invalid old password" {
return response.BadRequest(c, "Invalid old password", "")
}
return response.InternalServerError(c, "Password change failed")
}
h.logger.Info("Panel user password changed successfully", map[string]interface{}{
"user_id": user.ID.String(),
})
return response.Success(c, nil, "Password changed successfully")
}
// GetProfile returns current panel user profile
// @Summary Get panel user profile
// @Description Get authenticated panel user's profile information
// @Tags user-auth
// @Produce json
// @Security ApiKeyAuth
// @Success 200 {object} response.APIResponse{data=domain.User}
// @Failure 401 {object} response.APIResponse
// @Router /api/admin/auth/profile [get]
func (h *UserAuthHandler) GetProfile(c echo.Context) error {
// Get user from context (set by auth middleware)
user, ok := c.Get("user").(*domain.User)
if !ok {
return response.Unauthorized(c, "Authentication required")
}
return response.Success(c, user, "Profile retrieved successfully")
}
// CreatePanelUser handles creating new panel users (admin only)
// @Summary Create new panel user
// @Description Create a new panel user (admin only)
// @Tags user-auth
// @Accept json
// @Produce json
// @Security ApiKeyAuth
// @Param request body domain.CreateUserRequest true "Create user request"
// @Success 201 {object} response.APIResponse{data=domain.User}
// @Failure 400 {object} response.APIResponse
// @Failure 401 {object} response.APIResponse
// @Failure 403 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Router /api/admin/users [post]
func (h *UserAuthHandler) CreatePanelUser(c echo.Context) error {
var req domain.CreateUserRequest
// Bind request body
if err := c.Bind(&req); err != nil {
h.logger.Warn("Failed to bind create user request", map[string]interface{}{
"error": err.Error(),
})
return response.BadRequest(c, "Invalid request format", err.Error())
}
// Validate request
if err := h.validator.Struct(&req); err != nil {
h.logger.Warn("Create user request validation failed", map[string]interface{}{
"error": err.Error(),
"email": req.Email,
})
return response.ValidationError(c, "Validation failed", err.Error())
}
// Get current user from context (set by auth middleware)
currentUser, ok := c.Get("user").(*domain.User)
if !ok {
return response.Unauthorized(c, "Authentication required")
}
// Check if current user can create users
if !currentUser.CanManageUsers() {
return response.Forbidden(c, "Insufficient permissions to create users")
}
// Call service
newUser, err := h.userAuthService.CreatePanelUser(c.Request().Context(), req, currentUser.ID)
if err != nil {
h.logger.Error("Panel user creation failed", map[string]interface{}{
"error": err.Error(),
"email": req.Email,
"created_by": currentUser.ID.String(),
})
if err.Error() == "user already exists" {
return response.Conflict(c, "User already exists")
}
return response.InternalServerError(c, "User creation failed")
}
h.logger.Info("Panel user created successfully", map[string]interface{}{
"user_id": newUser.ID.String(),
"email": newUser.Email,
"role": newUser.Role,
"created_by": currentUser.ID.String(),
})
return response.Created(c, newUser, "User created successfully")
}
// UpdateUserPermissions handles updating user permissions (admin only)
// @Summary Update user permissions
// @Description Update panel user permissions (admin only)
// @Tags user-auth
// @Accept json
// @Produce json
// @Security ApiKeyAuth
// @Param user_id path string true "User ID"
// @Param request body UpdatePermissionsRequest true "Update permissions request"
// @Success 200 {object} response.APIResponse
// @Failure 400 {object} response.APIResponse
// @Failure 401 {object} response.APIResponse
// @Failure 403 {object} response.APIResponse
// @Failure 404 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Router /api/admin/users/{user_id}/permissions [put]
func (h *UserAuthHandler) UpdateUserPermissions(c echo.Context) error {
var req struct {
Permissions []string `json:"permissions" validate:"required"`
}
// Bind request body
if err := c.Bind(&req); err != nil {
return response.BadRequest(c, "Invalid request format", err.Error())
}
// Validate request
if err := h.validator.Struct(&req); err != nil {
return response.ValidationError(c, "Validation failed", err.Error())
}
// Get user ID from path
userIDStr := c.Param("user_id")
if userIDStr == "" {
return response.BadRequest(c, "User ID is required", "")
}
userID, err := uuid.Parse(userIDStr)
if err != nil {
return response.BadRequest(c, "Invalid user ID format", "")
}
// Get current user from context (set by auth middleware)
currentUser, ok := c.Get("user").(*domain.User)
if !ok {
return response.Unauthorized(c, "Authentication required")
}
// Check if current user can manage users
if !currentUser.CanManageUsers() {
return response.Forbidden(c, "Insufficient permissions to update user permissions")
}
// Call service
err = h.userAuthService.UpdateUserPermissions(c.Request().Context(), userID, req.Permissions, currentUser.ID)
if err != nil {
h.logger.Error("User permissions update failed", map[string]interface{}{
"error": err.Error(),
"user_id": userID.String(),
"updated_by": currentUser.ID.String(),
})
if err.Error() == "user not found" {
return response.NotFound(c, "User not found")
}
return response.InternalServerError(c, "Permissions update failed")
}
h.logger.Info("User permissions updated successfully", map[string]interface{}{
"user_id": userID.String(),
"permissions": req.Permissions,
"updated_by": currentUser.ID.String(),
})
return response.Success(c, nil, "Permissions updated successfully")
}
// Logout handles panel user logout
// @Summary Panel user logout
// @Description Logout panel user (client should remove tokens)
// @Tags user-auth
// @Produce json
// @Security ApiKeyAuth
// @Success 200 {object} response.APIResponse
// @Router /api/admin/auth/logout [post]
func (h *UserAuthHandler) Logout(c echo.Context) error {
// In a stateless JWT implementation, logout is typically handled client-side
// by removing the tokens from storage. However, you could implement token
// blacklisting here if needed.
user, ok := c.Get("user").(*domain.User)
if ok {
h.logger.Info("Panel user logged out", map[string]interface{}{
"user_id": user.ID.String(),
"email": user.Email,
})
}
return response.Success(c, nil, "Logged out successfully")
}