Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 3 additions & 2 deletions backend/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,11 @@ import { TypeOrmModule } from "@nestjs/typeorm";

import "dotenv/config";

import { createDataSource, typeOrmConfig } from "./config/typeorm.config";
import { createDataSource, typeOrmConfig } from "./infra/typeorm/typeorm.config";
import { QuestionListModule } from "./question-list/question-list.module";
import { RedisOmModule } from "@moozeh/nestjs-redis-om";
import { SigServerModule } from "@/signaling-server/sig-server.module";
import { redisConfig } from "@/infra/infra.config";

@Module({
imports: [
Expand All @@ -22,7 +23,7 @@ import { SigServerModule } from "@/signaling-server/sig-server.module";
dataSourceFactory: async () => await createDataSource(), // 분리된 데이터소스 생성 함수 사용
}),
RedisOmModule.forRoot({
url: `redis://${process.env.REDIS_HOST}:${process.env.REDIS_PORT}`,
url: `redis://${redisConfig.host}:${redisConfig.port}`,
}),
RoomModule,
AuthModule,
Expand Down
2 changes: 1 addition & 1 deletion backend/src/auth/auth.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
} from "@nestjs/common";
import { AuthGuard } from "@nestjs/passport";
import { Response } from "express";
import { setCookieConfig } from "@/config/cookie.config";
import { setCookieConfig } from "@/infra/cookie/cookie.config";
import { JwtPayload, JwtTokenPair } from "./jwt/jwt.decorator";
import { IJwtPayload, IJwtToken, IJwtTokenPair } from "./jwt/jwt.model";

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import "dotenv/config";

export const gatewayConfig = {
cors: {
origin: process.env.DOMAIN || "*",
},
};

export const redisConfig = {
host: process.env.REDIS_HOST,
port: parseInt(process.env.REDIS_PORT),
Expand Down
14 changes: 14 additions & 0 deletions backend/src/infra/infra.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { Global, Module } from "@nestjs/common";
import { InfraService } from "@/infra/infra.service";
import { WebsocketRepository } from "@/infra/websocket/websocket.repository";
import { RedisOmModule } from "@moozeh/nestjs-redis-om";
import { RoomEntity } from "@/room/room.entity";
import { WebsocketEntity } from "@/infra/websocket/websocket.entity";

@Global()
@Module({
imports: [RedisOmModule.forFeature([RoomEntity, WebsocketEntity])],
providers: [InfraService, WebsocketRepository],
exports: [InfraService, WebsocketRepository],
})
export class InfraModule {}
52 changes: 52 additions & 0 deletions backend/src/infra/infra.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { Injectable } from "@nestjs/common";
import { Server, Socket } from "socket.io";
import Redis from "ioredis";
import { redisClient } from "@/infra/redis/redis-client";
import { WebsocketRepository } from "@/infra/websocket/websocket.repository";

@Injectable()
export class InfraService {
private server: Server;
private readonly redisClient: Redis = redisClient;

constructor(private readonly websocketRepository: WebsocketRepository) {}

public setServer(server: Server) {
this.server = server;
}

public async createSocketMetadata(socket: Socket) {
return this.websocketRepository.createWebsocketMetadata(socket);
}

public async getSocketMetadata(socket: Socket) {
return this.websocketRepository.getWebsocketMetadataById(socket.id);
}

public async removeSocketMetadata(socket: Socket) {
await this.websocketRepository.removeWebsocketMetadata(socket);
}

public async joinRoom(socket: Socket, roomId: string) {
const entity = await this.websocketRepository.getWebsocketMetadataById(socket.id);
entity.joinedRooms.push(roomId);
await this.websocketRepository.updateWebsocketMetadata(socket.id, entity);
socket.join(roomId);
}

public getRedisClient() {
return this.redisClient;
}

public getServer() {
return this.server;
}

public getSocket(socketId: string) {
return this.server.sockets.sockets.get(socketId);
}

public emitToRoom(roomId: string, event: string, data?: any) {
this.server.to(roomId).emit(event, data);
}
}
3 changes: 3 additions & 0 deletions backend/src/infra/redis/redis-client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import Redis from "ioredis";

export const redisClient = new Redis();
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Injectable } from "@nestjs/common";
import { Socket } from "socket.io";
import { InjectRepository } from "@moozeh/nestjs-redis-om";
import { Repository } from "redis-om";
import { WebsocketEntity } from "@/websocket/websocket.entity";
import { WebsocketEntity } from "@/infra/websocket/websocket.entity";

@Injectable()
export class WebsocketRepository {
Expand All @@ -11,19 +11,18 @@ export class WebsocketRepository {
private readonly socketRepository: Repository<WebsocketEntity>
) {}

public async register(socket: Socket) {
public async createWebsocketMetadata(socket: Socket) {
const entity = new WebsocketEntity();
entity.id = socket.id;
entity.joinedRooms = [];
await this.socketRepository.remove(entity.id);
await this.socketRepository.save(entity.id, entity);
return this.socketRepository.save(entity.id, entity);
}

public async getRoomOfSocket(id: string) {
public async getWebsocketMetadataById(id: string) {
return this.socketRepository.search().where("id").eq(id).return.first();
}

public async clean(socket: Socket) {
public async removeWebsocketMetadata(socket: Socket) {
const entities = await this.socketRepository
.search()
.where("id")
Expand All @@ -35,9 +34,7 @@ export class WebsocketRepository {
}
}

public async joinRoom(socketId: string, roomId: string) {
const entity = await this.socketRepository.search().where("id").eq(socketId).return.first();
entity.joinedRooms.push(roomId);
await this.socketRepository.save(entity.id, entity);
public async updateWebsocketMetadata(socketId: string, entity: WebsocketEntity) {
return this.socketRepository.save(entity.id, entity);
}
}
115 changes: 115 additions & 0 deletions backend/src/room/domain/room.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import { RoomEntity } from "@/room/room.entity";

export interface Connection {
socketId: string;
nickname: string;
createAt: number;
}

export enum RoomStatus {
PUBLIC = "PUBLIC",
PRIVATE = "PRIVATE",
}

export interface IRoom {
id: string;
title: string;
category: string[];
inProgress: boolean;
currentIndex: number;
host: Connection;
status: RoomStatus;
maxParticipants: number;
maxQuestionListLength: number;
questionListId: number;
questionListTitle: string;
createdAt: number;
connectionMap: Record<string, Connection>;
}

export class Room {
public entity: RoomEntity;

constructor(entity?: IRoom) {
if (!entity) {
this.entity = null;
return this;
}
this.entity = new RoomEntity();
this.entity.id = entity.id;
this.entity.status = entity.status;
this.setConnection(entity.connectionMap);
this.entity.title = entity.title;
this.entity.createdAt = entity.createdAt;
this.entity.questionListId = entity.questionListId;
this.entity.questionListTitle = entity.questionListTitle;
this.entity.inProgress = entity.inProgress;
this.entity.maxParticipants = entity.maxParticipants;
this.entity.currentIndex = entity.currentIndex;
this.entity.category = entity.category;
this.entity.maxQuestionListLength = entity.maxQuestionListLength;
this.entity.host = JSON.stringify(entity.host);
}

static fromEntity(entity: RoomEntity) {
const room = new Room();
room.entity = entity;
return room;
}

build() {
return this.entity;
}

toObject(): IRoom {
return {
id: this.entity.id,
status: this.entity.status,
connectionMap: this.getConnection(),
title: this.entity.title,
createdAt: this.entity.createdAt,
questionListId: this.entity.questionListId,
questionListTitle: this.entity.questionListTitle,
inProgress: this.entity.inProgress,
maxParticipants: this.entity.maxParticipants,
currentIndex: this.entity.currentIndex,
category: this.entity.category,
maxQuestionListLength: this.entity.maxQuestionListLength,
host: this.getHost(),
};
}

setHost(connection: Connection) {
this.entity.host = JSON.stringify(connection);
return this;
}

getHost(): Connection {
return JSON.parse(this.entity.host);
}

setConnection(connectionMap: Record<string, Connection>) {
this.entity.connectionMap = JSON.stringify(connectionMap);
return this;
}

getConnection(): Record<string, Connection> {
return JSON.parse(this.entity.connectionMap);
}

getParticipants(): number {
return Object.keys(this.getConnection()).length;
}

addConnection(connection: Connection) {
const connectionMap = this.getConnection();
connectionMap[connection.socketId] = connection;
return this.setConnection(connectionMap);
}

removeConnection(socketId: string) {
const connectionMap = this.getConnection();
delete connectionMap[socketId];
return this.setConnection(connectionMap);
}
}
14 changes: 14 additions & 0 deletions backend/src/room/dto/all-room.dto.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// DTO 클래스 정의
import { Transform } from "class-transformer";
import { IsBoolean, IsOptional } from "class-validator";
import { Connection, RoomStatus } from "@/room/domain/room";

export class AllRoomQueryParamDto {
@IsOptional()
Expand All @@ -12,3 +13,16 @@ export class AllRoomQueryParamDto {
@IsBoolean()
inProgress: boolean;
}

export class RoomListResponseDto {
createdAt: number;
host: Connection;
maxParticipants: number;
status: RoomStatus;
title: string;
id: string;
category: string[];
inProgress: boolean;
questionListTitle: string;
participants: number;
}
48 changes: 20 additions & 28 deletions backend/src/room/dto/create-room.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,14 @@ import {
Max,
Min,
} from "class-validator";
import { RoomStatus } from "@/room/room.entity";
import { Connection, RoomStatus } from "@/room/domain/room";
import { Question } from "@/question-list/entity/question.entity";

export class CreateRoomDto {
private static MAX_CATEGORY_SIZE = 3;
private static MIN_CATEGORY_SIZE = 1;
private static MAX_PARTICIPANT_SIZE = 5;
private static MIN_PARTICIPANT_SIZE = 1;
@IsNotEmpty()
title: string;

Expand All @@ -24,45 +29,32 @@ export class CreateRoomDto {

@IsNotEmpty()
@IsArray()
@ArrayMaxSize(3)
@ArrayMinSize(1)
@ArrayMaxSize(CreateRoomDto.MAX_CATEGORY_SIZE)
@ArrayMinSize(CreateRoomDto.MIN_CATEGORY_SIZE)
category: string[];

@IsNumber()
@Min(1)
@Max(5)
@Min(CreateRoomDto.MIN_PARTICIPANT_SIZE)
@Max(CreateRoomDto.MAX_PARTICIPANT_SIZE)
maxParticipants: number;

@IsNumber()
questionListId: number;
}

export class CreateRoomInternalDto {
@IsNotEmpty()
export interface CreateRoomResponseDto {
title: string;

@IsEnum(RoomStatus, {
message: "Status must be either PUBLIC or PRIVATE",
})
status: RoomStatus;

@IsNotEmpty()
nickname: string;

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

@IsNotEmpty()
socketId: string;

@IsNumber()
@Min(1)
@Max(5)
maxParticipants: number;

@IsNumber()
category: string[];
questionListId: number;
socketId: string;
id: string;
inProgress: boolean;
connectionMap: Record<string, Connection>;
participants: number;
questionListContents: Question[];
createdAt: number;
host: Connection;
}
7 changes: 7 additions & 0 deletions backend/src/room/dto/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export * from "./create-room.dto";
export * from "./finish-room.dto";
export * from "./join-room.dto";
export * from "./move-index.dto";
export * from "./reaction.dto";
export * from "./room-id.dto";
export * from "./all-room.dto";
Loading