From d6e21cc3ff812b514d57cf75d453a84b5adb0024 Mon Sep 17 00:00:00 2001 From: ChangHoonOh Date: Fri, 23 Feb 2024 18:02:03 +0900 Subject: [PATCH 1/4] deploy/QYOC-61 add slack webhook url --- .github/workflows/dongurami-v2.yml | 2 ++ dockerfile | 2 ++ src/core/app-config/constants/app-config.constant.ts | 5 +++++ 3 files changed, 9 insertions(+) diff --git a/.github/workflows/dongurami-v2.yml b/.github/workflows/dongurami-v2.yml index bc7d43cd..748b61ad 100644 --- a/.github/workflows/dongurami-v2.yml +++ b/.github/workflows/dongurami-v2.yml @@ -30,6 +30,7 @@ jobs: echo "RDB_DATABASE=${{ secrets.DEV_RDB_DATABASE }}" >> $GITHUB_ENV echo "JWT_SECRET=${{ secrets.DEV_JWT_SECRET }}" >> $GITHUB_ENV echo "DOMAIN=${{ secrets.DEV_DOMAIN }}" >> $GITHUB_ENV + echo "SERVER_ERROR_WEB_HOOK_URL=${{ secrets.SLACK_DEV_ERROR_WEB_HOOK_URL }}" >> $GITHUB_ENV cat $GITHUB_ENV - name: Build and package @@ -46,6 +47,7 @@ jobs: --build-arg RDB_DATABASE=${{ secrets.DEV_RDB_DATABASE }} \ --build-arg JWT_SECRET=${{ secrets.DEV_JWT_SECRET }} \ --build-arg DOMAIN=${{ secrets.DEV_DOMAIN }} \ + --build-arg SERVER_ERROR_WEB_HOOK_URL=${{ secrets.SLACK_DEV_ERROR_WEB_HOOK_URL }} \ -t ${{ secrets.DOCKER_IMAGE_NAME }}:latest . - name: Login to Docker Hub diff --git a/dockerfile b/dockerfile index 9246d1ce..b42e4b00 100644 --- a/dockerfile +++ b/dockerfile @@ -21,6 +21,7 @@ ARG RDB_PASSWORD ARG RDB_DATABASE ARG JWT_SECRET ARG DOMAIN +ARG SERVER_ERROR_WEB_HOOK_URL # ENV로 환경 변수 설정 ENV NODE_ENV=${NODE_ENV} @@ -32,6 +33,7 @@ ENV RDB_PASSWORD=${RDB_PASSWORD} ENV RDB_DATABASE=${RDB_DATABASE} ENV JWT_SECRET=${JWT_SECRET} ENV DOMAIN=${DOMAIN} +ENV SERVER_ERROR_WEB_HOOK_URL=${SERVER_ERROR_WEB_HOOK_URL} # 애플리케이션 실행 CMD ["npm", "run", "start:prod"] diff --git a/src/core/app-config/constants/app-config.constant.ts b/src/core/app-config/constants/app-config.constant.ts index caaab110..33ae9016 100644 --- a/src/core/app-config/constants/app-config.constant.ts +++ b/src/core/app-config/constants/app-config.constant.ts @@ -10,10 +10,15 @@ const JWT = { JWT_SECRET: 'JWT_SECRET', } as const; +const SLACK = { + SERVER_ERROR_WEB_HOOK_URL: 'SERVER_ERROR_WEB_HOOK_URL' +} as const; + export const ENV_KEY = { PORT: 'PORT', NODE_ENV: 'NODE_ENV', DOMAIN: 'DOMAIN', ...RDB, ...JWT, + ...SLACK, } as const; From 46c3eddbee376ab99ecd506f67520a5b1f3a719d Mon Sep 17 00:00:00 2001 From: ChangHoonOh Date: Fri, 23 Feb 2024 18:02:44 +0900 Subject: [PATCH 2/4] feat/QYOC-61 install slack dependencies --- package-lock.json | 342 +++++++++++++++++++++++++++++++++++++++++++++- package.json | 2 + 2 files changed, 339 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7be857ba..990cefa4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,6 +18,7 @@ "@nestjs/platform-express": "^10.0.0", "@nestjs/swagger": "^7.1.16", "@nestjs/typeorm": "^10.0.0", + "@slack/client": "^5.0.2", "bcrypt": "^5.1.1", "class-transformer": "^0.5.1", "class-validator": "^0.14.0", @@ -29,6 +30,7 @@ "mariadb": "^3.2.2", "moment": "^2.30.1", "mysql2": "^3.6.3", + "nestjs-slack-webhook": "^10.3.0", "passport": "^0.6.0", "passport-jwt": "^4.0.1", "reflect-metadata": "^0.1.13", @@ -2111,6 +2113,191 @@ "@sinonjs/commons": "^3.0.0" } }, + "node_modules/@slack/client": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@slack/client/-/client-5.0.2.tgz", + "integrity": "sha512-HurKTUBZlwj/1cnZ6QOHpYR7k+G62WlL+13DgYD7onVRnQWggkIyCg+ymX1kQn6txzNdUyDEaixyCvBvmhH8tQ==", + "deprecated": "Slack Client is deprecated - Use @slack/web-api, @slack/rtm-api, or @slack/webhook instead.", + "dependencies": { + "@slack/logger": "^1.0.0", + "@slack/rtm-api": "^5.0.2", + "@slack/types": "^1.1.0", + "@slack/web-api": "^5.1.0", + "@slack/webhook": "^5.0.1" + }, + "engines": { + "node": ">= 8.9.0", + "npm": ">= 5.5.1" + } + }, + "node_modules/@slack/client/node_modules/@slack/types": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@slack/types/-/types-1.10.0.tgz", + "integrity": "sha512-tA7GG7Tj479vojfV3AoxbckalA48aK6giGjNtgH6ihpLwTyHE3fIgRrvt8TWfLwW8X8dyu7vgmAsGLRG7hWWOg==", + "engines": { + "node": ">= 8.9.0", + "npm": ">= 5.5.1" + } + }, + "node_modules/@slack/client/node_modules/@slack/webhook": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@slack/webhook/-/webhook-5.0.4.tgz", + "integrity": "sha512-IC1dpVSc2F/pmwCxOb0QzH2xnGKmyT7MofPGhNkeaoiMrLMU+Oc7xV/AxGnz40mURtCtaDchZSM3tDo9c9x6BA==", + "dependencies": { + "@slack/types": "^1.2.1", + "@types/node": ">=8.9.0", + "axios": "^0.21.1" + }, + "engines": { + "node": ">= 8.9.0", + "npm": ">= 5.5.1" + } + }, + "node_modules/@slack/client/node_modules/axios": { + "version": "0.21.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz", + "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==", + "dependencies": { + "follow-redirects": "^1.14.0" + } + }, + "node_modules/@slack/logger": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@slack/logger/-/logger-1.1.1.tgz", + "integrity": "sha512-PAC5CMnNAv/FPtJ0le+YD2wUV+tZ7n3Bnjj9dBI+deIcHsExCnQkQmZE79cLvfuYXbz3PWyv5coti30MJQhEjA==", + "dependencies": { + "@types/node": ">=8.9.0" + }, + "engines": { + "node": ">= 8.9.0", + "npm": ">= 5.5.1" + } + }, + "node_modules/@slack/rtm-api": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/@slack/rtm-api/-/rtm-api-5.0.5.tgz", + "integrity": "sha512-x2B4hyoxjg62cxf4M5QRomx+xYp2XoajPKdd24SM2Sl4m+IrzwKzmcrysQuYmF6BMsm3IoTKymW5BBGckHGTIw==", + "dependencies": { + "@slack/logger": ">=1.0.0 <3.0.0", + "@slack/web-api": "^5.3.0", + "@types/node": ">=8.9.0", + "@types/p-queue": "^2.3.2", + "@types/ws": "^7.2.5", + "eventemitter3": "^3.1.0", + "finity": "^0.5.4", + "p-cancelable": "^1.1.0", + "p-queue": "^2.4.2", + "ws": "^5.2.0" + }, + "engines": { + "node": ">= 8.9.0", + "npm": ">= 5.5.1" + } + }, + "node_modules/@slack/types": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/@slack/types/-/types-2.11.0.tgz", + "integrity": "sha512-UlIrDWvuLaDly3QZhCPnwUSI/KYmV1N9LyhuH6EDKCRS1HWZhyTG3Ja46T3D0rYfqdltKYFXbJSSRPwZpwO0cQ==", + "peer": true, + "engines": { + "node": ">= 12.13.0", + "npm": ">= 6.12.0" + } + }, + "node_modules/@slack/web-api": { + "version": "5.15.0", + "resolved": "https://registry.npmjs.org/@slack/web-api/-/web-api-5.15.0.tgz", + "integrity": "sha512-tjQ8Zqv/Fmj9SOL9yIEd7IpTiKfKHi9DKAkfRVeotoX0clMr3SqQtBqO+KZMX27gm7dmgJsQaDKlILyzdCO+IA==", + "dependencies": { + "@slack/logger": ">=1.0.0 <3.0.0", + "@slack/types": "^1.7.0", + "@types/is-stream": "^1.1.0", + "@types/node": ">=8.9.0", + "axios": "^0.21.1", + "eventemitter3": "^3.1.0", + "form-data": "^2.5.0", + "is-stream": "^1.1.0", + "p-queue": "^6.6.1", + "p-retry": "^4.0.0" + }, + "engines": { + "node": ">= 8.9.0", + "npm": ">= 5.5.1" + } + }, + "node_modules/@slack/web-api/node_modules/@slack/types": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@slack/types/-/types-1.10.0.tgz", + "integrity": "sha512-tA7GG7Tj479vojfV3AoxbckalA48aK6giGjNtgH6ihpLwTyHE3fIgRrvt8TWfLwW8X8dyu7vgmAsGLRG7hWWOg==", + "engines": { + "node": ">= 8.9.0", + "npm": ">= 5.5.1" + } + }, + "node_modules/@slack/web-api/node_modules/axios": { + "version": "0.21.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz", + "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==", + "dependencies": { + "follow-redirects": "^1.14.0" + } + }, + "node_modules/@slack/web-api/node_modules/form-data": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.1.tgz", + "integrity": "sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 0.12" + } + }, + "node_modules/@slack/web-api/node_modules/is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@slack/web-api/node_modules/p-queue": { + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-6.6.2.tgz", + "integrity": "sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==", + "dependencies": { + "eventemitter3": "^4.0.4", + "p-timeout": "^3.2.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@slack/web-api/node_modules/p-queue/node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" + }, + "node_modules/@slack/webhook": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@slack/webhook/-/webhook-7.0.2.tgz", + "integrity": "sha512-dsrO/ow6a6+xkLm/lZKbUNTsFJlBc679tD+qwlVTztsQkDxPLH6odM7FKALz1IHa+KpLX8HKUIPV13a7y7z29w==", + "peer": true, + "dependencies": { + "@slack/types": "^2.9.0", + "@types/node": ">=18.0.0", + "axios": "^1.6.3" + }, + "engines": { + "node": ">= 18", + "npm": ">= 8.6.0" + } + }, "node_modules/@sqltools/formatter": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/@sqltools/formatter/-/formatter-1.2.5.tgz", @@ -2411,6 +2598,14 @@ "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", "dev": true }, + "node_modules/@types/is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@types/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha512-jkZatu4QVbR60mpIzjINmtS1ZF4a/FqdTUTBeQDVOQ2PYyidtwFKr0B5G6ERukKwliq+7mIXvxyppwzG5EgRYg==", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", @@ -2483,6 +2678,11 @@ "form-data": "^4.0.0" } }, + "node_modules/@types/p-queue": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/@types/p-queue/-/p-queue-2.3.2.tgz", + "integrity": "sha512-eKAv5Ql6k78dh3ULCsSBxX6bFNuGjTmof5Q/T6PiECDq0Yf8IIn46jCyp3RJvCi8owaEmm3DZH1PEImjBMd/vQ==" + }, "node_modules/@types/passport": { "version": "1.0.16", "resolved": "https://registry.npmjs.org/@types/passport/-/passport-1.0.16.tgz", @@ -2525,6 +2725,11 @@ "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", "dev": true }, + "node_modules/@types/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==" + }, "node_modules/@types/semver": { "version": "7.5.5", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.5.tgz", @@ -2582,6 +2787,14 @@ "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.11.6.tgz", "integrity": "sha512-HUgHujPhKuNzgNXBRZKYexwoG+gHKU+tnfPqjWXFghZAnn73JElicMkuSKJyLGr9JgyA8IgK7fj88IyA9rwYeQ==" }, + "node_modules/@types/ws": { + "version": "7.4.7", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-7.4.7.tgz", + "integrity": "sha512-JQbbmxZTZehdc2iszGKs5oC3NFnjeay7mtAWrdt7qNtAVK0g19muApzAy4bm9byz79xa2ZnO/BOBC2R8RC5Lww==", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/yargs": { "version": "17.0.31", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.31.tgz", @@ -3217,11 +3430,26 @@ "node": "^4.7 || >=6.9 || >=7.3" } }, + "node_modules/async-limiter": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", + "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==" + }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "dev": true + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/axios": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.7.tgz", + "integrity": "sha512-/hDJGff6/c7u0hDkvkGxR/oy6CbCs8ziCsC7SqmhjfozqiJGc8Z11wrv9z9lYfY4K8l+H9TpjcMDX0xOZmx+RA==", + "peer": true, + "dependencies": { + "follow-redirects": "^1.15.4", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } }, "node_modules/babel-jest": { "version": "29.7.0", @@ -3997,7 +4225,6 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, "dependencies": { "delayed-stream": "~1.0.0" }, @@ -4469,7 +4696,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "dev": true, "engines": { "node": ">=0.4.0" } @@ -4969,6 +5195,11 @@ "node": ">= 0.6" } }, + "node_modules/eventemitter3": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.2.tgz", + "integrity": "sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q==" + }, "node_modules/events": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", @@ -5299,6 +5530,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/finity": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/finity/-/finity-0.5.4.tgz", + "integrity": "sha512-3l+5/1tuw616Lgb0QBimxfdd2TqaDGpfCBpfX6EqtFmqUV3FtQnVEX4Aa62DagYEqnsTIjZcTfbq9msDbXYgyA==" + }, "node_modules/flat-cache": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", @@ -5354,6 +5590,25 @@ "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==", "dev": true }, + "node_modules/follow-redirects": { + "version": "1.15.5", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz", + "integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, "node_modules/foreground-child": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", @@ -5402,7 +5657,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", - "dev": true, "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", @@ -7592,6 +7846,15 @@ "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", "dev": true }, + "node_modules/nestjs-slack-webhook": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/nestjs-slack-webhook/-/nestjs-slack-webhook-10.3.0.tgz", + "integrity": "sha512-upoTzOJjen3vc2oQJbjFZd9bBpvDbZF6mi8pkXXW3Z773yUtsK8HjNRk5ejopn4+9coPTPCAqNjUCUssiJLgRA==", + "peerDependencies": { + "@nestjs/common": "^10.0.0", + "@slack/webhook": "^6.0.0 || ^7.0.0" + } + }, "node_modules/node-abort-controller": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/node-abort-controller/-/node-abort-controller-3.1.1.tgz", @@ -7822,6 +8085,22 @@ "node": ">=0.10.0" } }, + "node_modules/p-cancelable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz", + "integrity": "sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==", + "engines": { + "node": ">=6" + } + }, + "node_modules/p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==", + "engines": { + "node": ">=4" + } + }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -7852,6 +8131,37 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/p-queue": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-2.4.2.tgz", + "integrity": "sha512-n8/y+yDJwBjoLQe1GSJbbaYQLTI7QHNZI2+rpmCDbe++WLf9HC3gf6iqj5yfPAV71W4UF3ql5W1+UBPXoXTxng==", + "engines": { + "node": ">=4" + } + }, + "node_modules/p-retry": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", + "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", + "dependencies": { + "@types/retry": "0.12.0", + "retry": "^0.13.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-timeout": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz", + "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==", + "dependencies": { + "p-finally": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/p-try": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", @@ -8221,6 +8531,12 @@ "node": ">= 0.10" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "peer": true + }, "node_modules/pump": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", @@ -8481,6 +8797,14 @@ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "dev": true }, + "node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "engines": { + "node": ">= 4" + } + }, "node_modules/reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", @@ -10330,6 +10654,14 @@ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "dev": true }, + "node_modules/ws": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-5.2.3.tgz", + "integrity": "sha512-jZArVERrMsKUatIdnLzqvcfydI85dvd/Fp1u/VOpfdDWQ4c9qWXe+VIeAbQ5FrDwciAkr+lzofXLz3Kuf26AOA==", + "dependencies": { + "async-limiter": "~1.0.0" + } + }, "node_modules/xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", diff --git a/package.json b/package.json index 0c0d31d0..9280b749 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,7 @@ "@nestjs/platform-express": "^10.0.0", "@nestjs/swagger": "^7.1.16", "@nestjs/typeorm": "^10.0.0", + "@slack/client": "^5.0.2", "bcrypt": "^5.1.1", "class-transformer": "^0.5.1", "class-validator": "^0.14.0", @@ -56,6 +57,7 @@ "mariadb": "^3.2.2", "moment": "^2.30.1", "mysql2": "^3.6.3", + "nestjs-slack-webhook": "^10.3.0", "passport": "^0.6.0", "passport-jwt": "^4.0.1", "reflect-metadata": "^0.1.13", From 799f9f27cf1081ba67623f4023b71d7fa9b840ad Mon Sep 17 00:00:00 2001 From: ChangHoonOh Date: Fri, 23 Feb 2024 18:03:13 +0900 Subject: [PATCH 3/4] feat/QYOC-61 create server error slack notifications --- src/app.module.ts | 2 + src/core/slack/slack-global.module.ts | 9 ++++ src/core/slack/slack-global.service.ts | 42 +++++++++++++++++++ ...-internal-server-error-exception.filter.ts | 7 +++- .../http-process-error-exception.filter.ts | 7 +++- .../http-remainder-exception.filter.ts | 7 +++- 6 files changed, 71 insertions(+), 3 deletions(-) create mode 100644 src/core/slack/slack-global.module.ts create mode 100644 src/core/slack/slack-global.service.ts diff --git a/src/app.module.ts b/src/app.module.ts index 7be2cb4d..13c887a3 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -13,6 +13,7 @@ import { InterceptorModule } from '@src/interceptors/interceptor.module'; import { LibsModule } from '@src/libs/libs.module'; import { LoggerMiddleware } from '@src/middlewares/logger.middleware'; import { UseDevelopmentMiddleware } from '@src/middlewares/use-development.middleware'; +import { SlackGlobalModule } from './core/slack/slack-global.module'; @Module({ imports: [ @@ -21,6 +22,7 @@ import { UseDevelopmentMiddleware } from '@src/middlewares/use-development.middl LibsModule, InterceptorModule, HttpExceptionModule, + SlackGlobalModule, ], providers: [BootstrapService], }) diff --git a/src/core/slack/slack-global.module.ts b/src/core/slack/slack-global.module.ts new file mode 100644 index 00000000..c984e60a --- /dev/null +++ b/src/core/slack/slack-global.module.ts @@ -0,0 +1,9 @@ +import { Global, Module } from '@nestjs/common'; +import { SlackGlobalService } from './slack-global.service'; + +@Global() +@Module({ + providers: [SlackGlobalService], + exports: [SlackGlobalService], +}) +export class SlackGlobalModule {} diff --git a/src/core/slack/slack-global.service.ts b/src/core/slack/slack-global.service.ts new file mode 100644 index 00000000..330f1fbf --- /dev/null +++ b/src/core/slack/slack-global.service.ts @@ -0,0 +1,42 @@ +import { Injectable } from '@nestjs/common'; +import { HttpInternalServerErrorException } from '@src/http-exceptions/exceptions/http-internal-server-error.exception'; +import { HttpProcessErrorException } from '@src/http-exceptions/exceptions/http-process-error.exception'; +import axios from 'axios'; +import { AppConfigService } from '../app-config/services/app-config.service'; +import { ENV_KEY } from '../app-config/constants/app-config.constant'; + +@Injectable() +export class SlackGlobalService { + private readonly webhookUrl: string; + + constructor( + private readonly appConfigService: AppConfigService + ) { + this.webhookUrl = this.appConfigService.get(ENV_KEY.SERVER_ERROR_WEB_HOOK_URL); + } + + async sendNotification({ statusCode, exceptionError, processError }: { statusCode: number, exceptionError?: HttpInternalServerErrorException, processError?: HttpProcessErrorException }): Promise { + console.log('this.webhookUrl: ', this.webhookUrl); + + let message = `[${process.env.NODE_ENV}-error || code: ${statusCode}]`; + + if (exceptionError) { + const { errors, ctx, stack } = exceptionError; + message += ` - ${ctx}`; + if (errors) message += `\n${errors}`; + if (stack) message += `\n${stack}`; + } + + if (processError) { + const { code } = processError; + message += `${code ? `\nCode: ${code}` : ''}`; + } + + try { + await axios.post(this.webhookUrl, { text: message }); + } catch (error) { + console.error('Error sending Slack notification:', error); + throw new Error('Failed to send Slack notification'); + } + } +} diff --git a/src/http-exceptions/filters/http-internal-server-error-exception.filter.ts b/src/http-exceptions/filters/http-internal-server-error-exception.filter.ts index 1f9ac69f..485850ae 100644 --- a/src/http-exceptions/filters/http-internal-server-error-exception.filter.ts +++ b/src/http-exceptions/filters/http-internal-server-error-exception.filter.ts @@ -4,6 +4,7 @@ import { Response } from 'express'; import { HttpInternalServerErrorException } from '@src/http-exceptions/exceptions/http-internal-server-error.exception'; import { HttpExceptionService } from '@src/http-exceptions/services/http-exception.service'; +import { SlackGlobalService } from '@src/core/slack/slack-global.service'; /** * nestJS 메서드를 이용한 500번 에러 를 잡는 exception filter @@ -13,7 +14,10 @@ import { HttpExceptionService } from '@src/http-exceptions/services/http-excepti export class HttpInternalServerErrorExceptionFilter implements ExceptionFilter { - constructor(private readonly httpExceptionService: HttpExceptionService) {} + constructor( + private readonly httpExceptionService: HttpExceptionService, + private readonly slackService: SlackGlobalService, + ) {} catch( exception: HttpInternalServerErrorException, @@ -30,6 +34,7 @@ export class HttpInternalServerErrorExceptionFilter statusCode, exceptionError, ); + this.slackService.sendNotification({ statusCode, exceptionError }); this.httpExceptionService.printLog({ ctx: exception.ctx, diff --git a/src/http-exceptions/filters/http-process-error-exception.filter.ts b/src/http-exceptions/filters/http-process-error-exception.filter.ts index 342fd2b3..32e4ffc6 100644 --- a/src/http-exceptions/filters/http-process-error-exception.filter.ts +++ b/src/http-exceptions/filters/http-process-error-exception.filter.ts @@ -10,6 +10,7 @@ import { Response } from 'express'; import { COMMON_ERROR_CODE } from '@src/constants/error/common/common-error-code.constant'; import { HttpProcessErrorException } from '@src/http-exceptions/exceptions/http-process-error.exception'; import { HttpExceptionService } from '@src/http-exceptions/services/http-exception.service'; +import { SlackGlobalService } from '@src/core/slack/slack-global.service'; /** * node process error exception @@ -17,7 +18,10 @@ import { HttpExceptionService } from '@src/http-exceptions/services/http-excepti */ @Catch() export class HttpProcessErrorExceptionFilter implements ExceptionFilter { - constructor(private readonly httpExceptionService: HttpExceptionService) {} + constructor( + private readonly httpExceptionService: HttpExceptionService, + private readonly slackService: SlackGlobalService, + ) {} catch(exception: any, host: ArgumentsHost): void { const ctx = host.switchToHttp(); @@ -34,6 +38,7 @@ export class HttpProcessErrorExceptionFilter implements ExceptionFilter { statusCode, exceptionError, ); + this.slackService.sendNotification({ statusCode, processError: exceptionError }); this.httpExceptionService.printLog({ ctx: 'Node Process Error', diff --git a/src/http-exceptions/filters/http-remainder-exception.filter.ts b/src/http-exceptions/filters/http-remainder-exception.filter.ts index 490f60ad..09bd5090 100644 --- a/src/http-exceptions/filters/http-remainder-exception.filter.ts +++ b/src/http-exceptions/filters/http-remainder-exception.filter.ts @@ -11,6 +11,7 @@ import { Response } from 'express'; import { COMMON_ERROR_CODE } from '@src/constants/error/common/common-error-code.constant'; import { HttpInternalServerErrorException } from '@src/http-exceptions/exceptions/http-internal-server-error.exception'; import { HttpExceptionService } from '@src/http-exceptions/services/http-exception.service'; +import { SlackGlobalService } from '@src/core/slack/slack-global.service'; /** * 다른 exception filter 가 잡지않는 exception 을 잡는 필터 @@ -20,7 +21,10 @@ import { HttpExceptionService } from '@src/http-exceptions/services/http-excepti export class HttpRemainderExceptionFilter implements ExceptionFilter { - constructor(private readonly httpExceptionService: HttpExceptionService) {} + constructor( + private readonly httpExceptionService: HttpExceptionService, + private readonly slackService: SlackGlobalService, + ) {} catch(exception: HttpException, host: ArgumentsHost): void { const ctx = host.switchToHttp(); @@ -40,6 +44,7 @@ export class HttpRemainderExceptionFilter statusCode, exceptionError, ); + this.slackService.sendNotification({ statusCode, exceptionError }); this.httpExceptionService.printLog({ ctx: httpInternalServerErrorException.ctx, From 26b135bbfda78f9ea73e7108714e18e9a3d2a661 Mon Sep 17 00:00:00 2001 From: ChangHoonOh Date: Fri, 23 Feb 2024 18:08:21 +0900 Subject: [PATCH 4/4] refactor/QYOC-61 fix lint --- src/app.module.ts | 2 +- src/core/slack/slack-global.module.ts | 2 +- src/core/slack/slack-global.service.ts | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/app.module.ts b/src/app.module.ts index 13c887a3..91c33732 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -13,7 +13,7 @@ import { InterceptorModule } from '@src/interceptors/interceptor.module'; import { LibsModule } from '@src/libs/libs.module'; import { LoggerMiddleware } from '@src/middlewares/logger.middleware'; import { UseDevelopmentMiddleware } from '@src/middlewares/use-development.middleware'; -import { SlackGlobalModule } from './core/slack/slack-global.module'; +import { SlackGlobalModule } from '@src/core/slack/slack-global.module'; @Module({ imports: [ diff --git a/src/core/slack/slack-global.module.ts b/src/core/slack/slack-global.module.ts index c984e60a..8543de63 100644 --- a/src/core/slack/slack-global.module.ts +++ b/src/core/slack/slack-global.module.ts @@ -1,5 +1,5 @@ import { Global, Module } from '@nestjs/common'; -import { SlackGlobalService } from './slack-global.service'; +import { SlackGlobalService } from '@src/core/slack/slack-global.service'; @Global() @Module({ diff --git a/src/core/slack/slack-global.service.ts b/src/core/slack/slack-global.service.ts index 330f1fbf..6d20109e 100644 --- a/src/core/slack/slack-global.service.ts +++ b/src/core/slack/slack-global.service.ts @@ -2,8 +2,8 @@ import { Injectable } from '@nestjs/common'; import { HttpInternalServerErrorException } from '@src/http-exceptions/exceptions/http-internal-server-error.exception'; import { HttpProcessErrorException } from '@src/http-exceptions/exceptions/http-process-error.exception'; import axios from 'axios'; -import { AppConfigService } from '../app-config/services/app-config.service'; -import { ENV_KEY } from '../app-config/constants/app-config.constant'; +import { AppConfigService } from '@src/core/app-config/services/app-config.service'; +import { ENV_KEY } from '@src/core/app-config/constants/app-config.constant'; @Injectable() export class SlackGlobalService {