diff --git a/dto/product.go b/dto/product.go index 982cd56..6a50b29 100644 --- a/dto/product.go +++ b/dto/product.go @@ -2,6 +2,33 @@ package dto import "errors" +type ProductResponseWithSoldDTO struct { + ID string `json:"id"` + UserID string `json:"user_id"` + ProductTitle string `json:"product_title"` + ProductImages []ProductImageDTO `json:"product_images"` + TrashDetail TrashDetailResponseDTO `json:"trash_detail"` + SalePrice int64 `json:"sale_price"` + Quantity int `json:"quantity"` + ProductDescribe string `json:"product_describe"` + Sold int `json:"sold"` + CreatedAt string `json:"created_at"` + UpdatedAt string `json:"updated_at"` +} + +type ProductResponseWithoutSoldDTO struct { + ID string `json:"id"` + UserID string `json:"user_id"` + ProductTitle string `json:"product_title"` + ProductImages []ProductImageDTO `json:"product_images"` + TrashDetail TrashDetailResponseDTO `json:"trash_detail"` + SalePrice int64 `json:"sale_price"` + Quantity int `json:"quantity"` + ProductDescribe string `json:"product_describe"` + CreatedAt string `json:"created_at"` + UpdatedAt string `json:"updated_at"` +} + type ProductResponseDTO struct { ID string `json:"id"` UserID string `json:"user_id"` @@ -11,7 +38,6 @@ type ProductResponseDTO struct { SalePrice int64 `json:"sale_price"` Quantity int `json:"quantity"` ProductDescribe string `json:"product_describe"` - Sold int `json:"sold"` CreatedAt string `json:"created_at"` UpdatedAt string `json:"updated_at"` } @@ -36,13 +62,24 @@ type CreateProductRequestDTO struct { } type CreateProductResponseDTO struct { - ID string `json:"id"` - UserID string `json:"user_id"` - ProductTitle string `json:"product_title"` - ProductImages []string `json:"product_images"` - TrashDetailID string `json:"trash_detail_id"` - SalePrice int64 `json:"sale_price"` - Quantity int `json:"quantity"` + ID string `json:"id"` + UserID string `json:"user_id"` + ProductTitle string `json:"product_title"` + ProductImages []string `json:"product_images"` + TrashDetail TrashDetailResponseDTO `json:"trash_detail"` + SalePrice int64 `json:"sale_price"` + Quantity int `json:"quantity"` + ProductDescribe string `json:"product_describe,omitempty"` + CreatedAt string `json:"created_at"` + UpdatedAt string `json:"updated_at"` +} + +type UpdateProductRequestDTO struct { + ProductTitle string `json:"product_title" validate:"required,min=3,max=255"` + ProductImages []string `json:"product_images" validate:"required,min=1,dive,url"` + TrashDetailID string `json:"trash_detail_id" validate:"required,uuid"` + SalePrice int64 `json:"sale_price" validate:"required,gt=0"` + Quantity int `json:"quantity" validate:"required,gt=0"` ProductDescribe string `json:"product_describe,omitempty"` } diff --git a/internal/api/routes.go b/internal/api/routes.go index 0885147..3ca8424 100644 --- a/internal/api/routes.go +++ b/internal/api/routes.go @@ -110,4 +110,6 @@ func AppRouter(app *fiber.App) { api.Get("/post/products", middleware.RoleRequired(utils.RolePengepul), controllers.GetAllProducts) api.Get("/post/product/:productid", middleware.RoleRequired(utils.RolePengepul), controllers.GetProductByID) api.Post("/post/addproduct", middleware.RoleRequired(utils.RolePengepul), controllers.CreateProduct) + api.Put("/post/product/:productid", middleware.RoleRequired(utils.RolePengepul), controllers.UpdateProduct) + api.Delete("/delete/product/:productid", middleware.RoleRequired(utils.RolePengepul), controllers.DeleteProduct) } diff --git a/internal/controllers/product.go b/internal/controllers/product.go index c0c4548..0be53ab 100644 --- a/internal/controllers/product.go +++ b/internal/controllers/product.go @@ -84,7 +84,7 @@ func CreateProduct(c *fiber.Ctx) error { if err := c.BodyParser(&input); err != nil { return c.Status(fiber.StatusBadRequest).JSON(utils.FormatResponse( fiber.StatusBadRequest, - "lengkapi data dengan benar", + "Invalid request payload", nil, )) } @@ -112,4 +112,90 @@ func CreateProduct(c *fiber.Ctx) error { "Product created successfully", product, )) -} \ No newline at end of file +} + +func UpdateProduct(c *fiber.Ctx) error { + var input dto.UpdateProductRequestDTO + if err := c.BodyParser(&input); err != nil { + return c.Status(fiber.StatusBadRequest).JSON(utils.FormatResponse( + fiber.StatusBadRequest, + "Invalid request payload", + nil, + )) + } + + userID, ok := c.Locals("userID").(string) + if !ok || userID == "" { + return c.Status(fiber.StatusUnauthorized).JSON(utils.FormatResponse( + fiber.StatusUnauthorized, + "Unauthorized: user ID is missing", + nil, + )) + } + + productID := c.Params("productid") + if productID == "" { + return c.Status(fiber.StatusBadRequest).JSON(utils.FormatResponse( + fiber.StatusBadRequest, + "Product ID is required", + nil, + )) + } + + product, err := services.UpdateProduct(productID, userID, input) + if err != nil { + return c.Status(fiber.StatusUnprocessableEntity).JSON(utils.FormatResponse( + fiber.StatusUnprocessableEntity, + err.Error(), + nil, + )) + } + + return c.Status(fiber.StatusOK).JSON(utils.FormatResponse( + fiber.StatusOK, + "Product updated successfully", + product, + )) +} + +func DeleteProduct(c *fiber.Ctx) error { + productID := c.Params("productid") + if productID == "" { + return c.Status(fiber.StatusBadRequest).JSON(utils.FormatResponse( + fiber.StatusBadRequest, + "Product ID is required", + nil, + )) + } + + userID, ok := c.Locals("userID").(string) + if !ok || userID == "" { + return c.Status(fiber.StatusUnauthorized).JSON(utils.FormatResponse( + fiber.StatusUnauthorized, + "Unauthorized: user ID is missing", + nil, + )) + } + + err := services.DeleteProduct(productID, userID) + if err != nil { + if err.Error() == "product not found or not authorized to delete" { + return c.Status(fiber.StatusNotFound).JSON(utils.FormatResponse( + fiber.StatusNotFound, + "Product not found: mungkin idnya salah", + nil, + )) + } + return c.Status(fiber.StatusInternalServerError).JSON(utils.FormatResponse( + fiber.StatusInternalServerError, + "Failed to delete product: "+err.Error(), + nil, + )) + } + + return c.Status(fiber.StatusOK).JSON(utils.FormatResponse( + fiber.StatusOK, + "Product deleted successfully", + nil, + )) +} diff --git a/internal/repositories/product.go b/internal/repositories/product.go index 5edc4e6..c7d391c 100644 --- a/internal/repositories/product.go +++ b/internal/repositories/product.go @@ -27,6 +27,13 @@ func GetProductByIDAndUserID(productID, userID string) (domain.Product, error) { return product, err } +func GetProductByID(productID string) (domain.Product, error) { + var product domain.Product + err := config.DB.Preload("ProductImages").Preload("TrashDetail"). + Where("id = ?", productID).First(&product).Error + return product, err +} + func CreateProduct(product *domain.Product, images []domain.ProductImage) error { err := config.DB.Transaction(func(tx *gorm.DB) error { @@ -46,3 +53,32 @@ func CreateProduct(product *domain.Product, images []domain.ProductImage) error }) return err } + +func UpdateProduct(product *domain.Product, images []domain.ProductImage) error { + return config.DB.Transaction(func(tx *gorm.DB) error { + + if err := tx.Model(&domain.Product{}).Where("id = ?", product.ID).Updates(product).Error; err != nil { + return err + } + + if err := tx.Where("product_id = ?", product.ID).Delete(&domain.ProductImage{}).Error; err != nil { + return err + } + + if len(images) > 0 { + for i := range images { + images[i].ProductID = product.ID + } + if err := tx.Create(&images).Error; err != nil { + return err + } + } + + return nil + }) +} + +func DeleteProduct(productID, userID string) (int64, error) { + result := config.DB.Where("id = ? AND user_id = ?", productID, userID).Delete(&domain.Product{}) + return result.RowsAffected, result.Error +} diff --git a/internal/services/product.go b/internal/services/product.go index 4192e04..38dc4f1 100644 --- a/internal/services/product.go +++ b/internal/services/product.go @@ -1,27 +1,29 @@ package services import ( + "errors" + "github.com/pahmiudahgede/senggoldong/domain" "github.com/pahmiudahgede/senggoldong/dto" "github.com/pahmiudahgede/senggoldong/internal/repositories" "github.com/pahmiudahgede/senggoldong/utils" ) -func GetProductsByUserID(userID string, limit, page int) ([]dto.ProductResponseDTO, error) { +func GetProductsByUserID(userID string, limit, page int) ([]dto.ProductResponseWithSoldDTO, error) { offset := (page - 1) * limit products, err := repositories.GetProductsByUserID(userID, limit, offset) if err != nil { return nil, err } - var productResponses []dto.ProductResponseDTO + var productResponses []dto.ProductResponseWithSoldDTO for _, product := range products { var images []dto.ProductImageDTO for _, img := range product.ProductImages { images = append(images, dto.ProductImageDTO{ImageURL: img.ImageURL}) } - productResponses = append(productResponses, dto.ProductResponseDTO{ + productResponses = append(productResponses, dto.ProductResponseWithSoldDTO{ ID: product.ID, UserID: product.UserID, ProductTitle: product.ProductTitle, @@ -43,10 +45,10 @@ func GetProductsByUserID(userID string, limit, page int) ([]dto.ProductResponseD return productResponses, nil } -func GetProductByIDAndUserID(productID, userID string) (dto.ProductResponseDTO, error) { +func GetProductByIDAndUserID(productID, userID string) (dto.ProductResponseWithSoldDTO, error) { product, err := repositories.GetProductByIDAndUserID(productID, userID) if err != nil { - return dto.ProductResponseDTO{}, err + return dto.ProductResponseWithSoldDTO{}, err } var images []dto.ProductImageDTO @@ -54,7 +56,7 @@ func GetProductByIDAndUserID(productID, userID string) (dto.ProductResponseDTO, images = append(images, dto.ProductImageDTO{ImageURL: img.ImageURL}) } - return dto.ProductResponseDTO{ + return dto.ProductResponseWithSoldDTO{ ID: product.ID, UserID: product.UserID, ProductTitle: product.ProductTitle, @@ -96,14 +98,88 @@ func CreateProduct(input dto.CreateProductRequestDTO, userID string) (dto.Create return dto.CreateProductResponseDTO{}, err } + trashDetail, err := repositories.GetTrashDetailByID(product.TrashDetailID) + if err != nil { + return dto.CreateProductResponseDTO{}, err + } + return dto.CreateProductResponseDTO{ - ID: product.ID, - UserID: product.UserID, - ProductTitle: product.ProductTitle, - ProductImages: input.ProductImages, - TrashDetailID: product.TrashDetailID, + ID: product.ID, + UserID: product.UserID, + ProductTitle: product.ProductTitle, + ProductImages: input.ProductImages, + TrashDetail: dto.TrashDetailResponseDTO{ + ID: trashDetail.ID, + Description: trashDetail.Description, + Price: trashDetail.Price, + }, SalePrice: product.SalePrice, Quantity: product.Quantity, ProductDescribe: product.ProductDescribe, + CreatedAt: utils.FormatDateToIndonesianFormat(product.CreatedAt), + UpdatedAt: utils.FormatDateToIndonesianFormat(product.UpdatedAt), }, nil } + +func UpdateProduct(productID, userID string, input dto.UpdateProductRequestDTO) (dto.ProductResponseDTO, error) { + if err := dto.GetValidator().Struct(input); err != nil { + return dto.ProductResponseDTO{}, err + } + + product := &domain.Product{ + ID: productID, + UserID: userID, + ProductTitle: input.ProductTitle, + TrashDetailID: input.TrashDetailID, + SalePrice: input.SalePrice, + Quantity: input.Quantity, + ProductDescribe: input.ProductDescribe, + } + + var images []domain.ProductImage + for _, imageURL := range input.ProductImages { + images = append(images, domain.ProductImage{ImageURL: imageURL}) + } + + if err := repositories.UpdateProduct(product, images); err != nil { + return dto.ProductResponseDTO{}, err + } + + updatedProduct, err := repositories.GetProductByID(productID) + if err != nil { + return dto.ProductResponseDTO{}, err + } + + var productImages []dto.ProductImageDTO + for _, img := range updatedProduct.ProductImages { + productImages = append(productImages, dto.ProductImageDTO{ImageURL: img.ImageURL}) + } + + return dto.ProductResponseDTO{ + ID: updatedProduct.ID, + UserID: updatedProduct.UserID, + ProductTitle: updatedProduct.ProductTitle, + ProductImages: productImages, + TrashDetail: dto.TrashDetailResponseDTO{ + ID: updatedProduct.TrashDetail.ID, + Description: updatedProduct.TrashDetail.Description, + Price: updatedProduct.TrashDetail.Price, + }, + SalePrice: updatedProduct.SalePrice, + Quantity: updatedProduct.Quantity, + ProductDescribe: updatedProduct.ProductDescribe, + CreatedAt: utils.FormatDateToIndonesianFormat(updatedProduct.CreatedAt), + UpdatedAt: utils.FormatDateToIndonesianFormat(updatedProduct.UpdatedAt), + }, nil +} + +func DeleteProduct(productID, userID string) error { + rowsAffected, err := repositories.DeleteProduct(productID, userID) + if err != nil { + return err + } + if rowsAffected == 0 { + return errors.New("product not found or not authorized to delete") + } + return nil +}