// One-time migration: moves Qdrant auth users and client records into PostgreSQL. // Run from the DoormileBackend directory: go run ./cmd/migrate_qdrant package main import ( "database/sql" "fmt" "log" "os" "github.com/joho/godotenv" _ "github.com/lib/pq" "golang.org/x/crypto/bcrypt" ) func hash(pw string) string { b, err := bcrypt.GenerateFromPassword([]byte(pw), bcrypt.DefaultCost) if err != nil { log.Fatalf("bcrypt error: %v", err) } return string(b) } func getenv(key, fallback string) string { if v := os.Getenv(key); v != "" { return v } return fallback } func main() { _ = godotenv.Load() dsn := fmt.Sprintf( "host=%s user=%s password=%s dbname=%s port=%s sslmode=disable TimeZone=Asia/Kolkata", getenv("DB_HOST", "127.0.0.1"), getenv("DB_USER", "admin"), getenv("DB_PASSWORD", "Package@321#"), getenv("DB_NAME", "logistics"), getenv("DB_PORT", "5433"), ) db, err := sql.Open("postgres", dsn) if err != nil { log.Fatalf("open: %v", err) } defer db.Close() if err = db.Ping(); err != nil { log.Fatalf("ping: %v", err) } log.Println("✅ Connected to PostgreSQL") // ── 1. Ensure client_id is nullable ────────────────────────────────────── log.Println("🔧 Ensuring client_id is nullable in doormile_auth …") _, _ = db.Exec(`ALTER TABLE doormile_auth ALTER COLUMN client_id DROP NOT NULL`) // ── 2. Migrate doormile_auth ────────────────────────────────────────────── // Source: Qdrant doormile_auth collection (5 users, original PIN = 1234) // admin@doormile.com added to match the hardcoded React console login. log.Println("\n📋 Migrating auth users …") pin := "1234" type authUser struct { email string role string password string } users := []authUser{ {"suriya@tenext.in", "admin", "admin"}, {"admin@doormile.com", "admin", "admin"}, {"kamesh@doormile.com", "user", pin}, {"parthiban@doormile.com", "user", pin}, {"fazul@doormile.com", "user", pin}, {"care@doormile.com", "user", pin}, } authInsert := ` INSERT INTO doormile_auth (email, password_hash, role, created_at, updated_at) VALUES ($1, $2, $3, NOW(), NOW()) ON CONFLICT (email) DO NOTHING` for _, u := range users { if _, err := db.Exec(authInsert, u.email, hash(u.password), u.role); err != nil { log.Printf(" ⚠️ skip %s: %v", u.email, err) } else { log.Printf(" ✓ %s [%s]", u.email, u.role) } } // ── 3. Migrate doormile_clients ─────────────────────────────────────────── // Exact data from Qdrant payloads provided by user. log.Println("\n📋 Migrating clients …") var fazulID uint64 _ = db.QueryRow(`SELECT id FROM doormile_auth WHERE email = 'fazul@doormile.com'`).Scan(&fazulID) type client struct { firstName string lastName string phone string city string state string neighbourhood string pincode string businessType string status string shippingFrequency string logisticsSegment string transitFrom string transitTo string parcelVolume float64 activeContracts int logisticsProvider string providerEfficiency string dataConsent string notes string surveyLat float64 surveyLong float64 surveyAddress string surveyZone string surveyPincode string registeredByID uint64 } clients := []client{ { // clientId: suriya_001 firstName: "Suriya", phone: "PENDING_004", // phone was empty in Qdrant payload — update manually city: "Coimbatore", state: "Tamil Nadu", neighbourhood: "RS Puram", businessType: "ecommerce", status: "newClient", shippingFrequency: "Weekly", transitFrom: "Coimbatore", transitTo: "Chennai", parcelVolume: 200, activeContracts: 5, logisticsProvider: "DTDC Express", providerEfficiency: "High Efficiency", dataConsent: "full", registeredByID: 0, }, { // clientId: client_1780462654467 firstName: "Abhishek", phone: "6374605674", city: "Perur", state: "Tamil Nadu", neighbourhood: "RS Puram", businessType: "wholesale", status: "newClient", shippingFrequency: "Weekly", logisticsSegment: "First Mile, Last Mile", transitFrom: "Perur", transitTo: "Bengaluru", parcelVolume: 0, activeContracts: 0, logisticsProvider: "Not disclosed", providerEfficiency: "Not disclosed", dataConsent: "basicOnly", surveyLat: 11.0050875, surveyLong: 76.9508337, surveyAddress: "Indian Bank, Diwan Bahadur Road, RS Puram, Ward 24, Coimbatore", surveyZone: "RS Puram", registeredByID: 0, }, { // clientId: client_1780558882620 firstName: "Doormile", phone: "9876543210", city: "Coimbatore", state: "Tamil Nadu", neighbourhood: "RS Puram", businessType: "retail", status: "newClient", shippingFrequency: "Daily", logisticsSegment: "First Mile, Last Mile", transitFrom: "Coimbatore", transitTo: "Bengaluru, Mumbai", parcelVolume: 0, activeContracts: 0, logisticsProvider: "Not disclosed", providerEfficiency: "Not disclosed", dataConsent: "basicOnly", surveyLat: 11.0050816, surveyLong: 76.9508575, surveyAddress: "Indian Bank, Diwan Bahadur Road, RS Puram, Ward 24, Coimbatore", surveyZone: "RS Puram", registeredByID: 0, }, { // clientId: client_1781506364119 — recorded by Fazul firstName: "Jonathan", phone: "9629415740", city: "Perur", state: "Tamil Nadu", neighbourhood: "RS Puram", pincode: "641007", businessType: "wholesale", status: "newClient", shippingFrequency: "Daily", logisticsSegment: "First Mile, Middle Mile, Last Mile", transitFrom: "Perur", transitTo: "Chennai, Coimbatore, Madurai", parcelVolume: 450, activeContracts: 12, logisticsProvider: "XpressBees", providerEfficiency: "High Efficiency", dataConsent: "full", surveyLat: 11.0050812, surveyLong: 76.9508625, surveyAddress: "Indian Bank, Diwan Bahadur Road, RS Puram, Ward 24, Coimbatore", surveyZone: "RS Puram", surveyPincode: "641007", registeredByID: fazulID, }, } clientInsert := ` INSERT INTO doormile_clients ( first_name, last_name, phone, city, state, neighbourhood, pincode, surveylat, surveylong, survey_address, survey_zone, survey_pincode, business_type, status, shipping_frequency, logistics_segment, transit_from, transit_to, parcel_volume, active_contracts, logistics_provider, provider_efficiency, data_consent, notes, registered_by_id, created_at, updated_at ) VALUES ( $1,$2,$3,$4,$5,$6,$7, $8,$9,$10,$11,$12, $13,$14,$15,$16,$17,$18, $19,$20,$21,$22,$23,$24, $25, NOW(),NOW() ) ON CONFLICT (phone) DO NOTHING` for _, cl := range clients { _, err := db.Exec(clientInsert, cl.firstName, cl.lastName, cl.phone, cl.city, cl.state, cl.neighbourhood, cl.pincode, cl.surveyLat, cl.surveyLong, cl.surveyAddress, cl.surveyZone, cl.surveyPincode, cl.businessType, cl.status, cl.shippingFrequency, cl.logisticsSegment, cl.transitFrom, cl.transitTo, cl.parcelVolume, cl.activeContracts, cl.logisticsProvider, cl.providerEfficiency, cl.dataConsent, cl.notes, cl.registeredByID, ) if err != nil { log.Printf(" ⚠️ skip %s: %v", cl.firstName, err) } else { log.Printf(" ✓ %s — %s (%s) phone=%s", cl.firstName, cl.city, cl.businessType, cl.phone) } } log.Println("\n✅ Migration complete.") log.Println("\n⚠️ ACTION REQUIRED — Suriya client has no phone on record.") log.Println(" Once you have Suriya's phone number run:") log.Println(" UPDATE doormile_clients SET phone = '' WHERE phone = 'PENDING_004';") }