places.go 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  1. package services
  2. import (
  3. "context"
  4. "errors"
  5. "fmt"
  6. "gogs.fxtmmsk.ru/foxtime/photoplaces/backend/internal/models"
  7. )
  8. var (
  9. ErrNotYourPlace = errors.New("not your place")
  10. ErrPlaceNotFound = errors.New("place not found")
  11. )
  12. type PlaceRepo interface {
  13. Create(ctx context.Context, place *models.Place) error
  14. GetByID(ctx context.Context, id string) (*models.Place, error)
  15. List(ctx context.Context, filter models.PlaceFilter) ([]*models.Place, error)
  16. Update(ctx context.Context, place *models.Place) error
  17. UpdateStatus(ctx context.Context, id, status, comment string) error
  18. SoftDelete(ctx context.Context, id string) error
  19. SetTags(ctx context.Context, placeID string, tags []models.Tag) error
  20. SetFeatures(ctx context.Context, placeID string, features []models.Feature) error
  21. GetTags(ctx context.Context, placeID string) ([]models.Tag, error)
  22. GetFeatures(ctx context.Context, placeID string) ([]models.Feature, error)
  23. GetTagsBatch(ctx context.Context, placeIDs []string) (map[string][]models.Tag, error)
  24. GetFeaturesBatch(ctx context.Context, placeIDs []string) (map[string][]models.Feature, error)
  25. }
  26. type PlaceService struct {
  27. placeRepo PlaceRepo
  28. }
  29. func NewPlaceService(placeRepo PlaceRepo) *PlaceService {
  30. return &PlaceService{placeRepo: placeRepo}
  31. }
  32. type CreatePlaceInput struct {
  33. OwnerID string
  34. Type string
  35. Title string
  36. Description *string
  37. Address *string
  38. Lat float64
  39. Lng float64
  40. CoverImage *string
  41. AccessInfo *string
  42. HourlyRate *int
  43. Currency string
  44. MinHours int
  45. Tags []models.Tag
  46. Features []models.Feature
  47. }
  48. func (s *PlaceService) Create(ctx context.Context, input CreatePlaceInput) (*models.Place, error) {
  49. place := &models.Place{
  50. Type: input.Type,
  51. OwnerID: input.OwnerID,
  52. Title: input.Title,
  53. Description: input.Description,
  54. Address: input.Address,
  55. Lat: input.Lat,
  56. Lng: input.Lng,
  57. CoverImage: input.CoverImage,
  58. AccessInfo: input.AccessInfo,
  59. Status: "pending_moderation",
  60. HourlyRate: input.HourlyRate,
  61. Currency: input.Currency,
  62. MinHours: input.MinHours,
  63. Tags: input.Tags,
  64. Features: input.Features,
  65. }
  66. if err := s.placeRepo.Create(ctx, place); err != nil {
  67. return nil, fmt.Errorf("create place: %w", err)
  68. }
  69. return place, nil
  70. }
  71. func (s *PlaceService) GetByID(ctx context.Context, id string, fetchTags bool) (*models.Place, error) {
  72. place, err := s.placeRepo.GetByID(ctx, id)
  73. if err != nil {
  74. return nil, err
  75. }
  76. if place == nil {
  77. return nil, nil
  78. }
  79. if fetchTags {
  80. tags, err := s.placeRepo.GetTags(ctx, id)
  81. if err != nil {
  82. return nil, err
  83. }
  84. place.Tags = tags
  85. features, err := s.placeRepo.GetFeatures(ctx, id)
  86. if err != nil {
  87. return nil, err
  88. }
  89. place.Features = features
  90. }
  91. return place, nil
  92. }
  93. func (s *PlaceService) List(ctx context.Context, filter models.PlaceFilter) ([]*models.Place, error) {
  94. places, err := s.placeRepo.List(ctx, filter)
  95. if err != nil {
  96. return nil, err
  97. }
  98. if filter.IncludeTagsFeatures && len(places) > 0 {
  99. placeIDs := make([]string, len(places))
  100. for i, p := range places {
  101. placeIDs[i] = p.ID
  102. }
  103. tagsMap, err := s.placeRepo.GetTagsBatch(ctx, placeIDs)
  104. if err != nil {
  105. return nil, err
  106. }
  107. featuresMap, err := s.placeRepo.GetFeaturesBatch(ctx, placeIDs)
  108. if err != nil {
  109. return nil, err
  110. }
  111. for _, p := range places {
  112. if tags, ok := tagsMap[p.ID]; ok {
  113. p.Tags = tags
  114. }
  115. if features, ok := featuresMap[p.ID]; ok {
  116. p.Features = features
  117. }
  118. }
  119. }
  120. return places, nil
  121. }
  122. type UpdatePlaceInput struct {
  123. ID string
  124. OwnerID string
  125. Title *string
  126. Description *string
  127. Address *string
  128. Lat *float64
  129. Lng *float64
  130. CoverImage *string
  131. AccessInfo *string
  132. HourlyRate *int
  133. Currency *string
  134. MinHours *int
  135. }
  136. func (s *PlaceService) Update(ctx context.Context, input UpdatePlaceInput, isModerator bool) (*models.Place, error) {
  137. place, err := s.placeRepo.GetByID(ctx, input.ID)
  138. if err != nil {
  139. return nil, err
  140. }
  141. if place == nil {
  142. return nil, nil
  143. }
  144. if !isModerator && place.OwnerID != input.OwnerID {
  145. return nil, fmt.Errorf("%w: user %s tried to update place %s", ErrNotYourPlace, input.OwnerID, input.ID)
  146. }
  147. if input.Title != nil { place.Title = *input.Title }
  148. if input.Description != nil { place.Description = input.Description }
  149. if input.Address != nil { place.Address = input.Address }
  150. if input.Lat != nil { place.Lat = *input.Lat }
  151. if input.Lng != nil { place.Lng = *input.Lng }
  152. if input.CoverImage != nil { place.CoverImage = input.CoverImage }
  153. if input.AccessInfo != nil { place.AccessInfo = input.AccessInfo }
  154. if input.HourlyRate != nil { place.HourlyRate = input.HourlyRate }
  155. if input.Currency != nil { place.Currency = *input.Currency }
  156. if input.MinHours != nil { place.MinHours = *input.MinHours }
  157. if !isModerator {
  158. place.Status = "pending_moderation"
  159. }
  160. if err := s.placeRepo.Update(ctx, place); err != nil {
  161. return nil, err
  162. }
  163. return place, nil
  164. }
  165. func (s *PlaceService) Moderate(ctx context.Context, id, action, comment, moderatorID string) error {
  166. var status string
  167. switch action {
  168. case "approve":
  169. status = "published"
  170. case "reject":
  171. status = "rejected"
  172. default:
  173. return fmt.Errorf("unknown action: %s", action)
  174. }
  175. return s.placeRepo.UpdateStatus(ctx, id, status, comment)
  176. }
  177. func (s *PlaceService) Delete(ctx context.Context, id string) error {
  178. return s.placeRepo.SoftDelete(ctx, id)
  179. }