//go:build ignore // Read-only probe: why a customer-app booking may not reach the console. // // STRICTLY READ-ONLY. SELECTs and COUNTs only — no DDL, no INSERT, no UPDATE, // no DELETE. It answers three questions before anything is changed: // // 1. Do customer-app bookings exist, and what tenantid do they carry? // 2. Which tenants exist, and which are Doormile's own operational ones? // 3. Which console logins are tenant-scoped, and would therefore be unable // to see a booking whose tenantid is NULL? // // go run scratch/cx_tenant_probe.go package main import ( "database/sql" "fmt" "os" "github.com/joho/godotenv" _ "github.com/lib/pq" ) func main() { _ = godotenv.Load() dsn := fmt.Sprintf( "host=%s port=%s user=%s password=%s dbname=%s sslmode=disable", env("DB_HOST", "127.0.0.1"), env("DB_PORT", "5433"), env("DB_USER", "admin"), env("DB_PASSWORD", ""), env("DB_NAME", "logistics"), ) db, err := sql.Open("postgres", dsn) if err != nil { fmt.Println("open:", err) os.Exit(1) } defer db.Close() if err := db.Ping(); err != nil { fmt.Println("ping:", err) os.Exit(1) } section("1. Bookings by source and tenant attribution") rows(db, ` SELECT COALESCE(bookingsource,'(null)') AS source, CASE WHEN tenantid IS NULL THEN 'NULL' ELSE 'set' END AS tenant, COUNT(*) AS n, MAX(bookingid) AS newest_id FROM pickupbookings GROUP BY 1,2 ORDER BY 1,2`) section("2. The 5 newest customer-app bookings") rows(db, ` SELECT bookingid, bookingno, COALESCE(tenantid::text,'NULL') AS tenantid, status, COALESCE(customerstatus,'') AS customerstatus, createdat FROM pickupbookings WHERE bookingsource = 'Customer_App' ORDER BY bookingid DESC LIMIT 5`) section("3. Tenants") rows(db, `SELECT tenantid, tenantname, status FROM tenants ORDER BY tenantid`) section("4. Console logins and their tenant scope (NULL = Doormile staff, sees everything)") rows(db, ` SELECT email, role, COALESCE(tenantid::text,'NULL (unscoped)') AS tenant_scope FROM doormile_auth ORDER BY tenantid NULLS FIRST, email`) section("5. Total bookings (does the console's page budget still truncate?)") rows(db, `SELECT COUNT(*) AS total_bookings FROM pickupbookings`) } func section(title string) { fmt.Printf("\n===== %s =====\n", title) } func rows(db *sql.DB, query string) { rs, err := db.Query(query) if err != nil { fmt.Println(" query failed:", err) return } defer rs.Close() cols, _ := rs.Columns() fmt.Println(" " + join(cols, " | ")) for rs.Next() { vals := make([]interface{}, len(cols)) ptrs := make([]interface{}, len(cols)) for i := range vals { ptrs[i] = &vals[i] } if err := rs.Scan(ptrs...); err != nil { fmt.Println(" scan:", err) return } out := make([]string, len(cols)) for i, v := range vals { switch t := v.(type) { case nil: out[i] = "NULL" case []byte: out[i] = string(t) default: out[i] = fmt.Sprint(t) } } fmt.Println(" " + join(out, " | ")) } } func join(parts []string, sep string) string { s := "" for i, p := range parts { if i > 0 { s += sep } s += p } return s } func env(k, fallback string) string { if v := os.Getenv(k); v != "" { return v } return fallback }