From 930206080e55a0fa50d5c4231f6d279522b15fff Mon Sep 17 00:00:00 2001 From: Conut-1 <1mim1@naver.com> Date: Sat, 1 Feb 2025 17:42:37 +0900 Subject: [PATCH 01/32] =?UTF-8?q?feat:=20=EB=A7=88=EC=9D=B8=EB=93=9C?= =?UTF-8?q?=EB=A7=B5=20=EC=9E=91=EC=97=85=20=EB=A1=9C=EA=B7=B8=20=EC=8A=A4?= =?UTF-8?q?=ED=82=A4=EB=A7=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../schemas/board-operation.schema.ts | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 nestjs-BE/server/src/board-trees/schemas/board-operation.schema.ts diff --git a/nestjs-BE/server/src/board-trees/schemas/board-operation.schema.ts b/nestjs-BE/server/src/board-trees/schemas/board-operation.schema.ts new file mode 100644 index 00000000..5261a03b --- /dev/null +++ b/nestjs-BE/server/src/board-trees/schemas/board-operation.schema.ts @@ -0,0 +1,28 @@ +import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'; +import { HydratedDocument } from 'mongoose'; + +export type BoardOperationDocument = HydratedDocument; + +@Schema() +export class BoardOperation { + @Prop({ required: true }) + boardId: string; + + @Prop({ required: true }) + type: string; + + @Prop() + parentId: string; + + @Prop() + oldParentId: string; + + @Prop() + content: string; + + @Prop() + oldContent: string; +} + +export const BoardOperationSchema = + SchemaFactory.createForClass(BoardOperation); From 0c5f0b515c35d9631999a1e779f27244797b2503 Mon Sep 17 00:00:00 2001 From: Conut-1 <1mim1@naver.com> Date: Sat, 1 Feb 2025 21:17:31 +0900 Subject: [PATCH 02/32] =?UTF-8?q?feat:=20=EB=AA=A8=EB=93=88=EC=97=90=20Boa?= =?UTF-8?q?rdOperation=20=EC=8A=A4=ED=82=A4=EB=A7=88=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nestjs-BE/server/src/board-trees/board-trees.module.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/nestjs-BE/server/src/board-trees/board-trees.module.ts b/nestjs-BE/server/src/board-trees/board-trees.module.ts index 211084d2..d7e47a91 100644 --- a/nestjs-BE/server/src/board-trees/board-trees.module.ts +++ b/nestjs-BE/server/src/board-trees/board-trees.module.ts @@ -4,11 +4,16 @@ import { MongooseModule } from '@nestjs/mongoose'; import { BoardTreesService } from './board-trees.service'; import { BoardTreesGateway } from './board-trees.gateway'; import { BoardTree, BoardTreeSchema } from './schemas/board-tree.schema'; +import { + BoardOperation, + BoardOperationSchema, +} from './schemas/board-operation.schema'; @Module({ imports: [ MongooseModule.forFeature([ { name: BoardTree.name, schema: BoardTreeSchema }, + { name: BoardOperation.name, schema: BoardOperationSchema }, ]), JwtModule, ], From 68522c491e2501c53d1501e62cdcb21e74d182d7 Mon Sep 17 00:00:00 2001 From: Conut-1 <1mim1@naver.com> Date: Sat, 1 Feb 2025 21:59:32 +0900 Subject: [PATCH 03/32] =?UTF-8?q?feat:=20=EB=B3=B4=EB=93=9C=20=EC=9E=91?= =?UTF-8?q?=EC=97=85=20=EC=83=9D=EC=84=B1=20=EC=9A=94=EC=B2=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nestjs-BE/server/src/board-trees/board-trees.gateway.ts | 6 ++++++ nestjs-BE/server/src/board-trees/board-trees.service.ts | 7 +++++++ 2 files changed, 13 insertions(+) diff --git a/nestjs-BE/server/src/board-trees/board-trees.gateway.ts b/nestjs-BE/server/src/board-trees/board-trees.gateway.ts index 6d8a896a..f76c76b3 100644 --- a/nestjs-BE/server/src/board-trees/board-trees.gateway.ts +++ b/nestjs-BE/server/src/board-trees/board-trees.gateway.ts @@ -14,6 +14,7 @@ import { OperationMove, OperationUpdate, } from '../crdt/operation'; +import type { BoardOperation } from './schemas/board-operation.schema'; @WebSocketGateway({ namespace: 'board' }) export class BoardTreesGateway implements OnGatewayConnection { @@ -75,4 +76,9 @@ export class BoardTreesGateway implements OnGatewayConnection { .to(boardId) .emit('operationFromServer', serializedOperation); } + + @SubscribeMessage('createOperation') + handleCreateOperation(client: Socket, operation: BoardOperation) { + client.broadcast.to(operation.boardId).emit('operation', operation); + } } diff --git a/nestjs-BE/server/src/board-trees/board-trees.service.ts b/nestjs-BE/server/src/board-trees/board-trees.service.ts index d8f39d1e..03ca1570 100644 --- a/nestjs-BE/server/src/board-trees/board-trees.service.ts +++ b/nestjs-BE/server/src/board-trees/board-trees.service.ts @@ -2,6 +2,7 @@ import { Injectable } from '@nestjs/common'; import { InjectModel } from '@nestjs/mongoose'; import { Model } from 'mongoose'; import { BoardTree } from './schemas/board-tree.schema'; +import { BoardOperation } from './schemas/board-operation.schema'; import { CrdtTree } from '../crdt/crdt-tree'; import { Operation } from '../crdt/operation'; @@ -9,6 +10,8 @@ import { Operation } from '../crdt/operation'; export class BoardTreesService { constructor( @InjectModel(BoardTree.name) private boardTreeModel: Model, + @InjectModel(BoardOperation.name) + private boardOperationModel: Model, ) {} private boardTrees = new Map(); @@ -57,4 +60,8 @@ export class BoardTreesService { .updateOne({ boardId }, { tree: JSON.stringify(tree) }) .exec(); } + + async createOperationLog(operation: BoardOperation) { + return this.boardOperationModel.create({ operation }); + } } From 929927ff90cacf8f88be473273ccf4d66e3f2606 Mon Sep 17 00:00:00 2001 From: Conut-1 <1mim1@naver.com> Date: Mon, 3 Feb 2025 20:47:05 +0900 Subject: [PATCH 04/32] =?UTF-8?q?fix:=20=EC=9E=91=EC=97=85=20DB=EC=97=90?= =?UTF-8?q?=20=EC=A0=80=EC=9E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nestjs-BE/server/src/board-trees/board-trees.gateway.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/nestjs-BE/server/src/board-trees/board-trees.gateway.ts b/nestjs-BE/server/src/board-trees/board-trees.gateway.ts index f76c76b3..d65f74fe 100644 --- a/nestjs-BE/server/src/board-trees/board-trees.gateway.ts +++ b/nestjs-BE/server/src/board-trees/board-trees.gateway.ts @@ -78,7 +78,8 @@ export class BoardTreesGateway implements OnGatewayConnection { } @SubscribeMessage('createOperation') - handleCreateOperation(client: Socket, operation: BoardOperation) { + async handleCreateOperation(client: Socket, operation: BoardOperation) { + await this.boardTreesService.createOperationLog(operation); client.broadcast.to(operation.boardId).emit('operation', operation); } } From 7620434bb8b1f208cd4a11e70a58648aba7608b5 Mon Sep 17 00:00:00 2001 From: Conut-1 <1mim1@naver.com> Date: Mon, 3 Feb 2025 21:09:55 +0900 Subject: [PATCH 05/32] =?UTF-8?q?feat;=20=EC=9E=91=EC=97=85=20=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=20=EA=B0=80=EC=A0=B8=EC=98=A4=EA=B8=B0=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nestjs-BE/server/src/board-trees/board-trees.gateway.ts | 6 ++++++ nestjs-BE/server/src/board-trees/board-trees.service.ts | 4 ++++ 2 files changed, 10 insertions(+) diff --git a/nestjs-BE/server/src/board-trees/board-trees.gateway.ts b/nestjs-BE/server/src/board-trees/board-trees.gateway.ts index d65f74fe..21d9f0e8 100644 --- a/nestjs-BE/server/src/board-trees/board-trees.gateway.ts +++ b/nestjs-BE/server/src/board-trees/board-trees.gateway.ts @@ -82,4 +82,10 @@ export class BoardTreesGateway implements OnGatewayConnection { await this.boardTreesService.createOperationLog(operation); client.broadcast.to(operation.boardId).emit('operation', operation); } + + @SubscribeMessage('getOperations') + async handleGetOperations(client: Socket, boardId: string) { + const operations = await this.boardTreesService.getOperationLogs(boardId); + client.emit('getOperations', operations); + } } diff --git a/nestjs-BE/server/src/board-trees/board-trees.service.ts b/nestjs-BE/server/src/board-trees/board-trees.service.ts index 03ca1570..e8179aa8 100644 --- a/nestjs-BE/server/src/board-trees/board-trees.service.ts +++ b/nestjs-BE/server/src/board-trees/board-trees.service.ts @@ -64,4 +64,8 @@ export class BoardTreesService { async createOperationLog(operation: BoardOperation) { return this.boardOperationModel.create({ operation }); } + + async getOperationLogs(boardId: string) { + return this.boardOperationModel.find({ boardId }); + } } From 2244e2e1c2064fe6d6bc8f63c9f5c69ffd96ace7 Mon Sep 17 00:00:00 2001 From: Conut-1 <1mim1@naver.com> Date: Mon, 3 Feb 2025 21:25:35 +0900 Subject: [PATCH 06/32] =?UTF-8?q?fix:=20gateway=EC=97=90=EC=84=9C=20?= =?UTF-8?q?=ED=8A=B8=EB=A6=AC=20=EC=A1=B0=EC=9E=91=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/board-trees/board-trees.gateway.ts | 39 +------------------ 1 file changed, 1 insertion(+), 38 deletions(-) diff --git a/nestjs-BE/server/src/board-trees/board-trees.gateway.ts b/nestjs-BE/server/src/board-trees/board-trees.gateway.ts index 21d9f0e8..330b2e53 100644 --- a/nestjs-BE/server/src/board-trees/board-trees.gateway.ts +++ b/nestjs-BE/server/src/board-trees/board-trees.gateway.ts @@ -8,12 +8,6 @@ import { } from '@nestjs/websockets'; import { Server, Socket } from 'socket.io'; import { BoardTreesService } from './board-trees.service'; -import { - OperationAdd, - OperationDelete, - OperationMove, - OperationUpdate, -} from '../crdt/operation'; import type { BoardOperation } from './schemas/board-operation.schema'; @WebSocketGateway({ namespace: 'board' }) @@ -42,39 +36,8 @@ export class BoardTreesGateway implements OnGatewayConnection { @SubscribeMessage('joinBoard') async handleJoinBoard(client: Socket, payload: string) { const payloadObject = JSON.parse(payload); - if (!this.boardTreesService.hasTree(payloadObject.boardId)) { - await this.boardTreesService.initBoardTree( - payloadObject.boardId, - payloadObject.boardName, - ); - } client.join(payloadObject.boardId); - client.emit( - 'initTree', - this.boardTreesService.getTreeData(payloadObject.boardId), - ); - } - - @SubscribeMessage('updateMindmap') - handleUpdateMindmap(client: Socket, payload: string) { - const payloadObject = JSON.parse(payload); - const { boardId, operation: serializedOperation } = payloadObject; - - const operationTypeMap = { - add: OperationAdd.parse, - delete: OperationDelete.parse, - move: OperationMove.parse, - update: OperationUpdate.parse, - }; - - const operation = - operationTypeMap[serializedOperation.operationType](serializedOperation); - this.boardTreesService.applyOperation(boardId, operation); - this.boardTreesService.updateTreeData(boardId); - - client.broadcast - .to(boardId) - .emit('operationFromServer', serializedOperation); + client.emit('boardJoined'); } @SubscribeMessage('createOperation') From c45c5fc8067011ec2f94ea0204fc240fd07c718a Mon Sep 17 00:00:00 2001 From: Conut-1 <1mim1@naver.com> Date: Tue, 4 Feb 2025 19:03:26 +0900 Subject: [PATCH 07/32] =?UTF-8?q?test:=20board=20trees=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EB=AA=A8=EB=93=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nestjs-BE/server/test/board-trees.e2e-spec.ts | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 nestjs-BE/server/test/board-trees.e2e-spec.ts diff --git a/nestjs-BE/server/test/board-trees.e2e-spec.ts b/nestjs-BE/server/test/board-trees.e2e-spec.ts new file mode 100644 index 00000000..c6465df3 --- /dev/null +++ b/nestjs-BE/server/test/board-trees.e2e-spec.ts @@ -0,0 +1,33 @@ +import { INestApplication } from '@nestjs/common'; +import { ConfigModule, ConfigService } from '@nestjs/config'; +import { Test, TestingModule } from '@nestjs/testing'; +import { MongooseModule } from '@nestjs/mongoose'; +import { BoardTreesModule } from '../src/board-trees/board-trees.module'; + +describe('BoardTreesGateway (e2e)', () => { + let app: INestApplication; + + beforeAll(async () => { + const module: TestingModule = await Test.createTestingModule({ + imports: [ + ConfigModule.forRoot({ isGlobal: true }), + MongooseModule.forRootAsync({ + imports: [ConfigModule], + inject: [ConfigService], + useFactory: async (configService: ConfigService) => ({ + uri: configService.get('MONGODB_DATABASE_URI'), + }), + }), + BoardTreesModule, + ], + }).compile(); + + app = module.createNestApplication(); + + await app.init(); + }); + + afterAll(async () => { + await app.close(); + }); +}); From 1e041be1ba92af3f5d5003b2fe780f9bb8881a40 Mon Sep 17 00:00:00 2001 From: Conut-1 <1mim1@naver.com> Date: Tue, 4 Feb 2025 21:55:42 +0900 Subject: [PATCH 08/32] =?UTF-8?q?chore:=20socket.io-client=20=ED=8C=A8?= =?UTF-8?q?=ED=82=A4=EC=A7=80=20=EC=84=A4=EC=B9=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nestjs-BE/server/package-lock.json | 40 ++++++++++++++++++++++++++++++ nestjs-BE/server/package.json | 1 + 2 files changed, 41 insertions(+) diff --git a/nestjs-BE/server/package-lock.json b/nestjs-BE/server/package-lock.json index eacf71a3..65779b59 100644 --- a/nestjs-BE/server/package-lock.json +++ b/nestjs-BE/server/package-lock.json @@ -51,6 +51,7 @@ "jest": "^29.5.0", "prettier": "^3.0.0", "prisma": "^5.6.0", + "socket.io-client": "^4.8.1", "source-map-support": "^0.5.21", "supertest": "^6.3.3", "ts-jest": "^29.1.0", @@ -5926,6 +5927,20 @@ "node": ">=10.2.0" } }, + "node_modules/engine.io-client": { + "version": "6.6.3", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.3.tgz", + "integrity": "sha512-T0iLjnyNWahNyv/lcjS2y4oE358tVS/SYQNxYXGAJ9/GLgH4VCvOQ/mhTjqU88mLZCQgiG8RIegFHYCdVC+j5w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.17.1", + "xmlhttprequest-ssl": "~2.1.1" + } + }, "node_modules/engine.io-parser": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.1.tgz", @@ -9901,6 +9916,22 @@ "ws": "~8.17.1" } }, + "node_modules/socket.io-client": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.1.tgz", + "integrity": "sha512-hJVXfu3E28NmzGk8o1sHhN3om52tRvwYeidbj7xKy2eIIse5IoKX3USlS6Tqt3BHAtflLIkCQBkzVrEEfWUyYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.2", + "engine.io-client": "~6.6.1", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/socket.io-parser": { "version": "4.2.4", "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", @@ -11009,6 +11040,15 @@ } } }, + "node_modules/xmlhttprequest-ssl": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz", + "integrity": "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", diff --git a/nestjs-BE/server/package.json b/nestjs-BE/server/package.json index 53e99d80..f35f96cb 100644 --- a/nestjs-BE/server/package.json +++ b/nestjs-BE/server/package.json @@ -62,6 +62,7 @@ "jest": "^29.5.0", "prettier": "^3.0.0", "prisma": "^5.6.0", + "socket.io-client": "^4.8.1", "source-map-support": "^0.5.21", "supertest": "^6.3.3", "ts-jest": "^29.1.0", From d143d989c3599984161665667013305df8361ba7 Mon Sep 17 00:00:00 2001 From: Conut-1 <1mim1@naver.com> Date: Tue, 4 Feb 2025 22:45:44 +0900 Subject: [PATCH 09/32] =?UTF-8?q?test:=20socket.io-client=20=EB=8F=99?= =?UTF-8?q?=EC=9E=91=20=ED=99=95=EC=9D=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nestjs-BE/server/test/board-trees.e2e-spec.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/nestjs-BE/server/test/board-trees.e2e-spec.ts b/nestjs-BE/server/test/board-trees.e2e-spec.ts index c6465df3..ed7eaf2b 100644 --- a/nestjs-BE/server/test/board-trees.e2e-spec.ts +++ b/nestjs-BE/server/test/board-trees.e2e-spec.ts @@ -3,6 +3,9 @@ import { ConfigModule, ConfigService } from '@nestjs/config'; import { Test, TestingModule } from '@nestjs/testing'; import { MongooseModule } from '@nestjs/mongoose'; import { BoardTreesModule } from '../src/board-trees/board-trees.module'; +import { io } from 'socket.io-client'; + +const PORT = 3000; describe('BoardTreesGateway (e2e)', () => { let app: INestApplication; @@ -25,9 +28,21 @@ describe('BoardTreesGateway (e2e)', () => { app = module.createNestApplication(); await app.init(); + await app.listen(PORT); }); afterAll(async () => { await app.close(); }); + + describe('socket connection', () => { + it('fail', (done) => { + const socket = io(`http://localhost:${PORT}`); + socket.on('connect', () => { + expect('this is connected').toBe('this is connected'); + socket.disconnect(); + done(); + }); + }); + }); }); From 531db28d4ef881db664cc371cc7123ebe22a4e7b Mon Sep 17 00:00:00 2001 From: Conut-1 <1mim1@naver.com> Date: Wed, 5 Feb 2025 22:56:21 +0900 Subject: [PATCH 10/32] =?UTF-8?q?test:=20=EB=84=A4=EC=9E=84=EC=8A=A4?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=8A=A4=EB=A1=9C=20=EC=97=B0=EA=B2=B0?= =?UTF-8?q?=ED=95=98=EB=8F=84=EB=A1=9D=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EB=B3=80=EA=B2=BD=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nestjs-BE/server/test/board-trees.e2e-spec.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/nestjs-BE/server/test/board-trees.e2e-spec.ts b/nestjs-BE/server/test/board-trees.e2e-spec.ts index ed7eaf2b..32e4c731 100644 --- a/nestjs-BE/server/test/board-trees.e2e-spec.ts +++ b/nestjs-BE/server/test/board-trees.e2e-spec.ts @@ -37,10 +37,13 @@ describe('BoardTreesGateway (e2e)', () => { describe('socket connection', () => { it('fail', (done) => { - const socket = io(`http://localhost:${PORT}`); + const socket = io(`ws://localhost:${PORT}/board`); + socket.on('connect', () => { expect('this is connected').toBe('this is connected'); - socket.disconnect(); + }); + + socket.on('disconnect', () => { done(); }); }); From a2a65d13738b846b6850b461ba1709c6cfb77900 Mon Sep 17 00:00:00 2001 From: Conut-1 <1mim1@naver.com> Date: Wed, 5 Feb 2025 22:57:04 +0900 Subject: [PATCH 11/32] =?UTF-8?q?fix:=20=EC=97=90=EB=9F=AC=20throw=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nestjs-BE/server/src/board-trees/board-trees.gateway.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/nestjs-BE/server/src/board-trees/board-trees.gateway.ts b/nestjs-BE/server/src/board-trees/board-trees.gateway.ts index 330b2e53..6e04bda7 100644 --- a/nestjs-BE/server/src/board-trees/board-trees.gateway.ts +++ b/nestjs-BE/server/src/board-trees/board-trees.gateway.ts @@ -1,4 +1,3 @@ -import { UnauthorizedException } from '@nestjs/common'; import { JwtService } from '@nestjs/jwt'; import { OnGatewayConnection, @@ -23,13 +22,11 @@ export class BoardTreesGateway implements OnGatewayConnection { handleConnection(client: Socket, token: string) { if (!token) { client.disconnect(); - throw new UnauthorizedException(); } try { this.jwtService.verify(token); } catch (error) { client.disconnect(); - throw new UnauthorizedException(); } } From 588a59ff00bc399de51637e187c939ca2559ff05 Mon Sep 17 00:00:00 2001 From: Conut-1 <1mim1@naver.com> Date: Thu, 6 Feb 2025 15:15:29 +0900 Subject: [PATCH 12/32] =?UTF-8?q?test:=20=EC=97=B0=EA=B2=B0=20=EC=8B=A4?= =?UTF-8?q?=ED=8C=A8=20=EC=8B=9C=20connect=5Ferror=20=EC=9D=B4=EB=B2=A4?= =?UTF-8?q?=ED=8A=B8=EB=A1=9C=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit connect_error는 이유를 설정할 수 있으므로 connect_error가 일어나는 것으로 변경 --- nestjs-BE/server/test/board-trees.e2e-spec.ts | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/nestjs-BE/server/test/board-trees.e2e-spec.ts b/nestjs-BE/server/test/board-trees.e2e-spec.ts index 32e4c731..7914beb7 100644 --- a/nestjs-BE/server/test/board-trees.e2e-spec.ts +++ b/nestjs-BE/server/test/board-trees.e2e-spec.ts @@ -36,14 +36,11 @@ describe('BoardTreesGateway (e2e)', () => { }); describe('socket connection', () => { - it('fail', (done) => { + it('fail when access token is not included', (done) => { const socket = io(`ws://localhost:${PORT}/board`); - socket.on('connect', () => { - expect('this is connected').toBe('this is connected'); - }); - - socket.on('disconnect', () => { + socket.on('connect_error', (error) => { + expect(error.message).toBe('access token required'); done(); }); }); From 89c5ab0e0d963082e32ff827c07afb27c5943cbe Mon Sep 17 00:00:00 2001 From: Conut-1 <1mim1@naver.com> Date: Thu, 6 Feb 2025 15:15:48 +0900 Subject: [PATCH 13/32] =?UTF-8?q?refactor:=20import=20=EC=88=9C=EC=84=9C?= =?UTF-8?q?=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nestjs-BE/server/test/board-trees.e2e-spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nestjs-BE/server/test/board-trees.e2e-spec.ts b/nestjs-BE/server/test/board-trees.e2e-spec.ts index 7914beb7..4db77a75 100644 --- a/nestjs-BE/server/test/board-trees.e2e-spec.ts +++ b/nestjs-BE/server/test/board-trees.e2e-spec.ts @@ -2,8 +2,8 @@ import { INestApplication } from '@nestjs/common'; import { ConfigModule, ConfigService } from '@nestjs/config'; import { Test, TestingModule } from '@nestjs/testing'; import { MongooseModule } from '@nestjs/mongoose'; -import { BoardTreesModule } from '../src/board-trees/board-trees.module'; import { io } from 'socket.io-client'; +import { BoardTreesModule } from '../src/board-trees/board-trees.module'; const PORT = 3000; From 229977ad3edccd829ab1c344da2f29d623ac2912 Mon Sep 17 00:00:00 2001 From: Conut-1 <1mim1@naver.com> Date: Thu, 6 Feb 2025 15:22:05 +0900 Subject: [PATCH 14/32] =?UTF-8?q?refactor:=20=EC=84=9C=EB=B2=84=20url=20?= =?UTF-8?q?=EB=B3=80=EC=88=98=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nestjs-BE/server/test/board-trees.e2e-spec.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/nestjs-BE/server/test/board-trees.e2e-spec.ts b/nestjs-BE/server/test/board-trees.e2e-spec.ts index 4db77a75..ad6ca6f4 100644 --- a/nestjs-BE/server/test/board-trees.e2e-spec.ts +++ b/nestjs-BE/server/test/board-trees.e2e-spec.ts @@ -36,8 +36,10 @@ describe('BoardTreesGateway (e2e)', () => { }); describe('socket connection', () => { + const serverUrl = `ws://localhost:${PORT}/board`; + it('fail when access token is not included', (done) => { - const socket = io(`ws://localhost:${PORT}/board`); + const socket = io(serverUrl); socket.on('connect_error', (error) => { expect(error.message).toBe('access token required'); From 33d9eba6f607af2ab30ab8423e442a1158c38fca Mon Sep 17 00:00:00 2001 From: Conut-1 <1mim1@naver.com> Date: Thu, 6 Feb 2025 15:58:17 +0900 Subject: [PATCH 15/32] =?UTF-8?q?test:=20=EC=9C=A0=ED=9A=A8=ED=95=98?= =?UTF-8?q?=EC=A7=80=20=EC=95=8A=EC=9D=80=20=ED=86=A0=ED=81=B0=20=EC=97=B0?= =?UTF-8?q?=EA=B2=B0=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nestjs-BE/server/test/board-trees.e2e-spec.ts | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/nestjs-BE/server/test/board-trees.e2e-spec.ts b/nestjs-BE/server/test/board-trees.e2e-spec.ts index ad6ca6f4..f8ce9897 100644 --- a/nestjs-BE/server/test/board-trees.e2e-spec.ts +++ b/nestjs-BE/server/test/board-trees.e2e-spec.ts @@ -2,13 +2,19 @@ import { INestApplication } from '@nestjs/common'; import { ConfigModule, ConfigService } from '@nestjs/config'; import { Test, TestingModule } from '@nestjs/testing'; import { MongooseModule } from '@nestjs/mongoose'; +import { sign } from 'jsonwebtoken'; import { io } from 'socket.io-client'; +import { v4 as uuid } from 'uuid'; import { BoardTreesModule } from '../src/board-trees/board-trees.module'; +import { PrismaModule } from '../src/prisma/prisma.module'; +import { PrismaService } from '../src/prisma/prisma.service'; const PORT = 3000; describe('BoardTreesGateway (e2e)', () => { let app: INestApplication; + let prisma: PrismaService; + let config: ConfigService; beforeAll(async () => { const module: TestingModule = await Test.createTestingModule({ @@ -22,6 +28,7 @@ describe('BoardTreesGateway (e2e)', () => { }), }), BoardTreesModule, + PrismaModule, ], }).compile(); @@ -29,6 +36,9 @@ describe('BoardTreesGateway (e2e)', () => { await app.init(); await app.listen(PORT); + + prisma = module.get(PrismaService); + config = module.get(ConfigService); }); afterAll(async () => { @@ -46,5 +56,23 @@ describe('BoardTreesGateway (e2e)', () => { done(); }); }); + + it('fail when access token is invalid', async () => { + const testUser = await prisma.user.create({ data: { uuid: uuid() } }); + const testToken = sign( + { sub: testUser.uuid }, + config.get('JWT_ACCESS_SECRET'), + { expiresIn: '-5m' }, + ); + + const error: Error = await new Promise((resolve) => { + const socket = io(serverUrl, { auth: { token: testToken } }); + socket.on('connect_error', (error) => { + resolve(error); + }); + }); + + expect(error.message).toBe('token is invalid'); + }); }); }); From 96df96530f210e344356f6c1e3bdc61eb5e1753e Mon Sep 17 00:00:00 2001 From: Conut-1 <1mim1@naver.com> Date: Thu, 6 Feb 2025 16:00:05 +0900 Subject: [PATCH 16/32] =?UTF-8?q?fix:=20=EC=82=AC=EC=9A=A9=EC=9E=90=20?= =?UTF-8?q?=EC=9D=B8=EC=A6=9D=EC=9D=84=20afterInit=EC=97=90=EC=84=9C=20?= =?UTF-8?q?=EC=B2=98=EB=A6=AC=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit socket.io 서버에 미들웨어를 등록해서 사용자 인증을 처리리 --- .../src/board-trees/board-trees.gateway.ts | 27 +++++++++++-------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/nestjs-BE/server/src/board-trees/board-trees.gateway.ts b/nestjs-BE/server/src/board-trees/board-trees.gateway.ts index 6e04bda7..17049ae5 100644 --- a/nestjs-BE/server/src/board-trees/board-trees.gateway.ts +++ b/nestjs-BE/server/src/board-trees/board-trees.gateway.ts @@ -1,16 +1,17 @@ import { JwtService } from '@nestjs/jwt'; import { - OnGatewayConnection, + OnGatewayInit, SubscribeMessage, WebSocketGateway, WebSocketServer, + WsException, } from '@nestjs/websockets'; import { Server, Socket } from 'socket.io'; import { BoardTreesService } from './board-trees.service'; import type { BoardOperation } from './schemas/board-operation.schema'; @WebSocketGateway({ namespace: 'board' }) -export class BoardTreesGateway implements OnGatewayConnection { +export class BoardTreesGateway implements OnGatewayInit { constructor( private boardTreesService: BoardTreesService, private jwtService: JwtService, @@ -19,15 +20,19 @@ export class BoardTreesGateway implements OnGatewayConnection { @WebSocketServer() server: Server; - handleConnection(client: Socket, token: string) { - if (!token) { - client.disconnect(); - } - try { - this.jwtService.verify(token); - } catch (error) { - client.disconnect(); - } + afterInit(server: Server) { + server.use((socket, next) => { + const token = socket.handshake.auth.token; + if (!token) { + next(new WsException('access token required')); + } + try { + this.jwtService.verify(token); + next(); + } catch (error) { + next(new WsException('token is invalid')); + } + }); } @SubscribeMessage('joinBoard') From 70685599d07ad9161ec942ab3e39f03b1430218b Mon Sep 17 00:00:00 2001 From: Conut-1 <1mim1@naver.com> Date: Thu, 6 Feb 2025 16:19:52 +0900 Subject: [PATCH 17/32] =?UTF-8?q?test:=20=EC=97=B0=EA=B2=B0=20=EC=84=B1?= =?UTF-8?q?=EA=B3=B5=20=ED=85=8C=EC=8A=A4=ED=8A=B8=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nestjs-BE/server/test/board-trees.e2e-spec.ts | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/nestjs-BE/server/test/board-trees.e2e-spec.ts b/nestjs-BE/server/test/board-trees.e2e-spec.ts index f8ce9897..26e5a0cd 100644 --- a/nestjs-BE/server/test/board-trees.e2e-spec.ts +++ b/nestjs-BE/server/test/board-trees.e2e-spec.ts @@ -74,5 +74,25 @@ describe('BoardTreesGateway (e2e)', () => { expect(error.message).toBe('token is invalid'); }); + + it('success', async () => { + const testUser = await prisma.user.create({ data: { uuid: uuid() } }); + const testToken = sign( + { sub: testUser.uuid }, + config.get('JWT_ACCESS_SECRET'), + { expiresIn: '5m' }, + ); + + const connected = await new Promise((resolve) => { + const socket = io(serverUrl, { auth: { token: testToken } }); + socket.on('connect', () => { + const connected = socket.connected; + socket.disconnect(); + resolve(connected); + }); + }); + + expect(connected).toBeTruthy(); + }); }); }); From b9a55c735de54d03d17e82d2364d99be080ab56a Mon Sep 17 00:00:00 2001 From: Conut-1 <1mim1@naver.com> Date: Thu, 6 Feb 2025 16:20:30 +0900 Subject: [PATCH 18/32] =?UTF-8?q?fix:=20secret=20=EC=98=B5=EC=85=98=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nestjs-BE/server/src/board-trees/board-trees.gateway.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/nestjs-BE/server/src/board-trees/board-trees.gateway.ts b/nestjs-BE/server/src/board-trees/board-trees.gateway.ts index 17049ae5..47f1ca0d 100644 --- a/nestjs-BE/server/src/board-trees/board-trees.gateway.ts +++ b/nestjs-BE/server/src/board-trees/board-trees.gateway.ts @@ -6,6 +6,7 @@ import { WebSocketServer, WsException, } from '@nestjs/websockets'; +import { ConfigService } from '@nestjs/config'; import { Server, Socket } from 'socket.io'; import { BoardTreesService } from './board-trees.service'; import type { BoardOperation } from './schemas/board-operation.schema'; @@ -15,6 +16,7 @@ export class BoardTreesGateway implements OnGatewayInit { constructor( private boardTreesService: BoardTreesService, private jwtService: JwtService, + private configService: ConfigService, ) {} @WebSocketServer() @@ -27,7 +29,9 @@ export class BoardTreesGateway implements OnGatewayInit { next(new WsException('access token required')); } try { - this.jwtService.verify(token); + this.jwtService.verify(token, { + secret: this.configService.get('JWT_ACCESS_SECRET'), + }); next(); } catch (error) { next(new WsException('token is invalid')); From 1f670f576bf5d35b808491b2a1e5329c7a19213c Mon Sep 17 00:00:00 2001 From: Conut-1 <1mim1@naver.com> Date: Fri, 7 Feb 2025 20:26:10 +0900 Subject: [PATCH 19/32] =?UTF-8?q?test:=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=9D=B4=EB=A6=84=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nestjs-BE/server/test/board-trees.e2e-spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nestjs-BE/server/test/board-trees.e2e-spec.ts b/nestjs-BE/server/test/board-trees.e2e-spec.ts index 26e5a0cd..05e8fa69 100644 --- a/nestjs-BE/server/test/board-trees.e2e-spec.ts +++ b/nestjs-BE/server/test/board-trees.e2e-spec.ts @@ -45,7 +45,7 @@ describe('BoardTreesGateway (e2e)', () => { await app.close(); }); - describe('socket connection', () => { + describe('socket connection authentication', () => { const serverUrl = `ws://localhost:${PORT}/board`; it('fail when access token is not included', (done) => { From a4b4d29480f391fe44df9593ded9f47cf31a0faa Mon Sep 17 00:00:00 2001 From: Conut-1 <1mim1@naver.com> Date: Fri, 7 Feb 2025 20:27:56 +0900 Subject: [PATCH 20/32] =?UTF-8?q?test:=20=EC=97=B0=EA=B2=B0=20=EC=8B=9C=20?= =?UTF-8?q?boardId=20=ED=99=95=EC=9D=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nestjs-BE/server/test/board-trees.e2e-spec.ts | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/nestjs-BE/server/test/board-trees.e2e-spec.ts b/nestjs-BE/server/test/board-trees.e2e-spec.ts index 05e8fa69..9e07dd0f 100644 --- a/nestjs-BE/server/test/board-trees.e2e-spec.ts +++ b/nestjs-BE/server/test/board-trees.e2e-spec.ts @@ -95,4 +95,31 @@ describe('BoardTreesGateway (e2e)', () => { expect(connected).toBeTruthy(); }); }); + + describe('join board on connection', () => { + const serverUrl = `ws://localhost:${PORT}/board`; + let testToken: string; + + beforeEach(async () => { + const testUser = await prisma.user.create({ data: { uuid: uuid() } }); + testToken = sign( + { sub: testUser.uuid }, + config.get('JWT_ACCESS_SECRET'), + { expiresIn: '5m' }, + ); + }); + + it('board_id_required error when board id not included', async () => { + const error: Error = await new Promise((resolve) => { + const socket = io(serverUrl, { + auth: { token: testToken }, + }); + socket.on('board_id_required', (error) => { + resolve(error); + }); + }); + + expect(error.message).toBe('board id required'); + }); + }); }); From f62c9c7e34d125ec3225030eb71b761c3f3ff537 Mon Sep 17 00:00:00 2001 From: Conut-1 <1mim1@naver.com> Date: Fri, 7 Feb 2025 20:30:11 +0900 Subject: [PATCH 21/32] =?UTF-8?q?feat:=20socket.io=20=EC=97=B0=EA=B2=B0=20?= =?UTF-8?q?=EC=8B=9C=EC=97=90=20=EB=B3=B4=EB=93=9C=20room=EC=97=90=20join?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/board-trees/board-trees.gateway.ts | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/nestjs-BE/server/src/board-trees/board-trees.gateway.ts b/nestjs-BE/server/src/board-trees/board-trees.gateway.ts index 47f1ca0d..d32e3afe 100644 --- a/nestjs-BE/server/src/board-trees/board-trees.gateway.ts +++ b/nestjs-BE/server/src/board-trees/board-trees.gateway.ts @@ -1,5 +1,6 @@ import { JwtService } from '@nestjs/jwt'; import { + OnGatewayConnection, OnGatewayInit, SubscribeMessage, WebSocketGateway, @@ -12,7 +13,7 @@ import { BoardTreesService } from './board-trees.service'; import type { BoardOperation } from './schemas/board-operation.schema'; @WebSocketGateway({ namespace: 'board' }) -export class BoardTreesGateway implements OnGatewayInit { +export class BoardTreesGateway implements OnGatewayInit, OnGatewayConnection { constructor( private boardTreesService: BoardTreesService, private jwtService: JwtService, @@ -39,11 +40,16 @@ export class BoardTreesGateway implements OnGatewayInit { }); } - @SubscribeMessage('joinBoard') - async handleJoinBoard(client: Socket, payload: string) { - const payloadObject = JSON.parse(payload); - client.join(payloadObject.boardId); - client.emit('boardJoined'); + handleConnection(client: Socket) { + const query = client.handshake.query; + const boardId = query.boardId; + + if (!boardId) { + client.emit('board_id_required', new WsException('board id required')); + client.disconnect(); + } + client.join(boardId); + client.emit('board_joined', boardId); } @SubscribeMessage('createOperation') From d2c116e16f0d86d058270753f8f2b881149f1da0 Mon Sep 17 00:00:00 2001 From: Conut-1 <1mim1@naver.com> Date: Fri, 7 Feb 2025 20:35:00 +0900 Subject: [PATCH 22/32] =?UTF-8?q?fix:=20Error=EB=A5=BC=20resolve=EB=8C=80?= =?UTF-8?q?=EC=8B=A0=20reject=EB=A1=9C=20=EB=B0=9B=EA=B8=B0=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nestjs-BE/server/test/board-trees.e2e-spec.ts | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/nestjs-BE/server/test/board-trees.e2e-spec.ts b/nestjs-BE/server/test/board-trees.e2e-spec.ts index 9e07dd0f..e7519311 100644 --- a/nestjs-BE/server/test/board-trees.e2e-spec.ts +++ b/nestjs-BE/server/test/board-trees.e2e-spec.ts @@ -65,14 +65,14 @@ describe('BoardTreesGateway (e2e)', () => { { expiresIn: '-5m' }, ); - const error: Error = await new Promise((resolve) => { + const error = new Promise((resolve, reject) => { const socket = io(serverUrl, { auth: { token: testToken } }); socket.on('connect_error', (error) => { - resolve(error); + reject(error); }); }); - expect(error.message).toBe('token is invalid'); + await expect(error).rejects.toHaveProperty('message', 'token is invalid'); }); it('success', async () => { @@ -110,16 +110,19 @@ describe('BoardTreesGateway (e2e)', () => { }); it('board_id_required error when board id not included', async () => { - const error: Error = await new Promise((resolve) => { + const error = new Promise((resolve, reject) => { const socket = io(serverUrl, { auth: { token: testToken }, }); socket.on('board_id_required', (error) => { - resolve(error); + reject(error); }); }); - expect(error.message).toBe('board id required'); + await expect(error).rejects.toHaveProperty( + 'message', + 'board id required', + ); }); }); }); From effa9ddd5f6fddc27c4b96892c3ee0dc3b391431 Mon Sep 17 00:00:00 2001 From: Conut-1 <1mim1@naver.com> Date: Fri, 7 Feb 2025 20:48:06 +0900 Subject: [PATCH 23/32] =?UTF-8?q?test:=20=EB=B3=B4=EB=93=9C=20=EC=9E=85?= =?UTF-8?q?=EC=9E=A5=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nestjs-BE/server/test/board-trees.e2e-spec.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/nestjs-BE/server/test/board-trees.e2e-spec.ts b/nestjs-BE/server/test/board-trees.e2e-spec.ts index e7519311..aa815f5f 100644 --- a/nestjs-BE/server/test/board-trees.e2e-spec.ts +++ b/nestjs-BE/server/test/board-trees.e2e-spec.ts @@ -124,5 +124,22 @@ describe('BoardTreesGateway (e2e)', () => { 'board id required', ); }); + + it('join board', async () => { + const boardId = 'board id'; + + const response = await new Promise((resolve) => { + const socket = io(serverUrl, { + auth: { token: testToken }, + query: { boardId }, + }); + socket.on('board_joined', (boardId) => { + socket.disconnect(); + resolve(boardId); + }); + }); + + expect(response).toBe(boardId); + }); }); }); From fb6568cccaf533504af2629f06167b1646e79de4 Mon Sep 17 00:00:00 2001 From: Conut-1 <1mim1@naver.com> Date: Sat, 8 Feb 2025 12:51:38 +0900 Subject: [PATCH 24/32] =?UTF-8?q?refactor:=20serverUrl=20=EC=83=81?= =?UTF-8?q?=EC=9C=84=20=EC=8A=A4=EC=BD=94=ED=94=84=EB=A1=9C=20=EC=9D=B4?= =?UTF-8?q?=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nestjs-BE/server/test/board-trees.e2e-spec.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/nestjs-BE/server/test/board-trees.e2e-spec.ts b/nestjs-BE/server/test/board-trees.e2e-spec.ts index aa815f5f..76a7ee3e 100644 --- a/nestjs-BE/server/test/board-trees.e2e-spec.ts +++ b/nestjs-BE/server/test/board-trees.e2e-spec.ts @@ -12,6 +12,7 @@ import { PrismaService } from '../src/prisma/prisma.service'; const PORT = 3000; describe('BoardTreesGateway (e2e)', () => { + const serverUrl = `ws://localhost:${PORT}/board`; let app: INestApplication; let prisma: PrismaService; let config: ConfigService; @@ -46,8 +47,6 @@ describe('BoardTreesGateway (e2e)', () => { }); describe('socket connection authentication', () => { - const serverUrl = `ws://localhost:${PORT}/board`; - it('fail when access token is not included', (done) => { const socket = io(serverUrl); @@ -97,7 +96,6 @@ describe('BoardTreesGateway (e2e)', () => { }); describe('join board on connection', () => { - const serverUrl = `ws://localhost:${PORT}/board`; let testToken: string; beforeEach(async () => { From e4ee3f3bee5905f69214d50a1048232e6bbb4ee0 Mon Sep 17 00:00:00 2001 From: Conut-1 <1mim1@naver.com> Date: Sat, 8 Feb 2025 15:06:22 +0900 Subject: [PATCH 25/32] =?UTF-8?q?test:=20=EC=9E=91=EC=97=85=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20=ED=85=8C=EC=8A=A4=ED=8A=B8=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nestjs-BE/server/test/board-trees.e2e-spec.ts | 58 ++++++++++++++++++- 1 file changed, 57 insertions(+), 1 deletion(-) diff --git a/nestjs-BE/server/test/board-trees.e2e-spec.ts b/nestjs-BE/server/test/board-trees.e2e-spec.ts index 76a7ee3e..a0aa15a0 100644 --- a/nestjs-BE/server/test/board-trees.e2e-spec.ts +++ b/nestjs-BE/server/test/board-trees.e2e-spec.ts @@ -3,9 +3,10 @@ import { ConfigModule, ConfigService } from '@nestjs/config'; import { Test, TestingModule } from '@nestjs/testing'; import { MongooseModule } from '@nestjs/mongoose'; import { sign } from 'jsonwebtoken'; -import { io } from 'socket.io-client'; +import { io, Socket } from 'socket.io-client'; import { v4 as uuid } from 'uuid'; import { BoardTreesModule } from '../src/board-trees/board-trees.module'; +import { BoardTreesService } from '../src/board-trees/board-trees.service'; import { PrismaModule } from '../src/prisma/prisma.module'; import { PrismaService } from '../src/prisma/prisma.service'; @@ -16,6 +17,7 @@ describe('BoardTreesGateway (e2e)', () => { let app: INestApplication; let prisma: PrismaService; let config: ConfigService; + let boardTreesService: BoardTreesService; beforeAll(async () => { const module: TestingModule = await Test.createTestingModule({ @@ -40,6 +42,7 @@ describe('BoardTreesGateway (e2e)', () => { prisma = module.get(PrismaService); config = module.get(ConfigService); + boardTreesService = module.get(BoardTreesService); }); afterAll(async () => { @@ -140,4 +143,57 @@ describe('BoardTreesGateway (e2e)', () => { expect(response).toBe(boardId); }); }); + + describe('createOperation', () => { + const boardId = 'board id'; + let testToken: string; + let client: Socket; + + beforeEach(async () => { + const testUser = await prisma.user.create({ data: { uuid: uuid() } }); + testToken = sign( + { sub: testUser.uuid }, + config.get('JWT_ACCESS_SECRET'), + { expiresIn: '5m' }, + ); + + await new Promise((resolve) => { + client = io(serverUrl, { + auth: { token: testToken }, + query: { boardId }, + }); + client.on('board_joined', () => { + resolve(null); + }); + }); + }); + + afterEach(() => { + if (client.connected) { + client.disconnect(); + } + }); + + it('create operation', async () => { + const testOperation = { + boardId: uuid(), + type: 'add', + parentId: 'root', + content: 'new node', + }; + + await new Promise((resolve) => { + client.on('operationCreated', () => { + resolve(null); + }); + + client.emit('createOperation', testOperation); + }); + + const operations = await boardTreesService.getOperationLogs( + testOperation.boardId, + ); + expect(operations).toContainEqual(testOperation); + }); + }); }); From edf825eebdca6cbd37ac22c4675676b5832c5652 Mon Sep 17 00:00:00 2001 From: Conut-1 <1mim1@naver.com> Date: Sat, 8 Feb 2025 15:10:52 +0900 Subject: [PATCH 26/32] =?UTF-8?q?fix:=20=EC=9E=91=EC=97=85=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20=EC=9D=B8=EC=9E=90=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nestjs-BE/server/src/board-trees/board-trees.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nestjs-BE/server/src/board-trees/board-trees.service.ts b/nestjs-BE/server/src/board-trees/board-trees.service.ts index e8179aa8..4b8cc887 100644 --- a/nestjs-BE/server/src/board-trees/board-trees.service.ts +++ b/nestjs-BE/server/src/board-trees/board-trees.service.ts @@ -62,7 +62,7 @@ export class BoardTreesService { } async createOperationLog(operation: BoardOperation) { - return this.boardOperationModel.create({ operation }); + return this.boardOperationModel.create(operation); } async getOperationLogs(boardId: string) { From 5fde4efed5cf669ecbcb8530478ed1f4262ccc88 Mon Sep 17 00:00:00 2001 From: Conut-1 <1mim1@naver.com> Date: Sat, 8 Feb 2025 15:11:50 +0900 Subject: [PATCH 27/32] =?UTF-8?q?fix:=20select,=20lean=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit select를 사용해서 가져올 필드를 설정한다. lean은 단순한 객체를 생성하여 데이터 수정이 없는 경우에 빠르게 생성한다. --- nestjs-BE/server/src/board-trees/board-trees.service.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/nestjs-BE/server/src/board-trees/board-trees.service.ts b/nestjs-BE/server/src/board-trees/board-trees.service.ts index 4b8cc887..1fe2c693 100644 --- a/nestjs-BE/server/src/board-trees/board-trees.service.ts +++ b/nestjs-BE/server/src/board-trees/board-trees.service.ts @@ -66,6 +66,9 @@ export class BoardTreesService { } async getOperationLogs(boardId: string) { - return this.boardOperationModel.find({ boardId }); + return this.boardOperationModel + .find({ boardId }) + .select('-_id -__v') + .lean(); } } From d1f59c1cacacc24ce9dbf99d161c1942eb6a88a2 Mon Sep 17 00:00:00 2001 From: Conut-1 <1mim1@naver.com> Date: Sat, 8 Feb 2025 15:12:16 +0900 Subject: [PATCH 28/32] =?UTF-8?q?feat:=20=EC=9E=91=EC=97=85=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=EC=97=90=20=EB=8C=80=ED=95=9C=20=EC=9D=91=EB=8B=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nestjs-BE/server/src/board-trees/board-trees.gateway.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/nestjs-BE/server/src/board-trees/board-trees.gateway.ts b/nestjs-BE/server/src/board-trees/board-trees.gateway.ts index d32e3afe..72eabc92 100644 --- a/nestjs-BE/server/src/board-trees/board-trees.gateway.ts +++ b/nestjs-BE/server/src/board-trees/board-trees.gateway.ts @@ -56,6 +56,7 @@ export class BoardTreesGateway implements OnGatewayInit, OnGatewayConnection { async handleCreateOperation(client: Socket, operation: BoardOperation) { await this.boardTreesService.createOperationLog(operation); client.broadcast.to(operation.boardId).emit('operation', operation); + client.emit('operationCreated'); } @SubscribeMessage('getOperations') From 9d396c5b03d3219f5cf8a57198db54006a602d32 Mon Sep 17 00:00:00 2001 From: Conut-1 <1mim1@naver.com> Date: Sat, 8 Feb 2025 15:25:59 +0900 Subject: [PATCH 29/32] =?UTF-8?q?test:=20=EB=8B=A4=EB=A5=B8=20=EC=9C=A0?= =?UTF-8?q?=EC=A0=80=EC=97=90=20=EC=9E=91=EC=97=85=20=EB=B3=B4=EB=82=B4?= =?UTF-8?q?=EA=B8=B0=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nestjs-BE/server/test/board-trees.e2e-spec.ts | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/nestjs-BE/server/test/board-trees.e2e-spec.ts b/nestjs-BE/server/test/board-trees.e2e-spec.ts index a0aa15a0..11216543 100644 --- a/nestjs-BE/server/test/board-trees.e2e-spec.ts +++ b/nestjs-BE/server/test/board-trees.e2e-spec.ts @@ -195,5 +195,44 @@ describe('BoardTreesGateway (e2e)', () => { ); expect(operations).toContainEqual(testOperation); }); + + it('other client received operation', async () => { + let otherClient: Socket; + + const otherUser = await prisma.user.create({ data: { uuid: uuid() } }); + const otherToken = sign( + { sub: otherUser.uuid }, + config.get('JWT_ACCESS_SECRET'), + { expiresIn: '5m' }, + ); + + await new Promise((resolve) => { + otherClient = io(serverUrl, { + auth: { token: otherToken }, + query: { boardId }, + }); + otherClient.on('board_joined', () => { + resolve(null); + }); + }); + + const testOperation = { + boardId, + type: 'add', + parentId: 'root', + content: 'new node', + }; + + const response = await new Promise((resolve) => { + otherClient.on('operation', (operation) => { + otherClient.disconnect(); + resolve(operation); + }); + + client.emit('createOperation', testOperation); + }); + + expect(response).toEqual(testOperation); + }); }); }); From e5bfdc4327fa8a4301057953b4601226a2c2ced8 Mon Sep 17 00:00:00 2001 From: Conut-1 <1mim1@naver.com> Date: Sun, 9 Feb 2025 15:37:26 +0900 Subject: [PATCH 30/32] =?UTF-8?q?test:=20=EC=9E=91=EC=97=85=20=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=20=EA=B0=80=EC=A0=B8=EC=98=A4=EA=B8=B0=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nestjs-BE/server/test/board-trees.e2e-spec.ts | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/nestjs-BE/server/test/board-trees.e2e-spec.ts b/nestjs-BE/server/test/board-trees.e2e-spec.ts index 11216543..76b57177 100644 --- a/nestjs-BE/server/test/board-trees.e2e-spec.ts +++ b/nestjs-BE/server/test/board-trees.e2e-spec.ts @@ -9,6 +9,7 @@ import { BoardTreesModule } from '../src/board-trees/board-trees.module'; import { BoardTreesService } from '../src/board-trees/board-trees.service'; import { PrismaModule } from '../src/prisma/prisma.module'; import { PrismaService } from '../src/prisma/prisma.service'; +import type { BoardOperation } from '../src/board-trees/schemas/board-operation.schema'; const PORT = 3000; @@ -235,4 +236,62 @@ describe('BoardTreesGateway (e2e)', () => { expect(response).toEqual(testOperation); }); }); + + describe('getOperations', () => { + const boardId = uuid(); + let testToken: string; + let testOperations: BoardOperation[]; + let client: Socket; + + beforeEach(async () => { + const testUser = await prisma.user.create({ data: { uuid: uuid() } }); + testToken = sign( + { sub: testUser.uuid }, + config.get('JWT_ACCESS_SECRET'), + { expiresIn: '5m' }, + ); + + await new Promise((resolve) => { + client = io(serverUrl, { + auth: { token: testToken }, + query: { boardId }, + }); + client.on('board_joined', () => { + resolve(null); + }); + }); + + testOperations = Array.from({ length: 5 }, () => { + return { + boardId, + type: 'add', + parentId: 'root', + content: 'new node', + } as BoardOperation; + }); + await Promise.all( + testOperations.map((operation) => + boardTreesService.createOperationLog(operation as BoardOperation), + ), + ); + }); + + afterEach(() => { + if (client.connected) { + client.disconnect(); + } + }); + + it('get operation logs', async () => { + const response = await new Promise((resolve) => { + client.on('getOperations', (operationLogs) => { + resolve(operationLogs); + }); + + client.emit('getOperations', boardId); + }); + + expect(response).toEqual(expect.arrayContaining(testOperations)); + }); + }); }); From 9f16602e74ac8ee9b44c5b12c1b7b418e471f006 Mon Sep 17 00:00:00 2001 From: Conut-1 <1mim1@naver.com> Date: Sun, 9 Feb 2025 16:24:10 +0900 Subject: [PATCH 31/32] =?UTF-8?q?refactor:=20=ED=81=B4=EB=9D=BC=EC=9D=B4?= =?UTF-8?q?=EC=96=B8=ED=8A=B8=20=EC=83=9D=EC=84=B1=20=ED=95=A8=EC=88=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nestjs-BE/server/test/board-trees.e2e-spec.ts | 51 +++++++++---------- 1 file changed, 25 insertions(+), 26 deletions(-) diff --git a/nestjs-BE/server/test/board-trees.e2e-spec.ts b/nestjs-BE/server/test/board-trees.e2e-spec.ts index 76b57177..cdd46b9e 100644 --- a/nestjs-BE/server/test/board-trees.e2e-spec.ts +++ b/nestjs-BE/server/test/board-trees.e2e-spec.ts @@ -9,6 +9,8 @@ import { BoardTreesModule } from '../src/board-trees/board-trees.module'; import { BoardTreesService } from '../src/board-trees/board-trees.service'; import { PrismaModule } from '../src/prisma/prisma.module'; import { PrismaService } from '../src/prisma/prisma.service'; + +import type { ManagerOptions, SocketOptions } from 'socket.io-client'; import type { BoardOperation } from '../src/board-trees/schemas/board-operation.schema'; const PORT = 3000; @@ -158,14 +160,9 @@ describe('BoardTreesGateway (e2e)', () => { { expiresIn: '5m' }, ); - await new Promise((resolve) => { - client = io(serverUrl, { - auth: { token: testToken }, - query: { boardId }, - }); - client.on('board_joined', () => { - resolve(null); - }); + client = await createClientSocket(serverUrl, { + auth: { token: testToken }, + query: { boardId }, }); }); @@ -198,8 +195,6 @@ describe('BoardTreesGateway (e2e)', () => { }); it('other client received operation', async () => { - let otherClient: Socket; - const otherUser = await prisma.user.create({ data: { uuid: uuid() } }); const otherToken = sign( { sub: otherUser.uuid }, @@ -207,14 +202,9 @@ describe('BoardTreesGateway (e2e)', () => { { expiresIn: '5m' }, ); - await new Promise((resolve) => { - otherClient = io(serverUrl, { - auth: { token: otherToken }, - query: { boardId }, - }); - otherClient.on('board_joined', () => { - resolve(null); - }); + const otherClient = await createClientSocket(serverUrl, { + auth: { token: otherToken }, + query: { boardId }, }); const testOperation = { @@ -251,14 +241,9 @@ describe('BoardTreesGateway (e2e)', () => { { expiresIn: '5m' }, ); - await new Promise((resolve) => { - client = io(serverUrl, { - auth: { token: testToken }, - query: { boardId }, - }); - client.on('board_joined', () => { - resolve(null); - }); + client = await createClientSocket(serverUrl, { + auth: { token: testToken }, + query: { boardId }, }); testOperations = Array.from({ length: 5 }, () => { @@ -295,3 +280,17 @@ describe('BoardTreesGateway (e2e)', () => { }); }); }); + +async function createClientSocket( + uri: string, + opts: Partial, +) { + let client: Socket; + await new Promise((resolve) => { + client = io(uri, opts); + client.on('board_joined', () => { + resolve(null); + }); + }); + return client; +} From e32b012758c77214431bbaf38acc848965d73a38 Mon Sep 17 00:00:00 2001 From: Conut-1 <1mim1@naver.com> Date: Sun, 9 Feb 2025 16:35:38 +0900 Subject: [PATCH 32/32] =?UTF-8?q?refactor:=20=ED=86=A0=ED=81=B0=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1=20=ED=95=A8=EC=88=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nestjs-BE/server/test/board-trees.e2e-spec.ts | 48 ++++++------------- 1 file changed, 15 insertions(+), 33 deletions(-) diff --git a/nestjs-BE/server/test/board-trees.e2e-spec.ts b/nestjs-BE/server/test/board-trees.e2e-spec.ts index cdd46b9e..8e7b89b9 100644 --- a/nestjs-BE/server/test/board-trees.e2e-spec.ts +++ b/nestjs-BE/server/test/board-trees.e2e-spec.ts @@ -81,12 +81,7 @@ describe('BoardTreesGateway (e2e)', () => { }); it('success', async () => { - const testUser = await prisma.user.create({ data: { uuid: uuid() } }); - const testToken = sign( - { sub: testUser.uuid }, - config.get('JWT_ACCESS_SECRET'), - { expiresIn: '5m' }, - ); + const testToken = await createUserToken(prisma, config); const connected = await new Promise((resolve) => { const socket = io(serverUrl, { auth: { token: testToken } }); @@ -105,12 +100,7 @@ describe('BoardTreesGateway (e2e)', () => { let testToken: string; beforeEach(async () => { - const testUser = await prisma.user.create({ data: { uuid: uuid() } }); - testToken = sign( - { sub: testUser.uuid }, - config.get('JWT_ACCESS_SECRET'), - { expiresIn: '5m' }, - ); + testToken = await createUserToken(prisma, config); }); it('board_id_required error when board id not included', async () => { @@ -153,13 +143,7 @@ describe('BoardTreesGateway (e2e)', () => { let client: Socket; beforeEach(async () => { - const testUser = await prisma.user.create({ data: { uuid: uuid() } }); - testToken = sign( - { sub: testUser.uuid }, - config.get('JWT_ACCESS_SECRET'), - { expiresIn: '5m' }, - ); - + testToken = await createUserToken(prisma, config); client = await createClientSocket(serverUrl, { auth: { token: testToken }, query: { boardId }, @@ -195,13 +179,7 @@ describe('BoardTreesGateway (e2e)', () => { }); it('other client received operation', async () => { - const otherUser = await prisma.user.create({ data: { uuid: uuid() } }); - const otherToken = sign( - { sub: otherUser.uuid }, - config.get('JWT_ACCESS_SECRET'), - { expiresIn: '5m' }, - ); - + const otherToken = await createUserToken(prisma, config); const otherClient = await createClientSocket(serverUrl, { auth: { token: otherToken }, query: { boardId }, @@ -234,13 +212,7 @@ describe('BoardTreesGateway (e2e)', () => { let client: Socket; beforeEach(async () => { - const testUser = await prisma.user.create({ data: { uuid: uuid() } }); - testToken = sign( - { sub: testUser.uuid }, - config.get('JWT_ACCESS_SECRET'), - { expiresIn: '5m' }, - ); - + testToken = await createUserToken(prisma, config); client = await createClientSocket(serverUrl, { auth: { token: testToken }, query: { boardId }, @@ -281,6 +253,16 @@ describe('BoardTreesGateway (e2e)', () => { }); }); +async function createUserToken(prisma: PrismaService, config: ConfigService) { + const user = await prisma.user.create({ data: { uuid: uuid() } }); + const token = sign( + { sub: user.uuid }, + config.get('JWT_ACCESS_SECRET'), + { expiresIn: '5m' }, + ); + return token; +} + async function createClientSocket( uri: string, opts: Partial,