diff --git a/internal/handler/product_handler.go b/internal/handler/product_handler.go index f41b931..9a84911 100644 --- a/internal/handler/product_handler.go +++ b/internal/handler/product_handler.go @@ -118,3 +118,109 @@ func (h *ProductHandler) GetProductByID(c *fiber.Ctx) error { return utils.SuccessResponse(c, product, "Product fetched successfully") } + +func (h *ProductHandler) UpdateProduct(c *fiber.Ctx) error { + + userID, ok := c.Locals("userID").(string) + if !ok { + log.Println("User ID not found in Locals") + return utils.GenericResponse(c, fiber.StatusUnauthorized, "User ID not found") + } + + productID := c.Params("product_id") + if productID == "" { + return utils.GenericResponse(c, fiber.StatusBadRequest, "Product ID is required") + } + + var productDTO dto.RequestProductDTO + if err := c.BodyParser(&productDTO); err != nil { + log.Printf("Error parsing body: %v", err) + return utils.GenericResponse(c, fiber.StatusBadRequest, "Invalid request body") + } + + productImages, err := c.MultipartForm() + if err != nil { + log.Printf("Error parsing form data: %v", err) + return utils.GenericResponse(c, fiber.StatusBadRequest, "Error parsing form data") + } + + productDTO.ProductImages = productImages.File["product_images"] + + product, err := h.ProductService.UpdateProduct(userID, productID, &productDTO) + if err != nil { + log.Printf("Error updating product: %v", err) + return utils.GenericResponse(c, fiber.StatusConflict, err.Error()) + } + + return utils.CreateResponse(c, product, "Product updated successfully") +} + +func (h *ProductHandler) DeleteProduct(c *fiber.Ctx) error { + productID := c.Params("product_id") + if productID == "" { + return utils.GenericResponse(c, fiber.StatusBadRequest, "Product ID is required") + } + + err := h.ProductService.DeleteProduct(productID) + if err != nil { + log.Printf("Error deleting product: %v", err) + return utils.GenericResponse(c, fiber.StatusConflict, fmt.Sprintf("Failed to delete product: %v", err)) + } + + return utils.GenericResponse(c, fiber.StatusOK, "Product deleted successfully") +} + +func (h *ProductHandler) DeleteProducts(c *fiber.Ctx) error { + var productIDs []string + if err := c.BodyParser(&productIDs); err != nil { + log.Printf("Error parsing product IDs: %v", err) + return utils.GenericResponse(c, fiber.StatusBadRequest, "Invalid input for product IDs") + } + + if len(productIDs) == 0 { + return utils.GenericResponse(c, fiber.StatusBadRequest, "No product IDs provided") + } + + err := h.ProductService.DeleteProducts(productIDs) + if err != nil { + log.Printf("Error deleting products: %v", err) + return utils.GenericResponse(c, fiber.StatusConflict, fmt.Sprintf("Failed to delete products: %v", err)) + } + + return utils.GenericResponse(c, fiber.StatusOK, "Products deleted successfully") +} + +func (h *ProductHandler) DeleteProductImage(c *fiber.Ctx) error { + imageID := c.Params("image_id") + if imageID == "" { + return utils.GenericResponse(c, fiber.StatusBadRequest, "Image ID is required") + } + + err := h.ProductService.DeleteProductImage(imageID) + if err != nil { + log.Printf("Error deleting product image: %v", err) + return utils.GenericResponse(c, fiber.StatusConflict, fmt.Sprintf("Failed to delete product image: %v", err)) + } + + return utils.GenericResponse(c, fiber.StatusOK, "Product image deleted successfully") +} + +func (h *ProductHandler) DeleteProductImages(c *fiber.Ctx) error { + var imageIDs []string + if err := c.BodyParser(&imageIDs); err != nil { + log.Printf("Error parsing image IDs: %v", err) + return utils.GenericResponse(c, fiber.StatusBadRequest, "Invalid input for image IDs") + } + + if len(imageIDs) == 0 { + return utils.GenericResponse(c, fiber.StatusBadRequest, "No image IDs provided") + } + + err := h.ProductService.DeleteProductImages(imageIDs) + if err != nil { + log.Printf("Error deleting product images: %v", err) + return utils.GenericResponse(c, fiber.StatusConflict, fmt.Sprintf("Failed to delete product images: %v", err)) + } + + return utils.GenericResponse(c, fiber.StatusOK, "Product images deleted successfully") +} diff --git a/internal/repositories/product_repo.go b/internal/repositories/product_repo.go index 66a60e7..5ab4f56 100644 --- a/internal/repositories/product_repo.go +++ b/internal/repositories/product_repo.go @@ -1,6 +1,8 @@ package repositories import ( + "fmt" + "github.com/pahmiudahgede/senggoldong/model" "gorm.io/gorm" ) @@ -12,11 +14,14 @@ type ProductRepository interface { GetProductsByStoreID(storeID string) ([]model.Product, error) FindProductsByStoreID(storeID string, page, limit int) ([]model.Product, error) FindProductImagesByProductID(productID string) ([]model.ProductImage, error) + GetProductImageByID(imageID string) (*model.ProductImage, error) UpdateProduct(product *model.Product) error DeleteProduct(productID string) error - + DeleteProductsByID(productIDs []string) error AddProductImages(images []model.ProductImage) error DeleteProductImagesByProductID(productID string) error + DeleteProductImagesByID(imageIDs []string) error + DeleteProductImageByID(imageID string) error } type productRepository struct { @@ -81,6 +86,17 @@ func (r *productRepository) FindProductImagesByProductID(productID string) ([]mo return productImages, nil } +func (r *productRepository) GetProductImageByID(imageID string) (*model.ProductImage, error) { + var productImage model.ProductImage + if err := r.DB.Where("id = ?", imageID).First(&productImage).Error; err != nil { + if err == gorm.ErrRecordNotFound { + return nil, nil + } + return nil, err + } + return &productImage, nil +} + func (r *productRepository) UpdateProduct(product *model.Product) error { return r.DB.Save(product).Error } @@ -89,6 +105,13 @@ func (r *productRepository) DeleteProduct(productID string) error { return r.DB.Delete(&model.Product{}, "id = ?", productID).Error } +func (r *productRepository) DeleteProductsByID(productIDs []string) error { + if err := r.DB.Where("id IN ?", productIDs).Delete(&model.Product{}).Error; err != nil { + return fmt.Errorf("failed to delete products: %v", err) + } + return nil +} + func (r *productRepository) AddProductImages(images []model.ProductImage) error { if len(images) == 0 { return nil @@ -99,3 +122,17 @@ func (r *productRepository) AddProductImages(images []model.ProductImage) error func (r *productRepository) DeleteProductImagesByProductID(productID string) error { return r.DB.Where("product_id = ?", productID).Delete(&model.ProductImage{}).Error } + +func (r *productRepository) DeleteProductImagesByID(imageIDs []string) error { + if err := r.DB.Where("id IN ?", imageIDs).Delete(&model.ProductImage{}).Error; err != nil { + return fmt.Errorf("failed to delete product images: %v", err) + } + return nil +} + +func (r *productRepository) DeleteProductImageByID(imageID string) error { + if err := r.DB.Where("id = ?", imageID).Delete(&model.ProductImage{}).Error; err != nil { + return fmt.Errorf("failed to delete product image: %v", err) + } + return nil +} diff --git a/internal/services/product_service.go b/internal/services/product_service.go index c24fa16..448c6c6 100644 --- a/internal/services/product_service.go +++ b/internal/services/product_service.go @@ -19,6 +19,13 @@ type ProductService interface { GetAllProductsByStoreID(userID string, page, limit int) ([]dto.ResponseProductDTO, int64, error) GetProductByID(productID string) (*dto.ResponseProductDTO, error) + + UpdateProduct(userID, productID string, productDTO *dto.RequestProductDTO) (*dto.ResponseProductDTO, error) + + DeleteProduct(productID string) error + DeleteProducts(productIDs []string) error + DeleteProductImages(imageIDs []string) error + DeleteProductImage(imageID string) error } type productService struct { @@ -191,8 +198,135 @@ func (s *productService) GetProductByID(productID string) (*dto.ResponseProductD return productDTO, nil } +func (s *productService) UpdateProduct(userID, productID string, productDTO *dto.RequestProductDTO) (*dto.ResponseProductDTO, error) { + store, err := s.storeRepo.FindStoreByUserID(userID) + if err != nil { + return nil, fmt.Errorf("error retrieving store by user ID: %w", err) + } + if store == nil { + return nil, fmt.Errorf("store not found for user %s", userID) + } + + product, err := s.productRepo.GetProductByID(productID) + if err != nil { + return nil, fmt.Errorf("failed to retrieve product: %v", err) + } + if product == nil { + return nil, fmt.Errorf("product not found") + } + + if product.StoreID != store.ID { + return nil, fmt.Errorf("user does not own the store for this product") + } + + if err := s.deleteProductImages(productID); err != nil { + return nil, fmt.Errorf("failed to delete old product images: %v", err) + } + + var productImages []model.ProductImage + for _, file := range productDTO.ProductImages { + imagePath, err := s.SaveProductImage(file, "product") + if err != nil { + return nil, fmt.Errorf("failed to save product image: %w", err) + } + + productImages = append(productImages, model.ProductImage{ + ImageURL: imagePath, + }) + } + + product.ProductName = productDTO.ProductName + product.Quantity = productDTO.Quantity + product.ProductImages = productImages + + if err := s.productRepo.UpdateProduct(product); err != nil { + return nil, fmt.Errorf("failed to update product: %w", err) + } + + createdAt, _ := utils.FormatDateToIndonesianFormat(product.CreatedAt) + updatedAt, _ := utils.FormatDateToIndonesianFormat(product.UpdatedAt) + + var productImagesDTO []dto.ResponseProductImageDTO + for _, img := range product.ProductImages { + productImagesDTO = append(productImagesDTO, dto.ResponseProductImageDTO{ + ID: img.ID, + ProductID: img.ProductID, + ImageURL: img.ImageURL, + }) + } + + productDTOResponse := &dto.ResponseProductDTO{ + ID: product.ID, + StoreID: product.StoreID, + ProductName: product.ProductName, + Quantity: product.Quantity, + ProductImages: productImagesDTO, + CreatedAt: createdAt, + UpdatedAt: updatedAt, + } + + return productDTOResponse, nil +} + +func (s *productService) DeleteProduct(productID string) error { + + if err := s.deleteProductImages(productID); err != nil { + return fmt.Errorf("failed to delete associated product images: %w", err) + } + + if err := s.productRepo.DeleteProduct(productID); err != nil { + return fmt.Errorf("failed to delete product: %w", err) + } + return nil +} + +func (s *productService) DeleteProducts(productIDs []string) error { + + for _, productID := range productIDs { + if err := s.deleteProductImages(productID); err != nil { + return fmt.Errorf("failed to delete associated images for product %s: %w", productID, err) + } + } + + if err := s.productRepo.DeleteProductsByID(productIDs); err != nil { + return fmt.Errorf("failed to delete products: %w", err) + } + + return nil +} + +func (s *productService) DeleteProductImages(imageIDs []string) error { + + for _, imageID := range imageIDs { + if err := s.deleteImageFile(imageID); err != nil { + return fmt.Errorf("failed to delete image file with ID %s: %w", imageID, err) + } + } + + if err := s.productRepo.DeleteProductImagesByID(imageIDs); err != nil { + return fmt.Errorf("failed to delete product images from database: %w", err) + } + + return nil +} + +func (s *productService) DeleteProductImage(imageID string) error { + + if err := s.deleteImageFile(imageID); err != nil { + return fmt.Errorf("failed to delete image file with ID %s: %w", imageID, err) + } + + if err := s.productRepo.DeleteProductImageByID(imageID); err != nil { + return fmt.Errorf("failed to delete product image from database: %w", err) + } + + return nil +} + func (s *productService) SaveProductImage(file *multipart.FileHeader, imageType string) (string, error) { + imageDir := fmt.Sprintf("./public%s/uploads/store/%s", os.Getenv("BASE_URL"), imageType) + if _, err := os.Stat(imageDir); os.IsNotExist(err) { if err := os.MkdirAll(imageDir, os.ModePerm); err != nil { return "", fmt.Errorf("failed to create directory for %s image: %v", imageType, err) @@ -226,3 +360,45 @@ func (s *productService) SaveProductImage(file *multipart.FileHeader, imageType return filepath.Join("/uploads/store/", imageType, fileName), nil } + +func (s *productService) deleteProductImages(productID string) error { + + productImages, err := s.productRepo.FindProductImagesByProductID(productID) + if err != nil { + return fmt.Errorf("failed to fetch product images: %w", err) + } + + for _, img := range productImages { + if err := s.deleteImageFile(img.ID); err != nil { + return fmt.Errorf("failed to delete image file: %w", err) + } + } + + if err := s.productRepo.DeleteProductImagesByProductID(productID); err != nil { + return fmt.Errorf("failed to delete product images in database: %w", err) + } + + return nil +} + +func (s *productService) deleteImageFile(imageID string) error { + + productImage, err := s.productRepo.GetProductImageByID(imageID) + if err != nil { + return fmt.Errorf("failed to fetch product image: %w", err) + } + + if productImage == nil { + return fmt.Errorf("product image with ID %s not found", imageID) + } + + extension := filepath.Ext(productImage.ImageURL) + + imagePath := fmt.Sprintf("./public/uploads/store/product/%s%s", imageID, extension) + + if err := os.Remove(imagePath); err != nil { + return fmt.Errorf("failed to delete image file: %w", err) + } + + return nil +} diff --git a/presentation/product_route.go b/presentation/product_route.go index 0fa5f8a..7afab8d 100644 --- a/presentation/product_route.go +++ b/presentation/product_route.go @@ -19,6 +19,17 @@ func ProductRouter(api fiber.Router) { productAPI := api.Group("/productinstore") productAPI.Post("/add-product", middleware.AuthMiddleware, middleware.RoleMiddleware(utils.RoleAdministrator, utils.RolePengelola, utils.RolePengepul), productHandler.CreateProduct) + productAPI.Get("/getproductbyuser", middleware.AuthMiddleware, productHandler.GetAllProductsByStoreID) productAPI.Get("getproduct/:product_id", middleware.AuthMiddleware, productHandler.GetProductByID) + + productAPI.Put("updateproduct/:product_id", middleware.AuthMiddleware, middleware.RoleMiddleware(utils.RoleAdministrator, utils.RolePengelola, utils.RolePengepul), productHandler.UpdateProduct) + + productAPI.Delete("/delete/:product_id", middleware.AuthMiddleware, middleware.RoleMiddleware(utils.RoleAdministrator, utils.RolePengelola, utils.RolePengepul), productHandler.DeleteProduct) + + productAPI.Delete("/delete-products", middleware.AuthMiddleware, middleware.RoleMiddleware(utils.RoleAdministrator, utils.RolePengelola, utils.RolePengepul), productHandler.DeleteProducts) + + productAPI.Delete("/delete-image/:image_id", middleware.AuthMiddleware, middleware.RoleMiddleware(utils.RoleAdministrator, utils.RolePengelola, utils.RolePengepul), productHandler.DeleteProductImage) + + productAPI.Delete("/delete-images", middleware.AuthMiddleware, middleware.RoleMiddleware(utils.RoleAdministrator, utils.RolePengelola, utils.RolePengepul), productHandler.DeleteProductImages) }