package repository import ( "context" "fmt" "github.com/jackc/pgx/v5/pgxpool" "github.com/photoplaces/backend/internal/models" ) type ReviewRepo struct { pool *pgxpool.Pool } func NewReviewRepo(pool *pgxpool.Pool) *ReviewRepo { return &ReviewRepo{pool: pool} } func (r *ReviewRepo) Create(ctx context.Context, review *models.Review) error { tx, err := r.pool.Begin(ctx) if err != nil { return err } defer tx.Rollback(ctx) err = tx.QueryRow(ctx, `INSERT INTO reviews (user_id, target_type, target_id, rating, text) VALUES ($1, $2, $3, $4, $5) RETURNING id, created_at, updated_at`, review.UserID, review.TargetType, review.TargetID, review.Rating, review.Text, ).Scan(&review.ID, &review.CreatedAt, &review.UpdatedAt) if err != nil { if isUniqueViolation(err) { return fmt.Errorf("you already reviewed this") } return fmt.Errorf("create review: %w", err) } tableName := "places" if review.TargetType == "service" { tableName = "services" } _, err = tx.Exec(ctx, fmt.Sprintf(`UPDATE %s SET reviews_count = (SELECT COUNT(*) FROM reviews WHERE target_type = $1 AND target_id = $2 AND deleted_at IS NULL), rating = (SELECT COALESCE(AVG(rating), 0) FROM reviews WHERE target_type = $1 AND target_id = $2 AND deleted_at IS NULL) WHERE id = $2`, tableName), review.TargetType, review.TargetID) if err != nil { return fmt.Errorf("update rating: %w", err) } return tx.Commit(ctx) } func (r *ReviewRepo) List(ctx context.Context, targetType, targetID string) ([]*models.Review, error) { rows, err := r.pool.Query(ctx, `SELECT r.id, r.user_id, r.target_type, r.target_id, r.rating, r.text, r.created_at, r.updated_at, u.name, u.avatar_url FROM reviews r JOIN users u ON u.id = r.user_id WHERE r.target_type = $1 AND r.target_id = $2 AND r.deleted_at IS NULL ORDER BY r.created_at DESC`, targetType, targetID) if err != nil { return nil, err } defer rows.Close() var reviews []*models.Review for rows.Next() { var rev models.Review var userName, userAvatar *string if err := rows.Scan( &rev.ID, &rev.UserID, &rev.TargetType, &rev.TargetID, &rev.Rating, &rev.Text, &rev.CreatedAt, &rev.UpdatedAt, &userName, &userAvatar, ); err != nil { return nil, err } rev.User = &models.User{Name: userName, AvatarURL: userAvatar} reviews = append(reviews, &rev) } return reviews, nil } func isUniqueViolation(err error) bool { return err != nil && contains(err.Error(), "unique") } func contains(s, substr string) bool { return len(s) >= len(substr) && searchString(s, substr) } func searchString(s, sub string) bool { for i := 0; i <= len(s)-len(sub); i++ { if s[i:i+len(sub)] == sub { return true } } return false }