Files
skybridge/user/internal/handlers/user_handler.go
2025-08-31 22:35:23 -04:00

280 lines
6.6 KiB
Go

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"
}