import { faker } from '@faker-js/faker' import { globalConfig } from '../config' import type { AudioGenre, AudioMetadata, UserRole, ConversationType, MessageType } from '../schemas/database' type AudioFormat = 'mp3' | 'wav' | 'flac' | 'aac' | 'ogg' type AudioQuality = 'low' | 'medium' | 'high' | 'lossless' /** * Enhanced Faker configuration for Veza Platform */ export class VezaFaker { private static instance: VezaFaker private seed: string private locale: string constructor(seed?: string, locale?: string) { this.seed = seed || globalConfig.seed this.locale = locale || globalConfig.locale // Configure faker with seed for reproducibility faker.seed(this.hashSeed(this.seed)) } static getInstance(seed?: string, locale?: string): VezaFaker { if (!VezaFaker.instance) { VezaFaker.instance = new VezaFaker(seed, locale) } return VezaFaker.instance } /** * Hash seed string to number for faker */ private hashSeed(seed: string): number { let hash = 0 for (let i = 0; i < seed.length; i++) { const char = seed.charCodeAt(i) hash = ((hash << 5) - hash) + char hash = hash & hash // Convert to 32-bit integer } return Math.abs(hash) } /** * Music-specific generators */ music = { /** * Generate realistic song titles */ songTitle: (): string => { const patterns = [ () => faker.word.adjective() + ' ' + faker.word.noun(), () => faker.word.noun() + ' in ' + faker.location.city(), () => faker.word.verb() + ' ' + faker.word.adverb(), () => faker.person.firstName() + "'s " + faker.word.noun(), () => faker.color.human() + ' ' + faker.word.noun(), () => 'The ' + faker.word.adjective() + ' ' + faker.word.noun(), ] return faker.helpers.arrayElement(patterns)() }, /** * Generate artist names */ artistName: (): string => { const patterns = [ () => faker.person.firstName() + ' ' + faker.person.lastName(), () => faker.person.firstName(), () => 'The ' + faker.word.noun() + 's', () => faker.word.adjective() + ' ' + faker.word.noun(), () => faker.person.lastName() + ' & ' + faker.person.lastName(), () => faker.word.noun() + ' ' + faker.word.noun(), ] return faker.helpers.arrayElement(patterns)() }, /** * Generate album titles */ albumTitle: (): string => { const patterns = [ () => faker.word.adjective() + ' ' + faker.word.noun(), () => faker.location.city() + ' ' + faker.word.noun(), () => 'The ' + faker.word.adjective() + ' ' + faker.word.noun(), () => faker.word.noun() + ' of ' + faker.word.noun(), () => faker.date.recent().getFullYear().toString(), ] return faker.helpers.arrayElement(patterns)() }, /** * Generate playlist names */ playlistName: (): string => { const patterns = [ () => faker.word.adjective() + ' Vibes', () => faker.word.noun() + ' Mix', () => 'My ' + faker.word.adjective() + ' Playlist', () => faker.date.month() + ' ' + faker.date.recent().getFullYear(), () => faker.word.adjective() + ' ' + faker.word.noun() + 's', () => 'Best of ' + faker.word.noun(), ] return faker.helpers.arrayElement(patterns)() }, /** * Generate music genre */ genre: (): AudioGenre => { return faker.helpers.arrayElement([ 'rock', 'pop', 'jazz', 'classical', 'electronic', 'hip-hop', 'country', 'blues', 'reggae', 'folk', 'metal', 'punk', 'indie', 'ambient', 'techno' ] as AudioGenre[]) }, /** * Generate realistic audio metadata */ audioMetadata: (genre?: AudioGenre): AudioMetadata => { const baseMetadata = { duration: faker.number.int({ min: 30, max: 600 }), // 30s to 10min bitrate: faker.helpers.arrayElement([128, 192, 256, 320]), // kbps sampleRate: faker.helpers.arrayElement([44100, 48000, 96000]), // Hz channels: faker.helpers.arrayElement([1, 2]), // Mono or Stereo format: faker.helpers.arrayElement(['mp3', 'wav', 'flac']) as AudioFormat, quality: faker.helpers.arrayElement(['medium', 'high', 'lossless']) as AudioQuality, bpm: faker.number.int({ min: 60, max: 200 }), loudness: faker.number.float({ min: -30, max: -6, precision: 0.1 }), } // Calculate file size based on metadata const bytesPerSecond = (baseMetadata.bitrate * 1000) / 8 const fileSize = Math.round(bytesPerSecond * baseMetadata.duration) return { ...baseMetadata, fileSize } }, /** * Generate waveform data */ waveformData: (duration: number, samples: number = 200): number[] => { const data: number[] = [] const samplesPerSecond = samples / duration for (let i = 0; i < samples; i++) { // Create realistic waveform with peaks and valleys const time = i / samplesPerSecond const base = Math.sin(time * 0.5) * 0.3 const noise = (faker.number.float({ min: -0.2, max: 0.2 })) const peak = Math.random() > 0.95 ? faker.number.float({ min: 0.7, max: 1.0 }) : 0 data.push(Math.max(0, Math.min(1, base + noise + peak))) } return data }, /** * Generate music tags */ tags: (count: number = 3): string[] => { const musicTags = [ 'chill', 'upbeat', 'acoustic', 'electric', 'instrumental', 'vocal', 'experimental', 'mainstream', 'underground', 'vintage', 'modern', 'energetic', 'relaxing', 'emotional', 'party', 'study', 'workout', 'romantic', 'melancholic', 'happy', 'dark', 'bright', 'atmospheric' ] return faker.helpers.arrayElements(musicTags, count) } } /** * User-specific generators */ user = { /** * Generate username variations */ username: (firstName?: string, lastName?: string): string => { const first = firstName || faker.person.firstName() const last = lastName || faker.person.lastName() const patterns = [ () => first.toLowerCase() + last.toLowerCase(), () => first.toLowerCase() + '.' + last.toLowerCase(), () => first.toLowerCase() + '_' + last.toLowerCase(), () => first.toLowerCase() + faker.number.int({ min: 1, max: 999 }), () => first.toLowerCase() + last.toLowerCase() + faker.number.int({ min: 10, max: 99 }), () => last.toLowerCase() + first.charAt(0).toLowerCase(), ] return faker.helpers.arrayElement(patterns)() }, /** * Generate role-appropriate bio */ bio: (role: UserRole): string => { const bios = { admin: [ 'Administrateur de la plateforme Veza', 'Gardien de la communauté musicale', 'Passionné de musique et de technologie' ], artist: [ 'Artiste indépendant passionné de musique', 'Compositeur et interprète', 'Créateur musical en constante évolution', 'Musicien cherchant à partager sa passion', 'Artiste émergent sur la scène musicale' ], producer: [ 'Producteur musical professionnel', 'Ingénieur du son et producteur', 'Spécialiste en production musicale', 'Créateur de beats et arrangements' ], user: [ 'Mélomane et amateur de musique', 'Passionné de découvertes musicales', 'Collectionneur de playlists', 'Amateur de concerts et festivals' ], moderator: [ 'Modérateur communautaire', 'Gardien de la bonne ambiance', 'Facilitateur des échanges musicaux' ] } return faker.helpers.arrayElement(bios[role]) }, /** * Generate realistic location */ location: (): string => { const frenchCities = [ 'Paris', 'Lyon', 'Marseille', 'Toulouse', 'Nice', 'Nantes', 'Strasbourg', 'Montpellier', 'Bordeaux', 'Lille', 'Rennes', 'Reims', 'Le Havre', 'Toulon', 'Grenoble' ] return faker.helpers.arrayElement(frenchCities) } } /** * Chat-specific generators */ chat = { /** * Generate conversation name */ conversationName: (type: ConversationType): string => { if (type === 'direct') return '' // Direct messages don't have names const groupNames = [ 'Équipe Créative', 'Studio Session', 'Projet Musical', 'Collaboration', 'Mix & Master', 'Songwriting', 'Feedback Loop', 'Artistes Unis', 'Production Team', 'Sound Design', 'Remix Club', 'Beat Makers' ] const channelNames = [ 'général', 'annonces', 'musique', 'collaborations', 'feedback', 'technique', 'événements', 'showcase', 'random', 'aide' ] return type === 'group' ? faker.helpers.arrayElement(groupNames) : faker.helpers.arrayElement(channelNames) }, /** * Generate realistic message content */ messageContent: (type: MessageType = 'text'): string => { if (type === 'system') { const systemMessages = [ 'a rejoint la conversation', 'a quitté la conversation', 'a changé le nom du groupe', 'a ajouté une photo de profil', 'a partagé un fichier' ] return faker.helpers.arrayElement(systemMessages) } const musicMessages = [ 'Salut ! Comment ça va ?', 'Tu as écouté le nouveau son ?', 'On fait une session studio demain ?', 'J\'ai une idée de mélodie géniale !', 'Quel est ton genre musical préféré ?', 'Cette prod est incroyable ! 🔥', 'On pourrait collaborer sur ce projet', 'Le mix sonne vraiment bien', 'Merci pour le feedback !', 'À quelle heure on se retrouve ?', 'Cette mélodie me donne des frissons', 'Le mastering est parfait', 'Bravo pour cette composition !', 'On devrait faire un featuring ensemble', 'Cette basse claque vraiment fort ! 💥' ] return faker.helpers.arrayElement(musicMessages) }, /** * Generate emoji reactions */ reactionEmoji: (): string => { const musicEmojis = ['🎵', '🎶', '🎤', '🎸', '🥁', '🎹', '🎺', '🎷', '🔥', '💥', '👏', '❤️', '😍', '🤩', '💯'] return faker.helpers.arrayElement(musicEmojis) } } /** * Date generators with realistic patterns */ date = { /** * Recent activity date */ recentActivity: (): Date => { return faker.date.recent({ days: 7 }) }, /** * User creation date (platform launch to now) */ userCreation: (): Date => { return faker.date.between({ from: new Date('2024-01-01'), to: new Date() }) }, /** * Content creation date (after user creation) */ contentCreation: (userCreatedAt: Date): Date => { return faker.date.between({ from: userCreatedAt, to: new Date() }) }, /** * Release date for music */ releaseDate: (): Date => { return faker.date.between({ from: new Date('2020-01-01'), to: new Date() }) } } /** * Statistics generators */ stats = { /** * User statistics based on role and creation date */ userStats: (role: UserRole, createdAt: Date): any => { const daysSinceCreation = Math.floor((Date.now() - createdAt.getTime()) / (1000 * 60 * 60 * 24)) const activityMultiplier = Math.min(daysSinceCreation / 30, 12) // Max 12 months of activity const baseStats = { user: { tracks: 0, playlists: 2, followers: 5, following: 20, plays: 100 }, artist: { tracks: 15, playlists: 8, followers: 150, following: 50, plays: 2000 }, producer: { tracks: 25, playlists: 12, followers: 300, following: 80, plays: 5000 }, admin: { tracks: 5, playlists: 20, followers: 500, following: 100, plays: 1000 }, moderator: { tracks: 8, playlists: 15, followers: 200, following: 150, plays: 1500 } } const base = baseStats[role] return { tracksUploaded: Math.floor(base.tracks * activityMultiplier * faker.number.float({ min: 0.5, max: 2 })), playlistsCreated: Math.floor(base.playlists * activityMultiplier * faker.number.float({ min: 0.8, max: 1.5 })), followersCount: Math.floor(base.followers * activityMultiplier * faker.number.float({ min: 0.3, max: 3 })), followingCount: Math.floor(base.following * activityMultiplier * faker.number.float({ min: 0.5, max: 1.8 })), totalPlays: Math.floor(base.plays * activityMultiplier * faker.number.float({ min: 0.2, max: 5 })), } }, /** * Audio track statistics */ audioStats: (createdAt: Date, isPopular: boolean = false): any => { const daysSinceCreation = Math.floor((Date.now() - createdAt.getTime()) / (1000 * 60 * 60 * 24)) const timeMultiplier = Math.min(daysSinceCreation / 7, 52) // Max 52 weeks const popularityMultiplier = isPopular ? faker.number.float({ min: 5, max: 20 }) : 1 const basePlays = faker.number.int({ min: 10, max: 500 }) const plays = Math.floor(basePlays * timeMultiplier * popularityMultiplier) return { plays, likes: Math.floor(plays * faker.number.float({ min: 0.05, max: 0.15 })), shares: Math.floor(plays * faker.number.float({ min: 0.01, max: 0.05 })), downloads: Math.floor(plays * faker.number.float({ min: 0.02, max: 0.08 })), comments: Math.floor(plays * faker.number.float({ min: 0.008, max: 0.03 })), } } } /** * Utility methods */ utils = { /** * Generate a slug from text */ slug: (text: string): string => { return text .toLowerCase() .replace(/[^a-z0-9]+/g, '-') .replace(/^-+|-+$/g, '') }, /** * Generate a realistic file URL */ fileUrl: (type: 'audio' | 'image', filename?: string): string => { const baseUrl = globalConfig.paths.audioFiles const actualFilename = filename || faker.system.fileName({ extensionCount: 1 }) return `${baseUrl}/${type}/${actualFilename}` }, /** * Weighted random selection */ weightedChoice: (choices: Array<{ item: T; weight: number }>): T => { const totalWeight = choices.reduce((sum, choice) => sum + choice.weight, 0) let random = faker.number.float({ min: 0, max: totalWeight }) for (const choice of choices) { random -= choice.weight if (random <= 0) { return choice.item } } return choices[choices.length - 1]?.item || choices[0].item } } } /** * Global faker instance with direct faker access */ export const vezaFaker = { ...VezaFaker.getInstance(), // Direct faker access for compatibility datatype: faker.datatype, number: faker.number, string: faker.string, helpers: faker.helpers, image: faker.image, location: faker.location, internet: faker.internet, person: faker.person, word: faker.word, color: faker.color, date: { ...VezaFaker.getInstance().date, recent: faker.date.recent, soon: faker.date.soon, between: faker.date.between } }