bookings.go 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133
  1. package repository
  2. import (
  3. "context"
  4. "fmt"
  5. "time"
  6. "github.com/jackc/pgx/v5"
  7. "github.com/jackc/pgx/v5/pgxpool"
  8. "github.com/photoplaces/backend/internal/models"
  9. )
  10. type BookingRepo struct {
  11. pool *pgxpool.Pool
  12. }
  13. func NewBookingRepo(pool *pgxpool.Pool) *BookingRepo {
  14. return &BookingRepo{pool: pool}
  15. }
  16. func (r *BookingRepo) Create(ctx context.Context, b *models.Booking) error {
  17. return r.pool.QueryRow(ctx,
  18. `INSERT INTO bookings (place_id, user_id, start_time, end_time, status, total_price, currency, comment)
  19. VALUES ($1, $2, $3, $4, 'pending', $5, $6, $7)
  20. RETURNING id, created_at, updated_at`,
  21. b.PlaceID, b.UserID, b.StartTime, b.EndTime, b.TotalPrice, b.Currency, b.Comment,
  22. ).Scan(&b.ID, &b.CreatedAt, &b.UpdatedAt)
  23. }
  24. func (r *BookingRepo) IsTimeSlotAvailable(ctx context.Context, placeID string, start, end time.Time) (bool, error) {
  25. var count int
  26. err := r.pool.QueryRow(ctx,
  27. `SELECT COUNT(*) FROM bookings
  28. WHERE place_id = $1 AND status != 'cancelled'
  29. AND tsrange(start_time, end_time) && tsrange($2, $3)`,
  30. placeID, start, end).Scan(&count)
  31. if err != nil {
  32. return false, err
  33. }
  34. return count == 0, nil
  35. }
  36. func (r *BookingRepo) GetByID(ctx context.Context, id string) (*models.Booking, error) {
  37. row := r.pool.QueryRow(ctx,
  38. `SELECT id, place_id, user_id, start_time, end_time, status, total_price, currency, comment,
  39. created_at, updated_at
  40. FROM bookings WHERE id = $1`, id)
  41. var b models.Booking
  42. err := row.Scan(&b.ID, &b.PlaceID, &b.UserID, &b.StartTime, &b.EndTime,
  43. &b.Status, &b.TotalPrice, &b.Currency, &b.Comment, &b.CreatedAt, &b.UpdatedAt)
  44. if err != nil {
  45. if err == pgx.ErrNoRows {
  46. return nil, nil
  47. }
  48. return nil, err
  49. }
  50. return &b, nil
  51. }
  52. func (r *BookingRepo) ListByUser(ctx context.Context, userID string) ([]*models.Booking, error) {
  53. rows, err := r.pool.Query(ctx,
  54. `SELECT id, place_id, user_id, start_time, end_time, status, total_price, currency, comment,
  55. created_at, updated_at
  56. FROM bookings WHERE user_id = $1 ORDER BY start_time DESC`, userID)
  57. if err != nil {
  58. return nil, err
  59. }
  60. defer rows.Close()
  61. return scanBookings(rows)
  62. }
  63. func (r *BookingRepo) ListByPlace(ctx context.Context, placeID string) ([]*models.Booking, error) {
  64. rows, err := r.pool.Query(ctx,
  65. `SELECT id, place_id, user_id, start_time, end_time, status, total_price, currency, comment,
  66. created_at, updated_at
  67. FROM bookings WHERE place_id = $1 ORDER BY start_time DESC`, placeID)
  68. if err != nil {
  69. return nil, err
  70. }
  71. defer rows.Close()
  72. return scanBookings(rows)
  73. }
  74. func (r *BookingRepo) UpdateStatus(ctx context.Context, id, status string) error {
  75. _, err := r.pool.Exec(ctx,
  76. `UPDATE bookings SET status=$1, updated_at=now() WHERE id=$2`, status, id)
  77. return err
  78. }
  79. func scanBookings(rows pgx.Rows) ([]*models.Booking, error) {
  80. var bookings []*models.Booking
  81. for rows.Next() {
  82. var b models.Booking
  83. if err := rows.Scan(&b.ID, &b.PlaceID, &b.UserID, &b.StartTime, &b.EndTime,
  84. &b.Status, &b.TotalPrice, &b.Currency, &b.Comment, &b.CreatedAt, &b.UpdatedAt); err != nil {
  85. return nil, err
  86. }
  87. bookings = append(bookings, &b)
  88. }
  89. return bookings, nil
  90. }
  91. func (r *BookingRepo) CancelOverlapping(ctx context.Context, placeID string, start, end time.Time) error {
  92. _, err := r.pool.Exec(ctx,
  93. `UPDATE bookings SET status='cancelled', updated_at=now()
  94. WHERE place_id=$1 AND status='pending'
  95. AND tsrange(start_time, end_time) && tsrange($2, $3)`,
  96. placeID, start, end)
  97. return err
  98. }
  99. func (r *BookingRepo) CalculateTotalPrice(ctx context.Context, placeID string, start, end time.Time) (*int, error) {
  100. var hourlyRate *int
  101. err := r.pool.QueryRow(ctx,
  102. `SELECT hourly_rate FROM places WHERE id=$1 AND deleted_at IS NULL AND type='studio'`,
  103. placeID).Scan(&hourlyRate)
  104. if err != nil {
  105. return nil, fmt.Errorf("get studio rate: %w", err)
  106. }
  107. if hourlyRate == nil {
  108. return nil, nil
  109. }
  110. hours := int(end.Sub(start).Hours())
  111. if hours < 1 {
  112. hours = 1
  113. }
  114. total := *hourlyRate * hours
  115. return &total, nil
  116. }