import { WebSocketGateway, WebSocketServer, SubscribeMessage, OnGatewayConnection, OnGatewayDisconnect, OnGatewayInit, } from '@nestjs/websockets'; import { Server, Socket } from 'socket.io'; import { RoomsService } from '../rooms/rooms.service'; import { RoomEventsService } from './room-events.service'; import { PrismaService } from '../prisma/prisma.service'; import { RoomPackService } from '../room-pack/room-pack.service'; @WebSocketGateway({ cors: { // Примечание: декоратор выполняется на этапе инициализации, // ConfigModule.forRoot() уже загружает переменные в process.env origin: process.env.CORS_ORIGIN || 'http://localhost:5173', credentials: true, }, }) export class GameGateway implements OnGatewayConnection, OnGatewayDisconnect, OnGatewayInit { @WebSocketServer() server: Server; constructor( private roomsService: RoomsService, private roomEventsService: RoomEventsService, private prisma: PrismaService, private roomPackService: RoomPackService, ) {} afterInit(server: Server) { this.roomEventsService.setServer(server); } handleConnection(client: Socket) { console.log(`Client connected: ${client.id}`); } handleDisconnect(client: Socket) { console.log(`Client disconnected: ${client.id}`); } private async isHost(roomId: string, userId: string): Promise { const room = await this.prisma.room.findUnique({ where: { id: roomId }, select: { hostId: true }, }); return room?.hostId === userId; } @SubscribeMessage('joinRoom') async handleJoinRoom(client: Socket, payload: { roomCode: string; userId: string }) { client.join(payload.roomCode); const room = await this.roomsService.getRoomByCode(payload.roomCode); this.server.to(payload.roomCode).emit('roomUpdate', room); } @SubscribeMessage('startGame') async handleStartGame(client: Socket, payload: { roomId: string; roomCode: string; userId: string }) { const isHost = await this.isHost(payload.roomId, payload.userId); if (!isHost) { client.emit('error', { message: 'Only the host can start the game' }); return; } await this.roomsService.updateRoomStatus(payload.roomId, 'PLAYING'); const room = await this.roomsService.getRoomByCode(payload.roomCode); if (room) { this.server.to(room.code).emit('gameStarted', room); } } @SubscribeMessage('revealAnswer') async handleRevealAnswer(client: Socket, payload: { roomCode: string; answerIndex: number; userId: string; roomId: string }) { const isHost = await this.isHost(payload.roomId, payload.userId); if (!isHost) { client.emit('error', { message: 'Only the host can reveal answers' }); return; } this.server.to(payload.roomCode).emit('answerRevealed', payload); } @SubscribeMessage('updateScore') async handleUpdateScore(client: Socket, payload: { participantId: string; score: number; roomCode: string; userId: string; roomId: string }) { const isHost = await this.isHost(payload.roomId, payload.userId); if (!isHost) { client.emit('error', { message: 'Only the host can update scores' }); return; } await this.roomsService.updateParticipantScore(payload.participantId, payload.score); this.server.to(payload.roomCode).emit('scoreUpdated', payload); } @SubscribeMessage('nextQuestion') async handleNextQuestion(client: Socket, payload: { roomCode: string; userId: string; roomId: string }) { const isHost = await this.isHost(payload.roomId, payload.userId); if (!isHost) { client.emit('error', { message: 'Only the host can change questions' }); return; } this.server.to(payload.roomCode).emit('questionChanged', payload); } @SubscribeMessage('endGame') async handleEndGame(client: Socket, payload: { roomId: string; roomCode: string; userId: string }) { const isHost = await this.isHost(payload.roomId, payload.userId); if (!isHost) { client.emit('error', { message: 'Only the host can end the game' }); return; } await this.roomsService.updateRoomStatus(payload.roomId, 'FINISHED'); this.server.to(payload.roomCode).emit('gameEnded', payload); } @SubscribeMessage('setCurrentPlayer') async handleSetCurrentPlayer(client: Socket, payload: { roomId: string; roomCode: string; userId: string; playerId: string }) { const isHost = await this.isHost(payload.roomId, payload.userId); if (!isHost) { client.emit('error', { message: 'Only the host can select the current player' }); return; } await this.prisma.room.update({ where: { id: payload.roomId }, data: { currentPlayerId: payload.playerId }, }); this.server.to(payload.roomCode).emit('currentPlayerChanged', { playerId: payload.playerId }); } @SubscribeMessage('updateRoomSettings') async handleUpdateRoomSettings(client: Socket, payload: { roomId: string; roomCode: string; userId: string; settings: any }) { const isHost = await this.isHost(payload.roomId, payload.userId); if (!isHost) { client.emit('error', { message: 'Only the host can update room settings' }); return; } await this.prisma.room.update({ where: { id: payload.roomId }, data: payload.settings, }); const room = await this.roomsService.getRoomByCode(payload.roomCode); this.server.to(payload.roomCode).emit('roomUpdate', room); } @SubscribeMessage('restartGame') async handleRestartGame(client: Socket, payload: { roomId: string; roomCode: string; userId: string }) { const isHost = await this.isHost(payload.roomId, payload.userId); if (!isHost) { client.emit('error', { message: 'Only the host can restart the game' }); return; } await this.prisma.room.update({ where: { id: payload.roomId }, data: { status: 'WAITING', currentQuestionIndex: 0, revealedAnswers: {}, currentPlayerId: null, isGameOver: false, answeredQuestions: 0, }, }); await this.prisma.participant.updateMany({ where: { roomId: payload.roomId }, data: { score: 0 }, }); const room = await this.roomsService.getRoomByCode(payload.roomCode); this.server.to(payload.roomCode).emit('gameRestarted', room); } @SubscribeMessage('updateCustomQuestions') async handleUpdateCustomQuestions(client: Socket, payload: { roomId: string; roomCode: string; userId: string; questions: any }) { // DEPRECATED: Use updateRoomPack instead return this.handleUpdateRoomPack(client, payload); } @SubscribeMessage('updateRoomPack') async handleUpdateRoomPack(client: Socket, payload: { roomId: string; roomCode: string; userId: string; questions: any[] }) { const isHost = await this.isHost(payload.roomId, payload.userId); if (!isHost) { client.emit('error', { message: 'Only the host can update questions' }); return; } const room = await this.roomsService.updateRoomPack(payload.roomId, payload.questions); this.server.to(payload.roomCode).emit('roomPackUpdated', room); } @SubscribeMessage('importQuestions') async handleImportQuestions(client: Socket, payload: { roomId: string; roomCode: string; userId: string; sourcePackId: string; questionIndices: number[]; }) { const isHost = await this.isHost(payload.roomId, payload.userId); if (!isHost) { client.emit('error', { message: 'Only the host can import questions' }); return; } await this.roomPackService.importQuestions(payload.roomId, payload.sourcePackId, payload.questionIndices); const room = await this.roomsService.getRoomByCode(payload.roomCode); this.server.to(payload.roomCode).emit('roomPackUpdated', room); } @SubscribeMessage('kickPlayer') async handleKickPlayer(client: Socket, payload: { roomId: string; roomCode: string; userId: string; participantId: string }) { const isHost = await this.isHost(payload.roomId, payload.userId); if (!isHost) { client.emit('error', { message: 'Only the host can kick players' }); return; } await this.prisma.participant.update({ where: { id: payload.participantId }, data: { isActive: false }, }); const room = await this.roomsService.getRoomByCode(payload.roomCode); this.server.to(payload.roomCode).emit('playerKicked', { participantId: payload.participantId, room }); } }