package service import ( "context" "errors" "fmt" "time" apperrors "with_you/internal/errors" "with_you/internal/dto" "with_you/internal/model" "with_you/internal/pkg/cursor" "with_you/internal/repository" "gorm.io/gorm" ) type TradeService interface { Create(ctx context.Context, userID string, req dto.CreateTradeItemRequest) (*model.TradeItem, error) GetByID(ctx context.Context, id string, currentUserID *string) (*dto.TradeItemDetailResponse, error) Update(ctx context.Context, userID string, id string, req dto.UpdateTradeItemRequest) error Delete(ctx context.Context, userID string, id string) error UpdateStatus(ctx context.Context, userID string, id string, status model.TradeItemStatus) error List(ctx context.Context, tradeType *model.TradeType, category *model.TradeCategory, cursorStr string, pageSize int, currentUserID *string) (*dto.TradeCursorPageResponse, error) Search(ctx context.Context, keyword string, tradeType *model.TradeType, category *model.TradeCategory, cursorStr string, pageSize int, currentUserID *string) (*dto.TradeCursorPageResponse, error) RecordView(ctx context.Context, id string) error Favorite(ctx context.Context, userID string, id string) error Unfavorite(ctx context.Context, userID string, id string) error } type tradeService struct { tradeRepo repository.TradeRepository } func NewTradeService(tradeRepo repository.TradeRepository) TradeService { return &tradeService{tradeRepo: tradeRepo} } func (s *tradeService) Create(ctx context.Context, userID string, req dto.CreateTradeItemRequest) (*model.TradeItem, error) { item := &model.TradeItem{ UserID: userID, TradeType: model.TradeType(req.TradeType), Category: model.TradeCategory(req.Category), Title: req.Title, Content: req.Content, Segments: req.Segments, Price: req.Price, Condition: req.Condition, Status: model.TradeItemActive, } if item.Content == "" { item.Content = item.Title } images := req.Images if images == nil { images = []string{} } if err := s.tradeRepo.Create(ctx, item, images); err != nil { return nil, fmt.Errorf("create trade item: %w", err) } return item, nil } func (s *tradeService) GetByID(ctx context.Context, id string, currentUserID *string) (*dto.TradeItemDetailResponse, error) { item, err := s.tradeRepo.GetByID(ctx, id) if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil, fmt.Errorf("trade item not found") } return nil, err } isFavorited := false if currentUserID != nil { fav, err := s.tradeRepo.IsFavorited(ctx, id, *currentUserID) if err == nil { isFavorited = fav } } _ = s.tradeRepo.IncrementViews(ctx, id) return dto.ConvertTradeItemToDetailResponse(item, isFavorited), nil } func (s *tradeService) Update(ctx context.Context, userID string, id string, req dto.UpdateTradeItemRequest) error { item, err := s.tradeRepo.GetByID(ctx, id) if err != nil { return apperrors.ErrNotFound } if item.UserID != userID { return apperrors.ErrForbidden } if req.TradeType != nil { item.TradeType = model.TradeType(*req.TradeType) } if req.Category != nil { item.Category = model.TradeCategory(*req.Category) } if req.Title != nil { item.Title = *req.Title } if req.Content != nil { item.Content = *req.Content } if req.Segments != nil { item.Segments = req.Segments } if req.Price != nil { item.Price = req.Price } if req.Condition != nil { item.Condition = *req.Condition } item.UpdatedAt = time.Now() if req.Images != nil { return s.tradeRepo.UpdateWithImages(ctx, item, &req.Images) } return s.tradeRepo.Update(ctx, item) } func (s *tradeService) Delete(ctx context.Context, userID string, id string) error { item, err := s.tradeRepo.GetByID(ctx, id) if err != nil { return apperrors.ErrNotFound } if item.UserID != userID { return apperrors.ErrForbidden } return s.tradeRepo.Delete(ctx, id) } func (s *tradeService) UpdateStatus(ctx context.Context, userID string, id string, status model.TradeItemStatus) error { item, err := s.tradeRepo.GetByID(ctx, id) if err != nil { return apperrors.ErrNotFound } if item.UserID != userID { return apperrors.ErrForbidden } return s.tradeRepo.UpdateStatus(ctx, id, status) } func (s *tradeService) List(ctx context.Context, tradeType *model.TradeType, category *model.TradeCategory, cursorStr string, pageSize int, currentUserID *string) (*dto.TradeCursorPageResponse, error) { req := cursor.NewPageRequest(cursorStr, cursor.Forward, pageSize) result, err := s.tradeRepo.ListByCursor(ctx, tradeType, category, nil, req) if err != nil { return nil, err } return s.buildCursorResponse(ctx, result, currentUserID) } func (s *tradeService) Search(ctx context.Context, keyword string, tradeType *model.TradeType, category *model.TradeCategory, cursorStr string, pageSize int, currentUserID *string) (*dto.TradeCursorPageResponse, error) { req := cursor.NewPageRequest(cursorStr, cursor.Forward, pageSize) result, err := s.tradeRepo.SearchByCursor(ctx, keyword, tradeType, category, req) if err != nil { return nil, err } return s.buildCursorResponse(ctx, result, currentUserID) } func (s *tradeService) buildCursorResponse(ctx context.Context, result *cursor.CursorPageResult[*model.TradeItem], currentUserID *string) (*dto.TradeCursorPageResponse, error) { favoriteMap := make(map[string]bool) if currentUserID != nil && len(result.Items) > 0 { ids := make([]string, 0, len(result.Items)) for _, item := range result.Items { ids = append(ids, item.ID) } fm, err := s.tradeRepo.IsFavoritedBatch(ctx, ids, *currentUserID) if err == nil { favoriteMap = fm } } resp := &dto.TradeCursorPageResponse{ Items: dto.ConvertTradeItemsToResponse(result.Items, favoriteMap), NextCursor: result.NextCursor, PrevCursor: result.PrevCursor, HasMore: result.HasMore, } return resp, nil } func (s *tradeService) RecordView(ctx context.Context, id string) error { return s.tradeRepo.IncrementViews(ctx, id) } func (s *tradeService) Favorite(ctx context.Context, userID string, id string) error { _, err := s.tradeRepo.GetByID(ctx, id) if err != nil { return fmt.Errorf("trade item not found") } exists, err := s.tradeRepo.IsFavorited(ctx, id, userID) if err != nil { return err } if exists { return nil } if err := s.tradeRepo.CreateFavorite(ctx, id, userID); err != nil { return err } _ = s.tradeRepo.IncrementFavorites(ctx, id) return nil } func (s *tradeService) Unfavorite(ctx context.Context, userID string, id string) error { exists, err := s.tradeRepo.IsFavorited(ctx, id, userID) if err != nil { return err } if !exists { return nil } if err := s.tradeRepo.DeleteFavorite(ctx, id, userID); err != nil { return err } _ = s.tradeRepo.DecrementFavorites(ctx, id) return nil }