280 lines
6.6 KiB
Go
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"
|
|
} |