veza/fixtures/core/generators/users.ts
2025-12-03 22:56:50 +01:00

344 lines
No EOL
9.4 KiB
TypeScript

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<string, User> = new Map()
private static usedUsernames: Set<string> = new Set()
private static usedEmails: Set<string> = 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<UserGenerationOptions> = {}): User {
return this.generate({
...options,
role: 'admin',
status: 'active',
isVerified: true,
emailVerified: true,
withAvatar: true,
withBio: true,
})
}
static generateArtist(options: Partial<UserGenerationOptions> = {}): User {
return this.generate({
...options,
role: 'artist',
isVerified: vezaFaker.datatype.boolean({ probability: 0.6 }),
withAvatar: true,
withBio: true,
})
}
static generateProducer(options: Partial<UserGenerationOptions> = {}): 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<string, UserRelations> {
const relationships = new Map<string, UserRelations>()
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<UserRole, number> } {
const users = this.getAllGeneratedUsers()
const byRole: Record<UserRole, number> = {
admin: 0,
moderator: 0,
producer: 0,
artist: 0,
user: 0
}
users.forEach(user => {
byRole[user.role]++
})
return {
totalUsers: users.length,
byRole
}
}
}