diff --git a/veza-backend-api/internal/handlers/admin_transfer_handler.go b/veza-backend-api/internal/handlers/admin_transfer_handler.go index fb164898a..6ff3907ae 100644 --- a/veza-backend-api/internal/handlers/admin_transfer_handler.go +++ b/veza-backend-api/internal/handlers/admin_transfer_handler.go @@ -9,6 +9,7 @@ import ( "go.uber.org/zap" "gorm.io/gorm" + apperrors "veza-backend-api/internal/errors" "veza-backend-api/internal/core/marketplace" ) @@ -41,7 +42,7 @@ func (h *AdminTransferHandler) GetTransfers(c *gin.Context) { if sellerIDStr := c.Query("seller_id"); sellerIDStr != "" { sellerID, err := uuid.Parse(sellerIDStr) if err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": "invalid seller_id"}) + RespondWithAppError(c, apperrors.NewValidationError("invalid seller_id")) return } query = query.Where("seller_id = ?", sellerID) @@ -56,7 +57,7 @@ func (h *AdminTransferHandler) GetTransfers(c *gin.Context) { var total int64 if err := query.Count(&total).Error; err != nil { h.logger.Error("GetTransfers count failed", zap.Error(err)) - c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to count transfers"}) + RespondWithAppError(c, apperrors.NewInternalErrorWrap("Failed to count transfers", err)) return } @@ -76,7 +77,7 @@ func (h *AdminTransferHandler) GetTransfers(c *gin.Context) { var transfers []marketplace.SellerTransfer if err := query.Order("created_at DESC").Limit(limit).Offset(offset).Find(&transfers).Error; err != nil { h.logger.Error("GetTransfers find failed", zap.Error(err)) - c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch transfers"}) + RespondWithAppError(c, apperrors.NewInternalErrorWrap("Failed to fetch transfers", err)) return } @@ -89,34 +90,34 @@ func (h *AdminTransferHandler) GetTransfers(c *gin.Context) { // RetryTransfer manually retries a failed transfer. func (h *AdminTransferHandler) RetryTransfer(c *gin.Context) { if h.ts == nil { - c.JSON(http.StatusServiceUnavailable, gin.H{"error": "Stripe Connect is not enabled"}) + RespondWithAppError(c, apperrors.NewServiceUnavailableError("Stripe Connect is not enabled")) return } idStr := c.Param("id") if idStr == "" { - c.JSON(http.StatusBadRequest, gin.H{"error": "transfer id required"}) + RespondWithAppError(c, apperrors.NewValidationError("transfer id required")) return } transferID, err := uuid.Parse(idStr) if err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": "invalid transfer id"}) + RespondWithAppError(c, apperrors.NewValidationError("invalid transfer id")) return } var t marketplace.SellerTransfer if err := h.db.First(&t, transferID).Error; err != nil { if err == gorm.ErrRecordNotFound { - c.JSON(http.StatusNotFound, gin.H{"error": "transfer not found"}) + RespondWithAppError(c, apperrors.NewNotFoundError("transfer")) return } h.logger.Error("RetryTransfer find failed", zap.Error(err), zap.String("transfer_id", idStr)) - c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch transfer"}) + RespondWithAppError(c, apperrors.NewInternalErrorWrap("Failed to fetch transfer", err)) return } if t.Status != "failed" { - c.JSON(http.StatusBadRequest, gin.H{"error": "only failed transfers can be retried"}) + RespondWithAppError(c, apperrors.NewValidationError("only failed transfers can be retried")) return } @@ -129,7 +130,7 @@ func (h *AdminTransferHandler) RetryTransfer(c *gin.Context) { h.logger.Error("RetryTransfer save failed", zap.Error(saveErr)) } h.logger.Error("RetryTransfer CreateTransfer failed", zap.Error(err), zap.String("transfer_id", idStr)) - c.JSON(http.StatusInternalServerError, gin.H{"error": "Transfer failed: " + err.Error()}) + RespondWithAppError(c, apperrors.NewInternalErrorWrap("Transfer failed", err)) return } @@ -137,7 +138,7 @@ func (h *AdminTransferHandler) RetryTransfer(c *gin.Context) { t.ErrorMessage = "" if err := h.db.Save(&t).Error; err != nil { h.logger.Error("RetryTransfer save failed", zap.Error(err)) - c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update transfer"}) + RespondWithAppError(c, apperrors.NewInternalErrorWrap("Failed to update transfer", err)) return } diff --git a/veza-backend-api/internal/handlers/chat_attachment_handler.go b/veza-backend-api/internal/handlers/chat_attachment_handler.go index c08ae9f26..fdd8ad949 100644 --- a/veza-backend-api/internal/handlers/chat_attachment_handler.go +++ b/veza-backend-api/internal/handlers/chat_attachment_handler.go @@ -6,8 +6,8 @@ import ( "net/http" "path/filepath" + apperrors "veza-backend-api/internal/errors" "veza-backend-api/internal/services" - chatws "veza-backend-api/internal/websocket/chat" "github.com/gin-gonic/gin" @@ -55,64 +55,64 @@ func (h *ChatAttachmentHandler) UploadChatAttachment(c *gin.Context) { roomID, err := uuid.Parse(c.Param("roomId")) if err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid room ID"}) + RespondWithAppError(c, apperrors.NewValidationError("Invalid room ID")) return } if !h.permissions.CanRead(c.Request.Context(), userID, roomID) { - c.JSON(http.StatusForbidden, gin.H{"error": "Not allowed to access this conversation"}) + RespondWithAppError(c, apperrors.NewForbiddenError("Not allowed to access this conversation")) return } fileHeader, err := c.FormFile("file") if err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": "No file provided"}) + RespondWithAppError(c, apperrors.NewValidationError("No file provided")) return } if fileHeader.Size > chatAttachmentMaxSize { - c.JSON(http.StatusBadRequest, gin.H{"error": "File too large. Max 50MB"}) + RespondWithAppError(c, apperrors.NewValidationError("File too large. Max 50MB")) return } if fileHeader.Size == 0 { - c.JSON(http.StatusBadRequest, gin.H{"error": "Empty file"}) + RespondWithAppError(c, apperrors.NewValidationError("Empty file")) return } fileType := h.validator.GetFileTypeFromPath(fileHeader.Filename) if fileType == "unknown" { - c.JSON(http.StatusBadRequest, gin.H{"error": "Unsupported file type. Allowed: mp3, wav, ogg, jpg, png, pdf"}) + RespondWithAppError(c, apperrors.NewValidationError("Unsupported file type. Allowed: mp3, wav, ogg, jpg, png, pdf")) return } validationResult, err := h.validator.ValidateFile(c.Request.Context(), fileHeader, fileType) if err != nil { h.logger.Warn("Chat attachment validation failed", zap.Error(err), zap.String("filename", fileHeader.Filename)) - c.JSON(http.StatusUnprocessableEntity, gin.H{"error": "File validation failed: " + err.Error()}) + RespondWithAppError(c, apperrors.NewValidationError("File validation failed: "+err.Error())) return } if !validationResult.Valid { - c.JSON(http.StatusBadRequest, gin.H{"error": validationResult.Error}) + RespondWithAppError(c, apperrors.NewValidationError(validationResult.Error)) return } if validationResult.Quarantined { - c.JSON(http.StatusUnprocessableEntity, gin.H{"error": "File rejected: virus detected"}) + RespondWithAppError(c, apperrors.NewForbiddenError("File rejected: virus detected")) return } file, err := fileHeader.Open() if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to read file"}) + RespondWithAppError(c, apperrors.NewInternalErrorWrap("Failed to read file", err)) return } defer file.Close() data, err := io.ReadAll(file) if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to read file"}) + RespondWithAppError(c, apperrors.NewInternalErrorWrap("Failed to read file", err)) return } @@ -125,7 +125,7 @@ func (h *ChatAttachmentHandler) UploadChatAttachment(c *gin.Context) { _, err = h.s3Service.UploadFile(c.Request.Context(), data, key, fileHeader.Header.Get("Content-Type")) if err != nil { h.logger.Error("Failed to upload chat attachment", zap.Error(err), zap.String("key", key)) - c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to upload file"}) + RespondWithAppError(c, apperrors.NewInternalErrorWrap("Failed to upload file", err)) return } diff --git a/veza-backend-api/internal/handlers/chat_handler.go b/veza-backend-api/internal/handlers/chat_handler.go index fe7e3632a..9ac4260b9 100644 --- a/veza-backend-api/internal/handlers/chat_handler.go +++ b/veza-backend-api/internal/handlers/chat_handler.go @@ -84,12 +84,12 @@ func NewChatHandlerWithInterface(chatService ChatServiceInterfaceForChatHandler, func (h *ChatHandler) GetToken(c *gin.Context) { userIDVal, exists := c.Get("user_id") if !exists { - c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"}) + RespondWithAppError(c, apperrors.NewUnauthorizedError("unauthorized")) return } userID, ok := userIDVal.(uuid.UUID) if !ok || userID == uuid.Nil { - c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"}) + RespondWithAppError(c, apperrors.NewUnauthorizedError("unauthorized")) return } @@ -106,7 +106,7 @@ func (h *ChatHandler) GetToken(c *gin.Context) { token, err := h.chatService.GenerateToken(userID, username) if err != nil { h.logger.Error("Failed to generate chat token", zap.Error(err)) - c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to generate token"}) + RespondWithAppError(c, apperrors.NewInternalErrorWrap("failed to generate token", err)) return } diff --git a/veza-backend-api/internal/handlers/chat_search_handler.go b/veza-backend-api/internal/handlers/chat_search_handler.go index a9df74f38..12cd613de 100644 --- a/veza-backend-api/internal/handlers/chat_search_handler.go +++ b/veza-backend-api/internal/handlers/chat_search_handler.go @@ -4,8 +4,8 @@ import ( "net/http" "strconv" + apperrors "veza-backend-api/internal/errors" "veza-backend-api/internal/repositories" - chatws "veza-backend-api/internal/websocket/chat" "github.com/gin-gonic/gin" @@ -51,18 +51,18 @@ func (h *ChatSearchHandler) SearchMessages(c *gin.Context) { roomID, err := uuid.Parse(c.Param("roomId")) if err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid room ID"}) + RespondWithAppError(c, apperrors.NewValidationError("Invalid room ID")) return } if !h.permissions.CanRead(c.Request.Context(), userID, roomID) { - c.JSON(http.StatusForbidden, gin.H{"error": "Not allowed to access this conversation"}) + RespondWithAppError(c, apperrors.NewForbiddenError("Not allowed to access this conversation")) return } query := c.Query("q") if query == "" { - c.JSON(http.StatusBadRequest, gin.H{"error": "query parameter 'q' is required"}) + RespondWithAppError(c, apperrors.NewValidationError("query parameter 'q' is required")) return } @@ -85,7 +85,7 @@ func (h *ChatSearchHandler) SearchMessages(c *gin.Context) { messages, total, err := h.msgRepo.Search(c.Request.Context(), roomID, query, limit, offset) if err != nil { h.logger.Error("Failed to search messages", zap.Error(err)) - c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to search messages"}) + RespondWithAppError(c, apperrors.NewInternalErrorWrap("Failed to search messages", err)) return } diff --git a/veza-backend-api/internal/handlers/cloud_handler.go b/veza-backend-api/internal/handlers/cloud_handler.go index 71f6a2704..8b1d1ecf7 100644 --- a/veza-backend-api/internal/handlers/cloud_handler.go +++ b/veza-backend-api/internal/handlers/cloud_handler.go @@ -12,6 +12,7 @@ import ( "github.com/google/uuid" "go.uber.org/zap" + apperrors "veza-backend-api/internal/errors" "veza-backend-api/internal/services" ) @@ -45,7 +46,7 @@ func (h *CloudHandler) getUserID(c *gin.Context) (uuid.UUID, error) { func (h *CloudHandler) ListFolders(c *gin.Context) { userID, err := h.getUserID(c) if err != nil { - c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"}) + RespondWithAppError(c, apperrors.NewUnauthorizedError("unauthorized")) return } @@ -53,7 +54,7 @@ func (h *CloudHandler) ListFolders(c *gin.Context) { if pidStr := c.Query("parent_id"); pidStr != "" { pid, err := uuid.Parse(pidStr) if err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": "invalid parent_id"}) + RespondWithAppError(c, apperrors.NewValidationError("invalid parent_id")) return } parentID = &pid @@ -62,7 +63,7 @@ func (h *CloudHandler) ListFolders(c *gin.Context) { folders, err := h.cloudService.ListFolders(c.Request.Context(), userID, parentID) if err != nil { h.logger.Error("Failed to list folders", zap.Error(err)) - c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to list folders"}) + RespondWithAppError(c, apperrors.NewInternalErrorWrap("failed to list folders", err)) return } @@ -72,7 +73,7 @@ func (h *CloudHandler) ListFolders(c *gin.Context) { func (h *CloudHandler) CreateFolder(c *gin.Context) { userID, err := h.getUserID(c) if err != nil { - c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"}) + RespondWithAppError(c, apperrors.NewUnauthorizedError("unauthorized")) return } @@ -81,7 +82,7 @@ func (h *CloudHandler) CreateFolder(c *gin.Context) { ParentID *string `json:"parent_id"` } if err := c.ShouldBindJSON(&req); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": "name is required"}) + RespondWithAppError(c, apperrors.NewValidationError("name is required")) return } @@ -89,7 +90,7 @@ func (h *CloudHandler) CreateFolder(c *gin.Context) { if req.ParentID != nil && *req.ParentID != "" { pid, err := uuid.Parse(*req.ParentID) if err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": "invalid parent_id"}) + RespondWithAppError(c, apperrors.NewValidationError("invalid parent_id")) return } parentID = &pid @@ -98,11 +99,11 @@ func (h *CloudHandler) CreateFolder(c *gin.Context) { folder, err := h.cloudService.CreateFolder(c.Request.Context(), userID, req.Name, parentID) if err != nil { if errors.Is(err, services.ErrFolderNotFound) { - c.JSON(http.StatusNotFound, gin.H{"error": "parent folder not found"}) + RespondWithAppError(c, apperrors.NewNotFoundError("parent folder")) return } h.logger.Error("Failed to create folder", zap.Error(err)) - c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to create folder"}) + RespondWithAppError(c, apperrors.NewInternalErrorWrap("failed to create folder", err)) return } @@ -112,13 +113,13 @@ func (h *CloudHandler) CreateFolder(c *gin.Context) { func (h *CloudHandler) RenameFolder(c *gin.Context) { userID, err := h.getUserID(c) if err != nil { - c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"}) + RespondWithAppError(c, apperrors.NewUnauthorizedError("unauthorized")) return } folderID, err := uuid.Parse(c.Param("id")) if err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": "invalid folder id"}) + RespondWithAppError(c, apperrors.NewValidationError("invalid folder id")) return } @@ -126,16 +127,16 @@ func (h *CloudHandler) RenameFolder(c *gin.Context) { Name string `json:"name" binding:"required"` } if err := c.ShouldBindJSON(&req); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": "name is required"}) + RespondWithAppError(c, apperrors.NewValidationError("name is required")) return } if err := h.cloudService.RenameFolder(c.Request.Context(), userID, folderID, req.Name); err != nil { if errors.Is(err, services.ErrFolderNotFound) { - c.JSON(http.StatusNotFound, gin.H{"error": "folder not found"}) + RespondWithAppError(c, apperrors.NewNotFoundError("folder")) return } - c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to rename folder"}) + RespondWithAppError(c, apperrors.NewInternalErrorWrap("failed to rename folder", err)) return } @@ -145,22 +146,22 @@ func (h *CloudHandler) RenameFolder(c *gin.Context) { func (h *CloudHandler) DeleteFolder(c *gin.Context) { userID, err := h.getUserID(c) if err != nil { - c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"}) + RespondWithAppError(c, apperrors.NewUnauthorizedError("unauthorized")) return } folderID, err := uuid.Parse(c.Param("id")) if err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": "invalid folder id"}) + RespondWithAppError(c, apperrors.NewValidationError("invalid folder id")) return } if err := h.cloudService.DeleteFolder(c.Request.Context(), userID, folderID); err != nil { if errors.Is(err, services.ErrFolderNotFound) { - c.JSON(http.StatusNotFound, gin.H{"error": "folder not found"}) + RespondWithAppError(c, apperrors.NewNotFoundError("folder")) return } - c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to delete folder"}) + RespondWithAppError(c, apperrors.NewInternalErrorWrap("failed to delete folder", err)) return } @@ -170,7 +171,7 @@ func (h *CloudHandler) DeleteFolder(c *gin.Context) { func (h *CloudHandler) ListFiles(c *gin.Context) { userID, err := h.getUserID(c) if err != nil { - c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"}) + RespondWithAppError(c, apperrors.NewUnauthorizedError("unauthorized")) return } @@ -178,7 +179,7 @@ func (h *CloudHandler) ListFiles(c *gin.Context) { if fidStr := c.Query("folder_id"); fidStr != "" { fid, err := uuid.Parse(fidStr) if err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": "invalid folder_id"}) + RespondWithAppError(c, apperrors.NewValidationError("invalid folder_id")) return } folderID = &fid @@ -187,7 +188,7 @@ func (h *CloudHandler) ListFiles(c *gin.Context) { files, err := h.cloudService.ListFiles(c.Request.Context(), userID, folderID) if err != nil { h.logger.Error("Failed to list files", zap.Error(err)) - c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to list files"}) + RespondWithAppError(c, apperrors.NewInternalErrorWrap("failed to list files", err)) return } @@ -197,20 +198,20 @@ func (h *CloudHandler) ListFiles(c *gin.Context) { func (h *CloudHandler) UploadFile(c *gin.Context) { userID, err := h.getUserID(c) if err != nil { - c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"}) + RespondWithAppError(c, apperrors.NewUnauthorizedError("unauthorized")) return } file, header, err := c.Request.FormFile("file") if err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": "file is required"}) + RespondWithAppError(c, apperrors.NewValidationError("file is required")) return } defer file.Close() data, err := io.ReadAll(file) if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to read file"}) + RespondWithAppError(c, apperrors.NewInternalErrorWrap("failed to read file", err)) return } @@ -223,7 +224,7 @@ func (h *CloudHandler) UploadFile(c *gin.Context) { if fidStr := c.PostForm("folder_id"); fidStr != "" { fid, err := uuid.Parse(fidStr) if err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": "invalid folder_id"}) + RespondWithAppError(c, apperrors.NewValidationError("invalid folder_id")) return } folderID = &fid @@ -232,19 +233,19 @@ func (h *CloudHandler) UploadFile(c *gin.Context) { result, err := h.cloudService.UploadFile(c.Request.Context(), userID, folderID, header.Filename, data, mimeType) if err != nil { if errors.Is(err, services.ErrQuotaExceeded) { - c.JSON(http.StatusForbidden, gin.H{"error": "storage quota exceeded"}) + RespondWithAppError(c, apperrors.NewForbiddenError("storage quota exceeded")) return } if errors.Is(err, services.ErrInvalidMimeType) { - c.JSON(http.StatusBadRequest, gin.H{"error": "invalid file type"}) + RespondWithAppError(c, apperrors.NewValidationError("invalid file type")) return } if errors.Is(err, services.ErrFileTooLarge) { - c.JSON(http.StatusBadRequest, gin.H{"error": "file too large (max 500MB)"}) + RespondWithAppError(c, apperrors.NewValidationError("file too large (max 500MB)")) return } h.logger.Error("Failed to upload file", zap.Error(err)) - c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to upload file"}) + RespondWithAppError(c, apperrors.NewInternalErrorWrap("failed to upload file", err)) return } @@ -254,19 +255,19 @@ func (h *CloudHandler) UploadFile(c *gin.Context) { func (h *CloudHandler) GetFile(c *gin.Context) { userID, err := h.getUserID(c) if err != nil { - c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"}) + RespondWithAppError(c, apperrors.NewUnauthorizedError("unauthorized")) return } fileID, err := uuid.Parse(c.Param("id")) if err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": "invalid file id"}) + RespondWithAppError(c, apperrors.NewValidationError("invalid file id")) return } file, err := h.cloudService.GetFileByID(c.Request.Context(), userID, fileID) if err != nil { - c.JSON(http.StatusNotFound, gin.H{"error": "file not found"}) + RespondWithAppError(c, apperrors.NewNotFoundError("file")) return } @@ -276,22 +277,22 @@ func (h *CloudHandler) GetFile(c *gin.Context) { func (h *CloudHandler) DeleteFile(c *gin.Context) { userID, err := h.getUserID(c) if err != nil { - c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"}) + RespondWithAppError(c, apperrors.NewUnauthorizedError("unauthorized")) return } fileID, err := uuid.Parse(c.Param("id")) if err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": "invalid file id"}) + RespondWithAppError(c, apperrors.NewValidationError("invalid file id")) return } if err := h.cloudService.DeleteFile(c.Request.Context(), userID, fileID); err != nil { if errors.Is(err, services.ErrFileNotFound) { - c.JSON(http.StatusNotFound, gin.H{"error": "file not found"}) + RespondWithAppError(c, apperrors.NewNotFoundError("file")) return } - c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to delete file"}) + RespondWithAppError(c, apperrors.NewInternalErrorWrap("failed to delete file", err)) return } @@ -302,23 +303,23 @@ func (h *CloudHandler) DeleteFile(c *gin.Context) { func (h *CloudHandler) StreamFile(c *gin.Context) { userID, err := h.getUserID(c) if err != nil { - c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"}) + RespondWithAppError(c, apperrors.NewUnauthorizedError("unauthorized")) return } fileID, err := uuid.Parse(c.Param("id")) if err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": "invalid file id"}) + RespondWithAppError(c, apperrors.NewValidationError("invalid file id")) return } data, file, err := h.cloudService.StreamFile(c.Request.Context(), userID, fileID) if err != nil { if errors.Is(err, services.ErrFileNotFound) { - c.JSON(http.StatusNotFound, gin.H{"error": "file not found"}) + RespondWithAppError(c, apperrors.NewNotFoundError("file")) return } - c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to stream file"}) + RespondWithAppError(c, apperrors.NewInternalErrorWrap("failed to stream file", err)) return } @@ -336,7 +337,7 @@ func (h *CloudHandler) StreamFile(c *gin.Context) { rangeStr := strings.TrimPrefix(rangeHeader, "bytes=") parts := strings.SplitN(rangeStr, "-", 2) if len(parts) != 2 { - c.JSON(http.StatusRequestedRangeNotSatisfiable, gin.H{"error": "invalid range"}) + RespondWithAppError(c, apperrors.NewValidationError("invalid range")) return } @@ -344,7 +345,7 @@ func (h *CloudHandler) StreamFile(c *gin.Context) { if parts[0] != "" { start, err = strconv.ParseInt(parts[0], 10, 64) if err != nil || start < 0 || start >= totalSize { - c.JSON(http.StatusRequestedRangeNotSatisfiable, gin.H{"error": "invalid range start"}) + RespondWithAppError(c, apperrors.NewValidationError("invalid range start")) return } } @@ -359,7 +360,7 @@ func (h *CloudHandler) StreamFile(c *gin.Context) { } if start > end { - c.JSON(http.StatusRequestedRangeNotSatisfiable, gin.H{"error": "invalid range"}) + RespondWithAppError(c, apperrors.NewValidationError("invalid range")) return } @@ -372,13 +373,13 @@ func (h *CloudHandler) StreamFile(c *gin.Context) { func (h *CloudHandler) GetQuota(c *gin.Context) { userID, err := h.getUserID(c) if err != nil { - c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"}) + RespondWithAppError(c, apperrors.NewUnauthorizedError("unauthorized")) return } quota, err := h.cloudService.GetQuota(c.Request.Context(), userID) if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to get quota"}) + RespondWithAppError(c, apperrors.NewInternalErrorWrap("failed to get quota", err)) return } @@ -397,24 +398,24 @@ func (h *CloudHandler) GetQuota(c *gin.Context) { func (h *CloudHandler) ListVersions(c *gin.Context) { userID, err := h.getUserID(c) if err != nil { - c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"}) + RespondWithAppError(c, apperrors.NewUnauthorizedError("unauthorized")) return } fileID, err := uuid.Parse(c.Param("id")) if err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": "invalid file id"}) + RespondWithAppError(c, apperrors.NewValidationError("invalid file id")) return } versions, err := h.cloudService.ListVersions(c.Request.Context(), userID, fileID) if err != nil { if errors.Is(err, services.ErrFileNotFound) { - c.JSON(http.StatusNotFound, gin.H{"error": "file not found"}) + RespondWithAppError(c, apperrors.NewNotFoundError("file")) return } h.logger.Error("Failed to list versions", zap.Error(err)) - c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to list versions"}) + RespondWithAppError(c, apperrors.NewInternalErrorWrap("failed to list versions", err)) return } @@ -425,30 +426,30 @@ func (h *CloudHandler) ListVersions(c *gin.Context) { func (h *CloudHandler) RestoreVersion(c *gin.Context) { userID, err := h.getUserID(c) if err != nil { - c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"}) + RespondWithAppError(c, apperrors.NewUnauthorizedError("unauthorized")) return } fileID, err := uuid.Parse(c.Param("id")) if err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": "invalid file id"}) + RespondWithAppError(c, apperrors.NewValidationError("invalid file id")) return } versionStr := c.Param("version") version, err := strconv.Atoi(versionStr) if err != nil || version < 1 { - c.JSON(http.StatusBadRequest, gin.H{"error": "invalid version"}) + RespondWithAppError(c, apperrors.NewValidationError("invalid version")) return } if err := h.cloudService.RestoreVersion(c.Request.Context(), userID, fileID, version); err != nil { if errors.Is(err, services.ErrFileNotFound) { - c.JSON(http.StatusNotFound, gin.H{"error": "file not found"}) + RespondWithAppError(c, apperrors.NewNotFoundError("file")) return } h.logger.Error("Failed to restore version", zap.Error(err)) - c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to restore version"}) + RespondWithAppError(c, apperrors.NewInternalErrorWrap("failed to restore version", err)) return } @@ -459,13 +460,13 @@ func (h *CloudHandler) RestoreVersion(c *gin.Context) { func (h *CloudHandler) ShareFile(c *gin.Context) { userID, err := h.getUserID(c) if err != nil { - c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"}) + RespondWithAppError(c, apperrors.NewUnauthorizedError("unauthorized")) return } fileID, err := uuid.Parse(c.Param("id")) if err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": "invalid file id"}) + RespondWithAppError(c, apperrors.NewValidationError("invalid file id")) return } @@ -490,11 +491,11 @@ func (h *CloudHandler) ShareFile(c *gin.Context) { share, err := h.cloudService.ShareFile(c.Request.Context(), userID, fileID, req.Permissions, req.ExpiresInHours) if err != nil { if errors.Is(err, services.ErrFileNotFound) { - c.JSON(http.StatusNotFound, gin.H{"error": "file not found"}) + RespondWithAppError(c, apperrors.NewNotFoundError("file")) return } h.logger.Error("Failed to share file", zap.Error(err)) - c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to share file"}) + RespondWithAppError(c, apperrors.NewInternalErrorWrap("failed to share file", err)) return } @@ -512,17 +513,17 @@ func (h *CloudHandler) ShareFile(c *gin.Context) { func (h *CloudHandler) GetSharedFile(c *gin.Context) { token := c.Param("token") if token == "" { - c.JSON(http.StatusBadRequest, gin.H{"error": "token required"}) + RespondWithAppError(c, apperrors.NewValidationError("token required")) return } file, err := h.cloudService.GetSharedFile(c.Request.Context(), token) if err != nil { if errors.Is(err, services.ErrShareExpired) || errors.Is(err, services.ErrShareNotFound) || errors.Is(err, services.ErrFileNotFound) { - c.JSON(http.StatusGone, gin.H{"error": "share link expired"}) + RespondWithAppError(c, apperrors.NewNotFoundError("share link expired")) return } - c.JSON(http.StatusNotFound, gin.H{"error": "file not found"}) + RespondWithAppError(c, apperrors.NewNotFoundError("file")) return } @@ -533,24 +534,24 @@ func (h *CloudHandler) GetSharedFile(c *gin.Context) { func (h *CloudHandler) CreateVersion(c *gin.Context) { userID, err := h.getUserID(c) if err != nil { - c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"}) + RespondWithAppError(c, apperrors.NewUnauthorizedError("unauthorized")) return } fileID, err := uuid.Parse(c.Param("id")) if err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": "invalid file id"}) + RespondWithAppError(c, apperrors.NewValidationError("invalid file id")) return } version, err := h.cloudService.CreateVersion(c.Request.Context(), userID, fileID) if err != nil { if errors.Is(err, services.ErrFileNotFound) { - c.JSON(http.StatusNotFound, gin.H{"error": "file not found"}) + RespondWithAppError(c, apperrors.NewNotFoundError("file")) return } h.logger.Error("Failed to create version", zap.Error(err)) - c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to create version"}) + RespondWithAppError(c, apperrors.NewInternalErrorWrap("failed to create version", err)) return } @@ -561,24 +562,24 @@ func (h *CloudHandler) CreateVersion(c *gin.Context) { func (h *CloudHandler) PublishAsTrack(c *gin.Context) { userID, err := h.getUserID(c) if err != nil { - c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"}) + RespondWithAppError(c, apperrors.NewUnauthorizedError("unauthorized")) return } fileID, err := uuid.Parse(c.Param("id")) if err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": "invalid file id"}) + RespondWithAppError(c, apperrors.NewValidationError("invalid file id")) return } file, err := h.cloudService.GetFileByID(c.Request.Context(), userID, fileID) if err != nil { - c.JSON(http.StatusNotFound, gin.H{"error": "file not found"}) + RespondWithAppError(c, apperrors.NewNotFoundError("file")) return } if !strings.HasPrefix(file.MimeType, "audio/") { - c.JSON(http.StatusBadRequest, gin.H{"error": "only audio files can be published as tracks"}) + RespondWithAppError(c, apperrors.NewValidationError("only audio files can be published as tracks")) return } diff --git a/veza-backend-api/internal/handlers/comment_handler.go b/veza-backend-api/internal/handlers/comment_handler.go index 4d16bd83e..85c259cc9 100644 --- a/veza-backend-api/internal/handlers/comment_handler.go +++ b/veza-backend-api/internal/handlers/comment_handler.go @@ -228,19 +228,19 @@ func (h *CommentHandler) UpdateComment(c *gin.Context) { return // Erreur déjà envoyée par GetUserIDUUID } if userID == uuid.Nil { - c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"}) + RespondWithAppError(c, apperrors.NewUnauthorizedError("unauthorized")) return } commentIDStr := c.Param("id") if commentIDStr == "" { - c.JSON(http.StatusBadRequest, gin.H{"error": "comment id is required"}) + RespondWithAppError(c, apperrors.NewValidationError("comment id is required")) return } commentID, err := uuid.Parse(commentIDStr) // Changed to uuid.Parse if err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": "invalid comment id"}) + RespondWithAppError(c, apperrors.NewValidationError("invalid comment id")) return } @@ -256,18 +256,18 @@ func (h *CommentHandler) UpdateComment(c *gin.Context) { comment, err := h.commentService.UpdateComment(c.Request.Context(), commentID, userID, req.Content) if err != nil { if errors.Is(err, services.ErrCommentNotFound) { - c.JSON(http.StatusNotFound, gin.H{"error": "comment not found"}) + RespondWithAppError(c, apperrors.NewNotFoundError("comment")) return } if errors.Is(err, services.ErrForbidden) { - c.JSON(http.StatusForbidden, gin.H{"error": "unauthorized: you can only edit your own comments"}) + RespondWithAppError(c, apperrors.NewForbiddenError("unauthorized: you can only edit your own comments")) return } - c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + RespondWithAppError(c, apperrors.NewInternalErrorWrap("Failed to update comment", err)) return } - c.JSON(http.StatusOK, gin.H{"comment": comment}) + RespondSuccess(c, http.StatusOK, gin.H{"comment": comment}) } // DeleteComment gère la suppression d'un commentaire @@ -342,13 +342,13 @@ func (h *CommentHandler) DeleteComment(c *gin.Context) { func (h *CommentHandler) GetReplies(c *gin.Context) { parentIDStr := c.Param("id") if parentIDStr == "" { - c.JSON(http.StatusBadRequest, gin.H{"error": "parent comment id is required"}) + RespondWithAppError(c, apperrors.NewValidationError("parent comment id is required")) return } parentID, err := uuid.Parse(parentIDStr) // Changed to uuid.Parse if err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": "invalid parent comment id"}) + RespondWithAppError(c, apperrors.NewValidationError("invalid parent comment id")) return } @@ -368,14 +368,14 @@ func (h *CommentHandler) GetReplies(c *gin.Context) { replies, total, err := h.commentService.GetReplies(c.Request.Context(), parentID, page, limit) if err != nil { if errors.Is(err, services.ErrParentCommentNotFound) { - c.JSON(http.StatusNotFound, gin.H{"error": "parent comment not found"}) + RespondWithAppError(c, apperrors.NewNotFoundError("parent comment")) return } - c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + RespondWithAppError(c, apperrors.NewInternalErrorWrap("Failed to get replies", err)) return } - c.JSON(http.StatusOK, gin.H{ + RespondSuccess(c, http.StatusOK, gin.H{ "replies": replies, "total": total, "page": page, diff --git a/veza-backend-api/internal/handlers/feature_flag_handler.go b/veza-backend-api/internal/handlers/feature_flag_handler.go index aa574c0d5..76e5696ae 100644 --- a/veza-backend-api/internal/handlers/feature_flag_handler.go +++ b/veza-backend-api/internal/handlers/feature_flag_handler.go @@ -5,6 +5,7 @@ import ( "github.com/gin-gonic/gin" + apperrors "veza-backend-api/internal/errors" "veza-backend-api/internal/services" ) @@ -22,7 +23,7 @@ func NewFeatureFlagHandler(svc *services.FeatureFlagService) *FeatureFlagHandler func (h *FeatureFlagHandler) List(c *gin.Context) { list, err := h.svc.List(c.Request.Context()) if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to list feature flags"}) + RespondWithAppError(c, apperrors.NewInternalErrorWrap("Failed to list feature flags", err)) return } c.JSON(http.StatusOK, gin.H{"feature_flags": list}) @@ -32,7 +33,7 @@ func (h *FeatureFlagHandler) List(c *gin.Context) { func (h *FeatureFlagHandler) Toggle(c *gin.Context) { name := c.Param("name") if name == "" { - c.JSON(http.StatusBadRequest, gin.H{"error": "name is required"}) + RespondWithAppError(c, apperrors.NewValidationError("name is required")) return } @@ -40,13 +41,13 @@ func (h *FeatureFlagHandler) Toggle(c *gin.Context) { Enabled bool `json:"enabled"` } if err := c.ShouldBindJSON(&req); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": "enabled is required"}) + RespondWithAppError(c, apperrors.NewValidationError("enabled is required")) return } flag, err := h.svc.Toggle(c.Request.Context(), name, req.Enabled) if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + RespondWithAppError(c, apperrors.NewInternalErrorWrap("Failed to toggle feature flag", err)) return } c.JSON(http.StatusOK, flag) diff --git a/veza-backend-api/internal/handlers/gear_handler.go b/veza-backend-api/internal/handlers/gear_handler.go index 78ad84fb7..244969846 100644 --- a/veza-backend-api/internal/handlers/gear_handler.go +++ b/veza-backend-api/internal/handlers/gear_handler.go @@ -330,14 +330,14 @@ func (h *GearHandler) DeleteGear(c *gin.Context) { func (h *GearHandler) ListPublicGear(c *gin.Context) { username := c.Param("id") // route uses :id to match /users/:id/* pattern if username == "" { - c.JSON(http.StatusBadRequest, gin.H{"error": "username is required"}) + RespondWithAppError(c, apperrors.NewValidationError("username is required")) return } items, err := h.gearService.ListPublicGearByUsername(c.Request.Context(), username) if err != nil { h.logger.Error("Failed to list public gear", zap.String("username", username), zap.Error(err)) - c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to list gear"}) + RespondWithAppError(c, apperrors.NewInternalErrorWrap("failed to list gear", err)) return } diff --git a/veza-backend-api/internal/handlers/playback_websocket_handler.go b/veza-backend-api/internal/handlers/playback_websocket_handler.go index a2f5a5eea..f49c0f8bd 100644 --- a/veza-backend-api/internal/handlers/playback_websocket_handler.go +++ b/veza-backend-api/internal/handlers/playback_websocket_handler.go @@ -5,13 +5,13 @@ import ( "encoding/json" "errors" "net" - "net/http" "strconv" "sync" "time" "github.com/google/uuid" + apperrors "veza-backend-api/internal/errors" "veza-backend-api/internal/models" "veza-backend-api/internal/services" wsmsg "veza-backend-api/internal/websocket" @@ -97,7 +97,7 @@ func (h *PlaybackWebSocketHandler) WebSocketHandler(c *gin.Context) { return // Erreur déjà envoyée par GetUserIDUUID } if userID == uuid.Nil { - c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"}) + RespondWithAppError(c, apperrors.NewUnauthorizedError("unauthorized")) return } diff --git a/veza-backend-api/internal/handlers/presence_handler.go b/veza-backend-api/internal/handlers/presence_handler.go index d617c900c..1e5e8c1be 100644 --- a/veza-backend-api/internal/handlers/presence_handler.go +++ b/veza-backend-api/internal/handlers/presence_handler.go @@ -46,7 +46,7 @@ func (h *PresenceHandler) GetPresence(c *gin.Context) { p, err := h.presenceService.GetPresenceForViewer(c.Request.Context(), userID, viewerID) if err != nil { h.logger.Error("Failed to get presence", zap.Error(err), zap.String("user_id", userID.String())) - c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get presence"}) + RespondWithAppError(c, apperrors.NewInternalErrorWrap("Failed to get presence", err)) return } if p == nil { @@ -117,7 +117,7 @@ func (h *PresenceHandler) UpdatePresence(c *gin.Context) { if err := h.presenceService.UpdatePresenceFull(c.Request.Context(), userID, input); err != nil { h.logger.Error("Failed to update presence", zap.Error(err), zap.String("user_id", userID.String())) - c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update presence"}) + RespondWithAppError(c, apperrors.NewInternalErrorWrap("Failed to update presence", err)) return } RespondSuccess(c, http.StatusOK, gin.H{"message": "Presence updated"}) diff --git a/veza-backend-api/internal/handlers/privacy_handler.go b/veza-backend-api/internal/handlers/privacy_handler.go index 1b3f7f21b..69012631e 100644 --- a/veza-backend-api/internal/handlers/privacy_handler.go +++ b/veza-backend-api/internal/handlers/privacy_handler.go @@ -6,6 +6,8 @@ import ( "github.com/gin-gonic/gin" "github.com/google/uuid" "gorm.io/gorm" + + apperrors "veza-backend-api/internal/errors" ) // PrivacyOptOut sets the CCPA "Do Not Sell" preference for the authenticated user. @@ -23,7 +25,7 @@ func PrivacyOptOut(db *gorm.DB) gin.HandlerFunc { return func(c *gin.Context) { userID, ok := GetUserIDUUID(c) if !ok || userID == uuid.Nil { - c.JSON(http.StatusUnauthorized, gin.H{"error": "Authentication required"}) + RespondWithAppError(c, apperrors.NewUnauthorizedError("Authentication required")) return } @@ -38,7 +40,7 @@ func PrivacyOptOut(db *gorm.DB) gin.HandlerFunc { `, userID) if result.Error != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to save privacy preference"}) + RespondWithAppError(c, apperrors.NewInternalErrorWrap("Failed to save privacy preference", result.Error)) return } diff --git a/veza-backend-api/internal/handlers/report_handler.go b/veza-backend-api/internal/handlers/report_handler.go index b0751ac45..9ab3c8f5e 100644 --- a/veza-backend-api/internal/handlers/report_handler.go +++ b/veza-backend-api/internal/handlers/report_handler.go @@ -7,6 +7,7 @@ import ( "github.com/gin-gonic/gin" "github.com/google/uuid" + apperrors "veza-backend-api/internal/errors" "veza-backend-api/internal/services" ) @@ -32,7 +33,7 @@ func (h *ReportHandler) ListReports(c *gin.Context) { Offset: offset, }) if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to list reports"}) + RespondWithAppError(c, apperrors.NewInternalErrorWrap("Failed to list reports", err)) return } @@ -51,24 +52,24 @@ func (h *ReportHandler) ResolveReport(c *gin.Context) { reportIDStr := c.Param("id") reportID, err := uuid.Parse(reportIDStr) if err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid report ID"}) + RespondWithAppError(c, apperrors.NewValidationError("Invalid report ID")) return } userID, ok := GetUserIDUUID(c) if !ok || userID == uuid.Nil { - c.JSON(http.StatusUnauthorized, gin.H{"error": "Authentication required"}) + RespondWithAppError(c, apperrors.NewUnauthorizedError("Authentication required")) return } var req services.ResolveReportRequest if err := c.ShouldBindJSON(&req); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": "action is required"}) + RespondWithAppError(c, apperrors.NewValidationError("action is required")) return } if err := h.reportService.Resolve(c.Request.Context(), reportID, userID, req.Action); err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + RespondWithAppError(c, apperrors.NewInternalErrorWrap("Failed to resolve report", err)) return } @@ -79,19 +80,19 @@ func (h *ReportHandler) ResolveReport(c *gin.Context) { func (h *ReportHandler) CreateReport(c *gin.Context) { userID, ok := GetUserIDUUID(c) if !ok || userID == uuid.Nil { - c.JSON(http.StatusUnauthorized, gin.H{"error": "Authentication required"}) + RespondWithAppError(c, apperrors.NewUnauthorizedError("Authentication required")) return } var req services.CreateReportRequest if err := c.ShouldBindJSON(&req); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": "content_type and reason are required"}) + RespondWithAppError(c, apperrors.NewValidationError("content_type and reason are required")) return } report, err := h.reportService.Create(c.Request.Context(), userID, req) if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create report"}) + RespondWithAppError(c, apperrors.NewInternalErrorWrap("Failed to create report", err)) return } diff --git a/veza-backend-api/internal/handlers/room_handler.go b/veza-backend-api/internal/handlers/room_handler.go index 67506e525..0c8e90fa0 100644 --- a/veza-backend-api/internal/handlers/room_handler.go +++ b/veza-backend-api/internal/handlers/room_handler.go @@ -54,14 +54,14 @@ func (h *RoomHandler) CreateRoom(c *gin.Context) { // Récupérer l'ID utilisateur du contexte userIDInterface, exists := c.Get("user_id") if !exists { - c.JSON(http.StatusUnauthorized, gin.H{"error": "User not authenticated"}) + RespondWithAppError(c, apperrors.NewUnauthorizedError("User not authenticated")) return } // Convertir userID en uuid.UUID userID, ok := userIDInterface.(uuid.UUID) if !ok { - c.JSON(http.StatusInternalServerError, gin.H{"error": "Invalid user ID type in context"}) + RespondWithAppError(c, apperrors.NewInternalError("Invalid user ID type in context")) return } @@ -84,7 +84,7 @@ func (h *RoomHandler) CreateRoom(c *gin.Context) { zap.Error(err), zap.String("user_id", userID.String()), zap.String("room_name", req.Name)) - c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create conversation"}) + RespondWithAppError(c, apperrors.NewInternalErrorWrap("Failed to create conversation", err)) return } @@ -102,14 +102,14 @@ func (h *RoomHandler) GetUserRooms(c *gin.Context) { // Récupérer l'ID utilisateur du contexte userIDInterface, exists := c.Get("user_id") if !exists { - c.JSON(http.StatusUnauthorized, gin.H{"error": "User not authenticated"}) + RespondWithAppError(c, apperrors.NewUnauthorizedError("User not authenticated")) return } // Convertir userID en uuid.UUID userID, ok := userIDInterface.(uuid.UUID) if !ok { - c.JSON(http.StatusInternalServerError, gin.H{"error": "Invalid user ID type in context"}) + RespondWithAppError(c, apperrors.NewInternalError("Invalid user ID type in context")) return } @@ -119,7 +119,7 @@ func (h *RoomHandler) GetUserRooms(c *gin.Context) { h.logger.Error("failed to get user rooms", zap.Error(err), zap.String("user_id", userID.String())) - c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch conversations"}) + RespondWithAppError(c, apperrors.NewInternalErrorWrap("Failed to fetch conversations", err)) return } @@ -136,7 +136,7 @@ func (h *RoomHandler) GetRoom(c *gin.Context) { roomIDStr := c.Param("id") roomID, err := uuid.Parse(roomIDStr) if err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid room ID"}) + RespondWithAppError(c, apperrors.NewValidationError("Invalid room ID")) return } @@ -144,13 +144,13 @@ func (h *RoomHandler) GetRoom(c *gin.Context) { room, err := h.roomService.GetRoom(c.Request.Context(), roomID) if err != nil { if errors.Is(err, services.ErrRoomNotFound) { - c.JSON(http.StatusNotFound, gin.H{"error": "Conversation not found"}) + RespondWithAppError(c, apperrors.NewNotFoundError("Conversation")) return } h.logger.Error("failed to get room", zap.Error(err), zap.String("room_id", roomID.String())) - c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get conversation"}) + RespondWithAppError(c, apperrors.NewInternalErrorWrap("Failed to get conversation", err)) return } @@ -221,7 +221,7 @@ func (h *RoomHandler) AddMember(c *gin.Context) { roomIDStr := c.Param("id") roomID, err := uuid.Parse(roomIDStr) if err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid room ID"}) + RespondWithAppError(c, apperrors.NewValidationError("Invalid room ID")) return } @@ -238,7 +238,7 @@ func (h *RoomHandler) AddMember(c *gin.Context) { zap.Error(err), zap.String("room_id", roomID.String()), zap.String("user_id", req.UserID.String())) - c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to add member"}) + RespondWithAppError(c, apperrors.NewInternalErrorWrap("Failed to add member", err)) return } @@ -256,7 +256,7 @@ func (h *RoomHandler) GetRoomHistory(c *gin.Context) { conversationIDStr := c.Param("id") conversationID, err := uuid.Parse(conversationIDStr) if err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid conversation ID"}) + RespondWithAppError(c, apperrors.NewValidationError("Invalid conversation ID")) return } @@ -274,13 +274,13 @@ func (h *RoomHandler) GetRoomHistory(c *gin.Context) { result, err := h.roomService.GetRoomHistoryWithCursor(c.Request.Context(), conversationID, limitInt, cursor) if err != nil { if errors.Is(err, services.ErrRoomNotFound) { - c.JSON(http.StatusNotFound, gin.H{"error": "Conversation not found"}) + RespondWithAppError(c, apperrors.NewNotFoundError("Conversation")) return } h.logger.Error("failed to get room history", zap.Error(err), zap.String("conversation_id", conversationID.String())) - c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get conversation history"}) + RespondWithAppError(c, apperrors.NewInternalErrorWrap("Failed to get conversation history", err)) return } resp := gin.H{"messages": result.Messages} @@ -300,13 +300,13 @@ func (h *RoomHandler) GetRoomHistory(c *gin.Context) { messages, err := h.roomService.GetRoomHistory(c.Request.Context(), conversationID, limitInt, offsetInt) if err != nil { if errors.Is(err, services.ErrRoomNotFound) { - c.JSON(http.StatusNotFound, gin.H{"error": "Conversation not found"}) + RespondWithAppError(c, apperrors.NewNotFoundError("Conversation")) return } h.logger.Error("failed to get room history", zap.Error(err), zap.String("conversation_id", conversationID.String())) - c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get conversation history"}) + RespondWithAppError(c, apperrors.NewInternalErrorWrap("Failed to get conversation history", err)) return } @@ -446,21 +446,21 @@ func (h *RoomHandler) CreateInvitation(c *gin.Context) { } roomID, err := uuid.Parse(c.Param("id")) if err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid room ID"}) + RespondWithAppError(c, apperrors.NewValidationError("Invalid room ID")) return } resp, err := h.roomService.CreateInvitation(c.Request.Context(), roomID, userID) if err != nil { if err.Error() == "forbidden: only owner or admin can create invitations" { - c.JSON(http.StatusForbidden, gin.H{"error": err.Error()}) + RespondWithAppError(c, apperrors.NewForbiddenError(err.Error())) return } if errors.Is(err, services.ErrRoomNotFound) { - c.JSON(http.StatusNotFound, gin.H{"error": "Conversation not found"}) + RespondWithAppError(c, apperrors.NewNotFoundError("Conversation")) return } h.logger.Error("failed to create invitation", zap.Error(err), zap.String("room_id", roomID.String())) - c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create invitation"}) + RespondWithAppError(c, apperrors.NewInternalErrorWrap("Failed to create invitation", err)) return } RespondSuccess(c, http.StatusCreated, resp) @@ -475,17 +475,17 @@ func (h *RoomHandler) JoinByToken(c *gin.Context) { } token, err := uuid.Parse(c.Param("token")) if err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid token"}) + RespondWithAppError(c, apperrors.NewValidationError("Invalid token")) return } roomID, err := h.roomService.JoinByToken(c.Request.Context(), token, userID) if err != nil { if err.Error() == "invitation not found or expired" { - c.JSON(http.StatusNotFound, gin.H{"error": err.Error()}) + RespondWithAppError(c, apperrors.NewNotFoundError(err.Error())) return } h.logger.Error("failed to join via token", zap.Error(err), zap.String("token", token.String())) - c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to join"}) + RespondWithAppError(c, apperrors.NewInternalErrorWrap("Failed to join", err)) return } RespondSuccess(c, http.StatusOK, gin.H{"room_id": roomID}) @@ -500,25 +500,25 @@ func (h *RoomHandler) KickMember(c *gin.Context) { } roomID, err := uuid.Parse(c.Param("id")) if err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid room ID"}) + RespondWithAppError(c, apperrors.NewValidationError("Invalid room ID")) return } targetUserID, err := uuid.Parse(c.Param("userId")) if err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid user ID"}) + RespondWithAppError(c, apperrors.NewValidationError("Invalid user ID")) return } if err := h.roomService.KickMember(c.Request.Context(), roomID, targetUserID, requestUserID); err != nil { if err.Error() == "forbidden: only owner or admin can remove members" { - c.JSON(http.StatusForbidden, gin.H{"error": err.Error()}) + RespondWithAppError(c, apperrors.NewForbiddenError(err.Error())) return } if errors.Is(err, services.ErrRoomNotFound) { - c.JSON(http.StatusNotFound, gin.H{"error": "Conversation not found"}) + RespondWithAppError(c, apperrors.NewNotFoundError("Conversation")) return } h.logger.Error("failed to kick member", zap.Error(err), zap.String("room_id", roomID.String())) - c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to remove member"}) + RespondWithAppError(c, apperrors.NewInternalErrorWrap("Failed to remove member", err)) return } RespondSuccess(c, http.StatusOK, gin.H{"message": "Member removed successfully"}) @@ -533,19 +533,19 @@ func (h *RoomHandler) LeaveRoom(c *gin.Context) { } roomID, err := uuid.Parse(c.Param("id")) if err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid conversation ID"}) + RespondWithAppError(c, apperrors.NewValidationError("Invalid conversation ID")) return } if err := h.roomService.RemoveMember(c.Request.Context(), roomID, userID); err != nil { if errors.Is(err, services.ErrRoomNotFound) { - c.JSON(http.StatusNotFound, gin.H{"error": "Conversation not found"}) + RespondWithAppError(c, apperrors.NewNotFoundError("Conversation")) return } h.logger.Error("failed to leave room", zap.Error(err), zap.String("room_id", roomID.String()), zap.String("user_id", userID.String())) - c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to leave conversation"}) + RespondWithAppError(c, apperrors.NewInternalErrorWrap("Failed to leave conversation", err)) return } h.logger.Info("user left room", @@ -563,12 +563,12 @@ func (h *RoomHandler) UpdateMemberRole(c *gin.Context) { } roomID, err := uuid.Parse(c.Param("id")) if err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid conversation ID"}) + RespondWithAppError(c, apperrors.NewValidationError("Invalid conversation ID")) return } targetUserID, err := uuid.Parse(c.Param("userId")) if err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid user ID"}) + RespondWithAppError(c, apperrors.NewValidationError("Invalid user ID")) return } var req services.UpdateMemberRoleRequest @@ -581,15 +581,15 @@ func (h *RoomHandler) UpdateMemberRole(c *gin.Context) { err.Error() == "forbidden: cannot change owner role" || err.Error() == "forbidden: owner cannot change their own role" || err.Error() == "forbidden: cannot promote to owner via this endpoint" { - c.JSON(http.StatusForbidden, gin.H{"error": err.Error()}) + RespondWithAppError(c, apperrors.NewForbiddenError(err.Error())) return } if errors.Is(err, services.ErrRoomNotFound) { - c.JSON(http.StatusNotFound, gin.H{"error": "Conversation or member not found"}) + RespondWithAppError(c, apperrors.NewNotFoundError("Conversation or member")) return } h.logger.Error("failed to update member role", zap.Error(err)) - c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update member role"}) + RespondWithAppError(c, apperrors.NewInternalErrorWrap("Failed to update member role", err)) return } RespondSuccess(c, http.StatusOK, gin.H{"message": "Member role updated successfully"}) @@ -605,17 +605,17 @@ func (h *RoomHandler) GetMembers(c *gin.Context) { roomIDStr := c.Param("id") roomID, err := uuid.Parse(roomIDStr) if err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid room ID"}) + RespondWithAppError(c, apperrors.NewValidationError("Invalid room ID")) return } resp, err := h.roomService.GetRoomMembers(c.Request.Context(), roomID, userID) if err != nil { if errors.Is(err, services.ErrRoomNotFound) { - c.JSON(http.StatusNotFound, gin.H{"error": "Conversation not found"}) + RespondWithAppError(c, apperrors.NewNotFoundError("Conversation")) return } h.logger.Error("failed to get room members", zap.Error(err), zap.String("room_id", roomID.String())) - c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get members"}) + RespondWithAppError(c, apperrors.NewInternalErrorWrap("Failed to get members", err)) return } RespondSuccess(c, http.StatusOK, resp) diff --git a/veza-backend-api/internal/handlers/sell_handler.go b/veza-backend-api/internal/handlers/sell_handler.go index 288659b0e..1a45abda9 100644 --- a/veza-backend-api/internal/handlers/sell_handler.go +++ b/veza-backend-api/internal/handlers/sell_handler.go @@ -4,6 +4,7 @@ import ( "errors" "net/http" + apperrors "veza-backend-api/internal/errors" "veza-backend-api/internal/core/marketplace" "veza-backend-api/internal/services" @@ -37,7 +38,7 @@ type ConnectOnboardRequest struct { // ConnectOnboard starts Stripe Connect onboarding and returns the onboarding URL func (h *SellHandler) ConnectOnboard(c *gin.Context) { if h.stripeConnect == nil { - c.JSON(http.StatusServiceUnavailable, gin.H{"error": "Stripe Connect is not enabled"}) + RespondWithAppError(c, apperrors.NewServiceUnavailableError("Stripe Connect is not enabled")) return } @@ -60,11 +61,11 @@ func (h *SellHandler) ConnectOnboard(c *gin.Context) { url, err := h.stripeConnect.CreateOnboardingLink(c.Request.Context(), userID, returnURL, refreshURL) if err != nil { if errors.Is(err, services.ErrStripeConnectDisabled) { - c.JSON(http.StatusServiceUnavailable, gin.H{"error": "Stripe Connect is not enabled"}) + RespondWithAppError(c, apperrors.NewServiceUnavailableError("Stripe Connect is not enabled")) return } h.logger.Error("CreateOnboardingLink failed", zap.Error(err), zap.String("user_id", userID.String())) - c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create onboarding link"}) + RespondWithAppError(c, apperrors.NewInternalErrorWrap("Failed to create onboarding link", err)) return } @@ -74,7 +75,7 @@ func (h *SellHandler) ConnectOnboard(c *gin.Context) { // ConnectCallback syncs account status after Stripe redirect (called by frontend after return) func (h *SellHandler) ConnectCallback(c *gin.Context) { if h.stripeConnect == nil { - c.JSON(http.StatusServiceUnavailable, gin.H{"error": "Stripe Connect is not enabled"}) + RespondWithAppError(c, apperrors.NewServiceUnavailableError("Stripe Connect is not enabled")) return } @@ -85,15 +86,15 @@ func (h *SellHandler) ConnectCallback(c *gin.Context) { if err := h.stripeConnect.HandleOnboardingCallback(c.Request.Context(), userID); err != nil { if errors.Is(err, services.ErrNoStripeAccount) { - c.JSON(http.StatusNotFound, gin.H{"error": "No Stripe account found"}) + RespondWithAppError(c, apperrors.NewNotFoundError("Stripe account")) return } if errors.Is(err, services.ErrStripeConnectDisabled) { - c.JSON(http.StatusServiceUnavailable, gin.H{"error": "Stripe Connect is not enabled"}) + RespondWithAppError(c, apperrors.NewServiceUnavailableError("Stripe Connect is not enabled")) return } h.logger.Error("HandleOnboardingCallback failed", zap.Error(err), zap.String("user_id", userID.String())) - c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to sync account status"}) + RespondWithAppError(c, apperrors.NewInternalErrorWrap("Failed to sync account status", err)) return } @@ -103,7 +104,7 @@ func (h *SellHandler) ConnectCallback(c *gin.Context) { // GetBalance returns the seller's Stripe Connect balance func (h *SellHandler) GetBalance(c *gin.Context) { if h.stripeConnect == nil { - c.JSON(http.StatusServiceUnavailable, gin.H{"error": "Stripe Connect is not enabled"}) + RespondWithAppError(c, apperrors.NewServiceUnavailableError("Stripe Connect is not enabled")) return } @@ -115,11 +116,11 @@ func (h *SellHandler) GetBalance(c *gin.Context) { bal, err := h.stripeConnect.GetBalance(c.Request.Context(), userID) if err != nil { if errors.Is(err, services.ErrStripeConnectDisabled) { - c.JSON(http.StatusServiceUnavailable, gin.H{"error": "Stripe Connect is not enabled"}) + RespondWithAppError(c, apperrors.NewServiceUnavailableError("Stripe Connect is not enabled")) return } h.logger.Error("GetBalance failed", zap.Error(err), zap.String("user_id", userID.String())) - c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get balance"}) + RespondWithAppError(c, apperrors.NewInternalErrorWrap("Failed to get balance", err)) return } @@ -140,7 +141,7 @@ func (h *SellHandler) GetSellerTransfers(c *gin.Context) { var transfers []marketplace.SellerTransfer if err := h.db.Where("seller_id = ?", userID).Order("created_at DESC").Find(&transfers).Error; err != nil { h.logger.Error("GetSellerTransfers failed", zap.Error(err), zap.String("user_id", userID.String())) - c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch transfers"}) + RespondWithAppError(c, apperrors.NewInternalErrorWrap("Failed to fetch transfers", err)) return } diff --git a/veza-backend-api/internal/handlers/session.go b/veza-backend-api/internal/handlers/session.go index 7617e7875..2722b7c73 100644 --- a/veza-backend-api/internal/handlers/session.go +++ b/veza-backend-api/internal/handlers/session.go @@ -125,7 +125,7 @@ func (sh *SessionHandler) Logout() gin.HandlerFunc { // Récupérer l'ID utilisateur depuis le contexte userIDInterface, exists := c.Get("user_id") if !exists { - c.JSON(http.StatusUnauthorized, gin.H{"error": "User not authenticated"}) + RespondWithAppError(c, apperrors.NewUnauthorizedError("User not authenticated")) return } @@ -137,25 +137,25 @@ func (sh *SessionHandler) Logout() gin.HandlerFunc { var err error userID, err = uuid.Parse(v) if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": "Invalid user ID format"}) + RespondWithAppError(c, apperrors.NewInternalError("Invalid user ID format")) return } default: - c.JSON(http.StatusInternalServerError, gin.H{"error": "Invalid user ID type"}) + RespondWithAppError(c, apperrors.NewInternalError("Invalid user ID type")) return } // Récupérer le token depuis le header Authorization authHeader := c.GetHeader("Authorization") if authHeader == "" { - c.JSON(http.StatusBadRequest, gin.H{"error": "Authorization header required"}) + RespondWithAppError(c, apperrors.NewValidationError("Authorization header required")) return } // Extraire le token tokenParts := strings.Split(authHeader, " ") if len(tokenParts) != 2 || tokenParts[0] != "Bearer" { - c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid Authorization header format"}) + RespondWithAppError(c, apperrors.NewValidationError("Invalid Authorization header format")) return } @@ -168,7 +168,7 @@ func (sh *SessionHandler) Logout() gin.HandlerFunc { zap.Error(err), zap.String("user_id", userID.String()), ) - c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to logout"}) + RespondWithAppError(c, apperrors.NewInternalErrorWrap("Failed to logout", err)) return } @@ -189,7 +189,7 @@ func (sh *SessionHandler) LogoutAll() gin.HandlerFunc { // Récupérer l'ID utilisateur depuis le contexte userIDInterface, exists := c.Get("user_id") if !exists { - c.JSON(http.StatusUnauthorized, gin.H{"error": "User not authenticated"}) + RespondWithAppError(c, apperrors.NewUnauthorizedError("User not authenticated")) return } @@ -201,11 +201,11 @@ func (sh *SessionHandler) LogoutAll() gin.HandlerFunc { var err error userID, err = uuid.Parse(v) if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": "Invalid user ID format"}) + RespondWithAppError(c, apperrors.NewInternalError("Invalid user ID format")) return } default: - c.JSON(http.StatusInternalServerError, gin.H{"error": "Invalid user ID type"}) + RespondWithAppError(c, apperrors.NewInternalError("Invalid user ID type")) return } @@ -216,7 +216,7 @@ func (sh *SessionHandler) LogoutAll() gin.HandlerFunc { zap.Error(err), zap.String("user_id", userID.String()), ) - c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to logout all sessions"}) + RespondWithAppError(c, apperrors.NewInternalErrorWrap("Failed to logout all sessions", err)) return } @@ -239,7 +239,7 @@ func (sh *SessionHandler) LogoutOthers() gin.HandlerFunc { return func(c *gin.Context) { userIDInterface, exists := c.Get("user_id") if !exists { - c.JSON(http.StatusUnauthorized, gin.H{"error": "User not authenticated"}) + RespondWithAppError(c, apperrors.NewUnauthorizedError("User not authenticated")) return } @@ -251,11 +251,11 @@ func (sh *SessionHandler) LogoutOthers() gin.HandlerFunc { var err error userID, err = uuid.Parse(v) if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": "Invalid user ID format"}) + RespondWithAppError(c, apperrors.NewInternalError("Invalid user ID format")) return } default: - c.JSON(http.StatusInternalServerError, gin.H{"error": "Invalid user ID type"}) + RespondWithAppError(c, apperrors.NewInternalError("Invalid user ID type")) return } @@ -274,7 +274,7 @@ func (sh *SessionHandler) LogoutOthers() gin.HandlerFunc { zap.Error(err), zap.String("user_id", userID.String()), ) - c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to revoke other sessions"}) + RespondWithAppError(c, apperrors.NewInternalErrorWrap("Failed to revoke other sessions", err)) return } @@ -291,7 +291,7 @@ func (sh *SessionHandler) GetSessions() gin.HandlerFunc { // Récupérer l'ID utilisateur depuis le contexte userIDInterface, exists := c.Get("user_id") if !exists { - c.JSON(http.StatusUnauthorized, gin.H{"error": "User not authenticated"}) + RespondWithAppError(c, apperrors.NewUnauthorizedError("User not authenticated")) return } @@ -303,11 +303,11 @@ func (sh *SessionHandler) GetSessions() gin.HandlerFunc { var err error userID, err = uuid.Parse(v) if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": "Invalid user ID format"}) + RespondWithAppError(c, apperrors.NewInternalError("Invalid user ID format")) return } default: - c.JSON(http.StatusInternalServerError, gin.H{"error": "Invalid user ID type"}) + RespondWithAppError(c, apperrors.NewInternalError("Invalid user ID type")) return } @@ -328,7 +328,7 @@ func (sh *SessionHandler) GetSessions() gin.HandlerFunc { zap.Error(err), zap.String("user_id", userID.String()), ) - c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get sessions"}) + RespondWithAppError(c, apperrors.NewInternalErrorWrap("Failed to get sessions", err)) return } @@ -360,7 +360,7 @@ func (sh *SessionHandler) RevokeSession() gin.HandlerFunc { // Récupérer l'ID utilisateur depuis le contexte userIDInterface, exists := c.Get("user_id") if !exists { - c.JSON(http.StatusUnauthorized, gin.H{"error": "User not authenticated"}) + RespondWithAppError(c, apperrors.NewUnauthorizedError("User not authenticated")) return } @@ -372,11 +372,11 @@ func (sh *SessionHandler) RevokeSession() gin.HandlerFunc { var err error userID, err = uuid.Parse(v) if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": "Invalid user ID format"}) + RespondWithAppError(c, apperrors.NewInternalError("Invalid user ID format")) return } default: - c.JSON(http.StatusInternalServerError, gin.H{"error": "Invalid user ID type"}) + RespondWithAppError(c, apperrors.NewInternalError("Invalid user ID type")) return } @@ -384,7 +384,7 @@ func (sh *SessionHandler) RevokeSession() gin.HandlerFunc { sessionIDStr := c.Param("session_id") sessionID, err := uuid.Parse(sessionIDStr) if err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid session ID"}) + RespondWithAppError(c, apperrors.NewValidationError("Invalid session ID")) return } @@ -395,7 +395,7 @@ func (sh *SessionHandler) RevokeSession() gin.HandlerFunc { zap.Error(err), zap.String("user_id", userID.String()), ) - c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get sessions"}) + RespondWithAppError(c, apperrors.NewInternalErrorWrap("Failed to get sessions", err)) return } @@ -411,7 +411,7 @@ func (sh *SessionHandler) RevokeSession() gin.HandlerFunc { } if !sessionFound { - c.JSON(http.StatusNotFound, gin.H{"error": "Session not found"}) + RespondWithAppError(c, apperrors.NewNotFoundError("Session")) return } @@ -423,7 +423,7 @@ func (sh *SessionHandler) RevokeSession() gin.HandlerFunc { zap.Error(err), zap.String("user_id", userID.String()), ) - c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to revoke session"}) + RespondWithAppError(c, apperrors.NewInternalErrorWrap("Failed to revoke session", err)) return } } diff --git a/veza-backend-api/internal/handlers/social.go b/veza-backend-api/internal/handlers/social.go index 09a32282b..601da1a7b 100644 --- a/veza-backend-api/internal/handlers/social.go +++ b/veza-backend-api/internal/handlers/social.go @@ -5,6 +5,7 @@ import ( "strconv" "veza-backend-api/internal/core/social" + apperrors "veza-backend-api/internal/errors" "veza-backend-api/internal/utils" "github.com/gin-gonic/gin" @@ -69,7 +70,7 @@ func (h *SocialHandler) CreatePost(c *gin.Context) { post, err := h.service.CreatePost(c.Request.Context(), userID, req.Content, attachments) if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create post"}) + RespondWithAppError(c, apperrors.NewInternalErrorWrap("Failed to create post", err)) return } @@ -102,13 +103,13 @@ func (h *SocialHandler) ToggleLike(c *gin.Context) { // UUID validation déjà fait par binding tag, mais on garde le parse pour compatibilité targetID, err := uuid.Parse(req.TargetID) if err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid target_id format"}) + RespondWithAppError(c, apperrors.NewValidationError("Invalid target_id format")) return } liked, err := h.service.ToggleLike(c.Request.Context(), userID, targetID, req.TargetType) if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to toggle like"}) + RespondWithAppError(c, apperrors.NewInternalErrorWrap("Failed to toggle like", err)) return } @@ -142,13 +143,13 @@ func (h *SocialHandler) AddComment(c *gin.Context) { // UUID validation déjà fait par binding tag, mais on garde le parse pour compatibilité targetID, err := uuid.Parse(req.TargetID) if err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid target_id format"}) + RespondWithAppError(c, apperrors.NewValidationError("Invalid target_id format")) return } comment, err := h.service.AddComment(c.Request.Context(), userID, targetID, req.TargetType, req.Content) if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to add comment"}) + RespondWithAppError(c, apperrors.NewInternalErrorWrap("Failed to add comment", err)) return } @@ -197,7 +198,7 @@ func (h *SocialHandler) GetFeed(c *gin.Context) { if useCursorKeyset { feed, nextCursor, err := h.service.GetGlobalFeedWithCursor(c.Request.Context(), limit, cursor, feedType, userID) if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get feed"}) + RespondWithAppError(c, apperrors.NewInternalErrorWrap("Failed to get feed", err)) return } RespondSuccess(c, http.StatusOK, gin.H{ @@ -209,7 +210,7 @@ func (h *SocialHandler) GetFeed(c *gin.Context) { feed, err := h.service.GetGlobalFeed(c.Request.Context(), limit, offset, feedType, userID) if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get feed"}) + RespondWithAppError(c, apperrors.NewInternalErrorWrap("Failed to get feed", err)) return } nextCursor := "" @@ -234,7 +235,7 @@ func (h *SocialHandler) GetExplore(c *gin.Context) { } tags, err := h.service.GetTrendingHashtags(c.Request.Context(), limit) if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get explore data"}) + RespondWithAppError(c, apperrors.NewInternalErrorWrap("Failed to get explore data", err)) return } // suggested_users: placeholder - can add users not followed with most followers @@ -257,7 +258,7 @@ func (h *SocialHandler) GetTrending(c *gin.Context) { tags, err := h.service.GetTrendingHashtags(c.Request.Context(), limit) if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get trending"}) + RespondWithAppError(c, apperrors.NewInternalErrorWrap("Failed to get trending", err)) return } RespondSuccess(c, http.StatusOK, gin.H{"tags": tags}) @@ -268,7 +269,7 @@ func (h *SocialHandler) GetPostsByUser(c *gin.Context) { userIDStr := c.Param("user_id") userID, err := uuid.Parse(userIDStr) if err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid user ID"}) + RespondWithAppError(c, apperrors.NewValidationError("Invalid user ID")) return } @@ -289,7 +290,7 @@ func (h *SocialHandler) GetPostsByUser(c *gin.Context) { posts, err := h.service.GetPostsByUser(c.Request.Context(), userID, limit, offset) if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get posts"}) + RespondWithAppError(c, apperrors.NewInternalErrorWrap("Failed to get posts", err)) return } diff --git a/veza-backend-api/internal/handlers/social_group_handler.go b/veza-backend-api/internal/handlers/social_group_handler.go index 6a63a7bd3..c4c3a0ca8 100644 --- a/veza-backend-api/internal/handlers/social_group_handler.go +++ b/veza-backend-api/internal/handlers/social_group_handler.go @@ -139,20 +139,20 @@ func (h *GroupHandler) JoinGroup(c *gin.Context) { groupID, err := uuid.Parse(c.Param("id")) if err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid group ID"}) + RespondWithAppError(c, apperrors.NewValidationError("Invalid group ID")) return } if err := h.service.JoinGroup(c.Request.Context(), userID, groupID); err != nil { if errors.Is(err, social.ErrGroupNotFound) { - c.JSON(http.StatusNotFound, gin.H{"error": "Group not found"}) + RespondWithAppError(c, apperrors.NewNotFoundError("Group")) return } if errors.Is(err, social.ErrAlreadyMember) { - c.JSON(http.StatusConflict, gin.H{"error": "Already a member"}) + RespondWithAppError(c, apperrors.New(apperrors.ErrCodeConflict, "Already a member")) return } - c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to join group"}) + RespondWithAppError(c, apperrors.NewInternalErrorWrap("Failed to join group", err)) return } @@ -168,24 +168,24 @@ func (h *GroupHandler) LeaveGroup(c *gin.Context) { groupID, err := uuid.Parse(c.Param("id")) if err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid group ID"}) + RespondWithAppError(c, apperrors.NewValidationError("Invalid group ID")) return } if err := h.service.LeaveGroup(c.Request.Context(), userID, groupID); err != nil { if errors.Is(err, social.ErrGroupNotFound) { - c.JSON(http.StatusNotFound, gin.H{"error": "Group not found"}) + RespondWithAppError(c, apperrors.NewNotFoundError("Group")) return } if errors.Is(err, social.ErrNotMember) { - c.JSON(http.StatusBadRequest, gin.H{"error": "Not a member of this group"}) + RespondWithAppError(c, apperrors.NewValidationError("Not a member of this group")) return } if errors.Is(err, social.ErrCannotLeaveOwned) { - c.JSON(http.StatusForbidden, gin.H{"error": "Creator cannot leave group"}) + RespondWithAppError(c, apperrors.NewForbiddenError("Creator cannot leave group")) return } - c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to leave group"}) + RespondWithAppError(c, apperrors.NewInternalErrorWrap("Failed to leave group", err)) return } @@ -201,25 +201,25 @@ func (h *GroupHandler) RequestToJoin(c *gin.Context) { groupID, err := uuid.Parse(c.Param("id")) if err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid group ID"}) + RespondWithAppError(c, apperrors.NewValidationError("Invalid group ID")) return } req, err := h.service.RequestToJoin(c.Request.Context(), userID, groupID) if err != nil { if errors.Is(err, social.ErrGroupNotFound) { - c.JSON(http.StatusNotFound, gin.H{"error": "Group not found"}) + RespondWithAppError(c, apperrors.NewNotFoundError("Group")) return } if errors.Is(err, social.ErrAlreadyMember) { - c.JSON(http.StatusConflict, gin.H{"error": "Already a member"}) + RespondWithAppError(c, apperrors.New(apperrors.ErrCodeConflict, "Already a member")) return } if errors.Is(err, social.ErrRequestAlreadyExist) { - c.JSON(http.StatusConflict, gin.H{"error": "Join request already pending"}) + RespondWithAppError(c, apperrors.New(apperrors.ErrCodeConflict, "Join request already pending")) return } - c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to request join"}) + RespondWithAppError(c, apperrors.NewInternalErrorWrap("Failed to request join", err)) return } @@ -239,17 +239,17 @@ func (h *GroupHandler) ListJoinRequests(c *gin.Context) { groupID, err := uuid.Parse(c.Param("id")) if err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid group ID"}) + RespondWithAppError(c, apperrors.NewValidationError("Invalid group ID")) return } requests, err := h.service.ListJoinRequests(c.Request.Context(), groupID, userID) if err != nil { if errors.Is(err, social.ErrForbidden) { - c.JSON(http.StatusForbidden, gin.H{"error": "Forbidden"}) + RespondWithAppError(c, apperrors.NewForbiddenError("Forbidden")) return } - c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to list requests"}) + RespondWithAppError(c, apperrors.NewInternalErrorWrap("Failed to list requests", err)) return } @@ -265,20 +265,20 @@ func (h *GroupHandler) ApproveJoinRequest(c *gin.Context) { requestID, err := uuid.Parse(c.Param("request_id")) if err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request ID"}) + RespondWithAppError(c, apperrors.NewValidationError("Invalid request ID")) return } if err := h.service.ApproveJoinRequest(c.Request.Context(), requestID, userID); err != nil { if errors.Is(err, social.ErrRequestNotFound) { - c.JSON(http.StatusNotFound, gin.H{"error": "Request not found"}) + RespondWithAppError(c, apperrors.NewNotFoundError("Request")) return } if errors.Is(err, social.ErrForbidden) { - c.JSON(http.StatusForbidden, gin.H{"error": "Forbidden"}) + RespondWithAppError(c, apperrors.NewForbiddenError("Forbidden")) return } - c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to approve request"}) + RespondWithAppError(c, apperrors.NewInternalErrorWrap("Failed to approve request", err)) return } @@ -294,20 +294,20 @@ func (h *GroupHandler) RejectJoinRequest(c *gin.Context) { requestID, err := uuid.Parse(c.Param("request_id")) if err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request ID"}) + RespondWithAppError(c, apperrors.NewValidationError("Invalid request ID")) return } if err := h.service.RejectJoinRequest(c.Request.Context(), requestID, userID); err != nil { if errors.Is(err, social.ErrRequestNotFound) { - c.JSON(http.StatusNotFound, gin.H{"error": "Request not found"}) + RespondWithAppError(c, apperrors.NewNotFoundError("Request")) return } if errors.Is(err, social.ErrForbidden) { - c.JSON(http.StatusForbidden, gin.H{"error": "Forbidden"}) + RespondWithAppError(c, apperrors.NewForbiddenError("Forbidden")) return } - c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to reject request"}) + RespondWithAppError(c, apperrors.NewInternalErrorWrap("Failed to reject request", err)) return } @@ -329,13 +329,13 @@ func (h *GroupHandler) InviteMember(c *gin.Context) { groupID, err := uuid.Parse(c.Param("id")) if err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid group ID"}) + RespondWithAppError(c, apperrors.NewValidationError("Invalid group ID")) return } var req InviteMemberRequest if err := c.ShouldBindJSON(&req); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request body"}) + RespondWithAppError(c, apperrors.NewValidationError("Invalid request body")) return } @@ -346,35 +346,35 @@ func (h *GroupHandler) InviteMember(c *gin.Context) { inviteeID, err = h.service.ResolveInviteeByEmailOrID(c.Request.Context(), *req.Email) if err != nil { if errors.Is(err, social.ErrUserNotFound) { - c.JSON(http.StatusNotFound, gin.H{"error": "User not found"}) + RespondWithAppError(c, apperrors.NewNotFoundError("User")) return } - c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to resolve user"}) + RespondWithAppError(c, apperrors.NewInternalErrorWrap("Failed to resolve user", err)) return } } else { - c.JSON(http.StatusBadRequest, gin.H{"error": "email or user_id required"}) + RespondWithAppError(c, apperrors.NewValidationError("email or user_id required")) return } if err := h.service.InviteMember(c.Request.Context(), userID, groupID, inviteeID); err != nil { if errors.Is(err, social.ErrForbidden) { - c.JSON(http.StatusForbidden, gin.H{"error": "Forbidden"}) + RespondWithAppError(c, apperrors.NewForbiddenError("Forbidden")) return } if errors.Is(err, social.ErrAlreadyMember) { - c.JSON(http.StatusConflict, gin.H{"error": "User is already a member"}) + RespondWithAppError(c, apperrors.New(apperrors.ErrCodeConflict, "User is already a member")) return } if errors.Is(err, social.ErrUserNotFound) { - c.JSON(http.StatusNotFound, gin.H{"error": "User not found"}) + RespondWithAppError(c, apperrors.NewNotFoundError("User")) return } if err.Error() == "invitation already pending" { - c.JSON(http.StatusConflict, gin.H{"error": "Invitation already pending"}) + RespondWithAppError(c, apperrors.New(apperrors.ErrCodeConflict, "Invitation already pending")) return } - c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to invite member"}) + RespondWithAppError(c, apperrors.NewInternalErrorWrap("Failed to invite member", err)) return } @@ -395,32 +395,32 @@ func (h *GroupHandler) UpdateMemberRole(c *gin.Context) { groupID, err := uuid.Parse(c.Param("id")) if err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid group ID"}) + RespondWithAppError(c, apperrors.NewValidationError("Invalid group ID")) return } targetUserID, err := uuid.Parse(c.Param("user_id")) if err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid user ID"}) + RespondWithAppError(c, apperrors.NewValidationError("Invalid user ID")) return } var req UpdateMemberRoleRequest if err := c.ShouldBindJSON(&req); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request body"}) + RespondWithAppError(c, apperrors.NewValidationError("Invalid request body")) return } if err := h.service.UpdateMemberRole(c.Request.Context(), userID, groupID, targetUserID, req.Role); err != nil { if errors.Is(err, social.ErrForbidden) { - c.JSON(http.StatusForbidden, gin.H{"error": "Forbidden"}) + RespondWithAppError(c, apperrors.NewForbiddenError("Forbidden")) return } if errors.Is(err, social.ErrNotMember) { - c.JSON(http.StatusNotFound, gin.H{"error": "User is not a member"}) + RespondWithAppError(c, apperrors.NewNotFoundError("User")) return } - c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update role"}) + RespondWithAppError(c, apperrors.NewInternalErrorWrap("Failed to update role", err)) return } diff --git a/veza-backend-api/internal/handlers/tag_handler.go b/veza-backend-api/internal/handlers/tag_handler.go index 3d746b05c..137fe7f0b 100644 --- a/veza-backend-api/internal/handlers/tag_handler.go +++ b/veza-backend-api/internal/handlers/tag_handler.go @@ -6,6 +6,7 @@ import ( "github.com/gin-gonic/gin" + apperrors "veza-backend-api/internal/errors" "veza-backend-api/internal/services" ) @@ -32,7 +33,7 @@ func (h *TagHandler) Suggest(c *gin.Context) { suggestions, err := h.tagService.Suggest(c.Request.Context(), q, limit) if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to get suggestions"}) + RespondWithAppError(c, apperrors.NewInternalErrorWrap("failed to get suggestions", err)) return }