Skip to content

Commit 538d2b8

Browse files
committed
Merge branch 'develop-be-#278' into feature-fe-#270
2 parents c8cf864 + 8609e80 commit 538d2b8

Some content is hidden

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

66 files changed

+1497
-769
lines changed

.dockerignore

Lines changed: 46 additions & 0 deletions
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

.gitignore

Lines changed: 5 additions & 0 deletions
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,5 @@ lerna-debug.log*
4952
!.vscode/launch.json
5053
!.vscode/extensions.json
5154
db.sqlite
55+
services/nginx/ssl/localhost.crt
56+
services/nginx/ssl/localhost.key

.vscode/settings.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"files.associations": {
3+
"*.ttml": "xml",
4+
"*.ttss": "css"
5+
}
6+
}

apps/backend/package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,16 +34,17 @@
3434
"@nestjs/swagger": "^8.0.5",
3535
"@nestjs/typeorm": "^10.0.2",
3636
"@nestjs/websockets": "^10.4.8",
37+
"@theinternetfolks/snowflake": "^1.3.0",
3738
"@types/multer": "^1.4.12",
3839
"class-transformer": "^0.5.1",
3940
"class-validator": "^0.14.1",
4041
"lib0": "^0.2.98",
41-
"node-ts-cache": "^4.4.0",
42-
"node-ts-cache-storage-memory": "^4.4.0",
4342
"passport": "^0.7.0",
4443
"passport-kakao": "^1.0.1",
4544
"passport-naver": "^1.0.6",
4645
"path": "^0.12.7",
46+
"pg": "^8.13.1",
47+
"prosemirror-view": "^1.37.0",
4748
"reflect-metadata": "^0.1.13",
4849
"rxjs": "^7.8.1",
4950
"socket.io": "^4.8.1",

apps/backend/src/app.module.ts

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,16 @@ 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';
1923

2024
@Module({
2125
imports: [
@@ -24,15 +28,19 @@ import { UserModule } from './user/user.module';
2428
}),
2529
ConfigModule.forRoot({
2630
isGlobal: true,
27-
envFilePath: path.join(__dirname, '..', '.env'), // * nest 디렉터리 기준
31+
envFilePath: path.join(__dirname, '..', '..', '..', '.env'), // * nest 디렉터리 기준
2832
}),
2933
TypeOrmModule.forRootAsync({
3034
imports: [ConfigModule],
3135
inject: [ConfigService],
3236
useFactory: (configService: ConfigService) => ({
33-
type: 'sqlite',
37+
type: 'postgres',
38+
host: configService.get('DB_HOST'),
39+
port: configService.get('DB_PORT'),
40+
username: configService.get('DB_USER'),
41+
password: configService.get('DB_PASSWORD'),
3442
database: configService.get('DB_NAME'),
35-
entities: [Node, Page, Edge, User],
43+
entities: [Node, Page, Edge, User, Workspace, Role],
3644
logging: true,
3745
synchronize: true,
3846
}),
@@ -44,6 +52,8 @@ import { UserModule } from './user/user.module';
4452
UploadModule,
4553
AuthModule,
4654
UserModule,
55+
WorkspaceModule,
56+
RoleModule,
4757
],
4858
controllers: [AppController],
4959
providers: [AppService],
Lines changed: 22 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +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';
4+
import { JwtAuthGuard } from './guards/jwt-auth.guard';
5+
import { TokenService } from './token/token.service';
6+
import { LoginRequiredException } from '../exception/login.exception';
77

8-
// TODO: 테스트 코드 개선
98
describe('AuthController', () => {
109
let authController: AuthController;
11-
// let authService: AuthService;
12-
let jwtService: JwtService;
10+
let authService: AuthService;
1311

1412
beforeEach(async () => {
1513
const module: TestingModule = await Test.createTestingModule({
1614
controllers: [AuthController],
1715
providers: [
18-
AuthService,
19-
JwtService,
2016
{
2117
provide: AuthService,
2218
useValue: {
@@ -26,23 +22,23 @@ describe('AuthController', () => {
2622
},
2723
},
2824
{
29-
provide: JwtService,
25+
provide: TokenService,
3026
useValue: {
31-
sign: jest.fn().mockReturnValue('test-token'),
32-
verify: jest.fn((token: string) => {
33-
if (token === 'invalid-token') {
34-
throw new InvalidTokenException();
35-
}
36-
return { sub: 1, provider: 'naver' };
37-
}),
27+
generateAccessToken: jest.fn(() => 'mockedAccessToken'),
28+
generateRefreshToken: jest.fn(() => 'mockedRefreshToken'),
29+
refreshAccessToken: jest.fn(() => 'mockedAccessToken'),
3830
},
3931
},
4032
],
41-
}).compile();
33+
})
34+
.overrideGuard(JwtAuthGuard)
35+
.useValue({
36+
canActivate: jest.fn(() => true),
37+
})
38+
.compile();
4239

4340
authController = module.get<AuthController>(AuthController);
44-
// authService = module.get<AuthService>(AuthService);
45-
jwtService = module.get<JwtService>(JwtService);
41+
authService = module.get<AuthService>(AuthService);
4642
});
4743

4844
it('컨트롤러 클래스가 정상적으로 인스턴스화된다.', () => {
@@ -61,40 +57,13 @@ describe('AuthController', () => {
6157
});
6258
});
6359

64-
// it('JWT 토큰이 유효가지 않은 경우 InvalidTokenException을 throw한다.', async () => {
65-
// const req = {
66-
// headers: { authorization: 'Bearer invalid-token' },
67-
// user: undefined,
68-
// } as any;
69-
// try {
70-
// await authController.getProfile(req);
71-
// } catch (error) {
72-
// expect(error).toBeInstanceOf(InvalidTokenException);
73-
// }
74-
// });
75-
76-
// it('JWT 토큰이 없는 경우 LoginRequiredException을 throw한다.', async () => {
77-
// const req = { headers: {}, user: undefined } as any;
78-
// try {
79-
// await authController.getProfile(req);
80-
// } catch (error) {
81-
// expect(error).toBeInstanceOf(LoginRequiredException);
82-
// }
83-
// });
84-
});
85-
86-
describe('refreshAccessToken', () => {
87-
it('refresh token이 유효한 경우 access token을 성공적으로 발급한다.', async () => {
88-
jest
89-
.spyOn(jwtService, 'verify')
90-
.mockReturnValue({ sub: 1, provider: 'naver' });
91-
const req = { body: { refreshToken: 'valid-refresh-token' } } as any;
92-
93-
const result = await authController.refreshAccessToken(req);
94-
expect(result).toEqual({
95-
message: '새로운 Access Token 발급 성공',
96-
accessToken: 'test-token',
97-
});
60+
it('JWT 토큰이 없는 경우 예외를 던진다.', async () => {
61+
const req = {} as any;
62+
try {
63+
authController.getProfile(req);
64+
} catch (error) {
65+
expect(error).toBeInstanceOf(LoginRequiredException);
66+
}
9867
});
9968
});
10069
});

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

Lines changed: 45 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,21 @@
1-
import { Controller, Get, UseGuards, Req, Post } from '@nestjs/common';
1+
import { Controller, Get, UseGuards, Req, Res, Post } from '@nestjs/common';
22
import { AuthGuard } from '@nestjs/passport';
33
import { AuthService } from './auth.service';
4-
import { JwtService } from '@nestjs/jwt';
54
import { JwtAuthGuard } from './guards/jwt-auth.guard';
5+
import { Response } from 'express';
6+
import { MessageResponseDto } from './dtos/messageResponse.dto';
7+
import { ApiOperation, ApiResponse } from '@nestjs/swagger';
8+
import { TokenService } from './token/token.service';
9+
10+
export enum AuthResponseMessage {
11+
AUTH_LOGGED_OUT = '로그아웃하였습니다.',
12+
}
613

714
@Controller('auth')
815
export class AuthController {
916
constructor(
1017
private readonly authService: AuthService,
11-
private readonly jwtService: JwtService,
18+
private readonly tokenService: TokenService,
1219
) {}
1320

1421
@Get('naver')
@@ -20,19 +27,21 @@ export class AuthController {
2027

2128
@Get('naver/callback')
2229
@UseGuards(AuthGuard('naver'))
23-
async naverCallback(@Req() req) {
30+
async naverCallback(@Req() req, @Res() res: Response) {
2431
// 네이버 인증 후 사용자 정보 반환
2532
const user = req.user;
26-
// TODO: 후에 권한 (workspace 조회, 편집 기능)도 payload에 추가
27-
const payload = { sub: user.id, provider: user.provider };
28-
const accessToken = this.jwtService.sign(payload, { expiresIn: '1h' });
29-
const refreshToken = this.jwtService.sign(payload, { expiresIn: '7d' });
30-
return {
31-
message: '네이버 로그인 성공',
32-
user,
33-
accessToken,
34-
refreshToken,
35-
};
33+
34+
// primary Key인 id 포함 payload 생성함
35+
// TODO: 여기서 권한 추가해야함
36+
const payload = { sub: user.id };
37+
const accessToken = this.tokenService.generateAccessToken(payload);
38+
const refreshToken = this.tokenService.generateRefreshToken(payload);
39+
40+
// 토큰을 쿠키에 담아서 메인 페이지로 리디렉션
41+
this.tokenService.setAccessTokenCookie(res, accessToken);
42+
this.tokenService.setRefreshTokenCookie(res, refreshToken);
43+
44+
res.redirect(302, '/');
3645
}
3746

3847
@Get('kakao')
@@ -44,34 +53,32 @@ export class AuthController {
4453

4554
@Get('kakao/callback')
4655
@UseGuards(AuthGuard('kakao'))
47-
async kakaoCallback(@Req() req) {
48-
// 카카오 인증 후 사용자 정보 반환
56+
async kakaoCallback(@Req() req, @Res() res: Response) {
57+
/// 카카오 인증 후 사용자 정보 반환
4958
const user = req.user;
50-
// TODO: 후에 권한 (workspace 조회, 편집 기능)도 payload에 추가
51-
const payload = { sub: user.id, provider: user.provider };
52-
const accessToken = this.jwtService.sign(payload, { expiresIn: '1h' });
53-
const refreshToken = this.jwtService.sign(payload, { expiresIn: '7d' });
54-
return {
55-
message: '카카오 로그인 성공',
56-
user,
57-
accessToken,
58-
refreshToken,
59-
};
60-
}
6159

62-
@Post('refresh')
63-
async refreshAccessToken(@Req() req) {
64-
const { refreshToken } = req.body;
60+
// primary Key인 id 포함 payload 생성함
61+
// TODO: 여기서 권한 추가해야함
62+
const payload = { sub: user.id };
63+
const accessToken = this.tokenService.generateAccessToken(payload);
64+
const refreshToken = this.tokenService.generateRefreshToken(payload);
6565

66-
const decoded = this.jwtService.verify(refreshToken, {
67-
secret: process.env.JWT_SECRET,
66+
// 토큰을 쿠키에 담아서 메인 페이지로 리디렉션
67+
this.tokenService.setAccessTokenCookie(res, accessToken);
68+
this.tokenService.setRefreshTokenCookie(res, refreshToken);
69+
70+
res.redirect(302, '/');
71+
}
72+
73+
@ApiResponse({ type: MessageResponseDto })
74+
@ApiOperation({ summary: '사용자가 로그아웃합니다.' })
75+
@Post('logout')
76+
logout(@Res() res: Response) {
77+
// 쿠키 삭제 (옵션이 일치해야 삭제됨)
78+
this.tokenService.clearCookies(res);
79+
return res.status(200).json({
80+
message: AuthResponseMessage.AUTH_LOGGED_OUT,
6881
});
69-
const payload = { sub: decoded.sub, provider: decoded.provider };
70-
const newAccessToken = this.jwtService.sign(payload, { expiresIn: '1h' });
71-
return {
72-
message: '새로운 Access Token 발급 성공',
73-
accessToken: newAccessToken,
74-
};
7582
}
7683

7784
// Example: 로그인한 사용자만 접근할 수 있는 엔드포인트

apps/backend/src/auth/auth.module.ts

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,22 +5,11 @@ import { AuthService } from './auth.service';
55
import { AuthController } from './auth.controller';
66
import { NaverStrategy } from './strategies/naver.strategy';
77
import { KakaoStrategy } from './strategies/kakao.strategy';
8-
import { JwtModule } from '@nestjs/jwt';
98
import { JwtAuthGuard } from './guards/jwt-auth.guard';
10-
import { ConfigModule, ConfigService } from '@nestjs/config';
9+
import { TokenModule } from './token/token.module';
1110

1211
@Module({
13-
imports: [
14-
UserModule,
15-
ConfigModule.forRoot({ isGlobal: true }),
16-
JwtModule.registerAsync({
17-
imports: [ConfigModule],
18-
inject: [ConfigService],
19-
useFactory: async (configService: ConfigService) => ({
20-
secret: configService.get<string>('JWT_SECRET'),
21-
}),
22-
}),
23-
],
12+
imports: [UserModule, TokenModule],
2413
controllers: [AuthController],
2514
providers: [
2615
AuthService,

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { Test, TestingModule } from '@nestjs/testing';
22
import { AuthService } from './auth.service';
33
import { UserRepository } from '../user/user.repository';
4-
import { SignUpDto } from './dto/signUp.dto';
4+
import { SignUpDto } from './dtos/signUp.dto';
55
import { User } from '../user/user.entity';
66

77
describe('AuthService', () => {

0 commit comments

Comments
 (0)