//go:build ignore package main import ( "database/sql" "encoding/json" "fmt" "log" "net/http" "net/url" "strings" "time" _ "github.com/jackc/pgx/v5/stdlib" ) // One-off backfill for tenants/tenantlocations rows that were onboarded // before the admin console geocoded addresses automatically, so their // latitude/longitude columns were left blank. Geocodes each row's existing // address via OpenStreetMap Nominatim (same keyless provider // AddressAutocomplete.tsx already uses on the frontend) and writes the // result back. Run manually: `go run scratch/backfill_location_coordinates.go` // — do not wire this into any request path, it's a single pass over // historical rows. const nominatimURL = "https://nominatim.openstreetmap.org/search" // Nominatim's usage policy caps free lookups at ~1 request/sec. const rateLimit = 1100 * time.Millisecond type nominatimRow struct { Lat string `json:"lat"` Lon string `json:"lon"` } func geocode(client *http.Client, address string) (lat, lon string, ok bool) { q := url.Values{} q.Set("format", "json") q.Set("limit", "1") q.Set("q", address) req, err := http.NewRequest("GET", nominatimURL+"?"+q.Encode(), nil) if err != nil { return "", "", false } // Required by Nominatim's usage policy — identifies the calling app. req.Header.Set("User-Agent", "fiesta-backend-backfill/1.0 (care@nearle.in)") resp, err := client.Do(req) if err != nil { fmt.Printf(" geocode request error: %v\n", err) return "", "", false } defer resp.Body.Close() var rows []nominatimRow if err := json.NewDecoder(resp.Body).Decode(&rows); err != nil || len(rows) == 0 { return "", "", false } return rows[0].Lat, rows[0].Lon, true } type row struct { id int address string } func backfillTable(db *sql.DB, client *http.Client, table, idCol string) { fmt.Printf("\n=== %s ===\n", table) query := fmt.Sprintf(` SELECT %s, TRIM(BOTH ', ' FROM CONCAT_WS(', ', address, suburb, city, state, postcode)) FROM %s WHERE (latitude IS NULL OR latitude = '' OR latitude = '0') AND (address IS NOT NULL AND address <> '')`, idCol, table) rows, err := db.Query(query) if err != nil { log.Fatalf("%s: query error: %v", table, err) } var targets []row for rows.Next() { var r row if err := rows.Scan(&r.id, &r.address); err != nil { log.Fatalf("%s: scan error: %v", table, err) } targets = append(targets, r) } rows.Close() fmt.Printf("Found %d row(s) missing coordinates.\n", len(targets)) updated, skipped := 0, 0 for i, r := range targets { if i > 0 { time.Sleep(rateLimit) } lat, lon, ok := geocode(client, r.address) if !ok { fmt.Printf(" [%s=%d] %q — geocode FAILED, left untouched\n", idCol, r.id, r.address) skipped++ continue } res, err := db.Exec( fmt.Sprintf(`UPDATE %s SET latitude = $1, longitude = $2 WHERE %s = $3`, table, idCol), lat, lon, r.id, ) if err != nil { fmt.Printf(" [%s=%d] update error: %v\n", idCol, r.id, err) skipped++ continue } n, _ := res.RowsAffected() if n > 0 { fmt.Printf(" [%s=%d] %q -> lat=%s lon=%s\n", idCol, r.id, r.address, lat, lon) updated++ } } fmt.Printf("%s: updated=%d skipped=%d\n", table, updated, skipped) } func main() { dsn := "host=66.116.207.225 port=5433 user=admin password=Package@123# dbname=nearledb sslmode=disable" db, err := sql.Open("pgx", dsn) if err != nil { log.Fatalf("open error: %v", err) } defer db.Close() if err := db.Ping(); err != nil { log.Fatalf("ping error: %v", err) } fmt.Println("Connected to nearledb.") client := &http.Client{Timeout: 10 * time.Second} backfillTable(db, client, "tenants", "tenantid") backfillTable(db, client, "tenantlocations", "locationid") fmt.Println(strings.Repeat("-", 40)) fmt.Println("Done.") }