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:
@@ -61,7 +61,7 @@ func ToggleService(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
query := "UPDATE services SET is_active = NOT is_active WHERE id = $1"
|
||||
result, err := db.DB.Exec(r.Context(), query, serviceID)
|
||||
result, err := db.Conn.Exec(r.Context(), query, serviceID)
|
||||
if err != nil {
|
||||
http.Error(w, "Failed to toggle service: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
@@ -136,7 +136,7 @@ func CreateServiceHandler(w http.ResponseWriter, r *http.Request) {
|
||||
var service Service
|
||||
var createdByDB sql.NullString
|
||||
|
||||
err := db.DB.QueryRow(r.Context(),
|
||||
err := db.Conn.QueryRow(r.Context(),
|
||||
query,
|
||||
req.Name,
|
||||
req.Description,
|
||||
@@ -201,7 +201,7 @@ func DeleteServiceHandler(w http.ResponseWriter, r *http.Request) {
|
||||
// Use soft delete - set is_active to FALSE instead of hard delete
|
||||
// This preserves referential integrity with booking_services
|
||||
query := "UPDATE services SET is_active = FALSE WHERE id = $1"
|
||||
result, err := db.DB.Exec(r.Context(), query, serviceID)
|
||||
result, err := db.Conn.Exec(r.Context(), query, serviceID)
|
||||
if err != nil {
|
||||
http.Error(w, "Failed to delete service: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
@@ -254,7 +254,7 @@ func ServicesHandler(w http.ResponseWriter, r *http.Request) {
|
||||
ORDER BY s.name
|
||||
`
|
||||
|
||||
rows, err := db.DB.Query(r.Context(), query)
|
||||
rows, err := db.Conn.Query(r.Context(), query)
|
||||
if err != nil {
|
||||
http.Error(w, "Failed to fetch services: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
@@ -304,7 +304,7 @@ func ServicesHandler(w http.ResponseWriter, r *http.Request) {
|
||||
// User is logged in and not admin - check eligibility
|
||||
// Get user's date of birth
|
||||
var dob time.Time
|
||||
err := db.DB.QueryRow(r.Context(), `SELECT date_of_birth FROM users WHERE id = $1`, userID).Scan(&dob)
|
||||
err := db.Conn.QueryRow(r.Context(), `SELECT date_of_birth FROM users WHERE id = $1`, userID).Scan(&dob)
|
||||
if err != nil {
|
||||
http.Error(w, "Failed to get user data: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
@@ -317,6 +317,10 @@ func ServicesHandler(w http.ResponseWriter, r *http.Request) {
|
||||
age--
|
||||
}
|
||||
|
||||
// Preload patch test data once to avoid N+1 queries.
|
||||
// Must be done BEFORE querying services so the tx connection isn't busy.
|
||||
patchTests := loadPatchTests(r.Context(), userID)
|
||||
|
||||
// Get all active services
|
||||
query := `
|
||||
SELECT s.id, s.name, s.description, s.price, s.duration_minutes,
|
||||
@@ -327,7 +331,7 @@ func ServicesHandler(w http.ResponseWriter, r *http.Request) {
|
||||
ORDER BY s.name
|
||||
`
|
||||
|
||||
rows, err := db.DB.Query(r.Context(), query)
|
||||
rows, err := db.Conn.Query(r.Context(), query)
|
||||
if err != nil {
|
||||
http.Error(w, "Failed to fetch services: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
@@ -337,9 +341,6 @@ func ServicesHandler(w http.ResponseWriter, r *http.Request) {
|
||||
var services []ServiceResponse
|
||||
var ineligibleServices []ServiceResponse
|
||||
|
||||
// Preload patch test data once to avoid N+1 queries
|
||||
patchTests := loadPatchTests(r.Context(), userID)
|
||||
|
||||
for rows.Next() {
|
||||
var service ServiceResponse
|
||||
|
||||
@@ -407,7 +408,7 @@ func ServicesEligibleForUserHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// Get user's date of birth
|
||||
var dob time.Time
|
||||
err := db.DB.QueryRow(r.Context(), `SELECT date_of_birth FROM users WHERE id = $1`, userID).Scan(&dob)
|
||||
err := db.Conn.QueryRow(r.Context(), `SELECT date_of_birth FROM users WHERE id = $1`, userID).Scan(&dob)
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
http.Error(w, "user not found", http.StatusNotFound)
|
||||
return
|
||||
@@ -424,6 +425,10 @@ func ServicesEligibleForUserHandler(w http.ResponseWriter, r *http.Request) {
|
||||
age--
|
||||
}
|
||||
|
||||
// Preload patch test data once to avoid N+1 queries.
|
||||
// Must be done BEFORE querying services so the tx connection isn't busy.
|
||||
patchTests := loadPatchTests(r.Context(), userID)
|
||||
|
||||
// Get all active services
|
||||
query := `
|
||||
SELECT s.id, s.name, s.description, s.price, s.duration_minutes,
|
||||
@@ -434,7 +439,7 @@ func ServicesEligibleForUserHandler(w http.ResponseWriter, r *http.Request) {
|
||||
ORDER BY s.name
|
||||
`
|
||||
|
||||
rows, err := db.DB.Query(r.Context(), query)
|
||||
rows, err := db.Conn.Query(r.Context(), query)
|
||||
if err != nil {
|
||||
http.Error(w, "Failed to fetch services: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
@@ -444,9 +449,6 @@ func ServicesEligibleForUserHandler(w http.ResponseWriter, r *http.Request) {
|
||||
var services []ServiceResponse
|
||||
var grayedOutServices []ServiceResponse
|
||||
|
||||
// Preload patch test data once to avoid N+1 queries
|
||||
patchTests := loadPatchTests(r.Context(), userID)
|
||||
|
||||
for rows.Next() {
|
||||
var service ServiceResponse
|
||||
|
||||
@@ -516,7 +518,7 @@ func loadPatchTests(ctx context.Context, userID string) map[string]*patchTestInf
|
||||
result := make(map[string]*patchTestInfo)
|
||||
|
||||
// Query 1: load all patch_test records
|
||||
rows, err := db.DB.Query(ctx, `
|
||||
rows, err := db.Conn.Query(ctx, `
|
||||
SELECT id, notice_duration_hours, expiry_months, service_ids
|
||||
FROM patch_tests
|
||||
`)
|
||||
@@ -547,7 +549,7 @@ func loadPatchTests(ctx context.Context, userID string) map[string]*patchTestInf
|
||||
// Query 2: load user_patch_test records for this user
|
||||
testedAtMap := make(map[string]time.Time)
|
||||
if len(patchTests) > 0 {
|
||||
uRows, err := db.DB.Query(ctx, `
|
||||
uRows, err := db.Conn.Query(ctx, `
|
||||
SELECT patch_test_id, tested_at
|
||||
FROM user_patch_tests
|
||||
WHERE user_id = $1
|
||||
@@ -632,7 +634,7 @@ func AllServicesHandler(w http.ResponseWriter, r *http.Request) {
|
||||
ORDER BY s.is_active DESC, s.name
|
||||
`
|
||||
|
||||
rows, err := db.DB.Query(r.Context(), query)
|
||||
rows, err := db.Conn.Query(r.Context(), query)
|
||||
if err != nil {
|
||||
http.Error(w, "Failed to fetch services: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
|
||||
Reference in New Issue
Block a user