package handler import ( "net/http" "strconv" "bugulma/backend/internal/domain" "bugulma/backend/internal/service" "github.com/gin-gonic/gin" ) type UserHandler struct { userService *service.UserService } func NewUserHandler(userService *service.UserService) *UserHandler { return &UserHandler{ userService: userService, } } // ListUsers lists users with filters and pagination (admin only) // @Summary List users // @Tags admin // @Produce json // @Param role query string false "Filter by role" // @Param isActive query bool false "Filter by active status" // @Param search query string false "Search in email and name" // @Param limit query int false "Limit" default(25) // @Param offset query int false "Offset" default(0) // @Success 200 {object} map[string]interface{} // @Router /api/v1/admin/users [get] func (h *UserHandler) ListUsers(c *gin.Context) { filters := domain.UserListFilters{} // Parse role filter if roleStr := c.Query("role"); roleStr != "" { role := domain.UserRole(roleStr) filters.Role = &role } // Parse isActive filter if isActiveStr := c.Query("isActive"); isActiveStr != "" { isActive := isActiveStr == "true" filters.IsActive = &isActive } // Parse search filters.Search = c.Query("search") // Parse pagination limit := 25 offset := 0 if limitStr := c.Query("limit"); limitStr != "" { if parsed, err := strconv.Atoi(limitStr); err == nil { limit = parsed } } if offsetStr := c.Query("offset"); offsetStr != "" { if parsed, err := strconv.Atoi(offsetStr); err == nil { offset = parsed } } pagination := domain.PaginationParams{ Limit: limit, Offset: offset, } result, err := h.userService.ListUsers(c.Request.Context(), filters, pagination) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{ "users": result.Items, "total": result.Total, "limit": limit, "offset": offset, }) } // GetUser gets a user by ID (admin only) // @Summary Get user // @Tags admin // @Produce json // @Param id path string true "User ID" // @Success 200 {object} domain.User // @Router /api/v1/admin/users/:id [get] func (h *UserHandler) GetUser(c *gin.Context) { userID := c.Param("id") user, err := h.userService.GetUser(c.Request.Context(), userID) if err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "User not found"}) return } // Don't return password user.Password = "" c.JSON(http.StatusOK, user) } // CreateUser creates a new user (admin only) // @Summary Create user // @Tags admin // @Accept json // @Produce json // @Param request body CreateUserRequest true "User request" // @Success 201 {object} domain.User // @Router /api/v1/admin/users [post] func (h *UserHandler) CreateUser(c *gin.Context) { var req CreateUserRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } createReq := service.CreateUserRequest{ Email: req.Email, Name: req.Name, Password: req.Password, Role: domain.UserRole(req.Role), Permissions: req.Permissions, } user, err := h.userService.CreateUser(c.Request.Context(), createReq) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } // Don't return password user.Password = "" c.JSON(http.StatusCreated, user) } type CreateUserRequest struct { Email string `json:"email" binding:"required,email"` Name string `json:"name" binding:"required"` Password string `json:"password" binding:"required,min=8"` Role string `json:"role" binding:"required"` Permissions []string `json:"permissions"` } // UpdateUser updates a user (admin only) // @Summary Update user // @Tags admin // @Accept json // @Produce json // @Param id path string true "User ID" // @Param request body UpdateUserRequest true "Update request" // @Success 200 {object} domain.User // @Router /api/v1/admin/users/:id [put] func (h *UserHandler) UpdateUser(c *gin.Context) { userID := c.Param("id") var req UpdateUserRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } updateReq := service.UpdateUserRequest{} if req.Name != nil { updateReq.Name = req.Name } if req.Email != nil { updateReq.Email = req.Email } if req.Role != nil { role := domain.UserRole(*req.Role) updateReq.Role = &role } if req.Permissions != nil { updateReq.Permissions = req.Permissions } if req.IsActive != nil { updateReq.IsActive = req.IsActive } user, err := h.userService.UpdateUser(c.Request.Context(), userID, updateReq) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } // Don't return password user.Password = "" c.JSON(http.StatusOK, user) } type UpdateUserRequest struct { Name *string `json:"name"` Email *string `json:"email"` Role *string `json:"role"` Permissions *[]string `json:"permissions"` IsActive *bool `json:"isActive"` } // UpdateRole updates a user's role (admin only) // @Summary Update user role // @Tags admin // @Accept json // @Produce json // @Param id path string true "User ID" // @Param request body UpdateRoleRequest true "Role request" // @Success 200 {object} map[string]string // @Router /api/v1/admin/users/:id/role [patch] func (h *UserHandler) UpdateRole(c *gin.Context) { userID := c.Param("id") var req UpdateRoleRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } if err := h.userService.UpdateRole(c.Request.Context(), userID, domain.UserRole(req.Role)); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"message": "Role updated"}) } type UpdateRoleRequest struct { Role string `json:"role" binding:"required"` } // UpdatePermissions updates a user's permissions (admin only) // @Summary Update user permissions // @Tags admin // @Accept json // @Produce json // @Param id path string true "User ID" // @Param request body UpdatePermissionsRequest true "Permissions request" // @Success 200 {object} map[string]string // @Router /api/v1/admin/users/:id/permissions [patch] func (h *UserHandler) UpdatePermissions(c *gin.Context) { userID := c.Param("id") var req UpdatePermissionsRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } if err := h.userService.UpdatePermissions(c.Request.Context(), userID, req.Permissions); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"message": "Permissions updated"}) } type UpdatePermissionsRequest struct { Permissions []string `json:"permissions" binding:"required"` } // DeactivateUser deactivates a user (admin only) // @Summary Deactivate user // @Tags admin // @Success 200 {object} map[string]string // @Router /api/v1/admin/users/:id [delete] func (h *UserHandler) DeactivateUser(c *gin.Context) { userID := c.Param("id") if err := h.userService.DeactivateUser(c.Request.Context(), userID); err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "User not found"}) return } c.JSON(http.StatusOK, gin.H{"message": "User deactivated"}) } // GetUserActivity gets activity log for a user (admin only) // @Summary Get user activity // @Tags admin // @Produce json // @Param id path string true "User ID" // @Param limit query int false "Limit" default(50) // @Param offset query int false "Offset" default(0) // @Success 200 {object} map[string]interface{} // @Router /api/v1/admin/users/:id/activity [get] func (h *UserHandler) GetUserActivity(c *gin.Context) { userID := c.Param("id") limit := 50 offset := 0 if limitStr := c.Query("limit"); limitStr != "" { if parsed, err := strconv.Atoi(limitStr); err == nil { limit = parsed } } if offsetStr := c.Query("offset"); offsetStr != "" { if parsed, err := strconv.Atoi(offsetStr); err == nil { offset = parsed } } activities, total, err := h.userService.GetUserActivity(c.Request.Context(), userID, limit, offset) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{ "activities": activities, "total": total, "limit": limit, "offset": offset, }) } // GetUserStats gets user statistics (admin only) // @Summary Get user statistics // @Tags admin // @Produce json // @Success 200 {object} map[string]interface{} // @Router /api/v1/admin/users/stats [get] func (h *UserHandler) GetUserStats(c *gin.Context) { stats, err := h.userService.GetUserStats(c.Request.Context()) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, stats) }