Files
tm_back/internal/user/handler.go
T
n.nakhostin dbcc2d1d4a Refactor customer domain by removing obsolete files and updating main application structure
- Deleted customer domain files including entities, services, handlers, and forms to streamline the codebase.
- Removed customer-related routes and service initializations from the main application.
- Updated Swagger documentation to reflect the removal of customer endpoints and definitions.
- Cleaned up the project structure to enhance maintainability and focus on user management functionality.
2025-08-10 19:05:46 +03:30

698 lines
21 KiB
Go

package user
import (
"strconv"
"tm/pkg/authorization"
"tm/pkg/logger"
"tm/pkg/response"
"github.com/asaskevich/govalidator"
"github.com/google/uuid"
"github.com/labstack/echo/v4"
)
// Handler handles HTTP requests for user operations
type Handler struct {
service Service
logger logger.Logger
validator ValidationService
authService authorization.AuthorizationService
}
// NewUserHandler creates a new user handler
func NewUserHandler(service Service, logger logger.Logger, validator ValidationService, authService authorization.AuthorizationService) *Handler {
return &Handler{
service: service,
logger: logger,
validator: validator,
authService: authService,
}
}
// RegisterRoutes registers user routes
func (h *Handler) RegisterRoutes(e *echo.Echo) {
v1 := e.Group("/admin/v1")
// Public routes (no authentication required)
authorizationGP := v1.Group("")
authorizationGP.POST("/login", h.Login)
authorizationGP.POST("/refresh-token", h.RefreshToken)
authorizationGP.POST("/reset-password", h.ResetPassword)
// Protected routes (require authentication)
profileGP := v1.Group("")
profileGP.Use(h.AuthMiddleware())
profileGP.GET("/profile", h.GetProfile)
profileGP.PUT("/profile", h.UpdateProfile)
profileGP.PUT("/change-password", h.ChangePassword)
profileGP.DELETE("/logout", h.Logout)
// Admin routes (require admin role)
adminGroup := v1.Group("/users")
adminGroup.Use(h.AuthMiddleware(), h.AdminMiddleware())
adminGroup.POST("", h.CreateUser)
adminGroup.GET("", h.ListUsers)
adminGroup.GET("/:id", h.GetUserByID)
adminGroup.PUT("/:id", h.UpdateUser)
adminGroup.DELETE("/:id", h.DeleteUser)
adminGroup.PUT("/:id/status", h.UpdateUserStatus)
adminGroup.PUT("/:id/role", h.UpdateUserRole)
adminGroup.GET("/company/:company_id", h.GetUsersByCompanyID)
adminGroup.GET("/role/:role", h.GetUsersByRole)
}
// Login handles user login
// @Summary Login user
// @Description Authenticate user with email/username and password
// @Tags users
// @Accept json
// @Produce json
// @Param login body LoginForm true "Login credentials"
// @Success 200 {object} response.APIResponse{data=AuthResponse}
// @Failure 400 {object} response.APIResponse
// @Failure 401 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Router /users/login [post]
func (h *Handler) Login(c echo.Context) error {
form, err := response.Parse[LoginForm](c)
if err != nil {
return response.BadRequest(c, "Invalid request format", "")
}
// Call service
authResponse, err := h.service.Login(c.Request().Context(), form)
if err != nil {
return response.Unauthorized(c, err.Error())
}
return response.Success(c, authResponse, "Login successful")
}
// RefreshToken handles token refresh
// @Summary Refresh access token
// @Description Refresh access token using refresh token
// @Tags users
// @Accept json
// @Produce json
// @Param refresh body RefreshTokenForm true "Refresh token"
// @Success 200 {object} response.APIResponse{data=AuthResponse}
// @Failure 400 {object} response.APIResponse
// @Failure 401 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Router /users/refresh-token [post]
func (h *Handler) RefreshToken(c echo.Context) error {
form, err := response.Parse[RefreshTokenForm](c)
if err != nil {
return response.BadRequest(c, "Invalid request format", "")
}
authResponse, err := h.service.RefreshToken(c.Request().Context(), form.RefreshToken)
if err != nil {
return response.Unauthorized(c, err.Error())
}
return response.Success(c, authResponse, "Token refreshed successfully")
}
// ResetPassword handles password reset request
// @Summary Reset password
// @Description Send password reset email to user
// @Tags users
// @Accept json
// @Produce json
// @Param reset body ResetPasswordForm true "Password reset request"
// @Success 200 {object} response.APIResponse
// @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Router /users/reset-password [post]
func (h *Handler) ResetPassword(c echo.Context) error {
form, err := response.Parse[ResetPasswordForm](c)
if err != nil {
return response.BadRequest(c, "Invalid request format", "")
}
err = h.service.ResetPassword(c.Request().Context(), form)
if err != nil {
return response.InternalServerError(c, "Failed to process password reset")
}
return response.Success(c, map[string]interface{}{
"message": "Password reset email sent successfully",
}, "Password reset initiated")
}
// GetProfile gets current user profile
// @Summary Get user profile
// @Description Get current user profile information
// @Tags users
// @Accept json
// @Produce json
// @Security BearerAuth
// @Success 200 {object} response.APIResponse{data=UserResponse}
// @Failure 401 {object} response.APIResponse
// @Failure 404 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Router /users/profile [get]
func (h *Handler) GetProfile(c echo.Context) error {
// Extract user ID from JWT token context
userID, err := GetUserIDFromContext(c)
if err != nil {
return response.Unauthorized(c, "User not authenticated")
}
user, err := h.service.GetProfile(c.Request().Context(), userID)
if err != nil {
return response.NotFound(c, "User not found")
}
return response.Success(c, user.ToResponse(), "Profile retrieved successfully")
}
// UpdateProfile updates current user profile
// @Summary Update user profile
// @Description Update current user profile information
// @Tags users
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param profile body UpdateUserForm true "Profile update data"
// @Success 200 {object} response.APIResponse{data=UserResponse}
// @Failure 400 {object} response.APIResponse
// @Failure 401 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Router /users/profile [put]
func (h *Handler) UpdateProfile(c echo.Context) error {
// Extract user ID from JWT token context
userID, err := GetUserIDFromContext(c)
if err != nil {
return response.Unauthorized(c, "User not authenticated")
}
form, err := response.Parse[UpdateUserForm](c)
if err != nil {
return response.BadRequest(c, "Invalid request format", "")
}
user, err := h.service.UpdateUser(c.Request().Context(), userID, form, &userID)
if err != nil {
return response.BadRequest(c, err.Error(), "")
}
return response.Success(c, user.ToResponse(), "Profile updated successfully")
}
// ChangePassword changes current user password
// @Summary Change password
// @Description Change current user password
// @Tags users
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param password body ChangePasswordForm true "Password change data"
// @Success 200 {object} response.APIResponse
// @Failure 400 {object} response.APIResponse
// @Failure 401 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Router /users/change-password [put]
func (h *Handler) ChangePassword(c echo.Context) error {
userID, err := GetUserIDFromContext(c)
if err != nil {
return response.Unauthorized(c, "User not authenticated")
}
form, err := response.Parse[ChangePasswordForm](c)
if err != nil {
return response.BadRequest(c, "Invalid request format", "")
}
err = h.service.ChangePassword(c.Request().Context(), userID, form)
if err != nil {
return response.BadRequest(c, err.Error(), "")
}
return response.Success(c, map[string]interface{}{
"message": "Password changed successfully",
}, "Password changed successfully")
}
// Logout handles user logout
// @Summary Logout user
// @Description Logout current user and invalidate tokens
// @Tags users
// @Accept json
// @Produce json
// @Security BearerAuth
// @Success 200 {object} response.APIResponse
// @Failure 401 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Router /users/logout [post]
func (h *Handler) Logout(c echo.Context) error {
// Extract user ID and access token from context
userID, err := GetUserIDFromContext(c)
if err != nil {
return response.Unauthorized(c, "User not authenticated")
}
accessToken, err := GetAccessTokenFromContext(c)
if err != nil {
return response.Unauthorized(c, "Access token not found")
}
err = h.service.Logout(c.Request().Context(), userID, accessToken)
if err != nil {
return response.InternalServerError(c, "Failed to logout")
}
return response.Success(c, map[string]interface{}{
"message": "Logged out successfully",
}, "Logged out successfully")
}
// CreateUser creates a new user (admin only)
// @Summary Create user
// @Description Create a new user (admin only)
// @Tags users
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param user body CreateUserForm true "User creation data"
// @Success 201 {object} response.APIResponse{data=UserResponse}
// @Failure 400 {object} response.APIResponse
// @Failure 401 {object} response.APIResponse
// @Failure 403 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Router /users/admin [post]
func (h *Handler) CreateUser(c echo.Context) error {
// Get current user ID from JWT token
currentUserID, err := GetUserIDFromContext(c)
if err != nil {
return response.Unauthorized(c, "User not authenticated")
}
var form CreateUserForm
if err := c.Bind(&form); err != nil {
return response.BadRequest(c, "Invalid request format", "")
}
// Validate form
if _, err := govalidator.ValidateStruct(form); err != nil {
return response.ValidationError(c, err.Error(), "")
}
// Call service
user, err := h.service.CreateUser(c.Request().Context(), &form, &currentUserID)
if err != nil {
return response.BadRequest(c, err.Error(), "")
}
return response.Created(c, user.ToResponse(), "User created successfully")
}
// ListUsers lists users with search and filters (admin only)
// @Summary List users
// @Description List users with search and filters (admin only)
// @Tags users
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param search query string false "Search term"
// @Param status query string false "User status filter"
// @Param role query string false "User role filter"
// @Param company_id query string false "Company ID filter"
// @Param limit query int false "Limit results"
// @Param offset query int false "Offset results"
// @Param sort_by query string false "Sort field"
// @Param sort_order query string false "Sort order"
// @Success 200 {object} response.APIResponse{data=UserListResponse}
// @Failure 400 {object} response.APIResponse
// @Failure 401 {object} response.APIResponse
// @Failure 403 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Router /users/admin [get]
func (h *Handler) ListUsers(c echo.Context) error {
var form ListUsersForm
if err := c.Bind(&form); err != nil {
return response.BadRequest(c, "Invalid request format", "")
}
// Validate form
if _, err := govalidator.ValidateStruct(form); err != nil {
return response.ValidationError(c, err.Error(), "")
}
// Call service
users, err := h.service.ListUsers(c.Request().Context(), &form)
if err != nil {
return response.InternalServerError(c, "Failed to list users")
}
return response.Success(c, users, "Users retrieved successfully")
}
// GetUserByID gets a user by ID (admin only)
// @Summary Get user by ID
// @Description Get user details by ID (admin only)
// @Tags users
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param id path string true "User ID"
// @Success 200 {object} response.APIResponse{data=UserResponse}
// @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 /users/admin/{id} [get]
func (h *Handler) GetUserByID(c echo.Context) error {
idStr := c.Param("id")
userID, err := uuid.Parse(idStr)
if err != nil {
return response.BadRequest(c, "Invalid user ID", "")
}
user, err := h.service.GetUserByID(c.Request().Context(), userID)
if err != nil {
return response.NotFound(c, "User not found")
}
return response.Success(c, user.ToResponse(), "User retrieved successfully")
}
// UpdateUser updates a user (admin only)
// @Summary Update user
// @Description Update user information (admin only)
// @Tags users
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param id path string true "User ID"
// @Param user body UpdateUserForm true "User update data"
// @Success 200 {object} response.APIResponse{data=UserResponse}
// @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 /users/admin/{id} [put]
func (h *Handler) UpdateUser(c echo.Context) error {
// Extract user ID from JWT token context
currentUserID, err := GetUserIDFromContext(c)
if err != nil {
return response.Unauthorized(c, "User not authenticated")
}
idStr := c.Param("id")
userID, err := uuid.Parse(idStr)
if err != nil {
return response.BadRequest(c, "Invalid user ID", "")
}
var form UpdateUserForm
if err := c.Bind(&form); err != nil {
return response.BadRequest(c, "Invalid request format", "")
}
// Validate form
if _, err := govalidator.ValidateStruct(form); err != nil {
return response.ValidationError(c, err.Error(), "")
}
// Call service
user, err := h.service.UpdateUser(c.Request().Context(), userID, &form, &currentUserID)
if err != nil {
return response.BadRequest(c, err.Error(), "")
}
return response.Success(c, user.ToResponse(), "User updated successfully")
}
// DeleteUser deletes a user (admin only)
// @Summary Delete user
// @Description Delete a user (admin only)
// @Tags users
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param id path string true "User ID"
// @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 /users/admin/{id} [delete]
func (h *Handler) DeleteUser(c echo.Context) error {
// Extract user ID from JWT token context
currentUserID, err := GetUserIDFromContext(c)
if err != nil {
return response.Unauthorized(c, "User not authenticated")
}
idStr := c.Param("id")
userID, err := uuid.Parse(idStr)
if err != nil {
return response.BadRequest(c, "Invalid user ID", "")
}
err = h.service.DeleteUser(c.Request().Context(), userID, &currentUserID)
if err != nil {
return response.BadRequest(c, err.Error(), "")
}
return response.Success(c, map[string]interface{}{
"message": "User deleted successfully",
}, "User deleted successfully")
}
// UpdateUserStatus updates user status (admin only)
// @Summary Update user status
// @Description Update user account status (admin only)
// @Tags users
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param id path string true "User ID"
// @Param status body UpdateUserStatusForm true "Status update data"
// @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 /users/admin/{id}/status [put]
func (h *Handler) UpdateUserStatus(c echo.Context) error {
currentUserID, err := GetUserIDFromContext(c)
if err != nil {
return response.Unauthorized(c, "User not authenticated")
}
idStr := c.Param("id")
userID, err := uuid.Parse(idStr)
if err != nil {
return response.BadRequest(c, "Invalid user ID", "")
}
var form UpdateUserStatusForm
if err := c.Bind(&form); err != nil {
return response.BadRequest(c, "Invalid request format", "")
}
// Validate form
if _, err := govalidator.ValidateStruct(form); err != nil {
return response.ValidationError(c, err.Error(), "")
}
// Call service
err = h.service.UpdateUserStatus(c.Request().Context(), userID, &form, &currentUserID)
if err != nil {
return response.BadRequest(c, err.Error(), "")
}
return response.Success(c, map[string]interface{}{
"message": "User status updated successfully",
}, "User status updated successfully")
}
// UpdateUserRole updates user role (admin only)
// @Summary Update user role
// @Description Update user role (admin only)
// @Tags users
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param id path string true "User ID"
// @Param role body UpdateUserRoleForm true "Role update data"
// @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 /users/admin/{id}/role [put]
func (h *Handler) UpdateUserRole(c echo.Context) error {
// Extract user ID from JWT token context
currentUserID, err := GetUserIDFromContext(c)
if err != nil {
return response.Unauthorized(c, "User not authenticated")
}
idStr := c.Param("id")
userID, err := uuid.Parse(idStr)
if err != nil {
return response.BadRequest(c, "Invalid user ID", "")
}
var form UpdateUserRoleForm
if err := c.Bind(&form); err != nil {
return response.BadRequest(c, "Invalid request format", "")
}
// Validate form
if _, err := govalidator.ValidateStruct(form); err != nil {
return response.ValidationError(c, err.Error(), "")
}
// Call service
err = h.service.UpdateUserRole(c.Request().Context(), userID, &form, &currentUserID)
if err != nil {
return response.BadRequest(c, err.Error(), "")
}
return response.Success(c, map[string]interface{}{
"message": "User role updated successfully",
}, "User role updated successfully")
}
// GetUsersByCompanyID gets users by company ID (admin only)
// @Summary Get users by company ID
// @Description Get users belonging to a specific company (admin only)
// @Tags users
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param company_id path string true "Company ID"
// @Param limit query int false "Limit results"
// @Param offset query int false "Offset results"
// @Success 200 {object} response.APIResponse
// @Failure 400 {object} response.APIResponse
// @Failure 401 {object} response.APIResponse
// @Failure 403 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Router /users/admin/company/{company_id} [get]
func (h *Handler) GetUsersByCompanyID(c echo.Context) error {
companyIDStr := c.Param("company_id")
companyID, err := uuid.Parse(companyIDStr)
if err != nil {
return response.BadRequest(c, "Invalid company ID", "")
}
// Get query parameters
limitStr := c.QueryParam("limit")
if limitStr == "" {
limitStr = "20"
}
offsetStr := c.QueryParam("offset")
if offsetStr == "" {
offsetStr = "0"
}
limit, err := strconv.Atoi(limitStr)
if err != nil {
return response.BadRequest(c, "Invalid limit parameter", "")
}
offset, err := strconv.Atoi(offsetStr)
if err != nil {
return response.BadRequest(c, "Invalid offset parameter", "")
}
// Call service
users, total, err := h.service.GetUsersByCompanyID(c.Request().Context(), companyID, limit, offset)
if err != nil {
return response.InternalServerError(c, "Failed to get users by company")
}
// Convert to responses
var userResponses []*UserResponse
for _, user := range users {
userResponses = append(userResponses, user.ToResponse())
}
return response.SuccessWithMeta(c, map[string]interface{}{
"users": userResponses,
}, &response.Meta{
Total: int(total),
Limit: limit,
Offset: offset,
}, "Users retrieved successfully")
}
// GetUsersByRole gets users by role (admin only)
// @Summary Get users by role
// @Description Get users with a specific role (admin only)
// @Tags users
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param role path string true "User role"
// @Param limit query int false "Limit results"
// @Param offset query int false "Offset results"
// @Success 200 {object} response.APIResponse
// @Failure 400 {object} response.APIResponse
// @Failure 401 {object} response.APIResponse
// @Failure 403 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Router /users/admin/role/{role} [get]
func (h *Handler) GetUsersByRole(c echo.Context) error {
roleStr := c.Param("role")
role := UserRole(roleStr)
// Validate role
if !h.validator.IsValidRole(role) {
return response.BadRequest(c, "Invalid role", "")
}
// Get query parameters
limitStr := c.QueryParam("limit")
if limitStr == "" {
limitStr = "20"
}
offsetStr := c.QueryParam("offset")
if offsetStr == "" {
offsetStr = "0"
}
limit, err := strconv.Atoi(limitStr)
if err != nil {
return response.BadRequest(c, "Invalid limit parameter", "")
}
offset, err := strconv.Atoi(offsetStr)
if err != nil {
return response.BadRequest(c, "Invalid offset parameter", "")
}
// Call service
users, err := h.service.GetUsersByRole(c.Request().Context(), role, limit, offset)
if err != nil {
return response.InternalServerError(c, "Failed to get users by role")
}
// Convert to responses
var userResponses []*UserResponse
for _, user := range users {
userResponses = append(userResponses, user.ToResponse())
}
return response.SuccessWithMeta(c, map[string]interface{}{
"users": userResponses,
"role": role,
}, &response.Meta{
Limit: limit,
Offset: offset,
}, "Users retrieved successfully")
}
// AuthMiddleware and AdminMiddleware are now implemented in middleware.go