import { WebSocketGateway, WebSocketServer, SubscribeMessage, OnGatewayConnection, OnGatewayDisconnect, ConnectedSocket, MessageBody, } from '@nestjs/websockets'; import { Logger } from '@nestjs/common'; import { Server, Socket } from 'socket.io'; /** * ContentGen AI — Gerçek Zamanlı WebSocket Gateway * * Frontend'e render ilerleme bildirimlerini iletir. * C# Worker → RenderCallbackController → EventsGateway → Frontend * * Room yapısı: "project:{projectId}" — her proje kendi room'una abone olur */ @WebSocketGateway({ cors: { origin: ['http://localhost:3001', 'http://localhost:3000', process.env.FRONTEND_URL || '*'], credentials: true, }, namespace: '/ws', transports: ['websocket', 'polling'], }) export class EventsGateway implements OnGatewayConnection, OnGatewayDisconnect { @WebSocketServer() server: Server; private readonly logger = new Logger(EventsGateway.name); private connectedClients = 0; handleConnection(client: Socket) { this.connectedClients++; this.logger.log(`Client bağlandı: ${client.id} (toplam: ${this.connectedClients})`); } handleDisconnect(client: Socket) { this.connectedClients--; this.logger.log(`Client ayrıldı: ${client.id} (toplam: ${this.connectedClients})`); } /** * Frontend, proje detay sayfasına girdiğinde bu event'i gönderir. * Client ilgili proje room'una katılır. */ @SubscribeMessage('join:project') handleJoinProject( @ConnectedSocket() client: Socket, @MessageBody() data: { projectId: string }, ) { const room = `project:${data.projectId}`; client.join(room); this.logger.debug(`Client ${client.id} → room: ${room}`); return { event: 'joined', data: { room, projectId: data.projectId } }; } /** * Frontend, proje sayfasından ayrıldığında bu event'i gönderir. */ @SubscribeMessage('leave:project') handleLeaveProject( @ConnectedSocket() client: Socket, @MessageBody() data: { projectId: string }, ) { const room = `project:${data.projectId}`; client.leave(room); this.logger.debug(`Client ${client.id} ← room: ${room}`); return { event: 'left', data: { room } }; } /** * Kullanıcı kendi bildirim room'una katılır. * Frontend login sonrası bu event'i göndererek anlık bildirimleri alır. */ @SubscribeMessage('join:user') handleJoinUser( @ConnectedSocket() client: Socket, @MessageBody() data: { userId: string }, ) { const room = `user:${data.userId}`; client.join(room); this.logger.debug(`Client ${client.id} → user room: ${room}`); return { event: 'joined', data: { room, userId: data.userId } }; } /** * Kullanıcı bildirim room'undan ayrılır. */ @SubscribeMessage('leave:user') handleLeaveUser( @ConnectedSocket() client: Socket, @MessageBody() data: { userId: string }, ) { const room = `user:${data.userId}`; client.leave(room); this.logger.debug(`Client ${client.id} ← user room: ${room}`); return { event: 'left', data: { room } }; } // ═══════════════════════════════════════════════════════ // Server → Client Events (RenderCallbackController tarafından tetiklenir) // ═══════════════════════════════════════════════════════ /** * Render ilerleme bildirimi gönder. * Stage: 'tts' | 'image_generation' | 'music_generation' | 'compositing' | 'encoding' */ emitRenderProgress( projectId: string, payload: { progress: number; stage: string; stageLabel: string; currentScene?: number; totalScenes?: number; eta?: number; // Tahmini kalan saniye }, ) { this.server.to(`project:${projectId}`).emit('render:progress', { projectId, ...payload, timestamp: new Date().toISOString(), }); } /** * Render tamamlandı bildirimi. */ emitRenderCompleted( projectId: string, payload: { finalVideoUrl: string; thumbnailUrl?: string; processingTimeMs: number; fileSize: number; }, ) { this.server.to(`project:${projectId}`).emit('render:completed', { projectId, ...payload, timestamp: new Date().toISOString(), }); } /** * Render hatası bildirimi. */ emitRenderFailed( projectId: string, payload: { error: string; stage: string; attemptNumber: number; canRetry: boolean; }, ) { this.server.to(`project:${projectId}`).emit('render:failed', { projectId, ...payload, timestamp: new Date().toISOString(), }); } /** * Proje durum değişikliği bildirimi (status change). */ emitProjectStatusChanged(projectId: string, status: string) { this.server.to(`project:${projectId}`).emit('project:status', { projectId, status, timestamp: new Date().toISOString(), }); } /** * Kullanıcıya anlık bildirim gönder. * NotificationsService.createNotification() tarafından çağrılır. */ emitNotification( userId: string, payload: { id: string; type: string; title: string; message?: string | null; metadata?: unknown; isRead: boolean; createdAt: string; }, ) { this.server.to(`user:${userId}`).emit('notification:new', { ...payload, timestamp: new Date().toISOString(), }); } /** Bağlı client sayısını döndür (health check için) */ getConnectedClientsCount(): number { return this.connectedClients; } }