From 3c628ae7314b435bcd45119811f05dcc3f626ad6 Mon Sep 17 00:00:00 2001 From: rockpell Date: Tue, 1 Feb 2022 12:41:12 +0900 Subject: [PATCH 01/55] =?UTF-8?q?Doc:=20readme=EC=97=90=20env=20=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=20=EC=83=98=ED=94=8C=20=EC=B6=94=EA=B0=80,=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EB=82=B4=EC=9A=A9=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - env 파일 샘플 추가 - e2e 테스트 코드 실행 방법 추가 --- README.md | 56 +++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 54 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 35401c56..4d98ac5d 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ https://www.42world.kr/ ``` ├── config -│ └── dev.env +│ └── .env.dev ├── src │ ├── article │ ├── auth @@ -32,7 +32,11 @@ https://www.42world.kr/ ## 기술스택 - Frontend: [React.js](https://reactjs.org/) -- Backend: [Nest.js](https://nestjs.com/) +- Backend + - [Nest.js](https://nestjs.com/) + - [TypeORM](https://typeorm.io/#/) + - [Redis](https://redis.io/) + - [Docker](https://www.docker.com/) # 프로젝트 **! Docker 가 설치되어 있어야 합니다.** @@ -54,12 +58,60 @@ https://www.42world.kr/ yarn install ``` +## env 파일 형식 +비어있는 부분을 채워서 파일을 생성해주세요 + +.env.dev +``` +MYSQL_DATABASE=ft_world +MYSQL_USER=ft_world +MYSQL_PASSWORD=ft_world +MYSQL_EXTERNAL_PORT=3306 +API_EXTERNAL_PORT=8888 + +DB_HOST=db +DB_PORT=3306 +DB_NAME=ft_world +DB_USER_NAME= +DB_USER_PASSWORD= + +REDIS_HOST=redis6379 +REDIS_PORT=6379 + +PORT=8888 +GITHUB_CLIENT_ID= +GITHUB_CLIENT_SECRET= +GITHUB_CALLBACK_URL=http://localhost:3000/auth/github/callback +JWT_SECRET= + +EMAIL_AUTH_EMAIL=42world.official@gmail.com +EMAIL_AUTH_PASSWORD= +EMAIL_HOST=smtp.gmail.com +EMAIL_FROM_USER_NAME=42world +EMAIL_ENDPOINT=http://localhost:8888/ft-auth +FRONT_URL=http://localhost:3000 + +SLACK_HOOK_URL= +``` + ## 실행하기 - 아래 명령어를 입력하여 실행해주세요. ``` make dev ``` - 명령어는 디렉토리 최상단에서 실행해주세요. + +## 테스트 실행하기 +현재 e2e 테스트의 일부만 제대로 구성되어 있습니다. + +다른 테스트 및 유닛테스트는 추후 보강 예정입니다. + +``` +./run_test_db.sh + +yarn test:e2e ./test/app.e2e-spec.ts +``` + # 기여하기 42world 커뮤니티 제작은 오픈소스로 진행되고 있습니다. From fde7dce22803e3127cc5c3a15b01ef7910e16863 Mon Sep 17 00:00:00 2001 From: rockpell Date: Tue, 1 Feb 2022 12:46:17 +0900 Subject: [PATCH 02/55] =?UTF-8?q?Doc:=20readme=EC=97=90=20docker=20compose?= =?UTF-8?q?=20=EB=82=B4=EC=9A=A9=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 현재 로컬에서 사용할 수 없는 docker-compose.yml이라 내용 추가 설명 --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 4d98ac5d..7e17dc4a 100644 --- a/README.md +++ b/README.md @@ -95,6 +95,10 @@ SLACK_HOOK_URL= ``` ## 실행하기 +- docker-compose_backup.yml을 docker-compose.yml로 바꿔주세요 + - 현재 docker-compose.yml은 배포하기 위한 세팅을 하느라 실험중인 상태입니다. + - docker-compose.yml은 추후 하나로 합쳐지거나 사라질 예정입니다. + - 아래 명령어를 입력하여 실행해주세요. ``` make dev From 1e27d3588207ef0b5a72fadec43905df99ba743e Mon Sep 17 00:00:00 2001 From: Euimin Chung Date: Tue, 1 Feb 2022 14:08:12 +0900 Subject: [PATCH 03/55] =?UTF-8?q?Chore:=20=EB=A6=AC=EB=93=9C=EB=AF=B8=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 36 ++---------------------------------- config/sample.env.dev | 23 +++++++++++++++++++++++ 2 files changed, 25 insertions(+), 34 deletions(-) create mode 100644 config/sample.env.dev diff --git a/README.md b/README.md index 7e17dc4a..00a6ffd4 100644 --- a/README.md +++ b/README.md @@ -59,40 +59,8 @@ https://www.42world.kr/ ``` ## env 파일 형식 -비어있는 부분을 채워서 파일을 생성해주세요 +sample.env.dev 파일을 .env.dev 로 이름을 바꾸고 비어있는 부분을 채워주세요. -.env.dev -``` -MYSQL_DATABASE=ft_world -MYSQL_USER=ft_world -MYSQL_PASSWORD=ft_world -MYSQL_EXTERNAL_PORT=3306 -API_EXTERNAL_PORT=8888 - -DB_HOST=db -DB_PORT=3306 -DB_NAME=ft_world -DB_USER_NAME= -DB_USER_PASSWORD= - -REDIS_HOST=redis6379 -REDIS_PORT=6379 - -PORT=8888 -GITHUB_CLIENT_ID= -GITHUB_CLIENT_SECRET= -GITHUB_CALLBACK_URL=http://localhost:3000/auth/github/callback -JWT_SECRET= - -EMAIL_AUTH_EMAIL=42world.official@gmail.com -EMAIL_AUTH_PASSWORD= -EMAIL_HOST=smtp.gmail.com -EMAIL_FROM_USER_NAME=42world -EMAIL_ENDPOINT=http://localhost:8888/ft-auth -FRONT_URL=http://localhost:3000 - -SLACK_HOOK_URL= -``` ## 실행하기 - docker-compose_backup.yml을 docker-compose.yml로 바꿔주세요 @@ -150,4 +118,4 @@ yarn test:e2e ./test/app.e2e-spec.ts 질문이 생기면 이메일(42world.official@gmail.com)로 언제든 연락주세요. -저장소에 별 달아주시는거 잊지마세요 ✨✨✨ \ No newline at end of file +저장소에 별 달아주시는거 잊지마세요 ✨✨✨ diff --git a/config/sample.env.dev b/config/sample.env.dev new file mode 100644 index 00000000..f3848b8f --- /dev/null +++ b/config/sample.env.dev @@ -0,0 +1,23 @@ +MYSQL_DATABASE=ft_world +MYSQL_USER=ft_world +MYSQL_PASSWORD=ft_world +MYSQL_EXTERNAL_PORT=3306 +API_EXTERNAL_PORT=8888 +DB_HOST=db +DB_PORT=3306 +DB_NAME=ft_world +DB_USER_NAME= +DB_USER_PASSWORD= +REDIS_HOST=redis6379 +REDIS_PORT=6379 +PORT=8888 +GITHUB_CLIENT_ID= +GITHUB_CLIENT_SECRET= +GITHUB_CALLBACK_URL=http://localhost:3000/auth/github/callback +JWT_SECRET= +EMAIL_AUTH_EMAIL=42world.official@gmail.com +EMAIL_AUTH_PASSWORD= +EMAIL_HOST=smtp.gmail.com +EMAIL_FROM_USER_NAME=42world +EMAIL_ENDPOINT=http://localhost:8888/ft-auth +FRONT_URL=http://localhost:3000 From f0720623a44936acaffaf7a3375d5395e4987f1c Mon Sep 17 00:00:00 2001 From: rockpell Date: Tue, 1 Feb 2022 18:38:00 +0900 Subject: [PATCH 04/55] =?UTF-8?q?Fix:=20dev=20=ED=99=98=EA=B2=BD=EC=97=90?= =?UTF-8?q?=EC=84=9C=20makefile=20redis=20=EC=8B=A4=ED=96=89=ED=95=98?= =?UTF-8?q?=EB=8A=94=20=EB=B6=80=EB=B6=84=20=EB=B9=A0=EC=A7=84=EA=B1=B0=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 3b755e4a..cb1b3c5c 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ COMPOSE = sudo docker-compose COMPOSE_ENV = ${COMPOSE} --env-file config/.env.$(1) -dev: db +dev: db redis alpha: export NODE_ENV=alpha && $(call COMPOSE_ENV,alpha) up --build -d From 0965b66583d62b6958f2c4749b2e39b843c648f6 Mon Sep 17 00:00:00 2001 From: rockpell Date: Tue, 1 Feb 2022 18:39:12 +0900 Subject: [PATCH 05/55] =?UTF-8?q?Doc:=20readme=20dev=20=ED=99=98=EA=B2=BD,?= =?UTF-8?q?=20yarn=20start=20=EB=82=B4=EC=9A=A9=20=EB=B9=A0=EC=A7=84?= =?UTF-8?q?=EA=B1=B0=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 00a6ffd4..dea04c46 100644 --- a/README.md +++ b/README.md @@ -70,6 +70,7 @@ sample.env.dev 파일을 .env.dev 로 이름을 바꾸고 비어있는 부분을 - 아래 명령어를 입력하여 실행해주세요. ``` make dev + yarn start:dev ``` - 명령어는 디렉토리 최상단에서 실행해주세요. From dbafbd989e657897f231fe392df57b4f5272a2b2 Mon Sep 17 00:00:00 2001 From: rockpell Date: Tue, 1 Feb 2022 18:50:28 +0900 Subject: [PATCH 06/55] =?UTF-8?q?Fix:=20typeorm=20log,=20dev=20=ED=99=98?= =?UTF-8?q?=EA=B2=BD=EC=9D=BC=EB=95=8C=EB=A7=8C=20=EB=90=98=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/database/ormconfig.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/database/ormconfig.ts b/src/database/ormconfig.ts index 0b2cfcbc..fd1e20ed 100644 --- a/src/database/ormconfig.ts +++ b/src/database/ormconfig.ts @@ -17,7 +17,7 @@ export const ormconfig = (): IOrmconfig => ({ synchronize: false, migrationsRun: true, - logging: process.env.NODE_ENV !== 'prod', + logging: process.env.NODE_ENV === 'dev', migrations: [__dirname + '/migrations/**/*{.ts,.js}'], cli: { From 4ab55ce1b449f5cd9cc5a49e6171e76772184835 Mon Sep 17 00:00:00 2001 From: rockpell Date: Fri, 4 Feb 2022 02:05:36 +0900 Subject: [PATCH 07/55] =?UTF-8?q?Fix:=20env=EB=A5=BC=20dockerfile=20?= =?UTF-8?q?=EB=82=B4=EB=B6=80=EB=A1=9C=20copy=20=ED=95=98=EC=A7=80=20?= =?UTF-8?q?=EC=95=8A=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - dockefile 내부로 env를 copy하면 도커 이미지를 ecr에 업로드했을때 env 내용이 공개될 수 있기 때문에 수정 --- Dockerfile | 1 - Makefile | 38 ++++++++++++----------- docker-compose.yml | 22 ++++++++++++++ docker-compose_backup.yml | 45 ---------------------------- package.json | 6 ++-- src/app.module.ts | 3 +- src/database/seeder/seeder.module.ts | 3 +- src/utils.ts | 8 ----- test/test.base.module.ts | 3 +- 9 files changed, 49 insertions(+), 80 deletions(-) delete mode 100644 docker-compose_backup.yml diff --git a/Dockerfile b/Dockerfile index 76cae0c9..318b889c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,7 +5,6 @@ RUN mkdir ft-world WORKDIR ft-world -COPY ./config ./config COPY ./src ./src COPY ./test ./test COPY ./views ./views diff --git a/Makefile b/Makefile index 3b755e4a..299a37b7 100644 --- a/Makefile +++ b/Makefile @@ -1,22 +1,23 @@ COMPOSE = sudo docker-compose -COMPOSE_ENV = ${COMPOSE} --env-file config/.env.$(1) +COMPOSE_ENV = ${COMPOSE} --env-file config/.env -dev: db +dev: + cp ./config/.env.dev ./config/.env + make db redis alpha: - export NODE_ENV=alpha && $(call COMPOSE_ENV,alpha) up --build -d + cp ./config/.env.alpha ./config/.env + export NODE_ENV=alpha && $(call COMPOSE_ENV) up --build -d prod: - export NODE_ENV=prod && $(call COMPOSE_ENV,prod) up --build -d + cp ./config/.env.prod ./config/.env + make api-prod db-dev: - $(call COMPOSE_ENV,dev) up --build -d db + export NODE_ENV=dev && $(call COMPOSE_ENV) up --build -d db db-alpha: - $(call COMPOSE_ENV,alpha) up --build -d db - -db-prod: - $(call COMPOSE_ENV,prod) up --build -d db + export NODE_ENV=alpha && $(call COMPOSE_ENV) up --build -d db db: db-dev @@ -29,25 +30,28 @@ redis-down: ${COMPOSE} down redis redis-dev: - $(call COMPOSE_ENV,dev) up --build -d redis + export NODE_ENV=dev && $(call COMPOSE_ENV) up --build -d redis redis-alpha: - $(call COMPOSE_ENV,alpha) up --build -d redis - -redis-prod: - $(call COMPOSE_ENV,prod) up --build -d redis + export NODE_ENV=alpha && $(call COMPOSE_ENV) up --build -d redis api: api-dev api-dev: - $(call COMPOSE_ENV,dev) up --build --no-deps -d api + export NODE_ENV=dev && $(call COMPOSE_ENV) up --build --no-deps -d api api-alpha: - $(call COMPOSE_ENV,alpha) up --build --no-deps -d api + export NODE_ENV=alpha && $(call COMPOSE_ENV) up --build --no-deps -d api api-prod: - $(call COMPOSE_ENV,prod) up --build --no-deps -d api + sudo docker build -t ft-world-api . + sudo docker run -d -p 80:8888 --env-file config/.env ft-world-api + +api-build: + sudo docker build -t ft-world-api . + sudo docker tag ft-world-api:latest public.ecr.aws/x9y9d0k9/ft-world-api:latest + sudo docker push public.ecr.aws/x9y9d0k9/ft-world-api:latest db-init: sudo yarn typeorm:run diff --git a/docker-compose.yml b/docker-compose.yml index 27fc0d7e..08762c0b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,5 +1,26 @@ version: '3.4' services: + db: + image: mysql:5.7 + platform: linux/x86_64 + ports: + - '${MYSQL_EXTERNAL_PORT:-2345}:3306' + environment: + - MYSQL_DATABASE=${MYSQL_DATABASE} + - MYSQL_USER=${MYSQL_USER} + - MYSQL_PASSWORD=${MYSQL_PASSWORD} + - MYSQL_ALLOW_EMPTY_PASSWORD=true + - MYSQL_INITDB_ARGS=--encoding=UTF-8 + - TZ=Asia/Seoul + command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci + healthcheck: + test: 'mysqladmin ping -h localhost -u $$MYSQL_USER --password=$$MYSQL_PASSWORD' + interval: 5s + timeout: 1s + retries: 10 + start_period: 10s + volumes: + - ./db:/var/lib/mysql api: build: context: . @@ -9,6 +30,7 @@ services: environment: - NODE_ENV=${NODE_ENV:-alpha} depends_on: + - db - redis redis: diff --git a/docker-compose_backup.yml b/docker-compose_backup.yml deleted file mode 100644 index 6d1081b7..00000000 --- a/docker-compose_backup.yml +++ /dev/null @@ -1,45 +0,0 @@ -version: '3.4' -services: - db: - image: mysql:5.7 - platform: linux/x86_64 - ports: - - '${MYSQL_EXTERNAL_PORT:-2345}:3306' - environment: - - MYSQL_DATABASE=${MYSQL_DATABASE} - - MYSQL_USER=${MYSQL_USER} - - MYSQL_PASSWORD=${MYSQL_PASSWORD} - - MYSQL_ALLOW_EMPTY_PASSWORD=true - - MYSQL_INITDB_ARGS=--encoding=UTF-8 - - TZ=Asia/Seoul - command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci - healthcheck: - test: 'mysqladmin ping -h localhost -u $$MYSQL_USER --password=$$MYSQL_PASSWORD' - interval: 5s - timeout: 1s - retries: 10 - start_period: 10s - volumes: - - ./db:/var/lib/mysql - api: - build: - context: . - dockerfile: ./Dockerfile - ports: - - '${API_EXTERNAL_PORT:-8888}:8888' - environment: - - NODE_ENV=${NODE_ENV:-alpha} - depends_on: - - db - - redis: - image: redis:6.2.5 - command: redis-server --port 6379 - container_name: redis6379 - hostname: redis6379 - labels: - - "name=redis" - - "mode=standalone" - ports: - - ${REDIS_PORT:-6379}:6379 - diff --git a/package.json b/package.json index 1f9d1d74..e9006490 100644 --- a/package.json +++ b/package.json @@ -9,10 +9,10 @@ "prebuild": "rimraf dist", "build": "nest build", "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", - "start": "nest start", - "start:dev": "nest start --watch", + "start": "NODE_ENV=dev nest start", + "start:dev": "NODE_ENV=dev nest start --watch", "start:debug": "NODE_ENV=dev nest start --debug --watch", - "start:prod": "node dist/main", + "start:prod": "NODE_ENV=prod node dist/main", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", "test": "jest", "test:watch": "jest --watch", diff --git a/src/app.module.ts b/src/app.module.ts index 89c09e1e..9cfe8ce9 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -23,7 +23,6 @@ import { AuthModule } from './auth/auth.module'; import { BestModule } from './best/best.module'; import { ReactionModule } from './reaction/reaction.module'; import { DatabaseModule } from './database/database.module'; -import { getEnvPath } from './utils'; import { JwtAuthGuard } from './auth/jwt-auth.guard'; import { ormconfig } from './database/ormconfig'; import { FtCheckinModule } from './ft-checkin/ft-checkin.module'; @@ -31,7 +30,7 @@ import { FtCheckinModule } from './ft-checkin/ft-checkin.module'; @Module({ imports: [ ConfigModule.forRoot({ - envFilePath: getEnvPath(), + envFilePath: 'config/.env', isGlobal: true, cache: true, load: [ormconfig, configEmail], diff --git a/src/database/seeder/seeder.module.ts b/src/database/seeder/seeder.module.ts index 4814c23e..4b63968e 100644 --- a/src/database/seeder/seeder.module.ts +++ b/src/database/seeder/seeder.module.ts @@ -6,13 +6,12 @@ import { Seeder } from './seeder'; import { UserSeederModule } from './user/user-seeder.module'; import { CategorySeederModule } from './category/category-seeder.module'; import { ArticleSeederModule } from './article/article-seeder.module'; -import { getEnvPath } from '@root/utils'; import { ormconfig } from '../ormconfig'; @Module({ imports: [ ConfigModule.forRoot({ - envFilePath: getEnvPath(), + envFilePath: 'config/.env', isGlobal: true, cache: true, load: [ormconfig], diff --git a/src/utils.ts b/src/utils.ts index 1d20cf9b..ef90b82b 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -20,14 +20,6 @@ export const isExpired = (exp: Date): boolean => { return now >= exp; }; -export const getEnvPath = () => { - if (process.env.NODE_ENV === 'test') return 'config/.env.test'; - if (process.env.NODE_ENV === 'dev') return 'config/.env.dev'; - if (process.env.NODE_ENV === 'alpha') return 'config/.env.alpha'; - if (process.env.NODE_ENV === 'prod') return 'config/.env.prod'; - return 'config/.env.dev'; -}; - export const getCookieOption = (): CookieOptions => { if (process.env.NODE_ENV === 'alpha' || process.env.NODE_ENV === 'prod') { return { secure: true, sameSite: 'none' }; diff --git a/test/test.base.module.ts b/test/test.base.module.ts index ed92f2b0..44981dd5 100644 --- a/test/test.base.module.ts +++ b/test/test.base.module.ts @@ -5,7 +5,6 @@ import * as path from 'path'; import { ConfigModule } from '@nestjs/config'; import { APP_GUARD } from '@nestjs/core'; import { JwtAuthGuard } from '@auth/jwt-auth.guard'; -import { getEnvPath } from '@root/utils'; import configEmail from '@root/config/mail.config'; @Module({ @@ -23,7 +22,7 @@ import configEmail from '@root/config/mail.config'; logging: true, }), ConfigModule.forRoot({ - envFilePath: getEnvPath(), + envFilePath: 'config/.env', isGlobal: true, cache: true, load: [configEmail], From ecb315644ba500e60438a7bd468bef9fad63fac1 Mon Sep 17 00:00:00 2001 From: rockpell Date: Sun, 6 Feb 2022 01:46:31 +0900 Subject: [PATCH 08/55] =?UTF-8?q?Fix:=20docker=20compose=EA=B0=80=20api=20?= =?UTF-8?q?docker=EB=A5=BC=20=EC=8B=A4=ED=96=89=20=ED=95=A0=20=EB=95=8C=20?= =?UTF-8?q?env=20=ED=8C=8C=EC=9D=BC=EC=9D=84=20=EA=B0=80=EC=A0=B8=EC=98=A4?= =?UTF-8?q?=EC=A7=80=20=EC=95=8A=EB=8A=94=20=EB=AC=B8=EC=A0=9C=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - env_file을 지정하여 api docker가 실행 될 때 .env 파일을 읽도록 지정 --- docker-compose.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index 08762c0b..31f4f8de 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -26,7 +26,9 @@ services: context: . dockerfile: ./Dockerfile ports: - - '${API_EXTERNAL_PORT:-8888}:8888' + - '${API_EXTERNAL_PORT:-8888}:${PORT:-8888}' + env_file: + - ./config/.env environment: - NODE_ENV=${NODE_ENV:-alpha} depends_on: From bb68728829b7ac6e260d9fbcbd556cf826b1e3b0 Mon Sep 17 00:00:00 2001 From: rockpell Date: Sun, 6 Feb 2022 11:23:24 +0900 Subject: [PATCH 09/55] =?UTF-8?q?Doc:=20=EB=B3=80=EA=B2=BD=EB=90=9C=20env?= =?UTF-8?q?=EC=97=90=20=EB=A7=9E=EA=B2=8C=20=EC=84=A4=EB=AA=85=20=EC=88=98?= =?UTF-8?q?=EC=A0=95,=20=EB=B6=80=EC=A1=B1=ED=95=9C=20env=20=EC=84=A4?= =?UTF-8?q?=EB=AA=85=20=EB=B3=B4=EA=B0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - docker-compose 관련 내용은 수정되었기때문에 제거 - github, email의 env 설명 보강 --- README.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index dea04c46..8aff42e2 100644 --- a/README.md +++ b/README.md @@ -61,12 +61,15 @@ https://www.42world.kr/ ## env 파일 형식 sample.env.dev 파일을 .env.dev 로 이름을 바꾸고 비어있는 부분을 채워주세요. +`GITHUB_CLIENT_ID`, `GITHUB_CLIENT_SECRET`, `GITHUB_CALLBACK_URL` 은 github oauth 로그인 관련 설정입니다. -## 실행하기 -- docker-compose_backup.yml을 docker-compose.yml로 바꿔주세요 - - 현재 docker-compose.yml은 배포하기 위한 세팅을 하느라 실험중인 상태입니다. - - docker-compose.yml은 추후 하나로 합쳐지거나 사라질 예정입니다. +이 링크를 참조하여 생성한 한 후 채워주세요 [github building-oauth-apps](https://docs.github.com/en/developers/apps/building-oauth-apps/creating-an-oauth-app) + +`EMAIL_*`은 이메일 인증과 관련된 설정입니다. +완벽히 같지는 않지만 이 링크를 참조하여 원하는 설정을 채워주세요 [node-mailer](https://nodemailer.com/about/) + +## 실행하기 - 아래 명령어를 입력하여 실행해주세요. ``` make dev From 70fba17d6c97f1a888e8a073aed86a2213734226 Mon Sep 17 00:00:00 2001 From: rockpell Date: Sun, 6 Feb 2022 22:27:55 +0900 Subject: [PATCH 10/55] =?UTF-8?q?Test:=20test=20code=20=EC=8B=A4=ED=96=89?= =?UTF-8?q?=20=EC=8B=9C=20db=20log=20=EB=82=98=EC=98=A4=EB=8A=94=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EB=B9=84=ED=99=9C=EC=84=B1=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/test.base.module.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test.base.module.ts b/test/test.base.module.ts index ed92f2b0..5593877b 100644 --- a/test/test.base.module.ts +++ b/test/test.base.module.ts @@ -20,7 +20,7 @@ import configEmail from '@root/config/mail.config'; entities: [path.join(__dirname, '../src/**/*.entity{.ts,.js}')], namingStrategy: new SnakeNamingStrategy(), synchronize: true, - logging: true, + logging: false, }), ConfigModule.forRoot({ envFilePath: getEnvPath(), From b42454be9fd0ec400ae56b0b700b12ab56bc3233 Mon Sep 17 00:00:00 2001 From: huni Date: Sun, 6 Feb 2022 22:34:48 +0900 Subject: [PATCH 11/55] =?UTF-8?q?Fix:=20app.e2e-spec=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - database 모듈을 포함하고 있어서 자꾸 에러남 --- test/app.e2e-spec.ts | 31 ------------------------------- 1 file changed, 31 deletions(-) delete mode 100644 test/app.e2e-spec.ts diff --git a/test/app.e2e-spec.ts b/test/app.e2e-spec.ts deleted file mode 100644 index 3c3f2a87..00000000 --- a/test/app.e2e-spec.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { getConnection } from 'typeorm'; -import { Test, TestingModule } from '@nestjs/testing'; -import { INestApplication } from '@nestjs/common'; -import * as request from 'supertest'; -import { AppModule } from './../src/app.module'; -import { AuthModule } from '@root/auth/auth.module'; - -describe('AppController (e2e)', () => { - let app: INestApplication; - - beforeEach(async () => { - const moduleFixture: TestingModule = await Test.createTestingModule({ - imports: [AppModule, AuthModule], - }).compile(); - - app = moduleFixture.createNestApplication(); - await app.init(); - }); - - it('/ (GET)', () => { - return request(app.getHttpServer()) - .get('/') - .expect(200) - .expect('Hello World!'); - }); - - afterAll(async () => { - await getConnection().dropDatabase(); - await app.close(); - }); -}); From 076edef07cc9d150f723ade39be18f5d64499991 Mon Sep 17 00:00:00 2001 From: rockpell Date: Mon, 7 Feb 2022 12:48:16 +0900 Subject: [PATCH 12/55] =?UTF-8?q?Feat:=20500=20error=20=EA=B8=80=EB=A1=9C?= =?UTF-8?q?=EB=B2=8C=20=ED=95=84=ED=84=B0=20=EC=A0=81=EC=9A=A9,=20?= =?UTF-8?q?=EC=97=90=EB=9F=AC=20=EB=B0=9C=EC=83=9D=EC=8B=9C=20=EC=8A=AC?= =?UTF-8?q?=EB=9E=99=EC=9C=BC=EB=A1=9C=20=EB=A9=94=EC=8B=9C=EC=A7=80=20?= =?UTF-8?q?=EB=B0=9C=EC=86=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 500에러 글로벌 필터 적용 - 해당 필터에서 slack으로 에러메시지 발송하도록 기능 추가 --- src/app.controller.ts | 1 + .../internal-server-error-exception.filter.ts | 25 +++++++++++++++++++ src/filters/typeorm-exception.filter.ts | 12 ++------- src/main.ts | 2 ++ src/utils.ts | 12 +++++++++ 5 files changed, 42 insertions(+), 10 deletions(-) create mode 100644 src/filters/internal-server-error-exception.filter.ts diff --git a/src/app.controller.ts b/src/app.controller.ts index 93729405..e27ff187 100644 --- a/src/app.controller.ts +++ b/src/app.controller.ts @@ -1,6 +1,7 @@ import { Controller, Get } from '@nestjs/common'; import { ApiOkResponse, ApiOperation, ApiTags } from '@nestjs/swagger'; import { Public } from '@auth/auth.decorator'; + @ApiTags('Hello') @Controller() export class AppController { diff --git a/src/filters/internal-server-error-exception.filter.ts b/src/filters/internal-server-error-exception.filter.ts new file mode 100644 index 00000000..1f248eef --- /dev/null +++ b/src/filters/internal-server-error-exception.filter.ts @@ -0,0 +1,25 @@ +import { + Catch, + ExceptionFilter, + ArgumentsHost, + InternalServerErrorException, +} from '@nestjs/common'; +import { Response } from 'express'; +import { errorHook } from '@root/utils'; + +@Catch(InternalServerErrorException) +export class InternalServerErrorExceptionFilter implements ExceptionFilter { + public catch(exception: InternalServerErrorException, host: ArgumentsHost) { + const ctx = host.switchToHttp(); + const response = ctx.getResponse(); + + errorHook(exception.name, exception.message); + + return response.status(500).json({ + message: { + statusCode: 500, + message: 'Something Went Wrong', + }, + }); + } +} diff --git a/src/filters/typeorm-exception.filter.ts b/src/filters/typeorm-exception.filter.ts index bc59a25e..615daa36 100644 --- a/src/filters/typeorm-exception.filter.ts +++ b/src/filters/typeorm-exception.filter.ts @@ -2,7 +2,7 @@ import { Catch, ExceptionFilter, ArgumentsHost } from '@nestjs/common'; import { Response } from 'express'; import { TypeORMError } from 'typeorm'; import { EntityNotFoundError } from 'typeorm/error/EntityNotFoundError'; -import axios from 'axios'; +import { errorHook } from '@root/utils'; const FIND_DOUBLE_QUOTE = /\"/g; @@ -21,15 +21,7 @@ export class TypeormExceptionFilter implements ExceptionFilter { }); } - const PHASE = process.env.NODE_ENV; - const slackMessage = `[${PHASE}] ${exception.name}: ${exception.message}}`; - - try { - axios.post(process.env.SLACK_HOOK_URL, { text: slackMessage }).then(); - } catch (e) { - throw e; - } - + errorHook(exception.name, exception.message); console.error(exception); return response.status(500).json({ diff --git a/src/main.ts b/src/main.ts index 20c4986d..f486a1e0 100644 --- a/src/main.ts +++ b/src/main.ts @@ -12,6 +12,7 @@ import { AppModule } from './app.module'; import { ACCESS_TOKEN } from '@auth/constants/access-token'; import { ValidationPipe } from '@nestjs/common'; import { TypeormExceptionFilter } from '@root/filters/typeorm-exception.filter'; +import { InternalServerErrorExceptionFilter } from '@root/filters/internal-server-error-exception.filter'; async function bootstrap() { const app = await NestFactory.create(AppModule); @@ -41,6 +42,7 @@ async function bootstrap() { credentials: true, }); app.useGlobalFilters(new TypeormExceptionFilter()); + app.useGlobalFilters(new InternalServerErrorExceptionFilter()); app.useGlobalPipes( new ValidationPipe({ whitelist: true, // decorator(@)가 없는 속성이 들어오면 해당 속성은 제거하고 받아들입니다. diff --git a/src/utils.ts b/src/utils.ts index 1d20cf9b..0b8f5d10 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,4 +1,5 @@ import { CookieOptions } from 'express'; +import axios from 'axios'; export const MINUTE = 60; export const HOUR = 60 * MINUTE; @@ -34,3 +35,14 @@ export const getCookieOption = (): CookieOptions => { } return {}; }; + +export const errorHook = (exceptionName, exceptionMessage) => { + const PHASE = process.env.NODE_ENV; + const slackMessage = `[${PHASE}] ${exceptionName}: ${exceptionMessage}`; + + try { + axios.post(process.env.SLACK_HOOK_URL, { text: slackMessage }).then(); + } catch (e) { + throw e; + } +}; From 869dc4b413a1215b4acb36c88903ca8102c61bcc Mon Sep 17 00:00:00 2001 From: AndroidNetrunner Date: Mon, 7 Feb 2022 17:49:03 +0900 Subject: [PATCH 13/55] =?UTF-8?q?[FIX]=20=EC=9E=91=EC=84=B1=EC=9E=90?= =?UTF-8?q?=EA=B0=80=20=EA=B8=80=EC=97=90=20=EC=A0=91=EC=86=8D=ED=95=98?= =?UTF-8?q?=EB=A9=B4=20=EC=A1=B0=ED=9A=8C=EC=88=98=EA=B0=80=20=EC=98=A4?= =?UTF-8?q?=EB=A5=B4=EC=A7=80=20=EC=95=8A=EB=8F=84=EB=A1=9D=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/article/article.controller.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/article/article.controller.ts b/src/article/article.controller.ts index e0857d84..fc68574b 100644 --- a/src/article/article.controller.ts +++ b/src/article/article.controller.ts @@ -81,8 +81,8 @@ export class ArticleController { userId, articleId, ); - - this.articleService.increaseViewCount(article); + if (article.writerId !== userId) + this.articleService.increaseViewCount(article); return { ...article, isLike }; } From 42bcfcc5a7add0ae08fd177ed52da952d552ae4b Mon Sep 17 00:00:00 2001 From: rockpell Date: Mon, 7 Feb 2022 22:46:10 +0900 Subject: [PATCH 14/55] =?UTF-8?q?Feat:=20500=EC=97=90=EB=9F=AC=EB=A1=9C?= =?UTF-8?q?=EB=8F=84=20=EC=95=88=EC=9E=A1=ED=9E=88=EB=8A=94=20=EC=97=90?= =?UTF-8?q?=EB=9F=AC=EB=A5=BC=20=EC=9E=A1=EA=B8=B0=20=EC=9C=84=ED=95=9C=20?= =?UTF-8?q?=ED=95=84=ED=84=B0=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 모든 에러를 잡는 필터를 추가하여 github code 중복 에러 로깅 및 hook 처리 --- src/auth/auth.controller.ts | 20 +++++----- src/filters/all-exception.filter.ts | 39 +++++++++++++++++++ src/filters/constant.ts | 1 + .../internal-server-error-exception.filter.ts | 4 +- src/filters/typeorm-exception.filter.ts | 5 ++- 5 files changed, 57 insertions(+), 12 deletions(-) create mode 100644 src/filters/all-exception.filter.ts create mode 100644 src/filters/constant.ts diff --git a/src/auth/auth.controller.ts b/src/auth/auth.controller.ts index 01f64f16..0f7c0651 100644 --- a/src/auth/auth.controller.ts +++ b/src/auth/auth.controller.ts @@ -1,20 +1,12 @@ import { - Body, Controller, Delete, Get, - Post, Res, + UseFilters, UseGuards, } from '@nestjs/common'; import { Response } from 'express'; -import { UserService } from '@user/user.service'; -import { AuthService } from './auth.service'; -import { GithubAuthGuard } from './github-auth.guard'; -import { JWTPayload } from './interfaces/jwt-payload.interface'; -import { GithubProfile } from './interfaces/github-profile.interface'; -import { ACCESS_TOKEN } from './constants/access-token'; -import { GetGithubProfile, Public } from './auth.decorator'; import { ApiCookieAuth, ApiOkResponse, @@ -22,6 +14,15 @@ import { ApiTags, ApiUnauthorizedResponse, } from '@nestjs/swagger'; +import { AllExceptionsFilter } from '@root/filters/all-exception.filter'; + +import { UserService } from '@user/user.service'; +import { AuthService } from './auth.service'; +import { GithubAuthGuard } from './github-auth.guard'; +import { JWTPayload } from './interfaces/jwt-payload.interface'; +import { GithubProfile } from './interfaces/github-profile.interface'; +import { ACCESS_TOKEN } from './constants/access-token'; +import { GetGithubProfile, Public } from './auth.decorator'; import { getCookieOption } from '@root/utils'; @ApiTags('Auth') @@ -50,6 +51,7 @@ export class AuthController { @Get('github/callback') @Public() @UseGuards(GithubAuthGuard) + @UseFilters(AllExceptionsFilter) @ApiOperation({ summary: '깃허브 로그인 콜백', description: ` diff --git a/src/filters/all-exception.filter.ts b/src/filters/all-exception.filter.ts new file mode 100644 index 00000000..e587274e --- /dev/null +++ b/src/filters/all-exception.filter.ts @@ -0,0 +1,39 @@ +import { + ExceptionFilter, + Catch, + ArgumentsHost, + HttpException, + HttpStatus, +} from '@nestjs/common'; +import { HttpAdapterHost } from '@nestjs/core'; +import { INTERNAL_ERROR_MESSAGE } from '@root/filters/constant'; +import { errorHook } from '@root/utils'; + +@Catch() +export class AllExceptionsFilter implements ExceptionFilter { + constructor(private readonly httpAdapterHost: HttpAdapterHost) {} + + catch( + exception: { stack: string; message: string }, + host: ArgumentsHost, + ): void { + const { httpAdapter } = this.httpAdapterHost; + + const ctx = host.switchToHttp(); + + const httpStatus = + exception instanceof HttpException + ? exception.getStatus() + : HttpStatus.INTERNAL_SERVER_ERROR; + + const responseBody = { + statusCode: httpStatus, + message: INTERNAL_ERROR_MESSAGE, + }; + + console.error(exception); + errorHook('Unknown Error', exception.message); + + httpAdapter.reply(ctx.getResponse(), responseBody, httpStatus); + } +} diff --git a/src/filters/constant.ts b/src/filters/constant.ts new file mode 100644 index 00000000..043601d8 --- /dev/null +++ b/src/filters/constant.ts @@ -0,0 +1 @@ +export const INTERNAL_ERROR_MESSAGE = 'Something Went Wrong'; diff --git a/src/filters/internal-server-error-exception.filter.ts b/src/filters/internal-server-error-exception.filter.ts index 1f248eef..9864dd63 100644 --- a/src/filters/internal-server-error-exception.filter.ts +++ b/src/filters/internal-server-error-exception.filter.ts @@ -6,6 +6,7 @@ import { } from '@nestjs/common'; import { Response } from 'express'; import { errorHook } from '@root/utils'; +import { INTERNAL_ERROR_MESSAGE } from '@root/filters/constant'; @Catch(InternalServerErrorException) export class InternalServerErrorExceptionFilter implements ExceptionFilter { @@ -13,12 +14,13 @@ export class InternalServerErrorExceptionFilter implements ExceptionFilter { const ctx = host.switchToHttp(); const response = ctx.getResponse(); + console.error(exception); errorHook(exception.name, exception.message); return response.status(500).json({ message: { statusCode: 500, - message: 'Something Went Wrong', + message: INTERNAL_ERROR_MESSAGE, }, }); } diff --git a/src/filters/typeorm-exception.filter.ts b/src/filters/typeorm-exception.filter.ts index 615daa36..910df28c 100644 --- a/src/filters/typeorm-exception.filter.ts +++ b/src/filters/typeorm-exception.filter.ts @@ -3,6 +3,7 @@ import { Response } from 'express'; import { TypeORMError } from 'typeorm'; import { EntityNotFoundError } from 'typeorm/error/EntityNotFoundError'; import { errorHook } from '@root/utils'; +import { INTERNAL_ERROR_MESSAGE } from '@root/filters/constant'; const FIND_DOUBLE_QUOTE = /\"/g; @@ -21,13 +22,13 @@ export class TypeormExceptionFilter implements ExceptionFilter { }); } - errorHook(exception.name, exception.message); console.error(exception); + errorHook(exception.name, exception.message); return response.status(500).json({ message: { statusCode: 500, - message: 'Something Went Wrong', + message: INTERNAL_ERROR_MESSAGE, }, }); } From 4e43c7e6428cb569358bc7e8ddf7412cf4390e7f Mon Sep 17 00:00:00 2001 From: rockpell Date: Mon, 7 Feb 2022 23:33:46 +0900 Subject: [PATCH 15/55] =?UTF-8?q?Fix:=20nest=20=EB=B9=8C=EB=93=9C=ED=95=A0?= =?UTF-8?q?=20=EB=95=8C=20ejs=EB=A5=BC=20=EA=B0=80=EC=A0=B8=EA=B0=80?= =?UTF-8?q?=EB=8A=94=20option=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - **은 모든 디렉토리를 검사하기 때문에 빌드 속도가 느려지는 문제가 있었다 - views 디렉토리로 특정하여 기존 빌드 속도로 복구 완료 --- nest-cli.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nest-cli.json b/nest-cli.json index 6bf113fb..cbf8261c 100644 --- a/nest-cli.json +++ b/nest-cli.json @@ -2,7 +2,7 @@ "collection": "@nestjs/schematics", "sourceRoot": "", "compilerOptions": { - "assets": [{ "include": "**/*.ejs" }], + "assets": [{ "include": "views/**/*.ejs" }], "watchAssets": true } } From a953161982de3a79fb5a0abdf3d7ceedee36f004 Mon Sep 17 00:00:00 2001 From: rockpell Date: Tue, 8 Feb 2022 12:38:15 +0900 Subject: [PATCH 16/55] =?UTF-8?q?Fix:=20timezone=EC=9D=84=20=EC=A0=84?= =?UTF-8?q?=EB=B6=80=20utc=EB=A1=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docker-compose.yml | 2 +- run_test_db.sh | 2 +- src/database/ormconfig.ts | 2 ++ 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 31f4f8de..efa509b2 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -11,7 +11,7 @@ services: - MYSQL_PASSWORD=${MYSQL_PASSWORD} - MYSQL_ALLOW_EMPTY_PASSWORD=true - MYSQL_INITDB_ARGS=--encoding=UTF-8 - - TZ=Asia/Seoul + - TZ=UTC command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci healthcheck: test: 'mysqladmin ping -h localhost -u $$MYSQL_USER --password=$$MYSQL_PASSWORD' diff --git a/run_test_db.sh b/run_test_db.sh index 32e2ab9d..1371f655 100755 --- a/run_test_db.sh +++ b/run_test_db.sh @@ -7,6 +7,6 @@ docker run -d --rm --name ft_world-mysql-test \ -e MYSQL_PASSWORD=ft_world \ -e MYSQL_ALLOW_EMPTY_PASSWORD=true \ -e MYSQL_INITDB_ARGS=--encoding=UTF-8 \ --e TZ=Asia/Seoul \ +-e TZ=UTC \ -p 3308:3306 mysql:5.7 \ mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci diff --git a/src/database/ormconfig.ts b/src/database/ormconfig.ts index fd1e20ed..3e6d7cbc 100644 --- a/src/database/ormconfig.ts +++ b/src/database/ormconfig.ts @@ -15,6 +15,8 @@ export const ormconfig = (): IOrmconfig => ({ entities: [__dirname + '../../**/*.entity{.ts,.js}'], namingStrategy: new SnakeNamingStrategy(), + timezone: 'Z', + synchronize: false, migrationsRun: true, logging: process.env.NODE_ENV === 'dev', From a4695e40f4296b34f4ddfab05cfa70d5318786f8 Mon Sep 17 00:00:00 2001 From: huni Date: Wed, 9 Feb 2022 19:54:26 +0900 Subject: [PATCH 17/55] =?UTF-8?q?Feat:=20notification=20=EC=97=90=20conten?= =?UTF-8?q?t=20id=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - entity column 에 contentId 추가 - 알람 생성시, 게시글 id 를 저장하도록 변경 --- src/notification/dto/create-notification.dto.ts | 1 + src/notification/entities/notification.entity.ts | 4 ++++ src/notification/notification.service.ts | 1 + 3 files changed, 6 insertions(+) diff --git a/src/notification/dto/create-notification.dto.ts b/src/notification/dto/create-notification.dto.ts index 73da118d..3420a7df 100644 --- a/src/notification/dto/create-notification.dto.ts +++ b/src/notification/dto/create-notification.dto.ts @@ -3,5 +3,6 @@ import { NotificationType } from '../entities/notification.entity'; export class CreateNotificationDto { readonly type!: NotificationType; readonly content!: string; + readonly contentId!: number; readonly userId!: number; } diff --git a/src/notification/entities/notification.entity.ts b/src/notification/entities/notification.entity.ts index 6006bab9..4fe5a037 100644 --- a/src/notification/entities/notification.entity.ts +++ b/src/notification/entities/notification.entity.ts @@ -37,6 +37,10 @@ export class Notification { @Column({ type: 'text', nullable: false }) content!: string; + @ApiProperty() + @Column({ nullable: false }) + contentId: number; + @ApiProperty() @Column({ nullable: false, default: false }) isRead!: boolean; diff --git a/src/notification/notification.service.ts b/src/notification/notification.service.ts index 1885a0b2..82f097cf 100644 --- a/src/notification/notification.service.ts +++ b/src/notification/notification.service.ts @@ -17,6 +17,7 @@ export class NotificationService { const notification: CreateNotificationDto = { type: NotificationType.NEW_COMMENT, content: comment.content, + contentId: article.id, userId: article.writerId, }; return this.notificationRepository.save(notification); From 7a055c4e1b79d3705483a003545ac5202bc6306b Mon Sep 17 00:00:00 2001 From: huni Date: Wed, 9 Feb 2022 21:56:48 +0900 Subject: [PATCH 18/55] =?UTF-8?q?Feat:=20=EB=8C=93=EA=B8=80=20=EC=95=8C?= =?UTF-8?q?=EB=A6=BC=20=EB=82=B4=EC=9A=A9=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 게시글 제목을 포함하는 형식으로 변경 --- src/notification/notification.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/notification/notification.service.ts b/src/notification/notification.service.ts index 82f097cf..5fa3ba32 100644 --- a/src/notification/notification.service.ts +++ b/src/notification/notification.service.ts @@ -16,7 +16,7 @@ export class NotificationService { createNewComment(article: Article, comment: Comment): Promise { const notification: CreateNotificationDto = { type: NotificationType.NEW_COMMENT, - content: comment.content, + content: `게시글 ${article.title} 에 새로운 댓글이 달렸습니다.\n${comment.content}`, contentId: article.id, userId: article.writerId, }; From 2c03d5ba5c3571b4db337a3188bf2e5afa997422 Mon Sep 17 00:00:00 2001 From: rockpell Date: Wed, 9 Feb 2022 22:35:15 +0900 Subject: [PATCH 19/55] =?UTF-8?q?Fix:=20all=20exception=20filter,=20http?= =?UTF-8?q?=20adapter=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - http adapter는 express 외의 다른 프레임워크와의 호환을 위해 사용하는건데 우리는 굳이 필요없어서 제거 --- src/filters/all-exception.filter.ts | 37 ++++++++--------------------- 1 file changed, 10 insertions(+), 27 deletions(-) diff --git a/src/filters/all-exception.filter.ts b/src/filters/all-exception.filter.ts index e587274e..933fc156 100644 --- a/src/filters/all-exception.filter.ts +++ b/src/filters/all-exception.filter.ts @@ -1,39 +1,22 @@ -import { - ExceptionFilter, - Catch, - ArgumentsHost, - HttpException, - HttpStatus, -} from '@nestjs/common'; -import { HttpAdapterHost } from '@nestjs/core'; +import { ExceptionFilter, Catch, ArgumentsHost } from '@nestjs/common'; import { INTERNAL_ERROR_MESSAGE } from '@root/filters/constant'; import { errorHook } from '@root/utils'; +import { Response } from 'express'; @Catch() export class AllExceptionsFilter implements ExceptionFilter { - constructor(private readonly httpAdapterHost: HttpAdapterHost) {} - - catch( - exception: { stack: string; message: string }, - host: ArgumentsHost, - ): void { - const { httpAdapter } = this.httpAdapterHost; - + catch(exception: { stack: string; message: string }, host: ArgumentsHost) { const ctx = host.switchToHttp(); - - const httpStatus = - exception instanceof HttpException - ? exception.getStatus() - : HttpStatus.INTERNAL_SERVER_ERROR; - - const responseBody = { - statusCode: httpStatus, - message: INTERNAL_ERROR_MESSAGE, - }; + const response = ctx.getResponse(); console.error(exception); errorHook('Unknown Error', exception.message); - httpAdapter.reply(ctx.getResponse(), responseBody, httpStatus); + return response.status(500).json({ + message: { + statusCode: 500, + message: INTERNAL_ERROR_MESSAGE, + }, + }); } } From 6c9970582a73296f1840e52ba9ec9642c11805ed Mon Sep 17 00:00:00 2001 From: rockpell Date: Wed, 9 Feb 2022 22:42:22 +0900 Subject: [PATCH 20/55] =?UTF-8?q?Fix:=20errorHook,=20=EB=B6=88=ED=95=84?= =?UTF-8?q?=EC=9A=94=ED=95=9C=20throw=20=EC=A0=9C=EA=B1=B0,=20await?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/utils.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/utils.ts b/src/utils.ts index 0b8f5d10..49de1d3a 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -36,13 +36,16 @@ export const getCookieOption = (): CookieOptions => { return {}; }; -export const errorHook = (exceptionName, exceptionMessage) => { +export const errorHook = async ( + exceptionName: string, + exceptionMessage: string, +) => { const PHASE = process.env.NODE_ENV; const slackMessage = `[${PHASE}] ${exceptionName}: ${exceptionMessage}`; try { - axios.post(process.env.SLACK_HOOK_URL, { text: slackMessage }).then(); + await axios.post(process.env.SLACK_HOOK_URL, { text: slackMessage }); } catch (e) { - throw e; + console.error(e); } }; From e39c8b78f7f394b7d473dc3ed9fc9464d59ed961 Mon Sep 17 00:00:00 2001 From: rockpell Date: Wed, 9 Feb 2022 23:02:36 +0900 Subject: [PATCH 21/55] =?UTF-8?q?Refactor:=20docker=20compose=EC=97=90?= =?UTF-8?q?=EC=84=9C=EB=A7=8C=20=EC=82=AC=EC=9A=A9=ED=95=98=EB=8A=94=20?= =?UTF-8?q?=EB=B6=88=ED=95=84=EC=9A=94=ED=95=9C=20env=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - MYSQL_로 시작하는 env는 docker-compose에서만 사용하는데 server에서 사용하는 env랑 같은 내용이라 불필요하여 제거 --- docker-compose.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 31f4f8de..50bff69d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -4,17 +4,17 @@ services: image: mysql:5.7 platform: linux/x86_64 ports: - - '${MYSQL_EXTERNAL_PORT:-2345}:3306' + - '${DB_PORT:-2345}:3306' environment: - - MYSQL_DATABASE=${MYSQL_DATABASE} - - MYSQL_USER=${MYSQL_USER} - - MYSQL_PASSWORD=${MYSQL_PASSWORD} + - MYSQL_DATABASE=${DB_NAME} + - MYSQL_USER=${DB_USER_NAME} + - MYSQL_PASSWORD=${DB_USER_PASSWORD} - MYSQL_ALLOW_EMPTY_PASSWORD=true - MYSQL_INITDB_ARGS=--encoding=UTF-8 - TZ=Asia/Seoul command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci healthcheck: - test: 'mysqladmin ping -h localhost -u $$MYSQL_USER --password=$$MYSQL_PASSWORD' + test: 'mysqladmin ping -h localhost -u $$DB_USER_NAME --password=$$DB_USER_PASSWORD' interval: 5s timeout: 1s retries: 10 From 66a45c2720d70e52e56bd23b02e00f659ef1a179 Mon Sep 17 00:00:00 2001 From: rockpell Date: Wed, 9 Feb 2022 23:06:31 +0900 Subject: [PATCH 22/55] =?UTF-8?q?Fix:=20seeder=20=EB=AA=A8=EB=93=88=20?= =?UTF-8?q?=EB=82=B4=EB=B6=80=EC=97=90=EC=84=9C=20=EC=A0=88=EB=8C=80?= =?UTF-8?q?=EA=B2=BD=EB=A1=9C=EB=A1=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/database/seeder/seeder.module.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/database/seeder/seeder.module.ts b/src/database/seeder/seeder.module.ts index 4b63968e..ef5d0419 100644 --- a/src/database/seeder/seeder.module.ts +++ b/src/database/seeder/seeder.module.ts @@ -6,7 +6,7 @@ import { Seeder } from './seeder'; import { UserSeederModule } from './user/user-seeder.module'; import { CategorySeederModule } from './category/category-seeder.module'; import { ArticleSeederModule } from './article/article-seeder.module'; -import { ormconfig } from '../ormconfig'; +import { ormconfig } from '@database/ormconfig'; @Module({ imports: [ From 0190012d9840376a84ccbecadd43580dfe642f25 Mon Sep 17 00:00:00 2001 From: huni Date: Thu, 10 Feb 2022 00:16:15 +0900 Subject: [PATCH 23/55] =?UTF-8?q?fix:=20contentId=EB=A5=BC=20articleId?= =?UTF-8?q?=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - contentId로 할경우, type에 따라서 엔티티를 다르게 join해야함 - id가 항상 일관되게 join 되지 않기 떄문에 오류가 발생할 가능성이 있음 - 따라서 articleId로 고정하는것으로 변경 --- src/article/entities/article.entity.ts | 7 +++++++ src/notification/dto/create-notification.dto.ts | 2 +- src/notification/entities/notification.entity.ts | 10 +++++++++- src/notification/notification.service.ts | 2 +- 4 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/article/entities/article.entity.ts b/src/article/entities/article.entity.ts index a9fc91d5..e49a1ec8 100644 --- a/src/article/entities/article.entity.ts +++ b/src/article/entities/article.entity.ts @@ -18,6 +18,7 @@ import { Category } from '@category/entities/category.entity'; import { Best } from '@root/best/entities/best.entity'; import { ReactionArticle } from '@root/reaction/entities/reaction-article.entity'; import { ReactionComment } from '@root/reaction/entities/reaction-comment.entity'; +import { Notification } from '@root/notification/entities/notification.entity'; @Entity('article') export class Article { @@ -89,6 +90,12 @@ export class Article { }) comment?: Comment[]; + @OneToMany(() => Notification, (notification) => notification.article, { + createForeignKeyConstraints: false, + nullable: true, + }) + notification?: Notification[]; + @ApiProperty({ type: () => ReactionArticle }) @OneToMany( () => ReactionArticle, diff --git a/src/notification/dto/create-notification.dto.ts b/src/notification/dto/create-notification.dto.ts index 3420a7df..db6acf9e 100644 --- a/src/notification/dto/create-notification.dto.ts +++ b/src/notification/dto/create-notification.dto.ts @@ -3,6 +3,6 @@ import { NotificationType } from '../entities/notification.entity'; export class CreateNotificationDto { readonly type!: NotificationType; readonly content!: string; - readonly contentId!: number; + readonly articleId!: number; readonly userId!: number; } diff --git a/src/notification/entities/notification.entity.ts b/src/notification/entities/notification.entity.ts index 4fe5a037..73eb36c5 100644 --- a/src/notification/entities/notification.entity.ts +++ b/src/notification/entities/notification.entity.ts @@ -1,4 +1,5 @@ import { ApiProperty } from '@nestjs/swagger'; +import { Article } from '@root/article/entities/article.entity'; import { User } from '@user/entities/user.entity'; import { @@ -39,7 +40,14 @@ export class Notification { @ApiProperty() @Column({ nullable: false }) - contentId: number; + articleId: number; + + @ManyToOne(() => Article, (article) => article.notification, { + createForeignKeyConstraints: false, + nullable: false, + }) + @JoinColumn({ name: 'article_id', referencedColumnName: 'id' }) + article?: Article; @ApiProperty() @Column({ nullable: false, default: false }) diff --git a/src/notification/notification.service.ts b/src/notification/notification.service.ts index 5fa3ba32..3581d0e2 100644 --- a/src/notification/notification.service.ts +++ b/src/notification/notification.service.ts @@ -17,7 +17,7 @@ export class NotificationService { const notification: CreateNotificationDto = { type: NotificationType.NEW_COMMENT, content: `게시글 ${article.title} 에 새로운 댓글이 달렸습니다.\n${comment.content}`, - contentId: article.id, + articleId: article.id, userId: article.writerId, }; return this.notificationRepository.save(notification); From 40f1bf79505a04ec0f73008a51b127194c5d1d89 Mon Sep 17 00:00:00 2001 From: huni Date: Thu, 10 Feb 2022 02:23:14 +0900 Subject: [PATCH 24/55] =?UTF-8?q?Feat:=20notification=20article=20id=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=EB=90=9C=20migrate=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../1644422087542-add_notification_articleid.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 src/database/migrations/1644422087542-add_notification_articleid.ts diff --git a/src/database/migrations/1644422087542-add_notification_articleid.ts b/src/database/migrations/1644422087542-add_notification_articleid.ts new file mode 100644 index 00000000..75c05414 --- /dev/null +++ b/src/database/migrations/1644422087542-add_notification_articleid.ts @@ -0,0 +1,14 @@ +import {MigrationInterface, QueryRunner} from "typeorm"; + +export class addNotificationArticleid1644422087542 implements MigrationInterface { + name = 'addNotificationArticleid1644422087542' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE \`notification\` ADD \`article_id\` int NOT NULL`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE \`notification\` DROP COLUMN \`article_id\``); + } + +} From 7a9e050a13a17d3f6fccf3078e860efc55392de6 Mon Sep 17 00:00:00 2001 From: rockpell Date: Thu, 10 Feb 2022 12:31:05 +0900 Subject: [PATCH 25/55] =?UTF-8?q?Fix:=20utc=20=ED=83=80=EC=9E=84=EC=A1=B4?= =?UTF-8?q?=EC=9D=84=20=EB=8B=A4=EC=8B=9C=20kst=EB=A1=9C=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Asia/Seoul, KST로 했을 경우 서버 시작 시에 경고가 뜬다 - 지금과 같은 형식으로 적용하면 경고가 안뜨고 잘 적용됨 --- docker-compose.yml | 2 +- run_test_db.sh | 2 +- src/database/ormconfig.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 1b36c746..50bff69d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -11,7 +11,7 @@ services: - MYSQL_PASSWORD=${DB_USER_PASSWORD} - MYSQL_ALLOW_EMPTY_PASSWORD=true - MYSQL_INITDB_ARGS=--encoding=UTF-8 - - TZ=UTC + - TZ=Asia/Seoul command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci healthcheck: test: 'mysqladmin ping -h localhost -u $$DB_USER_NAME --password=$$DB_USER_PASSWORD' diff --git a/run_test_db.sh b/run_test_db.sh index 1371f655..32e2ab9d 100755 --- a/run_test_db.sh +++ b/run_test_db.sh @@ -7,6 +7,6 @@ docker run -d --rm --name ft_world-mysql-test \ -e MYSQL_PASSWORD=ft_world \ -e MYSQL_ALLOW_EMPTY_PASSWORD=true \ -e MYSQL_INITDB_ARGS=--encoding=UTF-8 \ --e TZ=UTC \ +-e TZ=Asia/Seoul \ -p 3308:3306 mysql:5.7 \ mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci diff --git a/src/database/ormconfig.ts b/src/database/ormconfig.ts index 3e6d7cbc..ba33c95a 100644 --- a/src/database/ormconfig.ts +++ b/src/database/ormconfig.ts @@ -15,7 +15,7 @@ export const ormconfig = (): IOrmconfig => ({ entities: [__dirname + '../../**/*.entity{.ts,.js}'], namingStrategy: new SnakeNamingStrategy(), - timezone: 'Z', + timezone: '+09:00', // KST synchronize: false, migrationsRun: true, From 96672dccca076d9c6dc1286487f1898b12b6ec7b Mon Sep 17 00:00:00 2001 From: rockpell Date: Fri, 11 Feb 2022 23:24:49 +0900 Subject: [PATCH 26/55] =?UTF-8?q?Refactor:=20make=20dev=EA=B0=80=20?= =?UTF-8?q?=EB=B6=88=ED=8E=B8=ED=95=B4=EC=84=9C=20=EA=B0=9C=ED=8E=B8,=20su?= =?UTF-8?q?do=20=EC=A0=9C=EA=B1=B0,=20yarn=20start:dev=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - make dev 실행 할때 sudo로 실행되면 dist가 root로 생성되어 삭제하기 귀찮은 문제가 있었음 - sudo는 alpha에서만 붙이도록 수정 --- Makefile | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Makefile b/Makefile index 045cdc9d..b6d47e2c 100644 --- a/Makefile +++ b/Makefile @@ -1,13 +1,14 @@ -COMPOSE = sudo docker-compose +COMPOSE = docker-compose COMPOSE_ENV = ${COMPOSE} --env-file config/.env dev: cp ./config/.env.dev ./config/.env make db redis + yarn start:dev alpha: cp ./config/.env.alpha ./config/.env - export NODE_ENV=alpha && $(call COMPOSE_ENV) up --build -d + export NODE_ENV=alpha && sudo $(call COMPOSE_ENV) up --build -d prod: cp ./config/.env.prod ./config/.env @@ -17,7 +18,7 @@ db-dev: export NODE_ENV=dev && $(call COMPOSE_ENV) up --build -d db db-alpha: - export NODE_ENV=alpha && $(call COMPOSE_ENV) up --build -d db + export NODE_ENV=alpha && sudo $(call COMPOSE_ENV) up --build -d db db: db-dev @@ -33,7 +34,7 @@ redis-dev: export NODE_ENV=dev && $(call COMPOSE_ENV) up --build -d redis redis-alpha: - export NODE_ENV=alpha && $(call COMPOSE_ENV) up --build -d redis + export NODE_ENV=alpha && sudo $(call COMPOSE_ENV) up --build -d redis api: api-dev @@ -42,7 +43,7 @@ api-dev: export NODE_ENV=dev && $(call COMPOSE_ENV) up --build --no-deps -d api api-alpha: - export NODE_ENV=alpha && $(call COMPOSE_ENV) up --build --no-deps -d api + export NODE_ENV=alpha && sudo $(call COMPOSE_ENV) up --build --no-deps -d api api-prod: sudo docker build -t ft-world-api . From 12f1a39dafef12c2607574d495fbef31e848b2ec Mon Sep 17 00:00:00 2001 From: Luna Date: Sun, 13 Feb 2022 17:33:49 +0900 Subject: [PATCH 27/55] Fix: access_token_naming --- src/auth/constants/access-token.ts | 2 +- test/user.e2e-spec.ts | 15 ++++++++------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/auth/constants/access-token.ts b/src/auth/constants/access-token.ts index 574b4465..5d7635f9 100644 --- a/src/auth/constants/access-token.ts +++ b/src/auth/constants/access-token.ts @@ -1 +1 @@ -export const ACCESS_TOKEN = 'access_token'; +export const ACCESS_TOKEN = '6c3c0c426475c5322e919ae9ba551dcb6e6e94d4'; diff --git a/test/user.e2e-spec.ts b/test/user.e2e-spec.ts index f26614fe..ae833a49 100644 --- a/test/user.e2e-spec.ts +++ b/test/user.e2e-spec.ts @@ -25,6 +25,7 @@ import { CommentRepository } from '@comment/repositories/comment.repository'; import { ReactionModule } from '@root/reaction/reaction.module'; import { ReactionArticle } from '@root/reaction/entities/reaction-article.entity'; import { ReactionArticleRepository } from '@root/reaction/repositories/reaction-article.repository'; +import { ACCESS_TOKEN } from '@root/auth/constants/access-token'; describe('UserController (e2e)', () => { let app: INestApplication; @@ -148,7 +149,7 @@ describe('UserController (e2e)', () => { it('내 정보 가져오기', async () => { const response = await request(app) .get('/users/me') - .set('Cookie', `access_token=${JWT}`); + .set('Cookie', `${ACCESS_TOKEN}=${JWT}`); expect(response.status).toEqual(200); }); @@ -156,7 +157,7 @@ describe('UserController (e2e)', () => { it('특정 유저 정보 가져오기', async () => { const response = await request(app) .get('/users/2') - .set('Cookie', `access_token=${JWT}`); + .set('Cookie', `${ACCESS_TOKEN}=${JWT}`); expect(response.status).toEqual(200); expect(response.body.id).toEqual(2); @@ -171,7 +172,7 @@ describe('UserController (e2e)', () => { const response = await request(app) .put('/users') .send(updateData) - .set('Cookie', `access_token=${JWT}`); + .set('Cookie', `${ACCESS_TOKEN}=${JWT}`); expect(response.status).toEqual(200); @@ -183,7 +184,7 @@ describe('UserController (e2e)', () => { it('유저 삭제하기', async () => { const response = await request(app) .delete('/users') - .set('Cookie', `access_token=${JWT}`); + .set('Cookie', `${ACCESS_TOKEN}=${JWT}`); expect(response.status).toEqual(200); @@ -195,7 +196,7 @@ describe('UserController (e2e)', () => { it('내가 작성한 글 가져오기', async () => { const response = await request(app) .get('/users/me/articles') - .set('Cookie', `access_token=${JWT}`); + .set('Cookie', `${ACCESS_TOKEN}=${JWT}`); expect(response.status).toEqual(200); @@ -207,7 +208,7 @@ describe('UserController (e2e)', () => { it('내가 작성한 댓글 가져오기', async () => { const response = await request(app) .get('/users/me/comments') - .set('Cookie', `access_token=${JWT}`); + .set('Cookie', `${ACCESS_TOKEN}=${JWT}`); expect(response.status).toEqual(200); @@ -220,7 +221,7 @@ describe('UserController (e2e)', () => { it('내가 좋아요 누른 게시글 목록 확인', async () => { const response = await request(app) .get('/users/me/like-articles') - .set('Cookie', `access_token=${JWT}`); + .set('Cookie', `${ACCESS_TOKEN}=${JWT}`); expect(response.status).toEqual(200); From 64f6397104051ed25a6fabfbc7c36318040e4cc3 Mon Sep 17 00:00:00 2001 From: SeongMin Park Date: Sun, 13 Feb 2022 22:36:52 +0900 Subject: [PATCH 28/55] =?UTF-8?q?Refactor:=20=EC=82=AC=EC=9A=A9=ED=95=98?= =?UTF-8?q?=EC=A7=80=20=EC=95=8A=EB=8A=94=20=EC=A3=BC=EC=84=9D=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main.ts b/src/main.ts index f486a1e0..99264112 100644 --- a/src/main.ts +++ b/src/main.ts @@ -6,7 +6,6 @@ import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger'; import { WINSTON_MODULE_NEST_PROVIDER } from 'nest-winston'; import * as morgan from 'morgan'; import { join } from 'path'; -// import * as csurf from 'csurf'; import { AppModule } from './app.module'; import { ACCESS_TOKEN } from '@auth/constants/access-token'; @@ -54,7 +53,6 @@ async function bootstrap() { app.setBaseViewsDir(join(__dirname, '..', 'views/ft-auth')); app.setViewEngine('ejs'); app.use(cookieParser()); - // app.use(csurf({ cookie: true })); await app.listen(port || 3000); } bootstrap(); From f54152c1b6534c4e332efc19074ce9bcc563139c Mon Sep 17 00:00:00 2001 From: SeongMin Park Date: Sun, 13 Feb 2022 22:44:24 +0900 Subject: [PATCH 29/55] =?UTF-8?q?Refactor:=20=EB=A1=9C=EC=BB=AC=20?= =?UTF-8?q?=EB=B3=80=EC=88=98=20=EC=B9=B4=EB=A9=9C=EC=BC=80=EC=9D=B4?= =?UTF-8?q?=EC=8A=A4=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/article/repositories/article.repository.ts | 6 +++--- src/category/repositories/category.repository.ts | 6 +++--- src/comment/repositories/comment.repository.ts | 6 +++--- src/reaction/repositories/reaction-article.repository.ts | 4 ++-- src/user/repositories/user.repository.ts | 6 +++--- src/utils.ts | 4 ++-- 6 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/article/repositories/article.repository.ts b/src/article/repositories/article.repository.ts index 24487850..8f0ff96b 100644 --- a/src/article/repositories/article.repository.ts +++ b/src/article/repositories/article.repository.ts @@ -54,10 +54,10 @@ export class ArticleRepository extends Repository
{ } async existOrFail(id: number): Promise { - const exist_query = await this.query(`SELECT EXISTS + const existQuery = await this.query(`SELECT EXISTS (SELECT * FROM article WHERE id=${id} AND deleted_at IS NULL)`); - const is_exist = Object.values(exist_query[0])[0]; - if (is_exist === '0') { + const isExist = Object.values(existQuery[0])[0]; + if (isExist === '0') { throw new NotFoundException(`Can't find Article with id ${id}`); } } diff --git a/src/category/repositories/category.repository.ts b/src/category/repositories/category.repository.ts index d4716c52..cb1d35ed 100644 --- a/src/category/repositories/category.repository.ts +++ b/src/category/repositories/category.repository.ts @@ -6,10 +6,10 @@ import { Category } from '../entities/category.entity'; @EntityRepository(Category) export class CategoryRepository extends Repository { async existOrFail(id: number): Promise { - const exist_query = await this.query(`SELECT EXISTS + const existQuery = await this.query(`SELECT EXISTS (SELECT * FROM category WHERE id=${id} AND deleted_at IS NULL)`); - const is_exist = Object.values(exist_query[0])[0]; - if (is_exist === '0') { + const isExist = Object.values(existQuery[0])[0]; + if (isExist === '0') { throw new NotFoundException(`Can't find Category with id ${id}`); } } diff --git a/src/comment/repositories/comment.repository.ts b/src/comment/repositories/comment.repository.ts index 52379511..0caae15e 100644 --- a/src/comment/repositories/comment.repository.ts +++ b/src/comment/repositories/comment.repository.ts @@ -30,10 +30,10 @@ export class CommentRepository extends Repository { } async existOrFail(id: number): Promise { - const exist_query = await this.query(`SELECT EXISTS + const existQuery = await this.query(`SELECT EXISTS (SELECT * FROM comment WHERE id=${id} AND deleted_at IS NULL)`); - const is_exist = Object.values(exist_query[0])[0]; - if (is_exist === '0') { + const isExist = Object.values(existQuery[0])[0]; + if (isExist === '0') { throw new NotFoundException(`Can't find Comments with id ${id}`); } } diff --git a/src/reaction/repositories/reaction-article.repository.ts b/src/reaction/repositories/reaction-article.repository.ts index 24231be0..4d92fb75 100644 --- a/src/reaction/repositories/reaction-article.repository.ts +++ b/src/reaction/repositories/reaction-article.repository.ts @@ -11,9 +11,9 @@ export class ReactionArticleRepository extends Repository { articleId: number, type: ReactionArticleType, ): Promise { - const exist_query = await this.query(`SELECT EXISTS + const existQuery = await this.query(`SELECT EXISTS (SELECT * FROM reaction_article WHERE user_id=${userId} AND article_id=${articleId} AND type='${type}')`); - return Object.values(exist_query[0])[0] === '1'; + return Object.values(existQuery[0])[0] === '1'; } async findAllArticleByUserId(userId: number): Promise { diff --git a/src/user/repositories/user.repository.ts b/src/user/repositories/user.repository.ts index 0aa2b8e0..56416dda 100644 --- a/src/user/repositories/user.repository.ts +++ b/src/user/repositories/user.repository.ts @@ -5,9 +5,9 @@ import { User } from '@user/entities/user.entity'; @EntityRepository(User) export class UserRepository extends Repository { async isExistById(id: number): Promise { - const exist_query = await this.query(`SELECT EXISTS + const existQuery = await this.query(`SELECT EXISTS (SELECT * FROM user WHERE id=${id} AND deleted_at IS NULL)`); - const is_exist = Object.values(exist_query[0])[0]; - return is_exist === '1' ? true : false; + const isExist = Object.values(existQuery[0])[0]; + return isExist === '1' ? true : false; } } diff --git a/src/utils.ts b/src/utils.ts index dbb05cb3..dcdc3a2e 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -32,8 +32,8 @@ export const errorHook = async ( exceptionName: string, exceptionMessage: string, ) => { - const PHASE = process.env.NODE_ENV; - const slackMessage = `[${PHASE}] ${exceptionName}: ${exceptionMessage}`; + const phase = process.env.NODE_ENV; + const slackMessage = `[${phase}] ${exceptionName}: ${exceptionMessage}`; try { await axios.post(process.env.SLACK_HOOK_URL, { text: slackMessage }); From 9174e9ba9946b4847e70b63c02edbc891698eb19 Mon Sep 17 00:00:00 2001 From: SeongMin Park Date: Sun, 13 Feb 2022 23:17:36 +0900 Subject: [PATCH 30/55] =?UTF-8?q?Refactor:=20e2e=20=EB=94=94=EB=A0=89?= =?UTF-8?q?=ED=86=A0=EB=A6=AC=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 +- test/e2e/jest-e2e.json | 22 ++++++++++++++++++++++ test/{ => e2e}/test.base.module.ts | 2 +- test/{ => e2e}/user.e2e-spec.ts | 0 test/jest-e2e.json | 22 ---------------------- 5 files changed, 24 insertions(+), 24 deletions(-) create mode 100644 test/e2e/jest-e2e.json rename test/{ => e2e}/test.base.module.ts (92%) rename test/{ => e2e}/user.e2e-spec.ts (100%) delete mode 100644 test/jest-e2e.json diff --git a/package.json b/package.json index e9006490..1059113c 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "test:watch": "jest --watch", "test:cov": "jest --coverage", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", - "test:e2e": "NODE_ENV=test jest --config ./test/jest-e2e.json", + "test:e2e": "NODE_ENV=test jest --config ./test/e2e/jest-e2e.json", "typeorm": "ts-node -r tsconfig-paths/register ./node_modules/typeorm/cli.js --config src/database/ormconfig.ts", "typeorm:migrate": "yarn typeorm migration:generate -n", "typeorm:run": "yarn typeorm migration:run", diff --git a/test/e2e/jest-e2e.json b/test/e2e/jest-e2e.json new file mode 100644 index 00000000..59d2c587 --- /dev/null +++ b/test/e2e/jest-e2e.json @@ -0,0 +1,22 @@ +{ + "moduleFileExtensions": ["js", "json", "ts"], + "rootDir": ".", + "testEnvironment": "node", + "testRegex": ".e2e-spec.ts$", + "transform": { + "^.+\\.(t|j)s$": "ts-jest" + }, + "moduleNameMapper": { + "^@root/(.*)$": "/../../src/$1", + "^@auth/(.*)$": "/../../src/auth/$1", + "^@user/(.*)$": "/../../src/user/$1", + "^@article/(.*)$": "/../../src/article/$1", + "^@best/(.*)$": "/../../src/best/$1", + "^@category/(.*)$": "/../../src/category/$1", + "^@comment/(.*)$": "/../../src/comment/$1", + "^@authenticate/(.*)$": "/../../src/authenticate/$1", + "^@notification/(.*)$": "/../../src/notification/$1", + "^@database/(.*)$": "/../../src/database/$1", + "^@ft-auth/(.*)$": "/../../src/ft-auth/$1" + } +} diff --git a/test/test.base.module.ts b/test/e2e/test.base.module.ts similarity index 92% rename from test/test.base.module.ts rename to test/e2e/test.base.module.ts index 03a8bca3..3b503316 100644 --- a/test/test.base.module.ts +++ b/test/e2e/test.base.module.ts @@ -16,7 +16,7 @@ import configEmail from '@root/config/mail.config'; username: 'ft_world', password: 'ft_world', database: 'ft_world', - entities: [path.join(__dirname, '../src/**/*.entity{.ts,.js}')], + entities: [path.join(__dirname, '../../src/**/*.entity{.ts,.js}')], namingStrategy: new SnakeNamingStrategy(), synchronize: true, logging: false, diff --git a/test/user.e2e-spec.ts b/test/e2e/user.e2e-spec.ts similarity index 100% rename from test/user.e2e-spec.ts rename to test/e2e/user.e2e-spec.ts diff --git a/test/jest-e2e.json b/test/jest-e2e.json deleted file mode 100644 index 895e130c..00000000 --- a/test/jest-e2e.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "moduleFileExtensions": ["js", "json", "ts"], - "rootDir": ".", - "testEnvironment": "node", - "testRegex": ".e2e-spec.ts$", - "transform": { - "^.+\\.(t|j)s$": "ts-jest" - }, - "moduleNameMapper": { - "^@root/(.*)$": "/../src/$1", - "^@auth/(.*)$": "/../src/auth/$1", - "^@user/(.*)$": "/../src/user/$1", - "^@article/(.*)$": "/../src/article/$1", - "^@best/(.*)$": "/../src/best/$1", - "^@category/(.*)$": "/../src/category/$1", - "^@comment/(.*)$": "/../src/comment/$1", - "^@authenticate/(.*)$": "/../src/authenticate/$1", - "^@notification/(.*)$": "/../src/notification/$1", - "^@database/(.*)$": "/../src/database/$1", - "^@ft-auth/(.*)$": "/../src/ft-auth/$1" - } -} From 8d044c703cca51fc924d4e981c47f9a522dd222e Mon Sep 17 00:00:00 2001 From: huni Date: Tue, 15 Feb 2022 04:08:30 +0900 Subject: [PATCH 31/55] =?UTF-8?q?Fix:=20=EB=82=B4=20=EC=A2=8B=EC=95=84?= =?UTF-8?q?=EC=9A=94=20=EA=B2=8C=EC=8B=9C=EA=B8=80=20=EC=9D=91=EB=8B=B5=20?= =?UTF-8?q?=ED=83=80=EC=9E=85=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 기존에 ReactionArticle[] 반환에서 - Article[] 반환으로 변경 - 함수이름 findAllMyReactionArticle로 변경 - query시 type도 포함하여 검색 --- src/reaction/reaction.service.ts | 10 ++++++++-- .../repositories/reaction-article.repository.ts | 6 +++++- src/user/user.controller.ts | 15 +++++++++++---- 3 files changed, 24 insertions(+), 7 deletions(-) diff --git a/src/reaction/reaction.service.ts b/src/reaction/reaction.service.ts index 03e028e9..e2d0f624 100644 --- a/src/reaction/reaction.service.ts +++ b/src/reaction/reaction.service.ts @@ -118,7 +118,13 @@ export class ReactionService { }); } - findAllArticleByUserId(userId: number): Promise { - return this.reactionArticleRepository.findAllArticleByUserId(userId); + findAllMyReactionArticle( + userId: number, + type: ReactionArticleType = ReactionArticleType.LIKE, + ): Promise { + return this.reactionArticleRepository.findAllMyReactionArticle( + userId, + type, + ); } } diff --git a/src/reaction/repositories/reaction-article.repository.ts b/src/reaction/repositories/reaction-article.repository.ts index 4d92fb75..3a551876 100644 --- a/src/reaction/repositories/reaction-article.repository.ts +++ b/src/reaction/repositories/reaction-article.repository.ts @@ -16,11 +16,15 @@ export class ReactionArticleRepository extends Repository { return Object.values(existQuery[0])[0] === '1'; } - async findAllArticleByUserId(userId: number): Promise { + async findAllMyReactionArticle( + userId: number, + type: ReactionArticleType = ReactionArticleType.LIKE, + ): Promise { return this.createQueryBuilder('reactionArticle') .leftJoinAndSelect('reactionArticle.article', 'article') .leftJoinAndSelect('article.category', 'category') .andWhere('reactionArticle.userId = :id', { id: userId }) + .andWhere('reactionArticle.type = :type', { type }) .getMany(); } } diff --git a/src/user/user.controller.ts b/src/user/user.controller.ts index c9e52e0c..9d7351f3 100644 --- a/src/user/user.controller.ts +++ b/src/user/user.controller.ts @@ -20,7 +20,10 @@ import { UpdateUserDto } from './dto/update-user.dto'; import { User } from './entities/user.entity'; import { NotificationService } from '@root/notification/notification.service'; import { ReactionService } from '@root/reaction/reaction.service'; -import { ReactionArticle } from '@root/reaction/entities/reaction-article.entity'; +import { + ReactionArticle, + ReactionArticleType, +} from '@root/reaction/entities/reaction-article.entity'; import { ArticleService } from '@article/article.service'; import { CommentService } from '@comment/comment.service'; import { Article } from '@article/entities/article.entity'; @@ -85,10 +88,14 @@ export class UserController { description: '유저가 좋아요 누른 게시글 목록', type: [ReactionArticle], }) - findAllReactionArticle( + async findAllReactionArticle( @GetUser('id') userId: number, - ): Promise { - return this.reactionService.findAllArticleByUserId(userId); + ): Promise { + const likeArticles = await this.reactionService.findAllMyReactionArticle( + userId, + ReactionArticleType.LIKE, + ); + return likeArticles.map((e) => e.article); } @Get('me/articles') From 80bbc5fda3974c24654f1e43657e8677dc460ba0 Mon Sep 17 00:00:00 2001 From: huni Date: Tue, 15 Feb 2022 22:09:00 +0900 Subject: [PATCH 32/55] =?UTF-8?q?Chore:=20user=20=EC=BB=A8=ED=8A=B8?= =?UTF-8?q?=EB=A1=A4=EB=9F=AC=20=ED=95=A8=EC=88=98=20=EB=AA=85=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - findAllReactionArticle 에서 - findAllMyReactionArticle 로 변경 --- src/user/user.controller.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/user/user.controller.ts b/src/user/user.controller.ts index 9d7351f3..a9d1d629 100644 --- a/src/user/user.controller.ts +++ b/src/user/user.controller.ts @@ -88,7 +88,7 @@ export class UserController { description: '유저가 좋아요 누른 게시글 목록', type: [ReactionArticle], }) - async findAllReactionArticle( + async findAllMyReactionArticle( @GetUser('id') userId: number, ): Promise { const likeArticles = await this.reactionService.findAllMyReactionArticle( From 7585d231b5f98c443b69556b1947373eb7a66763 Mon Sep 17 00:00:00 2001 From: SeongMin Park Date: Tue, 15 Feb 2022 23:20:28 +0900 Subject: [PATCH 33/55] =?UTF-8?q?Refactor:=200=EC=9D=B4=ED=95=98=20?= =?UTF-8?q?=EC=A1=B0=EA=B1=B4=20=EC=A1=B0=EA=B8=88=20=EB=8D=94=20=EB=B3=B4?= =?UTF-8?q?=EA=B8=B0=20=EC=89=BD=EA=B2=8C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/article/article.service.ts | 2 +- src/comment/comment.service.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/article/article.service.ts b/src/article/article.service.ts index f8ddf6f3..3119d5ab 100644 --- a/src/article/article.service.ts +++ b/src/article/article.service.ts @@ -98,7 +98,7 @@ export class ArticleService { } decreaseLikeCount(article: Article): Promise
{ - if (article.likeCount < 1) { + if (article.likeCount <= 0) { throw new NotAcceptableException('좋아요는 0이하가 될 수 없습니다.'); } article.likeCount -= 1; diff --git a/src/comment/comment.service.ts b/src/comment/comment.service.ts index 0aa33bef..da4a1fd6 100644 --- a/src/comment/comment.service.ts +++ b/src/comment/comment.service.ts @@ -88,7 +88,7 @@ export class CommentService { } decreaseLikeCount(comment: Comment): Promise { - if (comment.likeCount < 1) { + if (comment.likeCount <= 0) { throw new NotAcceptableException('좋아요는 0이하가 될 수 없습니다.'); } comment.likeCount -= 1; From 4eaf3788a3189a2c6ee0ad49f69307f7c036fcae Mon Sep 17 00:00:00 2001 From: SeongMin Park Date: Tue, 15 Feb 2022 23:21:32 +0900 Subject: [PATCH 34/55] =?UTF-8?q?Feat:=20=EA=B2=8C=EC=8B=9C=EA=B8=80=20?= =?UTF-8?q?=EC=A2=8B=EC=95=84=EC=9A=94=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/e2e/reaction.e2e-spec.ts | 158 ++++++++++++++++++++++++++++++++++ 1 file changed, 158 insertions(+) create mode 100644 test/e2e/reaction.e2e-spec.ts diff --git a/test/e2e/reaction.e2e-spec.ts b/test/e2e/reaction.e2e-spec.ts new file mode 100644 index 00000000..dcb73e77 --- /dev/null +++ b/test/e2e/reaction.e2e-spec.ts @@ -0,0 +1,158 @@ +import { ArticleModule } from '@article/article.module'; +import { Article } from '@article/entities/article.entity'; +import { ArticleRepository } from '@article/repositories/article.repository'; +import { AuthModule } from '@auth/auth.module'; +import { AuthService } from '@auth/auth.service'; +import { JWTPayload } from '@auth/interfaces/jwt-payload.interface'; +import { CategoryModule } from '@category/category.module'; +import { Category } from '@category/entities/category.entity'; +import { CategoryRepository } from '@category/repositories/category.repository'; +import { CommentModule } from '@comment/comment.module'; +import { Comment } from '@comment/entities/comment.entity'; +import { CommentRepository } from '@comment/repositories/comment.repository'; +import { INestApplication, ValidationPipe } from '@nestjs/common'; +import { Test, TestingModule } from '@nestjs/testing'; +import { TypeormExceptionFilter } from '@root/filters/typeorm-exception.filter'; +import { ReactionModule } from '@root/reaction/reaction.module'; +import { ReactionArticleRepository } from '@root/reaction/repositories/reaction-article.repository'; +import { User, UserRole } from '@user/entities/user.entity'; +import { UserRepository } from '@user/repositories/user.repository'; +import { UserModule } from '@user/user.module'; +import * as cookieParser from 'cookie-parser'; +import * as request from 'supertest'; +import { getConnection } from 'typeorm'; +import { TestBaseModule } from './test.base.module'; + +describe('UserController (e2e)', () => { + let app: INestApplication; + let userRepository: UserRepository; + let articleRepository: ArticleRepository; + let categoryRepository: CategoryRepository; + let commentRepository: CommentRepository; + let reactionArticleRepository: ReactionArticleRepository; + let authService: AuthService; + let JWT; + + beforeAll(async () => { + const moduleFixture: TestingModule = await Test.createTestingModule({ + imports: [ + TestBaseModule, + UserModule, + AuthModule, + ArticleModule, + CategoryModule, + CommentModule, + ReactionModule, + ], + }).compile(); + + app = moduleFixture.createNestApplication(); + app.use(cookieParser()); + + app.useGlobalFilters(new TypeormExceptionFilter()); + app.useGlobalPipes( + new ValidationPipe({ + whitelist: true, + forbidNonWhitelisted: true, + transform: true, + }), + ); + await app.init(); + + userRepository = moduleFixture.get(UserRepository); + articleRepository = moduleFixture.get(ArticleRepository); + categoryRepository = + moduleFixture.get(CategoryRepository); + commentRepository = moduleFixture.get(CommentRepository); + reactionArticleRepository = moduleFixture.get( + ReactionArticleRepository, + ); + + authService = moduleFixture.get(AuthService); + + app = app.getHttpServer(); + }); + + beforeEach(async () => { + const newUser = new User(); + newUser.oauthToken = 'test1234'; + newUser.nickname = 'first user'; + newUser.role = UserRole.CADET; + await userRepository.save(newUser); + + const newUser2 = new User(); + newUser2.oauthToken = 'test1234'; + newUser2.nickname = 'second user'; + newUser2.role = UserRole.CADET; + await userRepository.save(newUser2); + + JWT = authService.getJWT({ + userId: newUser.id, + userRole: newUser.role, + } as JWTPayload); + + const newCategory = new Category(); + newCategory.name = '자유게시판'; + + await categoryRepository.save(newCategory); + + const newArticle = new Article(); + newArticle.title = 'a'; + newArticle.content = 'bb'; + newArticle.categoryId = newCategory.id; + newArticle.writerId = newUser.id; + await articleRepository.save(newArticle); + + const newComment = new Comment(); + newComment.content = 'cc'; + newComment.writerId = newUser.id; + newComment.articleId = newArticle.id; + await commentRepository.save(newComment); + }); + + afterEach(async () => { + await Promise.all([ + userRepository.clear(), + articleRepository.clear(), + commentRepository.clear(), + categoryRepository.clear(), + reactionArticleRepository.clear(), + ]); + }); + + afterAll(async () => { + await getConnection().dropDatabase(); + await getConnection().close(); + await app.close(); + }); + + test('[성공] 게시글 리엑션 성공 - 좋아요가 없는 경우', async () => { + const response = await request(app) + .post('/reactions/articles/1') + .set('Cookie', `access_token=${JWT}`); + + expect(response.status).toEqual(201); + expect(response.body.isLike).toEqual(true); + expect(response.body.likeCount).toEqual(1); + }); + + test('[성공] 게시글 리엑션 성공 - 좋아요가 있는 경우', async () => { + await request(app) + .post('/reactions/articles/1') + .set('Cookie', `access_token=${JWT}`); + + const response2 = await request(app) + .post('/reactions/articles/1') + .set('Cookie', `access_token=${JWT}`); + + expect(response2.status).toEqual(201); + expect(response2.body.isLike).toEqual(false); + expect(response2.body.likeCount).toEqual(0); + }); + + test('[실패] 게시글 리엑션 실패 - unauthorize', async () => { + const response2 = await request(app).post('/reactions/articles/1'); + + expect(response2.status).toEqual(401); + }); +}); From 83da32a4eec489d6ce7e5341a49d09785b850df3 Mon Sep 17 00:00:00 2001 From: SeongMin Park Date: Tue, 15 Feb 2022 23:28:14 +0900 Subject: [PATCH 35/55] =?UTF-8?q?Feat:=20comment=20=EB=8D=94=EB=AF=B8=20?= =?UTF-8?q?=EB=8D=B0=EC=9D=B4=ED=84=B0=20=ED=95=A8=EC=88=98=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/e2e/reaction.e2e-spec.ts | 7 ++----- test/e2e/utils/dummy.ts | 9 +++++++++ 2 files changed, 11 insertions(+), 5 deletions(-) create mode 100644 test/e2e/utils/dummy.ts diff --git a/test/e2e/reaction.e2e-spec.ts b/test/e2e/reaction.e2e-spec.ts index dcb73e77..00c3b210 100644 --- a/test/e2e/reaction.e2e-spec.ts +++ b/test/e2e/reaction.e2e-spec.ts @@ -8,7 +8,6 @@ import { CategoryModule } from '@category/category.module'; import { Category } from '@category/entities/category.entity'; import { CategoryRepository } from '@category/repositories/category.repository'; import { CommentModule } from '@comment/comment.module'; -import { Comment } from '@comment/entities/comment.entity'; import { CommentRepository } from '@comment/repositories/comment.repository'; import { INestApplication, ValidationPipe } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; @@ -22,6 +21,7 @@ import * as cookieParser from 'cookie-parser'; import * as request from 'supertest'; import { getConnection } from 'typeorm'; import { TestBaseModule } from './test.base.module'; +import * as dummy from './utils/dummy'; describe('UserController (e2e)', () => { let app: INestApplication; @@ -103,10 +103,7 @@ describe('UserController (e2e)', () => { newArticle.writerId = newUser.id; await articleRepository.save(newArticle); - const newComment = new Comment(); - newComment.content = 'cc'; - newComment.writerId = newUser.id; - newComment.articleId = newArticle.id; + const newComment = dummy.comment(newUser.id, newArticle.id); await commentRepository.save(newComment); }); diff --git a/test/e2e/utils/dummy.ts b/test/e2e/utils/dummy.ts new file mode 100644 index 00000000..9f24b743 --- /dev/null +++ b/test/e2e/utils/dummy.ts @@ -0,0 +1,9 @@ +import { Comment } from '@comment/entities/comment.entity'; + +export const comment = (userId: number, articleId: number) => { + const comment = new Comment(); + comment.content = 'cc'; + comment.writerId = userId; + comment.articleId = articleId; + return comment; +}; From c0a5032a416d5967da5b07eae9b8545bcfc6d80e Mon Sep 17 00:00:00 2001 From: SeongMin Park Date: Wed, 16 Feb 2022 22:44:51 +0900 Subject: [PATCH 36/55] =?UTF-8?q?Feat:=20user,=20jwt,=20category,=20articl?= =?UTF-8?q?e=20=EB=8D=94=EB=AF=B8=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20?= =?UTF-8?q?=ED=95=A8=EC=88=98=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/e2e/reaction.e2e-spec.ts | 37 +++++------------------ test/e2e/utils/dummy.ts | 56 +++++++++++++++++++++++++++++++++-- 2 files changed, 61 insertions(+), 32 deletions(-) diff --git a/test/e2e/reaction.e2e-spec.ts b/test/e2e/reaction.e2e-spec.ts index 00c3b210..3406fb16 100644 --- a/test/e2e/reaction.e2e-spec.ts +++ b/test/e2e/reaction.e2e-spec.ts @@ -1,11 +1,8 @@ import { ArticleModule } from '@article/article.module'; -import { Article } from '@article/entities/article.entity'; import { ArticleRepository } from '@article/repositories/article.repository'; import { AuthModule } from '@auth/auth.module'; import { AuthService } from '@auth/auth.service'; -import { JWTPayload } from '@auth/interfaces/jwt-payload.interface'; import { CategoryModule } from '@category/category.module'; -import { Category } from '@category/entities/category.entity'; import { CategoryRepository } from '@category/repositories/category.repository'; import { CommentModule } from '@comment/comment.module'; import { CommentRepository } from '@comment/repositories/comment.repository'; @@ -14,7 +11,7 @@ import { Test, TestingModule } from '@nestjs/testing'; import { TypeormExceptionFilter } from '@root/filters/typeorm-exception.filter'; import { ReactionModule } from '@root/reaction/reaction.module'; import { ReactionArticleRepository } from '@root/reaction/repositories/reaction-article.repository'; -import { User, UserRole } from '@user/entities/user.entity'; +import { UserRole } from '@user/entities/user.entity'; import { UserRepository } from '@user/repositories/user.repository'; import { UserModule } from '@user/user.module'; import * as cookieParser from 'cookie-parser'; @@ -74,37 +71,17 @@ describe('UserController (e2e)', () => { }); beforeEach(async () => { - const newUser = new User(); - newUser.oauthToken = 'test1234'; - newUser.nickname = 'first user'; - newUser.role = UserRole.CADET; - await userRepository.save(newUser); - - const newUser2 = new User(); - newUser2.oauthToken = 'test1234'; - newUser2.nickname = 'second user'; - newUser2.role = UserRole.CADET; + const newUser1 = dummy.user('testAuth', 'nickname', UserRole.CADET); + const newUser2 = dummy.user('testAuth', 'nickname', UserRole.CADET); + await userRepository.save(newUser1); await userRepository.save(newUser2); - JWT = authService.getJWT({ - userId: newUser.id, - userRole: newUser.role, - } as JWTPayload); - - const newCategory = new Category(); - newCategory.name = '자유게시판'; + JWT = dummy.jwt(newUser1.id, newUser1.role, authService); + const newCategory = dummy.category('새로운 카테고리'); await categoryRepository.save(newCategory); - - const newArticle = new Article(); - newArticle.title = 'a'; - newArticle.content = 'bb'; - newArticle.categoryId = newCategory.id; - newArticle.writerId = newUser.id; + const newArticle = dummy.article(newCategory.id, newUser1.id, '', ''); await articleRepository.save(newArticle); - - const newComment = dummy.comment(newUser.id, newArticle.id); - await commentRepository.save(newComment); }); afterEach(async () => { diff --git a/test/e2e/utils/dummy.ts b/test/e2e/utils/dummy.ts index 9f24b743..f2705849 100644 --- a/test/e2e/utils/dummy.ts +++ b/test/e2e/utils/dummy.ts @@ -1,8 +1,60 @@ +import { Article } from '@article/entities/article.entity'; +import { AuthService } from '@auth/auth.service'; +import { JWTPayload } from '@auth/interfaces/jwt-payload.interface'; +import { Category } from '@category/entities/category.entity'; import { Comment } from '@comment/entities/comment.entity'; +import { User, UserRole } from '@user/entities/user.entity'; -export const comment = (userId: number, articleId: number) => { +export const user = ( + oauthToken: string, + nickname: string, + role: UserRole, +): User => { + const newUser2 = new User(); + newUser2.oauthToken = oauthToken; + newUser2.nickname = nickname; + newUser2.role = role; + return newUser2; +}; + +export const jwt = ( + userId: number, + role: string, + authService: AuthService, +): string => { + return authService.getJWT({ + userId: userId, + userRole: role, + } as JWTPayload); +}; + +export const category = (name: string) => { + const category = new Category(); + category.name = name; + return category; +}; + +export const article = ( + categoryId: number, + writerId: number, + title: string, + content: string, +): Article => { + const article = new Article(); + article.title = title; + article.content = content; + article.categoryId = categoryId; + article.writerId = writerId; + return article; +}; + +export const comment = ( + userId: number, + articleId: number, + content: string, +): Comment => { const comment = new Comment(); - comment.content = 'cc'; + comment.content = content; comment.writerId = userId; comment.articleId = articleId; return comment; From c9d3f241fe7ec322c25e8b30fdb88a45f01282d4 Mon Sep 17 00:00:00 2001 From: rockpell Date: Thu, 17 Feb 2022 01:12:30 +0900 Subject: [PATCH 37/55] =?UTF-8?q?Add:=20s3=EC=97=90=20=EC=97=85=EB=A1=9C?= =?UTF-8?q?=EB=93=9C=ED=95=98=EA=B8=B0=20=EC=9C=84=ED=95=9C=20url=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1=20api?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - aws module 추가 - aws module을 이용한 s3에 업로드 하기 위한 url 생성 - url을 생성할때 public-read ACL을 설정해야함 - 기존에 s3에 대한 권한은 미리 해둬야함 --- package.json | 6 +- src/app.module.ts | 21 +++++ src/config/constants.ts | 6 +- .../dto/upload-image-url-response.dto.ts | 10 ++ src/image/image.controller.ts | 27 ++++++ src/image/image.module.ts | 13 +++ src/image/image.service.ts | 25 +++++ src/image/interfaces/s3-param.interface.ts | 7 ++ tsconfig.json | 3 +- yarn.lock | 91 ++++++++++++++++++- 10 files changed, 201 insertions(+), 8 deletions(-) create mode 100644 src/image/dto/upload-image-url-response.dto.ts create mode 100644 src/image/image.controller.ts create mode 100644 src/image/image.module.ts create mode 100644 src/image/image.service.ts create mode 100644 src/image/interfaces/s3-param.interface.ts diff --git a/package.json b/package.json index e9006490..4ba5b7c7 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ "@nestjs/platform-express": "^8.0.0", "@nestjs/swagger": "^5.1.5", "@nestjs/typeorm": "^8.0.2", + "aws-sdk": "^2.1074.0", "axios": "^0.25.0", "bcryptjs": "^2.4.3", "cache-manager": "^3.6.0", @@ -43,11 +44,12 @@ "class-transformer": "^0.5.1", "class-validator": "^0.13.2", "cookie-parser": "^1.4.6", + "csurf": "^1.11.0", + "ejs": "^3.1.6", "morgan": "^1.10.0", "mysql2": "^2.3.3", + "nest-aws-sdk": "^2.1.0", "nest-winston": "^1.6.2", - "csurf": "^1.11.0", - "ejs": "^3.1.6", "nodemailer": "^6.7.2", "passport": "^0.5.2", "passport-github2": "^0.1.12", diff --git a/src/app.module.ts b/src/app.module.ts index 9cfe8ce9..b19a3009 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -26,6 +26,13 @@ import { DatabaseModule } from './database/database.module'; import { JwtAuthGuard } from './auth/jwt-auth.guard'; import { ormconfig } from './database/ormconfig'; import { FtCheckinModule } from './ft-checkin/ft-checkin.module'; +import { AwsSdkModule } from 'nest-aws-sdk'; +import { + AWS_ACCESS_KEY, + AWS_REGION, + AWS_SECRET_KEY, +} from '@root/config/constants'; +import { ImageModule } from '@root/image/image.module'; @Module({ imports: [ @@ -71,6 +78,19 @@ import { FtCheckinModule } from './ft-checkin/ft-checkin.module'; }), ], }), + AwsSdkModule.forRootAsync({ + defaultServiceOptions: { + imports: [ConfigModule], + inject: [ConfigService], + useFactory: (configService: ConfigService) => { + return { + region: configService.get(AWS_REGION), + accessKeyId: configService.get(AWS_ACCESS_KEY), + secretAccessKey: configService.get(AWS_SECRET_KEY), + }; + }, + }, + }), CommentModule, UserModule, ArticleModule, @@ -81,6 +101,7 @@ import { FtCheckinModule } from './ft-checkin/ft-checkin.module'; BestModule, ReactionModule, FtCheckinModule, + ImageModule, ], controllers: [AppController], providers: [ diff --git a/src/config/constants.ts b/src/config/constants.ts index 1e167d68..ebaf13c4 100644 --- a/src/config/constants.ts +++ b/src/config/constants.ts @@ -1,4 +1,6 @@ -export const CONFIG_OPTIONS = 'CONFIG_OPTIONS'; export const GITHUB_CLIENT_ID = 'GITHUB_CLIENT_ID'; export const GITHUB_CLIENT_SECRET = 'GITHUB_CLIENT_SECRET'; -export const JWT_SECRET = 'JWT_SECRET'; +export const AWS_REGION = 'AWS_REGION'; +export const AWS_ACCESS_KEY = 'AWS_ACCESS_KEY'; +export const AWS_SECRET_KEY = 'AWS_SECRET_KEY'; +export const S3_URL_EXPIRATION_SECONDS = 300; diff --git a/src/image/dto/upload-image-url-response.dto.ts b/src/image/dto/upload-image-url-response.dto.ts new file mode 100644 index 00000000..14c98e0a --- /dev/null +++ b/src/image/dto/upload-image-url-response.dto.ts @@ -0,0 +1,10 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export class UploadImageUrlResponseDto { + @ApiProperty() + uploadUrl!: string; + + constructor(uploadUrl: string) { + this.uploadUrl = uploadUrl; + } +} diff --git a/src/image/image.controller.ts b/src/image/image.controller.ts new file mode 100644 index 00000000..9403590a --- /dev/null +++ b/src/image/image.controller.ts @@ -0,0 +1,27 @@ +import { Controller, Post } from '@nestjs/common'; +import { + ApiOkResponse, + ApiOperation, + ApiTags, + ApiUnauthorizedResponse, +} from '@nestjs/swagger'; +import { ImageService } from '@image/image.service'; +import { UploadImageUrlResponseDto } from '@image/dto/upload-image-url-response.dto'; + +@ApiUnauthorizedResponse({ description: '인증 실패' }) +@ApiTags('Image') +@Controller('image') +export class ImageController { + constructor(private readonly imageService: ImageService) {} + + @Post() + @ApiOperation({ summary: '이미지 업로드 URL 생성' }) + @ApiOkResponse({ + description: '생성된 업로드 URL', + type: UploadImageUrlResponseDto, + }) + async createUploadURL(): Promise { + const uploadUrl = await this.imageService.createUploadURL(); + return new UploadImageUrlResponseDto(uploadUrl); + } +} diff --git a/src/image/image.module.ts b/src/image/image.module.ts new file mode 100644 index 00000000..d7c2dbaf --- /dev/null +++ b/src/image/image.module.ts @@ -0,0 +1,13 @@ +import { Module } from '@nestjs/common'; +import { AwsSdkModule } from 'nest-aws-sdk'; +import { S3 } from 'aws-sdk'; +import { ImageService } from '@root/image/image.service'; +import { ImageController } from '@root/image/image.controller'; + +@Module({ + imports: [AwsSdkModule.forFeatures([S3])], + controllers: [ImageController], + providers: [ImageService], + exports: [], +}) +export class ImageModule {} diff --git a/src/image/image.service.ts b/src/image/image.service.ts new file mode 100644 index 00000000..cfe7d957 --- /dev/null +++ b/src/image/image.service.ts @@ -0,0 +1,25 @@ +import { Injectable } from '@nestjs/common'; +import { InjectAwsService } from 'nest-aws-sdk'; +import { S3 } from 'aws-sdk'; +import { S3_URL_EXPIRATION_SECONDS } from '@root/config/constants'; +import { S3Param } from '@image/interfaces/s3-param.interface'; + +@Injectable() +export class ImageService { + constructor(@InjectAwsService(S3) private readonly s3: S3) {} + + createUploadURL(): Promise { + const randomId = Math.random() * 10000000; + const key = `${randomId}.png`; + + const s3Params: S3Param = { + Bucket: process.env.AWS_S3_UPLOAD_BUCKET, + Key: key, + Expires: S3_URL_EXPIRATION_SECONDS, + ContentType: 'image/png', + ACL: 'public-read', + }; + + return this.s3.getSignedUrlPromise('putObject', s3Params); + } +} diff --git a/src/image/interfaces/s3-param.interface.ts b/src/image/interfaces/s3-param.interface.ts new file mode 100644 index 00000000..aa1fecc0 --- /dev/null +++ b/src/image/interfaces/s3-param.interface.ts @@ -0,0 +1,7 @@ +export interface S3Param { + Bucket: string; + Key: string; + Expires: number; + ContentType: string; + ACL: string; +} diff --git a/tsconfig.json b/tsconfig.json index 2ad4d4f1..c65255c2 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -20,7 +20,8 @@ "@auth/*": ["./src/auth/*"], "@ft-auth/*": ["src/ft-auth/*"], "@database/*": ["./src/database/*"], - "@test/*": ["./test/*"] + "@test/*": ["./test/*"], + "@image/*": ["./src/image/*"] }, "incremental": true, "skipLibCheck": true, diff --git a/yarn.lock b/yarn.lock index c92fd504..10ed4f9a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1574,6 +1574,21 @@ at-least-node@^1.0.0: resolved "https://registry.yarnpkg.com/at-least-node/-/at-least-node-1.0.0.tgz#602cd4b46e844ad4effc92a8011a3c46e0238dc2" integrity sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg== +aws-sdk@^2.1074.0: + version "2.1074.0" + resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.1074.0.tgz#be3283f781b3060cd67d5abef50d1edacefede70" + integrity sha512-tD478mkukglutjs+mq5FQmYFzz+l/wddl5u3tTMWTNa+j1eSL+AqaHPFM1rC3O9h98QqpKKzeKbLrPhGDvYaRg== + dependencies: + buffer "4.9.2" + events "1.1.1" + ieee754 "1.1.13" + jmespath "0.16.0" + querystring "0.2.0" + sax "1.2.1" + url "0.10.3" + uuid "3.3.2" + xml2js "0.4.19" + axios@0.24.0: version "0.24.0" resolved "https://registry.yarnpkg.com/axios/-/axios-0.24.0.tgz#804e6fa1e4b9c5288501dd9dff56a7a0940d20d6" @@ -1661,7 +1676,7 @@ balanced-match@^1.0.0: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== -base64-js@^1.3.1: +base64-js@^1.0.2, base64-js@^1.3.1: version "1.5.1" resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== @@ -1778,6 +1793,15 @@ buffer-from@^1.0.0: resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== +buffer@4.9.2: + version "4.9.2" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-4.9.2.tgz#230ead344002988644841ab0244af8c44bbe3ef8" + integrity sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg== + dependencies: + base64-js "^1.0.2" + ieee754 "^1.1.4" + isarray "^1.0.0" + buffer@^5.5.0: version "5.7.1" resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" @@ -2885,6 +2909,11 @@ event-emitter@^0.3.5: d "1" es5-ext "~0.10.14" +events@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/events/-/events-1.1.1.tgz#9ebdb7635ad099c70dcc4c2a1f5004288e8bd924" + integrity sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ= + events@^3.2.0: version "3.3.0" resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" @@ -3551,7 +3580,12 @@ iconv-lite@0.6.3, iconv-lite@^0.6.3: dependencies: safer-buffer ">= 2.1.2 < 3.0.0" -ieee754@^1.1.13, ieee754@^1.2.1: +ieee754@1.1.13: + version "1.1.13" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.13.tgz#ec168558e95aa181fd87d37f55c32bbcb6708b84" + integrity sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg== + +ieee754@^1.1.13, ieee754@^1.1.4, ieee754@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== @@ -3803,7 +3837,7 @@ isarray@0.0.1: resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" integrity sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8= -isarray@~1.0.0: +isarray@^1.0.0, isarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= @@ -4274,6 +4308,11 @@ jest@^27.0.6: import-local "^3.0.2" jest-cli "^27.4.7" +jmespath@0.16.0: + version "0.16.0" + resolved "https://registry.yarnpkg.com/jmespath/-/jmespath-0.16.0.tgz#b15b0a85dfd4d930d43e69ed605943c802785076" + integrity sha512-9FzQjJ7MATs1tSpnco1K6ayiYE3figslrXA72G2HQ/n76RzvYlofyi5QM+iX4YRs/pu3yzxlVQSST23+dMDknw== + js-stringify@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/js-stringify/-/js-stringify-1.0.2.tgz#1736fddfd9724f28a3682adc6230ae7e4e9679db" @@ -4966,6 +5005,11 @@ neo-async@^2.6.0, neo-async@^2.6.2: resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== +nest-aws-sdk@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/nest-aws-sdk/-/nest-aws-sdk-2.1.0.tgz#73c145d3b45915adb40f3b61999776f09b8620dc" + integrity sha512-1C0xz1oJgzFQKAYcHH43nBle4Qu/0B2v2f8N0bIInNn/uQ+DU5Jgf9RhxkAwAuUUGZqR+/bLBpL5ls0J10DqtA== + nest-winston@^1.6.2: version "1.6.2" resolved "https://registry.yarnpkg.com/nest-winston/-/nest-winston-1.6.2.tgz#e4f24096b7e5cd9c8649b3d15203e576114d07e0" @@ -5590,6 +5634,11 @@ pump@^3.0.0: end-of-stream "^1.1.0" once "^1.3.1" +punycode@1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d" + integrity sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0= + punycode@^2.1.0, punycode@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" @@ -5612,6 +5661,11 @@ qs@^6.10.1: dependencies: side-channel "^1.0.4" +querystring@0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" + integrity sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA= + queue-microtask@^1.2.2: version "1.2.3" resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" @@ -5861,6 +5915,11 @@ safe-stable-stringify@^1.1.0: resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== +sax@1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.1.tgz#7b8e656190b228e81a66aea748480d828cd2d37a" + integrity sha1-e45lYZCyKOgaZq6nSEgNgozS03o= + sax@>=0.6.0: version "1.2.4" resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" @@ -6691,6 +6750,14 @@ uri-js@^4.2.2: dependencies: punycode "^2.1.0" +url@0.10.3: + version "0.10.3" + resolved "https://registry.yarnpkg.com/url/-/url-0.10.3.tgz#021e4d9c7705f21bbf37d03ceb58767402774c64" + integrity sha1-Ah5NnHcF8hu/N9A861h2dAJ3TGQ= + dependencies: + punycode "1.3.2" + querystring "0.2.0" + util-deprecate@^1.0.1, util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" @@ -6701,6 +6768,11 @@ utils-merge@1.0.1, utils-merge@1.x.x: resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM= +uuid@3.3.2: + version "3.3.2" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131" + integrity sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA== + uuid@8.3.2, uuid@^8.3.2: version "8.3.2" resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" @@ -6952,6 +7024,14 @@ xml-name-validator@^3.0.0: resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a" integrity sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw== +xml2js@0.4.19: + version "0.4.19" + resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.19.tgz#686c20f213209e94abf0d1bcf1efaa291c7827a7" + integrity sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q== + dependencies: + sax ">=0.6.0" + xmlbuilder "~9.0.1" + xml2js@^0.4.23: version "0.4.23" resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.23.tgz#a0c69516752421eb2ac758ee4d4ccf58843eac66" @@ -6965,6 +7045,11 @@ xmlbuilder@~11.0.0: resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-11.0.1.tgz#be9bae1c8a046e76b31127726347d0ad7002beb3" integrity sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA== +xmlbuilder@~9.0.1: + version "9.0.7" + resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-9.0.7.tgz#132ee63d2ec5565c557e20f4c22df9aca686b10d" + integrity sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0= + xmlchars@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb" From 705446e7b132a7d2c2cfdb18da18c5571dc259fd Mon Sep 17 00:00:00 2001 From: rockpell Date: Thu, 17 Feb 2022 01:12:30 +0900 Subject: [PATCH 38/55] =?UTF-8?q?Test:=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20?= =?UTF-8?q?=EC=97=85=EB=A1=9C=EB=93=9C=EB=A5=BC=20=EC=9C=84=ED=95=9C=20url?= =?UTF-8?q?=20=EC=83=9D=EC=84=B1=20api?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - url 생성이 성공하는 e2e 테스트 추가 --- test/image.e2e-spec.ts | 81 ++++++++++++++++++++++++++++++++++++++++ test/jest-e2e.json | 4 +- test/test.base.module.ts | 21 ++++++++++- 3 files changed, 104 insertions(+), 2 deletions(-) create mode 100644 test/image.e2e-spec.ts diff --git a/test/image.e2e-spec.ts b/test/image.e2e-spec.ts new file mode 100644 index 00000000..09c63a77 --- /dev/null +++ b/test/image.e2e-spec.ts @@ -0,0 +1,81 @@ +import { INestApplication, ValidationPipe } from '@nestjs/common'; +import * as request from 'supertest'; +import * as cookieParser from 'cookie-parser'; + +import { UserRepository } from '@user/repositories/user.repository'; +import { AuthService } from '@auth/auth.service'; +import { Test, TestingModule } from '@nestjs/testing'; +import { TestBaseModule } from './test.base.module'; +import { UserModule } from '@user/user.module'; +import { AuthModule } from '@auth/auth.module'; +import { TypeormExceptionFilter } from '@root/filters/typeorm-exception.filter'; +import { User, UserRole } from '@user/entities/user.entity'; +import { JWTPayload } from '@auth/interfaces/jwt-payload.interface'; +import { ImageModule } from '@image/image.module'; +import { getConnection } from 'typeorm'; +import { UploadImageUrlResponseDto } from '@image/dto/upload-image-url-response.dto'; + +describe('UserController (e2e)', () => { + let app: INestApplication; + let userRepository: UserRepository; + let authService: AuthService; + let JWT; + + beforeAll(async () => { + const moduleFixture: TestingModule = await Test.createTestingModule({ + imports: [TestBaseModule, UserModule, AuthModule, ImageModule], + }).compile(); + + app = moduleFixture.createNestApplication(); + app.use(cookieParser()); + + app.useGlobalFilters(new TypeormExceptionFilter()); + app.useGlobalPipes( + new ValidationPipe({ + whitelist: true, + forbidNonWhitelisted: true, + transform: true, + }), + ); + await app.init(); + + userRepository = moduleFixture.get(UserRepository); + authService = moduleFixture.get(AuthService); + + app = app.getHttpServer(); + }); + + beforeEach(async () => { + const newUser = new User(); + newUser.oauthToken = 'test1234'; + newUser.nickname = 'first user'; + newUser.role = UserRole.CADET; + await userRepository.save(newUser); + + JWT = authService.getJWT({ + userId: newUser.id, + userRole: newUser.role, + } as JWTPayload); + }); + + afterEach(async () => { + await Promise.all([userRepository.clear()]); + }); + + afterAll(async () => { + await getConnection().dropDatabase(); + await getConnection().close(); + await app.close(); + }); + + test('이미지 업로드 url 생성', async () => { + const response = await request(app) + .post('/image') + .set('Cookie', `access_token=${JWT}`); + + const result = response.body as UploadImageUrlResponseDto; + + expect(response.status).toEqual(201); + expect(result.uploadUrl).toBeTruthy(); + }); +}); diff --git a/test/jest-e2e.json b/test/jest-e2e.json index 895e130c..7adb048e 100644 --- a/test/jest-e2e.json +++ b/test/jest-e2e.json @@ -17,6 +17,8 @@ "^@authenticate/(.*)$": "/../src/authenticate/$1", "^@notification/(.*)$": "/../src/notification/$1", "^@database/(.*)$": "/../src/database/$1", - "^@ft-auth/(.*)$": "/../src/ft-auth/$1" + "^@ft-auth/(.*)$": "/../src/ft-auth/$1", + "^@test/(.*)$": "/../test/$1", + "^@image/(.*)$": "/../src/image/$1" } } diff --git a/test/test.base.module.ts b/test/test.base.module.ts index 03a8bca3..20e189a3 100644 --- a/test/test.base.module.ts +++ b/test/test.base.module.ts @@ -2,10 +2,16 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { SnakeNamingStrategy } from 'typeorm-naming-strategies'; import * as path from 'path'; -import { ConfigModule } from '@nestjs/config'; +import { ConfigModule, ConfigService } from '@nestjs/config'; import { APP_GUARD } from '@nestjs/core'; import { JwtAuthGuard } from '@auth/jwt-auth.guard'; import configEmail from '@root/config/mail.config'; +import { AwsSdkModule } from 'nest-aws-sdk'; +import { + AWS_ACCESS_KEY, + AWS_REGION, + AWS_SECRET_KEY, +} from '@root/config/constants'; @Module({ imports: [ @@ -27,6 +33,19 @@ import configEmail from '@root/config/mail.config'; cache: true, load: [configEmail], }), + AwsSdkModule.forRootAsync({ + defaultServiceOptions: { + imports: [ConfigModule], + inject: [ConfigService], + useFactory: (configService: ConfigService) => { + return { + region: configService.get(AWS_REGION), + accessKeyId: configService.get(AWS_ACCESS_KEY), + secretAccessKey: configService.get(AWS_SECRET_KEY), + }; + }, + }, + }), ], providers: [ { From 5f19b47735299dc619c358c8ec5b97972744f030 Mon Sep 17 00:00:00 2001 From: SeongMin Park Date: Fri, 18 Feb 2022 19:40:32 +0900 Subject: [PATCH 39/55] =?UTF-8?q?Feat:=20=EC=95=84=EC=9D=B4=EB=94=94?= =?UTF-8?q?=EA=B0=80=20=EC=9E=98=EB=AA=BB=EB=90=9C=20=EA=B2=BD=EC=9A=B0=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/e2e/reaction.e2e-spec.ts | 48 +++++++++++++++++++---------------- test/e2e/utils/dummy.ts | 10 ++++---- 2 files changed, 31 insertions(+), 27 deletions(-) diff --git a/test/e2e/reaction.e2e-spec.ts b/test/e2e/reaction.e2e-spec.ts index 3406fb16..f8d0a395 100644 --- a/test/e2e/reaction.e2e-spec.ts +++ b/test/e2e/reaction.e2e-spec.ts @@ -70,18 +70,20 @@ describe('UserController (e2e)', () => { app = app.getHttpServer(); }); - beforeEach(async () => { - const newUser1 = dummy.user('testAuth', 'nickname', UserRole.CADET); - const newUser2 = dummy.user('testAuth', 'nickname', UserRole.CADET); - await userRepository.save(newUser1); - await userRepository.save(newUser2); - - JWT = dummy.jwt(newUser1.id, newUser1.role, authService); + afterAll(async () => { + await getConnection().dropDatabase(); + await getConnection().close(); + await app.close(); + }); - const newCategory = dummy.category('새로운 카테고리'); - await categoryRepository.save(newCategory); - const newArticle = dummy.article(newCategory.id, newUser1.id, '', ''); - await articleRepository.save(newArticle); + beforeEach(async () => { + const user = dummy.user('token', 'nickname', UserRole.CADET); + await userRepository.save(user); + JWT = dummy.jwt(user.id, user.role, authService); + const category = dummy.category('category'); + await categoryRepository.save(category); + const article = dummy.article(category.id, user.id, 'title', 'content'); + await articleRepository.save(article); }); afterEach(async () => { @@ -94,13 +96,7 @@ describe('UserController (e2e)', () => { ]); }); - afterAll(async () => { - await getConnection().dropDatabase(); - await getConnection().close(); - await app.close(); - }); - - test('[성공] 게시글 리엑션 성공 - 좋아요가 없는 경우', async () => { + test('[성공] POST /reactions/articles - 좋아요가 없는 경우', async () => { const response = await request(app) .post('/reactions/articles/1') .set('Cookie', `access_token=${JWT}`); @@ -110,7 +106,7 @@ describe('UserController (e2e)', () => { expect(response.body.likeCount).toEqual(1); }); - test('[성공] 게시글 리엑션 성공 - 좋아요가 있는 경우', async () => { + test('[성공] POST /reactions/articles - 좋아요가 있는 경우', async () => { await request(app) .post('/reactions/articles/1') .set('Cookie', `access_token=${JWT}`); @@ -124,9 +120,17 @@ describe('UserController (e2e)', () => { expect(response2.body.likeCount).toEqual(0); }); - test('[실패] 게시글 리엑션 실패 - unauthorize', async () => { - const response2 = await request(app).post('/reactions/articles/1'); + test('[실패] POST /reactions/articles - unauthorize', async () => { + const response = await request(app).post('/reactions/articles/1'); + + expect(response.status).toEqual(401); + }); + + test('[실패] POST /reactions/articles - 없는 id를 보내는 경우', async () => { + const response = await request(app) + .post('/reactions/articles/0') + .set('Cookie', `access_token=${JWT}`); - expect(response2.status).toEqual(401); + expect(response.status).toEqual(404); }); }); diff --git a/test/e2e/utils/dummy.ts b/test/e2e/utils/dummy.ts index f2705849..0b7e84f2 100644 --- a/test/e2e/utils/dummy.ts +++ b/test/e2e/utils/dummy.ts @@ -10,11 +10,11 @@ export const user = ( nickname: string, role: UserRole, ): User => { - const newUser2 = new User(); - newUser2.oauthToken = oauthToken; - newUser2.nickname = nickname; - newUser2.role = role; - return newUser2; + const user = new User(); + user.oauthToken = oauthToken; + user.nickname = nickname; + user.role = role; + return user; }; export const jwt = ( From b9a644e630e61079dd1fafd9fae4941c5e878e4b Mon Sep 17 00:00:00 2001 From: SeongMin Park Date: Fri, 18 Feb 2022 20:04:07 +0900 Subject: [PATCH 40/55] =?UTF-8?q?Chore:=20api=20=EB=B3=84=EB=A1=9C=20descr?= =?UTF-8?q?ibe=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/e2e/reaction.e2e-spec.ts | 116 +++++++++++++++++----------------- 1 file changed, 59 insertions(+), 57 deletions(-) diff --git a/test/e2e/reaction.e2e-spec.ts b/test/e2e/reaction.e2e-spec.ts index f8d0a395..2f3c6aa0 100644 --- a/test/e2e/reaction.e2e-spec.ts +++ b/test/e2e/reaction.e2e-spec.ts @@ -20,7 +20,7 @@ import { getConnection } from 'typeorm'; import { TestBaseModule } from './test.base.module'; import * as dummy from './utils/dummy'; -describe('UserController (e2e)', () => { +describe('Reaction', () => { let app: INestApplication; let userRepository: UserRepository; let articleRepository: ArticleRepository; @@ -76,61 +76,63 @@ describe('UserController (e2e)', () => { await app.close(); }); - beforeEach(async () => { - const user = dummy.user('token', 'nickname', UserRole.CADET); - await userRepository.save(user); - JWT = dummy.jwt(user.id, user.role, authService); - const category = dummy.category('category'); - await categoryRepository.save(category); - const article = dummy.article(category.id, user.id, 'title', 'content'); - await articleRepository.save(article); - }); - - afterEach(async () => { - await Promise.all([ - userRepository.clear(), - articleRepository.clear(), - commentRepository.clear(), - categoryRepository.clear(), - reactionArticleRepository.clear(), - ]); - }); - - test('[성공] POST /reactions/articles - 좋아요가 없는 경우', async () => { - const response = await request(app) - .post('/reactions/articles/1') - .set('Cookie', `access_token=${JWT}`); - - expect(response.status).toEqual(201); - expect(response.body.isLike).toEqual(true); - expect(response.body.likeCount).toEqual(1); - }); - - test('[성공] POST /reactions/articles - 좋아요가 있는 경우', async () => { - await request(app) - .post('/reactions/articles/1') - .set('Cookie', `access_token=${JWT}`); - - const response2 = await request(app) - .post('/reactions/articles/1') - .set('Cookie', `access_token=${JWT}`); - - expect(response2.status).toEqual(201); - expect(response2.body.isLike).toEqual(false); - expect(response2.body.likeCount).toEqual(0); - }); - - test('[실패] POST /reactions/articles - unauthorize', async () => { - const response = await request(app).post('/reactions/articles/1'); - - expect(response.status).toEqual(401); - }); - - test('[실패] POST /reactions/articles - 없는 id를 보내는 경우', async () => { - const response = await request(app) - .post('/reactions/articles/0') - .set('Cookie', `access_token=${JWT}`); - - expect(response.status).toEqual(404); + describe('/reactions/articles/{id}', async () => { + beforeAll(async () => { + const user = dummy.user('token', 'nickname', UserRole.CADET); + await userRepository.save(user); + JWT = dummy.jwt(user.id, user.role, authService); + const category = dummy.category('category'); + await categoryRepository.save(category); + const article = dummy.article(category.id, user.id, 'title', 'content'); + await articleRepository.save(article); + }); + + afterAll(async () => { + await Promise.all([ + userRepository.clear(), + articleRepository.clear(), + commentRepository.clear(), + categoryRepository.clear(), + reactionArticleRepository.clear(), + ]); + }); + + test('[성공] POST - 좋아요가 없는 경우', async () => { + const response = await request(app) + .post('/reactions/articles/1') + .set('Cookie', `access_token=${JWT}`); + + expect(response.status).toEqual(201); + expect(response.body.isLike).toEqual(true); + expect(response.body.likeCount).toEqual(1); + }); + + test('[성공] POST - 좋아요가 있는 경우', async () => { + await request(app) + .post('/reactions/articles/1') + .set('Cookie', `access_token=${JWT}`); + + const response2 = await request(app) + .post('/reactions/articles/1') + .set('Cookie', `access_token=${JWT}`); + + expect(response2.status).toEqual(201); + expect(response2.body.isLike).toEqual(false); + expect(response2.body.likeCount).toEqual(0); + }); + + test('[실패] POST - unauthorize', async () => { + const response = await request(app).post('/reactions/articles/1'); + + expect(response.status).toEqual(401); + }); + + test('[실패] POST - 없는 id를 보내는 경우', async () => { + const response = await request(app) + .post('/reactions/articles/0') + .set('Cookie', `access_token=${JWT}`); + + expect(response.status).toEqual(404); + }); }); }); From 8b5ea4ab1258d1361f2f191516a8ec8c42473e81 Mon Sep 17 00:00:00 2001 From: SeongMin Park Date: Fri, 18 Feb 2022 20:14:39 +0900 Subject: [PATCH 41/55] =?UTF-8?q?Chore:=20--runInBand=20=EC=98=B5=EC=85=98?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 1059113c..6f862c49 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "test:watch": "jest --watch", "test:cov": "jest --coverage", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", - "test:e2e": "NODE_ENV=test jest --config ./test/e2e/jest-e2e.json", + "test:e2e": "NODE_ENV=test jest --runInBand --config ./test/e2e/jest-e2e.json", "typeorm": "ts-node -r tsconfig-paths/register ./node_modules/typeorm/cli.js --config src/database/ormconfig.ts", "typeorm:migrate": "yarn typeorm migration:generate -n", "typeorm:run": "yarn typeorm migration:run", From 524b78006493a2549f367208e5a82768e8ca41b9 Mon Sep 17 00:00:00 2001 From: Luna Date: Fri, 18 Feb 2022 20:40:20 +0900 Subject: [PATCH 42/55] Fix: using .env file --- src/auth/auth.controller.ts | 5 ++--- src/auth/constants/access-token.ts | 1 - src/auth/jwt.strategy.ts | 3 +-- src/main.ts | 3 +-- test/user.e2e-spec.ts | 15 +++++++-------- 5 files changed, 11 insertions(+), 16 deletions(-) delete mode 100644 src/auth/constants/access-token.ts diff --git a/src/auth/auth.controller.ts b/src/auth/auth.controller.ts index 0f7c0651..3498346f 100644 --- a/src/auth/auth.controller.ts +++ b/src/auth/auth.controller.ts @@ -21,7 +21,6 @@ import { AuthService } from './auth.service'; import { GithubAuthGuard } from './github-auth.guard'; import { JWTPayload } from './interfaces/jwt-payload.interface'; import { GithubProfile } from './interfaces/github-profile.interface'; -import { ACCESS_TOKEN } from './constants/access-token'; import { GetGithubProfile, Public } from './auth.decorator'; import { getCookieOption } from '@root/utils'; @@ -69,7 +68,7 @@ export class AuthController { userId: user.id, userRole: user.role, } as JWTPayload); - response.cookie(ACCESS_TOKEN, jwt, getCookieOption()); + response.cookie(process.env.ACCESS_TOKEN_KEY, jwt, getCookieOption()); } @Delete('signout') @@ -78,6 +77,6 @@ export class AuthController { @ApiOkResponse({ description: '로그아웃 성공' }) @ApiUnauthorizedResponse({ description: '인증 실패' }) signout(@Res({ passthrough: true }) response: Response): void { - response.clearCookie(ACCESS_TOKEN); + response.clearCookie(process.env.ACCESS_TOKEN_KEY); } } diff --git a/src/auth/constants/access-token.ts b/src/auth/constants/access-token.ts deleted file mode 100644 index 5d7635f9..00000000 --- a/src/auth/constants/access-token.ts +++ /dev/null @@ -1 +0,0 @@ -export const ACCESS_TOKEN = '6c3c0c426475c5322e919ae9ba551dcb6e6e94d4'; diff --git a/src/auth/jwt.strategy.ts b/src/auth/jwt.strategy.ts index 1bec5815..86213e6e 100644 --- a/src/auth/jwt.strategy.ts +++ b/src/auth/jwt.strategy.ts @@ -7,7 +7,6 @@ import { } from '@nestjs/common'; import { PassportStrategy } from '@nestjs/passport'; import { ExtractJwt, Strategy } from 'passport-jwt'; -import { ACCESS_TOKEN } from './constants/access-token'; import { JWTPayload } from './interfaces/jwt-payload.interface'; import { User } from '@root/user/entities/user.entity'; @@ -16,7 +15,7 @@ const getAccessToken = (request: any): string => { return request.headers.authorization; } - return request.cookies[ACCESS_TOKEN]; + return request.cookies[process.env.ACCESS_TOKEN_KEY]; }; @Injectable() diff --git a/src/main.ts b/src/main.ts index f486a1e0..1b825141 100644 --- a/src/main.ts +++ b/src/main.ts @@ -9,7 +9,6 @@ import { join } from 'path'; // import * as csurf from 'csurf'; import { AppModule } from './app.module'; -import { ACCESS_TOKEN } from '@auth/constants/access-token'; import { ValidationPipe } from '@nestjs/common'; import { TypeormExceptionFilter } from '@root/filters/typeorm-exception.filter'; import { InternalServerErrorExceptionFilter } from '@root/filters/internal-server-error-exception.filter'; @@ -32,7 +31,7 @@ async function bootstrap() { .setTitle('42World API') .setDescription('42World API') .setVersion('0.1') - .addCookieAuth(ACCESS_TOKEN) + .addCookieAuth(process.env.ACCESS_TOKEN_KEY) .build(); const document = SwaggerModule.createDocument(app, config); SwaggerModule.setup('docs', app, document); diff --git a/test/user.e2e-spec.ts b/test/user.e2e-spec.ts index ae833a49..211ca660 100644 --- a/test/user.e2e-spec.ts +++ b/test/user.e2e-spec.ts @@ -25,7 +25,6 @@ import { CommentRepository } from '@comment/repositories/comment.repository'; import { ReactionModule } from '@root/reaction/reaction.module'; import { ReactionArticle } from '@root/reaction/entities/reaction-article.entity'; import { ReactionArticleRepository } from '@root/reaction/repositories/reaction-article.repository'; -import { ACCESS_TOKEN } from '@root/auth/constants/access-token'; describe('UserController (e2e)', () => { let app: INestApplication; @@ -149,7 +148,7 @@ describe('UserController (e2e)', () => { it('내 정보 가져오기', async () => { const response = await request(app) .get('/users/me') - .set('Cookie', `${ACCESS_TOKEN}=${JWT}`); + .set('Cookie', `${process.env.ACCESS_TOKEN_KEY}=${JWT}`); expect(response.status).toEqual(200); }); @@ -157,7 +156,7 @@ describe('UserController (e2e)', () => { it('특정 유저 정보 가져오기', async () => { const response = await request(app) .get('/users/2') - .set('Cookie', `${ACCESS_TOKEN}=${JWT}`); + .set('Cookie', `${process.env.ACCESS_TOKEN_KEY}=${JWT}`); expect(response.status).toEqual(200); expect(response.body.id).toEqual(2); @@ -172,7 +171,7 @@ describe('UserController (e2e)', () => { const response = await request(app) .put('/users') .send(updateData) - .set('Cookie', `${ACCESS_TOKEN}=${JWT}`); + .set('Cookie', `${process.env.ACCESS_TOKEN_KEY}=${JWT}`); expect(response.status).toEqual(200); @@ -184,7 +183,7 @@ describe('UserController (e2e)', () => { it('유저 삭제하기', async () => { const response = await request(app) .delete('/users') - .set('Cookie', `${ACCESS_TOKEN}=${JWT}`); + .set('Cookie', `${process.env.ACCESS_TOKEN_KEY}=${JWT}`); expect(response.status).toEqual(200); @@ -196,7 +195,7 @@ describe('UserController (e2e)', () => { it('내가 작성한 글 가져오기', async () => { const response = await request(app) .get('/users/me/articles') - .set('Cookie', `${ACCESS_TOKEN}=${JWT}`); + .set('Cookie', `${process.env.ACCESS_TOKEN_KEY}=${JWT}`); expect(response.status).toEqual(200); @@ -208,7 +207,7 @@ describe('UserController (e2e)', () => { it('내가 작성한 댓글 가져오기', async () => { const response = await request(app) .get('/users/me/comments') - .set('Cookie', `${ACCESS_TOKEN}=${JWT}`); + .set('Cookie', `${process.env.ACCESS_TOKEN_KEY}=${JWT}`); expect(response.status).toEqual(200); @@ -221,7 +220,7 @@ describe('UserController (e2e)', () => { it('내가 좋아요 누른 게시글 목록 확인', async () => { const response = await request(app) .get('/users/me/like-articles') - .set('Cookie', `${ACCESS_TOKEN}=${JWT}`); + .set('Cookie', `${process.env.ACCESS_TOKEN_KEY}=${JWT}`); expect(response.status).toEqual(200); From 5b4ef982bb1a5bb44d7c5144b88bcbb1f2c69e3e Mon Sep 17 00:00:00 2001 From: SeongMin Park Date: Fri, 18 Feb 2022 21:16:18 +0900 Subject: [PATCH 43/55] =?UTF-8?q?Feat:=20/reactions/articles/{articleId}/c?= =?UTF-8?q?omments/{commentId}=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/e2e/reaction.e2e-spec.ts | 100 ++++++++++++++++++++++++++++++---- 1 file changed, 88 insertions(+), 12 deletions(-) diff --git a/test/e2e/reaction.e2e-spec.ts b/test/e2e/reaction.e2e-spec.ts index 2f3c6aa0..96bb88f6 100644 --- a/test/e2e/reaction.e2e-spec.ts +++ b/test/e2e/reaction.e2e-spec.ts @@ -76,25 +76,23 @@ describe('Reaction', () => { await app.close(); }); - describe('/reactions/articles/{id}', async () => { - beforeAll(async () => { + describe('/reactions/articles/{id}', () => { + beforeEach(async () => { const user = dummy.user('token', 'nickname', UserRole.CADET); await userRepository.save(user); - JWT = dummy.jwt(user.id, user.role, authService); const category = dummy.category('category'); await categoryRepository.save(category); const article = dummy.article(category.id, user.id, 'title', 'content'); await articleRepository.save(article); + JWT = dummy.jwt(user.id, user.role, authService); }); - afterAll(async () => { - await Promise.all([ - userRepository.clear(), - articleRepository.clear(), - commentRepository.clear(), - categoryRepository.clear(), - reactionArticleRepository.clear(), - ]); + afterEach(async () => { + await userRepository.clear(); + await categoryRepository.clear(); + await articleRepository.clear(); + await commentRepository.clear(); + await reactionArticleRepository.clear(); }); test('[성공] POST - 좋아요가 없는 경우', async () => { @@ -128,8 +126,86 @@ describe('Reaction', () => { }); test('[실패] POST - 없는 id를 보내는 경우', async () => { + const notExistId = 0; + + const response = await request(app) + .post('/reactions/articles/' + notExistId) + .set('Cookie', `access_token=${JWT}`); + + expect(response.status).toEqual(404); + }); + }); + + describe('/reactions/articles/{articleId}/comments/{commentId}', () => { + beforeEach(async () => { + const user = dummy.user('token', 'nickname', UserRole.CADET); + await userRepository.save(user); + JWT = dummy.jwt(user.id, user.role, authService); + const category = dummy.category('category'); + await categoryRepository.save(category); + const article = dummy.article(category.id, user.id, 'title', 'content'); + await articleRepository.save(article); + const comment = dummy.comment(user.id, article.id, 'content'); + await commentRepository.save(comment); + }); + + afterEach(async () => { + await userRepository.clear(); + await categoryRepository.clear(); + await articleRepository.clear(); + await commentRepository.clear(); + await reactionArticleRepository.clear(); + }); + + test('[성공] POST - 좋아요가 없는 경우', async () => { + const response = await request(app) + .post('/reactions/articles/1/comments/1') + .set('Cookie', `access_token=${JWT}`); + + expect(response.status).toEqual(201); + expect(response.body.isLike).toEqual(true); + expect(response.body.likeCount).toEqual(1); + }); + + test('[성공] POST - 좋아요가 있는 경우', async () => { + await request(app) + .post('/reactions/articles/1/comments/1') + .set('Cookie', `access_token=${JWT}`); + + const response2 = await request(app) + .post('/reactions/articles/1/comments/1') + .set('Cookie', `access_token=${JWT}`); + + reactionArticleRepository.findOne(1); + + expect(response2.status).toEqual(201); + expect(response2.body.isLike).toEqual(false); + expect(response2.body.likeCount).toEqual(0); + }); + + test('[실패] POST - unauthorize', async () => { + const response = await request(app).post( + '/reactions/articles/1/comments/1', + ); + + expect(response.status).toEqual(401); + }); + + test('[실패] POST - 없는 articleId를 보내는 경우', async () => { + const notExistId = 999; + + const response = await request(app) + .post('/reactions/articles/' + notExistId + '/comments/1') + .set('Cookie', `access_token=${JWT}`); + + expect(response.status).toEqual(201); // TODO - 코드가 이상하다 201을 돌려준다 + }); + + test('[실패] POST - 없는 commentId를 보내는 경우', async () => { + const notExistId = 0; + const response = await request(app) - .post('/reactions/articles/0') + .post('/reactions/articles/1/comments/' + notExistId) .set('Cookie', `access_token=${JWT}`); expect(response.status).toEqual(404); From 8fb774a2b5b5b381f02f4efcbcd93ca11e20cdb3 Mon Sep 17 00:00:00 2001 From: rockpell Date: Sat, 19 Feb 2022 22:49:20 +0900 Subject: [PATCH 44/55] =?UTF-8?q?Test:=20image=20test=20=ED=98=95=EC=8B=9D?= =?UTF-8?q?=20=EB=B3=80=EA=B2=BD=20=EB=B0=8F=20=EB=B3=B4=EA=B0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 실패 테스트도 추가 - 새롭게 추가된 규칙에 맞게 테스트 코드 수정 --- test/image.e2e-spec.ts | 58 ++++++++++++++++++++++++------------------ 1 file changed, 33 insertions(+), 25 deletions(-) diff --git a/test/image.e2e-spec.ts b/test/image.e2e-spec.ts index 09c63a77..c3560e9a 100644 --- a/test/image.e2e-spec.ts +++ b/test/image.e2e-spec.ts @@ -15,7 +15,7 @@ import { ImageModule } from '@image/image.module'; import { getConnection } from 'typeorm'; import { UploadImageUrlResponseDto } from '@image/dto/upload-image-url-response.dto'; -describe('UserController (e2e)', () => { +describe('Image', () => { let app: INestApplication; let userRepository: UserRepository; let authService: AuthService; @@ -45,37 +45,45 @@ describe('UserController (e2e)', () => { app = app.getHttpServer(); }); - beforeEach(async () => { - const newUser = new User(); - newUser.oauthToken = 'test1234'; - newUser.nickname = 'first user'; - newUser.role = UserRole.CADET; - await userRepository.save(newUser); - - JWT = authService.getJWT({ - userId: newUser.id, - userRole: newUser.role, - } as JWTPayload); - }); - - afterEach(async () => { - await Promise.all([userRepository.clear()]); - }); - afterAll(async () => { await getConnection().dropDatabase(); await getConnection().close(); await app.close(); }); - test('이미지 업로드 url 생성', async () => { - const response = await request(app) - .post('/image') - .set('Cookie', `access_token=${JWT}`); + describe('/image', () => { + beforeEach(async () => { + const newUser = new User(); + newUser.oauthToken = 'test1234'; + newUser.nickname = 'first user'; + newUser.role = UserRole.CADET; + await userRepository.save(newUser); + + JWT = authService.getJWT({ + userId: newUser.id, + userRole: newUser.role, + } as JWTPayload); + }); + + afterEach(async () => { + await Promise.all([userRepository.clear()]); + }); + + test('[성공] POST', async () => { + const response = await request(app) + .post('/image') + .set('Cookie', `access_token=${JWT}`); + + const result = response.body as UploadImageUrlResponseDto; + + expect(response.status).toEqual(201); + expect(result.uploadUrl).toBeTruthy(); + }); - const result = response.body as UploadImageUrlResponseDto; + test('[실패] POST - unauthorized', async () => { + const response = await request(app).post('/image'); - expect(response.status).toEqual(201); - expect(result.uploadUrl).toBeTruthy(); + expect(response.status).toEqual(401); + }); }); }); From ec0b166eeff8422e8ceeea3960ce1a2871decc5f Mon Sep 17 00:00:00 2001 From: rockpell Date: Sat, 19 Feb 2022 23:14:12 +0900 Subject: [PATCH 45/55] =?UTF-8?q?Test:=20=EC=83=88=EB=A1=9C=EC=9A=B4=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EA=B5=AC=EC=A1=B0=EC=97=90=20?= =?UTF-8?q?=EB=A7=9E=EA=B2=8C=20=EB=94=94=EB=A0=89=ED=86=A0=EB=A6=AC=20?= =?UTF-8?q?=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 새로운 테스트 구조 pr이 merge되면 e2e라는 디렉토리가 생기기때문에 이동하였음 --- test/{ => e2e}/image.e2e-spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename test/{ => e2e}/image.e2e-spec.ts (98%) diff --git a/test/image.e2e-spec.ts b/test/e2e/image.e2e-spec.ts similarity index 98% rename from test/image.e2e-spec.ts rename to test/e2e/image.e2e-spec.ts index c3560e9a..226d2e0f 100644 --- a/test/image.e2e-spec.ts +++ b/test/e2e/image.e2e-spec.ts @@ -5,7 +5,7 @@ import * as cookieParser from 'cookie-parser'; import { UserRepository } from '@user/repositories/user.repository'; import { AuthService } from '@auth/auth.service'; import { Test, TestingModule } from '@nestjs/testing'; -import { TestBaseModule } from './test.base.module'; +import { TestBaseModule } from '../test.base.module'; import { UserModule } from '@user/user.module'; import { AuthModule } from '@auth/auth.module'; import { TypeormExceptionFilter } from '@root/filters/typeorm-exception.filter'; From 0f5b57afa9a659a222940b704a155ff651856ec2 Mon Sep 17 00:00:00 2001 From: SeongMin Park Date: Sun, 20 Feb 2022 09:57:56 +0900 Subject: [PATCH 46/55] =?UTF-8?q?Feat:=20clearDB=20=ED=95=A8=EC=88=98=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/e2e/utils/utils.ts | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 test/e2e/utils/utils.ts diff --git a/test/e2e/utils/utils.ts b/test/e2e/utils/utils.ts new file mode 100644 index 00000000..3d4df8a5 --- /dev/null +++ b/test/e2e/utils/utils.ts @@ -0,0 +1,9 @@ +import { getConnection } from 'typeorm'; + +export const clearDB = async () => { + const entities = getConnection().entityMetadatas; + for (const entity of entities) { + const repository = getConnection().getRepository(entity.name); + await repository.query(`TRUNCATE TABLE ${entity.tableName}`); + } +}; From 6cb9d7484ceceb707b812e5fab48e25ee6b8ca0f Mon Sep 17 00:00:00 2001 From: SeongMin Park Date: Sun, 20 Feb 2022 23:08:52 +0900 Subject: [PATCH 47/55] =?UTF-8?q?Test:=20clearDB=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/e2e/reaction.e2e-spec.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/e2e/reaction.e2e-spec.ts b/test/e2e/reaction.e2e-spec.ts index 96bb88f6..3652e546 100644 --- a/test/e2e/reaction.e2e-spec.ts +++ b/test/e2e/reaction.e2e-spec.ts @@ -19,6 +19,7 @@ import * as request from 'supertest'; import { getConnection } from 'typeorm'; import { TestBaseModule } from './test.base.module'; import * as dummy from './utils/dummy'; +import { clearDB } from './utils/utils'; describe('Reaction', () => { let app: INestApplication; @@ -76,6 +77,10 @@ describe('Reaction', () => { await app.close(); }); + beforeEach(async () => { + await clearDB(); + }); + describe('/reactions/articles/{id}', () => { beforeEach(async () => { const user = dummy.user('token', 'nickname', UserRole.CADET); From 018e2c063b5f8bad116dce84c4a76e96b688ea68 Mon Sep 17 00:00:00 2001 From: SeongMin Park Date: Sun, 20 Feb 2022 23:14:28 +0900 Subject: [PATCH 48/55] =?UTF-8?q?Delete:=20=EC=82=AC=EC=9A=A9=ED=95=98?= =?UTF-8?q?=EC=A7=80=20=EC=95=8A=EB=8A=94=20clear=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/e2e/reaction.e2e-spec.ts | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/test/e2e/reaction.e2e-spec.ts b/test/e2e/reaction.e2e-spec.ts index 3652e546..0db0b59c 100644 --- a/test/e2e/reaction.e2e-spec.ts +++ b/test/e2e/reaction.e2e-spec.ts @@ -92,14 +92,6 @@ describe('Reaction', () => { JWT = dummy.jwt(user.id, user.role, authService); }); - afterEach(async () => { - await userRepository.clear(); - await categoryRepository.clear(); - await articleRepository.clear(); - await commentRepository.clear(); - await reactionArticleRepository.clear(); - }); - test('[성공] POST - 좋아요가 없는 경우', async () => { const response = await request(app) .post('/reactions/articles/1') @@ -154,14 +146,6 @@ describe('Reaction', () => { await commentRepository.save(comment); }); - afterEach(async () => { - await userRepository.clear(); - await categoryRepository.clear(); - await articleRepository.clear(); - await commentRepository.clear(); - await reactionArticleRepository.clear(); - }); - test('[성공] POST - 좋아요가 없는 경우', async () => { const response = await request(app) .post('/reactions/articles/1/comments/1') From 5df6bf456b559a0ca494add293ad4b8ced813eed Mon Sep 17 00:00:00 2001 From: SeongMin Park Date: Sun, 20 Feb 2022 23:22:31 +0900 Subject: [PATCH 49/55] =?UTF-8?q?Chore:=20=EC=82=AC=EC=9A=A9=ED=95=98?= =?UTF-8?q?=EC=A7=80=20=EC=95=8A=EB=8A=94=20=EC=A4=84=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/e2e/reaction.e2e-spec.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/e2e/reaction.e2e-spec.ts b/test/e2e/reaction.e2e-spec.ts index 0db0b59c..de5fc280 100644 --- a/test/e2e/reaction.e2e-spec.ts +++ b/test/e2e/reaction.e2e-spec.ts @@ -165,8 +165,6 @@ describe('Reaction', () => { .post('/reactions/articles/1/comments/1') .set('Cookie', `access_token=${JWT}`); - reactionArticleRepository.findOne(1); - expect(response2.status).toEqual(201); expect(response2.body.isLike).toEqual(false); expect(response2.body.likeCount).toEqual(0); From 164c8be3fae44729cb147eb6bb08dc45440db60f Mon Sep 17 00:00:00 2001 From: rockpell Date: Mon, 21 Feb 2022 02:10:29 +0900 Subject: [PATCH 50/55] =?UTF-8?q?Test:=20image=20api,=20dummy=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/e2e/image.e2e-spec.ts | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/test/e2e/image.e2e-spec.ts b/test/e2e/image.e2e-spec.ts index 226d2e0f..88021a4a 100644 --- a/test/e2e/image.e2e-spec.ts +++ b/test/e2e/image.e2e-spec.ts @@ -5,15 +5,16 @@ import * as cookieParser from 'cookie-parser'; import { UserRepository } from '@user/repositories/user.repository'; import { AuthService } from '@auth/auth.service'; import { Test, TestingModule } from '@nestjs/testing'; -import { TestBaseModule } from '../test.base.module'; import { UserModule } from '@user/user.module'; import { AuthModule } from '@auth/auth.module'; import { TypeormExceptionFilter } from '@root/filters/typeorm-exception.filter'; -import { User, UserRole } from '@user/entities/user.entity'; -import { JWTPayload } from '@auth/interfaces/jwt-payload.interface'; +import { UserRole } from '@user/entities/user.entity'; import { ImageModule } from '@image/image.module'; import { getConnection } from 'typeorm'; import { UploadImageUrlResponseDto } from '@image/dto/upload-image-url-response.dto'; +import { TestBaseModule } from '@test/e2e/test.base.module'; +import { clearDB } from '@test/e2e/utils/utils'; +import * as dummy from './utils/dummy'; describe('Image', () => { let app: INestApplication; @@ -53,20 +54,14 @@ describe('Image', () => { describe('/image', () => { beforeEach(async () => { - const newUser = new User(); - newUser.oauthToken = 'test1234'; - newUser.nickname = 'first user'; - newUser.role = UserRole.CADET; + const newUser = dummy.user('test1234', 'first user', UserRole.CADET); await userRepository.save(newUser); - JWT = authService.getJWT({ - userId: newUser.id, - userRole: newUser.role, - } as JWTPayload); + JWT = dummy.jwt(newUser.id, newUser.role, authService); }); afterEach(async () => { - await Promise.all([userRepository.clear()]); + await clearDB(); }); test('[성공] POST', async () => { From 92ac160d5508e6c8d3830c98a345aa5356a58155 Mon Sep 17 00:00:00 2001 From: rockpell Date: Mon, 21 Feb 2022 22:35:04 +0900 Subject: [PATCH 51/55] =?UTF-8?q?Test:=20=EC=BF=A0=ED=82=A4=EC=9D=98=20acc?= =?UTF-8?q?ess=5Ftoken=20key=20=EA=B0=92,=20env=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 테스트 코드에도 process.env.ACCESS_TOKEN_KEY를 사용하도록 수정 --- test/e2e/image.e2e-spec.ts | 2 +- test/e2e/reaction.e2e-spec.ts | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/test/e2e/image.e2e-spec.ts b/test/e2e/image.e2e-spec.ts index 88021a4a..b0902ac4 100644 --- a/test/e2e/image.e2e-spec.ts +++ b/test/e2e/image.e2e-spec.ts @@ -67,7 +67,7 @@ describe('Image', () => { test('[성공] POST', async () => { const response = await request(app) .post('/image') - .set('Cookie', `access_token=${JWT}`); + .set('Cookie', `${process.env.ACCESS_TOKEN_KEY}=${JWT}`); const result = response.body as UploadImageUrlResponseDto; diff --git a/test/e2e/reaction.e2e-spec.ts b/test/e2e/reaction.e2e-spec.ts index de5fc280..bbbd3dbb 100644 --- a/test/e2e/reaction.e2e-spec.ts +++ b/test/e2e/reaction.e2e-spec.ts @@ -95,7 +95,7 @@ describe('Reaction', () => { test('[성공] POST - 좋아요가 없는 경우', async () => { const response = await request(app) .post('/reactions/articles/1') - .set('Cookie', `access_token=${JWT}`); + .set('Cookie', `${process.env.ACCESS_TOKEN_KEY}=${JWT}`); expect(response.status).toEqual(201); expect(response.body.isLike).toEqual(true); @@ -105,11 +105,11 @@ describe('Reaction', () => { test('[성공] POST - 좋아요가 있는 경우', async () => { await request(app) .post('/reactions/articles/1') - .set('Cookie', `access_token=${JWT}`); + .set('Cookie', `${process.env.ACCESS_TOKEN_KEY}=${JWT}`); const response2 = await request(app) .post('/reactions/articles/1') - .set('Cookie', `access_token=${JWT}`); + .set('Cookie', `${process.env.ACCESS_TOKEN_KEY}=${JWT}`); expect(response2.status).toEqual(201); expect(response2.body.isLike).toEqual(false); @@ -127,7 +127,7 @@ describe('Reaction', () => { const response = await request(app) .post('/reactions/articles/' + notExistId) - .set('Cookie', `access_token=${JWT}`); + .set('Cookie', `${process.env.ACCESS_TOKEN_KEY}=${JWT}`); expect(response.status).toEqual(404); }); @@ -149,7 +149,7 @@ describe('Reaction', () => { test('[성공] POST - 좋아요가 없는 경우', async () => { const response = await request(app) .post('/reactions/articles/1/comments/1') - .set('Cookie', `access_token=${JWT}`); + .set('Cookie', `${process.env.ACCESS_TOKEN_KEY}=${JWT}`); expect(response.status).toEqual(201); expect(response.body.isLike).toEqual(true); @@ -159,11 +159,11 @@ describe('Reaction', () => { test('[성공] POST - 좋아요가 있는 경우', async () => { await request(app) .post('/reactions/articles/1/comments/1') - .set('Cookie', `access_token=${JWT}`); + .set('Cookie', `${process.env.ACCESS_TOKEN_KEY}=${JWT}`); const response2 = await request(app) .post('/reactions/articles/1/comments/1') - .set('Cookie', `access_token=${JWT}`); + .set('Cookie', `${process.env.ACCESS_TOKEN_KEY}=${JWT}`); expect(response2.status).toEqual(201); expect(response2.body.isLike).toEqual(false); @@ -183,7 +183,7 @@ describe('Reaction', () => { const response = await request(app) .post('/reactions/articles/' + notExistId + '/comments/1') - .set('Cookie', `access_token=${JWT}`); + .set('Cookie', `${process.env.ACCESS_TOKEN_KEY}=${JWT}`); expect(response.status).toEqual(201); // TODO - 코드가 이상하다 201을 돌려준다 }); @@ -193,7 +193,7 @@ describe('Reaction', () => { const response = await request(app) .post('/reactions/articles/1/comments/' + notExistId) - .set('Cookie', `access_token=${JWT}`); + .set('Cookie', `${process.env.ACCESS_TOKEN_KEY}=${JWT}`); expect(response.status).toEqual(404); }); From 1dcfdc8b8fd3fcda6b069ffae9edb9618d2b5832 Mon Sep 17 00:00:00 2001 From: rockpell Date: Mon, 21 Feb 2022 22:55:41 +0900 Subject: [PATCH 52/55] =?UTF-8?q?Doc:=20image=20api=20cookie=20auth=20swag?= =?UTF-8?q?ger=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - image api에 누락된 Authroize 어노테이션 추가 --- src/image/image.controller.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/image/image.controller.ts b/src/image/image.controller.ts index 9403590a..ec729856 100644 --- a/src/image/image.controller.ts +++ b/src/image/image.controller.ts @@ -1,5 +1,6 @@ import { Controller, Post } from '@nestjs/common'; import { + ApiCookieAuth, ApiOkResponse, ApiOperation, ApiTags, @@ -8,6 +9,7 @@ import { import { ImageService } from '@image/image.service'; import { UploadImageUrlResponseDto } from '@image/dto/upload-image-url-response.dto'; +@ApiCookieAuth() @ApiUnauthorizedResponse({ description: '인증 실패' }) @ApiTags('Image') @Controller('image') From 8c5744a2356bf8b67f199620b78e6ca08bddfa50 Mon Sep 17 00:00:00 2001 From: rockpell Date: Tue, 22 Feb 2022 23:44:56 +0900 Subject: [PATCH 53/55] =?UTF-8?q?Test:=20=EB=82=B4=EA=B0=80=20=EC=A2=8B?= =?UTF-8?q?=EC=95=84=EC=9A=94=20=EB=88=84=EB=A5=B8=20=EA=B2=8C=EC=8B=9C?= =?UTF-8?q?=EA=B8=80=20=EB=AA=A9=EB=A1=9D=20response=20=EB=B3=80=EA=B2=BD?= =?UTF-8?q?=EC=97=90=20=EB=94=B0=EB=A5=B8=20test=20code=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/e2e/user.e2e-spec.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/e2e/user.e2e-spec.ts b/test/e2e/user.e2e-spec.ts index 211ca660..2906fcd5 100644 --- a/test/e2e/user.e2e-spec.ts +++ b/test/e2e/user.e2e-spec.ts @@ -224,9 +224,9 @@ describe('UserController (e2e)', () => { expect(response.status).toEqual(200); - const comments = response.body as ReactionArticle[]; + const articles = response.body as Article[]; - expect(comments.length).toEqual(1); - expect(comments[0].articleId).toEqual(1); + expect(articles.length).toEqual(1); + expect(articles[0].id).toEqual(1); }); }); From eb746b60bd4974fb218a49669920251ecf46e15c Mon Sep 17 00:00:00 2001 From: rockpell Date: Thu, 24 Feb 2022 00:12:58 +0900 Subject: [PATCH 54/55] =?UTF-8?q?!HOTFIX:=20=EC=9D=B4=EB=A9=94=EC=9D=BC=20?= =?UTF-8?q?=EC=A0=84=EC=86=A1=20api=EC=97=90=EC=84=9C=20=EB=82=98=EB=8A=94?= =?UTF-8?q?=20=EA=B5=AC=EA=B8=80=20=EC=97=90=EB=9F=AC=EB=A5=BC=20=EC=9E=A1?= =?UTF-8?q?=EA=B8=B0=20=EC=9C=84=ED=95=B4=20all=20filter=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ft-auth/ft-auth.controller.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/ft-auth/ft-auth.controller.ts b/src/ft-auth/ft-auth.controller.ts index 6264f682..59b969ff 100644 --- a/src/ft-auth/ft-auth.controller.ts +++ b/src/ft-auth/ft-auth.controller.ts @@ -1,4 +1,12 @@ -import { Body, Controller, Post, Get, Query, Render } from '@nestjs/common'; +import { + Body, + Controller, + Post, + Get, + Query, + Render, + UseFilters, +} from '@nestjs/common'; import { FtAuthService } from './ft-auth.service'; import { GetUser, OnlyNovice, Public } from '@root/auth/auth.decorator'; import { User } from '@root/user/entities/user.entity'; @@ -11,6 +19,7 @@ import { ApiTags, ApiUnauthorizedResponse, } from '@nestjs/swagger'; +import { AllExceptionsFilter } from '@root/filters/all-exception.filter'; @ApiTags('FT Auth') @Controller('ft-auth') @@ -20,6 +29,7 @@ export class FtAuthController { @Post() @OnlyNovice() @ApiCookieAuth() + @UseFilters(AllExceptionsFilter) @ApiOperation({ summary: '42인증 메일 전송' }) @ApiOkResponse({ description: '메일 전송 성공' }) @ApiUnauthorizedResponse({ description: '인증 실패' }) From 67b36d1f578646cec805a9884c957c998c8741f8 Mon Sep 17 00:00:00 2001 From: rockpell Date: Sat, 26 Feb 2022 15:55:47 +0900 Subject: [PATCH 55/55] =?UTF-8?q?Fix:=20docker=20compose=20db=20=ED=97=AC?= =?UTF-8?q?=EC=8A=A4=EC=B2=B4=ED=81=AC=20=EC=8A=A4=ED=81=AC=EB=A6=BD?= =?UTF-8?q?=ED=8A=B8=20=EC=98=A4=EB=A5=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 환경 변수를 지정을 제대로 안해줘서 healthcheck가 제대로 안되고 있던 문제 수정 --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index 50bff69d..645809ce 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -14,7 +14,7 @@ services: - TZ=Asia/Seoul command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci healthcheck: - test: 'mysqladmin ping -h localhost -u $$DB_USER_NAME --password=$$DB_USER_PASSWORD' + test: 'mysqladmin ping -h localhost -u ${DB_USER_NAME} --password=${DB_USER_PASSWORD}' interval: 5s timeout: 1s retries: 10