├── .env.example ├── .gitignore ├── README.md ├── package-lock.json ├── package.json ├── prettier.config.js ├── prisma ├── migrations │ ├── 20240125170057_init │ │ └── migration.sql │ ├── 20240125173028_change_timestamp_name_and_order │ │ └── migration.sql │ ├── 20240125174544_change_user_id_type_to_string │ │ └── migration.sql │ └── migration_lock.toml ├── schema.prisma └── seed.ts ├── src ├── controllers │ ├── auth.controller.ts │ ├── author.controller.ts │ ├── book.controller.ts │ └── profile.controller.ts ├── index.ts ├── middleware │ ├── auth-middleware.ts │ ├── error-handler.ts │ ├── not-found.ts │ └── requestLogger.ts ├── routes │ ├── auth.router.ts │ ├── author.router.ts │ ├── book.router.ts │ └── profile.router.ts ├── services │ ├── author.service.ts │ ├── book.service.ts │ └── user.service.ts ├── types │ ├── express.d.ts │ ├── general.ts │ └── zod.ts └── utils │ ├── HttpStatusCode.ts │ ├── bcryptHandler.ts │ ├── db.server.ts │ ├── jwtHandler.ts │ └── responseHandler.ts └── tsconfig.json /.env.example: -------------------------------------------------------------------------------- 1 | APP_ENV="developement" 2 | DATABASE_URL="mysql://username:password@localhost:3306/my_database_name" 3 | PORT=6000 4 | JWT_SECRET= 5 | ORIGIN=https://your-production-website.com -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .env 3 | dist -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Express Backend API With Prisma and TypeScript 2 | 3 | ## Author: 4 | 5 | [Younesse ElKars](#) - [LinkedIn](https://www.linkedin.com/in/younesse-elkars/) 6 | 7 | ## Description: 8 | 9 | An Express-based RESTful API with TypeScript and Prisma , managing both authentication and CRUD operations. 10 | 11 | ## Features 12 | 13 | - [x] MVC Pattern 14 | - [x] Prima as ORM 15 | - [x] Seed Script 16 | - [x] Authentication Routes (/Login , /Logout) 17 | - [x] JWT (HTTP-Only) 18 | - [x] Protected Routes 19 | - [x] Validation With Zod as middleware 20 | - [x] Error Handler middleware : 21 | - [x] Prisma Errors 22 | - [x] Zod Errors 23 | - [x] JWT Parsing Errors 24 | - [x] Route Not Found Error 25 | - [x] CRUD Operations ( Author , Books resources) 26 | - [x] Custom Handler for Response 27 | - [x] Custom HTTP Codes enum list 28 | - [x] CORS Middleware with custom config object 29 | 30 | ## Icoming Features 31 | 32 | - [ ] API Documentation Using Swagger 33 | 34 | - [ ] Sanitize queries against SQL Injection and XSS 35 | 36 | - [x] Middlewrae logger 37 | 38 | - [ ] Testing 39 | 40 | ## Getting Started 41 | 42 | #### Clone the repo: 43 | 44 | ```bash 45 | git clone https://github.com/Unesdevdev/express-prisma-ts.git 46 | ``` 47 | 48 | #### Install dependencies: 49 | 50 | ```bash 51 | npm install 52 | ``` 53 | 54 | #### Set environment variables: 55 | 56 | ```bash 57 | cp .env.example .env 58 | ``` 59 | 60 | ## Running Locally 61 | 62 | ```bash 63 | npm run dev 64 | ``` 65 | 66 | ## Running in Production 67 | 68 | ```bash 69 | npm run start 70 | ``` 71 | 72 | ## Contributions 73 | 74 | Contributions are welcome! Feel free to submit issues, feature requests, or pull requests to enhance the functionality or fix any issues. 75 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "express-prisma-ts-api", 3 | "version": "1.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "express-prisma-ts-api", 9 | "version": "1.0.0", 10 | "license": "ISC", 11 | "dependencies": { 12 | "@prisma/client": "^5.8.1", 13 | "@types/uuid": "^9.0.7", 14 | "bcryptjs": "^2.4.3", 15 | "cookie-parser": "^1.4.6", 16 | "cors": "^2.8.5", 17 | "dotenv": "^16.3.2", 18 | "esbuild": "^0.19.11", 19 | "express": "^4.18.2", 20 | "jsonwebtoken": "^9.0.2", 21 | "pino": "^9.5.0", 22 | "uuid": "^9.0.1", 23 | "zod": "^3.22.4" 24 | }, 25 | "devDependencies": { 26 | "@types/bcryptjs": "^2.4.6", 27 | "@types/cookie-parser": "^1.4.6", 28 | "@types/cors": "^2.8.17", 29 | "@types/dotenv": "^8.2.0", 30 | "@types/express": "^4.17.21", 31 | "@types/jsonwebtoken": "^9.0.5", 32 | "@types/node": "^20.11.5", 33 | "esbuild-register": "^3.5.0", 34 | "pino-pretty": "^13.0.0", 35 | "prisma": "^5.8.1", 36 | "ts-node-dev": "^2.0.0", 37 | "typescript": "^5.3.3" 38 | } 39 | }, 40 | "node_modules/@cspotcode/source-map-support": { 41 | "version": "0.8.1", 42 | "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", 43 | "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", 44 | "dev": true, 45 | "dependencies": { 46 | "@jridgewell/trace-mapping": "0.3.9" 47 | }, 48 | "engines": { 49 | "node": ">=12" 50 | } 51 | }, 52 | "node_modules/@esbuild/aix-ppc64": { 53 | "version": "0.19.11", 54 | "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.11.tgz", 55 | "integrity": "sha512-FnzU0LyE3ySQk7UntJO4+qIiQgI7KoODnZg5xzXIrFJlKd2P2gwHsHY4927xj9y5PJmJSzULiUCWmv7iWnNa7g==", 56 | "cpu": [ 57 | "ppc64" 58 | ], 59 | "optional": true, 60 | "os": [ 61 | "aix" 62 | ], 63 | "engines": { 64 | "node": ">=12" 65 | } 66 | }, 67 | "node_modules/@esbuild/android-arm": { 68 | "version": "0.19.11", 69 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.11.tgz", 70 | "integrity": "sha512-5OVapq0ClabvKvQ58Bws8+wkLCV+Rxg7tUVbo9xu034Nm536QTII4YzhaFriQ7rMrorfnFKUsArD2lqKbFY4vw==", 71 | "cpu": [ 72 | "arm" 73 | ], 74 | "optional": true, 75 | "os": [ 76 | "android" 77 | ], 78 | "engines": { 79 | "node": ">=12" 80 | } 81 | }, 82 | "node_modules/@esbuild/android-arm64": { 83 | "version": "0.19.11", 84 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.11.tgz", 85 | "integrity": "sha512-aiu7K/5JnLj//KOnOfEZ0D90obUkRzDMyqd/wNAUQ34m4YUPVhRZpnqKV9uqDGxT7cToSDnIHsGooyIczu9T+Q==", 86 | "cpu": [ 87 | "arm64" 88 | ], 89 | "optional": true, 90 | "os": [ 91 | "android" 92 | ], 93 | "engines": { 94 | "node": ">=12" 95 | } 96 | }, 97 | "node_modules/@esbuild/android-x64": { 98 | "version": "0.19.11", 99 | "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.11.tgz", 100 | "integrity": "sha512-eccxjlfGw43WYoY9QgB82SgGgDbibcqyDTlk3l3C0jOVHKxrjdc9CTwDUQd0vkvYg5um0OH+GpxYvp39r+IPOg==", 101 | "cpu": [ 102 | "x64" 103 | ], 104 | "optional": true, 105 | "os": [ 106 | "android" 107 | ], 108 | "engines": { 109 | "node": ">=12" 110 | } 111 | }, 112 | "node_modules/@esbuild/darwin-arm64": { 113 | "version": "0.19.11", 114 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.11.tgz", 115 | "integrity": "sha512-ETp87DRWuSt9KdDVkqSoKoLFHYTrkyz2+65fj9nfXsaV3bMhTCjtQfw3y+um88vGRKRiF7erPrh/ZuIdLUIVxQ==", 116 | "cpu": [ 117 | "arm64" 118 | ], 119 | "optional": true, 120 | "os": [ 121 | "darwin" 122 | ], 123 | "engines": { 124 | "node": ">=12" 125 | } 126 | }, 127 | "node_modules/@esbuild/darwin-x64": { 128 | "version": "0.19.11", 129 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.11.tgz", 130 | "integrity": "sha512-fkFUiS6IUK9WYUO/+22omwetaSNl5/A8giXvQlcinLIjVkxwTLSktbF5f/kJMftM2MJp9+fXqZ5ezS7+SALp4g==", 131 | "cpu": [ 132 | "x64" 133 | ], 134 | "optional": true, 135 | "os": [ 136 | "darwin" 137 | ], 138 | "engines": { 139 | "node": ">=12" 140 | } 141 | }, 142 | "node_modules/@esbuild/freebsd-arm64": { 143 | "version": "0.19.11", 144 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.11.tgz", 145 | "integrity": "sha512-lhoSp5K6bxKRNdXUtHoNc5HhbXVCS8V0iZmDvyWvYq9S5WSfTIHU2UGjcGt7UeS6iEYp9eeymIl5mJBn0yiuxA==", 146 | "cpu": [ 147 | "arm64" 148 | ], 149 | "optional": true, 150 | "os": [ 151 | "freebsd" 152 | ], 153 | "engines": { 154 | "node": ">=12" 155 | } 156 | }, 157 | "node_modules/@esbuild/freebsd-x64": { 158 | "version": "0.19.11", 159 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.11.tgz", 160 | "integrity": "sha512-JkUqn44AffGXitVI6/AbQdoYAq0TEullFdqcMY/PCUZ36xJ9ZJRtQabzMA+Vi7r78+25ZIBosLTOKnUXBSi1Kw==", 161 | "cpu": [ 162 | "x64" 163 | ], 164 | "optional": true, 165 | "os": [ 166 | "freebsd" 167 | ], 168 | "engines": { 169 | "node": ">=12" 170 | } 171 | }, 172 | "node_modules/@esbuild/linux-arm": { 173 | "version": "0.19.11", 174 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.11.tgz", 175 | "integrity": "sha512-3CRkr9+vCV2XJbjwgzjPtO8T0SZUmRZla+UL1jw+XqHZPkPgZiyWvbDvl9rqAN8Zl7qJF0O/9ycMtjU67HN9/Q==", 176 | "cpu": [ 177 | "arm" 178 | ], 179 | "optional": true, 180 | "os": [ 181 | "linux" 182 | ], 183 | "engines": { 184 | "node": ">=12" 185 | } 186 | }, 187 | "node_modules/@esbuild/linux-arm64": { 188 | "version": "0.19.11", 189 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.11.tgz", 190 | "integrity": "sha512-LneLg3ypEeveBSMuoa0kwMpCGmpu8XQUh+mL8XXwoYZ6Be2qBnVtcDI5azSvh7vioMDhoJFZzp9GWp9IWpYoUg==", 191 | "cpu": [ 192 | "arm64" 193 | ], 194 | "optional": true, 195 | "os": [ 196 | "linux" 197 | ], 198 | "engines": { 199 | "node": ">=12" 200 | } 201 | }, 202 | "node_modules/@esbuild/linux-ia32": { 203 | "version": "0.19.11", 204 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.11.tgz", 205 | "integrity": "sha512-caHy++CsD8Bgq2V5CodbJjFPEiDPq8JJmBdeyZ8GWVQMjRD0sU548nNdwPNvKjVpamYYVL40AORekgfIubwHoA==", 206 | "cpu": [ 207 | "ia32" 208 | ], 209 | "optional": true, 210 | "os": [ 211 | "linux" 212 | ], 213 | "engines": { 214 | "node": ">=12" 215 | } 216 | }, 217 | "node_modules/@esbuild/linux-loong64": { 218 | "version": "0.19.11", 219 | "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.11.tgz", 220 | "integrity": "sha512-ppZSSLVpPrwHccvC6nQVZaSHlFsvCQyjnvirnVjbKSHuE5N24Yl8F3UwYUUR1UEPaFObGD2tSvVKbvR+uT1Nrg==", 221 | "cpu": [ 222 | "loong64" 223 | ], 224 | "optional": true, 225 | "os": [ 226 | "linux" 227 | ], 228 | "engines": { 229 | "node": ">=12" 230 | } 231 | }, 232 | "node_modules/@esbuild/linux-mips64el": { 233 | "version": "0.19.11", 234 | "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.11.tgz", 235 | "integrity": "sha512-B5x9j0OgjG+v1dF2DkH34lr+7Gmv0kzX6/V0afF41FkPMMqaQ77pH7CrhWeR22aEeHKaeZVtZ6yFwlxOKPVFyg==", 236 | "cpu": [ 237 | "mips64el" 238 | ], 239 | "optional": true, 240 | "os": [ 241 | "linux" 242 | ], 243 | "engines": { 244 | "node": ">=12" 245 | } 246 | }, 247 | "node_modules/@esbuild/linux-ppc64": { 248 | "version": "0.19.11", 249 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.11.tgz", 250 | "integrity": "sha512-MHrZYLeCG8vXblMetWyttkdVRjQlQUb/oMgBNurVEnhj4YWOr4G5lmBfZjHYQHHN0g6yDmCAQRR8MUHldvvRDA==", 251 | "cpu": [ 252 | "ppc64" 253 | ], 254 | "optional": true, 255 | "os": [ 256 | "linux" 257 | ], 258 | "engines": { 259 | "node": ">=12" 260 | } 261 | }, 262 | "node_modules/@esbuild/linux-riscv64": { 263 | "version": "0.19.11", 264 | "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.11.tgz", 265 | "integrity": "sha512-f3DY++t94uVg141dozDu4CCUkYW+09rWtaWfnb3bqe4w5NqmZd6nPVBm+qbz7WaHZCoqXqHz5p6CM6qv3qnSSQ==", 266 | "cpu": [ 267 | "riscv64" 268 | ], 269 | "optional": true, 270 | "os": [ 271 | "linux" 272 | ], 273 | "engines": { 274 | "node": ">=12" 275 | } 276 | }, 277 | "node_modules/@esbuild/linux-s390x": { 278 | "version": "0.19.11", 279 | "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.11.tgz", 280 | "integrity": "sha512-A5xdUoyWJHMMlcSMcPGVLzYzpcY8QP1RtYzX5/bS4dvjBGVxdhuiYyFwp7z74ocV7WDc0n1harxmpq2ePOjI0Q==", 281 | "cpu": [ 282 | "s390x" 283 | ], 284 | "optional": true, 285 | "os": [ 286 | "linux" 287 | ], 288 | "engines": { 289 | "node": ">=12" 290 | } 291 | }, 292 | "node_modules/@esbuild/linux-x64": { 293 | "version": "0.19.11", 294 | "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.11.tgz", 295 | "integrity": "sha512-grbyMlVCvJSfxFQUndw5mCtWs5LO1gUlwP4CDi4iJBbVpZcqLVT29FxgGuBJGSzyOxotFG4LoO5X+M1350zmPA==", 296 | "cpu": [ 297 | "x64" 298 | ], 299 | "optional": true, 300 | "os": [ 301 | "linux" 302 | ], 303 | "engines": { 304 | "node": ">=12" 305 | } 306 | }, 307 | "node_modules/@esbuild/netbsd-x64": { 308 | "version": "0.19.11", 309 | "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.11.tgz", 310 | "integrity": "sha512-13jvrQZJc3P230OhU8xgwUnDeuC/9egsjTkXN49b3GcS5BKvJqZn86aGM8W9pd14Kd+u7HuFBMVtrNGhh6fHEQ==", 311 | "cpu": [ 312 | "x64" 313 | ], 314 | "optional": true, 315 | "os": [ 316 | "netbsd" 317 | ], 318 | "engines": { 319 | "node": ">=12" 320 | } 321 | }, 322 | "node_modules/@esbuild/openbsd-x64": { 323 | "version": "0.19.11", 324 | "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.11.tgz", 325 | "integrity": "sha512-ysyOGZuTp6SNKPE11INDUeFVVQFrhcNDVUgSQVDzqsqX38DjhPEPATpid04LCoUr2WXhQTEZ8ct/EgJCUDpyNw==", 326 | "cpu": [ 327 | "x64" 328 | ], 329 | "optional": true, 330 | "os": [ 331 | "openbsd" 332 | ], 333 | "engines": { 334 | "node": ">=12" 335 | } 336 | }, 337 | "node_modules/@esbuild/sunos-x64": { 338 | "version": "0.19.11", 339 | "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.11.tgz", 340 | "integrity": "sha512-Hf+Sad9nVwvtxy4DXCZQqLpgmRTQqyFyhT3bZ4F2XlJCjxGmRFF0Shwn9rzhOYRB61w9VMXUkxlBy56dk9JJiQ==", 341 | "cpu": [ 342 | "x64" 343 | ], 344 | "optional": true, 345 | "os": [ 346 | "sunos" 347 | ], 348 | "engines": { 349 | "node": ">=12" 350 | } 351 | }, 352 | "node_modules/@esbuild/win32-arm64": { 353 | "version": "0.19.11", 354 | "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.11.tgz", 355 | "integrity": "sha512-0P58Sbi0LctOMOQbpEOvOL44Ne0sqbS0XWHMvvrg6NE5jQ1xguCSSw9jQeUk2lfrXYsKDdOe6K+oZiwKPilYPQ==", 356 | "cpu": [ 357 | "arm64" 358 | ], 359 | "optional": true, 360 | "os": [ 361 | "win32" 362 | ], 363 | "engines": { 364 | "node": ">=12" 365 | } 366 | }, 367 | "node_modules/@esbuild/win32-ia32": { 368 | "version": "0.19.11", 369 | "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.11.tgz", 370 | "integrity": "sha512-6YOrWS+sDJDmshdBIQU+Uoyh7pQKrdykdefC1avn76ss5c+RN6gut3LZA4E2cH5xUEp5/cA0+YxRaVtRAb0xBg==", 371 | "cpu": [ 372 | "ia32" 373 | ], 374 | "optional": true, 375 | "os": [ 376 | "win32" 377 | ], 378 | "engines": { 379 | "node": ">=12" 380 | } 381 | }, 382 | "node_modules/@esbuild/win32-x64": { 383 | "version": "0.19.11", 384 | "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.11.tgz", 385 | "integrity": "sha512-vfkhltrjCAb603XaFhqhAF4LGDi2M4OrCRrFusyQ+iTLQ/o60QQXxc9cZC/FFpihBI9N1Grn6SMKVJ4KP7Fuiw==", 386 | "cpu": [ 387 | "x64" 388 | ], 389 | "optional": true, 390 | "os": [ 391 | "win32" 392 | ], 393 | "engines": { 394 | "node": ">=12" 395 | } 396 | }, 397 | "node_modules/@jridgewell/resolve-uri": { 398 | "version": "3.1.1", 399 | "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", 400 | "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", 401 | "dev": true, 402 | "engines": { 403 | "node": ">=6.0.0" 404 | } 405 | }, 406 | "node_modules/@jridgewell/sourcemap-codec": { 407 | "version": "1.4.15", 408 | "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", 409 | "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", 410 | "dev": true 411 | }, 412 | "node_modules/@jridgewell/trace-mapping": { 413 | "version": "0.3.9", 414 | "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", 415 | "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", 416 | "dev": true, 417 | "dependencies": { 418 | "@jridgewell/resolve-uri": "^3.0.3", 419 | "@jridgewell/sourcemap-codec": "^1.4.10" 420 | } 421 | }, 422 | "node_modules/@prisma/client": { 423 | "version": "5.8.1", 424 | "resolved": "https://registry.npmjs.org/@prisma/client/-/client-5.8.1.tgz", 425 | "integrity": "sha512-xQtMPfbIwLlbm0VVIVQY2yqQVOxPwRQhvIp7Z3m2900g1bu/zRHKhYZJQWELqmjl6d8YwBy0K2NvMqh47v1ubw==", 426 | "hasInstallScript": true, 427 | "engines": { 428 | "node": ">=16.13" 429 | }, 430 | "peerDependencies": { 431 | "prisma": "*" 432 | }, 433 | "peerDependenciesMeta": { 434 | "prisma": { 435 | "optional": true 436 | } 437 | } 438 | }, 439 | "node_modules/@prisma/debug": { 440 | "version": "5.8.1", 441 | "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-5.8.1.tgz", 442 | "integrity": "sha512-tjuw7eA0Us3T42jx9AmAgL58rzwzpFGYc3R7Y4Ip75EBYrKMBA1YihuWMcBC92ILmjlQ/u3p8VxcIE0hr+fZfg==", 443 | "devOptional": true 444 | }, 445 | "node_modules/@prisma/engines": { 446 | "version": "5.8.1", 447 | "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-5.8.1.tgz", 448 | "integrity": "sha512-TJgYLRrZr56uhqcXO4GmP5be+zjCIHtLDK20Cnfg+o9d905hsN065QOL+3Z0zQAy6YD31Ol4u2kzSfRmbJv/uA==", 449 | "devOptional": true, 450 | "hasInstallScript": true, 451 | "dependencies": { 452 | "@prisma/debug": "5.8.1", 453 | "@prisma/engines-version": "5.8.1-1.78caf6feeaed953168c64e15a249c3e9a033ebe2", 454 | "@prisma/fetch-engine": "5.8.1", 455 | "@prisma/get-platform": "5.8.1" 456 | } 457 | }, 458 | "node_modules/@prisma/engines-version": { 459 | "version": "5.8.1-1.78caf6feeaed953168c64e15a249c3e9a033ebe2", 460 | "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-5.8.1-1.78caf6feeaed953168c64e15a249c3e9a033ebe2.tgz", 461 | "integrity": "sha512-f5C3JM3l9yhGr3cr4FMqWloFaSCpNpMi58Om22rjD2DOz3owci2mFdFXMgnAGazFPKrCbbEhcxdsRfspEYRoFQ==", 462 | "devOptional": true 463 | }, 464 | "node_modules/@prisma/fetch-engine": { 465 | "version": "5.8.1", 466 | "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-5.8.1.tgz", 467 | "integrity": "sha512-+bgjjoSFa6uYEbAPlklfoVSStOEfcpheOjoBoNsNNSQdSzcwE2nM4Q0prun0+P8/0sCHo18JZ9xqa8gObvgOUw==", 468 | "devOptional": true, 469 | "dependencies": { 470 | "@prisma/debug": "5.8.1", 471 | "@prisma/engines-version": "5.8.1-1.78caf6feeaed953168c64e15a249c3e9a033ebe2", 472 | "@prisma/get-platform": "5.8.1" 473 | } 474 | }, 475 | "node_modules/@prisma/get-platform": { 476 | "version": "5.8.1", 477 | "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-5.8.1.tgz", 478 | "integrity": "sha512-wnA+6HTFcY+tkykMokix9GiAkaauPC5W/gg0O5JB0J8tCTNWrqpnQ7AsaGRfkYUbeOIioh6woDjQrGTTRf1Zag==", 479 | "devOptional": true, 480 | "dependencies": { 481 | "@prisma/debug": "5.8.1" 482 | } 483 | }, 484 | "node_modules/@tsconfig/node10": { 485 | "version": "1.0.9", 486 | "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", 487 | "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", 488 | "dev": true 489 | }, 490 | "node_modules/@tsconfig/node12": { 491 | "version": "1.0.11", 492 | "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", 493 | "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", 494 | "dev": true 495 | }, 496 | "node_modules/@tsconfig/node14": { 497 | "version": "1.0.3", 498 | "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", 499 | "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", 500 | "dev": true 501 | }, 502 | "node_modules/@tsconfig/node16": { 503 | "version": "1.0.4", 504 | "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", 505 | "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", 506 | "dev": true 507 | }, 508 | "node_modules/@types/bcryptjs": { 509 | "version": "2.4.6", 510 | "resolved": "https://registry.npmjs.org/@types/bcryptjs/-/bcryptjs-2.4.6.tgz", 511 | "integrity": "sha512-9xlo6R2qDs5uixm0bcIqCeMCE6HiQsIyel9KQySStiyqNl2tnj2mP3DX1Nf56MD6KMenNNlBBsy3LJ7gUEQPXQ==", 512 | "dev": true 513 | }, 514 | "node_modules/@types/body-parser": { 515 | "version": "1.19.5", 516 | "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", 517 | "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", 518 | "dev": true, 519 | "dependencies": { 520 | "@types/connect": "*", 521 | "@types/node": "*" 522 | } 523 | }, 524 | "node_modules/@types/connect": { 525 | "version": "3.4.38", 526 | "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", 527 | "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", 528 | "dev": true, 529 | "dependencies": { 530 | "@types/node": "*" 531 | } 532 | }, 533 | "node_modules/@types/cookie-parser": { 534 | "version": "1.4.6", 535 | "resolved": "https://registry.npmjs.org/@types/cookie-parser/-/cookie-parser-1.4.6.tgz", 536 | "integrity": "sha512-KoooCrD56qlLskXPLGUiJxOMnv5l/8m7cQD2OxJ73NPMhuSz9PmvwRD6EpjDyKBVrdJDdQ4bQK7JFNHnNmax0w==", 537 | "dev": true, 538 | "dependencies": { 539 | "@types/express": "*" 540 | } 541 | }, 542 | "node_modules/@types/cors": { 543 | "version": "2.8.17", 544 | "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz", 545 | "integrity": "sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==", 546 | "dev": true, 547 | "dependencies": { 548 | "@types/node": "*" 549 | } 550 | }, 551 | "node_modules/@types/dotenv": { 552 | "version": "8.2.0", 553 | "resolved": "https://registry.npmjs.org/@types/dotenv/-/dotenv-8.2.0.tgz", 554 | "integrity": "sha512-ylSC9GhfRH7m1EUXBXofhgx4lUWmFeQDINW5oLuS+gxWdfUeW4zJdeVTYVkexEW+e2VUvlZR2kGnGGipAWR7kw==", 555 | "deprecated": "This is a stub types definition. dotenv provides its own type definitions, so you do not need this installed.", 556 | "dev": true, 557 | "dependencies": { 558 | "dotenv": "*" 559 | } 560 | }, 561 | "node_modules/@types/express": { 562 | "version": "4.17.21", 563 | "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", 564 | "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==", 565 | "dev": true, 566 | "dependencies": { 567 | "@types/body-parser": "*", 568 | "@types/express-serve-static-core": "^4.17.33", 569 | "@types/qs": "*", 570 | "@types/serve-static": "*" 571 | } 572 | }, 573 | "node_modules/@types/express-serve-static-core": { 574 | "version": "4.17.41", 575 | "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.41.tgz", 576 | "integrity": "sha512-OaJ7XLaelTgrvlZD8/aa0vvvxZdUmlCn6MtWeB7TkiKW70BQLc9XEPpDLPdbo52ZhXUCrznlWdCHWxJWtdyajA==", 577 | "dev": true, 578 | "dependencies": { 579 | "@types/node": "*", 580 | "@types/qs": "*", 581 | "@types/range-parser": "*", 582 | "@types/send": "*" 583 | } 584 | }, 585 | "node_modules/@types/http-errors": { 586 | "version": "2.0.4", 587 | "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", 588 | "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", 589 | "dev": true 590 | }, 591 | "node_modules/@types/jsonwebtoken": { 592 | "version": "9.0.5", 593 | "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.5.tgz", 594 | "integrity": "sha512-VRLSGzik+Unrup6BsouBeHsf4d1hOEgYWTm/7Nmw1sXoN1+tRly/Gy/po3yeahnP4jfnQWWAhQAqcNfH7ngOkA==", 595 | "dev": true, 596 | "dependencies": { 597 | "@types/node": "*" 598 | } 599 | }, 600 | "node_modules/@types/mime": { 601 | "version": "1.3.5", 602 | "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", 603 | "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", 604 | "dev": true 605 | }, 606 | "node_modules/@types/node": { 607 | "version": "20.11.5", 608 | "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.5.tgz", 609 | "integrity": "sha512-g557vgQjUUfN76MZAN/dt1z3dzcUsimuysco0KeluHgrPdJXkP/XdAURgyO2W9fZWHRtRBiVKzKn8vyOAwlG+w==", 610 | "dev": true, 611 | "dependencies": { 612 | "undici-types": "~5.26.4" 613 | } 614 | }, 615 | "node_modules/@types/qs": { 616 | "version": "6.9.11", 617 | "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.11.tgz", 618 | "integrity": "sha512-oGk0gmhnEJK4Yyk+oI7EfXsLayXatCWPHary1MtcmbAifkobT9cM9yutG/hZKIseOU0MqbIwQ/u2nn/Gb+ltuQ==", 619 | "dev": true 620 | }, 621 | "node_modules/@types/range-parser": { 622 | "version": "1.2.7", 623 | "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", 624 | "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", 625 | "dev": true 626 | }, 627 | "node_modules/@types/send": { 628 | "version": "0.17.4", 629 | "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", 630 | "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", 631 | "dev": true, 632 | "dependencies": { 633 | "@types/mime": "^1", 634 | "@types/node": "*" 635 | } 636 | }, 637 | "node_modules/@types/serve-static": { 638 | "version": "1.15.5", 639 | "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.5.tgz", 640 | "integrity": "sha512-PDRk21MnK70hja/YF8AHfC7yIsiQHn1rcXx7ijCFBX/k+XQJhQT/gw3xekXKJvx+5SXaMMS8oqQy09Mzvz2TuQ==", 641 | "dev": true, 642 | "dependencies": { 643 | "@types/http-errors": "*", 644 | "@types/mime": "*", 645 | "@types/node": "*" 646 | } 647 | }, 648 | "node_modules/@types/strip-bom": { 649 | "version": "3.0.0", 650 | "resolved": "https://registry.npmjs.org/@types/strip-bom/-/strip-bom-3.0.0.tgz", 651 | "integrity": "sha512-xevGOReSYGM7g/kUBZzPqCrR/KYAo+F0yiPc85WFTJa0MSLtyFTVTU6cJu/aV4mid7IffDIWqo69THF2o4JiEQ==", 652 | "dev": true 653 | }, 654 | "node_modules/@types/strip-json-comments": { 655 | "version": "0.0.30", 656 | "resolved": "https://registry.npmjs.org/@types/strip-json-comments/-/strip-json-comments-0.0.30.tgz", 657 | "integrity": "sha512-7NQmHra/JILCd1QqpSzl8+mJRc8ZHz3uDm8YV1Ks9IhK0epEiTw8aIErbvH9PI+6XbqhyIQy3462nEsn7UVzjQ==", 658 | "dev": true 659 | }, 660 | "node_modules/@types/uuid": { 661 | "version": "9.0.7", 662 | "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.7.tgz", 663 | "integrity": "sha512-WUtIVRUZ9i5dYXefDEAI7sh9/O7jGvHg7Df/5O/gtH3Yabe5odI3UWopVR1qbPXQtvOxWu3mM4XxlYeZtMWF4g==" 664 | }, 665 | "node_modules/accepts": { 666 | "version": "1.3.8", 667 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", 668 | "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", 669 | "dependencies": { 670 | "mime-types": "~2.1.34", 671 | "negotiator": "0.6.3" 672 | }, 673 | "engines": { 674 | "node": ">= 0.6" 675 | } 676 | }, 677 | "node_modules/acorn": { 678 | "version": "8.11.3", 679 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", 680 | "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", 681 | "dev": true, 682 | "bin": { 683 | "acorn": "bin/acorn" 684 | }, 685 | "engines": { 686 | "node": ">=0.4.0" 687 | } 688 | }, 689 | "node_modules/acorn-walk": { 690 | "version": "8.3.2", 691 | "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz", 692 | "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==", 693 | "dev": true, 694 | "engines": { 695 | "node": ">=0.4.0" 696 | } 697 | }, 698 | "node_modules/anymatch": { 699 | "version": "3.1.3", 700 | "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", 701 | "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", 702 | "dev": true, 703 | "dependencies": { 704 | "normalize-path": "^3.0.0", 705 | "picomatch": "^2.0.4" 706 | }, 707 | "engines": { 708 | "node": ">= 8" 709 | } 710 | }, 711 | "node_modules/arg": { 712 | "version": "4.1.3", 713 | "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", 714 | "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", 715 | "dev": true 716 | }, 717 | "node_modules/array-flatten": { 718 | "version": "1.1.1", 719 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", 720 | "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" 721 | }, 722 | "node_modules/atomic-sleep": { 723 | "version": "1.0.0", 724 | "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", 725 | "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==", 726 | "engines": { 727 | "node": ">=8.0.0" 728 | } 729 | }, 730 | "node_modules/balanced-match": { 731 | "version": "1.0.2", 732 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", 733 | "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", 734 | "dev": true 735 | }, 736 | "node_modules/bcryptjs": { 737 | "version": "2.4.3", 738 | "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz", 739 | "integrity": "sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ==" 740 | }, 741 | "node_modules/binary-extensions": { 742 | "version": "2.2.0", 743 | "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", 744 | "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", 745 | "dev": true, 746 | "engines": { 747 | "node": ">=8" 748 | } 749 | }, 750 | "node_modules/body-parser": { 751 | "version": "1.20.1", 752 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", 753 | "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", 754 | "dependencies": { 755 | "bytes": "3.1.2", 756 | "content-type": "~1.0.4", 757 | "debug": "2.6.9", 758 | "depd": "2.0.0", 759 | "destroy": "1.2.0", 760 | "http-errors": "2.0.0", 761 | "iconv-lite": "0.4.24", 762 | "on-finished": "2.4.1", 763 | "qs": "6.11.0", 764 | "raw-body": "2.5.1", 765 | "type-is": "~1.6.18", 766 | "unpipe": "1.0.0" 767 | }, 768 | "engines": { 769 | "node": ">= 0.8", 770 | "npm": "1.2.8000 || >= 1.4.16" 771 | } 772 | }, 773 | "node_modules/brace-expansion": { 774 | "version": "1.1.11", 775 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 776 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 777 | "dev": true, 778 | "dependencies": { 779 | "balanced-match": "^1.0.0", 780 | "concat-map": "0.0.1" 781 | } 782 | }, 783 | "node_modules/braces": { 784 | "version": "3.0.2", 785 | "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", 786 | "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", 787 | "dev": true, 788 | "dependencies": { 789 | "fill-range": "^7.0.1" 790 | }, 791 | "engines": { 792 | "node": ">=8" 793 | } 794 | }, 795 | "node_modules/buffer-equal-constant-time": { 796 | "version": "1.0.1", 797 | "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", 798 | "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" 799 | }, 800 | "node_modules/buffer-from": { 801 | "version": "1.1.2", 802 | "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", 803 | "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", 804 | "dev": true 805 | }, 806 | "node_modules/bytes": { 807 | "version": "3.1.2", 808 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", 809 | "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", 810 | "engines": { 811 | "node": ">= 0.8" 812 | } 813 | }, 814 | "node_modules/call-bind": { 815 | "version": "1.0.5", 816 | "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz", 817 | "integrity": "sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==", 818 | "dependencies": { 819 | "function-bind": "^1.1.2", 820 | "get-intrinsic": "^1.2.1", 821 | "set-function-length": "^1.1.1" 822 | }, 823 | "funding": { 824 | "url": "https://github.com/sponsors/ljharb" 825 | } 826 | }, 827 | "node_modules/chokidar": { 828 | "version": "3.5.3", 829 | "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", 830 | "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", 831 | "dev": true, 832 | "funding": [ 833 | { 834 | "type": "individual", 835 | "url": "https://paulmillr.com/funding/" 836 | } 837 | ], 838 | "dependencies": { 839 | "anymatch": "~3.1.2", 840 | "braces": "~3.0.2", 841 | "glob-parent": "~5.1.2", 842 | "is-binary-path": "~2.1.0", 843 | "is-glob": "~4.0.1", 844 | "normalize-path": "~3.0.0", 845 | "readdirp": "~3.6.0" 846 | }, 847 | "engines": { 848 | "node": ">= 8.10.0" 849 | }, 850 | "optionalDependencies": { 851 | "fsevents": "~2.3.2" 852 | } 853 | }, 854 | "node_modules/colorette": { 855 | "version": "2.0.20", 856 | "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", 857 | "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", 858 | "dev": true 859 | }, 860 | "node_modules/concat-map": { 861 | "version": "0.0.1", 862 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 863 | "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", 864 | "dev": true 865 | }, 866 | "node_modules/content-disposition": { 867 | "version": "0.5.4", 868 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", 869 | "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", 870 | "dependencies": { 871 | "safe-buffer": "5.2.1" 872 | }, 873 | "engines": { 874 | "node": ">= 0.6" 875 | } 876 | }, 877 | "node_modules/content-type": { 878 | "version": "1.0.5", 879 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", 880 | "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", 881 | "engines": { 882 | "node": ">= 0.6" 883 | } 884 | }, 885 | "node_modules/cookie": { 886 | "version": "0.5.0", 887 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", 888 | "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", 889 | "engines": { 890 | "node": ">= 0.6" 891 | } 892 | }, 893 | "node_modules/cookie-parser": { 894 | "version": "1.4.6", 895 | "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.6.tgz", 896 | "integrity": "sha512-z3IzaNjdwUC2olLIB5/ITd0/setiaFMLYiZJle7xg5Fe9KWAceil7xszYfHHBtDFYLSgJduS2Ty0P1uJdPDJeA==", 897 | "dependencies": { 898 | "cookie": "0.4.1", 899 | "cookie-signature": "1.0.6" 900 | }, 901 | "engines": { 902 | "node": ">= 0.8.0" 903 | } 904 | }, 905 | "node_modules/cookie-parser/node_modules/cookie": { 906 | "version": "0.4.1", 907 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", 908 | "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==", 909 | "engines": { 910 | "node": ">= 0.6" 911 | } 912 | }, 913 | "node_modules/cookie-signature": { 914 | "version": "1.0.6", 915 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", 916 | "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" 917 | }, 918 | "node_modules/cors": { 919 | "version": "2.8.5", 920 | "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", 921 | "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", 922 | "dependencies": { 923 | "object-assign": "^4", 924 | "vary": "^1" 925 | }, 926 | "engines": { 927 | "node": ">= 0.10" 928 | } 929 | }, 930 | "node_modules/create-require": { 931 | "version": "1.1.1", 932 | "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", 933 | "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", 934 | "dev": true 935 | }, 936 | "node_modules/dateformat": { 937 | "version": "4.6.3", 938 | "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz", 939 | "integrity": "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==", 940 | "dev": true, 941 | "engines": { 942 | "node": "*" 943 | } 944 | }, 945 | "node_modules/debug": { 946 | "version": "2.6.9", 947 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 948 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 949 | "dependencies": { 950 | "ms": "2.0.0" 951 | } 952 | }, 953 | "node_modules/define-data-property": { 954 | "version": "1.1.1", 955 | "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.1.tgz", 956 | "integrity": "sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==", 957 | "dependencies": { 958 | "get-intrinsic": "^1.2.1", 959 | "gopd": "^1.0.1", 960 | "has-property-descriptors": "^1.0.0" 961 | }, 962 | "engines": { 963 | "node": ">= 0.4" 964 | } 965 | }, 966 | "node_modules/depd": { 967 | "version": "2.0.0", 968 | "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", 969 | "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", 970 | "engines": { 971 | "node": ">= 0.8" 972 | } 973 | }, 974 | "node_modules/destroy": { 975 | "version": "1.2.0", 976 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", 977 | "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", 978 | "engines": { 979 | "node": ">= 0.8", 980 | "npm": "1.2.8000 || >= 1.4.16" 981 | } 982 | }, 983 | "node_modules/diff": { 984 | "version": "4.0.2", 985 | "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", 986 | "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", 987 | "dev": true, 988 | "engines": { 989 | "node": ">=0.3.1" 990 | } 991 | }, 992 | "node_modules/dotenv": { 993 | "version": "16.3.2", 994 | "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.2.tgz", 995 | "integrity": "sha512-HTlk5nmhkm8F6JcdXvHIzaorzCoziNQT9mGxLPVXW8wJF1TiGSL60ZGB4gHWabHOaMmWmhvk2/lPHfnBiT78AQ==", 996 | "engines": { 997 | "node": ">=12" 998 | }, 999 | "funding": { 1000 | "url": "https://github.com/motdotla/dotenv?sponsor=1" 1001 | } 1002 | }, 1003 | "node_modules/dynamic-dedupe": { 1004 | "version": "0.3.0", 1005 | "resolved": "https://registry.npmjs.org/dynamic-dedupe/-/dynamic-dedupe-0.3.0.tgz", 1006 | "integrity": "sha512-ssuANeD+z97meYOqd50e04Ze5qp4bPqo8cCkI4TRjZkzAUgIDTrXV1R8QCdINpiI+hw14+rYazvTRdQrz0/rFQ==", 1007 | "dev": true, 1008 | "dependencies": { 1009 | "xtend": "^4.0.0" 1010 | } 1011 | }, 1012 | "node_modules/ecdsa-sig-formatter": { 1013 | "version": "1.0.11", 1014 | "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", 1015 | "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", 1016 | "dependencies": { 1017 | "safe-buffer": "^5.0.1" 1018 | } 1019 | }, 1020 | "node_modules/ee-first": { 1021 | "version": "1.1.1", 1022 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 1023 | "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" 1024 | }, 1025 | "node_modules/encodeurl": { 1026 | "version": "1.0.2", 1027 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", 1028 | "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", 1029 | "engines": { 1030 | "node": ">= 0.8" 1031 | } 1032 | }, 1033 | "node_modules/end-of-stream": { 1034 | "version": "1.4.4", 1035 | "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", 1036 | "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", 1037 | "dev": true, 1038 | "dependencies": { 1039 | "once": "^1.4.0" 1040 | } 1041 | }, 1042 | "node_modules/esbuild": { 1043 | "version": "0.19.11", 1044 | "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.11.tgz", 1045 | "integrity": "sha512-HJ96Hev2hX/6i5cDVwcqiJBBtuo9+FeIJOtZ9W1kA5M6AMJRHUZlpYZ1/SbEwtO0ioNAW8rUooVpC/WehY2SfA==", 1046 | "hasInstallScript": true, 1047 | "bin": { 1048 | "esbuild": "bin/esbuild" 1049 | }, 1050 | "engines": { 1051 | "node": ">=12" 1052 | }, 1053 | "optionalDependencies": { 1054 | "@esbuild/aix-ppc64": "0.19.11", 1055 | "@esbuild/android-arm": "0.19.11", 1056 | "@esbuild/android-arm64": "0.19.11", 1057 | "@esbuild/android-x64": "0.19.11", 1058 | "@esbuild/darwin-arm64": "0.19.11", 1059 | "@esbuild/darwin-x64": "0.19.11", 1060 | "@esbuild/freebsd-arm64": "0.19.11", 1061 | "@esbuild/freebsd-x64": "0.19.11", 1062 | "@esbuild/linux-arm": "0.19.11", 1063 | "@esbuild/linux-arm64": "0.19.11", 1064 | "@esbuild/linux-ia32": "0.19.11", 1065 | "@esbuild/linux-loong64": "0.19.11", 1066 | "@esbuild/linux-mips64el": "0.19.11", 1067 | "@esbuild/linux-ppc64": "0.19.11", 1068 | "@esbuild/linux-riscv64": "0.19.11", 1069 | "@esbuild/linux-s390x": "0.19.11", 1070 | "@esbuild/linux-x64": "0.19.11", 1071 | "@esbuild/netbsd-x64": "0.19.11", 1072 | "@esbuild/openbsd-x64": "0.19.11", 1073 | "@esbuild/sunos-x64": "0.19.11", 1074 | "@esbuild/win32-arm64": "0.19.11", 1075 | "@esbuild/win32-ia32": "0.19.11", 1076 | "@esbuild/win32-x64": "0.19.11" 1077 | } 1078 | }, 1079 | "node_modules/esbuild-register": { 1080 | "version": "3.5.0", 1081 | "resolved": "https://registry.npmjs.org/esbuild-register/-/esbuild-register-3.5.0.tgz", 1082 | "integrity": "sha512-+4G/XmakeBAsvJuDugJvtyF1x+XJT4FMocynNpxrvEBViirpfUn2PgNpCHedfWhF4WokNsO/OvMKrmJOIJsI5A==", 1083 | "dev": true, 1084 | "dependencies": { 1085 | "debug": "^4.3.4" 1086 | }, 1087 | "peerDependencies": { 1088 | "esbuild": ">=0.12 <1" 1089 | } 1090 | }, 1091 | "node_modules/esbuild-register/node_modules/debug": { 1092 | "version": "4.3.4", 1093 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", 1094 | "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", 1095 | "dev": true, 1096 | "dependencies": { 1097 | "ms": "2.1.2" 1098 | }, 1099 | "engines": { 1100 | "node": ">=6.0" 1101 | }, 1102 | "peerDependenciesMeta": { 1103 | "supports-color": { 1104 | "optional": true 1105 | } 1106 | } 1107 | }, 1108 | "node_modules/esbuild-register/node_modules/ms": { 1109 | "version": "2.1.2", 1110 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 1111 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", 1112 | "dev": true 1113 | }, 1114 | "node_modules/escape-html": { 1115 | "version": "1.0.3", 1116 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 1117 | "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" 1118 | }, 1119 | "node_modules/etag": { 1120 | "version": "1.8.1", 1121 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", 1122 | "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", 1123 | "engines": { 1124 | "node": ">= 0.6" 1125 | } 1126 | }, 1127 | "node_modules/express": { 1128 | "version": "4.18.2", 1129 | "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", 1130 | "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", 1131 | "dependencies": { 1132 | "accepts": "~1.3.8", 1133 | "array-flatten": "1.1.1", 1134 | "body-parser": "1.20.1", 1135 | "content-disposition": "0.5.4", 1136 | "content-type": "~1.0.4", 1137 | "cookie": "0.5.0", 1138 | "cookie-signature": "1.0.6", 1139 | "debug": "2.6.9", 1140 | "depd": "2.0.0", 1141 | "encodeurl": "~1.0.2", 1142 | "escape-html": "~1.0.3", 1143 | "etag": "~1.8.1", 1144 | "finalhandler": "1.2.0", 1145 | "fresh": "0.5.2", 1146 | "http-errors": "2.0.0", 1147 | "merge-descriptors": "1.0.1", 1148 | "methods": "~1.1.2", 1149 | "on-finished": "2.4.1", 1150 | "parseurl": "~1.3.3", 1151 | "path-to-regexp": "0.1.7", 1152 | "proxy-addr": "~2.0.7", 1153 | "qs": "6.11.0", 1154 | "range-parser": "~1.2.1", 1155 | "safe-buffer": "5.2.1", 1156 | "send": "0.18.0", 1157 | "serve-static": "1.15.0", 1158 | "setprototypeof": "1.2.0", 1159 | "statuses": "2.0.1", 1160 | "type-is": "~1.6.18", 1161 | "utils-merge": "1.0.1", 1162 | "vary": "~1.1.2" 1163 | }, 1164 | "engines": { 1165 | "node": ">= 0.10.0" 1166 | } 1167 | }, 1168 | "node_modules/fast-copy": { 1169 | "version": "3.0.2", 1170 | "resolved": "https://registry.npmjs.org/fast-copy/-/fast-copy-3.0.2.tgz", 1171 | "integrity": "sha512-dl0O9Vhju8IrcLndv2eU4ldt1ftXMqqfgN4H1cpmGV7P6jeB9FwpN9a2c8DPGE1Ys88rNUJVYDHq73CGAGOPfQ==", 1172 | "dev": true 1173 | }, 1174 | "node_modules/fast-redact": { 1175 | "version": "3.5.0", 1176 | "resolved": "https://registry.npmjs.org/fast-redact/-/fast-redact-3.5.0.tgz", 1177 | "integrity": "sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A==", 1178 | "engines": { 1179 | "node": ">=6" 1180 | } 1181 | }, 1182 | "node_modules/fast-safe-stringify": { 1183 | "version": "2.1.1", 1184 | "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", 1185 | "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", 1186 | "dev": true 1187 | }, 1188 | "node_modules/fill-range": { 1189 | "version": "7.0.1", 1190 | "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", 1191 | "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", 1192 | "dev": true, 1193 | "dependencies": { 1194 | "to-regex-range": "^5.0.1" 1195 | }, 1196 | "engines": { 1197 | "node": ">=8" 1198 | } 1199 | }, 1200 | "node_modules/finalhandler": { 1201 | "version": "1.2.0", 1202 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", 1203 | "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", 1204 | "dependencies": { 1205 | "debug": "2.6.9", 1206 | "encodeurl": "~1.0.2", 1207 | "escape-html": "~1.0.3", 1208 | "on-finished": "2.4.1", 1209 | "parseurl": "~1.3.3", 1210 | "statuses": "2.0.1", 1211 | "unpipe": "~1.0.0" 1212 | }, 1213 | "engines": { 1214 | "node": ">= 0.8" 1215 | } 1216 | }, 1217 | "node_modules/forwarded": { 1218 | "version": "0.2.0", 1219 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", 1220 | "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", 1221 | "engines": { 1222 | "node": ">= 0.6" 1223 | } 1224 | }, 1225 | "node_modules/fresh": { 1226 | "version": "0.5.2", 1227 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", 1228 | "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", 1229 | "engines": { 1230 | "node": ">= 0.6" 1231 | } 1232 | }, 1233 | "node_modules/fs.realpath": { 1234 | "version": "1.0.0", 1235 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 1236 | "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", 1237 | "dev": true 1238 | }, 1239 | "node_modules/fsevents": { 1240 | "version": "2.3.3", 1241 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", 1242 | "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", 1243 | "dev": true, 1244 | "hasInstallScript": true, 1245 | "optional": true, 1246 | "os": [ 1247 | "darwin" 1248 | ], 1249 | "engines": { 1250 | "node": "^8.16.0 || ^10.6.0 || >=11.0.0" 1251 | } 1252 | }, 1253 | "node_modules/function-bind": { 1254 | "version": "1.1.2", 1255 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", 1256 | "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", 1257 | "funding": { 1258 | "url": "https://github.com/sponsors/ljharb" 1259 | } 1260 | }, 1261 | "node_modules/get-intrinsic": { 1262 | "version": "1.2.2", 1263 | "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz", 1264 | "integrity": "sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==", 1265 | "dependencies": { 1266 | "function-bind": "^1.1.2", 1267 | "has-proto": "^1.0.1", 1268 | "has-symbols": "^1.0.3", 1269 | "hasown": "^2.0.0" 1270 | }, 1271 | "funding": { 1272 | "url": "https://github.com/sponsors/ljharb" 1273 | } 1274 | }, 1275 | "node_modules/glob": { 1276 | "version": "7.2.3", 1277 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", 1278 | "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", 1279 | "dev": true, 1280 | "dependencies": { 1281 | "fs.realpath": "^1.0.0", 1282 | "inflight": "^1.0.4", 1283 | "inherits": "2", 1284 | "minimatch": "^3.1.1", 1285 | "once": "^1.3.0", 1286 | "path-is-absolute": "^1.0.0" 1287 | }, 1288 | "engines": { 1289 | "node": "*" 1290 | }, 1291 | "funding": { 1292 | "url": "https://github.com/sponsors/isaacs" 1293 | } 1294 | }, 1295 | "node_modules/glob-parent": { 1296 | "version": "5.1.2", 1297 | "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", 1298 | "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", 1299 | "dev": true, 1300 | "dependencies": { 1301 | "is-glob": "^4.0.1" 1302 | }, 1303 | "engines": { 1304 | "node": ">= 6" 1305 | } 1306 | }, 1307 | "node_modules/gopd": { 1308 | "version": "1.0.1", 1309 | "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", 1310 | "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", 1311 | "dependencies": { 1312 | "get-intrinsic": "^1.1.3" 1313 | }, 1314 | "funding": { 1315 | "url": "https://github.com/sponsors/ljharb" 1316 | } 1317 | }, 1318 | "node_modules/has-property-descriptors": { 1319 | "version": "1.0.1", 1320 | "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz", 1321 | "integrity": "sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==", 1322 | "dependencies": { 1323 | "get-intrinsic": "^1.2.2" 1324 | }, 1325 | "funding": { 1326 | "url": "https://github.com/sponsors/ljharb" 1327 | } 1328 | }, 1329 | "node_modules/has-proto": { 1330 | "version": "1.0.1", 1331 | "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", 1332 | "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", 1333 | "engines": { 1334 | "node": ">= 0.4" 1335 | }, 1336 | "funding": { 1337 | "url": "https://github.com/sponsors/ljharb" 1338 | } 1339 | }, 1340 | "node_modules/has-symbols": { 1341 | "version": "1.0.3", 1342 | "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", 1343 | "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", 1344 | "engines": { 1345 | "node": ">= 0.4" 1346 | }, 1347 | "funding": { 1348 | "url": "https://github.com/sponsors/ljharb" 1349 | } 1350 | }, 1351 | "node_modules/hasown": { 1352 | "version": "2.0.0", 1353 | "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", 1354 | "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", 1355 | "dependencies": { 1356 | "function-bind": "^1.1.2" 1357 | }, 1358 | "engines": { 1359 | "node": ">= 0.4" 1360 | } 1361 | }, 1362 | "node_modules/help-me": { 1363 | "version": "5.0.0", 1364 | "resolved": "https://registry.npmjs.org/help-me/-/help-me-5.0.0.tgz", 1365 | "integrity": "sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==", 1366 | "dev": true 1367 | }, 1368 | "node_modules/http-errors": { 1369 | "version": "2.0.0", 1370 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", 1371 | "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", 1372 | "dependencies": { 1373 | "depd": "2.0.0", 1374 | "inherits": "2.0.4", 1375 | "setprototypeof": "1.2.0", 1376 | "statuses": "2.0.1", 1377 | "toidentifier": "1.0.1" 1378 | }, 1379 | "engines": { 1380 | "node": ">= 0.8" 1381 | } 1382 | }, 1383 | "node_modules/iconv-lite": { 1384 | "version": "0.4.24", 1385 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", 1386 | "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", 1387 | "dependencies": { 1388 | "safer-buffer": ">= 2.1.2 < 3" 1389 | }, 1390 | "engines": { 1391 | "node": ">=0.10.0" 1392 | } 1393 | }, 1394 | "node_modules/inflight": { 1395 | "version": "1.0.6", 1396 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 1397 | "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", 1398 | "dev": true, 1399 | "dependencies": { 1400 | "once": "^1.3.0", 1401 | "wrappy": "1" 1402 | } 1403 | }, 1404 | "node_modules/inherits": { 1405 | "version": "2.0.4", 1406 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 1407 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" 1408 | }, 1409 | "node_modules/ipaddr.js": { 1410 | "version": "1.9.1", 1411 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", 1412 | "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", 1413 | "engines": { 1414 | "node": ">= 0.10" 1415 | } 1416 | }, 1417 | "node_modules/is-binary-path": { 1418 | "version": "2.1.0", 1419 | "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", 1420 | "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", 1421 | "dev": true, 1422 | "dependencies": { 1423 | "binary-extensions": "^2.0.0" 1424 | }, 1425 | "engines": { 1426 | "node": ">=8" 1427 | } 1428 | }, 1429 | "node_modules/is-core-module": { 1430 | "version": "2.13.1", 1431 | "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", 1432 | "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", 1433 | "dev": true, 1434 | "dependencies": { 1435 | "hasown": "^2.0.0" 1436 | }, 1437 | "funding": { 1438 | "url": "https://github.com/sponsors/ljharb" 1439 | } 1440 | }, 1441 | "node_modules/is-extglob": { 1442 | "version": "2.1.1", 1443 | "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", 1444 | "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", 1445 | "dev": true, 1446 | "engines": { 1447 | "node": ">=0.10.0" 1448 | } 1449 | }, 1450 | "node_modules/is-glob": { 1451 | "version": "4.0.3", 1452 | "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", 1453 | "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", 1454 | "dev": true, 1455 | "dependencies": { 1456 | "is-extglob": "^2.1.1" 1457 | }, 1458 | "engines": { 1459 | "node": ">=0.10.0" 1460 | } 1461 | }, 1462 | "node_modules/is-number": { 1463 | "version": "7.0.0", 1464 | "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", 1465 | "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", 1466 | "dev": true, 1467 | "engines": { 1468 | "node": ">=0.12.0" 1469 | } 1470 | }, 1471 | "node_modules/joycon": { 1472 | "version": "3.1.1", 1473 | "resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz", 1474 | "integrity": "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==", 1475 | "dev": true, 1476 | "engines": { 1477 | "node": ">=10" 1478 | } 1479 | }, 1480 | "node_modules/jsonwebtoken": { 1481 | "version": "9.0.2", 1482 | "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", 1483 | "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", 1484 | "dependencies": { 1485 | "jws": "^3.2.2", 1486 | "lodash.includes": "^4.3.0", 1487 | "lodash.isboolean": "^3.0.3", 1488 | "lodash.isinteger": "^4.0.4", 1489 | "lodash.isnumber": "^3.0.3", 1490 | "lodash.isplainobject": "^4.0.6", 1491 | "lodash.isstring": "^4.0.1", 1492 | "lodash.once": "^4.0.0", 1493 | "ms": "^2.1.1", 1494 | "semver": "^7.5.4" 1495 | }, 1496 | "engines": { 1497 | "node": ">=12", 1498 | "npm": ">=6" 1499 | } 1500 | }, 1501 | "node_modules/jsonwebtoken/node_modules/ms": { 1502 | "version": "2.1.3", 1503 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 1504 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" 1505 | }, 1506 | "node_modules/jwa": { 1507 | "version": "1.4.1", 1508 | "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", 1509 | "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", 1510 | "dependencies": { 1511 | "buffer-equal-constant-time": "1.0.1", 1512 | "ecdsa-sig-formatter": "1.0.11", 1513 | "safe-buffer": "^5.0.1" 1514 | } 1515 | }, 1516 | "node_modules/jws": { 1517 | "version": "3.2.2", 1518 | "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", 1519 | "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", 1520 | "dependencies": { 1521 | "jwa": "^1.4.1", 1522 | "safe-buffer": "^5.0.1" 1523 | } 1524 | }, 1525 | "node_modules/lodash.includes": { 1526 | "version": "4.3.0", 1527 | "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", 1528 | "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==" 1529 | }, 1530 | "node_modules/lodash.isboolean": { 1531 | "version": "3.0.3", 1532 | "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", 1533 | "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==" 1534 | }, 1535 | "node_modules/lodash.isinteger": { 1536 | "version": "4.0.4", 1537 | "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", 1538 | "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==" 1539 | }, 1540 | "node_modules/lodash.isnumber": { 1541 | "version": "3.0.3", 1542 | "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", 1543 | "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==" 1544 | }, 1545 | "node_modules/lodash.isplainobject": { 1546 | "version": "4.0.6", 1547 | "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", 1548 | "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==" 1549 | }, 1550 | "node_modules/lodash.isstring": { 1551 | "version": "4.0.1", 1552 | "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", 1553 | "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==" 1554 | }, 1555 | "node_modules/lodash.once": { 1556 | "version": "4.1.1", 1557 | "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", 1558 | "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==" 1559 | }, 1560 | "node_modules/lru-cache": { 1561 | "version": "6.0.0", 1562 | "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", 1563 | "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", 1564 | "dependencies": { 1565 | "yallist": "^4.0.0" 1566 | }, 1567 | "engines": { 1568 | "node": ">=10" 1569 | } 1570 | }, 1571 | "node_modules/make-error": { 1572 | "version": "1.3.6", 1573 | "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", 1574 | "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", 1575 | "dev": true 1576 | }, 1577 | "node_modules/media-typer": { 1578 | "version": "0.3.0", 1579 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", 1580 | "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", 1581 | "engines": { 1582 | "node": ">= 0.6" 1583 | } 1584 | }, 1585 | "node_modules/merge-descriptors": { 1586 | "version": "1.0.1", 1587 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", 1588 | "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" 1589 | }, 1590 | "node_modules/methods": { 1591 | "version": "1.1.2", 1592 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", 1593 | "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", 1594 | "engines": { 1595 | "node": ">= 0.6" 1596 | } 1597 | }, 1598 | "node_modules/mime": { 1599 | "version": "1.6.0", 1600 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", 1601 | "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", 1602 | "bin": { 1603 | "mime": "cli.js" 1604 | }, 1605 | "engines": { 1606 | "node": ">=4" 1607 | } 1608 | }, 1609 | "node_modules/mime-db": { 1610 | "version": "1.52.0", 1611 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", 1612 | "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", 1613 | "engines": { 1614 | "node": ">= 0.6" 1615 | } 1616 | }, 1617 | "node_modules/mime-types": { 1618 | "version": "2.1.35", 1619 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", 1620 | "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", 1621 | "dependencies": { 1622 | "mime-db": "1.52.0" 1623 | }, 1624 | "engines": { 1625 | "node": ">= 0.6" 1626 | } 1627 | }, 1628 | "node_modules/minimatch": { 1629 | "version": "3.1.2", 1630 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", 1631 | "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", 1632 | "dev": true, 1633 | "dependencies": { 1634 | "brace-expansion": "^1.1.7" 1635 | }, 1636 | "engines": { 1637 | "node": "*" 1638 | } 1639 | }, 1640 | "node_modules/minimist": { 1641 | "version": "1.2.8", 1642 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", 1643 | "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", 1644 | "dev": true, 1645 | "funding": { 1646 | "url": "https://github.com/sponsors/ljharb" 1647 | } 1648 | }, 1649 | "node_modules/mkdirp": { 1650 | "version": "1.0.4", 1651 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", 1652 | "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", 1653 | "dev": true, 1654 | "bin": { 1655 | "mkdirp": "bin/cmd.js" 1656 | }, 1657 | "engines": { 1658 | "node": ">=10" 1659 | } 1660 | }, 1661 | "node_modules/ms": { 1662 | "version": "2.0.0", 1663 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 1664 | "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" 1665 | }, 1666 | "node_modules/negotiator": { 1667 | "version": "0.6.3", 1668 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", 1669 | "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", 1670 | "engines": { 1671 | "node": ">= 0.6" 1672 | } 1673 | }, 1674 | "node_modules/normalize-path": { 1675 | "version": "3.0.0", 1676 | "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", 1677 | "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", 1678 | "dev": true, 1679 | "engines": { 1680 | "node": ">=0.10.0" 1681 | } 1682 | }, 1683 | "node_modules/object-assign": { 1684 | "version": "4.1.1", 1685 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", 1686 | "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", 1687 | "engines": { 1688 | "node": ">=0.10.0" 1689 | } 1690 | }, 1691 | "node_modules/object-inspect": { 1692 | "version": "1.13.1", 1693 | "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", 1694 | "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", 1695 | "funding": { 1696 | "url": "https://github.com/sponsors/ljharb" 1697 | } 1698 | }, 1699 | "node_modules/on-exit-leak-free": { 1700 | "version": "2.1.2", 1701 | "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz", 1702 | "integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==", 1703 | "engines": { 1704 | "node": ">=14.0.0" 1705 | } 1706 | }, 1707 | "node_modules/on-finished": { 1708 | "version": "2.4.1", 1709 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", 1710 | "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", 1711 | "dependencies": { 1712 | "ee-first": "1.1.1" 1713 | }, 1714 | "engines": { 1715 | "node": ">= 0.8" 1716 | } 1717 | }, 1718 | "node_modules/once": { 1719 | "version": "1.4.0", 1720 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 1721 | "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", 1722 | "dev": true, 1723 | "dependencies": { 1724 | "wrappy": "1" 1725 | } 1726 | }, 1727 | "node_modules/parseurl": { 1728 | "version": "1.3.3", 1729 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", 1730 | "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", 1731 | "engines": { 1732 | "node": ">= 0.8" 1733 | } 1734 | }, 1735 | "node_modules/path-is-absolute": { 1736 | "version": "1.0.1", 1737 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 1738 | "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", 1739 | "dev": true, 1740 | "engines": { 1741 | "node": ">=0.10.0" 1742 | } 1743 | }, 1744 | "node_modules/path-parse": { 1745 | "version": "1.0.7", 1746 | "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", 1747 | "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", 1748 | "dev": true 1749 | }, 1750 | "node_modules/path-to-regexp": { 1751 | "version": "0.1.7", 1752 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", 1753 | "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" 1754 | }, 1755 | "node_modules/picomatch": { 1756 | "version": "2.3.1", 1757 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", 1758 | "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", 1759 | "dev": true, 1760 | "engines": { 1761 | "node": ">=8.6" 1762 | }, 1763 | "funding": { 1764 | "url": "https://github.com/sponsors/jonschlinkert" 1765 | } 1766 | }, 1767 | "node_modules/pino": { 1768 | "version": "9.5.0", 1769 | "resolved": "https://registry.npmjs.org/pino/-/pino-9.5.0.tgz", 1770 | "integrity": "sha512-xSEmD4pLnV54t0NOUN16yCl7RIB1c5UUOse5HSyEXtBp+FgFQyPeDutc+Q2ZO7/22vImV7VfEjH/1zV2QuqvYw==", 1771 | "dependencies": { 1772 | "atomic-sleep": "^1.0.0", 1773 | "fast-redact": "^3.1.1", 1774 | "on-exit-leak-free": "^2.1.0", 1775 | "pino-abstract-transport": "^2.0.0", 1776 | "pino-std-serializers": "^7.0.0", 1777 | "process-warning": "^4.0.0", 1778 | "quick-format-unescaped": "^4.0.3", 1779 | "real-require": "^0.2.0", 1780 | "safe-stable-stringify": "^2.3.1", 1781 | "sonic-boom": "^4.0.1", 1782 | "thread-stream": "^3.0.0" 1783 | }, 1784 | "bin": { 1785 | "pino": "bin.js" 1786 | } 1787 | }, 1788 | "node_modules/pino-abstract-transport": { 1789 | "version": "2.0.0", 1790 | "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-2.0.0.tgz", 1791 | "integrity": "sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==", 1792 | "dependencies": { 1793 | "split2": "^4.0.0" 1794 | } 1795 | }, 1796 | "node_modules/pino-pretty": { 1797 | "version": "13.0.0", 1798 | "resolved": "https://registry.npmjs.org/pino-pretty/-/pino-pretty-13.0.0.tgz", 1799 | "integrity": "sha512-cQBBIVG3YajgoUjo1FdKVRX6t9XPxwB9lcNJVD5GCnNM4Y6T12YYx8c6zEejxQsU0wrg9TwmDulcE9LR7qcJqA==", 1800 | "dev": true, 1801 | "dependencies": { 1802 | "colorette": "^2.0.7", 1803 | "dateformat": "^4.6.3", 1804 | "fast-copy": "^3.0.2", 1805 | "fast-safe-stringify": "^2.1.1", 1806 | "help-me": "^5.0.0", 1807 | "joycon": "^3.1.1", 1808 | "minimist": "^1.2.6", 1809 | "on-exit-leak-free": "^2.1.0", 1810 | "pino-abstract-transport": "^2.0.0", 1811 | "pump": "^3.0.0", 1812 | "secure-json-parse": "^2.4.0", 1813 | "sonic-boom": "^4.0.1", 1814 | "strip-json-comments": "^3.1.1" 1815 | }, 1816 | "bin": { 1817 | "pino-pretty": "bin.js" 1818 | } 1819 | }, 1820 | "node_modules/pino-pretty/node_modules/strip-json-comments": { 1821 | "version": "3.1.1", 1822 | "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", 1823 | "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", 1824 | "dev": true, 1825 | "engines": { 1826 | "node": ">=8" 1827 | }, 1828 | "funding": { 1829 | "url": "https://github.com/sponsors/sindresorhus" 1830 | } 1831 | }, 1832 | "node_modules/pino-std-serializers": { 1833 | "version": "7.0.0", 1834 | "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-7.0.0.tgz", 1835 | "integrity": "sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA==" 1836 | }, 1837 | "node_modules/prisma": { 1838 | "version": "5.8.1", 1839 | "resolved": "https://registry.npmjs.org/prisma/-/prisma-5.8.1.tgz", 1840 | "integrity": "sha512-N6CpjzECnUHZ5beeYpDzkt2rYpEdAeqXX2dweu6BoQaeYkNZrC/WJHM+5MO/uidFHTak8QhkPKBWck1o/4MD4A==", 1841 | "devOptional": true, 1842 | "hasInstallScript": true, 1843 | "dependencies": { 1844 | "@prisma/engines": "5.8.1" 1845 | }, 1846 | "bin": { 1847 | "prisma": "build/index.js" 1848 | }, 1849 | "engines": { 1850 | "node": ">=16.13" 1851 | } 1852 | }, 1853 | "node_modules/process-warning": { 1854 | "version": "4.0.0", 1855 | "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-4.0.0.tgz", 1856 | "integrity": "sha512-/MyYDxttz7DfGMMHiysAsFE4qF+pQYAA8ziO/3NcRVrQ5fSk+Mns4QZA/oRPFzvcqNoVJXQNWNAsdwBXLUkQKw==" 1857 | }, 1858 | "node_modules/proxy-addr": { 1859 | "version": "2.0.7", 1860 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", 1861 | "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", 1862 | "dependencies": { 1863 | "forwarded": "0.2.0", 1864 | "ipaddr.js": "1.9.1" 1865 | }, 1866 | "engines": { 1867 | "node": ">= 0.10" 1868 | } 1869 | }, 1870 | "node_modules/pump": { 1871 | "version": "3.0.2", 1872 | "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz", 1873 | "integrity": "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==", 1874 | "dev": true, 1875 | "dependencies": { 1876 | "end-of-stream": "^1.1.0", 1877 | "once": "^1.3.1" 1878 | } 1879 | }, 1880 | "node_modules/qs": { 1881 | "version": "6.11.0", 1882 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", 1883 | "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", 1884 | "dependencies": { 1885 | "side-channel": "^1.0.4" 1886 | }, 1887 | "engines": { 1888 | "node": ">=0.6" 1889 | }, 1890 | "funding": { 1891 | "url": "https://github.com/sponsors/ljharb" 1892 | } 1893 | }, 1894 | "node_modules/quick-format-unescaped": { 1895 | "version": "4.0.4", 1896 | "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", 1897 | "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==" 1898 | }, 1899 | "node_modules/range-parser": { 1900 | "version": "1.2.1", 1901 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", 1902 | "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", 1903 | "engines": { 1904 | "node": ">= 0.6" 1905 | } 1906 | }, 1907 | "node_modules/raw-body": { 1908 | "version": "2.5.1", 1909 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", 1910 | "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", 1911 | "dependencies": { 1912 | "bytes": "3.1.2", 1913 | "http-errors": "2.0.0", 1914 | "iconv-lite": "0.4.24", 1915 | "unpipe": "1.0.0" 1916 | }, 1917 | "engines": { 1918 | "node": ">= 0.8" 1919 | } 1920 | }, 1921 | "node_modules/readdirp": { 1922 | "version": "3.6.0", 1923 | "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", 1924 | "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", 1925 | "dev": true, 1926 | "dependencies": { 1927 | "picomatch": "^2.2.1" 1928 | }, 1929 | "engines": { 1930 | "node": ">=8.10.0" 1931 | } 1932 | }, 1933 | "node_modules/real-require": { 1934 | "version": "0.2.0", 1935 | "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz", 1936 | "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==", 1937 | "engines": { 1938 | "node": ">= 12.13.0" 1939 | } 1940 | }, 1941 | "node_modules/resolve": { 1942 | "version": "1.22.8", 1943 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", 1944 | "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", 1945 | "dev": true, 1946 | "dependencies": { 1947 | "is-core-module": "^2.13.0", 1948 | "path-parse": "^1.0.7", 1949 | "supports-preserve-symlinks-flag": "^1.0.0" 1950 | }, 1951 | "bin": { 1952 | "resolve": "bin/resolve" 1953 | }, 1954 | "funding": { 1955 | "url": "https://github.com/sponsors/ljharb" 1956 | } 1957 | }, 1958 | "node_modules/rimraf": { 1959 | "version": "2.7.1", 1960 | "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", 1961 | "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", 1962 | "dev": true, 1963 | "dependencies": { 1964 | "glob": "^7.1.3" 1965 | }, 1966 | "bin": { 1967 | "rimraf": "bin.js" 1968 | } 1969 | }, 1970 | "node_modules/safe-buffer": { 1971 | "version": "5.2.1", 1972 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 1973 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", 1974 | "funding": [ 1975 | { 1976 | "type": "github", 1977 | "url": "https://github.com/sponsors/feross" 1978 | }, 1979 | { 1980 | "type": "patreon", 1981 | "url": "https://www.patreon.com/feross" 1982 | }, 1983 | { 1984 | "type": "consulting", 1985 | "url": "https://feross.org/support" 1986 | } 1987 | ] 1988 | }, 1989 | "node_modules/safe-stable-stringify": { 1990 | "version": "2.5.0", 1991 | "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", 1992 | "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", 1993 | "engines": { 1994 | "node": ">=10" 1995 | } 1996 | }, 1997 | "node_modules/safer-buffer": { 1998 | "version": "2.1.2", 1999 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 2000 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" 2001 | }, 2002 | "node_modules/secure-json-parse": { 2003 | "version": "2.7.0", 2004 | "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-2.7.0.tgz", 2005 | "integrity": "sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==", 2006 | "dev": true 2007 | }, 2008 | "node_modules/semver": { 2009 | "version": "7.5.4", 2010 | "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", 2011 | "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", 2012 | "dependencies": { 2013 | "lru-cache": "^6.0.0" 2014 | }, 2015 | "bin": { 2016 | "semver": "bin/semver.js" 2017 | }, 2018 | "engines": { 2019 | "node": ">=10" 2020 | } 2021 | }, 2022 | "node_modules/send": { 2023 | "version": "0.18.0", 2024 | "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", 2025 | "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", 2026 | "dependencies": { 2027 | "debug": "2.6.9", 2028 | "depd": "2.0.0", 2029 | "destroy": "1.2.0", 2030 | "encodeurl": "~1.0.2", 2031 | "escape-html": "~1.0.3", 2032 | "etag": "~1.8.1", 2033 | "fresh": "0.5.2", 2034 | "http-errors": "2.0.0", 2035 | "mime": "1.6.0", 2036 | "ms": "2.1.3", 2037 | "on-finished": "2.4.1", 2038 | "range-parser": "~1.2.1", 2039 | "statuses": "2.0.1" 2040 | }, 2041 | "engines": { 2042 | "node": ">= 0.8.0" 2043 | } 2044 | }, 2045 | "node_modules/send/node_modules/ms": { 2046 | "version": "2.1.3", 2047 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 2048 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" 2049 | }, 2050 | "node_modules/serve-static": { 2051 | "version": "1.15.0", 2052 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", 2053 | "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", 2054 | "dependencies": { 2055 | "encodeurl": "~1.0.2", 2056 | "escape-html": "~1.0.3", 2057 | "parseurl": "~1.3.3", 2058 | "send": "0.18.0" 2059 | }, 2060 | "engines": { 2061 | "node": ">= 0.8.0" 2062 | } 2063 | }, 2064 | "node_modules/set-function-length": { 2065 | "version": "1.2.0", 2066 | "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.0.tgz", 2067 | "integrity": "sha512-4DBHDoyHlM1IRPGYcoxexgh67y4ueR53FKV1yyxwFMY7aCqcN/38M1+SwZ/qJQ8iLv7+ck385ot4CcisOAPT9w==", 2068 | "dependencies": { 2069 | "define-data-property": "^1.1.1", 2070 | "function-bind": "^1.1.2", 2071 | "get-intrinsic": "^1.2.2", 2072 | "gopd": "^1.0.1", 2073 | "has-property-descriptors": "^1.0.1" 2074 | }, 2075 | "engines": { 2076 | "node": ">= 0.4" 2077 | } 2078 | }, 2079 | "node_modules/setprototypeof": { 2080 | "version": "1.2.0", 2081 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", 2082 | "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" 2083 | }, 2084 | "node_modules/side-channel": { 2085 | "version": "1.0.4", 2086 | "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", 2087 | "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", 2088 | "dependencies": { 2089 | "call-bind": "^1.0.0", 2090 | "get-intrinsic": "^1.0.2", 2091 | "object-inspect": "^1.9.0" 2092 | }, 2093 | "funding": { 2094 | "url": "https://github.com/sponsors/ljharb" 2095 | } 2096 | }, 2097 | "node_modules/sonic-boom": { 2098 | "version": "4.2.0", 2099 | "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.0.tgz", 2100 | "integrity": "sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww==", 2101 | "dependencies": { 2102 | "atomic-sleep": "^1.0.0" 2103 | } 2104 | }, 2105 | "node_modules/source-map": { 2106 | "version": "0.6.1", 2107 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", 2108 | "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", 2109 | "dev": true, 2110 | "engines": { 2111 | "node": ">=0.10.0" 2112 | } 2113 | }, 2114 | "node_modules/source-map-support": { 2115 | "version": "0.5.21", 2116 | "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", 2117 | "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", 2118 | "dev": true, 2119 | "dependencies": { 2120 | "buffer-from": "^1.0.0", 2121 | "source-map": "^0.6.0" 2122 | } 2123 | }, 2124 | "node_modules/split2": { 2125 | "version": "4.2.0", 2126 | "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", 2127 | "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", 2128 | "engines": { 2129 | "node": ">= 10.x" 2130 | } 2131 | }, 2132 | "node_modules/statuses": { 2133 | "version": "2.0.1", 2134 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", 2135 | "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", 2136 | "engines": { 2137 | "node": ">= 0.8" 2138 | } 2139 | }, 2140 | "node_modules/strip-bom": { 2141 | "version": "3.0.0", 2142 | "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", 2143 | "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", 2144 | "dev": true, 2145 | "engines": { 2146 | "node": ">=4" 2147 | } 2148 | }, 2149 | "node_modules/strip-json-comments": { 2150 | "version": "2.0.1", 2151 | "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", 2152 | "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", 2153 | "dev": true, 2154 | "engines": { 2155 | "node": ">=0.10.0" 2156 | } 2157 | }, 2158 | "node_modules/supports-preserve-symlinks-flag": { 2159 | "version": "1.0.0", 2160 | "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", 2161 | "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", 2162 | "dev": true, 2163 | "engines": { 2164 | "node": ">= 0.4" 2165 | }, 2166 | "funding": { 2167 | "url": "https://github.com/sponsors/ljharb" 2168 | } 2169 | }, 2170 | "node_modules/thread-stream": { 2171 | "version": "3.1.0", 2172 | "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-3.1.0.tgz", 2173 | "integrity": "sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==", 2174 | "dependencies": { 2175 | "real-require": "^0.2.0" 2176 | } 2177 | }, 2178 | "node_modules/to-regex-range": { 2179 | "version": "5.0.1", 2180 | "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", 2181 | "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", 2182 | "dev": true, 2183 | "dependencies": { 2184 | "is-number": "^7.0.0" 2185 | }, 2186 | "engines": { 2187 | "node": ">=8.0" 2188 | } 2189 | }, 2190 | "node_modules/toidentifier": { 2191 | "version": "1.0.1", 2192 | "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", 2193 | "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", 2194 | "engines": { 2195 | "node": ">=0.6" 2196 | } 2197 | }, 2198 | "node_modules/tree-kill": { 2199 | "version": "1.2.2", 2200 | "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", 2201 | "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", 2202 | "dev": true, 2203 | "bin": { 2204 | "tree-kill": "cli.js" 2205 | } 2206 | }, 2207 | "node_modules/ts-node": { 2208 | "version": "10.9.2", 2209 | "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", 2210 | "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", 2211 | "dev": true, 2212 | "dependencies": { 2213 | "@cspotcode/source-map-support": "^0.8.0", 2214 | "@tsconfig/node10": "^1.0.7", 2215 | "@tsconfig/node12": "^1.0.7", 2216 | "@tsconfig/node14": "^1.0.0", 2217 | "@tsconfig/node16": "^1.0.2", 2218 | "acorn": "^8.4.1", 2219 | "acorn-walk": "^8.1.1", 2220 | "arg": "^4.1.0", 2221 | "create-require": "^1.1.0", 2222 | "diff": "^4.0.1", 2223 | "make-error": "^1.1.1", 2224 | "v8-compile-cache-lib": "^3.0.1", 2225 | "yn": "3.1.1" 2226 | }, 2227 | "bin": { 2228 | "ts-node": "dist/bin.js", 2229 | "ts-node-cwd": "dist/bin-cwd.js", 2230 | "ts-node-esm": "dist/bin-esm.js", 2231 | "ts-node-script": "dist/bin-script.js", 2232 | "ts-node-transpile-only": "dist/bin-transpile.js", 2233 | "ts-script": "dist/bin-script-deprecated.js" 2234 | }, 2235 | "peerDependencies": { 2236 | "@swc/core": ">=1.2.50", 2237 | "@swc/wasm": ">=1.2.50", 2238 | "@types/node": "*", 2239 | "typescript": ">=2.7" 2240 | }, 2241 | "peerDependenciesMeta": { 2242 | "@swc/core": { 2243 | "optional": true 2244 | }, 2245 | "@swc/wasm": { 2246 | "optional": true 2247 | } 2248 | } 2249 | }, 2250 | "node_modules/ts-node-dev": { 2251 | "version": "2.0.0", 2252 | "resolved": "https://registry.npmjs.org/ts-node-dev/-/ts-node-dev-2.0.0.tgz", 2253 | "integrity": "sha512-ywMrhCfH6M75yftYvrvNarLEY+SUXtUvU8/0Z6llrHQVBx12GiFk5sStF8UdfE/yfzk9IAq7O5EEbTQsxlBI8w==", 2254 | "dev": true, 2255 | "dependencies": { 2256 | "chokidar": "^3.5.1", 2257 | "dynamic-dedupe": "^0.3.0", 2258 | "minimist": "^1.2.6", 2259 | "mkdirp": "^1.0.4", 2260 | "resolve": "^1.0.0", 2261 | "rimraf": "^2.6.1", 2262 | "source-map-support": "^0.5.12", 2263 | "tree-kill": "^1.2.2", 2264 | "ts-node": "^10.4.0", 2265 | "tsconfig": "^7.0.0" 2266 | }, 2267 | "bin": { 2268 | "ts-node-dev": "lib/bin.js", 2269 | "tsnd": "lib/bin.js" 2270 | }, 2271 | "engines": { 2272 | "node": ">=0.8.0" 2273 | }, 2274 | "peerDependencies": { 2275 | "node-notifier": "*", 2276 | "typescript": "*" 2277 | }, 2278 | "peerDependenciesMeta": { 2279 | "node-notifier": { 2280 | "optional": true 2281 | } 2282 | } 2283 | }, 2284 | "node_modules/tsconfig": { 2285 | "version": "7.0.0", 2286 | "resolved": "https://registry.npmjs.org/tsconfig/-/tsconfig-7.0.0.tgz", 2287 | "integrity": "sha512-vZXmzPrL+EmC4T/4rVlT2jNVMWCi/O4DIiSj3UHg1OE5kCKbk4mfrXc6dZksLgRM/TZlKnousKH9bbTazUWRRw==", 2288 | "dev": true, 2289 | "dependencies": { 2290 | "@types/strip-bom": "^3.0.0", 2291 | "@types/strip-json-comments": "0.0.30", 2292 | "strip-bom": "^3.0.0", 2293 | "strip-json-comments": "^2.0.0" 2294 | } 2295 | }, 2296 | "node_modules/type-is": { 2297 | "version": "1.6.18", 2298 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", 2299 | "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", 2300 | "dependencies": { 2301 | "media-typer": "0.3.0", 2302 | "mime-types": "~2.1.24" 2303 | }, 2304 | "engines": { 2305 | "node": ">= 0.6" 2306 | } 2307 | }, 2308 | "node_modules/typescript": { 2309 | "version": "5.3.3", 2310 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", 2311 | "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", 2312 | "dev": true, 2313 | "bin": { 2314 | "tsc": "bin/tsc", 2315 | "tsserver": "bin/tsserver" 2316 | }, 2317 | "engines": { 2318 | "node": ">=14.17" 2319 | } 2320 | }, 2321 | "node_modules/undici-types": { 2322 | "version": "5.26.5", 2323 | "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", 2324 | "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", 2325 | "dev": true 2326 | }, 2327 | "node_modules/unpipe": { 2328 | "version": "1.0.0", 2329 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 2330 | "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", 2331 | "engines": { 2332 | "node": ">= 0.8" 2333 | } 2334 | }, 2335 | "node_modules/utils-merge": { 2336 | "version": "1.0.1", 2337 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", 2338 | "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", 2339 | "engines": { 2340 | "node": ">= 0.4.0" 2341 | } 2342 | }, 2343 | "node_modules/uuid": { 2344 | "version": "9.0.1", 2345 | "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", 2346 | "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", 2347 | "funding": [ 2348 | "https://github.com/sponsors/broofa", 2349 | "https://github.com/sponsors/ctavan" 2350 | ], 2351 | "bin": { 2352 | "uuid": "dist/bin/uuid" 2353 | } 2354 | }, 2355 | "node_modules/v8-compile-cache-lib": { 2356 | "version": "3.0.1", 2357 | "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", 2358 | "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", 2359 | "dev": true 2360 | }, 2361 | "node_modules/vary": { 2362 | "version": "1.1.2", 2363 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", 2364 | "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", 2365 | "engines": { 2366 | "node": ">= 0.8" 2367 | } 2368 | }, 2369 | "node_modules/wrappy": { 2370 | "version": "1.0.2", 2371 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 2372 | "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", 2373 | "dev": true 2374 | }, 2375 | "node_modules/xtend": { 2376 | "version": "4.0.2", 2377 | "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", 2378 | "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", 2379 | "dev": true, 2380 | "engines": { 2381 | "node": ">=0.4" 2382 | } 2383 | }, 2384 | "node_modules/yallist": { 2385 | "version": "4.0.0", 2386 | "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", 2387 | "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" 2388 | }, 2389 | "node_modules/yn": { 2390 | "version": "3.1.1", 2391 | "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", 2392 | "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", 2393 | "dev": true, 2394 | "engines": { 2395 | "node": ">=6" 2396 | } 2397 | }, 2398 | "node_modules/zod": { 2399 | "version": "3.22.4", 2400 | "resolved": "https://registry.npmjs.org/zod/-/zod-3.22.4.tgz", 2401 | "integrity": "sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==", 2402 | "funding": { 2403 | "url": "https://github.com/sponsors/colinhacks" 2404 | } 2405 | } 2406 | } 2407 | } 2408 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "express-prisma-ts-api", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "prisma": { 7 | "seed": "node --require esbuild-register prisma/seed.ts" 8 | }, 9 | "scripts": { 10 | "seed": "npx prisma db seed", 11 | "build": "npx tsc", 12 | "start": "node dist/index.js", 13 | "dev": "ts-node-dev src/index.ts | pino-pretty" 14 | }, 15 | "keywords": [], 16 | "author": "", 17 | "license": "ISC", 18 | "dependencies": { 19 | "@prisma/client": "^5.8.1", 20 | "@types/uuid": "^9.0.7", 21 | "bcryptjs": "^2.4.3", 22 | "cookie-parser": "^1.4.6", 23 | "cors": "^2.8.5", 24 | "dotenv": "^16.3.2", 25 | "esbuild": "^0.19.11", 26 | "express": "^4.18.2", 27 | "jsonwebtoken": "^9.0.2", 28 | "pino": "^9.5.0", 29 | "uuid": "^9.0.1", 30 | "zod": "^3.22.4" 31 | }, 32 | "devDependencies": { 33 | "@types/bcryptjs": "^2.4.6", 34 | "@types/cookie-parser": "^1.4.6", 35 | "@types/cors": "^2.8.17", 36 | "@types/dotenv": "^8.2.0", 37 | "@types/express": "^4.17.21", 38 | "@types/jsonwebtoken": "^9.0.5", 39 | "@types/node": "^20.11.5", 40 | "esbuild-register": "^3.5.0", 41 | "pino-pretty": "^13.0.0", 42 | "prisma": "^5.8.1", 43 | "ts-node-dev": "^2.0.0", 44 | "typescript": "^5.3.3" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /prettier.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | printWidth: 120, // max 120 chars in line, code is easy to read 3 | useTabs: false, // use spaces instead of tabs 4 | tabWidth: 2, // "visual width" of of the "tab" 5 | trailingComma: 'es5', // add trailing commas in objects, arrays, etc. 6 | semi: true, // add ; when needed 7 | singleQuote: true, // '' for stings instead of "" 8 | bracketSpacing: true, // import { some } ... instead of import {some} ... 9 | arrowParens: 'always', // braces even for single param in arrow functions (a) => { } 10 | jsxSingleQuote: false, // "" for react props, like in html 11 | bracketSameLine: false, // pretty JSX 12 | endOfLine: 'lf', // 'lf' for linux, 'crlf' for windows, we need to use 'lf' for git 13 | }; 14 | -------------------------------------------------------------------------------- /prisma/migrations/20240125170057_init/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateTable 2 | CREATE TABLE `Author` ( 3 | `id` INTEGER NOT NULL AUTO_INCREMENT, 4 | `createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), 5 | `updatedAt` DATETIME(3) NOT NULL, 6 | `firstName` VARCHAR(191) NOT NULL, 7 | `lastName` VARCHAR(191) NOT NULL, 8 | 9 | PRIMARY KEY (`id`) 10 | ) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; 11 | 12 | -- CreateTable 13 | CREATE TABLE `Book` ( 14 | `id` INTEGER NOT NULL AUTO_INCREMENT, 15 | `createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), 16 | `updatedAt` DATETIME(3) NOT NULL, 17 | `title` VARCHAR(191) NOT NULL, 18 | `isFiction` BOOLEAN NOT NULL, 19 | `datePublished` DATETIME(3) NOT NULL, 20 | `authorId` INTEGER NOT NULL, 21 | 22 | PRIMARY KEY (`id`) 23 | ) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; 24 | 25 | -- CreateTable 26 | CREATE TABLE `User` ( 27 | `id` INTEGER NOT NULL, 28 | `createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), 29 | `modifiedAt` DATETIME(3) NOT NULL, 30 | `fullName` VARCHAR(191) NOT NULL, 31 | `username` VARCHAR(191) NOT NULL, 32 | `password` VARCHAR(191) NOT NULL, 33 | `email` VARCHAR(191) NOT NULL, 34 | 35 | UNIQUE INDEX `User_username_key`(`username`), 36 | PRIMARY KEY (`id`) 37 | ) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; 38 | 39 | -- AddForeignKey 40 | ALTER TABLE `Book` ADD CONSTRAINT `Book_authorId_fkey` FOREIGN KEY (`authorId`) REFERENCES `Author`(`id`) ON DELETE CASCADE ON UPDATE CASCADE; 41 | -------------------------------------------------------------------------------- /prisma/migrations/20240125173028_change_timestamp_name_and_order/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - You are about to drop the column `modifiedAt` on the `User` table. All the data in the column will be lost. 5 | - Added the required column `updatedAt` to the `User` table without a default value. This is not possible if the table is not empty. 6 | 7 | */ 8 | -- AlterTable 9 | ALTER TABLE `User` DROP COLUMN `modifiedAt`, 10 | ADD COLUMN `updatedAt` DATETIME(3) NOT NULL; 11 | -------------------------------------------------------------------------------- /prisma/migrations/20240125174544_change_user_id_type_to_string/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - The primary key for the `User` table will be changed. If it partially fails, the table could be left without primary key constraint. 5 | - A unique constraint covering the columns `[id]` on the table `User` will be added. If there are existing duplicate values, this will fail. 6 | 7 | */ 8 | -- AlterTable 9 | ALTER TABLE `User` DROP PRIMARY KEY, 10 | MODIFY `id` VARCHAR(191) NOT NULL, 11 | ADD PRIMARY KEY (`id`); 12 | 13 | -- CreateIndex 14 | CREATE UNIQUE INDEX `User_id_key` ON `User`(`id`); 15 | -------------------------------------------------------------------------------- /prisma/migrations/migration_lock.toml: -------------------------------------------------------------------------------- 1 | # Please do not edit this file manually 2 | # It should be added in your version-control system (i.e. Git) 3 | provider = "mysql" -------------------------------------------------------------------------------- /prisma/schema.prisma: -------------------------------------------------------------------------------- 1 | generator client { 2 | provider = "prisma-client-js" 3 | } 4 | 5 | datasource db { 6 | provider = "mysql" 7 | url = env("DATABASE_URL") 8 | } 9 | 10 | model Author { 11 | id Int @id @default(autoincrement()) 12 | firstName String 13 | lastName String 14 | Book Book[] 15 | createdAt DateTime @default(now()) 16 | updatedAt DateTime @updatedAt 17 | } 18 | 19 | model Book { 20 | id Int @id @default(autoincrement()) 21 | title String 22 | isFiction Boolean 23 | datePublished DateTime 24 | author Author @relation(fields: [authorId], references: [id], onDelete: Cascade) 25 | authorId Int 26 | createdAt DateTime @default(now()) 27 | updatedAt DateTime @updatedAt 28 | } 29 | 30 | model User { 31 | id String @id @unique 32 | fullName String 33 | username String @unique 34 | password String 35 | email String 36 | createdAt DateTime @default(now()) 37 | updatedAt DateTime @updatedAt 38 | } 39 | -------------------------------------------------------------------------------- /prisma/seed.ts: -------------------------------------------------------------------------------- 1 | import { hashPassword } from './../src/utils/bcryptHandler'; 2 | import { TAuthorWrite, TBookWrite, TUserRegisterWrite } from '../src/types/general'; 3 | import { db } from '../src/utils/db.server'; 4 | import bcrypt from 'bcryptjs'; 5 | import { v4 as uuidv4 } from 'uuid'; 6 | 7 | async function getUser(): Promise { 8 | const password = 'admin'; 9 | const hashedPassword = await hashPassword(password); 10 | return { 11 | id: uuidv4(), 12 | fullName: 'john doe', 13 | username: 'admin', 14 | password: hashedPassword, 15 | email: 'email@company.com', 16 | }; 17 | } 18 | 19 | function getAuthors(): Array { 20 | return [ 21 | { firstName: 'john', lastName: 'doe' }, 22 | { firstName: 'william', lastName: 'parker' }, 23 | ]; 24 | } 25 | 26 | function getBooks(): Array> { 27 | return [ 28 | { 29 | title: 'Book 1', 30 | isFiction: false, 31 | datePublished: new Date(), 32 | }, 33 | { 34 | title: 'Book 2', 35 | isFiction: true, 36 | datePublished: new Date(), 37 | }, 38 | ]; 39 | } 40 | 41 | async function seed() { 42 | // Delete user Records 43 | await db.user.deleteMany(); 44 | console.log('Deleted records in user table'); 45 | 46 | // Seed new user 47 | const user = await getUser(); 48 | console.log(`[*] Seeding Author : ${JSON.stringify(user)}`); 49 | console.log(`[*] password : admin `); 50 | await db.user.create({ 51 | data: { 52 | ...user, 53 | }, 54 | }); 55 | 56 | //Seed Author 57 | await Promise.all( 58 | getAuthors().map((author) => { 59 | console.log(`[*] Seeding Author : ${JSON.stringify(author)}`); 60 | return db.author.create({ 61 | data: { 62 | firstName: author.firstName, 63 | lastName: author.lastName, 64 | }, 65 | }); 66 | }) 67 | ); 68 | 69 | const author = await db.author.findFirst({ 70 | where: { 71 | firstName: 'william', 72 | }, 73 | }); 74 | 75 | // Seed book 76 | await Promise.all( 77 | getBooks().map((book) => { 78 | const createBook = { 79 | datePublished: book.datePublished, 80 | isFiction: book.isFiction, 81 | title: book.title, 82 | authorId: author?.id || 0, 83 | }; 84 | console.log(`[*] Seeding Book : ${JSON.stringify(createBook)}`); 85 | return db.book.create({ 86 | data: { 87 | ...createBook, 88 | }, 89 | }); 90 | }) 91 | ); 92 | } 93 | 94 | seed(); 95 | -------------------------------------------------------------------------------- /src/controllers/auth.controller.ts: -------------------------------------------------------------------------------- 1 | import * as UserService from '../services/user.service'; 2 | import { NextFunction, Request, Response } from 'express'; 3 | import { TUserSchema, userSchema } from '../types/zod'; 4 | import { sendSuccessNoDataResponse, sendSuccessResponse, sendUnauthorizedResponse } from '../utils/responseHandler'; 5 | import { comparePasswords } from '../utils/bcryptHandler'; 6 | import { generateToken } from '../utils/jwtHandler'; 7 | 8 | export const login = async (request: Request, response: Response, next: NextFunction) => { 9 | try { 10 | const userRequest: TUserSchema = request.body; 11 | const user = await UserService.getUserByUsername(userRequest.username); 12 | 13 | if (!user) { 14 | return sendUnauthorizedResponse(response, 'Credentials Error'); 15 | } 16 | 17 | const passwordCompare = await comparePasswords(userRequest.password, user.password); 18 | 19 | if (passwordCompare) { 20 | const token = generateToken({ id: user.id }, '30d'); 21 | 22 | response.cookie('jwt', token, { 23 | httpOnly: true, 24 | secure: process.env.APP_ENV !== 'developement', 25 | sameSite: 'strict', 26 | maxAge: 30 * 24 * 60 * 60 * 1000, 27 | }); 28 | 29 | const responseData = { 30 | fullName: user.fullName, 31 | username: user.username, 32 | email: user.email, 33 | }; 34 | return sendSuccessResponse(response, responseData); 35 | } else { 36 | return sendUnauthorizedResponse(response, 'Credentials Error'); 37 | } 38 | } catch (error: any) { 39 | next(error); 40 | } 41 | }; 42 | 43 | export const logout = async (request: Request, response: Response, next: NextFunction) => { 44 | try { 45 | response.cookie('jwt', '', { 46 | httpOnly: true, 47 | expires: new Date(0), 48 | }); 49 | 50 | return sendSuccessNoDataResponse(response, 'Logout Successful'); 51 | } catch (error) { 52 | next(error); 53 | } 54 | }; 55 | 56 | // Middlewares ________________________ 57 | 58 | export const validateLoginData = (request: Request, response: Response, next: NextFunction) => { 59 | try { 60 | const data = request.body; 61 | userSchema.parse(data); 62 | next(); 63 | } catch (error) { 64 | next(error); 65 | } 66 | }; 67 | -------------------------------------------------------------------------------- /src/controllers/author.controller.ts: -------------------------------------------------------------------------------- 1 | import HttpStatusCode from '../utils/HttpStatusCode'; 2 | import * as AuthorService from '../services/author.service'; 3 | import { NextFunction, Request, Response } from 'express'; 4 | import { authorSchema } from '../types/zod'; 5 | import { TAuthorWrite } from '../types/general'; 6 | import { sendNotFoundResponse, sendSuccessNoDataResponse, sendSuccessResponse } from '../utils/responseHandler'; 7 | 8 | export const listAuthors = async (request: Request, response: Response, next: NextFunction) => { 9 | try { 10 | const authors = await AuthorService.listAuthors(); 11 | return sendSuccessResponse(response, authors); 12 | } catch (error: any) { 13 | next(error); 14 | } 15 | }; 16 | 17 | export const getAuthor = async (request: Request, response: Response, next: NextFunction) => { 18 | try { 19 | const id = parseInt(request.params.id, 10); 20 | const author = await AuthorService.getAuthor(id); 21 | return sendSuccessResponse(response, author); 22 | } catch (error: any) { 23 | next(error); 24 | } 25 | }; 26 | 27 | export const createAuthor = async (request: Request, response: Response, next: NextFunction) => { 28 | try { 29 | const author: TAuthorWrite = request.body; 30 | const newAuthor = await AuthorService.createAuthor(author); 31 | return sendSuccessResponse(response, newAuthor, HttpStatusCode.CREATED); 32 | } catch (error: any) { 33 | next(error); 34 | } 35 | }; 36 | 37 | export const updateAuthor = async (request: Request, response: Response, next: NextFunction) => { 38 | try { 39 | const id = parseInt(request.params.id, 10); 40 | const author: TAuthorWrite = request.body; 41 | const updatedAuthor = await AuthorService.updateAuthor(author, id); 42 | return sendSuccessResponse(response, updatedAuthor); 43 | } catch (error: any) { 44 | next(error); 45 | } 46 | }; 47 | 48 | export const deleteAuthor = async (request: Request, response: Response, next: NextFunction) => { 49 | try { 50 | const id = parseInt(request.params.id, 10); 51 | await AuthorService.deleteAuthor(id); 52 | return sendSuccessNoDataResponse(response, 'Author has been deleted'); 53 | } catch (error: any) { 54 | next(error); 55 | } 56 | }; 57 | 58 | export const checkExistingAuthor = async (request: Request, response: Response, next: NextFunction) => { 59 | try { 60 | const id = parseInt(request.params.id, 10); 61 | const author = await AuthorService.getAuthor(id); 62 | if (!author) { 63 | return sendNotFoundResponse(response, 'Author Not Found'); 64 | } 65 | next(); 66 | } catch (error) { 67 | next(error); 68 | } 69 | }; 70 | 71 | export const validateAuthorData = (request: Request, response: Response, next: NextFunction) => { 72 | try { 73 | const author = request.body; 74 | authorSchema.parse(author); 75 | next(); 76 | } catch (error) { 77 | next(error); 78 | } 79 | }; 80 | -------------------------------------------------------------------------------- /src/controllers/book.controller.ts: -------------------------------------------------------------------------------- 1 | import HttpStatusCode from '../utils/HttpStatusCode'; 2 | import * as BookService from '../services/book.service'; 3 | import * as AuthorService from '../services/author.service'; 4 | import { NextFunction, Request, Response } from 'express'; 5 | import { bookSchema } from '../types/zod'; 6 | import { TBookWrite } from '../types/general'; 7 | import { sendNotFoundResponse, sendSuccessNoDataResponse, sendSuccessResponse } from '../utils/responseHandler'; 8 | 9 | export const listBooks = async (request: Request, response: Response, next: NextFunction) => { 10 | try { 11 | const books = await BookService.listBooks(); 12 | return sendSuccessResponse(response, books); 13 | } catch (error: any) { 14 | next(error); 15 | } 16 | }; 17 | 18 | export const getBook = async (request: Request, response: Response, next: NextFunction) => { 19 | try { 20 | const id = parseInt(request.params.id, 10); 21 | const book = await BookService.getBook(id); 22 | return sendSuccessResponse(response, book); 23 | } catch (error: any) { 24 | next(error); 25 | } 26 | }; 27 | 28 | export const createBook = async (request: Request, response: Response, next: NextFunction) => { 29 | try { 30 | const book: TBookWrite = request.body; 31 | book.datePublished = new Date(book.datePublished); 32 | const newBook = await BookService.createBook(book); 33 | return sendSuccessResponse(response, newBook, HttpStatusCode.CREATED); 34 | } catch (error: any) { 35 | next(error); 36 | } 37 | }; 38 | 39 | export const updateBook = async (request: Request, response: Response, next: NextFunction) => { 40 | try { 41 | const id = parseInt(request.params.id, 10); 42 | const book: TBookWrite = request.body; 43 | book.datePublished = new Date(book.datePublished); 44 | const updateBook = await BookService.updateBook(book, id); 45 | return sendSuccessResponse(response, updateBook); 46 | } catch (error: any) { 47 | next(error); 48 | } 49 | }; 50 | 51 | export const deleteBook = async (request: Request, response: Response, next: NextFunction) => { 52 | try { 53 | const id = parseInt(request.params.id, 10); 54 | await BookService.deleteBook(id); 55 | return sendSuccessNoDataResponse(response, 'Book has been deleted'); 56 | } catch (error: any) { 57 | next(error); 58 | } 59 | }; 60 | 61 | export const checkExistingBook = async (request: Request, response: Response, next: NextFunction) => { 62 | try { 63 | const id = parseInt(request.params.id, 10); 64 | const existingBook = await BookService.getBook(id); 65 | if (!existingBook) { 66 | return sendNotFoundResponse(response, 'Book Not Found'); 67 | } 68 | next(); 69 | } catch (error) { 70 | next(error); 71 | } 72 | }; 73 | 74 | export const checkExistingBookAuthor = async (request: Request, response: Response, next: NextFunction) => { 75 | try { 76 | const authorId: number = request.body.authorId; 77 | 78 | const existingAuthor = await AuthorService.getAuthor(authorId); 79 | 80 | if (!existingAuthor) { 81 | return sendNotFoundResponse(response, 'Author Not Found'); 82 | } 83 | next(); 84 | } catch (error) { 85 | next(error); 86 | } 87 | }; 88 | 89 | export const validateBookData = (request: Request, response: Response, next: NextFunction) => { 90 | try { 91 | const book = request.body; 92 | book.datePublished = new Date(book.datePublished); 93 | bookSchema.parse(book); 94 | next(); 95 | } catch (error) { 96 | next(error); 97 | } 98 | }; 99 | -------------------------------------------------------------------------------- /src/controllers/profile.controller.ts: -------------------------------------------------------------------------------- 1 | import { TuserUpdateSchema, userUpdateSchema } from '../types/zod'; 2 | import { NextFunction, Request, Response } from 'express'; 3 | import * as UserService from '../services/user.service'; 4 | import { sendSuccessResponse } from '../utils/responseHandler'; 5 | import { hashPassword } from '../utils/bcryptHandler'; 6 | 7 | export const getUserProfile = async (request: Request, response: Response, next: NextFunction) => { 8 | try { 9 | const user = { 10 | id: request.user?.id, 11 | fullName: request.user?.fullName, 12 | username: request.user?.username, 13 | email: request.user?.email, 14 | }; 15 | return sendSuccessResponse(response, user); 16 | } catch (error) { 17 | next(error); 18 | } 19 | }; 20 | 21 | export const updateUserProfile = async (request: Request, response: Response, next: NextFunction) => { 22 | try { 23 | let userId = ''; 24 | if (request.user) { 25 | userId = request.user?.id; 26 | } 27 | const data: TuserUpdateSchema = request.body; 28 | const password = data.password; 29 | const hashedPassword = await hashPassword(password); 30 | const dataWithHash = { ...data, password: hashedPassword }; 31 | 32 | const user = await UserService.updateUserByID(userId, dataWithHash); 33 | return sendSuccessResponse(response, user); 34 | } catch (error) { 35 | next(error); 36 | } 37 | }; 38 | 39 | export const validateUpdateUserProfile = (request: Request, response: Response, next: NextFunction) => { 40 | try { 41 | const data = request.body; 42 | userUpdateSchema.parse(data); 43 | next(); 44 | } catch (error) { 45 | next(error); 46 | } 47 | }; 48 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import * as dotenv from 'dotenv'; 2 | import express from 'express'; 3 | import cors from 'cors'; 4 | import authorRouter from './routes/author.router'; 5 | import bookRouter from './routes/book.router'; 6 | import authRouter from './routes/auth.router'; 7 | import profileRouter from './routes/profile.router'; 8 | import { notFoundHandler } from './middleware/not-found'; 9 | import { errorHandler } from './middleware/error-handler'; 10 | import cookieParser from 'cookie-parser'; 11 | import requestLogger from './middleware/requestLogger'; 12 | import { pino } from "pino"; 13 | 14 | dotenv.config(); 15 | 16 | export const logger = pino({ name: "server start" }); 17 | const PORT: number = parseInt(process.env.PORT as string, 10); 18 | 19 | const app = express(); 20 | 21 | // CORS Middleware 22 | const corsOptions = { 23 | origin: process.env.APP_ENV == 'developement' ? '*' : process.env.ORIGIN, 24 | methods: 'GET,HEAD,PUT,PATCH,POST,DELETE', 25 | credentials: true, 26 | optionsSuccessStatus: 204, 27 | }; 28 | app.use(cors(corsOptions)); 29 | // JSON Middleware & Form Data 30 | app.use(express.json()); 31 | app.use(express.urlencoded({ extended: true })); 32 | 33 | // cookie parser middleware 34 | app.use(cookieParser()); 35 | 36 | // Request Logger 37 | app.use(requestLogger) 38 | 39 | // Main Routes 40 | app.use('/api/auth', authRouter); 41 | app.use('/api/profile', profileRouter); 42 | app.use('/api/authors', authorRouter); 43 | app.use('/api/books', bookRouter); 44 | 45 | // Not Found Middleware 46 | app.use(notFoundHandler); 47 | 48 | // Error Handling Middleware 49 | app.use(errorHandler); 50 | 51 | app.listen(PORT, () => { 52 | logger.info(`Listening on PORT ${PORT}`); 53 | }); 54 | -------------------------------------------------------------------------------- /src/middleware/auth-middleware.ts: -------------------------------------------------------------------------------- 1 | import * as UserService from '../services/user.service'; 2 | import { NextFunction, Request, Response } from 'express'; 3 | import { sendBadRequestResponse } from '../utils/responseHandler'; 4 | import { verifyToken } from '../utils/jwtHandler'; 5 | 6 | const protectAuth = async (request: Request, response: Response, next: NextFunction) => { 7 | const allCookies = request.cookies; 8 | const token = allCookies.jwt; 9 | if (token) { 10 | try { 11 | const decoded = verifyToken(token); 12 | const authUser = await UserService.getUserByID(decoded.id); 13 | if (authUser?.username) { 14 | request.user = authUser; 15 | } 16 | next(); 17 | } catch (error: any) { 18 | next(error); 19 | } 20 | } else { 21 | return sendBadRequestResponse(response, 'Unauthorized - you need to login'); 22 | } 23 | }; 24 | 25 | export { protectAuth }; 26 | -------------------------------------------------------------------------------- /src/middleware/error-handler.ts: -------------------------------------------------------------------------------- 1 | import { Prisma } from '@prisma/client'; 2 | import { Request, Response, NextFunction } from 'express'; 3 | import { z } from 'zod'; 4 | import { JsonWebTokenError } from 'jsonwebtoken'; 5 | import { sendBadRequestResponse, sendErrorResponse, sendValidationError } from '../utils/responseHandler'; 6 | 7 | export const errorHandler = (error: any, request: Request, response: Response, next: NextFunction) => { 8 | // Log the error stack for debugging purposes 9 | 10 | /* 11 | 12 | REPLACE IT WITH WINSTON 13 | console.error(error.stack); 14 | */ 15 | 16 | // Handle Zod validation errors 17 | if (error instanceof z.ZodError) { 18 | const errors = error.errors.map((e: any) => e.message) as string[]; 19 | return sendValidationError(response, 'Validation Error', errors); 20 | } 21 | 22 | // Handle known Prisma errors 23 | if (error instanceof Prisma.PrismaClientKnownRequestError) { 24 | const res = 25 | process.env.APP_ENV == 'developement' 26 | ? { error: 'Prisma Error occurred', details: error } 27 | : { error: 'Error occurred' }; 28 | 29 | return sendBadRequestResponse(response, res); 30 | } 31 | 32 | // Handle Json Web Token Error 33 | if (error instanceof JsonWebTokenError) { 34 | const res = 35 | process.env.APP_ENV == 'developement' 36 | ? { error: 'Json Web Token Error occurred', message: error } 37 | : { error: 'Error occurred' }; 38 | return sendBadRequestResponse(response, res); 39 | } 40 | 41 | // Handle other types of errors 42 | const res = process.env.APP_ENV == 'developement' ? { message: error.message } : { message: 'Internal Server Error' }; 43 | return sendErrorResponse(response, res); 44 | }; 45 | -------------------------------------------------------------------------------- /src/middleware/not-found.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response, NextFunction } from 'express'; 2 | import { sendNotFoundResponse } from '../utils/responseHandler'; 3 | 4 | export const notFoundHandler = (request: Request, response: Response, next: NextFunction) => { 5 | const notFoundMessage = { 6 | Requested_URL: request.originalUrl, 7 | success: false, 8 | error: 'Error 404 - Not Found', 9 | }; 10 | return sendNotFoundResponse(response, notFoundMessage); 11 | }; 12 | -------------------------------------------------------------------------------- /src/middleware/requestLogger.ts: -------------------------------------------------------------------------------- 1 | import type { ServerResponse } from "node:http"; 2 | import type { RequestHandler } from "express"; 3 | import pino, { type LevelWithSilent } from "pino"; 4 | 5 | enum LogLevel { 6 | Fatal = "fatal", 7 | Error = "error", 8 | Warn = "warn", 9 | Info = "info", 10 | Debug = "debug", 11 | Trace = "trace", 12 | Silent = "silent", 13 | } 14 | 15 | const customLogLevel = (res: ServerResponse, err?: Error): LevelWithSilent => { 16 | if (err || res.statusCode >= 500) return LogLevel.Error; 17 | if (res.statusCode >= 400) return LogLevel.Warn; 18 | if (res.statusCode >= 300) return LogLevel.Silent; 19 | return LogLevel.Info; 20 | }; 21 | 22 | const logger = pino({ 23 | level: "info", 24 | timestamp: pino.stdTimeFunctions.isoTime, 25 | base: null, 26 | }); 27 | 28 | const requestLogger: RequestHandler = (req, res, next) => { 29 | const startTime = process.hrtime(); 30 | 31 | res.on("finish", () => { 32 | const [seconds, nanoseconds] = process.hrtime(startTime); 33 | const duration = (seconds * 1000 + nanoseconds / 1e6).toFixed(2); // duration in ms 34 | 35 | const level = customLogLevel(res); 36 | if (level === LogLevel.Silent) return; 37 | 38 | logger[level](`${req.method} ${req.originalUrl} - ${res.statusCode} (${duration}ms) `); 39 | }); 40 | 41 | next(); 42 | }; 43 | 44 | export default requestLogger; 45 | -------------------------------------------------------------------------------- /src/routes/auth.router.ts: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import * as AuthController from '../controllers/auth.controller'; 3 | import { protectAuth } from '../middleware/auth-middleware'; 4 | const router = express.Router(); 5 | 6 | // Acess : public 7 | // POST : login 8 | // Params body : username , password 9 | router.post('/login', AuthController.validateLoginData, AuthController.login); 10 | 11 | // Acess : Private 12 | // POST : logout 13 | 14 | router.post('/logout', protectAuth, AuthController.logout); 15 | 16 | export default router; 17 | -------------------------------------------------------------------------------- /src/routes/author.router.ts: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import * as AuthorController from '../controllers/author.controller'; 3 | import { protectAuth } from '../middleware/auth-middleware'; 4 | 5 | const router = express.Router(); 6 | 7 | // Acess : Public 8 | // GET : Get List of all authors 9 | router.get('/', AuthorController.listAuthors); 10 | 11 | // Acess : Public 12 | // GET : Get One author by ID 13 | // Params query : id 14 | router.get('/:id', AuthorController.checkExistingAuthor, AuthorController.getAuthor); 15 | 16 | // Acess : Private 17 | // POST : Create one author 18 | // Params body : firstName , lastName 19 | router.post('/', protectAuth, AuthorController.validateAuthorData, AuthorController.createAuthor); 20 | 21 | // Acess : Private 22 | // PUT : update an author 23 | // Params query : id 24 | // Params body : firstName , lastName 25 | router.put( 26 | '/:id', 27 | protectAuth, 28 | AuthorController.validateAuthorData, 29 | AuthorController.checkExistingAuthor, 30 | AuthorController.updateAuthor 31 | ); 32 | 33 | // Acess : Private 34 | // DELETE : delete an author 35 | // Params query : id 36 | router.delete('/:id', protectAuth, AuthorController.checkExistingAuthor, AuthorController.deleteAuthor); 37 | 38 | export default router; 39 | -------------------------------------------------------------------------------- /src/routes/book.router.ts: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import * as BookController from '../controllers/book.controller'; 3 | import { protectAuth } from '../middleware/auth-middleware'; 4 | 5 | const router = express.Router(); 6 | 7 | // Acess : Public 8 | // GET: List all the books 9 | router.get('/', BookController.listBooks); 10 | 11 | // Acess : Public 12 | // GET : Get One book by ID 13 | // Params query : id 14 | router.get('/:id', BookController.checkExistingBook, BookController.getBook); 15 | 16 | // Acess : Private 17 | // POST : Create one book 18 | // Params body : title , authorId , datePublished , isFiction 19 | router.post( 20 | '/', 21 | protectAuth, 22 | BookController.validateBookData, 23 | BookController.checkExistingBookAuthor, 24 | BookController.createBook 25 | ); 26 | 27 | // Acess : Private 28 | // PUT : update one book 29 | // Params query : id 30 | // Params body : title , authorId , datePublished , isFiction 31 | router.put( 32 | '/:id', 33 | protectAuth, 34 | BookController.validateBookData, 35 | BookController.checkExistingBook, 36 | BookController.checkExistingBookAuthor, 37 | BookController.updateBook 38 | ); 39 | 40 | // Acess : Private 41 | // DELETE : delete a book 42 | // Params query : id 43 | router.delete('/:id', protectAuth, BookController.checkExistingBook, BookController.deleteBook); 44 | 45 | export default router; 46 | -------------------------------------------------------------------------------- /src/routes/profile.router.ts: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import * as ProfileController from '../controllers/profile.controller'; 3 | import { protectAuth } from '../middleware/auth-middleware'; 4 | const router = express.Router(); 5 | 6 | // Acess : Private 7 | // GET : profile information ( fullName , username , email ) 8 | router.get('/', protectAuth, ProfileController.getUserProfile); 9 | 10 | // Acess : Private 11 | // PUT : Change profile information 12 | // Params body : fullName , username , password , email 13 | router.put('/', protectAuth, ProfileController.validateUpdateUserProfile, ProfileController.updateUserProfile); 14 | 15 | export default router; 16 | -------------------------------------------------------------------------------- /src/services/author.service.ts: -------------------------------------------------------------------------------- 1 | import { db } from '../utils/db.server'; 2 | import { TAuthorRead, TAuthorID, TAuthorWrite } from '../types/general'; 3 | 4 | export const listAuthors = async (): Promise => { 5 | return db.author.findMany({ 6 | select: { 7 | id: true, 8 | firstName: true, 9 | lastName: true, 10 | createdAt: true, 11 | updatedAt: true, 12 | }, 13 | }); 14 | }; 15 | 16 | export const getAuthor = async (id: TAuthorID): Promise => { 17 | return db.author.findUnique({ 18 | where: { 19 | id: id, 20 | }, 21 | }); 22 | }; 23 | 24 | export const createAuthor = async (author: TAuthorWrite): Promise => { 25 | const { firstName, lastName } = author; 26 | return db.author.create({ 27 | data: { 28 | firstName, 29 | lastName, 30 | }, 31 | select: { 32 | id: true, 33 | firstName: true, 34 | lastName: true, 35 | }, 36 | }); 37 | }; 38 | 39 | export const updateAuthor = async (author: TAuthorWrite, id: TAuthorID): Promise => { 40 | const { firstName, lastName } = author; 41 | return db.author.update({ 42 | where: { 43 | id: id, 44 | }, 45 | data: { 46 | firstName, 47 | lastName, 48 | }, 49 | select: { 50 | id: true, 51 | firstName: true, 52 | lastName: true, 53 | }, 54 | }); 55 | }; 56 | 57 | export const deleteAuthor = async (id: TAuthorID): Promise => { 58 | await db.author.delete({ 59 | where: { 60 | id: id, 61 | }, 62 | }); 63 | }; 64 | -------------------------------------------------------------------------------- /src/services/book.service.ts: -------------------------------------------------------------------------------- 1 | import { db } from '../utils/db.server'; 2 | import { TBookID, TBookRead, TBookWrite } from '../types/general'; 3 | 4 | export const listBooks = async (): Promise => { 5 | return db.book.findMany({ 6 | select: { 7 | id: true, 8 | title: true, 9 | datePublished: true, 10 | isFiction: true, 11 | author: { 12 | select: { 13 | id: true, 14 | firstName: true, 15 | lastName: true, 16 | }, 17 | }, 18 | }, 19 | }); 20 | }; 21 | 22 | export const getBook = async (id: TBookID): Promise => { 23 | return db.book.findUnique({ 24 | where: { 25 | id, 26 | }, 27 | select: { 28 | id: true, 29 | title: true, 30 | isFiction: true, 31 | datePublished: true, 32 | author: { 33 | select: { 34 | id: true, 35 | firstName: true, 36 | lastName: true, 37 | }, 38 | }, 39 | }, 40 | }); 41 | }; 42 | 43 | export const createBook = async (book: TBookWrite): Promise => { 44 | const { title, authorId, datePublished, isFiction } = book; 45 | const parsedDate: Date = new Date(datePublished); 46 | 47 | return db.book.create({ 48 | data: { 49 | title, 50 | authorId, 51 | isFiction, 52 | datePublished: parsedDate, 53 | }, 54 | select: { 55 | id: true, 56 | title: true, 57 | isFiction: true, 58 | datePublished: true, 59 | author: { 60 | select: { 61 | id: true, 62 | firstName: true, 63 | lastName: true, 64 | }, 65 | }, 66 | }, 67 | }); 68 | }; 69 | 70 | export const updateBook = async (book: TBookWrite, id: TBookID): Promise => { 71 | const { title, isFiction, datePublished, authorId } = book; 72 | return db.book.update({ 73 | where: { 74 | id, 75 | }, 76 | data: { 77 | title, 78 | isFiction, 79 | datePublished, 80 | authorId, 81 | }, 82 | select: { 83 | id: true, 84 | title: true, 85 | isFiction: true, 86 | datePublished: true, 87 | author: { 88 | select: { 89 | id: true, 90 | firstName: true, 91 | lastName: true, 92 | }, 93 | }, 94 | }, 95 | }); 96 | }; 97 | 98 | export const deleteBook = async (id: TBookID): Promise => { 99 | await db.book.delete({ 100 | where: { 101 | id, 102 | }, 103 | }); 104 | }; 105 | -------------------------------------------------------------------------------- /src/services/user.service.ts: -------------------------------------------------------------------------------- 1 | import { TuserUpdateSchema } from './../types/zod'; 2 | import { db } from '../utils/db.server'; 3 | import { TloginRead, TloginRequest } from '../types/general'; 4 | 5 | export const getUserByUsername = async (username: string): Promise => { 6 | return db.user.findUnique({ 7 | where: { 8 | username: username, 9 | }, 10 | select: { 11 | id: true, 12 | fullName: true, 13 | username: true, 14 | email: true, 15 | password: true, 16 | }, 17 | }); 18 | }; 19 | 20 | export const getUserByID = async (id: string): Promise => { 21 | return db.user.findUnique({ 22 | where: { 23 | id: id, 24 | }, 25 | select: { 26 | id: true, 27 | fullName: true, 28 | username: true, 29 | email: true, 30 | }, 31 | }); 32 | }; 33 | 34 | export const updateUserByID = async ( 35 | id: string, 36 | data: TuserUpdateSchema 37 | ): Promise | null> => { 38 | return db.user.update({ 39 | where: { 40 | id: id, 41 | }, 42 | data: data, 43 | select: { 44 | fullName: true, 45 | username: true, 46 | email: true, 47 | }, 48 | }); 49 | }; 50 | -------------------------------------------------------------------------------- /src/types/express.d.ts: -------------------------------------------------------------------------------- 1 | import { TloginRequest } from './general'; 2 | 3 | declare module 'express' { 4 | interface Request { 5 | user?: TloginRequest; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/types/general.ts: -------------------------------------------------------------------------------- 1 | import { Author, Book, User } from '@prisma/client'; 2 | 3 | // _____________ Author Types _____________ 4 | 5 | export type TAuthorID = Author['id']; 6 | export type TAuthorRead = Omit; 7 | export type TAuthorWrite = Omit; 8 | 9 | // _____________ Book Types _____________ 10 | 11 | export type TBookID = Book['id']; 12 | export type TBookRead = Pick & { 13 | author: TAuthorRead; 14 | }; 15 | export type TBookWrite = Omit; 16 | 17 | // _____________ User Types _____________ 18 | export type TUserRegisterWrite = Omit; 19 | export type TloginRead = Omit; 20 | export type TloginRequest = Omit; 21 | -------------------------------------------------------------------------------- /src/types/zod.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | 3 | // _____________ Author Schema _____________ 4 | 5 | export const authorSchema = z.object({ 6 | firstName: z.string().min(1, { message: 'Your first name must be at least 1 characters long' }).max(30, { 7 | message: 'your first name cannot be longer than 30 characters', 8 | }), 9 | lastName: z.string().min(1, { message: 'Your last name must be at least 1 characters long' }).max(30, { 10 | message: 'your last name cannot be longer than 30 characters', 11 | }), 12 | }); 13 | 14 | // _____________ Book Schema _____________ 15 | 16 | export const bookSchema = z.object({ 17 | title: z.string().min(1, { message: 'title must be at least 1 characters long' }).max(250, { 18 | message: 'title cannot be longer than 250 characters', 19 | }), 20 | authorId: z.number(), 21 | datePublished: z.date(), 22 | isFiction: z.boolean(), 23 | }); 24 | 25 | // _____________ User Schema Login _____________ 26 | 27 | const userBaseSchema = { 28 | username: z.string().min(1, { message: 'username must be at least 1 characters long' }).max(50, { 29 | message: 'username cannot be longer than 50 characters', 30 | }), 31 | password: z.string().min(1, { message: 'password must be at least 1 characters long' }).max(50, { 32 | message: 'password cannot be longer than 50 characters', 33 | }), 34 | }; 35 | 36 | export const userSchema = z.object(userBaseSchema); 37 | 38 | // _____________ User Update Schema _____________ 39 | 40 | export const userUpdateSchema = z.object({ 41 | ...userBaseSchema, 42 | fullName: z.string().min(1, { message: 'fullName must be at least 1 characters long' }).max(50, { 43 | message: 'fullName cannot be longer than 50 characters', 44 | }), 45 | email: z.string().email({ message: 'Invalid email address' }), 46 | }); 47 | 48 | // _____________ Export Types _____________ 49 | 50 | export type TUserSchema = z.infer; 51 | export type TuserUpdateSchema = z.infer; 52 | -------------------------------------------------------------------------------- /src/utils/HttpStatusCode.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Hypertext Transfer Protocol (HTTP) response status codes. 5 | * @see {@link https://en.wikipedia.org/wiki/List_of_HTTP_status_codes} 6 | */ 7 | 8 | enum HttpStatusCode { 9 | /** 10 | * The server has received the request headers and the client should proceed to send the request body 11 | * (in the case of a request for which a body needs to be sent; for example, a POST request). 12 | * Sending a large request body to a server after a request has been rejected for inappropriate headers would be inefficient. 13 | * To have a server check the request's headers, a client must send Expect: 100-continue as a header in its initial request 14 | * and receive a 100 Continue status code in response before sending the body. The response 417 Expectation Failed indicates the request should not be continued. 15 | */ 16 | CONTINUE = 100, 17 | 18 | /** 19 | * The requester has asked the server to switch protocols and the server has agreed to do so. 20 | */ 21 | SWITCHING_PROTOCOLS = 101, 22 | 23 | /** 24 | * A WebDAV request may contain many sub-requests involving file operations, requiring a long time to complete the request. 25 | * This code indicates that the server has received and is processing the request, but no response is available yet. 26 | * This prevents the client from timing out and assuming the request was lost. 27 | */ 28 | PROCESSING = 102, 29 | 30 | /** 31 | * Standard response for successful HTTP requests. 32 | * The actual response will depend on the request method used. 33 | * In a GET request, the response will contain an entity corresponding to the requested resource. 34 | * In a POST request, the response will contain an entity describing or containing the result of the action. 35 | */ 36 | OK = 200, 37 | 38 | /** 39 | * The request has been fulfilled, resulting in the creation of a new resource. 40 | */ 41 | CREATED = 201, 42 | 43 | /** 44 | * The request has been accepted for processing, but the processing has not been completed. 45 | * The request might or might not be eventually acted upon, and may be disallowed when processing occurs. 46 | */ 47 | ACCEPTED = 202, 48 | 49 | /** 50 | * SINCE HTTP/1.1 51 | * The server is a transforming proxy that received a 200 OK from its origin, 52 | * but is returning a modified version of the origin's response. 53 | */ 54 | NON_AUTHORITATIVE_INFORMATION = 203, 55 | 56 | /** 57 | * The server successfully processed the request and is not returning any content. 58 | */ 59 | NO_CONTENT = 204, 60 | 61 | /** 62 | * The server successfully processed the request, but is not returning any content. 63 | * Unlike a 204 response, this response requires that the requester reset the document view. 64 | */ 65 | RESET_CONTENT = 205, 66 | 67 | /** 68 | * The server is delivering only part of the resource (byte serving) due to a range header sent by the client. 69 | * The range header is used by HTTP clients to enable resuming of interrupted downloads, 70 | * or split a download into multiple simultaneous streams. 71 | */ 72 | PARTIAL_CONTENT = 206, 73 | 74 | /** 75 | * The message body that follows is an XML message and can contain a number of separate response codes, 76 | * depending on how many sub-requests were made. 77 | */ 78 | MULTI_STATUS = 207, 79 | 80 | /** 81 | * The members of a DAV binding have already been enumerated in a preceding part of the (multistatus) response, 82 | * and are not being included again. 83 | */ 84 | ALREADY_REPORTED = 208, 85 | 86 | /** 87 | * The server has fulfilled a request for the resource, 88 | * and the response is a representation of the result of one or more instance-manipulations applied to the current instance. 89 | */ 90 | IM_USED = 226, 91 | 92 | /** 93 | * Indicates multiple options for the resource from which the client may choose (via agent-driven content negotiation). 94 | * For example, this code could be used to present multiple video format options, 95 | * to list files with different filename extensions, or to suggest word-sense disambiguation. 96 | */ 97 | MULTIPLE_CHOICES = 300, 98 | 99 | /** 100 | * This and all future requests should be directed to the given URI. 101 | */ 102 | MOVED_PERMANENTLY = 301, 103 | 104 | /** 105 | * This is an example of industry practice contradicting the standard. 106 | * The HTTP/1.0 specification (RFC 1945) required the client to perform a temporary redirect 107 | * (the original describing phrase was "Moved Temporarily"), but popular browsers implemented 302 108 | * with the functionality of a 303 See Other. Therefore, HTTP/1.1 added status codes 303 and 307 109 | * to distinguish between the two behaviours. However, some Web applications and frameworks 110 | * use the 302 status code as if it were the 303. 111 | */ 112 | FOUND = 302, 113 | 114 | /** 115 | * SINCE HTTP/1.1 116 | * The response to the request can be found under another URI using a GET method. 117 | * When received in response to a POST (or PUT/DELETE), the client should presume that 118 | * the server has received the data and should issue a redirect with a separate GET message. 119 | */ 120 | SEE_OTHER = 303, 121 | 122 | /** 123 | * Indicates that the resource has not been modified since the version specified by the request headers If-Modified-Since or If-None-Match. 124 | * In such case, there is no need to retransmit the resource since the client still has a previously-downloaded copy. 125 | */ 126 | NOT_MODIFIED = 304, 127 | 128 | /** 129 | * SINCE HTTP/1.1 130 | * The requested resource is available only through a proxy, the address for which is provided in the response. 131 | * Many HTTP clients (such as Mozilla and Internet Explorer) do not correctly handle responses with this status code, primarily for security reasons. 132 | */ 133 | USE_PROXY = 305, 134 | 135 | /** 136 | * No longer used. Originally meant "Subsequent requests should use the specified proxy." 137 | */ 138 | SWITCH_PROXY = 306, 139 | 140 | /** 141 | * SINCE HTTP/1.1 142 | * In this case, the request should be repeated with another URI; however, future requests should still use the original URI. 143 | * In contrast to how 302 was historically implemented, the request method is not allowed to be changed when reissuing the original request. 144 | * For example, a POST request should be repeated using another POST request. 145 | */ 146 | TEMPORARY_REDIRECT = 307, 147 | 148 | /** 149 | * The request and all future requests should be repeated using another URI. 150 | * 307 and 308 parallel the behaviors of 302 and 301, but do not allow the HTTP method to change. 151 | * So, for example, submitting a form to a permanently redirected resource may continue smoothly. 152 | */ 153 | PERMANENT_REDIRECT = 308, 154 | 155 | /** 156 | * The server cannot or will not process the request due to an apparent client error 157 | * (e.g., malformed request syntax, too large size, invalid request message framing, or deceptive request routing). 158 | */ 159 | BAD_REQUEST = 400, 160 | 161 | /** 162 | * Similar to 403 Forbidden, but specifically for use when authentication is required and has failed or has not yet 163 | * been provided. The response must include a WWW-Authenticate header field containing a challenge applicable to the 164 | * requested resource. See Basic access authentication and Digest access authentication. 401 semantically means 165 | * "unauthenticated",i.e. the user does not have the necessary credentials. 166 | */ 167 | UNAUTHORIZED = 401, 168 | 169 | /** 170 | * Reserved for future use. The original intention was that this code might be used as part of some form of digital 171 | * cash or micro payment scheme, but that has not happened, and this code is not usually used. 172 | * Google Developers API uses this status if a particular developer has exceeded the daily limit on requests. 173 | */ 174 | PAYMENT_REQUIRED = 402, 175 | 176 | /** 177 | * The request was valid, but the server is refusing action. 178 | * The user might not have the necessary permissions for a resource. 179 | */ 180 | FORBIDDEN = 403, 181 | 182 | /** 183 | * The requested resource could not be found but may be available in the future. 184 | * Subsequent requests by the client are permissible. 185 | */ 186 | NOT_FOUND = 404, 187 | 188 | /** 189 | * A request method is not supported for the requested resource; 190 | * for example, a GET request on a form that requires data to be presented via POST, or a PUT request on a read-only resource. 191 | */ 192 | METHOD_NOT_ALLOWED = 405, 193 | 194 | /** 195 | * The requested resource is capable of generating only content not acceptable according to the Accept headers sent in the request. 196 | */ 197 | NOT_ACCEPTABLE = 406, 198 | 199 | /** 200 | * The client must first authenticate itself with the proxy. 201 | */ 202 | PROXY_AUTHENTICATION_REQUIRED = 407, 203 | 204 | /** 205 | * The server timed out waiting for the request. 206 | * According to HTTP specifications: 207 | * "The client did not produce a request within the time that the server was prepared to wait. The client MAY repeat the request without modifications at any later time." 208 | */ 209 | REQUEST_TIMEOUT = 408, 210 | 211 | /** 212 | * Indicates that the request could not be processed because of conflict in the request, 213 | * such as an edit conflict between multiple simultaneous updates. 214 | */ 215 | CONFLICT = 409, 216 | 217 | /** 218 | * Indicates that the resource requested is no longer available and will not be available again. 219 | * This should be used when a resource has been intentionally removed and the resource should be purged. 220 | * Upon receiving a 410 status code, the client should not request the resource in the future. 221 | * Clients such as search engines should remove the resource from their indices. 222 | * Most use cases do not require clients and search engines to purge the resource, and a "404 Not Found" may be used instead. 223 | */ 224 | GONE = 410, 225 | 226 | /** 227 | * The request did not specify the length of its content, which is required by the requested resource. 228 | */ 229 | LENGTH_REQUIRED = 411, 230 | 231 | /** 232 | * The server does not meet one of the preconditions that the requester put on the request. 233 | */ 234 | PRECONDITION_FAILED = 412, 235 | 236 | /** 237 | * The request is larger than the server is willing or able to process. Previously called "Request Entity Too Large". 238 | */ 239 | PAYLOAD_TOO_LARGE = 413, 240 | 241 | /** 242 | * The URI provided was too long for the server to process. Often the result of too much data being encoded as a query-string of a GET request, 243 | * in which case it should be converted to a POST request. 244 | * Called "Request-URI Too Long" previously. 245 | */ 246 | URI_TOO_LONG = 414, 247 | 248 | /** 249 | * The request entity has a media type which the server or resource does not support. 250 | * For example, the client uploads an image as image/svg+xml, but the server requires that images use a different format. 251 | */ 252 | UNSUPPORTED_MEDIA_TYPE = 415, 253 | 254 | /** 255 | * The client has asked for a portion of the file (byte serving), but the server cannot supply that portion. 256 | * For example, if the client asked for a part of the file that lies beyond the end of the file. 257 | * Called "Requested Range Not Satisfiable" previously. 258 | */ 259 | RANGE_NOT_SATISFIABLE = 416, 260 | 261 | /** 262 | * The server cannot meet the requirements of the Expect request-header field. 263 | */ 264 | EXPECTATION_FAILED = 417, 265 | 266 | /** 267 | * This code was defined in 1998 as one of the traditional IETF April Fools' jokes, in RFC 2324, Hyper Text Coffee Pot Control Protocol, 268 | * and is not expected to be implemented by actual HTTP servers. The RFC specifies this code should be returned by 269 | * teapots requested to brew coffee. This HTTP status is used as an Easter egg in some websites, including Google.com. 270 | */ 271 | I_AM_A_TEAPOT = 418, 272 | 273 | /** 274 | * The request was directed at a server that is not able to produce a response (for example because a connection reuse). 275 | */ 276 | MISDIRECTED_REQUEST = 421, 277 | 278 | /** 279 | * The request was well-formed but was unable to be followed due to semantic errors. 280 | */ 281 | UNPROCESSABLE_ENTITY = 422, 282 | 283 | /** 284 | * The resource that is being accessed is locked. 285 | */ 286 | LOCKED = 423, 287 | 288 | /** 289 | * The request failed due to failure of a previous request (e.g., a PROPPATCH). 290 | */ 291 | FAILED_DEPENDENCY = 424, 292 | 293 | /** 294 | * The client should switch to a different protocol such as TLS/1.0, given in the Upgrade header field. 295 | */ 296 | UPGRADE_REQUIRED = 426, 297 | 298 | /** 299 | * The origin server requires the request to be conditional. 300 | * Intended to prevent "the 'lost update' problem, where a client 301 | * GETs a resource's state, modifies it, and PUTs it back to the server, 302 | * when meanwhile a third party has modified the state on the server, leading to a conflict." 303 | */ 304 | PRECONDITION_REQUIRED = 428, 305 | 306 | /** 307 | * The user has sent too many requests in a given amount of time. Intended for use with rate-limiting schemes. 308 | */ 309 | TOO_MANY_REQUESTS = 429, 310 | 311 | /** 312 | * The server is unwilling to process the request because either an individual header field, 313 | * or all the header fields collectively, are too large. 314 | */ 315 | REQUEST_HEADER_FIELDS_TOO_LARGE = 431, 316 | 317 | /** 318 | * A server operator has received a legal demand to deny access to a resource or to a set of resources 319 | * that includes the requested resource. The code 451 was chosen as a reference to the novel Fahrenheit 451. 320 | */ 321 | UNAVAILABLE_FOR_LEGAL_REASONS = 451, 322 | 323 | /** 324 | * A generic error message, given when an unexpected condition was encountered and no more specific message is suitable. 325 | */ 326 | INTERNAL_SERVER_ERROR = 500, 327 | 328 | /** 329 | * The server either does not recognize the request method, or it lacks the ability to fulfill the request. 330 | * Usually this implies future availability (e.g., a new feature of a web-service API). 331 | */ 332 | NOT_IMPLEMENTED = 501, 333 | 334 | /** 335 | * The server was acting as a gateway or proxy and received an invalid response from the upstream server. 336 | */ 337 | BAD_GATEWAY = 502, 338 | 339 | /** 340 | * The server is currently unavailable (because it is overloaded or down for maintenance). 341 | * Generally, this is a temporary state. 342 | */ 343 | SERVICE_UNAVAILABLE = 503, 344 | 345 | /** 346 | * The server was acting as a gateway or proxy and did not receive a timely response from the upstream server. 347 | */ 348 | GATEWAY_TIMEOUT = 504, 349 | 350 | /** 351 | * The server does not support the HTTP protocol version used in the request 352 | */ 353 | HTTP_VERSION_NOT_SUPPORTED = 505, 354 | 355 | /** 356 | * Transparent content negotiation for the request results in a circular reference. 357 | */ 358 | VARIANT_ALSO_NEGOTIATES = 506, 359 | 360 | /** 361 | * The server is unable to store the representation needed to complete the request. 362 | */ 363 | INSUFFICIENT_STORAGE = 507, 364 | 365 | /** 366 | * The server detected an infinite loop while processing the request. 367 | */ 368 | LOOP_DETECTED = 508, 369 | 370 | /** 371 | * Further extensions to the request are required for the server to fulfill it. 372 | */ 373 | NOT_EXTENDED = 510, 374 | 375 | /** 376 | * The client needs to authenticate to gain network access. 377 | * Intended for use by intercepting proxies used to control access to the network (e.g., "captive portals" used 378 | * to require agreement to Terms of Service before granting full Internet access via a Wi-Fi hotspot). 379 | */ 380 | NETWORK_AUTHENTICATION_REQUIRED = 511, 381 | } 382 | 383 | export default HttpStatusCode; 384 | -------------------------------------------------------------------------------- /src/utils/bcryptHandler.ts: -------------------------------------------------------------------------------- 1 | import bcrypt from 'bcryptjs'; 2 | 3 | export const hashPassword = async (password: string): Promise => { 4 | const salt = await bcrypt.genSalt(10); 5 | const hashedPassword = await bcrypt.hash(password, salt); 6 | return hashedPassword; 7 | }; 8 | 9 | export const comparePasswords = async (dbPassword: string, incomingPassword: string): Promise => { 10 | return await bcrypt.compare(dbPassword, incomingPassword); 11 | }; 12 | -------------------------------------------------------------------------------- /src/utils/db.server.ts: -------------------------------------------------------------------------------- 1 | import { PrismaClient } from '@prisma/client'; 2 | 3 | let db: PrismaClient; 4 | 5 | declare global { 6 | var __db: PrismaClient | undefined; 7 | } 8 | 9 | if (!global.__db) { 10 | global.__db = new PrismaClient(); 11 | } 12 | 13 | db = global.__db; 14 | 15 | export { db }; 16 | -------------------------------------------------------------------------------- /src/utils/jwtHandler.ts: -------------------------------------------------------------------------------- 1 | import jwt from 'jsonwebtoken'; 2 | 3 | type TPayload = { id: string }; 4 | 5 | export const generateToken = (payload: TPayload, expiresIn: string): string => { 6 | return jwt.sign(payload, process.env.JWT_SECRET as string, { expiresIn: expiresIn }); 7 | }; 8 | 9 | export const verifyToken = (token: string): TPayload => { 10 | return jwt.verify(token, process.env.JWT_SECRET as string) as TPayload; 11 | }; 12 | -------------------------------------------------------------------------------- /src/utils/responseHandler.ts: -------------------------------------------------------------------------------- 1 | import { Response } from 'express'; 2 | import HttpStatusCode from './HttpStatusCode'; 3 | 4 | interface SuccessResponse { 5 | success: true; 6 | data: T; 7 | } 8 | 9 | interface ErrorResponse { 10 | success: false; 11 | error: { 12 | message: T; 13 | }; 14 | } 15 | 16 | // Success response with data 17 | export const sendSuccessResponse = ( 18 | res: Response, 19 | data: T, 20 | status = HttpStatusCode.OK 21 | ): Response> => { 22 | return res.status(status).json({ success: true, data }); 23 | }; 24 | 25 | // Success response without data (e.g., for delete operations) 26 | export const sendSuccessNoDataResponse = ( 27 | res: Response, 28 | message = 'Operation successful', 29 | status = HttpStatusCode.OK 30 | ): Response> => { 31 | return res.status(status).json({ success: true, message }); 32 | }; 33 | 34 | // Error response 35 | export const sendErrorResponse = ( 36 | res: Response, 37 | message: T, 38 | status = HttpStatusCode.INTERNAL_SERVER_ERROR 39 | ): Response> => { 40 | return res.status(status).json({ success: false, error: { message } }); 41 | }; 42 | 43 | // Not Found response 44 | export const sendNotFoundResponse = ( 45 | res: Response, 46 | message: T, 47 | status = HttpStatusCode.NOT_FOUND 48 | ): Response> => { 49 | return res.status(status).json({ success: false, error: { message } }); 50 | }; 51 | 52 | // Validation Error response 53 | export const sendValidationError = ( 54 | res: Response, 55 | message: T, 56 | errors: string[], 57 | status = HttpStatusCode.BAD_REQUEST 58 | ): Response> => { 59 | return res.status(status).json({ 60 | success: false, 61 | error: { 62 | message: message, 63 | errors: errors, 64 | }, 65 | }); 66 | }; 67 | 68 | // Unauthorized response 69 | export const sendUnauthorizedResponse = ( 70 | res: Response, 71 | message = 'Unauthorized', 72 | status = HttpStatusCode.UNAUTHORIZED 73 | ): Response> => { 74 | return res.status(status).json({ success: false, error: { message } }); 75 | }; 76 | 77 | // Forbidden response 78 | export const sendForbiddenResponse = ( 79 | res: Response, 80 | message = 'Forbidden', 81 | status = HttpStatusCode.FORBIDDEN 82 | ): Response> => { 83 | return res.status(status).json({ success: false, error: { message } }); 84 | }; 85 | 86 | // Bad Request response 87 | export const sendBadRequestResponse = ( 88 | res: Response, 89 | message: T, 90 | status = HttpStatusCode.BAD_REQUEST 91 | ): Response> => { 92 | return res.status(status).json({ success: false, error: { message } }); 93 | }; 94 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "typeRoots": ["./src/types", "./node_modules/@types"], 4 | /* Visit https://aka.ms/tsconfig to read more about this file */ 5 | 6 | /* Projects */ 7 | // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ 8 | // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ 9 | // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ 10 | // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ 11 | // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ 12 | // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ 13 | 14 | /* Language and Environment */ 15 | "target": "es2016" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */, 16 | // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ 17 | // "jsx": "preserve", /* Specify what JSX code is generated. */ 18 | // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */ 19 | // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ 20 | // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ 21 | // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ 22 | // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ 23 | // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ 24 | // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ 25 | // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ 26 | // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ 27 | 28 | /* Modules */ 29 | "module": "commonjs" /* Specify what module code is generated. */, 30 | "rootDir": "./src" /* Specify the root folder within your source files. */, 31 | // "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */ 32 | // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ 33 | // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ 34 | // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ 35 | // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ 36 | // "types": [], /* Specify type package names to be included without being referenced in a source file. */ 37 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 38 | // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ 39 | // "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */ 40 | // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */ 41 | // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */ 42 | // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */ 43 | "resolveJsonModule": true, 44 | // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */ 45 | // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ 46 | 47 | /* JavaScript Support */ 48 | // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ 49 | // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ 50 | // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ 51 | 52 | /* Emit */ 53 | // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ 54 | // "declarationMap": true, /* Create sourcemaps for d.ts files. */ 55 | // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ 56 | // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ 57 | // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ 58 | // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ 59 | "outDir": "./dist" /* Specify an output folder for all emitted files. */, 60 | // "removeComments": true, /* Disable emitting comments. */ 61 | // "noEmit": true, /* Disable emitting files from a compilation. */ 62 | // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ 63 | // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ 64 | // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ 65 | // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ 66 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 67 | // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ 68 | // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ 69 | // "newLine": "crlf", /* Set the newline character for emitting files. */ 70 | // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ 71 | // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ 72 | // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ 73 | // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ 74 | // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ 75 | // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ 76 | 77 | /* Interop Constraints */ 78 | // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ 79 | // "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */ 80 | // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ 81 | "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */, 82 | // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ 83 | "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */, 84 | 85 | /* Type Checking */ 86 | "strict": true /* Enable all strict type-checking options. */, 87 | // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ 88 | // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ 89 | // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ 90 | // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ 91 | // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ 92 | // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ 93 | // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ 94 | // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ 95 | "noUnusedLocals": true /* Enable error reporting when local variables aren't read. */, 96 | // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ 97 | // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ 98 | // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ 99 | // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ 100 | // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ 101 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ 102 | // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ 103 | // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ 104 | // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ 105 | 106 | /* Completeness */ 107 | // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ 108 | "skipLibCheck": true /* Skip type checking all .d.ts files. */ 109 | }, 110 | "exclude": ["node_modules", "prisma", "dist"] 111 | } 112 | --------------------------------------------------------------------------------