package handlers import ( "mime/multipart" "net/http" "github.com/gin-gonic/gin" "github.com/google/uuid" apperrors "veza-backend-api/internal/errors" "veza-backend-api/internal/models" "veza-backend-api/internal/services" ) // ImageServiceInterface defines the interface for image operations // This allows for easier testing with mocks type ImageServiceInterface interface { ProcessAvatar(fileHeader *multipart.FileHeader) ([]byte, error) GenerateS3Key(userID uuid.UUID) string UploadToS3(data []byte, key string) (string, error) DeleteFromS3(avatarURL string) error } // UserServiceInterfaceForAvatar defines the interface for user operations needed by avatar handler // This allows for easier testing with mocks type UserServiceInterfaceForAvatar interface { GetByID(userID uuid.UUID) (*models.User, error) UpdateAvatarURL(userID uuid.UUID, avatarURL string) error } // AvatarHandler handles avatar-related operations type AvatarHandler struct { imageService ImageServiceInterface userService UserServiceInterfaceForAvatar } // NewAvatarHandler creates a new AvatarHandler instance func NewAvatarHandler(imageService *services.ImageService, userService *services.UserService) *AvatarHandler { return &AvatarHandler{ imageService: imageService, userService: userService, } } // NewAvatarHandlerWithInterface creates a new AvatarHandler with interfaces (for testing) func NewAvatarHandlerWithInterface(imageService ImageServiceInterface, userService UserServiceInterfaceForAvatar) *AvatarHandler { return &AvatarHandler{ imageService: imageService, userService: userService, } } // UploadAvatar handles avatar upload // POST /api/v1/users/:userId/avatar // BE-API-021: Implement avatar upload endpoint // T0221: Validates user_id, file format/size, processes image, uploads to S3, and updates DB func (h *AvatarHandler) UploadAvatar(c *gin.Context) { // Récupérer l'ID utilisateur depuis l'URL (peut être "id" ou "userId") userIDStr := c.Param("userId") if userIDStr == "" { userIDStr = c.Param("id") } userID, err := uuid.Parse(userIDStr) if err != nil { RespondWithAppError(c, apperrors.NewValidationError("invalid user id")) return } // Vérifier que l'utilisateur est authentifié authenticatedUserID, ok := GetUserIDUUID(c) if !ok { return // Erreur déjà envoyée par GetUserIDUUID } // Vérifier que l'utilisateur ne peut modifier que son propre avatar if userID != authenticatedUserID { RespondWithAppError(c, apperrors.NewForbiddenError("cannot update other user's avatar")) return } // Récupérer le fichier fileHeader, err := c.FormFile("avatar") if err != nil { RespondWithAppError(c, apperrors.NewValidationError("no file provided")) return } // Valider et traiter l'image resizedImage, err := h.imageService.ProcessAvatar(fileHeader) if err != nil { RespondWithAppError(c, apperrors.NewValidationError(err.Error())) return } // Générer la clé S3 s3Key := h.imageService.GenerateS3Key(userID) // Upload vers S3 (ou stockage local pour l'instant) avatarURL, err := h.imageService.UploadToS3(resizedImage, s3Key) if err != nil { RespondWithAppError(c, apperrors.Wrap(apperrors.ErrCodeInternal, "failed to upload avatar", err)) return } // Mettre à jour l'URL de l'avatar dans la DB if err := h.userService.UpdateAvatarURL(userID, avatarURL); err != nil { RespondWithAppError(c, apperrors.Wrap(apperrors.ErrCodeInternal, "failed to update avatar", err)) return } RespondSuccess(c, http.StatusOK, gin.H{"avatar_url": avatarURL}) } // DeleteAvatar handles avatar deletion // DELETE /api/v1/users/:userId/avatar // BE-API-022: Implement avatar delete endpoint // T0222: Validates user_id, deletes file from S3, and sets avatar_url to NULL in DB func (h *AvatarHandler) DeleteAvatar(c *gin.Context) { // Récupérer l'ID utilisateur depuis l'URL (peut être "id" ou "userId") userIDStr := c.Param("userId") if userIDStr == "" { userIDStr = c.Param("id") } userID, err := uuid.Parse(userIDStr) if err != nil { RespondWithAppError(c, apperrors.NewValidationError("invalid user id")) return } // Vérifier que l'utilisateur est authentifié authenticatedUserID, ok := GetUserIDUUID(c) if !ok { return // Erreur déjà envoyée par GetUserIDUUID } // Vérifier que l'utilisateur ne peut supprimer que son propre avatar if userID != authenticatedUserID { RespondWithAppError(c, apperrors.NewForbiddenError("cannot delete other user's avatar")) return } // Récupérer l'utilisateur actuel pour obtenir l'URL de l'avatar user, err := h.userService.GetByID(userID) if err != nil { RespondWithAppError(c, apperrors.NewNotFoundError("user")) return } // Supprimer le fichier de S3 (ou stockage local) s'il existe if user.Avatar != "" { if err := h.imageService.DeleteFromS3(user.Avatar); err != nil { // Logger l'erreur mais continuer (le fichier peut déjà être supprimé) // En production, vous pourriez vouloir utiliser un logger ici _ = err } } // Mettre l'URL de l'avatar à une chaîne vide (NULL dans la DB) if err := h.userService.UpdateAvatarURL(userID, ""); err != nil { RespondWithAppError(c, apperrors.Wrap(apperrors.ErrCodeInternal, "failed to delete avatar", err)) return } RespondSuccess(c, http.StatusOK, gin.H{"message": "avatar deleted"}) }