bookings.go 3.8 KB

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