From c15495f2e82422d9cc5619d054136ae82bff29a2 Mon Sep 17 00:00:00 2001
From: withsang <jsa5115@naver.com>
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 <jsa5115@naver.com>
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 <jsa5115@naver.com>
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 <jsa5115@naver.com>
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 <jsa5115@naver.com>
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 <jsa5115@naver.com>
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 <jsa5115@naver.com>
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 <jsa5115@naver.com>
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 <jsa5115@naver.com>
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 <jsa5115@naver.com>
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(
     "미정산 내역 관련 안내",
     `<b><font color="#6E3678">${name} (${nickname})</font></b> 님께<br /><br />
@@ -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
 ) => `<div style="font-family: system-ui; position: relative; background: #ffffff; margin: 0; padding: 72px;">
   <div style="width: max(min(100%, 800px), 320px); margin: 0 auto; padding 0;">
     <div style="height: 102px; background: #6E3678; margin: 0 0 48px; padding: 0;">
@@ -29,3 +29,5 @@ module.exports = (
     </div>
   </div>
 </div>`;
+
+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 = `
 <!DOCTYPE html>
 <html lang="ko">
     <head>
@@ -44,3 +44,5 @@ module.exports = `
     </body>
 </html>
 `;
+
+export default loginPage;

From 76242380db2e96e86bda5881e2f2b725473d1f59 Mon Sep 17 00:00:00 2001
From: static <kmc7468@naver.com>
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 = <string | undefined>req.headers["x-forwarded-for"] || req.connection.remoteAddress;
   req.timestamp = Date.now();
   next();
 };

From f5ece38f7e282265efc6e96b75032e66667ae88f Mon Sep 17 00:00:00 2001
From: static <kmc7468@naver.com>
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 <kmc7468@naver.com>
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, Model<User>, 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, Model<Participant>, 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, Model<DeviceToken>, DeviceToken>({
   userId: {
     type: Schema.Types.ObjectId,
     ref: "User",
@@ -48,7 +47,7 @@ const deviceTokenSchema = Schema({
 });
 
 // 각 디바이스의 알림 설정
-const notificationOptionSchema = Schema({
+const notificationOptionSchema = new Schema<NotificationOption, Model<NotificationOption>, NotificationOption>({
   deviceToken: {
     type: String,
     required: true,
@@ -82,7 +81,7 @@ const notificationOptionSchema = Schema({
   }, //광고성 알림 수신 여부
 });
 
-const topicSubscriptionSchema = Schema({
+const topicSubscriptionSchema = new Schema<TopicSubscription, Model<TopicSubscription>, TopicSubscription>({
   deviceToken: String,
   topic: String,
   subscribedAt: {
@@ -92,7 +91,7 @@ const topicSubscriptionSchema = Schema({
   },
 });
 
-const roomSchema = Schema({
+const roomSchema = new Schema<Room, Model<Room>, 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, Model<Location>, 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, Model<Chat>, 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, Model<Report>, 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, Model<AdminIPWhitelist>, AdminIPWhitelist>({
   ip: { type: String, required: true }, // IP 주소
   description: { type: String, default: "" }, // 설명
 });
 
-const adminLogSchema = Schema({
+const adminLogSchema = new Schema<AdminLog, Model<AdminLog>, 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 <kmc7468@naver.com>
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, Model<User>, User>({
+const userSchema = new Schema<User>({
   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, Model<User>, User>({
   account: { type: String, default: "" }, //계좌번호 정보
 });
 
-const participantSchema = new Schema<Participant, Model<Participant>, Participant>({
+export const userModel = model("User", userSchema);
+
+const participantSchema = new Schema<Participant>({
   user: { type: Schema.Types.ObjectId, ref: "User", required: true },
   settlementStatus: {
     type: String,
@@ -36,7 +38,7 @@ const participantSchema = new Schema<Participant, Model<Participant>, Participan
   readAt: { type: Date },
 });
 
-const deviceTokenSchema = new Schema<DeviceToken, Model<DeviceToken>, DeviceToken>({
+const deviceTokenSchema = new Schema<DeviceToken>({
   userId: {
     type: Schema.Types.ObjectId,
     ref: "User",
@@ -46,8 +48,10 @@ const deviceTokenSchema = new Schema<DeviceToken, Model<DeviceToken>, DeviceToke
   deviceTokens: [{ type: String, required: true }],
 });
 
+export const deviceTokenModel = model("DeviceToken", deviceTokenSchema);
+
 // 각 디바이스의 알림 설정
-const notificationOptionSchema = new Schema<NotificationOption, Model<NotificationOption>, NotificationOption>({
+const notificationOptionSchema = new Schema<NotificationOption>({
   deviceToken: {
     type: String,
     required: true,
@@ -81,7 +85,12 @@ const notificationOptionSchema = new Schema<NotificationOption, Model<Notificati
   }, //광고성 알림 수신 여부
 });
 
-const topicSubscriptionSchema = new Schema<TopicSubscription, Model<TopicSubscription>, TopicSubscription>({
+export const notificationOptionModel = model(
+  "NotificationOption",
+  notificationOptionSchema
+);
+
+const topicSubscriptionSchema = new Schema<TopicSubscription>({
   deviceToken: String,
   topic: String,
   subscribedAt: {
@@ -91,7 +100,12 @@ const topicSubscriptionSchema = new Schema<TopicSubscription, Model<TopicSubscri
   },
 });
 
-const roomSchema = new Schema<Room, Model<Room>, Room>({
+export const topicSubscriptionModel = model(
+  "TopicSubscription",
+  topicSubscriptionSchema
+);
+
+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 },
@@ -99,7 +113,7 @@ const roomSchema = new Schema<Room, Model<Room>, Room>({
   part: {
     type: [participantSchema],
     validate: [
-      function (this: Room, value: Participant[]) {
+      function (this: Room, value: Types.DocumentArray<Participant>) {
         return value.length <= this.maxPartLength;
       },
     ],
@@ -109,16 +123,20 @@ const roomSchema = new Schema<Room, Model<Room>, Room>({
   maxPartLength: { type: Number, require: true, default: 4 },
 });
 
-const locationSchema = new Schema<Location, Model<Location>, Location>({
+export const roomModel = model("Room", roomSchema);
+
+const locationSchema = new Schema<Location>({
   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, Model<Chat>, Chat>({
+export const locationModel = model("Location", locationSchema);
+
+const chatSchema = new Schema<Chat>({
   roomId: { type: Schema.Types.ObjectId, ref: "Room", required: true },
   type: {
     type: String,
@@ -141,7 +159,9 @@ const chatSchema = new Schema<Chat, Model<Chat>, Chat>({
 });
 chatSchema.index({ roomId: 1, time: -1 });
 
-const reportSchema = new Schema<Report, Model<Report>, Report>({
+export const chatModel = model("Chat", chatSchema);
+
+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: {
@@ -154,12 +174,19 @@ const reportSchema = new Schema<Report, Model<Report>, Report>({
   roomId: { type: Schema.Types.ObjectId, ref: "Room" }, // 신고한 방 id
 });
 
-const adminIPWhitelistSchema = new Schema<AdminIPWhitelist, Model<AdminIPWhitelist>, AdminIPWhitelist>({
+export const reportModel = model("Report", reportSchema);
+
+const adminIPWhitelistSchema = new Schema<AdminIPWhitelist>({
   ip: { type: String, required: true }, // IP 주소
   description: { type: String, default: "" }, // 설명
 });
 
-const adminLogSchema = new Schema<AdminLog, Model<AdminLog>, AdminLog>({
+export const adminIPWhitelistModel = model(
+  "AdminIPWhitelist",
+  adminIPWhitelistSchema
+);
+
+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 주소
@@ -171,6 +198,8 @@ const adminLogSchema = new Schema<AdminLog, Model<AdminLog>, 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<Types.ObjectId>;
   /** 사용자가 참여한 방 중 완료된 방의 배열. */
-  doneRoom?: string[];
-  withdraw?: boolean;
+  doneRoom?: Types.Array<Types.ObjectId>;
+  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<string>;
 }
 
 export interface NotificationOption {
@@ -58,7 +58,7 @@ export interface NotificationOption {
   /** 채팅 알림 수신 여부. */
   chatting: boolean;
   /** 방 알림 키워드. */
-  keywords: string[];
+  keywords: Types.Array<string>;
   /** 출발 전 알림 발송 여부. */
   beforeDepart: boolean;
   /** 공지성 알림 수신 여부. */
@@ -83,7 +83,7 @@ export interface Room {
   /** 방의 출발 시각. */
   time: Date;
   /** 방 참여자의 배열. */
-  part?: Participant[];
+  part?: Types.DocumentArray<Participant>;
   /** 방의 생성 시각. */
   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 <kmc7468@naver.com>
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 <kmc7468@naver.com>
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 <kmc7468@naver.com>
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 <kmc7468@naver.com>
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<User>({
   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 <kmc7468@naver.com>
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 <kmc7468@naver.com>
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<Array<string>>} 변경된 사용자의 deviceToken의 목록 Array를 반환합니다. 오류가 발생하면 빈 배열을 반환합니다.
  */
-const registerDeviceToken = async (userId, deviceToken) => {
+export const registerDeviceToken = async (userId: string, deviceToken: string): Promise<string[]> => {
   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<boolean>} 해당 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<SendResponse>} fcmResponses - 등록하려는 FCM device token입니다.
  * @return {Promise<Array<Boolean>>} 각각의 토큰들의 삭제 성공 여부가 저장된 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<Boolean>} 해당 디바이스에 알림을 보낸다는 요청을 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<Array<string>>} 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<string>} 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<Number>} 메시지 알림 전송에 실패한 기기의 수를 반환합니다. 오류가 발생하면 -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<boolean>} 메시지 알림 전송에 성공했으면 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<Number>} 토픽 구독에 실패한 기기의 수를 반환합니다. 오류가 발생하면 -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<Number>} 토픽 구독 해제에 실패한 기기의 수를 반환합니다. 오류가 발생하면 -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 <kmc7468@naver.com>
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<Chat, "authorId"> {
+  authorId?: Pick<User, "_id" | "nickname" | "profileImageUrl">;
+};
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<Report, "reportedId"> {
+  reportedId: Pick<User, "_id" | "id" | "name" | "nickname" | "profileImageUrl">;
+};
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<Participant, "settlementStatus" | "readAt"> {
+  user: Pick<User, "_id" | "id" | "name" | "nickname" | "profileImageUrl">;
+}
+
+export interface PopulatedRoom extends Omit<Room, "from" | "to" | "part"> {
+  from: Pick<Location, "_id" | "koName" | "enName">;
+  to: Pick<Location, "_id" | "koName" | "enName">;
+  part?: PopulatedParticipant[];
+}
+
+export interface FormattedRoom extends Omit<PopulatedRoom, "part" | "settlementTotal"> {
+  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<string>;
 }
 
-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 <kmc7468@naver.com>
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 = <string | undefined>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 <kmc7468@naver.com>
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<Array<string>>} 변경된 사용자의 deviceToken의 목록 Array를 반환합니다. 오류가 발생하면 빈 배열을 반환합니다.
  */
-export const registerDeviceToken = async (userId: string, deviceToken: string): Promise<string[]> => {
+export const registerDeviceToken = async (
+  userId: string,
+  deviceToken: string
+): Promise<string[]> => {
   try {
     // 디바이스 토큰을 다른 사용자가 사용하고 있는지 확인 및 삭제합니다.
     await deviceTokenModel.updateMany(
@@ -96,7 +99,10 @@ export const unregisterDeviceToken = async (deviceToken: string) => {
  * @param {Array<SendResponse>} fcmResponses - 등록하려는 FCM device token입니다.
  * @return {Promise<Array<Boolean>>} 각각의 토큰들의 삭제 성공 여부가 저장된 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<Array<string>>} 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<Number>} 메시지 알림 전송에 실패한 기기의 수를 반환합니다. 오류가 발생하면 -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<boolean>} 메시지 알림 전송에 성공했으면 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<Number>} 토픽 구독 해제에 실패한 기기의 수를 반환합니다. 오류가 발생하면 -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<Chat, "authorId"> {
   authorId?: Pick<User, "_id" | "nickname" | "profileImageUrl">;
-};
+}
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<Report, "reportedId"> {
-  reportedId: Pick<User, "_id" | "id" | "name" | "nickname" | "profileImageUrl">;
-};
+  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<Participant, "settlementStatus" | "readAt"> {
+interface PopulatedParticipant
+  extends Pick<Participant, "settlementStatus" | "readAt"> {
   user: Pick<User, "_id" | "id" | "name" | "nickname" | "profileImageUrl">;
 }
 
@@ -24,7 +31,8 @@ export interface PopulatedRoom extends Omit<Room, "from" | "to" | "part"> {
   part?: PopulatedParticipant[];
 }
 
-export interface FormattedRoom extends Omit<PopulatedRoom, "part" | "settlementTotal"> {
+export interface FormattedRoom
+  extends Omit<PopulatedRoom, "part" | "settlementTotal"> {
   part?: {
     _id: string;
     name: string;
@@ -36,7 +44,7 @@ export interface FormattedRoom extends Omit<PopulatedRoom, "part" | "settlementT
   settlementTotal?: number;
   isOver?: boolean;
   isDeparted: boolean;
-};
+}
 
 /**
  * Room Object가 주어졌을 때 room의 part array의 각 요소를 API 명세에서와 같이 {userId: String, ... , isSettlement: String}으로 가공합니다.
@@ -71,7 +79,7 @@ export const formatSettlement = (
     isOver: includeSettlement ? isOver : undefined,
     isDeparted: new Date(roomObject.time) < new Date(timestamp),
   };
-}
+};
 
 /**
  * roomPopulateOption을 사용해 populate된 Room Object와 사용자의 id(userId)가 주어졌을 때, 해당 사용자의 정산 상태를 반환합니다.
@@ -86,7 +94,8 @@ export const getIsOver = (roomObject: PopulatedRoom, userId: string) => {
   });
 
   // 방에 참여중이지 않은 사용자의 경우, 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<User>({
   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 <kmc7468@naver.com>
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 <kmc7468@naver.com>
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 <kmc7468@naver.com>
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 <kmc7468@naver.com>
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 <kmc7468@naver.com>
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 <kmc7468@naver.com>
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 <kmc7468@naver.com>
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 <kmc7468@naver.com>
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<User>({
@@ -39,7 +41,7 @@ const userSchema = new Schema<User>({
 
 export const userModel = model("User", userSchema);
 
-const banSchema = Schema({
+const banSchema = new Schema<Ban>({
   // 정지 시킬 사용자를 기제함.
   userSid: { type: String, required: true },
   // 정지 사유
@@ -58,6 +60,8 @@ const banSchema = Schema({
   },
 });
 
+export const banModel = model("Ban", banSchema);
+
 const participantSchema = new Schema<Participant>({
   user: { type: Schema.Types.ObjectId, ref: "User", required: true },
   settlementStatus: {
@@ -231,19 +235,21 @@ const adminLogSchema = new Schema<AdminLog>({
 
 export const adminLogModel = model("AdminLog", adminLogSchema);
 
-const taxiFareSchema = Schema(
+const taxiFareSchema = new Schema<TaxiFare>(
   {
     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 <kmc7468@naver.com>
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 <hanu@hanukoon.com>
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 <kmc7468@naver.com>
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 <kmc7468@naver.com>
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 <kmc7468@naver.com>
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 <devtaehyeon@gmail.com>
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<string, string> = 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 <devtaehyeon@gmail.com>
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 <kmc7468@naver.com>
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 <kmc7468@naver.com>
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<string, string> = 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 <devtaehyeon@gmail.com>
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 <kmc7468@naver.com>
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<Array<string>>} 변경된 사용자의 deviceToken의 목록 Array를 반환합니다. 오류가 발생하면 빈 배열을 반환합니다.
+ * @param userId - 사용자의 ObjectId입니다.
+ * @param deviceToken - 등록하려는 FCM device token입니다.
+ * @return 변경된 사용자의 deviceToken의 목록 Array를 반환합니다. 오류가 발생하면 빈 배열을 반환합니다.
  */
 export const registerDeviceToken = async (
   userId: string,
   deviceToken: string
-): Promise<string[]> => {
+) => {
   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<boolean>} 해당 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<string>} deviceTokens - 사용자의 ObjectId입니다.
- * @param {Array<SendResponse>} fcmResponses - 등록하려는 FCM device token입니다.
- * @return {Promise<Array<Boolean>>} 각각의 토큰들의 삭제 성공 여부가 저장된 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<Boolean>} 해당 디바이스에 알림을 보낸다는 요청을 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<string>} 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<Array<string>>} 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<string>} 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<Number>} 메시지 알림 전송에 실패한 기기의 수를 반환합니다. 오류가 발생하면 -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<boolean>} 메시지 알림 전송에 성공했으면 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<Number>} 토픽 구독에 실패한 기기의 수를 반환합니다. 오류가 발생하면 -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<Number>} 토픽 구독 해제에 실패한 기기의 수를 반환합니다. 오류가 발생하면 -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 <kmc7468@naver.com>
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 <devtaehyeon@gmail.com>
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 <kmc7468@naver.com>
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 <kmc7468@naver.com>
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 <devtaehyeon@gmail.com>
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 <kmc7468@naver.com>
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 <kmc7468@naver.com>
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 <kmc7468@naver.com>
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<Room>({
   }, // 참여 멤버 및 정산 여부
   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<Types.ObjectId>;
   /** 사용자가 참여한 방 중 완료된 방의 배열. */
   doneRoom?: Types.Array<Types.ObjectId>;
+  /** 계정 탈퇴 여부. */
   withdraw: boolean;
   /** 사용자의 전화번호. 2023 가을 이벤트부터 추가됨. */
   phoneNumber?: string;

From d8c691960feb234d21a80215487c33ce47bf4285 Mon Sep 17 00:00:00 2001
From: TaehyeonPark <devtaehyeon@gmail.com>
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 <devtaehyeon@gmail.com>
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 <kmc7468@naver.com>
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 <devtaehyeon@gmail.com>
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)