package handlers import ( "net/http" "strconv" "github.com/gin-gonic/gin" "github.com/google/uuid" "go.uber.org/zap" "github.com/RyanCopley/skybridge/user/internal/domain" "github.com/RyanCopley/skybridge/user/internal/services" ) // UserHandler handles HTTP requests for user operations type UserHandler struct { userService services.UserService logger *zap.Logger } // NewUserHandler creates a new user handler func NewUserHandler(userService services.UserService, logger *zap.Logger) *UserHandler { return &UserHandler{ userService: userService, logger: logger, } } // Create handles POST /users func (h *UserHandler) Create(c *gin.Context) { var req domain.CreateUserRequest if err := c.ShouldBindJSON(&req); err != nil { h.logger.Error("Invalid request body", zap.Error(err)) c.JSON(http.StatusBadRequest, gin.H{ "error": "Invalid request body", "details": err.Error(), }) return } // Get actor from context (set by auth middleware) actorID := getActorFromContext(c) user, err := h.userService.Create(c.Request.Context(), &req, actorID) if err != nil { h.logger.Error("Failed to create user", zap.Error(err)) c.JSON(http.StatusInternalServerError, gin.H{ "error": "Failed to create user", "details": err.Error(), }) return } c.JSON(http.StatusCreated, user) } // GetByID handles GET /users/:id func (h *UserHandler) GetByID(c *gin.Context) { idParam := c.Param("id") id, err := uuid.Parse(idParam) if err != nil { c.JSON(http.StatusBadRequest, gin.H{ "error": "Invalid user ID", "details": "User ID must be a valid UUID", }) return } user, err := h.userService.GetByID(c.Request.Context(), id) if err != nil { if err.Error() == "user not found" { c.JSON(http.StatusNotFound, gin.H{ "error": "User not found", }) return } h.logger.Error("Failed to get user", zap.String("id", id.String()), zap.Error(err)) c.JSON(http.StatusInternalServerError, gin.H{ "error": "Failed to get user", "details": err.Error(), }) return } c.JSON(http.StatusOK, user) } // Update handles PUT /users/:id func (h *UserHandler) Update(c *gin.Context) { idParam := c.Param("id") id, err := uuid.Parse(idParam) if err != nil { c.JSON(http.StatusBadRequest, gin.H{ "error": "Invalid user ID", "details": "User ID must be a valid UUID", }) return } var req domain.UpdateUserRequest if err := c.ShouldBindJSON(&req); err != nil { h.logger.Error("Invalid request body", zap.Error(err)) c.JSON(http.StatusBadRequest, gin.H{ "error": "Invalid request body", "details": err.Error(), }) return } // Get actor from context (set by auth middleware) actorID := getActorFromContext(c) user, err := h.userService.Update(c.Request.Context(), id, &req, actorID) if err != nil { if err.Error() == "user not found" { c.JSON(http.StatusNotFound, gin.H{ "error": "User not found", }) return } h.logger.Error("Failed to update user", zap.String("id", id.String()), zap.Error(err)) c.JSON(http.StatusInternalServerError, gin.H{ "error": "Failed to update user", "details": err.Error(), }) return } c.JSON(http.StatusOK, user) } // Delete handles DELETE /users/:id func (h *UserHandler) Delete(c *gin.Context) { idParam := c.Param("id") id, err := uuid.Parse(idParam) if err != nil { c.JSON(http.StatusBadRequest, gin.H{ "error": "Invalid user ID", "details": "User ID must be a valid UUID", }) return } // Get actor from context (set by auth middleware) actorID := getActorFromContext(c) err = h.userService.Delete(c.Request.Context(), id, actorID) if err != nil { if err.Error() == "user not found" { c.JSON(http.StatusNotFound, gin.H{ "error": "User not found", }) return } h.logger.Error("Failed to delete user", zap.String("id", id.String()), zap.Error(err)) c.JSON(http.StatusInternalServerError, gin.H{ "error": "Failed to delete user", "details": err.Error(), }) return } c.JSON(http.StatusNoContent, nil) } // List handles GET /users func (h *UserHandler) List(c *gin.Context) { var req domain.ListUsersRequest // Parse query parameters if status := c.Query("status"); status != "" { s := domain.UserStatus(status) req.Status = &s } if role := c.Query("role"); role != "" { r := domain.UserRole(role) req.Role = &r } req.Search = c.Query("search") if limit := c.Query("limit"); limit != "" { if l, err := strconv.Atoi(limit); err == nil && l > 0 { req.Limit = l } } if offset := c.Query("offset"); offset != "" { if o, err := strconv.Atoi(offset); err == nil && o >= 0 { req.Offset = o } } req.OrderBy = c.DefaultQuery("order_by", "created_at") req.OrderDir = c.DefaultQuery("order_dir", "desc") response, err := h.userService.List(c.Request.Context(), &req) if err != nil { h.logger.Error("Failed to list users", zap.Error(err)) c.JSON(http.StatusInternalServerError, gin.H{ "error": "Failed to list users", "details": err.Error(), }) return } c.JSON(http.StatusOK, response) } // GetByEmail handles GET /users/email/:email func (h *UserHandler) GetByEmail(c *gin.Context) { email := c.Param("email") if email == "" { c.JSON(http.StatusBadRequest, gin.H{ "error": "Email parameter is required", }) return } user, err := h.userService.GetByEmail(c.Request.Context(), email) if err != nil { if err.Error() == "user not found" { c.JSON(http.StatusNotFound, gin.H{ "error": "User not found", }) return } h.logger.Error("Failed to get user by email", zap.String("email", email), zap.Error(err)) c.JSON(http.StatusInternalServerError, gin.H{ "error": "Failed to get user", "details": err.Error(), }) return } c.JSON(http.StatusOK, user) } // ExistsByEmail handles GET /users/exists/:email func (h *UserHandler) ExistsByEmail(c *gin.Context) { email := c.Param("email") if email == "" { c.JSON(http.StatusBadRequest, gin.H{ "error": "Email parameter is required", }) return } exists, err := h.userService.ExistsByEmail(c.Request.Context(), email) if err != nil { h.logger.Error("Failed to check user existence", zap.String("email", email), zap.Error(err)) c.JSON(http.StatusInternalServerError, gin.H{ "error": "Failed to check user existence", "details": err.Error(), }) return } c.JSON(http.StatusOK, gin.H{ "exists": exists, "email": email, }) } // Helper function to get actor from gin context func getActorFromContext(c *gin.Context) string { if actor, exists := c.Get("actor_id"); exists { if actorStr, ok := actor.(string); ok { return actorStr } } // Fallback to email header if available if email := c.GetHeader("X-User-Email"); email != "" { return email } return "system" }