Skip to content

Commit

Permalink
Merge pull request #206 from boostcampwm2023/feat/socket-service
Browse files Browse the repository at this point in the history
  • Loading branch information
Daehyun Kim authored Dec 11, 2023
2 parents f967213 + 32c76b7 commit 034c40f
Show file tree
Hide file tree
Showing 10 changed files with 206 additions and 134 deletions.
7 changes: 0 additions & 7 deletions server/src/entities/room.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,6 @@ export default class Room extends BaseEntity {
})
endAt?: Date;

@Column({
type: 'timestamp',
nullable: true,
comment: 'duration of the room',
})
duration?: Date;

@Column({
type: 'boolean',
})
Expand Down
2 changes: 1 addition & 1 deletion server/src/entities/user.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export default class User extends BaseEntity {
id!: number;

@Column({ comment: 'OAuth provider string id' })
username?: string;
username!: string;

@Column({ comment: 'OAuth provider' })
provider!: string;
Expand Down
8 changes: 7 additions & 1 deletion server/src/room/room.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,15 @@ import { RoomUserModule } from 'src/roomUser/room.user.module';
import { UserModule } from 'src/user/user.module';
import { RoomController } from './room.controller';
import { RoomService } from './room.service';
import { SocketModule } from '../socket/socket.module';

@Module({
imports: [UserModule, RoomUserModule, TypeOrmModule.forFeature([Room])],
imports: [
UserModule,
RoomUserModule,
TypeOrmModule.forFeature([Room]),
SocketModule,
],
controllers: [RoomController],
providers: [RoomService],
exports: [RoomService, TypeOrmModule],
Expand Down
13 changes: 8 additions & 5 deletions server/src/room/room.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { RoomUserService } from 'src/roomUser/room.user.service';
import { UserService } from 'src/user/user.service';
import { Repository } from 'typeorm';
import RoomUser from '../entities/roomUser.entity';
import { SocketService } from '../socket/socket.service';

@Injectable()
export class RoomService {
Expand All @@ -24,6 +25,7 @@ export class RoomService {
private readonly roomUserRepository: Repository<RoomUser>,
@InjectRepository(Room)
private readonly roomRepository: Repository<Room>,
private readonly socketService: SocketService,
) {}

/**
Expand All @@ -49,6 +51,7 @@ export class RoomService {
this.logger.log(`room ${code} successfully created by ${user.username}!`);

await this.roomUserService.create({ room, user });
this.socketService.notifyCreatingRoom(user.username, room.code);
return room;
}

Expand Down Expand Up @@ -77,7 +80,8 @@ export class RoomService {
throw new InternalServerErrorException('방을 찾을 수 없습니다.');
}
this.logger.debug(`user ${user.username} joining room ${room.code}...`);
return this.roomUserRepository.create({ room, user }).save();
await this.roomUserRepository.create({ room, user }).save();
this.socketService.notifyJoiningRoom(user.username, roomCode);
}

async destroyRoom(room: Room) {
Expand Down Expand Up @@ -105,11 +109,10 @@ export class RoomService {
throw new InternalServerErrorException('방을 찾을 수 없습니다.');
}

const numberOfJoinedUsers = await this.roomUserRepository.count({
where: { room: { id: room.id } },
});
await this.socketService.notifyExit(user.username, room);

if (numberOfJoinedUsers === 0) {
const numberOfJoinedUsers = (await room.joinedUsers)?.length;
if (numberOfJoinedUsers == null || numberOfJoinedUsers === 0) {
await this.destroyRoom(room);
}
}
Expand Down
157 changes: 44 additions & 113 deletions server/src/socket/socket.gateway.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,26 +10,14 @@ import {
} from '@nestjs/websockets';
import { Server, Socket } from 'socket.io';
import { ChatEvent, MessageInterface } from '../types/MessageInterface';
import { Logger, UseFilters, UseGuards } from '@nestjs/common';
import { Logger, UseFilters } from '@nestjs/common';
import { UserService } from '../user/user.service';
import User from '../entities/user.entity';
import { SessionAuthGuard } from 'src/auth/auth.guard';
import { WebsocketExceptionsFilter } from './socket.filter';
import { Status } from '../const/bojResults';
import Room from 'src/entities/room.entity';

// export type Problem = {
// bojProblemId: string;
// title: string;
// level: number;
// };

export type RoomInfo = {
participantNames: string[];
problems: string[];
isStarted: boolean;
endTime: string;
};
import * as util from 'util';
import { RoomInfo } from 'src/types/RoomInfo';
import { SocketService } from './socket.service';

@WebSocketGateway({
cors: {
Expand All @@ -50,9 +38,16 @@ export class SocketGateway implements OnGatewayConnection, OnGatewayDisconnect {

private readonly logger = new Logger(SocketGateway.name);

constructor(private readonly userService: UserService) {}
constructor(
private readonly userService: UserService,
private readonly socketService: SocketService,
) {}

afterInit(server: Server) {
this.logger.log('socket gateway initialized');
this.socketService.setServer(server);
}

@UseGuards(SessionAuthGuard)
async handleConnection(@ConnectedSocket() client: Socket) {
try {
const user = this.getUser(client);
Expand All @@ -64,14 +59,16 @@ export class SocketGateway implements OnGatewayConnection, OnGatewayDisconnect {
client.join(roomCode);

const message: Partial<MessageInterface> = {
username: 'system',
body: `${user.username}님이 입장하셨습니다.`,
username: user.username,
body: `님께서 연결되었습니다.`,
timestamp: Date.now(),
chatEvent: ChatEvent.Join,
};
this.server.to(roomCode).emit('chat-message', message);

const roomInfo: RoomInfo = await this.makeRoomInfo(joinedRoom.room);
const roomInfo: RoomInfo = await this.socketService.makeRoomInfo(
joinedRoom.room,
);

this.server.to(roomCode).emit('room-info', roomInfo);
} catch (e) {
Expand All @@ -81,66 +78,45 @@ export class SocketGateway implements OnGatewayConnection, OnGatewayDisconnect {
}
}

async makeRoomInfo(room: Room) {
const roomUsers = await room.joinedUsers;

if (roomUsers == null) throw new WsException('roomUsers is null');

const host = await room.host;
if (host == null) throw new WsException('host is null');

const problems = await room.problems;
if (problems == null) throw new WsException('problems is null');

if (problems.length === 0) {
this.logger.debug(`no problems in room ${room.code}`);
}

if (this.server == null) throw new WsException('server is null');

const roomInfo: RoomInfo = {
participantNames: roomUsers.map(
(roomUser) => roomUser.user?.username || '',
),
problems: problems.map((problem) => problem.bojProblemId.toString()),
isStarted: room.isStarted,
endTime: room.endAt?.toISOString() ?? '',
};

return roomInfo;
}

@SubscribeMessage('game-start')
async handleGameStart(@ConnectedSocket() client: Socket) {
async handleGameStart(
@ConnectedSocket() client: Socket,
@MessageBody() roomInfo: RoomInfo,
) {
const user = this.getUser(client);

const joinedRoom = await this.userService.getJoinedRoom(user);

const room: Room = joinedRoom.room;
const host = await joinedRoom.room.host;

if (host == null) throw new WsException('host is null');
if (host.id !== user.id) {
throw new WsException('방장이 아닙니다.');
}

const roomCode = joinedRoom.room.code;
this.logger.debug(`--> ws: game-start from ${user.username}`);

const roomCode = joinedRoom.room.code;

const message = {
username: 'system',
body: `${user.username}님이 게임을 시작하셨습니다.`,
username: user.username,
body: `님이 게임을 시작하셨습니다.`,
timestamp: Date.now(),
chatEvent: ChatEvent.Message,
};
this.logger.debug(`<-- ws: chat-message ${util.inspect(message)}`);
this.server.to(roomCode).emit('chat-message', message);

const room = joinedRoom.room;
if (roomInfo.duration == null) throw new WsException('duration is null');
const now = new Date();
const endTime = new Date(now.getTime() + roomInfo.duration * 60 * 1000);
room.endAt = endTime;
room.isStarted = true;
room.save();
await room.save();

this.server
.to(roomCode)
.emit('room-info', await this.makeRoomInfo(joinedRoom.room));
const roomInfoResponse = await this.socketService.makeRoomInfo(room);
this.logger.debug(`<-- ws: room-info ${util.inspect(roomInfoResponse)}`);
this.server.to(roomCode).emit('room-info', roomInfoResponse);
}

@SubscribeMessage('chat-message')
Expand Down Expand Up @@ -170,67 +146,22 @@ export class SocketGateway implements OnGatewayConnection, OnGatewayDisconnect {
return user as User;
}

async submitCode(username: string, roomCode: string, problemId: string) {
const message: MessageInterface = {
username: 'system',
body: `${username}님이 ${problemId} 문제를 제출하셨습니다.`,
timestamp: Date.now(),
chatEvent: ChatEvent.Submit,
color: 'green',
};
this.server.to(roomCode).emit('chat-message', message);
}

async notifySubmissionStatus(
username: string,
roomCode: string,
problemId: string,
status: Status,
) {
let message: MessageInterface;

switch (status) {
case Status.ACCEPTED:
message = {
username: 'system',
body: `${username}님이 ${problemId} 문제를 맞았습니다.`,
timestamp: Date.now(),
chatEvent: ChatEvent.Accepted,
color: 'green',
};
break;
case Status.WAITING:
throw new WsException('status가 WAITING일 수 없습니다.');
case Status.WRONG:
message = {
username: 'system',
body: `${username}님이 ${problemId}를 틀렸습니다.`,
timestamp: Date.now(),
chatEvent: ChatEvent.Message,
color: 'red',
};
break;
default:
throw new WsException('status가 알 수 없는 값입니다.');
}

this.server.to(roomCode).emit('chat-message', message);
}

async handleDisconnect(@ConnectedSocket() client: Socket) {
try {
const user = this.getUser(client);
const message: Partial<MessageInterface> = {
username: 'system',
body: `${user.username}님이 퇴장하셨습니다.`,
timestamp: Date.now(),
chatEvent: ChatEvent.Leave,
};

const joinedRoom = await this.userService.getJoinedRoom(user);
const roomCode = joinedRoom.room.code;
this.logger.log(
`client ${user.username} leaving room ${roomCode} and disconnecting...`,
);

const message: Partial<MessageInterface> = {
username: user.username,
body: `님의 연결이 끊어졌습니다.`,
timestamp: Date.now(),
chatEvent: ChatEvent.Leave,
};
this.server.to(roomCode).emit('chat-message', message);
} catch (e) {
if (e instanceof WsException) {
Expand Down
5 changes: 3 additions & 2 deletions server/src/socket/socket.module.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { Module } from '@nestjs/common';
import { SocketGateway } from './socket.gateway';
import { UserModule } from '../user/user.module';
import { SocketService } from './socket.service';

@Module({
providers: [SocketGateway],
providers: [SocketGateway, SocketService],
imports: [UserModule],
exports: [SocketGateway],
exports: [SocketService],
})
export class SocketModule {}
Loading

0 comments on commit 034c40f

Please sign in to comment.