import { v4 as uuidv4 } from 'uuid' import { vezaFaker } from '../utils/faker-config' import { globalConfig } from '../config' import type { User, UserRole, UserStatus } from '../schemas/database' /** * User generation options */ export interface UserGenerationOptions { role?: UserRole status?: UserStatus isVerified?: boolean emailVerified?: boolean withAvatar?: boolean withBio?: boolean withStats?: boolean createdAt?: Date lastSeenAt?: Date } /** * User relationship data */ export interface UserRelations { followers: string[] following: string[] blockedUsers: string[] mutedUsers: string[] } /** * Advanced user generator for Veza Platform */ export class UserGenerator { private static generatedUsers: Map = new Map() private static usedUsernames: Set = new Set() private static usedEmails: Set = new Set() /** * Generate a single user with realistic data */ static generate(options: UserGenerationOptions = {}): User { const createdAt = options.createdAt || vezaFaker.date.userCreation() const firstName = vezaFaker.person.firstName() const lastName = vezaFaker.person.lastName() // Ensure unique username and email let username = vezaFaker.user.username(firstName, lastName) let attempts = 0 while (this.usedUsernames.has(username) && attempts < 10) { username = vezaFaker.user.username(firstName, lastName) attempts++ } this.usedUsernames.add(username) let email = `${username}@${vezaFaker.internet.domainName()}` attempts = 0 while (this.usedEmails.has(email) && attempts < 10) { email = `${username}${vezaFaker.number.int({ min: 1, max: 999 })}@${vezaFaker.internet.domainName()}` attempts++ } this.usedEmails.add(email) const role = options.role || this.generateWeightedRole() const status = options.status || 'active' const isVerified = options.isVerified ?? this.shouldBeVerified(role) const stats = options.withStats !== false ? vezaFaker.stats.userStats(role, createdAt) : { tracksUploaded: 0, playlistsCreated: 0, followersCount: 0, followingCount: 0, totalPlays: 0, } const user: User = { id: uuidv4(), username, email, firstName, lastName, displayName: options.withBio !== false ? `${firstName} ${lastName}` : undefined, role, status, avatar: options.withAvatar !== false ? vezaFaker.image.avatar() : undefined, bio: options.withBio !== false ? vezaFaker.user.bio(role) : undefined, location: vezaFaker.user.location(), website: role === 'artist' || role === 'producer' ? vezaFaker.internet.url() : undefined, isVerified, emailVerified: options.emailVerified ?? vezaFaker.datatype.boolean({ probability: 0.8 }), preferences: { language: vezaFaker.helpers.arrayElement(['fr', 'en']), theme: vezaFaker.helpers.arrayElement(['light', 'dark', 'system']), notifications: { email: vezaFaker.datatype.boolean({ probability: 0.7 }), push: vezaFaker.datatype.boolean({ probability: 0.8 }), desktop: vezaFaker.datatype.boolean({ probability: 0.3 }), }, privacy: { profileVisibility: vezaFaker.helpers.arrayElement(['public', 'friends', 'private']), showOnlineStatus: vezaFaker.datatype.boolean({ probability: 0.6 }), allowDirectMessages: vezaFaker.datatype.boolean({ probability: 0.9 }), }, }, stats, lastSeenAt: options.lastSeenAt || (status === 'active' ? vezaFaker.date.recentActivity() : undefined), createdAt, updatedAt: vezaFaker.date.between({ from: createdAt, to: new Date() }), } this.generatedUsers.set(user.id, user) return user } /** * Generate multiple users with distribution */ static generateBatch(count: number, options: UserGenerationOptions = {}): User[] { const users: User[] = [] for (let i = 0; i < count; i++) { users.push(this.generate(options)) } return users } /** * Generate users with role distribution */ static generateWithDistribution(): User[] { const config = globalConfig.generation.users const users: User[] = [] // Generate admins for (let i = 0; i < config.adminCount; i++) { users.push(this.generate({ role: 'admin', isVerified: true, emailVerified: true, withAvatar: true, withBio: true, status: 'active' })) } // Generate artists for (let i = 0; i < config.artistCount; i++) { users.push(this.generate({ role: 'artist', isVerified: vezaFaker.datatype.boolean({ probability: 0.6 }), withAvatar: true, withBio: true, })) } // Generate producers for (let i = 0; i < config.producerCount; i++) { users.push(this.generate({ role: 'producer', isVerified: vezaFaker.datatype.boolean({ probability: 0.7 }), withAvatar: true, withBio: true, })) } // Generate moderators (small number) const moderatorCount = Math.max(1, Math.floor(config.count * 0.02)) for (let i = 0; i < moderatorCount; i++) { users.push(this.generate({ role: 'moderator', isVerified: true, emailVerified: true, withAvatar: true, withBio: true, status: 'active' })) } // Generate regular users const regularUserCount = config.count - users.length for (let i = 0; i < regularUserCount; i++) { users.push(this.generate({ role: 'user', withAvatar: vezaFaker.datatype.boolean({ probability: 0.4 }), withBio: vezaFaker.datatype.boolean({ probability: 0.3 }), })) } return users } /** * Generate specific user types */ static generateAdmin(options: Partial = {}): User { return this.generate({ ...options, role: 'admin', status: 'active', isVerified: true, emailVerified: true, withAvatar: true, withBio: true, }) } static generateArtist(options: Partial = {}): User { return this.generate({ ...options, role: 'artist', isVerified: vezaFaker.datatype.boolean({ probability: 0.6 }), withAvatar: true, withBio: true, }) } static generateProducer(options: Partial = {}): User { return this.generate({ ...options, role: 'producer', isVerified: vezaFaker.datatype.boolean({ probability: 0.7 }), withAvatar: true, withBio: true, }) } static generateTestUser(username?: string): User { return this.generate({ role: 'user', status: 'active', isVerified: false, emailVerified: true, withAvatar: false, withBio: false, }) } /** * Generate user relationships */ static generateRelationships(users: User[]): Map { const relationships = new Map() users.forEach(user => { const maxFollowing = Math.min(user.stats.followingCount, users.length - 1) const maxFollowers = Math.min(user.stats.followersCount, users.length - 1) // Generate following relationships const following = vezaFaker.helpers.arrayElements( users.filter(u => u.id !== user.id).map(u => u.id), Math.min(maxFollowing, 50) ) // Generate followers (some overlap with following) const potentialFollowers = users .filter(u => u.id !== user.id && !following.includes(u.id)) .map(u => u.id) const additionalFollowers = vezaFaker.helpers.arrayElements( potentialFollowers, Math.min(maxFollowers - following.length, potentialFollowers.length) ) const followers = [...following.slice(0, Math.floor(following.length * 0.3)), ...additionalFollowers] relationships.set(user.id, { followers, following, blockedUsers: [], mutedUsers: [] }) }) return relationships } /** * Private helper methods */ private static generateWeightedRole(): UserRole { return vezaFaker.utils.weightedChoice([ { item: 'user', weight: 70 }, { item: 'artist', weight: 20 }, { item: 'producer', weight: 8 }, { item: 'moderator', weight: 1.5 }, { item: 'admin', weight: 0.5 } ]) } private static shouldBeVerified(role: UserRole): boolean { const verificationProbability = { admin: 1.0, moderator: 1.0, producer: 0.7, artist: 0.6, user: 0.1 } return vezaFaker.datatype.boolean({ probability: verificationProbability[role] }) } /** * Utility methods */ static getGeneratedUser(id: string): User | undefined { return this.generatedUsers.get(id) } static getAllGeneratedUsers(): User[] { return Array.from(this.generatedUsers.values()) } static clearCache(): void { this.generatedUsers.clear() this.usedUsernames.clear() this.usedEmails.clear() } static getStats(): { totalUsers: number; byRole: Record } { const users = this.getAllGeneratedUsers() const byRole: Record = { admin: 0, moderator: 0, producer: 0, artist: 0, user: 0 } users.forEach(user => { byRole[user.role]++ }) return { totalUsers: users.length, byRole } } }