Skip to content

Commit

Permalink
[Feat] 2차배포 (11/27)
Browse files Browse the repository at this point in the history
[Feat] 2차배포 (11/27)
  • Loading branch information
seoko97 authored Nov 27, 2024
2 parents 468f12a + 83643f4 commit 2a6c714
Show file tree
Hide file tree
Showing 159 changed files with 3,917 additions and 1,663 deletions.
23 changes: 12 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,21 @@
<img width="600" alt="메인 배너" src="https://github.com/user-attachments/assets/eb4c89c8-6870-4114-bddc-796b51bd7163">
<br/>

### [:ledger: 팀 노션](https://www.notion.so/simeunseo/9-Ticle-12e599a6f0d2804682ccd2251248a435?pvs=4) | [:mag: 위키](https://github.com/boostcampwm-2024/web21-boostproject/wiki) | [🎨 피그마](https://www.figma.com/design/nw74detTvjXGrDP2cfdmwp/TICLE-%EB%94%94%EC%9E%90%EC%9D%B8?node-id=32-4477&t=3FCCnBpgQXMZs63X-1) | [🗓️ 스프린트 백로그](https://github.com/orgs/boostcampwm-2024/projects/82/views/7)
### [:ledger: 팀 노션](https://www.notion.so/simeunseo/9-Ticle-12e599a6f0d2804682ccd2251248a435?pvs=4) | [:mag: 위키](https://github.com/boostcampwm-2024/web21-boostproject/wiki) | [🎨 피그마](https://www.figma.com/design/nw74detTvjXGrDP2cfdmwp/TICLE-%EB%94%94%EC%9E%90%EC%9D%B8?node-id=32-4477&t=3FCCnBpgQXMZs63X-1) | [🗓️ 스프린트 백로그](https://github.com/orgs/boostcampwm-2024/projects/82/views/7) | [🧪 스토리북](https://673a0cccd15a760db778c591-kxdpixadfg.chromatic.com/?path=/docs/common-dialog--docs)

</div>

# 🖧 시스템 아키텍처

<img width="6176" alt="아키텍처" src="https://github.com/user-attachments/assets/dc0a4690-e906-4068-b391-f15f6fcdc9a6">

# 🏃 작업 진행 상황

- [🆕 4주차 발표자료](https://simeunseo.notion.site/4-1ccf63bab4b14fd8b249f5d7c7cd7e53?pvs=4)
- [3주차 발표자료](https://simeunseo.notion.site/3-0df689ca7cd3407b89a93284854a54b8?pvs=4)
- [2주차 발표자료](https://simeunseo.notion.site/2-137599a6f0d2809fa498fa1cc31d97f9?pvs=4)
- [1주차 발표자료](https://simeunseo.notion.site/1-130599a6f0d2804597e0c55e8ee33920?pvs=4)

# 💡 핵심 기능

### **✔️ 실시간 화상 지식 공유**
Expand Down Expand Up @@ -40,16 +51,6 @@

CLOVA API를 이용해 티클 내용을 요약하여 제공합니다.

# 📽️ 스트리밍 구현 흐름

### Producer(미디어 전송 엔드포인트)

<img width="2768" alt="mediasoup sequence diagram - producer" src="https://github.com/user-attachments/assets/8b10a0be-c0c4-4e41-b3a9-462b5fc78be6">

### Consumer(미디어 수신 엔드포인트)

<img width="2768" alt="mediasoup sequence diagram - consumer" src="https://github.com/user-attachments/assets/fd5d2819-bc96-4598-9875-18cb67abb20d">

# ✍️ 학습 정리

| 분야 | 기술 |
Expand Down
9 changes: 7 additions & 2 deletions apps/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
"license": "UNLICENSED",
"scripts": {
"build": "nest build",
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
"format:check": "prettier --check \"src/**/*.ts\" \"test/**/*.ts\"",
"format": "prettier --write \"src/**/*.ts\" ",
"format:check": "prettier --check \"src/**/*.ts\" ",
"start": "nest start",
"dev": "nest start --watch",
"start:debug": "nest start --debug --watch",
Expand All @@ -29,9 +29,13 @@
"@nestjs/passport": "^10.0.3",
"@nestjs/platform-express": "^10.0.0",
"@nestjs/swagger": "^8.0.1",
"@nestjs/throttler": "^6.2.1",
"@nestjs/typeorm": "^10.0.2",
"@repo/types": "workspace:*",
"bcrypt": "^5.1.1",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.1",
"cookie-parser": "^1.4.7",
"eslint-import-resolver-typescript": "^3.6.3",
"jsonwebtoken": "^9.0.2",
"mysql2": "^3.11.3",
Expand All @@ -52,6 +56,7 @@
"@repo/lint": "workspace:*",
"@repo/tsconfig": "workspace:*",
"@types/bcrypt": "^5.0.2",
"@types/cookie-parser": "^1.4.7",
"@types/express": "^5.0.0",
"@types/jest": "^29.5.2",
"@types/jsonwebtoken": "^9.0.7",
Expand Down
7 changes: 7 additions & 0 deletions apps/api/src/app.module.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { ThrottlerModule } from '@nestjs/throttler';
import { TypeOrmModule } from '@nestjs/typeorm';

import { TypeOrmConfigService } from '@/config/typeorm.config';
Expand All @@ -16,6 +17,12 @@ import { UserModule } from './user/user.module';
isGlobal: true,
envFilePath: '.env',
}),
ThrottlerModule.forRoot([
{
ttl: 60000,
limit: 100,
},
]),
AuthModule,
TicleModule,
StreamModule,
Expand Down
59 changes: 37 additions & 22 deletions apps/api/src/auth/auth.controller.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { Body, Controller, Get, Post, Res, UseGuards } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { ApiBody, ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger';
import { ThrottlerGuard } from '@nestjs/throttler';
import { Response } from 'express';

import { GetUserId } from '@/common/decorator/get-userId.decorator';
import { CookieConfig } from '@/config/cookie.config';

import { AuthService } from './auth.service';
import { LocalLoginRequestDto } from './dto/localLoginRequest.dto';
Expand All @@ -16,13 +18,18 @@ import { LocalAuthGuard } from './local/local-auth.guard';
@Controller('auth')
@ApiTags('Auth')
export class AuthController {
private readonly redirectUrl: string;

constructor(
private authService: AuthService,
private configService: ConfigService
) {}
private readonly authService: AuthService,
private readonly configService: ConfigService,
private readonly cookieConfig: CookieConfig
) {
this.redirectUrl = this.configService.get<string>('LOGIN_REDIRECT_URL');
}

@Post('signup')
@ApiOperation({ summary: '회원가입' })
@ApiOperation({ summary: '로컬 회원가입' })
@ApiResponse({ status: 201, type: SignupResponseDto })
@ApiResponse({ status: 409 })
async signup(@Body() createUserDto: LocalSignupRequestDto): Promise<SignupResponseDto> {
Expand All @@ -37,7 +44,16 @@ export class AuthController {
@ApiResponse({ status: 401 })
@UseGuards(LocalAuthGuard)
localLogin(@GetUserId() userId: number, @Res() response: Response) {
this.cookieInsertJWT(response, userId);
this.loginProcess(response, userId);
}

@Get('guest/login')
@ApiOperation({ summary: '게스트 로그인' })
@ApiResponse({ status: 302, description: '홈으로 리다이렉션' })
@UseGuards(ThrottlerGuard)
async guestLogin(@Res() response: Response) {
const guestUser = await this.authService.createGuestUser();
this.loginProcess(response, guestUser.id);
}

@Get('google/login')
Expand All @@ -50,7 +66,7 @@ export class AuthController {
@Get('google/callback')
@UseGuards(GoogleAuthGuard)
googleAuthCallback(@GetUserId() userId: number, @Res() response: Response) {
this.cookieInsertJWT(response, userId);
this.loginProcess(response, userId);
}

@Get('github/login')
Expand All @@ -63,25 +79,24 @@ export class AuthController {
@Get('github/callback')
@UseGuards(GitHubAuthGuard)
githubAuthCallback(@GetUserId() userId: number, @Res() response: Response) {
this.cookieInsertJWT(response, userId);
this.loginProcess(response, userId);
}

private setAuthCookie(response: Response, accessToken: string) {
response.cookie('accessToken', accessToken, {
httpOnly: true,
secure: this.configService.get<string>('NODE_ENV') === 'production',
sameSite: 'lax',
path: '/',
});
@Get('logout')
@ApiOperation({ summary: '로그아웃' })
@ApiResponse({ status: 302, description: '홈으로 리다이렉션' })
logout(@Res() response: Response) {
response.clearCookie('accessToken', this.cookieConfig.getAuthCookieOptions());
this.redirectToHome(response);
}

private async cookieInsertJWT(
response: Response,
userId: number,
redirectUrl: string = this.configService.get<string>('LOGIN_REDIRECT_URL')
) {
const { accessToken } = await this.authService.createJWT(userId);
this.setAuthCookie(response, accessToken);
response.redirect(redirectUrl);
private loginProcess(response: Response, userId: number) {
const { accessToken } = this.authService.createJWT(userId);
response.cookie('accessToken', accessToken, this.cookieConfig.getAuthCookieOptions());
this.redirectToHome(response);
}

private redirectToHome(response: Response) {
response.redirect(this.redirectUrl);
}
}
10 changes: 9 additions & 1 deletion apps/api/src/auth/auth.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { ConfigModule, ConfigService } from '@nestjs/config';
import { JwtModule } from '@nestjs/jwt';
import { PassportModule } from '@nestjs/passport';

import { CookieConfig } from '@/config/cookie.config';
import { UserModule } from '@/user/user.module';

import { AuthController } from './auth.controller';
Expand All @@ -27,6 +28,13 @@ import { LocalStrategy } from './local/local.strategy';
}),
],
controllers: [AuthController],
providers: [AuthService, LocalStrategy, JwtStrategy, GitHubStrategy, GoogleStrategy],
providers: [
AuthService,
LocalStrategy,
JwtStrategy,
GitHubStrategy,
GoogleStrategy,
CookieConfig,
],
})
export class AuthModule {}
11 changes: 2 additions & 9 deletions apps/api/src/auth/auth.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jest.mock('bcrypt', () => ({

describe('AuthService', () => {
let service: AuthService;
let userService: UserService;

let jwtService: JwtService;

// Mock UserService
Expand Down Expand Up @@ -49,7 +49,7 @@ describe('AuthService', () => {
}).compile();

service = module.get<AuthService>(AuthService);
userService = module.get<UserService>(UserService);

jwtService = module.get<JwtService>(JwtService);

// Clear all mocks before each test
Expand All @@ -63,13 +63,6 @@ describe('AuthService', () => {
password: 'password123',
};

const mockUser = {
id: 1,
username: 'testuser',
password: 'hashedPassword',
email: '[email protected]',
};

it('should throw UnauthorizedException if user is not found', async () => {
mockUserService.findUserByUsername.mockResolvedValue(null);

Expand Down
34 changes: 25 additions & 9 deletions apps/api/src/auth/auth.service.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { BadRequestException, Injectable, UnauthorizedException } from '@nestjs/common';
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import * as bcrypt from 'bcrypt';
import { Provider } from '@repo/types';

import { CreateSocialUserDto } from '@/user/dto/createSocialUser.dto';
import { UserService } from '@/user/user.service';
Expand All @@ -15,11 +16,7 @@ export class AuthService {
) {}

async signupLocal(signupRequestDto: LocalSignupRequestDto) {
const existingUser = await this.userService.findUserByUsername(signupRequestDto.username);
if (existingUser) {
throw new BadRequestException('이미 사용 중인 사용자 이름입니다.');
}
return this.userService.createLocalUser({ provider: 'local', ...signupRequestDto });
return this.userService.createLocalUser({ provider: Provider.local, ...signupRequestDto });
}

async validateLocalLogin(username: string, inputPassword: string) {
Expand All @@ -31,8 +28,27 @@ export class AuthService {
if (!isPasswordValid) {
throw new UnauthorizedException('잘못된 로그인 정보');
}
const { password, ...result } = user;
return result;
return user;
}

async createGuestUser() {
const randomNum = Math.floor(Math.random() * 10000);
const response = await fetch('https://api.thecatapi.com/v1/images/search');
const catImageUrl = (await response.json())[0].url;

const guestUser = {
username: `guest_${randomNum}`,
password: `guest_password_${randomNum}`,
email: `[email protected]`,
nickname: `guest_${randomNum}`,
introduce: `게스트 사용자입니다. `,
profileImageUrl: catImageUrl,
};
const user = await this.userService.findUserByUsername(guestUser.username);
if (!user) {
return this.userService.createLocalUser({ provider: Provider.guest, ...guestUser });
}
return user;
}

async checkSocialUser(socialUserData: CreateSocialUserDto) {
Expand All @@ -46,7 +62,7 @@ export class AuthService {
return user;
}

async createJWT(userId: number) {
createJWT(userId: number) {
const payload = { sub: userId };
return {
accessToken: this.jwtService.sign(payload),
Expand Down
5 changes: 3 additions & 2 deletions apps/api/src/auth/github/github.strategy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@ import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { PassportStrategy } from '@nestjs/passport';
import { Profile, Strategy } from 'passport-github2';
import { Provider } from '@repo/types';

import { AuthService } from '../auth.service';

@Injectable()
export class GitHubStrategy extends PassportStrategy(Strategy, 'github') {
export class GitHubStrategy extends PassportStrategy(Strategy, Provider.github) {
constructor(
private configService: ConfigService,
private authService: AuthService
Expand All @@ -23,7 +24,7 @@ export class GitHubStrategy extends PassportStrategy(Strategy, 'github') {
const { id, username, emails, photos } = profile;

const user = {
provider: 'github',
provider: Provider.github,
socialId: id,
nickname: username,
email: emails[0].value,
Expand Down
5 changes: 3 additions & 2 deletions apps/api/src/auth/google/google.strategy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@ import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { PassportStrategy } from '@nestjs/passport';
import { Profile, Strategy } from 'passport-google-oauth20';
import { Provider } from '@repo/types';

import { AuthService } from '../auth.service';

@Injectable()
export class GoogleStrategy extends PassportStrategy(Strategy, 'google') {
export class GoogleStrategy extends PassportStrategy(Strategy, Provider.google) {
constructor(
private configService: ConfigService,
private authService: AuthService
Expand All @@ -23,7 +24,7 @@ export class GoogleStrategy extends PassportStrategy(Strategy, 'google') {
const { id, displayName, emails, photos } = profile;

const user = {
provider: 'google',
provider: Provider.google,
socialId: id,
nickname: displayName,
email: emails[0].value,
Expand Down
17 changes: 13 additions & 4 deletions apps/api/src/auth/jwt/jwt-auth.guard.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,26 @@
import { ExecutionContext, Injectable } from '@nestjs/common';
import { ExecutionContext, Injectable, UnauthorizedException } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { ErrorMessage } from '@repo/types';

@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {
getRequest(context: ExecutionContext) {
const req = context.switchToHttp().getRequest();

const cookies = req.cookies;

const token = cookies['accessToken'];

if (token) req.headers.authorization = `Bearer ${token}`;
if (!token) {
throw new UnauthorizedException(ErrorMessage.LOGIN_REQUIRED);
}
req.headers.authorization = `Bearer ${token}`;

return req;
}

handleRequest(err: any, user: any) {
if (err || !user) {
throw new UnauthorizedException(ErrorMessage.INVALID_AUTHENTICATION_INFORMATION);
}
return user;
}
}
Loading

0 comments on commit 2a6c714

Please sign in to comment.