132 lines
3.3 KiB
Go
132 lines
3.3 KiB
Go
//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
|
|
}
|