From c15495f2e82422d9cc5619d054136ae82bff29a2 Mon Sep 17 00:00:00 2001 From: withsang Date: Fri, 6 Oct 2023 18:36:33 +0900 Subject: [PATCH 01/61] Fix: skip validation for trust proxy setting in rate-limit middleware --- package.json | 2 +- pnpm-lock.yaml | 10 +++++----- src/middlewares/limitRate.js | 4 ++++ 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index cdb57f4f..20ec12eb 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "eslint-config-prettier": "^8.3.0", "express": "^4.17.1", "express-formidable": "^1.2.0", - "express-rate-limit": "^6.6.0", + "express-rate-limit": "^7.1.0", "express-session": "^1.17.3", "express-validator": "^6.14.0", "firebase-admin": "^11.4.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2249f8ac..8e1b598e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -60,8 +60,8 @@ dependencies: specifier: ^1.2.0 version: 1.2.0 express-rate-limit: - specifier: ^6.6.0 - version: 6.8.1(express@4.18.2) + specifier: ^7.1.0 + version: 7.1.0(express@4.18.2) express-session: specifier: ^1.17.3 version: 1.17.3 @@ -5088,9 +5088,9 @@ packages: formidable: 1.2.6 dev: false - /express-rate-limit@6.8.1(express@4.18.2): - resolution: {integrity: sha512-xJyudsE60CsDShK74Ni1MxsldYaIoivmG3ieK2tAckMsYCBewEuGalss6p/jHmFFnqM9xd5ojE0W2VlanxcOKg==} - engines: {node: '>= 14.0.0'} + /express-rate-limit@7.1.0(express@4.18.2): + resolution: {integrity: sha512-pwKOMedrpJJeINON/9jhAa18udV2qwxPZSoklPZK8pmXxUyE5uXaptiwjGw8bZILbxqfUZ/p8pQA99ODjSgA5Q==} + engines: {node: '>= 16'} peerDependencies: express: ^4 || ^5 dependencies: diff --git a/src/middlewares/limitRate.js b/src/middlewares/limitRate.js index 4cba6af3..c5069c8f 100644 --- a/src/middlewares/limitRate.js +++ b/src/middlewares/limitRate.js @@ -5,6 +5,10 @@ const limiter = rateLimit({ max: 1500, // Limit each IP to 1500 requests per `window` (here, per 15 minutes) standardHeaders: true, // Return rate limit info in the `RateLimit-*` headers legacyHeaders: false, // Disable the `X-RateLimit-*` headers + validate: { + default: true, + trustProxy: false, // Disable the validation error caused by 'trust proxy' set to true + }, }); module.exports = limiter; From 07f181a0632467efbeacc68e490d79b38c3495eb Mon Sep 17 00:00:00 2001 From: withsang Date: Tue, 7 Nov 2023 20:35:01 +0900 Subject: [PATCH 02/61] Merge branch 'origin/main' From 387aee7d963eba6b2674de381bdc3cbf6672bb6f Mon Sep 17 00:00:00 2001 From: withsang Date: Tue, 7 Nov 2023 20:38:15 +0900 Subject: [PATCH 03/61] Fix: validate proxy in rateLimit middleware --- src/middlewares/limitRate.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/middlewares/limitRate.js b/src/middlewares/limitRate.js index c5069c8f..4cba6af3 100644 --- a/src/middlewares/limitRate.js +++ b/src/middlewares/limitRate.js @@ -5,10 +5,6 @@ const limiter = rateLimit({ max: 1500, // Limit each IP to 1500 requests per `window` (here, per 15 minutes) standardHeaders: true, // Return rate limit info in the `RateLimit-*` headers legacyHeaders: false, // Disable the `X-RateLimit-*` headers - validate: { - default: true, - trustProxy: false, // Disable the validation error caused by 'trust proxy' set to true - }, }); module.exports = limiter; From b05405217b8dc7061af5a29898c5f3facc8901d7 Mon Sep 17 00:00:00 2001 From: withsang Date: Thu, 16 Nov 2023 02:55:49 +0900 Subject: [PATCH 04/61] fix(github): disable unit test --- .github/workflows/test_ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/test_ci.yml b/.github/workflows/test_ci.yml index 29f7f21b..b08563d1 100644 --- a/.github/workflows/test_ci.yml +++ b/.github/workflows/test_ci.yml @@ -15,6 +15,8 @@ jobs: node-version: ['18.x'] mongodb-version: ['5.0'] steps: + - name: Exit because unit tests are not implemented in TypeScript for now + run: exit 1 - name: Start MongoDB run: sudo docker run --name mongodb -d -p 27017:27017 mongo:${{ matrix.mongodb-version }} - uses: actions/checkout@v3 From cf5da433b1b6fc9becd4acdf0a5ff5db4ad034d9 Mon Sep 17 00:00:00 2001 From: withsang Date: Thu, 16 Nov 2023 03:18:54 +0900 Subject: [PATCH 05/61] feat(typescript): init typescript project --- .dockerignore | 3 + .eslintignore | 7 + .eslintrc.js => .eslintrc.cjs | 0 .gitignore | 2 + .prettierignore | 9 +- loadenv.js | 47 ------ nodemon.json | 2 + package.json | 16 ++- pnpm-lock.yaml | 264 ++++++++++++++++++++++++++++------ app.js => src/index.ts | 14 +- src/loadenv.ts | 59 ++++++++ tsconfig.json | 20 +++ 12 files changed, 342 insertions(+), 101 deletions(-) create mode 100644 .eslintignore rename .eslintrc.js => .eslintrc.cjs (100%) delete mode 100644 loadenv.js rename app.js => src/index.ts (87%) create mode 100644 src/loadenv.ts create mode 100644 tsconfig.json diff --git a/.dockerignore b/.dockerignore index 8ffa241b..bafcba69 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,4 +1,6 @@ /node_modules +/dist +.env .env.test .env.production .env.development @@ -6,6 +8,7 @@ *.code-workspace *.swp /logs/*.log +.vscode # AdminJS 관련 디렉토리 .adminjs diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 00000000..15e64178 --- /dev/null +++ b/.eslintignore @@ -0,0 +1,7 @@ +node_modules/ +dist/ +package.json +tsconfig.json +.prettierrc.json +.eslintrc.cjs +nodemon.json \ No newline at end of file diff --git a/.eslintrc.js b/.eslintrc.cjs similarity index 100% rename from .eslintrc.js rename to .eslintrc.cjs diff --git a/.gitignore b/.gitignore index a7e86767..bafcba69 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ /node_modules +/dist +.env .env.test .env.production .env.development diff --git a/.prettierignore b/.prettierignore index dd87e2d7..15e64178 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,2 +1,7 @@ -node_modules -build +node_modules/ +dist/ +package.json +tsconfig.json +.prettierrc.json +.eslintrc.cjs +nodemon.json \ No newline at end of file diff --git a/loadenv.js b/loadenv.js deleted file mode 100644 index f7224601..00000000 --- a/loadenv.js +++ /dev/null @@ -1,47 +0,0 @@ -// 환경 변수에 따라 .env.production 또는 .env.development 파일을 읽어옴 -require("dotenv").config({ path: `./.env.${process.env.NODE_ENV}` }); - -module.exports = { - nodeEnv: process.env.NODE_ENV, // required - mongo: process.env.DB_PATH, // required - session: { - secret: process.env.SESSION_KEY || "TAXI_SESSION_KEY", // optional - expiry: 14 * 24 * 3600 * 1000, // 14일, ms 단위입니다. - }, - redis: process.env.REDIS_PATH, // optional - sparcssso: { - id: process.env.SPARCSSSO_CLIENT_ID || "", // optional - key: process.env.SPARCSSSO_CLIENT_KEY || "", // optional - }, - port: process.env.PORT || 80, // optional (default = 80) - corsWhiteList: (process.env.CORS_WHITELIST && - JSON.parse(process.env.CORS_WHITELIST)) || [true], // optional (default = [true]) - aws: { - accessKeyId: process.env.AWS_ACCESS_KEY_ID, // required - secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY, // required - s3BucketName: process.env.AWS_S3_BUCKET_NAME, // required - s3Url: - process.env.AWS_S3_URL || - `https://${process.env.AWS_S3_BUCKET_NAME}.s3.ap-northeast-2.amazonaws.com`, // optional - }, - jwt: { - secretKey: process.env.JWT_SECRET_KEY || "TAXI_JWT_KEY", - option: { - algorithm: "HS256", - // FIXME: remove FRONT_URL from issuer. 단, issuer를 변경하면 이전에 발급했던 모든 JWT가 무효화됩니다. - // See https://github.com/sparcs-kaist/taxi-back/issues/415 - issuer: process.env.FRONT_URL || "http://localhost:3000", // optional (default = "http://localhost:3000") - }, - TOKEN_EXPIRED: -3, - TOKEN_INVALID: -2, - }, - googleApplicationCredentials: - process.env.GOOGLE_APPLICATION_CREDENTIALS && - JSON.parse(process.env.GOOGLE_APPLICATION_CREDENTIALS), // optional - testAccounts: - (process.env.TEST_ACCOUNTS && JSON.parse(process.env.TEST_ACCOUNTS)) || [], // optional - slackWebhookUrl: { - report: process.env.SLACK_REPORT_WEBHOOK_URL || "", // optional - }, - eventConfig: process.env.EVENT_CONFIG && JSON.parse(process.env.EVENT_CONFIG), -}; diff --git a/nodemon.json b/nodemon.json index 62f8de10..5d6b9eaa 100644 --- a/nodemon.json +++ b/nodemon.json @@ -1,5 +1,7 @@ { "ignore": ["node_modules/*"], + "watch": ["./src", ".env.development"], + "exec": "tsc && tsc-alias && node dist/index.js", "env": { "TZ": "Asia/Seoul", "NODE_ENV": "development" diff --git a/package.json b/package.json index fecc4769..30d8ba6b 100644 --- a/package.json +++ b/package.json @@ -7,10 +7,12 @@ "main": "app.js", "scripts": { "preinstall": "npx only-allow pnpm", - "start": "cross-env TZ='Asia/Seoul' npx nodemon app.js", - "test": "npm run sample && cross-env TZ='Asia/Seoul' npm run mocha", + "start": "npx tsc && tsc-alias && npx nodemon", "mocha": "cross-env TZ='Asia/Seoul' NODE_ENV=test mocha --recursive --reporter spec --exit", - "serve": "cross-env TZ='Asia/Seoul' NODE_ENV=production node app.js", + "test": "npm run sample && cross-env TZ='Asia/Seoul' npm run mocha", + "build": "tsc && tsc-alias", + "clean": "rimraf dist/", + "serve": "cross-env TZ='Asia/Seoul' NODE_ENV=production node dist/index.js", "lint": "npx eslint --fix .", "sample": "cd sampleGenerator && npm start && cd .." }, @@ -55,11 +57,17 @@ "winston-daily-rotate-file": "^4.7.1" }, "devDependencies": { + "@types/cors": "^2.8.16", + "@types/express": "^4.17.21", + "@types/node": "^20.9.0", "chai": "^4.3.10", "eslint": "^8.22.0", "eslint-plugin-mocha": "^10.1.0", "mocha": "^10.2.0", "nodemon": "^3.0.1", - "supertest": "^6.2.4" + "rimraf": "^5.0.5", + "supertest": "^6.2.4", + "tsc-alias": "^1.8.8", + "typescript": "^5.2.2" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d197fbe1..8900aeb3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -109,6 +109,15 @@ dependencies: version: 4.7.1(winston@3.10.0) devDependencies: + '@types/cors': + specifier: ^2.8.16 + version: 2.8.16 + '@types/express': + specifier: ^4.17.21 + version: 4.17.21 + '@types/node': + specifier: ^20.9.0 + version: 20.9.0 chai: specifier: ^4.3.10 version: 4.3.10 @@ -124,9 +133,18 @@ devDependencies: nodemon: specifier: ^3.0.1 version: 3.0.1 + rimraf: + specifier: ^5.0.5 + version: 5.0.5 supertest: specifier: ^6.2.4 version: 6.3.3 + tsc-alias: + specifier: ^1.8.8 + version: 1.8.8 + typescript: + specifier: ^5.2.2 + version: 5.2.2 packages: @@ -2273,7 +2291,7 @@ packages: resolution: {integrity: sha512-rLMyrXuO9jcAUCaQXCMjCMUsWrba5fzHlNK24xz5j2W6A/SRmK8mZJ/hn7V0fViLbxC0lPMtrK1eYzk6Fg03jA==} dependencies: '@firebase/util': 1.9.3 - tslib: 2.6.1 + tslib: 2.6.2 dev: false /@firebase/database-compat@0.3.4: @@ -2302,19 +2320,19 @@ packages: '@firebase/logger': 0.4.0 '@firebase/util': 1.9.3 faye-websocket: 0.11.4 - tslib: 2.6.1 + tslib: 2.6.2 dev: false /@firebase/logger@0.4.0: resolution: {integrity: sha512-eRKSeykumZ5+cJPdxxJRgAC3G5NknY2GwEbKfymdnXtnT0Ucm4pspfR6GT4MUQEDuJwRVbVcSx85kgJulMoFFA==} dependencies: - tslib: 2.6.1 + tslib: 2.6.2 dev: false /@firebase/util@1.9.3: resolution: {integrity: sha512-DY02CRhOZwpzO36fHpuVysz6JZrscPiBXD0fXp6qSrL9oNOx5KWICKdR95C0lSITzxp0TZosVyHqzatE8JbcjA==} dependencies: - tslib: 2.6.1 + tslib: 2.6.2 dev: false /@floating-ui/core@1.4.1: @@ -2408,7 +2426,7 @@ packages: requiresBuild: true dependencies: '@grpc/proto-loader': 0.7.8 - '@types/node': 20.4.7 + '@types/node': 20.9.0 dev: false optional: true @@ -2475,6 +2493,18 @@ packages: warning: 4.0.3 dev: false + /@isaacs/cliui@8.0.2: + resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} + engines: {node: '>=12'} + dependencies: + string-width: 5.1.2 + string-width-cjs: /string-width@4.2.3 + strip-ansi: 7.1.0 + strip-ansi-cjs: /strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: /wrap-ansi@7.0.0 + dev: true + /@jridgewell/gen-mapping@0.3.3: resolution: {integrity: sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==} engines: {node: '>=6.0.0'} @@ -2551,6 +2581,13 @@ packages: '@nodelib/fs.scandir': 2.1.5 fastq: 1.15.0 + /@pkgjs/parseargs@0.11.0: + resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} + engines: {node: '>=14'} + requiresBuild: true + dev: true + optional: true + /@popperjs/core@2.11.8: resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==} dev: false @@ -3702,24 +3739,21 @@ packages: resolution: {integrity: sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==} dependencies: '@types/connect': 3.4.35 - '@types/node': 20.4.7 - dev: false + '@types/node': 20.9.0 /@types/connect@3.4.35: resolution: {integrity: sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==} dependencies: - '@types/node': 20.4.7 - dev: false + '@types/node': 20.9.0 /@types/cookie@0.4.1: resolution: {integrity: sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==} dev: false - /@types/cors@2.8.13: - resolution: {integrity: sha512-RG8AStHlUiV5ysZQKq97copd2UmVYw3/pRMLefISZ3S1hK104Cwm7iLQ3fTKx+lsUH2CE8FlLaYeEA2LSeqYUA==} + /@types/cors@2.8.16: + resolution: {integrity: sha512-Trx5or1Nyg1Fq138PCuWqoApzvoSLWzZ25ORBiHMbbUT42g578lH1GT4TwYDbiUOLFuDsCkfLneT2105fsFWGg==} dependencies: - '@types/node': 20.4.7 - dev: false + '@types/node': 20.9.0 /@types/estree@0.0.39: resolution: {integrity: sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==} @@ -3728,27 +3762,25 @@ packages: /@types/express-serve-static-core@4.17.35: resolution: {integrity: sha512-wALWQwrgiB2AWTT91CB62b6Yt0sNHpznUXeZEcnPU3DRdlDIz74x8Qg1UUYKSVFi+va5vKOLYRBI1bRKiLLKIg==} dependencies: - '@types/node': 20.4.7 + '@types/node': 20.9.0 '@types/qs': 6.9.7 '@types/range-parser': 1.2.4 '@types/send': 0.17.1 - dev: false - /@types/express@4.17.17: - resolution: {integrity: sha512-Q4FmmuLGBG58btUnfS1c1r/NQdlp3DMfGDGig8WhfpA2YRUtEkxAjkZb0yvplJGYdF1fsQ81iMDcH24sSCNC/Q==} + /@types/express@4.17.21: + resolution: {integrity: sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==} dependencies: '@types/body-parser': 1.19.2 '@types/express-serve-static-core': 4.17.35 '@types/qs': 6.9.7 '@types/serve-static': 1.15.2 - dev: false /@types/glob@8.1.0: resolution: {integrity: sha512-IO+MJPVhoqz+28h1qLAcBEH2+xHMK6MTyHJc7MTnnYb6wsoLR29POVGJ7LycmVXIqyy/4/2ShP5sUwTXuOwb/w==} requiresBuild: true dependencies: '@types/minimatch': 5.1.2 - '@types/node': 20.4.7 + '@types/node': 20.9.0 dev: false optional: true @@ -3761,12 +3793,11 @@ packages: /@types/http-errors@2.0.1: resolution: {integrity: sha512-/K3ds8TRAfBvi5vfjuz8y6+GiAYBZ0x4tXv1Av6CWBWn0IlADc+ZX9pMq7oU0fNQPnBwIZl3rmeLp6SBApbxSQ==} - dev: false /@types/jsonwebtoken@9.0.2: resolution: {integrity: sha512-drE6uz7QBKq1fYqqoFKTDRdFCPHd5TCub75BM+D+cMx7NU9hUz7SESLfC2fSCXVFMO5Yj8sOWHuGqPgjc+fz0Q==} dependencies: - '@types/node': 20.4.7 + '@types/node': 20.9.0 dev: false /@types/linkify-it@3.0.2: @@ -3798,7 +3829,6 @@ packages: /@types/mime@1.3.2: resolution: {integrity: sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==} - dev: false /@types/minimatch@5.1.2: resolution: {integrity: sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==} @@ -3806,9 +3836,10 @@ packages: dev: false optional: true - /@types/node@20.4.7: - resolution: {integrity: sha512-bUBrPjEry2QUTsnuEjzjbS7voGWCc30W0qzgMf90GPeDGFRakvrz47ju+oqDAKCXLUCe39u57/ORMl/O/04/9g==} - dev: false + /@types/node@20.9.0: + resolution: {integrity: sha512-nekiGu2NDb1BcVofVcEKMIwzlx4NjHlcjhoxxKBNLtz15Y1z7MYf549DFvkHSId02Ax6kGwWntIBPC3l/JZcmw==} + dependencies: + undici-types: 5.26.5 /@types/object.omit@3.0.0: resolution: {integrity: sha512-I27IoPpH250TUzc9FzXd0P1BV/BMJuzqD3jOz98ehf9dQqGkxlq+hO1bIqZGWqCg5bVOy0g4AUVJtnxe0klDmw==} @@ -3828,11 +3859,9 @@ packages: /@types/qs@6.9.7: resolution: {integrity: sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==} - dev: false /@types/range-parser@1.2.4: resolution: {integrity: sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==} - dev: false /@types/react-transition-group@4.4.6: resolution: {integrity: sha512-VnCdSxfcm08KjsJVQcfBmhEQAPnLB8G08hAxn39azX1qYBQ/5RVQuoHuKIcfKOdncuaUvEpFKFzEvbtIMsfVew==} @@ -3851,7 +3880,7 @@ packages: /@types/resolve@1.17.1: resolution: {integrity: sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw==} dependencies: - '@types/node': 20.4.7 + '@types/node': 20.9.0 dev: false /@types/rimraf@3.0.2: @@ -3859,7 +3888,7 @@ packages: requiresBuild: true dependencies: '@types/glob': 8.1.0 - '@types/node': 20.4.7 + '@types/node': 20.9.0 dev: false optional: true @@ -3871,16 +3900,14 @@ packages: resolution: {integrity: sha512-Cwo8LE/0rnvX7kIIa3QHCkcuF21c05Ayb0ZfxPiv0W8VRiZiNW/WuRupHKpqqGVGf7SUA44QSOUKaEd9lIrd/Q==} dependencies: '@types/mime': 1.3.2 - '@types/node': 20.4.7 - dev: false + '@types/node': 20.9.0 /@types/serve-static@1.15.2: resolution: {integrity: sha512-J2LqtvFYCzaj8pVYKw8klQXrLLk7TBZmQ4ShlcdkELFKGwGMfevMLneMMRkMgZxotOD9wg497LpC7O8PcvAmfw==} dependencies: '@types/http-errors': 2.0.1 '@types/mime': 1.3.2 - '@types/node': 20.4.7 - dev: false + '@types/node': 20.9.0 /@types/throttle-debounce@2.1.0: resolution: {integrity: sha512-5eQEtSCoESnh2FsiLTxE121IiE60hnMqcb435fShf4bpLRjEu1Eoekht23y6zXS9Ts3l+Szu3TARnTsA0GkOkQ==} @@ -3901,7 +3928,7 @@ packages: /@types/whatwg-url@8.2.2: resolution: {integrity: sha512-FtQu10RWgn3D9U4aazdwIE2yzphmTJREDqNdODHrbrZmmMqI0vMheC/6NE/J1Yveaj8H+ela+YwWTjq5PGmuhA==} dependencies: - '@types/node': 20.4.7 + '@types/node': 20.9.0 '@types/webidl-conversions': 7.0.0 dev: false @@ -4046,6 +4073,11 @@ packages: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} + /ansi-regex@6.0.1: + resolution: {integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==} + engines: {node: '>=12'} + dev: true + /ansi-styles@3.2.1: resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} engines: {node: '>=4'} @@ -4059,6 +4091,11 @@ packages: dependencies: color-convert: 2.0.1 + /ansi-styles@6.2.1: + resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} + engines: {node: '>=12'} + dev: true + /anymatch@3.1.3: resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} engines: {node: '>= 8'} @@ -4548,6 +4585,11 @@ packages: engines: {node: '>= 6'} dev: false + /commander@9.5.0: + resolution: {integrity: sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==} + engines: {node: ^12.20.0 || >=14} + dev: true + /commondir@1.0.1: resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==} dev: false @@ -4881,6 +4923,10 @@ packages: dev: false optional: true + /eastasianwidth@0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + dev: true + /ecdsa-sig-formatter@1.0.11: resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==} dependencies: @@ -4898,6 +4944,10 @@ packages: /emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + /emoji-regex@9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + dev: true + /enabled@2.0.0: resolution: {integrity: sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==} dev: false @@ -4925,8 +4975,8 @@ packages: engines: {node: '>=10.2.0'} dependencies: '@types/cookie': 0.4.1 - '@types/cors': 2.8.13 - '@types/node': 20.4.7 + '@types/cors': 2.8.16 + '@types/node': 20.9.0 accepts: 1.3.8 base64id: 2.0.0 cookie: 0.4.2 @@ -5380,7 +5430,7 @@ packages: '@fastify/busboy': 1.2.1 '@firebase/database-compat': 0.3.4 '@firebase/database-types': 0.10.4 - '@types/node': 20.4.7 + '@types/node': 20.9.0 jsonwebtoken: 9.0.2 jwks-rsa: 3.0.1 node-forge: 1.3.1 @@ -5427,6 +5477,14 @@ packages: is-callable: 1.2.7 dev: false + /foreground-child@3.1.1: + resolution: {integrity: sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==} + engines: {node: '>=14'} + dependencies: + cross-spawn: 7.0.3 + signal-exit: 4.1.0 + dev: true + /form-data@4.0.0: resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==} engines: {node: '>= 6'} @@ -5541,6 +5599,18 @@ packages: dependencies: is-glob: 4.0.3 + /glob@10.3.10: + resolution: {integrity: sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==} + engines: {node: '>=16 || 14 >=14.17'} + hasBin: true + dependencies: + foreground-child: 3.1.1 + jackspeak: 2.3.6 + minimatch: 9.0.3 + minipass: 7.0.4 + path-scurry: 1.10.1 + dev: true + /glob@7.2.0: resolution: {integrity: sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==} dependencies: @@ -5966,11 +6036,20 @@ packages: engines: {node: '>=0.10.0'} dev: false + /jackspeak@2.3.6: + resolution: {integrity: sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==} + engines: {node: '>=14'} + dependencies: + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 + dev: true + /jest-worker@26.6.2: resolution: {integrity: sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==} engines: {node: '>= 10.13.0'} dependencies: - '@types/node': 20.4.7 + '@types/node': 20.9.0 merge-stream: 2.0.0 supports-color: 7.2.0 dev: false @@ -6107,7 +6186,7 @@ packages: resolution: {integrity: sha512-UUOZ0CVReK1QVU3rbi9bC7N5/le8ziUj0A2ef1Q0M7OPD2KvjEYizptqIxGIo6fSLYDkqBrazILS18tYuRc8gw==} engines: {node: '>=14'} dependencies: - '@types/express': 4.17.17 + '@types/express': 4.17.21 '@types/jsonwebtoken': 9.0.2 debug: 4.3.4 jose: 4.14.4 @@ -6323,6 +6402,13 @@ packages: get-func-name: 2.0.2 dev: true + /lru-cache@10.0.2: + resolution: {integrity: sha512-Yj9mA8fPiVgOUpByoTZO5pNrcl5Yk37FcSHsUINpAsaBIEZIuqcCclDZJCVxqQShDsmYX8QG63svJiTbOATZwg==} + engines: {node: 14 || >=16.14} + dependencies: + semver: 7.5.4 + dev: true + /lru-cache@4.0.2: resolution: {integrity: sha512-uQw9OqphAGiZhkuPlpFGmdTU2tEuhxTourM/19qGJrxBPHAr/f8BT1a0i/lOclESnGatdJG/UCkP9kZB/Lh1iw==} dependencies: @@ -6513,12 +6599,24 @@ packages: dev: false optional: true + /minimatch@9.0.3: + resolution: {integrity: sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==} + engines: {node: '>=16 || 14 >=14.17'} + dependencies: + brace-expansion: 2.0.1 + dev: true + /minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} requiresBuild: true dev: false optional: true + /minipass@7.0.4: + resolution: {integrity: sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==} + engines: {node: '>=16 || 14 >=14.17'} + dev: true + /mkdirp@1.0.4: resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} engines: {node: '>=10'} @@ -6620,6 +6718,11 @@ packages: /ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + /mylas@2.1.13: + resolution: {integrity: sha512-+MrqnJRtxdF+xngFfUUkIMQrUUL0KsxbADUkn23Z/4ibGg192Q+z+CQyiYwvWTsYjJygmMR8+w3ZDa98Zh6ESg==} + engines: {node: '>=12.0.0'} + dev: true + /nanoid@3.3.3: resolution: {integrity: sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} @@ -6890,6 +6993,14 @@ packages: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} dev: false + /path-scurry@1.10.1: + resolution: {integrity: sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==} + engines: {node: '>=16 || 14 >=14.17'} + dependencies: + lru-cache: 10.0.2 + minipass: 7.0.4 + dev: true + /path-to-regexp@0.1.7: resolution: {integrity: sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==} dev: false @@ -6931,6 +7042,13 @@ packages: find-up: 3.0.0 dev: false + /plimit-lit@1.6.1: + resolution: {integrity: sha512-B7+VDyb8Tl6oMJT9oSO2CW8XC/T4UcJGrwOVoNGwOQsQYhlpfajmrMj5xeejqaASq3V/EqThyOeATEOMuSEXiA==} + engines: {node: '>=12'} + dependencies: + queue-lit: 1.5.2 + dev: true + /polished@3.7.2: resolution: {integrity: sha512-pQKtpZGmsZrW8UUpQMAnR7s3ppHeMQVNyMDKtUyKwuvDmklzcEyM5Kllb3JyE/sE/x7arDmyd35i+4vp99H6sQ==} engines: {node: '>=10'} @@ -7150,7 +7268,7 @@ packages: '@protobufjs/path': 1.1.2 '@protobufjs/pool': 1.1.0 '@protobufjs/utf8': 1.1.0 - '@types/node': 20.4.7 + '@types/node': 20.9.0 long: 5.2.3 dev: false optional: true @@ -7170,7 +7288,7 @@ packages: '@protobufjs/path': 1.1.2 '@protobufjs/pool': 1.1.0 '@protobufjs/utf8': 1.1.0 - '@types/node': 20.4.7 + '@types/node': 20.9.0 long: 5.2.3 dev: false optional: true @@ -7217,6 +7335,11 @@ packages: deprecated: The querystring API is considered Legacy. new code should use the URLSearchParams API instead. dev: false + /queue-lit@1.5.2: + resolution: {integrity: sha512-tLc36IOPeMAubu8BkW8YDBV+WyIgKlYU7zUNs0J5Vk9skSZ4JfGlPOqplP0aHdfv7HL0B2Pg6nwiq60Qc6M2Hw==} + engines: {node: '>=12'} + dev: true + /queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} @@ -7618,6 +7741,14 @@ packages: dependencies: glob: 7.2.3 + /rimraf@5.0.5: + resolution: {integrity: sha512-CqDakW+hMe/Bz202FPEymy68P+G50RfMQK+Qo5YUqc9SPipvbGjCGKd0RSKEelbsfQuw3g5NZDSrlZZAJurH1A==} + engines: {node: '>=14'} + hasBin: true + dependencies: + glob: 10.3.10 + dev: true + /rollup-plugin-terser@7.0.2(rollup@2.79.1): resolution: {integrity: sha512-w3iIaU4OxcF52UUXiZNsNeuXIMDvFrr+ZXK6bFZ0Q60qyVfq4uLptoS4bbq3paG3x216eQllFZX7zt6TIImguQ==} deprecated: This package has been deprecated and is no longer maintained. Please use @rollup/plugin-terser @@ -7772,6 +7903,11 @@ packages: resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} dev: false + /signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + dev: true + /simple-swizzle@0.2.2: resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==} dependencies: @@ -7899,6 +8035,15 @@ packages: is-fullwidth-code-point: 3.0.0 strip-ansi: 6.0.1 + /string-width@5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.1.0 + dev: true + /string_decoder@1.3.0: resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} dependencies: @@ -7911,6 +8056,13 @@ packages: dependencies: ansi-regex: 5.0.1 + /strip-ansi@7.1.0: + resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} + engines: {node: '>=12'} + dependencies: + ansi-regex: 6.0.1 + dev: true + /strip-json-comments@3.1.1: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} @@ -8145,6 +8297,18 @@ packages: engines: {node: '>= 14.0.0'} dev: false + /tsc-alias@1.8.8: + resolution: {integrity: sha512-OYUOd2wl0H858NvABWr/BoSKNERw3N9GTi3rHPK8Iv4O1UyUXIrTTOAZNHsjlVpXFOhpJBVARI1s+rzwLivN3Q==} + hasBin: true + dependencies: + chokidar: 3.5.3 + commander: 9.5.0 + globby: 11.1.0 + mylas: 2.1.13 + normalize-path: 3.0.0 + plimit-lit: 1.6.1 + dev: true + /tslib@1.14.1: resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} requiresBuild: true @@ -8196,6 +8360,12 @@ packages: mime-types: 2.1.35 dev: false + /typescript@5.2.2: + resolution: {integrity: sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==} + engines: {node: '>=14.17'} + hasBin: true + dev: true + /uc.micro@1.0.6: resolution: {integrity: sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==} dev: false @@ -8225,6 +8395,9 @@ packages: dev: false optional: true + /undici-types@5.26.5: + resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} + /unicode-canonical-property-names-ecmascript@2.0.0: resolution: {integrity: sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==} engines: {node: '>=4'} @@ -8491,6 +8664,15 @@ packages: string-width: 4.2.3 strip-ansi: 6.0.1 + /wrap-ansi@8.1.0: + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} + engines: {node: '>=12'} + dependencies: + ansi-styles: 6.2.1 + string-width: 5.1.2 + strip-ansi: 7.1.0 + dev: true + /wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} diff --git a/app.js b/src/index.ts similarity index 87% rename from app.js rename to src/index.ts index f8c24842..a24277e9 100644 --- a/app.js +++ b/src/index.ts @@ -1,10 +1,10 @@ -// 모듈 require -const express = require("express"); -const http = require("http"); -const { nodeEnv, port: httpPort, eventConfig } = require("./loadenv"); -const logger = require("./src/modules/logger"); -const { connectDatabase } = require("./src/modules/stores/mongo"); -const { startSocketServer } = require("./src/modules/socket"); +// 모듈 import +import express from "express"; +import http from "http"; +import { nodeEnv, port as httpPort, eventConfig } from "@/loadenv"; +import logger from "@/modules/logger"; +import { connectDatabase } from "@/modules/stores/mongo"; +import { startSocketServer } from "@/modules/socket"; // Firebase Admin 초기설정 require("./src/modules/fcm").initializeApp(); diff --git a/src/loadenv.ts b/src/loadenv.ts new file mode 100644 index 00000000..88cca713 --- /dev/null +++ b/src/loadenv.ts @@ -0,0 +1,59 @@ +// 환경 변수에 따라 .env.production 또는 .env.development 파일을 읽어옵니다. +import dotenv from "dotenv"; + +if (process.env.NODE_ENV === undefined) { + // logger.ts가 아직 초기화되지 않았으므로 console.error를 사용합니다. + console.error("NODE_ENV 환경변수가 설정되어 있지 않습니다."); + process.exit(1); +} +dotenv.config({ path: `./.env.${process.env.NODE_ENV}` }); + +if (process.env.DB_PATH === undefined) { + // logger.ts가 아직 초기화되지 않았으므로 console.error를 사용합니다. + console.error("DB_PATH 환경변수가 설정되어 있지 않습니다."); + process.exit(1); +} + +export const nodeEnv = process.env.NODE_ENV; // required +export const mongo = process.env.DB_PATH; // required +export const session = { + secret: process.env.SESSION_KEY || "TAXI_SESSION_KEY", // optional + expiry: 14 * 24 * 3600 * 1000, // 14일, ms 단위입니다. +}; +export const redis = process.env.REDIS_PATH; // optional +export const sparcssso = { + id: process.env.SPARCSSSO_CLIENT_ID || "", // optional + key: process.env.SPARCSSSO_CLIENT_KEY || "", // optional +}; +export const port = process.env.PORT ? parseInt(process.env.PORT) : 80; // optional (default = 80) +export const corsWhiteList = (process.env.CORS_WHITELIST && + JSON.parse(process.env.CORS_WHITELIST)) || [true]; // optional (default = [true]) +export const aws = { + accessKeyId: process.env.AWS_ACCESS_KEY_ID, // required + secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY, // required + s3BucketName: process.env.AWS_S3_BUCKET_NAME, // required + s3Url: + process.env.AWS_S3_URL || + `https://${process.env.AWS_S3_BUCKET_NAME}.s3.ap-northeast-2.amazonaws.com`, // optional +}; +export const jwt = { + secretKey: process.env.JWT_SECRET_KEY || "TAXI_JWT_KEY", + option: { + algorithm: "HS256", + // FIXME: remove FRONT_URL from issuer. 단, issuer를 변경하면 이전에 발급했던 모든 JWT가 무효화됩니다. + // See https://github.com/sparcs-kaist/taxi-back/issues/415 + issuer: process.env.FRONT_URL || "http://localhost:3000", // optional (default = "http://localhost:3000") + }, + TOKEN_EXPIRED: -3, + TOKEN_INVALID: -2, +}; +export const googleApplicationCredentials = + process.env.GOOGLE_APPLICATION_CREDENTIALS && + JSON.parse(process.env.GOOGLE_APPLICATION_CREDENTIALS); // optional +export const testAccounts = + (process.env.TEST_ACCOUNTS && JSON.parse(process.env.TEST_ACCOUNTS)) || []; // optional +export const slackWebhookUrl = { + report: process.env.SLACK_REPORT_WEBHOOK_URL || "", // optional +}; +export const eventConfig = + process.env.EVENT_CONFIG && JSON.parse(process.env.EVENT_CONFIG); diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 00000000..0f059817 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "target": "es6", + "module": "node16", + "moduleResolution": "node16", + "allowJs": true, + "outDir": "./dist", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "baseUrl": "./src", + "paths": { + "@/*": ["./*"] + } + }, + "include": ["src"], + "exclude": ["dist", "node_modules"] + } + \ No newline at end of file From 972366568fc59790757f69c47c2cff1c3d78ac97 Mon Sep 17 00:00:00 2001 From: withsang Date: Thu, 16 Nov 2023 03:43:45 +0900 Subject: [PATCH 06/61] feat(typescript): type index.ts --- package.json | 2 + pnpm-lock.yaml | 16 +++++++ src/index.ts | 72 ++++++++++++++++++---------- src/loadenv.ts | 4 +- src/middlewares/index.ts | 12 +++++ src/routes/index.ts | 10 ++++ src/schedules/{index.js => index.ts} | 7 +-- tsconfig.json | 2 +- 8 files changed, 93 insertions(+), 32 deletions(-) create mode 100644 src/middlewares/index.ts create mode 100644 src/routes/index.ts rename src/schedules/{index.js => index.ts} (50%) diff --git a/package.json b/package.json index 30d8ba6b..fd1270b2 100644 --- a/package.json +++ b/package.json @@ -57,9 +57,11 @@ "winston-daily-rotate-file": "^4.7.1" }, "devDependencies": { + "@types/cookie-parser": "^1.4.6", "@types/cors": "^2.8.16", "@types/express": "^4.17.21", "@types/node": "^20.9.0", + "@types/node-cron": "^3.0.11", "chai": "^4.3.10", "eslint": "^8.22.0", "eslint-plugin-mocha": "^10.1.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8900aeb3..be691a0e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -109,6 +109,9 @@ dependencies: version: 4.7.1(winston@3.10.0) devDependencies: + '@types/cookie-parser': + specifier: ^1.4.6 + version: 1.4.6 '@types/cors': specifier: ^2.8.16 version: 2.8.16 @@ -118,6 +121,9 @@ devDependencies: '@types/node': specifier: ^20.9.0 version: 20.9.0 + '@types/node-cron': + specifier: ^3.0.11 + version: 3.0.11 chai: specifier: ^4.3.10 version: 4.3.10 @@ -3746,6 +3752,12 @@ packages: dependencies: '@types/node': 20.9.0 + /@types/cookie-parser@1.4.6: + resolution: {integrity: sha512-KoooCrD56qlLskXPLGUiJxOMnv5l/8m7cQD2OxJ73NPMhuSz9PmvwRD6EpjDyKBVrdJDdQ4bQK7JFNHnNmax0w==} + dependencies: + '@types/express': 4.17.21 + dev: true + /@types/cookie@0.4.1: resolution: {integrity: sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==} dev: false @@ -3836,6 +3848,10 @@ packages: dev: false optional: true + /@types/node-cron@3.0.11: + resolution: {integrity: sha512-0ikrnug3/IyneSHqCBeslAhlK2aBfYek1fGo4bP4QnZPmiqSGRK+Oy7ZMisLWkesffJvQ1cqAcBnJC+8+nxIAg==} + dev: true + /@types/node@20.9.0: resolution: {integrity: sha512-nekiGu2NDb1BcVofVcEKMIwzlx4NjHlcjhoxxKBNLtz15Y1z7MYf549DFvkHSId02Ax6kGwWntIBPC3l/JZcmw==} dependencies: diff --git a/src/index.ts b/src/index.ts index a24277e9..67648cde 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,10 +1,34 @@ // 모듈 import import express from "express"; +import cookieParser from "cookie-parser"; import http from "http"; -import { nodeEnv, port as httpPort, eventConfig } from "@/loadenv"; + +import { nodeEnv, port as httpPort } from "@/loadenv"; +import { + corsMiddleware, + sessionMiddleware, + informationMiddleware, + responseTimeMiddleware, + limitRateMiddleware, + originValidatorMiddleware, + errorHandler, +} from "@/middlewares"; +import { + authRouter, + logininfoRouter, + userRouter, + roomRouter, + chatRouter, + locationRouter, + reportRouter, + notificationRouter, + adminRouter, + docsRouter, +} from "@/routes"; import logger from "@/modules/logger"; import { connectDatabase } from "@/modules/stores/mongo"; import { startSocketServer } from "@/modules/socket"; +import registerSchedules from "@/schedules"; // Firebase Admin 초기설정 require("./src/modules/fcm").initializeApp(); @@ -23,50 +47,46 @@ app.use(express.json()); if (nodeEnv === "production") app.set("trust proxy", 2); // [Middleware] CORS 설정 -app.use(require("./src/middlewares/cors")); +app.use(corsMiddleware); // [Middleware] 세션 및 쿠키 -const session = require("./src/middlewares/session"); -app.use(session); -app.use(require("cookie-parser")()); +app.use(sessionMiddleware); +app.use(cookieParser()); // [Middleware] Timestamp 및 clientIP 확인 -app.use(require("./src/middlewares/information")); +app.use(informationMiddleware); // [Middleware] API 접근 기록 및 응답 시간을 http response의 헤더에 기록합니다. -app.use(require("./src/middlewares/responseTime")); +app.use(responseTimeMiddleware); // [Router] admin 페이지는 rate limiting을 적용하지 않습니다. -app.use("/admin", require("./src/routes/admin")); +app.use("/admin", adminRouter); // [Middleware] 모든 요청에 대하여 rate limiting 적용 -app.use(require("./src/middlewares/limitRate")); +app.use(limitRateMiddleware); // [Router] Swagger (API 문서) -app.use("/docs", require("./src/routes/docs")); +app.use("/docs", docsRouter); // 2023 추석 이벤트 전용 라우터입니다. -eventConfig && - app.use( - `/events/${eventConfig.mode}`, - require("./src/lottery").lotteryRouter - ); +// eventConfig && +// app.use(`/events/${eventConfig.mode}`, require("@/lottery").lotteryRouter); // [Middleware] 모든 API 요청에 대하여 origin 검증 -app.use(require("./src/middlewares/originValidator")); +app.use(originValidatorMiddleware); // [Router] APIs -app.use("/auth", require("./src/routes/auth")); -app.use("/logininfo", require("./src/routes/logininfo")); -app.use("/users", require("./src/routes/users")); -app.use("/rooms", require("./src/routes/rooms")); -app.use("/chats", require("./src/routes/chats")); -app.use("/locations", require("./src/routes/locations")); -app.use("/reports", require("./src/routes/reports")); -app.use("/notifications", require("./src/routes/notifications")); +app.use("/auth", authRouter); +app.use("/logininfo", logininfoRouter); +app.use("/users", userRouter); +app.use("/rooms", roomRouter); +app.use("/chats", chatRouter); +app.use("/locations", locationRouter); +app.use("/reports", reportRouter); +app.use("/notifications", notificationRouter); // [Middleware] 전역 에러 핸들러. 에러 핸들러는 router들보다 아래에 등록되어야 합니다. -app.use(require("./src/middlewares/errorHandler")); +app.use(errorHandler); // express 서버 시작 const serverHttp = http @@ -79,4 +99,4 @@ const serverHttp = http app.set("io", startSocketServer(serverHttp)); // [Schedule] 스케줄러 시작 -require("./src/schedules")(app); +registerSchedules(app); diff --git a/src/loadenv.ts b/src/loadenv.ts index 88cca713..6307bdca 100644 --- a/src/loadenv.ts +++ b/src/loadenv.ts @@ -55,5 +55,5 @@ export const testAccounts = export const slackWebhookUrl = { report: process.env.SLACK_REPORT_WEBHOOK_URL || "", // optional }; -export const eventConfig = - process.env.EVENT_CONFIG && JSON.parse(process.env.EVENT_CONFIG); +// export const eventConfig = +// process.env.EVENT_CONFIG && JSON.parse(process.env.EVENT_CONFIG); diff --git a/src/middlewares/index.ts b/src/middlewares/index.ts new file mode 100644 index 00000000..d25fec2b --- /dev/null +++ b/src/middlewares/index.ts @@ -0,0 +1,12 @@ +// middleware를 모아 export합니다. +export * from "./ajv"; +export { default as authMiddleware } from "./auth"; +export { default as authAdminMiddleware } from "./authAdmin"; +export { default as corsMiddleware } from "./cors"; +export { default as errorHandler } from "./errorHandler"; +export { default as informationMiddleware } from "./information"; +export { default as limitRateMiddleware } from "./limitRate"; +export { default as originValidatorMiddleware } from "./originValidator"; +export { default as responseTimeMiddleware } from "./responseTime"; +export { default as sessionMiddleware } from "./session"; +export { default as validatorMiddleware } from "./validator"; diff --git a/src/routes/index.ts b/src/routes/index.ts new file mode 100644 index 00000000..d2084458 --- /dev/null +++ b/src/routes/index.ts @@ -0,0 +1,10 @@ +export { default as adminRouter } from "./admin"; +export { default as authRouter } from "./auth"; +export { default as chatRouter } from "./chats"; +export { default as docsRouter } from "./docs"; +export { default as locationRouter } from "./locations"; +export { default as logininfoRouter } from "./logininfo"; +export { default as notificationRouter } from "./notifications"; +export { default as reportRouter } from "./reports"; +export { default as roomRouter } from "./rooms"; +export { default as userRouter } from "./users"; diff --git a/src/schedules/index.js b/src/schedules/index.ts similarity index 50% rename from src/schedules/index.js rename to src/schedules/index.ts index 97818b92..f57ca9dc 100644 --- a/src/schedules/index.js +++ b/src/schedules/index.ts @@ -1,8 +1,9 @@ -const cron = require("node-cron"); +import { Express } from "express"; +import cron from "node-cron"; -const registerSchedules = (app) => { +const registerSchedules = (app: Express) => { cron.schedule("*/5 * * * *", require("./notifyBeforeDepart")(app)); cron.schedule("*/10 * * * *", require("./notifyAfterArrival")(app)); }; -module.exports = registerSchedules; +export default registerSchedules; diff --git a/tsconfig.json b/tsconfig.json index 0f059817..415e2140 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -15,6 +15,6 @@ } }, "include": ["src"], - "exclude": ["dist", "node_modules"] + "exclude": ["dist", "node_modules", "src/lottery"] } \ No newline at end of file From 5dfb074b0b24fea695d5e36835680fdd74555d69 Mon Sep 17 00:00:00 2001 From: withsang Date: Tue, 28 Nov 2023 21:01:04 +0900 Subject: [PATCH 07/61] Refactor: add path alias --- src/index.ts | 3 ++- src/middlewares/auth.js | 2 +- src/middlewares/authAdmin.js | 4 ++-- src/middlewares/cors.js | 2 +- src/middlewares/errorHandler.js | 2 +- src/middlewares/responseTime.js | 2 +- src/middlewares/session.js | 2 +- src/modules/adminResource.js | 2 +- src/modules/auths/jwt.js | 2 +- src/modules/auths/login.js | 4 ++-- src/modules/fcm.js | 6 +++--- src/modules/logger.js | 2 +- src/modules/slackNotification.js | 4 ++-- src/modules/socket.js | 4 ++-- src/modules/stores/aws.js | 8 +++++--- src/modules/stores/mongo.js | 4 ++-- src/modules/stores/sessionStore.js | 4 ++-- src/routes/admin.js | 10 +++++----- src/routes/auth.js | 10 +++++----- src/routes/chats.js | 8 ++++---- src/routes/locations.js | 2 +- src/routes/logininfo.js | 2 +- src/routes/notifications.js | 8 ++++---- src/routes/reports.js | 6 +++--- src/routes/rooms.js | 6 +++--- src/routes/users.js | 10 +++++----- src/schedules/notifyAfterArrival.js | 8 ++++---- src/schedules/notifyBeforeDepart.js | 6 +++--- src/services/auth.js | 24 ++++++++++-------------- src/services/auth.mobile.js | 15 ++++++--------- src/services/auth.replace.js | 16 ++++++++-------- src/services/chats.js | 12 ++++++------ src/services/locations.js | 4 ++-- src/services/logininfo.js | 6 +++--- src/services/notifications.js | 8 ++++---- src/services/reports.js | 16 ++++++---------- src/services/rooms.js | 8 ++++---- src/services/users.js | 8 ++++---- src/views/emailPage.js | 2 +- 39 files changed, 122 insertions(+), 130 deletions(-) diff --git a/src/index.ts b/src/index.ts index 67648cde..ef692ea5 100644 --- a/src/index.ts +++ b/src/index.ts @@ -25,13 +25,14 @@ import { adminRouter, docsRouter, } from "@/routes"; +import { initializeApp } from "@/modules/fcm"; import logger from "@/modules/logger"; import { connectDatabase } from "@/modules/stores/mongo"; import { startSocketServer } from "@/modules/socket"; import registerSchedules from "@/schedules"; // Firebase Admin 초기설정 -require("./src/modules/fcm").initializeApp(); +initializeApp(); // 익스프레스 서버 생성 const app = express(); diff --git a/src/middlewares/auth.js b/src/middlewares/auth.js index e521f9f4..15d1f5a5 100644 --- a/src/middlewares/auth.js +++ b/src/middlewares/auth.js @@ -1,6 +1,6 @@ // 로그인된 상태에만 접근할 수 있는 라우터(rooms)를 위한 미들웨어입니다. -const { isLogin, getLoginInfo } = require("../modules/auths/login"); +const { isLogin, getLoginInfo } = require("@/modules/auths/login"); const authMiddleware = (req, res, next) => { if (!isLogin(req)) { diff --git a/src/middlewares/authAdmin.js b/src/middlewares/authAdmin.js index 8db1f2a0..34fc5df6 100644 --- a/src/middlewares/authAdmin.js +++ b/src/middlewares/authAdmin.js @@ -1,7 +1,7 @@ // 관리자 유무를 확인하기 위한 미들웨어입니다. -const { isLogin, getLoginInfo } = require("../modules/auths/login"); -const { userModel, adminIPWhitelistModel } = require("../modules/stores/mongo"); +const { isLogin, getLoginInfo } = require("@/modules/auths/login"); +const { userModel, adminIPWhitelistModel } = require("@/modules/stores/mongo"); const authAdminMiddleware = async (req, res, next) => { try { diff --git a/src/middlewares/cors.js b/src/middlewares/cors.js index 0b644243..a1012607 100644 --- a/src/middlewares/cors.js +++ b/src/middlewares/cors.js @@ -1,5 +1,5 @@ var cors = require("cors"); -const { corsWhiteList } = require("../../loadenv"); +const { corsWhiteList } = require("@/loadenv"); module.exports = cors({ origin: corsWhiteList, diff --git a/src/middlewares/errorHandler.js b/src/middlewares/errorHandler.js index 369391c6..d9bdcc0c 100644 --- a/src/middlewares/errorHandler.js +++ b/src/middlewares/errorHandler.js @@ -1,4 +1,4 @@ -const logger = require("../modules/logger"); +const logger = require("@/modules/logger"); /** * Express app에서 사용할 custom global error handler를 정의합니다. diff --git a/src/middlewares/responseTime.js b/src/middlewares/responseTime.js index 1d674364..c042ffb3 100644 --- a/src/middlewares/responseTime.js +++ b/src/middlewares/responseTime.js @@ -1,4 +1,4 @@ -const logger = require("../modules/logger"); +const logger = require("@/modules/logger"); const responseTime = require("response-time"); module.exports = responseTime((req, res, time) => { diff --git a/src/middlewares/session.js b/src/middlewares/session.js index 5412ba1c..b4be3266 100644 --- a/src/middlewares/session.js +++ b/src/middlewares/session.js @@ -1,6 +1,6 @@ const expressSession = require("express-session"); const { nodeEnv, session: sessionConfig } = require("../../loadenv"); -const sessionStore = require("../modules/stores/sessionStore"); +const sessionStore = require("@/modules/stores/sessionStore"); module.exports = expressSession({ secret: sessionConfig.secret, diff --git a/src/modules/adminResource.js b/src/modules/adminResource.js index f5ba3eb8..19deb694 100644 --- a/src/modules/adminResource.js +++ b/src/modules/adminResource.js @@ -1,5 +1,5 @@ const { buildFeature } = require("adminjs"); -const { adminLogModel } = require("./stores/mongo"); +const { adminLogModel } = require("@/modules/stores/mongo"); const createLog = async (req, action, target) => { const newLog = new adminLogModel({ diff --git a/src/modules/auths/jwt.js b/src/modules/auths/jwt.js index 52945347..4e8f7259 100644 --- a/src/modules/auths/jwt.js +++ b/src/modules/auths/jwt.js @@ -1,6 +1,6 @@ const jwt = require("jsonwebtoken"); const { secretKey, option, TOKEN_EXPIRED, TOKEN_INVALID } = - require("../../../loadenv").jwt; + require("@/loadenv").jwt; const signJwt = async ({ id, type }) => { const payload = { diff --git a/src/modules/auths/login.js b/src/modules/auths/login.js index 9c72434f..7c4f6baf 100644 --- a/src/modules/auths/login.js +++ b/src/modules/auths/login.js @@ -1,5 +1,5 @@ -const { session: sessionConfig } = require("../../../loadenv"); -const logger = require("../logger"); +const { session: sessionConfig } = require("@/loadenv"); +const logger = require("@/modules/logger"); const getLoginInfo = (req) => { if (req.session.loginInfo) { diff --git a/src/modules/fcm.js b/src/modules/fcm.js index cf6a0433..c9ab01d6 100644 --- a/src/modules/fcm.js +++ b/src/modules/fcm.js @@ -4,9 +4,9 @@ const { deviceTokenModel, notificationOptionModel, topicSubscriptionModel, -} = require("./stores/mongo"); -const logger = require("../modules/logger"); -const { googleApplicationCredentials } = require("../../loadenv"); +} = require("@/modules/stores/mongo"); +const logger = require("./logger"); +const { googleApplicationCredentials } = require("@/loadenv"); /** * credential을 등록합니다. diff --git a/src/modules/logger.js b/src/modules/logger.js index e532e7aa..3baa80d5 100644 --- a/src/modules/logger.js +++ b/src/modules/logger.js @@ -2,7 +2,7 @@ const path = require("path"); const { createLogger, format, transports } = require("winston"); const DailyRotateFileTransport = require("winston-daily-rotate-file"); -const { nodeEnv } = require("../../loadenv"); +const { nodeEnv } = require("@/loadenv"); // logger에서 사용할 포맷들을 정의합니다. const baseFormat = format.combine( diff --git a/src/modules/slackNotification.js b/src/modules/slackNotification.js index dc00e4a0..4ac3d378 100644 --- a/src/modules/slackNotification.js +++ b/src/modules/slackNotification.js @@ -1,6 +1,6 @@ -const { slackWebhookUrl: slackUrl } = require("../../loadenv"); +const { slackWebhookUrl: slackUrl } = require("@/loadenv"); const axios = require("axios"); -const logger = require("../modules/logger"); +const logger = require("./logger"); module.exports.notifyToReportChannel = (reportUser, report) => { if (!slackUrl.report) return; diff --git a/src/modules/socket.js b/src/modules/socket.js index bca99161..eabbbe62 100644 --- a/src/modules/socket.js +++ b/src/modules/socket.js @@ -1,13 +1,13 @@ const { Server } = require("socket.io"); -const sessionMiddleware = require("../middlewares/session"); +const sessionMiddleware = require("@/middlewares/session"); const logger = require("./logger"); const { getLoginInfo } = require("./auths/login"); const { roomModel, userModel, chatModel } = require("./stores/mongo"); const { getS3Url } = require("./stores/aws"); const { getTokensOfUsers, sendMessageByTokens } = require("./fcm"); -const { corsWhiteList } = require("../../loadenv"); +const { corsWhiteList } = require("@/loadenv"); const { chatPopulateOption } = require("./populates/chats"); /** diff --git a/src/modules/stores/aws.js b/src/modules/stores/aws.js index bade704b..2947410c 100644 --- a/src/modules/stores/aws.js +++ b/src/modules/stores/aws.js @@ -1,6 +1,6 @@ -const { aws: awsEnv } = require("../../../loadenv"); +const { aws: awsEnv } = require("@/loadenv"); -const logger = require("../logger"); +const logger = require("@/modules/logger"); // Load the AWS-SDK and s3 const AWS = require("aws-sdk"); AWS.config.update({ @@ -103,7 +103,9 @@ module.exports.sendReportEmail = (reportedEmail, report, html) => { }, Subject: { Charset: "UTF-8", - Data: `[SPARCS TAXI] 신고가 접수되었습니다 (사유: ${reportTypeMap[report.type]})`, + Data: `[SPARCS TAXI] 신고가 접수되었습니다 (사유: ${ + reportTypeMap[report.type] + })`, }, }, Source: "taxi.sparcs@gmail.com", diff --git a/src/modules/stores/mongo.js b/src/modules/stores/mongo.js index f05f266a..f4741652 100755 --- a/src/modules/stores/mongo.js +++ b/src/modules/stores/mongo.js @@ -1,8 +1,8 @@ const mongoose = require("mongoose"); const Schema = mongoose.Schema; -const { mongo: mongoUrl } = require("../../../loadenv"); -const logger = require("../logger"); +const { mongo: mongoUrl } = require("@/loadenv"); +const logger = require("@/modules/logger"); const userSchema = Schema({ name: { type: String, required: true }, //실명 diff --git a/src/modules/stores/sessionStore.js b/src/modules/stores/sessionStore.js index fca4da55..3d247d54 100644 --- a/src/modules/stores/sessionStore.js +++ b/src/modules/stores/sessionStore.js @@ -6,8 +6,8 @@ const { redis: redisUrl, mongo: mongoUrl, session: sessionConfig, -} = require("../../../loadenv"); -const logger = require("../logger"); +} = require("@/loadenv"); +const logger = require("@/modules/logger"); const getSessionStore = (redisUrl) => { // 환경변수 REDIS_PATH 유무에 따라 session 저장 방식이 변경됩니다. diff --git a/src/routes/admin.js b/src/routes/admin.js index e2ddaae4..a621db61 100644 --- a/src/routes/admin.js +++ b/src/routes/admin.js @@ -12,15 +12,15 @@ const { adminLogModel, deviceTokenModel, notificationOptionModel, -} = require("../modules/stores/mongo"); -const { eventConfig } = require("../../loadenv"); -const { buildResource } = require("../modules/adminResource"); +} = require("@/modules/stores/mongo"); +const { eventConfig } = require("@/loadenv"); +const { buildResource } = require("@/modules/adminResource"); const router = express.Router(); // Requires admin property of the user to enter admin page. -router.use(require("../middlewares/authAdmin")); -router.use(require("../middlewares/auth")); +router.use(require("@/middlewares/authAdmin")); +router.use(require("@/middlewares/auth")); // Registration of the mongoose adapter AdminJS.registerAdapter(AdminJSMongoose); diff --git a/src/routes/auth.js b/src/routes/auth.js index 7b29a267..9ce57404 100644 --- a/src/routes/auth.js +++ b/src/routes/auth.js @@ -1,14 +1,14 @@ const express = require("express"); const router = express.Router(); const { body, query } = require("express-validator"); -const validator = require("../middlewares/validator"); +const validator = require("@/middlewares/validator"); -const authHandlers = require("../services/auth"); -const authReplaceHandlers = require("../services/auth.replace"); -const mobileAuthHandlers = require("../services/auth.mobile"); +const authHandlers = require("@/services/auth"); +const authReplaceHandlers = require("@/services/auth.replace"); +const mobileAuthHandlers = require("@/services/auth.mobile"); // 환경변수 SPARCSSSO_CLIENT_ID 유무에 따라 로그인 방식이 변경됩니다. -const { sparcssso: sparcsssoEnv } = require("../../loadenv"); +const { sparcssso: sparcsssoEnv } = require("@/loadenv"); const isAuthReplace = !sparcsssoEnv?.id; // 로그인 페이지로 redirect합니다. diff --git a/src/routes/chats.js b/src/routes/chats.js index f689348c..f27ace38 100644 --- a/src/routes/chats.js +++ b/src/routes/chats.js @@ -1,13 +1,13 @@ const express = require("express"); const { body } = require("express-validator"); -const validator = require("../middlewares/validator"); -const patterns = require("../modules/patterns"); +const validator = require("@/middlewares/validator"); +const patterns = require("@/modules/patterns"); const router = express.Router(); -const chatsHandlers = require("../services/chats"); +const chatsHandlers = require("@/services/chats"); // 라우터 접근 시 로그인 필요 -router.use(require("../middlewares/auth")); +router.use(require("@/middlewares/auth")); /** * 가장 최근에 도착한 60개의 채팅을 가져옵니다. diff --git a/src/routes/locations.js b/src/routes/locations.js index 7724d371..20b16ada 100644 --- a/src/routes/locations.js +++ b/src/routes/locations.js @@ -1,7 +1,7 @@ const express = require("express"); const router = express.Router(); -const locationsHandlers = require("../services/locations"); +const locationsHandlers = require("@/services/locations"); router.get("/", locationsHandlers.getAllLocationsHandler); diff --git a/src/routes/logininfo.js b/src/routes/logininfo.js index a3d9d4f7..ab8c32e9 100644 --- a/src/routes/logininfo.js +++ b/src/routes/logininfo.js @@ -1,7 +1,7 @@ const express = require("express"); const router = express.Router(); -const logininfoHandlers = require("../services/logininfo"); +const logininfoHandlers = require("@/services/logininfo"); router.route("/").get(logininfoHandlers.logininfoHandler); diff --git a/src/routes/notifications.js b/src/routes/notifications.js index dd666953..89ec5be9 100644 --- a/src/routes/notifications.js +++ b/src/routes/notifications.js @@ -1,12 +1,12 @@ const express = require("express"); const router = express.Router(); -const { query, body } = require("express-validator"); +const { body } = require("express-validator"); -const notificationHandlers = require("../services/notifications"); -const validator = require("../middlewares/validator"); +const notificationHandlers = require("@/services/notifications"); +const validator = require("@/middlewares/validator"); // 라우터 접근 시 로그인 필요 -router.use(require("../middlewares/auth")); +router.use(require("@/middlewares/auth")); // FCM 토큰 등록 router.post( diff --git a/src/routes/reports.js b/src/routes/reports.js index 07fcbf51..9c1f79d7 100644 --- a/src/routes/reports.js +++ b/src/routes/reports.js @@ -1,11 +1,11 @@ const express = require("express"); const reportsSchema = require("./docs/reportsSchema"); -const { validateBody } = require("../middlewares/ajv"); +const { validateBody } = require("@/middlewares/ajv"); const router = express.Router(); -const reportHandlers = require("../services/reports"); +const reportHandlers = require("@/services/reports"); // 라우터 접근 시 로그인 필요 -router.use(require("../middlewares/auth")); +router.use(require("@/middlewares/auth")); router.post( "/create", diff --git a/src/routes/rooms.js b/src/routes/rooms.js index be1c3f59..240b2f74 100644 --- a/src/routes/rooms.js +++ b/src/routes/rooms.js @@ -2,9 +2,9 @@ const express = require("express"); const { query, body } = require("express-validator"); const router = express.Router(); -const roomHandlers = require("../services/rooms"); -const validator = require("../middlewares/validator"); -const patterns = require("../modules/patterns"); +const roomHandlers = require("@/services/rooms"); +const validator = require("@/middlewares/validator"); +const patterns = require("@/modules/patterns"); // 조건(이름, 출발지, 도착지, 날짜)에 맞는 방들을 모두 반환한다. router.get( diff --git a/src/routes/users.js b/src/routes/users.js index 31bde597..9bc1d4eb 100755 --- a/src/routes/users.js +++ b/src/routes/users.js @@ -1,15 +1,15 @@ const express = require("express"); const { body } = require("express-validator"); -const validator = require("../middlewares/validator"); -const patterns = require("../modules/patterns"); +const validator = require("@/middlewares/validator"); +const patterns = require("@/modules/patterns"); const router = express.Router(); -const userHandlers = require("../services/users"); +const userHandlers = require("@/services/users"); -const { replaceSpaceInNickname } = require("../modules/modifyProfile"); +const { replaceSpaceInNickname } = require("@/modules/modifyProfile"); // 라우터 접근 시 로그인 필요 -router.use(require("../middlewares/auth")); +router.use(require("@/middlewares/auth")); // 이용 약관에 동의합니다. router.post( diff --git a/src/schedules/notifyAfterArrival.js b/src/schedules/notifyAfterArrival.js index 3202af08..5c1a5a6c 100644 --- a/src/schedules/notifyAfterArrival.js +++ b/src/schedules/notifyAfterArrival.js @@ -1,7 +1,7 @@ -const { roomModel, chatModel } = require("../modules/stores/mongo"); -// const { roomPopulateOption } = require("../modules/populates/rooms"); -const { emitChatEvent } = require("../modules/socket"); -const logger = require("../modules/logger"); +const { roomModel, chatModel } = require("@/modules/stores/mongo"); +// const { roomPopulateOption } = require("@/modules/populates/rooms"); +const { emitChatEvent } = require("@/modules/socket"); +const logger = require("@/modules/logger"); const MS_PER_MINUTE = 60000; diff --git a/src/schedules/notifyBeforeDepart.js b/src/schedules/notifyBeforeDepart.js index ffe66386..523f6dff 100644 --- a/src/schedules/notifyBeforeDepart.js +++ b/src/schedules/notifyBeforeDepart.js @@ -1,6 +1,6 @@ -const { roomModel, chatModel } = require("../modules/stores/mongo"); -const { emitChatEvent } = require("../modules/socket"); -const logger = require("../modules/logger"); +const { roomModel, chatModel } = require("@/modules/stores/mongo"); +const { emitChatEvent } = require("@/modules/socket"); +const logger = require("@/modules/logger"); const MS_PER_MINUTE = 60000; diff --git a/src/services/auth.js b/src/services/auth.js index 83b598de..2169aeb6 100644 --- a/src/services/auth.js +++ b/src/services/auth.js @@ -1,23 +1,19 @@ -const { - sparcssso: sparcsssoEnv, - nodeEnv, - testAccounts, -} = require("../../loadenv"); -const { userModel } = require("../modules/stores/mongo"); -const { user: userPattern } = require("../modules/patterns"); -const { getLoginInfo, logout, login } = require("../modules/auths/login"); - -const { unregisterDeviceToken } = require("../modules/fcm"); +const { sparcssso: sparcsssoEnv, nodeEnv, testAccounts } = require("@/loadenv"); +const { userModel } = require("@/modules/stores/mongo"); +const { user: userPattern } = require("@/modules/patterns"); +const { getLoginInfo, logout, login } = require("@/modules/auths/login"); + +const { unregisterDeviceToken } = require("@/modules/fcm"); const { generateNickname, generateProfileImageUrl, getFullUsername, -} = require("../modules/modifyProfile"); -const jwt = require("../modules/auths/jwt"); -const logger = require("../modules/logger"); +} = require("@/modules/modifyProfile"); +const jwt = require("@/modules/auths/jwt"); +const logger = require("@/modules/logger"); // SPARCS SSO -const Client = require("../modules/auths/sparcssso"); +const Client = require("@/modules/auths/sparcssso"); const client = new Client(sparcsssoEnv?.id, sparcsssoEnv?.key); const transUserData = (userData) => { diff --git a/src/services/auth.mobile.js b/src/services/auth.mobile.js index 0e537b33..7dc2798a 100644 --- a/src/services/auth.mobile.js +++ b/src/services/auth.mobile.js @@ -1,14 +1,11 @@ -const { userModel } = require("../modules/stores/mongo"); -const { login } = require("../modules/auths/login"); +const { userModel } = require("@/modules/stores/mongo"); +const { login } = require("@/modules/auths/login"); -const { - registerDeviceToken, - unregisterDeviceToken, -} = require("../modules/fcm"); -const jwt = require("../modules/auths/jwt"); -const logger = require("../modules/logger"); +const { registerDeviceToken, unregisterDeviceToken } = require("@/modules/fcm"); +const jwt = require("@/modules/auths/jwt"); +const logger = require("@/modules/logger"); -const { TOKEN_EXPIRED, TOKEN_INVALID } = require("../../loadenv").jwt; +const { TOKEN_EXPIRED, TOKEN_INVALID } = require("@/loadenv").jwt; const tokenLoginHandler = async (req, res) => { const { accessToken, deviceToken } = req.query; diff --git a/src/services/auth.replace.js b/src/services/auth.replace.js index a433e6f6..a054ea43 100644 --- a/src/services/auth.replace.js +++ b/src/services/auth.replace.js @@ -1,16 +1,16 @@ -const { userModel } = require("../modules/stores/mongo"); -const { logout, login } = require("../modules/auths/login"); +const { userModel } = require("@/modules/stores/mongo"); +const { logout, login } = require("@/modules/auths/login"); -const { unregisterDeviceToken } = require("../modules/fcm"); +const { unregisterDeviceToken } = require("@/modules/fcm"); const { generateNickname, generateProfileImageUrl, -} = require("../modules/modifyProfile"); -const logger = require("../modules/logger"); -const jwt = require("../modules/auths/jwt"); +} = require("@/modules/modifyProfile"); +const logger = require("@/modules/logger"); +const jwt = require("@/modules/auths/jwt"); -const { registerDeviceTokenHandler, tryLogin } = require("../services/auth"); -const loginReplacePage = require("../views/loginReplacePage"); +const { registerDeviceTokenHandler, tryLogin } = require("@/services/auth"); +const loginReplacePage = require("@/views/loginReplacePage"); const createUserData = (id) => { const info = { diff --git a/src/services/chats.js b/src/services/chats.js index abcbd073..921c4946 100644 --- a/src/services/chats.js +++ b/src/services/chats.js @@ -1,13 +1,13 @@ -const { chatModel, userModel, roomModel } = require("../modules/stores/mongo"); -const { chatPopulateOption } = require("../modules/populates/chats"); -const { roomPopulateOption } = require("../modules/populates/rooms"); -const aws = require("../modules/stores/aws"); +const { chatModel, userModel, roomModel } = require("@/modules/stores/mongo"); +const { chatPopulateOption } = require("@/modules/populates/chats"); +const { roomPopulateOption } = require("@/modules/populates/rooms"); +const aws = require("@/modules/stores/aws"); const { transformChatsForRoom, emitChatEvent, emitUpdateEvent, -} = require("../modules/socket"); -const logger = require("../modules/logger"); +} = require("@/modules/socket"); +const logger = require("@/modules/logger"); const chatCount = 60; diff --git a/src/services/locations.js b/src/services/locations.js index 5b02042d..ed81ed43 100644 --- a/src/services/locations.js +++ b/src/services/locations.js @@ -1,5 +1,5 @@ -const { locationModel } = require("../modules/stores/mongo"); -const logger = require("../modules/logger"); +const { locationModel } = require("@/modules/stores/mongo"); +const logger = require("@/modules/logger"); const getAllLocationsHandler = async (_, res) => { try { diff --git a/src/services/logininfo.js b/src/services/logininfo.js index eebb09ff..b074d847 100644 --- a/src/services/logininfo.js +++ b/src/services/logininfo.js @@ -1,6 +1,6 @@ -const { userModel } = require("../modules/stores/mongo"); -const { getLoginInfo } = require("../modules/auths/login"); -const logger = require("../modules/logger"); +const { userModel } = require("@/modules/stores/mongo"); +const { getLoginInfo } = require("@/modules/auths/login"); +const logger = require("@/modules/logger"); const logininfoHandler = async (req, res) => { try { diff --git a/src/services/notifications.js b/src/services/notifications.js index 633f6739..7134fab5 100644 --- a/src/services/notifications.js +++ b/src/services/notifications.js @@ -1,8 +1,8 @@ -const { userModel } = require("../modules/stores/mongo"); -const { notificationOptionModel } = require("../modules/stores/mongo"); -const logger = require("../modules/logger"); +const { userModel } = require("@/modules/stores/mongo"); +const { notificationOptionModel } = require("@/modules/stores/mongo"); +const logger = require("@/modules/logger"); -const { registerDeviceToken, validateDeviceToken } = require("../modules/fcm"); +const { registerDeviceToken, validateDeviceToken } = require("@/modules/fcm"); // 이벤트 코드입니다. const { contracts } = require("../lottery"); diff --git a/src/services/reports.js b/src/services/reports.js index 0451b0cc..be981f4a 100644 --- a/src/services/reports.js +++ b/src/services/reports.js @@ -1,13 +1,9 @@ -const { - userModel, - reportModel, - roomModel, -} = require("../modules/stores/mongo"); -const { reportPopulateOption } = require("../modules/populates/reports"); -const { sendReportEmail } = require("../modules/stores/aws"); -const logger = require("../modules/logger"); -const emailPage = require("../views/emailNoSettlementPage"); -const { notifyToReportChannel } = require("../modules/slackNotification"); +const { userModel, reportModel, roomModel } = require("@/modules/stores/mongo"); +const { reportPopulateOption } = require("@/modules/populates/reports"); +const { sendReportEmail } = require("@/modules/stores/aws"); +const logger = require("@/modules/logger"); +const emailPage = require("@/views/emailNoSettlementPage"); +const { notifyToReportChannel } = require("@/modules/slackNotification"); const createHandler = async (req, res) => { try { diff --git a/src/services/rooms.js b/src/services/rooms.js index d4b7557e..fed1ae66 100644 --- a/src/services/rooms.js +++ b/src/services/rooms.js @@ -2,14 +2,14 @@ const { roomModel, locationModel, userModel, -} = require("../modules/stores/mongo"); -const { emitChatEvent } = require("../modules/socket"); -const logger = require("../modules/logger"); +} = require("@/modules/stores/mongo"); +const { emitChatEvent } = require("@/modules/socket"); +const logger = require("@/modules/logger"); const { roomPopulateOption, formatSettlement, getIsOver, -} = require("../modules/populates/rooms"); +} = require("@/modules/populates/rooms"); // 이벤트 코드입니다. const { contracts } = require("../lottery"); diff --git a/src/services/users.js b/src/services/users.js index 3c26b164..73f79dde 100644 --- a/src/services/users.js +++ b/src/services/users.js @@ -1,13 +1,13 @@ -const { userModel } = require("../modules/stores/mongo"); -const logger = require("../modules/logger"); -const aws = require("../modules/stores/aws"); +const { userModel } = require("@/modules/stores/mongo"); +const logger = require("@/modules/logger"); +const aws = require("@/modules/stores/aws"); // 이벤트 코드입니다. const { contracts } = require("../lottery"); const { generateNickname, generateProfileImageUrl, -} = require("../modules/modifyProfile"); +} = require("@/modules/modifyProfile"); const agreeOnTermsOfServiceHandler = async (req, res) => { try { diff --git a/src/views/emailPage.js b/src/views/emailPage.js index d99dafd1..d89f297a 100644 --- a/src/views/emailPage.js +++ b/src/views/emailPage.js @@ -1,4 +1,4 @@ -const { getS3Url } = require("../modules/stores/aws"); +const { getS3Url } = require("@/modules/stores/aws"); module.exports = ( title, From f8cbff856da398f6d8cdcecb72770ed463394bdf Mon Sep 17 00:00:00 2001 From: withsang Date: Tue, 28 Nov 2023 22:32:15 +0900 Subject: [PATCH 08/61] Add: add rules for typescript-eslint --- .eslintrc.cjs | 73 +++- .prettierignore | 2 - package.json | 10 +- pnpm-lock.yaml | 1080 ++++++++++++++++++++++++++++++++++++++++++----- 4 files changed, 1057 insertions(+), 108 deletions(-) diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 37ae58c8..5812d98a 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -1,15 +1,78 @@ module.exports = { env: { - commonjs: true, es2021: true, node: true, }, - extends: ["eslint:recommended", "prettier", "plugin:mocha/recommended"], + extends: [ + "plugin:@typescript-eslint/recommended", + "airbnb-base", + "airbnb-typescript/base", + "plugin:mocha/recommended", + "prettier", + ], + overrides: [ + { + env: { + node: true, + mocha: true, + }, + files: [".eslintrc.{js,cjs}"], + parserOptions: { + sourceType: "script", + }, + }, + ], + parser: "@typescript-eslint/parser", parserOptions: { - ecmaVersion: 13, + ecmaVersion: "latest", + sourceType: "module", + project: "./tsconfig.json", }, + plugins: ["import", "@typescript-eslint", "mocha"], rules: { - "no-unused-vars": 1, - "mocha/no-mocha-arrows": 0, + "import/extensions": [ + "error", + "ignorePackages", + { + ts: "never", + }, + ], + "import/named": "error", + "import/no-extraneous-dependencies": [ + "error", + { + packageDir: "./", + }, + ], + "mocha/no-mocha-arrows": "off", + "no-restricted-imports": [ + "error", + { + patterns: [ + { + group: ["../*"], + message: + "Usage of relative parent imports is not allowed. Use path alias instead.", + }, + ], + }, + ], + radix: ["error", "as-needed"], + "@typescript-eslint/consistent-type-imports": [ + "error", + { + prefer: "type-imports", + }, + ], + }, + settings: { + "import/parsers": { + "@typescript-eslint/parser": [".ts"], + }, + "import/resolver": { + typescript: { + project: ["./tsconfig.json"], + }, + }, }, }; diff --git a/.prettierignore b/.prettierignore index 15e64178..e0ed084a 100644 --- a/.prettierignore +++ b/.prettierignore @@ -2,6 +2,4 @@ node_modules/ dist/ package.json tsconfig.json -.prettierrc.json -.eslintrc.cjs nodemon.json \ No newline at end of file diff --git a/package.json b/package.json index fd1270b2..1f74e2a1 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "build": "tsc && tsc-alias", "clean": "rimraf dist/", "serve": "cross-env TZ='Asia/Seoul' NODE_ENV=production node dist/index.js", - "lint": "npx eslint --fix .", + "lint": "pnpm eslint .", "sample": "cd sampleGenerator && npm start && cd .." }, "engines": { @@ -36,7 +36,6 @@ "cors": "^2.8.5", "cross-env": "^7.0.3", "dotenv": "^16.0.1", - "eslint-config-prettier": "^8.3.0", "express": "^4.17.1", "express-formidable": "^1.2.0", "express-rate-limit": "^7.1.0", @@ -62,8 +61,15 @@ "@types/express": "^4.17.21", "@types/node": "^20.9.0", "@types/node-cron": "^3.0.11", + "@typescript-eslint/eslint-plugin": "^6.13.1", + "@typescript-eslint/parser": "^6.13.1", "chai": "^4.3.10", "eslint": "^8.22.0", + "eslint-config-airbnb-base": "^15.0.0", + "eslint-config-airbnb-typescript": "^17.1.0", + "eslint-config-prettier": "^8.3.0", + "eslint-import-resolver-typescript": "^3.6.1", + "eslint-plugin-import": "^2.29.0", "eslint-plugin-mocha": "^10.1.0", "mocha": "^10.2.0", "nodemon": "^3.0.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index be691a0e..c08ab98a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -50,9 +50,6 @@ dependencies: dotenv: specifier: ^16.0.1 version: 16.3.1 - eslint-config-prettier: - specifier: ^8.3.0 - version: 8.3.0(eslint@8.22.0) express: specifier: ^4.17.1 version: 4.18.2 @@ -124,12 +121,33 @@ devDependencies: '@types/node-cron': specifier: ^3.0.11 version: 3.0.11 + '@typescript-eslint/eslint-plugin': + specifier: ^6.13.1 + version: 6.13.1(@typescript-eslint/parser@6.13.1)(eslint@8.22.0)(typescript@5.2.2) + '@typescript-eslint/parser': + specifier: ^6.13.1 + version: 6.13.1(eslint@8.22.0)(typescript@5.2.2) chai: specifier: ^4.3.10 version: 4.3.10 eslint: specifier: ^8.22.0 version: 8.22.0 + eslint-config-airbnb-base: + specifier: ^15.0.0 + version: 15.0.0(eslint-plugin-import@2.29.0)(eslint@8.22.0) + eslint-config-airbnb-typescript: + specifier: ^17.1.0 + version: 17.1.0(@typescript-eslint/eslint-plugin@6.13.1)(@typescript-eslint/parser@6.13.1)(eslint-plugin-import@2.29.0)(eslint@8.22.0) + eslint-config-prettier: + specifier: ^8.3.0 + version: 8.3.0(eslint@8.22.0) + eslint-import-resolver-typescript: + specifier: ^3.6.1 + version: 3.6.1(@typescript-eslint/parser@6.13.1)(eslint-plugin-import@2.29.0)(eslint@8.22.0) + eslint-plugin-import: + specifier: ^2.29.0 + version: 2.29.0(@typescript-eslint/parser@6.13.1)(eslint-import-resolver-typescript@3.6.1)(eslint@8.22.0) eslint-plugin-mocha: specifier: ^10.1.0 version: 10.1.0(eslint@8.22.0) @@ -157,6 +175,7 @@ packages: /@aashutoshrathi/word-wrap@1.2.6: resolution: {integrity: sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==} engines: {node: '>=0.10.0'} + dev: true /@adminjs/design-system@3.1.8(@types/react@18.2.18)(prop-types@15.8.1)(react-dom@18.2.0)(react@18.2.0)(styled-components@5.3.11): resolution: {integrity: sha512-M0l8NXoHKFoJ9XLv6BkrgRPnE0hCYNYWVNiQKA4qOpzifB2LAPAViqQ36Qyxgz1mL9nnzl7OJpGlb8cHSrIajg==} @@ -816,10 +835,10 @@ packages: '@babel/helpers': 7.22.6 '@babel/parser': 7.22.7 '@babel/template': 7.22.5 - '@babel/traverse': 7.22.8 + '@babel/traverse': 7.22.8(supports-color@5.5.0) '@babel/types': 7.22.5 convert-source-map: 1.9.0 - debug: 4.3.4 + debug: 4.3.4(supports-color@5.5.0) gensync: 1.0.0-beta.2 json5: 2.2.3 semver: 6.3.1 @@ -903,7 +922,7 @@ packages: '@babel/core': 7.22.9 '@babel/helper-compilation-targets': 7.22.9(@babel/core@7.22.9) '@babel/helper-plugin-utils': 7.22.5 - debug: 4.3.4 + debug: 4.3.4(supports-color@5.5.0) lodash.debounce: 4.0.8 resolve: 1.22.4 transitivePeerDependencies: @@ -1044,7 +1063,7 @@ packages: engines: {node: '>=6.9.0'} dependencies: '@babel/template': 7.22.5 - '@babel/traverse': 7.22.8 + '@babel/traverse': 7.22.8(supports-color@5.5.0) '@babel/types': 7.22.5 transitivePeerDependencies: - supports-color @@ -2080,24 +2099,6 @@ packages: '@babel/types': 7.22.5 dev: false - /@babel/traverse@7.22.8: - resolution: {integrity: sha512-y6LPR+wpM2I3qJrsheCTwhIinzkETbplIgPBbwvqPKc+uljeA5gP+3nP8irdYt1mjQaDnlIcG+dw8OjAco4GXw==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/code-frame': 7.22.5 - '@babel/generator': 7.22.9 - '@babel/helper-environment-visitor': 7.22.5 - '@babel/helper-function-name': 7.22.5 - '@babel/helper-hoist-variables': 7.22.5 - '@babel/helper-split-export-declaration': 7.22.6 - '@babel/parser': 7.22.7 - '@babel/types': 7.22.5 - debug: 4.3.4 - globals: 11.12.0 - transitivePeerDependencies: - - supports-color - dev: false - /@babel/traverse@7.22.8(supports-color@5.5.0): resolution: {integrity: sha512-y6LPR+wpM2I3qJrsheCTwhIinzkETbplIgPBbwvqPKc+uljeA5gP+3nP8irdYt1mjQaDnlIcG+dw8OjAco4GXw==} engines: {node: '>=6.9.0'} @@ -2262,12 +2263,27 @@ packages: resolution: {integrity: sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww==} dev: false + /@eslint-community/eslint-utils@4.4.0(eslint@8.22.0): + resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + dependencies: + eslint: 8.22.0 + eslint-visitor-keys: 3.4.2 + dev: true + + /@eslint-community/regexpp@4.10.0: + resolution: {integrity: sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + dev: true + /@eslint/eslintrc@1.4.1: resolution: {integrity: sha512-XXrH9Uarn0stsyldqDYq8r++mROmWRI1xKMXa640Bb//SY1+ECYX6VzT6Lcx5frD0V30XieqJ0oX9I2Xj5aoMA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: ajv: 6.12.6 - debug: 4.3.4 + debug: 4.3.4(supports-color@5.5.0) espree: 9.6.1 globals: 13.20.0 ignore: 5.2.4 @@ -2277,6 +2293,7 @@ packages: strip-json-comments: 3.1.1 transitivePeerDependencies: - supports-color + dev: true /@fastify/busboy@1.2.1: resolution: {integrity: sha512-7PQA7EH43S0CxcOa9OeAnaeA0oQ+e/DHNPZwSQM9CQHW76jle5+OvLdibRp/Aafs9KXbLhxyjOTkRjWUbQEd3Q==} @@ -2476,16 +2493,19 @@ packages: engines: {node: '>=10.10.0'} dependencies: '@humanwhocodes/object-schema': 1.2.1 - debug: 4.3.4 + debug: 4.3.4(supports-color@5.5.0) minimatch: 3.1.2 transitivePeerDependencies: - supports-color + dev: true /@humanwhocodes/gitignore-to-minimatch@1.0.2: resolution: {integrity: sha512-rSqmMJDdLFUsyxR6FMtD00nfQKKLFb1kv+qBbOVKqErvloEIJLo5bDTJTQNTYgeyp78JsA7u/NPi5jT1GR/MuA==} + dev: true /@humanwhocodes/object-schema@1.2.1: resolution: {integrity: sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==} + dev: true /@hypnosphi/create-react-context@0.3.1(prop-types@15.8.1)(react@18.2.0): resolution: {integrity: sha512-V1klUed202XahrWJLLOT3EXNeCpFHCcJntdFGI15ntCwau+jfT386w7OFTMaCqOgXUH1fa0w/I1oZs+i/Rfr0A==} @@ -2575,10 +2595,12 @@ packages: dependencies: '@nodelib/fs.stat': 2.0.5 run-parallel: 1.2.0 + dev: true /@nodelib/fs.stat@2.0.5: resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} engines: {node: '>= 8'} + dev: true /@nodelib/fs.walk@1.2.8: resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} @@ -2586,6 +2608,7 @@ packages: dependencies: '@nodelib/fs.scandir': 2.1.5 fastq: 1.15.0 + dev: true /@pkgjs/parseargs@0.11.0: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} @@ -3806,6 +3829,14 @@ packages: /@types/http-errors@2.0.1: resolution: {integrity: sha512-/K3ds8TRAfBvi5vfjuz8y6+GiAYBZ0x4tXv1Av6CWBWn0IlADc+ZX9pMq7oU0fNQPnBwIZl3rmeLp6SBApbxSQ==} + /@types/json-schema@7.0.15: + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + dev: true + + /@types/json5@0.0.29: + resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} + dev: true + /@types/jsonwebtoken@9.0.2: resolution: {integrity: sha512-drE6uz7QBKq1fYqqoFKTDRdFCPHd5TCub75BM+D+cMx7NU9hUz7SESLfC2fSCXVFMO5Yj8sOWHuGqPgjc+fz0Q==} dependencies: @@ -3912,6 +3943,10 @@ packages: resolution: {integrity: sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ==} dev: false + /@types/semver@7.5.6: + resolution: {integrity: sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==} + dev: true + /@types/send@0.17.1: resolution: {integrity: sha512-Cwo8LE/0rnvX7kIIa3QHCkcuF21c05Ayb0ZfxPiv0W8VRiZiNW/WuRupHKpqqGVGf7SUA44QSOUKaEd9lIrd/Q==} dependencies: @@ -3948,6 +3983,137 @@ packages: '@types/webidl-conversions': 7.0.0 dev: false + /@typescript-eslint/eslint-plugin@6.13.1(@typescript-eslint/parser@6.13.1)(eslint@8.22.0)(typescript@5.2.2): + resolution: {integrity: sha512-5bQDGkXaxD46bPvQt08BUz9YSaO4S0fB1LB5JHQuXTfkGPI3+UUeS387C/e9jRie5GqT8u5kFTrMvAjtX4O5kA==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + '@typescript-eslint/parser': ^6.0.0 || ^6.0.0-alpha + eslint: ^7.0.0 || ^8.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@eslint-community/regexpp': 4.10.0 + '@typescript-eslint/parser': 6.13.1(eslint@8.22.0)(typescript@5.2.2) + '@typescript-eslint/scope-manager': 6.13.1 + '@typescript-eslint/type-utils': 6.13.1(eslint@8.22.0)(typescript@5.2.2) + '@typescript-eslint/utils': 6.13.1(eslint@8.22.0)(typescript@5.2.2) + '@typescript-eslint/visitor-keys': 6.13.1 + debug: 4.3.4(supports-color@5.5.0) + eslint: 8.22.0 + graphemer: 1.4.0 + ignore: 5.2.4 + natural-compare: 1.4.0 + semver: 7.5.4 + ts-api-utils: 1.0.3(typescript@5.2.2) + typescript: 5.2.2 + transitivePeerDependencies: + - supports-color + dev: true + + /@typescript-eslint/parser@6.13.1(eslint@8.22.0)(typescript@5.2.2): + resolution: {integrity: sha512-fs2XOhWCzRhqMmQf0eicLa/CWSaYss2feXsy7xBD/pLyWke/jCIVc2s1ikEAtSW7ina1HNhv7kONoEfVNEcdDQ==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + eslint: ^7.0.0 || ^8.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/scope-manager': 6.13.1 + '@typescript-eslint/types': 6.13.1 + '@typescript-eslint/typescript-estree': 6.13.1(typescript@5.2.2) + '@typescript-eslint/visitor-keys': 6.13.1 + debug: 4.3.4(supports-color@5.5.0) + eslint: 8.22.0 + typescript: 5.2.2 + transitivePeerDependencies: + - supports-color + dev: true + + /@typescript-eslint/scope-manager@6.13.1: + resolution: {integrity: sha512-BW0kJ7ceiKi56GbT2KKzZzN+nDxzQK2DS6x0PiSMPjciPgd/JRQGMibyaN2cPt2cAvuoH0oNvn2fwonHI+4QUQ==} + engines: {node: ^16.0.0 || >=18.0.0} + dependencies: + '@typescript-eslint/types': 6.13.1 + '@typescript-eslint/visitor-keys': 6.13.1 + dev: true + + /@typescript-eslint/type-utils@6.13.1(eslint@8.22.0)(typescript@5.2.2): + resolution: {integrity: sha512-A2qPlgpxx2v//3meMqQyB1qqTg1h1dJvzca7TugM3Yc2USDY+fsRBiojAEo92HO7f5hW5mjAUF6qobOPzlBCBQ==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + eslint: ^7.0.0 || ^8.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/typescript-estree': 6.13.1(typescript@5.2.2) + '@typescript-eslint/utils': 6.13.1(eslint@8.22.0)(typescript@5.2.2) + debug: 4.3.4(supports-color@5.5.0) + eslint: 8.22.0 + ts-api-utils: 1.0.3(typescript@5.2.2) + typescript: 5.2.2 + transitivePeerDependencies: + - supports-color + dev: true + + /@typescript-eslint/types@6.13.1: + resolution: {integrity: sha512-gjeEskSmiEKKFIbnhDXUyiqVma1gRCQNbVZ1C8q7Zjcxh3WZMbzWVfGE9rHfWd1msQtPS0BVD9Jz9jded44eKg==} + engines: {node: ^16.0.0 || >=18.0.0} + dev: true + + /@typescript-eslint/typescript-estree@6.13.1(typescript@5.2.2): + resolution: {integrity: sha512-sBLQsvOC0Q7LGcUHO5qpG1HxRgePbT6wwqOiGLpR8uOJvPJbfs0mW3jPA3ujsDvfiVwVlWUDESNXv44KtINkUQ==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/types': 6.13.1 + '@typescript-eslint/visitor-keys': 6.13.1 + debug: 4.3.4(supports-color@5.5.0) + globby: 11.1.0 + is-glob: 4.0.3 + semver: 7.5.4 + ts-api-utils: 1.0.3(typescript@5.2.2) + typescript: 5.2.2 + transitivePeerDependencies: + - supports-color + dev: true + + /@typescript-eslint/utils@6.13.1(eslint@8.22.0)(typescript@5.2.2): + resolution: {integrity: sha512-ouPn/zVoan92JgAegesTXDB/oUp6BP1v8WpfYcqh649ejNc9Qv+B4FF2Ff626kO1xg0wWwwG48lAJ4JuesgdOw==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + eslint: ^7.0.0 || ^8.0.0 + dependencies: + '@eslint-community/eslint-utils': 4.4.0(eslint@8.22.0) + '@types/json-schema': 7.0.15 + '@types/semver': 7.5.6 + '@typescript-eslint/scope-manager': 6.13.1 + '@typescript-eslint/types': 6.13.1 + '@typescript-eslint/typescript-estree': 6.13.1(typescript@5.2.2) + eslint: 8.22.0 + semver: 7.5.4 + transitivePeerDependencies: + - supports-color + - typescript + dev: true + + /@typescript-eslint/visitor-keys@6.13.1: + resolution: {integrity: sha512-NDhQUy2tg6XGNBGDRm1XybOHSia8mcXmlbKWoQP+nm1BIIMxa55shyJfZkHpEBN62KNPLrocSM2PdPcaLgDKMQ==} + engines: {node: ^16.0.0 || >=18.0.0} + dependencies: + '@typescript-eslint/types': 6.13.1 + eslint-visitor-keys: 3.4.2 + dev: true + /abbrev@1.1.1: resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==} dev: true @@ -4038,7 +4204,7 @@ packages: engines: {node: '>= 6.0.0'} requiresBuild: true dependencies: - debug: 4.3.4 + debug: 4.3.4(supports-color@5.5.0) transitivePeerDependencies: - supports-color dev: false @@ -4070,6 +4236,7 @@ packages: fast-json-stable-stringify: 2.1.0 json-schema-traverse: 0.4.1 uri-js: 4.4.1 + dev: true /ajv@8.12.0: resolution: {integrity: sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==} @@ -4123,13 +4290,76 @@ packages: /argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + /array-buffer-byte-length@1.0.0: + resolution: {integrity: sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==} + dependencies: + call-bind: 1.0.5 + is-array-buffer: 3.0.2 + dev: true + /array-flatten@1.1.1: resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==} dev: false + /array-includes@3.1.7: + resolution: {integrity: sha512-dlcsNBIiWhPkHdOEEKnehA+RNUWDc4UqFtnIXU4uuYDPtA4LDkr7qip2p0VvFAEXNDr0yWZ9PJyIRiGjRLQzwQ==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.5 + define-properties: 1.2.1 + es-abstract: 1.22.3 + get-intrinsic: 1.2.2 + is-string: 1.0.7 + dev: true + /array-union@2.1.0: resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} engines: {node: '>=8'} + dev: true + + /array.prototype.findlastindex@1.2.3: + resolution: {integrity: sha512-LzLoiOMAxvy+Gd3BAq3B7VeIgPdo+Q8hthvKtXybMvRV0jrXfJM/t8mw7nNlpEcVlVUnCnM2KSX4XU5HmpodOA==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.5 + define-properties: 1.2.1 + es-abstract: 1.22.3 + es-shim-unscopables: 1.0.2 + get-intrinsic: 1.2.2 + dev: true + + /array.prototype.flat@1.3.2: + resolution: {integrity: sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.5 + define-properties: 1.2.1 + es-abstract: 1.22.3 + es-shim-unscopables: 1.0.2 + dev: true + + /array.prototype.flatmap@1.3.2: + resolution: {integrity: sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.5 + define-properties: 1.2.1 + es-abstract: 1.22.3 + es-shim-unscopables: 1.0.2 + dev: true + + /arraybuffer.prototype.slice@1.0.2: + resolution: {integrity: sha512-yMBKppFur/fbHu9/6USUe03bZ4knMYiwFBcyiaXB8Go0qNehwX6inYPzK9U0NeQvGxKthcmHcaR8P5MStSRBAw==} + engines: {node: '>= 0.4'} + dependencies: + array-buffer-byte-length: 1.0.0 + call-bind: 1.0.5 + define-properties: 1.2.1 + es-abstract: 1.22.3 + get-intrinsic: 1.2.2 + is-array-buffer: 3.0.2 + is-shared-array-buffer: 1.0.2 + dev: true /arrify@2.0.1: resolution: {integrity: sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==} @@ -4173,7 +4403,6 @@ packages: /available-typed-arrays@1.0.5: resolution: {integrity: sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==} engines: {node: '>= 0.4'} - dev: false /aws-sdk@2.1430.0: resolution: {integrity: sha512-827BjW9Q9NwUucZBHHU64dh96ihE857LC0ZOEub0C5wjxujoEqET0i4qJR7k+/wn8tFMNtWO0rIGCSGSmgrm5A==} @@ -4344,6 +4573,7 @@ packages: engines: {node: '>=8'} dependencies: fill-range: 7.0.1 + dev: true /browser-stdout@1.3.1: resolution: {integrity: sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==} @@ -4406,6 +4636,13 @@ packages: function-bind: 1.1.1 get-intrinsic: 1.2.1 + /call-bind@1.0.5: + resolution: {integrity: sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==} + dependencies: + function-bind: 1.1.2 + get-intrinsic: 1.2.2 + set-function-length: 1.1.1 + /callsites@3.1.0: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} @@ -4626,6 +4863,10 @@ packages: /concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + /confusing-browser-globals@1.0.11: + resolution: {integrity: sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA==} + dev: true + /connect-mongo@4.6.0(express-session@1.17.3)(mongodb@4.17.1): resolution: {integrity: sha512-8new4Z7NLP3CGP65Aw6ls3xDBeKVvHRSh39CXuDZTQsvpeeU9oNMzfFgvqmHqZ6gWpxIl663RyoVEmCAGf1yOg==} engines: {node: '>=10'} @@ -4633,7 +4874,7 @@ packages: express-session: ^1.17.1 mongodb: ^4.1.0 dependencies: - debug: 4.3.4 + debug: 4.3.4(supports-color@5.5.0) express-session: 1.17.3 kruptein: 3.0.6 mongodb: 4.17.1 @@ -4810,17 +5051,6 @@ packages: supports-color: 5.5.0 dev: true - /debug@4.3.4: - resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} - engines: {node: '>=6.0'} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - dependencies: - ms: 2.1.2 - /debug@4.3.4(supports-color@5.5.0): resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} engines: {node: '>=6.0'} @@ -4832,7 +5062,6 @@ packages: dependencies: ms: 2.1.2 supports-color: 5.5.0 - dev: false /debug@4.3.4(supports-color@8.1.1): resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} @@ -4873,6 +5102,23 @@ packages: clone: 1.0.4 dev: false + /define-data-property@1.1.1: + resolution: {integrity: sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==} + engines: {node: '>= 0.4'} + dependencies: + get-intrinsic: 1.2.1 + gopd: 1.0.1 + has-property-descriptors: 1.0.1 + + /define-properties@1.2.1: + resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} + engines: {node: '>= 0.4'} + dependencies: + define-data-property: 1.1.1 + has-property-descriptors: 1.0.1 + object-keys: 1.1.1 + dev: true + /delayed-stream@1.0.0: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} engines: {node: '>=0.4.0'} @@ -4909,12 +5155,21 @@ packages: engines: {node: '>=8'} dependencies: path-type: 4.0.0 + dev: true + + /doctrine@2.1.0: + resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} + engines: {node: '>=0.10.0'} + dependencies: + esutils: 2.0.3 + dev: true /doctrine@3.0.0: resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} engines: {node: '>=6.0.0'} dependencies: esutils: 2.0.3 + dev: true /dom-helpers@5.2.1: resolution: {integrity: sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==} @@ -4997,7 +5252,7 @@ packages: base64id: 2.0.0 cookie: 0.4.2 cors: 2.8.5 - debug: 4.3.4 + debug: 4.3.4(supports-color@5.5.0) engine.io-parser: 5.2.1 ws: 8.11.0 transitivePeerDependencies: @@ -5006,6 +5261,14 @@ packages: - utf-8-validate dev: false + /enhanced-resolve@5.15.0: + resolution: {integrity: sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg==} + engines: {node: '>=10.13.0'} + dependencies: + graceful-fs: 4.2.11 + tapable: 2.2.1 + dev: true + /ent@2.2.0: resolution: {integrity: sha512-GHrMyVZQWvTIdDtpiEXdHZnFQKzeO09apj8Cbl4pKWy4i0Oprcq17usfDt5aO63swf0JOeMWjWQE/LzgSRuWpA==} requiresBuild: true @@ -5029,6 +5292,75 @@ packages: is-arrayish: 0.2.1 dev: false + /es-abstract@1.22.3: + resolution: {integrity: sha512-eiiY8HQeYfYH2Con2berK+To6GrK2RxbPawDkGq4UiCQQfZHb6wX9qQqkbpPqaxQFcl8d9QzZqo0tGE0VcrdwA==} + engines: {node: '>= 0.4'} + dependencies: + array-buffer-byte-length: 1.0.0 + arraybuffer.prototype.slice: 1.0.2 + available-typed-arrays: 1.0.5 + call-bind: 1.0.5 + es-set-tostringtag: 2.0.2 + es-to-primitive: 1.2.1 + function.prototype.name: 1.1.6 + get-intrinsic: 1.2.2 + get-symbol-description: 1.0.0 + globalthis: 1.0.3 + gopd: 1.0.1 + has-property-descriptors: 1.0.1 + has-proto: 1.0.1 + has-symbols: 1.0.3 + hasown: 2.0.0 + internal-slot: 1.0.6 + is-array-buffer: 3.0.2 + is-callable: 1.2.7 + is-negative-zero: 2.0.2 + is-regex: 1.1.4 + is-shared-array-buffer: 1.0.2 + is-string: 1.0.7 + is-typed-array: 1.1.12 + is-weakref: 1.0.2 + object-inspect: 1.13.1 + object-keys: 1.1.1 + object.assign: 4.1.4 + regexp.prototype.flags: 1.5.1 + safe-array-concat: 1.0.1 + safe-regex-test: 1.0.0 + string.prototype.trim: 1.2.8 + string.prototype.trimend: 1.0.7 + string.prototype.trimstart: 1.0.7 + typed-array-buffer: 1.0.0 + typed-array-byte-length: 1.0.0 + typed-array-byte-offset: 1.0.0 + typed-array-length: 1.0.4 + unbox-primitive: 1.0.2 + which-typed-array: 1.1.13 + dev: true + + /es-set-tostringtag@2.0.2: + resolution: {integrity: sha512-BuDyupZt65P9D2D2vA/zqcI3G5xRsklm5N3xCwuiy+/vKy8i0ifdsQP1sLgO4tZDSCaQUSnmC48khknGMV3D2Q==} + engines: {node: '>= 0.4'} + dependencies: + get-intrinsic: 1.2.2 + has-tostringtag: 1.0.0 + hasown: 2.0.0 + dev: true + + /es-shim-unscopables@1.0.2: + resolution: {integrity: sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==} + dependencies: + hasown: 2.0.0 + dev: true + + /es-to-primitive@1.2.1: + resolution: {integrity: sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==} + engines: {node: '>= 0.4'} + dependencies: + is-callable: 1.2.7 + is-date-object: 1.0.5 + is-symbol: 1.0.4 + dev: true + /escalade@3.1.1: resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==} engines: {node: '>=6'} @@ -5072,54 +5404,185 @@ packages: dev: false optional: true - /eslint-config-prettier@8.3.0(eslint@8.22.0): - resolution: {integrity: sha512-BgZuLUSeKzvlL/VUjx/Yb787VQ26RU3gGjA3iiFvdsp/2bMfVIWUVP7tjxtjS0e+HP409cPlPvNkQloz8C91ew==} - hasBin: true + /eslint-config-airbnb-base@15.0.0(eslint-plugin-import@2.29.0)(eslint@8.22.0): + resolution: {integrity: sha512-xaX3z4ZZIcFLvh2oUNvcX5oEofXda7giYmuplVxoOg5A7EXJMrUyqRgR+mhDhPK8LZ4PttFOBvCYDbX3sUoUig==} + engines: {node: ^10.12.0 || >=12.0.0} peerDependencies: - eslint: '>=7.0.0' + eslint: ^7.32.0 || ^8.2.0 + eslint-plugin-import: ^2.25.2 dependencies: + confusing-browser-globals: 1.0.11 eslint: 8.22.0 - dev: false + eslint-plugin-import: 2.29.0(@typescript-eslint/parser@6.13.1)(eslint-import-resolver-typescript@3.6.1)(eslint@8.22.0) + object.assign: 4.1.4 + object.entries: 1.1.7 + semver: 6.3.1 + dev: true - /eslint-plugin-mocha@10.1.0(eslint@8.22.0): - resolution: {integrity: sha512-xLqqWUF17llsogVOC+8C6/jvQ+4IoOREbN7ZCHuOHuD6cT5cDD4h7f2LgsZuzMAiwswWE21tO7ExaknHVDrSkw==} - engines: {node: '>=14.0.0'} + /eslint-config-airbnb-typescript@17.1.0(@typescript-eslint/eslint-plugin@6.13.1)(@typescript-eslint/parser@6.13.1)(eslint-plugin-import@2.29.0)(eslint@8.22.0): + resolution: {integrity: sha512-GPxI5URre6dDpJ0CtcthSZVBAfI+Uw7un5OYNVxP2EYi3H81Jw701yFP7AU+/vCE7xBtFmjge7kfhhk4+RAiig==} + peerDependencies: + '@typescript-eslint/eslint-plugin': ^5.13.0 || ^6.0.0 + '@typescript-eslint/parser': ^5.0.0 || ^6.0.0 + eslint: ^7.32.0 || ^8.2.0 + eslint-plugin-import: ^2.25.3 + dependencies: + '@typescript-eslint/eslint-plugin': 6.13.1(@typescript-eslint/parser@6.13.1)(eslint@8.22.0)(typescript@5.2.2) + '@typescript-eslint/parser': 6.13.1(eslint@8.22.0)(typescript@5.2.2) + eslint: 8.22.0 + eslint-config-airbnb-base: 15.0.0(eslint-plugin-import@2.29.0)(eslint@8.22.0) + eslint-plugin-import: 2.29.0(@typescript-eslint/parser@6.13.1)(eslint-import-resolver-typescript@3.6.1)(eslint@8.22.0) + dev: true + + /eslint-config-prettier@8.3.0(eslint@8.22.0): + resolution: {integrity: sha512-BgZuLUSeKzvlL/VUjx/Yb787VQ26RU3gGjA3iiFvdsp/2bMfVIWUVP7tjxtjS0e+HP409cPlPvNkQloz8C91ew==} + hasBin: true peerDependencies: eslint: '>=7.0.0' dependencies: eslint: 8.22.0 - eslint-utils: 3.0.0(eslint@8.22.0) - rambda: 7.5.0 dev: true - /eslint-scope@7.2.2: - resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + /eslint-import-resolver-node@0.3.9: + resolution: {integrity: sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==} dependencies: - esrecurse: 4.3.0 - estraverse: 5.3.0 + debug: 3.2.7(supports-color@5.5.0) + is-core-module: 2.13.1 + resolve: 1.22.4 + transitivePeerDependencies: + - supports-color + dev: true - /eslint-utils@3.0.0(eslint@8.22.0): - resolution: {integrity: sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==} - engines: {node: ^10.0.0 || ^12.0.0 || >= 14.0.0} + /eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.13.1)(eslint-plugin-import@2.29.0)(eslint@8.22.0): + resolution: {integrity: sha512-xgdptdoi5W3niYeuQxKmzVDTATvLYqhpwmykwsh7f6HIOStGWEIL9iqZgQDF9u9OEzrRwR8no5q2VT+bjAujTg==} + engines: {node: ^14.18.0 || >=16.0.0} peerDependencies: - eslint: '>=5' + eslint: '*' + eslint-plugin-import: '*' dependencies: + debug: 4.3.4(supports-color@5.5.0) + enhanced-resolve: 5.15.0 eslint: 8.22.0 - eslint-visitor-keys: 2.1.0 - - /eslint-visitor-keys@2.1.0: - resolution: {integrity: sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==} - engines: {node: '>=10'} - - /eslint-visitor-keys@3.4.2: - resolution: {integrity: sha512-8drBzUEyZ2llkpCA67iYrgEssKDUu68V8ChqqOfFupIaG/LCVPUT+CoGJpT77zJprs4T/W7p07LP7zAIMuweVw==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + eslint-module-utils: 2.8.0(@typescript-eslint/parser@6.13.1)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.22.0) + eslint-plugin-import: 2.29.0(@typescript-eslint/parser@6.13.1)(eslint-import-resolver-typescript@3.6.1)(eslint@8.22.0) + fast-glob: 3.3.1 + get-tsconfig: 4.7.2 + is-core-module: 2.13.1 + is-glob: 4.0.3 + transitivePeerDependencies: + - '@typescript-eslint/parser' + - eslint-import-resolver-node + - eslint-import-resolver-webpack + - supports-color + dev: true - /eslint@8.22.0: - resolution: {integrity: sha512-ci4t0sz6vSRKdmkOGmprBo6fmI4PrphDFMy5JEq/fNS0gQkJM3rLmrqcp8ipMcdobH3KtUP40KniAE9W19S4wA==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - hasBin: true + /eslint-module-utils@2.8.0(@typescript-eslint/parser@6.13.1)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.22.0): + resolution: {integrity: sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==} + engines: {node: '>=4'} + peerDependencies: + '@typescript-eslint/parser': '*' + eslint: '*' + eslint-import-resolver-node: '*' + eslint-import-resolver-typescript: '*' + eslint-import-resolver-webpack: '*' + peerDependenciesMeta: + '@typescript-eslint/parser': + optional: true + eslint: + optional: true + eslint-import-resolver-node: + optional: true + eslint-import-resolver-typescript: + optional: true + eslint-import-resolver-webpack: + optional: true + dependencies: + '@typescript-eslint/parser': 6.13.1(eslint@8.22.0)(typescript@5.2.2) + debug: 3.2.7(supports-color@5.5.0) + eslint: 8.22.0 + eslint-import-resolver-node: 0.3.9 + eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@6.13.1)(eslint-plugin-import@2.29.0)(eslint@8.22.0) + transitivePeerDependencies: + - supports-color + dev: true + + /eslint-plugin-import@2.29.0(@typescript-eslint/parser@6.13.1)(eslint-import-resolver-typescript@3.6.1)(eslint@8.22.0): + resolution: {integrity: sha512-QPOO5NO6Odv5lpoTkddtutccQjysJuFxoPS7fAHO+9m9udNHvTCPSAMW9zGAYj8lAIdr40I8yPCdUYrncXtrwg==} + engines: {node: '>=4'} + peerDependencies: + '@typescript-eslint/parser': '*' + eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 + peerDependenciesMeta: + '@typescript-eslint/parser': + optional: true + dependencies: + '@typescript-eslint/parser': 6.13.1(eslint@8.22.0)(typescript@5.2.2) + array-includes: 3.1.7 + array.prototype.findlastindex: 1.2.3 + array.prototype.flat: 1.3.2 + array.prototype.flatmap: 1.3.2 + debug: 3.2.7(supports-color@5.5.0) + doctrine: 2.1.0 + eslint: 8.22.0 + eslint-import-resolver-node: 0.3.9 + eslint-module-utils: 2.8.0(@typescript-eslint/parser@6.13.1)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.22.0) + hasown: 2.0.0 + is-core-module: 2.13.1 + is-glob: 4.0.3 + minimatch: 3.1.2 + object.fromentries: 2.0.7 + object.groupby: 1.0.1 + object.values: 1.1.7 + semver: 6.3.1 + tsconfig-paths: 3.14.2 + transitivePeerDependencies: + - eslint-import-resolver-typescript + - eslint-import-resolver-webpack + - supports-color + dev: true + + /eslint-plugin-mocha@10.1.0(eslint@8.22.0): + resolution: {integrity: sha512-xLqqWUF17llsogVOC+8C6/jvQ+4IoOREbN7ZCHuOHuD6cT5cDD4h7f2LgsZuzMAiwswWE21tO7ExaknHVDrSkw==} + engines: {node: '>=14.0.0'} + peerDependencies: + eslint: '>=7.0.0' + dependencies: + eslint: 8.22.0 + eslint-utils: 3.0.0(eslint@8.22.0) + rambda: 7.5.0 + dev: true + + /eslint-scope@7.2.2: + resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + dev: true + + /eslint-utils@3.0.0(eslint@8.22.0): + resolution: {integrity: sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==} + engines: {node: ^10.0.0 || ^12.0.0 || >= 14.0.0} + peerDependencies: + eslint: '>=5' + dependencies: + eslint: 8.22.0 + eslint-visitor-keys: 2.1.0 + dev: true + + /eslint-visitor-keys@2.1.0: + resolution: {integrity: sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==} + engines: {node: '>=10'} + dev: true + + /eslint-visitor-keys@3.4.2: + resolution: {integrity: sha512-8drBzUEyZ2llkpCA67iYrgEssKDUu68V8ChqqOfFupIaG/LCVPUT+CoGJpT77zJprs4T/W7p07LP7zAIMuweVw==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + /eslint@8.22.0: + resolution: {integrity: sha512-ci4t0sz6vSRKdmkOGmprBo6fmI4PrphDFMy5JEq/fNS0gQkJM3rLmrqcp8ipMcdobH3KtUP40KniAE9W19S4wA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + hasBin: true dependencies: '@eslint/eslintrc': 1.4.1 '@humanwhocodes/config-array': 0.10.7 @@ -5127,7 +5590,7 @@ packages: ajv: 6.12.6 chalk: 4.1.2 cross-spawn: 7.0.3 - debug: 4.3.4 + debug: 4.3.4(supports-color@5.5.0) doctrine: 3.0.0 escape-string-regexp: 4.0.0 eslint-scope: 7.2.2 @@ -5162,6 +5625,7 @@ packages: v8-compile-cache: 2.3.0 transitivePeerDependencies: - supports-color + dev: true /espree@9.6.1: resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} @@ -5184,12 +5648,14 @@ packages: engines: {node: '>=0.10'} dependencies: estraverse: 5.3.0 + dev: true /esrecurse@4.3.0: resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} engines: {node: '>=4.0'} dependencies: estraverse: 5.3.0 + dev: true /estraverse@4.3.0: resolution: {integrity: sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==} @@ -5328,9 +5794,11 @@ packages: glob-parent: 5.1.2 merge2: 1.4.1 micromatch: 4.0.5 + dev: true /fast-json-stable-stringify@2.1.0: resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + dev: true /fast-levenshtein@2.0.6: resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} @@ -5367,6 +5835,7 @@ packages: resolution: {integrity: sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==} dependencies: reusify: 1.0.4 + dev: true /faye-websocket@0.11.4: resolution: {integrity: sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==} @@ -5384,6 +5853,7 @@ packages: engines: {node: ^10.12.0 || >=12.0.0} dependencies: flat-cache: 3.0.4 + dev: true /file-stream-rotator@0.6.1: resolution: {integrity: sha512-u+dBid4PvZw17PmDeRcNOtCP9CCK/9lRN2w+r1xIS7yOL9JFrIBKTvrYsxT4P0pGtThYTn++QS5ChHaUov3+zQ==} @@ -5396,6 +5866,7 @@ packages: engines: {node: '>=8'} dependencies: to-regex-range: 5.0.1 + dev: true /finalhandler@1.2.0: resolution: {integrity: sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==} @@ -5438,6 +5909,7 @@ packages: dependencies: locate-path: 6.0.0 path-exists: 4.0.0 + dev: true /firebase-admin@11.10.1: resolution: {integrity: sha512-atv1E6GbuvcvWaD3eHwrjeP5dAVs+EaHEJhu9CThMzPY6In8QYDiUR6tq5SwGl4SdA/GcAU0nhwWc/FSJsAzfQ==} @@ -5465,6 +5937,7 @@ packages: dependencies: flatted: 3.2.7 rimraf: 3.0.2 + dev: true /flat@5.0.2: resolution: {integrity: sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==} @@ -5472,6 +5945,7 @@ packages: /flatted@3.2.7: resolution: {integrity: sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==} + dev: true /fn.name@1.1.0: resolution: {integrity: sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==} @@ -5491,7 +5965,6 @@ packages: resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} dependencies: is-callable: 1.2.7 - dev: false /foreground-child@3.1.1: resolution: {integrity: sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==} @@ -5546,9 +6019,26 @@ packages: /function-bind@1.1.1: resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==} + /function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + /function.prototype.name@1.1.6: + resolution: {integrity: sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.5 + define-properties: 1.2.1 + es-abstract: 1.22.3 + functions-have-names: 1.2.3 + dev: true + /functional-red-black-tree@1.0.1: resolution: {integrity: sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==} + /functions-have-names@1.2.3: + resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} + dev: true + /gaxios@5.1.3: resolution: {integrity: sha512-95hVgBRgEIRQQQHIbnxBXeHbW4TqFk4ZDJW7wmVtvYar72FdhRIo1UGOLS2eRAKCPEdPBWu+M7+A33D9CdX9rA==} engines: {node: '>=12'} @@ -5603,17 +6093,41 @@ packages: has-proto: 1.0.1 has-symbols: 1.0.3 + /get-intrinsic@1.2.2: + resolution: {integrity: sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==} + dependencies: + function-bind: 1.1.2 + has-proto: 1.0.1 + has-symbols: 1.0.3 + hasown: 2.0.0 + + /get-symbol-description@1.0.0: + resolution: {integrity: sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.5 + get-intrinsic: 1.2.2 + dev: true + + /get-tsconfig@4.7.2: + resolution: {integrity: sha512-wuMsz4leaj5hbGgg4IvDU0bqJagpftG5l5cXIAvo8uZrqn0NJqwtfupTN00VnkQJPcIRrxYrm1Ue24btpCha2A==} + dependencies: + resolve-pkg-maps: 1.0.0 + dev: true + /glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} dependencies: is-glob: 4.0.3 + dev: true /glob-parent@6.0.2: resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} engines: {node: '>=10.13.0'} dependencies: is-glob: 4.0.3 + dev: true /glob@10.3.10: resolution: {integrity: sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==} @@ -5671,6 +6185,14 @@ packages: engines: {node: '>=8'} dependencies: type-fest: 0.20.2 + dev: true + + /globalthis@1.0.3: + resolution: {integrity: sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==} + engines: {node: '>= 0.4'} + dependencies: + define-properties: 1.2.1 + dev: true /globby@11.1.0: resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} @@ -5682,6 +6204,7 @@ packages: ignore: 5.2.4 merge2: 1.4.1 slash: 3.0.0 + dev: true /google-auth-library@8.9.0: resolution: {integrity: sha512-f7aQCJODJFmYWN6PeNKzgvy9LI2tYmXnzpNDHEjG5sDNPgGb2FXQyTBnXeSH+PAtpKESFD+LmHw3Ox3mN7e1Fg==} @@ -5744,16 +6267,18 @@ packages: resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==} dependencies: get-intrinsic: 1.2.1 - dev: false /graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} requiresBuild: true - dev: false - optional: true /grapheme-splitter@1.0.4: resolution: {integrity: sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==} + dev: true + + /graphemer@1.4.0: + resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} + dev: true /gtoken@6.1.2: resolution: {integrity: sha512-4ccGpzz7YAr7lxrT2neugmXQ3hP9ho2gcaityLVkiUecAiwiy60Ii8gRbZeOsXV19fYaRjgBSshs8kXw+NKCPQ==} @@ -5773,6 +6298,10 @@ packages: resolution: {integrity: sha512-zGEOVKFM5sVPPrYs7J5/hYEw2Pof8KCyOwyhG8sAF26mCAeUFAcYPu1mwB7hhpIP29zOIBaDqwuHdLp0jvZXjw==} dev: false + /has-bigints@1.0.2: + resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==} + dev: true + /has-flag@3.0.0: resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} engines: {node: '>=4'} @@ -5781,6 +6310,11 @@ packages: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} + /has-property-descriptors@1.0.1: + resolution: {integrity: sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==} + dependencies: + get-intrinsic: 1.2.2 + /has-proto@1.0.1: resolution: {integrity: sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==} engines: {node: '>= 0.4'} @@ -5794,7 +6328,6 @@ packages: engines: {node: '>= 0.4'} dependencies: has-symbols: 1.0.3 - dev: false /has@1.0.3: resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==} @@ -5802,6 +6335,12 @@ packages: dependencies: function-bind: 1.1.1 + /hasown@2.0.0: + resolution: {integrity: sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==} + engines: {node: '>= 0.4'} + dependencies: + function-bind: 1.1.2 + /he@1.2.0: resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} hasBin: true @@ -5846,7 +6385,7 @@ packages: dependencies: '@tootallnate/once': 2.0.0 agent-base: 6.0.2 - debug: 4.3.4 + debug: 4.3.4(supports-color@5.5.0) transitivePeerDependencies: - supports-color dev: false @@ -5858,7 +6397,7 @@ packages: requiresBuild: true dependencies: agent-base: 6.0.2 - debug: 4.3.4 + debug: 4.3.4(supports-color@5.5.0) transitivePeerDependencies: - supports-color dev: false @@ -5888,6 +6427,7 @@ packages: /ignore@5.2.4: resolution: {integrity: sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==} engines: {node: '>= 4'} + dev: true /import-fresh@3.3.0: resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} @@ -5899,6 +6439,7 @@ packages: /imurmurhash@0.1.4: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} + dev: true /inflight@1.0.6: resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} @@ -5909,6 +6450,15 @@ packages: /inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + /internal-slot@1.0.6: + resolution: {integrity: sha512-Xj6dv+PsbtwyPpEflsejS+oIZxmMlV44zAhG479uYu89MsjcYOhCFnNyKrkJrihbsiasQyY0afoCl/9BLR65bg==} + engines: {node: '>= 0.4'} + dependencies: + get-intrinsic: 1.2.2 + hasown: 2.0.0 + side-channel: 1.0.4 + dev: true + /ip@2.0.0: resolution: {integrity: sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==} dev: false @@ -5926,6 +6476,14 @@ packages: has-tostringtag: 1.0.0 dev: false + /is-array-buffer@3.0.2: + resolution: {integrity: sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==} + dependencies: + call-bind: 1.0.5 + get-intrinsic: 1.2.2 + is-typed-array: 1.1.12 + dev: true + /is-arrayish@0.2.1: resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} dev: false @@ -5934,6 +6492,12 @@ packages: resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==} dev: false + /is-bigint@1.0.4: + resolution: {integrity: sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==} + dependencies: + has-bigints: 1.0.2 + dev: true + /is-binary-path@2.1.0: resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} engines: {node: '>=8'} @@ -5941,6 +6505,14 @@ packages: binary-extensions: 2.2.0 dev: true + /is-boolean-object@1.1.2: + resolution: {integrity: sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.5 + has-tostringtag: 1.0.0 + dev: true + /is-builtin-module@3.2.1: resolution: {integrity: sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==} engines: {node: '>=6'} @@ -5951,13 +6523,24 @@ packages: /is-callable@1.2.7: resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} engines: {node: '>= 0.4'} - dev: false /is-core-module@2.13.0: resolution: {integrity: sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ==} dependencies: has: 1.0.3 - dev: false + + /is-core-module@2.13.1: + resolution: {integrity: sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==} + dependencies: + hasown: 2.0.0 + dev: true + + /is-date-object@1.0.5: + resolution: {integrity: sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==} + engines: {node: '>= 0.4'} + dependencies: + has-tostringtag: 1.0.0 + dev: true /is-extendable@1.0.1: resolution: {integrity: sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==} @@ -5969,6 +6552,7 @@ packages: /is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} + dev: true /is-fullwidth-code-point@3.0.0: resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} @@ -5986,6 +6570,7 @@ packages: engines: {node: '>=0.10.0'} dependencies: is-extglob: 2.1.1 + dev: true /is-interactive@1.0.0: resolution: {integrity: sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==} @@ -5996,9 +6581,22 @@ packages: resolution: {integrity: sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==} dev: false + /is-negative-zero@2.0.2: + resolution: {integrity: sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==} + engines: {node: '>= 0.4'} + dev: true + + /is-number-object@1.0.7: + resolution: {integrity: sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==} + engines: {node: '>= 0.4'} + dependencies: + has-tostringtag: 1.0.0 + dev: true + /is-number@7.0.0: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} + dev: true /is-plain-obj@2.1.0: resolution: {integrity: sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==} @@ -6018,6 +6616,20 @@ packages: '@types/estree': 0.0.39 dev: false + /is-regex@1.1.4: + resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.5 + has-tostringtag: 1.0.0 + dev: true + + /is-shared-array-buffer@1.0.2: + resolution: {integrity: sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==} + dependencies: + call-bind: 1.0.5 + dev: true + /is-stream-ended@0.1.4: resolution: {integrity: sha512-xj0XPvmr7bQFTvirqnFr50o0hQIh6ZItDqloxt5aJrR4NQsYeSsyFQERYGCAzfindAcnKjINnwEEgLx4IqVzQw==} requiresBuild: true @@ -6029,21 +6641,44 @@ packages: engines: {node: '>=8'} dev: false + /is-string@1.0.7: + resolution: {integrity: sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==} + engines: {node: '>= 0.4'} + dependencies: + has-tostringtag: 1.0.0 + dev: true + + /is-symbol@1.0.4: + resolution: {integrity: sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==} + engines: {node: '>= 0.4'} + dependencies: + has-symbols: 1.0.3 + dev: true + /is-typed-array@1.1.12: resolution: {integrity: sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg==} engines: {node: '>= 0.4'} dependencies: - which-typed-array: 1.1.11 - dev: false + which-typed-array: 1.1.13 /is-unicode-supported@0.1.0: resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==} engines: {node: '>=10'} + /is-weakref@1.0.2: + resolution: {integrity: sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==} + dependencies: + call-bind: 1.0.5 + dev: true + /isarray@1.0.0: resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} dev: false + /isarray@2.0.5: + resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} + dev: true + /isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} @@ -6088,6 +6723,7 @@ packages: hasBin: true dependencies: argparse: 2.0.1 + dev: true /js2xmlparser@4.0.2: resolution: {integrity: sha512-6n4D8gLlLf1n5mNLQPRfViYzu9RATblzPEtm1SthMX1Pjao0r9YI9nw7ZIfRxQMERS87mcswrg+r/OYrPRX6jA==} @@ -6146,6 +6782,7 @@ packages: /json-schema-traverse@0.4.1: resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + dev: true /json-schema-traverse@1.0.0: resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} @@ -6153,6 +6790,14 @@ packages: /json-stable-stringify-without-jsonify@1.0.1: resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + dev: true + + /json5@1.0.2: + resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==} + hasBin: true + dependencies: + minimist: 1.2.8 + dev: true /json5@2.2.3: resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} @@ -6204,7 +6849,7 @@ packages: dependencies: '@types/express': 4.17.21 '@types/jsonwebtoken': 9.0.2 - debug: 4.3.4 + debug: 4.3.4(supports-color@5.5.0) jose: 4.14.4 limiter: 1.1.5 lru-memoizer: 2.2.0 @@ -6273,6 +6918,7 @@ packages: dependencies: prelude-ls: 1.2.1 type-check: 0.4.0 + dev: true /limiter@1.1.5: resolution: {integrity: sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA==} @@ -6313,6 +6959,7 @@ packages: engines: {node: '>=10'} dependencies: p-locate: 5.0.0 + dev: true /lodash.camelcase@4.3.0: resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==} @@ -6358,6 +7005,7 @@ packages: /lodash.merge@4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + dev: true /lodash.once@4.1.1: resolution: {integrity: sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==} @@ -6543,6 +7191,7 @@ packages: /merge2@1.4.1: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} + dev: true /methods@1.1.2: resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==} @@ -6554,6 +7203,7 @@ packages: dependencies: braces: 3.0.2 picomatch: 2.3.1 + dev: true /mime-db@1.52.0: resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} @@ -6625,8 +7275,6 @@ packages: /minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} requiresBuild: true - dev: false - optional: true /minipass@7.0.4: resolution: {integrity: sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==} @@ -6719,7 +7367,7 @@ packages: resolution: {integrity: sha512-J5heI+P08I6VJ2Ky3+33IpCdAvlYGTSUjwTPxkAr8i8EoduPMBX2OY/wa3IKZIQl7MU4SbFk8ndgSKyB/cl1zA==} engines: {node: '>=12.0.0'} dependencies: - debug: 4.3.4 + debug: 4.3.4(supports-color@5.5.0) transitivePeerDependencies: - supports-color dev: false @@ -6747,6 +7395,7 @@ packages: /natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + dev: true /negotiator@0.6.3: resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} @@ -6848,6 +7497,52 @@ packages: /object-inspect@1.12.3: resolution: {integrity: sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==} + /object-inspect@1.13.1: + resolution: {integrity: sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==} + dev: true + + /object-keys@1.1.1: + resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} + engines: {node: '>= 0.4'} + dev: true + + /object.assign@4.1.4: + resolution: {integrity: sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + define-properties: 1.2.1 + has-symbols: 1.0.3 + object-keys: 1.1.1 + dev: true + + /object.entries@1.1.7: + resolution: {integrity: sha512-jCBs/0plmPsOnrKAfFQXRG2NFjlhZgjjcBLSmTnEhU8U6vVTsVe8ANeQJCHTl3gSsI4J+0emOoCgoKlmQPMgmA==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + define-properties: 1.2.1 + es-abstract: 1.22.3 + dev: true + + /object.fromentries@2.0.7: + resolution: {integrity: sha512-UPbPHML6sL8PI/mOqPwsH4G6iyXcCGzLin8KvEPenOZN5lpCNBZZQ+V62vdjB1mQHrmqGQt5/OJzemUA+KJmEA==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.5 + define-properties: 1.2.1 + es-abstract: 1.22.3 + dev: true + + /object.groupby@1.0.1: + resolution: {integrity: sha512-HqaQtqLnp/8Bn4GL16cj+CUYbnpe1bh0TtEaWvybszDG4tgxCJuRpV8VGuvNaI1fAnI4lUJzDG55MXcOH4JZcQ==} + dependencies: + call-bind: 1.0.5 + define-properties: 1.2.1 + es-abstract: 1.22.3 + get-intrinsic: 1.2.2 + dev: true + /object.omit@3.0.0: resolution: {integrity: sha512-EO+BCv6LJfu+gBIF3ggLicFebFLN5zqzz/WWJlMFfkMyGth+oBkhxzDl0wx2W4GkLzuQs/FsSkXZb2IMWQqmBQ==} engines: {node: '>=0.10.0'} @@ -6862,6 +7557,15 @@ packages: isobject: 3.0.1 dev: false + /object.values@1.1.7: + resolution: {integrity: sha512-aU6xnDFYT3x17e/f0IiiwlGPTy2jzMySGfUB4fq6z7CV8l85CWHDk5ErhyhpfDHhrOMwGFhSQkhMGHaIotA6Ng==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.5 + define-properties: 1.2.1 + es-abstract: 1.22.3 + dev: true + /on-finished@2.4.1: resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} engines: {node: '>= 0.8'} @@ -6916,6 +7620,7 @@ packages: levn: 0.4.1 prelude-ls: 1.2.1 type-check: 0.4.0 + dev: true /ora@5.4.1: resolution: {integrity: sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==} @@ -6961,6 +7666,7 @@ packages: engines: {node: '>=10'} dependencies: p-limit: 3.1.0 + dev: true /p-try@2.2.0: resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} @@ -6996,6 +7702,7 @@ packages: /path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} + dev: true /path-is-absolute@1.0.1: resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} @@ -7007,7 +7714,6 @@ packages: /path-parse@1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} - dev: false /path-scurry@1.10.1: resolution: {integrity: sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==} @@ -7086,6 +7792,7 @@ packages: /prelude-ls@1.2.1: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} + dev: true /prop-types@15.8.1: resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} @@ -7358,6 +8065,7 @@ packages: /queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + dev: true /raf-schd@4.0.3: resolution: {integrity: sha512-tQkJl2GRWh83ui2DiPTJz9wEiMN20syf+5oKfB03yYP7ioZcJwsIK8FjrtLwH1m7C7e+Tt2yYBlrOpdT+dyeIQ==} @@ -7659,9 +8367,19 @@ packages: '@babel/runtime': 7.22.6 dev: false + /regexp.prototype.flags@1.5.1: + resolution: {integrity: sha512-sy6TXMN+hnP/wMy+ISxg3krXx7BAtWVO4UouuCN/ziM9UEne0euamVNafDfvC83bRNr95y0V5iijeDQFUNpvrg==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.5 + define-properties: 1.2.1 + set-function-name: 2.0.1 + dev: true + /regexpp@3.2.0: resolution: {integrity: sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==} engines: {node: '>=8'} + dev: true /regexpu-core@5.3.2: resolution: {integrity: sha512-RAM5FlZz+Lhmo7db9L298p2vHP5ZywrVXmVXpmAD9GuL5MPH6t9ROw1iA/wfHkQ76Qe7AaPF0nGuim96/IrQMQ==} @@ -7703,6 +8421,10 @@ packages: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} + /resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + dev: true + /resolve@1.22.4: resolution: {integrity: sha512-PXNdCiPqDqeUou+w1C2eTQbNfxKSuMxqTCuvlmmMsk1NWHL5fRrhY6Pl0qEYYc6+QqGClco1Qj8XnjPego4wfg==} hasBin: true @@ -7710,7 +8432,6 @@ packages: is-core-module: 2.13.0 path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 - dev: false /response-time@2.3.2: resolution: {integrity: sha512-MUIDaDQf+CVqflfTdQ5yam+aYCkXj1PY8fjlPDQ6ppxJlmgZb864pHtA750mayywNg8tx4rS7qH9JXd/OF+3gw==} @@ -7733,7 +8454,7 @@ packages: engines: {node: '>=12'} requiresBuild: true dependencies: - debug: 4.3.4 + debug: 4.3.4(supports-color@5.5.0) extend: 3.0.2 transitivePeerDependencies: - supports-color @@ -7750,6 +8471,7 @@ packages: /reusify@1.0.4: resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + dev: true /rimraf@3.0.2: resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} @@ -7794,10 +8516,29 @@ packages: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} dependencies: queue-microtask: 1.2.3 + dev: true + + /safe-array-concat@1.0.1: + resolution: {integrity: sha512-6XbUAseYE2KtOuGueyeobCySj9L4+66Tn6KQMOPQJrAJEowYKW/YR/MGJZl7FdydUdaFu4LYyDZjxf4/Nmo23Q==} + engines: {node: '>=0.4'} + dependencies: + call-bind: 1.0.5 + get-intrinsic: 1.2.2 + has-symbols: 1.0.3 + isarray: 2.0.5 + dev: true /safe-buffer@5.2.1: resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + /safe-regex-test@1.0.0: + resolution: {integrity: sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==} + dependencies: + call-bind: 1.0.5 + get-intrinsic: 1.2.2 + is-regex: 1.1.4 + dev: true + /safe-stable-stringify@2.4.3: resolution: {integrity: sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g==} engines: {node: '>=10'} @@ -7825,7 +8566,6 @@ packages: /semver@6.3.1: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true - dev: false /semver@7.5.4: resolution: {integrity: sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==} @@ -7879,6 +8619,24 @@ packages: - supports-color dev: false + /set-function-length@1.1.1: + resolution: {integrity: sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ==} + engines: {node: '>= 0.4'} + dependencies: + define-data-property: 1.1.1 + get-intrinsic: 1.2.2 + gopd: 1.0.1 + has-property-descriptors: 1.0.1 + + /set-function-name@2.0.1: + resolution: {integrity: sha512-tMNCiqYVkXIZgc2Hnoy2IvC/f8ezc5koaRFkCjrpWzGpCd3qbZXPzVy9MAZzK1ch/X0jvSkojys3oqJN0qCmdA==} + engines: {node: '>= 0.4'} + dependencies: + define-data-property: 1.1.1 + functions-have-names: 1.2.3 + has-property-descriptors: 1.0.1 + dev: true + /setprototypeof@1.2.0: resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} dev: false @@ -7960,7 +8718,7 @@ packages: engines: {node: '>=10.0.0'} dependencies: '@socket.io/component-emitter': 3.1.0 - debug: 4.3.4 + debug: 4.3.4(supports-color@5.5.0) transitivePeerDependencies: - supports-color dev: false @@ -7972,7 +8730,7 @@ packages: accepts: 1.3.8 base64id: 2.0.0 cors: 2.8.5 - debug: 4.3.4 + debug: 4.3.4(supports-color@5.5.0) engine.io: 6.5.2 socket.io-adapter: 2.5.2 socket.io-parser: 4.2.4 @@ -8060,6 +8818,31 @@ packages: strip-ansi: 7.1.0 dev: true + /string.prototype.trim@1.2.8: + resolution: {integrity: sha512-lfjY4HcixfQXOfaqCvcBuOIapyaroTXhbkfJN3gcB1OtyupngWK4sEET9Knd0cXd28kTUqu/kHoV4HKSJdnjiQ==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.5 + define-properties: 1.2.1 + es-abstract: 1.22.3 + dev: true + + /string.prototype.trimend@1.0.7: + resolution: {integrity: sha512-Ni79DqeB72ZFq1uH/L6zJ+DKZTkOtPIHovb3YZHQViE+HDouuU4mBrLOLDn5Dde3RF8qw5qVETEjhu9locMLvA==} + dependencies: + call-bind: 1.0.5 + define-properties: 1.2.1 + es-abstract: 1.22.3 + dev: true + + /string.prototype.trimstart@1.0.7: + resolution: {integrity: sha512-NGhtDFu3jCEm7B4Fy0DpLewdJQOZcQ0rGbwQ/+stjnrp2i+rlKeCvos9hOIeCmqwratM47OBxY7uFZzjxHXmrg==} + dependencies: + call-bind: 1.0.5 + define-properties: 1.2.1 + es-abstract: 1.22.3 + dev: true + /string_decoder@1.3.0: resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} dependencies: @@ -8079,6 +8862,11 @@ packages: ansi-regex: 6.0.1 dev: true + /strip-bom@3.0.0: + resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} + engines: {node: '>=4'} + dev: true + /strip-json-comments@3.1.1: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} @@ -8146,7 +8934,7 @@ packages: dependencies: component-emitter: 1.3.0 cookiejar: 2.1.4 - debug: 4.3.4 + debug: 4.3.4(supports-color@5.5.0) fast-safe-stringify: 2.1.1 form-data: 4.0.0 formidable: 2.1.2 @@ -8190,7 +8978,6 @@ packages: /supports-preserve-symlinks-flag@1.0.0: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} - dev: false /swagger-ui-dist@5.3.1: resolution: {integrity: sha512-El78OvXp9zMasfPrshtkW1CRx8AugAKoZuGGOTW+8llJzOV1RtDJYqQRz/6+2OakjeWWnZuRlN2Qj1Y0ilux3w==} @@ -8206,6 +8993,11 @@ packages: swagger-ui-dist: 5.3.1 dev: false + /tapable@2.2.1: + resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==} + engines: {node: '>=6'} + dev: true + /teeny-request@8.0.3: resolution: {integrity: sha512-jJZpA5He2y52yUhA7pyAGZlgQpcB+xLjcN0eUFxr9c8hP/H7uOXbBNVo/O0C/xVfJLJs680jvkFgVJEEvk9+ww==} engines: {node: '>=12'} @@ -8247,6 +9039,7 @@ packages: /text-table@0.2.0: resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} + dev: true /throttle-debounce@3.0.1: resolution: {integrity: sha512-dTEWWNu6JmeVXY0ZYoPuH5cRIwc0MeGbJwah9KUNYSJwommQpCzTySTpEe8Gs1J23aeWEuAobe4Ag7EHVt/LOg==} @@ -8282,6 +9075,7 @@ packages: engines: {node: '>=8.0'} dependencies: is-number: 7.0.0 + dev: true /toidentifier@1.0.1: resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} @@ -8313,6 +9107,15 @@ packages: engines: {node: '>= 14.0.0'} dev: false + /ts-api-utils@1.0.3(typescript@5.2.2): + resolution: {integrity: sha512-wNMeqtMz5NtwpT/UZGY5alT+VoKdSsOOP/kqHFcUW1P/VRhH2wJ48+DN2WwUliNbQ976ETwDL0Ifd2VVvgonvg==} + engines: {node: '>=16.13.0'} + peerDependencies: + typescript: '>=4.2.0' + dependencies: + typescript: 5.2.2 + dev: true + /tsc-alias@1.8.8: resolution: {integrity: sha512-OYUOd2wl0H858NvABWr/BoSKNERw3N9GTi3rHPK8Iv4O1UyUXIrTTOAZNHsjlVpXFOhpJBVARI1s+rzwLivN3Q==} hasBin: true @@ -8325,6 +9128,15 @@ packages: plimit-lit: 1.6.1 dev: true + /tsconfig-paths@3.14.2: + resolution: {integrity: sha512-o/9iXgCYc5L/JxCHPe3Hvh8Q/2xm5Z+p18PESBU6Ff33695QnCHBEjcytY2q19ua7Mbl/DavtBOLq+oG0RCL+g==} + dependencies: + '@types/json5': 0.0.29 + json5: 1.0.2 + minimist: 1.2.8 + strip-bom: 3.0.0 + dev: true + /tslib@1.14.1: resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} requiresBuild: true @@ -8353,6 +9165,7 @@ packages: engines: {node: '>= 0.8.0'} dependencies: prelude-ls: 1.2.1 + dev: true /type-detect@4.0.8: resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==} @@ -8362,6 +9175,7 @@ packages: /type-fest@0.20.2: resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} engines: {node: '>=10'} + dev: true /type-fest@2.19.0: resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==} @@ -8376,6 +9190,44 @@ packages: mime-types: 2.1.35 dev: false + /typed-array-buffer@1.0.0: + resolution: {integrity: sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.5 + get-intrinsic: 1.2.2 + is-typed-array: 1.1.12 + dev: true + + /typed-array-byte-length@1.0.0: + resolution: {integrity: sha512-Or/+kvLxNpeQ9DtSydonMxCx+9ZXOswtwJn17SNLvhptaXYDJvkFFP5zbfU/uLmvnBJlI4yrnXRxpdWH/M5tNA==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.5 + for-each: 0.3.3 + has-proto: 1.0.1 + is-typed-array: 1.1.12 + dev: true + + /typed-array-byte-offset@1.0.0: + resolution: {integrity: sha512-RD97prjEt9EL8YgAgpOkf3O4IF9lhJFr9g0htQkm0rchFp/Vx7LW5Q8fSXXub7BXAODyUQohRMyOc3faCPd0hg==} + engines: {node: '>= 0.4'} + dependencies: + available-typed-arrays: 1.0.5 + call-bind: 1.0.5 + for-each: 0.3.3 + has-proto: 1.0.1 + is-typed-array: 1.1.12 + dev: true + + /typed-array-length@1.0.4: + resolution: {integrity: sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==} + dependencies: + call-bind: 1.0.5 + for-each: 0.3.3 + is-typed-array: 1.1.12 + dev: true + /typescript@5.2.2: resolution: {integrity: sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==} engines: {node: '>=14.17'} @@ -8401,6 +9253,15 @@ packages: random-bytes: 1.0.0 dev: false + /unbox-primitive@1.0.2: + resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==} + dependencies: + call-bind: 1.0.5 + has-bigints: 1.0.2 + has-symbols: 1.0.3 + which-boxed-primitive: 1.0.2 + dev: true + /undefsafe@2.0.5: resolution: {integrity: sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==} dev: true @@ -8530,6 +9391,7 @@ packages: /v8-compile-cache@2.3.0: resolution: {integrity: sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==} + dev: true /validator@13.11.0: resolution: {integrity: sha512-Ii+sehpSfZy+At5nPdnyMhx78fEoPDkR2XW/zimHEL3MyGJQOCQ7WeP20jPYRz7ZCpcKLB21NxuXHF3bxjStBQ==} @@ -8604,6 +9466,16 @@ packages: dev: false optional: true + /which-boxed-primitive@1.0.2: + resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==} + dependencies: + is-bigint: 1.0.4 + is-boolean-object: 1.1.2 + is-number-object: 1.0.7 + is-string: 1.0.7 + is-symbol: 1.0.4 + dev: true + /which-typed-array@1.1.11: resolution: {integrity: sha512-qe9UWWpkeG5yzZ0tNYxDmd7vo58HDBc39mZ0xWWpolAGADdFOzkfamWLDxkOWcvHQKVmdTyQdLD4NOfjLWTKew==} engines: {node: '>= 0.4'} @@ -8615,6 +9487,16 @@ packages: has-tostringtag: 1.0.0 dev: false + /which-typed-array@1.1.13: + resolution: {integrity: sha512-P5Nra0qjSncduVPEAr7xhoF5guty49ArDTwzJ/yNuPIbZppyRxFQsRCWrocxIY+CnMVG+qfbU2FmDKyvSGClow==} + engines: {node: '>= 0.4'} + dependencies: + available-typed-arrays: 1.0.5 + call-bind: 1.0.5 + for-each: 0.3.3 + gopd: 1.0.1 + has-tostringtag: 1.0.0 + /which@2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} engines: {node: '>= 8'} From e1155abc44d4841744c83501c342aae2696011da Mon Sep 17 00:00:00 2001 From: withsang Date: Tue, 28 Nov 2023 23:40:52 +0900 Subject: [PATCH 09/61] Add: type definitions for some middlewares --- .eslintrc.cjs | 2 + package.json | 3 ++ pnpm-lock.yaml | 29 ++++++++++-- src/loadenv.ts | 18 ++++--- src/middlewares/{auth.js => auth.ts} | 8 ++-- .../{authAdmin.js => authAdmin.ts} | 18 ++++--- src/middlewares/cors.js | 8 ---- src/middlewares/cors.ts | 10 ++++ .../{errorHandler.js => errorHandler.ts} | 22 +++++---- src/middlewares/information.js | 5 -- src/middlewares/information.ts | 13 +++++ .../{limitRate.js => limitRate.ts} | 4 +- ...{originValidator.js => originValidator.ts} | 12 ++++- src/middlewares/responseTime.js | 11 ----- src/middlewares/responseTime.ts | 16 +++++++ src/middlewares/session.js | 14 ------ src/middlewares/session.ts | 40 ++++++++++++++++ src/modules/auths/jwt.js | 43 ----------------- src/modules/auths/jwt.ts | 47 +++++++++++++++++++ src/modules/auths/{login.js => login.ts} | 36 ++++++++------ src/modules/{logger.js => logger.ts} | 10 ++-- tsconfig.json | 4 +- types/index.d.ts | 19 ++++++++ 23 files changed, 256 insertions(+), 136 deletions(-) rename src/middlewares/{auth.js => auth.ts} (55%) rename src/middlewares/{authAdmin.js => authAdmin.ts} (63%) delete mode 100644 src/middlewares/cors.js create mode 100644 src/middlewares/cors.ts rename src/middlewares/{errorHandler.js => errorHandler.ts} (64%) delete mode 100644 src/middlewares/information.js create mode 100644 src/middlewares/information.ts rename src/middlewares/{limitRate.js => limitRate.ts} (80%) rename src/middlewares/{originValidator.js => originValidator.ts} (57%) delete mode 100644 src/middlewares/responseTime.js create mode 100644 src/middlewares/responseTime.ts delete mode 100644 src/middlewares/session.js create mode 100644 src/middlewares/session.ts delete mode 100644 src/modules/auths/jwt.js create mode 100644 src/modules/auths/jwt.ts rename src/modules/auths/{login.js => login.ts} (65%) rename src/modules/{logger.js => logger.ts} (93%) create mode 100644 types/index.d.ts diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 5812d98a..6f4e2b8c 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -34,6 +34,7 @@ module.exports = { "error", "ignorePackages", { + js: "never", // temporary fix for #159 ts: "never", }, ], @@ -45,6 +46,7 @@ module.exports = { }, ], "mocha/no-mocha-arrows": "off", + "no-console": "error", "no-restricted-imports": [ "error", { diff --git a/package.json b/package.json index 1f74e2a1..d0a3d172 100644 --- a/package.json +++ b/package.json @@ -59,8 +59,11 @@ "@types/cookie-parser": "^1.4.6", "@types/cors": "^2.8.16", "@types/express": "^4.17.21", + "@types/express-session": "^1.17.10", + "@types/jsonwebtoken": "^9.0.5", "@types/node": "^20.9.0", "@types/node-cron": "^3.0.11", + "@types/response-time": "^2.3.8", "@typescript-eslint/eslint-plugin": "^6.13.1", "@typescript-eslint/parser": "^6.13.1", "chai": "^4.3.10", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c08ab98a..72a2f835 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -115,12 +115,21 @@ devDependencies: '@types/express': specifier: ^4.17.21 version: 4.17.21 + '@types/express-session': + specifier: ^1.17.10 + version: 1.17.10 + '@types/jsonwebtoken': + specifier: ^9.0.5 + version: 9.0.5 '@types/node': specifier: ^20.9.0 version: 20.9.0 '@types/node-cron': specifier: ^3.0.11 version: 3.0.11 + '@types/response-time': + specifier: ^2.3.8 + version: 2.3.8 '@typescript-eslint/eslint-plugin': specifier: ^6.13.1 version: 6.13.1(@typescript-eslint/parser@6.13.1)(eslint@8.22.0)(typescript@5.2.2) @@ -3802,6 +3811,12 @@ packages: '@types/range-parser': 1.2.4 '@types/send': 0.17.1 + /@types/express-session@1.17.10: + resolution: {integrity: sha512-U32bC/s0ejXijw5MAzyaV4tuZopCh/K7fPoUDyNbsRXHvPSeymygYD1RFL99YOLhF5PNOkzswvOTRaVHdL1zMw==} + dependencies: + '@types/express': 4.17.21 + dev: true + /@types/express@4.17.21: resolution: {integrity: sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==} dependencies: @@ -3837,11 +3852,10 @@ packages: resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} dev: true - /@types/jsonwebtoken@9.0.2: - resolution: {integrity: sha512-drE6uz7QBKq1fYqqoFKTDRdFCPHd5TCub75BM+D+cMx7NU9hUz7SESLfC2fSCXVFMO5Yj8sOWHuGqPgjc+fz0Q==} + /@types/jsonwebtoken@9.0.5: + resolution: {integrity: sha512-VRLSGzik+Unrup6BsouBeHsf4d1hOEgYWTm/7Nmw1sXoN1+tRly/Gy/po3yeahnP4jfnQWWAhQAqcNfH7ngOkA==} dependencies: '@types/node': 20.9.0 - dev: false /@types/linkify-it@3.0.2: resolution: {integrity: sha512-HZQYqbiFVWufzCwexrvh694SOim8z2d+xJl5UNamcvQFejLY/2YUtzXHYi3cHdI7PMlS8ejH2slRAOJQ32aNbA==} @@ -3930,6 +3944,13 @@ packages: '@types/node': 20.9.0 dev: false + /@types/response-time@2.3.8: + resolution: {integrity: sha512-7qGaNYvdxc0zRab8oHpYx7AW17qj+G0xuag1eCrw3M2VWPJQ/HyKaaghWygiaOUl0y9x7QGQwppDpqLJ5V9pzw==} + dependencies: + '@types/express': 4.17.21 + '@types/node': 20.9.0 + dev: true + /@types/rimraf@3.0.2: resolution: {integrity: sha512-F3OznnSLAUxFrCEu/L5PY8+ny8DtcFRjx7fZZ9bycvXRi3KPTRS9HOitGZwvPg0juRhXFWIeKX58cnX5YqLohQ==} requiresBuild: true @@ -6848,7 +6869,7 @@ packages: engines: {node: '>=14'} dependencies: '@types/express': 4.17.21 - '@types/jsonwebtoken': 9.0.2 + '@types/jsonwebtoken': 9.0.5 debug: 4.3.4(supports-color@5.5.0) jose: 4.14.4 limiter: 1.1.5 diff --git a/src/loadenv.ts b/src/loadenv.ts index 6307bdca..15d6bf08 100644 --- a/src/loadenv.ts +++ b/src/loadenv.ts @@ -1,8 +1,11 @@ // 환경 변수에 따라 .env.production 또는 .env.development 파일을 읽어옵니다. import dotenv from "dotenv"; +import { type Algorithm } from "jsonwebtoken"; +import { type ServiceAccount } from "firebase-admin"; if (process.env.NODE_ENV === undefined) { // logger.ts가 아직 초기화되지 않았으므로 console.error를 사용합니다. + // eslint-disable-next-line no-console console.error("NODE_ENV 환경변수가 설정되어 있지 않습니다."); process.exit(1); } @@ -10,6 +13,7 @@ dotenv.config({ path: `./.env.${process.env.NODE_ENV}` }); if (process.env.DB_PATH === undefined) { // logger.ts가 아직 초기화되지 않았으므로 console.error를 사용합니다. + // eslint-disable-next-line no-console console.error("DB_PATH 환경변수가 설정되어 있지 않습니다."); process.exit(1); } @@ -27,11 +31,11 @@ export const sparcssso = { }; export const port = process.env.PORT ? parseInt(process.env.PORT) : 80; // optional (default = 80) export const corsWhiteList = (process.env.CORS_WHITELIST && - JSON.parse(process.env.CORS_WHITELIST)) || [true]; // optional (default = [true]) + JSON.parse(process.env.CORS_WHITELIST) as string[]) || [true]; // optional (default = [true]) export const aws = { - accessKeyId: process.env.AWS_ACCESS_KEY_ID, // required - secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY, // required - s3BucketName: process.env.AWS_S3_BUCKET_NAME, // required + accessKeyId: process.env.AWS_ACCESS_KEY_ID as string, // required + secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY as string, // required + s3BucketName: process.env.AWS_S3_BUCKET_NAME as string, // required s3Url: process.env.AWS_S3_URL || `https://${process.env.AWS_S3_BUCKET_NAME}.s3.ap-northeast-2.amazonaws.com`, // optional @@ -39,7 +43,7 @@ export const aws = { export const jwt = { secretKey: process.env.JWT_SECRET_KEY || "TAXI_JWT_KEY", option: { - algorithm: "HS256", + algorithm: "HS256" as Algorithm, // FIXME: remove FRONT_URL from issuer. 단, issuer를 변경하면 이전에 발급했던 모든 JWT가 무효화됩니다. // See https://github.com/sparcs-kaist/taxi-back/issues/415 issuer: process.env.FRONT_URL || "http://localhost:3000", // optional (default = "http://localhost:3000") @@ -49,9 +53,9 @@ export const jwt = { }; export const googleApplicationCredentials = process.env.GOOGLE_APPLICATION_CREDENTIALS && - JSON.parse(process.env.GOOGLE_APPLICATION_CREDENTIALS); // optional + JSON.parse(process.env.GOOGLE_APPLICATION_CREDENTIALS) as ServiceAccount; // optional export const testAccounts = - (process.env.TEST_ACCOUNTS && JSON.parse(process.env.TEST_ACCOUNTS)) || []; // optional + (process.env.TEST_ACCOUNTS && JSON.parse(process.env.TEST_ACCOUNTS) as string[]) || []; // optional export const slackWebhookUrl = { report: process.env.SLACK_REPORT_WEBHOOK_URL || "", // optional }; diff --git a/src/middlewares/auth.js b/src/middlewares/auth.ts similarity index 55% rename from src/middlewares/auth.js rename to src/middlewares/auth.ts index 15d1f5a5..535ed1ec 100644 --- a/src/middlewares/auth.js +++ b/src/middlewares/auth.ts @@ -1,8 +1,8 @@ // 로그인된 상태에만 접근할 수 있는 라우터(rooms)를 위한 미들웨어입니다. +import { type Request, type Response, type NextFunction } from "express"; +import { isLogin, getLoginInfo } from "@/modules/auths/login"; -const { isLogin, getLoginInfo } = require("@/modules/auths/login"); - -const authMiddleware = (req, res, next) => { +const authMiddleware = (req: Request, res: Response, next: NextFunction) => { if (!isLogin(req)) { res.status(403).json({ error: "not logged in", @@ -15,4 +15,4 @@ const authMiddleware = (req, res, next) => { } }; -module.exports = authMiddleware; +export default authMiddleware; diff --git a/src/middlewares/authAdmin.js b/src/middlewares/authAdmin.ts similarity index 63% rename from src/middlewares/authAdmin.js rename to src/middlewares/authAdmin.ts index 34fc5df6..75c67083 100644 --- a/src/middlewares/authAdmin.js +++ b/src/middlewares/authAdmin.ts @@ -1,9 +1,13 @@ // 관리자 유무를 확인하기 위한 미들웨어입니다. +import { type Request, type Response, type NextFunction } from "express"; +import { isLogin, getLoginInfo } from "@/modules/auths/login"; +import { userModel, adminIPWhitelistModel } from "@/modules/stores/mongo"; -const { isLogin, getLoginInfo } = require("@/modules/auths/login"); -const { userModel, adminIPWhitelistModel } = require("@/modules/stores/mongo"); - -const authAdminMiddleware = async (req, res, next) => { +const authAdminMiddleware = async ( + req: Request, + res: Response, + next: NextFunction +) => { try { // 로그인 여부를 확인 if (!isLogin(req)) return res.redirect(req.origin); @@ -22,10 +26,10 @@ const authAdminMiddleware = async (req, res, next) => { ) return res.redirect(req.origin); - next(); + return next(); } catch (e) { - res.redirect(req.origin); + return res.redirect(req.origin); } }; -module.exports = authAdminMiddleware; +export default authAdminMiddleware; diff --git a/src/middlewares/cors.js b/src/middlewares/cors.js deleted file mode 100644 index a1012607..00000000 --- a/src/middlewares/cors.js +++ /dev/null @@ -1,8 +0,0 @@ -var cors = require("cors"); -const { corsWhiteList } = require("@/loadenv"); - -module.exports = cors({ - origin: corsWhiteList, - credentials: true, - exposedHeaders: ["Date"], -}); diff --git a/src/middlewares/cors.ts b/src/middlewares/cors.ts new file mode 100644 index 00000000..63559565 --- /dev/null +++ b/src/middlewares/cors.ts @@ -0,0 +1,10 @@ +import cors from "cors"; +import { corsWhiteList } from "@/loadenv"; + +const corsMiddleware = cors({ + origin: corsWhiteList, + credentials: true, + exposedHeaders: ["Date"], +}); + +export default corsMiddleware; diff --git a/src/middlewares/errorHandler.js b/src/middlewares/errorHandler.ts similarity index 64% rename from src/middlewares/errorHandler.js rename to src/middlewares/errorHandler.ts index d9bdcc0c..1b8c26ca 100644 --- a/src/middlewares/errorHandler.js +++ b/src/middlewares/errorHandler.ts @@ -1,16 +1,22 @@ -const logger = require("@/modules/logger"); +import logger from "@/modules/logger"; +import { type Request, type Response, type NextFunction } from "express"; /** * Express app에서 사용할 custom global error handler를 정의합니다. * @summary Express 핸들러에서 발생한 uncaught exception은 이 핸들러를 통해 처리됩니다. * Express에서 제공하는 기본 global error handler는 클라이언트에 오류 발생 call stack을 그대로 반환합니다. * 이 때문에 클라이언트에게 잠재적으로 보안 취약점을 노출할 수 있으므로, call stack을 반환하지 않는 error handler를 정의합니다. - * @param {Error} err - 오류 객체 - * @param {Express.Request} req - 요청 객체 - * @param {Express.Response} res - 응답 객체 - * @param {Function} next - 다음 미들웨어 함수. Express에서는 next 함수에 err 인자를 넘겨주면 기본 global error handler가 호출됩니다. + * @param err - 오류 객체 + * @param req - 요청 객체 + * @param res - 응답 객체 + * @param next - 다음 미들웨어 함수. Express에서는 next 함수에 err 인자를 넘겨주면 기본 global error handler가 호출됩니다. */ -const errorHandler = (err, req, res, next) => { +const errorHandler = ( + err: Error, + req: Request, + res: Response, + next: NextFunction +) => { // 이미 클라이언트에 HTTP 응답 헤더를 전송한 경우, 응답 헤더를 다시 전송하지 않아야 합니다. // 클라이언트에게 스트리밍 형태로 응답을 전송하는 도중 오류가 발생하는 경우가 여기에 해당합니다. // 이럴 때 기본 global error handler를 호출하면 기본 global error handler가 클라이언트와의 연결을 종료시켜 줍니다. @@ -18,7 +24,7 @@ const errorHandler = (err, req, res, next) => { if (res.headersSent) { return next(err); } - res.status(500).send("internal server error"); + return res.status(500).send("internal server error"); }; -module.exports = errorHandler; +export default errorHandler; diff --git a/src/middlewares/information.js b/src/middlewares/information.js deleted file mode 100644 index f7197d1f..00000000 --- a/src/middlewares/information.js +++ /dev/null @@ -1,5 +0,0 @@ -module.exports = (req, res, next) => { - req.clientIP = req.headers["x-forwarded-for"] || req.connection.remoteAddress; - req.timestamp = Date.now(); - next(); -}; diff --git a/src/middlewares/information.ts b/src/middlewares/information.ts new file mode 100644 index 00000000..3d2cd606 --- /dev/null +++ b/src/middlewares/information.ts @@ -0,0 +1,13 @@ +import { type Request, type Response, type NextFunction } from "express"; + +const informationMiddleware = ( + req: Request, + res: Response, + next: NextFunction +) => { + req.clientIP = req.headers["x-forwarded-for"] || req.connection.remoteAddress; + req.timestamp = Date.now(); + next(); +}; + +export default informationMiddleware; diff --git a/src/middlewares/limitRate.js b/src/middlewares/limitRate.ts similarity index 80% rename from src/middlewares/limitRate.js rename to src/middlewares/limitRate.ts index 4cba6af3..d137892b 100644 --- a/src/middlewares/limitRate.js +++ b/src/middlewares/limitRate.ts @@ -1,4 +1,4 @@ -const rateLimit = require("express-rate-limit"); +import rateLimit from "express-rate-limit"; const limiter = rateLimit({ windowMs: 15 * 60 * 1000, // 15 minutes @@ -7,4 +7,4 @@ const limiter = rateLimit({ legacyHeaders: false, // Disable the `X-RateLimit-*` headers }); -module.exports = limiter; +export default limiter; diff --git a/src/middlewares/originValidator.js b/src/middlewares/originValidator.ts similarity index 57% rename from src/middlewares/originValidator.js rename to src/middlewares/originValidator.ts index 2aec851d..3927d46e 100644 --- a/src/middlewares/originValidator.js +++ b/src/middlewares/originValidator.ts @@ -1,4 +1,10 @@ -module.exports = (req, res, next) => { +import { type Request, type Response, type NextFunction } from "express"; + +const originValidatorMiddleware = ( + req: Request, + res: Response, + next: NextFunction +) => { req.origin = req.headers.origin || req.headers.referer || @@ -9,5 +15,7 @@ module.exports = (req, res, next) => { error: "Bad Request : request must have origin in header", }); } - next(); + return next(); }; + +export default originValidatorMiddleware; diff --git a/src/middlewares/responseTime.js b/src/middlewares/responseTime.js deleted file mode 100644 index c042ffb3..00000000 --- a/src/middlewares/responseTime.js +++ /dev/null @@ -1,11 +0,0 @@ -const logger = require("@/modules/logger"); -const responseTime = require("response-time"); - -module.exports = responseTime((req, res, time) => { - const { method, originalUrl, clientIP } = req; - const { statusCode } = res; - const userId = req.session?.loginInfo?.id || "anonymous"; - logger.info( - `${userId}(${clientIP}) "${method} ${originalUrl}" ${statusCode} on ${time}ms` - ); -}); diff --git a/src/middlewares/responseTime.ts b/src/middlewares/responseTime.ts new file mode 100644 index 00000000..aec93458 --- /dev/null +++ b/src/middlewares/responseTime.ts @@ -0,0 +1,16 @@ +import { type Request, type Response } from "express"; +import logger from "@/modules/logger"; +import responseTime from "response-time"; + +const responseTimeMiddleware = responseTime( + (req: Request, res: Response, time: number) => { + const { method, originalUrl, clientIP } = req; + const { statusCode } = res; + const userId = req.session?.loginInfo?.id || "anonymous"; + logger.info( + `${userId}(${clientIP}) "${method} ${originalUrl}" ${statusCode} on ${time}ms` + ); + } +); + +export default responseTimeMiddleware; diff --git a/src/middlewares/session.js b/src/middlewares/session.js deleted file mode 100644 index b4be3266..00000000 --- a/src/middlewares/session.js +++ /dev/null @@ -1,14 +0,0 @@ -const expressSession = require("express-session"); -const { nodeEnv, session: sessionConfig } = require("../../loadenv"); -const sessionStore = require("@/modules/stores/sessionStore"); - -module.exports = expressSession({ - secret: sessionConfig.secret, - resave: false, - saveUninitialized: false, - store: sessionStore, - cookie: { - maxAge: sessionConfig.expiry, - secure: nodeEnv === "production", - }, -}); diff --git a/src/middlewares/session.ts b/src/middlewares/session.ts new file mode 100644 index 00000000..5a49e208 --- /dev/null +++ b/src/middlewares/session.ts @@ -0,0 +1,40 @@ +import expressSession from "express-session"; +import { nodeEnv, session as sessionConfig } from "@/loadenv"; +import sessionStore from "@/modules/stores/sessionStore"; +import { type LoginInfo } from "@/modules/auths/login"; + +// 세션에 저장할 데이터 타입을 지정합니다. +declare module 'express-session' { + interface SessionData { + /** 사용자 로그인 정보 */ + loginInfo?: LoginInfo; + /** 현재 로그인된 사용자가 앱으로 접속했는지 여부 */ + isApp?: boolean; + /** SPARCS SSO 로그인 시 state와 로그인 후 redirect 주소를 저장할 object. 타입 수정 필요. */ + loginAfterState?: { + state?: string; + redirectOrigin?: string; + redirectPath?: string; + }; + /** 앱 로그인용 access token */ + accessToken?: string; + /** 앱 로그인용 refresh token */ + refreshToken?: string; + /** FCM용 device token */ + deviceToken?: string; + } +} + +const sessionMiddleware = expressSession({ + secret: sessionConfig.secret, + resave: false, + saveUninitialized: false, + store: sessionStore, + cookie: { + maxAge: sessionConfig.expiry, + // nodeEnv가 production일 때만 secure cookie를 사용합니다. + secure: nodeEnv === "production", + }, +}); + +export default sessionMiddleware; \ No newline at end of file diff --git a/src/modules/auths/jwt.js b/src/modules/auths/jwt.js deleted file mode 100644 index 4e8f7259..00000000 --- a/src/modules/auths/jwt.js +++ /dev/null @@ -1,43 +0,0 @@ -const jwt = require("jsonwebtoken"); -const { secretKey, option, TOKEN_EXPIRED, TOKEN_INVALID } = - require("@/loadenv").jwt; - -const signJwt = async ({ id, type }) => { - const payload = { - id: id, - type: type, - }; - - const options = { ...option }; - - if (type === "refresh") { - options.expiresIn = "30d"; - } - if (type === "access") { - options.expiresIn = "14d"; - } - - const result = { - token: jwt.sign(payload, secretKey, options), - }; - return result; -}; - -const verifyJwt = async (token) => { - let decoded; - try { - decoded = jwt.verify(token, secretKey); - } catch (err) { - if (err.message === "jwt expired") { - return TOKEN_EXPIRED; - } else { - return TOKEN_INVALID; - } - } - return decoded; -}; - -module.exports = { - sign: signJwt, - verify: verifyJwt, -}; diff --git a/src/modules/auths/jwt.ts b/src/modules/auths/jwt.ts new file mode 100644 index 00000000..ee009330 --- /dev/null +++ b/src/modules/auths/jwt.ts @@ -0,0 +1,47 @@ +import jwt, { type SignOptions } from "jsonwebtoken"; +import { jwt as jwtConfig } from "@/loadenv"; + +const { secretKey, option, TOKEN_EXPIRED, TOKEN_INVALID } = jwtConfig; + +type TokenType = "access" | "refresh"; + +interface SignType { + id: string; + type: TokenType; +} + +export const sign = async ({ id, type }: SignType) => { + const payload = { + id, + type, + }; + + const options: SignOptions = { ...option }; + + if (type === "refresh") { + options.expiresIn = "30d"; + } + if (type === "access") { + options.expiresIn = "14d"; + } + + const result = { + token: jwt.sign(payload, secretKey, options), + }; + return result; +}; + +export const verify = async (token: string) => { + let decoded; + try { + decoded = jwt.verify(token, secretKey); + } catch (err) { + if (err instanceof Error) { + if (err.message === "jwt expired") { + return TOKEN_EXPIRED; + } + } + return TOKEN_INVALID; + } + return decoded; +}; diff --git a/src/modules/auths/login.js b/src/modules/auths/login.ts similarity index 65% rename from src/modules/auths/login.js rename to src/modules/auths/login.ts index 7c4f6baf..a55677b6 100644 --- a/src/modules/auths/login.js +++ b/src/modules/auths/login.ts @@ -1,7 +1,16 @@ -const { session: sessionConfig } = require("@/loadenv"); -const logger = require("@/modules/logger"); +import { type Request } from "express"; +import { session as sessionConfig } from "@/loadenv"; +import logger from "@/modules/logger"; -const getLoginInfo = (req) => { +export interface LoginInfo { + id: string; + sid: string; + oid: string; + name: string; + time: number; +} + +export const getLoginInfo = (req: Request) => { if (req.session.loginInfo) { const { id, sid, oid, name, time } = req.session.loginInfo; const timeFlow = Date.now() - time; @@ -15,17 +24,23 @@ const getLoginInfo = (req) => { return { id: undefined, sid: undefined, oid: undefined, name: undefined }; }; -const isLogin = (req) => { +export const isLogin = (req: Request) => { const loginInfo = getLoginInfo(req); if (loginInfo.id) return true; - else return false; + return false; }; -const login = (req, sid, id, oid, name) => { +export const login = ( + req: Request, + sid: string, + id: string, + oid: string, + name: string +) => { req.session.loginInfo = { sid, id, oid, name, time: Date.now() }; }; -const logout = (req) => { +export const logout = (req: Request) => { // 로그아웃 전 socket.io 소켓들 연결부터 끊기 const io = req.app.get("io"); if (io) io.in(`session-${req.session.id}`).disconnectSockets(true); @@ -34,10 +49,3 @@ const logout = (req) => { if (err) logger.error(err); }); }; - -module.exports = { - getLoginInfo, - isLogin, - login, - logout, -}; diff --git a/src/modules/logger.js b/src/modules/logger.ts similarity index 93% rename from src/modules/logger.js rename to src/modules/logger.ts index 3baa80d5..b4b91610 100644 --- a/src/modules/logger.js +++ b/src/modules/logger.ts @@ -1,8 +1,8 @@ -const path = require("path"); -const { createLogger, format, transports } = require("winston"); -const DailyRotateFileTransport = require("winston-daily-rotate-file"); +import path from "path"; +import { createLogger, format, transports } from "winston"; +import DailyRotateFileTransport from "winston-daily-rotate-file"; -const { nodeEnv } = require("@/loadenv"); +import { nodeEnv } from "@/loadenv"; // logger에서 사용할 포맷들을 정의합니다. const baseFormat = format.combine( @@ -92,4 +92,4 @@ const logger = exceptionHandlers: [consoleTransport], }); -module.exports = logger; +export default logger; diff --git a/tsconfig.json b/tsconfig.json index 415e2140..0e7f68ff 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -14,7 +14,7 @@ "@/*": ["./*"] } }, - "include": ["src"], - "exclude": ["dist", "node_modules", "src/lottery"] + "include": ["src", "types"], + "exclude": ["dist", "node_modules"] } \ No newline at end of file diff --git a/types/index.d.ts b/types/index.d.ts new file mode 100644 index 00000000..acee1ace --- /dev/null +++ b/types/index.d.ts @@ -0,0 +1,19 @@ +// to make the file a module and avoid the TypeScript error +export {}; + +declare global { + namespace Express { + export interface Request { + /** 사용자 ID. SPARCS SSO로부터 전달받습니다. */ + userId?: string; + /** 사용자의 ObjectID. MongoDB에서 사용됩니다. */ + userOid?: string; + /** 요청의 origin. */ + origin?: string; + /** 사용자의 IP 주소. */ + clientIP?: string; + /** 요청의 timestamp. */ + timestamp?: number; + } + } +} \ No newline at end of file From 052d144e70adcff4ed7b2d8bf6f2b74acb0e4078 Mon Sep 17 00:00:00 2001 From: withsang Date: Wed, 29 Nov 2023 00:05:48 +0900 Subject: [PATCH 10/61] Add: type definitions for templates --- src/middlewares/validator.js | 11 ----------- src/middlewares/validator.ts | 18 ++++++++++++++++++ src/schedules/index.ts | 8 +++++--- ...tlementPage.js => emailNoSettlementPage.ts} | 13 +++++++++++-- src/views/{emailPage.js => emailPage.ts} | 10 ++++++---- ...loginReplacePage.js => loginReplacePage.ts} | 4 +++- 6 files changed, 43 insertions(+), 21 deletions(-) delete mode 100644 src/middlewares/validator.js create mode 100644 src/middlewares/validator.ts rename src/views/{emailNoSettlementPage.js => emailNoSettlementPage.ts} (88%) rename src/views/{emailPage.js => emailPage.ts} (92%) rename src/views/{loginReplacePage.js => loginReplacePage.ts} (97%) diff --git a/src/middlewares/validator.js b/src/middlewares/validator.js deleted file mode 100644 index 8bdeb4c4..00000000 --- a/src/middlewares/validator.js +++ /dev/null @@ -1,11 +0,0 @@ -const { validationResult } = require("express-validator"); - -module.exports = (req, res, next) => { - const validationErrors = validationResult(req); - if (!validationErrors.isEmpty()) { - return res.status(400).json({ - error: "validation : bad request", - }); - } - next(); -}; diff --git a/src/middlewares/validator.ts b/src/middlewares/validator.ts new file mode 100644 index 00000000..b4e1b0cf --- /dev/null +++ b/src/middlewares/validator.ts @@ -0,0 +1,18 @@ +import { type Request, type Response, type NextFunction } from "express"; +import { validationResult } from "express-validator"; + +const validatorMiddleware = ( + req: Request, + res: Response, + next: NextFunction +) => { + const validationErrors = validationResult(req); + if (!validationErrors.isEmpty()) { + return res.status(400).json({ + error: "validation : bad request", + }); + } + return next(); +}; + +export default validatorMiddleware; diff --git a/src/schedules/index.ts b/src/schedules/index.ts index f57ca9dc..f9e62477 100644 --- a/src/schedules/index.ts +++ b/src/schedules/index.ts @@ -1,9 +1,11 @@ -import { Express } from "express"; +import { type Express } from "express"; import cron from "node-cron"; +import notifyBeforeDepart from "./notifyBeforeDepart"; +import notifyAfterArrival from "./notifyAfterArrival"; const registerSchedules = (app: Express) => { - cron.schedule("*/5 * * * *", require("./notifyBeforeDepart")(app)); - cron.schedule("*/10 * * * *", require("./notifyAfterArrival")(app)); + cron.schedule("*/5 * * * *", notifyBeforeDepart(app)); + cron.schedule("*/10 * * * *", notifyAfterArrival(app)); }; export default registerSchedules; diff --git a/src/views/emailNoSettlementPage.js b/src/views/emailNoSettlementPage.ts similarity index 88% rename from src/views/emailNoSettlementPage.js rename to src/views/emailNoSettlementPage.ts index 5143c279..b9bd3ece 100644 --- a/src/views/emailNoSettlementPage.js +++ b/src/views/emailNoSettlementPage.ts @@ -1,6 +1,13 @@ -const emailPage = require("./emailPage"); +import emailPage from "./emailPage"; -module.exports = (origin, name, nickname, roomName, payer, roomId) => +const emailNoSettlementPage = ( + origin: string, + name: string, + nickname: string, + roomName: string, + payer: string, + roomId: string +) => emailPage( "미정산 내역 관련 안내", `${name} (${nickname}) 님께

@@ -32,3 +39,5 @@ module.exports = (origin, name, nickname, roomName, payer, roomId) => SPARCS Taxi팀 드림. ` ); + +export default emailNoSettlementPage; diff --git a/src/views/emailPage.js b/src/views/emailPage.ts similarity index 92% rename from src/views/emailPage.js rename to src/views/emailPage.ts index d89f297a..b6b1a145 100644 --- a/src/views/emailPage.js +++ b/src/views/emailPage.ts @@ -1,8 +1,8 @@ -const { getS3Url } = require("@/modules/stores/aws"); +import { getS3Url } from "@/modules/stores/aws"; -module.exports = ( - title, - content +const emailPage = ( + title: string, + content: string ) => `
@@ -29,3 +29,5 @@ module.exports = (
`; + +export default emailPage; diff --git a/src/views/loginReplacePage.js b/src/views/loginReplacePage.ts similarity index 97% rename from src/views/loginReplacePage.js rename to src/views/loginReplacePage.ts index 209e0bb0..cc20046c 100644 --- a/src/views/loginReplacePage.js +++ b/src/views/loginReplacePage.ts @@ -1,4 +1,4 @@ -module.exports = ` +const loginPage = ` @@ -44,3 +44,5 @@ module.exports = ` `; + +export default loginPage; From 76242380db2e96e86bda5881e2f2b725473d1f59 Mon Sep 17 00:00:00 2001 From: static Date: Thu, 25 Jan 2024 06:57:50 +0900 Subject: [PATCH 11/61] Fix: compile error --- src/middlewares/authAdmin.ts | 12 ++++++------ src/middlewares/information.ts | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/middlewares/authAdmin.ts b/src/middlewares/authAdmin.ts index 75c67083..342906b9 100644 --- a/src/middlewares/authAdmin.ts +++ b/src/middlewares/authAdmin.ts @@ -10,25 +10,25 @@ const authAdminMiddleware = async ( ) => { try { // 로그인 여부를 확인 - if (!isLogin(req)) return res.redirect(req.origin); + if (!isLogin(req)) return res.redirect(req.origin ?? "/"); // 관리자 유무를 확인 const { id } = getLoginInfo(req); const user = await userModel.findOne({ id }); - if (!user.isAdmin) return res.redirect(req.origin); + if (!user.isAdmin) return res.redirect(req.origin ?? "/"); // 접속한 IP가 화이트리스트에 있는지 확인 const ipWhitelist = await adminIPWhitelistModel.find({}); - if (!req.clientIP) return res.redirect(req.origin); + if (!req.clientIP) return res.redirect(req.origin ?? "/"); if ( ipWhitelist.length > 0 && - ipWhitelist.map((x) => x.ip).indexOf(req.clientIP) < 0 + ipWhitelist.map((x: any) => x.ip).indexOf(req.clientIP) < 0 // TODO: Remove any ) - return res.redirect(req.origin); + return res.redirect(req.origin ?? "/"); return next(); } catch (e) { - return res.redirect(req.origin); + return res.redirect(req.origin ?? "/"); } }; diff --git a/src/middlewares/information.ts b/src/middlewares/information.ts index 3d2cd606..4d209eec 100644 --- a/src/middlewares/information.ts +++ b/src/middlewares/information.ts @@ -5,7 +5,7 @@ const informationMiddleware = ( res: Response, next: NextFunction ) => { - req.clientIP = req.headers["x-forwarded-for"] || req.connection.remoteAddress; + req.clientIP = req.headers["x-forwarded-for"] || req.connection.remoteAddress; req.timestamp = Date.now(); next(); }; From f5ece38f7e282265efc6e96b75032e66667ae88f Mon Sep 17 00:00:00 2001 From: static Date: Thu, 25 Jan 2024 22:12:39 +0900 Subject: [PATCH 12/61] Refactor: migrate middlewares/ajv to TS --- src/middlewares/ajv.js | 45 ------------------------------------------ src/middlewares/ajv.ts | 36 +++++++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 45 deletions(-) delete mode 100644 src/middlewares/ajv.js create mode 100644 src/middlewares/ajv.ts diff --git a/src/middlewares/ajv.js b/src/middlewares/ajv.js deleted file mode 100644 index 0c8a8a3b..00000000 --- a/src/middlewares/ajv.js +++ /dev/null @@ -1,45 +0,0 @@ -const Ajv = require("ajv"); -const ajvErrors = require("ajv-errors"); -const { default: addFormats } = require("ajv-formats"); - -const ajv = new Ajv({ verbose: true, allErrors: true }); -addFormats(ajv); -ajvErrors(ajv); - -const parseAjvErrors = (errors, res) => { - const error_message = errors; - res.status(400).send(error_message); -}; - -const validate = (schema, req, res) => { - const validate = ajv.compile(schema); - if (validate(req)) { - return true; - } else { - parseAjvErrors(validate.errors[0].message, res); - } -}; - -const validateBody = (schema) => { - return (req, res, next) => { - if (validate(schema, req.body, res)) return next(); - }; -}; - -const validateQuery = (schema) => { - return (req, res, next) => { - if (validate(schema, req.query, res)) return next(); - }; -}; - -const validateParams = (schema) => { - return (req, res, next) => { - if (validate(schema, req.params, res)) return next(); - }; -}; - -module.exports = { - validateParams, - validateBody, - validateQuery, -}; diff --git a/src/middlewares/ajv.ts b/src/middlewares/ajv.ts new file mode 100644 index 00000000..359adc6e --- /dev/null +++ b/src/middlewares/ajv.ts @@ -0,0 +1,36 @@ +import Ajv from "ajv"; +import ajvErrors from "ajv-errors"; +import { default as addFormats } from "ajv-formats"; +import { type Request, type Response, type NextFunction } from "express"; + +const ajv = new Ajv({ verbose: true, allErrors: true }); +addFormats(ajv); +ajvErrors(ajv); + +const validate = (schema: Object, req: Request, res: Response) => { + const validate = ajv.compile(schema); + if (validate(req)) { + return true; + } else { + res.status(400).send(validate.errors?.[0].message ?? "Validation Error"); // TODO: 에러 메시지 수정 + return false; + } +}; + +export const validateBody = (schema: Object) => { + return (req: Request, res: Response, next: NextFunction) => { + if (validate(schema, req, res)) return next(); + }; +}; + +export const validateQuery = (schema: Object) => { + return (req: Request, res: Response, next: NextFunction) => { + if (validate(schema, req, res)) return next(); + }; +}; + +export const validateParams = (schema: Object) => { + return (req: Request, res: Response, next: NextFunction) => { + if (validate(schema, req, res)) return next(); + }; +}; From 5deedf9941e64538ee05f965acd0438aa16eca55 Mon Sep 17 00:00:00 2001 From: static Date: Fri, 26 Jan 2024 22:44:48 +0900 Subject: [PATCH 13/61] Refactor: migrate modules/stores/mongo to TS --- src/middlewares/authAdmin.ts | 2 +- src/modules/stores/{mongo.js => mongo.ts} | 82 ++++++------ types/mongo.d.ts | 153 ++++++++++++++++++++++ 3 files changed, 193 insertions(+), 44 deletions(-) rename src/modules/stores/{mongo.js => mongo.ts} (72%) create mode 100644 types/mongo.d.ts diff --git a/src/middlewares/authAdmin.ts b/src/middlewares/authAdmin.ts index 342906b9..af9c7b5d 100644 --- a/src/middlewares/authAdmin.ts +++ b/src/middlewares/authAdmin.ts @@ -15,7 +15,7 @@ const authAdminMiddleware = async ( // 관리자 유무를 확인 const { id } = getLoginInfo(req); const user = await userModel.findOne({ id }); - if (!user.isAdmin) return res.redirect(req.origin ?? "/"); + if (!user?.isAdmin) return res.redirect(req.origin ?? "/"); // 접속한 IP가 화이트리스트에 있는지 확인 const ipWhitelist = await adminIPWhitelistModel.find({}); diff --git a/src/modules/stores/mongo.js b/src/modules/stores/mongo.ts similarity index 72% rename from src/modules/stores/mongo.js rename to src/modules/stores/mongo.ts index 7aaebb27..f69e206a 100755 --- a/src/modules/stores/mongo.js +++ b/src/modules/stores/mongo.ts @@ -1,9 +1,8 @@ -const mongoose = require("mongoose"); -const Schema = mongoose.Schema; +import mongoose, { Schema, Model } from "mongoose"; +import logger from "@/modules/logger"; +import type { User, Participant, DeviceToken, NotificationOption, TopicSubscription, Room, Location, Chat, Report, AdminIPWhitelist, AdminLog } from "@/../types/mongo"; // TODO: Am I right..? -const logger = require("@/modules/logger"); - -const userSchema = Schema({ +const userSchema = new Schema, User>({ name: { type: String, required: true }, //실명 nickname: { type: String, required: true }, //닉네임 id: { type: String, required: true, unique: true }, //택시 서비스에서만 사용되는 id @@ -26,7 +25,7 @@ const userSchema = Schema({ account: { type: String, default: "" }, //계좌번호 정보 }); -const participantSchema = Schema({ +const participantSchema = new Schema, Participant>({ user: { type: Schema.Types.ObjectId, ref: "User", required: true }, settlementStatus: { type: String, @@ -37,7 +36,7 @@ const participantSchema = Schema({ readAt: { type: Date }, }); -const deviceTokenSchema = Schema({ +const deviceTokenSchema = new Schema, DeviceToken>({ userId: { type: Schema.Types.ObjectId, ref: "User", @@ -48,7 +47,7 @@ const deviceTokenSchema = Schema({ }); // 각 디바이스의 알림 설정 -const notificationOptionSchema = Schema({ +const notificationOptionSchema = new Schema, NotificationOption>({ deviceToken: { type: String, required: true, @@ -82,7 +81,7 @@ const notificationOptionSchema = Schema({ }, //광고성 알림 수신 여부 }); -const topicSubscriptionSchema = Schema({ +const topicSubscriptionSchema = new Schema, TopicSubscription>({ deviceToken: String, topic: String, subscribedAt: { @@ -92,7 +91,7 @@ const topicSubscriptionSchema = Schema({ }, }); -const roomSchema = Schema({ +const roomSchema = new Schema, Room>({ name: { type: String, required: true, default: "이름 없음", text: true }, from: { type: Schema.Types.ObjectId, ref: "Location", required: true }, to: { type: Schema.Types.ObjectId, ref: "Location", required: true }, @@ -100,7 +99,7 @@ const roomSchema = Schema({ part: { type: [participantSchema], validate: [ - function (value) { + function (this: Room, value: Participant[]) { return value.length <= this.maxPartLength; }, ], @@ -110,7 +109,7 @@ const roomSchema = Schema({ maxPartLength: { type: Number, require: true, default: 4 }, }); -const locationSchema = Schema({ +const locationSchema = new Schema, Location>({ enName: { type: String, required: true }, koName: { type: String, required: true }, priority: { type: Number, default: 0 }, @@ -119,7 +118,7 @@ const locationSchema = Schema({ longitude: { type: Number }, // 이후 required: true 로 수정 필요 }); -const chatSchema = Schema({ +const chatSchema = new Schema, Chat>({ roomId: { type: Schema.Types.ObjectId, ref: "Room", required: true }, type: { type: String, @@ -142,7 +141,7 @@ const chatSchema = Schema({ }); chatSchema.index({ roomId: 1, time: -1 }); -const reportSchema = Schema({ +const reportSchema = new Schema, Report>({ creatorId: { type: Schema.Types.ObjectId, ref: "User", required: true }, // 신고한 사람 id reportedId: { type: Schema.Types.ObjectId, ref: "User", required: true }, // 신고받은 사람 id type: { @@ -155,12 +154,12 @@ const reportSchema = Schema({ roomId: { type: Schema.Types.ObjectId, ref: "Room" }, // 신고한 방 id }); -const adminIPWhitelistSchema = Schema({ +const adminIPWhitelistSchema = new Schema, AdminIPWhitelist>({ ip: { type: String, required: true }, // IP 주소 description: { type: String, default: "" }, // 설명 }); -const adminLogSchema = Schema({ +const adminLogSchema = new Schema, AdminLog>({ user: { type: Schema.Types.ObjectId, ref: "User", required: true }, // Log 취급자 User time: { type: Date, required: true }, // Log 발생 시각 ip: { type: String, required: true }, // 접속 IP 주소 @@ -184,45 +183,42 @@ database.on("error", function (err) { mongoose.disconnect(); }); -const connectDatabase = (mongoUrl) => { +export const connectDatabase = (mongoUrl: string) => { database.on("disconnected", function () { // 데이터베이스 연결이 끊어지면 5초 후 재연결을 시도합니다. logger.error("데이터베이스와 연결이 끊어졌습니다!"); setTimeout(() => { - mongoose.connect(mongoUrl, { + mongoose.connect(mongoUrl, /*{ useNewUrlParser: true, useUnifiedTopology: true, - }); + }*/); // NOTE: https://velog.io/@untiring_dev/MongoDB-MongoDB-Mongoose%EC%97%90-%EC%97%B0%EA%B2%B0 }, 5000); }); - mongoose.connect(mongoUrl, { + mongoose.connect(mongoUrl, /*{ useNewUrlParser: true, useUnifiedTopology: true, - }); + }*/); return database; }; -module.exports = { - connectDatabase, - userModel: mongoose.model("User", userSchema), - deviceTokenModel: mongoose.model("DeviceToken", deviceTokenSchema), - notificationOptionModel: mongoose.model( - "NotificationOption", - notificationOptionSchema - ), - topicSubscriptionModel: mongoose.model( - "TopicSubscription", - topicSubscriptionSchema - ), - roomModel: mongoose.model("Room", roomSchema), - locationModel: mongoose.model("Location", locationSchema), - chatModel: mongoose.model("Chat", chatSchema), - reportModel: mongoose.model("Report", reportSchema), - adminIPWhitelistModel: mongoose.model( - "AdminIPWhitelist", - adminIPWhitelistSchema - ), - adminLogModel: mongoose.model("AdminLog", adminLogSchema), -}; +export const userModel = mongoose.model("User", userSchema); +export const deviceTokenModel = mongoose.model("DeviceToken", deviceTokenSchema); +export const notificationOptionModel = mongoose.model( + "NotificationOption", + notificationOptionSchema +); +export const topicSubscriptionModel = mongoose.model( + "TopicSubscription", + topicSubscriptionSchema +); +export const roomModel = mongoose.model("Room", roomSchema); +export const locationModel = mongoose.model("Location", locationSchema); +export const chatModel = mongoose.model("Chat", chatSchema); +export const reportModel = mongoose.model("Report", reportSchema); +export const adminIPWhitelistModel = mongoose.model( + "AdminIPWhitelist", + adminIPWhitelistSchema +); +export const adminLogModel = mongoose.model("AdminLog", adminLogSchema); diff --git a/types/mongo.d.ts b/types/mongo.d.ts new file mode 100644 index 00000000..c7f63359 --- /dev/null +++ b/types/mongo.d.ts @@ -0,0 +1,153 @@ +import { Types } from "mongoose"; + +export interface User { + /** 사용자의 실명. */ + name: string; + /** 사용자의 닉네임. */ + nickname: string; + /** Taxi에서만 사용되는 사용자의 ID. */ + id: string; + /** 계정 프로필 이미지 주소. */ + profileImageUrl: string; + /** 사용자가 참여한 방 중 현재 진행 중인 방의 배열. */ + ongoingRoom?: string[]; + /** 사용자가 참여한 방 중 완료된 방의 배열. */ + doneRoom?: string[]; + withdraw?: boolean; + /** 사용자의 전화번호. 2023 가을 이벤트부터 추가됨. */ + phoneNumber?: string; + /** 계정 정지 여부. */ + ban?: boolean; + /** 계정 가입 시각. */ + joinat: Date; + /** 사용자의 Taxi 이용약관 동의 여부. */ + agreeOnTermsOfService?: boolean; + subinfo?: { + /** 사용자의 KAIST 학번. */ + kaist?: string, + sparcs?: string, + facebook?: string, + twitter?: string, + }; + /** 사용자의 이메일 주소. */ + email: string; + /** 계정의 관리자 여부. */ + isAdmin?: boolean; + /** 사용자의 계좌번호 정보. */ + account?: string; +} + +export interface Participant { + /** 방 참여자의 User ObjectID. */ + user: Types.ObjectId; + /** 방 참여자의 정산 상태. */ + settlementStatus: "not-departed" | "paid" | "send-required" | "sent"; + /** 방 참여자가 마지막으로 채팅을 읽은 시각. */ + readAt?: Date; +} + +export interface DeviceToken { + /** 디바이스 토큰 소유자의 User ObjectID. */ + userId: Types.ObjectId; + /** 소유한 디바이스 토큰의 배열. */ + deviceTokens: string[]; +} + +export interface NotificationOption { + deviceToken: string; + /** 채팅 알림 수신 여부. */ + chatting: boolean; + /** 방 알림 키워드. */ + keywords: string[]; + /** 출발 전 알림 발송 여부. */ + beforeDepart: boolean; + /** 공지성 알림 수신 여부. */ + notice: boolean; + /** 광고성 알림 수신 여부. */ + advertisement: boolean; +} + +export interface TopicSubscription { + deviceToken?: string; + topic?: string; + subscribedAt: Date; +} + +export interface Room { + /** 방의 이름. */ + name: string; + /** 방의 출발지의 Location ObjectID. */ + from: Types.ObjectId; + /** 방의 목적지의 Location ObjectID. */ + to: Types.ObjectId; + /** 방의 출발 시각. */ + time: Date; + /** 방 참여자의 배열. */ + part?: Participant[]; + /** 방의 생성 시각. */ + madeat: Date; + /** 방 참여자 중 정산을 완료한 참여자의 수. */ + settlementTotal: number; + /** 방의 최대 참여자 수. */ + maxPartLength: number; +} + +export interface Location { + enName: string; + koName: string; + priority?: number; + isValid?: boolean; + /** 위도. */ + latitude?: number; + /** 경도. */ + longitude?: number; +} + +export interface Chat { + /** 메세지가 전송된 방의 Room ObjectID. */ + roomId: Types.ObjectId; + /** 메세지의 종류. */ + type?: + | "text" + | "in" + | "out" + | "s3img" + | "payment" + | "settlement" + | "account" + | "departure" + | "arrival"; + /** 메세지의 작성자의 User ObjectID. */ + authorId?: Types.ObjectId; + content?: string; + time: Date; + isValid?: boolean; +} + +export interface Report { + /** 신고한 사용자의 ObjectID. */ + creatorId: Types.ObjectId; + /** 신고받은 사용자의 ObjectID. */ + reportedId: Types.ObjectId; + /** 신고의 종류. */ + type: "no-settlement" | "no-show" | "etc-reason"; + /** 신고의 기타 세부 사유. */ + etcDetail?: string; + /** 신고한 시각. */ + time: Date; + /** 신고한 방의 ObjectID. */ + roomId?: Types.ObjectId; +} + +export interface AdminIPWhitelist { + ip: string; + description?: string; +} + +export interface AdminLog { + user: Types.ObjectId; + time: Date; + ip: string; + target?: string; + action: "create" | "read" | "update" | "delete"; +} From ea89f43fbe5032f7cb90c8c15a225125a28df4d8 Mon Sep 17 00:00:00 2001 From: static Date: Fri, 2 Feb 2024 08:26:54 +0900 Subject: [PATCH 14/61] Refactor: mongo.ts --- src/modules/stores/mongo.ts | 79 +++++++++++++++++++++---------------- types/mongo.d.ts | 51 +++++++++++++----------- 2 files changed, 72 insertions(+), 58 deletions(-) diff --git a/src/modules/stores/mongo.ts b/src/modules/stores/mongo.ts index f69e206a..ef608f99 100755 --- a/src/modules/stores/mongo.ts +++ b/src/modules/stores/mongo.ts @@ -1,8 +1,8 @@ -import mongoose, { Schema, Model } from "mongoose"; +import mongoose, { model, Schema, Types } from "mongoose"; import logger from "@/modules/logger"; import type { User, Participant, DeviceToken, NotificationOption, TopicSubscription, Room, Location, Chat, Report, AdminIPWhitelist, AdminLog } from "@/../types/mongo"; // TODO: Am I right..? -const userSchema = new Schema, User>({ +const userSchema = new Schema({ name: { type: String, required: true }, //실명 nickname: { type: String, required: true }, //닉네임 id: { type: String, required: true, unique: true }, //택시 서비스에서만 사용되는 id @@ -25,7 +25,9 @@ const userSchema = new Schema, User>({ account: { type: String, default: "" }, //계좌번호 정보 }); -const participantSchema = new Schema, Participant>({ +export const userModel = model("User", userSchema); + +const participantSchema = new Schema({ user: { type: Schema.Types.ObjectId, ref: "User", required: true }, settlementStatus: { type: String, @@ -36,7 +38,7 @@ const participantSchema = new Schema, Participan readAt: { type: Date }, }); -const deviceTokenSchema = new Schema, DeviceToken>({ +const deviceTokenSchema = new Schema({ userId: { type: Schema.Types.ObjectId, ref: "User", @@ -46,8 +48,10 @@ const deviceTokenSchema = new Schema, DeviceToke deviceTokens: [{ type: String, required: true }], }); +export const deviceTokenModel = model("DeviceToken", deviceTokenSchema); + // 각 디바이스의 알림 설정 -const notificationOptionSchema = new Schema, NotificationOption>({ +const notificationOptionSchema = new Schema({ deviceToken: { type: String, required: true, @@ -81,7 +85,12 @@ const notificationOptionSchema = new Schema, TopicSubscription>({ +export const notificationOptionModel = model( + "NotificationOption", + notificationOptionSchema +); + +const topicSubscriptionSchema = new Schema({ deviceToken: String, topic: String, subscribedAt: { @@ -91,7 +100,12 @@ const topicSubscriptionSchema = new Schema, Room>({ +export const topicSubscriptionModel = model( + "TopicSubscription", + topicSubscriptionSchema +); + +const roomSchema = new Schema({ name: { type: String, required: true, default: "이름 없음", text: true }, from: { type: Schema.Types.ObjectId, ref: "Location", required: true }, to: { type: Schema.Types.ObjectId, ref: "Location", required: true }, @@ -99,7 +113,7 @@ const roomSchema = new Schema, Room>({ part: { type: [participantSchema], validate: [ - function (this: Room, value: Participant[]) { + function (this: Room, value: Types.DocumentArray) { return value.length <= this.maxPartLength; }, ], @@ -109,16 +123,20 @@ const roomSchema = new Schema, Room>({ maxPartLength: { type: Number, require: true, default: 4 }, }); -const locationSchema = new Schema, Location>({ +export const roomModel = model("Room", roomSchema); + +const locationSchema = new Schema({ enName: { type: String, required: true }, koName: { type: String, required: true }, priority: { type: Number, default: 0 }, isValid: { type: Boolean, default: true }, - latitude: { type: Number }, // 이후 required: true 로 수정 필요 - longitude: { type: Number }, // 이후 required: true 로 수정 필요 + latitude: { type: Number, required: true }, + longitude: { type: Number, required: true }, }); -const chatSchema = new Schema, Chat>({ +export const locationModel = model("Location", locationSchema); + +const chatSchema = new Schema({ roomId: { type: Schema.Types.ObjectId, ref: "Room", required: true }, type: { type: String, @@ -141,7 +159,9 @@ const chatSchema = new Schema, Chat>({ }); chatSchema.index({ roomId: 1, time: -1 }); -const reportSchema = new Schema, Report>({ +export const chatModel = model("Chat", chatSchema); + +const reportSchema = new Schema({ creatorId: { type: Schema.Types.ObjectId, ref: "User", required: true }, // 신고한 사람 id reportedId: { type: Schema.Types.ObjectId, ref: "User", required: true }, // 신고받은 사람 id type: { @@ -154,12 +174,19 @@ const reportSchema = new Schema, Report>({ roomId: { type: Schema.Types.ObjectId, ref: "Room" }, // 신고한 방 id }); -const adminIPWhitelistSchema = new Schema, AdminIPWhitelist>({ +export const reportModel = model("Report", reportSchema); + +const adminIPWhitelistSchema = new Schema({ ip: { type: String, required: true }, // IP 주소 description: { type: String, default: "" }, // 설명 }); -const adminLogSchema = new Schema, AdminLog>({ +export const adminIPWhitelistModel = model( + "AdminIPWhitelist", + adminIPWhitelistSchema +); + +const adminLogSchema = new Schema({ user: { type: Schema.Types.ObjectId, ref: "User", required: true }, // Log 취급자 User time: { type: Date, required: true }, // Log 발생 시각 ip: { type: String, required: true }, // 접속 IP 주소 @@ -171,6 +198,8 @@ const adminLogSchema = new Schema, AdminLog>({ }, // 수행 업무 }); +export const adminLogModel = model("AdminLog", adminLogSchema); + mongoose.set("strictQuery", true); const database = mongoose.connection; @@ -202,23 +231,3 @@ export const connectDatabase = (mongoUrl: string) => { return database; }; - -export const userModel = mongoose.model("User", userSchema); -export const deviceTokenModel = mongoose.model("DeviceToken", deviceTokenSchema); -export const notificationOptionModel = mongoose.model( - "NotificationOption", - notificationOptionSchema -); -export const topicSubscriptionModel = mongoose.model( - "TopicSubscription", - topicSubscriptionSchema -); -export const roomModel = mongoose.model("Room", roomSchema); -export const locationModel = mongoose.model("Location", locationSchema); -export const chatModel = mongoose.model("Chat", chatSchema); -export const reportModel = mongoose.model("Report", reportSchema); -export const adminIPWhitelistModel = mongoose.model( - "AdminIPWhitelist", - adminIPWhitelistSchema -); -export const adminLogModel = mongoose.model("AdminLog", adminLogSchema); diff --git a/types/mongo.d.ts b/types/mongo.d.ts index c7f63359..5010c22e 100644 --- a/types/mongo.d.ts +++ b/types/mongo.d.ts @@ -10,31 +10,31 @@ export interface User { /** 계정 프로필 이미지 주소. */ profileImageUrl: string; /** 사용자가 참여한 방 중 현재 진행 중인 방의 배열. */ - ongoingRoom?: string[]; + ongoingRoom?: Types.Array; /** 사용자가 참여한 방 중 완료된 방의 배열. */ - doneRoom?: string[]; - withdraw?: boolean; + doneRoom?: Types.Array; + withdraw: boolean; /** 사용자의 전화번호. 2023 가을 이벤트부터 추가됨. */ phoneNumber?: string; /** 계정 정지 여부. */ - ban?: boolean; + ban: boolean; /** 계정 가입 시각. */ joinat: Date; /** 사용자의 Taxi 이용약관 동의 여부. */ - agreeOnTermsOfService?: boolean; + agreeOnTermsOfService: boolean; subinfo?: { /** 사용자의 KAIST 학번. */ - kaist?: string, - sparcs?: string, - facebook?: string, - twitter?: string, + kaist: string, + sparcs: string, + facebook: string, + twitter: string, }; /** 사용자의 이메일 주소. */ email: string; /** 계정의 관리자 여부. */ - isAdmin?: boolean; + isAdmin: boolean; /** 사용자의 계좌번호 정보. */ - account?: string; + account: string; } export interface Participant { @@ -50,7 +50,7 @@ export interface DeviceToken { /** 디바이스 토큰 소유자의 User ObjectID. */ userId: Types.ObjectId; /** 소유한 디바이스 토큰의 배열. */ - deviceTokens: string[]; + deviceTokens: Types.Array; } export interface NotificationOption { @@ -58,7 +58,7 @@ export interface NotificationOption { /** 채팅 알림 수신 여부. */ chatting: boolean; /** 방 알림 키워드. */ - keywords: string[]; + keywords: Types.Array; /** 출발 전 알림 발송 여부. */ beforeDepart: boolean; /** 공지성 알림 수신 여부. */ @@ -83,7 +83,7 @@ export interface Room { /** 방의 출발 시각. */ time: Date; /** 방 참여자의 배열. */ - part?: Participant[]; + part?: Types.DocumentArray; /** 방의 생성 시각. */ madeat: Date; /** 방 참여자 중 정산을 완료한 참여자의 수. */ @@ -95,12 +95,12 @@ export interface Room { export interface Location { enName: string; koName: string; - priority?: number; - isValid?: boolean; + priority: number; + isValid: boolean; /** 위도. */ - latitude?: number; + latitude: number; /** 경도. */ - longitude?: number; + longitude: number; } export interface Chat { @@ -119,9 +119,9 @@ export interface Chat { | "arrival"; /** 메세지의 작성자의 User ObjectID. */ authorId?: Types.ObjectId; - content?: string; + content: string; time: Date; - isValid?: boolean; + isValid: boolean; } export interface Report { @@ -132,7 +132,7 @@ export interface Report { /** 신고의 종류. */ type: "no-settlement" | "no-show" | "etc-reason"; /** 신고의 기타 세부 사유. */ - etcDetail?: string; + etcDetail: string; /** 신고한 시각. */ time: Date; /** 신고한 방의 ObjectID. */ @@ -141,13 +141,18 @@ export interface Report { export interface AdminIPWhitelist { ip: string; - description?: string; + description: string; } export interface AdminLog { + /** 로그 발생자의 User ObjectID. */ user: Types.ObjectId; + /** 로그의 발생 시각. */ time: Date; + /** 로그의 발생 IP 주소. */ ip: string; - target?: string; + /** 취급한 대상. */ + target: string; + /** 수행한 업무. */ action: "create" | "read" | "update" | "delete"; } From db8ce7ed30f60fe40a24d1dddd8e5f8a62ec9f73 Mon Sep 17 00:00:00 2001 From: static Date: Fri, 2 Feb 2024 08:34:40 +0900 Subject: [PATCH 15/61] Refactor: migrate modules/stores/aws to TS --- src/modules/stores/{aws.js => aws.ts} | 44 +++++++++++++++++++-------- 1 file changed, 32 insertions(+), 12 deletions(-) rename src/modules/stores/{aws.js => aws.ts} (68%) diff --git a/src/modules/stores/aws.js b/src/modules/stores/aws.ts similarity index 68% rename from src/modules/stores/aws.js rename to src/modules/stores/aws.ts index 2947410c..f46c9628 100644 --- a/src/modules/stores/aws.js +++ b/src/modules/stores/aws.ts @@ -1,8 +1,8 @@ -const { aws: awsEnv } = require("@/loadenv"); +import AWS from "aws-sdk"; +import { aws as awsEnv } from "@/loadenv"; +import logger from "@/modules/logger"; +import { type Report } from "@/../types/mongo"; // TODO: 이게맞나 -const logger = require("@/modules/logger"); -// Load the AWS-SDK and s3 -const AWS = require("aws-sdk"); AWS.config.update({ region: "ap-northeast-2", signatureVersion: "v4", @@ -12,7 +12,10 @@ const s3 = new AWS.S3({ apiVersion: "2006-03-01" }); const ses = new AWS.SES({ apiVersion: "2010-12-01" }); // function to list Object -module.exports.getList = (directoryPath, cb) => { +export const getList = ( + directoryPath: string, + cb: (err: AWS.AWSError, data: AWS.S3.ListObjectsOutput) => void +) => { s3.listObjects( { Bucket: awsEnv.s3BucketName, @@ -25,7 +28,10 @@ module.exports.getList = (directoryPath, cb) => { }; // function to generate signed-url for upload(PUT) -module.exports.getUploadPUrlPut = (filePath, contentType = "image/png") => { +export const getUploadPUrlPut = ( + filePath: string, + contentType: string = "image/png" +) => { const presignedUrl = s3.getSignedUrl("putObject", { Bucket: awsEnv.s3BucketName, Key: filePath, @@ -36,7 +42,11 @@ module.exports.getUploadPUrlPut = (filePath, contentType = "image/png") => { }; // function to generate signed-url for upload(POST) -module.exports.getUploadPUrlPost = (filePath, contentType, cb) => { +export const getUploadPUrlPost = ( + filePath: string, + contentType: string, + cb: (err: Error, data: AWS.S3.PresignedPost) => void +) => { s3.createPresignedPost( { Bucket: awsEnv.s3BucketName, @@ -54,7 +64,10 @@ module.exports.getUploadPUrlPost = (filePath, contentType, cb) => { }; // function to delete object -module.exports.deleteObject = (filePath, cb) => { +export const deleteObject = ( + filePath: string, + cb: (err: AWS.AWSError, data: AWS.S3.DeleteObjectOutput) => void +) => { s3.deleteObject( { Bucket: awsEnv.s3BucketName, @@ -67,7 +80,10 @@ module.exports.deleteObject = (filePath, cb) => { }; // function to check exist of Object -module.exports.foundObject = (filePath, cb) => { +export const foundObject = ( + filePath: string, + cb: (err: AWS.AWSError, data: AWS.S3.HeadObjectOutput) => void +) => { s3.headObject( { Bucket: awsEnv.s3BucketName, @@ -80,11 +96,15 @@ module.exports.foundObject = (filePath, cb) => { }; // function to return full URL of the object -module.exports.getS3Url = (filePath) => { +export const getS3Url = (filePath: string) => { return `${awsEnv.s3Url}${filePath}`; }; -module.exports.sendReportEmail = (reportedEmail, report, html) => { +export const sendReportEmail = ( + reportedEmail: string, + report: Report, + html: string +) => { const reportTypeMap = { "no-settlement": "정산을 하지 않음", "no-show": "택시에 동승하지 않음", @@ -111,7 +131,7 @@ module.exports.sendReportEmail = (reportedEmail, report, html) => { Source: "taxi.sparcs@gmail.com", }; - ses.sendEmail(params, (err, data) => { + ses.sendEmail(params, (err) => { if (err) { logger.error("Fail to send email", err); } else { From b6cdd88e8818b85258a46f6bbfe8389e26cd3840 Mon Sep 17 00:00:00 2001 From: static Date: Fri, 2 Feb 2024 11:42:37 +0900 Subject: [PATCH 16/61] Refactor: migrate modules/stores/sessionStore to TS --- package.json | 2 +- pnpm-lock.yaml | 14 ++++++++----- .../{sessionStore.js => sessionStore.ts} | 20 +++++++------------ 3 files changed, 17 insertions(+), 19 deletions(-) rename src/modules/stores/{sessionStore.js => sessionStore.ts} (60%) diff --git a/package.json b/package.json index e9d3ff5e..feab2b26 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,7 @@ "axios": "^0.27.2", "ci": "^2.2.0", "connect-mongo": "^4.6.0", - "connect-redis": "^6.1.3", + "connect-redis": "^7.1.1", "cookie-parser": "^1.4.5", "cors": "^2.8.5", "cross-env": "^7.0.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 469bd3d7..bf573a51 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -36,8 +36,8 @@ dependencies: specifier: ^4.6.0 version: 4.6.0(express-session@1.17.3)(mongodb@4.17.1) connect-redis: - specifier: ^6.1.3 - version: 6.1.3 + specifier: ^7.1.1 + version: 7.1.1(express-session@1.17.3) cookie-parser: specifier: ^1.4.5 version: 1.4.6 @@ -4829,9 +4829,13 @@ packages: - supports-color dev: false - /connect-redis@6.1.3: - resolution: {integrity: sha512-aaNluLlAn/3JPxRwdzw7lhvEoU6Enb+d83xnokUNhC9dktqBoawKWL+WuxinxvBLTz6q9vReTnUDnUslaz74aw==} - engines: {node: '>=12'} + /connect-redis@7.1.1(express-session@1.17.3): + resolution: {integrity: sha512-M+z7alnCJiuzKa8/1qAYdGUXHYfDnLolOGAUjOioB07pP39qxjG+X9ibsud7qUBc4jMV5Mcy3ugGv8eFcgamJQ==} + engines: {node: '>=16'} + peerDependencies: + express-session: '>=1' + dependencies: + express-session: 1.17.3 dev: false /content-disposition@0.5.4: diff --git a/src/modules/stores/sessionStore.js b/src/modules/stores/sessionStore.ts similarity index 60% rename from src/modules/stores/sessionStore.js rename to src/modules/stores/sessionStore.ts index 3d247d54..11647e00 100644 --- a/src/modules/stores/sessionStore.js +++ b/src/modules/stores/sessionStore.ts @@ -1,20 +1,14 @@ -const expressSession = require("express-session"); -const redis = require("redis"); -const MongoStore = require("connect-mongo"); -const RedisStore = require("connect-redis")(expressSession); -const { - redis: redisUrl, - mongo: mongoUrl, - session: sessionConfig, -} = require("@/loadenv"); -const logger = require("@/modules/logger"); +import MongoStore from "connect-mongo"; +import RedisStore from "connect-redis" +import redis from "redis"; +import { redis as redisUrl, mongo as mongoUrl, session as sessionConfig } from "@/loadenv"; +import logger from "@/modules/logger"; -const getSessionStore = (redisUrl) => { +const getSessionStore = () => { // 환경변수 REDIS_PATH 유무에 따라 session 저장 방식이 변경됩니다. if (redisUrl) { const client = redis.createClient({ url: redisUrl, - legacyMode: true, }); // redis client 연결 성공 시 로그를 출력합니다. @@ -34,4 +28,4 @@ const getSessionStore = (redisUrl) => { } }; -module.exports = getSessionStore(redisUrl); +export default getSessionStore(); From 94daf703427cd1aa2044dbba2620a0523e3e5f71 Mon Sep 17 00:00:00 2001 From: static Date: Sat, 3 Feb 2024 04:48:47 +0900 Subject: [PATCH 17/61] Refactor: migrate some files in modules directory to TS --- .../{modifyProfile.js => modifyProfile.ts} | 21 +++++++------------ src/modules/{patterns.js => patterns.ts} | 2 +- ...ckNotification.js => slackNotification.ts} | 13 ++++++------ types/mongo.d.ts | 4 +++- 4 files changed, 18 insertions(+), 22 deletions(-) rename src/modules/{modifyProfile.js => modifyProfile.ts} (83%) rename src/modules/{patterns.js => patterns.ts} (97%) rename src/modules/{slackNotification.js => slackNotification.ts} (59%) diff --git a/src/modules/modifyProfile.js b/src/modules/modifyProfile.ts similarity index 83% rename from src/modules/modifyProfile.js rename to src/modules/modifyProfile.ts index e8702f98..a440e484 100755 --- a/src/modules/modifyProfile.js +++ b/src/modules/modifyProfile.ts @@ -1,5 +1,5 @@ -const crypto = require("crypto"); -const aws = require("./stores/aws"); +import crypto from "crypto"; +import { getS3Url } from "@/modules/stores/aws"; const nouns = [ "재료역학", @@ -64,7 +64,7 @@ const defaultProfile = [ // 닉네임 규칙에 따라 새 유저의 닉네임을 생성해 반환합니다. // Ara의 닉네임 생성 규칙을 참고하였습니다. -const generateNickname = (id) => { +export const generateNickname = (id: string) => { const nounIdx = crypto.randomInt(nouns.length); const adjectiveIdx = crypto.randomInt(adjectives.length); const noun = nouns[nounIdx]; @@ -80,26 +80,19 @@ const generateNickname = (id) => { }; // 기존 프로필 사진의 URI 중 하나를 무작위로 선택해 반환합니다. -const generateProfileImageUrl = () => { +export const generateProfileImageUrl = () => { const ridx = crypto.randomInt(defaultProfile.length); - return aws.getS3Url(`/profile-img/default/${defaultProfile[ridx]}`); + return getS3Url(`/profile-img/default/${defaultProfile[ridx]}`); }; // 사용자의 이름과 성을 받아, 한글인지 영어인지에 따라 전체 이름을 반환합니다. -const getFullUsername = (firstName, lastName) => { +export const getFullUsername = (firstName: string, lastName: string) => { const koPattern = new RegExp("[가-힣]+"); if (koPattern.test(firstName) && koPattern.test(lastName)) return `${lastName}${firstName}`; else return `${firstName} ${lastName}`; }; -const replaceSpaceInNickname = (nickname) => { +export const replaceSpaceInNickname = (nickname: string) => { return nickname.replace(/\s+/g, " "); }; - -module.exports = { - generateNickname, - generateProfileImageUrl, - getFullUsername, - replaceSpaceInNickname, -}; diff --git a/src/modules/patterns.js b/src/modules/patterns.ts similarity index 97% rename from src/modules/patterns.js rename to src/modules/patterns.ts index 7111ec1a..8e9adc56 100644 --- a/src/modules/patterns.js +++ b/src/modules/patterns.ts @@ -1,4 +1,4 @@ -module.exports = { +export default { room: { name: RegExp( "^[A-Za-z0-9가-힣ㄱ-ㅎㅏ-ㅣ,.?! _~/#'\\\\@=\"\\-\\^()+*<>{}[\\]]{1,50}$" // ,.?/#'\@="-^()+*<>{}[] 허용 diff --git a/src/modules/slackNotification.js b/src/modules/slackNotification.ts similarity index 59% rename from src/modules/slackNotification.js rename to src/modules/slackNotification.ts index 4ac3d378..e03386a1 100644 --- a/src/modules/slackNotification.js +++ b/src/modules/slackNotification.ts @@ -1,8 +1,9 @@ -const { slackWebhookUrl: slackUrl } = require("@/loadenv"); -const axios = require("axios"); -const logger = require("./logger"); +import axios from "axios"; +import { slackWebhookUrl as slackUrl } from "@/loadenv"; +import logger from "@/modules/logger"; +import { type Report } from "@/../types/mongo"; -module.exports.notifyToReportChannel = (reportUser, report) => { +export const notifyToReportChannel = (reportUser: string, report: Report) => { if (!slackUrl.report) return; const data = { @@ -15,11 +16,11 @@ module.exports.notifyToReportChannel = (reportUser, report) => { 기타: ${report.etcDetail} `, }; - const config = { "Content-Type": "application/json" }; + const config = { headers: { "Content-Type": "application/json" } }; axios .post(slackUrl.report, data, config) - .then((res) => { + .then(() => { logger.info("Slack webhook sent successfully"); }) .catch((err) => { diff --git a/types/mongo.d.ts b/types/mongo.d.ts index 5010c22e..0929eccc 100644 --- a/types/mongo.d.ts +++ b/types/mongo.d.ts @@ -144,6 +144,8 @@ export interface AdminIPWhitelist { description: string; } +export type AdminLogAction = "create" | "read" | "update" | "delete"; + export interface AdminLog { /** 로그 발생자의 User ObjectID. */ user: Types.ObjectId; @@ -154,5 +156,5 @@ export interface AdminLog { /** 취급한 대상. */ target: string; /** 수행한 업무. */ - action: "create" | "read" | "update" | "delete"; + action: AdminLogAction; } From 2a7c55f538a907d277a3520e53742a873f4f1777 Mon Sep 17 00:00:00 2001 From: static Date: Sat, 3 Feb 2024 09:27:51 +0900 Subject: [PATCH 18/61] Refactor: move types directory into src directory --- src/modules/slackNotification.ts | 2 +- src/modules/stores/aws.ts | 2 +- src/modules/stores/mongo.ts | 2 +- {types => src/types}/index.d.ts | 0 {types => src/types}/mongo.d.ts | 0 5 files changed, 3 insertions(+), 3 deletions(-) rename {types => src/types}/index.d.ts (100%) rename {types => src/types}/mongo.d.ts (100%) diff --git a/src/modules/slackNotification.ts b/src/modules/slackNotification.ts index e03386a1..49a5d44e 100644 --- a/src/modules/slackNotification.ts +++ b/src/modules/slackNotification.ts @@ -1,7 +1,7 @@ import axios from "axios"; import { slackWebhookUrl as slackUrl } from "@/loadenv"; import logger from "@/modules/logger"; -import { type Report } from "@/../types/mongo"; +import { type Report } from "@/types/mongo"; export const notifyToReportChannel = (reportUser: string, report: Report) => { if (!slackUrl.report) return; diff --git a/src/modules/stores/aws.ts b/src/modules/stores/aws.ts index f46c9628..67dd9f10 100644 --- a/src/modules/stores/aws.ts +++ b/src/modules/stores/aws.ts @@ -1,7 +1,7 @@ import AWS from "aws-sdk"; import { aws as awsEnv } from "@/loadenv"; import logger from "@/modules/logger"; -import { type Report } from "@/../types/mongo"; // TODO: 이게맞나 +import { type Report } from "@/types/mongo"; AWS.config.update({ region: "ap-northeast-2", diff --git a/src/modules/stores/mongo.ts b/src/modules/stores/mongo.ts index ef608f99..bc248bce 100755 --- a/src/modules/stores/mongo.ts +++ b/src/modules/stores/mongo.ts @@ -1,6 +1,6 @@ import mongoose, { model, Schema, Types } from "mongoose"; import logger from "@/modules/logger"; -import type { User, Participant, DeviceToken, NotificationOption, TopicSubscription, Room, Location, Chat, Report, AdminIPWhitelist, AdminLog } from "@/../types/mongo"; // TODO: Am I right..? +import type { User, Participant, DeviceToken, NotificationOption, TopicSubscription, Room, Location, Chat, Report, AdminIPWhitelist, AdminLog } from "@/types/mongo"; const userSchema = new Schema({ name: { type: String, required: true }, //실명 diff --git a/types/index.d.ts b/src/types/index.d.ts similarity index 100% rename from types/index.d.ts rename to src/types/index.d.ts diff --git a/types/mongo.d.ts b/src/types/mongo.d.ts similarity index 100% rename from types/mongo.d.ts rename to src/types/mongo.d.ts From 3a2ab6533482caf03225cb997048ac6e4c2a5956 Mon Sep 17 00:00:00 2001 From: static Date: Sat, 3 Feb 2024 09:29:22 +0900 Subject: [PATCH 19/61] Refactor: update tsconfig.json --- tsconfig.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tsconfig.json b/tsconfig.json index 0e7f68ff..0f059817 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -14,7 +14,7 @@ "@/*": ["./*"] } }, - "include": ["src", "types"], + "include": ["src"], "exclude": ["dist", "node_modules"] } \ No newline at end of file From 7795c73fa5545d7f4eefc226596bad70743a078b Mon Sep 17 00:00:00 2001 From: static Date: Sat, 3 Feb 2024 10:38:42 +0900 Subject: [PATCH 20/61] Refactor: migrate modules/fcm to TS --- src/modules/{fcm.js => fcm.ts} | 53 +++++++++++++--------------------- src/types/mongo.d.ts | 26 ++++++++++------- 2 files changed, 35 insertions(+), 44 deletions(-) rename src/modules/{fcm.js => fcm.ts} (88%) diff --git a/src/modules/fcm.js b/src/modules/fcm.ts similarity index 88% rename from src/modules/fcm.js rename to src/modules/fcm.ts index c9ab01d6..44074d7f 100644 --- a/src/modules/fcm.js +++ b/src/modules/fcm.ts @@ -1,17 +1,18 @@ -const firebaseAdmin = require("firebase-admin"); -const { getMessaging } = require("firebase-admin/messaging"); -const { +import firebaseAdmin from "firebase-admin"; +import { type SendResponse, getMessaging } from "firebase-admin/messaging"; +import { googleApplicationCredentials } from "@/loadenv"; +import logger from "@/modules/logger"; +import { deviceTokenModel, notificationOptionModel, topicSubscriptionModel, -} = require("@/modules/stores/mongo"); -const logger = require("./logger"); -const { googleApplicationCredentials } = require("@/loadenv"); +} from "@/modules/stores/mongo"; +import { type ChatType } from "@/types/mongo"; /** * credential을 등록합니다. */ -const initializeApp = () => { +export const initializeApp = () => { if (googleApplicationCredentials) { firebaseAdmin.initializeApp({ credential: firebaseAdmin.credential.cert(googleApplicationCredentials), @@ -29,7 +30,7 @@ const initializeApp = () => { * @param {string} deviceToken - 등록하려는 FCM device token입니다. * @return {Promise>} 변경된 사용자의 deviceToken의 목록 Array를 반환합니다. 오류가 발생하면 빈 배열을 반환합니다. */ -const registerDeviceToken = async (userId, deviceToken) => { +export const registerDeviceToken = async (userId: string, deviceToken: string): Promise => { try { // 디바이스 토큰을 다른 사용자가 사용하고 있는지 확인 및 삭제합니다. await deviceTokenModel.updateMany( @@ -61,14 +62,12 @@ const registerDeviceToken = async (userId, deviceToken) => { } }; -// TODO: remove userId /** * 사용자의 ObjectId와 FCM device token이 주어졌을 때, 해당 사용자의 해당 deviceToken을 DB에서 삭제합니다. - * @param {string} userId - 사용자의 ObjectId입니다. * @param {string} deviceToken - 삭제하려는 FCM device token입니다. * @return {Promise} 해당 deviceToken을 가진 모든 사용자로부터 해당 deviceToken을 삭제하는 데 성공하면 true, 하나 이상의 사용자에게서 해당 deviceToken을 삭제하는 데 실패하면 false를 반환합니다. 삭제할 deviceToken이 존재하지 않는 경우에는 true를 반환합니다. */ -const unregisterDeviceToken = async (deviceToken) => { +export const unregisterDeviceToken = async (deviceToken: string) => { try { // 디바이스 토큰을 DB에서 삭제합니다. const { matchedCount, modifiedCount } = await deviceTokenModel.updateMany( @@ -97,13 +96,13 @@ const unregisterDeviceToken = async (deviceToken) => { * @param {Array} fcmResponses - 등록하려는 FCM device token입니다. * @return {Promise>} 각각의 토큰들의 삭제 성공 여부가 저장된 Array를 반환합니다. 해당 토큰을 DB에서 삭제하는 데 성공했으면 true, 아니면 false가 포함됩니다. */ -const removeExpiredTokens = async (deviceTokens, fcmResponses) => { +const removeExpiredTokens = async (deviceTokens: string[], fcmResponses: SendResponse[]) => { const removalResults = await Promise.all( deviceTokens.map(async (deviceToken, index) => { try { // FCM device token이 유효하지 않아 메시지 전송에 실패한 경우, 해당 device token을 DB에서 삭제합니다. if ( - fcmResponses[index].error.code === + fcmResponses[index].error?.code === "messaging/registration-token-not-registered" ) { await unregisterDeviceToken(deviceToken); @@ -129,7 +128,7 @@ const removeExpiredTokens = async (deviceTokens, fcmResponses) => { * @param {string} deviceToken - 사용 가능 여부를 확인하려고 하는 FCM device token입니다. * @return {Promise} 해당 디바이스에 알림을 보낸다는 요청을 FCM 서버에 성공적으로 보냈으면 true, 아니면 false를 반환합니다. */ -const validateDeviceToken = async (deviceToken) => { +export const validateDeviceToken = async (deviceToken: string) => { try { const message = { token: deviceToken, @@ -152,7 +151,7 @@ const validateDeviceToken = async (deviceToken) => { * @param {Boolean?} notificationOptions.chatting - true 또는 false로 주어진 경우, 채팅 알림 설정이 각각 true 또는 false로 설정된 사용자들의 deviceToken만 반환합니다. * @return {Promise>} deviceToken의 Array를 반환합니다. 오류가 발생하면 빈 배열을 반환합니다. */ -const getTokensOfUsers = async (userIds, notificationOptions = {}) => { +export const getTokensOfUsers = async (userIds: string[], notificationOptions: Object = {}) => { const deviceTokensOfUsers = ( await Promise.all( userIds.map( @@ -180,14 +179,14 @@ const getTokensOfUsers = async (userIds, notificationOptions = {}) => { * 주어진 token들에 메시지 알림을 전송합니다. * TODO: 알림 전송 실패한 토큰 삭제하기 * @param {Array} tokens - 메시지 알림을 받을 기기의 deviceToken들로 구성된 Array입니다. - * @param {string} type - 메시지 유형으로, "text" | "in" | "out" | "s3img" | "payment" | "settlement" 입니다. + * @param {ChatType} type - 메시지 유형으로, "text" | "in" | "out" | "s3img" | "payment" | "settlement" 입니다. * @param {string} title - 보낼 메시지의 제목입니다. * @param {string} body - 보낼 메시지의 본문입니다. * @param {string?} icon - 메시지를 보낸 사람의 프로필 사진 주소입니다. * @param {string?} link - 메시지 알림 팝업을 클릭했을 때 이동할 주소입니다. * @return {Promise} 메시지 알림 전송에 실패한 기기의 수를 반환합니다. 오류가 발생하면 -1을 반환합니다. */ -const sendMessageByTokens = async (tokens, type, title, body, icon, link) => { +export const sendMessageByTokens = async (tokens: string[], type: ChatType, title: string, body: string, icon?: string, link?: string) => { if (tokens.length === 0) return -1; try { const message = { @@ -221,14 +220,14 @@ const sendMessageByTokens = async (tokens, type, title, body, icon, link) => { /** * 주어진 topic을 구독하고 있는 모든 기기에 메시지 알림을 전송합니다. * @param {string} topic - 메시지 알림을 보낼 기기들이 구독하고 있는 topic입니다. - * @param {string} type - 메시지 유형으로, "text" | "in" | "out" | "s3img" | "payment" | "settlement" 입니다. + * @param {ChatType} type - 메시지 유형으로, "text" | "in" | "out" | "s3img" | "payment" | "settlement" 입니다. * @param {string} title - 보낼 메시지의 제목입니다. * @param {string} body - 보낼 메시지의 본문입니다. * @param {string?} icon - 메시지를 보낸 사람의 프로필 사진 주소입니다. * @param {string?} link - 메시지 알림 팝업을 클릭했을 때 이동할 주소입니다. * @return {Promise} 메시지 알림 전송에 성공했으면 true, 아니면 false를 반환합니다. */ -const sendMessageByTopic = async (topic, type, title, body, icon, link) => { +export const sendMessageByTopic = async (topic: string, type: ChatType, title: string, body: string, icon?: string, link?: string) => { try { const message = { topic, @@ -255,7 +254,7 @@ const sendMessageByTopic = async (topic, type, title, body, icon, link) => { * @param {string} topic - 구독할 topic입니다. * @return {Promise} 토픽 구독에 실패한 기기의 수를 반환합니다. 오류가 발생하면 -1을 반환합니다. */ -const subscribeUserToTopic = async (userId, topic) => { +export const subscribeUserToTopic = async (userId: string, topic: string) => { try { const deviceToken = await deviceTokenModel.findOne({ userId, @@ -306,7 +305,7 @@ const subscribeUserToTopic = async (userId, topic) => { * @param {string} topic - 구독을 해제할 topic입니다. * @return {Promise} 토픽 구독 해제에 실패한 기기의 수를 반환합니다. 오류가 발생하면 -1을 반환합니다. */ -const unsubscribeUserFromTopic = async (userId, topic) => { +export const unsubscribeUserFromTopic = async (userId: string, topic: string) => { try { const deviceToken = await deviceTokenModel.findOne({ userId, @@ -340,15 +339,3 @@ const unsubscribeUserFromTopic = async (userId, topic) => { return -1; } }; - -module.exports = { - initializeApp, - registerDeviceToken, - unregisterDeviceToken, - validateDeviceToken, - getTokensOfUsers, - sendMessageByTokens, - sendMessageByTopic, - subscribeUserToTopic, - unsubscribeUserFromTopic, -}; diff --git a/src/types/mongo.d.ts b/src/types/mongo.d.ts index 0929eccc..810755ce 100644 --- a/src/types/mongo.d.ts +++ b/src/types/mongo.d.ts @@ -37,11 +37,13 @@ export interface User { account: string; } +export type SettlementStatus = "not-departed" | "paid" | "send-required" | "sent"; + export interface Participant { /** 방 참여자의 User ObjectID. */ user: Types.ObjectId; /** 방 참여자의 정산 상태. */ - settlementStatus: "not-departed" | "paid" | "send-required" | "sent"; + settlementStatus: SettlementStatus; /** 방 참여자가 마지막으로 채팅을 읽은 시각. */ readAt?: Date; } @@ -103,20 +105,22 @@ export interface Location { longitude: number; } +export type ChatType = + | "text" + | "in" + | "out" + | "s3img" + | "payment" + | "settlement" + | "account" + | "departure" + | "arrival"; + export interface Chat { /** 메세지가 전송된 방의 Room ObjectID. */ roomId: Types.ObjectId; /** 메세지의 종류. */ - type?: - | "text" - | "in" - | "out" - | "s3img" - | "payment" - | "settlement" - | "account" - | "departure" - | "arrival"; + type?: ChatType; /** 메세지의 작성자의 User ObjectID. */ authorId?: Types.ObjectId; content: string; From 8d58a791ae0ae55edbc33c47734c3308d70ee1cf Mon Sep 17 00:00:00 2001 From: static Date: Sat, 3 Feb 2024 11:19:19 +0900 Subject: [PATCH 21/61] Refactor: migrate populate directory to TS --- src/modules/populates/chats.js | 10 ---- src/modules/populates/chats.ts | 12 +++++ src/modules/populates/reports.js | 10 ---- src/modules/populates/reports.ts | 12 +++++ src/modules/populates/rooms.js | 73 ------------------------- src/modules/populates/rooms.ts | 93 ++++++++++++++++++++++++++++++++ src/types/mongo.d.ts | 24 ++++----- 7 files changed, 129 insertions(+), 105 deletions(-) delete mode 100644 src/modules/populates/chats.js create mode 100644 src/modules/populates/chats.ts delete mode 100644 src/modules/populates/reports.js create mode 100644 src/modules/populates/reports.ts delete mode 100644 src/modules/populates/rooms.js create mode 100644 src/modules/populates/rooms.ts diff --git a/src/modules/populates/chats.js b/src/modules/populates/chats.js deleted file mode 100644 index 2e18ccb6..00000000 --- a/src/modules/populates/chats.js +++ /dev/null @@ -1,10 +0,0 @@ -/** @constant {{path: string, select: string}[]} - * 쿼리를 통해 얻은 Chat Document를 populate할 설정값을 정의합니다. - */ -const chatPopulateOption = [ - { path: "authorId", select: "_id nickname profileImageUrl" }, -]; - -module.exports = { - chatPopulateOption, -}; diff --git a/src/modules/populates/chats.ts b/src/modules/populates/chats.ts new file mode 100644 index 00000000..fa92f2d6 --- /dev/null +++ b/src/modules/populates/chats.ts @@ -0,0 +1,12 @@ +import { type User, type Chat } from "@/types/mongo"; + +/** @constant {{path: string, select: string}[]} + * 쿼리를 통해 얻은 Chat Document를 populate할 설정값을 정의합니다. + */ +export const chatPopulateOption = [ + { path: "authorId", select: "_id nickname profileImageUrl" }, +]; + +export interface PopulatedChat extends Omit { + authorId?: Pick; +}; diff --git a/src/modules/populates/reports.js b/src/modules/populates/reports.js deleted file mode 100644 index 9f5b3fa4..00000000 --- a/src/modules/populates/reports.js +++ /dev/null @@ -1,10 +0,0 @@ -const reportPopulateOption = [ - { - path: "reportedId", - select: "_id id name nickname profileImageUrl", - }, -]; - -module.exports = { - reportPopulateOption, -}; diff --git a/src/modules/populates/reports.ts b/src/modules/populates/reports.ts new file mode 100644 index 00000000..439311a7 --- /dev/null +++ b/src/modules/populates/reports.ts @@ -0,0 +1,12 @@ +import { type User, type Report } from "@/types/mongo"; + +export const reportPopulateOption = [ + { + path: "reportedId", + select: "_id id name nickname profileImageUrl", + }, +]; + +export interface PopulatedReport extends Omit { + reportedId: Pick; +}; diff --git a/src/modules/populates/rooms.js b/src/modules/populates/rooms.js deleted file mode 100644 index 7243d648..00000000 --- a/src/modules/populates/rooms.js +++ /dev/null @@ -1,73 +0,0 @@ -/** - * 쿼리를 통해 얻은 Room Document를 populate할 설정값을 정의합니다. - * @constant {{path: string, select: string, populate?: {path: string, select: string}}[]} - */ -const roomPopulateOption = [ - { path: "from", select: "_id koName enName" }, - { path: "to", select: "_id koName enName" }, - { - path: "part", - select: "-_id user settlementStatus readAt", - populate: { path: "user", select: "_id id name nickname profileImageUrl" }, - }, -]; - -/** - * Room Object가 주어졌을 때 room의 part array의 각 요소를 API 명세에서와 같이 {userId: String, ... , isSettlement: String}으로 가공합니다. - * 또한, 방이 현재 출발했는지 유무인 isDeparted 속성을 추가합니다. - * @param {Object} roomObject - 정산 정보를 가공할 room Object로, Mongoose Document가 아닌 순수 Javascript Object여야 합니다. - * @param {Object} options - 추가 파라미터로, 기본값은 {}입니다. - * @param {Boolean} options.includeSettlement - 반환 결과에 정산 정보를 포함할 지 여부로, 기본값은 true입니다. - * @param {Date} options.timestamp - 방의 출발 여부(isDeparted)를 판단하는 기준이 되는 시각입니다. - * @param {Boolean} options.isOver - 방의 완료 여부(isOver)로, 기본값은 false입니다. includeSettlement가 false인 경우 roomDocument의 isOver 속성은 undefined로 설정됩니다. - * @return {Object} 정산 여부가 위와 같이 가공되고 isDeparted 속성이 추가된 Room Object가 반환됩니다. - */ -const formatSettlement = ( - roomObject, - { includeSettlement = true, isOver = false, timestamp = Date.now() } = {} -) => { - roomObject.part = roomObject.part.map((participantSubDocument) => { - const { _id, name, nickname, profileImageUrl } = - participantSubDocument.user; - const { settlementStatus, readAt } = participantSubDocument; - return { - _id, - name, - nickname, - profileImageUrl, - isSettlement: includeSettlement ? settlementStatus : undefined, - readAt: readAt ?? roomObject.madeat, - }; - }); - roomObject.settlementTotal = includeSettlement - ? roomObject.settlementTotal - : undefined; - roomObject.isOver = includeSettlement ? isOver : undefined; - roomObject.isDeparted = new Date(roomObject.time) < new Date(timestamp); - return roomObject; -}; - -/** - * formatSettlement 함수를 사용하여 변환한 Room Object와 사용자의 id(userId)가 주어졌을 때, 해당 사용자의 정산 상태를 반환합니다. - * @param {Object} roomObject - formatSettlement 함수를 사용하여 변환한 Room Object입니다. - * @param {String} userId - 방 완료 상태를 확인하려는 사용자의 id(user.id)입니다. - * @return {Boolean | undefined} 사용자의 해당 방에 대한 완료 여부(true | false)를 반환합니다. 사용자가 참여중인 방이 아닐 경우 undefined를 반환합니다. - **/ -const getIsOver = (roomObject, userId) => { - // room document의 part subdoocument에서 사용자 id와 일치하는 정산 정보를 찾습니다. - const participantSubDocuments = roomObject.part.filter((part) => { - return part.user.id === userId; - }); - - // 방에 참여중이지 않은 사용자의 경우, undefined을 반환합니다. - if (participantSubDocuments.length === 0) return undefined; - - // 방에 참여중인 사용자의 경우, 정산 상태가 완료된 것인지("paid"거나 "sent"인지)를 반환합니다. - return ["paid", "sent"].includes(participantSubDocuments[0].settlementStatus); -}; - -module.exports = { - roomPopulateOption, - formatSettlement, - getIsOver, -}; diff --git a/src/modules/populates/rooms.ts b/src/modules/populates/rooms.ts new file mode 100644 index 00000000..8fa42fbf --- /dev/null +++ b/src/modules/populates/rooms.ts @@ -0,0 +1,93 @@ +import { type User, type SettlementStatus, type Participant, type Room, type Location } from "@/types/mongo"; + +/** + * 쿼리를 통해 얻은 Room Document를 populate할 설정값을 정의합니다. + * @constant {{path: string, select: string, populate?: {path: string, select: string}}[]} + */ +export const roomPopulateOption = [ + { path: "from", select: "_id koName enName" }, + { path: "to", select: "_id koName enName" }, + { + path: "part", + select: "-_id user settlementStatus readAt", + populate: { path: "user", select: "_id id name nickname profileImageUrl" }, + }, +]; + +interface PopulatedParticipant extends Pick { + user: Pick; +} + +export interface PopulatedRoom extends Omit { + from: Pick; + to: Pick; + part?: PopulatedParticipant[]; +} + +export interface FormattedRoom extends Omit { + part?: { + _id: string; + name: string; + nickname: string; + profileImageUrl: string; + isSettlement?: SettlementStatus; + readAt: Date; + }[]; + settlementTotal?: number; + isOver?: boolean; + isDeparted: boolean; +}; + +/** + * Room Object가 주어졌을 때 room의 part array의 각 요소를 API 명세에서와 같이 {userId: String, ... , isSettlement: String}으로 가공합니다. + * 또한, 방이 현재 출발했는지 유무인 isDeparted 속성을 추가합니다. + * @param {PopulatedRoom} roomObject - 정산 정보를 가공할 room Object로, Mongoose Document가 아닌 순수 Javascript Object여야 합니다. + * @param {Object} options - 추가 파라미터로, 기본값은 {}입니다. + * @param {Boolean} options.includeSettlement - 반환 결과에 정산 정보를 포함할 지 여부로, 기본값은 true입니다. + * @param {Date} options.timestamp - 방의 출발 여부(isDeparted)를 판단하는 기준이 되는 시각입니다. + * @param {Boolean} options.isOver - 방의 완료 여부(isOver)로, 기본값은 false입니다. includeSettlement가 false인 경우 roomDocument의 isOver 속성은 undefined로 설정됩니다. + * @return {FormattedRoom} 정산 여부가 위와 같이 가공되고 isDeparted 속성이 추가된 Room Object가 반환됩니다. + */ +export const formatSettlement = ( + roomObject: PopulatedRoom, + { includeSettlement = true, isOver = false, timestamp = Date.now() } = {} +): FormattedRoom => { + return { + ...roomObject, + part: roomObject.part?.map((participantSubDocument) => { + const { _id, name, nickname, profileImageUrl } = + participantSubDocument.user; + const { settlementStatus, readAt } = participantSubDocument; + return { + _id, + name, + nickname, + profileImageUrl, + isSettlement: includeSettlement ? settlementStatus : undefined, + readAt: readAt ?? roomObject.madeat, + }; + }), + settlementTotal: includeSettlement ? roomObject.settlementTotal : undefined, + isOver: includeSettlement ? isOver : undefined, + isDeparted: new Date(roomObject.time) < new Date(timestamp), + }; +} + +/** + * roomPopulateOption을 사용해 populate된 Room Object와 사용자의 id(userId)가 주어졌을 때, 해당 사용자의 정산 상태를 반환합니다. + * @param {PopulatedRoom} roomObject - roomPopulateOption을 사용해 populate된 변환한 Room Object입니다. + * @param {String} userId - 방 완료 상태를 확인하려는 사용자의 id(user.id)입니다. + * @return {Boolean | undefined} 사용자의 해당 방에 대한 완료 여부(true | false)를 반환합니다. 사용자가 참여중인 방이 아닐 경우 undefined를 반환합니다. + **/ +export const getIsOver = (roomObject: PopulatedRoom, userId: string) => { + // room document의 part subdoocument에서 사용자 id와 일치하는 정산 정보를 찾습니다. + const participantSubDocuments = roomObject.part?.filter((part) => { + return part.user.id === userId; + }); + + // 방에 참여중이지 않은 사용자의 경우, undefined을 반환합니다. + if (!participantSubDocuments || participantSubDocuments.length === 0) return undefined; + + // 방에 참여중인 사용자의 경우, 정산 상태가 완료된 것인지("paid"거나 "sent"인지)를 반환합니다. + return ["paid", "sent"].includes(participantSubDocuments[0].settlementStatus); +}; diff --git a/src/types/mongo.d.ts b/src/types/mongo.d.ts index 810755ce..c05894df 100644 --- a/src/types/mongo.d.ts +++ b/src/types/mongo.d.ts @@ -1,6 +1,6 @@ -import { Types } from "mongoose"; +import { Document, Types } from "mongoose"; -export interface User { +export interface User extends Document { /** 사용자의 실명. */ name: string; /** 사용자의 닉네임. */ @@ -39,7 +39,7 @@ export interface User { export type SettlementStatus = "not-departed" | "paid" | "send-required" | "sent"; -export interface Participant { +export interface Participant extends Document { /** 방 참여자의 User ObjectID. */ user: Types.ObjectId; /** 방 참여자의 정산 상태. */ @@ -48,14 +48,14 @@ export interface Participant { readAt?: Date; } -export interface DeviceToken { +export interface DeviceToken extends Document { /** 디바이스 토큰 소유자의 User ObjectID. */ userId: Types.ObjectId; /** 소유한 디바이스 토큰의 배열. */ deviceTokens: Types.Array; } -export interface NotificationOption { +export interface NotificationOption extends Document { deviceToken: string; /** 채팅 알림 수신 여부. */ chatting: boolean; @@ -69,13 +69,13 @@ export interface NotificationOption { advertisement: boolean; } -export interface TopicSubscription { +export interface TopicSubscription extends Document { deviceToken?: string; topic?: string; subscribedAt: Date; } -export interface Room { +export interface Room extends Document { /** 방의 이름. */ name: string; /** 방의 출발지의 Location ObjectID. */ @@ -94,7 +94,7 @@ export interface Room { maxPartLength: number; } -export interface Location { +export interface Location extends Document { enName: string; koName: string; priority: number; @@ -116,7 +116,7 @@ export type ChatType = | "departure" | "arrival"; -export interface Chat { +export interface Chat extends Document { /** 메세지가 전송된 방의 Room ObjectID. */ roomId: Types.ObjectId; /** 메세지의 종류. */ @@ -128,7 +128,7 @@ export interface Chat { isValid: boolean; } -export interface Report { +export interface Report extends Document { /** 신고한 사용자의 ObjectID. */ creatorId: Types.ObjectId; /** 신고받은 사용자의 ObjectID. */ @@ -143,14 +143,14 @@ export interface Report { roomId?: Types.ObjectId; } -export interface AdminIPWhitelist { +export interface AdminIPWhitelist extends Document { ip: string; description: string; } export type AdminLogAction = "create" | "read" | "update" | "delete"; -export interface AdminLog { +export interface AdminLog extends Document { /** 로그 발생자의 User ObjectID. */ user: Types.ObjectId; /** 로그의 발생 시각. */ From 73fc564a56558808f1ce5a304d4799f549559096 Mon Sep 17 00:00:00 2001 From: static Date: Tue, 6 Feb 2024 14:26:52 +0900 Subject: [PATCH 22/61] Fix: prettier TypeScript parsing error --- .prettierrc.json | 2 +- src/middlewares/errorHandler.ts | 2 +- src/middlewares/information.ts | 4 +++- src/middlewares/responseTime.ts | 2 +- src/middlewares/session.ts | 6 +++--- 5 files changed, 9 insertions(+), 7 deletions(-) diff --git a/.prettierrc.json b/.prettierrc.json index 873dba77..8be05812 100644 --- a/.prettierrc.json +++ b/.prettierrc.json @@ -16,5 +16,5 @@ "trailingComma": "es5", "useTabs": false, "vueIndentScriptAndStyle": false, - "parser": "babel" + "parser": "typescript" } diff --git a/src/middlewares/errorHandler.ts b/src/middlewares/errorHandler.ts index 1b8c26ca..e0a7ba72 100644 --- a/src/middlewares/errorHandler.ts +++ b/src/middlewares/errorHandler.ts @@ -1,5 +1,5 @@ -import logger from "@/modules/logger"; import { type Request, type Response, type NextFunction } from "express"; +import logger from "@/modules/logger"; /** * Express app에서 사용할 custom global error handler를 정의합니다. diff --git a/src/middlewares/information.ts b/src/middlewares/information.ts index 4d209eec..0006ea40 100644 --- a/src/middlewares/information.ts +++ b/src/middlewares/information.ts @@ -5,7 +5,9 @@ const informationMiddleware = ( res: Response, next: NextFunction ) => { - req.clientIP = req.headers["x-forwarded-for"] || req.connection.remoteAddress; + req.clientIP = + (req.headers["x-forwarded-for"] as string | undefined) || + req.connection.remoteAddress; req.timestamp = Date.now(); next(); }; diff --git a/src/middlewares/responseTime.ts b/src/middlewares/responseTime.ts index aec93458..a1bd8c54 100644 --- a/src/middlewares/responseTime.ts +++ b/src/middlewares/responseTime.ts @@ -1,6 +1,6 @@ import { type Request, type Response } from "express"; -import logger from "@/modules/logger"; import responseTime from "response-time"; +import logger from "@/modules/logger"; const responseTimeMiddleware = responseTime( (req: Request, res: Response, time: number) => { diff --git a/src/middlewares/session.ts b/src/middlewares/session.ts index 5a49e208..8cf0dcf8 100644 --- a/src/middlewares/session.ts +++ b/src/middlewares/session.ts @@ -1,10 +1,10 @@ import expressSession from "express-session"; import { nodeEnv, session as sessionConfig } from "@/loadenv"; -import sessionStore from "@/modules/stores/sessionStore"; import { type LoginInfo } from "@/modules/auths/login"; +import sessionStore from "@/modules/stores/sessionStore"; // 세션에 저장할 데이터 타입을 지정합니다. -declare module 'express-session' { +declare module "express-session" { interface SessionData { /** 사용자 로그인 정보 */ loginInfo?: LoginInfo; @@ -37,4 +37,4 @@ const sessionMiddleware = expressSession({ }, }); -export default sessionMiddleware; \ No newline at end of file +export default sessionMiddleware; From 4af19ff01bcaf4a8a8e4c685ab67938eb5411fb3 Mon Sep 17 00:00:00 2001 From: static Date: Tue, 6 Feb 2024 15:31:18 +0900 Subject: [PATCH 23/61] Refactor: apply prettier --- src/modules/fcm.ts | 38 +++++++++++++++++++++++++----- src/modules/populates/chats.ts | 2 +- src/modules/populates/reports.ts | 7 ++++-- src/modules/populates/rooms.ts | 21 ++++++++++++----- src/modules/stores/mongo.ts | 26 ++++++++++++++++---- src/modules/stores/sessionStore.ts | 8 +++++-- src/types/index.d.ts | 2 +- src/types/mongo.d.ts | 14 +++++++---- 8 files changed, 90 insertions(+), 28 deletions(-) diff --git a/src/modules/fcm.ts b/src/modules/fcm.ts index 44074d7f..24418331 100644 --- a/src/modules/fcm.ts +++ b/src/modules/fcm.ts @@ -30,7 +30,10 @@ export const initializeApp = () => { * @param {string} deviceToken - 등록하려는 FCM device token입니다. * @return {Promise>} 변경된 사용자의 deviceToken의 목록 Array를 반환합니다. 오류가 발생하면 빈 배열을 반환합니다. */ -export const registerDeviceToken = async (userId: string, deviceToken: string): Promise => { +export const registerDeviceToken = async ( + userId: string, + deviceToken: string +): Promise => { try { // 디바이스 토큰을 다른 사용자가 사용하고 있는지 확인 및 삭제합니다. await deviceTokenModel.updateMany( @@ -96,7 +99,10 @@ export const unregisterDeviceToken = async (deviceToken: string) => { * @param {Array} fcmResponses - 등록하려는 FCM device token입니다. * @return {Promise>} 각각의 토큰들의 삭제 성공 여부가 저장된 Array를 반환합니다. 해당 토큰을 DB에서 삭제하는 데 성공했으면 true, 아니면 false가 포함됩니다. */ -const removeExpiredTokens = async (deviceTokens: string[], fcmResponses: SendResponse[]) => { +const removeExpiredTokens = async ( + deviceTokens: string[], + fcmResponses: SendResponse[] +) => { const removalResults = await Promise.all( deviceTokens.map(async (deviceToken, index) => { try { @@ -151,7 +157,10 @@ export const validateDeviceToken = async (deviceToken: string) => { * @param {Boolean?} notificationOptions.chatting - true 또는 false로 주어진 경우, 채팅 알림 설정이 각각 true 또는 false로 설정된 사용자들의 deviceToken만 반환합니다. * @return {Promise>} deviceToken의 Array를 반환합니다. 오류가 발생하면 빈 배열을 반환합니다. */ -export const getTokensOfUsers = async (userIds: string[], notificationOptions: Object = {}) => { +export const getTokensOfUsers = async ( + userIds: string[], + notificationOptions: Object = {} +) => { const deviceTokensOfUsers = ( await Promise.all( userIds.map( @@ -186,7 +195,14 @@ export const getTokensOfUsers = async (userIds: string[], notificationOptions: O * @param {string?} link - 메시지 알림 팝업을 클릭했을 때 이동할 주소입니다. * @return {Promise} 메시지 알림 전송에 실패한 기기의 수를 반환합니다. 오류가 발생하면 -1을 반환합니다. */ -export const sendMessageByTokens = async (tokens: string[], type: ChatType, title: string, body: string, icon?: string, link?: string) => { +export const sendMessageByTokens = async ( + tokens: string[], + type: ChatType, + title: string, + body: string, + icon?: string, + link?: string +) => { if (tokens.length === 0) return -1; try { const message = { @@ -227,7 +243,14 @@ export const sendMessageByTokens = async (tokens: string[], type: ChatType, titl * @param {string?} link - 메시지 알림 팝업을 클릭했을 때 이동할 주소입니다. * @return {Promise} 메시지 알림 전송에 성공했으면 true, 아니면 false를 반환합니다. */ -export const sendMessageByTopic = async (topic: string, type: ChatType, title: string, body: string, icon?: string, link?: string) => { +export const sendMessageByTopic = async ( + topic: string, + type: ChatType, + title: string, + body: string, + icon?: string, + link?: string +) => { try { const message = { topic, @@ -305,7 +328,10 @@ export const subscribeUserToTopic = async (userId: string, topic: string) => { * @param {string} topic - 구독을 해제할 topic입니다. * @return {Promise} 토픽 구독 해제에 실패한 기기의 수를 반환합니다. 오류가 발생하면 -1을 반환합니다. */ -export const unsubscribeUserFromTopic = async (userId: string, topic: string) => { +export const unsubscribeUserFromTopic = async ( + userId: string, + topic: string +) => { try { const deviceToken = await deviceTokenModel.findOne({ userId, diff --git a/src/modules/populates/chats.ts b/src/modules/populates/chats.ts index fa92f2d6..633dd174 100644 --- a/src/modules/populates/chats.ts +++ b/src/modules/populates/chats.ts @@ -9,4 +9,4 @@ export const chatPopulateOption = [ export interface PopulatedChat extends Omit { authorId?: Pick; -}; +} diff --git a/src/modules/populates/reports.ts b/src/modules/populates/reports.ts index 439311a7..a9fe3c9e 100644 --- a/src/modules/populates/reports.ts +++ b/src/modules/populates/reports.ts @@ -8,5 +8,8 @@ export const reportPopulateOption = [ ]; export interface PopulatedReport extends Omit { - reportedId: Pick; -}; + reportedId: Pick< + User, + "_id" | "id" | "name" | "nickname" | "profileImageUrl" + >; +} diff --git a/src/modules/populates/rooms.ts b/src/modules/populates/rooms.ts index 8fa42fbf..15f39338 100644 --- a/src/modules/populates/rooms.ts +++ b/src/modules/populates/rooms.ts @@ -1,4 +1,10 @@ -import { type User, type SettlementStatus, type Participant, type Room, type Location } from "@/types/mongo"; +import { + type User, + type SettlementStatus, + type Participant, + type Room, + type Location, +} from "@/types/mongo"; /** * 쿼리를 통해 얻은 Room Document를 populate할 설정값을 정의합니다. @@ -14,7 +20,8 @@ export const roomPopulateOption = [ }, ]; -interface PopulatedParticipant extends Pick { +interface PopulatedParticipant + extends Pick { user: Pick; } @@ -24,7 +31,8 @@ export interface PopulatedRoom extends Omit { part?: PopulatedParticipant[]; } -export interface FormattedRoom extends Omit { +export interface FormattedRoom + extends Omit { part?: { _id: string; name: string; @@ -36,7 +44,7 @@ export interface FormattedRoom extends Omit { }); // 방에 참여중이지 않은 사용자의 경우, undefined을 반환합니다. - if (!participantSubDocuments || participantSubDocuments.length === 0) return undefined; + if (!participantSubDocuments || participantSubDocuments.length === 0) + return undefined; // 방에 참여중인 사용자의 경우, 정산 상태가 완료된 것인지("paid"거나 "sent"인지)를 반환합니다. return ["paid", "sent"].includes(participantSubDocuments[0].settlementStatus); diff --git a/src/modules/stores/mongo.ts b/src/modules/stores/mongo.ts index bc248bce..51c69c15 100755 --- a/src/modules/stores/mongo.ts +++ b/src/modules/stores/mongo.ts @@ -1,6 +1,18 @@ import mongoose, { model, Schema, Types } from "mongoose"; import logger from "@/modules/logger"; -import type { User, Participant, DeviceToken, NotificationOption, TopicSubscription, Room, Location, Chat, Report, AdminIPWhitelist, AdminLog } from "@/types/mongo"; +import type { + User, + Participant, + DeviceToken, + NotificationOption, + TopicSubscription, + Room, + Location, + Chat, + Report, + AdminIPWhitelist, + AdminLog, +} from "@/types/mongo"; const userSchema = new Schema({ name: { type: String, required: true }, //실명 @@ -217,17 +229,21 @@ export const connectDatabase = (mongoUrl: string) => { // 데이터베이스 연결이 끊어지면 5초 후 재연결을 시도합니다. logger.error("데이터베이스와 연결이 끊어졌습니다!"); setTimeout(() => { - mongoose.connect(mongoUrl, /*{ + mongoose.connect( + mongoUrl /*{ useNewUrlParser: true, useUnifiedTopology: true, - }*/); // NOTE: https://velog.io/@untiring_dev/MongoDB-MongoDB-Mongoose%EC%97%90-%EC%97%B0%EA%B2%B0 + }*/ + ); // NOTE: https://velog.io/@untiring_dev/MongoDB-MongoDB-Mongoose%EC%97%90-%EC%97%B0%EA%B2%B0 }, 5000); }); - mongoose.connect(mongoUrl, /*{ + mongoose.connect( + mongoUrl /*{ useNewUrlParser: true, useUnifiedTopology: true, - }*/); + }*/ + ); return database; }; diff --git a/src/modules/stores/sessionStore.ts b/src/modules/stores/sessionStore.ts index 11647e00..11ec9a78 100644 --- a/src/modules/stores/sessionStore.ts +++ b/src/modules/stores/sessionStore.ts @@ -1,7 +1,11 @@ import MongoStore from "connect-mongo"; -import RedisStore from "connect-redis" +import RedisStore from "connect-redis"; import redis from "redis"; -import { redis as redisUrl, mongo as mongoUrl, session as sessionConfig } from "@/loadenv"; +import { + redis as redisUrl, + mongo as mongoUrl, + session as sessionConfig, +} from "@/loadenv"; import logger from "@/modules/logger"; const getSessionStore = () => { diff --git a/src/types/index.d.ts b/src/types/index.d.ts index acee1ace..d679d420 100644 --- a/src/types/index.d.ts +++ b/src/types/index.d.ts @@ -16,4 +16,4 @@ declare global { timestamp?: number; } } -} \ No newline at end of file +} diff --git a/src/types/mongo.d.ts b/src/types/mongo.d.ts index c05894df..1f0bf64f 100644 --- a/src/types/mongo.d.ts +++ b/src/types/mongo.d.ts @@ -24,10 +24,10 @@ export interface User extends Document { agreeOnTermsOfService: boolean; subinfo?: { /** 사용자의 KAIST 학번. */ - kaist: string, - sparcs: string, - facebook: string, - twitter: string, + kaist: string; + sparcs: string; + facebook: string; + twitter: string; }; /** 사용자의 이메일 주소. */ email: string; @@ -37,7 +37,11 @@ export interface User extends Document { account: string; } -export type SettlementStatus = "not-departed" | "paid" | "send-required" | "sent"; +export type SettlementStatus = + | "not-departed" + | "paid" + | "send-required" + | "sent"; export interface Participant extends Document { /** 방 참여자의 User ObjectID. */ From 86f8b4af2d34fb35df1bfb51692de95eb55b49b8 Mon Sep 17 00:00:00 2001 From: static Date: Wed, 20 Mar 2024 23:18:16 +0900 Subject: [PATCH 24/61] Remove: all references to lottery module from outside lottery module --- src/services/notifications.js | 12 +-- src/services/rooms.js | 153 +++++++++++++++++----------------- src/services/users.js | 24 +++--- 3 files changed, 95 insertions(+), 94 deletions(-) diff --git a/src/services/notifications.js b/src/services/notifications.js index 7134fab5..7a0b810e 100644 --- a/src/services/notifications.js +++ b/src/services/notifications.js @@ -5,7 +5,7 @@ const logger = require("@/modules/logger"); const { registerDeviceToken, validateDeviceToken } = require("@/modules/fcm"); // 이벤트 코드입니다. -const { contracts } = require("../lottery"); +// const { contracts } = require("../lottery"); const registerDeviceTokenHandler = async (req, res) => { try { @@ -108,11 +108,11 @@ const editOptionsHandler = async (req, res) => { } // 이벤트 코드입니다. - await contracts?.completeAdPushAgreementQuest( - req.userOid, - req.timestamp, - options.advertisement - ); + // await contracts?.completeAdPushAgreementQuest( + // req.userOid, + // req.timestamp, + // options.advertisement + // ); res.status(200).json(updatedNotificationOptions); } catch (err) { diff --git a/src/services/rooms.js b/src/services/rooms.js index 1a5a444b..f2dd1cbf 100644 --- a/src/services/rooms.js +++ b/src/services/rooms.js @@ -15,12 +15,12 @@ const { } = require("@/modules/slackNotification"); // 이벤트 코드입니다. -const { eventConfig } = require("../../loadenv"); -const eventPeriod = eventConfig && { - startAt: new Date(eventConfig.period.startAt), - endAt: new Date(eventConfig.period.endAt), -}; -const { contracts } = require("../lottery"); +// const { eventConfig } = require("../../loadenv"); +// const eventPeriod = eventConfig && { +// startAt: new Date(eventConfig.period.startAt), +// endAt: new Date(eventConfig.period.endAt), +// }; +// const { contracts } = require("../lottery"); const createHandler = async (req, res) => { const { name, from, to, time, maxPartLength } = req.body; @@ -102,7 +102,7 @@ const createHandler = async (req, res) => { const roomObjectFormated = formatSettlement(roomObject); // 이벤트 코드입니다. - await contracts?.completeFirstRoomCreationQuest(req.userOid, req.timestamp); + // await contracts?.completeFirstRoomCreationQuest(req.userOid, req.timestamp); return res.send(roomObjectFormated); } catch (err) { @@ -172,56 +172,57 @@ const createTestHandler = async (req, res) => { try { // 이벤트 코드입니다. - if ( - !eventPeriod || - req.timestamp >= eventPeriod.endAt || - req.timestamp < eventPeriod.startAt - ) - return res.json({ result: true }); - - const countRecentlyMadeRooms = await roomModel.countDocuments({ - madeat: { $gte: new Date(req.timestamp - 86400000) }, // 밀리초 단위로 24시간을 나타냅니다. - "part.0.user": req.userOid, // 방 최초 생성자를 저장하는 필드가 없으므로, 첫 번째 참여자를 생성자로 간주합니다. - }); - if (!countRecentlyMadeRooms && countRecentlyMadeRooms !== 0) - return res - .status(500) - .json({ error: "Rooms/create/test : internal server error" }); - - const dateTime = new Date(time); - const candidateRooms = await roomModel - .find( - { - time: { - $gte: new Date(dateTime.getTime() - 43200000), - $lte: new Date(dateTime.getTime() + 43200000), - }, - part: { $elemMatch: { user: req.userOid } }, - }, - "from to time maxPartLength" - ) - .limit(2) - .lean(); - if (!candidateRooms) - return res - .status(500) - .json({ error: "Rooms/create/test : internal server error" }); - - const isAbusing = checkIsAbusing( - req.body, - countRecentlyMadeRooms, - candidateRooms - ); - if (isAbusing) { - const user = await userModel.findById(req.userOid).lean(); - notifyRoomCreationAbuseToReportChannel( - req.userOid, - user?.nickname ?? req.userOid, - req.body - ); - } + // if ( + // !eventPeriod || + // req.timestamp >= eventPeriod.endAt || + // req.timestamp < eventPeriod.startAt + // ) + // return res.json({ result: true }); + + // const countRecentlyMadeRooms = await roomModel.countDocuments({ + // madeat: { $gte: new Date(req.timestamp - 86400000) }, // 밀리초 단위로 24시간을 나타냅니다. + // "part.0.user": req.userOid, // 방 최초 생성자를 저장하는 필드가 없으므로, 첫 번째 참여자를 생성자로 간주합니다. + // }); + // if (!countRecentlyMadeRooms && countRecentlyMadeRooms !== 0) + // return res + // .status(500) + // .json({ error: "Rooms/create/test : internal server error" }); + + // const dateTime = new Date(time); + // const candidateRooms = await roomModel + // .find( + // { + // time: { + // $gte: new Date(dateTime.getTime() - 43200000), + // $lte: new Date(dateTime.getTime() + 43200000), + // }, + // part: { $elemMatch: { user: req.userOid } }, + // }, + // "from to time maxPartLength" + // ) + // .limit(2) + // .lean(); + // if (!candidateRooms) + // return res + // .status(500) + // .json({ error: "Rooms/create/test : internal server error" }); + + // const isAbusing = checkIsAbusing( + // req.body, + // countRecentlyMadeRooms, + // candidateRooms + // ); + // if (isAbusing) { + // const user = await userModel.findById(req.userOid).lean(); + // notifyRoomCreationAbuseToReportChannel( + // req.userOid, + // user?.nickname ?? req.userOid, + // req.body + // ); + // } - return res.json({ result: !isAbusing }); + // return res.json({ result: !isAbusing }); + return res.json({ result: true }); } catch (err) { logger.error(err); res.status(500).json({ @@ -622,16 +623,16 @@ const commitPaymentHandler = async (req, res) => { }); // 이벤트 코드입니다. - await contracts?.completePayingQuest( - req.userOid, - req.timestamp, - roomObject - ); - await contracts?.completePayingAndSendingQuest( - req.userOid, - req.timestamp, - roomObject - ); + // await contracts?.completePayingQuest( + // req.userOid, + // req.timestamp, + // roomObject + // ); + // await contracts?.completePayingAndSendingQuest( + // req.userOid, + // req.timestamp, + // roomObject + // ); // 수정한 방 정보를 반환합니다. res.send(formatSettlement(roomObject, { isOver: true })); @@ -700,16 +701,16 @@ const settlementHandler = async (req, res) => { }); // 이벤트 코드입니다. - await contracts?.completeSendingQuest( - req.userOid, - req.timestamp, - roomObject - ); - await contracts?.completePayingAndSendingQuest( - req.userOid, - req.timestamp, - roomObject - ); + // await contracts?.completeSendingQuest( + // req.userOid, + // req.timestamp, + // roomObject + // ); + // await contracts?.completePayingAndSendingQuest( + // req.userOid, + // req.timestamp, + // roomObject + // ); // 수정한 방 정보를 반환합니다. res.send(formatSettlement(roomObject, { isOver: true })); diff --git a/src/services/users.js b/src/services/users.js index d3adde6f..10008c0a 100644 --- a/src/services/users.js +++ b/src/services/users.js @@ -1,14 +1,14 @@ const { userModel } = require("@/modules/stores/mongo"); const logger = require("@/modules/logger"); const aws = require("@/modules/stores/aws"); - -// 이벤트 코드입니다. -const { contracts } = require("../lottery"); const { generateNickname, generateProfileImageUrl, } = require("@/modules/modifyProfile"); +// 이벤트 코드입니다. +// const { contracts } = require("../lottery"); + const agreeOnTermsOfServiceHandler = async (req, res) => { try { let user = await userModel.findOne({ id: req.userId }); @@ -52,10 +52,10 @@ const editNicknameHandler = async (req, res) => { if (result) { // 이벤트 코드입니다. - await contracts?.completeNicknameChangingQuest( - req.userOid, - req.timestamp - ); + // await contracts?.completeNicknameChangingQuest( + // req.userOid, + // req.timestamp + // ); res .status(200) @@ -79,11 +79,11 @@ const editAccountHandler = async (req, res) => { if (result) { // 이벤트 코드입니다. - await contracts?.completeAccountChangingQuest( - req.userOid, - req.timestamp, - newAccount - ); + // await contracts?.completeAccountChangingQuest( + // req.userOid, + // req.timestamp, + // newAccount + // ); res.status(200).send("Users/editAccount : edit user account successful"); } else { From 2a32d961eb214a3ec6c4e0364ced6cf1daec15b6 Mon Sep 17 00:00:00 2001 From: static Date: Wed, 20 Mar 2024 23:42:13 +0900 Subject: [PATCH 25/61] Fix: runtime errors --- scripts/profileImageUrlUpdater.js | 2 +- src/middlewares/zod.js | 2 +- src/modules/email.js | 4 ++-- src/modules/socket.js | 14 +++++++------- src/routes/admin.js | 6 +++--- src/routes/auth.js | 2 +- src/routes/chats.js | 6 +++--- src/routes/docs.js | 2 +- src/routes/docs/auth.replace.js | 2 +- src/routes/docs/chats.js | 2 +- src/routes/docs/logininfo.js | 2 +- src/routes/docs/reports.js | 2 +- src/routes/docs/rooms.js | 2 +- src/routes/docs/schemas/reportsSchema.js | 2 +- src/routes/docs/schemas/roomsSchema.js | 2 +- src/routes/docs/swaggerDocs.js | 2 +- src/routes/docs/utils.js | 2 +- src/routes/notifications.js | 4 ++-- src/routes/reports.js | 4 ++-- src/routes/rooms.js | 6 +++--- src/routes/users.js | 6 +++--- src/schedules/notifyAfterArrival.js | 2 +- src/schedules/notifyBeforeDepart.js | 2 +- src/services/auth.js | 4 ++-- src/services/auth.mobile.js | 4 ++-- src/services/auth.replace.js | 6 +++--- src/services/chats.js | 4 ++-- src/services/locations.js | 2 +- src/services/logininfo.js | 2 +- src/services/notifications.js | 4 ++-- src/services/reports.js | 4 ++-- src/services/rooms.js | 6 +++--- src/services/users.js | 6 +++--- test/utils.js | 2 +- 34 files changed, 62 insertions(+), 62 deletions(-) diff --git a/scripts/profileImageUrlUpdater.js b/scripts/profileImageUrlUpdater.js index 78ebe778..2b35bf1a 100644 --- a/scripts/profileImageUrlUpdater.js +++ b/scripts/profileImageUrlUpdater.js @@ -2,7 +2,7 @@ // https://github.com/sparcs-kaist/taxi-back/issues/173 const { MongoClient } = require("mongodb"); -const { mongo: mongoUrl, aws: awsEnv } = require("../loadenv"); +const { mongo: mongoUrl, aws: awsEnv } = require("../loadenv"); // FIXME: 올바른 경로로 수정해야 합니다. const time = Date.now(); diff --git a/src/middlewares/zod.js b/src/middlewares/zod.js index 63f5668a..b8660634 100644 --- a/src/middlewares/zod.js +++ b/src/middlewares/zod.js @@ -1,4 +1,4 @@ -const logger = require("../modules/logger"); +const logger = require("@/modules/logger").default; const parseZodErrors = (statusCode, errors, res) => { const error_message = errors; diff --git a/src/modules/email.js b/src/modules/email.js index 05bdfe3e..d6a20285 100644 --- a/src/modules/email.js +++ b/src/modules/email.js @@ -1,6 +1,6 @@ const nodemailer = require("nodemailer"); -const logger = require("./logger"); -const { nodeEnv } = require("../../loadenv"); +const logger = require("@/modules/logger").default; +const { nodeEnv } = require("@/loadenv"); /** * production 환경에서 메일을 전송하기 위해 사용되는 agent입니다. diff --git a/src/modules/socket.js b/src/modules/socket.js index 7741e597..c4de7f56 100644 --- a/src/modules/socket.js +++ b/src/modules/socket.js @@ -1,14 +1,14 @@ const { Server } = require("socket.io"); -const sessionMiddleware = require("@/middlewares/session"); -const logger = require("./logger"); -const { getLoginInfo } = require("./auths/login"); -const { roomModel, userModel, chatModel } = require("./stores/mongo"); -const { getS3Url } = require("./stores/aws"); -const { getTokensOfUsers, sendMessageByTokens } = require("./fcm"); +const sessionMiddleware = require("@/middlewares/session").default; +const logger = require("@/modules/logger").default; +const { getLoginInfo } = require("@/modules/auths/login"); +const { roomModel, userModel, chatModel } = require("@/modules/stores/mongo"); +const { getS3Url } = require("@/modules/stores/aws"); +const { getTokensOfUsers, sendMessageByTokens } = require("@/modules/fcm"); const { corsWhiteList } = require("@/loadenv"); -const { chatPopulateOption } = require("./populates/chats"); +const { chatPopulateOption } = require("@/modules/populates/chats"); /** * emitChatEvent의 필수 파라미터가 주어지지 않은 경우 발생하는 예외를 정의하는 클래스입니다. diff --git a/src/routes/admin.js b/src/routes/admin.js index d495497c..81cf20a3 100644 --- a/src/routes/admin.js +++ b/src/routes/admin.js @@ -18,8 +18,8 @@ const { buildResource } = require("@/modules/adminResource"); const router = express.Router(); // Requires admin property of the user to enter admin page. -router.use(require("@/middlewares/authAdmin")); -router.use(require("@/middlewares/auth")); +router.use(require("@/middlewares/authAdmin").default); +router.use(require("@/middlewares/auth").default); // Registration of the mongoose adapter AdminJS.registerAdapter(AdminJSMongoose); @@ -36,7 +36,7 @@ const resources = [ notificationOptionModel, ] .map(buildResource()) - .concat(require("../lottery").resources); + .concat(/*require("@/lottery").resources*/ []); // Create router for admin page const adminJS = new AdminJS({ resources }); diff --git a/src/routes/auth.js b/src/routes/auth.js index 9ce57404..534814f6 100644 --- a/src/routes/auth.js +++ b/src/routes/auth.js @@ -1,7 +1,7 @@ const express = require("express"); const router = express.Router(); const { body, query } = require("express-validator"); -const validator = require("@/middlewares/validator"); +const validator = require("@/middlewares/validator").default; const authHandlers = require("@/services/auth"); const authReplaceHandlers = require("@/services/auth.replace"); diff --git a/src/routes/chats.js b/src/routes/chats.js index f27ace38..8af77944 100644 --- a/src/routes/chats.js +++ b/src/routes/chats.js @@ -1,13 +1,13 @@ const express = require("express"); const { body } = require("express-validator"); -const validator = require("@/middlewares/validator"); -const patterns = require("@/modules/patterns"); +const validator = require("@/middlewares/validator").default; +const patterns = require("@/modules/patterns").default; const router = express.Router(); const chatsHandlers = require("@/services/chats"); // 라우터 접근 시 로그인 필요 -router.use(require("@/middlewares/auth")); +router.use(require("@/middlewares/auth").default); /** * 가장 최근에 도착한 60개의 채팅을 가져옵니다. diff --git a/src/routes/docs.js b/src/routes/docs.js index 97a1b288..0a571d8e 100644 --- a/src/routes/docs.js +++ b/src/routes/docs.js @@ -1,6 +1,6 @@ const express = require("express"); const swaggerUi = require("swagger-ui-express"); -const swaggerDocs = require("./docs/swaggerDocs.js"); +const swaggerDocs = require("./docs/swaggerDocs"); const router = express.Router(); router.use(swaggerUi.serve); diff --git a/src/routes/docs/auth.replace.js b/src/routes/docs/auth.replace.js index 8246836b..be8b8174 100644 --- a/src/routes/docs/auth.replace.js +++ b/src/routes/docs/auth.replace.js @@ -1,4 +1,4 @@ -const loginReplacePage = require("../../views/loginReplacePage"); +const loginReplacePage = require("../../views/loginReplacePage").default; const tag = "auth"; const apiPrefix = "/auth(dev)"; diff --git a/src/routes/docs/chats.js b/src/routes/docs/chats.js index 0aaa1c6d..81834042 100644 --- a/src/routes/docs/chats.js +++ b/src/routes/docs/chats.js @@ -1,4 +1,4 @@ -const { objectId } = require("../../modules/patterns"); +const { objectId } = require("../../modules/patterns").default; const tag = "chats"; const apiPrefix = "/chats"; diff --git a/src/routes/docs/logininfo.js b/src/routes/docs/logininfo.js index 7c447760..caec1f96 100644 --- a/src/routes/docs/logininfo.js +++ b/src/routes/docs/logininfo.js @@ -1,4 +1,4 @@ -const { objectId } = require("../../modules/patterns"); +const { objectId } = require("../../modules/patterns").default; const tag = "logininfo"; const apiPrefix = "/logininfo"; diff --git a/src/routes/docs/reports.js b/src/routes/docs/reports.js index a11933ee..3acf99da 100644 --- a/src/routes/docs/reports.js +++ b/src/routes/docs/reports.js @@ -1,4 +1,4 @@ -const { objectId } = require("../../modules/patterns"); +const { objectId } = require("../../modules/patterns").default; const tag = "reports"; const apiPrefix = "/reports"; diff --git a/src/routes/docs/rooms.js b/src/routes/docs/rooms.js index 710bf649..a481bbc1 100644 --- a/src/routes/docs/rooms.js +++ b/src/routes/docs/rooms.js @@ -1,4 +1,4 @@ -const { objectId, room } = require("../../modules/patterns"); +const { objectId, room } = require("../../modules/patterns").default; const tag = "rooms"; const apiPrefix = "/rooms"; diff --git a/src/routes/docs/schemas/reportsSchema.js b/src/routes/docs/schemas/reportsSchema.js index d208dbb7..0e4c43b9 100644 --- a/src/routes/docs/schemas/reportsSchema.js +++ b/src/routes/docs/schemas/reportsSchema.js @@ -1,6 +1,6 @@ const { z } = require("zod"); const { zodToSchemaObject } = require("../utils"); -const { objectId } = require("../../../modules/patterns"); +const { objectId } = require("../../../modules/patterns").default; const reportsZod = { createHandler: z diff --git a/src/routes/docs/schemas/roomsSchema.js b/src/routes/docs/schemas/roomsSchema.js index 39dcd8bf..c4075204 100644 --- a/src/routes/docs/schemas/roomsSchema.js +++ b/src/routes/docs/schemas/roomsSchema.js @@ -1,6 +1,6 @@ const { z } = require("zod"); const { zodToSchemaObject } = require("../utils"); -const { objectId, room } = require("../../../modules/patterns"); +const { objectId, room } = require("../../../modules/patterns").default; const roomsZod = {}; roomsZod["part"] = z diff --git a/src/routes/docs/swaggerDocs.js b/src/routes/docs/swaggerDocs.js index 62639dfa..2bca48c7 100644 --- a/src/routes/docs/swaggerDocs.js +++ b/src/routes/docs/swaggerDocs.js @@ -8,7 +8,7 @@ const authReplaceDocs = require("./auth.replace"); const usersDocs = require("./users"); const roomsDocs = require("./rooms"); const chatsDocs = require("./chats"); -const { port, nodeEnv } = require("../../../loadenv"); +const { port, nodeEnv } = require("../../loadenv"); const serverList = [ { diff --git a/src/routes/docs/utils.js b/src/routes/docs/utils.js index 2f99c13c..bd006150 100644 --- a/src/routes/docs/utils.js +++ b/src/routes/docs/utils.js @@ -1,5 +1,5 @@ const { zodToJsonSchema } = require("zod-to-json-schema"); -const logger = require("../../modules/logger"); +const logger = require("../../modules/logger").default; const zodToSchemaObject = (zodObejct) => { try { diff --git a/src/routes/notifications.js b/src/routes/notifications.js index 89ec5be9..c27c22b3 100644 --- a/src/routes/notifications.js +++ b/src/routes/notifications.js @@ -3,10 +3,10 @@ const router = express.Router(); const { body } = require("express-validator"); const notificationHandlers = require("@/services/notifications"); -const validator = require("@/middlewares/validator"); +const validator = require("@/middlewares/validator").default; // 라우터 접근 시 로그인 필요 -router.use(require("@/middlewares/auth")); +router.use(require("@/middlewares/auth").default); // FCM 토큰 등록 router.post( diff --git a/src/routes/reports.js b/src/routes/reports.js index 0fc8b24a..4d76e2f2 100644 --- a/src/routes/reports.js +++ b/src/routes/reports.js @@ -1,11 +1,11 @@ const express = require("express"); -const { validateBody } = require("../middlewares/zod"); +const { validateBody } = require("@/middlewares/zod"); const { reportsZod } = require("./docs/schemas/reportsSchema"); const router = express.Router(); const reportHandlers = require("@/services/reports"); // 라우터 접근 시 로그인 필요 -router.use(require("@/middlewares/auth")); +router.use(require("@/middlewares/auth").default); router.post( "/create", diff --git a/src/routes/rooms.js b/src/routes/rooms.js index e0c8a631..60a235e2 100644 --- a/src/routes/rooms.js +++ b/src/routes/rooms.js @@ -3,8 +3,8 @@ const { query, body } = require("express-validator"); const router = express.Router(); const roomHandlers = require("@/services/rooms"); -const validator = require("@/middlewares/validator"); -const patterns = require("@/modules/patterns"); +const validator = require("@/middlewares/validator").default; +const patterns = require("@/modules/patterns").default; // 조건(이름, 출발지, 도착지, 날짜)에 맞는 방들을 모두 반환한다. router.get( @@ -31,7 +31,7 @@ router.get( ); // 이후 API 접근 시 로그인 필요 -router.use(require("../middlewares/auth")); +router.use(require("@/middlewares/auth").default); // 특정 id 방 세부사항 보기 router.get( diff --git a/src/routes/users.js b/src/routes/users.js index 9bc1d4eb..aa00bcb2 100755 --- a/src/routes/users.js +++ b/src/routes/users.js @@ -1,7 +1,7 @@ const express = require("express"); const { body } = require("express-validator"); -const validator = require("@/middlewares/validator"); -const patterns = require("@/modules/patterns"); +const validator = require("@/middlewares/validator").default; +const patterns = require("@/modules/patterns").default; const router = express.Router(); const userHandlers = require("@/services/users"); @@ -9,7 +9,7 @@ const userHandlers = require("@/services/users"); const { replaceSpaceInNickname } = require("@/modules/modifyProfile"); // 라우터 접근 시 로그인 필요 -router.use(require("@/middlewares/auth")); +router.use(require("@/middlewares/auth").default); // 이용 약관에 동의합니다. router.post( diff --git a/src/schedules/notifyAfterArrival.js b/src/schedules/notifyAfterArrival.js index 5c1a5a6c..c2c01b25 100644 --- a/src/schedules/notifyAfterArrival.js +++ b/src/schedules/notifyAfterArrival.js @@ -1,7 +1,7 @@ const { roomModel, chatModel } = require("@/modules/stores/mongo"); // const { roomPopulateOption } = require("@/modules/populates/rooms"); const { emitChatEvent } = require("@/modules/socket"); -const logger = require("@/modules/logger"); +const logger = require("@/modules/logger").default; const MS_PER_MINUTE = 60000; diff --git a/src/schedules/notifyBeforeDepart.js b/src/schedules/notifyBeforeDepart.js index 523f6dff..b1b6e87d 100644 --- a/src/schedules/notifyBeforeDepart.js +++ b/src/schedules/notifyBeforeDepart.js @@ -1,6 +1,6 @@ const { roomModel, chatModel } = require("@/modules/stores/mongo"); const { emitChatEvent } = require("@/modules/socket"); -const logger = require("@/modules/logger"); +const logger = require("@/modules/logger").default; const MS_PER_MINUTE = 60000; diff --git a/src/services/auth.js b/src/services/auth.js index c95d263c..96de32c3 100644 --- a/src/services/auth.js +++ b/src/services/auth.js @@ -9,8 +9,8 @@ const { generateProfileImageUrl, getFullUsername, } = require("@/modules/modifyProfile"); -const jwt = require("@/modules/auths/jwt"); -const logger = require("@/modules/logger"); +const jwt = require("@/modules/auths/jwt").default; +const logger = require("@/modules/logger").default; // SPARCS SSO const Client = require("@/modules/auths/sparcssso"); diff --git a/src/services/auth.mobile.js b/src/services/auth.mobile.js index 7dc2798a..8e66db0e 100644 --- a/src/services/auth.mobile.js +++ b/src/services/auth.mobile.js @@ -2,8 +2,8 @@ const { userModel } = require("@/modules/stores/mongo"); const { login } = require("@/modules/auths/login"); const { registerDeviceToken, unregisterDeviceToken } = require("@/modules/fcm"); -const jwt = require("@/modules/auths/jwt"); -const logger = require("@/modules/logger"); +const jwt = require("@/modules/auths/jwt").default; +const logger = require("@/modules/logger").default; const { TOKEN_EXPIRED, TOKEN_INVALID } = require("@/loadenv").jwt; diff --git a/src/services/auth.replace.js b/src/services/auth.replace.js index 1c64e45f..65ae92c7 100644 --- a/src/services/auth.replace.js +++ b/src/services/auth.replace.js @@ -6,11 +6,11 @@ const { generateNickname, generateProfileImageUrl, } = require("@/modules/modifyProfile"); -const logger = require("@/modules/logger"); -const jwt = require("@/modules/auths/jwt"); +const logger = require("@/modules/logger").default; +const jwt = require("@/modules/auths/jwt").default; const { registerDeviceTokenHandler, tryLogin } = require("@/services/auth"); -const loginReplacePage = require("@/views/loginReplacePage"); +const loginReplacePage = require("@/views/loginReplacePage").default; const createUserData = (id) => { const info = { diff --git a/src/services/chats.js b/src/services/chats.js index c21f4b75..f8190cfd 100644 --- a/src/services/chats.js +++ b/src/services/chats.js @@ -1,13 +1,13 @@ const { chatModel, userModel, roomModel } = require("@/modules/stores/mongo"); const { chatPopulateOption } = require("@/modules/populates/chats"); const { roomPopulateOption } = require("@/modules/populates/rooms"); -const aws = require("@/modules/stores/aws"); +const aws = require("@/modules/stores/aws").default; const { transformChatsForRoom, emitChatEvent, emitUpdateEvent, } = require("@/modules/socket"); -const logger = require("@/modules/logger"); +const logger = require("@/modules/logger").default; const chatCount = 60; diff --git a/src/services/locations.js b/src/services/locations.js index ed81ed43..9a2c5e7d 100644 --- a/src/services/locations.js +++ b/src/services/locations.js @@ -1,5 +1,5 @@ const { locationModel } = require("@/modules/stores/mongo"); -const logger = require("@/modules/logger"); +const logger = require("@/modules/logger").default; const getAllLocationsHandler = async (_, res) => { try { diff --git a/src/services/logininfo.js b/src/services/logininfo.js index b074d847..affac40a 100644 --- a/src/services/logininfo.js +++ b/src/services/logininfo.js @@ -1,6 +1,6 @@ const { userModel } = require("@/modules/stores/mongo"); const { getLoginInfo } = require("@/modules/auths/login"); -const logger = require("@/modules/logger"); +const logger = require("@/modules/logger").default; const logininfoHandler = async (req, res) => { try { diff --git a/src/services/notifications.js b/src/services/notifications.js index 7a0b810e..fc2090cf 100644 --- a/src/services/notifications.js +++ b/src/services/notifications.js @@ -1,11 +1,11 @@ const { userModel } = require("@/modules/stores/mongo"); const { notificationOptionModel } = require("@/modules/stores/mongo"); -const logger = require("@/modules/logger"); +const logger = require("@/modules/logger").default; const { registerDeviceToken, validateDeviceToken } = require("@/modules/fcm"); // 이벤트 코드입니다. -// const { contracts } = require("../lottery"); +// const { contracts } = require("@/lottery"); const registerDeviceTokenHandler = async (req, res) => { try { diff --git a/src/services/reports.js b/src/services/reports.js index 7ca7d002..a6dfc42d 100644 --- a/src/services/reports.js +++ b/src/services/reports.js @@ -1,8 +1,8 @@ const { userModel, reportModel, roomModel } = require("@/modules/stores/mongo"); const { reportPopulateOption } = require("@/modules/populates/reports"); const { sendReportEmail } = require("@/modules/stores/aws"); -const logger = require("@/modules/logger"); -const emailPage = require("@/views/emailNoSettlementPage"); +const logger = require("@/modules/logger").default; +const emailPage = require("@/views/emailNoSettlementPage").default; const { notifyReportToReportChannel } = require("@/modules/slackNotification"); const createHandler = async (req, res) => { diff --git a/src/services/rooms.js b/src/services/rooms.js index f2dd1cbf..fe0a5cf8 100644 --- a/src/services/rooms.js +++ b/src/services/rooms.js @@ -4,7 +4,7 @@ const { userModel, } = require("@/modules/stores/mongo"); const { emitChatEvent } = require("@/modules/socket"); -const logger = require("@/modules/logger"); +const logger = require("@/modules/logger").default; const { roomPopulateOption, formatSettlement, @@ -15,12 +15,12 @@ const { } = require("@/modules/slackNotification"); // 이벤트 코드입니다. -// const { eventConfig } = require("../../loadenv"); +// const { eventConfig } = require("@/loadenv"); // const eventPeriod = eventConfig && { // startAt: new Date(eventConfig.period.startAt), // endAt: new Date(eventConfig.period.endAt), // }; -// const { contracts } = require("../lottery"); +// const { contracts } = require("@/lottery"); const createHandler = async (req, res) => { const { name, from, to, time, maxPartLength } = req.body; diff --git a/src/services/users.js b/src/services/users.js index 10008c0a..5fe9bafc 100644 --- a/src/services/users.js +++ b/src/services/users.js @@ -1,13 +1,13 @@ const { userModel } = require("@/modules/stores/mongo"); -const logger = require("@/modules/logger"); -const aws = require("@/modules/stores/aws"); +const logger = require("@/modules/logger").default; +const aws = require("@/modules/stores/aws").default; const { generateNickname, generateProfileImageUrl, } = require("@/modules/modifyProfile"); // 이벤트 코드입니다. -// const { contracts } = require("../lottery"); +// const { contracts } = require("@/lottery"); const agreeOnTermsOfServiceHandler = async (req, res) => { try { diff --git a/test/utils.js b/test/utils.js index e537913b..ba8d2bca 100644 --- a/test/utils.js +++ b/test/utils.js @@ -7,7 +7,7 @@ const { connectDatabase, } = require("../src/modules/stores/mongo"); const { generateProfileImageUrl } = require("../src/modules/modifyProfile"); -const { mongo: mongoUrl } = require("../loadenv"); +const { mongo: mongoUrl } = require("@/loadenv"); connectDatabase(mongoUrl); From da9de887fb351ab53c81a1fa11b853da6092b373 Mon Sep 17 00:00:00 2001 From: static Date: Tue, 30 Apr 2024 20:57:20 +0900 Subject: [PATCH 26/61] Refactor: merge two package.json files into one file --- package.json | 4 +- src/sampleGenerator/.gitignore | 107 --------------------------------- src/sampleGenerator/index.js | 2 +- tsconfig.json | 1 + 4 files changed, 5 insertions(+), 109 deletions(-) delete mode 100644 src/sampleGenerator/.gitignore diff --git a/package.json b/package.json index 19f05815..e28269bb 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,9 @@ "serve": "cross-env TZ='Asia/Seoul' NODE_ENV=production node dist/index.js", "lint": "pnpm eslint .", "runscript": "cross-env TZ='Asia/Seoul' NODE_ENV=production node", - "sample": "cd src/sampleGenerator && npm start && cd .." + "sample": "npx tsc && tsc-alias && cross-env NODE_ENV=test node dist/sampleGenerator/index.js", + "dumpDB": "npx tsc && tsc-alias && cross-env NODE_ENV=test node dist/sampleGenerator/tools/dump.js", + "restoreDB": "node src/sampleGenerator/tools/restore.js" }, "engines": { "node": ">=18.0.0", diff --git a/src/sampleGenerator/.gitignore b/src/sampleGenerator/.gitignore deleted file mode 100644 index 2909449b..00000000 --- a/src/sampleGenerator/.gitignore +++ /dev/null @@ -1,107 +0,0 @@ -# Logs -logs -*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* -lerna-debug.log* - -# Diagnostic reports (https://nodejs.org/api/report.html) -report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json - -# Runtime data -pids -*.pid -*.seed -*.pid.lock - -# Directory for instrumented libs generated by jscoverage/JSCover -lib-cov - -# Coverage directory used by tools like istanbul -coverage -*.lcov - -# nyc test coverage -.nyc_output - -# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) -.grunt - -# Bower dependency directory (https://bower.io/) -bower_components - -# node-waf configuration -.lock-wscript - -# Compiled binary addons (https://nodejs.org/api/addons.html) -build/Release - -# Dependency directories -node_modules/ -jspm_packages/ - -# TypeScript v1 declaration files -typings/ - -# TypeScript cache -*.tsbuildinfo - -# Optional npm cache directory -.npm - -# Optional eslint cache -.eslintcache - -# Microbundle cache -.rpt2_cache/ -.rts2_cache_cjs/ -.rts2_cache_es/ -.rts2_cache_umd/ - -# Optional REPL history -.node_repl_history - -# Output of 'npm pack' -*.tgz - -# Yarn Integrity file -.yarn-integrity - -# dotenv environment variables file -.env -.env.test - -# parcel-bundler cache (https://parceljs.org/) -.cache - -# Next.js build output -.next - -# Nuxt.js build / generate output -.nuxt -dist - -# Gatsby files -.cache/ -# Comment in the public line in if your project uses Gatsby and *not* Next.js -# https://nextjs.org/blog/next-9-1#public-directory-support -# public - -# vuepress build output -.vuepress/dist - -# Serverless directories -.serverless/ - -# FuseBox cache -.fusebox/ - -# DynamoDB Local files -.dynamodb/ - -# TernJS port file -.tern-port - -# MongoDB Dump -dump/ diff --git a/src/sampleGenerator/index.js b/src/sampleGenerator/index.js index e83a9335..c1168dab 100644 --- a/src/sampleGenerator/index.js +++ b/src/sampleGenerator/index.js @@ -10,7 +10,7 @@ const { mongo: mongoUrl, numberOfChats, numberOfRooms } = require("./loadenv"); const database = connectDatabase(mongoUrl); const fs = require("fs"); -const sampleData = JSON.parse(fs.readFileSync("./sampleData.json")); +const sampleData = require("./sampleData.json"); const main = async () => { await database.db.dropDatabase(); diff --git a/tsconfig.json b/tsconfig.json index 0f059817..0c842280 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -5,6 +5,7 @@ "moduleResolution": "node16", "allowJs": true, "outDir": "./dist", + "resolveJsonModule": true, "strict": true, "esModuleInterop": true, "skipLibCheck": true, From decef9b08fc4b307d94110595abf937816a7818a Mon Sep 17 00:00:00 2001 From: static Date: Mon, 13 May 2024 00:11:39 +0900 Subject: [PATCH 27/61] Fix: dumpDB and restoreDB scripts --- package.json | 2 +- src/sampleGenerator/loadenv.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index e28269bb..dfb4a606 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ "runscript": "cross-env TZ='Asia/Seoul' NODE_ENV=production node", "sample": "npx tsc && tsc-alias && cross-env NODE_ENV=test node dist/sampleGenerator/index.js", "dumpDB": "npx tsc && tsc-alias && cross-env NODE_ENV=test node dist/sampleGenerator/tools/dump.js", - "restoreDB": "node src/sampleGenerator/tools/restore.js" + "restoreDB": "npx tsc && tsc-alias && cross-env NODE_ENV=test node dist/sampleGenerator/tools/restore.js" }, "engines": { "node": ">=18.0.0", diff --git a/src/sampleGenerator/loadenv.js b/src/sampleGenerator/loadenv.js index 0843789b..d248ee91 100644 --- a/src/sampleGenerator/loadenv.js +++ b/src/sampleGenerator/loadenv.js @@ -1,5 +1,5 @@ // Root directory에 있는 .env.test 파일을 읽어옴 -require("dotenv").config({ path: "../../.env.test" }); +require("dotenv").config({ path: "./.env.test" }); module.exports = { mongo: process.env.DB_PATH, // required From 9f72dc77b4aba223fd3498e49e427daf6904a4c5 Mon Sep 17 00:00:00 2001 From: static Date: Mon, 13 May 2024 00:39:18 +0900 Subject: [PATCH 28/61] Add: ts-node dev dependency --- .gitignore | 1 + package.json | 7 ++-- pnpm-lock.yaml | 95 ++++++++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 97 insertions(+), 6 deletions(-) diff --git a/.gitignore b/.gitignore index bafcba69..12d89e61 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ /node_modules /dist +/dump .env .env.test .env.production diff --git a/package.json b/package.json index dfb4a606..5ceb6730 100644 --- a/package.json +++ b/package.json @@ -15,9 +15,9 @@ "serve": "cross-env TZ='Asia/Seoul' NODE_ENV=production node dist/index.js", "lint": "pnpm eslint .", "runscript": "cross-env TZ='Asia/Seoul' NODE_ENV=production node", - "sample": "npx tsc && tsc-alias && cross-env NODE_ENV=test node dist/sampleGenerator/index.js", - "dumpDB": "npx tsc && tsc-alias && cross-env NODE_ENV=test node dist/sampleGenerator/tools/dump.js", - "restoreDB": "npx tsc && tsc-alias && cross-env NODE_ENV=test node dist/sampleGenerator/tools/restore.js" + "sample": "cross-env NODE_ENV=test ts-node --require tsconfig-paths/register src/sampleGenerator/index.js", + "dumpDB": "cross-env NODE_ENV=test ts-node --require tsconfig-paths/register src/sampleGenerator/tools/dump.js", + "restoreDB": "cross-env NODE_ENV=test ts-node --require tsconfig-paths/register src/sampleGenerator/tools/restore.js" }, "engines": { "node": ">=18.0.0", @@ -82,6 +82,7 @@ "nodemon": "^3.0.1", "rimraf": "^5.0.5", "supertest": "^6.2.4", + "ts-node": "^10.9.2", "tsc-alias": "^1.8.8", "typescript": "^5.2.2" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c9202423..98dc3d4e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -175,6 +175,9 @@ devDependencies: supertest: specifier: ^6.2.4 version: 6.3.3 + ts-node: + specifier: ^10.9.2 + version: 10.9.2(@types/node@20.9.0)(typescript@5.2.2) tsc-alias: specifier: ^1.8.8 version: 1.8.8 @@ -2132,6 +2135,13 @@ packages: engines: {node: '>=0.1.90'} dev: false + /@cspotcode/source-map-support@0.8.1: + resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} + engines: {node: '>=12'} + dependencies: + '@jridgewell/trace-mapping': 0.3.9 + dev: true + /@dabh/diagnostics@2.0.3: resolution: {integrity: sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==} dependencies: @@ -2523,7 +2533,6 @@ packages: /@jridgewell/resolve-uri@3.1.0: resolution: {integrity: sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==} engines: {node: '>=6.0.0'} - dev: false /@jridgewell/set-array@1.1.2: resolution: {integrity: sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==} @@ -2543,7 +2552,6 @@ packages: /@jridgewell/sourcemap-codec@1.4.15: resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==} - dev: false /@jridgewell/trace-mapping@0.3.18: resolution: {integrity: sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==} @@ -2552,6 +2560,13 @@ packages: '@jridgewell/sourcemap-codec': 1.4.14 dev: false + /@jridgewell/trace-mapping@0.3.9: + resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} + dependencies: + '@jridgewell/resolve-uri': 3.1.0 + '@jridgewell/sourcemap-codec': 1.4.15 + dev: true + /@jsdoc/salty@0.2.5: resolution: {integrity: sha512-TfRP53RqunNe2HBobVBJ0VLhK1HbfvBYeTC1ahnN64PWvyYyGebmMiPkuwvD9fpw2ZbkoPb8Q7mwy0aR8Z9rvw==} engines: {node: '>=v12.0.0'} @@ -3666,6 +3681,22 @@ packages: dev: false optional: true + /@tsconfig/node10@1.0.11: + resolution: {integrity: sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==} + dev: true + + /@tsconfig/node12@1.0.11: + resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==} + dev: true + + /@tsconfig/node14@1.0.3: + resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==} + dev: true + + /@tsconfig/node16@1.0.4: + resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} + dev: true + /@types/babel-core@6.25.7: resolution: {integrity: sha512-WPnyzNFVRo6bxpr7bcL27qXtNKNQ3iToziNBpibaXHyKGWQA0+tTLt73QQxC/5zzbM544ih6Ni5L5xrck6rGwg==} dependencies: @@ -4093,6 +4124,11 @@ packages: dependencies: acorn: 8.10.0 + /acorn-walk@8.3.2: + resolution: {integrity: sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==} + engines: {node: '>=0.4.0'} + dev: true + /acorn@8.10.0: resolution: {integrity: sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==} engines: {node: '>=0.4.0'} @@ -4210,6 +4246,10 @@ packages: picomatch: 2.3.1 dev: true + /arg@4.1.3: + resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} + dev: true + /argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} @@ -4888,6 +4928,10 @@ packages: yaml: 1.10.2 dev: false + /create-require@1.1.1: + resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} + dev: true + /crelt@1.0.6: resolution: {integrity: sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==} dev: false @@ -5068,6 +5112,11 @@ packages: wrappy: 1.0.2 dev: true + /diff@4.0.2: + resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} + engines: {node: '>=0.3.1'} + dev: true + /diff@5.0.0: resolution: {integrity: sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==} engines: {node: '>=0.3.1'} @@ -7031,7 +7080,6 @@ packages: /make-error@1.3.6: resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} - dev: false /markdown-it-anchor@8.6.7(@types/markdown-it@12.2.3)(markdown-it@12.3.2): resolution: {integrity: sha512-FlCHFwNnutLgVTflOYHPW2pPcl2AACqVzExlkGQNsi4CJgqOHN7YTgDd4LuhgN1BFO3TS0vLAruV1Td6dwWPJA==} @@ -9024,6 +9072,37 @@ packages: typescript: 5.2.2 dev: true + /ts-node@10.9.2(@types/node@20.9.0)(typescript@5.2.2): + resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==} + hasBin: true + peerDependencies: + '@swc/core': '>=1.2.50' + '@swc/wasm': '>=1.2.50' + '@types/node': '*' + typescript: '>=2.7' + peerDependenciesMeta: + '@swc/core': + optional: true + '@swc/wasm': + optional: true + dependencies: + '@cspotcode/source-map-support': 0.8.1 + '@tsconfig/node10': 1.0.11 + '@tsconfig/node12': 1.0.11 + '@tsconfig/node14': 1.0.3 + '@tsconfig/node16': 1.0.4 + '@types/node': 20.9.0 + acorn: 8.10.0 + acorn-walk: 8.3.2 + arg: 4.1.3 + create-require: 1.1.1 + diff: 4.0.2 + make-error: 1.3.6 + typescript: 5.2.2 + v8-compile-cache-lib: 3.0.1 + yn: 3.1.1 + dev: true + /tsc-alias@1.8.8: resolution: {integrity: sha512-OYUOd2wl0H858NvABWr/BoSKNERw3N9GTi3rHPK8Iv4O1UyUXIrTTOAZNHsjlVpXFOhpJBVARI1s+rzwLivN3Q==} hasBin: true @@ -9220,6 +9299,7 @@ packages: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} dependencies: punycode: 2.3.0 + dev: true /url@0.10.3: resolution: {integrity: sha512-hzSUW2q06EqL1gKM/a+obYHLIO6ct2hwPuviqTTOcfFVc61UbfJ2Q32+uGL/HCPxKqrdGB5QUwIe7UqlDgwsOQ==} @@ -9290,6 +9370,10 @@ packages: hasBin: true dev: false + /v8-compile-cache-lib@3.0.1: + resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} + dev: true + /v8-compile-cache@2.3.0: resolution: {integrity: sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==} dev: true @@ -9584,6 +9668,11 @@ packages: dev: false optional: true + /yn@3.1.1: + resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} + engines: {node: '>=6'} + dev: true + /yocto-queue@0.1.0: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} From 8804b00997f6c2e08bdb5b440ca8b43bf36487a0 Mon Sep 17 00:00:00 2001 From: static Date: Mon, 13 May 2024 00:43:10 +0900 Subject: [PATCH 29/61] Fix: test script --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5ceb6730..92a1021c 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "scripts": { "preinstall": "npx only-allow pnpm", "start": "npx tsc && tsc-alias && npx nodemon", - "mocha": "cross-env TZ='Asia/Seoul' NODE_ENV=test mocha --recursive --reporter spec --exit", + "mocha": "cross-env TZ='Asia/Seoul' NODE_ENV=test mocha --require ts-node/register --require tsconfig-paths/register --recursive --reporter spec --exit", "test": "npm run sample && cross-env TZ='Asia/Seoul' npm run mocha", "build": "tsc && tsc-alias", "clean": "rimraf dist/", From 21ea16079f045899df1753f208ffcc937ec479fc Mon Sep 17 00:00:00 2001 From: static Date: Tue, 14 May 2024 23:56:05 +0900 Subject: [PATCH 30/61] Fix: test error in TypeScript environment --- src/services/users.js | 2 +- tsconfig.json | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/services/users.js b/src/services/users.js index 5fe9bafc..7197f01f 100644 --- a/src/services/users.js +++ b/src/services/users.js @@ -1,6 +1,6 @@ const { userModel } = require("@/modules/stores/mongo"); const logger = require("@/modules/logger").default; -const aws = require("@/modules/stores/aws").default; +const aws = require("@/modules/stores/aws"); const { generateNickname, generateProfileImageUrl, diff --git a/tsconfig.json b/tsconfig.json index 0c842280..09d4273e 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -16,6 +16,9 @@ } }, "include": ["src"], - "exclude": ["dist", "node_modules"] + "exclude": ["dist", "node_modules"], + "ts-node": { + "files": true + } } \ No newline at end of file From 87eaf2ec95584708a96b6aacb67568e4976a94c6 Mon Sep 17 00:00:00 2001 From: neymar <0208mjkim@gmail.com> Date: Tue, 5 Nov 2024 23:43:46 +0900 Subject: [PATCH 31/61] Refactor: migrate routes/users and services/users to ts --- src/routes/{users.js => users.ts} | 12 ++++---- src/services/{users.js => users.ts} | 43 +++++++++++++---------------- 2 files changed, 25 insertions(+), 30 deletions(-) rename src/routes/{users.js => users.ts} (82%) rename src/services/{users.js => users.ts} (82%) diff --git a/src/routes/users.js b/src/routes/users.ts similarity index 82% rename from src/routes/users.js rename to src/routes/users.ts index aa00bcb2..b3020054 100755 --- a/src/routes/users.js +++ b/src/routes/users.ts @@ -1,12 +1,12 @@ -const express = require("express"); -const { body } = require("express-validator"); -const validator = require("@/middlewares/validator").default; -const patterns = require("@/modules/patterns").default; +import express from "express"; +import { body } from "express-validator"; +import validator from "@/middlewares/validator"; +import patterns from "@/modules/patterns"; const router = express.Router(); -const userHandlers = require("@/services/users"); +import * as userHandlers from "@/services/users"; -const { replaceSpaceInNickname } = require("@/modules/modifyProfile"); +import { replaceSpaceInNickname } from "@/modules/modifyProfile"; // 라우터 접근 시 로그인 필요 router.use(require("@/middlewares/auth").default); diff --git a/src/services/users.js b/src/services/users.ts similarity index 82% rename from src/services/users.js rename to src/services/users.ts index 7197f01f..de83c073 100644 --- a/src/services/users.js +++ b/src/services/users.ts @@ -1,6 +1,9 @@ -const { userModel } = require("@/modules/stores/mongo"); -const logger = require("@/modules/logger").default; -const aws = require("@/modules/stores/aws"); +import type { Request, Response } from "express"; + +import { userModel } from "@/modules/stores/mongo"; +import logger from "@/modules/logger"; +import * as aws from "@/modules/stores/aws"; + const { generateNickname, generateProfileImageUrl, @@ -9,10 +12,10 @@ const { // 이벤트 코드입니다. // const { contracts } = require("@/lottery"); -const agreeOnTermsOfServiceHandler = async (req, res) => { +export const agreeOnTermsOfServiceHandler = async (req: Request, res: Response) => { try { let user = await userModel.findOne({ id: req.userId }); - if (user.agreeOnTermsOfService !== true) { + if (user && user.agreeOnTermsOfService !== true) { user.agreeOnTermsOfService = true; await user.save(); res @@ -28,13 +31,15 @@ const agreeOnTermsOfServiceHandler = async (req, res) => { } }; -const getAgreeOnTermsOfServiceHandler = async (req, res) => { +export const getAgreeOnTermsOfServiceHandler = async (req: Request, res: Response) => { try { const user = await userModel .findOne({ id: req.userId }, "agreeOnTermsOfService") .lean(); - const agreeOnTermsOfService = user.agreeOnTermsOfService === true; - res.json({ agreeOnTermsOfService }); + if (user) { + const agreeOnTermsOfService = user.agreeOnTermsOfService === true; + res.json({ agreeOnTermsOfService }); + } } catch { res .status(500) @@ -42,7 +47,7 @@ const getAgreeOnTermsOfServiceHandler = async (req, res) => { } }; -const editNicknameHandler = async (req, res) => { +export const editNicknameHandler = async (req: Request, res: Response) => { try { const newNickname = req.body.nickname; const result = await userModel.findOneAndUpdate( @@ -69,7 +74,7 @@ const editNicknameHandler = async (req, res) => { } }; -const editAccountHandler = async (req, res) => { +export const editAccountHandler = async (req: Request, res: Response) => { try { const newAccount = req.body.account; const result = await userModel.findOneAndUpdate( @@ -95,7 +100,7 @@ const editAccountHandler = async (req, res) => { } }; -const editProfileImgGetPUrlHandler = async (req, res) => { +export const editProfileImgGetPUrlHandler = async (req: Request, res: Response) => { try { const type = req.body.type; const user = await userModel.findOne({ id: req.userId }, "_id"); @@ -125,7 +130,7 @@ const editProfileImgGetPUrlHandler = async (req, res) => { } }; -const editProfileImgDoneHandler = async (req, res) => { +export const editProfileImgDoneHandler = async (req: Request, res: Response) => { try { const user = await userModel.findOne({ id: req.userId }, "_id"); if (!user) { @@ -161,7 +166,7 @@ const editProfileImgDoneHandler = async (req, res) => { } }; -const resetNicknameHandler = async (req, res) => { +export const resetNicknameHandler = async (req: Request, res: Response) => { try { const result = await userModel.findOneAndUpdate( { id: req.userId }, @@ -181,7 +186,7 @@ const resetNicknameHandler = async (req, res) => { } }; -const resetProfileImgHandler = async (req, res) => { +export const resetProfileImgHandler = async (req: Request, res: Response) => { try { const result = await userModel.findOneAndUpdate( { id: req.userId }, @@ -200,13 +205,3 @@ const resetProfileImgHandler = async (req, res) => { } }; -module.exports = { - agreeOnTermsOfServiceHandler, - getAgreeOnTermsOfServiceHandler, - editNicknameHandler, - editAccountHandler, - editProfileImgGetPUrlHandler, - editProfileImgDoneHandler, - resetNicknameHandler, - resetProfileImgHandler, -}; From a6db7d918800eaa814956eda4b96a96feb60b013 Mon Sep 17 00:00:00 2001 From: neymar <0208mjkim@gmail.com> Date: Tue, 5 Nov 2024 23:48:20 +0900 Subject: [PATCH 32/61] Fix: Updated one import in users --- src/services/users.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/services/users.ts b/src/services/users.ts index de83c073..3554690f 100644 --- a/src/services/users.ts +++ b/src/services/users.ts @@ -4,11 +4,8 @@ import { userModel } from "@/modules/stores/mongo"; import logger from "@/modules/logger"; import * as aws from "@/modules/stores/aws"; -const { - generateNickname, - generateProfileImageUrl, -} = require("@/modules/modifyProfile"); +import { generateNickname, generateProfileImageUrl } from "@/modules/modifyProfile"; // 이벤트 코드입니다. // const { contracts } = require("@/lottery"); From 226435bd360ccb239f52805511e47c973a8f7a8f Mon Sep 17 00:00:00 2001 From: static Date: Tue, 12 Nov 2024 22:57:53 +0900 Subject: [PATCH 33/61] Fix: merge conflicts --- src/index.ts | 6 +++++ src/loadenv.ts | 4 ++++ .../routes/docs/schemas/globalStateSchema.js | 2 +- .../routes/docs/schemas/invitesSchema.js | 2 +- .../routes/docs/schemas/itemsSchema.js | 2 +- src/modules/fare.js | 4 ++-- src/modules/fcm.ts | 2 +- src/modules/stores/mongo.ts | 12 +++++++--- src/routes/chats.js | 1 - src/routes/docs/chats.js | 2 +- src/routes/docs/logininfo.js | 2 +- src/routes/docs/reports.js | 2 +- src/routes/docs/rooms.js | 2 +- src/routes/docs/schemas/chatsSchema.js | 2 +- src/routes/docs/schemas/fareSchema.js | 2 +- src/routes/docs/users.js | 2 +- src/routes/index.ts | 1 + src/routes/users.ts | 2 +- src/schedules/index.ts | 7 ++++++ src/services/auth.js | 2 +- src/services/fare.js | 8 +++---- src/services/users.ts | 2 +- src/types/mongo.d.ts | 24 +++++++++++++++++++ 23 files changed, 71 insertions(+), 24 deletions(-) diff --git a/src/index.ts b/src/index.ts index a3470242..f741d146 100644 --- a/src/index.ts +++ b/src/index.ts @@ -24,8 +24,10 @@ import { notificationRouter, adminRouter, docsRouter, + fareRouter, } from "@/routes"; import { initializeApp } from "@/modules/fcm"; +import { initializeDatabase as initializeFareDatabase } from "@/modules/fare"; import logger from "@/modules/logger"; import { connectDatabase } from "@/modules/stores/mongo"; import { startSocketServer } from "@/modules/socket"; @@ -85,6 +87,7 @@ app.use("/chats", chatRouter); app.use("/locations", locationRouter); app.use("/reports", reportRouter); app.use("/notifications", notificationRouter); +app.use("/fare", fareRouter); // [Middleware] 전역 에러 핸들러. 에러 핸들러는 router들보다 아래에 등록되어야 합니다. app.use(errorHandler); @@ -101,3 +104,6 @@ app.set("io", startSocketServer(serverHttp)); // [Schedule] 스케줄러 시작 registerSchedules(app); + +// [Module] 택시 예상 비용 db 초기화 +initializeFareDatabase(); diff --git a/src/loadenv.ts b/src/loadenv.ts index e251eab4..c70bb4fa 100644 --- a/src/loadenv.ts +++ b/src/loadenv.ts @@ -63,3 +63,7 @@ export const slackWebhookUrl = { }; // export const eventConfig = // process.env.EVENT_CONFIG && JSON.parse(process.env.EVENT_CONFIG); // optional +export const naverMap = { + apiId: process.env.NAVER_MAP_API_ID || "", // optional + apiKey: process.env.NAVER_MAP_API_KEY || "", // optional +}; diff --git a/src/lottery/routes/docs/schemas/globalStateSchema.js b/src/lottery/routes/docs/schemas/globalStateSchema.js index 15055525..26fe4ed9 100644 --- a/src/lottery/routes/docs/schemas/globalStateSchema.js +++ b/src/lottery/routes/docs/schemas/globalStateSchema.js @@ -1,6 +1,6 @@ const { z } = require("zod"); const { zodToSchemaObject } = require("../../../../routes/docs/utils"); -const { objectId, user } = require("../../../../modules/patterns"); +const { objectId, user } = require("../../../../modules/patterns").default; const globalStateZod = { createUserGlobalStateHandler: z diff --git a/src/lottery/routes/docs/schemas/invitesSchema.js b/src/lottery/routes/docs/schemas/invitesSchema.js index dfc33c5c..d43498cc 100644 --- a/src/lottery/routes/docs/schemas/invitesSchema.js +++ b/src/lottery/routes/docs/schemas/invitesSchema.js @@ -1,6 +1,6 @@ const { z } = require("zod"); const { zodToSchemaObject } = require("../../../../routes/docs/utils"); -const { objectId } = require("../../../../modules/patterns"); +const { objectId } = require("../../../../modules/patterns").default; const invitesZod = { searchInviterHandler: z.object({ diff --git a/src/lottery/routes/docs/schemas/itemsSchema.js b/src/lottery/routes/docs/schemas/itemsSchema.js index d224ba70..68061d35 100644 --- a/src/lottery/routes/docs/schemas/itemsSchema.js +++ b/src/lottery/routes/docs/schemas/itemsSchema.js @@ -1,6 +1,6 @@ const { z } = require("zod"); const { zodToSchemaObject } = require("../../../../routes/docs/utils"); -const { objectId } = require("../../../../modules/patterns"); +const { objectId } = require("../../../../modules/patterns").default; const itemsZod = { getItemHandler: z.object({ diff --git a/src/modules/fare.js b/src/modules/fare.js index 350b8207..be3c3c4d 100644 --- a/src/modules/fare.js +++ b/src/modules/fare.js @@ -1,7 +1,7 @@ const axios = require("axios"); -const logger = require("./logger"); +const logger = require("./logger").default; -const { naverMap } = require("../../loadenv"); +const { naverMap } = require("@/loadenv"); const { taxiFareModel, locationModel } = require("./stores/mongo"); const naverMapApi = { diff --git a/src/modules/fcm.ts b/src/modules/fcm.ts index aac7ab9f..ae5abf5b 100644 --- a/src/modules/fcm.ts +++ b/src/modules/fcm.ts @@ -216,7 +216,7 @@ export const sendMessageByTokens = async ( }, apns: { payload: { aps: { alert: { title, body } } } }, android: { - priority: "high", + priority: "high" as const, }, }; const { responses, failureCount } = diff --git a/src/modules/stores/mongo.ts b/src/modules/stores/mongo.ts index 9549dc5d..b4affe32 100755 --- a/src/modules/stores/mongo.ts +++ b/src/modules/stores/mongo.ts @@ -2,6 +2,7 @@ import mongoose, { model, Schema, Types } from "mongoose"; import logger from "@/modules/logger"; import type { User, + Ban, Participant, DeviceToken, NotificationOption, @@ -12,6 +13,7 @@ import type { Report, AdminIPWhitelist, AdminLog, + TaxiFare, } from "@/types/mongo"; const userSchema = new Schema({ @@ -39,7 +41,7 @@ const userSchema = new Schema({ export const userModel = model("User", userSchema); -const banSchema = Schema({ +const banSchema = new Schema({ // 정지 시킬 사용자를 기제함. userSid: { type: String, required: true }, // 정지 사유 @@ -58,6 +60,8 @@ const banSchema = Schema({ }, }); +export const banModel = model("Ban", banSchema); + const participantSchema = new Schema({ user: { type: Schema.Types.ObjectId, ref: "User", required: true }, settlementStatus: { @@ -231,19 +235,21 @@ const adminLogSchema = new Schema({ export const adminLogModel = model("AdminLog", adminLogSchema); -const taxiFareSchema = Schema( +const taxiFareSchema = new Schema( { from: { type: Schema.Types.ObjectId, ref: "Location", required: true }, // 출발지 to: { type: Schema.Types.ObjectId, ref: "Location", required: true }, // 도착지 isMajor: { type: Boolean, default: false }, // 카이스트 본원 <-> 대전역 경로 여부 time: { type: Number, required: true }, // 출발 시간 (24h를 30분 단위로 분리 & 요일 정보도 하나로 관리, 0 ~ 6 (Sunday~Saturday) * 48 + 0 ~ 47 (0:00 ~ 23:30)) - fare: { type: Number, default: false }, // 예상 택시 요금 + fare: { type: Number, default: 0 }, // 예상 택시 요금 }, { timestamps: true, // 최근 업데이트 시간 기록용 } ); +export const taxiFareModel = model("TaxiFare", taxiFareSchema); + mongoose.set("strictQuery", true); const database = mongoose.connection; diff --git a/src/routes/chats.js b/src/routes/chats.js index d6f7ddce..32b01329 100644 --- a/src/routes/chats.js +++ b/src/routes/chats.js @@ -4,7 +4,6 @@ const validator = require("@/middlewares/validator").default; const patterns = require("@/modules/patterns").default; const { validateBody } = require("@/middlewares/zod"); const { chatsZod } = require("./docs/schemas/chatsSchema"); -const patterns = require("@/modules/patterns"); const router = express.Router(); const chatsHandlers = require("@/services/chats"); diff --git a/src/routes/docs/chats.js b/src/routes/docs/chats.js index 3d934181..3f4b63a3 100644 --- a/src/routes/docs/chats.js +++ b/src/routes/docs/chats.js @@ -1,4 +1,4 @@ -const { objectId } = require("../../modules/patterns").default; +const { objectId } = require("@/modules/patterns").default; const tag = "chats"; const apiPrefix = "/chats"; diff --git a/src/routes/docs/logininfo.js b/src/routes/docs/logininfo.js index caec1f96..3f9c0b0b 100644 --- a/src/routes/docs/logininfo.js +++ b/src/routes/docs/logininfo.js @@ -1,4 +1,4 @@ -const { objectId } = require("../../modules/patterns").default; +const { objectId } = require("@/modules/patterns").default; const tag = "logininfo"; const apiPrefix = "/logininfo"; diff --git a/src/routes/docs/reports.js b/src/routes/docs/reports.js index 3acf99da..bfc29687 100644 --- a/src/routes/docs/reports.js +++ b/src/routes/docs/reports.js @@ -1,4 +1,4 @@ -const { objectId } = require("../../modules/patterns").default; +const { objectId } = require("@/modules/patterns").default; const tag = "reports"; const apiPrefix = "/reports"; diff --git a/src/routes/docs/rooms.js b/src/routes/docs/rooms.js index e8f3aa99..41d6a1cb 100644 --- a/src/routes/docs/rooms.js +++ b/src/routes/docs/rooms.js @@ -1,4 +1,4 @@ -const { objectId, room } = require("../../modules/patterns").default; +const { objectId, room } = require("@/modules/patterns").default; const tag = "rooms"; const apiPrefix = "/rooms"; diff --git a/src/routes/docs/schemas/chatsSchema.js b/src/routes/docs/schemas/chatsSchema.js index fa0ae257..5f1d534c 100644 --- a/src/routes/docs/schemas/chatsSchema.js +++ b/src/routes/docs/schemas/chatsSchema.js @@ -1,6 +1,6 @@ const { z } = require("zod"); const { zodToSchemaObject } = require("../utils"); -const { objectId, chat } = require("../../../modules/patterns"); +const { objectId, chat } = require("@/modules/patterns").default; const chatsZod = { sendChatHandler: z.object({ diff --git a/src/routes/docs/schemas/fareSchema.js b/src/routes/docs/schemas/fareSchema.js index 0812a86a..5caa87e7 100644 --- a/src/routes/docs/schemas/fareSchema.js +++ b/src/routes/docs/schemas/fareSchema.js @@ -1,6 +1,6 @@ const { z } = require("zod"); const { zodToSchemaObject } = require("../utils"); -const { objectId } = require("../../../modules/patterns"); +const { objectId } = require("../../../modules/patterns").default; const fareZod = { getTaxiFareHandler: z.object({ diff --git a/src/routes/docs/users.js b/src/routes/docs/users.js index 0f19ecde..f21c037a 100644 --- a/src/routes/docs/users.js +++ b/src/routes/docs/users.js @@ -1,6 +1,6 @@ const tag = "users"; const apiPrefix = "/users"; -const { objectId } = require("../../modules/patterns"); +const { objectId } = require("../../modules/patterns").default; const usersDocs = {}; usersDocs[`${apiPrefix}/agreeOnTermsOfService`] = { diff --git a/src/routes/index.ts b/src/routes/index.ts index d2084458..ddbe083a 100644 --- a/src/routes/index.ts +++ b/src/routes/index.ts @@ -8,3 +8,4 @@ export { default as notificationRouter } from "./notifications"; export { default as reportRouter } from "./reports"; export { default as roomRouter } from "./rooms"; export { default as userRouter } from "./users"; +export { default as fareRouter } from "./fare"; diff --git a/src/routes/users.ts b/src/routes/users.ts index 9125adee..1f6cb288 100755 --- a/src/routes/users.ts +++ b/src/routes/users.ts @@ -59,4 +59,4 @@ router.get("/resetProfileImg", userHandlers.resetProfileImgHandler); // 유저의 서비스 정지 기록들을 모두 반환합니다. router.get("/getBanRecord", userHandlers.getBanRecordHandler); -module.exports = router; +export default router; diff --git a/src/schedules/index.ts b/src/schedules/index.ts index f9e62477..1908fadd 100644 --- a/src/schedules/index.ts +++ b/src/schedules/index.ts @@ -2,10 +2,17 @@ import { type Express } from "express"; import cron from "node-cron"; import notifyBeforeDepart from "./notifyBeforeDepart"; import notifyAfterArrival from "./notifyAfterArrival"; +import updateMajorTaxiFare from "./updateMajorTaxiFare"; +import updateMinorTaxiFare from "./updateMinorTaxiFare"; +import { naverMap } from "@/loadenv"; const registerSchedules = (app: Express) => { cron.schedule("*/5 * * * *", notifyBeforeDepart(app)); cron.schedule("*/10 * * * *", notifyAfterArrival(app)); + if (naverMap.apiId && naverMap.apiKey) { + cron.schedule("0,30 * * * * ", updateMajorTaxiFare(app)); + cron.schedule("0 18 * * *", updateMinorTaxiFare(app)); + } }; export default registerSchedules; diff --git a/src/services/auth.js b/src/services/auth.js index 6aa0932e..cd660ab5 100644 --- a/src/services/auth.js +++ b/src/services/auth.js @@ -1,6 +1,6 @@ const { sparcssso: sparcsssoEnv, nodeEnv, testAccounts } = require("@/loadenv"); const { userModel } = require("@/modules/stores/mongo"); -const { user: userPattern } = require("@/modules/patterns"); +const { user: userPattern } = require("@/modules/patterns").default; const { getLoginInfo, logout, login } = require("@/modules/auths/login"); const { unregisterDeviceToken } = require("@/modules/fcm"); diff --git a/src/services/fare.js b/src/services/fare.js index 638faa65..c8b41831 100644 --- a/src/services/fare.js +++ b/src/services/fare.js @@ -1,8 +1,8 @@ -const logger = require("../modules/logger"); +const logger = require("@/modules/logger").default; -const { naverMap } = require("../../loadenv"); -const { taxiFareModel, locationModel } = require("../modules/stores/mongo"); -const { scaledTime, callTaxiFare } = require("../modules/fare"); +const { naverMap } = require("@/loadenv"); +const { taxiFareModel, locationModel } = require("@/modules/stores/mongo"); +const { scaledTime, callTaxiFare } = require("@/modules/fare"); const naverMapApi = { "X-NCP-APIGW-API-KEY-ID": naverMap.apiId, diff --git a/src/services/users.ts b/src/services/users.ts index 3d3cfa11..2fc8f0ba 100644 --- a/src/services/users.ts +++ b/src/services/users.ts @@ -220,7 +220,7 @@ export const getBanRecordHandler = async (req: Request, res: Response) => { // 본인인 경우(ban의 userId가 userSid랑 같은 경우)의 record를 모두 가져옴 const result = await banModel .find({ - userSid: req.session.loginInfo.sid, + userSid: req.session.loginInfo?.sid, }) .sort({ expireAt: -1 }); if (!result) diff --git a/src/types/mongo.d.ts b/src/types/mongo.d.ts index 1f0bf64f..93fcb382 100644 --- a/src/types/mongo.d.ts +++ b/src/types/mongo.d.ts @@ -37,6 +37,19 @@ export interface User extends Document { account: string; } +export interface Ban extends Document { + /** 정지된 사용자의 ID. */ + userSid: string; + /** 정지 사유. */ + reason: string; + /** 정지 시각. */ + bannedAt: Date; + /** 정지 만료 시각. */ + expireAt: Date; + /** 정지된 서비스의 이름. */ + serviceName: "service" | "2023-fall-event"; +} + export type SettlementStatus = | "not-departed" | "paid" @@ -166,3 +179,14 @@ export interface AdminLog extends Document { /** 수행한 업무. */ action: AdminLogAction; } + +export interface TaxiFare extends Document { + /** 출발지의 Location ObjectID. */ + from: Types.ObjectId; + /** 목적지의 Location ObjectID. */ + to: Types.ObjectId; + isMajor: boolean; + time: number; + /** 예상 택시 요금. */ + fare: number; +} From 1c8dac4ec8b11c7882291a26556dfc9997efd015 Mon Sep 17 00:00:00 2001 From: static Date: Tue, 12 Nov 2024 23:06:05 +0900 Subject: [PATCH 34/61] Refactor: enable CI --- .github/workflows/test_ci.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/test_ci.yml b/.github/workflows/test_ci.yml index 1f19bb94..2ccc5415 100644 --- a/.github/workflows/test_ci.yml +++ b/.github/workflows/test_ci.yml @@ -15,8 +15,6 @@ jobs: node-version: ['18.x'] mongodb-version: ['5.0'] steps: - - name: Exit because unit tests are not implemented in TypeScript for now - run: exit 1 - name: Start MongoDB run: sudo docker run --name mongodb -d -p 27017:27017 mongo:${{ matrix.mongodb-version }} - uses: actions/checkout@v3 From 7e17d5fa8bd9a1148d809c8feb24f2b2c28e0805 Mon Sep 17 00:00:00 2001 From: cokia Date: Tue, 19 Nov 2024 22:34:24 +0900 Subject: [PATCH 35/61] =?UTF-8?q?Fix:=20loadenv=20=EB=A5=BC=20As-Is?= =?UTF-8?q?=EC=97=90=EC=84=9C=20=EC=82=AC=EC=9A=A9=ED=95=98=EB=8D=98=20Nes?= =?UTF-8?q?ted=20object=EB=A1=9C=20=EB=B3=80=EA=B2=BD=ED=95=98=EC=98=80?= =?UTF-8?q?=EC=8A=B5=EB=8B=88=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/profileImageUrlUpdater.js | 2 +- src/index.ts | 10 +-- src/loadenv.ts | 98 ++++++++++++++++-------------- src/middlewares/cors.ts | 4 +- src/middlewares/session.ts | 8 +-- src/modules/auths/jwt.ts | 4 +- src/modules/auths/login.ts | 4 +- src/modules/fcm.ts | 8 ++- src/modules/logger.ts | 4 +- src/modules/slackNotification.ts | 11 ++-- src/modules/stores/aws.ts | 14 ++--- src/modules/stores/sessionStore.ts | 14 ++--- src/sampleGenerator/index.js | 2 +- src/schedules/index.ts | 4 +- 14 files changed, 95 insertions(+), 92 deletions(-) diff --git a/scripts/profileImageUrlUpdater.js b/scripts/profileImageUrlUpdater.js index 2b35bf1a..fdfa671e 100644 --- a/scripts/profileImageUrlUpdater.js +++ b/scripts/profileImageUrlUpdater.js @@ -2,7 +2,7 @@ // https://github.com/sparcs-kaist/taxi-back/issues/173 const { MongoClient } = require("mongodb"); -const { mongo: mongoUrl, aws: awsEnv } = require("../loadenv"); // FIXME: 올바른 경로로 수정해야 합니다. +const { mongoUrl, aws: awsEnv } = require("../loadenv"); // FIXME: 올바른 경로로 수정해야 합니다. const time = Date.now(); diff --git a/src/index.ts b/src/index.ts index f741d146..4aebcc46 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,7 +3,7 @@ import express from "express"; import cookieParser from "cookie-parser"; import http from "http"; -import { nodeEnv, mongo as mongoUrl, port as httpPort } from "@/loadenv"; +import config from "@/loadenv"; import { corsMiddleware, sessionMiddleware, @@ -40,14 +40,14 @@ initializeApp(); const app = express(); // 데이터베이스 연결 -connectDatabase(mongoUrl); +connectDatabase(config.mongoUrl); // [Middleware] request body 파싱 app.use(express.urlencoded({ extended: false })); app.use(express.json()); // reverse proxy가 설정한 헤더를 신뢰합니다. -if (nodeEnv === "production") app.set("trust proxy", 2); +if (config.nodeEnv === "production") app.set("trust proxy", 2); // [Middleware] CORS 설정 app.use(corsMiddleware); @@ -95,8 +95,8 @@ app.use(errorHandler); // express 서버 시작 const serverHttp = http .createServer(app) - .listen(httpPort, () => - logger.info(`Express server started from port ${httpPort}`) + .listen(config.port, () => + logger.info(`Express server started from port ${config.port}`) ); // socket.io 서버 시작 diff --git a/src/loadenv.ts b/src/loadenv.ts index c70bb4fa..6d6c508b 100644 --- a/src/loadenv.ts +++ b/src/loadenv.ts @@ -2,6 +2,7 @@ import dotenv from "dotenv"; import { type Algorithm } from "jsonwebtoken"; import { type ServiceAccount } from "firebase-admin"; +import exp from "constants"; if (process.env.NODE_ENV === undefined) { // logger.ts가 아직 초기화되지 않았으므로 console.error를 사용합니다. @@ -18,52 +19,55 @@ if (process.env.DB_PATH === undefined) { process.exit(1); } -export const nodeEnv = process.env.NODE_ENV; // required ("production" or "development" or "test") -export const mongo = process.env.DB_PATH; // required -export const session = { - secret: process.env.SESSION_KEY || "TAXI_SESSION_KEY", // optional - expiry: 14 * 24 * 3600 * 1000, // 14일, ms 단위입니다. -}; -export const redis = process.env.REDIS_PATH; // optional -export const sparcssso = { - id: process.env.SPARCSSSO_CLIENT_ID || "", // optional - key: process.env.SPARCSSSO_CLIENT_KEY || "", // optional -}; -export const port = process.env.PORT ? parseInt(process.env.PORT) : 80; // optional (default = 80) -export const corsWhiteList = (process.env.CORS_WHITELIST && - (JSON.parse(process.env.CORS_WHITELIST) as string[])) || [true]; // optional (default = [true]) -export const aws = { - accessKeyId: process.env.AWS_ACCESS_KEY_ID as string, // required - secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY as string, // required - s3BucketName: process.env.AWS_S3_BUCKET_NAME as string, // required - s3Url: - process.env.AWS_S3_URL || - `https://${process.env.AWS_S3_BUCKET_NAME}.s3.ap-northeast-2.amazonaws.com`, // optional -}; -export const jwt = { - secretKey: process.env.JWT_SECRET_KEY || "TAXI_JWT_KEY", - option: { - algorithm: "HS256" as Algorithm, - // FIXME: remove FRONT_URL from issuer. 단, issuer를 변경하면 이전에 발급했던 모든 JWT가 무효화됩니다. - // See https://github.com/sparcs-kaist/taxi-back/issues/415 - issuer: process.env.FRONT_URL || "http://localhost:3000", // optional (default = "http://localhost:3000") +const config = { + nodeEnv: process.env.NODE_ENV, + mongoUrl: process.env.DB_PATH, + session: { + secret: process.env.SESSION_KEY || "TAXI_SESSION_KEY", + expiry: 14 * 24 * 3600 * 1000, + }, + redisUrl: process.env.REDIS_PATH, + sparcssso: { + id: process.env.SPARCSSSO_CLIENT_ID || "", + key: process.env.SPARCSSSO_CLIENT_KEY || "", + }, + port: process.env.PORT ? parseInt(process.env.PORT) : 80, + corsWhiteList: (process.env.CORS_WHITELIST && + (JSON.parse(process.env.CORS_WHITELIST) as string[])) || [true], + aws: { + accessKeyId: process.env.AWS_ACCESS_KEY_ID as string, + secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY as string, + s3BucketName: process.env.AWS_S3_BUCKET_NAME as string, + s3Url: + process.env.AWS_S3_URL || + `https://${process.env.AWS_S3_BUCKET_NAME}.s3.ap-northeast-2.amazonaws.com`, + }, + jwt: { + secretKey: process.env.JWT_SECRET || "TAXI_JWT_KEY", + option: { + algorithm: "HS256" as Algorithm, + // FIXME: remove FRONT_URL from issuer. 단, issuer를 변경하면 이전에 발급했던 모든 JWT가 무효화됩니다. + // See https://github.com/sparcs-kaist/taxi-back/issues/415 + issuer: process.env.FRONT_URL || "http://localhost:3000", + }, + TOKEN_EXPIRED: -3, + TOKEN_INVALID: -2, + }, + + googleApplicationCredentials: + process.env.GOOGLE_APPLICATION_CREDENTIALS && + (JSON.parse(process.env.GOOGLE_APPLICATION_CREDENTIALS) as ServiceAccount), + testAccounts: + (process.env.TEST_ACCOUNTS && + (JSON.parse(process.env.TEST_ACCOUNTS) as string[])) || + [], + slackWebhookUrl: { + report: process.env.SLACK_REPORT_WEBHOOK_URL || "", + }, + naverMap: { + apiId: process.env.NAVER_MAP_API_ID || "", + apiKey: process.env.NAVER_MAP_API_KEY || "", }, - TOKEN_EXPIRED: -3, - TOKEN_INVALID: -2, -}; -export const googleApplicationCredentials = - process.env.GOOGLE_APPLICATION_CREDENTIALS && - (JSON.parse(process.env.GOOGLE_APPLICATION_CREDENTIALS) as ServiceAccount); // optional -export const testAccounts = - (process.env.TEST_ACCOUNTS && - (JSON.parse(process.env.TEST_ACCOUNTS) as string[])) || - []; // optional -export const slackWebhookUrl = { - report: process.env.SLACK_REPORT_WEBHOOK_URL || "", // optional -}; -// export const eventConfig = -// process.env.EVENT_CONFIG && JSON.parse(process.env.EVENT_CONFIG); // optional -export const naverMap = { - apiId: process.env.NAVER_MAP_API_ID || "", // optional - apiKey: process.env.NAVER_MAP_API_KEY || "", // optional }; + +export default config; \ No newline at end of file diff --git a/src/middlewares/cors.ts b/src/middlewares/cors.ts index 63559565..f8678f7f 100644 --- a/src/middlewares/cors.ts +++ b/src/middlewares/cors.ts @@ -1,8 +1,8 @@ import cors from "cors"; -import { corsWhiteList } from "@/loadenv"; +import config from "@/loadenv"; const corsMiddleware = cors({ - origin: corsWhiteList, + origin: config.corsWhiteList, credentials: true, exposedHeaders: ["Date"], }); diff --git a/src/middlewares/session.ts b/src/middlewares/session.ts index 8cf0dcf8..9b267b4a 100644 --- a/src/middlewares/session.ts +++ b/src/middlewares/session.ts @@ -1,5 +1,5 @@ import expressSession from "express-session"; -import { nodeEnv, session as sessionConfig } from "@/loadenv"; +import config from "@/loadenv"; import { type LoginInfo } from "@/modules/auths/login"; import sessionStore from "@/modules/stores/sessionStore"; @@ -26,14 +26,14 @@ declare module "express-session" { } const sessionMiddleware = expressSession({ - secret: sessionConfig.secret, + secret: config.session.secret, resave: false, saveUninitialized: false, store: sessionStore, cookie: { - maxAge: sessionConfig.expiry, + maxAge: config.session.expiry, // nodeEnv가 production일 때만 secure cookie를 사용합니다. - secure: nodeEnv === "production", + secure: config.nodeEnv === "production", }, }); diff --git a/src/modules/auths/jwt.ts b/src/modules/auths/jwt.ts index ee009330..a32cd47b 100644 --- a/src/modules/auths/jwt.ts +++ b/src/modules/auths/jwt.ts @@ -1,7 +1,7 @@ import jwt, { type SignOptions } from "jsonwebtoken"; -import { jwt as jwtConfig } from "@/loadenv"; +import config from "@/loadenv"; -const { secretKey, option, TOKEN_EXPIRED, TOKEN_INVALID } = jwtConfig; +const { secretKey, option, TOKEN_EXPIRED, TOKEN_INVALID } = config.jwt; type TokenType = "access" | "refresh"; diff --git a/src/modules/auths/login.ts b/src/modules/auths/login.ts index a55677b6..f026215b 100644 --- a/src/modules/auths/login.ts +++ b/src/modules/auths/login.ts @@ -1,5 +1,5 @@ import { type Request } from "express"; -import { session as sessionConfig } from "@/loadenv"; +import config from "@/loadenv"; import logger from "@/modules/logger"; export interface LoginInfo { @@ -16,7 +16,7 @@ export const getLoginInfo = (req: Request) => { const timeFlow = Date.now() - time; // 14일이 지난 세션에 대해서는 로그인 정보를 반환하지 않습니다. // 세션은 새로운 요청 시 갱신되지 않습니다. - if (timeFlow > sessionConfig.expiry) { + if (timeFlow > config.session.expiry) { return { id: undefined, sid: undefined, oid: undefined, name: undefined }; } return { id, sid, oid, name }; diff --git a/src/modules/fcm.ts b/src/modules/fcm.ts index ae5abf5b..44b9979a 100644 --- a/src/modules/fcm.ts +++ b/src/modules/fcm.ts @@ -1,6 +1,6 @@ import firebaseAdmin from "firebase-admin"; import { type SendResponse, getMessaging } from "firebase-admin/messaging"; -import { googleApplicationCredentials } from "@/loadenv"; +import config from "@/loadenv"; import logger from "@/modules/logger"; import { deviceTokenModel, @@ -13,9 +13,11 @@ import { type ChatType } from "@/types/mongo"; * credential을 등록합니다. */ export const initializeApp = () => { - if (googleApplicationCredentials) { + if (config.googleApplicationCredentials) { firebaseAdmin.initializeApp({ - credential: firebaseAdmin.credential.cert(googleApplicationCredentials), + credential: firebaseAdmin.credential.cert( + config.googleApplicationCredentials + ), }); } else { logger.error( diff --git a/src/modules/logger.ts b/src/modules/logger.ts index b4b91610..5a2c0424 100644 --- a/src/modules/logger.ts +++ b/src/modules/logger.ts @@ -2,7 +2,7 @@ import path from "path"; import { createLogger, format, transports } from "winston"; import DailyRotateFileTransport from "winston-daily-rotate-file"; -import { nodeEnv } from "@/loadenv"; +import config from "@/loadenv"; // logger에서 사용할 포맷들을 정의합니다. const baseFormat = format.combine( @@ -50,7 +50,7 @@ const consoleTransport = new transports.Console(); * @method error(message: string, callback: winston.LogCallback) - 오류 메시지를 기록하기 위해 사용합니다. */ const logger = - nodeEnv === "production" + config.nodeEnv === "production" ? // "production" 환경에서 사용되는 Logger 객체 createLogger({ level: "info", diff --git a/src/modules/slackNotification.ts b/src/modules/slackNotification.ts index bd3b9837..797aab30 100644 --- a/src/modules/slackNotification.ts +++ b/src/modules/slackNotification.ts @@ -1,18 +1,19 @@ import axios from "axios"; -import { nodeEnv, slackWebhookUrl as slackUrl } from "@/loadenv"; +import config from "@/loadenv"; import logger from "@/modules/logger"; import { type Report } from "@/types/mongo"; export const sendTextToReportChannel = (text: string) => { - if (!slackUrl.report) return; + if (!config.slackWebhookUrl.report) return; const data = { - text: nodeEnv === "production" ? text : `(${nodeEnv}) ${text}`, // Production 환경이 아닌 경우, 환경 이름을 붙여서 전송합니다. + text: + config.nodeEnv === "production" ? text : `(${config.nodeEnv}) ${text}`, // Production 환경이 아닌 경우, 환경 이름을 붙여서 전송합니다. }; - const config = { headers: { "Content-Type": "application/json" } }; + const axiosConfig = { headers: { "Content-Type": "application/json" } }; axios - .post(slackUrl.report, data, config) + .post(config.slackWebhookUrl.report, data, axiosConfig) .then(() => { logger.info("Slack webhook sent successfully"); }) diff --git a/src/modules/stores/aws.ts b/src/modules/stores/aws.ts index 4c1c6885..da0ad821 100644 --- a/src/modules/stores/aws.ts +++ b/src/modules/stores/aws.ts @@ -1,5 +1,5 @@ import AWS from "aws-sdk"; -import { aws as awsEnv } from "@/loadenv"; +import config from "@/loadenv"; AWS.config.update({ region: "ap-northeast-2", @@ -16,7 +16,7 @@ export const getList = ( ) => { s3.listObjects( { - Bucket: awsEnv.s3BucketName, + Bucket: config.aws.s3BucketName, Prefix: directoryPath, }, (err, data) => { @@ -31,7 +31,7 @@ export const getUploadPUrlPut = ( contentType: string = "image/png" ) => { const presignedUrl = s3.getSignedUrl("putObject", { - Bucket: awsEnv.s3BucketName, + Bucket: config.aws.s3BucketName, Key: filePath, ContentType: contentType, Expires: 60, // 1 min @@ -47,7 +47,7 @@ export const getUploadPUrlPost = ( ) => { s3.createPresignedPost( { - Bucket: awsEnv.s3BucketName, + Bucket: config.aws.s3BucketName, Expires: 60, // 1 min Conditions: [ { key: filePath }, @@ -68,7 +68,7 @@ export const deleteObject = ( ) => { s3.deleteObject( { - Bucket: awsEnv.s3BucketName, + Bucket: config.aws.s3BucketName, Key: filePath, }, (err, data) => { @@ -84,7 +84,7 @@ export const foundObject = ( ) => { s3.headObject( { - Bucket: awsEnv.s3BucketName, + Bucket: config.aws.s3BucketName, Key: filePath, }, (err, data) => { @@ -95,5 +95,5 @@ export const foundObject = ( // function to return full URL of the object export const getS3Url = (filePath: string) => { - return `${awsEnv.s3Url}${filePath}`; + return `${config.aws.s3Url}${filePath}`; }; diff --git a/src/modules/stores/sessionStore.ts b/src/modules/stores/sessionStore.ts index 11ec9a78..f4e89421 100644 --- a/src/modules/stores/sessionStore.ts +++ b/src/modules/stores/sessionStore.ts @@ -1,18 +1,14 @@ import MongoStore from "connect-mongo"; import RedisStore from "connect-redis"; import redis from "redis"; -import { - redis as redisUrl, - mongo as mongoUrl, - session as sessionConfig, -} from "@/loadenv"; +import config from "@/loadenv"; import logger from "@/modules/logger"; const getSessionStore = () => { // 환경변수 REDIS_PATH 유무에 따라 session 저장 방식이 변경됩니다. - if (redisUrl) { + if (config.redisUrl) { const client = redis.createClient({ - url: redisUrl, + url: config.redisUrl, }); // redis client 연결 성공 시 로그를 출력합니다. @@ -26,9 +22,9 @@ const getSessionStore = () => { }); client.connect().catch(logger.error); - return new RedisStore({ client, ttl: sessionConfig.expiry }); + return new RedisStore({ client, ttl: config.session.expiry }); } else { - return MongoStore.create({ mongoUrl }); + return MongoStore.create({ mongoUrl: config.mongoUrl }); } }; diff --git a/src/sampleGenerator/index.js b/src/sampleGenerator/index.js index c1168dab..68e2a532 100644 --- a/src/sampleGenerator/index.js +++ b/src/sampleGenerator/index.js @@ -5,7 +5,7 @@ const { generateChats, } = require("./src/testData"); const { connectDatabase } = require("../modules/stores/mongo"); -const { mongo: mongoUrl, numberOfChats, numberOfRooms } = require("./loadenv"); +const { mongoUrl, numberOfChats, numberOfRooms } = require("./loadenv"); const database = connectDatabase(mongoUrl); diff --git a/src/schedules/index.ts b/src/schedules/index.ts index 1908fadd..cf864c90 100644 --- a/src/schedules/index.ts +++ b/src/schedules/index.ts @@ -4,12 +4,12 @@ import notifyBeforeDepart from "./notifyBeforeDepart"; import notifyAfterArrival from "./notifyAfterArrival"; import updateMajorTaxiFare from "./updateMajorTaxiFare"; import updateMinorTaxiFare from "./updateMinorTaxiFare"; -import { naverMap } from "@/loadenv"; +import config from "@/loadenv"; const registerSchedules = (app: Express) => { cron.schedule("*/5 * * * *", notifyBeforeDepart(app)); cron.schedule("*/10 * * * *", notifyAfterArrival(app)); - if (naverMap.apiId && naverMap.apiKey) { + if (config.naverMap.apiId && config.naverMap.apiKey) { cron.schedule("0,30 * * * * ", updateMajorTaxiFare(app)); cron.schedule("0 18 * * *", updateMinorTaxiFare(app)); } From 3ce54065720747f35a8974e2d90bb7bdc9fa7493 Mon Sep 17 00:00:00 2001 From: static Date: Tue, 19 Nov 2024 22:50:41 +0900 Subject: [PATCH 36/61] Refactor: middlewares --- src/middlewares/auth.ts | 19 +++++++++---------- src/middlewares/authAdmin.ts | 24 +++++++++++------------- src/middlewares/errorHandler.ts | 17 +++++++---------- src/middlewares/information.ts | 8 ++------ src/middlewares/limitRate.ts | 4 ++-- src/middlewares/originValidator.ts | 14 +++++--------- src/middlewares/session.ts | 2 +- src/middlewares/validator.ts | 14 +++++--------- 8 files changed, 42 insertions(+), 60 deletions(-) diff --git a/src/middlewares/auth.ts b/src/middlewares/auth.ts index 535ed1ec..a0215b9d 100644 --- a/src/middlewares/auth.ts +++ b/src/middlewares/auth.ts @@ -1,18 +1,17 @@ // 로그인된 상태에만 접근할 수 있는 라우터(rooms)를 위한 미들웨어입니다. -import { type Request, type Response, type NextFunction } from "express"; +import type { RequestHandler } from "express"; import { isLogin, getLoginInfo } from "@/modules/auths/login"; -const authMiddleware = (req: Request, res: Response, next: NextFunction) => { - if (!isLogin(req)) { - res.status(403).json({ +const authMiddleware: RequestHandler = (req, res, next) => { + if (!isLogin(req)) + return res.status(403).json({ error: "not logged in", }); - } else { - const { id, oid } = getLoginInfo(req); - req.userId = id; - req.userOid = oid; - next(); - } + + const { id, oid } = getLoginInfo(req); + req.userId = id; + req.userOid = oid; + next(); }; export default authMiddleware; diff --git a/src/middlewares/authAdmin.ts b/src/middlewares/authAdmin.ts index af9c7b5d..cc79ef29 100644 --- a/src/middlewares/authAdmin.ts +++ b/src/middlewares/authAdmin.ts @@ -1,34 +1,32 @@ // 관리자 유무를 확인하기 위한 미들웨어입니다. -import { type Request, type Response, type NextFunction } from "express"; +import type { RequestHandler } from "express"; import { isLogin, getLoginInfo } from "@/modules/auths/login"; import { userModel, adminIPWhitelistModel } from "@/modules/stores/mongo"; -const authAdminMiddleware = async ( - req: Request, - res: Response, - next: NextFunction -) => { +const authAdminMiddleware: RequestHandler = async (req, res, next) => { + const redirectUrl = req.origin ?? "/"; + try { // 로그인 여부를 확인 - if (!isLogin(req)) return res.redirect(req.origin ?? "/"); + if (!isLogin(req)) return res.redirect(redirectUrl); // 관리자 유무를 확인 const { id } = getLoginInfo(req); const user = await userModel.findOne({ id }); - if (!user?.isAdmin) return res.redirect(req.origin ?? "/"); + if (!user?.isAdmin) return res.redirect(redirectUrl); // 접속한 IP가 화이트리스트에 있는지 확인 const ipWhitelist = await adminIPWhitelistModel.find({}); - if (!req.clientIP) return res.redirect(req.origin ?? "/"); + if (!req.clientIP) return res.redirect(redirectUrl); if ( ipWhitelist.length > 0 && - ipWhitelist.map((x: any) => x.ip).indexOf(req.clientIP) < 0 // TODO: Remove any + ipWhitelist.map((x) => x.ip).indexOf(req.clientIP) < 0 ) - return res.redirect(req.origin ?? "/"); + return res.redirect(redirectUrl); - return next(); + next(); } catch (e) { - return res.redirect(req.origin ?? "/"); + return res.redirect(redirectUrl); } }; diff --git a/src/middlewares/errorHandler.ts b/src/middlewares/errorHandler.ts index e0a7ba72..f130caa8 100644 --- a/src/middlewares/errorHandler.ts +++ b/src/middlewares/errorHandler.ts @@ -1,4 +1,4 @@ -import { type Request, type Response, type NextFunction } from "express"; +import type { ErrorRequestHandler } from "express"; import logger from "@/modules/logger"; /** @@ -11,20 +11,17 @@ import logger from "@/modules/logger"; * @param res - 응답 객체 * @param next - 다음 미들웨어 함수. Express에서는 next 함수에 err 인자를 넘겨주면 기본 global error handler가 호출됩니다. */ -const errorHandler = ( - err: Error, - req: Request, - res: Response, - next: NextFunction -) => { +const errorHandler: ErrorRequestHandler = (err, req, res, next) => { + logger.error(err); + // 이미 클라이언트에 HTTP 응답 헤더를 전송한 경우, 응답 헤더를 다시 전송하지 않아야 합니다. // 클라이언트에게 스트리밍 형태로 응답을 전송하는 도중 오류가 발생하는 경우가 여기에 해당합니다. // 이럴 때 기본 global error handler를 호출하면 기본 global error handler가 클라이언트와의 연결을 종료시켜 줍니다. - logger.error(err); if (res.headersSent) { - return next(err); + next(err); + } else { + return res.status(500).send("internal server error"); } - return res.status(500).send("internal server error"); }; export default errorHandler; diff --git a/src/middlewares/information.ts b/src/middlewares/information.ts index 0006ea40..91b3d66f 100644 --- a/src/middlewares/information.ts +++ b/src/middlewares/information.ts @@ -1,10 +1,6 @@ -import { type Request, type Response, type NextFunction } from "express"; +import type { RequestHandler } from "express"; -const informationMiddleware = ( - req: Request, - res: Response, - next: NextFunction -) => { +const informationMiddleware: RequestHandler = (req, res, next) => { req.clientIP = (req.headers["x-forwarded-for"] as string | undefined) || req.connection.remoteAddress; diff --git a/src/middlewares/limitRate.ts b/src/middlewares/limitRate.ts index d137892b..18a3203b 100644 --- a/src/middlewares/limitRate.ts +++ b/src/middlewares/limitRate.ts @@ -1,10 +1,10 @@ import rateLimit from "express-rate-limit"; -const limiter = rateLimit({ +const limitRateMiddleware = rateLimit({ windowMs: 15 * 60 * 1000, // 15 minutes max: 1500, // Limit each IP to 1500 requests per `window` (here, per 15 minutes) standardHeaders: true, // Return rate limit info in the `RateLimit-*` headers legacyHeaders: false, // Disable the `X-RateLimit-*` headers }); -export default limiter; +export default limitRateMiddleware; diff --git a/src/middlewares/originValidator.ts b/src/middlewares/originValidator.ts index 3927d46e..330226cc 100644 --- a/src/middlewares/originValidator.ts +++ b/src/middlewares/originValidator.ts @@ -1,21 +1,17 @@ -import { type Request, type Response, type NextFunction } from "express"; +import type { RequestHandler } from "express"; -const originValidatorMiddleware = ( - req: Request, - res: Response, - next: NextFunction -) => { +const originValidatorMiddleware: RequestHandler = (req, res, next) => { req.origin = req.headers.origin || req.headers.referer || req.session?.loginAfterState?.redirectOrigin; // sparcssso/callback 요청은 헤더에 origin이 없음 - if (!req.origin) { + if (!req.origin) return res.status(400).json({ error: "Bad Request : request must have origin in header", }); - } - return next(); + + next(); }; export default originValidatorMiddleware; diff --git a/src/middlewares/session.ts b/src/middlewares/session.ts index 8cf0dcf8..3486154e 100644 --- a/src/middlewares/session.ts +++ b/src/middlewares/session.ts @@ -1,6 +1,6 @@ import expressSession from "express-session"; import { nodeEnv, session as sessionConfig } from "@/loadenv"; -import { type LoginInfo } from "@/modules/auths/login"; +import type { LoginInfo } from "@/modules/auths/login"; import sessionStore from "@/modules/stores/sessionStore"; // 세션에 저장할 데이터 타입을 지정합니다. diff --git a/src/middlewares/validator.ts b/src/middlewares/validator.ts index b4e1b0cf..aafb165a 100644 --- a/src/middlewares/validator.ts +++ b/src/middlewares/validator.ts @@ -1,18 +1,14 @@ -import { type Request, type Response, type NextFunction } from "express"; +import type { RequestHandler } from "express"; import { validationResult } from "express-validator"; -const validatorMiddleware = ( - req: Request, - res: Response, - next: NextFunction -) => { +const validatorMiddleware: RequestHandler = (req, res, next) => { const validationErrors = validationResult(req); - if (!validationErrors.isEmpty()) { + if (!validationErrors.isEmpty()) return res.status(400).json({ error: "validation : bad request", }); - } - return next(); + + next(); }; export default validatorMiddleware; From d90f28c56b5ff6e97b95d2a14af5d7dbb2cbc086 Mon Sep 17 00:00:00 2001 From: static Date: Tue, 19 Nov 2024 23:26:00 +0900 Subject: [PATCH 37/61] =?UTF-8?q?Revert=20"Fix:=20loadenv=20=EB=A5=BC=20As?= =?UTF-8?q?-Is=EC=97=90=EC=84=9C=20=EC=82=AC=EC=9A=A9=ED=95=98=EB=8D=98=20?= =?UTF-8?q?Nested=20object=EB=A1=9C=20=EB=B3=80=EA=B2=BD=ED=95=98=EC=98=80?= =?UTF-8?q?=EC=8A=B5=EB=8B=88=EB=8B=A4"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 7e17d5fa8bd9a1148d809c8feb24f2b2c28e0805. --- scripts/profileImageUrlUpdater.js | 2 +- src/index.ts | 10 +-- src/loadenv.ts | 98 ++++++++++++++---------------- src/middlewares/cors.ts | 4 +- src/middlewares/session.ts | 8 +-- src/modules/auths/jwt.ts | 4 +- src/modules/auths/login.ts | 4 +- src/modules/fcm.ts | 8 +-- src/modules/logger.ts | 4 +- src/modules/slackNotification.ts | 11 ++-- src/modules/stores/aws.ts | 14 ++--- src/modules/stores/sessionStore.ts | 14 +++-- src/sampleGenerator/index.js | 2 +- src/schedules/index.ts | 4 +- 14 files changed, 92 insertions(+), 95 deletions(-) diff --git a/scripts/profileImageUrlUpdater.js b/scripts/profileImageUrlUpdater.js index fdfa671e..2b35bf1a 100644 --- a/scripts/profileImageUrlUpdater.js +++ b/scripts/profileImageUrlUpdater.js @@ -2,7 +2,7 @@ // https://github.com/sparcs-kaist/taxi-back/issues/173 const { MongoClient } = require("mongodb"); -const { mongoUrl, aws: awsEnv } = require("../loadenv"); // FIXME: 올바른 경로로 수정해야 합니다. +const { mongo: mongoUrl, aws: awsEnv } = require("../loadenv"); // FIXME: 올바른 경로로 수정해야 합니다. const time = Date.now(); diff --git a/src/index.ts b/src/index.ts index 4aebcc46..f741d146 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,7 +3,7 @@ import express from "express"; import cookieParser from "cookie-parser"; import http from "http"; -import config from "@/loadenv"; +import { nodeEnv, mongo as mongoUrl, port as httpPort } from "@/loadenv"; import { corsMiddleware, sessionMiddleware, @@ -40,14 +40,14 @@ initializeApp(); const app = express(); // 데이터베이스 연결 -connectDatabase(config.mongoUrl); +connectDatabase(mongoUrl); // [Middleware] request body 파싱 app.use(express.urlencoded({ extended: false })); app.use(express.json()); // reverse proxy가 설정한 헤더를 신뢰합니다. -if (config.nodeEnv === "production") app.set("trust proxy", 2); +if (nodeEnv === "production") app.set("trust proxy", 2); // [Middleware] CORS 설정 app.use(corsMiddleware); @@ -95,8 +95,8 @@ app.use(errorHandler); // express 서버 시작 const serverHttp = http .createServer(app) - .listen(config.port, () => - logger.info(`Express server started from port ${config.port}`) + .listen(httpPort, () => + logger.info(`Express server started from port ${httpPort}`) ); // socket.io 서버 시작 diff --git a/src/loadenv.ts b/src/loadenv.ts index 6d6c508b..c70bb4fa 100644 --- a/src/loadenv.ts +++ b/src/loadenv.ts @@ -2,7 +2,6 @@ import dotenv from "dotenv"; import { type Algorithm } from "jsonwebtoken"; import { type ServiceAccount } from "firebase-admin"; -import exp from "constants"; if (process.env.NODE_ENV === undefined) { // logger.ts가 아직 초기화되지 않았으므로 console.error를 사용합니다. @@ -19,55 +18,52 @@ if (process.env.DB_PATH === undefined) { process.exit(1); } -const config = { - nodeEnv: process.env.NODE_ENV, - mongoUrl: process.env.DB_PATH, - session: { - secret: process.env.SESSION_KEY || "TAXI_SESSION_KEY", - expiry: 14 * 24 * 3600 * 1000, - }, - redisUrl: process.env.REDIS_PATH, - sparcssso: { - id: process.env.SPARCSSSO_CLIENT_ID || "", - key: process.env.SPARCSSSO_CLIENT_KEY || "", - }, - port: process.env.PORT ? parseInt(process.env.PORT) : 80, - corsWhiteList: (process.env.CORS_WHITELIST && - (JSON.parse(process.env.CORS_WHITELIST) as string[])) || [true], - aws: { - accessKeyId: process.env.AWS_ACCESS_KEY_ID as string, - secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY as string, - s3BucketName: process.env.AWS_S3_BUCKET_NAME as string, - s3Url: - process.env.AWS_S3_URL || - `https://${process.env.AWS_S3_BUCKET_NAME}.s3.ap-northeast-2.amazonaws.com`, - }, - jwt: { - secretKey: process.env.JWT_SECRET || "TAXI_JWT_KEY", - option: { - algorithm: "HS256" as Algorithm, - // FIXME: remove FRONT_URL from issuer. 단, issuer를 변경하면 이전에 발급했던 모든 JWT가 무효화됩니다. - // See https://github.com/sparcs-kaist/taxi-back/issues/415 - issuer: process.env.FRONT_URL || "http://localhost:3000", - }, - TOKEN_EXPIRED: -3, - TOKEN_INVALID: -2, - }, - - googleApplicationCredentials: - process.env.GOOGLE_APPLICATION_CREDENTIALS && - (JSON.parse(process.env.GOOGLE_APPLICATION_CREDENTIALS) as ServiceAccount), - testAccounts: - (process.env.TEST_ACCOUNTS && - (JSON.parse(process.env.TEST_ACCOUNTS) as string[])) || - [], - slackWebhookUrl: { - report: process.env.SLACK_REPORT_WEBHOOK_URL || "", - }, - naverMap: { - apiId: process.env.NAVER_MAP_API_ID || "", - apiKey: process.env.NAVER_MAP_API_KEY || "", +export const nodeEnv = process.env.NODE_ENV; // required ("production" or "development" or "test") +export const mongo = process.env.DB_PATH; // required +export const session = { + secret: process.env.SESSION_KEY || "TAXI_SESSION_KEY", // optional + expiry: 14 * 24 * 3600 * 1000, // 14일, ms 단위입니다. +}; +export const redis = process.env.REDIS_PATH; // optional +export const sparcssso = { + id: process.env.SPARCSSSO_CLIENT_ID || "", // optional + key: process.env.SPARCSSSO_CLIENT_KEY || "", // optional +}; +export const port = process.env.PORT ? parseInt(process.env.PORT) : 80; // optional (default = 80) +export const corsWhiteList = (process.env.CORS_WHITELIST && + (JSON.parse(process.env.CORS_WHITELIST) as string[])) || [true]; // optional (default = [true]) +export const aws = { + accessKeyId: process.env.AWS_ACCESS_KEY_ID as string, // required + secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY as string, // required + s3BucketName: process.env.AWS_S3_BUCKET_NAME as string, // required + s3Url: + process.env.AWS_S3_URL || + `https://${process.env.AWS_S3_BUCKET_NAME}.s3.ap-northeast-2.amazonaws.com`, // optional +}; +export const jwt = { + secretKey: process.env.JWT_SECRET_KEY || "TAXI_JWT_KEY", + option: { + algorithm: "HS256" as Algorithm, + // FIXME: remove FRONT_URL from issuer. 단, issuer를 변경하면 이전에 발급했던 모든 JWT가 무효화됩니다. + // See https://github.com/sparcs-kaist/taxi-back/issues/415 + issuer: process.env.FRONT_URL || "http://localhost:3000", // optional (default = "http://localhost:3000") }, + TOKEN_EXPIRED: -3, + TOKEN_INVALID: -2, +}; +export const googleApplicationCredentials = + process.env.GOOGLE_APPLICATION_CREDENTIALS && + (JSON.parse(process.env.GOOGLE_APPLICATION_CREDENTIALS) as ServiceAccount); // optional +export const testAccounts = + (process.env.TEST_ACCOUNTS && + (JSON.parse(process.env.TEST_ACCOUNTS) as string[])) || + []; // optional +export const slackWebhookUrl = { + report: process.env.SLACK_REPORT_WEBHOOK_URL || "", // optional +}; +// export const eventConfig = +// process.env.EVENT_CONFIG && JSON.parse(process.env.EVENT_CONFIG); // optional +export const naverMap = { + apiId: process.env.NAVER_MAP_API_ID || "", // optional + apiKey: process.env.NAVER_MAP_API_KEY || "", // optional }; - -export default config; \ No newline at end of file diff --git a/src/middlewares/cors.ts b/src/middlewares/cors.ts index f8678f7f..63559565 100644 --- a/src/middlewares/cors.ts +++ b/src/middlewares/cors.ts @@ -1,8 +1,8 @@ import cors from "cors"; -import config from "@/loadenv"; +import { corsWhiteList } from "@/loadenv"; const corsMiddleware = cors({ - origin: config.corsWhiteList, + origin: corsWhiteList, credentials: true, exposedHeaders: ["Date"], }); diff --git a/src/middlewares/session.ts b/src/middlewares/session.ts index 3615fee8..3486154e 100644 --- a/src/middlewares/session.ts +++ b/src/middlewares/session.ts @@ -1,5 +1,5 @@ import expressSession from "express-session"; -import config from "@/loadenv"; +import { nodeEnv, session as sessionConfig } from "@/loadenv"; import type { LoginInfo } from "@/modules/auths/login"; import sessionStore from "@/modules/stores/sessionStore"; @@ -26,14 +26,14 @@ declare module "express-session" { } const sessionMiddleware = expressSession({ - secret: config.session.secret, + secret: sessionConfig.secret, resave: false, saveUninitialized: false, store: sessionStore, cookie: { - maxAge: config.session.expiry, + maxAge: sessionConfig.expiry, // nodeEnv가 production일 때만 secure cookie를 사용합니다. - secure: config.nodeEnv === "production", + secure: nodeEnv === "production", }, }); diff --git a/src/modules/auths/jwt.ts b/src/modules/auths/jwt.ts index a32cd47b..ee009330 100644 --- a/src/modules/auths/jwt.ts +++ b/src/modules/auths/jwt.ts @@ -1,7 +1,7 @@ import jwt, { type SignOptions } from "jsonwebtoken"; -import config from "@/loadenv"; +import { jwt as jwtConfig } from "@/loadenv"; -const { secretKey, option, TOKEN_EXPIRED, TOKEN_INVALID } = config.jwt; +const { secretKey, option, TOKEN_EXPIRED, TOKEN_INVALID } = jwtConfig; type TokenType = "access" | "refresh"; diff --git a/src/modules/auths/login.ts b/src/modules/auths/login.ts index f026215b..a55677b6 100644 --- a/src/modules/auths/login.ts +++ b/src/modules/auths/login.ts @@ -1,5 +1,5 @@ import { type Request } from "express"; -import config from "@/loadenv"; +import { session as sessionConfig } from "@/loadenv"; import logger from "@/modules/logger"; export interface LoginInfo { @@ -16,7 +16,7 @@ export const getLoginInfo = (req: Request) => { const timeFlow = Date.now() - time; // 14일이 지난 세션에 대해서는 로그인 정보를 반환하지 않습니다. // 세션은 새로운 요청 시 갱신되지 않습니다. - if (timeFlow > config.session.expiry) { + if (timeFlow > sessionConfig.expiry) { return { id: undefined, sid: undefined, oid: undefined, name: undefined }; } return { id, sid, oid, name }; diff --git a/src/modules/fcm.ts b/src/modules/fcm.ts index 44b9979a..ae5abf5b 100644 --- a/src/modules/fcm.ts +++ b/src/modules/fcm.ts @@ -1,6 +1,6 @@ import firebaseAdmin from "firebase-admin"; import { type SendResponse, getMessaging } from "firebase-admin/messaging"; -import config from "@/loadenv"; +import { googleApplicationCredentials } from "@/loadenv"; import logger from "@/modules/logger"; import { deviceTokenModel, @@ -13,11 +13,9 @@ import { type ChatType } from "@/types/mongo"; * credential을 등록합니다. */ export const initializeApp = () => { - if (config.googleApplicationCredentials) { + if (googleApplicationCredentials) { firebaseAdmin.initializeApp({ - credential: firebaseAdmin.credential.cert( - config.googleApplicationCredentials - ), + credential: firebaseAdmin.credential.cert(googleApplicationCredentials), }); } else { logger.error( diff --git a/src/modules/logger.ts b/src/modules/logger.ts index 5a2c0424..b4b91610 100644 --- a/src/modules/logger.ts +++ b/src/modules/logger.ts @@ -2,7 +2,7 @@ import path from "path"; import { createLogger, format, transports } from "winston"; import DailyRotateFileTransport from "winston-daily-rotate-file"; -import config from "@/loadenv"; +import { nodeEnv } from "@/loadenv"; // logger에서 사용할 포맷들을 정의합니다. const baseFormat = format.combine( @@ -50,7 +50,7 @@ const consoleTransport = new transports.Console(); * @method error(message: string, callback: winston.LogCallback) - 오류 메시지를 기록하기 위해 사용합니다. */ const logger = - config.nodeEnv === "production" + nodeEnv === "production" ? // "production" 환경에서 사용되는 Logger 객체 createLogger({ level: "info", diff --git a/src/modules/slackNotification.ts b/src/modules/slackNotification.ts index 797aab30..bd3b9837 100644 --- a/src/modules/slackNotification.ts +++ b/src/modules/slackNotification.ts @@ -1,19 +1,18 @@ import axios from "axios"; -import config from "@/loadenv"; +import { nodeEnv, slackWebhookUrl as slackUrl } from "@/loadenv"; import logger from "@/modules/logger"; import { type Report } from "@/types/mongo"; export const sendTextToReportChannel = (text: string) => { - if (!config.slackWebhookUrl.report) return; + if (!slackUrl.report) return; const data = { - text: - config.nodeEnv === "production" ? text : `(${config.nodeEnv}) ${text}`, // Production 환경이 아닌 경우, 환경 이름을 붙여서 전송합니다. + text: nodeEnv === "production" ? text : `(${nodeEnv}) ${text}`, // Production 환경이 아닌 경우, 환경 이름을 붙여서 전송합니다. }; - const axiosConfig = { headers: { "Content-Type": "application/json" } }; + const config = { headers: { "Content-Type": "application/json" } }; axios - .post(config.slackWebhookUrl.report, data, axiosConfig) + .post(slackUrl.report, data, config) .then(() => { logger.info("Slack webhook sent successfully"); }) diff --git a/src/modules/stores/aws.ts b/src/modules/stores/aws.ts index da0ad821..4c1c6885 100644 --- a/src/modules/stores/aws.ts +++ b/src/modules/stores/aws.ts @@ -1,5 +1,5 @@ import AWS from "aws-sdk"; -import config from "@/loadenv"; +import { aws as awsEnv } from "@/loadenv"; AWS.config.update({ region: "ap-northeast-2", @@ -16,7 +16,7 @@ export const getList = ( ) => { s3.listObjects( { - Bucket: config.aws.s3BucketName, + Bucket: awsEnv.s3BucketName, Prefix: directoryPath, }, (err, data) => { @@ -31,7 +31,7 @@ export const getUploadPUrlPut = ( contentType: string = "image/png" ) => { const presignedUrl = s3.getSignedUrl("putObject", { - Bucket: config.aws.s3BucketName, + Bucket: awsEnv.s3BucketName, Key: filePath, ContentType: contentType, Expires: 60, // 1 min @@ -47,7 +47,7 @@ export const getUploadPUrlPost = ( ) => { s3.createPresignedPost( { - Bucket: config.aws.s3BucketName, + Bucket: awsEnv.s3BucketName, Expires: 60, // 1 min Conditions: [ { key: filePath }, @@ -68,7 +68,7 @@ export const deleteObject = ( ) => { s3.deleteObject( { - Bucket: config.aws.s3BucketName, + Bucket: awsEnv.s3BucketName, Key: filePath, }, (err, data) => { @@ -84,7 +84,7 @@ export const foundObject = ( ) => { s3.headObject( { - Bucket: config.aws.s3BucketName, + Bucket: awsEnv.s3BucketName, Key: filePath, }, (err, data) => { @@ -95,5 +95,5 @@ export const foundObject = ( // function to return full URL of the object export const getS3Url = (filePath: string) => { - return `${config.aws.s3Url}${filePath}`; + return `${awsEnv.s3Url}${filePath}`; }; diff --git a/src/modules/stores/sessionStore.ts b/src/modules/stores/sessionStore.ts index f4e89421..11ec9a78 100644 --- a/src/modules/stores/sessionStore.ts +++ b/src/modules/stores/sessionStore.ts @@ -1,14 +1,18 @@ import MongoStore from "connect-mongo"; import RedisStore from "connect-redis"; import redis from "redis"; -import config from "@/loadenv"; +import { + redis as redisUrl, + mongo as mongoUrl, + session as sessionConfig, +} from "@/loadenv"; import logger from "@/modules/logger"; const getSessionStore = () => { // 환경변수 REDIS_PATH 유무에 따라 session 저장 방식이 변경됩니다. - if (config.redisUrl) { + if (redisUrl) { const client = redis.createClient({ - url: config.redisUrl, + url: redisUrl, }); // redis client 연결 성공 시 로그를 출력합니다. @@ -22,9 +26,9 @@ const getSessionStore = () => { }); client.connect().catch(logger.error); - return new RedisStore({ client, ttl: config.session.expiry }); + return new RedisStore({ client, ttl: sessionConfig.expiry }); } else { - return MongoStore.create({ mongoUrl: config.mongoUrl }); + return MongoStore.create({ mongoUrl }); } }; diff --git a/src/sampleGenerator/index.js b/src/sampleGenerator/index.js index 68e2a532..c1168dab 100644 --- a/src/sampleGenerator/index.js +++ b/src/sampleGenerator/index.js @@ -5,7 +5,7 @@ const { generateChats, } = require("./src/testData"); const { connectDatabase } = require("../modules/stores/mongo"); -const { mongoUrl, numberOfChats, numberOfRooms } = require("./loadenv"); +const { mongo: mongoUrl, numberOfChats, numberOfRooms } = require("./loadenv"); const database = connectDatabase(mongoUrl); diff --git a/src/schedules/index.ts b/src/schedules/index.ts index cf864c90..1908fadd 100644 --- a/src/schedules/index.ts +++ b/src/schedules/index.ts @@ -4,12 +4,12 @@ import notifyBeforeDepart from "./notifyBeforeDepart"; import notifyAfterArrival from "./notifyAfterArrival"; import updateMajorTaxiFare from "./updateMajorTaxiFare"; import updateMinorTaxiFare from "./updateMinorTaxiFare"; -import config from "@/loadenv"; +import { naverMap } from "@/loadenv"; const registerSchedules = (app: Express) => { cron.schedule("*/5 * * * *", notifyBeforeDepart(app)); cron.schedule("*/10 * * * *", notifyAfterArrival(app)); - if (config.naverMap.apiId && config.naverMap.apiKey) { + if (naverMap.apiId && naverMap.apiKey) { cron.schedule("0,30 * * * * ", updateMajorTaxiFare(app)); cron.schedule("0 18 * * *", updateMinorTaxiFare(app)); } From 63d08910ade36f1902de3ef4c0318d886fa15f96 Mon Sep 17 00:00:00 2001 From: static Date: Tue, 19 Nov 2024 23:36:45 +0900 Subject: [PATCH 38/61] Refactor: use ts-node for local development --- nodemon.json | 17 +++++++++-------- package.json | 2 +- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/nodemon.json b/nodemon.json index 5d6b9eaa..47114421 100644 --- a/nodemon.json +++ b/nodemon.json @@ -1,9 +1,10 @@ { - "ignore": ["node_modules/*"], - "watch": ["./src", ".env.development"], - "exec": "tsc && tsc-alias && node dist/index.js", - "env": { - "TZ": "Asia/Seoul", - "NODE_ENV": "development" - } -} \ No newline at end of file + "ignore": ["node_modules"], + "watch": ["src", ".env.development"], + "ext": "js,json,ts", + "exec": "ts-node --require tsconfig-paths/register src", + "env": { + "TZ": "Asia/Seoul", + "NODE_ENV": "development" + } +} diff --git a/package.json b/package.json index 95c9db46..c11b5408 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "main": "app.js", "scripts": { "preinstall": "npx only-allow pnpm", - "start": "npx tsc && tsc-alias && npx nodemon", + "start": "nodemon", "mocha": "cross-env TZ='Asia/Seoul' NODE_ENV=test mocha --require ts-node/register --require tsconfig-paths/register --recursive --reporter spec --exit", "test": "npm run sample && cross-env TZ='Asia/Seoul' npm run mocha", "build": "tsc && tsc-alias", From 6b6670b15eaf53f524c1fa2db9eac3f1dec37c22 Mon Sep 17 00:00:00 2001 From: TaehyeonPark Date: Tue, 19 Nov 2024 23:41:07 +0900 Subject: [PATCH 39/61] Refactor: ts conversion --- src/middlewares/ban.js | 19 ------------------- src/middlewares/ban.ts | 30 ++++++++++++++++++++++++++++++ src/modules/{ban.js => ban.ts} | 21 +++++++++++---------- 3 files changed, 41 insertions(+), 29 deletions(-) delete mode 100644 src/middlewares/ban.js create mode 100644 src/middlewares/ban.ts rename src/modules/{ban.js => ban.ts} (72%) diff --git a/src/middlewares/ban.js b/src/middlewares/ban.js deleted file mode 100644 index f70b9550..00000000 --- a/src/middlewares/ban.js +++ /dev/null @@ -1,19 +0,0 @@ -const { validateServiceBanRecord } = require("../modules/ban"); - -const serviceMapper = new Map([ - ["/rooms/create", "service"], - ["/rooms/join", "service"], -]); - -const banMiddleware = async (req, res, next) => { - const banErrorMessage = await validateServiceBanRecord( - req, - serviceMapper.get(req.originalUrl) - ); - if (banErrorMessage !== undefined) { - return res.status(400).json({ error: banErrorMessage }); - } - next(); -}; - -module.exports = banMiddleware; diff --git a/src/middlewares/ban.ts b/src/middlewares/ban.ts new file mode 100644 index 00000000..f340a093 --- /dev/null +++ b/src/middlewares/ban.ts @@ -0,0 +1,30 @@ +import type { RequestHandler } from "express"; +import { validateServiceBanRecord } from "@/modules/ban"; +import logger from "@/modules/logger"; + +const serviceMapper: Map = new Map([ + ["/rooms/create", "service"], + ["/rooms/join", "service"], +]); + +const banMiddleware: RequestHandler = async (req, res, next) => { + if (req.originalUrl === undefined) { + logger.error( + "Error occured while validateServiceBanRecord: req.originalUrl is undefined" + ); + return res.status(500).json({ + error: + "Error occured while validateServiceBanRecord: req.originalUrl is undefined", + }); + } + const banErrorMessage = await validateServiceBanRecord( + req, + serviceMapper.get(req.originalUrl) || "" + ); + if (banErrorMessage !== undefined) { + return res.status(400).json({ error: banErrorMessage }); + } + next(); +}; + +module.exports = banMiddleware; diff --git a/src/modules/ban.js b/src/modules/ban.ts similarity index 72% rename from src/modules/ban.js rename to src/modules/ban.ts index 4068db78..8bb8b473 100644 --- a/src/modules/ban.js +++ b/src/modules/ban.ts @@ -1,3 +1,5 @@ +import type { Request } from "express"; + const logger = require("./logger"); const { banModel } = require("./stores/mongo"); @@ -5,14 +7,17 @@ const { banModel } = require("./stores/mongo"); * @param {*} req * @param {String} service */ -const validateServiceBanRecord = async (req, service) => { +export const validateServiceBanRecord = async ( + req: Request, + service: string +) => { let banRecord = undefined; try { // 현재 시각이 expireAt 보다 작고, 본인인 경우(ban의 userId가 userId랑 같은 경우) 중 serviceName이 "service"인 record를 모두 가져옴 const bans = await banModel .find({ - userSid: req.session.loginInfo.sid, + userSid: req.session.loginInfo!.sid, expireAt: { $gte: req.timestamp, }, @@ -24,9 +29,7 @@ const validateServiceBanRecord = async (req, service) => { banRecord = bans[0]; } } catch (err) { - logger.error( - "Error occured while validateServiceBanRecord: " + err.message - ); + logger.error(err); return; } if (banRecord !== undefined) { @@ -34,12 +37,10 @@ const validateServiceBanRecord = async (req, service) => { .toISOString() .replace("T", " ") .split(".")[0]; - const banErrorMessage = `${req.originalUrl} : user ${req.userId} (${req.session.loginInfo.sid}) is temporarily restricted from service until ${formattedExpireAt}.`; + const banErrorMessage = `${req.originalUrl} : user ${req.userId} (${ + req.session.loginInfo!.sid + }) is temporarily restricted from service until ${formattedExpireAt}.`; return banErrorMessage; } return; }; - -module.exports = { - validateServiceBanRecord, -}; From 68ea4b681a2d3f1ee9d31b0df77f3cde89460364 Mon Sep 17 00:00:00 2001 From: TaehyeonPark Date: Wed, 20 Nov 2024 00:32:00 +0900 Subject: [PATCH 40/61] Refactor: ts conversion --- ...{reportEmailPage.js => reportEmailPage.ts} | 39 ++++++++++++------- 1 file changed, 26 insertions(+), 13 deletions(-) rename src/views/{reportEmailPage.js => reportEmailPage.ts} (87%) diff --git a/src/views/reportEmailPage.js b/src/views/reportEmailPage.ts similarity index 87% rename from src/views/reportEmailPage.js rename to src/views/reportEmailPage.ts index 629de233..d1567b81 100644 --- a/src/views/reportEmailPage.js +++ b/src/views/reportEmailPage.ts @@ -1,15 +1,28 @@ +import { ObjectId } from "mongoose"; + const emailPage = require("./emailPage").default; -const reportEmailPage = {}; +interface ReportEmailPage { + [key: string]: ( + origin: string, + name: string, + nickname: string, + roomName: string, + payer: string, + roomId: string | ObjectId + ) => string; +} + +const reportEmailPage: ReportEmailPage = {}; /* 미정산 알림 메일을 위한 템플릿 */ reportEmailPage["no-settlement"] = ( - origin, - name, - nickname, - roomName, - payer, - roomId + origin: string, + name: string, + nickname: string, + roomName: string, + payer: string, + roomId: string | ObjectId ) => emailPage( "미정산 내역 관련 안내", @@ -45,12 +58,12 @@ reportEmailPage["no-settlement"] = ( /* 미탑승 알림 메일을 위한 템플릿 */ reportEmailPage["no-show"] = ( - origin, - name, - nickname, - roomName, - payer, - roomId + origin: string, + name: string, + nickname: string, + roomName: string, + payer: string, + roomId: string | ObjectId ) => emailPage( "미탑승 내역 관련 안내", From 4fea34dc71c2a2d70a2c64047739f77fc812e913 Mon Sep 17 00:00:00 2001 From: static Date: Tue, 26 Nov 2024 21:59:38 +0900 Subject: [PATCH 41/61] Refactor: apply new coding conventions on loadenv.ts --- src/loadenv.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/loadenv.ts b/src/loadenv.ts index c70bb4fa..4e6f649b 100644 --- a/src/loadenv.ts +++ b/src/loadenv.ts @@ -1,12 +1,12 @@ // 환경 변수에 따라 .env.production 또는 .env.development 파일을 읽어옵니다. import dotenv from "dotenv"; -import { type Algorithm } from "jsonwebtoken"; -import { type ServiceAccount } from "firebase-admin"; +import type { ServiceAccount } from "firebase-admin"; +import type { Algorithm } from "jsonwebtoken"; if (process.env.NODE_ENV === undefined) { // logger.ts가 아직 초기화되지 않았으므로 console.error를 사용합니다. // eslint-disable-next-line no-console - console.error("NODE_ENV 환경변수가 설정되어 있지 않습니다."); + console.error("There is no NODE_ENV environment variable."); process.exit(1); } dotenv.config({ path: `./.env.${process.env.NODE_ENV}` }); @@ -14,7 +14,7 @@ dotenv.config({ path: `./.env.${process.env.NODE_ENV}` }); if (process.env.DB_PATH === undefined) { // logger.ts가 아직 초기화되지 않았으므로 console.error를 사용합니다. // eslint-disable-next-line no-console - console.error("DB_PATH 환경변수가 설정되어 있지 않습니다."); + console.error("There is no DB_PATH environment variable."); process.exit(1); } @@ -29,7 +29,7 @@ export const sparcssso = { id: process.env.SPARCSSSO_CLIENT_ID || "", // optional key: process.env.SPARCSSSO_CLIENT_KEY || "", // optional }; -export const port = process.env.PORT ? parseInt(process.env.PORT) : 80; // optional (default = 80) +export const port = parseInt(process.env.PORT || "80", 10); // optional (default = 80) export const corsWhiteList = (process.env.CORS_WHITELIST && (JSON.parse(process.env.CORS_WHITELIST) as string[])) || [true]; // optional (default = [true]) export const aws = { @@ -41,7 +41,7 @@ export const aws = { `https://${process.env.AWS_S3_BUCKET_NAME}.s3.ap-northeast-2.amazonaws.com`, // optional }; export const jwt = { - secretKey: process.env.JWT_SECRET_KEY || "TAXI_JWT_KEY", + secretKey: process.env.JWT_SECRET_KEY || "TAXI_JWT_KEY", // optional option: { algorithm: "HS256" as Algorithm, // FIXME: remove FRONT_URL from issuer. 단, issuer를 변경하면 이전에 발급했던 모든 JWT가 무효화됩니다. From c29b428b0514ce860334dff625226f93380da75c Mon Sep 17 00:00:00 2001 From: static Date: Tue, 26 Nov 2024 22:50:16 +0900 Subject: [PATCH 42/61] Refactor: convention --- src/middlewares/ban.ts | 14 ++------------ src/middlewares/index.ts | 1 + src/middlewares/responseTime.ts | 2 +- src/routes/rooms.js | 2 +- src/schedules/updateMajorTaxiFare.js | 2 +- src/schedules/updateMinorTaxiFare.js | 2 +- 6 files changed, 7 insertions(+), 16 deletions(-) diff --git a/src/middlewares/ban.ts b/src/middlewares/ban.ts index f340a093..d9a1f9d6 100644 --- a/src/middlewares/ban.ts +++ b/src/middlewares/ban.ts @@ -1,22 +1,12 @@ import type { RequestHandler } from "express"; import { validateServiceBanRecord } from "@/modules/ban"; -import logger from "@/modules/logger"; -const serviceMapper: Map = new Map([ +const serviceMapper = new Map([ ["/rooms/create", "service"], ["/rooms/join", "service"], ]); const banMiddleware: RequestHandler = async (req, res, next) => { - if (req.originalUrl === undefined) { - logger.error( - "Error occured while validateServiceBanRecord: req.originalUrl is undefined" - ); - return res.status(500).json({ - error: - "Error occured while validateServiceBanRecord: req.originalUrl is undefined", - }); - } const banErrorMessage = await validateServiceBanRecord( req, serviceMapper.get(req.originalUrl) || "" @@ -27,4 +17,4 @@ const banMiddleware: RequestHandler = async (req, res, next) => { next(); }; -module.exports = banMiddleware; +export default banMiddleware; diff --git a/src/middlewares/index.ts b/src/middlewares/index.ts index f244f41e..7484df61 100644 --- a/src/middlewares/index.ts +++ b/src/middlewares/index.ts @@ -1,6 +1,7 @@ // middleware를 모아 export합니다. export { default as authMiddleware } from "./auth"; export { default as authAdminMiddleware } from "./authAdmin"; +export { default as banMiddleware } from "./ban"; export { default as corsMiddleware } from "./cors"; export { default as errorHandler } from "./errorHandler"; export { default as informationMiddleware } from "./information"; diff --git a/src/middlewares/responseTime.ts b/src/middlewares/responseTime.ts index a1bd8c54..71a8245a 100644 --- a/src/middlewares/responseTime.ts +++ b/src/middlewares/responseTime.ts @@ -1,4 +1,4 @@ -import { type Request, type Response } from "express"; +import type { Request, Response } from "express"; import responseTime from "response-time"; import logger from "@/modules/logger"; diff --git a/src/routes/rooms.js b/src/routes/rooms.js index 187243bd..f08b35eb 100644 --- a/src/routes/rooms.js +++ b/src/routes/rooms.js @@ -36,7 +36,7 @@ router.get( router.use(require("@/middlewares/auth").default); // 방 생성/참여전 ban 여부 확인 -router.use(require("../middlewares/ban")); +router.use(require("@/middlewares/ban").default); // 특정 id 방 세부사항 보기 router.get( diff --git a/src/schedules/updateMajorTaxiFare.js b/src/schedules/updateMajorTaxiFare.js index 2f0afa03..68de1a3d 100644 --- a/src/schedules/updateMajorTaxiFare.js +++ b/src/schedules/updateMajorTaxiFare.js @@ -1,4 +1,4 @@ -const logger = require("../modules/logger"); +const logger = require("../modules/logger").default; const { scaledTime, updateTaxiFare } = require("../modules/fare"); diff --git a/src/schedules/updateMinorTaxiFare.js b/src/schedules/updateMinorTaxiFare.js index ef1dd241..5ef9d8a4 100644 --- a/src/schedules/updateMinorTaxiFare.js +++ b/src/schedules/updateMinorTaxiFare.js @@ -1,4 +1,4 @@ -const logger = require("../modules/logger"); +const logger = require("../modules/logger").default; const { updateTaxiFare } = require("../modules/fare"); From 97487fb76543bf684ec0d6502b7559f74503770b Mon Sep 17 00:00:00 2001 From: TaehyeonPark Date: Tue, 26 Nov 2024 22:58:15 +0900 Subject: [PATCH 43/61] Refactor: import convention --- src/modules/ban.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/modules/ban.ts b/src/modules/ban.ts index 8bb8b473..2a404f27 100644 --- a/src/modules/ban.ts +++ b/src/modules/ban.ts @@ -1,7 +1,7 @@ import type { Request } from "express"; -const logger = require("./logger"); -const { banModel } = require("./stores/mongo"); +import logger from "@/modules/logger"; +import { banModel } from "./stores/mongo"; /** * @param {*} req From b14e48ea8b65bdd0713b4f9a88e91e752ae99197 Mon Sep 17 00:00:00 2001 From: static Date: Tue, 26 Nov 2024 23:28:53 +0900 Subject: [PATCH 44/61] Refactor: convention 2 --- src/modules/auths/login.ts | 2 +- src/modules/ban.ts | 5 --- src/modules/fcm.ts | 72 ++++++++++++++++---------------- src/modules/populates/chats.ts | 4 +- src/modules/populates/reports.ts | 2 +- src/modules/populates/rooms.ts | 31 +++++++------- src/modules/slackNotification.ts | 2 +- src/routes/index.ts | 2 +- src/schedules/index.ts | 5 ++- 9 files changed, 60 insertions(+), 65 deletions(-) diff --git a/src/modules/auths/login.ts b/src/modules/auths/login.ts index a55677b6..962c260f 100644 --- a/src/modules/auths/login.ts +++ b/src/modules/auths/login.ts @@ -1,4 +1,4 @@ -import { type Request } from "express"; +import type { Request } from "express"; import { session as sessionConfig } from "@/loadenv"; import logger from "@/modules/logger"; diff --git a/src/modules/ban.ts b/src/modules/ban.ts index 2a404f27..366e5c54 100644 --- a/src/modules/ban.ts +++ b/src/modules/ban.ts @@ -1,12 +1,7 @@ import type { Request } from "express"; - import logger from "@/modules/logger"; import { banModel } from "./stores/mongo"; -/** - * @param {*} req - * @param {String} service - */ export const validateServiceBanRecord = async ( req: Request, service: string diff --git a/src/modules/fcm.ts b/src/modules/fcm.ts index ae5abf5b..50ee26db 100644 --- a/src/modules/fcm.ts +++ b/src/modules/fcm.ts @@ -7,7 +7,7 @@ import { notificationOptionModel, topicSubscriptionModel, } from "@/modules/stores/mongo"; -import { type ChatType } from "@/types/mongo"; +import type { ChatType } from "@/types/mongo"; /** * credential을 등록합니다. @@ -26,14 +26,14 @@ export const initializeApp = () => { /** * 사용자의 ObjectId와 FCM device token이 주어졌을 때, 해당 deviceToken을 사용자의 토큰으로 DB에 등록합니다. - * @param {string} userId - 사용자의 ObjectId입니다. - * @param {string} deviceToken - 등록하려는 FCM device token입니다. - * @return {Promise>} 변경된 사용자의 deviceToken의 목록 Array를 반환합니다. 오류가 발생하면 빈 배열을 반환합니다. + * @param userId - 사용자의 ObjectId입니다. + * @param deviceToken - 등록하려는 FCM device token입니다. + * @return 변경된 사용자의 deviceToken의 목록 Array를 반환합니다. 오류가 발생하면 빈 배열을 반환합니다. */ export const registerDeviceToken = async ( userId: string, deviceToken: string -): Promise => { +) => { try { // 디바이스 토큰을 다른 사용자가 사용하고 있는지 확인 및 삭제합니다. await deviceTokenModel.updateMany( @@ -67,8 +67,8 @@ export const registerDeviceToken = async ( /** * 사용자의 ObjectId와 FCM device token이 주어졌을 때, 해당 사용자의 해당 deviceToken을 DB에서 삭제합니다. - * @param {string} deviceToken - 삭제하려는 FCM device token입니다. - * @return {Promise} 해당 deviceToken을 가진 모든 사용자로부터 해당 deviceToken을 삭제하는 데 성공하면 true, 하나 이상의 사용자에게서 해당 deviceToken을 삭제하는 데 실패하면 false를 반환합니다. 삭제할 deviceToken이 존재하지 않는 경우에는 true를 반환합니다. + * @param deviceToken - 삭제하려는 FCM device token입니다. + * @return 해당 deviceToken을 가진 모든 사용자로부터 해당 deviceToken을 삭제하는 데 성공하면 true, 하나 이상의 사용자에게서 해당 deviceToken을 삭제하는 데 실패하면 false를 반환합니다. 삭제할 deviceToken이 존재하지 않는 경우에는 true를 반환합니다. */ export const unregisterDeviceToken = async (deviceToken: string) => { try { @@ -95,9 +95,9 @@ export const unregisterDeviceToken = async (deviceToken: string) => { /** * 메시지 전송에 실패한 deviceToken을 DB에서 삭제합니다. - * @param {Array} deviceTokens - 사용자의 ObjectId입니다. - * @param {Array} fcmResponses - 등록하려는 FCM device token입니다. - * @return {Promise>} 각각의 토큰들의 삭제 성공 여부가 저장된 Array를 반환합니다. 해당 토큰을 DB에서 삭제하는 데 성공했으면 true, 아니면 false가 포함됩니다. + * @param deviceTokens - 사용자의 ObjectId입니다. + * @param fcmResponses - 등록하려는 FCM device token입니다. + * @return 각각의 토큰들의 삭제 성공 여부가 저장된 Array를 반환합니다. 해당 토큰을 DB에서 삭제하는 데 성공했으면 true, 아니면 false가 포함됩니다. */ const removeExpiredTokens = async ( deviceTokens: string[], @@ -131,8 +131,8 @@ const removeExpiredTokens = async ( /** * 사용자의 FCM device token이 현재 사용 가능한지 검증합니다. * @summary 해당 디바이스에 dry-run 방식으로 메시지 전송을 시험함으로써 해당 deviceToken이 사용 가능한지 검증합니다. dry-run 시 FCM 서버에는 메시지 전송 요청이 전송되지만, 실제 기기에는 알림이 전송되지 않습니다. - * @param {string} deviceToken - 사용 가능 여부를 확인하려고 하는 FCM device token입니다. - * @return {Promise} 해당 디바이스에 알림을 보낸다는 요청을 FCM 서버에 성공적으로 보냈으면 true, 아니면 false를 반환합니다. + * @param deviceToken - 사용 가능 여부를 확인하려고 하는 FCM device token입니다. + * @return 해당 디바이스에 알림을 보낸다는 요청을 FCM 서버에 성공적으로 보냈으면 true, 아니면 false를 반환합니다. */ export const validateDeviceToken = async (deviceToken: string) => { try { @@ -152,14 +152,14 @@ export const validateDeviceToken = async (deviceToken: string) => { /** * 사용자들의 ObjectId의 배열이 주어졌을 때, 해당 사용자들의 모든 deviceToken을 하나의 Array로 반환합니다. - * @param {Array} userIds - 사용자의 ObjectId로 이루어진 Array입니다. - * @param {Object?} notificationOptions - 특정 알림 설정을 비활성화한 사용자를 필터링하기 위해 사용되는 Object입니다. + * @param userIds - 사용자의 ObjectId로 이루어진 Array입니다. + * @param notificationOptions - 특정 알림 설정을 비활성화한 사용자를 필터링하기 위해 사용되는 Object입니다. * @param {Boolean?} notificationOptions.chatting - true 또는 false로 주어진 경우, 채팅 알림 설정이 각각 true 또는 false로 설정된 사용자들의 deviceToken만 반환합니다. - * @return {Promise>} deviceToken의 Array를 반환합니다. 오류가 발생하면 빈 배열을 반환합니다. + * @return deviceToken의 Array를 반환합니다. 오류가 발생하면 빈 배열을 반환합니다. */ export const getTokensOfUsers = async ( userIds: string[], - notificationOptions: Object = {} + notificationOptions: object = {} ) => { const deviceTokensOfUsers = ( await Promise.all( @@ -187,13 +187,13 @@ export const getTokensOfUsers = async ( /** * 주어진 token들에 메시지 알림을 전송합니다. * TODO: 알림 전송 실패한 토큰 삭제하기 - * @param {Array} tokens - 메시지 알림을 받을 기기의 deviceToken들로 구성된 Array입니다. - * @param {ChatType} type - 메시지 유형으로, "text" | "in" | "out" | "s3img" | "payment" | "settlement" 입니다. - * @param {string} title - 보낼 메시지의 제목입니다. - * @param {string} body - 보낼 메시지의 본문입니다. - * @param {string?} icon - 메시지를 보낸 사람의 프로필 사진 주소입니다. - * @param {string?} link - 메시지 알림 팝업을 클릭했을 때 이동할 주소입니다. - * @return {Promise} 메시지 알림 전송에 실패한 기기의 수를 반환합니다. 오류가 발생하면 -1을 반환합니다. + * @param tokens - 메시지 알림을 받을 기기의 deviceToken들로 구성된 Array입니다. + * @param type - 메시지 유형으로, "text" | "in" | "out" | "s3img" | "payment" | "settlement" 입니다. + * @param title - 보낼 메시지의 제목입니다. + * @param body - 보낼 메시지의 본문입니다. + * @param icon - 메시지를 보낸 사람의 프로필 사진 주소입니다. + * @param link - 메시지 알림 팝업을 클릭했을 때 이동할 주소입니다. + * @return 메시지 알림 전송에 실패한 기기의 수를 반환합니다. 오류가 발생하면 -1을 반환합니다. */ export const sendMessageByTokens = async ( tokens: string[], @@ -237,13 +237,13 @@ export const sendMessageByTokens = async ( /** * 주어진 topic을 구독하고 있는 모든 기기에 메시지 알림을 전송합니다. - * @param {string} topic - 메시지 알림을 보낼 기기들이 구독하고 있는 topic입니다. - * @param {ChatType} type - 메시지 유형으로, "text" | "in" | "out" | "s3img" | "payment" | "settlement" 입니다. - * @param {string} title - 보낼 메시지의 제목입니다. - * @param {string} body - 보낼 메시지의 본문입니다. - * @param {string?} icon - 메시지를 보낸 사람의 프로필 사진 주소입니다. - * @param {string?} link - 메시지 알림 팝업을 클릭했을 때 이동할 주소입니다. - * @return {Promise} 메시지 알림 전송에 성공했으면 true, 아니면 false를 반환합니다. + * @param topic - 메시지 알림을 보낼 기기들이 구독하고 있는 topic입니다. + * @param type - 메시지 유형으로, "text" | "in" | "out" | "s3img" | "payment" | "settlement" 입니다. + * @param title - 보낼 메시지의 제목입니다. + * @param body - 보낼 메시지의 본문입니다. + * @param icon - 메시지를 보낸 사람의 프로필 사진 주소입니다. + * @param link - 메시지 알림 팝업을 클릭했을 때 이동할 주소입니다. + * @return 메시지 알림 전송에 성공했으면 true, 아니면 false를 반환합니다. */ export const sendMessageByTopic = async ( topic: string, @@ -278,9 +278,9 @@ export const sendMessageByTopic = async ( /** * 주어진 사용자를 특정한 topic에 구독시킵니다. - * @param {string} userId - topic을 구독할 사용자의 ObjectId입니다. - * @param {string} topic - 구독할 topic입니다. - * @return {Promise} 토픽 구독에 실패한 기기의 수를 반환합니다. 오류가 발생하면 -1을 반환합니다. + * @param userId - topic을 구독할 사용자의 ObjectId입니다. + * @param topic - 구독할 topic입니다. + * @return 토픽 구독에 실패한 기기의 수를 반환합니다. 오류가 발생하면 -1을 반환합니다. */ export const subscribeUserToTopic = async (userId: string, topic: string) => { try { @@ -329,9 +329,9 @@ export const subscribeUserToTopic = async (userId: string, topic: string) => { /** * 주어진 사용자를 특정한 topic으로부터 구독 해제시킵니다. - * @param {string} userId - topic을 구독 해제할 사용자의 id입니다. - * @param {string} topic - 구독을 해제할 topic입니다. - * @return {Promise} 토픽 구독 해제에 실패한 기기의 수를 반환합니다. 오류가 발생하면 -1을 반환합니다. + * @param userId - topic을 구독 해제할 사용자의 id입니다. + * @param topic - 구독을 해제할 topic입니다. + * @return 토픽 구독 해제에 실패한 기기의 수를 반환합니다. 오류가 발생하면 -1을 반환합니다. */ export const unsubscribeUserFromTopic = async ( userId: string, diff --git a/src/modules/populates/chats.ts b/src/modules/populates/chats.ts index 633dd174..077f7dad 100644 --- a/src/modules/populates/chats.ts +++ b/src/modules/populates/chats.ts @@ -1,6 +1,6 @@ -import { type User, type Chat } from "@/types/mongo"; +import type { User, Chat } from "@/types/mongo"; -/** @constant {{path: string, select: string}[]} +/** * 쿼리를 통해 얻은 Chat Document를 populate할 설정값을 정의합니다. */ export const chatPopulateOption = [ diff --git a/src/modules/populates/reports.ts b/src/modules/populates/reports.ts index a9fe3c9e..6fcf60f3 100644 --- a/src/modules/populates/reports.ts +++ b/src/modules/populates/reports.ts @@ -1,4 +1,4 @@ -import { type User, type Report } from "@/types/mongo"; +import type { User, Report } from "@/types/mongo"; export const reportPopulateOption = [ { diff --git a/src/modules/populates/rooms.ts b/src/modules/populates/rooms.ts index 15f39338..6eb87461 100644 --- a/src/modules/populates/rooms.ts +++ b/src/modules/populates/rooms.ts @@ -1,14 +1,13 @@ -import { - type User, - type SettlementStatus, - type Participant, - type Room, - type Location, +import type { + User, + SettlementStatus, + Participant, + Room, + Location, } from "@/types/mongo"; /** * 쿼리를 통해 얻은 Room Document를 populate할 설정값을 정의합니다. - * @constant {{path: string, select: string, populate?: {path: string, select: string}}[]} */ export const roomPopulateOption = [ { path: "from", select: "_id koName enName" }, @@ -49,12 +48,12 @@ export interface FormattedRoom /** * Room Object가 주어졌을 때 room의 part array의 각 요소를 API 명세에서와 같이 {userId: String, ... , isSettlement: String}으로 가공합니다. * 또한, 방이 현재 출발했는지 유무인 isDeparted 속성을 추가합니다. - * @param {PopulatedRoom} roomObject - 정산 정보를 가공할 room Object로, Mongoose Document가 아닌 순수 Javascript Object여야 합니다. - * @param {Object} options - 추가 파라미터로, 기본값은 {}입니다. - * @param {Boolean} options.includeSettlement - 반환 결과에 정산 정보를 포함할 지 여부로, 기본값은 true입니다. - * @param {Date} options.timestamp - 방의 출발 여부(isDeparted)를 판단하는 기준이 되는 시각입니다. - * @param {Boolean} options.isOver - 방의 완료 여부(isOver)로, 기본값은 false입니다. includeSettlement가 false인 경우 roomDocument의 isOver 속성은 undefined로 설정됩니다. - * @return {FormattedRoom} 정산 여부가 위와 같이 가공되고 isDeparted 속성이 추가된 Room Object가 반환됩니다. + * @param roomObject - 정산 정보를 가공할 room Object로, Mongoose Document가 아닌 순수 Javascript Object여야 합니다. + * @param options - 추가 파라미터로, 기본값은 {}입니다. + * @param options.includeSettlement - 반환 결과에 정산 정보를 포함할 지 여부로, 기본값은 true입니다. + * @param options.timestamp - 방의 출발 여부(isDeparted)를 판단하는 기준이 되는 시각입니다. + * @param options.isOver - 방의 완료 여부(isOver)로, 기본값은 false입니다. includeSettlement가 false인 경우 roomDocument의 isOver 속성은 undefined로 설정됩니다. + * @return 정산 여부가 위와 같이 가공되고 isDeparted 속성이 추가된 Room Object가 반환됩니다. */ export const formatSettlement = ( roomObject: PopulatedRoom, @@ -83,9 +82,9 @@ export const formatSettlement = ( /** * roomPopulateOption을 사용해 populate된 Room Object와 사용자의 id(userId)가 주어졌을 때, 해당 사용자의 정산 상태를 반환합니다. - * @param {PopulatedRoom} roomObject - roomPopulateOption을 사용해 populate된 변환한 Room Object입니다. - * @param {String} userId - 방 완료 상태를 확인하려는 사용자의 id(user.id)입니다. - * @return {Boolean | undefined} 사용자의 해당 방에 대한 완료 여부(true | false)를 반환합니다. 사용자가 참여중인 방이 아닐 경우 undefined를 반환합니다. + * @param roomObject - roomPopulateOption을 사용해 populate된 변환한 Room Object입니다. + * @param userId - 방 완료 상태를 확인하려는 사용자의 id(user.id)입니다. + * @return 사용자의 해당 방에 대한 완료 여부(true | false)를 반환합니다. 사용자가 참여중인 방이 아닐 경우 undefined를 반환합니다. **/ export const getIsOver = (roomObject: PopulatedRoom, userId: string) => { // room document의 part subdoocument에서 사용자 id와 일치하는 정산 정보를 찾습니다. diff --git a/src/modules/slackNotification.ts b/src/modules/slackNotification.ts index bd3b9837..c70cfdb6 100644 --- a/src/modules/slackNotification.ts +++ b/src/modules/slackNotification.ts @@ -1,7 +1,7 @@ import axios from "axios"; import { nodeEnv, slackWebhookUrl as slackUrl } from "@/loadenv"; import logger from "@/modules/logger"; -import { type Report } from "@/types/mongo"; +import type { Report } from "@/types/mongo"; export const sendTextToReportChannel = (text: string) => { if (!slackUrl.report) return; diff --git a/src/routes/index.ts b/src/routes/index.ts index ddbe083a..83bc81dc 100644 --- a/src/routes/index.ts +++ b/src/routes/index.ts @@ -2,10 +2,10 @@ export { default as adminRouter } from "./admin"; export { default as authRouter } from "./auth"; export { default as chatRouter } from "./chats"; export { default as docsRouter } from "./docs"; +export { default as fareRouter } from "./fare"; export { default as locationRouter } from "./locations"; export { default as logininfoRouter } from "./logininfo"; export { default as notificationRouter } from "./notifications"; export { default as reportRouter } from "./reports"; export { default as roomRouter } from "./rooms"; export { default as userRouter } from "./users"; -export { default as fareRouter } from "./fare"; diff --git a/src/schedules/index.ts b/src/schedules/index.ts index 1908fadd..f714dd61 100644 --- a/src/schedules/index.ts +++ b/src/schedules/index.ts @@ -1,14 +1,15 @@ -import { type Express } from "express"; +import type { Express } from "express"; import cron from "node-cron"; +import { naverMap } from "@/loadenv"; import notifyBeforeDepart from "./notifyBeforeDepart"; import notifyAfterArrival from "./notifyAfterArrival"; import updateMajorTaxiFare from "./updateMajorTaxiFare"; import updateMinorTaxiFare from "./updateMinorTaxiFare"; -import { naverMap } from "@/loadenv"; const registerSchedules = (app: Express) => { cron.schedule("*/5 * * * *", notifyBeforeDepart(app)); cron.schedule("*/10 * * * *", notifyAfterArrival(app)); + if (naverMap.apiId && naverMap.apiKey) { cron.schedule("0,30 * * * * ", updateMajorTaxiFare(app)); cron.schedule("0 18 * * *", updateMinorTaxiFare(app)); From 1c396a21480683baf7e5063595a43e6406e63b24 Mon Sep 17 00:00:00 2001 From: static Date: Tue, 26 Nov 2024 23:56:33 +0900 Subject: [PATCH 45/61] Refactor: convention 3 --- src/routes/users.ts | 10 ++-- src/services/users.ts | 103 +++++++++++++++++++++++------------------- 2 files changed, 62 insertions(+), 51 deletions(-) diff --git a/src/routes/users.ts b/src/routes/users.ts index 1f6cb288..45514925 100755 --- a/src/routes/users.ts +++ b/src/routes/users.ts @@ -1,6 +1,6 @@ import express from "express"; import { body } from "express-validator"; -import validator from "@/middlewares/validator"; +import { authMiddleware, validatorMiddleware } from "@/middlewares"; import patterns from "@/modules/patterns"; const router = express.Router(); @@ -9,7 +9,7 @@ import * as userHandlers from "@/services/users"; import { replaceSpaceInNickname } from "@/modules/modifyProfile"; // 라우터 접근 시 로그인 필요 -router.use(require("@/middlewares/auth").default); +router.use(authMiddleware); // 이용 약관에 동의합니다. router.post( @@ -27,7 +27,7 @@ router.post( body("nickname") .customSanitizer(replaceSpaceInNickname) .matches(patterns.user.nickname), - validator, + validatorMiddleware, userHandlers.editNicknameHandler ); @@ -38,7 +38,7 @@ router.get("/resetNickname", userHandlers.resetNicknameHandler); router.post( "/editAccount", body("account").matches(patterns.user.account), - validator, + validatorMiddleware, userHandlers.editAccountHandler ); @@ -46,7 +46,7 @@ router.post( router.post( "/editProfileImg/getPUrl", body("type").matches(patterns.user.profileImgType), - validator, + validatorMiddleware, userHandlers.editProfileImgGetPUrlHandler ); diff --git a/src/services/users.ts b/src/services/users.ts index 2fc8f0ba..a51ac859 100644 --- a/src/services/users.ts +++ b/src/services/users.ts @@ -1,40 +1,44 @@ -import type { Request, Response } from "express"; +import type { RequestHandler } from "express"; import { userModel, banModel } from "@/modules/stores/mongo"; import logger from "@/modules/logger"; -import * as aws from "@/modules/stores/aws"; - import { generateNickname, generateProfileImageUrl, } from "@/modules/modifyProfile"; +import * as aws from "@/modules/stores/aws"; + // 이벤트 코드입니다. // const { contracts } = require("@/lottery"); -export const agreeOnTermsOfServiceHandler = async ( - req: Request, - res: Response +export const agreeOnTermsOfServiceHandler: RequestHandler = async ( + req, + res ) => { try { let user = await userModel.findOne({ id: req.userId }); if (user && user.agreeOnTermsOfService !== true) { user.agreeOnTermsOfService = true; await user.save(); - res + return res .status(200) .send( "Users/agreeOnTermsOfService : agree on Terms of Service successful" ); } else { - res.status(400).send("Users/agreeOnTermsOfService : already agreed"); + return res + .status(400) + .send("Users/agreeOnTermsOfService : already agreed"); } } catch { - res.status(500).send("Users/agreeOnTermsOfService : internal server error"); + return res + .status(500) + .send("Users/agreeOnTermsOfService : internal server error"); } }; -export const getAgreeOnTermsOfServiceHandler = async ( - req: Request, - res: Response +export const getAgreeOnTermsOfServiceHandler: RequestHandler = async ( + req, + res ) => { try { const user = await userModel @@ -42,18 +46,18 @@ export const getAgreeOnTermsOfServiceHandler = async ( .lean(); if (user) { const agreeOnTermsOfService = user.agreeOnTermsOfService === true; - res.json({ agreeOnTermsOfService }); + return res.json({ agreeOnTermsOfService }); } } catch { - res + return res .status(500) .send("Users/getAgreeOnTermsOfService : internal server error"); } }; -export const editNicknameHandler = async (req: Request, res: Response) => { +export const editNicknameHandler: RequestHandler = async (req, res) => { try { - const newNickname = req.body.nickname; + const newNickname = req.body.nickname; // TODO: Typing const result = await userModel.findOneAndUpdate( { id: req.userId }, { nickname: newNickname } @@ -66,21 +70,23 @@ export const editNicknameHandler = async (req: Request, res: Response) => { // req.timestamp // ); - res + return res .status(200) .send("Users/editNickname : edit user nickname successful"); } else { - res.status(400).send("Users/editNickname : such user id does not exist"); + return res + .status(400) + .send("Users/editNickname : such user id does not exist"); } } catch (err) { logger.error(err); - res.status(500).send("Users/editNickname : internal server error"); + return res.status(500).send("Users/editNickname : internal server error"); } }; -export const editAccountHandler = async (req: Request, res: Response) => { +export const editAccountHandler: RequestHandler = async (req, res) => { try { - const newAccount = req.body.account; + const newAccount = req.body.account; // TODO: Typing const result = await userModel.findOneAndUpdate( { id: req.userId }, { account: newAccount } @@ -94,22 +100,26 @@ export const editAccountHandler = async (req: Request, res: Response) => { // newAccount // ); - res.status(200).send("Users/editAccount : edit user account successful"); + return res + .status(200) + .send("Users/editAccount : edit user account successful"); } else { - res.status(400).send("Users/editAccount : such user id does not exist"); + return res + .status(400) + .send("Users/editAccount : such user id does not exist"); } } catch (err) { logger.error(err); - res.status(500).send("Users/editAccount : internal server error"); + return res.status(500).send("Users/editAccount : internal server error"); } }; -export const editProfileImgGetPUrlHandler = async ( - req: Request, - res: Response +export const editProfileImgGetPUrlHandler: RequestHandler = async ( + req, + res ) => { try { - const type = req.body.type; + const type = req.body.type; // TODO: Typing const user = await userModel.findOne({ id: req.userId }, "_id"); if (!user) { return res @@ -125,22 +135,19 @@ export const editProfileImgGetPUrlHandler = async ( } data.fields["Content-Type"] = type; data.fields["key"] = key; - res.json({ + return res.json({ url: data.url, fields: data.fields, }); }); } catch (e) { - res + return res .status(500) .send("Users/editProfileImg/getPUrl : internal server error"); } }; -export const editProfileImgDoneHandler = async ( - req: Request, - res: Response -) => { +export const editProfileImgDoneHandler: RequestHandler = async (req, res) => { try { const user = await userModel.findOne({ id: req.userId }, "_id"); if (!user) { @@ -166,37 +173,39 @@ export const editProfileImgDoneHandler = async ( .status(500) .send("Users/editProfileImg/done : internal server error"); } - res.json({ + return res.json({ result: true, profileImageUrl: userAfter.profileImageUrl, }); }); } catch (e) { - res.status(500).send("Users/editProfileImg/done : internal server error"); + return res + .status(500) + .send("Users/editProfileImg/done : internal server error"); } }; -export const resetNicknameHandler = async (req: Request, res: Response) => { +export const resetNicknameHandler: RequestHandler = async (req, res) => { try { const result = await userModel.findOneAndUpdate( { id: req.userId }, - { nickname: generateNickname(req.body.id) }, + { nickname: generateNickname(req.body.id) }, // TODO: Typing or Validation { new: true } ); if (!result) return res .status(400) .send("Users/resetNickname : such user does not exist"); - res + return res .status(200) .send("Users/resetNickname : reset user nickname successful"); } catch (err) { logger.error(err); - res.status(500).send("Users/resetNickname : internal server error"); + return res.status(500).send("Users/resetNickname : internal server error"); } }; -export const resetProfileImgHandler = async (req: Request, res: Response) => { +export const resetProfileImgHandler: RequestHandler = async (req, res) => { try { const result = await userModel.findOneAndUpdate( { id: req.userId }, @@ -207,15 +216,17 @@ export const resetProfileImgHandler = async (req: Request, res: Response) => { return res .status(400) .send("Users/resetProfileImg : such user does not exist"); - res + return res .status(200) .send("Users/resetProfileImg : reset user profile image successful"); } catch (err) { - res.status(500).send("Users/resetProfileImg : internal server error"); + return res + .status(500) + .send("Users/resetProfileImg : internal server error"); } }; -export const getBanRecordHandler = async (req: Request, res: Response) => { +export const getBanRecordHandler: RequestHandler = async (req, res) => { try { // 본인인 경우(ban의 userId가 userSid랑 같은 경우)의 record를 모두 가져옴 const result = await banModel @@ -225,8 +236,8 @@ export const getBanRecordHandler = async (req: Request, res: Response) => { .sort({ expireAt: -1 }); if (!result) return res.status(500).send("Users/getBanRecord : internal server error"); - res.status(200).json(result); + return res.status(200).json(result); } catch (err) { - res.status(500).send("Users/getBanRecord : internal server error"); + return res.status(500).send("Users/getBanRecord : internal server error"); } }; From 327ba686dfe3264249767c44f8b0407b92c198be Mon Sep 17 00:00:00 2001 From: TaehyeonPark Date: Wed, 27 Nov 2024 00:19:10 +0900 Subject: [PATCH 46/61] Refactor: esm convention --- src/services/reports.js | 2 +- src/views/reportEmailPage.ts | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/services/reports.js b/src/services/reports.js index afbc08ba..f4cb17f4 100644 --- a/src/services/reports.js +++ b/src/services/reports.js @@ -2,7 +2,7 @@ const { userModel, reportModel, roomModel } = require("@/modules/stores/mongo"); const { reportPopulateOption } = require("@/modules/populates/reports"); const { sendReportEmail } = require("@/modules/email"); const logger = require("@/modules/logger").default; -const reportEmailPage = require("@/views/reportEmailPage"); +const reportEmailPage = require("@/views/reportEmailPage").default; const { notifyReportToReportChannel } = require("@/modules/slackNotification"); const createHandler = async (req, res) => { diff --git a/src/views/reportEmailPage.ts b/src/views/reportEmailPage.ts index d1567b81..779f36e0 100644 --- a/src/views/reportEmailPage.ts +++ b/src/views/reportEmailPage.ts @@ -1,6 +1,5 @@ -import { ObjectId } from "mongoose"; - -const emailPage = require("./emailPage").default; +import type { ObjectId } from "mongoose"; +import emailPage from "./emailPage"; interface ReportEmailPage { [key: string]: ( @@ -96,4 +95,4 @@ reportEmailPage["no-show"] = ( ` ); -module.exports = reportEmailPage; +export default reportEmailPage; From 2f60fd978f3dd22ed602d9bfd2b187ecccf64873 Mon Sep 17 00:00:00 2001 From: static Date: Wed, 27 Nov 2024 00:20:11 +0900 Subject: [PATCH 47/61] Refactor: tsconfig.json --- tsconfig.json | 41 ++++++++++++++++++++--------------------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/tsconfig.json b/tsconfig.json index 09d4273e..e7575db4 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,24 +1,23 @@ { - "compilerOptions": { - "target": "es6", - "module": "node16", - "moduleResolution": "node16", - "allowJs": true, - "outDir": "./dist", - "resolveJsonModule": true, - "strict": true, - "esModuleInterop": true, - "skipLibCheck": true, - "forceConsistentCasingInFileNames": true, - "baseUrl": "./src", - "paths": { - "@/*": ["./*"] - } - }, - "include": ["src"], - "exclude": ["dist", "node_modules"], - "ts-node": { - "files": true + "compilerOptions": { + "target": "es6", + "module": "node16", + "moduleResolution": "node16", + "allowJs": true, + "outDir": "./dist", + "resolveJsonModule": true, + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "baseUrl": "./src", + "paths": { + "@/*": ["./*"] } + }, + "include": ["src"], + "exclude": ["dist", "node_modules"], + "ts-node": { + "files": true } - \ No newline at end of file +} From b3b17b5da09e7e3a0ac95701a8378587e4fb9b1e Mon Sep 17 00:00:00 2001 From: static Date: Wed, 27 Nov 2024 00:41:31 +0900 Subject: [PATCH 48/61] Refactor: convention 4 --- src/index.ts | 43 ++++++++++++++++++------------------ src/views/reportEmailPage.ts | 24 ++++++++++---------- 2 files changed, 34 insertions(+), 33 deletions(-) diff --git a/src/index.ts b/src/index.ts index f741d146..ced953e8 100644 --- a/src/index.ts +++ b/src/index.ts @@ -6,42 +6,43 @@ import http from "http"; import { nodeEnv, mongo as mongoUrl, port as httpPort } from "@/loadenv"; import { corsMiddleware, - sessionMiddleware, + errorHandler, informationMiddleware, - responseTimeMiddleware, limitRateMiddleware, originValidatorMiddleware, - errorHandler, + responseTimeMiddleware, + sessionMiddleware, } from "@/middlewares"; import { + adminRouter, authRouter, - logininfoRouter, - userRouter, - roomRouter, chatRouter, - locationRouter, - reportRouter, - notificationRouter, - adminRouter, docsRouter, fareRouter, + locationRouter, + logininfoRouter, + notificationRouter, + reportRouter, + roomRouter, + userRouter, } from "@/routes"; -import { initializeApp } from "@/modules/fcm"; + +import { initializeApp as initializeFirebase } from "@/modules/fcm"; import { initializeDatabase as initializeFareDatabase } from "@/modules/fare"; import logger from "@/modules/logger"; -import { connectDatabase } from "@/modules/stores/mongo"; import { startSocketServer } from "@/modules/socket"; +import { connectDatabase } from "@/modules/stores/mongo"; import registerSchedules from "@/schedules"; // Firebase Admin 초기설정 -initializeApp(); - -// 익스프레스 서버 생성 -const app = express(); +initializeFirebase(); // 데이터베이스 연결 connectDatabase(mongoUrl); +// 익스프레스 서버 생성 +const app = express(); + // [Middleware] request body 파싱 app.use(express.urlencoded({ extended: false })); app.use(express.json()); @@ -80,14 +81,14 @@ app.use(originValidatorMiddleware); // [Router] APIs app.use("/auth", authRouter); -app.use("/logininfo", logininfoRouter); -app.use("/users", userRouter); -app.use("/rooms", roomRouter); app.use("/chats", chatRouter); +app.use("/fare", fareRouter); app.use("/locations", locationRouter); -app.use("/reports", reportRouter); +app.use("/logininfo", logininfoRouter); app.use("/notifications", notificationRouter); -app.use("/fare", fareRouter); +app.use("/reports", reportRouter); +app.use("/rooms", roomRouter); +app.use("/users", userRouter); // [Middleware] 전역 에러 핸들러. 에러 핸들러는 router들보다 아래에 등록되어야 합니다. app.use(errorHandler); diff --git a/src/views/reportEmailPage.ts b/src/views/reportEmailPage.ts index 779f36e0..49691aad 100644 --- a/src/views/reportEmailPage.ts +++ b/src/views/reportEmailPage.ts @@ -16,12 +16,12 @@ const reportEmailPage: ReportEmailPage = {}; /* 미정산 알림 메일을 위한 템플릿 */ reportEmailPage["no-settlement"] = ( - origin: string, - name: string, - nickname: string, - roomName: string, - payer: string, - roomId: string | ObjectId + origin, + name, + nickname, + roomName, + payer, + roomId ) => emailPage( "미정산 내역 관련 안내", @@ -57,12 +57,12 @@ reportEmailPage["no-settlement"] = ( /* 미탑승 알림 메일을 위한 템플릿 */ reportEmailPage["no-show"] = ( - origin: string, - name: string, - nickname: string, - roomName: string, - payer: string, - roomId: string | ObjectId + origin, + name, + nickname, + roomName, + payer, + roomId ) => emailPage( "미탑승 내역 관련 안내", From 99430cd623e9cdbe77d935893bac592029afbdac Mon Sep 17 00:00:00 2001 From: TaehyeonPark Date: Wed, 27 Nov 2024 00:43:01 +0900 Subject: [PATCH 49/61] Refactor: @ convention --- src/modules/ban.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/ban.ts b/src/modules/ban.ts index 366e5c54..f5276f23 100644 --- a/src/modules/ban.ts +++ b/src/modules/ban.ts @@ -1,6 +1,6 @@ import type { Request } from "express"; import logger from "@/modules/logger"; -import { banModel } from "./stores/mongo"; +import { banModel } from "@/modules/stores/mongo"; export const validateServiceBanRecord = async ( req: Request, From e93d89b7ac26c06d624ac2a243ff736035157601 Mon Sep 17 00:00:00 2001 From: static Date: Wed, 27 Nov 2024 01:21:33 +0900 Subject: [PATCH 50/61] Fix: redis undefined error --- src/modules/stores/sessionStore.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/modules/stores/sessionStore.ts b/src/modules/stores/sessionStore.ts index 11ec9a78..2eed4e2e 100644 --- a/src/modules/stores/sessionStore.ts +++ b/src/modules/stores/sessionStore.ts @@ -1,6 +1,6 @@ import MongoStore from "connect-mongo"; import RedisStore from "connect-redis"; -import redis from "redis"; +import { createClient } from "redis"; import { redis as redisUrl, mongo as mongoUrl, @@ -11,7 +11,7 @@ import logger from "@/modules/logger"; const getSessionStore = () => { // 환경변수 REDIS_PATH 유무에 따라 session 저장 방식이 변경됩니다. if (redisUrl) { - const client = redis.createClient({ + const client = createClient({ url: redisUrl, }); From 84a732e8b1566833b4fa1c6a84dbfa344279670f Mon Sep 17 00:00:00 2001 From: static Date: Wed, 27 Nov 2024 01:27:29 +0900 Subject: [PATCH 51/61] Fix: jwt undefiend error --- src/services/auth.js | 2 +- src/services/auth.mobile.js | 2 +- src/services/auth.replace.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/services/auth.js b/src/services/auth.js index cd660ab5..2b9ba658 100644 --- a/src/services/auth.js +++ b/src/services/auth.js @@ -9,7 +9,7 @@ const { generateProfileImageUrl, getFullUsername, } = require("@/modules/modifyProfile"); -const jwt = require("@/modules/auths/jwt").default; +const jwt = require("@/modules/auths/jwt"); const logger = require("@/modules/logger").default; // SPARCS SSO diff --git a/src/services/auth.mobile.js b/src/services/auth.mobile.js index 8e66db0e..6ae14db2 100644 --- a/src/services/auth.mobile.js +++ b/src/services/auth.mobile.js @@ -2,7 +2,7 @@ const { userModel } = require("@/modules/stores/mongo"); const { login } = require("@/modules/auths/login"); const { registerDeviceToken, unregisterDeviceToken } = require("@/modules/fcm"); -const jwt = require("@/modules/auths/jwt").default; +const jwt = require("@/modules/auths/jwt"); const logger = require("@/modules/logger").default; const { TOKEN_EXPIRED, TOKEN_INVALID } = require("@/loadenv").jwt; diff --git a/src/services/auth.replace.js b/src/services/auth.replace.js index 65ae92c7..111c7077 100644 --- a/src/services/auth.replace.js +++ b/src/services/auth.replace.js @@ -7,7 +7,7 @@ const { generateProfileImageUrl, } = require("@/modules/modifyProfile"); const logger = require("@/modules/logger").default; -const jwt = require("@/modules/auths/jwt").default; +const jwt = require("@/modules/auths/jwt"); const { registerDeviceTokenHandler, tryLogin } = require("@/services/auth"); const loginReplacePage = require("@/views/loginReplacePage").default; From e0e99c604e6987506990a9069a538cddfd13a139 Mon Sep 17 00:00:00 2001 From: static Date: Wed, 27 Nov 2024 01:58:46 +0900 Subject: [PATCH 52/61] Fix: minor bugs --- src/modules/stores/mongo.ts | 2 +- src/services/chats.js | 2 +- src/types/mongo.d.ts | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/modules/stores/mongo.ts b/src/modules/stores/mongo.ts index b4affe32..2c2e17e6 100755 --- a/src/modules/stores/mongo.ts +++ b/src/modules/stores/mongo.ts @@ -155,7 +155,7 @@ const roomSchema = new Schema({ }, // 참여 멤버 및 정산 여부 madeat: { type: Date, required: true }, // 생성 날짜 settlementTotal: { type: Number, default: 0, required: true }, - maxPartLength: { type: Number, require: true, default: 4 }, + maxPartLength: { type: Number, required: true, default: 4 }, }); export const roomModel = model("Room", roomSchema); diff --git a/src/services/chats.js b/src/services/chats.js index f8190cfd..b6bd6467 100644 --- a/src/services/chats.js +++ b/src/services/chats.js @@ -1,7 +1,7 @@ const { chatModel, userModel, roomModel } = require("@/modules/stores/mongo"); const { chatPopulateOption } = require("@/modules/populates/chats"); const { roomPopulateOption } = require("@/modules/populates/rooms"); -const aws = require("@/modules/stores/aws").default; +const aws = require("@/modules/stores/aws"); const { transformChatsForRoom, emitChatEvent, diff --git a/src/types/mongo.d.ts b/src/types/mongo.d.ts index 93fcb382..718b342e 100644 --- a/src/types/mongo.d.ts +++ b/src/types/mongo.d.ts @@ -1,4 +1,4 @@ -import { Document, Types } from "mongoose"; +import type { Document, Types } from "mongoose"; export interface User extends Document { /** 사용자의 실명. */ @@ -13,6 +13,7 @@ export interface User extends Document { ongoingRoom?: Types.Array; /** 사용자가 참여한 방 중 완료된 방의 배열. */ doneRoom?: Types.Array; + /** 계정 탈퇴 여부. */ withdraw: boolean; /** 사용자의 전화번호. 2023 가을 이벤트부터 추가됨. */ phoneNumber?: string; From d8c691960feb234d21a80215487c33ce47bf4285 Mon Sep 17 00:00:00 2001 From: TaehyeonPark Date: Wed, 27 Nov 2024 02:08:37 +0900 Subject: [PATCH 53/61] Refactor: type --- src/modules/stores/mongo.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/stores/mongo.ts b/src/modules/stores/mongo.ts index b4affe32..a884b455 100755 --- a/src/modules/stores/mongo.ts +++ b/src/modules/stores/mongo.ts @@ -1,4 +1,4 @@ -import mongoose, { model, Schema, Types } from "mongoose"; +import mongoose, { model, Schema, type Types } from "mongoose"; import logger from "@/modules/logger"; import type { User, From 8bf159903951b4d9fec86b1a416ae41fc3a40024 Mon Sep 17 00:00:00 2001 From: TaehyeonPark Date: Wed, 27 Nov 2024 02:26:29 +0900 Subject: [PATCH 54/61] Refactor: es6 --- src/modules/stores/mongo.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/stores/mongo.ts b/src/modules/stores/mongo.ts index 25b3d1f6..76924f2a 100755 --- a/src/modules/stores/mongo.ts +++ b/src/modules/stores/mongo.ts @@ -263,7 +263,7 @@ database.on("error", function (err) { }); export const connectDatabase = (mongoUrl: string) => { - database.on("disconnected", function () { + database.on("disconnected", () => { // 데이터베이스 연결이 끊어지면 5초 후 재연결을 시도합니다. logger.error("Disconnected from database!"); setTimeout(() => { From 5172ea55b031a015c648e41fd4e7d965ceeb4cbf Mon Sep 17 00:00:00 2001 From: static Date: Wed, 27 Nov 2024 02:52:51 +0900 Subject: [PATCH 55/61] Fix: scripts --- package.json | 6 +++--- scripts/chatPaymentSettlementUpdater.js | 2 +- scripts/profileImageUrlUpdater.js | 2 +- tsconfig.build.json | 4 ++++ tsconfig.json | 2 +- 5 files changed, 10 insertions(+), 6 deletions(-) create mode 100644 tsconfig.build.json diff --git a/package.json b/package.json index c11b5408..2882496f 100644 --- a/package.json +++ b/package.json @@ -9,12 +9,12 @@ "preinstall": "npx only-allow pnpm", "start": "nodemon", "mocha": "cross-env TZ='Asia/Seoul' NODE_ENV=test mocha --require ts-node/register --require tsconfig-paths/register --recursive --reporter spec --exit", - "test": "npm run sample && cross-env TZ='Asia/Seoul' npm run mocha", - "build": "tsc && tsc-alias", + "test": "pnpm run sample && cross-env TZ='Asia/Seoul' pnpm run mocha", + "build": "tsc --project tsconfig.build.json && tsc-alias", "clean": "rimraf dist/", "serve": "cross-env TZ='Asia/Seoul' NODE_ENV=production node dist/index.js", "lint": "pnpm eslint .", - "runscript": "cross-env TZ='Asia/Seoul' NODE_ENV=production node", + "runscript": "cross-env TZ='Asia/Seoul' NODE_ENV=production ts-node --require tsconfig-paths/register", "sample": "cross-env NODE_ENV=test ts-node --require tsconfig-paths/register src/sampleGenerator/index.js", "dumpDB": "cross-env NODE_ENV=test ts-node --require tsconfig-paths/register src/sampleGenerator/tools/dump.js", "restoreDB": "cross-env NODE_ENV=test ts-node --require tsconfig-paths/register src/sampleGenerator/tools/restore.js" diff --git a/scripts/chatPaymentSettlementUpdater.js b/scripts/chatPaymentSettlementUpdater.js index 76e43682..1b1d11dd 100644 --- a/scripts/chatPaymentSettlementUpdater.js +++ b/scripts/chatPaymentSettlementUpdater.js @@ -3,7 +3,7 @@ // https://github.com/sparcs-kaist/taxi-back/issues/449 const { MongoClient } = require("mongodb"); -const { mongo: mongoUrl } = require("../loadenv"); +const { mongo: mongoUrl } = require("@/loadenv"); const client = new MongoClient(mongoUrl); const db = client.db("taxi"); diff --git a/scripts/profileImageUrlUpdater.js b/scripts/profileImageUrlUpdater.js index 2b35bf1a..5814f609 100644 --- a/scripts/profileImageUrlUpdater.js +++ b/scripts/profileImageUrlUpdater.js @@ -2,7 +2,7 @@ // https://github.com/sparcs-kaist/taxi-back/issues/173 const { MongoClient } = require("mongodb"); -const { mongo: mongoUrl, aws: awsEnv } = require("../loadenv"); // FIXME: 올바른 경로로 수정해야 합니다. +const { mongo: mongoUrl, aws: awsEnv } = require("@/loadenv"); const time = Date.now(); diff --git a/tsconfig.build.json b/tsconfig.build.json new file mode 100644 index 00000000..68db80dd --- /dev/null +++ b/tsconfig.build.json @@ -0,0 +1,4 @@ +{ + "extends": "./tsconfig.json", + "include": ["src"], +} diff --git a/tsconfig.json b/tsconfig.json index e7575db4..166f2de6 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -15,7 +15,7 @@ "@/*": ["./*"] } }, - "include": ["src"], + "include": ["src", "scripts"], "exclude": ["dist", "node_modules"], "ts-node": { "files": true From 7e5b85c68263e1caafc697b20f1027e92198af23 Mon Sep 17 00:00:00 2001 From: TaehyeonPark Date: Wed, 27 Nov 2024 03:13:26 +0900 Subject: [PATCH 56/61] Refactor: unused fs --- src/sampleGenerator/index.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/sampleGenerator/index.js b/src/sampleGenerator/index.js index c1168dab..38481739 100644 --- a/src/sampleGenerator/index.js +++ b/src/sampleGenerator/index.js @@ -9,7 +9,6 @@ const { mongo: mongoUrl, numberOfChats, numberOfRooms } = require("./loadenv"); const database = connectDatabase(mongoUrl); -const fs = require("fs"); const sampleData = require("./sampleData.json"); const main = async () => { From 8d8bac81b05b8f4f5b51d65d4220cf73396993c0 Mon Sep 17 00:00:00 2001 From: neymar <0208mjkim@gmail.com> Date: Wed, 27 Nov 2024 03:19:09 +0900 Subject: [PATCH 57/61] fix: user null --- src/services/users.ts | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/src/services/users.ts b/src/services/users.ts index a51ac859..fe1e920a 100644 --- a/src/services/users.ts +++ b/src/services/users.ts @@ -16,19 +16,17 @@ export const agreeOnTermsOfServiceHandler: RequestHandler = async ( ) => { try { let user = await userModel.findOne({ id: req.userId }); - if (user && user.agreeOnTermsOfService !== true) { - user.agreeOnTermsOfService = true; - await user.save(); - return res - .status(200) - .send( - "Users/agreeOnTermsOfService : agree on Terms of Service successful" - ); - } else { - return res - .status(400) - .send("Users/agreeOnTermsOfService : already agreed"); + if (!user) { + return res.status(500).send("Users/agreeOnTermsOfService : no such user"); + } + + if (user.agreeOnTermsOfService === true) { + return res.status(400).send("Users/agreeOnTermsOfService: already agreed"); } + + user.agreeOnTermsOfService = true; + await user.save(); + return res.status(200).send("Users/agreeOnTermsOfService : agree successful"); } catch { return res .status(500) From be4159d865daa61feb2a53cf487f7b1d7e7be8d6 Mon Sep 17 00:00:00 2001 From: neymar <0208mjkim@gmail.com> Date: Wed, 27 Nov 2024 03:31:15 +0900 Subject: [PATCH 58/61] fix: change 500 to 400 --- src/services/users.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/services/users.ts b/src/services/users.ts index fe1e920a..b57709cc 100644 --- a/src/services/users.ts +++ b/src/services/users.ts @@ -17,7 +17,7 @@ export const agreeOnTermsOfServiceHandler: RequestHandler = async ( try { let user = await userModel.findOne({ id: req.userId }); if (!user) { - return res.status(500).send("Users/agreeOnTermsOfService : no such user"); + return res.status(400).send("Users/agreeOnTermsOfService : no such user"); } if (user.agreeOnTermsOfService === true) { @@ -42,6 +42,11 @@ export const getAgreeOnTermsOfServiceHandler: RequestHandler = async ( const user = await userModel .findOne({ id: req.userId }, "agreeOnTermsOfService") .lean(); + + if (!user) { + return res.status(400).send("Users/agreeOnTermsOfService : no such user"); + } + if (user) { const agreeOnTermsOfService = user.agreeOnTermsOfService === true; return res.json({ agreeOnTermsOfService }); From 5b32009ae514f94b3981adbf6ea5d2355f4be5bf Mon Sep 17 00:00:00 2001 From: neymar <0208mjkim@gmail.com> Date: Wed, 27 Nov 2024 03:39:05 +0900 Subject: [PATCH 59/61] fix: matched response to test case --- src/services/users.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/users.ts b/src/services/users.ts index b57709cc..be3cffa6 100644 --- a/src/services/users.ts +++ b/src/services/users.ts @@ -26,7 +26,7 @@ export const agreeOnTermsOfServiceHandler: RequestHandler = async ( user.agreeOnTermsOfService = true; await user.save(); - return res.status(200).send("Users/agreeOnTermsOfService : agree successful"); + return res.status(200).send("Users/agreeOnTermsOfService : agree on Terms of Service successful"); } catch { return res .status(500) From 74ba7d242c576aa053d93e2975ccef9397347d73 Mon Sep 17 00:00:00 2001 From: neymar <0208mjkim@gmail.com> Date: Wed, 27 Nov 2024 03:41:41 +0900 Subject: [PATCH 60/61] fix: add space --- src/services/users.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/users.ts b/src/services/users.ts index be3cffa6..9d16626e 100644 --- a/src/services/users.ts +++ b/src/services/users.ts @@ -21,7 +21,7 @@ export const agreeOnTermsOfServiceHandler: RequestHandler = async ( } if (user.agreeOnTermsOfService === true) { - return res.status(400).send("Users/agreeOnTermsOfService: already agreed"); + return res.status(400).send("Users/agreeOnTermsOfService : already agreed"); } user.agreeOnTermsOfService = true; From 94261d55bce94a6de427cffbc41deabd094a0644 Mon Sep 17 00:00:00 2001 From: neymar <0208mjkim@gmail.com> Date: Wed, 27 Nov 2024 03:48:08 +0900 Subject: [PATCH 61/61] fix: minor stuff --- src/services/users.ts | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/services/users.ts b/src/services/users.ts index 9d16626e..3caf35cf 100644 --- a/src/services/users.ts +++ b/src/services/users.ts @@ -42,15 +42,12 @@ export const getAgreeOnTermsOfServiceHandler: RequestHandler = async ( const user = await userModel .findOne({ id: req.userId }, "agreeOnTermsOfService") .lean(); - if (!user) { return res.status(400).send("Users/agreeOnTermsOfService : no such user"); } - if (user) { - const agreeOnTermsOfService = user.agreeOnTermsOfService === true; - return res.json({ agreeOnTermsOfService }); - } + const agreeOnTermsOfService = user.agreeOnTermsOfService === true; + return res.json({ agreeOnTermsOfService }); } catch { return res .status(500) @@ -234,7 +231,7 @@ export const getBanRecordHandler: RequestHandler = async (req, res) => { // 본인인 경우(ban의 userId가 userSid랑 같은 경우)의 record를 모두 가져옴 const result = await banModel .find({ - userSid: req.session.loginInfo?.sid, + userSid: req.session.loginInfo!.sid, }) .sort({ expireAt: -1 }); if (!result)