Files
doormile_backend/scratch/debug_booking.go
Suriya 2c26cbe4ba fix: panic recovery, rate limiting, transaction error handling, pagination
Hardening pass over the API surface. No route's auth requirements change.

Resilience:
- Add recover middleware. There was none, so an unhandled panic in any
  handler propagated out of the process instead of becoming a 500.
- Add a centralized ErrorHandler so errors and recovered panics return the
  same {success,message} envelope as the utils helpers, not Fiber's default
  plain-text body. 5xx responses are logged with method and path.

Rate limiting:
- Global 300/min per IP as an abuse backstop, exempting health/readiness
  probes and websocket upgrades.
- 10/min shared across every credential endpoint (customer/miler/admin/hub
  login, verify-pin, reset-pin, email OTP). PINs are 4 digits, so the whole
  keyspace was previously walkable in seconds. One shared limiter instance
  means rotating between endpoints doesn't reset the budget.
- Add TRUSTED_PROXIES config. Limits key on c.IP(), which behind a TLS
  terminator is the proxy, collapsing every client into one bucket. When set,
  X-Forwarded-For is honoured only from those proxies so the header can't be
  spoofed to dodge the limit. Logs a warning when unset.

Transactions:
- Check the error on all 51 previously-unchecked tx.Save/Create/Delete/
  Model(...).Update/Commit calls across 6 controllers. A failed write inside
  a transaction was silently ignored and the request still reported success;
  an unchecked Commit could fail with the caller told everything worked.
  Each site now rolls back and returns a specific message.

Pagination:
- Add utils.ParsePage/Paginated, reusing the pageno/pagesize convention
  GetAdminBookings already established. Default 500, hard cap 1000.
- Apply to the previously unbounded consignments, tripsheets, exceptions,
  app-users and clients endpoints. Defaults are high so existing consoles
  that don't paginate keep working; the cap only stops a growing table from
  being loaded wholesale. total is now a real COUNT, not len(data).
- GetClients also loaded the entire auth table to join in memory; it now
  fetches only the current page's rows.

Tests (first in the repo):
- Extract the hyperlocal pincode rule out of BookingPickupComplete into
  isHyperlocal so it is testable, covering the short/empty pincode fallback.
- Cover calculateVolumetricWeight and the ParsePage clamping rules.

Repo hygiene:
- Tag scratch/*.go with //go:build ignore. Each declared its own main(), so
  `go build ./...` failed on redeclaration; it now passes repo-wide.
- Untrack scratch/node_modules (216 files) and ignore node_modules, test
  artifacts, and the `doormile` binary `go build .` emits.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-27 12:13:39 +05:30

84 lines
2.3 KiB
Go

//go:build ignore
package main
import (
"database/sql"
"fmt"
"log"
_ "github.com/lib/pq"
)
func main() {
dsn := "host=31.97.228.132 user=admin password=Package@321# dbname=logistics port=5433 sslmode=disable"
db, err := sql.Open("postgres", dsn)
if err != nil {
log.Fatalf("Open failed: %v", err)
}
defer db.Close()
if err := db.Ping(); err != nil {
log.Fatalf("Ping failed: %v", err)
}
fmt.Println("Connected to Postgres OK")
// 1. Show pickupbookings columns + nullability
fmt.Println("\n--- pickupbookings columns ---")
rows, err := db.Query(`
SELECT column_name, data_type, is_nullable, column_default
FROM information_schema.columns
WHERE table_schema = 'public' AND table_name = 'pickupbookings'
ORDER BY ordinal_position
`)
if err != nil {
log.Fatalf("Column query failed: %v", err)
}
defer rows.Close()
for rows.Next() {
var col, dtype, nullable string
var def sql.NullString
rows.Scan(&col, &dtype, &nullable, &def)
fmt.Printf(" %-30s %-20s nullable=%-3s default=%s\n", col, dtype, nullable, def.String)
}
// 2. Show check constraints
fmt.Println("\n--- check constraints on pickupbookings ---")
crows, err := db.Query(`
SELECT con.conname, pg_get_constraintdef(con.oid)
FROM pg_constraint con
JOIN pg_class rel ON rel.oid = con.conrelid
WHERE rel.relname = 'pickupbookings' AND con.contype = 'c'
`)
if err != nil {
log.Fatalf("Constraint query failed: %v", err)
}
defer crows.Close()
for crows.Next() {
var name, def string
crows.Scan(&name, &def)
fmt.Printf(" %s: %s\n", name, def)
}
fmt.Println(" (none if blank)")
// 3. Attempt a raw INSERT to see the exact Postgres error
fmt.Println("\n--- Attempting raw INSERT ---")
_, insertErr := db.Exec(`
INSERT INTO pickupbookings
(bookingno, appcustomerid, pickupaddress, pickuppincode, pickuplatitude, pickuplongitude,
deliveryaddress, deliverypincode, deliverylatitude, deliverylongitude,
bookingsource, status, createdat, updatedat)
VALUES
('DM-BK-DEBUG-001', 1, '123 Test St', '641012', 11.0168, 76.9558,
'', '', 0.0, 0.0,
'Customer_App', 'Pending_Pickup', NOW(), NOW())
`)
if insertErr != nil {
fmt.Printf("INSERT ERROR: %v\n", insertErr)
} else {
fmt.Println("INSERT succeeded!")
// Clean up
db.Exec("DELETE FROM pickupbookings WHERE bookingno = 'DM-BK-DEBUG-001'")
fmt.Println("Cleaned up test row.")
}
}