refactor(backend): migrate db.DB to db.Conn PoolProxy across all handlers

Replace direct *pgxpool.Pool usage with PoolProxy wrapper across the entire backend:

- db.DB renamed to db.Conn (*pgxpool.Pool -> *PoolProxy)
- JWT functions now accept context.Context instead of using context.Background()
- Handler DB calls route through PoolProxy for per-test transaction support
- Fixture/helper/testdb functions accept Querier interface for decoupling
- Query ordering fixed in bookings handlers: COUNT after data query to avoid pgx conn busy
- Time truncation fixed: time.Date instead of Truncate(24*time.Hour) for week start calc
- testmain_test.go files updated with SeedBaseline and NewPoolProxy

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
2026-06-21 19:28:54 +01:00
co-authored by Sisyphus
parent c69a243f75
commit 3d0e2afc4c
39 changed files with 751 additions and 638 deletions
+24 -24
View File
@@ -138,7 +138,7 @@ func calculateDiscountPreview(ctx context.Context, bookingID string, userID stri
}
var bookingTotal float64
db.DB.QueryRow(ctx, `
db.Conn.QueryRow(ctx, `
SELECT COALESCE(SUM(price_val), 0) FROM (
SELECT COALESCE(bs.override_price, s.price) AS price_val
FROM booking_services bs
@@ -162,7 +162,7 @@ func calculateDiscountPreview(ctx context.Context, bookingID string, userID stri
var campaignID string
var campaignPercent float64
var campaignName string
if err := db.DB.QueryRow(ctx, `
if err := db.Conn.QueryRow(ctx, `
SELECT id, discount_percent, name FROM discount_campaigns
WHERE status = 'active' AND campaign_type = 'time_based'
AND start_date <= NOW() AND end_date >= NOW()
@@ -170,7 +170,7 @@ func calculateDiscountPreview(ctx context.Context, bookingID string, userID stri
ORDER BY discount_percent DESC LIMIT 1
`).Scan(&campaignID, &campaignPercent, &campaignName); err == nil && campaignID != "" {
var exists int
db.DB.QueryRow(ctx, `SELECT 1 FROM booking_discounts WHERE booking_id = $1 AND source_id = $2`, bookingID, campaignID).Scan(&exists)
db.Conn.QueryRow(ctx, `SELECT 1 FROM booking_discounts WHERE booking_id = $1 AND source_id = $2`, bookingID, campaignID).Scan(&exists)
if exists == 0 {
amount := roundTo2(bookingTotal * campaignPercent / 100)
resp.Discounts = append(resp.Discounts, DiscountPreview{
@@ -184,12 +184,12 @@ func calculateDiscountPreview(ctx context.Context, bookingID string, userID stri
}
var userBookingCount int
db.DB.QueryRow(ctx, `SELECT COUNT(*) FROM bookings WHERE user_id = $1 AND status = 'completed'`, userID).Scan(&userBookingCount)
db.Conn.QueryRow(ctx, `SELECT COUNT(*) FROM bookings WHERE user_id = $1 AND status = 'completed'`, userID).Scan(&userBookingCount)
var milestoneCampaignID string
var milestonePercent float64
var milestoneName string
db.DB.QueryRow(ctx, `
db.Conn.QueryRow(ctx, `
SELECT id, discount_percent, name FROM discount_campaigns
WHERE status = 'active' AND campaign_type = 'milestone' AND milestone_type = 'per_user_booking_count'
AND milestone_value = $1
@@ -198,7 +198,7 @@ func calculateDiscountPreview(ctx context.Context, bookingID string, userID stri
if milestoneCampaignID != "" {
var exists int
db.DB.QueryRow(ctx, `SELECT 1 FROM booking_discounts WHERE booking_id = $1 AND source_id = $2`, bookingID, milestoneCampaignID).Scan(&exists)
db.Conn.QueryRow(ctx, `SELECT 1 FROM booking_discounts WHERE booking_id = $1 AND source_id = $2`, bookingID, milestoneCampaignID).Scan(&exists)
if exists == 0 {
amount := roundTo2(bookingTotal * milestonePercent / 100)
resp.Discounts = append(resp.Discounts, DiscountPreview{
@@ -212,7 +212,7 @@ func calculateDiscountPreview(ctx context.Context, bookingID string, userID stri
}
var firstVisitDate time.Time
db.DB.QueryRow(ctx, `SELECT MIN(start_time) FROM bookings WHERE user_id = $1 AND status = 'completed'`, userID).Scan(&firstVisitDate)
db.Conn.QueryRow(ctx, `SELECT MIN(start_time) FROM bookings WHERE user_id = $1 AND status = 'completed'`, userID).Scan(&firstVisitDate)
if !firstVisitDate.IsZero() {
type annCamp struct {
id string
@@ -221,7 +221,7 @@ func calculateDiscountPreview(ctx context.Context, bookingID string, userID stri
unit string
name string
}
annRows, err := db.DB.Query(ctx, `
annRows, err := db.Conn.Query(ctx, `
SELECT id, discount_percent, milestone_value, milestone_unit, name FROM discount_campaigns
WHERE status = 'active' AND campaign_type = 'milestone' AND milestone_type = 'anniversary'
AND NOT EXISTS (SELECT 1 FROM booking_discounts WHERE user_id = $1 AND source_id = discount_campaigns.id AND milestone_type = 'anniversary')
@@ -238,7 +238,7 @@ func calculateDiscountPreview(ctx context.Context, bookingID string, userID stri
for _, c := range campaigns {
var exists int
db.DB.QueryRow(ctx, `SELECT 1 FROM booking_discounts WHERE booking_id = $1 AND source_id = $2`, bookingID, c.id).Scan(&exists)
db.Conn.QueryRow(ctx, `SELECT 1 FROM booking_discounts WHERE booking_id = $1 AND source_id = $2`, bookingID, c.id).Scan(&exists)
if exists > 0 {
continue
}
@@ -268,13 +268,13 @@ func calculateDiscountPreview(ctx context.Context, bookingID string, userID stri
// Check for referrer's unused referral discount
var rdID string
var rdPercent float64
if err := db.DB.QueryRow(ctx, `
if err := db.Conn.QueryRow(ctx, `
SELECT id, discount_percent FROM referral_discounts
WHERE user_id = $1 AND used = FALSE
LIMIT 1
`, userID).Scan(&rdID, &rdPercent); err == nil && rdID != "" {
exists := 0
db.DB.QueryRow(ctx, `SELECT 1 FROM booking_discounts WHERE booking_id = $1 AND discount_source = 'referral' AND source_id = $2`, bookingID, rdID).Scan(&exists)
db.Conn.QueryRow(ctx, `SELECT 1 FROM booking_discounts WHERE booking_id = $1 AND discount_source = 'referral' AND source_id = $2`, bookingID, rdID).Scan(&exists)
if exists == 0 {
amount := roundTo2(bookingTotal * rdPercent / 100)
resp.Discounts = append(resp.Discounts, DiscountPreview{
@@ -370,7 +370,7 @@ func CreateTerminalPayment(w http.ResponseWriter, r *http.Request) {
// Route based on payment method
if req.PaymentMethod != nil && (*req.PaymentMethod == "cash" || *req.PaymentMethod == "giftcard") {
tx, err := db.DB.Begin(r.Context())
tx, err := db.Conn.Begin(r.Context())
if err != nil {
log.Printf("Failed to begin transaction: %v", err)
http.Error(w, "internal server error", http.StatusInternalServerError)
@@ -700,10 +700,10 @@ func CreateBookingPayment(w http.ResponseWriter, r *http.Request) {
//
// We acquire a dedicated connection from the pool and hold it for the
// duration of the handler so that lock and unlock use the same connection.
// Using db.DB.Exec() for both would be unsafe — each call may get a
// Using db.Conn.Exec() for both would be unsafe — each call may get a
// different pool connection, and pg_advisory_unlock on a different session
// is a silent no-op, leaking the lock.
pinConn, err := db.DB.Acquire(r.Context())
pinConn, err := db.Conn.Acquire(r.Context())
if err != nil {
log.Printf("Failed to acquire connection for payment lock: %v", err)
http.Error(w, "Internal server error", http.StatusInternalServerError)
@@ -772,7 +772,7 @@ func CreateBookingPayment(w http.ResponseWriter, r *http.Request) {
// concurrent payments of the same type.
if req.PaymentType != "partial" {
var existingCount int
if err := db.DB.QueryRow(r.Context(), `
if err := db.Conn.QueryRow(r.Context(), `
SELECT COUNT(*) FROM payments
WHERE booking_id = $1
AND status = 'completed'
@@ -887,7 +887,7 @@ func CreateBookingPayment(w http.ResponseWriter, r *http.Request) {
// so that if any insert fails the entire group rolls back. This prevents
// a data inconsistency where Square charged the customer but only part of
// the split is reflected in the DB.
tx, txErr := db.DB.Begin(r.Context())
tx, txErr := db.Conn.Begin(r.Context())
if txErr != nil {
log.Printf("Failed to begin transaction for payment records: %v", txErr)
http.Error(w, "internal server error", http.StatusInternalServerError)
@@ -972,7 +972,7 @@ func CreateBookingPayment(w http.ResponseWriter, r *http.Request) {
// would create a credit balance or require a refund.
func applyEligibleCampaignsAtPayment(ctx context.Context, bookingID string, userID string) {
var existingPayment int
db.DB.QueryRow(ctx, `
db.Conn.QueryRow(ctx, `
SELECT COUNT(*) FROM payments
WHERE booking_id = $1 AND status = 'completed' AND payment_method NOT IN ('discount', 'on_the_house')
`, bookingID).Scan(&existingPayment)
@@ -987,7 +987,7 @@ func applyEligibleCampaignsAtPayment(ctx context.Context, bookingID string, user
}
var bookingTotal float64
if err := db.DB.QueryRow(ctx, `
if err := db.Conn.QueryRow(ctx, `
SELECT COALESCE(SUM(price_val), 0) FROM (
SELECT COALESCE(bs.override_price, s.price) AS price_val
FROM booking_services bs
@@ -1007,7 +1007,7 @@ func applyEligibleCampaignsAtPayment(ctx context.Context, bookingID string, user
return
}
tx, err := db.DB.Begin(ctx)
tx, err := db.Conn.Begin(ctx)
if err != nil {
log.Printf("Failed to begin discount application transaction: %v", err)
return
@@ -1768,7 +1768,7 @@ func AcquirePaymentLock(w http.ResponseWriter, r *http.Request) {
// Verify the user owns this booking.
var bookingUserID string
if err := db.DB.QueryRow(r.Context(),
if err := db.Conn.QueryRow(r.Context(),
"SELECT user_id FROM bookings WHERE id = $1", bookingID,
).Scan(&bookingUserID); err != nil {
http.Error(w, "Booking not found", http.StatusNotFound)
@@ -1786,7 +1786,7 @@ func AcquirePaymentLock(w http.ResponseWriter, r *http.Request) {
// handles the sub-5-minute race, this catches the >5-minute gap.
var currentStatus string
var startTime time.Time
if err := db.DB.QueryRow(r.Context(),
if err := db.Conn.QueryRow(r.Context(),
"SELECT status, start_time FROM bookings WHERE id = $1", bookingID,
).Scan(&currentStatus, &startTime); err != nil {
http.Error(w, "Booking not found", http.StatusNotFound)
@@ -1803,14 +1803,14 @@ func AcquirePaymentLock(w http.ResponseWriter, r *http.Request) {
// Upsert the time_blocker: delete any existing PAYMENT_IN_FLIGHT for this
// booking, then insert a fresh one. This effectively extends the lock.
if _, err := db.DB.Exec(r.Context(), `
if _, err := db.Conn.Exec(r.Context(), `
DELETE FROM time_blockers
WHERE description = 'PAYMENT_IN_FLIGHT:' || $1
`, bookingID); err != nil {
log.Printf("Failed to clear previous payment lock for booking %s: %v", bookingID, err)
}
if _, err := db.DB.Exec(r.Context(), `
if _, err := db.Conn.Exec(r.Context(), `
INSERT INTO time_blockers (start_time, duration_minutes, description, created_by)
VALUES (NOW(), $1, $2, $3)
`, PaymentLockDuration, "PAYMENT_IN_FLIGHT:"+bookingID, userID); err != nil {
@@ -1835,7 +1835,7 @@ func ReleasePaymentLock(w http.ResponseWriter, r *http.Request) {
return
}
if _, err := db.DB.Exec(r.Context(), `
if _, err := db.Conn.Exec(r.Context(), `
DELETE FROM time_blockers
WHERE description = 'PAYMENT_IN_FLIGHT:' || $1
`, bookingID); err != nil {