Skip to content

Commit 6f49f96

Browse files
authored
Merge pull request #364 from boostcampwm-2024/develop
안정 버전 main 병합
2 parents e9776f2 + 9973305 commit 6f49f96

File tree

259 files changed

+8703
-3336
lines changed

Some content is hidden

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

259 files changed

+8703
-3336
lines changed

Diff for: .dockerignore

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
2+
# compiled output
3+
*/dist
4+
*/node_modules
5+
dist
6+
node_modules
7+
dist-ssr
8+
9+
# Logs
10+
logs
11+
*.log
12+
npm-debug.log*
13+
pnpm-debug.log*
14+
yarn-debug.log*
15+
yarn-error.log*
16+
lerna-debug.log*
17+
18+
# OS
19+
.DS_Store
20+
21+
# Tests
22+
/coverage
23+
/.nyc_output
24+
25+
# IDEs and editors
26+
/.idea
27+
.idea
28+
.project
29+
.classpath
30+
.c9/
31+
*.launch
32+
.settings/
33+
*.sublime-workspace
34+
*.suo
35+
*.ntvs*
36+
*.njsproj
37+
*.sln
38+
*.sw?
39+
40+
# IDE - VSCode
41+
.vscode/*
42+
!.vscode/settings.json
43+
!.vscode/tasks.json
44+
!.vscode/launch.json
45+
!.vscode/extensions.json
46+
db.sqlite

Diff for: .gitignore

+6
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
.env*
44
*.local
55
.turbo
6+
*.crt
7+
*.key
8+
!Dockerfile.local
69

710
# compiled output
811
*/dist
@@ -49,3 +52,6 @@ lerna-debug.log*
4952
!.vscode/launch.json
5053
!.vscode/extensions.json
5154
db.sqlite
55+
56+
*.crt
57+
*.key

Diff for: README.md

+71-15
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
![Sprint 33](https://github.com/user-attachments/assets/2b23184d-90ed-458d-9dc4-dab9579c1e48)
22

3-
🪝 [**배포 링크**](http://211.188.48.107:3000/)
43

54
<br>
65

@@ -9,35 +8,92 @@
98

109

1110

11+
12+
1213
<div align="center">
1314

14-
![Hits](https://hits.seeyoufarm.com/api/count/incr/badge.svg?url=https%3A%2F%2Fgithub.com%2Fboostcampwm-2024%2Fweb15-OctoDocs&count_bg=%23000000&title_bg=%23000000&icon=&icon_color=%23E7E7E7&title=hits&edge_flat=false) [![Group 83 (2)](https://github.com/user-attachments/assets/2d106d94-430c-47bc-a9e2-1f0026f76c2f)](https://github.com/boostcampwm-2024/web15-OctoDocs/wiki) [![Group 84 (2)](https://github.com/user-attachments/assets/b29b191b-8172-42a9-b541-40fdb8f165f3)](https://github.com/orgs/boostcampwm-2024/projects/120)
15+
![Hits](https://hits.seeyoufarm.com/api/count/incr/badge.svg?url=https%3A%2F%2Fgithub.com%2Fboostcampwm-2024%2Fweb15-OctoDocs&count_bg=%23000000&title_bg=%23000000&icon=&icon_color=%23E7E7E7&title=hits&edge_flat=false) [![Group 112 (1)](https://github.com/user-attachments/assets/b7b4387e-ffe9-4469-82b7-c14509282d86)](https://octodocs.site)
16+
[![Group 83 (2)](https://github.com/user-attachments/assets/2d106d94-430c-47bc-a9e2-1f0026f76c2f)](https://github.com/boostcampwm-2024/web15-OctoDocs/wiki) [![Group 84 (2)](https://github.com/user-attachments/assets/b29b191b-8172-42a9-b541-40fdb8f165f3)](https://github.com/orgs/boostcampwm-2024/projects/120)
1517

1618
</div>
1719

18-
## 🐙 프로젝트 소개
20+
# 🐙 프로젝트 소개
21+
22+
### 🕸️ 관계형 지식 관리 툴
23+
24+
25+
> 문서들을 작성하고 연결하면서 문서들 간의 연관 관계를 시각적으로 확인 가능.
26+
27+
https://github.com/user-attachments/assets/1ac81d56-a0ce-403c-9e3f-7ba092b6a5b6
28+
29+
30+
<br>
31+
32+
33+
### 🧸 실시간 동시 편집 및 협업 기능
34+
35+
> 실시간으로 다른 사용자들과 동시 편집 및 협업 가능.
36+
37+
https://github.com/user-attachments/assets/ad1f6dc9-50af-46e4-bac4-267b1432b301
38+
39+
<br>
40+
41+
42+
### ⛺️ 워크스페이스 초대 기능
43+
44+
> 개별 워크 스페이스에 다른 사용자들을 초대해서 함께 이용 가능.
45+
46+
<br>
47+
48+
49+
# 🛠️ 프로젝트 구조
50+
51+
### 🖥️ System Architecture
52+
53+
![image (13)](https://github.com/user-attachments/assets/60bfb7a1-3c1a-436d-b961-5a30dc9dba7f)
54+
55+
56+
### 🐳 Sequence Diagram
57+
58+
<div align="center">
59+
60+
61+
![image (14)](https://github.com/user-attachments/assets/ea6853d8-398e-4448-ae0a-07bffc653722)
62+
63+
</div>
64+
65+
# 🗺️ 프로젝트 타임라인
66+
67+
![Overview-variant (18)](https://github.com/user-attachments/assets/a503f8fe-bab9-4cf3-8d9d-98ff43ab0c3e)
68+
69+
70+
# 🚧 문제와 해결과정
71+
72+
### 실시간 편집 구현 과정
73+
74+
Octodocs 팀은 핵심 기능인 에디터와 노드 캔버스의 **실시간 편집**을 위해 **CRDT** 라이브러리인 **YJS****SocketIO**를 어떻게 활용 했을까요? [🔗 링크 준비 중](https://github.com/boostcampwm-2024/web15-OctoDocs/wiki)
75+
76+
### 데이터 흐름 변경
1977

20-
> “Notion을 쓰고 있는데, 문서끼리 관계 표현이 너무 어려워요…”
21-
>
22-
> “Obsidian으로 노트 정리를 잘 하고 있는데, 공유하기가 너무 불편해요…”
78+
YDoc과 소켓을 사용하면서도 RESTful 방식으로 일부 상태를 관리하던 구조를, YDoc 중심의 **단일 truth source**와 소켓 기반 **단방향 흐름**으로 단순화했습니다. 그 결과 모든 상태가 YDoc을 통해 일관성 있게 관리되며, 클라이언트와 서버 간 데이터 흐름도 간소화되었습니다. 중복 관리와 데이터 충돌을 줄이기 위한 이런 **data flow 변화**의 핵심은 무엇이었을까요? [🔗 링크 준비 중](https://github.com/boostcampwm-2024/web15-OctoDocs/wiki)
2379

24-
이런 고민, 이제 **OctoDocs**로 해결해보세요!!
80+
### FE 프로젝트 구조 개선 과정
2581

26-
- **실시간 협업**이 지원되는 **연결형 지식관리 도구**입니다.
27-
- **실시간 동시편집****마크다운** 형식 문서편집을 지원합니다.
82+
Octodocs 팀은 기존 **프로젝트 구조**의 문제점을 어떻게 파악했고, 어떤 방법으로 개선을 했을까요? [🔗 링크 준비 중](https://github.com/boostcampwm-2024/web15-OctoDocs/wiki)
2883

29-
## 📢 핵심 기능
84+
### 드래그 이벤트 발생 시 생기는 쿼리 최적화
3085

31-
![image](https://github.com/user-attachments/assets/4c0010db-d4a3-455f-ab26-03e04c85e46e)
86+
**노트 카드를 드래그** 할 때, 그 위치를 DB에 저장하기 위해 **너무 많은 쿼리가 발생**하는 문제가 있었습니다. 이를 위해 **쿼리를 최적화**해야할 필요성이 생겼는데 이를 어떻게 해결할 수 있었을까요? [🔗 링크 준비 중](https://github.com/boostcampwm-2024/web15-OctoDocs/wiki)
3287

33-
## 🛠️ 시스템 아키텍처
88+
### redis 캐싱으로 데이터베이스 부하 감소
3489

35-
v.241115
90+
실시간 문서 동시 편집에서 발생하는 굉장히 많은 변경 사항을 모두 데이터베이스에 저장하기에는 데이터베이스 부하가 너무 많이 발생했고, Octodocs 팀은 redis를 도입하기로 결정했습니다. 저희는 왜 redis를 도입하기로 결정했고 또 redis를 어떻게 활용했을까요? [🔗 링크](https://github.com/boostcampwm-2024/web15-OctoDocs/wiki/redis%EB%A5%BC-%ED%86%B5%ED%95%B4-%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4-%EC%BF%BC%EB%A6%AC-%EC%A4%84%EC%9D%B4%EA%B8%B0)
3691

37-
![octodocs-architecture](https://github.com/user-attachments/assets/18461bff-25ad-439a-ada0-73f4ea37e4d7)
92+
### 개발 환경, 배포 환경 및 CI/CD에 대한 개선 과정
3893

94+
Octodocs 팀은 사용자 경험 향상은 물론, 일관된 코드 품질 유지와 개발자 친화적인 쾌적한 개발 환경 조성을 위해 많은 노력을 기울였습니다. 멀티 레포에서 모노레포로의 전환, GitHub Actions를 활용한 CI/CD 구축, Docker와 Docker Compose의 도입까지—우리는 어떤 변화를 거쳤을까요? [🔗 링크 준비 중](https://github.com/boostcampwm-2024/web15-OctoDocs/wiki)
3995

40-
## 🧸 팀원 소개
96+
# 🧸 팀원 소개
4197
| [J032_김동준](https://github.com/djk01281) | [J075_김현준](https://github.com/Tolerblanc) | [J097_민서진](https://github.com/summersummerwhy) | [J162_유성민](https://github.com/ezcolin2) | [J248_진예원](https://github.com/yewonJin) |
4298
|:----------------------------------------:|:------------------------------------------:|:------------------------------------------------:|:----------------------------------------:|:----------------------------------------:|
4399
| <img width="204" alt="스크린샷 2024-10-29 오후 4" src="https://github.com/user-attachments/assets/71a5a38e-f60c-4f60-97e3-30d7a73a3c77"> | <img width="204" alt="스크린샷 2024-10-29 오후 11 41 04" src="https://github.com/user-attachments/assets/e093f852-a6ea-4937-b0ce-b89276bd7135"> | <img width="204" alt="스크린샷 2024-10-29 오후 11 41 55" src="https://github.com/user-attachments/assets/0f638ba9-a1ad-47b8-a874-957c0119384c"> | <img width="204" alt="스크린샷 2024-10-29 오후 11 41 00" src="https://github.com/user-attachments/assets/1d77b650-70f1-4dee-9489-dc0122b7c9ff"> | <img width="204" alt="스크린샷 2024-10-29 오후 11 40 31" src="https://github.com/user-attachments/assets/db99b6b2-ae06-4758-8687-17ebb860a52b"> |

Diff for: apps/backend/package.json

+10-3
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
"build": "nest build",
1010
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
1111
"start": "nest start",
12-
"dev": "nest start --watch",
12+
"dev": "nest start --tsc --watch --preserveWatchOutput --watchAssets",
1313
"start:debug": "nest start --debug --watch",
1414
"start:prod": "node dist/main",
1515
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
@@ -30,25 +30,32 @@
3030
"@nestjs/platform-express": "^10.0.0",
3131
"@nestjs/platform-socket.io": "^10.4.8",
3232
"@nestjs/platform-ws": "^10.4.7",
33+
"@nestjs/schedule": "^4.1.1",
3334
"@nestjs/serve-static": "^4.0.2",
3435
"@nestjs/swagger": "^8.0.5",
3536
"@nestjs/typeorm": "^10.0.2",
3637
"@nestjs/websockets": "^10.4.8",
38+
"@theinternetfolks/snowflake": "^1.3.0",
3739
"@types/multer": "^1.4.12",
40+
"@types/redlock": "^4.0.7",
3841
"class-transformer": "^0.5.1",
3942
"class-validator": "^0.14.1",
43+
"cookie-parser": "^1.4.7",
44+
"ioredis": "^5.4.1",
4045
"lib0": "^0.2.98",
41-
"node-ts-cache": "^4.4.0",
42-
"node-ts-cache-storage-memory": "^4.4.0",
4346
"passport": "^0.7.0",
4447
"passport-kakao": "^1.0.1",
4548
"passport-naver": "^1.0.6",
4649
"path": "^0.12.7",
50+
"pg": "^8.13.1",
51+
"prosemirror-view": "^1.37.0",
52+
"redlock": "^5.0.0-beta.2",
4753
"reflect-metadata": "^0.1.13",
4854
"rxjs": "^7.8.1",
4955
"socket.io": "^4.8.1",
5056
"sqlite3": "^5.1.7",
5157
"typeorm": "^0.3.20",
58+
"uuid": "^11.0.3",
5259
"ws": "^8.14.2",
5360
"y-prosemirror": "^1.2.12",
5461
"y-protocols": "^1.0.6",

Diff for: apps/backend/src/app.module.ts

+21-2
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,23 @@ import { Page } from './page/page.entity';
1010
import { Edge } from './edge/edge.entity';
1111
import { Node } from './node/node.entity';
1212
import { User } from './user/user.entity';
13+
import { Workspace } from './workspace/workspace.entity';
14+
import { Role } from './role/role.entity';
1315
import { YjsModule } from './yjs/yjs.module';
1416
import * as path from 'path';
1517
import { ServeStaticModule } from '@nestjs/serve-static';
1618
import { UploadModule } from './upload/upload.module';
1719
import { AuthModule } from './auth/auth.module';
1820
import { UserModule } from './user/user.module';
21+
import { WorkspaceModule } from './workspace/workspace.module';
22+
import { RoleModule } from './role/role.module';
23+
import { TasksModule } from './tasks/tasks.module';
24+
import { ScheduleModule } from '@nestjs/schedule';
25+
import { RedLockModule } from './red-lock/red-lock.module';
1926

2027
@Module({
2128
imports: [
29+
ScheduleModule.forRoot(),
2230
ServeStaticModule.forRoot({
2331
rootPath: path.join(__dirname, '..', '..', 'frontend', 'dist'),
2432
}),
@@ -30,9 +38,16 @@ import { UserModule } from './user/user.module';
3038
imports: [ConfigModule],
3139
inject: [ConfigService],
3240
useFactory: (configService: ConfigService) => ({
33-
type: 'sqlite',
41+
42+
// type: 'sqlite',
43+
// database: 'db.sqlite',
44+
type: 'postgres',
45+
host: configService.get('DB_HOST'),
46+
port: configService.get('DB_PORT'),
47+
username: configService.get('DB_USER'),
48+
password: configService.get('DB_PASSWORD'),
3449
database: configService.get('DB_NAME'),
35-
entities: [Node, Page, Edge, User],
50+
entities: [Node, Page, Edge, User, Workspace, Role],
3651
logging: true,
3752
synchronize: true,
3853
}),
@@ -44,6 +59,10 @@ import { UserModule } from './user/user.module';
4459
UploadModule,
4560
AuthModule,
4661
UserModule,
62+
WorkspaceModule,
63+
RoleModule,
64+
TasksModule,
65+
RedLockModule,
4766
],
4867
controllers: [AppController],
4968
providers: [AppService],

Diff for: apps/backend/src/auth/auth.controller.spec.ts

+25-42
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,18 @@
11
import { Test, TestingModule } from '@nestjs/testing';
22
import { AuthController } from './auth.controller';
33
import { AuthService } from './auth.service';
4-
import { JwtService } from '@nestjs/jwt';
5-
import { InvalidTokenException } from '../exception/invalid.exception';
6-
// import { LoginRequiredException } from '../exception/login.exception';
7-
// TODO: 테스트 코드 개선
4+
import { JwtAuthGuard } from './guards/jwt-auth.guard';
5+
import { TokenService } from './token/token.service';
6+
import { User } from '../user/user.entity';
7+
88
describe('AuthController', () => {
9-
let authController: AuthController;
9+
let controller: AuthController;
10+
let authService: AuthService;
1011

1112
beforeEach(async () => {
1213
const module: TestingModule = await Test.createTestingModule({
1314
controllers: [AuthController],
1415
providers: [
15-
AuthService,
16-
JwtService,
1716
{
1817
provide: AuthService,
1918
useValue: {
@@ -23,58 +22,42 @@ describe('AuthController', () => {
2322
},
2423
},
2524
{
26-
provide: JwtService,
25+
provide: TokenService,
2726
useValue: {
28-
sign: jest.fn().mockReturnValue('test-token'),
29-
verify: jest.fn((token: string) => {
30-
if (token === 'invalid-token') {
31-
throw new InvalidTokenException();
32-
}
33-
return { sub: 1, provider: 'naver' };
34-
}),
27+
generateAccessToken: jest.fn(() => 'mockedAccessToken'),
28+
generateRefreshToken: jest.fn(() => 'mockedRefreshToken'),
29+
refreshAccessToken: jest.fn(() => 'mockedAccessToken'),
3530
},
3631
},
3732
],
38-
}).compile();
33+
})
34+
.overrideGuard(JwtAuthGuard)
35+
.useValue({
36+
canActivate: jest.fn(() => true),
37+
})
38+
.compile();
3939

40-
authController = module.get<AuthController>(AuthController);
40+
controller = module.get<AuthController>(AuthController);
41+
authService = module.get<AuthService>(AuthService);
4142
});
4243

4344
it('컨트롤러 클래스가 정상적으로 인스턴스화된다.', () => {
44-
expect(authController).toBeDefined();
45+
expect(controller).toBeDefined();
4546
});
4647

4748
describe('getProfile', () => {
4849
it('JWT 토큰이 유효한 경우 profile을 return한다.', async () => {
4950
const req = {
50-
user: { sub: 1, email: '[email protected]', provider: 'naver' },
51+
user: { sub: 1 },
5152
} as any;
52-
const result = await authController.getProfile(req);
53+
const returnedUser = { id: 1, snowflakeId: 'snowflake-id-1' } as User;
54+
jest.spyOn(authService, 'findUserById').mockResolvedValue(returnedUser);
55+
56+
const result = await controller.getProfile(req);
5357
expect(result).toEqual({
5458
message: '인증된 사용자 정보',
55-
user: req.user,
59+
snowflakeId: returnedUser.snowflakeId,
5660
});
5761
});
58-
59-
// it('JWT 토큰이 유효가지 않은 경우 InvalidTokenException을 throw한다.', async () => {
60-
// const req = {
61-
// headers: { authorization: 'Bearer invalid-token' },
62-
// user: undefined,
63-
// } as any;
64-
// try {
65-
// await authController.getProfile(req);
66-
// } catch (error) {
67-
// expect(error).toBeInstanceOf(InvalidTokenException);
68-
// }
69-
// });
70-
71-
// it('JWT 토큰이 없는 경우 LoginRequiredException을 throw한다.', async () => {
72-
// const req = { headers: {}, user: undefined } as any;
73-
// try {
74-
// await authController.getProfile(req);
75-
// } catch (error) {
76-
// expect(error).toBeInstanceOf(LoginRequiredException);
77-
// }
78-
// });
7962
});
8063
});

0 commit comments

Comments
 (0)