reviews.go 2.7 KB

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