import { Client } from 'pg' import { createClient } from 'redis' import { DataRelationManager } from '../../core/utils/data-relations' import { globalConfig } from '../../core/config' import type { User, Conversation, Message } from '../../core/schemas/database' /** * Chat server fixtures - Database seeding and Redis cache */ export class ChatServerFixtures { private static dbClient: Client | null = null private static redisClient: any = null /** * Initialize database connections */ static async initialize(): Promise { console.log('๐Ÿ’ฌ Initializing chat server fixtures...') // Initialize PostgreSQL connection this.dbClient = new Client({ host: globalConfig.database.host, port: globalConfig.database.port, user: globalConfig.database.username, password: globalConfig.database.password, database: globalConfig.database.databases.chat, }) try { await this.dbClient.connect() console.log('โœ… Connected to PostgreSQL (chat)') } catch (error) { console.warn('โš ๏ธ Could not connect to PostgreSQL (chat):', error) this.dbClient = null } // Initialize Redis connection this.redisClient = createClient({ socket: { host: globalConfig.redis.host, port: globalConfig.redis.port, }, password: globalConfig.redis.password || undefined, }) try { await this.redisClient.connect() console.log('โœ… Connected to Redis') } catch (error) { console.warn('โš ๏ธ Could not connect to Redis:', error) this.redisClient = null } } /** * Seed PostgreSQL database with chat data */ static async seedDatabase(): Promise { if (!this.dbClient) { console.warn('โš ๏ธ No database connection available') return } console.log('๐Ÿ—„๏ธ Seeding chat database...') try { // Begin transaction await this.dbClient.query('BEGIN') // Clear existing data await this.clearDatabase() // Get data from relation manager const allData = DataRelationManager.getAll() const users = Array.from(allData.users.values()) const conversations = Array.from(allData.conversations.values()) const messages = Array.from(allData.messages.values()) // Seed users console.log(`๐Ÿ“ Seeding ${users.length} users...`) await this.seedUsers(users) // Seed conversations console.log(`๐Ÿ“ Seeding ${conversations.length} conversations...`) await this.seedConversations(conversations) // Seed messages console.log(`๐Ÿ“ Seeding ${messages.length} messages...`) await this.seedMessages(messages) // Commit transaction await this.dbClient.query('COMMIT') console.log('โœ… Chat database seeded successfully') } catch (error) { await this.dbClient.query('ROLLBACK') console.error('โŒ Error seeding chat database:', error) throw error } } /** * Seed Redis cache with session data */ static async seedRedis(): Promise { if (!this.redisClient) { console.warn('โš ๏ธ No Redis connection available') return } console.log('๐Ÿ”„ Seeding Redis cache...') try { // Clear existing cache await this.redisClient.flushDb() const allData = DataRelationManager.getAll() const users = Array.from(allData.users.values()) const conversations = Array.from(allData.conversations.values()) // Cache active users const activeUsers = users.filter(user => user.status === 'active') for (const user of activeUsers) { const sessionData = { userId: user.id, username: user.username, status: 'online', lastSeen: new Date().toISOString(), socketId: `socket_${user.id}_${Date.now()}`, } await this.redisClient.setEx( `session:${user.id}`, 3600, // 1 hour TTL JSON.stringify(sessionData) ) // Add to online users set await this.redisClient.sAdd('users:online', user.id) } // Cache conversation metadata for (const conversation of conversations) { const metadata = { id: conversation.id, name: conversation.name, type: conversation.type, participantCount: conversation.participantIds.length, lastActivity: conversation.lastActivityAt.toISOString(), } await this.redisClient.setEx( `conversation:${conversation.id}`, 1800, // 30 minutes TTL JSON.stringify(metadata) ) // Cache participant lists await this.redisClient.sAdd( `conversation:${conversation.id}:participants`, ...conversation.participantIds ) // Set expiry for participant sets await this.redisClient.expire( `conversation:${conversation.id}:participants`, 1800 ) } // Cache typing indicators (empty initially) await this.redisClient.set('typing:indicators', '{}') // Cache message delivery status const recentMessages = Array.from(allData.messages.values()) .filter(msg => { const dayAgo = new Date(Date.now() - 24 * 60 * 60 * 1000) return msg.createdAt > dayAgo }) for (const message of recentMessages) { await this.redisClient.setEx( `message:${message.id}:status`, 86400, // 24 hours TTL message.status ) } console.log(`โœ… Redis cache seeded with ${activeUsers.length} sessions and ${conversations.length} conversations`) } catch (error) { console.error('โŒ Error seeding Redis cache:', error) throw error } } /** * Generate WebSocket event data */ static generateWebSocketEvents(): any[] { const allData = DataRelationManager.getAll() const users = Array.from(allData.users.values()) const conversations = Array.from(allData.conversations.values()) const messages = Array.from(allData.messages.values()) const events: any[] = [] // User connection events users.filter(u => u.status === 'active').forEach(user => { events.push({ type: 'user_connected', userId: user.id, data: { username: user.username, status: 'online', lastSeen: new Date().toISOString() }, timestamp: new Date().toISOString() }) }) // Recent message events const recentMessages = messages .filter(msg => { const hourAgo = new Date(Date.now() - 60 * 60 * 1000) return msg.createdAt > hourAgo }) .sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime()) recentMessages.forEach(message => { events.push({ type: 'message_sent', conversationId: message.conversationId, data: { id: message.id, senderId: message.senderId, content: message.content, type: message.type, createdAt: message.createdAt.toISOString() }, timestamp: message.createdAt.toISOString() }) }) // Typing indicator events conversations.forEach(conversation => { const typingUser = conversation.participantIds[0] events.push({ type: 'typing_start', conversationId: conversation.id, data: { userId: typingUser, isTyping: true }, timestamp: new Date(Date.now() - Math.random() * 30000).toISOString() }) }) return events.sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime() ) } /** * Get conversation statistics */ static async getConversationStats(): Promise { if (!this.dbClient) { return this.getMockConversationStats() } try { const result = await this.dbClient.query(` SELECT c.type, COUNT(*) as count, AVG(array_length(c.participant_ids, 1)) as avg_participants, COUNT(m.id) as total_messages FROM conversations c LEFT JOIN messages m ON c.id = m.conversation_id GROUP BY c.type `) return result.rows.reduce((stats, row) => { stats[row.type] = { count: parseInt(row.count), avgParticipants: Math.round(parseFloat(row.avg_participants)), totalMessages: parseInt(row.total_messages) } return stats }, {}) } catch (error) { console.error('Error getting conversation stats:', error) return this.getMockConversationStats() } } /** * Private helper methods */ private static async clearDatabase(): Promise { if (!this.dbClient) return const tables = ['messages', 'conversation_members', 'conversations', 'users'] for (const table of tables) { await this.dbClient.query(`DELETE FROM ${table}`) } console.log('๐Ÿ—‘๏ธ Cleared existing chat database data') } private static async seedUsers(users: User[]): Promise { if (!this.dbClient) return const query = ` INSERT INTO users ( id, username, email, display_name, avatar_url, is_active, last_seen, created_at, updated_at ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) ` for (const user of users) { await this.dbClient.query(query, [ user.id, user.username, user.email, user.displayName || `${user.firstName} ${user.lastName}`, user.avatar, user.status === 'active', user.lastSeenAt, user.createdAt, user.updatedAt ]) } } private static async seedConversations(conversations: Conversation[]): Promise { if (!this.dbClient) return const conversationQuery = ` INSERT INTO conversations ( id, name, description, conversation_type, is_private, created_by, created_at, updated_at ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) ` const memberQuery = ` INSERT INTO conversation_members (conversation_id, user_id, role, joined_at) VALUES ($1, $2, $3, $4) ` for (const conversation of conversations) { // Insert conversation await this.dbClient.query(conversationQuery, [ conversation.id, conversation.name, conversation.description, conversation.type, conversation.isPrivate, conversation.createdById, conversation.createdAt, conversation.updatedAt ]) // Insert participants for (const participantId of conversation.participantIds) { const role = participantId === conversation.createdById ? 'admin' : 'member' await this.dbClient.query(memberQuery, [ conversation.id, participantId, role, conversation.createdAt ]) } } } private static async seedMessages(messages: Message[]): Promise { if (!this.dbClient) return const query = ` INSERT INTO messages ( id, conversation_id, sender_id, content, message_type, parent_message_id, is_pinned, is_deleted, created_at, updated_at, status ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11) ` for (const message of messages) { await this.dbClient.query(query, [ message.id, message.conversationId, message.senderId, message.content, message.type, message.parentMessageId, false, // is_pinned message.isDeleted, message.createdAt, message.updatedAt, message.status ]) } } private static getMockConversationStats(): any { const stats = DataRelationManager.getStatistics() return { direct: { count: Math.floor(stats.conversations * 0.6), avgParticipants: 2, totalMessages: Math.floor(stats.messages * 0.4) }, group: { count: Math.floor(stats.conversations * 0.3), avgParticipants: 6, totalMessages: Math.floor(stats.messages * 0.4) }, channel: { count: Math.floor(stats.conversations * 0.1), avgParticipants: 25, totalMessages: Math.floor(stats.messages * 0.2) } } } /** * Cleanup methods */ static async cleanup(): Promise { if (this.dbClient) { await this.dbClient.end() this.dbClient = null } if (this.redisClient) { await this.redisClient.quit() this.redisClient = null } console.log('๐Ÿงน Chat server fixtures cleanup completed') } /** * Health check */ static async healthCheck(): Promise<{ database: boolean redis: boolean dataConsistency: boolean }> { let database = false let redis = false let dataConsistency = false // Check database connection if (this.dbClient) { try { await this.dbClient.query('SELECT 1') database = true } catch (error) { console.error('Database health check failed:', error) } } // Check Redis connection if (this.redisClient) { try { await this.redisClient.ping() redis = true } catch (error) { console.error('Redis health check failed:', error) } } // Check data consistency try { const validation = DataRelationManager.validateRelations() dataConsistency = validation.isValid if (!validation.isValid) { console.warn('Data consistency issues:', validation.errors) } } catch (error) { console.error('Data consistency check failed:', error) } return { database, redis, dataConsistency } } }