Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
18cd465
fix: category 필드 추가, 명세에 맞게 엔티티 DTO 수정
blu3fishez Nov 26, 2024
20f3b36
refactor: room_full 이벤트 핸들러 함수화
ShipFriend0516 Nov 26, 2024
2114c37
refactor: 로그문 삭제
blu3fishez Nov 26, 2024
e2e0a02
Merge remote-tracking branch 'upstream/fix/session-api-sepc' into ref…
ShipFriend0516 Nov 26, 2024
dca2e2b
fix: join 이벤트 emit 시 명세에 맞게 보내주도록 수정
blu3fishez Nov 26, 2024
09be264
refactor: 세션 생성 응답 형식 변경
ShipFriend0516 Nov 26, 2024
523bdd1
refactor: 세션 입장 데이터 타입 변경
ShipFriend0516 Nov 26, 2024
dad0226
refactor: 세션 헤더에서 타입을 import 하도록 수정
ShipFriend0516 Nov 26, 2024
d0cd585
feat: 카테고리 속성을 여러개 지원 가능하도록 수정
blu3fishez Nov 26, 2024
6ba45d8
Merge remote-tracking branch 'upstream/fix/session-api-sepc' into ref…
ShipFriend0516 Nov 26, 2024
4be9f21
fix: selected 대신 defaultValue를 사용하도록 수정
ShipFriend0516 Nov 26, 2024
a4fd881
fix: session 타입 스펠링 수정
ShipFriend0516 Nov 26, 2024
3582d75
refactor: 호스트 변경 이벤트 응답 형식 수정
ShipFriend0516 Nov 26, 2024
f5decb5
refactor: 세션 사이드바에서 퇴장 처리 이벤트 변경된 API에 맞게 수정
ShipFriend0516 Nov 26, 2024
e844e0d
feat: room dto 수정
blu3fishez Nov 26, 2024
f87697a
feat: 방 생성 시 빈방으로 생성하고 별도로 참가하도록 수정
blu3fishez Nov 26, 2024
aa11a9d
Merge remote-tracking branch 'upstream/fix/session-api-sepc' into ref…
ShipFriend0516 Nov 26, 2024
48c7427
feat: 로그인한 회원 input에 본인 닉네임 적혀있도록 구현
ShipFriend0516 Nov 27, 2024
34d81f0
refactor: 리액션기능 api 변경 대응
ShipFriend0516 Nov 27, 2024
16fb395
refactor: join room 이벤트 타입 수정
ShipFriend0516 Nov 27, 2024
762edd6
refactor: 세션 생성시 카테고리 배열로 변경
ShipFriend0516 Nov 27, 2024
ed347cb
chore: props 이름 변경
ShipFriend0516 Nov 27, 2024
e8ab2d8
refactor: webrtc peerconnection 수립과정 로깅 추가
ShipFriend0516 Nov 27, 2024
2dc7f4d
fix: 유저 join이 잘 되지 않던 오류 수정
ShipFriend0516 Nov 27, 2024
dc9bacf
fix: map 안정성 추가
ShipFriend0516 Nov 27, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 22 additions & 1 deletion backend/src/room/dto/create-room.dto.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
import { IsEnum, IsNotEmpty, IsNumber, Max, Min } from "class-validator";
import {
ArrayMaxSize,
ArrayMinSize,
IsArray,
IsEnum,
IsNotEmpty,
IsNumber,
Max,
Min,
} from "class-validator";
import { RoomStatus } from "@/room/room.entity";

export class CreateRoomDto {
Expand All @@ -13,6 +22,12 @@ export class CreateRoomDto {
@IsNotEmpty()
nickname: string;

@IsNotEmpty()
@IsArray()
@ArrayMaxSize(3)
@ArrayMinSize(1)
category: string[];

@IsNumber()
@Min(1)
@Max(5)
Expand All @@ -34,6 +49,12 @@ export class CreateRoomInternalDto {
@IsNotEmpty()
nickname: string;

@IsNotEmpty()
@IsArray()
@ArrayMaxSize(3)
@ArrayMinSize(1)
category: string[];

@IsNotEmpty()
socketId: string;

Expand Down
2 changes: 1 addition & 1 deletion backend/src/room/dto/reaction.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { IsNotEmpty } from "class-validator";

export class ReactionDto {
@IsNotEmpty()
socketId: string;
roomId: string;

@IsNotEmpty()
reactionType: string;
Expand Down
17 changes: 7 additions & 10 deletions backend/src/room/dto/room.dto.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,14 @@
import { RoomStatus } from "@/room/room.entity";

export interface Connection {
socketId: string;
createAt: number;
nickname: string;
}
import { Connection, RoomStatus } from "@/room/room.entity";

export interface RoomDto {
roomId: string;
id: string;
title: string;
category: string[];
inProgress: boolean;
host: Connection;
status: RoomStatus;
participants: number;
maxParticipants: number;
createdAt: number;
host: string;
connectionList: Connection[];
connectionMap: Record<string, Connection>;
}
8 changes: 6 additions & 2 deletions backend/src/room/room.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@ export class RoomController {
constructor(private readonly roomService: RoomService) {}

@Get()
public getPublicRoom() {
return this.roomService.getPublicRoom();
public async getPublicRoom() {
const rooms = await this.roomService.getPublicRoom();
return rooms.map((room) => ({
...room,
connectionMap: undefined,
}));
}
}
12 changes: 10 additions & 2 deletions backend/src/room/room.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,17 @@ export enum RoomStatus {
@Schema("room", {})
export class RoomEntity extends Entity {
@Field({ type: "string", indexed: true })
roomId: string;
id: string;

@Field({ type: "string" })
title: string;

@Field({ type: "string[]" })
category: string[];

@Field({ type: "boolean" })
inProgress: boolean;

@Field({ type: "string" })
status: RoomStatus;

Expand All @@ -28,9 +34,11 @@ export class RoomEntity extends Entity {
@Field({ type: "number", sortable: true })
createdAt: number;

// Connection
@Field({ type: "string" })
host: string;

// Record<string, Connection>
@Field({ type: "string" })
connectionList: string;
connectionMap: string;
}
9 changes: 7 additions & 2 deletions backend/src/room/room.gateway.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,13 @@ export class RoomGateway implements OnGatewayDisconnect {
@ConnectedSocket() client: Socket,
@MessageBody() dto: ReactionDto
) {
const room = await this.roomRepository.getRoom(dto.socketId);
const room = await this.roomRepository.getRoom(dto.roomId);

if (!room) return;
this.socketService.emitToRoom(room.roomId, EMIT_EVENT.REACTION, dto);

this.socketService.emitToRoom(room.id, EMIT_EVENT.REACTION, {
socketId: client.id,
reactionType: dto.reactionType,
});
}
}
53 changes: 33 additions & 20 deletions backend/src/room/room.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,51 +14,64 @@ export class RoomRepository {
// TODO : .from 메서드 구현 필요?
public async getAllRoom(): Promise<RoomDto[]> {
const allRooms = await this.roomRepository.search().return.all();
return allRooms.map((room: RoomEntity) => ({
connectionList: JSON.parse(room.connectionList),
createdAt: room.createdAt,
host: room.host,
maxParticipants: room.maxParticipants,
status: room.status,
title: room.title,
roomId: room.roomId,
}));
return allRooms.map((room: RoomEntity) => {
const connectionMap = JSON.parse(room.connectionMap || "{}");
return {
connectionMap: JSON.parse(room.connectionMap || "{}"),
createdAt: room.createdAt,
host: JSON.parse(room.host),
maxParticipants: room.maxParticipants,
status: room.status,
title: room.title,
id: room.id,
category: room.category,
inProgress: room.inProgress,
participants: Object.keys(connectionMap).length,
};
});
}

public async getRoom(id: string): Promise<RoomDto> {
const room = await this.roomRepository.search().where("roomId").eq(id).return.first();
const room = await this.roomRepository.search().where("id").eq(id).return.first();

if (!room) return null;

if (!room.roomId) return null;
const connectionMap = JSON.parse(room.connectionMap || "{}");

return {
connectionList: JSON.parse(room.connectionList),
category: room.category,
inProgress: room.inProgress,
connectionMap,
createdAt: room.createdAt,
host: room.host,
host: JSON.parse(room.host),
participants: Object.keys(connectionMap).length,
maxParticipants: room.maxParticipants,
status: room.status,
title: room.title,
roomId: room.roomId,
id: room.id,
};
}

public async setRoom(dto: RoomDto): Promise<void> {
const room = new RoomEntity();
room.roomId = dto.roomId;
room.id = dto.id;
room.category = dto.category;
room.inProgress = dto.inProgress;
room.title = dto.title;
room.status = dto.status;
room.connectionList = JSON.stringify(dto.connectionList);
room.connectionMap = JSON.stringify(dto.connectionMap);
room.maxParticipants = dto.maxParticipants;
room.createdAt = Date.now();
room.host = dto.host;
room.host = JSON.stringify(dto.host);

await this.roomRepository.save(room.roomId, room);
await this.roomRepository.save(room.id, room);
}

public async removeRoom(id: string): Promise<void> {
const entities = await this.roomRepository.search().where("roomId").equals(id).return.all();
const entities = await this.roomRepository.search().where("id").equals(id).return.all();

for await (const entity of entities) {
await this.roomRepository.remove(entity.roomId);
await this.roomRepository.remove(entity.id);
}
}
}
19 changes: 12 additions & 7 deletions backend/src/room/services/room-create.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,26 +21,31 @@ export class RoomCreateService {

public async createRoom(dto: CreateRoomInternalDto) {
const { socketId, nickname } = dto;
const roomId = await this.generateRoomId();
const id = await this.generateRoomId();
const socket = this.socketService.getSocket(socketId);
const currentTime = Date.now();
const questionListContents = await this.questionListRepository.getContentsByQuestionListId(
dto.questionListId
);

const roomDto = {
...dto,
roomId,
connectionList: [],
id,
inProgress: false,
connectionMap: {},
participants: 0,
questionListContents,
createdAt: currentTime,
host: dto.socketId,
host: {
socketId: dto.socketId,
nickname,
createAt: currentTime,
},
};

await this.roomRepository.setRoom(roomDto);

await this.roomJoinService.joinRoom({ roomId, socketId, nickname }, true);

this.socketService.emitToRoom(roomId, EMIT_EVENT.CREATE, roomDto);
socket.emit(EMIT_EVENT.CREATE, roomDto);
}

// TODO: 동시성 고려해봐야하지 않을까?
Expand Down
8 changes: 3 additions & 5 deletions backend/src/room/services/room-host.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export class RoomHostService {
public constructor(private readonly roomRepository: RoomRepository) {}

private getNewHost(room: RoomDto) {
return room.connectionList[0];
return Object.values(room.connectionMap).sort((a, b) => a.createAt - b.createAt)[0];
}

public async delegateHost(roomId: string) {
Expand All @@ -16,12 +16,10 @@ export class RoomHostService {

const newHost = this.getNewHost(room);

const found = room.connectionList.find(
(connection) => connection.socketId === newHost.socketId
);
const found = room.connectionMap[newHost.socketId];
if (!found) throw new Error("invalid new host id");

room.host = newHost.socketId;
room.host = newHost;

await this.roomRepository.setRoom(room);
return newHost;
Expand Down
15 changes: 9 additions & 6 deletions backend/src/room/services/room-join.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,33 +14,36 @@ export class RoomJoinService {
private readonly socketRepository: WebsocketRepository
) {}

public async joinRoom(dto: JoinRoomInternalDto, isCreate: boolean = false) {
public async joinRoom(dto: JoinRoomInternalDto) {
const { roomId, socketId, nickname } = dto;

const room = await this.roomRepository.getRoom(roomId);
const socket = this.socketService.getSocket(socketId);

if (!socket) throw new Error("Invalid Socket");

if (room.roomId === null) throw new Error("Redis: RoomEntity Entity type error");
if (!room) throw new Error("RoomEntity Not found");

if (this.isFullRoom(room)) return socket.emit(EMIT_EVENT.FULL, {});

socket.join(roomId);

await this.socketRepository.joinRoom(socket.id, roomId);
room.connectionList.push({

room.connectionMap[socketId] = {
socketId,
createAt: Date.now(),
nickname,
});
};

await this.roomRepository.setRoom(room);

room.connectionMap[socketId] = undefined;
// TODO: 성공 / 실패 여부를 전송하는데 있어서 결과에 따라 다르게 해야하는데.. 어떻게 관심 분리를 할까?
if (!isCreate) this.socketService.emitToRoom(roomId, EMIT_EVENT.JOIN, room);
socket.emit(EMIT_EVENT.JOIN, room);
}

private isFullRoom(room: RoomDto): boolean {
return room.maxParticipants <= room.connectionList.length;
return room.maxParticipants <= Object.keys(room.connectionMap).length;
}
}
16 changes: 7 additions & 9 deletions backend/src/room/services/room-leave.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,26 +27,24 @@ export class RoomLeaveService {
const room = await this.roomRepository.getRoom(roomId);
if (!room) return;

room.connectionList = room.connectionList.filter(
(connection) => connection.socketId !== socketId
);
delete room.connectionMap[socketId];

if (!room.connectionList.length) return this.deleteRoom(room.roomId);
if (!Object.keys(room.connectionMap).length) return this.deleteRoom(room.id);

await this.roomRepository.setRoom(room);

if (room.host === socketId) return this.handleHostChange(socketId, room);
if (room.host.socketId === socketId) return this.handleHostChange(socketId, room);

this.socketService.emitToRoom(room.roomId, EMIT_EVENT.QUIT, { socketId });
this.socketService.emitToRoom(room.id, EMIT_EVENT.QUIT, { socketId });
}

private async handleHostChange(socketId: string, room: RoomDto) {
if (room.host !== socketId) return;
if (room.host.socketId !== socketId) return;

const newHost = await this.roomHostService.delegateHost(room.roomId);
const newHost = await this.roomHostService.delegateHost(room.id);

// TODO : throw new Exception : host changed
this.socketService.emitToRoom(room.roomId, EMIT_EVENT.CHANGE_HOST, {
this.socketService.emitToRoom(room.id, EMIT_EVENT.CHANGE_HOST, {
nickname: newHost.nickname,
socketId: newHost.socketId,
});
Expand Down
6 changes: 1 addition & 5 deletions frontend/src/components/common/Select.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,7 @@ const Select = <T,>({
className={`rounded-custom-m ${backgroundColor} text-semibold-r text-gray-white appearance-none pl-4 pr-8 h-full`}
>
{options.map((option) => (
<option
key={option.value as string}
value={option.value as string}
selected={option.value === value}
>
<option key={option.value as string} value={option.value as string}>
{option.label}
</option>
))}
Expand Down
11 changes: 2 additions & 9 deletions frontend/src/components/session/SessionHeader.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,4 @@
type RoomStatus = "PUBLIC" | "PRIVATE";
interface RoomMetadata {
title: string;
status: RoomStatus;
maxParticipants: number;
createdAt: number;
host: string;
}
import { RoomMetadata } from "@hooks/type/session";

interface SessionHeaderProps {
roomMetadata: RoomMetadata | null;
Expand All @@ -18,7 +11,7 @@ const SessionHeader = ({
}: SessionHeaderProps) => {
return (
<h1 className={"text-center text-medium-xl font-bold w-full pt-4 pb-2"}>
{roomMetadata?.title}{" "}
{roomMetadata?.category} {roomMetadata?.title}{" "}
<span className={"font-light"}>
{" "}
{roomMetadata &&
Expand Down
Loading