751 lines
25 KiB
Go
751 lines
25 KiB
Go
package trash
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"log"
|
|
"mime/multipart"
|
|
"os"
|
|
"path/filepath"
|
|
"rijig/model"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
type TrashServiceInterface interface {
|
|
CreateTrashCategory(ctx context.Context, req RequestTrashCategoryDTO) (*ResponseTrashCategoryDTO, error)
|
|
CreateTrashCategoryWithDetails(ctx context.Context, categoryReq RequestTrashCategoryDTO, detailsReq []RequestTrashDetailDTO) (*ResponseTrashCategoryDTO, error)
|
|
CreateTrashCategoryWithIcon(ctx context.Context, req RequestTrashCategoryDTO, iconFile *multipart.FileHeader) (*ResponseTrashCategoryDTO, error)
|
|
UpdateTrashCategory(ctx context.Context, id string, req RequestTrashCategoryDTO) (*ResponseTrashCategoryDTO, error)
|
|
UpdateTrashCategoryWithIcon(ctx context.Context, id string, req RequestTrashCategoryDTO, iconFile *multipart.FileHeader) (*ResponseTrashCategoryDTO, error)
|
|
GetAllTrashCategories(ctx context.Context) ([]ResponseTrashCategoryDTO, error)
|
|
GetAllTrashCategoriesWithDetails(ctx context.Context) ([]ResponseTrashCategoryDTO, error)
|
|
GetTrashCategoryByID(ctx context.Context, id string) (*ResponseTrashCategoryDTO, error)
|
|
GetTrashCategoryByIDWithDetails(ctx context.Context, id string) (*ResponseTrashCategoryDTO, error)
|
|
DeleteTrashCategory(ctx context.Context, id string) error
|
|
|
|
CreateTrashDetail(ctx context.Context, req RequestTrashDetailDTO) (*ResponseTrashDetailDTO, error)
|
|
CreateTrashDetailWithIcon(ctx context.Context, req RequestTrashDetailDTO, iconFile *multipart.FileHeader) (*ResponseTrashDetailDTO, error)
|
|
AddTrashDetailToCategory(ctx context.Context, categoryID string, req RequestTrashDetailDTO) (*ResponseTrashDetailDTO, error)
|
|
AddTrashDetailToCategoryWithIcon(ctx context.Context, categoryID string, req RequestTrashDetailDTO, iconFile *multipart.FileHeader) (*ResponseTrashDetailDTO, error)
|
|
UpdateTrashDetail(ctx context.Context, id string, req RequestTrashDetailDTO) (*ResponseTrashDetailDTO, error)
|
|
UpdateTrashDetailWithIcon(ctx context.Context, id string, req RequestTrashDetailDTO, iconFile *multipart.FileHeader) (*ResponseTrashDetailDTO, error)
|
|
GetTrashDetailsByCategory(ctx context.Context, categoryID string) ([]ResponseTrashDetailDTO, error)
|
|
GetTrashDetailByID(ctx context.Context, id string) (*ResponseTrashDetailDTO, error)
|
|
DeleteTrashDetail(ctx context.Context, id string) error
|
|
|
|
BulkCreateTrashDetails(ctx context.Context, categoryID string, detailsReq []RequestTrashDetailDTO) ([]ResponseTrashDetailDTO, error)
|
|
BulkDeleteTrashDetails(ctx context.Context, detailIDs []string) error
|
|
ReorderTrashDetails(ctx context.Context, categoryID string, orderedDetailIDs []string) error
|
|
}
|
|
|
|
type TrashService struct {
|
|
trashRepo TrashRepositoryInterface
|
|
}
|
|
|
|
func NewTrashService(trashRepo TrashRepositoryInterface) TrashServiceInterface {
|
|
return &TrashService{
|
|
trashRepo: trashRepo,
|
|
}
|
|
}
|
|
|
|
func (s *TrashService) saveIconOfTrash(iconTrash *multipart.FileHeader) (string, error) {
|
|
pathImage := "/uploads/icontrash/"
|
|
iconTrashDir := "./public" + os.Getenv("BASE_URL") + pathImage
|
|
|
|
if _, err := os.Stat(iconTrashDir); os.IsNotExist(err) {
|
|
if err := os.MkdirAll(iconTrashDir, os.ModePerm); err != nil {
|
|
return "", fmt.Errorf("failed to create directory for icon trash: %v", err)
|
|
}
|
|
}
|
|
|
|
allowedExtensions := map[string]bool{".jpg": true, ".jpeg": true, ".png": true, ".svg": true}
|
|
extension := strings.ToLower(filepath.Ext(iconTrash.Filename))
|
|
if !allowedExtensions[extension] {
|
|
return "", fmt.Errorf("invalid file type, only .jpg, .jpeg, .png, and .svg are allowed")
|
|
}
|
|
|
|
iconTrashFileName := fmt.Sprintf("%s_icontrash%s", uuid.New().String(), extension)
|
|
iconTrashPath := filepath.Join(iconTrashDir, iconTrashFileName)
|
|
|
|
src, err := iconTrash.Open()
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to open uploaded file: %v", err)
|
|
}
|
|
defer src.Close()
|
|
|
|
dst, err := os.Create(iconTrashPath)
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to create icon trash file: %v", err)
|
|
}
|
|
defer dst.Close()
|
|
|
|
if _, err := dst.ReadFrom(src); err != nil {
|
|
return "", fmt.Errorf("failed to save icon trash: %v", err)
|
|
}
|
|
|
|
iconTrashUrl := fmt.Sprintf("%s%s", pathImage, iconTrashFileName)
|
|
return iconTrashUrl, nil
|
|
}
|
|
|
|
func (s *TrashService) saveIconOfTrashDetail(iconTrashDetail *multipart.FileHeader) (string, error) {
|
|
pathImage := "/uploads/icontrashdetail/"
|
|
iconTrashDetailDir := "./public" + os.Getenv("BASE_URL") + pathImage
|
|
|
|
if _, err := os.Stat(iconTrashDetailDir); os.IsNotExist(err) {
|
|
if err := os.MkdirAll(iconTrashDetailDir, os.ModePerm); err != nil {
|
|
return "", fmt.Errorf("failed to create directory for icon trash detail: %v", err)
|
|
}
|
|
}
|
|
|
|
allowedExtensions := map[string]bool{".jpg": true, ".jpeg": true, ".png": true, ".svg": true}
|
|
extension := strings.ToLower(filepath.Ext(iconTrashDetail.Filename))
|
|
if !allowedExtensions[extension] {
|
|
return "", fmt.Errorf("invalid file type, only .jpg, .jpeg, .png, and .svg are allowed")
|
|
}
|
|
|
|
iconTrashDetailFileName := fmt.Sprintf("%s_icontrashdetail%s", uuid.New().String(), extension)
|
|
iconTrashDetailPath := filepath.Join(iconTrashDetailDir, iconTrashDetailFileName)
|
|
|
|
src, err := iconTrashDetail.Open()
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to open uploaded file: %v", err)
|
|
}
|
|
defer src.Close()
|
|
|
|
dst, err := os.Create(iconTrashDetailPath)
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to create icon trash detail file: %v", err)
|
|
}
|
|
defer dst.Close()
|
|
|
|
if _, err := dst.ReadFrom(src); err != nil {
|
|
return "", fmt.Errorf("failed to save icon trash detail: %v", err)
|
|
}
|
|
|
|
iconTrashDetailUrl := fmt.Sprintf("%s%s", pathImage, iconTrashDetailFileName)
|
|
return iconTrashDetailUrl, nil
|
|
}
|
|
|
|
func (s *TrashService) deleteIconTrashFile(imagePath string) error {
|
|
if imagePath == "" {
|
|
return nil
|
|
}
|
|
|
|
baseDir := "./public/" + os.Getenv("BASE_URL")
|
|
absolutePath := baseDir + imagePath
|
|
|
|
if _, err := os.Stat(absolutePath); os.IsNotExist(err) {
|
|
return fmt.Errorf("image file not found: %v", err)
|
|
}
|
|
|
|
err := os.Remove(absolutePath)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to delete image: %v", err)
|
|
}
|
|
|
|
log.Printf("Image deleted successfully: %s", absolutePath)
|
|
return nil
|
|
}
|
|
|
|
func (s *TrashService) deleteIconTrashDetailFile(imagePath string) error {
|
|
if imagePath == "" {
|
|
return nil
|
|
}
|
|
|
|
baseDir := "./public/" + os.Getenv("BASE_URL")
|
|
absolutePath := baseDir + imagePath
|
|
|
|
if _, err := os.Stat(absolutePath); os.IsNotExist(err) {
|
|
return fmt.Errorf("image file not found: %v", err)
|
|
}
|
|
|
|
err := os.Remove(absolutePath)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to delete image: %v", err)
|
|
}
|
|
|
|
log.Printf("Trash detail image deleted successfully: %s", absolutePath)
|
|
return nil
|
|
}
|
|
|
|
func (s *TrashService) CreateTrashCategoryWithIcon(ctx context.Context, req RequestTrashCategoryDTO, iconFile *multipart.FileHeader) (*ResponseTrashCategoryDTO, error) {
|
|
if errors, valid := req.ValidateRequestTrashCategoryDTO(); !valid {
|
|
return nil, fmt.Errorf("validation failed: %v", errors)
|
|
}
|
|
|
|
var iconUrl string
|
|
var err error
|
|
|
|
if iconFile != nil {
|
|
iconUrl, err = s.saveIconOfTrash(iconFile)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to save icon: %w", err)
|
|
}
|
|
}
|
|
|
|
category := &model.TrashCategory{
|
|
Name: req.Name,
|
|
IconTrash: iconUrl,
|
|
EstimatedPrice: req.EstimatedPrice,
|
|
Variety: req.Variety,
|
|
}
|
|
|
|
if err := s.trashRepo.CreateTrashCategory(ctx, category); err != nil {
|
|
|
|
if iconUrl != "" {
|
|
s.deleteIconTrashFile(iconUrl)
|
|
}
|
|
return nil, fmt.Errorf("failed to create trash category: %w", err)
|
|
}
|
|
|
|
response := s.convertTrashCategoryToResponseDTO(category)
|
|
return response, nil
|
|
}
|
|
|
|
func (s *TrashService) UpdateTrashCategoryWithIcon(ctx context.Context, id string, req RequestTrashCategoryDTO, iconFile *multipart.FileHeader) (*ResponseTrashCategoryDTO, error) {
|
|
if errors, valid := req.ValidateRequestTrashCategoryDTO(); !valid {
|
|
return nil, fmt.Errorf("validation failed: %v", errors)
|
|
}
|
|
|
|
existingCategory, err := s.trashRepo.GetTrashCategoryByID(ctx, id)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get existing category: %w", err)
|
|
}
|
|
|
|
var iconUrl string = existingCategory.IconTrash
|
|
|
|
if iconFile != nil {
|
|
newIconUrl, err := s.saveIconOfTrash(iconFile)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to save new icon: %w", err)
|
|
}
|
|
iconUrl = newIconUrl
|
|
}
|
|
|
|
updates := map[string]interface{}{
|
|
"name": req.Name,
|
|
"icon_trash": iconUrl,
|
|
"estimated_price": req.EstimatedPrice,
|
|
"variety": req.Variety,
|
|
}
|
|
|
|
if err := s.trashRepo.UpdateTrashCategory(ctx, id, updates); err != nil {
|
|
|
|
if iconFile != nil && iconUrl != existingCategory.IconTrash {
|
|
s.deleteIconTrashFile(iconUrl)
|
|
}
|
|
return nil, fmt.Errorf("failed to update trash category: %w", err)
|
|
}
|
|
|
|
if iconFile != nil && existingCategory.IconTrash != "" && iconUrl != existingCategory.IconTrash {
|
|
if err := s.deleteIconTrashFile(existingCategory.IconTrash); err != nil {
|
|
log.Printf("Warning: failed to delete old icon: %v", err)
|
|
}
|
|
}
|
|
|
|
updatedCategory, err := s.trashRepo.GetTrashCategoryByID(ctx, id)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to retrieve updated category: %w", err)
|
|
}
|
|
|
|
response := s.convertTrashCategoryToResponseDTO(updatedCategory)
|
|
return response, nil
|
|
}
|
|
|
|
func (s *TrashService) CreateTrashDetailWithIcon(ctx context.Context, req RequestTrashDetailDTO, iconFile *multipart.FileHeader) (*ResponseTrashDetailDTO, error) {
|
|
if errors, valid := req.ValidateRequestTrashDetailDTO(); !valid {
|
|
return nil, fmt.Errorf("validation failed: %v", errors)
|
|
}
|
|
|
|
var iconUrl string
|
|
var err error
|
|
|
|
if iconFile != nil {
|
|
iconUrl, err = s.saveIconOfTrashDetail(iconFile)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to save icon: %w", err)
|
|
}
|
|
}
|
|
|
|
detail := &model.TrashDetail{
|
|
TrashCategoryID: req.CategoryID,
|
|
IconTrashDetail: iconUrl,
|
|
Description: req.Description,
|
|
StepOrder: req.StepOrder,
|
|
}
|
|
|
|
if err := s.trashRepo.CreateTrashDetail(ctx, detail); err != nil {
|
|
|
|
if iconUrl != "" {
|
|
s.deleteIconTrashDetailFile(iconUrl)
|
|
}
|
|
return nil, fmt.Errorf("failed to create trash detail: %w", err)
|
|
}
|
|
|
|
response := s.convertTrashDetailToResponseDTO(detail)
|
|
return response, nil
|
|
}
|
|
|
|
func (s *TrashService) AddTrashDetailToCategoryWithIcon(ctx context.Context, categoryID string, req RequestTrashDetailDTO, iconFile *multipart.FileHeader) (*ResponseTrashDetailDTO, error) {
|
|
if errors, valid := req.ValidateRequestTrashDetailDTO(); !valid {
|
|
return nil, fmt.Errorf("validation failed: %v", errors)
|
|
}
|
|
|
|
var iconUrl string
|
|
var err error
|
|
|
|
if iconFile != nil {
|
|
iconUrl, err = s.saveIconOfTrashDetail(iconFile)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to save icon: %w", err)
|
|
}
|
|
}
|
|
|
|
detail := &model.TrashDetail{
|
|
IconTrashDetail: iconUrl,
|
|
Description: req.Description,
|
|
StepOrder: req.StepOrder,
|
|
}
|
|
|
|
if err := s.trashRepo.AddTrashDetailToCategory(ctx, categoryID, detail); err != nil {
|
|
|
|
if iconUrl != "" {
|
|
s.deleteIconTrashDetailFile(iconUrl)
|
|
}
|
|
return nil, fmt.Errorf("failed to add trash detail to category: %w", err)
|
|
}
|
|
|
|
response := s.convertTrashDetailToResponseDTO(detail)
|
|
return response, nil
|
|
}
|
|
|
|
func (s *TrashService) UpdateTrashDetailWithIcon(ctx context.Context, id string, req RequestTrashDetailDTO, iconFile *multipart.FileHeader) (*ResponseTrashDetailDTO, error) {
|
|
if errors, valid := req.ValidateRequestTrashDetailDTO(); !valid {
|
|
return nil, fmt.Errorf("validation failed: %v", errors)
|
|
}
|
|
|
|
existingDetail, err := s.trashRepo.GetTrashDetailByID(ctx, id)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get existing detail: %w", err)
|
|
}
|
|
|
|
var iconUrl string = existingDetail.IconTrashDetail
|
|
|
|
if iconFile != nil {
|
|
newIconUrl, err := s.saveIconOfTrashDetail(iconFile)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to save new icon: %w", err)
|
|
}
|
|
iconUrl = newIconUrl
|
|
}
|
|
|
|
updates := map[string]interface{}{
|
|
"icon_trash_detail": iconUrl,
|
|
"description": req.Description,
|
|
"step_order": req.StepOrder,
|
|
}
|
|
|
|
if err := s.trashRepo.UpdateTrashDetail(ctx, id, updates); err != nil {
|
|
|
|
if iconFile != nil && iconUrl != existingDetail.IconTrashDetail {
|
|
s.deleteIconTrashDetailFile(iconUrl)
|
|
}
|
|
return nil, fmt.Errorf("failed to update trash detail: %w", err)
|
|
}
|
|
|
|
if iconFile != nil && existingDetail.IconTrashDetail != "" && iconUrl != existingDetail.IconTrashDetail {
|
|
if err := s.deleteIconTrashDetailFile(existingDetail.IconTrashDetail); err != nil {
|
|
log.Printf("Warning: failed to delete old icon: %v", err)
|
|
}
|
|
}
|
|
|
|
updatedDetail, err := s.trashRepo.GetTrashDetailByID(ctx, id)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to retrieve updated detail: %w", err)
|
|
}
|
|
|
|
response := s.convertTrashDetailToResponseDTO(updatedDetail)
|
|
return response, nil
|
|
}
|
|
|
|
func (s *TrashService) CreateTrashCategory(ctx context.Context, req RequestTrashCategoryDTO) (*ResponseTrashCategoryDTO, error) {
|
|
if errors, valid := req.ValidateRequestTrashCategoryDTO(); !valid {
|
|
return nil, fmt.Errorf("validation failed: %v", errors)
|
|
}
|
|
|
|
category := &model.TrashCategory{
|
|
Name: req.Name,
|
|
IconTrash: req.IconTrash,
|
|
EstimatedPrice: req.EstimatedPrice,
|
|
Variety: req.Variety,
|
|
}
|
|
|
|
if err := s.trashRepo.CreateTrashCategory(ctx, category); err != nil {
|
|
return nil, fmt.Errorf("failed to create trash category: %w", err)
|
|
}
|
|
|
|
response := s.convertTrashCategoryToResponseDTO(category)
|
|
return response, nil
|
|
}
|
|
|
|
func (s *TrashService) CreateTrashCategoryWithDetails(ctx context.Context, categoryReq RequestTrashCategoryDTO, detailsReq []RequestTrashDetailDTO) (*ResponseTrashCategoryDTO, error) {
|
|
if errors, valid := categoryReq.ValidateRequestTrashCategoryDTO(); !valid {
|
|
return nil, fmt.Errorf("category validation failed: %v", errors)
|
|
}
|
|
|
|
for i, detailReq := range detailsReq {
|
|
if errors, valid := detailReq.ValidateRequestTrashDetailDTO(); !valid {
|
|
return nil, fmt.Errorf("detail %d validation failed: %v", i+1, errors)
|
|
}
|
|
}
|
|
|
|
category := &model.TrashCategory{
|
|
Name: categoryReq.Name,
|
|
IconTrash: categoryReq.IconTrash,
|
|
EstimatedPrice: categoryReq.EstimatedPrice,
|
|
Variety: categoryReq.Variety,
|
|
}
|
|
|
|
details := make([]model.TrashDetail, len(detailsReq))
|
|
for i, detailReq := range detailsReq {
|
|
details[i] = model.TrashDetail{
|
|
IconTrashDetail: detailReq.IconTrashDetail,
|
|
Description: detailReq.Description,
|
|
StepOrder: detailReq.StepOrder,
|
|
}
|
|
}
|
|
|
|
if err := s.trashRepo.CreateTrashCategoryWithDetails(ctx, category, details); err != nil {
|
|
return nil, fmt.Errorf("failed to create trash category with details: %w", err)
|
|
}
|
|
|
|
createdCategory, err := s.trashRepo.GetTrashCategoryByIDWithDetails(ctx, category.ID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to retrieve created category: %w", err)
|
|
}
|
|
|
|
response := s.convertTrashCategoryToResponseDTOWithDetails(createdCategory)
|
|
return response, nil
|
|
}
|
|
|
|
func (s *TrashService) UpdateTrashCategory(ctx context.Context, id string, req RequestTrashCategoryDTO) (*ResponseTrashCategoryDTO, error) {
|
|
if errors, valid := req.ValidateRequestTrashCategoryDTO(); !valid {
|
|
return nil, fmt.Errorf("validation failed: %v", errors)
|
|
}
|
|
|
|
exists, err := s.trashRepo.CheckTrashCategoryExists(ctx, id)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to check category existence: %w", err)
|
|
}
|
|
if !exists {
|
|
return nil, errors.New("trash category not found")
|
|
}
|
|
|
|
updates := map[string]interface{}{
|
|
"name": req.Name,
|
|
"icon_trash": req.IconTrash,
|
|
"estimated_price": req.EstimatedPrice,
|
|
"variety": req.Variety,
|
|
}
|
|
|
|
if err := s.trashRepo.UpdateTrashCategory(ctx, id, updates); err != nil {
|
|
return nil, fmt.Errorf("failed to update trash category: %w", err)
|
|
}
|
|
|
|
updatedCategory, err := s.trashRepo.GetTrashCategoryByID(ctx, id)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to retrieve updated category: %w", err)
|
|
}
|
|
|
|
response := s.convertTrashCategoryToResponseDTO(updatedCategory)
|
|
return response, nil
|
|
}
|
|
|
|
func (s *TrashService) GetAllTrashCategories(ctx context.Context) ([]ResponseTrashCategoryDTO, error) {
|
|
categories, err := s.trashRepo.GetAllTrashCategories(ctx)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get trash categories: %w", err)
|
|
}
|
|
|
|
responses := make([]ResponseTrashCategoryDTO, len(categories))
|
|
for i, category := range categories {
|
|
responses[i] = *s.convertTrashCategoryToResponseDTO(&category)
|
|
}
|
|
|
|
return responses, nil
|
|
}
|
|
|
|
func (s *TrashService) GetAllTrashCategoriesWithDetails(ctx context.Context) ([]ResponseTrashCategoryDTO, error) {
|
|
categories, err := s.trashRepo.GetAllTrashCategoriesWithDetails(ctx)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get trash categories with details: %w", err)
|
|
}
|
|
|
|
responses := make([]ResponseTrashCategoryDTO, len(categories))
|
|
for i, category := range categories {
|
|
responses[i] = *s.convertTrashCategoryToResponseDTOWithDetails(&category)
|
|
}
|
|
|
|
return responses, nil
|
|
}
|
|
|
|
func (s *TrashService) GetTrashCategoryByID(ctx context.Context, id string) (*ResponseTrashCategoryDTO, error) {
|
|
category, err := s.trashRepo.GetTrashCategoryByID(ctx, id)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get trash category: %w", err)
|
|
}
|
|
|
|
response := s.convertTrashCategoryToResponseDTO(category)
|
|
return response, nil
|
|
}
|
|
|
|
func (s *TrashService) GetTrashCategoryByIDWithDetails(ctx context.Context, id string) (*ResponseTrashCategoryDTO, error) {
|
|
category, err := s.trashRepo.GetTrashCategoryByIDWithDetails(ctx, id)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get trash category with details: %w", err)
|
|
}
|
|
|
|
response := s.convertTrashCategoryToResponseDTOWithDetails(category)
|
|
return response, nil
|
|
}
|
|
|
|
func (s *TrashService) DeleteTrashCategory(ctx context.Context, id string) error {
|
|
|
|
category, err := s.trashRepo.GetTrashCategoryByID(ctx, id)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get category: %w", err)
|
|
}
|
|
|
|
if err := s.trashRepo.DeleteTrashCategory(ctx, id); err != nil {
|
|
return fmt.Errorf("failed to delete trash category: %w", err)
|
|
}
|
|
|
|
if category.IconTrash != "" {
|
|
if err := s.deleteIconTrashFile(category.IconTrash); err != nil {
|
|
log.Printf("Warning: failed to delete category icon: %v", err)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *TrashService) CreateTrashDetail(ctx context.Context, req RequestTrashDetailDTO) (*ResponseTrashDetailDTO, error) {
|
|
if errors, valid := req.ValidateRequestTrashDetailDTO(); !valid {
|
|
return nil, fmt.Errorf("validation failed: %v", errors)
|
|
}
|
|
|
|
detail := &model.TrashDetail{
|
|
TrashCategoryID: req.CategoryID,
|
|
IconTrashDetail: req.IconTrashDetail,
|
|
Description: req.Description,
|
|
StepOrder: req.StepOrder,
|
|
}
|
|
|
|
if err := s.trashRepo.CreateTrashDetail(ctx, detail); err != nil {
|
|
return nil, fmt.Errorf("failed to create trash detail: %w", err)
|
|
}
|
|
|
|
response := s.convertTrashDetailToResponseDTO(detail)
|
|
return response, nil
|
|
}
|
|
|
|
func (s *TrashService) AddTrashDetailToCategory(ctx context.Context, categoryID string, req RequestTrashDetailDTO) (*ResponseTrashDetailDTO, error) {
|
|
if errors, valid := req.ValidateRequestTrashDetailDTO(); !valid {
|
|
return nil, fmt.Errorf("validation failed: %v", errors)
|
|
}
|
|
|
|
detail := &model.TrashDetail{
|
|
IconTrashDetail: req.IconTrashDetail,
|
|
Description: req.Description,
|
|
StepOrder: req.StepOrder,
|
|
}
|
|
|
|
if err := s.trashRepo.AddTrashDetailToCategory(ctx, categoryID, detail); err != nil {
|
|
return nil, fmt.Errorf("failed to add trash detail to category: %w", err)
|
|
}
|
|
|
|
response := s.convertTrashDetailToResponseDTO(detail)
|
|
return response, nil
|
|
}
|
|
|
|
func (s *TrashService) UpdateTrashDetail(ctx context.Context, id string, req RequestTrashDetailDTO) (*ResponseTrashDetailDTO, error) {
|
|
if errors, valid := req.ValidateRequestTrashDetailDTO(); !valid {
|
|
return nil, fmt.Errorf("validation failed: %v", errors)
|
|
}
|
|
|
|
exists, err := s.trashRepo.CheckTrashDetailExists(ctx, id)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to check detail existence: %w", err)
|
|
}
|
|
if !exists {
|
|
return nil, errors.New("trash detail not found")
|
|
}
|
|
|
|
updates := map[string]interface{}{
|
|
"icon_trash_detail": req.IconTrashDetail,
|
|
"description": req.Description,
|
|
"step_order": req.StepOrder,
|
|
}
|
|
|
|
if err := s.trashRepo.UpdateTrashDetail(ctx, id, updates); err != nil {
|
|
return nil, fmt.Errorf("failed to update trash detail: %w", err)
|
|
}
|
|
|
|
updatedDetail, err := s.trashRepo.GetTrashDetailByID(ctx, id)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to retrieve updated detail: %w", err)
|
|
}
|
|
|
|
response := s.convertTrashDetailToResponseDTO(updatedDetail)
|
|
return response, nil
|
|
}
|
|
|
|
func (s *TrashService) GetTrashDetailsByCategory(ctx context.Context, categoryID string) ([]ResponseTrashDetailDTO, error) {
|
|
exists, err := s.trashRepo.CheckTrashCategoryExists(ctx, categoryID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to check category existence: %w", err)
|
|
}
|
|
if !exists {
|
|
return nil, errors.New("trash category not found")
|
|
}
|
|
|
|
details, err := s.trashRepo.GetTrashDetailsByCategory(ctx, categoryID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get trash details: %w", err)
|
|
}
|
|
|
|
responses := make([]ResponseTrashDetailDTO, len(details))
|
|
for i, detail := range details {
|
|
responses[i] = *s.convertTrashDetailToResponseDTO(&detail)
|
|
}
|
|
|
|
return responses, nil
|
|
}
|
|
|
|
func (s *TrashService) GetTrashDetailByID(ctx context.Context, id string) (*ResponseTrashDetailDTO, error) {
|
|
detail, err := s.trashRepo.GetTrashDetailByID(ctx, id)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get trash detail: %w", err)
|
|
}
|
|
|
|
response := s.convertTrashDetailToResponseDTO(detail)
|
|
return response, nil
|
|
}
|
|
|
|
func (s *TrashService) DeleteTrashDetail(ctx context.Context, id string) error {
|
|
|
|
detail, err := s.trashRepo.GetTrashDetailByID(ctx, id)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get detail: %w", err)
|
|
}
|
|
|
|
if err := s.trashRepo.DeleteTrashDetail(ctx, id); err != nil {
|
|
return fmt.Errorf("failed to delete trash detail: %w", err)
|
|
}
|
|
|
|
if detail.IconTrashDetail != "" {
|
|
if err := s.deleteIconTrashDetailFile(detail.IconTrashDetail); err != nil {
|
|
log.Printf("Warning: failed to delete detail icon: %v", err)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *TrashService) BulkCreateTrashDetails(ctx context.Context, categoryID string, detailsReq []RequestTrashDetailDTO) ([]ResponseTrashDetailDTO, error) {
|
|
exists, err := s.trashRepo.CheckTrashCategoryExists(ctx, categoryID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to check category existence: %w", err)
|
|
}
|
|
if !exists {
|
|
return nil, errors.New("trash category not found")
|
|
}
|
|
|
|
for i, detailReq := range detailsReq {
|
|
if errors, valid := detailReq.ValidateRequestTrashDetailDTO(); !valid {
|
|
return nil, fmt.Errorf("detail %d validation failed: %v", i+1, errors)
|
|
}
|
|
}
|
|
|
|
responses := make([]ResponseTrashDetailDTO, len(detailsReq))
|
|
for i, detailReq := range detailsReq {
|
|
response, err := s.AddTrashDetailToCategory(ctx, categoryID, detailReq)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create detail %d: %w", i+1, err)
|
|
}
|
|
responses[i] = *response
|
|
}
|
|
|
|
return responses, nil
|
|
}
|
|
|
|
func (s *TrashService) BulkDeleteTrashDetails(ctx context.Context, detailIDs []string) error {
|
|
for _, id := range detailIDs {
|
|
if err := s.DeleteTrashDetail(ctx, id); err != nil {
|
|
return fmt.Errorf("failed to delete detail %s: %w", id, err)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *TrashService) ReorderTrashDetails(ctx context.Context, categoryID string, orderedDetailIDs []string) error {
|
|
exists, err := s.trashRepo.CheckTrashCategoryExists(ctx, categoryID)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to check category existence: %w", err)
|
|
}
|
|
if !exists {
|
|
return errors.New("trash category not found")
|
|
}
|
|
|
|
for i, detailID := range orderedDetailIDs {
|
|
updates := map[string]interface{}{
|
|
"step_order": i + 1,
|
|
}
|
|
if err := s.trashRepo.UpdateTrashDetail(ctx, detailID, updates); err != nil {
|
|
return fmt.Errorf("failed to reorder detail %s: %w", detailID, err)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *TrashService) convertTrashCategoryToResponseDTO(category *model.TrashCategory) *ResponseTrashCategoryDTO {
|
|
return &ResponseTrashCategoryDTO{
|
|
ID: category.ID,
|
|
TrashName: category.Name,
|
|
TrashIcon: category.IconTrash,
|
|
EstimatedPrice: category.EstimatedPrice,
|
|
Variety: category.Variety,
|
|
CreatedAt: category.CreatedAt.Format(time.RFC3339),
|
|
UpdatedAt: category.UpdatedAt.Format(time.RFC3339),
|
|
}
|
|
}
|
|
|
|
func (s *TrashService) convertTrashCategoryToResponseDTOWithDetails(category *model.TrashCategory) *ResponseTrashCategoryDTO {
|
|
response := s.convertTrashCategoryToResponseDTO(category)
|
|
|
|
details := make([]ResponseTrashDetailDTO, len(category.Details))
|
|
for i, detail := range category.Details {
|
|
details[i] = *s.convertTrashDetailToResponseDTO(&detail)
|
|
}
|
|
response.TrashDetail = details
|
|
|
|
return response
|
|
}
|
|
|
|
func (s *TrashService) convertTrashDetailToResponseDTO(detail *model.TrashDetail) *ResponseTrashDetailDTO {
|
|
return &ResponseTrashDetailDTO{
|
|
ID: detail.ID,
|
|
CategoryID: detail.TrashCategoryID,
|
|
IconTrashDetail: detail.IconTrashDetail,
|
|
Description: detail.Description,
|
|
StepOrder: detail.StepOrder,
|
|
CreatedAt: detail.CreatedAt.Format(time.RFC3339),
|
|
UpdatedAt: detail.UpdatedAt.Format(time.RFC3339),
|
|
}
|
|
}
|