Skip to content

Commit

Permalink
Merge pull request #76 from Together42/dev
Browse files Browse the repository at this point in the history
ci: 운영 서버 배포
  • Loading branch information
sideseal authored Jan 5, 2024
2 parents 7b7c83a + 3a4bf4f commit 12bf706
Show file tree
Hide file tree
Showing 16 changed files with 343 additions and 35 deletions.
3 changes: 1 addition & 2 deletions .github/workflows/deploy-dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,5 @@ jobs:
SERVICE_KEY=${{ secrets.OPENAPI_HOLIDAY_SERVICE_KEY }}
SLACK_BOT_USER_OAUTH_ACCESS_TOKEN=${{ secrets.SLACK_BOT_USER_OAUTH_ACCESS_TOKEN_DEV }}
SLACK_CHANNEL_JIPHYEONJEON=${{ secrets.SLACK_CHANNEL_JIPHYEONJEON_DEV }}
FRONT_URL=${{ secrets.FRONT_URL_DEV }}
TOGETHER_HOME_URL=${{ secrets.TOGETHER_HOME_URL }}" > .env.dev
FRONT_URL=${{ secrets.FRONT_URL_DEV }}" > .env.dev
docker-compose --env-file .env.dev up -d --build backend_dev
3 changes: 1 addition & 2 deletions .github/workflows/deploy-main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,5 @@ jobs:
SERVICE_KEY=${{ secrets.OPENAPI_HOLIDAY_SERVICE_KEY }}
SLACK_BOT_USER_OAUTH_ACCESS_TOKEN=${{ secrets.SLACK_BOT_USER_OAUTH_ACCESS_TOKEN }}
SLACK_CHANNEL_JIPHYEONJEON=${{ secrets.SLACK_CHANNEL_JIPHYEONJEON }}
FRONT_URL=${{ secrets.TOGETHER_HOME_URL }}
TOGETHER_HOME_URL=${{ secrets.TOGETHER_HOME_URL }}" > .env.prod
FRONT_URL=${{ secrets.FRONT_URL }}" > .env.prod
docker-compose --env-file .env.prod up -d --build backend
91 changes: 90 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,99 @@


## Description
친해지길바라 express -> nest 마이그레이션
친해지길 바라 백엔드를 리팩토링합니다.
- DB 스키마 및 REST API 재설계
- JavaScript/Express -> TypeScript/NestJS
- Raw Query -> TypeORM

<br>

## 🏠 [HOME PAGE](https://together.42jip.net/)

**친해지길 바라**는 동아리 부원들의 상호작용을 돕기 위해 만든 웹 서비스입니다. 원하는 이벤트를 생성하여 사람들을 모을 수 있고, 이미 생성된 이벤트에 참가 신청을 할 수도 있습니다. 신청자들을 기반으로 팀 매칭 기능을 제공합니다.

<br>

## 기술 스택
<p align='center'>
<img src="https://img.shields.io/badge/Node.js-339933?logo=Node.js&logoColor=white"/>
<img src="https://img.shields.io/badge/TypeScript-3178C6?logo=TypeScript&logoColor=white"/>
<img src="https://img.shields.io/badge/NestJS-E0234E?style=flat&logo=NestJS&logoColor=white"/>
<img src="https://img.shields.io/badge/MySQL-4479A1?logo=MySQL&logoColor=white">
<img src="https://img.shields.io/badge/Google OAuth-4285F4?logo=google&logoColor=white">
<img src="https://img.shields.io/badge/Slack Bot-4A154B?logo=Slack&logoColor=white">
<br>
<img src="https://img.shields.io/badge/GitHub Actions-2088FF?logo=GitHub Actions&logoColor=white">
<img src="https://img.shields.io/badge/Amazon Route53-8C4FFF?logo=Amazon Route53&logoColor=white">
<img src="https://img.shields.io/badge/Amazon EC2-FF9900?logo=Amazon EC2&logoColor=white">
<img src="https://img.shields.io/badge/Amazon RDS-527FFF?logo=Amazon RDS&logoColor=white">
</p>

<br>

## 실행 방법
### 1. git clone
```sh
git clone https://github.com/Together42/nest-backend.git
```
### 2. 백엔드 루트 폴더에 `.env` 생성
```
# 백엔드를 실행할 port, 디폴트 9999
BACK_PORT=
# Google OAuth 설정
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
GOOGLE_REDIRECT_URI=
GOOGLE_PROMPT=
# JWT 설정
JWT_SECRET=
JWT_EXPIRES_IN=
JWT_REFRESH_SECRET=
JWT_REFRESH_EXPIRES_IN=
# DB 설정
MYSQL_ROOT_PASSWORD=
MYSQL_HOST=
MYSQL_DATABASE=
MYSQL_USER=
MYSQL_PASSWORD=
MYSQL_PORT=
# 슬랫봇 설정
SLACK_BOT_USER_OAUTH_ACCESS_TOKEN=
SLACK_CHANNEL_JIPHYEONJEON=
# Holiday를 가져오기 위한 OpenAPI 설정
SERVICE_KEY=
# 클라이언트 주소, 백엔드 로컬 환경에서 실행 시 프론트도 로컬 환경 실행 추천.
FRONT_URL=http://localhost:{프론트엔드 실행 포트}
```

### 3. 로컬 DB 실행
```
docker-compose up test_db -d --build
```

### 4. 프론트엔드 클론 및 실행
👉 [프론트엔드 레포 바로 가기](https://github.com/Together42/frontend?tab=readme-ov-file#%EF%B8%8F-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EA%B5%AC%EB%8F%99-%EB%B0%A9%EB%B2%95)

### 5. 실행
```
yarn install && yarn start:dev
```

### 6. Swagger에서 API 명세 확인
👉 `http://localhost:{백엔드 실행 포트}/swagger`

<br>

## ERD
![together-ERD](https://github.com/Together42/nest-backend/assets/74581396/88d077a5-526b-4750-8358-7145bd1a80b6)

<br>

## License

Expand Down
2 changes: 0 additions & 2 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ services:
SLACK_BOT_USER_OAUTH_ACCESS_TOKEN: ${SLACK_BOT_USER_OAUTH_ACCESS_TOKEN}
SLACK_CHANNEL_JIPHYEONJEON: ${SLACK_CHANNEL_JIPHYEONJEON}
FRONT_URL: ${FRONT_URL}
TOGETHER_HOME_URL: ${TOGETHER_HOME_URL}
tty: true
backend_dev:
build: .
Expand Down Expand Up @@ -59,7 +58,6 @@ services:
SLACK_BOT_USER_OAUTH_ACCESS_TOKEN: ${SLACK_BOT_USER_OAUTH_ACCESS_TOKEN}
SLACK_CHANNEL_JIPHYEONJEON: ${SLACK_CHANNEL_JIPHYEONJEON}
FRONT_URL: ${FRONT_URL}
TOGETHER_HOME_URL: ${TOGETHER_HOME_URL}
tty: true
test_db:
image: mysql:latest
Expand Down
19 changes: 13 additions & 6 deletions src/common/dto/error-response.dto.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
import {
HttpExceptionBody,
HttpExceptionBodyMessage,
HttpStatus,
} from '@nestjs/common';
import { HttpExceptionBody, HttpExceptionBodyMessage, HttpStatus } from '@nestjs/common';
import { ApiProperty } from '@nestjs/swagger';
import { STATUS_CODES } from 'http';

Expand Down Expand Up @@ -52,12 +48,23 @@ export class InternalServerExceptionBody extends ServerExceptionBody {
statusCode = HttpStatus.INTERNAL_SERVER_ERROR;
}

export class UnauthorizedExceptionBody extends ClientExceptionBody {
@ApiProperty({
type: 'string',
example: STATUS_CODES[HttpStatus.UNAUTHORIZED],
})
error = STATUS_CODES[HttpStatus.UNAUTHORIZED]!;

@ApiProperty({ type: Number, example: HttpStatus.UNAUTHORIZED })
statusCode = HttpStatus.UNAUTHORIZED;
}

export class NotFoundExceptionBody extends ClientExceptionBody {
@ApiProperty({
type: 'string',
example: STATUS_CODES[HttpStatus.NOT_FOUND],
})
error = STATUS_CODES[HttpStatus.FORBIDDEN]!;
error = STATUS_CODES[HttpStatus.NOT_FOUND]!;

@ApiProperty({ type: Number, example: HttpStatus.NOT_FOUND })
statusCode = HttpStatus.NOT_FOUND;
Expand Down
2 changes: 1 addition & 1 deletion src/common/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export const meetupCreatedMessage = ({
)}\n${description}\n이벤트가 생성되었습니다. 서둘러 참석해주세요!`,
}),
Blocks.Section({
text: process.env.TOGETHER_HOME_URL,
text: process.env.FRONT_URL,
}),
)
.buildToObject();
Expand Down
2 changes: 1 addition & 1 deletion src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ async function bootstrap() {
app.useGlobalPipes(new ValidationPipe({ transform: true }));

app.enableCors({
origin: [process.env.FRONT_URL || 'http://localhost:3050'],
origin: [process.env.FRONT_URL],
methods: 'GET,HEAD,PUT,PATCH,POST,DELETE,OPTIONS',
credentials: true,
});
Expand Down
5 changes: 5 additions & 0 deletions src/rotation/dto/create-registration.dto.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsArray, IsDefined, IsNumber } from 'class-validator';

export class CreateRegistrationDto {
@ApiProperty({
example: [1, 2, 3],
description: '신청자가 참여하지 못한다고 선택한 다음 달 사서 업무 일정이 담긴 배열',
})
@IsDefined()
@IsArray()
@IsNumber({}, { each: true })
Expand Down
14 changes: 14 additions & 0 deletions src/rotation/dto/create-rotation.dto.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,29 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsArray, IsDefined, IsNotEmpty, IsNumber } from 'class-validator';

export class CreateRotationDto {
@ApiProperty({
example: [25],
description:
'신청자가 참여하고자 하는 일정의 일이 담긴 배열. DB에서 JSON 타입으로 저장되어야 해서 개수와 상관 없이 JSON 배열 형태를 가집니다.',
})
@IsDefined()
@IsArray()
@IsNumber({}, { each: true })
attendDate: JSON;

@ApiProperty({
example: 2024,
description: '신청자가 참여하고자 하는 연도',
})
@IsNotEmpty()
@IsNumber()
year: number;

@ApiProperty({
example: 1,
description: '신청자가 참여하고자 하는 월',
})
@IsNotEmpty()
@IsNumber()
month: number;
Expand Down
9 changes: 9 additions & 0 deletions src/rotation/dto/find-rotation-query.dto.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,19 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsNumber, IsOptional } from 'class-validator';

export class FindRotationQueryDto {
@ApiProperty({
example: 1,
description: '로테이션 정보를 찾는 기준이 되는 월. 기본값은 다음 달 월입니다.',
})
@IsOptional()
@IsNumber()
month?: number;

@ApiProperty({
example: 2024,
description: '로테이션 정보를 찾는 기준이 되는 연도. 기본값은 다음 달 연도입니다.',
})
@IsOptional()
@IsNumber()
year?: number;
Expand Down
15 changes: 15 additions & 0 deletions src/rotation/dto/remove-rotation.dto.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,29 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsNotEmpty, IsNumber, IsOptional } from 'class-validator';

export class RemoveRotationQueryDto {
@ApiProperty({
example: 25,
description: '로테이션 정보를 삭제하는 기준이 되는 일',
})
@IsNotEmpty()
@IsNumber()
day: number;

@ApiProperty({
example: 1,
description:
'로테이션 정보를 삭제하는 기준이 되는 월. 기본값은 다음 달 월이지만 값을 적는 것을 권장합니다.',
})
@IsOptional()
@IsNumber()
month?: number;

@ApiProperty({
example: 2024,
description:
'로테이션 정보를 삭제하는 기준이 되는 연도. 기본값은 다음 달 연도이지만 값을 적는 것을 권장합니다.',
})
@IsOptional()
@IsNumber()
year?: number;
Expand Down
27 changes: 20 additions & 7 deletions src/rotation/dto/update-rotation.dto.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,41 @@
// import { PartialType } from '@nestjs/mapped-types';
// import { CreateRotationDto } from './create-rotation.dto';
import {
ArrayNotEmpty,
IsArray,
IsDefined,
IsNotEmpty,
IsNumber,
} from 'class-validator';
import { ApiProperty } from '@nestjs/swagger';
import { ArrayNotEmpty, IsArray, IsDefined, IsNotEmpty, IsNumber } from 'class-validator';

export class UpdateRotationDto {
@ApiProperty({
example: [25],
description:
'변경하고자 하는 유저의 기존 사서 업무 일정의 일이 담긴 배열. DB에서 JSON 타입으로 저장되어야 해서 개수와 상관 없이 JSON 배열 형태를 가집니다.',
})
@IsDefined()
@ArrayNotEmpty()
@IsArray()
@IsNumber({}, { each: true })
attendDate: JSON;

@ApiProperty({
example: [14],
description:
'변경하고자 하는 유저의 변경된 사서 업무 일정의 일이 담긴 배열. DB에서 JSON 타입으로 저장되어야 해서 개수와 상관 없이 JSON 배열 형태를 가집니다.',
})
@IsNotEmpty()
@IsNumber()
updateDate: number;

@ApiProperty({
example: 2024,
description: '변경하고자 하는 유저의 변경된 일정의 연도',
})
@IsNotEmpty()
@IsNumber()
year: number;

@ApiProperty({
example: 1,
description: '변경하고자 하는 유저의 변경된 일정의 월',
})
@IsNotEmpty()
@IsNumber()
month: number;
Expand Down
Loading

0 comments on commit 12bf706

Please sign in to comment.