diff --git a/config/database.go b/config/database.go index 03609b7..5322c65 100644 --- a/config/database.go +++ b/config/database.go @@ -46,6 +46,8 @@ func ConnectDatabase() { &model.Article{}, &model.Banner{}, &model.InitialCoint{}, + &model.TrashCategory{}, + &model.TrashDetail{}, // ==main feature== ) if err != nil { diff --git a/dto/trash_dto.go b/dto/trash_dto.go new file mode 100644 index 0000000..085fe59 --- /dev/null +++ b/dto/trash_dto.go @@ -0,0 +1,59 @@ +package dto + +import "strings" + +type RequestTrashCategoryDTO struct { + Name string `json:"name"` +} + +type ResponseTrashCategoryDTO struct { + ID string `json:"id"` + Name string `json:"name"` + CreatedAt string `json:"createdAt"` + UpdatedAt string `json:"updatedAt"` + Details []ResponseTrashDetailDTO `json:"details,omitempty"` +} + +type ResponseTrashDetailDTO struct { + ID string `json:"id"` + CategoryID string `json:"category_id"` + Description string `json:"description"` + Price float64 `json:"price"` + CreatedAt string `json:"createdAt"` + UpdatedAt string `json:"updatedAt"` +} + +type RequestTrashDetailDTO struct { + CategoryID string `json:"category_id"` + Description string `json:"description"` + Price float64 `json:"price"` +} + +func (r *RequestTrashCategoryDTO) ValidateTrashCategoryInput() (map[string][]string, bool) { + errors := make(map[string][]string) + + if strings.TrimSpace(r.Name) == "" { + errors["name"] = append(errors["name"], "name is required") + } + if len(errors) > 0 { + return errors, false + } + + return nil, true +} + +func (r *RequestTrashDetailDTO) ValidateTrashDetailInput() (map[string][]string, bool) { + errors := make(map[string][]string) + + if strings.TrimSpace(r.Description) == "" { + errors["description"] = append(errors["description"], "description is required") + } + if r.Price <= 0 { + errors["price"] = append(errors["price"], "price must be greater than 0") + } + if len(errors) > 0 { + return errors, false + } + + return nil, true +} diff --git a/internal/handler/trash_handler.go b/internal/handler/trash_handler.go new file mode 100644 index 0000000..d07c8d4 --- /dev/null +++ b/internal/handler/trash_handler.go @@ -0,0 +1,128 @@ +package handler + +import ( + "github.com/gofiber/fiber/v2" + "github.com/pahmiudahgede/senggoldong/dto" + "github.com/pahmiudahgede/senggoldong/internal/services" + "github.com/pahmiudahgede/senggoldong/utils" +) + +type TrashHandler struct { + TrashService services.TrashService +} + +func NewTrashHandler(trashService services.TrashService) *TrashHandler { + return &TrashHandler{TrashService: trashService} +} + +func (h *TrashHandler) CreateCategory(c *fiber.Ctx) error { + var request dto.RequestTrashCategoryDTO + if err := c.BodyParser(&request); err != nil { + return utils.ValidationErrorResponse(c, map[string][]string{"body": {"Invalid body"}}) + } + + categoryResponse, err := h.TrashService.CreateCategory(request) + if err != nil { + return utils.GenericResponse(c, fiber.StatusInternalServerError, "Failed to create category: "+err.Error()) + } + + return utils.CreateResponse(c, categoryResponse, "Category created successfully") +} + +func (h *TrashHandler) AddDetailToCategory(c *fiber.Ctx) error { + var request dto.RequestTrashDetailDTO + if err := c.BodyParser(&request); err != nil { + return utils.ValidationErrorResponse(c, map[string][]string{"body": {"Invalid body"}}) + } + + detailResponse, err := h.TrashService.AddDetailToCategory(request) + if err != nil { + return utils.GenericResponse(c, fiber.StatusInternalServerError, "Failed to add detail to category: "+err.Error()) + } + + return utils.CreateResponse(c, detailResponse, "Trash detail added successfully") +} + +func (h *TrashHandler) GetCategories(c *fiber.Ctx) error { + + categories, err := h.TrashService.GetCategories() + if err != nil { + return utils.GenericResponse(c, fiber.StatusInternalServerError, "Failed to fetch categories: "+err.Error()) + } + + return utils.NonPaginatedResponse(c, categories, len(categories), "Categories retrieved successfully") +} + +func (h *TrashHandler) GetCategoryByID(c *fiber.Ctx) error { + id := c.Params("category_id") + + category, err := h.TrashService.GetCategoryByID(id) + if err != nil { + return utils.GenericResponse(c, fiber.StatusNotFound, "Category not found: "+err.Error()) + } + + return utils.SuccessResponse(c, category, "Category retrieved successfully") +} + +func (h *TrashHandler) GetTrashDetailByID(c *fiber.Ctx) error { + id := c.Params("detail_id") + + detail, err := h.TrashService.GetTrashDetailByID(id) + if err != nil { + return utils.GenericResponse(c, fiber.StatusNotFound, "Trash detail not found: "+err.Error()) + } + + return utils.SuccessResponse(c, detail, "Trash detail retrieved successfully") +} + +func (h *TrashHandler) UpdateCategory(c *fiber.Ctx) error { + id := c.Params("category_id") + + var request dto.RequestTrashCategoryDTO + if err := c.BodyParser(&request); err != nil { + return utils.ValidationErrorResponse(c, map[string][]string{"body": {"Invalid request body"}}) + } + + updatedCategory, err := h.TrashService.UpdateCategory(id, request) + if err != nil { + return utils.GenericResponse(c, fiber.StatusInternalServerError, "Error updating category: "+err.Error()) + } + + return utils.SuccessResponse(c, updatedCategory, "Category updated successfully") +} + +func (h *TrashHandler) UpdateDetail(c *fiber.Ctx) error { + id := c.Params("detail_id") + + var request dto.RequestTrashDetailDTO + if err := c.BodyParser(&request); err != nil { + return utils.ValidationErrorResponse(c, map[string][]string{"body": {"Invalid request body"}}) + } + + updatedDetail, err := h.TrashService.UpdateDetail(id, request) + if err != nil { + return utils.GenericResponse(c, fiber.StatusInternalServerError, "Error updating detail: "+err.Error()) + } + + return utils.SuccessResponse(c, updatedDetail, "Trash detail updated successfully") +} + +func (h *TrashHandler) DeleteCategory(c *fiber.Ctx) error { + id := c.Params("category_id") + + if err := h.TrashService.DeleteCategory(id); err != nil { + return utils.GenericResponse(c, fiber.StatusInternalServerError, "Error deleting category: "+err.Error()) + } + + return utils.GenericResponse(c, fiber.StatusOK, "Category deleted successfully") +} + +func (h *TrashHandler) DeleteDetail(c *fiber.Ctx) error { + id := c.Params("detail_id") + + if err := h.TrashService.DeleteDetail(id); err != nil { + return utils.GenericResponse(c, fiber.StatusInternalServerError, "Error deleting detail: "+err.Error()) + } + + return utils.GenericResponse(c, fiber.StatusOK, "Trash detail deleted successfully") +} diff --git a/internal/repositories/trash_repo.go b/internal/repositories/trash_repo.go new file mode 100644 index 0000000..57ab5fc --- /dev/null +++ b/internal/repositories/trash_repo.go @@ -0,0 +1,95 @@ +package repositories + +import ( + "fmt" + + "github.com/pahmiudahgede/senggoldong/model" + "gorm.io/gorm" +) + +type TrashRepository interface { + CreateCategory(category *model.TrashCategory) error + AddDetailToCategory(detail *model.TrashDetail) error + GetCategories() ([]model.TrashCategory, error) + GetCategoryByID(id string) (*model.TrashCategory, error) + GetTrashDetailByID(id string) (*model.TrashDetail, error) + UpdateCategoryName(id string, newName string) error + UpdateTrashDetail(id string, description string, price float64) error + DeleteCategory(id string) error + DeleteTrashDetail(id string) error +} + +type trashRepository struct { + DB *gorm.DB +} + +func NewTrashRepository(db *gorm.DB) TrashRepository { + return &trashRepository{DB: db} +} + +func (r *trashRepository) CreateCategory(category *model.TrashCategory) error { + if err := r.DB.Create(category).Error; err != nil { + return fmt.Errorf("failed to create category: %v", err) + } + return nil +} + +func (r *trashRepository) AddDetailToCategory(detail *model.TrashDetail) error { + if err := r.DB.Create(detail).Error; err != nil { + return fmt.Errorf("failed to add detail to category: %v", err) + } + return nil +} + +func (r *trashRepository) GetCategories() ([]model.TrashCategory, error) { + var categories []model.TrashCategory + if err := r.DB.Preload("Details").Find(&categories).Error; err != nil { + return nil, fmt.Errorf("failed to fetch categories: %v", err) + } + return categories, nil +} + +func (r *trashRepository) GetCategoryByID(id string) (*model.TrashCategory, error) { + var category model.TrashCategory + + if err := r.DB.Preload("Details").First(&category, "id = ?", id).Error; err != nil { + return nil, fmt.Errorf("category not found: %v", err) + } + return &category, nil +} + +func (r *trashRepository) GetTrashDetailByID(id string) (*model.TrashDetail, error) { + var detail model.TrashDetail + if err := r.DB.First(&detail, "id = ?", id).Error; err != nil { + return nil, fmt.Errorf("trash detail not found: %v", err) + } + return &detail, nil +} + +func (r *trashRepository) UpdateCategoryName(id string, newName string) error { + if err := r.DB.Model(&model.TrashCategory{}).Where("id = ?", id).Update("name", newName).Error; err != nil { + return fmt.Errorf("failed to update category name: %v", err) + } + return nil +} + +func (r *trashRepository) UpdateTrashDetail(id string, description string, price float64) error { + if err := r.DB.Model(&model.TrashDetail{}).Where("id = ?", id).Updates(model.TrashDetail{Description: description, Price: price}).Error; err != nil { + return fmt.Errorf("failed to update trash detail: %v", err) + } + return nil +} + +func (r *trashRepository) DeleteCategory(id string) error { + if err := r.DB.Delete(&model.TrashCategory{}, "id = ?", id).Error; err != nil { + return fmt.Errorf("failed to delete category: %v", err) + } + return nil +} + +func (r *trashRepository) DeleteTrashDetail(id string) error { + if err := r.DB.Delete(&model.TrashDetail{}, "id = ?", id).Error; err != nil { + return fmt.Errorf("failed to delete trash detail: %v", err) + } + return nil +} diff --git a/internal/services/trash_service.go b/internal/services/trash_service.go new file mode 100644 index 0000000..df12009 --- /dev/null +++ b/internal/services/trash_service.go @@ -0,0 +1,465 @@ +package services + +import ( + "fmt" + "time" + + "github.com/pahmiudahgede/senggoldong/dto" + "github.com/pahmiudahgede/senggoldong/internal/repositories" + "github.com/pahmiudahgede/senggoldong/model" + "github.com/pahmiudahgede/senggoldong/utils" +) + +type TrashService interface { + CreateCategory(request dto.RequestTrashCategoryDTO) (*dto.ResponseTrashCategoryDTO, error) + AddDetailToCategory(request dto.RequestTrashDetailDTO) (*dto.ResponseTrashDetailDTO, error) + + GetCategories() ([]dto.ResponseTrashCategoryDTO, error) + GetCategoryByID(id string) (*dto.ResponseTrashCategoryDTO, error) + GetTrashDetailByID(id string) (*dto.ResponseTrashDetailDTO, error) + + UpdateCategory(id string, request dto.RequestTrashCategoryDTO) (*dto.ResponseTrashCategoryDTO, error) + UpdateDetail(id string, request dto.RequestTrashDetailDTO) (*dto.ResponseTrashDetailDTO, error) + + DeleteCategory(id string) error + DeleteDetail(id string) error +} + +type trashService struct { + TrashRepo repositories.TrashRepository +} + +func NewTrashService(trashRepo repositories.TrashRepository) TrashService { + return &trashService{TrashRepo: trashRepo} +} + +func (s *trashService) CreateCategory(request dto.RequestTrashCategoryDTO) (*dto.ResponseTrashCategoryDTO, error) { + errors, valid := request.ValidateTrashCategoryInput() + if !valid { + return nil, fmt.Errorf("validation error: %v", errors) + } + + category := model.TrashCategory{ + Name: request.Name, + } + + if err := s.TrashRepo.CreateCategory(&category); err != nil { + return nil, fmt.Errorf("failed to create category: %v", err) + } + + categoryResponseDTO := &dto.ResponseTrashCategoryDTO{ + ID: category.ID, + Name: category.Name, + CreatedAt: category.CreatedAt.Format("2006-01-02 15:04:05"), + UpdatedAt: category.UpdatedAt.Format("2006-01-02 15:04:05"), + } + + if err := s.CacheCategoryAndDetails(category.ID, categoryResponseDTO, category.Details, time.Hour*6); err != nil { + fmt.Printf("Error caching category: %v\n", err) + } + + categories, err := s.TrashRepo.GetCategories() + if err == nil { + var categoriesDTO []dto.ResponseTrashCategoryDTO + for _, c := range categories { + categoriesDTO = append(categoriesDTO, dto.ResponseTrashCategoryDTO{ + ID: c.ID, + Name: c.Name, + CreatedAt: c.CreatedAt.Format("2006-01-02 15:04:05"), + UpdatedAt: c.UpdatedAt.Format("2006-01-02 15:04:05"), + }) + } + + if err := s.CacheCategoryList(categoriesDTO, time.Hour*6); err != nil { + fmt.Printf("Error caching all categories: %v\n", err) + } + } + + return categoryResponseDTO, nil +} + +func (s *trashService) AddDetailToCategory(request dto.RequestTrashDetailDTO) (*dto.ResponseTrashDetailDTO, error) { + errors, valid := request.ValidateTrashDetailInput() + if !valid { + return nil, fmt.Errorf("validation error: %v", errors) + } + + detail := model.TrashDetail{ + CategoryID: request.CategoryID, + Description: request.Description, + Price: request.Price, + } + + if err := s.TrashRepo.AddDetailToCategory(&detail); err != nil { + return nil, fmt.Errorf("failed to add detail to category: %v", err) + } + + detailResponseDTO := &dto.ResponseTrashDetailDTO{ + ID: detail.ID, + CategoryID: detail.CategoryID, + Description: detail.Description, + Price: detail.Price, + CreatedAt: detail.CreatedAt.Format("2006-01-02 15:04:05"), + UpdatedAt: detail.UpdatedAt.Format("2006-01-02 15:04:05"), + } + + cacheKey := fmt.Sprintf("detail:%s", detail.ID) + cacheData := map[string]interface{}{ + "data": detailResponseDTO, + } + if err := utils.SetJSONData(cacheKey, cacheData, time.Hour*6); err != nil { + fmt.Printf("Error caching detail: %v\n", err) + } + + category, err := s.TrashRepo.GetCategoryByID(detail.CategoryID) + if err == nil { + categoryResponseDTO := &dto.ResponseTrashCategoryDTO{ + ID: category.ID, + Name: category.Name, + CreatedAt: category.CreatedAt.Format("2006-01-02 15:04:05"), + UpdatedAt: category.UpdatedAt.Format("2006-01-02 15:04:05"), + } + + if err := s.CacheCategoryAndDetails(detail.CategoryID, categoryResponseDTO, category.Details, time.Hour*6); err != nil { + fmt.Printf("Error caching updated category: %v\n", err) + } + } else { + fmt.Printf("Error fetching category for cache update: %v\n", err) + } + + return detailResponseDTO, nil +} + +func (s *trashService) GetCategories() ([]dto.ResponseTrashCategoryDTO, error) { + + cacheKey := "categories:all" + cachedCategories, err := utils.GetJSONData(cacheKey) + if err == nil && cachedCategories != nil { + var categoriesDTO []dto.ResponseTrashCategoryDTO + for _, category := range cachedCategories["data"].([]interface{}) { + categoryData := category.(map[string]interface{}) + categoriesDTO = append(categoriesDTO, dto.ResponseTrashCategoryDTO{ + ID: categoryData["id"].(string), + Name: categoryData["name"].(string), + CreatedAt: categoryData["createdAt"].(string), + UpdatedAt: categoryData["updatedAt"].(string), + }) + } + return categoriesDTO, nil + } + + categories, err := s.TrashRepo.GetCategories() + if err != nil { + return nil, fmt.Errorf("failed to fetch categories: %v", err) + } + + var categoriesDTO []dto.ResponseTrashCategoryDTO + for _, category := range categories { + categoriesDTO = append(categoriesDTO, dto.ResponseTrashCategoryDTO{ + ID: category.ID, + Name: category.Name, + CreatedAt: category.CreatedAt.Format("2006-01-02 15:04:05"), + UpdatedAt: category.UpdatedAt.Format("2006-01-02 15:04:05"), + }) + } + + cacheData := map[string]interface{}{ + "data": categoriesDTO, + } + if err := utils.SetJSONData(cacheKey, cacheData, time.Hour*6); err != nil { + fmt.Printf("Error caching categories: %v\n", err) + } + + return categoriesDTO, nil +} + +func (s *trashService) GetCategoryByID(id string) (*dto.ResponseTrashCategoryDTO, error) { + + cacheKey := fmt.Sprintf("category:%s", id) + cachedCategory, err := utils.GetJSONData(cacheKey) + if err == nil && cachedCategory != nil { + categoryData := cachedCategory["data"].(map[string]interface{}) + return &dto.ResponseTrashCategoryDTO{ + ID: categoryData["id"].(string), + Name: categoryData["name"].(string), + CreatedAt: categoryData["createdAt"].(string), + UpdatedAt: categoryData["updatedAt"].(string), + Details: mapDetails(cachedCategory["details"]), + }, nil + } + + category, err := s.TrashRepo.GetCategoryByID(id) + if err != nil { + return nil, fmt.Errorf("category not found: %v", err) + } + + categoryDTO := &dto.ResponseTrashCategoryDTO{ + ID: category.ID, + Name: category.Name, + CreatedAt: category.CreatedAt.Format("2006-01-02 15:04:05"), + UpdatedAt: category.UpdatedAt.Format("2006-01-02 15:04:05"), + } + + if category.Details != nil { + var detailsDTO []dto.ResponseTrashDetailDTO + for _, detail := range category.Details { + detailsDTO = append(detailsDTO, dto.ResponseTrashDetailDTO{ + ID: detail.ID, + CategoryID: detail.CategoryID, + Description: detail.Description, + Price: detail.Price, + CreatedAt: detail.CreatedAt.Format("2006-01-02 15:04:05"), + UpdatedAt: detail.UpdatedAt.Format("2006-01-02 15:04:05"), + }) + } + categoryDTO.Details = detailsDTO + } + + if err := s.CacheCategoryAndDetails(category.ID, categoryDTO, categoryDTO.Details, time.Hour*6); err != nil { + fmt.Printf("Error caching category and details: %v\n", err) + } + + return categoryDTO, nil +} + +func (s *trashService) GetTrashDetailByID(id string) (*dto.ResponseTrashDetailDTO, error) { + + cacheKey := fmt.Sprintf("detail:%s", id) + cachedDetail, err := utils.GetJSONData(cacheKey) + if err == nil && cachedDetail != nil { + detailData := cachedDetail["data"].(map[string]interface{}) + return &dto.ResponseTrashDetailDTO{ + ID: detailData["id"].(string), + CategoryID: detailData["category_id"].(string), + Description: detailData["description"].(string), + Price: detailData["price"].(float64), + CreatedAt: detailData["createdAt"].(string), + UpdatedAt: detailData["updatedAt"].(string), + }, nil + } + + detail, err := s.TrashRepo.GetTrashDetailByID(id) + if err != nil { + return nil, fmt.Errorf("trash detail not found: %v", err) + } + + detailDTO := &dto.ResponseTrashDetailDTO{ + ID: detail.ID, + CategoryID: detail.CategoryID, + Description: detail.Description, + Price: detail.Price, + CreatedAt: detail.CreatedAt.Format("2006-01-02 15:04:05"), + UpdatedAt: detail.UpdatedAt.Format("2006-01-02 15:04:05"), + } + + cacheData := map[string]interface{}{ + "data": detailDTO, + } + if err := utils.SetJSONData(cacheKey, cacheData, time.Hour*24); err != nil { + fmt.Printf("Error caching detail: %v\n", err) + } + + return detailDTO, nil +} + +func (s *trashService) UpdateCategory(id string, request dto.RequestTrashCategoryDTO) (*dto.ResponseTrashCategoryDTO, error) { + errors, valid := request.ValidateTrashCategoryInput() + if !valid { + return nil, fmt.Errorf("validation error: %v", errors) + } + + if err := s.TrashRepo.UpdateCategoryName(id, request.Name); err != nil { + return nil, fmt.Errorf("failed to update category: %v", err) + } + + category, err := s.TrashRepo.GetCategoryByID(id) + if err != nil { + return nil, fmt.Errorf("category not found: %v", err) + } + + categoryResponseDTO := &dto.ResponseTrashCategoryDTO{ + ID: category.ID, + Name: category.Name, + CreatedAt: category.CreatedAt.Format("2006-01-02 15:04:05"), + UpdatedAt: category.UpdatedAt.Format("2006-01-02 15:04:05"), + } + + if err := s.CacheCategoryAndDetails(category.ID, categoryResponseDTO, category.Details, time.Hour*6); err != nil { + fmt.Printf("Error caching updated category: %v\n", err) + } + + allCategories, err := s.TrashRepo.GetCategories() + if err == nil { + var categoriesDTO []dto.ResponseTrashCategoryDTO + for _, c := range allCategories { + categoriesDTO = append(categoriesDTO, dto.ResponseTrashCategoryDTO{ + ID: c.ID, + Name: c.Name, + CreatedAt: c.CreatedAt.Format("2006-01-02 15:04:05"), + UpdatedAt: c.UpdatedAt.Format("2006-01-02 15:04:05"), + }) + } + + if err := s.CacheCategoryList(categoriesDTO, time.Hour*6); err != nil { + fmt.Printf("Error caching all categories: %v\n", err) + } + } + + return categoryResponseDTO, nil +} + +func (s *trashService) UpdateDetail(id string, request dto.RequestTrashDetailDTO) (*dto.ResponseTrashDetailDTO, error) { + errors, valid := request.ValidateTrashDetailInput() + if !valid { + return nil, fmt.Errorf("validation error: %v", errors) + } + + if err := s.TrashRepo.UpdateTrashDetail(id, request.Description, request.Price); err != nil { + return nil, fmt.Errorf("failed to update detail: %v", err) + } + + detail, err := s.TrashRepo.GetTrashDetailByID(id) + if err != nil { + return nil, fmt.Errorf("trash detail not found: %v", err) + } + + detailResponseDTO := &dto.ResponseTrashDetailDTO{ + ID: detail.ID, + CategoryID: detail.CategoryID, + Description: detail.Description, + Price: detail.Price, + CreatedAt: detail.CreatedAt.Format("2006-01-02 15:04:05"), + UpdatedAt: detail.UpdatedAt.Format("2006-01-02 15:04:05"), + } + + cacheKey := fmt.Sprintf("detail:%s", detail.ID) + cacheData := map[string]interface{}{ + "data": detailResponseDTO, + } + if err := utils.SetJSONData(cacheKey, cacheData, time.Hour*6); err != nil { + fmt.Printf("Error caching updated detail: %v\n", err) + } + + category, err := s.TrashRepo.GetCategoryByID(detail.CategoryID) + if err == nil { + categoryResponseDTO := &dto.ResponseTrashCategoryDTO{ + ID: category.ID, + Name: category.Name, + CreatedAt: category.CreatedAt.Format("2006-01-02 15:04:05"), + UpdatedAt: category.UpdatedAt.Format("2006-01-02 15:04:05"), + } + + if err := s.CacheCategoryAndDetails(detail.CategoryID, categoryResponseDTO, category.Details, time.Hour*6); err != nil { + fmt.Printf("Error caching updated category: %v\n", err) + } + } else { + fmt.Printf("Error fetching category for cache update: %v\n", err) + } + + return detailResponseDTO, nil +} + +func (s *trashService) DeleteCategory(id string) error { + + if err := s.TrashRepo.DeleteCategory(id); err != nil { + return fmt.Errorf("failed to delete category: %v", err) + } + + cacheKey := fmt.Sprintf("category:%s", id) + if err := utils.DeleteData(cacheKey); err != nil { + fmt.Printf("Error clearing cache for deleted category: %v\n", err) + } + + allCategoriesCacheKey := "categories:all" + if err := utils.DeleteData(allCategoriesCacheKey); err != nil { + fmt.Printf("Error clearing categories list cache: %v\n", err) + } + + category, err := s.TrashRepo.GetCategoryByID(id) + if err != nil { + return fmt.Errorf("category not found after deletion: %v", err) + } + + if category.Details != nil { + for _, detail := range category.Details { + detailCacheKey := fmt.Sprintf("detail:%s", detail.ID) + + if err := utils.DeleteData(detailCacheKey); err != nil { + fmt.Printf("Error clearing cache for deleted detail: %v\n", err) + } + } + } + + return nil +} + +func (s *trashService) DeleteDetail(id string) error { + + detail, err := s.TrashRepo.GetTrashDetailByID(id) + if err != nil { + return fmt.Errorf("trash detail not found: %v", err) + } + + if err := s.TrashRepo.DeleteTrashDetail(id); err != nil { + return fmt.Errorf("failed to delete detail: %v", err) + } + + cacheKey := fmt.Sprintf("detail:%s", id) + if err := utils.DeleteData(cacheKey); err != nil { + fmt.Printf("Error clearing cache for deleted detail: %v\n", err) + } + + categoryCacheKey := fmt.Sprintf("category:%s", detail.CategoryID) + if err := utils.DeleteData(categoryCacheKey); err != nil { + fmt.Printf("Error clearing cache for category after detail deletion: %v\n", err) + } + + return nil +} + +func mapDetails(details interface{}) []dto.ResponseTrashDetailDTO { + var detailsDTO []dto.ResponseTrashDetailDTO + if details != nil { + for _, detail := range details.([]interface{}) { + detailData := detail.(map[string]interface{}) + detailsDTO = append(detailsDTO, dto.ResponseTrashDetailDTO{ + ID: detailData["id"].(string), + CategoryID: detailData["category_id"].(string), + Description: detailData["description"].(string), + Price: detailData["price"].(float64), + CreatedAt: detailData["createdAt"].(string), + UpdatedAt: detailData["updatedAt"].(string), + }) + } + } + return detailsDTO +} + +func (s *trashService) CacheCategoryAndDetails(categoryID string, categoryData interface{}, detailsData interface{}, expiration time.Duration) error { + cacheKey := fmt.Sprintf("category:%s", categoryID) + cacheData := map[string]interface{}{ + "data": categoryData, + "details": detailsData, + } + + err := utils.SetJSONData(cacheKey, cacheData, expiration) + if err != nil { + return fmt.Errorf("error caching category and details: %v", err) + } + + return nil +} + +func (s *trashService) CacheCategoryList(categoriesData interface{}, expiration time.Duration) error { + cacheKey := "categories:all" + cacheData := map[string]interface{}{ + "data": categoriesData, + } + + err := utils.SetJSONData(cacheKey, cacheData, expiration) + if err != nil { + return fmt.Errorf("error caching categories list: %v", err) + } + + return nil +} diff --git a/model/trash_model.go b/model/trash_model.go new file mode 100644 index 0000000..5bd05fb --- /dev/null +++ b/model/trash_model.go @@ -0,0 +1,20 @@ +package model + +import "time" + +type TrashCategory struct { + ID string `gorm:"primaryKey;type:uuid;default:uuid_generate_v4()" json:"id"` + Name string `gorm:"not null" json:"name"` + Details []TrashDetail `gorm:"foreignKey:CategoryID;constraint:OnDelete:CASCADE;" json:"details"` + CreatedAt time.Time `gorm:"default:current_timestamp" json:"createdAt"` + UpdatedAt time.Time `gorm:"default:current_timestamp" json:"updatedAt"` +} + +type TrashDetail struct { + ID string `gorm:"primaryKey;type:uuid;default:uuid_generate_v4()" json:"id"` + CategoryID string `gorm:"type:uuid;not null" json:"category_id"` + Description string `gorm:"not null" json:"description"` + Price float64 `gorm:"not null" json:"price"` + CreatedAt time.Time `gorm:"default:current_timestamp" json:"createdAt"` + UpdatedAt time.Time `gorm:"default:current_timestamp" json:"updatedAt"` +} diff --git a/presentation/trash_route.go b/presentation/trash_route.go new file mode 100644 index 0000000..f183797 --- /dev/null +++ b/presentation/trash_route.go @@ -0,0 +1,32 @@ +package presentation + +import ( + "github.com/gofiber/fiber/v2" + "github.com/pahmiudahgede/senggoldong/config" + "github.com/pahmiudahgede/senggoldong/internal/handler" + "github.com/pahmiudahgede/senggoldong/internal/repositories" + "github.com/pahmiudahgede/senggoldong/internal/services" + "github.com/pahmiudahgede/senggoldong/middleware" + "github.com/pahmiudahgede/senggoldong/utils" +) + +func TrashRouter(api fiber.Router) { + + trashRepo := repositories.NewTrashRepository(config.DB) + trashService := services.NewTrashService(trashRepo) + trashHandler := handler.NewTrashHandler(trashService) + + trashAPI := api.Group("/trash") + + trashAPI.Post("/createcategory-with-details", middleware.AuthMiddleware, middleware.RoleMiddleware(utils.RoleAdministrator), trashHandler.CreateCategory) + trashAPI.Post("/add-detailinexistingcategory", middleware.AuthMiddleware, middleware.RoleMiddleware(utils.RoleAdministrator), trashHandler.AddDetailToCategory) + trashAPI.Get("/allcategory", middleware.AuthMiddleware, middleware.RoleMiddleware(utils.RoleAdministrator, utils.RolePengelola, utils.RolePengepul), trashHandler.GetCategories) + trashAPI.Get("/category/:category_id", middleware.AuthMiddleware, middleware.RoleMiddleware(utils.RoleAdministrator, utils.RolePengelola, utils.RolePengepul), trashHandler.GetCategoryByID) + trashAPI.Get("/detail/:detail_id", middleware.AuthMiddleware, middleware.RoleMiddleware(utils.RoleAdministrator, utils.RolePengelola, utils.RolePengepul), trashHandler.GetTrashDetailByID) + + trashAPI.Patch("/update-category/:category_id", middleware.AuthMiddleware, middleware.RoleMiddleware(utils.RoleAdministrator), trashHandler.UpdateCategory) + trashAPI.Put("/update-detail/:detail_id", middleware.AuthMiddleware, middleware.RoleMiddleware(utils.RoleAdministrator), trashHandler.UpdateDetail) + + trashAPI.Delete("delete-category/:category_id", middleware.AuthMiddleware, middleware.RoleMiddleware(utils.RoleAdministrator), trashHandler.DeleteCategory) + trashAPI.Delete("delete-detail/:detail_id", middleware.AuthMiddleware, middleware.RoleMiddleware(utils.RoleAdministrator), trashHandler.DeleteDetail) +} diff --git a/router/setup_routes.go.go b/router/setup_routes.go.go index 8841ff7..374dbac 100644 --- a/router/setup_routes.go.go +++ b/router/setup_routes.go.go @@ -7,7 +7,7 @@ import ( ) func SetupRoutes(app *fiber.App) { - api := app.Group("/apirijikid") + api := app.Group("/apirijikid/v2") api.Use(middleware.APIKeyMiddleware) presentation.AuthRouter(api) @@ -19,4 +19,5 @@ func SetupRoutes(app *fiber.App) { presentation.ArticleRouter(api) presentation.BannerRouter(api) presentation.InitialCointRoute(api) + presentation.TrashRouter(api) }