Skip to content

Commit

Permalink
merge: [BE] 백엔드 배포
Browse files Browse the repository at this point in the history
백엔드 배포
  • Loading branch information
Gseungmin authored Dec 13, 2023
2 parents b468a5a + e50ed55 commit 4b0ce2e
Show file tree
Hide file tree
Showing 35 changed files with 674 additions and 198 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/back-center-cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,6 @@ jobs:
echo "REDIS_HOST=${{ secrets.REDIS_HOST }}" >> /root/.env
echo "REDIS_PORT=${{ secrets.REDIS_PORT }}" >> /root/.env
echo "REDIS_PASSWORD=${{ secrets.REDIS_PASSWORD }}" >> /root/.env
echo "CHAT_SOCKET_URL=${{ secrets.CHAT_SOCKET_URL }}" >> /root/.env
echo "SIGNAL_SOCKET_URL=${{ secrets.SERVER_A_SOCKET_URL }}" >> /root/.env
./deploy.sh
1 change: 1 addition & 0 deletions .github/workflows/back-chat-cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,4 +56,5 @@ jobs:
echo "Accept=${{ secrets.Accept }}" >> /root/.env
echo "ContentType=${{ secrets.ContentType }}" >> /root/.env
echo "MONGO_PROD=${{ secrets.MONGO_PROD }}" >> /root/.env
echo "SOCKET_URL=${{ secrets.CHAT_SOCKET_URL }}" >> /root/.env
./deploy.sh
145 changes: 118 additions & 27 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,46 +1,137 @@
# AlgoITNi
#### 동료들과 함께 소통하며(화상, 음성, 채팅) 알고리즘 학습을 할 수 있는 플랫폼
<img src="https://github.com/boostcampwm2023/web05-AlgoITNi/assets/84272873/03e6fe44-49fc-4ef1-9eb1-52747bb93251">
<div align="center">
<img src="https://github.com/boostcampwm2023/web05-AlgoITNi/assets/84272873/5d587a3b-d76d-4990-adfb-bb0624d74deb">

##### [팀 노션 바로가기](!https://energetic-palm-634.notion.site/AlgoITNi-4d712d57a7be42bfb625d23d5eab5453?pvs=4)

---
# 팀원소개
<h3>동료들과 함께 소통하며 알고리즘 학습을 할 수 있는 플랫폼</h3>
<h5>🗝️ KeyWords<h5>
#WebRTC #Socket #CRDT
<br>
<div align="center">
<img src="https://img.shields.io/badge/node-339933?&logo=node.js&logoColor=white">
<img src="https://img.shields.io/badge/NestJS-E0234E?logo=NestJS&logoColor=white">
<img src="https://img.shields.io/badge/TypeScript-3178C6?logo=typescript&logoColor=white">
<img src="https://img.shields.io/badge/MySQL-4479A1?logo=MySQL&logoColor=white"/>
<img src="https://img.shields.io/badge/MongoDB-339933?logo=MongoDB&logoColor=white">
<img src="https://img.shields.io/badge/Redis-DC382D?logo=Redis&logoColor=white">
<br>
<img src="https://img.shields.io/badge/React-61DAFB?logo=React&logoColor=white">
<img src="https://img.shields.io/badge/tailwindcss-DB7093?logo=tailwindcss&logoColor=white">
<img src="https://img.shields.io/badge/Socket.io-010101?logo=Socket.io&logoColor=white">
<img src="https://img.shields.io/badge/WebRTC-333333?logo=WebRTC&logoColor=white">
<img src="https://img.shields.io/badge/zustand-333333?logo=zustand&logoColor=white">
<img src="https://img.shields.io/badge/jest-944058?logo=jest&logoColor=white">
<br>
<img src="https://img.shields.io/badge/Docker-2496ED?logo=Docker&logoColor=white">
<img src="https://img.shields.io/badge/Nginx-009639?logo=Nginx&logoColor=white">
<img src="https://img.shields.io/badge/Naver Cloud Platform-F9F9F9?logo=Naver">
<img src="https://img.shields.io/badge/AWS S3-FF9900?logo=amazon-aws&logoColor=white">
<img src="https://img.shields.io/badge/AWS CloudFront-5930B4?logo=amazon-aws&logoColor=white">
</div>
<br>
</div>

| J065 서위영 | J094 이동길 | J126 이희경 | J151 지승민 |
| :----------------------------------------------------------------------------: | :----------------------------------------------------------------------------: | :--------------------------------------------------------------------------: | :--------------------------------------------------------------------------: |
| <img src="https://avatars.githubusercontent.com/u/96584994?v=4" width="200" /> | <img src="https://avatars.githubusercontent.com/u/99241871?v=4" width="200" /> | <img src="https://avatars.githubusercontent.com/u/84272873?v=4" width="200"> | <img src="https://avatars.githubusercontent.com/u/87487149?v=4" width="200"> |
| **Front-End** | **Front-End** | **Back-End** | **Back-End** |
| [HBSPS](https://github.com/HBSPS) | [d0422](https://github.com/d0422) | [HKLeeeee](https://github.com/HKLeeeee) | [Gseungmin](https://github.com/Gseungmin) |

---

# Tech
`#WebRTC` `#Socket` `#CRDT`
# 🔎서버 아키텍처
![Untitled](https://github.com/boostcampwm2023/web05-AlgoITNi/assets/84272873/019fa58f-c8c0-47f3-87a9-ea7cf36722d0)

| 분야 | 기술스택 |
|----|------|
| FE | React, Typescript, WebRTC, github action, socket.io |
| BE | NestJS, Typescript, Docker, github action, socket.io, mongoDB, MySQL, Redis |

# 주요 기능 소개
### 메인화면
# 🔎 주요 기능

### 🐱 화상회의
방 생성 버튼으로 새롭게 방을 만들거나 공유받은 방 코드로 이미 있는 방에 참여할 수 있습니다.
![EnterRoom](https://github.com/boostcampwm2023/web05-AlgoITNi/assets/84272873/fea34177-cffe-4700-914c-a304d0302f51)

동료들과 화상회의를 하며 소통할 수 있습니다. <br>
WebRTC P2P로 통신합시다. <br>
![4명입장](https://github.com/boostcampwm2023/web05-AlgoITNi/assets/84272873/323fc7d1-5b4e-455f-923d-5db5855a0146)

![main](https://github.com/boostcampwm2023/web05-AlgoITNi/assets/84272873/f4b34b43-efbf-4ada-8806-6a7bb9cd17d5)

### 🐱 화상회의
동료들과 화상회의를 하며 소통할 수 있습니다.
### 😎 코드 공동 편집
코드 편집기를 통해 코드를 공동 편집할 수 있습니다. <br>
코드 편집기를 통해 코드를 작성할 수 있습니다. <br>
CRDT로 공동편집을 구현해 참여한 사람들과 함께 편집할 수 있습니다.<br>
집단 지성을 발휘해보세요!

![crdt](https://github.com/boostcampwm2023/web05-AlgoITNi/assets/84272873/42d71ab8-da12-4288-9a5d-47f9524cce96)

### 📥 문제 보기
문제의 링크를 입력해 문제를 보면서 풀이할 수 있습니다. <br>
크롤링을 통해서 입력한 링크를 가져옵니다. <br>
백준 사이트를 가장 잘 보여줍니다.<br>
![showProm2](https://github.com/boostcampwm2023/web05-AlgoITNi/assets/84272873/c2174696-2465-47dd-b8ab-aa47c7e7abbc)

### 🐍 코드 실행
작성한 코드를 실행하고 실행 결과를 확인할 수 있습니다.
작성한 코드를 실행하고 실행 결과를 확인할 수 있습니다.<br>
소켓과 메세지 큐를 통해 코드 실행이 요청됩니다. <br>
지원 언어 : `Python` `Javascript` `Java` `C` `Swift` `Kotlin`

![running](https://github.com/boostcampwm2023/web05-AlgoITNi/assets/84272873/282bcc84-4045-49f6-a5f7-2e5995483e73)

### 💬 채팅
채팅을 통해서도 소통할 수 있습니다. <br>
음성 채팅이 어려운 상황에서나 참고할 자료를 보낼 때 활용할 수 있습니다.
채팅을 통해서도 소통할 수 있습니다. 음성 채팅이 어려운 상황에서나 참고할 자료를 보낼 때 활용할 수 있습니다. <br>
Pub/Sub을 활용해 다중 서버 환경에서도 채팅을 할 수 있습니다. <br>
채팅 중 **클로바X**에게 질문하고 답변 받을 수 있습니다.

![chat](https://github.com/boostcampwm2023/web05-AlgoITNi/assets/84272873/a8f14d69-863e-4c6f-8779-ee6422d2dcce)

# 우리가 일하는 방식
# 🔎 기술적 도전
### 프론트엔드의 기술적 도전
- [코드 에디터에서의 공동편집을 위한 CRDT구현](https://energetic-palm-634.notion.site/4826739090cf431e829bd928fd46a297?v=09650c23000d477f828c92563f0c8368&pvs=4)
- 3번의 시도와 구현, 문제해결기와 더 나은 기능을 위해 라이브러리를 도입한 이야기
- [홈화면 성능 최적화 도전하기](https://energetic-palm-634.notion.site/f7286ebaa50f484da0a88a37888f77dc?v=f46a3e1fd63e435c9b1f642d220888ac&pvs=40)
- 더 나은 UX를 위해 홈화면 초기 렌더링 성능 약 14% 개선
- [수많은 모달을 관리하기 위한 공통 모달 만들기](https://energetic-palm-634.notion.site/23cca8a3b3b44fce9a9df4b0a7e70dcd?v=9c4c39359a0e445dbdc2b7cdb2d74c68&pvs=4)
- 중첩 모달과 많은 모달들을 쉽고 효율적으로 관리하기위한 모달 시스템 만들기
### 백엔드의 기술적 도전
- [다중 서버 환경에서 코드 실행 동시 요청 처리하기](https://energetic-palm-634.notion.site/bfeb2b52f3f34fe2af9bf93f254f8f5c?v=82acb687cdb74475986d223ac753bf05&pvs=4)
- 소켓, Message Queue, Pub/Sub을 도입하여 CPU 사용량 43%, Memory 사용량 21% 감소시킨 이야기
- [다중 소켓 서버 트래픽 관리하기](https://energetic-palm-634.notion.site/d243a71d17f94018bd94a6b825fddfe4?v=803c0b95332343e1918ee10ff269e4f6&pvs=4)
- 중앙서버와 Pub/Sub 도입으로 트래픽 분산 & 확장에 자유로운 구조로 개선
- [DB 부하 분산하기](https://energetic-palm-634.notion.site/8c129aa38b2f40c784b7641d8941571d?v=340d00941d4641f9bc47ee292d9d9cf5&pvs=4)
- Master DB CPU 사용룰 90% 감소 및 요청처리 95% 증가
- [Nginx 캐싱으로 크롤링 속도 높이기](https://energetic-palm-634.notion.site/270f92cdadaa475aa3827b300c511172?v=d67c232d930549948bdd0ad4c306c14f&pvs=4)
- 429 Error 및 InMemory 용량부족 해결 과정 (16만건 처리에 걸리는 시간 64% 감소)
- [도커 이미지 최적화](https://energetic-palm-634.notion.site/f35c15bc99a842a18ce095fa6bf1c806?v=efbb8ec67beb43b89792200fc1f3c9a1&pvs=4)
- 도커 이미지 사이즈 85% 감소시킨 이야기
- [서버에서 OAuth 처리하여 자원 보호하기](https://energetic-palm-634.notion.site/69f2e78273884a65b52c370debb83073?v=2b272ead31924af59732edbda24cef84&pvs=4)
- OAuth2.0을 도입하고 안전하게 자원을 관리하는 이야기

# 🔎 개발기
개발하면서 공부한 내용들과 고민 과정, 이유, 해결 방법을 기록했습니다.

[FE]
- [Web RTC를 이해해보자](https://energetic-palm-634.notion.site/Web-RTC-1e8d918a19be444da6b0656167df35a6?pvs=4)
- [S3, CloudFront로 OAC를 통해 프론트엔드 배포하기](https://energetic-palm-634.notion.site/FrontEnd-CICD-with-S3-Cloud-Front-64ac0d2dab194a04b14743d034deb1c5?pvs=4)
- [로컬 환경에서 쿠키 테스트하기](https://energetic-palm-634.notion.site/8f53abc52d6a4b72816fc4aa9c211de2?pvs=4)
- [채팅창에 쓰로틀링 적용하기](https://energetic-palm-634.notion.site/9e768460a8904a8e859ba13cab0f78c2?pvs=4)
- [쉘 스크립트로 디렉토리별 pre-commit 적용하기](https://energetic-palm-634.notion.site/pre-commit-a60bec2c72e440a2ad414a1ab4b18f29?pvs=4)

[BE]
- [세션을 활용해 로그인 후 원래위치로 돌아가기](https://energetic-palm-634.notion.site/d2f6157bdcef40a6a72eacbb28acb798?pvs=4)
- [Transaction 관심사 분리하기](https://energetic-palm-634.notion.site/AsyncLocalStorage-Transaction-34f42523c0ec43f4b633eb7944c0b29d?pvs=4)
- [SSL Termination을 통해 안전하게 HTTP 통신하기](https://energetic-palm-634.notion.site/SSL-Termination-HTTP-70c76949740f4452a2899fa1e617628a?pvs=4)
- [Blue-Green으로 무중단 배포하기](https://energetic-palm-634.notion.site/57396ff1e3174251ba2c7487ab070a53?pvs=4)
- [Clove X 도입하기](https://www.notion.so/Clova-Studio-d990f41d3e814b708906e64fd4707a24?pvs=4)


[👉 더 많은 기술정리 보러가기](https://www.notion.so/f4562ec49e0245d2b6ef203588c031ea?v=fbfeb754b1a4471e8ffc174a45c64346&pvs=4)


# 🔎 팀 소개

| J065 서위영 | J094 이동길 | J126 이희경 | J151 지승민 |
|:------------------------------------------------------------------------------:| :----------------------------------------------------------------------------: | :--------------------------------------------------------------------------: | :--------------------------------------------------------------------------: |
| <img src="https://avatars.githubusercontent.com/u/96584994?v=4" width="200" /> | <img src="https://avatars.githubusercontent.com/u/99241871?v=4" width="200" /> | <img src="https://avatars.githubusercontent.com/u/84272873?v=4" width="200"> | <img src="https://avatars.githubusercontent.com/u/87487149?v=4" width="200"> |
| **Front-End** | **Front-End** | **Back-End** | **Back-End** |
| [@HBSPS](https://github.com/HBSPS) | [@d0422](https://github.com/d0422) | [@HKLeeeee](https://github.com/HKLeeeee) | [@Gseungmin](https://github.com/Gseungmin) |


## 우리가 일하는 방식
- [그라운드 룰](https://energetic-palm-634.notion.site/1f2cbea527e341c7ad1c8fd84ed5104d?pvs=4)
- [깃 컨벤션](https://energetic-palm-634.notion.site/Git-Convention-8563596644404eb49148a940773d2be8?pvs=4)
- [게더타운 규칙](https://energetic-palm-634.notion.site/b3b67313c1f748e7b58abf99466b000b?pvs=4)


---
<a href="https://energetic-palm-634.notion.site/AlgoITNi-4d712d57a7be42bfb625d23d5eab5453?pvs=4">😽 Team Notion </a>
2 changes: 2 additions & 0 deletions backEnd/api/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { RedisModule } from './redis/redis.module';
import { AuthModule } from './auth/auth.module';
import { CrawlerModule } from './crawler/crawler.module';
import { CodesModule } from './codes/codes.module';
import { TransactionModule } from './common/transaction/transaction.module';

@Module({
imports: [
Expand Down Expand Up @@ -69,6 +70,7 @@ import { CodesModule } from './codes/codes.module';
AuthModule,
CrawlerModule,
CodesModule,
TransactionModule,
],
controllers: [AppController],
providers: [AppService, TimeoutInterceptor, WinstonLogger],
Expand Down
10 changes: 3 additions & 7 deletions backEnd/api/src/codes/codes.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,12 +77,8 @@ export class CodesController {
async delete(@Req() req: Request, @Param('id') id: string) {
const user: UserInfoDto = req.user as UserInfoDto;
const userID = user.id;
try {
await this.codesService.delete(userID, id);
return { message: 'delete success' };
} catch (e) {
this.logger.error(e);
throw new ResourceNotFound();
}
const result = await this.codesService.delete(userID, id);
if (result.deletedCount <= 0) throw new ResourceNotFound();
return { message: 'delete success' };
}
}
66 changes: 23 additions & 43 deletions backEnd/api/src/codes/codes.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,12 @@ import { InjectConnection, InjectModel } from '@nestjs/mongoose';
import { Code } from './schemas/code.schemas';
import { Connection, Model } from 'mongoose';
import { SaveCodeDto } from './dto/saveCode.dto';
import { TransactionRollback } from '../common/exception/exception';
import {
getSession,
Transactional,
} from '../common/transaction/transaction.decorator';
import { ClientSession } from 'mongoose';
import { DeleteResult } from 'mongodb';

@Injectable()
export class CodesService {
Expand All @@ -13,20 +18,13 @@ export class CodesService {
@InjectConnection() private readonly connection: Connection,
) {}

async save(saveCodeDto: SaveCodeDto): Promise<Code> {
const session = await this.connection.startSession();
session.startTransaction();
try {
const code = await this.codeModel.create(saveCodeDto);
await session.commitTransaction();
return code;
} catch (e) {
await session.abortTransaction();
this.logger.error(e);
throw new TransactionRollback();
} finally {
await session.endSession();
}
@Transactional('mongoose')
async save(saveCodeDto: SaveCodeDto) {
const session = getSession();
const code = await this.codeModel.create([saveCodeDto], {
session: session,
});
return code[0];
}

async getAll(userID: number) {
Expand All @@ -36,38 +34,20 @@ export class CodesService {
async getOne(userID: number, objectID: string) {
return this.codeModel.find({ userID: userID, _id: objectID }).exec();
}

@Transactional('mongoose')
async update(userID: number, objectID: string, saveCodeDto: SaveCodeDto) {
const query = { userID: userID, _id: objectID };
const session = await this.connection.startSession();
session.startTransaction();
try {
const result = await this.codeModel.updateOne(query, saveCodeDto);
await session.commitTransaction();
return result;
} catch (e) {
await session.abortTransaction();
this.logger.error(e);
throw new TransactionRollback();
} finally {
await session.endSession();
}
return;
const session: ClientSession = getSession();
const result = await this.codeModel
.updateOne(query, saveCodeDto)
.session(session);
return result;
}

async delete(userID: number, objectID: string) {
@Transactional('mongoose')
async delete(userID: number, objectID: string): Promise<DeleteResult> {
const query = { userID: userID, _id: objectID };
const session = await this.connection.startSession();
session.startTransaction();
try {
await this.codeModel.deleteOne(query);
await session.commitTransaction();
} catch (e) {
await session.abortTransaction();
this.logger.error(e);
throw new TransactionRollback();
} finally {
await session.endSession();
}
const session = getSession();
return this.codeModel.deleteOne(query).session(session);
}
}
17 changes: 0 additions & 17 deletions backEnd/api/src/common/decorator/typeormTransactional.decorator.ts

This file was deleted.

5 changes: 4 additions & 1 deletion backEnd/api/src/common/exception/exception.filter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,10 @@ export class HttpExceptionFilter implements ExceptionFilter {
);
response.status(status).json({
statusCode: status,
message: exception.message,
message:
status !== HttpStatus.INTERNAL_SERVER_ERROR
? exception.message
: ResponseMessage.INTERNAL_SERVER_ERROR,
timestamp: new Date().toISOString(),
});
}
Expand Down
28 changes: 28 additions & 0 deletions backEnd/api/src/common/transaction/transaction.decorator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { applyDecorators, SetMetadata } from '@nestjs/common';
import { AsyncLocalStorage } from 'async_hooks';
import { ObjectLiteral, QueryRunner, Repository } from 'typeorm';
import { ClientSession } from 'mongoose';

export const TRANSACTIONAL_KEY = Symbol('TRANSACTION');
export type ORM = 'typeorm' | 'mongoose';
export function Transactional(orm: ORM): MethodDecorator {
return applyDecorators(SetMetadata(TRANSACTIONAL_KEY, orm));
}

export const queryRunnerLocalStorage = new AsyncLocalStorage<{
qr: QueryRunner;
}>();
export const sessionLocalStorage = new AsyncLocalStorage<{
session: ClientSession;
}>();

export function getLocalStorageRepository<T extends ObjectLiteral>(
target,
): Repository<T> {
const queryRunner = queryRunnerLocalStorage.getStore();
return queryRunner?.qr?.manager.getRepository(target);
}
export function getSession(): ClientSession {
const session = sessionLocalStorage.getStore();
return session?.session;
}
Loading

0 comments on commit 4b0ce2e

Please sign in to comment.