442 lines
No EOL
14 KiB
TypeScript
442 lines
No EOL
14 KiB
TypeScript
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<string, User> = new Map()
|
|
private static tracks: Map<string, Audio> = new Map()
|
|
private static playlists: Map<string, Playlist> = new Map()
|
|
private static conversations: Map<string, Conversation> = new Map()
|
|
private static messages: Map<string, Message> = new Map()
|
|
|
|
// Relationship mappings
|
|
private static userTracks: Map<string, string[]> = new Map()
|
|
private static userPlaylists: Map<string, string[]> = new Map()
|
|
private static userConversations: Map<string, string[]> = new Map()
|
|
private static playlistTracks: Map<string, string[]> = new Map()
|
|
private static conversationParticipants: Map<string, string[]> = new Map()
|
|
private static conversationMessages: Map<string, string[]> = 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<string, User>
|
|
tracks: Map<string, Audio>
|
|
playlists: Map<string, Playlist>
|
|
conversations: Map<string, Conversation>
|
|
messages: Map<string, Message>
|
|
} {
|
|
return {
|
|
users: this.users,
|
|
tracks: this.tracks,
|
|
playlists: this.playlists,
|
|
conversations: this.conversations,
|
|
messages: this.messages
|
|
}
|
|
}
|
|
} |