package main import ( "database/sql" "fmt" "log" "os" "time" "github.com/joho/godotenv" _ "github.com/lib/pq" ) func main() { _ = godotenv.Load() cfg := ParseFlags() // Deterministic seed for reproducibility InitRNG(42) dbURL := os.Getenv("DATABASE_URL") if dbURL == "" { log.Fatal("DATABASE_URL not set") } db, err := sql.Open("postgres", dbURL) if err != nil { log.Fatalf("DB connect: %v", err) } defer db.Close() if err := db.Ping(); err != nil { log.Fatalf("DB ping: %v", err) } // Increase connection pool for bulk operations db.SetMaxOpenConns(10) db.SetMaxIdleConns(5) startTime := time.Now() fmt.Println("╔═══════════════════════════════════════════════════════════╗") fmt.Println("║ VEZA — Realistic Database Seeder v2 ║") fmt.Println("╚═══════════════════════════════════════════════════════════╝") fmt.Printf("\n Mode: %s\n", modeName(cfg)) fmt.Printf(" Users: %d | Tracks: %d | Plays: %d\n\n", cfg.TotalUsers, cfg.Tracks, cfg.PlayEvents) // ── TRUNCATE all tables ────────────────────────────────────────────────── fmt.Print("Truncating all tables... ") if err := TruncateAll(db); err != nil { log.Fatalf("truncate: %v", err) } fmt.Println("done") // ── SEED in dependency order ───────────────────────────────────────────── // Level 0: Users (no FK dependencies) users, err := SeedUsers(db, cfg) if err != nil { log.Fatalf("seed users: %v", err) } // Level 1: Tracks (depends on users) tracks, err := SeedTracks(db, cfg, users) if err != nil { log.Fatalf("seed tracks: %v", err) } // Level 2: Playlists (depends on users, tracks) _, err = SeedPlaylists(db, cfg, users, tracks) if err != nil { log.Fatalf("seed playlists: %v", err) } // CI mode stops here — the E2E suite only needs the test accounts // + a fixture set of tracks/playlists. Skipping the remaining // seeders shaves ~50s off the typical CI seed time. if cfg.CIMode { fmt.Println("\n═══ CI MODE: skipping social/chat/live/marketplace/analytics/content/moderation/notifications/misc ═══") } else { // Level 2: Social (depends on users, tracks) if err := SeedSocial(db, cfg, users, tracks); err != nil { log.Fatalf("seed social: %v", err) } // Level 2: Chat (depends on users) _, err = SeedChat(db, cfg, users) if err != nil { log.Fatalf("seed chat: %v", err) } // Level 2: Live streams (depends on users) if err := SeedLive(db, cfg, users, tracks); err != nil { log.Fatalf("seed live: %v", err) } // Level 2: Marketplace (depends on users, tracks) _, err = SeedMarketplace(db, cfg, users, tracks) if err != nil { log.Fatalf("seed marketplace: %v", err) } // Level 3: Analytics (depends on users, tracks — heaviest step) if err := SeedAnalytics(db, cfg, users, tracks); err != nil { log.Fatalf("seed analytics: %v", err) } // Level 2: Content (depends on users, tracks) if err := SeedContent(db, cfg, users, tracks); err != nil { log.Fatalf("seed content: %v", err) } // Level 2: Moderation (depends on users, tracks) if err := SeedModeration(db, cfg, users, tracks); err != nil { log.Fatalf("seed moderation: %v", err) } // Level 2: Notifications (depends on users) if err := SeedNotifications(db, cfg, users); err != nil { log.Fatalf("seed notifications: %v", err) } // Level 2: Misc (depends on users) if err := SeedMisc(db, cfg, users); err != nil { log.Fatalf("seed misc: %v", err) } } // ── VALIDATION ─────────────────────────────────────────────────────────── fmt.Println("\n═══ VALIDATION ═══") validate(db) // ── SUMMARY ────────────────────────────────────────────────────────────── elapsed := time.Since(startTime) fmt.Println() fmt.Println("╔═══════════════════════════════════════════════════════════╗") fmt.Println("║ Seed Complete! ║") fmt.Println("╚═══════════════════════════════════════════════════════════╝") fmt.Println() tables := []string{ "users", "user_profiles", "user_settings", "user_roles", "tracks", "track_genres", "track_tags", "playlists", "playlist_tracks", "playlist_follows", "follows", "track_likes", "track_reposts", "track_comments", "comments", "user_blocks", "rooms", "room_members", "messages", "live_streams", "co_listening_sessions", "products", "orders", "order_items", "product_reviews", "seller_stripe_accounts", "seller_balances", "track_plays", "playback_history", "daily_track_stats", "geographic_play_stats", "analytics_events", "files", "user_storage_quotas", "courses", "lessons", "course_enrollments", "gear_items", "groups", "group_members", "reports", "moderation_actions", "user_strikes", "user_suspensions", "notifications", "notification_preferences", "support_tickets", "api_keys", "announcements", "data_exports", "login_history", "user_preferences", } totalRows := 0 for _, t := range tables { n := CountRows(db, t) totalRows += n fmt.Printf(" %-30s %7d rows\n", t, n) } fmt.Printf("\n %-30s %7d rows\n", "TOTAL", totalRows) fmt.Printf(" %-30s %s\n", "Duration", elapsed.Round(time.Millisecond)) fmt.Println("\n--- Test Accounts ---") for _, ta := range testAccounts { fmt.Printf(" %-12s %-25s password: %s\n", ta.Role, ta.Email, ta.Password) } fmt.Println() } func modeName(cfg Config) string { if cfg.CIMode { return "CI" } if cfg.TotalUsers <= 100 { return "MINIMAL" } return "FULL" } // validate checks data integrity. func validate(db *sql.DB) { checks := []struct { name string query string }{ {"test accounts exist", "SELECT COUNT(*) FROM users WHERE email IN ('admin@veza.music','artist@veza.music','user@veza.music','mod@veza.music','new@veza.music')"}, {"no orphan track_plays", "SELECT COUNT(*) FROM track_plays tp LEFT JOIN tracks t ON tp.track_id = t.id WHERE t.id IS NULL"}, {"no orphan follows", "SELECT COUNT(*) FROM follows f LEFT JOIN users u ON f.follower_id = u.id WHERE u.id IS NULL"}, {"no orphan playlist_tracks", "SELECT COUNT(*) FROM playlist_tracks pt LEFT JOIN playlists p ON pt.playlist_id = p.id WHERE p.id IS NULL"}, {"no orphan messages", "SELECT COUNT(*) FROM messages m LEFT JOIN rooms r ON m.room_id = r.id WHERE r.id IS NULL"}, {"no orphan order_items", "SELECT COUNT(*) FROM order_items oi LEFT JOIN orders o ON oi.order_id = o.id WHERE o.id IS NULL"}, } for _, c := range checks { var n int err := db.QueryRow(c.query).Scan(&n) if err != nil { fmt.Printf(" ⚠ %s: query error: %v\n", c.name, err) continue } if c.name == "test accounts exist" { if n == 5 { fmt.Printf(" ✓ %s: %d/5\n", c.name, n) } else { fmt.Printf(" ✗ %s: only %d/5 found!\n", c.name, n) } } else { if n == 0 { fmt.Printf(" ✓ %s: OK\n", c.name) } else { fmt.Printf(" ✗ %s: %d orphans found!\n", c.name, n) } } } }