package main import ( "database/sql" "fmt" "strings" "time" "golang.org/x/crypto/bcrypt" ) // TestAccount represents a predefined test account. type TestAccount struct { Email string Password string Username string DisplayName string Role string IsAdmin bool Bio string } var testAccounts = []TestAccount{ {"admin@veza.music", "Admin123!", "admin_veza", "Admin Veza", "admin", true, "Platform administrator. Managing all the things."}, {"artist@veza.music", "Artist123!", "top_artist", "Luna Dubois", "creator", false, "Productrice electro basée à Paris. Melodic techno & ambient. Label founder."}, {"user@veza.music", "User123!", "music_fan", "Music Fan", "user", false, "Music lover. Always discovering new sounds. Playlist curator."}, {"mod@veza.music", "Mod123!", "mod_veza", "Moderator Veza", "moderator", false, "Community moderator. Keeping the vibes positive."}, {"new@veza.music", "New123!", "new_user", "New User", "user", false, "Just joined!"}, } // SeededUser holds user data for cross-referencing in other seeders. type SeededUser struct { ID string Email string Username string DisplayName string Role string IsAdmin bool CreatedAt time.Time } // SeedUsers creates all users, profiles, settings, and role assignments. // Returns the full list of seeded users for use by other seeders. func SeedUsers(db *sql.DB, cfg Config) ([]SeededUser, error) { fmt.Println("\n═══ USERS ═══") // Pre-hash passwords (bcrypt cost 12) defaultHash, err := bcrypt.GenerateFromPassword([]byte("Password123!"), 12) if err != nil { return nil, fmt.Errorf("bcrypt default: %w", err) } // Hash each test account password testHashes := make([]string, len(testAccounts)) for i, ta := range testAccounts { h, err := bcrypt.GenerateFromPassword([]byte(ta.Password), 12) if err != nil { return nil, fmt.Errorf("bcrypt test account %s: %w", ta.Email, err) } testHashes[i] = string(h) } users := make([]SeededUser, 0, cfg.TotalUsers) // ── 1. Create test accounts first ──────────────────────────────────────── p := NewProgress("users (test accounts)", len(testAccounts)) testRows := make([][]interface{}, 0, len(testAccounts)) for i, ta := range testAccounts { id := newUUID() createdAt := DaysAgo(365 + (len(testAccounts)-i)*30) // Staggered creation u := SeededUser{ ID: id, Email: ta.Email, Username: ta.Username, DisplayName: ta.DisplayName, Role: ta.Role, IsAdmin: ta.IsAdmin, CreatedAt: createdAt, } users = append(users, u) testRows = append(testRows, []interface{}{ id, ta.Email, createdAt, // email_verified_at = created_at testHashes[i], ta.Username, ta.Username, ta.DisplayName, nil, nil, // first_name, last_name ta.Bio, nil, // location ta.Role, true, true, false, ta.IsAdmin, true, 0, nil, createdAt, 0, nil, nil, createdAt, createdAt, nil, "{}", }) } _, err = BulkInsert(db, "users", "id, email, email_verified_at, password_hash, username, slug, display_name, first_name, last_name, bio, location, role, is_active, is_verified, is_banned, is_admin, is_public, token_version, last_password_change_at, last_login_at, login_count, last_login_ip, username_changed_at, created_at, updated_at, deleted_at, social_links", testRows) if err != nil { return nil, fmt.Errorf("insert test users: %w", err) } p.Update(len(testAccounts)) p.Done() // ── 2. Generate remaining users ────────────────────────────────────────── remaining := cfg.TotalUsers - len(testAccounts) if remaining <= 0 { return users, nil } // Distribute roles artistCount := cfg.Artists - 1 // -1 for test artist labelCount := cfg.Labels modCount := cfg.Moderators - 1 // -1 for test mod adminCount := cfg.Admins - 1 // -1 for test admin normalCount := remaining - artistCount - labelCount - modCount - adminCount type roleAssignment struct { role string isAdmin bool count int } assignments := []roleAssignment{ {"admin", true, adminCount}, {"moderator", false, modCount}, {"creator", false, artistCount}, {"creator", false, labelCount}, // Labels are creators too {"user", false, normalCount}, } pwHash := string(defaultHash) userIdx := len(testAccounts) allRows := make([][]interface{}, 0, remaining) for _, ra := range assignments { p = NewProgress(fmt.Sprintf("users (%s)", ra.role), ra.count) for i := 0; i < ra.count; i++ { id := newUUID() createdAt := RandomRegistrationTime(userIdx, cfg.TotalUsers) createdAt = RealisticHour(createdAt) var displayName, bio, location string var firstName, lastName *string if ra.role == "creator" { displayName = GenArtistName(userIdx) genres := GenreForArtist(userIdx) bio = GenBio(genres[0].Name) location = pick(cities) fn := pick(frenchNames) ln := pick(frenchLastNames) firstName = &fn lastName = &ln } else { fn := pick(frenchNames) ln := pick(frenchLastNames) displayName = fn + " " + ln firstName = &fn lastName = &ln if randChance(60) { bio = GenShortBio() } if randChance(40) { location = pick(cities) } } username := GenUsername(displayName, userIdx) email := fmt.Sprintf("%s@veza-user.local", username) // Email verification: 90% verified var emailVerifiedAt interface{} isVerified := randChance(90) if isVerified { emailVerifiedAt = RandomTimeAfter(createdAt) } var socialLinks string if ra.role == "creator" && randChance(70) { socialLinks = fmt.Sprintf(`{"website":"https://%s.com","instagram":"@%s"}`, username, username) } else { socialLinks = "{}" } var lastLoginAt interface{} loginCount := 0 if randChance(80) { lastLoginAt = RandomTimeAfter(createdAt) loginCount = randInt(1, 200) } u := SeededUser{ ID: id, Email: email, Username: username, DisplayName: displayName, Role: ra.role, IsAdmin: ra.isAdmin, CreatedAt: createdAt, } users = append(users, u) allRows = append(allRows, []interface{}{ id, email, emailVerifiedAt, pwHash, username, username, displayName, firstName, lastName, bio, location, ra.role, true, isVerified, false, ra.isAdmin, true, 0, nil, lastLoginAt, loginCount, nil, nil, createdAt, createdAt, nil, socialLinks, }) userIdx++ } p.Update(ra.count) p.Done() } p = NewProgress("users (bulk insert)", len(allRows)) _, err = BulkInsert(db, "users", "id, email, email_verified_at, password_hash, username, slug, display_name, first_name, last_name, bio, location, role, is_active, is_verified, is_banned, is_admin, is_public, token_version, last_password_change_at, last_login_at, login_count, last_login_ip, username_changed_at, created_at, updated_at, deleted_at, social_links", allRows) if err != nil { return nil, fmt.Errorf("insert generated users: %w", err) } p.Update(len(allRows)) p.Done() // ── 3. User profiles ───────────────────────────────────────────────────── p = NewProgress("user_profiles", len(users)) profileRows := make([][]interface{}, 0, len(users)) for _, u := range users { var bio, tagline, website, bannerURL, avatarURL *string language := "fr" if randChance(30) { language = "en" } theme := pick([]string{"auto", "light", "dark"}) visibility := "public" if u.Role == "creator" { b := GenBio(GenreForArtist(0)[0].Name) bio = &b t := strings.Split(b, ".")[0] tagline = &t w := fmt.Sprintf("https://%s.com", u.Username) website = &w bn := fmt.Sprintf("https://cdn.veza.music/banners/%s.jpg", u.ID) bannerURL = &bn } av := fmt.Sprintf("https://cdn.veza.music/avatars/%s.jpg", u.ID) avatarURL = &av profileRows = append(profileRows, []interface{}{ newUUID(), u.ID, bio, tagline, nil, website, nil, nil, avatarURL, bannerURL, language, "UTC", theme, visibility, false, true, 0, 0, 0, 0, // counts will be updated u.CreatedAt, u.CreatedAt, }) } _, err = BulkInsert(db, "user_profiles", "id, user_id, bio, tagline, location, website_url, birthdate, gender, avatar_url, banner_url, language, timezone, theme, profile_visibility, show_email, show_location, follower_count, following_count, track_count, playlist_count, created_at, updated_at", profileRows) if err != nil { return nil, fmt.Errorf("insert profiles: %w", err) } p.Update(len(users)) p.Done() // ── 4. User settings ───────────────────────────────────────────────────── p = NewProgress("user_settings", len(users)) settingsRows := make([][]interface{}, 0, len(users)) for _, u := range users { settingsRows = append(settingsRows, []interface{}{ newUUID(), u.ID, true, true, true, true, true, true, true, true, false, true, true, false, true, u.CreatedAt, u.CreatedAt, }) } _, err = BulkInsert(db, "user_settings", "id, user_id, email_notifications, push_notifications, browser_notifications, email_on_follow, email_on_like, email_on_comment, email_on_message, email_on_mention, email_marketing, allow_search_indexing, show_activity, explicit_content, autoplay, created_at, updated_at", settingsRows) if err != nil { return nil, fmt.Errorf("insert settings: %w", err) } p.Update(len(users)) p.Done() // ── 5. User roles (via roles table) ────────────────────────────────────── p = NewProgress("user_roles", len(users)) // First, fetch role IDs from the roles table roleMap := make(map[string]string) rows, err := db.Query("SELECT id, name FROM roles") if err != nil { return nil, fmt.Errorf("fetch roles: %w", err) } for rows.Next() { var id, name string _ = rows.Scan(&id, &name) roleMap[name] = id } rows.Close() roleRows := make([][]interface{}, 0, len(users)) for _, u := range users { roleName := u.Role roleID, ok := roleMap[roleName] if !ok { // Try mapping 'creator' to a role in the table if roleName == "creator" { if rid, ok2 := roleMap["creator"]; ok2 { roleID = rid } else { continue } } else { continue } } roleRows = append(roleRows, []interface{}{ newUUID(), u.ID, roleID, nil, roleName, true, nil, nil, u.CreatedAt, nil, true, u.CreatedAt, }) } _, err = BulkInsert(db, "user_roles", "id, user_id, role_id, assigned_by, role, verified, verified_at, verified_by, assigned_at, expires_at, is_active, created_at", roleRows) if err != nil { return nil, fmt.Errorf("insert user_roles: %w", err) } p.Update(len(users)) p.Done() return users, nil } // GetArtists returns only users with role "creator". func GetArtists(users []SeededUser) []SeededUser { var artists []SeededUser for _, u := range users { if u.Role == "creator" { artists = append(artists, u) } } return artists } // GetListeners returns non-creator, non-admin, non-moderator users. func GetListeners(users []SeededUser) []SeededUser { var listeners []SeededUser for _, u := range users { if u.Role == "user" || u.Role == "premium" { listeners = append(listeners, u) } } return listeners } // GetModerators returns users with moderator role. func GetModerators(users []SeededUser) []SeededUser { var mods []SeededUser for _, u := range users { if u.Role == "moderator" { mods = append(mods, u) } } return mods } // GetAdmins returns users with admin role. func GetAdmins(users []SeededUser) []SeededUser { var admins []SeededUser for _, u := range users { if u.IsAdmin { admins = append(admins, u) } } return admins }