import { UserGenerator } from '../generators/users' import { AudioGenerator } from '../generators/audio' import { ConversationGenerator } from '../generators/conversations' import type { User, Audio, Playlist, Conversation, Message } from '../schemas/database' /** * Manages relationships and consistency between generated data */ export class DataRelationManager { private static users: Map = new Map() private static tracks: Map = new Map() private static playlists: Map = new Map() private static conversations: Map = new Map() private static messages: Map = new Map() // Relationship mappings private static userTracks: Map = new Map() private static userPlaylists: Map = new Map() private static userConversations: Map = new Map() private static playlistTracks: Map = new Map() private static conversationParticipants: Map = new Map() private static conversationMessages: Map = new Map() /** * Register entities and build relationships */ static registerUser(user: User): void { this.users.set(user.id, user) this.userTracks.set(user.id, []) this.userPlaylists.set(user.id, []) this.userConversations.set(user.id, []) } static registerTrack(track: Audio): void { this.tracks.set(track.id, track) // Link track to uploader const userTracks = this.userTracks.get(track.uploadedById) || [] userTracks.push(track.id) this.userTracks.set(track.uploadedById, userTracks) } static registerPlaylist(playlist: Playlist): void { this.playlists.set(playlist.id, playlist) this.playlistTracks.set(playlist.id, playlist.trackIds) // Link playlist to creator const userPlaylists = this.userPlaylists.get(playlist.createdById) || [] userPlaylists.push(playlist.id) this.userPlaylists.set(playlist.createdById, userPlaylists) } static registerConversation(conversation: Conversation): void { this.conversations.set(conversation.id, conversation) this.conversationParticipants.set(conversation.id, conversation.participantIds) this.conversationMessages.set(conversation.id, []) // Link conversation to participants conversation.participantIds.forEach(participantId => { const userConversations = this.userConversations.get(participantId) || [] userConversations.push(conversation.id) this.userConversations.set(participantId, userConversations) }) } static registerMessage(message: Message): void { this.messages.set(message.id, message) // Link message to conversation const conversationMessages = this.conversationMessages.get(message.conversationId) || [] conversationMessages.push(message.id) this.conversationMessages.set(message.conversationId, conversationMessages) } /** * Generate complete dataset with consistent relationships */ static generateCompleteDataset(): { users: User[] tracks: Audio[] playlists: Playlist[] conversations: Conversation[] messages: Message[] } { console.log('🎯 Generating complete dataset with consistent relationships...') // Clear existing data this.clearAll() // Step 1: Generate users console.log('👥 Generating users...') const users = UserGenerator.generateWithDistribution() users.forEach(user => this.registerUser(user)) console.log(`✅ Generated ${users.length} users`) // Step 2: Generate audio content for artists and producers console.log('🎵 Generating audio content...') const tracks: Audio[] = [] const playlists: Playlist[] = [] users.forEach(user => { if (user.role === 'artist' || user.role === 'producer') { const content = AudioGenerator.generateUserContent(user.id, user.role) // Register tracks content.tracks.forEach(track => { track.uploadedById = user.id // Ensure consistency this.registerTrack(track) tracks.push(track) }) // Register playlists content.playlists.forEach(playlist => { playlist.createdById = user.id // Ensure consistency // Use real track IDs for playlists playlist.trackIds = this.getRandomTrackIds(playlist.trackIds.length) this.registerPlaylist(playlist) playlists.push(playlist) }) } else if (user.role === 'user') { // Regular users create playlists but don't upload tracks const content = AudioGenerator.generateUserContent(user.id, user.role) content.playlists.forEach(playlist => { playlist.createdById = user.id playlist.trackIds = this.getRandomTrackIds(playlist.trackIds.length) this.registerPlaylist(playlist) playlists.push(playlist) }) } }) console.log(`✅ Generated ${tracks.length} tracks and ${playlists.length} playlists`) // Step 3: Generate conversations console.log('💬 Generating conversations...') const userIds = users.map(u => u.id) const conversations = ConversationGenerator.generateWithDistribution(userIds) conversations.forEach(conv => this.registerConversation(conv)) // Step 4: Generate messages for conversations console.log('📝 Generating messages...') const allMessages: Message[] = [] conversations.forEach(conversation => { const messages = ConversationGenerator.generateMessages( conversation.id, conversation.participantIds, this.getMessageCountForConversation(conversation.type) ) messages.forEach(message => { this.registerMessage(message) allMessages.push(message) }) // Update conversation with last message info if (messages.length > 0) { const lastMessage = messages[messages.length - 1] conversation.lastMessageId = lastMessage.id conversation.lastActivityAt = lastMessage.createdAt conversation.updatedAt = lastMessage.createdAt } }) console.log(`✅ Generated ${conversations.length} conversations with ${allMessages.length} messages`) // Step 5: Update user statistics based on generated content this.updateUserStatistics(users) console.log('✅ Dataset generation complete!') return { users, tracks, playlists, conversations, messages: allMessages } } /** * Validation methods */ static validateRelations(): { isValid: boolean errors: string[] warnings: string[] } { const errors: string[] = [] const warnings: string[] = [] // Validate user-track relationships this.userTracks.forEach((trackIds, userId) => { if (!this.users.has(userId)) { errors.push(`User ${userId} not found but has tracks`) } trackIds.forEach(trackId => { const track = this.tracks.get(trackId) if (!track) { errors.push(`Track ${trackId} not found`) } else if (track.uploadedById !== userId) { errors.push(`Track ${trackId} uploader mismatch`) } }) }) // Validate playlist-track relationships this.playlistTracks.forEach((trackIds, playlistId) => { const playlist = this.playlists.get(playlistId) if (!playlist) { errors.push(`Playlist ${playlistId} not found`) return } trackIds.forEach(trackId => { if (!this.tracks.has(trackId)) { warnings.push(`Playlist ${playlistId} references non-existent track ${trackId}`) } }) }) // Validate conversation-participant relationships this.conversationParticipants.forEach((participantIds, conversationId) => { const conversation = this.conversations.get(conversationId) if (!conversation) { errors.push(`Conversation ${conversationId} not found`) return } participantIds.forEach(participantId => { if (!this.users.has(participantId)) { errors.push(`Conversation ${conversationId} has non-existent participant ${participantId}`) } }) }) // Validate message-conversation relationships this.conversationMessages.forEach((messageIds, conversationId) => { if (!this.conversations.has(conversationId)) { errors.push(`Messages reference non-existent conversation ${conversationId}`) return } messageIds.forEach(messageId => { const message = this.messages.get(messageId) if (!message) { errors.push(`Message ${messageId} not found`) } else if (message.conversationId !== conversationId) { errors.push(`Message ${messageId} conversation mismatch`) } }) }) return { isValid: errors.length === 0, errors, warnings } } /** * Query methods */ static getUserTracks(userId: string): Audio[] { const trackIds = this.userTracks.get(userId) || [] return trackIds.map(id => this.tracks.get(id)!).filter(Boolean) } static getUserPlaylists(userId: string): Playlist[] { const playlistIds = this.userPlaylists.get(userId) || [] return playlistIds.map(id => this.playlists.get(id)!).filter(Boolean) } static getUserConversations(userId: string): Conversation[] { const conversationIds = this.userConversations.get(userId) || [] return conversationIds.map(id => this.conversations.get(id)!).filter(Boolean) } static getPlaylistTracks(playlistId: string): Audio[] { const trackIds = this.playlistTracks.get(playlistId) || [] return trackIds.map(id => this.tracks.get(id)!).filter(Boolean) } static getConversationMessages(conversationId: string): Message[] { const messageIds = this.conversationMessages.get(conversationId) || [] return messageIds.map(id => this.messages.get(id)!).filter(Boolean) } /** * Statistics and insights */ static getStatistics(): { users: number tracks: number playlists: number conversations: number messages: number relationships: { avgTracksPerArtist: number avgPlaylistsPerUser: number avgMessagesPerConversation: number avgParticipantsPerConversation: number } } { const artistCount = Array.from(this.users.values()) .filter(u => u.role === 'artist' || u.role === 'producer').length const totalTracks = Array.from(this.userTracks.values()) .reduce((sum, tracks) => sum + tracks.length, 0) const totalPlaylists = Array.from(this.userPlaylists.values()) .reduce((sum, playlists) => sum + playlists.length, 0) const totalMessages = Array.from(this.conversationMessages.values()) .reduce((sum, messages) => sum + messages.length, 0) const totalParticipants = Array.from(this.conversationParticipants.values()) .reduce((sum, participants) => sum + participants.length, 0) return { users: this.users.size, tracks: this.tracks.size, playlists: this.playlists.size, conversations: this.conversations.size, messages: this.messages.size, relationships: { avgTracksPerArtist: artistCount > 0 ? Math.round(totalTracks / artistCount) : 0, avgPlaylistsPerUser: this.users.size > 0 ? Math.round(totalPlaylists / this.users.size) : 0, avgMessagesPerConversation: this.conversations.size > 0 ? Math.round(totalMessages / this.conversations.size) : 0, avgParticipantsPerConversation: this.conversations.size > 0 ? Math.round(totalParticipants / this.conversations.size) : 0, } } } /** * Export methods for different formats */ static exportForDatabase(): { users: User[] tracks: Audio[] playlists: Playlist[] conversations: Conversation[] messages: Message[] } { return { users: Array.from(this.users.values()), tracks: Array.from(this.tracks.values()), playlists: Array.from(this.playlists.values()), conversations: Array.from(this.conversations.values()), messages: Array.from(this.messages.values()) } } static exportForAPI(): any { // Transform data for API responses const users = Array.from(this.users.values()).map(user => ({ ...user, tracks: this.getUserTracks(user.id).length, playlists: this.getUserPlaylists(user.id).length, conversations: this.getUserConversations(user.id).length })) const tracks = Array.from(this.tracks.values()).map(track => ({ ...track, uploader: this.users.get(track.uploadedById) })) return { users, tracks } } /** * Private helper methods */ private static getRandomTrackIds(count: number): string[] { const allTrackIds = Array.from(this.tracks.keys()) if (allTrackIds.length === 0) return [] const selectedIds: string[] = [] for (let i = 0; i < Math.min(count, allTrackIds.length); i++) { const randomId = allTrackIds[Math.floor(Math.random() * allTrackIds.length)] if (!selectedIds.includes(randomId)) { selectedIds.push(randomId) } } return selectedIds } private static getMessageCountForConversation(type: string): number { const counts = { direct: Math.floor(Math.random() * 50) + 10, group: Math.floor(Math.random() * 100) + 20, channel: Math.floor(Math.random() * 200) + 50 } return counts[type as keyof typeof counts] || 30 } private static updateUserStatistics(users: User[]): void { users.forEach(user => { const tracks = this.getUserTracks(user.id) const playlists = this.getUserPlaylists(user.id) user.stats.tracksUploaded = tracks.length user.stats.playlistsCreated = playlists.length user.stats.totalPlays = tracks.reduce((sum, track) => sum + track.stats.plays, 0) }) } /** * Cleanup methods */ static clearAll(): void { this.users.clear() this.tracks.clear() this.playlists.clear() this.conversations.clear() this.messages.clear() this.userTracks.clear() this.userPlaylists.clear() this.userConversations.clear() this.playlistTracks.clear() this.conversationParticipants.clear() this.conversationMessages.clear() } static getAll(): { users: Map tracks: Map playlists: Map conversations: Map messages: Map } { return { users: this.users, tracks: this.tracks, playlists: this.playlists, conversations: this.conversations, messages: this.messages } } }