// Package repository package repository import ( "context" "errors" "fmt" "math" "time" "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgxpool" "gogs.fxtmmsk.ru/foxtime/photoplaces/backend/internal/models" ) var ErrPlaceNotFound = errors.New("place not found") type BookingRepo struct { pool *pgxpool.Pool } func NewBookingRepo(pool *pgxpool.Pool) *BookingRepo { return &BookingRepo{pool: pool} } func (r *BookingRepo) Create(ctx context.Context, b *models.Booking) error { tx, err := r.pool.Begin(ctx) if err != nil { return fmt.Errorf("begin tx: %w", err) } defer tx.Rollback(ctx) var hourlyRate *int err = tx.QueryRow(ctx, `SELECT hourly_rate, currency FROM places WHERE id = $1 AND deleted_at IS NULL FOR UPDATE`, b.PlaceID).Scan(&hourlyRate, &b.Currency) if err != nil { if errors.Is(err, pgx.ErrNoRows) { return ErrPlaceNotFound } return fmt.Errorf("lock place: %w", err) } if hourlyRate != nil { hours := math.Ceil(b.EndTime.Sub(b.StartTime).Hours()) if hours < 1 { hours = 1 } total := *hourlyRate * int(hours) b.TotalPrice = &total } err = tx.QueryRow(ctx, `INSERT INTO bookings (place_id, user_id, start_time, end_time, status, total_price, currency, comment) VALUES ($1, $2, $3, $4, 'pending', $5, $6, $7) RETURNING id, created_at, updated_at`, b.PlaceID, b.UserID, b.StartTime, b.EndTime, b.TotalPrice, b.Currency, b.Comment, ).Scan(&b.ID, &b.CreatedAt, &b.UpdatedAt) if err != nil { return fmt.Errorf("insert booking: %w", err) } return tx.Commit(ctx) } func (r *BookingRepo) IsTimeSlotAvailable(ctx context.Context, placeID string, start, end time.Time) (bool, error) { var count int err := r.pool.QueryRow(ctx, `SELECT COUNT(*) FROM bookings WHERE place_id = $1 AND status != 'cancelled' AND tsrange(start_time, end_time) && tsrange($2, $3)`, placeID, start, end).Scan(&count) if err != nil { return false, err } return count == 0, nil } func (r *BookingRepo) GetByID(ctx context.Context, id string) (*models.Booking, error) { row := r.pool.QueryRow(ctx, `SELECT id, place_id, user_id, start_time, end_time, status, total_price, currency, comment, created_at, updated_at FROM bookings WHERE id = $1`, id) var b models.Booking err := row.Scan(&b.ID, &b.PlaceID, &b.UserID, &b.StartTime, &b.EndTime, &b.Status, &b.TotalPrice, &b.Currency, &b.Comment, &b.CreatedAt, &b.UpdatedAt) if err != nil { if err == pgx.ErrNoRows { return nil, nil } return nil, err } return &b, nil } func (r *BookingRepo) ListByUser(ctx context.Context, userID string) ([]*models.Booking, error) { rows, err := r.pool.Query(ctx, `SELECT id, place_id, user_id, start_time, end_time, status, total_price, currency, comment, created_at, updated_at FROM bookings WHERE user_id = $1 ORDER BY start_time DESC`, userID) if err != nil { return nil, err } defer rows.Close() return scanBookings(rows) } func (r *BookingRepo) ListByPlace(ctx context.Context, placeID string) ([]*models.Booking, error) { rows, err := r.pool.Query(ctx, `SELECT id, place_id, user_id, start_time, end_time, status, total_price, currency, comment, created_at, updated_at FROM bookings WHERE place_id = $1 ORDER BY start_time DESC`, placeID) if err != nil { return nil, err } defer rows.Close() return scanBookings(rows) } func (r *BookingRepo) UpdateStatus(ctx context.Context, id, status string) error { _, err := r.pool.Exec(ctx, `UPDATE bookings SET status=$1, updated_at=now() WHERE id=$2`, status, id) return err } func (r *BookingRepo) UpdateStatusIfPending(ctx context.Context, id, status string) (bool, error) { tag, err := r.pool.Exec(ctx, `UPDATE bookings SET status=$1, updated_at=now() WHERE id=$2 AND status='pending'`, status, id) if err != nil { return false, fmt.Errorf("update status if pending: %w", err) } return tag.RowsAffected() > 0, nil } func (r *BookingRepo) GetPlaceMinHours(ctx context.Context, placeID string) (int, error) { var minHours int err := r.pool.QueryRow(ctx, `SELECT min_hours FROM places WHERE id = $1 AND deleted_at IS NULL`, placeID).Scan(&minHours) if err != nil { if errors.Is(err, pgx.ErrNoRows) { return 0, ErrPlaceNotFound } return 0, fmt.Errorf("get place min_hours: %w", err) } return minHours, nil } func scanBookings(rows pgx.Rows) ([]*models.Booking, error) { var bookings []*models.Booking for rows.Next() { var b models.Booking if err := rows.Scan(&b.ID, &b.PlaceID, &b.UserID, &b.StartTime, &b.EndTime, &b.Status, &b.TotalPrice, &b.Currency, &b.Comment, &b.CreatedAt, &b.UpdatedAt); err != nil { return nil, err } bookings = append(bookings, &b) } return bookings, nil }