reviews.go 2.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103
  1. package repository
  2. import (
  3. "context"
  4. "fmt"
  5. "github.com/jackc/pgx/v5/pgxpool"
  6. "github.com/photoplaces/backend/internal/models"
  7. )
  8. type ReviewRepo struct {
  9. pool *pgxpool.Pool
  10. }
  11. func NewReviewRepo(pool *pgxpool.Pool) *ReviewRepo {
  12. return &ReviewRepo{pool: pool}
  13. }
  14. func (r *ReviewRepo) Create(ctx context.Context, review *models.Review) error {
  15. tx, err := r.pool.Begin(ctx)
  16. if err != nil {
  17. return err
  18. }
  19. defer tx.Rollback(ctx)
  20. err = tx.QueryRow(ctx,
  21. `INSERT INTO reviews (user_id, target_type, target_id, rating, text)
  22. VALUES ($1, $2, $3, $4, $5)
  23. RETURNING id, created_at, updated_at`,
  24. review.UserID, review.TargetType, review.TargetID, review.Rating, review.Text,
  25. ).Scan(&review.ID, &review.CreatedAt, &review.UpdatedAt)
  26. if err != nil {
  27. if isUniqueViolation(err) {
  28. return fmt.Errorf("you already reviewed this")
  29. }
  30. return fmt.Errorf("create review: %w", err)
  31. }
  32. tableName := "places"
  33. if review.TargetType == "service" {
  34. tableName = "services"
  35. }
  36. _, err = tx.Exec(ctx,
  37. fmt.Sprintf(`UPDATE %s SET
  38. reviews_count = (SELECT COUNT(*) FROM reviews WHERE target_type = $1 AND target_id = $2 AND deleted_at IS NULL),
  39. rating = (SELECT COALESCE(AVG(rating), 0) FROM reviews WHERE target_type = $1 AND target_id = $2 AND deleted_at IS NULL)
  40. WHERE id = $2`, tableName),
  41. review.TargetType, review.TargetID)
  42. if err != nil {
  43. return fmt.Errorf("update rating: %w", err)
  44. }
  45. return tx.Commit(ctx)
  46. }
  47. func (r *ReviewRepo) List(ctx context.Context, targetType, targetID string) ([]*models.Review, error) {
  48. rows, err := r.pool.Query(ctx,
  49. `SELECT r.id, r.user_id, r.target_type, r.target_id, r.rating, r.text,
  50. r.created_at, r.updated_at,
  51. u.name, u.avatar_url
  52. FROM reviews r
  53. JOIN users u ON u.id = r.user_id
  54. WHERE r.target_type = $1 AND r.target_id = $2 AND r.deleted_at IS NULL
  55. ORDER BY r.created_at DESC`, targetType, targetID)
  56. if err != nil {
  57. return nil, err
  58. }
  59. defer rows.Close()
  60. var reviews []*models.Review
  61. for rows.Next() {
  62. var rev models.Review
  63. var userName, userAvatar *string
  64. if err := rows.Scan(
  65. &rev.ID, &rev.UserID, &rev.TargetType, &rev.TargetID, &rev.Rating, &rev.Text,
  66. &rev.CreatedAt, &rev.UpdatedAt,
  67. &userName, &userAvatar,
  68. ); err != nil {
  69. return nil, err
  70. }
  71. rev.User = &models.User{Name: userName, AvatarURL: userAvatar}
  72. reviews = append(reviews, &rev)
  73. }
  74. return reviews, nil
  75. }
  76. func isUniqueViolation(err error) bool {
  77. return err != nil && contains(err.Error(), "unique")
  78. }
  79. func contains(s, substr string) bool {
  80. return len(s) >= len(substr) && searchString(s, substr)
  81. }
  82. func searchString(s, sub string) bool {
  83. for i := 0; i <= len(s)-len(sub); i++ {
  84. if s[i:i+len(sub)] == sub {
  85. return true
  86. }
  87. }
  88. return false
  89. }