From 3dac70767d1bb8ce29426a3beb084fcedfb5caa1 Mon Sep 17 00:00:00 2001 From: twalla26 Date: Tue, 12 Nov 2024 22:21:18 +0900 Subject: [PATCH 1/4] =?UTF-8?q?feat:=20redis=20=EC=84=A4=EC=B9=98=20?= =?UTF-8?q?=EB=B0=8F=20=EC=97=B0=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/package.json | 2 + backend/src/config/redis.config.ts | 5 ++ backend/src/redis/redis.module.ts | 7 +++ backend/src/redis/redis.service.spec.ts | 18 ++++++ backend/src/redis/redis.service.ts | 19 ++++++ pnpm-lock.yaml | 80 ++++++++++++++++++++++++- 6 files changed, 129 insertions(+), 2 deletions(-) create mode 100644 backend/src/config/redis.config.ts create mode 100644 backend/src/redis/redis.module.ts create mode 100644 backend/src/redis/redis.service.spec.ts create mode 100644 backend/src/redis/redis.service.ts diff --git a/backend/package.json b/backend/package.json index 11b922ba..a3918ab8 100644 --- a/backend/package.json +++ b/backend/package.json @@ -25,6 +25,8 @@ "@nestjs/platform-express": "^10.0.0", "@nestjs/platform-socket.io": "^10.4.6", "@nestjs/websockets": "^10.4.6", + "dotenv": "^16.4.5", + "ioredis": "^5.4.1", "reflect-metadata": "^0.2.0", "rxjs": "^7.8.1", "socket.io": "^4.8.1" diff --git a/backend/src/config/redis.config.ts b/backend/src/config/redis.config.ts new file mode 100644 index 00000000..830c9435 --- /dev/null +++ b/backend/src/config/redis.config.ts @@ -0,0 +1,5 @@ +export const redisConfig = { + host: process.env.REDIS_HOST, + port: Number(process.env.REDIS_PORT), + password: process.env.REDIS_PASSWORD, +}; diff --git a/backend/src/redis/redis.module.ts b/backend/src/redis/redis.module.ts new file mode 100644 index 00000000..77cec8eb --- /dev/null +++ b/backend/src/redis/redis.module.ts @@ -0,0 +1,7 @@ +import { Module } from "@nestjs/common"; +import { RedisService } from "./redis.service"; + +@Module({ + providers: [RedisService], +}) +export class RedisModule {} diff --git a/backend/src/redis/redis.service.spec.ts b/backend/src/redis/redis.service.spec.ts new file mode 100644 index 00000000..19faa0c1 --- /dev/null +++ b/backend/src/redis/redis.service.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from "@nestjs/testing"; +import { RedisService } from "./redis.service"; + +describe("RedisService", () => { + let service: RedisService; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [RedisService], + }).compile(); + + service = module.get(RedisService); + }); + + it("should be defined", () => { + expect(service).toBeDefined(); + }); +}); diff --git a/backend/src/redis/redis.service.ts b/backend/src/redis/redis.service.ts new file mode 100644 index 00000000..d07f05b5 --- /dev/null +++ b/backend/src/redis/redis.service.ts @@ -0,0 +1,19 @@ +import { Injectable } from "@nestjs/common"; +import Redis from "ioredis"; +import { redisConfig } from "../config/redis.config"; + +@Injectable() +export class RedisService { + private readonly client: Redis; + + constructor() { + this.client = new Redis({ + host: redisConfig.host, + port: redisConfig.port, + password: redisConfig.password, + }); + } + getClient(): Redis { + return this.client; + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7fa45d0e..3cab81e6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -35,6 +35,12 @@ importers: '@nestjs/websockets': specifier: ^10.4.6 version: 10.4.7(@nestjs/common@10.4.6(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.6)(@nestjs/platform-socket.io@10.4.7)(reflect-metadata@0.2.2)(rxjs@7.8.1) + dotenv: + specifier: ^16.4.5 + version: 16.4.5 + ioredis: + specifier: ^5.4.1 + version: 5.4.1 reflect-metadata: specifier: ^0.2.0 version: 0.2.2 @@ -53,7 +59,7 @@ importers: version: 10.2.3(chokidar@3.6.0)(typescript@5.6.3) '@nestjs/testing': specifier: ^10.0.0 - version: 10.4.6(@nestjs/common@10.4.6(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.6(@nestjs/common@10.4.6(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.6)(@nestjs/websockets@10.4.7)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.6(@nestjs/common@10.4.6(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.6)) + version: 10.4.6(@nestjs/common@10.4.6(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.6)(@nestjs/platform-express@10.4.6) '@types/express': specifier: ^5.0.0 version: 5.0.0 @@ -672,6 +678,9 @@ packages: resolution: {integrity: sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==} engines: {node: '>=18.18'} + '@ioredis/commands@1.2.0': + resolution: {integrity: sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg==} + '@isaacs/cliui@8.0.2': resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} @@ -1524,6 +1533,10 @@ packages: resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==} engines: {node: '>=0.8'} + cluster-key-slot@1.1.2: + resolution: {integrity: sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==} + engines: {node: '>=0.10.0'} + co@4.6.0: resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==} engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'} @@ -1707,6 +1720,10 @@ packages: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} engines: {node: '>=0.4.0'} + denque@2.1.0: + resolution: {integrity: sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==} + engines: {node: '>=0.10'} + depd@2.0.0: resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} engines: {node: '>= 0.8'} @@ -1744,6 +1761,10 @@ packages: resolution: {integrity: sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==} engines: {node: '>=8'} + dotenv@16.4.5: + resolution: {integrity: sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==} + engines: {node: '>=12'} + eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} @@ -2249,6 +2270,10 @@ packages: resolution: {integrity: sha512-vI2w4zl/mDluHt9YEQ/543VTCwPKWiHzKtm9dM2V0NdFcqEexDAjUHzO1oA60HRNaVifGXXM1tRRNluLVHa0Kg==} engines: {node: '>=18'} + ioredis@5.4.1: + resolution: {integrity: sha512-2YZsvl7jopIa1gaePkeMtd9rAcSjOOjPtpcLlOeusyO+XH2SK5ZcT+UCrElPP+WVIInh2TzeI4XW9ENaSLVVHA==} + engines: {node: '>=12.22.0'} + ipaddr.js@1.9.1: resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} engines: {node: '>= 0.10'} @@ -2581,6 +2606,12 @@ packages: lodash.camelcase@4.3.0: resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==} + lodash.defaults@4.2.0: + resolution: {integrity: sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==} + + lodash.isarguments@3.1.0: + resolution: {integrity: sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==} + lodash.isplainobject@4.0.6: resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} @@ -3061,6 +3092,14 @@ packages: resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} engines: {node: '>=8.10.0'} + redis-errors@1.2.0: + resolution: {integrity: sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==} + engines: {node: '>=4'} + + redis-parser@3.0.0: + resolution: {integrity: sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==} + engines: {node: '>=4'} + reflect-metadata@0.2.2: resolution: {integrity: sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==} @@ -3245,6 +3284,9 @@ packages: resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} engines: {node: '>=10'} + standard-as-callback@2.1.0: + resolution: {integrity: sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==} + statuses@2.0.1: resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} engines: {node: '>= 0.8'} @@ -4234,6 +4276,8 @@ snapshots: '@humanwhocodes/retry@0.3.1': {} + '@ioredis/commands@1.2.0': {} + '@isaacs/cliui@8.0.2': dependencies: string-width: 5.1.2 @@ -4545,7 +4589,7 @@ snapshots: transitivePeerDependencies: - chokidar - '@nestjs/testing@10.4.6(@nestjs/common@10.4.6(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.6(@nestjs/common@10.4.6(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.6)(@nestjs/websockets@10.4.7)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.6(@nestjs/common@10.4.6(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.6))': + '@nestjs/testing@10.4.6(@nestjs/common@10.4.6(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.6)(@nestjs/platform-express@10.4.6)': dependencies: '@nestjs/common': 10.4.6(reflect-metadata@0.2.2)(rxjs@7.8.1) '@nestjs/core': 10.4.6(@nestjs/common@10.4.6(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.6)(@nestjs/websockets@10.4.7)(reflect-metadata@0.2.2)(rxjs@7.8.1) @@ -5347,6 +5391,8 @@ snapshots: clone@1.0.4: {} + cluster-key-slot@1.1.2: {} + co@4.6.0: {} collect-v8-coverage@1.0.2: {} @@ -5509,6 +5555,8 @@ snapshots: delayed-stream@1.0.0: {} + denque@2.1.0: {} + depd@2.0.0: {} destroy@1.2.0: {} @@ -5536,6 +5584,8 @@ snapshots: dependencies: is-obj: 2.0.0 + dotenv@16.4.5: {} + eastasianwidth@0.2.0: {} ee-first@1.1.1: {} @@ -6187,6 +6237,20 @@ snapshots: strip-ansi: 6.0.1 wrap-ansi: 6.2.0 + ioredis@5.4.1: + dependencies: + '@ioredis/commands': 1.2.0 + cluster-key-slot: 1.1.2 + debug: 4.3.7 + denque: 2.1.0 + lodash.defaults: 4.2.0 + lodash.isarguments: 3.1.0 + redis-errors: 1.2.0 + redis-parser: 3.0.0 + standard-as-callback: 2.1.0 + transitivePeerDependencies: + - supports-color + ipaddr.js@1.9.1: {} is-arrayish@0.2.1: {} @@ -6674,6 +6738,10 @@ snapshots: lodash.camelcase@4.3.0: {} + lodash.defaults@4.2.0: {} + + lodash.isarguments@3.1.0: {} + lodash.isplainobject@4.0.6: {} lodash.kebabcase@4.1.1: {} @@ -7090,6 +7158,12 @@ snapshots: dependencies: picomatch: 2.3.1 + redis-errors@1.2.0: {} + + redis-parser@3.0.0: + dependencies: + redis-errors: 1.2.0 + reflect-metadata@0.2.2: {} repeat-string@1.6.1: {} @@ -7323,6 +7397,8 @@ snapshots: dependencies: escape-string-regexp: 2.0.0 + standard-as-callback@2.1.0: {} + statuses@2.0.1: {} streamsearch@1.1.0: {} From aa3c6b856873c11ab705a3b6bc98e5281823d19a Mon Sep 17 00:00:00 2001 From: twalla26 Date: Tue, 12 Nov 2024 22:22:21 +0900 Subject: [PATCH 2/4] =?UTF-8?q?feat:=20room=20=EB=AA=A8=EB=93=88,=20?= =?UTF-8?q?=EC=84=9C=EB=B9=84=EC=8A=A4,=20=EA=B2=8C=EC=9D=B4=ED=8A=B8?= =?UTF-8?q?=EC=9B=A8=EC=9D=B4=20=EC=B4=88=EA=B8=B0=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/src/app.module.ts | 6 +++++- backend/src/room/room.gateway.spec.ts | 18 ++++++++++++++++++ backend/src/room/room.gateway.ts | 9 +++++++++ backend/src/room/room.module.ts | 8 ++++++++ backend/src/room/room.service.spec.ts | 18 ++++++++++++++++++ backend/src/room/room.service.ts | 4 ++++ 6 files changed, 62 insertions(+), 1 deletion(-) create mode 100644 backend/src/room/room.gateway.spec.ts create mode 100644 backend/src/room/room.gateway.ts create mode 100644 backend/src/room/room.module.ts create mode 100644 backend/src/room/room.service.spec.ts create mode 100644 backend/src/room/room.service.ts diff --git a/backend/src/app.module.ts b/backend/src/app.module.ts index f6e8a2e5..51d78b51 100644 --- a/backend/src/app.module.ts +++ b/backend/src/app.module.ts @@ -2,9 +2,13 @@ import { Module } from "@nestjs/common"; import { AppController } from "./app.controller"; import { AppService } from "./app.service"; import { SocketModule } from "./socket/socket.module"; +import { RoomModule } from "./room/room.module"; +import { RedisModule } from "./redis/redis.module"; + +import "dotenv/config"; @Module({ - imports: [SocketModule], + imports: [SocketModule, RoomModule, RedisModule], controllers: [AppController], providers: [AppService], }) diff --git a/backend/src/room/room.gateway.spec.ts b/backend/src/room/room.gateway.spec.ts new file mode 100644 index 00000000..446b302b --- /dev/null +++ b/backend/src/room/room.gateway.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from "@nestjs/testing"; +import { RoomGateway } from "./room.gateway"; + +describe("RoomGateway", () => { + let gateway: RoomGateway; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [RoomGateway], + }).compile(); + + gateway = module.get(RoomGateway); + }); + + it("should be defined", () => { + expect(gateway).toBeDefined(); + }); +}); diff --git a/backend/src/room/room.gateway.ts b/backend/src/room/room.gateway.ts new file mode 100644 index 00000000..d5d2ba79 --- /dev/null +++ b/backend/src/room/room.gateway.ts @@ -0,0 +1,9 @@ +import { SubscribeMessage, WebSocketGateway } from "@nestjs/websockets"; + +@WebSocketGateway() +export class RoomGateway { + @SubscribeMessage("message") + handleMessage(client: any, payload: any): string { + return "Hello world!"; + } +} diff --git a/backend/src/room/room.module.ts b/backend/src/room/room.module.ts new file mode 100644 index 00000000..d42a749c --- /dev/null +++ b/backend/src/room/room.module.ts @@ -0,0 +1,8 @@ +import { Module } from "@nestjs/common"; +import { RoomService } from "./room.service"; +import { RoomGateway } from "./room.gateway"; + +@Module({ + providers: [RoomService, RoomGateway], +}) +export class RoomModule {} diff --git a/backend/src/room/room.service.spec.ts b/backend/src/room/room.service.spec.ts new file mode 100644 index 00000000..0ab7dff4 --- /dev/null +++ b/backend/src/room/room.service.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from "@nestjs/testing"; +import { RoomService } from "./room.service"; + +describe("RoomService", () => { + let service: RoomService; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [RoomService], + }).compile(); + + service = module.get(RoomService); + }); + + it("should be defined", () => { + expect(service).toBeDefined(); + }); +}); diff --git a/backend/src/room/room.service.ts b/backend/src/room/room.service.ts new file mode 100644 index 00000000..0a0fbd5c --- /dev/null +++ b/backend/src/room/room.service.ts @@ -0,0 +1,4 @@ +import { Injectable } from '@nestjs/common'; + +@Injectable() +export class RoomService {} From 10ecd44e65e7805d52ebc077baf359f667b6681c Mon Sep 17 00:00:00 2001 From: twalla26 Date: Tue, 12 Nov 2024 23:13:05 +0900 Subject: [PATCH 3/4] =?UTF-8?q?refactor:=20join=5Froom=20=EC=9D=B4?= =?UTF-8?q?=EB=B2=A4=ED=8A=B8=EB=A5=BC=20room=20=EB=AA=A8=EB=93=88?= =?UTF-8?q?=EB=A1=9C=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/src/app.module.ts | 2 +- backend/src/room/room.gateway.ts | 79 ++++++++++- .../src/signaling-server/socket.gateway.ts | 74 +++++++++++ .../socket.module.ts | 0 backend/src/socket/socket.gateway.ts | 124 ------------------ 5 files changed, 149 insertions(+), 130 deletions(-) create mode 100644 backend/src/signaling-server/socket.gateway.ts rename backend/src/{socket => signaling-server}/socket.module.ts (100%) delete mode 100644 backend/src/socket/socket.gateway.ts diff --git a/backend/src/app.module.ts b/backend/src/app.module.ts index 51d78b51..2e4f16bc 100644 --- a/backend/src/app.module.ts +++ b/backend/src/app.module.ts @@ -1,7 +1,7 @@ import { Module } from "@nestjs/common"; import { AppController } from "./app.controller"; import { AppService } from "./app.service"; -import { SocketModule } from "./socket/socket.module"; +import { SocketModule } from "./signaling-server/socket.module"; import { RoomModule } from "./room/room.module"; import { RedisModule } from "./redis/redis.module"; diff --git a/backend/src/room/room.gateway.ts b/backend/src/room/room.gateway.ts index d5d2ba79..9ea2b902 100644 --- a/backend/src/room/room.gateway.ts +++ b/backend/src/room/room.gateway.ts @@ -1,9 +1,78 @@ -import { SubscribeMessage, WebSocketGateway } from "@nestjs/websockets"; +import { + WebSocketGateway, + WebSocketServer, + OnGatewayConnection, + OnGatewayDisconnect, + SubscribeMessage, + MessageBody, +} from "@nestjs/websockets"; +import { Server, Socket } from "socket.io"; +interface User { + id: string; + nickname: string; +} + +@WebSocketGateway({ + cors: { + origin: "*", // CORS 설정 + }, +}) @WebSocketGateway() -export class RoomGateway { - @SubscribeMessage("message") - handleMessage(client: any, payload: any): string { - return "Hello world!"; +export class SocketGateway implements OnGatewayConnection, OnGatewayDisconnect { + @WebSocketServer() + server: Server; + + private users: { [key: string]: User[] } = {}; + private socketToRoom: { [key: string]: string } = {}; + private maximum = 5; + + handleConnection(client: Socket) { + console.log(`Client connected in room: ${client.id}`); + } + + handleDisconnect(client: Socket): any { + console.log(`Client disconnected in room: ${client.id}`); + const roomID = this.socketToRoom[client.id]; + if (roomID) { + const room = this.users[roomID]; + if (room) { + this.users[roomID] = room.filter( + (user) => user.id !== client.id + ); + if (this.users[roomID].length === 0) { + delete this.users[roomID]; + } else { + this.server.to(roomID).emit("user_exit", { id: client.id }); + } + } + } + } + + @SubscribeMessage("join_room") + handleJoinRoom(client: Socket, data: { room: string; nickname: string }) { + if (this.users[data.room]) { + if (this.users[data.room].length === this.maximum) { + client.emit("room_full"); + return; + } + this.users[data.room].push({ + id: client.id, + nickname: data.nickname, + }); + } else { + this.users[data.room] = [ + { id: client.id, nickname: data.nickname }, + ]; + } + + this.socketToRoom[client.id] = data.room; + client.join(data.room); + console.log(`[${data.room}]: ${client.id} enter`); + + const usersInThisRoom = this.users[data.room].filter( + (user) => user.id !== client.id + ); + client.emit("all_users", usersInThisRoom); } } diff --git a/backend/src/signaling-server/socket.gateway.ts b/backend/src/signaling-server/socket.gateway.ts new file mode 100644 index 00000000..49d01d8f --- /dev/null +++ b/backend/src/signaling-server/socket.gateway.ts @@ -0,0 +1,74 @@ +import { + WebSocketGateway, + WebSocketServer, + OnGatewayConnection, + OnGatewayDisconnect, + SubscribeMessage, + MessageBody, +} from "@nestjs/websockets"; +import { Server } from "socket.io"; + +@WebSocketGateway({ + cors: { + origin: "*", // CORS 설정 + }, +}) +export class SocketGateway implements OnGatewayConnection, OnGatewayDisconnect { + @WebSocketServer() + server: Server; + + handleConnection(socket: any) { + console.log(`Client connected in signaling server: ${socket.id}`); + } + + handleDisconnect(socket: any) { + console.log(`Client disconnected signaling server: ${socket.id}`); + } + + @SubscribeMessage("offer") + handleOffer( + @MessageBody() + data: { + offerReceiveID: string; + sdp: any; + offerSendID: string; + offerSendNickname: string; + } + ) { + this.server.to(data.offerReceiveID).emit("getOffer", { + sdp: data.sdp, + offerSendID: data.offerSendID, + offerSendNickname: data.offerSendNickname, + }); + } + + @SubscribeMessage("answer") + handleAnswer( + @MessageBody() + data: { + answerReceiveID: string; + sdp: any; + answerSendID: string; + } + ) { + this.server.to(data.answerReceiveID).emit("getAnswer", { + sdp: data.sdp, + answerSendID: data.answerSendID, + }); + } + + @SubscribeMessage("candidate") + handleCandidate( + @MessageBody() + data: { + candidateReceiveID: string; + candidate: any; + candidateSendID: string; + } + ) { + this.server.to(data.candidateReceiveID).emit("getCandidate", { + candidate: data.candidate, + candidateSendID: data.candidateSendID, + }); + } +} diff --git a/backend/src/socket/socket.module.ts b/backend/src/signaling-server/socket.module.ts similarity index 100% rename from backend/src/socket/socket.module.ts rename to backend/src/signaling-server/socket.module.ts diff --git a/backend/src/socket/socket.gateway.ts b/backend/src/socket/socket.gateway.ts deleted file mode 100644 index 7246aad3..00000000 --- a/backend/src/socket/socket.gateway.ts +++ /dev/null @@ -1,124 +0,0 @@ -import { - WebSocketGateway, - WebSocketServer, - OnGatewayConnection, - OnGatewayDisconnect, - SubscribeMessage, - MessageBody, -} from "@nestjs/websockets"; -import { Server } from "socket.io"; - -interface User { - id: string; - nickname: string; -} - -@WebSocketGateway({ - cors: { - origin: "*", // CORS 설정 - }, -}) -export class SocketGateway implements OnGatewayConnection, OnGatewayDisconnect { - @WebSocketServer() - server: Server; - - private users: { [key: string]: User[] } = {}; - private socketToRoom: { [key: string]: string } = {}; - private maximum = 5; - - handleConnection(socket: any) { - console.log(`Client connected: ${socket.id}`); - } - - handleDisconnect(socket: any) { - console.log(`Client disconnected: ${socket.id}`); - const roomID = this.socketToRoom[socket.id]; - if (roomID) { - const room = this.users[roomID]; - if (room) { - this.users[roomID] = room.filter( - (user) => user.id !== socket.id - ); - if (this.users[roomID].length === 0) { - delete this.users[roomID]; - } else { - this.server.to(roomID).emit("user_exit", { id: socket.id }); - } - } - } - } - - @SubscribeMessage("join_room") - handleJoinRoom(socket: any, data: { room: string; nickname: string }) { - if (this.users[data.room]) { - if (this.users[data.room].length === this.maximum) { - socket.emit("room_full"); - return; - } - this.users[data.room].push({ - id: socket.id, - nickname: data.nickname, - }); - } else { - this.users[data.room] = [ - { id: socket.id, nickname: data.nickname }, - ]; - } - - this.socketToRoom[socket.id] = data.room; - socket.join(data.room); - console.log(`[${data.room}]: ${socket.id} enter`); - - const usersInThisRoom = this.users[data.room].filter( - (user) => user.id !== socket.id - ); - socket.emit("all_users", usersInThisRoom); - } - - @SubscribeMessage("offer") - handleOffer( - @MessageBody() - data: { - offerReceiveID: string; - sdp: any; - offerSendID: string; - offerSendNickname: string; - } - ) { - this.server.to(data.offerReceiveID).emit("getOffer", { - sdp: data.sdp, - offerSendID: data.offerSendID, - offerSendNickname: data.offerSendNickname, - }); - } - - @SubscribeMessage("answer") - handleAnswer( - @MessageBody() - data: { - answerReceiveID: string; - sdp: any; - answerSendID: string; - } - ) { - this.server.to(data.answerReceiveID).emit("getAnswer", { - sdp: data.sdp, - answerSendID: data.answerSendID, - }); - } - - @SubscribeMessage("candidate") - handleCandidate( - @MessageBody() - data: { - candidateReceiveID: string; - candidate: any; - candidateSendID: string; - } - ) { - this.server.to(data.candidateReceiveID).emit("getCandidate", { - candidate: data.candidate, - candidateSendID: data.candidateSendID, - }); - } -} From d0b8a919ed91dc8131e7085be4a688bec8bb5cb8 Mon Sep 17 00:00:00 2001 From: twalla26 Date: Wed, 13 Nov 2024 00:43:13 +0900 Subject: [PATCH 4/4] =?UTF-8?q?feat:=20createRoom=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/src/config/redis.config.ts | 4 ++- backend/src/room/room.gateway.ts | 40 +++++++++++++++++++++++++---- backend/src/room/room.model.ts | 6 +++++ backend/src/room/room.module.ts | 3 ++- backend/src/room/room.service.ts | 25 ++++++++++++++++-- backend/src/utils/generateRoomId.ts | 9 +++++++ backend/src/utils/time.ts | 3 +++ 7 files changed, 81 insertions(+), 9 deletions(-) create mode 100644 backend/src/room/room.model.ts create mode 100644 backend/src/utils/generateRoomId.ts create mode 100644 backend/src/utils/time.ts diff --git a/backend/src/config/redis.config.ts b/backend/src/config/redis.config.ts index 830c9435..0b97e561 100644 --- a/backend/src/config/redis.config.ts +++ b/backend/src/config/redis.config.ts @@ -1,5 +1,7 @@ +import "dotenv/config"; + export const redisConfig = { host: process.env.REDIS_HOST, - port: Number(process.env.REDIS_PORT), + port: parseInt(process.env.REDIS_PORT), password: process.env.REDIS_PASSWORD, }; diff --git a/backend/src/room/room.gateway.ts b/backend/src/room/room.gateway.ts index 9ea2b902..aa7994d1 100644 --- a/backend/src/room/room.gateway.ts +++ b/backend/src/room/room.gateway.ts @@ -7,6 +7,19 @@ import { MessageBody, } from "@nestjs/websockets"; import { Server, Socket } from "socket.io"; +import { RoomService } from "./room.service"; + +const EVENT_NAME = { + CREATE_ROOM: "create_room", + JOIN_ROOM: "join_room", + MASTER_CHANGED: "master_changed", + FINISH_ROOM: "finish_room", + REACTION: "reaction", + + USER_EXIT: "user_exit", + ROOM_FULL: "room_full", + ALL_USERS: "all_users", +} as const; interface User { id: string; @@ -19,10 +32,12 @@ interface User { }, }) @WebSocketGateway() -export class SocketGateway implements OnGatewayConnection, OnGatewayDisconnect { +export class RoomGateway implements OnGatewayConnection, OnGatewayDisconnect { @WebSocketServer() server: Server; + constructor(private readonly roomService: RoomService) {} + private users: { [key: string]: User[] } = {}; private socketToRoom: { [key: string]: string } = {}; private maximum = 5; @@ -43,17 +58,32 @@ export class SocketGateway implements OnGatewayConnection, OnGatewayDisconnect { if (this.users[roomID].length === 0) { delete this.users[roomID]; } else { - this.server.to(roomID).emit("user_exit", { id: client.id }); + this.server.to(roomID).emit(EVENT_NAME.USER_EXIT, { id: client.id }); } } } } - @SubscribeMessage("join_room") + @SubscribeMessage(EVENT_NAME.CREATE_ROOM) + async handleCreateRoom(client: Socket, data: { title }) { + try { + const roomId = await this.roomService.createRoom( + data.title, + client.id + ); + this.server.emit(`room_created`, { roomId }); + } catch(error) { + console.error(error); + return null; + } + } + + + @SubscribeMessage(EVENT_NAME.JOIN_ROOM) handleJoinRoom(client: Socket, data: { room: string; nickname: string }) { if (this.users[data.room]) { if (this.users[data.room].length === this.maximum) { - client.emit("room_full"); + client.emit(EVENT_NAME.ROOM_FULL); return; } this.users[data.room].push({ @@ -73,6 +103,6 @@ export class SocketGateway implements OnGatewayConnection, OnGatewayDisconnect { const usersInThisRoom = this.users[data.room].filter( (user) => user.id !== client.id ); - client.emit("all_users", usersInThisRoom); + client.emit(EVENT_NAME.ALL_USERS, usersInThisRoom); } } diff --git a/backend/src/room/room.model.ts b/backend/src/room/room.model.ts new file mode 100644 index 00000000..460fc27a --- /dev/null +++ b/backend/src/room/room.model.ts @@ -0,0 +1,6 @@ +export interface Room { + title: string; + createdAt: number; + members: string[]; + host: string; +} \ No newline at end of file diff --git a/backend/src/room/room.module.ts b/backend/src/room/room.module.ts index d42a749c..2aa9a249 100644 --- a/backend/src/room/room.module.ts +++ b/backend/src/room/room.module.ts @@ -1,8 +1,9 @@ import { Module } from "@nestjs/common"; import { RoomService } from "./room.service"; import { RoomGateway } from "./room.gateway"; +import { RedisService } from "../redis/redis.service"; @Module({ - providers: [RoomService, RoomGateway], + providers: [RoomService, RoomGateway, RedisService], }) export class RoomModule {} diff --git a/backend/src/room/room.service.ts b/backend/src/room/room.service.ts index 0a0fbd5c..b3e08401 100644 --- a/backend/src/room/room.service.ts +++ b/backend/src/room/room.service.ts @@ -1,4 +1,25 @@ -import { Injectable } from '@nestjs/common'; +import { Injectable } from "@nestjs/common"; +import { RedisService } from "../redis/redis.service"; +import { Room } from "./room.model"; +import { generateRoomId } from "../utils/generateRoomId"; +import { HOUR } from "../utils/time"; @Injectable() -export class RoomService {} +export class RoomService { + constructor(private readonly redisService: RedisService) {} + + async createRoom(title: string, socketId: string) { + const client = this.redisService.getClient(); + const roomId = generateRoomId(); + + await client.hset(`room:${roomId}`, { + title: title, + createdAt: Date.now(), + members: [socketId], + host: socketId, + } as Room); + + await client.expire(`room:${roomId}`, 6 * HOUR); + return roomId; + } +} diff --git a/backend/src/utils/generateRoomId.ts b/backend/src/utils/generateRoomId.ts new file mode 100644 index 00000000..c808594a --- /dev/null +++ b/backend/src/utils/generateRoomId.ts @@ -0,0 +1,9 @@ +import { createHash } from "node:crypto"; +import "dotenv/config"; + +let id = parseInt(process.env.SESSION_HASH_START); +export function generateRoomId() { + return createHash("sha256") + .update(id++ + process.env.SESSION_HASH) + .digest("hex"); +} \ No newline at end of file diff --git a/backend/src/utils/time.ts b/backend/src/utils/time.ts new file mode 100644 index 00000000..655f5325 --- /dev/null +++ b/backend/src/utils/time.ts @@ -0,0 +1,3 @@ +export const SEC = 1; +export const MIN = 60 * SEC; +export const HOUR = 60 * MIN;