services.go 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161
  1. package repository
  2. import (
  3. "context"
  4. "fmt"
  5. "github.com/jackc/pgx/v5"
  6. "github.com/jackc/pgx/v5/pgxpool"
  7. "github.com/photoplaces/backend/internal/models"
  8. )
  9. type ServiceRepo struct {
  10. pool *pgxpool.Pool
  11. }
  12. func NewServiceRepo(pool *pgxpool.Pool) *ServiceRepo {
  13. return &ServiceRepo{pool: pool}
  14. }
  15. func (r *ServiceRepo) Create(ctx context.Context, s *models.Service) error {
  16. err := r.pool.QueryRow(ctx,
  17. `INSERT INTO services (executor_id, title, description, price, currency, duration_minutes, status)
  18. VALUES ($1, $2, $3, $4, $5, $6, $7)
  19. RETURNING id, created_at, updated_at`,
  20. s.ExecutorID, s.Title, s.Description, s.Price, s.Currency, s.DurationMinutes, s.Status,
  21. ).Scan(&s.ID, &s.CreatedAt, &s.UpdatedAt)
  22. if err != nil {
  23. return fmt.Errorf("create service: %w", err)
  24. }
  25. if len(s.Tags) > 0 {
  26. if err := r.SetTags(ctx, s.ID, s.Tags); err != nil {
  27. return err
  28. }
  29. }
  30. return nil
  31. }
  32. func (r *ServiceRepo) GetByID(ctx context.Context, id string) (*models.Service, error) {
  33. row := r.pool.QueryRow(ctx,
  34. `SELECT id, executor_id, title, description, price, currency, duration_minutes,
  35. status, rating, reviews_count, created_at, updated_at, deleted_at
  36. FROM services WHERE id = $1 AND deleted_at IS NULL`, id)
  37. return scanService(row)
  38. }
  39. func (r *ServiceRepo) List(ctx context.Context, filter models.ServiceFilter) ([]*models.Service, error) {
  40. q := `SELECT s.id, s.executor_id, s.title, s.description, s.price, s.currency,
  41. s.duration_minutes, s.status, s.rating, s.reviews_count,
  42. s.created_at, s.updated_at, s.deleted_at
  43. FROM services s`
  44. args := pgx.NamedArgs{}
  45. joins := ""
  46. if len(filter.Tags) > 0 {
  47. joins += ` JOIN service_tags st ON st.service_id = s.id`
  48. }
  49. q += joins + ` WHERE s.deleted_at IS NULL`
  50. if filter.Status != "" { q += ` AND s.status = @status`; args["status"] = filter.Status }
  51. if filter.MinRating > 0 { q += ` AND s.rating >= @min_rating`; args["min_rating"] = filter.MinRating }
  52. if filter.PriceMin != nil { q += ` AND s.price >= @price_min`; args["price_min"] = *filter.PriceMin }
  53. if filter.PriceMax != nil { q += ` AND s.price <= @price_max`; args["price_max"] = *filter.PriceMax }
  54. if len(filter.Tags) > 0 { q += ` AND st.tag_id = ANY(@tag_ids)`; args["tag_ids"] = filter.Tags }
  55. q += ` ORDER BY s.created_at DESC LIMIT @lim`
  56. args["lim"] = filter.Limit()
  57. rows, err := r.pool.Query(ctx, q, args)
  58. if err != nil {
  59. return nil, fmt.Errorf("list services: %w", err)
  60. }
  61. defer rows.Close()
  62. var services []*models.Service
  63. for rows.Next() {
  64. s, err := scanService(rows)
  65. if err != nil {
  66. return nil, err
  67. }
  68. services = append(services, s)
  69. }
  70. return services, nil
  71. }
  72. func (r *ServiceRepo) Update(ctx context.Context, s *models.Service) error {
  73. _, err := r.pool.Exec(ctx,
  74. `UPDATE services SET title=$1, description=$2, price=$3, currency=$4,
  75. duration_minutes=$5, status=$6, updated_at=now()
  76. WHERE id=$7 AND deleted_at IS NULL`,
  77. s.Title, s.Description, s.Price, s.Currency, s.DurationMinutes, s.Status, s.ID)
  78. return err
  79. }
  80. func (r *ServiceRepo) SoftDelete(ctx context.Context, id string) error {
  81. _, err := r.pool.Exec(ctx, `UPDATE services SET deleted_at=now() WHERE id=$1`, id)
  82. return err
  83. }
  84. func (r *ServiceRepo) SetTags(ctx context.Context, serviceID string, tags []models.Tag) error {
  85. tx, err := r.pool.Begin(ctx)
  86. if err != nil {
  87. return err
  88. }
  89. defer tx.Rollback(ctx)
  90. _, err = tx.Exec(ctx, `DELETE FROM service_tags WHERE service_id = $1`, serviceID)
  91. if err != nil {
  92. return err
  93. }
  94. for _, tag := range tags {
  95. _, err = tx.Exec(ctx,
  96. `INSERT INTO service_tags (service_id, tag_id) VALUES ($1, $2)`,
  97. serviceID, tag.ID)
  98. if err != nil {
  99. return err
  100. }
  101. }
  102. return tx.Commit(ctx)
  103. }
  104. func (r *ServiceRepo) GetTags(ctx context.Context, serviceID string) ([]models.Tag, error) {
  105. rows, err := r.pool.Query(ctx,
  106. `SELECT t.id, t.name, t.category FROM tags t
  107. JOIN service_tags st ON st.tag_id = t.id
  108. WHERE st.service_id = $1`, serviceID)
  109. if err != nil {
  110. return nil, err
  111. }
  112. defer rows.Close()
  113. var tags []models.Tag
  114. for rows.Next() {
  115. var t models.Tag
  116. if err := rows.Scan(&t.ID, &t.Name, &t.Category); err != nil {
  117. return nil, err
  118. }
  119. tags = append(tags, t)
  120. }
  121. return tags, nil
  122. }
  123. func scanService(row interface{ Scan(dest ...any) error }) (*models.Service, error) {
  124. var s models.Service
  125. err := row.Scan(
  126. &s.ID, &s.ExecutorID, &s.Title, &s.Description, &s.Price, &s.Currency,
  127. &s.DurationMinutes, &s.Status, &s.Rating, &s.ReviewsCount,
  128. &s.CreatedAt, &s.UpdatedAt, &s.DeletedAt,
  129. )
  130. if err != nil {
  131. if err == pgx.ErrNoRows {
  132. return nil, nil
  133. }
  134. return nil, err
  135. }
  136. return &s, nil
  137. }