Skip to content

Commit 49e4fe8

Browse files
authored
Merge pull request #101 from boostcampwm-2024/develop
v1.3.0 배포
2 parents 00f78e6 + d8edc8f commit 49e4fe8

File tree

165 files changed

+2618
-578
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

165 files changed

+2618
-578
lines changed

.github/workflows/cd-pipeline.yml

+2
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ jobs:
5454
docker build -f ./services/backend/Dockerfile.prod -t growth123/octodocs-backend . &
5555
docker build -f ./services/nginx/Dockerfile.prod -t growth123/octodocs-nginx . &
5656
docker build -f ./services/websocket/Dockerfile.prod -t growth123/octodocs-websocket . &
57+
docker build -f ./services/scheduler/Dockerfile.prod -t growth123/octodocs-scheduler . &
5758
wait
5859
5960
# Docker 이미지 푸시
@@ -63,6 +64,7 @@ jobs:
6364
docker push growth123/octodocs-backend &
6465
docker push growth123/octodocs-nginx &
6566
docker push growth123/octodocs-websocket &
67+
docker push growth123/octodocs-scheduler &
6668
wait
6769
6870
deploy:

apps/backend/nest-cli.json

+3-2
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,6 @@
44
"sourceRoot": "src",
55
"compilerOptions": {
66
"deleteOutDir": true
7-
}
8-
}
7+
},
8+
"entryFile": "./apps/backend/src/main.js"
9+
}

apps/backend/package.json

+21-5
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,9 @@
2121
},
2222
"dependencies": {
2323
"@aws-sdk/client-s3": "^3.693.0",
24-
"@huggingface/transformers": "^3.3.3",
25-
"@langchain/community": "^0.3.28",
26-
"@langchain/core": "^0.3.37",
27-
"@langchain/openai": "^0.4.2",
24+
"@langchain/community": "^0.3.30",
25+
"@langchain/core": "^0.3.40",
26+
"@langchain/openai": "^0.4.4",
2827
"@langchain/textsplitters": "^0.1.0",
2928
"@nestjs/common": "^10.0.0",
3029
"@nestjs/config": "^3.3.0",
@@ -36,6 +35,7 @@
3635
"@nestjs/platform-socket.io": "^10.4.8",
3736
"@nestjs/platform-ws": "^10.4.7",
3837
"@nestjs/schedule": "^4.1.1",
38+
"@nestjs/schematics": "^11.0.0",
3939
"@nestjs/serve-static": "^4.0.2",
4040
"@nestjs/swagger": "^8.0.5",
4141
"@nestjs/typeorm": "^10.0.2",
@@ -107,6 +107,22 @@
107107
"**/*.(t|j)s"
108108
],
109109
"coverageDirectory": "../coverage",
110-
"testEnvironment": "node"
110+
"testEnvironment": "node",
111+
"moduleDirectories": [
112+
"node_modules",
113+
"src"
114+
],
115+
"moduleNameMapper": {
116+
"@root/(.*)$": "<rootDir>/../../$1",
117+
"@app/redis/(.*)$": "<rootDir>/../../../libs/redis/src/$1",
118+
"@app/page/(.*)$": "<rootDir>/../../../libs/page/src/$1",
119+
"@app/node/(.*)$": "<rootDir>/../../../libs/node/src/$1",
120+
"@app/edge/(.*)$": "<rootDir>/../../../libs/edge/src/$1",
121+
"@app/workspace/(.*)$": "<rootDir>/../../../libs/workspace/src/$1",
122+
"@app/user/(.*)$": "<rootDir>/../../../libs/user/src/$1",
123+
"@app/role/(.*)$": "<rootDir>/../../../libs/role/src/$1",
124+
"@app/token/(.*)$": "<rootDir>/../../../libs/token/src/$1",
125+
"@app/exception/(.*)$": "<rootDir>/../../../libs/exception/$1"
126+
}
111127
}
112128
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { Test, TestingModule } from '@nestjs/testing';
2+
import { ChatAbortController } from './chat-abort.controller';
3+
import { ChatAbortService } from './chat-abort.service';
4+
import { AbortResponseMessage } from './chat-abort.controller';
5+
6+
describe('AbortController', () => {
7+
let abortController: ChatAbortController;
8+
let abortService: ChatAbortService;
9+
10+
beforeEach(async () => {
11+
const module: TestingModule = await Test.createTestingModule({
12+
controllers: [ChatAbortController],
13+
providers: [ChatAbortService],
14+
}).compile();
15+
16+
abortController = module.get<ChatAbortController>(ChatAbortController);
17+
abortService = module.get<ChatAbortService>(ChatAbortService);
18+
});
19+
20+
it('should be defined', () => {
21+
expect(abortController).toBeDefined();
22+
});
23+
24+
it('요청 중단 시 성공 테스트', () => {
25+
const requestId = 'test-request';
26+
// AbortService의 abortRequest가 true를 반환하도록 spy 설정
27+
jest.spyOn(abortService, 'abortRequest').mockReturnValue(true);
28+
29+
const result = abortController.abort(requestId);
30+
31+
expect(result).toEqual({
32+
success: true,
33+
message: AbortResponseMessage.ABORT_SUCCESS,
34+
});
35+
});
36+
37+
it('존재하지 않는 requestId로 요청 중단 시 실패 테스트', () => {
38+
const requestId = 'non-existent-request';
39+
// AbortService의 abortRequest가 false를 반환하도록 spy 설정
40+
jest.spyOn(abortService, 'abortRequest').mockReturnValue(false);
41+
42+
const result = abortController.abort(requestId);
43+
44+
expect(result).toEqual({
45+
success: false,
46+
message: AbortResponseMessage.ABORT_FAIL,
47+
});
48+
});
49+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { Controller, Get, Param } from '@nestjs/common';
2+
import { ChatAbortService } from './chat-abort.service';
3+
4+
export enum AbortResponseMessage {
5+
ABORT_SUCCESS = '웹 요청 중단에 성공했습니다.',
6+
ABORT_FAIL = '웹 요청 중단에 실패했습니다.',
7+
}
8+
9+
@Controller('abort')
10+
export class ChatAbortController {
11+
constructor(private readonly abortService: ChatAbortService) {}
12+
13+
@Get('/:requestId')
14+
abort(@Param('requestId') requestId: string) {
15+
const success = this.abortService.abortRequest(requestId);
16+
return {
17+
success,
18+
message: success
19+
? AbortResponseMessage.ABORT_SUCCESS
20+
: AbortResponseMessage.ABORT_FAIL,
21+
};
22+
}
23+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { Module } from '@nestjs/common';
2+
import { ChatAbortService } from './chat-abort.service';
3+
import { ChatAbortController } from './chat-abort.controller';
4+
5+
@Module({
6+
providers: [ChatAbortService],
7+
controllers: [ChatAbortController],
8+
exports: [ChatAbortService],
9+
})
10+
export class ChatAbortModule {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { Test, TestingModule } from '@nestjs/testing';
2+
import { ChatAbortService } from './chat-abort.service';
3+
4+
describe('AbortService', () => {
5+
let abortService: ChatAbortService;
6+
7+
beforeEach(async () => {
8+
const module: TestingModule = await Test.createTestingModule({
9+
providers: [ChatAbortService],
10+
}).compile();
11+
12+
abortService = module.get<ChatAbortService>(ChatAbortService);
13+
});
14+
15+
it('AbortController를 생성 성공 테스트', () => {
16+
const requestId = 'test';
17+
18+
const controller = abortService.createController(requestId);
19+
20+
expect(controller).toBeDefined(); // AbortController가 생성되었는지 확인
21+
expect(controller.signal.aborted).toBe(false); // 아직 중단되지 않았는지 확인
22+
23+
const storedController = abortService.getController(requestId);
24+
expect(storedController).toBe(controller); // 저장된 컨트롤러가 동일한지 확인
25+
});
26+
27+
it('요청을 중단하고 캐시 삭제 성공 테스트', () => {
28+
const requestId = 'test';
29+
const controller = abortService.createController(requestId);
30+
31+
expect(controller.signal.aborted).toBe(false); // 초기 상태 확인
32+
33+
const success = abortService.abortRequest(requestId);
34+
35+
expect(success).toBe(true); // 요청 중단이 성공했는지 확인
36+
expect(controller.signal.aborted).toBe(true); // AbortController가 중단되었는지 확인
37+
expect(abortService.getController(requestId)).toBeUndefined(); // 캐시에서 삭제되었는지 확인
38+
});
39+
40+
it('존재하지 않는 requestId로 요청 중단 실패 테스트', () => {
41+
const success = abortService.abortRequest('non-existent-id');
42+
expect(success).toBe(false); // 존재하지 않는 요청은 중단할 수 없어야 함
43+
});
44+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { Injectable } from '@nestjs/common';
2+
3+
@Injectable()
4+
export class ChatAbortService {
5+
private controllers: Map<
6+
string,
7+
{ controller: AbortController; timestamp: number }
8+
> = new Map();
9+
private ttl: number = 60000;
10+
11+
constructor() {
12+
setInterval(() => this.cleanup(), 10000);
13+
}
14+
15+
createController(requestId: string): AbortController {
16+
const controller = new AbortController();
17+
const timestamp = Date.now();
18+
this.controllers.set(requestId, { controller, timestamp });
19+
return controller;
20+
}
21+
22+
getController(requestId: string): AbortController | undefined {
23+
return this.controllers.get(requestId)?.controller;
24+
}
25+
26+
abortRequest(requestId: string): boolean {
27+
const entry = this.controllers.get(requestId);
28+
if (entry) {
29+
if (entry.controller.signal.aborted) {
30+
// 이미 취소된 경우, 추가 작업을 하지 않음
31+
console.log(`Request with ID ${requestId} is already aborted.`);
32+
return false;
33+
}
34+
35+
entry.controller.abort();
36+
this.controllers.delete(requestId);
37+
return true;
38+
}
39+
return false;
40+
}
41+
42+
private cleanup(): void {
43+
const now = Date.now();
44+
this.controllers.forEach((entry, requestId) => {
45+
if (now - entry.timestamp > this.ttl) {
46+
this.controllers.delete(requestId);
47+
}
48+
});
49+
}
50+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { ApiProperty } from '@nestjs/swagger';
2+
import { IsString } from 'class-validator';
3+
4+
export class AbortFailResponseDto {
5+
@ApiProperty({
6+
example: '웹 요청 중단에 실패했습니다.',
7+
description: '웹 요청 결과 메시지',
8+
})
9+
@IsString()
10+
message: string;
11+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { ApiProperty } from '@nestjs/swagger';
2+
import { IsString, IsBoolean } from 'class-validator';
3+
4+
export class AbortSuccessResponseDto {
5+
@ApiProperty({
6+
example: '웹 요청 중단에 성공했습니다.',
7+
description: '웹 요청 결과 메시지',
8+
})
9+
@IsString()
10+
message: string;
11+
}

apps/backend/src/app.module.ts

+30-16
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,32 @@ import { Module } from '@nestjs/common';
22
import { ConfigModule, ConfigService } from '@nestjs/config';
33
import { AppController } from './app.controller';
44
import { AppService } from './app.service';
5-
import { NodeModule } from './node/node.module';
6-
import { PageModule } from './page/page.module';
7-
import { EdgeModule } from './edge/edge.module';
5+
import { NodeModule } from '@app/node/node.module';
6+
import { PageModule } from '@app/page/page.module';
7+
import { EdgeModule } from '@app/edge/edge.module';
88
import { TypeOrmModule } from '@nestjs/typeorm';
9-
import { Page } from './page/page.entity';
10-
import { Edge } from './edge/edge.entity';
11-
import { Node } from './node/node.entity';
12-
import { User } from './user/user.entity';
13-
import { Workspace } from './workspace/workspace.entity';
14-
import { Role } from './role/role.entity';
9+
import { Page } from '@app/page/page.entity';
10+
import { Edge } from '@app/edge/edge.entity';
11+
import { Node } from '@app/node/node.entity';
12+
import { User } from '@app/user/user.entity';
13+
import { Workspace } from '@app/workspace/workspace.entity';
14+
import { Role } from '@app/role/role.entity';
1515
import * as path from 'path';
1616
import { UploadModule } from './upload/upload.module';
1717
import { AuthModule } from './auth/auth.module';
18-
import { UserModule } from './user/user.module';
19-
import { WorkspaceModule } from './workspace/workspace.module';
20-
import { RoleModule } from './role/role.module';
21-
import { TasksModule } from './tasks/tasks.module';
18+
import { UserModule } from '@app/user/user.module';
19+
import { WorkspaceModule } from '@app/workspace/workspace.module';
20+
import { RoleModule } from '@app/role/role.module';
2221
import { ScheduleModule } from '@nestjs/schedule';
2322
import { LangchainModule } from './langchain/langchain.module';
23+
import { NodeController } from './node/node.controller';
24+
import { PageController } from './page/page.controller';
25+
import { EdgeController } from './edge/edge.controller';
26+
import { WorkspaceController } from './workspace/workspace.controller';
27+
import { TokenModule } from '@app/token/token.module';
28+
import { TokenService } from '@app/token/token.service';
29+
import { ChatAbortModule } from './abort/chat-abort.module';
30+
import { ChatAbortService } from './abort/chat-abort.service';
2431

2532
@Module({
2633
imports: [
@@ -50,12 +57,19 @@ import { LangchainModule } from './langchain/langchain.module';
5057
UploadModule,
5158
AuthModule,
5259
UserModule,
60+
TokenModule,
5361
WorkspaceModule,
5462
RoleModule,
55-
TasksModule,
5663
LangchainModule,
64+
ChatAbortModule,
5765
],
58-
controllers: [AppController],
59-
providers: [AppService],
66+
controllers: [
67+
AppController,
68+
NodeController,
69+
PageController,
70+
EdgeController,
71+
WorkspaceController,
72+
],
73+
providers: [AppService, TokenService],
6074
})
6175
export class AppModule {}

apps/backend/src/auth/auth.controller.spec.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ import { Test, TestingModule } from '@nestjs/testing';
22
import { AuthController } from './auth.controller';
33
import { AuthService } from './auth.service';
44
import { JwtAuthGuard } from './guards/jwt-auth.guard';
5-
import { TokenService } from './token/token.service';
6-
import { User } from '../user/user.entity';
5+
import { TokenService } from '@app/token/token.service';
6+
import { User } from '@app/user/user.entity';
77

88
describe('AuthController', () => {
99
let controller: AuthController;

apps/backend/src/auth/auth.controller.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import { Response } from 'express';
1515
import { MessageResponseDto } from './dtos/messageResponse.dto';
1616
import { LoginResponseDto } from './dtos/loginResponse.dto';
1717
import { ApiOperation, ApiResponse } from '@nestjs/swagger';
18-
import { TokenService } from './token/token.service';
18+
import { TokenService } from '@app/token/token.service';
1919
import { UpdateUserDto } from './dtos/UpdateUser.dto';
2020

2121
export enum AuthResponseMessage {
@@ -40,7 +40,7 @@ export class AuthController {
4040

4141
@Get('naver/callback')
4242
@UseGuards(AuthGuard('naver'))
43-
async naverCallback(@Req() req, @Res() res: Response) {
43+
async naverCallback(@Req() req, @Res() res) {
4444
// 네이버 인증 후 사용자 정보 반환
4545
const user = req.user;
4646

@@ -66,7 +66,7 @@ export class AuthController {
6666

6767
@Get('kakao/callback')
6868
@UseGuards(AuthGuard('kakao'))
69-
async kakaoCallback(@Req() req, @Res() res: Response) {
69+
async kakaoCallback(@Req() req, @Res() res) {
7070
/// 카카오 인증 후 사용자 정보 반환
7171
const user = req.user;
7272

@@ -87,7 +87,7 @@ export class AuthController {
8787
@ApiOperation({ summary: '사용자가 로그아웃합니다.' })
8888
@Post('logout')
8989
@UseGuards(JwtAuthGuard) // JWT 인증 검사
90-
logout(@Req() req, @Res() res: Response) {
90+
logout(@Req() req, @Res() res) {
9191
// 쿠키 삭제 (옵션이 일치해야 삭제됨)
9292
this.tokenService.clearCookies(res);
9393
// 현재 자동로그인에 사용되는 refresh Token db에서 삭제

0 commit comments

Comments
 (0)