package handler import ( "context" "net/http" "strconv" "time" "eslogad-be/internal/appcontext" "eslogad-be/internal/constants" "eslogad-be/internal/contract" "eslogad-be/internal/logger" "github.com/gin-gonic/gin" "github.com/google/uuid" ) type UserHandler struct { userService UserService userValidator UserValidator } func NewUserHandler(userService UserService, userValidator UserValidator) *UserHandler { return &UserHandler{ userService: userService, userValidator: userValidator, } } func (h *UserHandler) CreateUser(c *gin.Context) { var req contract.CreateUserRequest if err := c.ShouldBindJSON(&req); err != nil { logger.FromContext(c).WithError(err).Error("UserHandler::CreateUser -> request binding failed") h.sendValidationErrorResponse(c, "Invalid request body", constants.MissingFieldErrorCode) return } validationError, validationErrorCode := h.userValidator.ValidateCreateUserRequest(&req) if validationError != nil { logger.FromContext(c).WithError(validationError).Error("UserHandler::CreateUser -> request validation failed") h.sendValidationErrorResponse(c, validationError.Error(), validationErrorCode) return } userResponse, err := h.userService.CreateUser(c.Request.Context(), &req) if err != nil { logger.FromContext(c).WithError(err).Error("UserHandler::CreateUser -> Failed to create user from service") h.sendErrorResponse(c, err.Error(), http.StatusInternalServerError) return } logger.FromContext(c).Infof("UserHandler::CreateUser -> Successfully created user = %+v", userResponse) c.JSON(http.StatusCreated, userResponse) } func (h *UserHandler) BulkCreateUsers(c *gin.Context) { var req contract.BulkCreateUsersRequest if err := c.ShouldBindJSON(&req); err != nil { logger.FromContext(c).WithError(err).Error("UserHandler::BulkCreateUsers -> request binding failed") h.sendValidationErrorResponse(c, "Invalid request body", constants.MissingFieldErrorCode) return } if len(req.Users) == 0 { h.sendValidationErrorResponse(c, "Users list cannot be empty", constants.MissingFieldErrorCode) return } // Increased limit to handle 1000+ users if len(req.Users) > 5000 { h.sendValidationErrorResponse(c, "Cannot create more than 5000 users at once", constants.MissingFieldErrorCode) return } // Set a longer timeout for large bulk operations ctx := c.Request.Context() if len(req.Users) > 500 { // Create a context with extended timeout for large operations var cancel context.CancelFunc ctx, cancel = context.WithTimeout(ctx, 10*time.Minute) defer cancel() } logger.FromContext(c).Infof("UserHandler::BulkCreateUsers -> Starting bulk creation of %d users", len(req.Users)) response, err := h.userService.BulkCreateUsers(ctx, &req) if err != nil { logger.FromContext(c).WithError(err).Error("UserHandler::BulkCreateUsers -> Failed to bulk create users") h.sendErrorResponse(c, err.Error(), http.StatusInternalServerError) return } statusCode := http.StatusCreated if response.Summary.Failed > 0 && response.Summary.Succeeded == 0 { statusCode = http.StatusBadRequest } else if response.Summary.Failed > 0 { statusCode = http.StatusMultiStatus } logger.FromContext(c).Infof("UserHandler::BulkCreateUsers -> Successfully processed bulk creation: %d succeeded, %d failed", response.Summary.Succeeded, response.Summary.Failed) c.JSON(statusCode, contract.BuildSuccessResponse(response)) } func (h *UserHandler) UpdateUser(c *gin.Context) { userIDStr := c.Param("id") userID, err := uuid.Parse(userIDStr) if err != nil { logger.FromContext(c).WithError(err).Error("UserHandler::UpdateUser -> Invalid user ID") h.sendValidationErrorResponse(c, "Invalid user ID", constants.MalformedFieldErrorCode) return } validationError, validationErrorCode := h.userValidator.ValidateUserID(userID) if validationError != nil { logger.FromContext(c).WithError(validationError).Error("UserHandler::UpdateUser -> user ID validation failed") h.sendValidationErrorResponse(c, validationError.Error(), validationErrorCode) return } var req contract.UpdateUserRequest if err := c.ShouldBindJSON(&req); err != nil { logger.FromContext(c).WithError(err).Error("UserHandler::UpdateUser -> request binding failed") h.sendValidationErrorResponse(c, "Invalid request body", constants.MissingFieldErrorCode) return } validationError, validationErrorCode = h.userValidator.ValidateUpdateUserRequest(&req) if validationError != nil { logger.FromContext(c).WithError(validationError).Error("UserHandler::UpdateUser -> request validation failed") h.sendValidationErrorResponse(c, validationError.Error(), validationErrorCode) return } userResponse, err := h.userService.UpdateUser(c.Request.Context(), userID, &req) if err != nil { logger.FromContext(c).WithError(err).Error("UserHandler::UpdateUser -> Failed to update user from service") h.sendErrorResponse(c, err.Error(), http.StatusInternalServerError) return } logger.FromContext(c).Infof("UserHandler::UpdateUser -> Successfully updated user = %+v", userResponse) c.JSON(http.StatusOK, userResponse) } func (h *UserHandler) DeleteUser(c *gin.Context) { userIDStr := c.Param("id") userID, err := uuid.Parse(userIDStr) if err != nil { logger.FromContext(c).WithError(err).Error("UserHandler::DeleteUser -> Invalid user ID") h.sendValidationErrorResponse(c, "Invalid user ID", constants.MalformedFieldErrorCode) return } validationError, validationErrorCode := h.userValidator.ValidateUserID(userID) if validationError != nil { logger.FromContext(c).WithError(validationError).Error("UserHandler::DeleteUser -> user ID validation failed") h.sendValidationErrorResponse(c, validationError.Error(), validationErrorCode) return } err = h.userService.DeleteUser(c.Request.Context(), userID) if err != nil { logger.FromContext(c).WithError(err).Error("UserHandler::DeleteUser -> Failed to delete user from service") h.sendErrorResponse(c, err.Error(), http.StatusInternalServerError) return } logger.FromContext(c).Info("UserHandler::DeleteUser -> Successfully deleted user") c.JSON(http.StatusOK, &contract.SuccessResponse{Message: "User deleted successfully"}) } func (h *UserHandler) GetUser(c *gin.Context) { userIDStr := c.Param("id") userID, err := uuid.Parse(userIDStr) if err != nil { logger.FromContext(c).WithError(err).Error("UserHandler::GetUser -> Invalid user ID") h.sendValidationErrorResponse(c, "Invalid user ID", constants.MalformedFieldErrorCode) return } validationError, validationErrorCode := h.userValidator.ValidateUserID(userID) if validationError != nil { logger.FromContext(c).WithError(validationError).Error("UserHandler::GetUser -> user ID validation failed") h.sendValidationErrorResponse(c, validationError.Error(), validationErrorCode) return } userResponse, err := h.userService.GetUserByID(c.Request.Context(), userID) if err != nil { logger.FromContext(c).WithError(err).Error("UserHandler::GetUser -> Failed to get user from service") h.sendErrorResponse(c, err.Error(), http.StatusInternalServerError) return } logger.FromContext(c).Infof("UserHandler::GetUser -> Successfully retrieved user = %+v", userResponse) c.JSON(http.StatusOK, userResponse) } func (h *UserHandler) ListUsers(c *gin.Context) { ctx := c.Request.Context() req := &contract.ListUsersRequest{ Page: 1, Limit: 10, } if page := c.Query("page"); page != "" { if p, err := strconv.Atoi(page); err == nil { req.Page = p } } if limit := c.Query("limit"); limit != "" { if l, err := strconv.Atoi(limit); err == nil { req.Limit = l } } var roleParam *string if role := c.Query("role"); role != "" { roleParam = &role req.Role = &role } if roleCode := c.Query("role_code"); roleCode != "" { req.RoleCode = &roleCode } if req.RoleCode == nil && roleParam != nil { req.RoleCode = roleParam } if search := c.Query("search"); search != "" { req.Search = &search } if isActiveStr := c.Query("is_active"); isActiveStr != "" { if isActive, err := strconv.ParseBool(isActiveStr); err == nil { req.IsActive = &isActive } } validationError, validationErrorCode := h.userValidator.ValidateListUsersRequest(req) if validationError != nil { logger.FromContext(c).WithError(validationError).Error("UserHandler::ListUsers -> request validation failed") h.sendValidationErrorResponse(c, validationError.Error(), validationErrorCode) return } usersResponse, err := h.userService.ListUsers(ctx, req) if err != nil { logger.FromContext(c).WithError(err).Error("UserHandler::ListUsers -> Failed to list users from service") h.sendErrorResponse(c, err.Error(), http.StatusInternalServerError) return } logger.FromContext(c).Infof("UserHandler::ListUsers -> Successfully listed users = %+v", usersResponse) c.JSON(http.StatusOK, contract.BuildSuccessResponse(usersResponse)) } func (h *UserHandler) ChangePassword(c *gin.Context) { userIDStr := c.Param("id") userID, err := uuid.Parse(userIDStr) if err != nil { logger.FromContext(c).WithError(err).Error("UserHandler::ChangePassword -> Invalid user ID") h.sendValidationErrorResponse(c, "Invalid user ID", constants.MalformedFieldErrorCode) return } validationError, validationErrorCode := h.userValidator.ValidateUserID(userID) if validationError != nil { logger.FromContext(c).WithError(validationError).Error("UserHandler::ChangePassword -> user ID validation failed") h.sendValidationErrorResponse(c, validationError.Error(), validationErrorCode) return } var req contract.ChangePasswordRequest if err := c.ShouldBindJSON(&req); err != nil { logger.FromContext(c).WithError(err).Error("UserHandler::ChangePassword -> request binding failed") h.sendValidationErrorResponse(c, "Invalid request body", constants.MissingFieldErrorCode) return } validationError, validationErrorCode = h.userValidator.ValidateChangePasswordRequest(&req) if validationError != nil { logger.FromContext(c).WithError(validationError).Error("UserHandler::ChangePassword -> request validation failed") h.sendValidationErrorResponse(c, validationError.Error(), validationErrorCode) return } err = h.userService.ChangePassword(c.Request.Context(), userID, &req) if err != nil { logger.FromContext(c).WithError(err).Error("UserHandler::ChangePassword -> Failed to change password from service") h.sendErrorResponse(c, err.Error(), http.StatusInternalServerError) return } logger.FromContext(c).Info("UserHandler::ChangePassword -> Successfully changed password") c.JSON(http.StatusOK, &contract.SuccessResponse{Message: "Password changed successfully"}) } func (h *UserHandler) GetProfile(c *gin.Context) { appCtx := appcontext.FromGinContext(c.Request.Context()) if appCtx.UserID == uuid.Nil { h.sendErrorResponse(c, "Unauthorized", http.StatusUnauthorized) return } profile, err := h.userService.GetProfile(c.Request.Context(), appCtx.UserID) if err != nil { logger.FromContext(c).WithError(err).Error("UserHandler::GetProfile -> Failed to get profile") h.sendErrorResponse(c, err.Error(), http.StatusInternalServerError) return } c.JSON(http.StatusOK, contract.BuildSuccessResponse(profile)) } func (h *UserHandler) UpdateProfile(c *gin.Context) { appCtx := appcontext.FromGinContext(c.Request.Context()) if appCtx.UserID == uuid.Nil { h.sendErrorResponse(c, "Unauthorized", http.StatusUnauthorized) return } var req contract.UpdateUserProfileRequest if err := c.ShouldBindJSON(&req); err != nil { h.sendValidationErrorResponse(c, "Invalid request body", constants.MissingFieldErrorCode) return } updated, err := h.userService.UpdateProfile(c.Request.Context(), appCtx.UserID, &req) if err != nil { logger.FromContext(c).WithError(err).Error("UserHandler::UpdateProfile -> Failed to update profile") h.sendErrorResponse(c, err.Error(), http.StatusInternalServerError) return } c.JSON(http.StatusOK, contract.BuildSuccessResponse(updated)) } func (h *UserHandler) ListTitles(c *gin.Context) { titles, err := h.userService.ListTitles(c.Request.Context()) if err != nil { logger.FromContext(c).WithError(err).Error("UserHandler::ListTitles -> Failed to get titles from service") h.sendErrorResponse(c, err.Error(), http.StatusInternalServerError) return } logger.FromContext(c).Infof("UserHandler::ListTitles -> Successfully retrieved titles = %+v", titles) c.JSON(http.StatusOK, titles) } func (h *UserHandler) GetActiveUsersForMention(c *gin.Context) { search := c.Query("search") limitStr := c.DefaultQuery("limit", "50") limit, err := strconv.Atoi(limitStr) if err != nil || limit <= 0 { limit = 50 } if limit > 100 { limit = 100 } var searchPtr *string if search != "" { searchPtr = &search } users, err := h.userService.GetActiveUsersForMention(c.Request.Context(), searchPtr, limit) if err != nil { logger.FromContext(c).WithError(err).Error("UserHandler::GetActiveUsersForMention -> Failed to get active users from service") h.sendErrorResponse(c, err.Error(), http.StatusInternalServerError) return } response := contract.MentionUsersResponse{ Users: users, Count: len(users), } logger.FromContext(c).Infof("UserHandler::GetActiveUsersForMention -> Successfully retrieved %d active users", len(users)) c.JSON(http.StatusOK, response) } func (h *UserHandler) sendErrorResponse(c *gin.Context, message string, statusCode int) { errorResponse := &contract.ErrorResponse{ Error: message, Code: statusCode, Details: map[string]interface{}{}, } c.JSON(statusCode, errorResponse) } func (h *UserHandler) sendValidationErrorResponse(c *gin.Context, message string, errorCode string) { statusCode := constants.HttpErrorMap[errorCode] if statusCode == 0 { statusCode = http.StatusBadRequest } errorResponse := &contract.ErrorResponse{ Error: message, Code: statusCode, Details: map[string]interface{}{ "error_code": errorCode, "entity": constants.UserValidatorEntity, }, } c.JSON(statusCode, errorResponse) }