From e0d2fb5b2a20ae9696d3c64a0d0dd55b0dd923a0 Mon Sep 17 00:00:00 2001 From: NgZiXin Date: Sat, 26 Oct 2024 23:24:49 +0800 Subject: [PATCH 01/24] Setup with TS --- server/dist/models/applicant.js | 2 + server/dist/routes/applicants.js | 51 + server/dist/validators/applicants.js | 8 + server/package-lock.json | 1292 ++++++++++++++++++++++++++ server/package.json | 25 + server/src/index.ts | 22 + server/src/models/applicant.ts | 6 + server/src/routes/applicant.ts | 59 ++ server/tsconfig.json | 14 + 9 files changed, 1479 insertions(+) create mode 100644 server/dist/models/applicant.js create mode 100644 server/dist/routes/applicants.js create mode 100644 server/dist/validators/applicants.js create mode 100644 server/package-lock.json create mode 100644 server/package.json create mode 100644 server/src/index.ts create mode 100644 server/src/models/applicant.ts create mode 100644 server/src/routes/applicant.ts create mode 100644 server/tsconfig.json diff --git a/server/dist/models/applicant.js b/server/dist/models/applicant.js new file mode 100644 index 0000000..c8ad2e5 --- /dev/null +++ b/server/dist/models/applicant.js @@ -0,0 +1,2 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); diff --git a/server/dist/routes/applicants.js b/server/dist/routes/applicants.js new file mode 100644 index 0000000..3eba60c --- /dev/null +++ b/server/dist/routes/applicants.js @@ -0,0 +1,51 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const express_1 = require("express"); +const router = (0, express_1.Router)(); +let applicants = []; +// Add your CRUD API implementation here +router.get('/', (req, res) => { + res.json(applicants); +}); +router.get('/:id', (req, res) => { + const applicant = applicants.find((t) => t.id === parseInt(req.params.id)); + if (!applicant) { + res.status(404).send('Applicant not found'); + } + else { + res.json(applicant); + } +}); +router.post('/', (req, res) => { + const applicant = { + id: applicants.length + 1, + title: req.body.title, + description: req.body.description, + completed: false, + }; + applicants.push(applicant); + res.status(201).json(applicant); +}); +router.put('/:id', (req, res) => { + const applicant = applicants.find((t) => t.id === parseInt(req.params.id)); + if (!applicant) { + res.status(404).send('Applicant not found'); + } + else { + applicant.title = req.body.title || applicant.title; + applicant.description = req.body.description || applicant.description; + applicant.completed = req.body.completed || applicant.completed; + res.json(applicant); + } +}); +router.delete('/:id', (req, res) => { + const index = applicants.findIndex((t) => t.id === parseInt(req.params.id)); + if (index === -1) { + res.status(404).send('Applicant not found'); + } + else { + applicants.splice(index, 1); + res.status(204).send(); + } +}); +exports.default = router; diff --git a/server/dist/validators/applicants.js b/server/dist/validators/applicants.js new file mode 100644 index 0000000..ac7dd7c --- /dev/null +++ b/server/dist/validators/applicants.js @@ -0,0 +1,8 @@ +"use strict"; +// import * as expressValidator from 'express-validator'; +// const { body, validationResult } = expressValidator; +// const taskValidationRules = [ +// body('title').notEmpty().withMessage('Title is required'), +// body('description').notEmpty().withMessage('Description is required'), +// body('completed').isBoolean().withMessage('Completed must be a boolean'), +// ]; diff --git a/server/package-lock.json b/server/package-lock.json new file mode 100644 index 0000000..5b2d65a --- /dev/null +++ b/server/package-lock.json @@ -0,0 +1,1292 @@ +{ + "name": "server", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "server", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "@prisma/client": "^5.21.1", + "@types/express-validator": "^2.20.33", + "express-validator": "^7.2.0" + }, + "devDependencies": { + "@types/express": "^5.0.0", + "@types/node": "^22.8.1", + "express": "^4.21.1", + "prisma": "^5.21.1", + "ts-node": "^10.9.2", + "typescript": "^5.6.3" + } + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@prisma/client": { + "version": "5.21.1", + "resolved": "https://registry.npmjs.org/@prisma/client/-/client-5.21.1.tgz", + "integrity": "sha512-3n+GgbAZYjaS/k0M03yQsQfR1APbr411r74foknnsGpmhNKBG49VuUkxIU6jORgvJPChoD4WC4PqoHImN1FP0w==", + "hasInstallScript": true, + "license": "Apache-2.0", + "engines": { + "node": ">=16.13" + }, + "peerDependencies": { + "prisma": "*" + }, + "peerDependenciesMeta": { + "prisma": { + "optional": true + } + } + }, + "node_modules/@prisma/debug": { + "version": "5.21.1", + "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-5.21.1.tgz", + "integrity": "sha512-uY8SAhcnORhvgtOrNdvWS98Aq/nkQ9QDUxrWAgW8XrCZaI3j2X7zb7Xe6GQSh6xSesKffFbFlkw0c2luHQviZA==", + "devOptional": true, + "license": "Apache-2.0" + }, + "node_modules/@prisma/engines": { + "version": "5.21.1", + "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-5.21.1.tgz", + "integrity": "sha512-hGVTldUkIkTwoV8//hmnAAiAchi4oMEKD3aW5H2RrnI50tTdwza7VQbTTAyN3OIHWlK5DVg6xV7X8N/9dtOydA==", + "devOptional": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "5.21.1", + "@prisma/engines-version": "5.21.1-1.bf0e5e8a04cada8225617067eaa03d041e2bba36", + "@prisma/fetch-engine": "5.21.1", + "@prisma/get-platform": "5.21.1" + } + }, + "node_modules/@prisma/engines-version": { + "version": "5.21.1-1.bf0e5e8a04cada8225617067eaa03d041e2bba36", + "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-5.21.1-1.bf0e5e8a04cada8225617067eaa03d041e2bba36.tgz", + "integrity": "sha512-qvnEflL0//lh44S/T9NcvTMxfyowNeUxTunPcDfKPjyJNrCNf2F1zQLcUv5UHAruECpX+zz21CzsC7V2xAeM7Q==", + "devOptional": true, + "license": "Apache-2.0" + }, + "node_modules/@prisma/fetch-engine": { + "version": "5.21.1", + "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-5.21.1.tgz", + "integrity": "sha512-70S31vgpCGcp9J+mh/wHtLCkVezLUqe/fGWk3J3JWZIN7prdYSlr1C0niaWUyNK2VflLXYi8kMjAmSxUVq6WGQ==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "5.21.1", + "@prisma/engines-version": "5.21.1-1.bf0e5e8a04cada8225617067eaa03d041e2bba36", + "@prisma/get-platform": "5.21.1" + } + }, + "node_modules/@prisma/get-platform": { + "version": "5.21.1", + "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-5.21.1.tgz", + "integrity": "sha512-sRxjL3Igst3ct+e8ya/x//cDXmpLbZQ5vfps2N4tWl4VGKQAmym77C/IG/psSMsQKszc8uFC/q1dgmKFLUgXZQ==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "5.21.1" + } + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", + "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/body-parser": { + "version": "1.19.5", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", + "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/express": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.0.tgz", + "integrity": "sha512-DvZriSMehGHL1ZNLzi6MidnsDhUZM/x2pRdDIKdwbUNqqwHxMlRdkxtn6/EPKyqKpHqTl/4nRZsRNLpZxZRpPQ==", + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^5.0.0", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.0.1.tgz", + "integrity": "sha512-CRICJIl0N5cXDONAdlTv5ShATZ4HEwk6kDDIW2/w9qOWKg+NU/5F8wYRWCrONad0/UKkloNSmmyN/wX4rtpbVA==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/express-validator": { + "version": "2.20.33", + "resolved": "https://registry.npmjs.org/@types/express-validator/-/express-validator-2.20.33.tgz", + "integrity": "sha512-dAlxnuNhKkM/Xq2148NySrnutOSeK8Xk6rpDdDrNT5akAKqFbtLmMKENaMYYh4xWcSqtSWc5l+TLxnZosa9p/g==", + "license": "MIT", + "dependencies": { + "@types/express": "*" + } + }, + "node_modules/@types/http-errors": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", + "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", + "license": "MIT" + }, + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "22.8.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.8.1.tgz", + "integrity": "sha512-k6Gi8Yyo8EtrNtkHXutUu2corfDf9su95VYVP10aGYMMROM6SAItZi0w1XszA6RtWTHSVp5OeFof37w0IEqCQg==", + "license": "MIT", + "dependencies": { + "undici-types": "~6.19.8" + } + }, + "node_modules/@types/qs": { + "version": "6.9.16", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.16.tgz", + "integrity": "sha512-7i+zxXdPD0T4cKDuxCUXJ4wHcsJLwENa6Z3dCu8cfCK743OGy5Nu1RmAGqDPsoTDINVEcdXKRvR/zre+P2Ku1A==", + "license": "MIT" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "license": "MIT" + }, + "node_modules/@types/send": { + "version": "0.17.4", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", + "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", + "license": "MIT", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.7", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz", + "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==", + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "*" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.13.0.tgz", + "integrity": "sha512-8zSiw54Oxrdym50NlZ9sUusyO1Z1ZchgRLWRaK6c86XJFClyCgFKetdowBg5bKxyp/u+CDBJG4Mpp0m3HLZl9w==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", + "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true, + "license": "MIT" + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/body-parser": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.13.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "dev": true, + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "dev": true, + "license": "MIT" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express": { + "version": "4.21.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz", + "integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.3", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.7.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.3.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.10", + "proxy-addr": "~2.0.7", + "qs": "6.13.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.19.0", + "serve-static": "1.16.2", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/express-validator": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/express-validator/-/express-validator-7.2.0.tgz", + "integrity": "sha512-I2ByKD8panjtr8Y05l21Wph9xk7kk64UMyvJCl/fFM/3CTJq8isXYPLeKW/aZBCdb/LYNv63PwhY8khw8VWocA==", + "license": "MIT", + "dependencies": { + "lodash": "^4.17.21", + "validator": "~13.12.0" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/finalhandler": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "license": "MIT" + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true, + "license": "ISC" + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true, + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/object-inspect": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", + "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-to-regexp": { + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", + "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==", + "dev": true, + "license": "MIT" + }, + "node_modules/prisma": { + "version": "5.21.1", + "resolved": "https://registry.npmjs.org/prisma/-/prisma-5.21.1.tgz", + "integrity": "sha512-PB+Iqzld/uQBPaaw2UVIk84kb0ITsLajzsxzsadxxl54eaU5Gyl2/L02ysivHxK89t7YrfQJm+Ggk37uvM70oQ==", + "devOptional": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/engines": "5.21.1" + }, + "bin": { + "prisma": "build/index.js" + }, + "engines": { + "node": ">=16.13" + }, + "optionalDependencies": { + "fsevents": "2.3.3" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/qs": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/send": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/serve-static": { + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.19.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "dev": true, + "license": "ISC" + }, + "node_modules/side-channel": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typescript": { + "version": "5.6.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", + "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", + "license": "MIT" + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true, + "license": "MIT" + }, + "node_modules/validator": { + "version": "13.12.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.12.0.tgz", + "integrity": "sha512-c1Q0mCiPlgdTVVVIJIrBuxNicYE+t/7oKeI9MWLj3fh/uq2Pxh/3eeWbVZ4OcGW1TUf53At0njHw5SMdA3tmMg==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + } + } +} diff --git a/server/package.json b/server/package.json new file mode 100644 index 0000000..3bfc055 --- /dev/null +++ b/server/package.json @@ -0,0 +1,25 @@ +{ + "name": "server", + "version": "1.0.0", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "description": "", + "devDependencies": { + "@types/express": "^5.0.0", + "@types/node": "^22.8.1", + "express": "^4.21.1", + "prisma": "^5.21.1", + "ts-node": "^10.9.2", + "typescript": "^5.6.3" + }, + "dependencies": { + "@prisma/client": "^5.21.1", + "@types/express-validator": "^2.20.33", + "express-validator": "^7.2.0" + } +} diff --git a/server/src/index.ts b/server/src/index.ts new file mode 100644 index 0000000..82026c5 --- /dev/null +++ b/server/src/index.ts @@ -0,0 +1,22 @@ +import express, { Request, Response, NextFunction } from 'express'; +import applicantRoutes from './routes/applicant'; + +const app = express(); +const port = process.env.PORT || 3000; + +app.use(express.json()); +app.use('/applicant', applicantRoutes); + +app.get('/', (req: Request, res: Response) => { + res.send('Hello, TypeScript Express!'); +}); + +// Add this error handling middleware +app.use((err: Error, req: Request, res: Response, next: NextFunction) => { + console.error(err.stack); + res.status(500).send('Something went wrong'); +}); + +app.listen(port, () => { + console.log(`Server running at http://localhost:${port}`); +}); \ No newline at end of file diff --git a/server/src/models/applicant.ts b/server/src/models/applicant.ts new file mode 100644 index 0000000..5d942f7 --- /dev/null +++ b/server/src/models/applicant.ts @@ -0,0 +1,6 @@ +export interface Applicant { + id: number; + title: string; + description: string; + completed: boolean; +} \ No newline at end of file diff --git a/server/src/routes/applicant.ts b/server/src/routes/applicant.ts new file mode 100644 index 0000000..f5061bb --- /dev/null +++ b/server/src/routes/applicant.ts @@ -0,0 +1,59 @@ +import { Router, Request, Response } from 'express'; +import { Applicant } from '../models/applicant'; + +const router = Router(); +let applicants: Applicant[] = []; + +// Add your CRUD API implementation here +router.get('/', (req: Request, res: Response) => { + res.json(applicants); +}); + +router.get('/:id', (req: Request, res: Response) => { + const applicant = applicants.find((t) => t.id === parseInt(req.params.id)); + + if (!applicant) { + res.status(404).send('Applicant not found'); + } else { + res.json(applicant); + } +}); + +router.post('/', (req: Request, res: Response) => { + const applicant: Applicant = { + id: applicants.length + 1, + title: req.body.title, + description: req.body.description, + completed: false, + }; + + applicants.push(applicant); + res.status(201).json(applicant); +}); + +router.put('/:id', (req: Request, res: Response) => { + const applicant = applicants.find((t) => t.id === parseInt(req.params.id)); + + if (!applicant) { + res.status(404).send('Applicant not found'); + } else { + applicant.title = req.body.title || applicant.title; + applicant.description = req.body.description || applicant.description; + applicant.completed = req.body.completed || applicant.completed; + + res.json(applicant); + } +}); + +router.delete('/:id', (req: Request, res: Response) => { + const index = applicants.findIndex((t) => t.id === parseInt(req.params.id)); + + if (index === -1) { + res.status(404).send('Applicant not found'); + } else { + applicants.splice(index, 1); + res.status(204).send(); + } +}); + +export default router; \ No newline at end of file diff --git a/server/tsconfig.json b/server/tsconfig.json new file mode 100644 index 0000000..407d5b1 --- /dev/null +++ b/server/tsconfig.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "target": "es6", + "module": "commonjs", + "outDir": "./dist", + "strict": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true + }, + "include": ["src/**/*.ts"], + "exclude": ["node_modules"] +} \ No newline at end of file From 99ba9c704ff77e71d1cb362070788fd92d798f25 Mon Sep 17 00:00:00 2001 From: NgZiXin Date: Sat, 26 Oct 2024 23:27:56 +0800 Subject: [PATCH 02/24] Add Schema.prisma --- server/src/models/schema.prisma | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 server/src/models/schema.prisma diff --git a/server/src/models/schema.prisma b/server/src/models/schema.prisma new file mode 100644 index 0000000..cc39100 --- /dev/null +++ b/server/src/models/schema.prisma @@ -0,0 +1,15 @@ +datasource db { + url = env("DATABASE_URL") + provider = "postgresql" +} + +generator client { + provider = "prisma-client-js" +} + +model User { + id Int @id @default(autoincrement()) + createdAt DateTime @default(now()) + email String @unique + name String? +} \ No newline at end of file From c3676c60df2caa25c9be2f9582b2bc9e31ed3b2d Mon Sep 17 00:00:00 2001 From: NgZiXin Date: Sat, 26 Oct 2024 23:28:29 +0800 Subject: [PATCH 03/24] Update .gitignore --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index fd3dbb5..21d7ea5 100644 --- a/.gitignore +++ b/.gitignore @@ -34,3 +34,6 @@ yarn-error.log* # typescript *.tsbuildinfo next-env.d.ts + +# server +server/node_modules \ No newline at end of file From f11444f35863d4c0976e79e33d68bcec61d3c4aa Mon Sep 17 00:00:00 2001 From: NgZiXin Date: Sat, 26 Oct 2024 23:40:34 +0800 Subject: [PATCH 04/24] Edit package.json --- server/dist/index.js | 25 ++++++++++++++++ server/dist/routes/applicant.js | 51 +++++++++++++++++++++++++++++++++ server/package-lock.json | 13 +++++++++ server/package.json | 3 ++ server/src/.env | 3 ++ server/src/index.ts | 10 +++++-- 6 files changed, 102 insertions(+), 3 deletions(-) create mode 100644 server/dist/index.js create mode 100644 server/dist/routes/applicant.js create mode 100644 server/src/.env diff --git a/server/dist/index.js b/server/dist/index.js new file mode 100644 index 0000000..94a2d45 --- /dev/null +++ b/server/dist/index.js @@ -0,0 +1,25 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const express_1 = __importDefault(require("express")); +const applicant_1 = __importDefault(require("./routes/applicant")); +const dotenv_1 = __importDefault(require("dotenv")); +dotenv_1.default.config(); +const app = (0, express_1.default)(); +const PORT = parseInt(process.env.PORT || '3000', 10); +const HOST = process.env.HOST || "localhost"; +app.use(express_1.default.json()); +app.use('/applicant', applicant_1.default); +app.get('/', (req, res) => { + res.send('Hello, TypeScript Express!'); +}); +// Add this error handling middleware +app.use((err, req, res, next) => { + console.error(err.stack); + res.status(500).send('Something went wrong'); +}); +app.listen(PORT, HOST, () => { + console.log(`Server running at http://${HOST}:${PORT}`); +}); diff --git a/server/dist/routes/applicant.js b/server/dist/routes/applicant.js new file mode 100644 index 0000000..3eba60c --- /dev/null +++ b/server/dist/routes/applicant.js @@ -0,0 +1,51 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const express_1 = require("express"); +const router = (0, express_1.Router)(); +let applicants = []; +// Add your CRUD API implementation here +router.get('/', (req, res) => { + res.json(applicants); +}); +router.get('/:id', (req, res) => { + const applicant = applicants.find((t) => t.id === parseInt(req.params.id)); + if (!applicant) { + res.status(404).send('Applicant not found'); + } + else { + res.json(applicant); + } +}); +router.post('/', (req, res) => { + const applicant = { + id: applicants.length + 1, + title: req.body.title, + description: req.body.description, + completed: false, + }; + applicants.push(applicant); + res.status(201).json(applicant); +}); +router.put('/:id', (req, res) => { + const applicant = applicants.find((t) => t.id === parseInt(req.params.id)); + if (!applicant) { + res.status(404).send('Applicant not found'); + } + else { + applicant.title = req.body.title || applicant.title; + applicant.description = req.body.description || applicant.description; + applicant.completed = req.body.completed || applicant.completed; + res.json(applicant); + } +}); +router.delete('/:id', (req, res) => { + const index = applicants.findIndex((t) => t.id === parseInt(req.params.id)); + if (index === -1) { + res.status(404).send('Applicant not found'); + } + else { + applicants.splice(index, 1); + res.status(204).send(); + } +}); +exports.default = router; diff --git a/server/package-lock.json b/server/package-lock.json index 5b2d65a..6451266 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -11,6 +11,7 @@ "dependencies": { "@prisma/client": "^5.21.1", "@types/express-validator": "^2.20.33", + "dotenv": "^16.4.5", "express-validator": "^7.2.0" }, "devDependencies": { @@ -480,6 +481,18 @@ "node": ">=0.3.1" } }, + "node_modules/dotenv": { + "version": "16.4.5", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", + "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", diff --git a/server/package.json b/server/package.json index 3bfc055..472da71 100644 --- a/server/package.json +++ b/server/package.json @@ -3,6 +3,8 @@ "version": "1.0.0", "main": "index.js", "scripts": { + "start": "node dist/index.js", + "dev": "ts-node src/index.ts", "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], @@ -20,6 +22,7 @@ "dependencies": { "@prisma/client": "^5.21.1", "@types/express-validator": "^2.20.33", + "dotenv": "^16.4.5", "express-validator": "^7.2.0" } } diff --git a/server/src/.env b/server/src/.env new file mode 100644 index 0000000..e8e1bc4 --- /dev/null +++ b/server/src/.env @@ -0,0 +1,3 @@ +# Server configuration +PORT=3000 +HOST=localhost \ No newline at end of file diff --git a/server/src/index.ts b/server/src/index.ts index 82026c5..b59101f 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -1,8 +1,12 @@ import express, { Request, Response, NextFunction } from 'express'; import applicantRoutes from './routes/applicant'; +import dotenv from 'dotenv'; + +dotenv.config(); const app = express(); -const port = process.env.PORT || 3000; +const PORT = parseInt(process.env.PORT || '3000', 10); +const HOST = process.env.HOST || "localhost"; app.use(express.json()); app.use('/applicant', applicantRoutes); @@ -17,6 +21,6 @@ app.use((err: Error, req: Request, res: Response, next: NextFunction) => { res.status(500).send('Something went wrong'); }); -app.listen(port, () => { - console.log(`Server running at http://localhost:${port}`); +app.listen(PORT, HOST, () => { + console.log(`Server running at http://${HOST}:${PORT}`); }); \ No newline at end of file From 300ad33558202698f5907b0caeb62594a78b01dd Mon Sep 17 00:00:00 2001 From: NgZiXin Date: Sun, 27 Oct 2024 00:01:51 +0800 Subject: [PATCH 05/24] Configure settings --- .gitignore | 3 +- server/dist/index.js | 25 -- server/dist/models/applicant.js | 2 - server/dist/routes/applicant.js | 51 --- server/dist/routes/applicants.js | 51 --- server/dist/validators/applicants.js | 8 - server/package-lock.json | 580 +++++++++++++++++++++++++-- server/package.json | 9 +- 8 files changed, 548 insertions(+), 181 deletions(-) delete mode 100644 server/dist/index.js delete mode 100644 server/dist/models/applicant.js delete mode 100644 server/dist/routes/applicant.js delete mode 100644 server/dist/routes/applicants.js delete mode 100644 server/dist/validators/applicants.js diff --git a/.gitignore b/.gitignore index 21d7ea5..3d7a5c9 100644 --- a/.gitignore +++ b/.gitignore @@ -36,4 +36,5 @@ yarn-error.log* next-env.d.ts # server -server/node_modules \ No newline at end of file +server/node_modules +server/dist \ No newline at end of file diff --git a/server/dist/index.js b/server/dist/index.js deleted file mode 100644 index 94a2d45..0000000 --- a/server/dist/index.js +++ /dev/null @@ -1,25 +0,0 @@ -"use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -const express_1 = __importDefault(require("express")); -const applicant_1 = __importDefault(require("./routes/applicant")); -const dotenv_1 = __importDefault(require("dotenv")); -dotenv_1.default.config(); -const app = (0, express_1.default)(); -const PORT = parseInt(process.env.PORT || '3000', 10); -const HOST = process.env.HOST || "localhost"; -app.use(express_1.default.json()); -app.use('/applicant', applicant_1.default); -app.get('/', (req, res) => { - res.send('Hello, TypeScript Express!'); -}); -// Add this error handling middleware -app.use((err, req, res, next) => { - console.error(err.stack); - res.status(500).send('Something went wrong'); -}); -app.listen(PORT, HOST, () => { - console.log(`Server running at http://${HOST}:${PORT}`); -}); diff --git a/server/dist/models/applicant.js b/server/dist/models/applicant.js deleted file mode 100644 index c8ad2e5..0000000 --- a/server/dist/models/applicant.js +++ /dev/null @@ -1,2 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); diff --git a/server/dist/routes/applicant.js b/server/dist/routes/applicant.js deleted file mode 100644 index 3eba60c..0000000 --- a/server/dist/routes/applicant.js +++ /dev/null @@ -1,51 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -const express_1 = require("express"); -const router = (0, express_1.Router)(); -let applicants = []; -// Add your CRUD API implementation here -router.get('/', (req, res) => { - res.json(applicants); -}); -router.get('/:id', (req, res) => { - const applicant = applicants.find((t) => t.id === parseInt(req.params.id)); - if (!applicant) { - res.status(404).send('Applicant not found'); - } - else { - res.json(applicant); - } -}); -router.post('/', (req, res) => { - const applicant = { - id: applicants.length + 1, - title: req.body.title, - description: req.body.description, - completed: false, - }; - applicants.push(applicant); - res.status(201).json(applicant); -}); -router.put('/:id', (req, res) => { - const applicant = applicants.find((t) => t.id === parseInt(req.params.id)); - if (!applicant) { - res.status(404).send('Applicant not found'); - } - else { - applicant.title = req.body.title || applicant.title; - applicant.description = req.body.description || applicant.description; - applicant.completed = req.body.completed || applicant.completed; - res.json(applicant); - } -}); -router.delete('/:id', (req, res) => { - const index = applicants.findIndex((t) => t.id === parseInt(req.params.id)); - if (index === -1) { - res.status(404).send('Applicant not found'); - } - else { - applicants.splice(index, 1); - res.status(204).send(); - } -}); -exports.default = router; diff --git a/server/dist/routes/applicants.js b/server/dist/routes/applicants.js deleted file mode 100644 index 3eba60c..0000000 --- a/server/dist/routes/applicants.js +++ /dev/null @@ -1,51 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -const express_1 = require("express"); -const router = (0, express_1.Router)(); -let applicants = []; -// Add your CRUD API implementation here -router.get('/', (req, res) => { - res.json(applicants); -}); -router.get('/:id', (req, res) => { - const applicant = applicants.find((t) => t.id === parseInt(req.params.id)); - if (!applicant) { - res.status(404).send('Applicant not found'); - } - else { - res.json(applicant); - } -}); -router.post('/', (req, res) => { - const applicant = { - id: applicants.length + 1, - title: req.body.title, - description: req.body.description, - completed: false, - }; - applicants.push(applicant); - res.status(201).json(applicant); -}); -router.put('/:id', (req, res) => { - const applicant = applicants.find((t) => t.id === parseInt(req.params.id)); - if (!applicant) { - res.status(404).send('Applicant not found'); - } - else { - applicant.title = req.body.title || applicant.title; - applicant.description = req.body.description || applicant.description; - applicant.completed = req.body.completed || applicant.completed; - res.json(applicant); - } -}); -router.delete('/:id', (req, res) => { - const index = applicants.findIndex((t) => t.id === parseInt(req.params.id)); - if (index === -1) { - res.status(404).send('Applicant not found'); - } - else { - applicants.splice(index, 1); - res.status(204).send(); - } -}); -exports.default = router; diff --git a/server/dist/validators/applicants.js b/server/dist/validators/applicants.js deleted file mode 100644 index ac7dd7c..0000000 --- a/server/dist/validators/applicants.js +++ /dev/null @@ -1,8 +0,0 @@ -"use strict"; -// import * as expressValidator from 'express-validator'; -// const { body, validationResult } = expressValidator; -// const taskValidationRules = [ -// body('title').notEmpty().withMessage('Title is required'), -// body('description').notEmpty().withMessage('Description is required'), -// body('completed').isBoolean().withMessage('Completed must be a boolean'), -// ]; diff --git a/server/package-lock.json b/server/package-lock.json index 6451266..16c3baa 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -10,15 +10,14 @@ "license": "ISC", "dependencies": { "@prisma/client": "^5.21.1", - "@types/express-validator": "^2.20.33", - "dotenv": "^16.4.5", - "express-validator": "^7.2.0" + "dotenv": "^16.4.5" }, "devDependencies": { "@types/express": "^5.0.0", "@types/node": "^22.8.1", "express": "^4.21.1", "prisma": "^5.21.1", + "rimraf": "^6.0.1", "ts-node": "^10.9.2", "typescript": "^5.6.3" } @@ -36,6 +35,24 @@ "node": ">=12" } }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/@jridgewell/resolve-uri": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", @@ -164,6 +181,7 @@ "version": "1.19.5", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", + "dev": true, "license": "MIT", "dependencies": { "@types/connect": "*", @@ -174,6 +192,7 @@ "version": "3.4.38", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dev": true, "license": "MIT", "dependencies": { "@types/node": "*" @@ -183,6 +202,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.0.tgz", "integrity": "sha512-DvZriSMehGHL1ZNLzi6MidnsDhUZM/x2pRdDIKdwbUNqqwHxMlRdkxtn6/EPKyqKpHqTl/4nRZsRNLpZxZRpPQ==", + "dev": true, "license": "MIT", "dependencies": { "@types/body-parser": "*", @@ -195,6 +215,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.0.1.tgz", "integrity": "sha512-CRICJIl0N5cXDONAdlTv5ShATZ4HEwk6kDDIW2/w9qOWKg+NU/5F8wYRWCrONad0/UKkloNSmmyN/wX4rtpbVA==", + "dev": true, "license": "MIT", "dependencies": { "@types/node": "*", @@ -203,31 +224,25 @@ "@types/send": "*" } }, - "node_modules/@types/express-validator": { - "version": "2.20.33", - "resolved": "https://registry.npmjs.org/@types/express-validator/-/express-validator-2.20.33.tgz", - "integrity": "sha512-dAlxnuNhKkM/Xq2148NySrnutOSeK8Xk6rpDdDrNT5akAKqFbtLmMKENaMYYh4xWcSqtSWc5l+TLxnZosa9p/g==", - "license": "MIT", - "dependencies": { - "@types/express": "*" - } - }, "node_modules/@types/http-errors": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", + "dev": true, "license": "MIT" }, "node_modules/@types/mime": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "dev": true, "license": "MIT" }, "node_modules/@types/node": { "version": "22.8.1", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.8.1.tgz", "integrity": "sha512-k6Gi8Yyo8EtrNtkHXutUu2corfDf9su95VYVP10aGYMMROM6SAItZi0w1XszA6RtWTHSVp5OeFof37w0IEqCQg==", + "dev": true, "license": "MIT", "dependencies": { "undici-types": "~6.19.8" @@ -237,18 +252,21 @@ "version": "6.9.16", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.16.tgz", "integrity": "sha512-7i+zxXdPD0T4cKDuxCUXJ4wHcsJLwENa6Z3dCu8cfCK743OGy5Nu1RmAGqDPsoTDINVEcdXKRvR/zre+P2Ku1A==", + "dev": true, "license": "MIT" }, "node_modules/@types/range-parser": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true, "license": "MIT" }, "node_modules/@types/send": { "version": "0.17.4", "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", + "dev": true, "license": "MIT", "dependencies": { "@types/mime": "^1", @@ -259,6 +277,7 @@ "version": "1.15.7", "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz", "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==", + "dev": true, "license": "MIT", "dependencies": { "@types/http-errors": "*", @@ -306,6 +325,32 @@ "node": ">=0.4.0" } }, + "node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/arg": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", @@ -320,6 +365,13 @@ "dev": true, "license": "MIT" }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, "node_modules/body-parser": { "version": "1.20.3", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", @@ -345,6 +397,16 @@ "npm": "1.2.8000 || >= 1.4.16" } }, + "node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -375,6 +437,26 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, "node_modules/content-disposition": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", @@ -422,6 +504,21 @@ "dev": true, "license": "MIT" }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -493,6 +590,13 @@ "url": "https://dotenvx.com" } }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -500,6 +604,13 @@ "dev": true, "license": "MIT" }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, "node_modules/encodeurl": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", @@ -593,19 +704,6 @@ "node": ">= 0.10.0" } }, - "node_modules/express-validator": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/express-validator/-/express-validator-7.2.0.tgz", - "integrity": "sha512-I2ByKD8panjtr8Y05l21Wph9xk7kk64UMyvJCl/fFM/3CTJq8isXYPLeKW/aZBCdb/LYNv63PwhY8khw8VWocA==", - "license": "MIT", - "dependencies": { - "lodash": "^4.17.21", - "validator": "~13.12.0" - }, - "engines": { - "node": ">= 8.0.0" - } - }, "node_modules/finalhandler": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", @@ -625,6 +723,23 @@ "node": ">= 0.8" } }, + "node_modules/foreground-child": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", + "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -690,6 +805,30 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/glob": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.0.tgz", + "integrity": "sha512-9UiX/Bl6J2yaBbxKoEBRm4Cipxgok8kQYcOPEhScPwebu2I0HoQOuYdIO6S3hLuWoZgpDpwQZMzTFxgpkyT76g==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^4.0.1", + "minimatch": "^10.0.0", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^2.0.0" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/gopd": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", @@ -802,11 +941,48 @@ "node": ">= 0.10" } }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "license": "MIT" + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/jackspeak": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.0.2.tgz", + "integrity": "sha512-bZsjR/iRjl1Nk1UkjGpAzLNfQtzuijhn2g+pbZb98HQ1Gk8vM9hfbxeMBP+M2/UUdwj0RqGG3mlvk2MsAqwvEw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/lru-cache": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.0.1.tgz", + "integrity": "sha512-CgeuL5uom6j/ZVrg7G/+1IXqRY8JXX4Hghfy5YE0EhoYQWvndP1kufu58cmZLNIDKnRhZrXfdS9urVWx98AipQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "20 || >=22" + } }, "node_modules/make-error": { "version": "1.3.6", @@ -881,6 +1057,32 @@ "node": ">= 0.6" } }, + "node_modules/minimatch": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz", + "integrity": "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, "node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -924,6 +1126,13 @@ "node": ">= 0.8" } }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -934,6 +1143,33 @@ "node": ">= 0.8" } }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-scurry": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz", + "integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/path-to-regexp": { "version": "0.1.10", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", @@ -1017,6 +1253,26 @@ "node": ">= 0.8" } }, + "node_modules/rimraf": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-6.0.1.tgz", + "integrity": "sha512-9dkvaxAsk/xNXSJzMgFqqMCuFgt2+KsOFek3TMLfo8NCPfWpBmqwyNn5Y+NX56QUYfCtsyhF3ayiboEoUmJk/A==", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^11.0.0", + "package-json-from-dist": "^1.0.0" + }, + "bin": { + "rimraf": "dist/esm/bin.mjs" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -1128,6 +1384,29 @@ "dev": true, "license": "ISC" }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/side-channel": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", @@ -1147,6 +1426,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", @@ -1157,6 +1449,110 @@ "node": ">= 0.8" } }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/toidentifier": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", @@ -1243,6 +1639,7 @@ "version": "6.19.8", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", + "dev": true, "license": "MIT" }, "node_modules/unpipe": { @@ -1272,15 +1669,6 @@ "dev": true, "license": "MIT" }, - "node_modules/validator": { - "version": "13.12.0", - "resolved": "https://registry.npmjs.org/validator/-/validator-13.12.0.tgz", - "integrity": "sha512-c1Q0mCiPlgdTVVVIJIrBuxNicYE+t/7oKeI9MWLj3fh/uq2Pxh/3eeWbVZ4OcGW1TUf53At0njHw5SMdA3tmMg==", - "license": "MIT", - "engines": { - "node": ">= 0.10" - } - }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -1291,6 +1679,120 @@ "node": ">= 0.8" } }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/yn": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", diff --git a/server/package.json b/server/package.json index 472da71..6f72804 100644 --- a/server/package.json +++ b/server/package.json @@ -1,10 +1,12 @@ { "name": "server", "version": "1.0.0", - "main": "index.js", + "main": "dist/index.ts", "scripts": { "start": "node dist/index.js", "dev": "ts-node src/index.ts", + "clean": "rimraf dist", + "build": "npm run clean && npx tsc", "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], @@ -16,13 +18,12 @@ "@types/node": "^22.8.1", "express": "^4.21.1", "prisma": "^5.21.1", + "rimraf": "^6.0.1", "ts-node": "^10.9.2", "typescript": "^5.6.3" }, "dependencies": { "@prisma/client": "^5.21.1", - "@types/express-validator": "^2.20.33", - "dotenv": "^16.4.5", - "express-validator": "^7.2.0" + "dotenv": "^16.4.5" } } From 9de0c640a4e8430982886d361a1c339b475e706a Mon Sep 17 00:00:00 2001 From: NgZiXin Date: Mon, 28 Oct 2024 01:29:03 +0800 Subject: [PATCH 06/24] Add applicant and department --- server/.env | 6 + server/package.json | 3 + .../migration.sql | 23 +++ server/prisma/migrations/migration_lock.toml | 3 + server/prisma/schema.prisma | 20 +++ server/prisma/seed.ts | 53 +++++++ server/src/.env | 3 - server/src/models/applicant.ts | 6 - server/src/models/schema.prisma | 15 -- server/src/routes/applicant.ts | 132 ++++++++++++------ server/src/utils/logUtil.ts | 6 + server/tsconfig.json | 2 +- 12 files changed, 203 insertions(+), 69 deletions(-) create mode 100644 server/.env create mode 100644 server/prisma/migrations/20241027161621_init_applicant_table/migration.sql create mode 100644 server/prisma/migrations/migration_lock.toml create mode 100644 server/prisma/schema.prisma create mode 100644 server/prisma/seed.ts delete mode 100644 server/src/.env delete mode 100644 server/src/models/applicant.ts delete mode 100644 server/src/models/schema.prisma create mode 100644 server/src/utils/logUtil.ts diff --git a/server/.env b/server/.env new file mode 100644 index 0000000..ad5ab83 --- /dev/null +++ b/server/.env @@ -0,0 +1,6 @@ +# Server configuration +PORT=3000 +HOST=localhost + +# Database configuration +DATABASE_URL="postgresql://postgres:91559732@localhost:5432/fintech_test?schema=public" \ No newline at end of file diff --git a/server/package.json b/server/package.json index 6f72804..63b04b8 100644 --- a/server/package.json +++ b/server/package.json @@ -2,6 +2,9 @@ "name": "server", "version": "1.0.0", "main": "dist/index.ts", + "prisma": { + "seed": "ts-node prisma/seed.ts" + }, "scripts": { "start": "node dist/index.js", "dev": "ts-node src/index.ts", diff --git a/server/prisma/migrations/20241027161621_init_applicant_table/migration.sql b/server/prisma/migrations/20241027161621_init_applicant_table/migration.sql new file mode 100644 index 0000000..3e7c8a3 --- /dev/null +++ b/server/prisma/migrations/20241027161621_init_applicant_table/migration.sql @@ -0,0 +1,23 @@ +-- CreateTable +CREATE TABLE "Applicant" ( + "applicant_id" SERIAL NOT NULL, + "username" TEXT NOT NULL, + "email" TEXT NOT NULL, + "password" TEXT NOT NULL, + + CONSTRAINT "Applicant_pkey" PRIMARY KEY ("applicant_id") +); + +-- CreateTable +CREATE TABLE "Department" ( + "department_id" SERIAL NOT NULL, + "department_name" TEXT NOT NULL, + + CONSTRAINT "Department_pkey" PRIMARY KEY ("department_id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "Applicant_email_key" ON "Applicant"("email"); + +-- CreateIndex +CREATE UNIQUE INDEX "Department_department_name_key" ON "Department"("department_name"); diff --git a/server/prisma/migrations/migration_lock.toml b/server/prisma/migrations/migration_lock.toml new file mode 100644 index 0000000..fbffa92 --- /dev/null +++ b/server/prisma/migrations/migration_lock.toml @@ -0,0 +1,3 @@ +# Please do not edit this file manually +# It should be added in your version-control system (i.e. Git) +provider = "postgresql" \ No newline at end of file diff --git a/server/prisma/schema.prisma b/server/prisma/schema.prisma new file mode 100644 index 0000000..c3abe2a --- /dev/null +++ b/server/prisma/schema.prisma @@ -0,0 +1,20 @@ +datasource db { + url = env("DATABASE_URL") + provider = "postgresql" +} + +generator client { + provider = "prisma-client-js" +} + +model Applicant { + applicant_id Int @id @default(autoincrement()) // Primary key with auto-increment + username String + email String @unique // Unique email field + password String // Hashed password +} + +model Department { + department_id Int @id @default(autoincrement()) // Primary key with auto-increment + department_name String @unique +} diff --git a/server/prisma/seed.ts b/server/prisma/seed.ts new file mode 100644 index 0000000..c30e00f --- /dev/null +++ b/server/prisma/seed.ts @@ -0,0 +1,53 @@ +import { PrismaClient } from '@prisma/client'; + +const prisma = new PrismaClient(); + +async function populate_department() { + const ml = await prisma.department.upsert({ + where: { department_name: 'ML' }, + update: {}, + create: { + department_name: 'ML', + }, + }); + + const sd = await prisma.department.upsert({ + where: { department_name: 'SD' }, + update: {}, + create: { + department_name: 'SD', + }, + }); + + const internal = await prisma.department.upsert({ + where: { department_name: 'Internal' }, + update: {}, + create: { + department_name: 'Internal', + }, + }); + + const quant = await prisma.department.upsert({ + where: { department_name: 'Quant' }, + update: {}, + create: { + department_name: 'Quant', + }, + }); + + console.log({ ml, sd, internal, quant }); +} + +async function main() { + await populate_department(); +} + +main() + .then(async () => { + await prisma.$disconnect(); + }) + .catch(async (e) => { + console.error(e); + await prisma.$disconnect(); + process.exit(1); + }); \ No newline at end of file diff --git a/server/src/.env b/server/src/.env deleted file mode 100644 index e8e1bc4..0000000 --- a/server/src/.env +++ /dev/null @@ -1,3 +0,0 @@ -# Server configuration -PORT=3000 -HOST=localhost \ No newline at end of file diff --git a/server/src/models/applicant.ts b/server/src/models/applicant.ts deleted file mode 100644 index 5d942f7..0000000 --- a/server/src/models/applicant.ts +++ /dev/null @@ -1,6 +0,0 @@ -export interface Applicant { - id: number; - title: string; - description: string; - completed: boolean; -} \ No newline at end of file diff --git a/server/src/models/schema.prisma b/server/src/models/schema.prisma deleted file mode 100644 index cc39100..0000000 --- a/server/src/models/schema.prisma +++ /dev/null @@ -1,15 +0,0 @@ -datasource db { - url = env("DATABASE_URL") - provider = "postgresql" -} - -generator client { - provider = "prisma-client-js" -} - -model User { - id Int @id @default(autoincrement()) - createdAt DateTime @default(now()) - email String @unique - name String? -} \ No newline at end of file diff --git a/server/src/routes/applicant.ts b/server/src/routes/applicant.ts index f5061bb..ad93bfd 100644 --- a/server/src/routes/applicant.ts +++ b/server/src/routes/applicant.ts @@ -1,59 +1,103 @@ -import { Router, Request, Response } from 'express'; -import { Applicant } from '../models/applicant'; +import { Router, Request, Response } from "express"; +import { PrismaClient } from "@prisma/client"; +import { logRequest } from "../utils/logUtil"; +const prisma = new PrismaClient(); const router = Router(); -let applicants: Applicant[] = []; -// Add your CRUD API implementation here -router.get('/', (req: Request, res: Response) => { - res.json(applicants); -}); +// Define the type for applicant input +interface ApplicantInput { + username: string; + email: string; + password: string; +} -router.get('/:id', (req: Request, res: Response) => { - const applicant = applicants.find((t) => t.id === parseInt(req.params.id)); - - if (!applicant) { - res.status(404).send('Applicant not found'); - } else { - res.json(applicant); - } +// Create a new applicant +router.post("/", async (req: Request<{}, {}, ApplicantInput>, res: Response) => { + logRequest(req); + const { username, email, password } = req.body; + try { + console.log(`Creating applicant with email: ${email}`); + const applicant = await prisma.applicant.create({ + data: { username, email, password }, + }); + console.log(`Applicant created with ID: ${applicant.applicant_id}`); + res.status(201).json(applicant); + } catch (error) { + console.error("Error creating applicant:", error); + res.status(400).json({ error: "Error creating applicant" }); + } }); -router.post('/', (req: Request, res: Response) => { - const applicant: Applicant = { - id: applicants.length + 1, - title: req.body.title, - description: req.body.description, - completed: false, - }; - - applicants.push(applicant); - res.status(201).json(applicant); +// Get all applicants +router.get("/", async (_req: Request, res: Response) => { + logRequest(_req); + try { + console.log("Fetching all applicants"); + const applicants = await prisma.applicant.findMany(); + res.json(applicants); + } catch (error) { + console.error("Error fetching applicants:", error); + res.status(500).json({ error: "Error fetching applicants" }); + } }); -router.put('/:id', (req: Request, res: Response) => { - const applicant = applicants.find((t) => t.id === parseInt(req.params.id)); - - if (!applicant) { - res.status(404).send('Applicant not found'); - } else { - applicant.title = req.body.title || applicant.title; - applicant.description = req.body.description || applicant.description; - applicant.completed = req.body.completed || applicant.completed; - +// Get a single applicant by ID +router.get("/:id", async (req: Request, res: Response) => { + logRequest(req); + const { id } = req.params; + try { + console.log(`Fetching applicant with ID: ${id}`); + const applicant = await prisma.applicant.findUnique({ + where: { applicant_id: Number(id) }, + }); + if (applicant) { + console.log(`Applicant found: ${applicant.username}`); res.json(applicant); + } else { + console.warn(`Applicant with ID ${id} not found`); + res.status(404).json({ error: "Applicant not found" }); } + } catch (error) { + console.error("Error fetching applicant:", error); + res.status(500).json({ error: "Error fetching applicant" }); + } }); -router.delete('/:id', (req: Request, res: Response) => { - const index = applicants.findIndex((t) => t.id === parseInt(req.params.id)); +// Update an applicant by ID +router.put("/:id", async (req: Request<{ id: string }, {}, ApplicantInput>, res: Response) => { + logRequest(req); + const { id } = req.params; + const { username, email, password } = req.body; + try { + console.log(`Updating applicant with ID: ${id}`); + const updatedApplicant = await prisma.applicant.update({ + where: { applicant_id: Number(id) }, + data: { username, email, password }, + }); + console.log(`Applicant with ID ${id} updated`); + res.json(updatedApplicant); + } catch (error) { + console.error("Error updating applicant:", error); + res.status(400).json({ error: "Error updating applicant" }); + } +}); - if (index === -1) { - res.status(404).send('Applicant not found'); - } else { - applicants.splice(index, 1); - res.status(204).send(); - } +// Delete an applicant by ID +router.delete("/:id", async (req: Request, res: Response) => { + logRequest(req); + const { id } = req.params; + try { + console.log(`Deleting applicant with ID: ${id}`); + await prisma.applicant.delete({ + where: { applicant_id: Number(id) }, + }); + console.log(`Applicant with ID ${id} deleted`); + res.status(204).end(); + } catch (error) { + console.error("Error deleting applicant:", error); + res.status(400).json({ error: "Error deleting applicant" }); + } }); -export default router; \ No newline at end of file +export default router; diff --git a/server/src/utils/logUtil.ts b/server/src/utils/logUtil.ts new file mode 100644 index 0000000..cc46f71 --- /dev/null +++ b/server/src/utils/logUtil.ts @@ -0,0 +1,6 @@ +import { Request } from "express"; + +// Helper function to log request information +export function logRequest(req: Request) { + console.log(`[${new Date().toISOString()}] ${req.method} ${req.originalUrl}`); +} \ No newline at end of file diff --git a/server/tsconfig.json b/server/tsconfig.json index 407d5b1..58fe1da 100644 --- a/server/tsconfig.json +++ b/server/tsconfig.json @@ -9,6 +9,6 @@ "skipLibCheck": true, "forceConsistentCasingInFileNames": true }, - "include": ["src/**/*.ts"], + "include": ["src/**/*.ts", "prisma/seed.ts"], "exclude": ["node_modules"] } \ No newline at end of file From fc389ee9649368318db4d2427380c1d962d8a6dd Mon Sep 17 00:00:00 2001 From: NgZiXin Date: Mon, 28 Oct 2024 01:32:48 +0800 Subject: [PATCH 07/24] Edit gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 3d7a5c9..6390773 100644 --- a/.gitignore +++ b/.gitignore @@ -37,4 +37,5 @@ next-env.d.ts # server server/node_modules +server/.env server/dist \ No newline at end of file From bd43899f816711cde043858f72a22a8610c90857 Mon Sep 17 00:00:00 2001 From: NgZiXin Date: Mon, 28 Oct 2024 01:33:35 +0800 Subject: [PATCH 08/24] Remove env --- server/.env | 6 ------ 1 file changed, 6 deletions(-) delete mode 100644 server/.env diff --git a/server/.env b/server/.env deleted file mode 100644 index ad5ab83..0000000 --- a/server/.env +++ /dev/null @@ -1,6 +0,0 @@ -# Server configuration -PORT=3000 -HOST=localhost - -# Database configuration -DATABASE_URL="postgresql://postgres:91559732@localhost:5432/fintech_test?schema=public" \ No newline at end of file From cef6277588a4565072793af21c79e37cdbb79f99 Mon Sep 17 00:00:00 2001 From: Luoqi Date: Thu, 31 Oct 2024 20:50:51 +0800 Subject: [PATCH 09/24] feat: Add job and admin routes - Update Prisma schema to include Job model with relations to Admin and Department - Implement CRUD routes for Job in job.ts - Update applicant routes in applicant.ts - Add admin routes in admin.ts - Update index.ts to include job routes" --- .../20241031073333_init/migration.sql | 12 ++ .../20241031082334_init/migration.sql | 22 ++++ server/prisma/schema.prisma | 53 +++++++-- server/src/index.ts | 8 +- server/src/routes/admin.ts | 104 +++++++++++++++++ server/src/routes/applicant.ts | 3 +- server/src/routes/job.ts | 109 ++++++++++++++++++ 7 files changed, 298 insertions(+), 13 deletions(-) create mode 100644 server/prisma/migrations/20241031073333_init/migration.sql create mode 100644 server/prisma/migrations/20241031082334_init/migration.sql create mode 100644 server/src/routes/admin.ts create mode 100644 server/src/routes/job.ts diff --git a/server/prisma/migrations/20241031073333_init/migration.sql b/server/prisma/migrations/20241031073333_init/migration.sql new file mode 100644 index 0000000..aa68235 --- /dev/null +++ b/server/prisma/migrations/20241031073333_init/migration.sql @@ -0,0 +1,12 @@ +-- CreateTable +CREATE TABLE "Admin" ( + "admin_id" SERIAL NOT NULL, + "username" TEXT NOT NULL, + "email" TEXT NOT NULL, + "password" TEXT NOT NULL, + + CONSTRAINT "Admin_pkey" PRIMARY KEY ("admin_id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "Admin_email_key" ON "Admin"("email"); diff --git a/server/prisma/migrations/20241031082334_init/migration.sql b/server/prisma/migrations/20241031082334_init/migration.sql new file mode 100644 index 0000000..37b0db7 --- /dev/null +++ b/server/prisma/migrations/20241031082334_init/migration.sql @@ -0,0 +1,22 @@ +-- CreateEnum +CREATE TYPE "JobStatus" AS ENUM ('open', 'closed'); + +-- CreateTable +CREATE TABLE "Job" ( + "job_id" SERIAL NOT NULL, + "title" TEXT NOT NULL, + "department_id" INTEGER NOT NULL, + "description" TEXT NOT NULL, + "semester" TEXT NOT NULL, + "deadline" TIMESTAMP(3) NOT NULL, + "status" "JobStatus" NOT NULL, + "created_by" INTEGER NOT NULL, + + CONSTRAINT "Job_pkey" PRIMARY KEY ("job_id") +); + +-- AddForeignKey +ALTER TABLE "Job" ADD CONSTRAINT "Job_department_id_fkey" FOREIGN KEY ("department_id") REFERENCES "Department"("department_id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Job" ADD CONSTRAINT "Job_created_by_fkey" FOREIGN KEY ("created_by") REFERENCES "Admin"("admin_id") ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/server/prisma/schema.prisma b/server/prisma/schema.prisma index c3abe2a..994a813 100644 --- a/server/prisma/schema.prisma +++ b/server/prisma/schema.prisma @@ -1,20 +1,53 @@ +generator client { + provider = "prisma-client-js" +} + datasource db { - url = env("DATABASE_URL") provider = "postgresql" + url = env("DATABASE_URL") } -generator client { - provider = "prisma-client-js" +model Applicant { + applicant_id Int @id @default(autoincrement()) + username String + email String @unique + password String } -model Applicant { - applicant_id Int @id @default(autoincrement()) // Primary key with auto-increment - username String - email String @unique // Unique email field - password String // Hashed password +model Admin { + admin_id Int @id @default(autoincrement()) + username String + email String @unique + password String + + // Relation to Job + jobs Job[] } model Department { - department_id Int @id @default(autoincrement()) // Primary key with auto-increment - department_name String @unique + department_id Int @id @default(autoincrement()) + department_name String @unique + + // Relation to Job + jobs Job[] } + +model Job { + job_id Int @id @default(autoincrement()) + title String + department_id Int + description String + semester String + deadline DateTime + status JobStatus + created_by Int + + // Relations + department Department @relation(fields: [department_id], references: [department_id]) + admin Admin @relation(fields: [created_by], references: [admin_id]) +} + +enum JobStatus { + open + closed +} \ No newline at end of file diff --git a/server/src/index.ts b/server/src/index.ts index b59101f..02a96a9 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -1,5 +1,7 @@ -import express, { Request, Response, NextFunction } from 'express'; +import express, { Request, Response} from 'express'; import applicantRoutes from './routes/applicant'; +import adminRoutes from './routes/admin'; +import jobRoutes from './routes/job'; import dotenv from 'dotenv'; dotenv.config(); @@ -10,13 +12,15 @@ const HOST = process.env.HOST || "localhost"; app.use(express.json()); app.use('/applicant', applicantRoutes); +app.use('/admin', adminRoutes); +app.use('/job', jobRoutes); app.get('/', (req: Request, res: Response) => { res.send('Hello, TypeScript Express!'); }); // Add this error handling middleware -app.use((err: Error, req: Request, res: Response, next: NextFunction) => { +app.use((err: Error, req: Request, res: Response) => { console.error(err.stack); res.status(500).send('Something went wrong'); }); diff --git a/server/src/routes/admin.ts b/server/src/routes/admin.ts new file mode 100644 index 0000000..9118b2b --- /dev/null +++ b/server/src/routes/admin.ts @@ -0,0 +1,104 @@ +import { Router, Request, Response } from "express"; +import { PrismaClient } from "@prisma/client"; +import { logRequest } from "../utils/logUtil"; + +const prisma = new PrismaClient(); +const router = Router(); + +// Define the type for admin input +interface AdminInput { + username: string; + email: string; + password: string; +} + +// Create a new admin +// eslint-disable-next-line @typescript-eslint/no-empty-object-type +router.post("/", async (req: Request<{}, {}, AdminInput>, res: Response) => { + logRequest(req); + const { username, email, password } = req.body; + try { + console.log(`Creating admin with email: ${email}`); + const admin = await prisma.admin.create({ + data: { username, email, password }, + }); + console.log(`Admin created with ID: ${admin.admin_id}`); + res.status(201).json(admin); + } catch (error) { + console.error("Error creating admin:", error); + res.status(400).json({ error: "Error creating admin" }); + } +}); + +// Get all admins +router.get("/", async (_req: Request, res: Response) => { + logRequest(_req); + try { + console.log("Fetching all admins"); + const admins = await prisma.admin.findMany(); + res.json(admins); + } catch (error) { + console.error("Error fetching admins:", error); + res.status(500).json({ error: "Error fetching admins" }); + } +}); + +// Get a single admin by ID +router.get("/:id", async (req: Request, res: Response) => { + logRequest(req); + const { id } = req.params; + try { + console.log(`Fetching admin with ID: ${id}`); + const admin = await prisma.admin.findUnique({ + where: { admin_id: Number(id) }, + }); + if (admin) { + console.log(`Admin found: ${admin.username}`); + res.json(admin); + } else { + console.warn(`Admin with ID ${id} not found`); + res.status(404).json({ error: "Admin not found" }); + } + } catch (error) { + console.error("Error fetching admin:", error); + res.status(500).json({ error: "Error fetching admin" }); + } +}); + +// Update an admin by ID +router.put("/:id", async (req: Request<{ id: string }, object, AdminInput>, res: Response) => { + logRequest(req); + const { id } = req.params; + const { username, email, password } = req.body; + try { + console.log(`Updating admin with ID: ${id}`); + const updatedAdmin = await prisma.admin.update({ + where: { admin_id: Number(id) }, + data: { username, email, password }, + }); + console.log(`Admin with ID ${id} updated`); + res.json(updatedAdmin); + } catch (error) { + console.error("Error updating admin:", error); + res.status(400).json({ error: "Error updating admin" }); + } +}); + +// Delete an admin by ID +router.delete("/:id", async (req: Request, res: Response) => { + logRequest(req); + const { id } = req.params; + try { + console.log(`Deleting admin with ID: ${id}`); + await prisma.admin.delete({ + where: { admin_id: Number(id) }, + }); + console.log(`Admin with ID ${id} deleted`); + res.status(204).end(); + } catch (error) { + console.error("Error deleting admin:", error); + res.status(400).json({ error: "Error deleting admin" }); + } +}); + +export default router; \ No newline at end of file diff --git a/server/src/routes/applicant.ts b/server/src/routes/applicant.ts index ad93bfd..bfd3372 100644 --- a/server/src/routes/applicant.ts +++ b/server/src/routes/applicant.ts @@ -13,6 +13,7 @@ interface ApplicantInput { } // Create a new applicant +// eslint-disable-next-line @typescript-eslint/no-empty-object-type router.post("/", async (req: Request<{}, {}, ApplicantInput>, res: Response) => { logRequest(req); const { username, email, password } = req.body; @@ -65,7 +66,7 @@ router.get("/:id", async (req: Request, res: Response) => { }); // Update an applicant by ID -router.put("/:id", async (req: Request<{ id: string }, {}, ApplicantInput>, res: Response) => { +router.put("/:id", async (req: Request<{ id: string }, object, ApplicantInput>, res: Response) => { logRequest(req); const { id } = req.params; const { username, email, password } = req.body; diff --git a/server/src/routes/job.ts b/server/src/routes/job.ts new file mode 100644 index 0000000..a00a91f --- /dev/null +++ b/server/src/routes/job.ts @@ -0,0 +1,109 @@ +import { Router, Request, Response } from "express"; +import { PrismaClient } from "@prisma/client"; +import { logRequest } from "../utils/logUtil"; + +const prisma = new PrismaClient(); +const router = Router(); + +// Define the type for job input +interface JobInput { + title: string; + department_id: number; + description: string; + semester: string; + deadline: Date; + status: 'open' | 'closed'; + created_by: number; +} + +// Create a new job +// eslint-disable-next-line @typescript-eslint/no-empty-object-type +router.post("/", async (req: Request<{}, {}, JobInput>, res: Response) => { + logRequest(req); + const { title, department_id, description, semester, deadline, status, created_by } = req.body; + try { + console.log(`Creating job with title: ${title}`); + const job = await prisma.job.create({ + data: { title, department_id, description, semester, deadline, status, created_by }, + }); + console.log(`Job created with ID: ${job.job_id}`); + res.status(201).json(job); + } catch (error) { + console.error("Error creating job:", error); + res.status(400).json({ error: "Error creating job" }); + } +}); + +// Get all jobs +router.get("/", async (_req: Request, res: Response) => { + logRequest(_req); + try { + console.log("Fetching all jobs"); + const jobs = await prisma.job.findMany(); + res.json(jobs); + } catch (error) { + console.error("Error fetching jobs:", error); + res.status(500).json({ error: "Error fetching jobs" }); + } +}); + +// Get a single job by ID +router.get("/:id", async (req: Request, res: Response) => { + logRequest(req); + const { id } = req.params; + try { + console.log(`Fetching job with ID: ${id}`); + const job = await prisma.job.findUnique({ + where: { job_id: Number(id) }, + }); + if (job) { + console.log(`Job found: ${job.title}`); + res.json(job); + } else { + console.warn(`Job with ID ${id} not found`); + res.status(404).json({ error: "Job not found" }); + } + } catch (error) { + console.error("Error fetching job:", error); + res.status(500).json({ error: "Error fetching job" }); + } +}); + +// Update a job by ID +// eslint-disable-next-line @typescript-eslint/no-empty-object-type +router.put("/:id", async (req: Request<{ id: string }, {}, JobInput>, res: Response) => { + logRequest(req); + const { id } = req.params; + const { title, department_id, description, semester, deadline, status, created_by } = req.body; + try { + console.log(`Updating job with ID: ${id}`); + const updatedJob = await prisma.job.update({ + where: { job_id: Number(id) }, + data: { title, department_id, description, semester, deadline, status, created_by }, + }); + console.log(`Job with ID ${id} updated`); + res.json(updatedJob); + } catch (error) { + console.error("Error updating job:", error); + res.status(400).json({ error: "Error updating job" }); + } +}); + +// Delete a job by ID +router.delete("/:id", async (req: Request, res: Response) => { + logRequest(req); + const { id } = req.params; + try { + console.log(`Deleting job with ID: ${id}`); + await prisma.job.delete({ + where: { job_id: Number(id) }, + }); + console.log(`Job with ID ${id} deleted`); + res.status(204).end(); + } catch (error) { + console.error("Error deleting job:", error); + res.status(400).json({ error: "Error deleting job" }); + } +}); + +export default router; \ No newline at end of file From 6af6f692590e967bd3bde4903c0433773e7239bc Mon Sep 17 00:00:00 2001 From: NgZiXin Date: Fri, 1 Nov 2024 12:25:39 +0800 Subject: [PATCH 10/24] Add validators and Application table --- server/package-lock.json | 45 ++++++- server/package.json | 3 +- .../migration.sql | 30 +++++ .../migration.sql | 2 + server/prisma/schema.prisma | 33 +++++ server/src/index.ts | 2 + server/src/routes/admin.ts | 63 ++++++--- server/src/routes/applicant.ts | 62 ++++++--- server/src/routes/application.ts | 126 ++++++++++++++++++ server/src/routes/job.ts | 67 ++++++---- server/src/validators/admin.ts | 13 ++ server/src/validators/applicant.ts | 13 ++ server/src/validators/application.ts | 87 ++++++++++++ server/src/validators/job.ts | 45 +++++++ 14 files changed, 526 insertions(+), 65 deletions(-) create mode 100644 server/prisma/migrations/20241101035445_init_application_table/migration.sql create mode 100644 server/prisma/migrations/20241101035832_update_application_table/migration.sql create mode 100644 server/src/routes/application.ts create mode 100644 server/src/validators/admin.ts create mode 100644 server/src/validators/applicant.ts create mode 100644 server/src/validators/application.ts create mode 100644 server/src/validators/job.ts diff --git a/server/package-lock.json b/server/package-lock.json index 16c3baa..814abb7 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -10,7 +10,8 @@ "license": "ISC", "dependencies": { "@prisma/client": "^5.21.1", - "dotenv": "^16.4.5" + "dotenv": "^16.4.5", + "yup": "^1.4.0" }, "devDependencies": { "@types/express": "^5.0.0", @@ -1197,6 +1198,12 @@ "fsevents": "2.3.3" } }, + "node_modules/property-expr": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/property-expr/-/property-expr-2.0.6.tgz", + "integrity": "sha512-SVtmxhRE/CGkn3eZY1T6pC8Nln6Fr/lu1mKSgRud0eC73whjGfoAogbn78LkD8aFL0zz3bAFerKSnOl7NlErBA==", + "license": "MIT" + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -1553,6 +1560,12 @@ "node": ">=8" } }, + "node_modules/tiny-case": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-case/-/tiny-case-1.0.3.tgz", + "integrity": "sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q==", + "license": "MIT" + }, "node_modules/toidentifier": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", @@ -1563,6 +1576,12 @@ "node": ">=0.6" } }, + "node_modules/toposort": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz", + "integrity": "sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg==", + "license": "MIT" + }, "node_modules/ts-node": { "version": "10.9.2", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", @@ -1607,6 +1626,18 @@ } } }, + "node_modules/type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", @@ -1802,6 +1833,18 @@ "engines": { "node": ">=6" } + }, + "node_modules/yup": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/yup/-/yup-1.4.0.tgz", + "integrity": "sha512-wPbgkJRCqIf+OHyiTBQoJiP5PFuAXaWiJK6AmYkzQAh5/c2K9hzSApBZG5wV9KoKSePF7sAxmNSvh/13YHkFDg==", + "license": "MIT", + "dependencies": { + "property-expr": "^2.0.5", + "tiny-case": "^1.0.3", + "toposort": "^2.0.2", + "type-fest": "^2.19.0" + } } } } diff --git a/server/package.json b/server/package.json index 63b04b8..2be5905 100644 --- a/server/package.json +++ b/server/package.json @@ -27,6 +27,7 @@ }, "dependencies": { "@prisma/client": "^5.21.1", - "dotenv": "^16.4.5" + "dotenv": "^16.4.5", + "yup": "^1.4.0" } } diff --git a/server/prisma/migrations/20241101035445_init_application_table/migration.sql b/server/prisma/migrations/20241101035445_init_application_table/migration.sql new file mode 100644 index 0000000..8986395 --- /dev/null +++ b/server/prisma/migrations/20241101035445_init_application_table/migration.sql @@ -0,0 +1,30 @@ +-- CreateEnum +CREATE TYPE "ApplicationStatus" AS ENUM ('submitted', 'shortlisted', 'rejected'); + +-- CreateTable +CREATE TABLE "Application" ( + "application_id" SERIAL NOT NULL, + "applicant_id" INTEGER NOT NULL, + "job_id" INTEGER NOT NULL, + "status" "ApplicationStatus" NOT NULL, + "name" TEXT NOT NULL, + "telegram" TEXT NOT NULL, + "phone" TEXT NOT NULL, + "year" INTEGER NOT NULL, + "major" TEXT NOT NULL, + "faculty" TEXT NOT NULL, + "linkedin_url" TEXT NOT NULL, + "resume_url" TEXT NOT NULL, + "applicant_desc" TEXT NOT NULL, + + CONSTRAINT "Application_pkey" PRIMARY KEY ("application_id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "Application_applicant_id_job_id_key" ON "Application"("applicant_id", "job_id"); + +-- AddForeignKey +ALTER TABLE "Application" ADD CONSTRAINT "Application_applicant_id_fkey" FOREIGN KEY ("applicant_id") REFERENCES "Applicant"("applicant_id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Application" ADD CONSTRAINT "Application_job_id_fkey" FOREIGN KEY ("job_id") REFERENCES "Job"("job_id") ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/server/prisma/migrations/20241101035832_update_application_table/migration.sql b/server/prisma/migrations/20241101035832_update_application_table/migration.sql new file mode 100644 index 0000000..43d67ec --- /dev/null +++ b/server/prisma/migrations/20241101035832_update_application_table/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "Application" ALTER COLUMN "linkedin_url" DROP NOT NULL; diff --git a/server/prisma/schema.prisma b/server/prisma/schema.prisma index 994a813..d247e32 100644 --- a/server/prisma/schema.prisma +++ b/server/prisma/schema.prisma @@ -12,6 +12,9 @@ model Applicant { username String email String @unique password String + + // Relation to Application + applications Application[] } model Admin { @@ -45,9 +48,39 @@ model Job { // Relations department Department @relation(fields: [department_id], references: [department_id]) admin Admin @relation(fields: [created_by], references: [admin_id]) + applications Application[] +} + +model Application { + application_id Int @id @default(autoincrement()) + applicant_id Int + job_id Int + status ApplicationStatus + name String + telegram String + phone String + year Int + major String + faculty String + linkedin_url String? // optional + resume_url String + applicant_desc String + + // Relations + applicant Applicant @relation(fields: [applicant_id], references: [applicant_id]) + job Job @relation(fields: [job_id], references: [job_id]) + + // Composite Constraints + @@unique([applicant_id, job_id]) } enum JobStatus { open closed +} + +enum ApplicationStatus { + submitted + shortlisted + rejected } \ No newline at end of file diff --git a/server/src/index.ts b/server/src/index.ts index 02a96a9..1444719 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -1,5 +1,6 @@ import express, { Request, Response} from 'express'; import applicantRoutes from './routes/applicant'; +import applicationRoutes from './routes/application' import adminRoutes from './routes/admin'; import jobRoutes from './routes/job'; import dotenv from 'dotenv'; @@ -14,6 +15,7 @@ app.use(express.json()); app.use('/applicant', applicantRoutes); app.use('/admin', adminRoutes); app.use('/job', jobRoutes); +app.use('/application', applicationRoutes); app.get('/', (req: Request, res: Response) => { res.send('Hello, TypeScript Express!'); diff --git a/server/src/routes/admin.ts b/server/src/routes/admin.ts index 9118b2b..d16549b 100644 --- a/server/src/routes/admin.ts +++ b/server/src/routes/admin.ts @@ -1,32 +1,43 @@ import { Router, Request, Response } from "express"; import { PrismaClient } from "@prisma/client"; +import { createAdminSchema, editAdminSchema } from "../validators/admin"; import { logRequest } from "../utils/logUtil"; +import * as yup from 'yup'; const prisma = new PrismaClient(); const router = Router(); -// Define the type for admin input -interface AdminInput { - username: string; - email: string; - password: string; -} - // Create a new admin -// eslint-disable-next-line @typescript-eslint/no-empty-object-type -router.post("/", async (req: Request<{}, {}, AdminInput>, res: Response) => { +router.post("/", async (req: Request, res: Response) => { logRequest(req); - const { username, email, password } = req.body; try { - console.log(`Creating admin with email: ${email}`); + console.log(`Validating input data`); + const validatedData = await createAdminSchema.validate(req.body, { + abortEarly: false, + stripUnknown: true, // Required to prevent mass assignment vulnerabilities + }); + + console.log(`Creating admin with email: ${validatedData.email}`); const admin = await prisma.admin.create({ - data: { username, email, password }, + data: validatedData, }); + console.log(`Admin created with ID: ${admin.admin_id}`); res.status(201).json(admin); } catch (error) { - console.error("Error creating admin:", error); - res.status(400).json({ error: "Error creating admin" }); + if (error instanceof yup.ValidationError) { + // If validation fails + res.status(400).json({ + error: "Validation error", + details: error.errors, // Array of validation error messages + }); + } else { + // Log the error on the server + console.error("Error creating admin:", error); + + // Send a JSON error response to the client + res.status(400).json({ error: "Error creating admin" }); + } } }); @@ -66,21 +77,35 @@ router.get("/:id", async (req: Request, res: Response) => { }); // Update an admin by ID -router.put("/:id", async (req: Request<{ id: string }, object, AdminInput>, res: Response) => { +router.put("/:id", async (req: Request, res: Response) => { logRequest(req); const { id } = req.params; - const { username, email, password } = req.body; try { + console.log(`Validating input data`); + const validatedData = await editAdminSchema.validate(req.body, { + abortEarly: false, + stripUnknown: true, // Required to prevent mass assignment vulnerabilities + }); + console.log(`Updating admin with ID: ${id}`); const updatedAdmin = await prisma.admin.update({ where: { admin_id: Number(id) }, - data: { username, email, password }, + data: validatedData, }); + console.log(`Admin with ID ${id} updated`); res.json(updatedAdmin); } catch (error) { - console.error("Error updating admin:", error); - res.status(400).json({ error: "Error updating admin" }); + if (error instanceof yup.ValidationError) { + // If validation fails + res.status(400).json({ + error: "Validation error", + details: error.errors, // Array of validation error messages + }); + } else { + console.error("Error updating admin:", error); + res.status(400).json({ error: "Error updating admin" }); + } } }); diff --git a/server/src/routes/applicant.ts b/server/src/routes/applicant.ts index bfd3372..231dc44 100644 --- a/server/src/routes/applicant.ts +++ b/server/src/routes/applicant.ts @@ -1,32 +1,40 @@ import { Router, Request, Response } from "express"; import { PrismaClient } from "@prisma/client"; +import { createApplicantSchema, editApplicantSchema } from "../validators/applicant"; import { logRequest } from "../utils/logUtil"; +import * as yup from 'yup'; const prisma = new PrismaClient(); const router = Router(); -// Define the type for applicant input -interface ApplicantInput { - username: string; - email: string; - password: string; -} - // Create a new applicant -// eslint-disable-next-line @typescript-eslint/no-empty-object-type -router.post("/", async (req: Request<{}, {}, ApplicantInput>, res: Response) => { +router.post("/", async (req: Request, res: Response) => { logRequest(req); - const { username, email, password } = req.body; try { - console.log(`Creating applicant with email: ${email}`); + console.log(`Validating input data`); + const validatedData = await createApplicantSchema.validate(req.body, { + abortEarly: false, + stripUnknown: true, // Required to prevent mass assignment vulnerabilities + }); + + console.log(`Creating applicant with email: ${validatedData.email}`); const applicant = await prisma.applicant.create({ - data: { username, email, password }, + data: validatedData, }); + console.log(`Applicant created with ID: ${applicant.applicant_id}`); res.status(201).json(applicant); } catch (error) { - console.error("Error creating applicant:", error); - res.status(400).json({ error: "Error creating applicant" }); + if (error instanceof yup.ValidationError) { + // If validation fails + res.status(400).json({ + error: "Validation error", + details: error.errors, // Array of validation error messages + }); + } else { + console.error("Error creating applicant:", error); + res.status(400).json({ error: "Error creating applicant" }); + } } }); @@ -66,21 +74,35 @@ router.get("/:id", async (req: Request, res: Response) => { }); // Update an applicant by ID -router.put("/:id", async (req: Request<{ id: string }, object, ApplicantInput>, res: Response) => { +router.put("/:id", async (req: Request, res: Response) => { logRequest(req); const { id } = req.params; - const { username, email, password } = req.body; try { + console.log(`Validating input data`); + const validatedData = await editApplicantSchema.validate(req.body, { + abortEarly: false, + stripUnknown: true, // Required to prevent mass assignment vulnerabilities + }); + console.log(`Updating applicant with ID: ${id}`); const updatedApplicant = await prisma.applicant.update({ where: { applicant_id: Number(id) }, - data: { username, email, password }, + data: validatedData, }); + console.log(`Applicant with ID ${id} updated`); res.json(updatedApplicant); } catch (error) { - console.error("Error updating applicant:", error); - res.status(400).json({ error: "Error updating applicant" }); + if (error instanceof yup.ValidationError) { + // If validation fails + res.status(400).json({ + error: "Validation error", + details: error.errors, // Array of validation error messages + }); + } else { + console.error("Error updating applicant:", error); + res.status(400).json({ error: "Error updating applicant" }); + } } }); @@ -101,4 +123,4 @@ router.delete("/:id", async (req: Request, res: Response) => { } }); -export default router; +export default router; \ No newline at end of file diff --git a/server/src/routes/application.ts b/server/src/routes/application.ts new file mode 100644 index 0000000..233b1ba --- /dev/null +++ b/server/src/routes/application.ts @@ -0,0 +1,126 @@ +import { Router, Request, Response } from "express"; +import { PrismaClient } from "@prisma/client"; +import { createApplicationSchema, editApplicationSchema } from "../validators/application"; +import { logRequest } from "../utils/logUtil"; +import * as yup from 'yup'; + +const prisma = new PrismaClient(); +const router = Router(); + +// Create a new application +router.post("/", async (req: Request, res: Response) => { + logRequest(req); + try { + console.log(`Validating input data`); + const validatedData = await createApplicationSchema.validate(req.body, { + abortEarly: false, + stripUnknown: true, // Required to prevent mass assignment vulnerabilities + }); + + console.log(`Creating application with Job id: ${validatedData.job_id} and Application id: ${validatedData.applicant_id}`); + const application = await prisma.application.create({ + data: validatedData, + }); + + console.log(`Application created with ID: ${application.application_id}`); + res.status(201).json(application); + } catch (error) { + if (error instanceof yup.ValidationError) { + // If validation fails + res.status(400).json({ + error: "Validation error", + details: error.errors, // Array of validation error messages + }); + } else { + console.error("Error creating application:", error); + res.status(400).json({ error: "Error creating application" }); + } + } +}); + +// Get all applications +router.get("/", async (_req: Request, res: Response) => { + logRequest(_req); + try { + console.log("Fetching all applications"); + const applications = await prisma.application.findMany(); + res.json(applications); + } catch (error) { + console.error("Error fetching applications:", error); + res.status(500).json({ error: "Error fetching applications" }); + } +}); + +// Get a single application by ID +router.get("/:id", async (req: Request, res: Response) => { + logRequest(req); + const { id } = req.params; + try { + console.log(`Fetching application with ID: ${id}`); + const application = await prisma.application.findUnique({ + where: { application_id: Number(id) }, + }); + if (application) { + console.log(`Application with ID ${id} found`); + res.json(application); + } else { + console.warn(`Application with ID ${id} not found`); + res.status(404).json({ error: "Application not found" }); + } + } catch (error) { + console.error("Error fetching application:", error); + res.status(500).json({ error: "Error fetching application" }); + } +}); + +// Update an application by ID +router.put("/:id", async (req: Request, res: Response) => { + logRequest(req); + const { id } = req.params; + try { + console.log(`Validating input data`); + const validatedData = await editApplicationSchema.validate(req.body, { + abortEarly: false, + stripUnknown: true, // Required to prevent mass assignment vulnerabilities + }); + + console.log(`Updating application with ID: ${id}`); + const updatedApplicant = await prisma.application.update({ + where: { application_id: Number(id) }, + data: validatedData, + }); + + console.log(`Application with ID ${id} updated`); + res.json(updatedApplicant); + } catch (error) { + if (error instanceof yup.ValidationError) { + // If validation fails + res.status(400).json({ + error: "Validation error", + details: error.errors, // Array of validation error messages + }); + } else { + console.error("Error updating application:", error); + res.status(400).json({ error: "Error updating application" }); + } + } +}); + +// Delete an application by ID +router.delete("/:id", async (req: Request, res: Response) => { + logRequest(req); + const { id } = req.params; + try { + console.log(`Deleting application with ID: ${id}`); + await prisma.application.delete({ + where: { application_id: Number(id) }, + }); + console.log(`Application with ID ${id} deleted`); + res.status(204).end(); + } catch (error) { + console.error("Error deleting application:", error); + res.status(400).json({ error: "Error deleting application" }); + } +}); + +export default router; \ No newline at end of file diff --git a/server/src/routes/job.ts b/server/src/routes/job.ts index a00a91f..3864f56 100644 --- a/server/src/routes/job.ts +++ b/server/src/routes/job.ts @@ -1,36 +1,41 @@ import { Router, Request, Response } from "express"; import { PrismaClient } from "@prisma/client"; +import { createJobSchema, editJobSchema } from "../validators/job"; import { logRequest } from "../utils/logUtil"; +import * as yup from 'yup'; const prisma = new PrismaClient(); const router = Router(); -// Define the type for job input -interface JobInput { - title: string; - department_id: number; - description: string; - semester: string; - deadline: Date; - status: 'open' | 'closed'; - created_by: number; -} - // Create a new job -// eslint-disable-next-line @typescript-eslint/no-empty-object-type -router.post("/", async (req: Request<{}, {}, JobInput>, res: Response) => { +router.post("/", async (req: Request, res: Response) => { logRequest(req); - const { title, department_id, description, semester, deadline, status, created_by } = req.body; try { - console.log(`Creating job with title: ${title}`); + console.log(`Validating input data`); + const validatedData = await createJobSchema.validate(req.body, { + abortEarly: false, + stripUnknown: true, // Required to prevent mass assignment vulnerabilities + }); + + console.log(`Creating job with title: ${validatedData.title}`); const job = await prisma.job.create({ - data: { title, department_id, description, semester, deadline, status, created_by }, + data: validatedData, }); + console.log(`Job created with ID: ${job.job_id}`); res.status(201).json(job); + } catch (error) { - console.error("Error creating job:", error); - res.status(400).json({ error: "Error creating job" }); + if (error instanceof yup.ValidationError) { + // If validation fails + res.status(400).json({ + error: "Validation error", + details: error.errors, // Array of validation error messages + }); + } else { + console.error("Error creating job:", error); + res.status(400).json({ error: "Error creating job" }); + } } }); @@ -70,22 +75,35 @@ router.get("/:id", async (req: Request, res: Response) => { }); // Update a job by ID -// eslint-disable-next-line @typescript-eslint/no-empty-object-type -router.put("/:id", async (req: Request<{ id: string }, {}, JobInput>, res: Response) => { +router.put("/:id", async (req: Request, res: Response) => { logRequest(req); const { id } = req.params; - const { title, department_id, description, semester, deadline, status, created_by } = req.body; try { + console.log(`Validating input data`); + const validatedData = await editJobSchema.validate(req.body, { + abortEarly: false, + stripUnknown: true, + }); + console.log(`Updating job with ID: ${id}`); const updatedJob = await prisma.job.update({ where: { job_id: Number(id) }, - data: { title, department_id, description, semester, deadline, status, created_by }, + data: validatedData, }); + console.log(`Job with ID ${id} updated`); res.json(updatedJob); } catch (error) { - console.error("Error updating job:", error); - res.status(400).json({ error: "Error updating job" }); + if (error instanceof yup.ValidationError) { + // If validation fails + res.status(400).json({ + error: "Validation error", + details: error.errors, // Array of validation error messages + }); + } else { + console.error("Error updating job:", error); + res.status(400).json({ error: "Error updating job" }); + } } }); @@ -98,6 +116,7 @@ router.delete("/:id", async (req: Request, res: Response) => { await prisma.job.delete({ where: { job_id: Number(id) }, }); + console.log(`Job with ID ${id} deleted`); res.status(204).end(); } catch (error) { diff --git a/server/src/validators/admin.ts b/server/src/validators/admin.ts new file mode 100644 index 0000000..2d3157c --- /dev/null +++ b/server/src/validators/admin.ts @@ -0,0 +1,13 @@ +import * as yup from 'yup'; + +export const createAdminSchema = yup.object().shape({ + username: yup.string().required('Username is required'), + email: yup.string().email('Email must be a valid email address').required('Email is required'), + password: yup.string().required('Password is required'), +}); + +export const editAdminSchema = yup.object().shape({ + username: yup.string().optional(), + email: yup.string().email('Email must be a valid email address').optional(), + password: yup.string().optional(), +}); \ No newline at end of file diff --git a/server/src/validators/applicant.ts b/server/src/validators/applicant.ts new file mode 100644 index 0000000..79f2927 --- /dev/null +++ b/server/src/validators/applicant.ts @@ -0,0 +1,13 @@ +import * as yup from 'yup'; + +export const createApplicantSchema = yup.object().shape({ + username: yup.string().required('Username is required'), + email: yup.string().email('Email must be a valid email address').required('Email is required'), + password: yup.string().required('Password is required'), +}); + +export const editApplicantSchema = yup.object().shape({ + username: yup.string().optional(), + email: yup.string().email('Email must be a valid email address').optional(), + password: yup.string().optional(), +}); \ No newline at end of file diff --git a/server/src/validators/application.ts b/server/src/validators/application.ts new file mode 100644 index 0000000..e4da3d5 --- /dev/null +++ b/server/src/validators/application.ts @@ -0,0 +1,87 @@ +import * as yup from 'yup'; + +export const createApplicationSchema = yup.object().shape({ + applicant_id: yup + .number() + .integer() + .positive() + .required('Applicant ID is required'), + job_id: yup + .number() + .integer() + .positive() + .required('Job ID is required'), + status: yup + .mixed<'submitted' | 'shortlisted' | 'rejected'>() + .oneOf(['submitted', 'shortlisted', 'rejected'], 'Status must be one of: submitted, shortlisted, rejected') + .required('Status is required'), + name: yup.string().required('Name is required'), + telegram: yup + .string() + .matches(/^@/, "Telegram handle must start with '@'") + .required('Telegram handle is required'), + phone: yup + .string() + .matches(/^\d{8}$/, 'Phone number must be exactly 8 digits') + .required('Phone number is required'), + year: yup + .number() + .integer() + .min(1, 'Year must be between 1 and 4') + .max(4, 'Year must be between 1 and 4') + .required('Year is required'), + major: yup.string().required('Major is required'), + faculty: yup.string().required('Faculty is required'), + linkedin_url: yup // Optional + .string() + .url('LinkedIn URL must be a valid URL') + .optional(), + resume_url: yup + .string() + .url('Resume URL must be a valid URL') + .required('Resume URL is required'), + applicant_desc: yup.string().required('Applicant description is required'), +}); + +export const editApplicationSchema = yup.object().shape({ + applicant_id: yup + .number() + .integer() + .positive() + .optional(), + job_id: yup + .number() + .integer() + .positive() + .optional(), + status: yup + .mixed<'submitted' | 'shortlisted' | 'rejected'>() + .oneOf(['submitted', 'shortlisted', 'rejected'], 'Status must be one of: submitted, shortlisted, rejected') + .optional(), + name: yup.string().optional(), + telegram: yup + .string() + .matches(/^@/, "Telegram handle must start with '@'") + .optional(), + phone: yup + .string() + .matches(/^\d{8}$/, 'Phone number must be exactly 8 digits') + .optional(), + year: yup + .number() + .integer() + .min(1, 'Year must be between 1 and 4') + .max(4, 'Year must be between 1 and 4') + .optional(), + major: yup.string().required('Major is required'), + faculty: yup.string().required('Faculty is required'), + linkedin_url: yup + .string() + .url('LinkedIn URL must be a valid URL') + .optional(), + resume_url: yup + .string() + .url('Resume URL must be a valid URL') + .optional(), + applicant_desc: yup.string().required('Applicant description is required'), +}); \ No newline at end of file diff --git a/server/src/validators/job.ts b/server/src/validators/job.ts new file mode 100644 index 0000000..127122b --- /dev/null +++ b/server/src/validators/job.ts @@ -0,0 +1,45 @@ +import * as yup from 'yup'; + +// Schema for creating a new job +export const createJobSchema = yup.object().shape({ + title: yup.string().required('Title is required'), + department_id: yup + .number() + .integer('Department ID must be an integer') + .positive('Department ID must be positive') + .required('Department ID is required'), + description: yup.string().required('Description is required'), + semester: yup.string().required('Semester is required'), + deadline: yup.date().required('Deadline is required'), + status: yup + .mixed<'open' | 'closed'>() + .oneOf(['open', 'closed'], 'Status must be either "open" or "closed"') + .required('Status is required'), + created_by: yup + .number() + .integer('Created by must be an integer') + .positive('Created by must be positive') + .required('Created by (Admin ID) is required'), +}); + +// Schema for editing an existing job +export const editJobSchema = yup.object().shape({ + title: yup.string().optional(), + department_id: yup + .number() + .integer('Department ID must be an integer') + .positive('Department ID must be positive') + .optional(), + description: yup.string().optional(), + semester: yup.string().optional(), + deadline: yup.date().optional(), + status: yup + .mixed<'open' | 'closed'>() + .oneOf(['open', 'closed'], 'Status must be either "open" or "closed"') + .optional(), + created_by: yup + .number() + .integer('Created by must be an integer') + .positive('Created by must be positive') + .optional(), +}); \ No newline at end of file From b89cbbecb7decfab5bccf2e85b09db184d5bb5ff Mon Sep 17 00:00:00 2001 From: NgZiXin Date: Tue, 17 Dec 2024 12:20:30 +0800 Subject: [PATCH 11/24] openid-client --- server/package-lock.json | 137 ++++++++++++++++-- server/package.json | 3 + .../migration.sql | 12 ++ .../migration.sql | 12 ++ .../migration.sql | 13 ++ server/prisma/schema.prisma | 9 +- server/src/index.ts | 47 +++++- server/src/routes/auth.ts | 129 +++++++++++++++++ server/src/validators/admin.ts | 2 - server/src/validators/applicant.ts | 4 +- server/tsconfig.json | 4 +- 11 files changed, 343 insertions(+), 29 deletions(-) create mode 100644 server/prisma/migrations/20241215160542_remove_password/migration.sql create mode 100644 server/prisma/migrations/20241216032927_add_pkce_session/migration.sql create mode 100644 server/prisma/migrations/20241216033113_rename_fields_in_pkce_session/migration.sql create mode 100644 server/src/routes/auth.ts diff --git a/server/package-lock.json b/server/package-lock.json index 814abb7..c3c5a4e 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -11,10 +11,13 @@ "dependencies": { "@prisma/client": "^5.21.1", "dotenv": "^16.4.5", + "express-session": "^1.18.1", + "openid-client": "^6.1.7", "yup": "^1.4.0" }, "devDependencies": { "@types/express": "^5.0.0", + "@types/express-session": "^1.18.1", "@types/node": "^22.8.1", "express": "^4.21.1", "prisma": "^5.21.1", @@ -225,6 +228,16 @@ "@types/send": "*" } }, + "node_modules/@types/express-session": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/@types/express-session/-/express-session-1.18.1.tgz", + "integrity": "sha512-S6TkD/lljxDlQ2u/4A70luD8/ZxZcrU5pQwI1rVXCiaVIywoFgbA+PIUNDjPhQpPdK0dGleLtYc/y7XWBfclBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/express": "*" + } + }, "node_modules/@types/http-errors": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", @@ -506,9 +519,9 @@ "license": "MIT" }, "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, "license": "MIT", "dependencies": { @@ -524,7 +537,6 @@ "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, "license": "MIT", "dependencies": { "ms": "2.0.0" @@ -552,7 +564,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.8" @@ -663,9 +674,9 @@ } }, "node_modules/express": { - "version": "4.21.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz", - "integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==", + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", "dev": true, "license": "MIT", "dependencies": { @@ -688,7 +699,7 @@ "methods": "~1.1.2", "on-finished": "2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.10", + "path-to-regexp": "0.1.12", "proxy-addr": "~2.0.7", "qs": "6.13.0", "range-parser": "~1.2.1", @@ -703,8 +714,46 @@ }, "engines": { "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express-session": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.18.1.tgz", + "integrity": "sha512-a5mtTqEaZvBCL9A9aqkrtfz+3SMDhOVUnjafjo+s7A9Txkq+SVX2DLvSp1Zrv4uCXa3lMSK3viWnh9Gg07PBUA==", + "license": "MIT", + "dependencies": { + "cookie": "0.7.2", + "cookie-signature": "1.0.7", + "debug": "2.6.9", + "depd": "~2.0.0", + "on-headers": "~1.0.2", + "parseurl": "~1.3.3", + "safe-buffer": "5.2.1", + "uid-safe": "~2.1.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/express-session/node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" } }, + "node_modules/express-session/node_modules/cookie-signature": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", + "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==", + "license": "MIT" + }, "node_modules/finalhandler": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", @@ -975,6 +1024,15 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/jose": { + "version": "5.9.6", + "resolved": "https://registry.npmjs.org/jose/-/jose-5.9.6.tgz", + "integrity": "sha512-AMlnetc9+CV9asI19zHmrgS/WYsWUwCn2R7RzlbJWD7F9eWYUTGyBmU9o6PxngtLGOiDGPRu+Uc4fhKzbpteZQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/lru-cache": { "version": "11.0.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.0.1.tgz", @@ -1088,7 +1146,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true, "license": "MIT" }, "node_modules/negotiator": { @@ -1101,6 +1158,15 @@ "node": ">= 0.6" } }, + "node_modules/oauth4webapi": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/oauth4webapi/-/oauth4webapi-3.1.4.tgz", + "integrity": "sha512-eVfN3nZNbok2s/ROifO0UAc5G8nRoLSbrcKJ09OqmucgnhXEfdIQOR4gq1eJH1rN3gV7rNw62bDEgftsgFtBEg==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/object-inspect": { "version": "1.13.2", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", @@ -1127,6 +1193,28 @@ "node": ">= 0.8" } }, + "node_modules/on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/openid-client": { + "version": "6.1.7", + "resolved": "https://registry.npmjs.org/openid-client/-/openid-client-6.1.7.tgz", + "integrity": "sha512-JfY/KvQgOutmG2P+oVNKInE7zIh+im1MQOaO7g5CtNnTWMociA563WweiEMKfR9ry9XG3K2HGvj9wEqhCQkPMg==", + "license": "MIT", + "dependencies": { + "jose": "^5.9.6", + "oauth4webapi": "^3.1.4" + }, + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/package-json-from-dist": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", @@ -1138,7 +1226,6 @@ "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.8" @@ -1172,9 +1259,9 @@ } }, "node_modules/path-to-regexp": { - "version": "0.1.10", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", - "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==", + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", "dev": true, "license": "MIT" }, @@ -1234,6 +1321,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/random-bytes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", + "integrity": "sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", @@ -1284,7 +1380,6 @@ "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, "funding": [ { "type": "github", @@ -1666,6 +1761,18 @@ "node": ">=14.17" } }, + "node_modules/uid-safe": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", + "integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==", + "license": "MIT", + "dependencies": { + "random-bytes": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/undici-types": { "version": "6.19.8", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", diff --git a/server/package.json b/server/package.json index 2be5905..f9c8916 100644 --- a/server/package.json +++ b/server/package.json @@ -18,6 +18,7 @@ "description": "", "devDependencies": { "@types/express": "^5.0.0", + "@types/express-session": "^1.18.1", "@types/node": "^22.8.1", "express": "^4.21.1", "prisma": "^5.21.1", @@ -28,6 +29,8 @@ "dependencies": { "@prisma/client": "^5.21.1", "dotenv": "^16.4.5", + "express-session": "^1.18.1", + "openid-client": "^6.1.7", "yup": "^1.4.0" } } diff --git a/server/prisma/migrations/20241215160542_remove_password/migration.sql b/server/prisma/migrations/20241215160542_remove_password/migration.sql new file mode 100644 index 0000000..252df85 --- /dev/null +++ b/server/prisma/migrations/20241215160542_remove_password/migration.sql @@ -0,0 +1,12 @@ +/* + Warnings: + + - You are about to drop the column `password` on the `Admin` table. All the data in the column will be lost. + - You are about to drop the column `password` on the `Applicant` table. All the data in the column will be lost. + +*/ +-- AlterTable +ALTER TABLE "Admin" DROP COLUMN "password"; + +-- AlterTable +ALTER TABLE "Applicant" DROP COLUMN "password"; diff --git a/server/prisma/migrations/20241216032927_add_pkce_session/migration.sql b/server/prisma/migrations/20241216032927_add_pkce_session/migration.sql new file mode 100644 index 0000000..77f5ee1 --- /dev/null +++ b/server/prisma/migrations/20241216032927_add_pkce_session/migration.sql @@ -0,0 +1,12 @@ +-- CreateTable +CREATE TABLE "PKCESession" ( + "id" SERIAL NOT NULL, + "state" TEXT NOT NULL, + "codeVerifier" TEXT NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "PKCESession_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "PKCESession_state_key" ON "PKCESession"("state"); diff --git a/server/prisma/migrations/20241216033113_rename_fields_in_pkce_session/migration.sql b/server/prisma/migrations/20241216033113_rename_fields_in_pkce_session/migration.sql new file mode 100644 index 0000000..b3dcce6 --- /dev/null +++ b/server/prisma/migrations/20241216033113_rename_fields_in_pkce_session/migration.sql @@ -0,0 +1,13 @@ +/* + Warnings: + + - You are about to drop the column `codeVerifier` on the `PKCESession` table. All the data in the column will be lost. + - You are about to drop the column `createdAt` on the `PKCESession` table. All the data in the column will be lost. + - Added the required column `code_verifier` to the `PKCESession` table without a default value. This is not possible if the table is not empty. + +*/ +-- AlterTable +ALTER TABLE "PKCESession" DROP COLUMN "codeVerifier", +DROP COLUMN "createdAt", +ADD COLUMN "code_verifier" TEXT NOT NULL, +ADD COLUMN "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP; diff --git a/server/prisma/schema.prisma b/server/prisma/schema.prisma index d247e32..cbffeb6 100644 --- a/server/prisma/schema.prisma +++ b/server/prisma/schema.prisma @@ -11,7 +11,6 @@ model Applicant { applicant_id Int @id @default(autoincrement()) username String email String @unique - password String // Relation to Application applications Application[] @@ -21,7 +20,6 @@ model Admin { admin_id Int @id @default(autoincrement()) username String email String @unique - password String // Relation to Job jobs Job[] @@ -83,4 +81,11 @@ enum ApplicationStatus { submitted shortlisted rejected +} + +model PKCESession { + id Int @id @default(autoincrement()) + state String @unique + code_verifier String + created_at DateTime @default(now()) } \ No newline at end of file diff --git a/server/src/index.ts b/server/src/index.ts index 1444719..b31c4e1 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -1,28 +1,63 @@ import express, { Request, Response} from 'express'; +import session from 'express-session'; import applicantRoutes from './routes/applicant'; import applicationRoutes from './routes/application' import adminRoutes from './routes/admin'; import jobRoutes from './routes/job'; +import authRoutes from './routes/auth'; +import assert from "assert"; import dotenv from 'dotenv'; -dotenv.config(); +// Extend SessionData inline +declare module 'express-session' { + interface SessionData { + code_verifier?: string; + state?: string; + } +} + +// Assertions for type safety +assert(process.env.PORT, "Environment variable PORT must be defined."); +assert(process.env.HOST, "Environment variable HOST must be defined."); +assert(process.env.SESSION_SECRET, "Environment variable SESSION_SECRET must be defined."); +// Declarations & Configurations +dotenv.config(); const app = express(); -const PORT = parseInt(process.env.PORT || '3000', 10); -const HOST = process.env.HOST || "localhost"; +const PORT = parseInt(process.env.PORT, 10); +const HOST = process.env.HOST; +const SESSION_SECRET = process.env.SESSION_SECRET; app.use(express.json()); app.use('/applicant', applicantRoutes); app.use('/admin', adminRoutes); app.use('/job', jobRoutes); app.use('/application', applicationRoutes); +app.use('/auth', authRoutes); -app.get('/', (req: Request, res: Response) => { +app.get('/', (_req: Request, res: Response) => { res.send('Hello, TypeScript Express!'); }); -// Add this error handling middleware -app.use((err: Error, req: Request, res: Response) => { + +// Configure session middleware +app.use( + session({ + secret: SESSION_SECRET, + resave: false, + saveUninitialized: false, + cookie: { secure: false }, // Set to true in production + }) +); + +// Handling middleware errors +app.use((err: any, _req: Request, res: Response) => { + if (err.name === 'UnauthorizedError') { + // Handle authentication errors (e.g., invalid or missing token) + console.error('Authentication error:', err.message); + res.status(401).json({ error: err.message }); + } + // Handle other types of errors console.error(err.stack); res.status(500).send('Something went wrong'); }); diff --git a/server/src/routes/auth.ts b/server/src/routes/auth.ts new file mode 100644 index 0000000..2c9a197 --- /dev/null +++ b/server/src/routes/auth.ts @@ -0,0 +1,129 @@ +import { Router, Request, Response } from "express"; +import * as client from "openid-client"; +import jwt from "jsonwebtoken"; +import assert from "assert"; + +assert( + process.env.GOOGLE_URL, + "Environment variable GOOGLE_URL must be defined." +); +assert( + process.env.GOOGLE_CLIENT_ID, + "Environment variable GOOGLE_CLIENT_ID must be defined." +); +assert( + process.env.GOOGLE_CLIENT_SECRET, + "Environment variable GOOGLE_CLIENT_SECRET must be defined." +); +assert( + process.env.JWT_SECRET, + "Environment variable JWT_SECRET must be defined." +); + +const router = Router(); +const server: URL = new URL(process.env.GOOGLE_URL); // Authorization Server's Issuer Identifier +const client_id: string = process.env.GOOGLE_CLIENT_ID; // Client identifier at the Authorization Server +const client_secret: string = process.env.GOOGLE_CLIENT_SECRET; // Client Secret +const jwt_secret: string = process.env.JWT_SECRET; // JWT Secret + +let config!: client.Configuration; + +(async () => { + // Discover Google's OpenID configuration + const client = await import("openid-client"); + config = await client.discovery(server, client_id, client_secret); + console.log("Google configuration discovered"); +})(); + +router.get("/login", async (req: Request, res: Response) => { + const code_verifier = client.randomPKCECodeVerifier(); + const state = client.randomState(); + + // Store codeVerifier and state in the session + req.session.code_verifier = code_verifier; + req.session.state = state; + + // Create parameters + const code_challenge = await client.calculatePKCECodeChallenge(code_verifier); + const parameters: Record = { + redirect_uri: `${req.protocol}://${req.get("host")}/callback`, + scope: "openid email", + code_challenge: code_challenge, + code_challenge_method: "S256", + }; + + // now redirect the user to redirectTo.href + const redirectTo: URL = client.buildAuthorizationUrl(config, parameters); + console.log("redirecting to", redirectTo.href); + res.redirect(redirectTo.href); +}); + +router.get("/callback", async (req: Request, res: Response) => { + try { + const currentUrl = new URL( + `${req.protocol}://${req.get("host")}${req.originalUrl}` + ); + + // Exchange the authorization code for tokens + if (!req.session.code_verifier || !req.session.state) { + res + .status(400) + .json({ error: "Session is invalid or missing required attributes" }); + return; + } + const tokenResponse = await client.authorizationCodeGrant( + config, + currentUrl, + { + pkceCodeVerifier: req.session.code_verifier, // Retrieve from session + expectedState: req.session.state, + } + ); + console.log("Token Response:", tokenResponse); + + // // Get the user information + // const userInfoResponse = await fetch('https://openidconnect.googleapis.com/v1/userinfo', { + // method: 'GET', + // headers: { + // Authorization: `Bearer ${tokenResponse.access_token}`, + // }, + // }); + + // const userInfo = await userInfoResponse.json(); + + // console.log('User Info:', userInfo); + // Return the user's email as the response + // res.json({ + // message: 'Login successful', + // email: userInfo.email, + // }); + + // Decode the ID token to get the user email + const idToken = tokenResponse.id_token; + assert(idToken) + const decodedIdToken = jwt.decode(idToken) as { email?: string }; + + const email = decodedIdToken?.email; + if (!email) { + throw new Error("Failed to extract email from ID token"); + } + + console.log("User Email:", email); + + // Generate JWT for the application + const payload = { email, role: "applicant" }; + const appToken = jwt.sign(payload, jwt_secret, { noTimestamp: true }); + + res.json({ token: appToken }); + + // Clean up the session after use + req.session.destroy(() => { + console.log("Session destroyed after successful login"); + }); + } catch (error) { + console.error("Error during callback:", error); + res.status(500).json({ error: "Login failed" }); + } +}); + +export default router; \ No newline at end of file diff --git a/server/src/validators/admin.ts b/server/src/validators/admin.ts index 2d3157c..794c1c1 100644 --- a/server/src/validators/admin.ts +++ b/server/src/validators/admin.ts @@ -3,11 +3,9 @@ import * as yup from 'yup'; export const createAdminSchema = yup.object().shape({ username: yup.string().required('Username is required'), email: yup.string().email('Email must be a valid email address').required('Email is required'), - password: yup.string().required('Password is required'), }); export const editAdminSchema = yup.object().shape({ username: yup.string().optional(), email: yup.string().email('Email must be a valid email address').optional(), - password: yup.string().optional(), }); \ No newline at end of file diff --git a/server/src/validators/applicant.ts b/server/src/validators/applicant.ts index 79f2927..1123d9e 100644 --- a/server/src/validators/applicant.ts +++ b/server/src/validators/applicant.ts @@ -2,12 +2,10 @@ import * as yup from 'yup'; export const createApplicantSchema = yup.object().shape({ username: yup.string().required('Username is required'), - email: yup.string().email('Email must be a valid email address').required('Email is required'), - password: yup.string().required('Password is required'), + email: yup.string().email('Email must be a valid email address').required('Email is required') }); export const editApplicantSchema = yup.object().shape({ username: yup.string().optional(), email: yup.string().email('Email must be a valid email address').optional(), - password: yup.string().optional(), }); \ No newline at end of file diff --git a/server/tsconfig.json b/server/tsconfig.json index 58fe1da..c38dab0 100644 --- a/server/tsconfig.json +++ b/server/tsconfig.json @@ -1,7 +1,9 @@ { "compilerOptions": { "target": "es6", - "module": "commonjs", + // "module": "commonjs", + "module": "ESNext", // Use modern ES module syntax + "moduleResolution": "Node", // Node.js resolution for imports "outDir": "./dist", "strict": true, "esModuleInterop": true, From acb61f27194f795453e9db42f39d8c6da5b3b7f0 Mon Sep 17 00:00:00 2001 From: NgZiXin Date: Tue, 17 Dec 2024 16:45:27 +0800 Subject: [PATCH 12/24] Add google log in feature --- server/loader-register.mjs | 5 + server/package-lock.json | 4141 +++++++++-------- server/package.json | 74 +- .../20241217081529_clean_db/migration.sql | 8 + server/prisma/schema.prisma | 7 - server/src/index.ts | 60 +- server/src/routes/admin.ts | 4 +- server/src/routes/applicant.ts | 4 +- server/src/routes/application.ts | 4 +- server/src/routes/auth.ts | 149 +- server/src/routes/job.ts | 4 +- server/src/utils/authUtil.ts | 70 + server/tsconfig.json | 5 +- 13 files changed, 2424 insertions(+), 2111 deletions(-) create mode 100644 server/loader-register.mjs create mode 100644 server/prisma/migrations/20241217081529_clean_db/migration.sql create mode 100644 server/src/utils/authUtil.ts diff --git a/server/loader-register.mjs b/server/loader-register.mjs new file mode 100644 index 0000000..9cef40e --- /dev/null +++ b/server/loader-register.mjs @@ -0,0 +1,5 @@ +import { register } from "node:module"; +import { pathToFileURL } from "node:url"; + +// Script for registering ts-node loader +register("ts-node/esm", pathToFileURL("./")); \ No newline at end of file diff --git a/server/package-lock.json b/server/package-lock.json index c3c5a4e..03b6bd7 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -1,1957 +1,2188 @@ { - "name": "server", - "version": "1.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "server", - "version": "1.0.0", - "license": "ISC", - "dependencies": { - "@prisma/client": "^5.21.1", - "dotenv": "^16.4.5", - "express-session": "^1.18.1", - "openid-client": "^6.1.7", - "yup": "^1.4.0" - }, - "devDependencies": { - "@types/express": "^5.0.0", - "@types/express-session": "^1.18.1", - "@types/node": "^22.8.1", - "express": "^4.21.1", - "prisma": "^5.21.1", - "rimraf": "^6.0.1", - "ts-node": "^10.9.2", - "typescript": "^5.6.3" - } - }, - "node_modules/@cspotcode/source-map-support": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", - "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/trace-mapping": "0.3.9" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@isaacs/cliui": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", - "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^5.1.2", - "string-width-cjs": "npm:string-width@^4.2.0", - "strip-ansi": "^7.0.1", - "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", - "wrap-ansi": "^8.1.0", - "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", - "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", - "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - }, - "node_modules/@prisma/client": { - "version": "5.21.1", - "resolved": "https://registry.npmjs.org/@prisma/client/-/client-5.21.1.tgz", - "integrity": "sha512-3n+GgbAZYjaS/k0M03yQsQfR1APbr411r74foknnsGpmhNKBG49VuUkxIU6jORgvJPChoD4WC4PqoHImN1FP0w==", - "hasInstallScript": true, - "license": "Apache-2.0", - "engines": { - "node": ">=16.13" - }, - "peerDependencies": { - "prisma": "*" - }, - "peerDependenciesMeta": { - "prisma": { - "optional": true - } - } - }, - "node_modules/@prisma/debug": { - "version": "5.21.1", - "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-5.21.1.tgz", - "integrity": "sha512-uY8SAhcnORhvgtOrNdvWS98Aq/nkQ9QDUxrWAgW8XrCZaI3j2X7zb7Xe6GQSh6xSesKffFbFlkw0c2luHQviZA==", - "devOptional": true, - "license": "Apache-2.0" - }, - "node_modules/@prisma/engines": { - "version": "5.21.1", - "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-5.21.1.tgz", - "integrity": "sha512-hGVTldUkIkTwoV8//hmnAAiAchi4oMEKD3aW5H2RrnI50tTdwza7VQbTTAyN3OIHWlK5DVg6xV7X8N/9dtOydA==", - "devOptional": true, - "hasInstallScript": true, - "license": "Apache-2.0", - "dependencies": { - "@prisma/debug": "5.21.1", - "@prisma/engines-version": "5.21.1-1.bf0e5e8a04cada8225617067eaa03d041e2bba36", - "@prisma/fetch-engine": "5.21.1", - "@prisma/get-platform": "5.21.1" - } - }, - "node_modules/@prisma/engines-version": { - "version": "5.21.1-1.bf0e5e8a04cada8225617067eaa03d041e2bba36", - "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-5.21.1-1.bf0e5e8a04cada8225617067eaa03d041e2bba36.tgz", - "integrity": "sha512-qvnEflL0//lh44S/T9NcvTMxfyowNeUxTunPcDfKPjyJNrCNf2F1zQLcUv5UHAruECpX+zz21CzsC7V2xAeM7Q==", - "devOptional": true, - "license": "Apache-2.0" - }, - "node_modules/@prisma/fetch-engine": { - "version": "5.21.1", - "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-5.21.1.tgz", - "integrity": "sha512-70S31vgpCGcp9J+mh/wHtLCkVezLUqe/fGWk3J3JWZIN7prdYSlr1C0niaWUyNK2VflLXYi8kMjAmSxUVq6WGQ==", - "devOptional": true, - "license": "Apache-2.0", - "dependencies": { - "@prisma/debug": "5.21.1", - "@prisma/engines-version": "5.21.1-1.bf0e5e8a04cada8225617067eaa03d041e2bba36", - "@prisma/get-platform": "5.21.1" - } - }, - "node_modules/@prisma/get-platform": { - "version": "5.21.1", - "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-5.21.1.tgz", - "integrity": "sha512-sRxjL3Igst3ct+e8ya/x//cDXmpLbZQ5vfps2N4tWl4VGKQAmym77C/IG/psSMsQKszc8uFC/q1dgmKFLUgXZQ==", - "devOptional": true, - "license": "Apache-2.0", - "dependencies": { - "@prisma/debug": "5.21.1" - } - }, - "node_modules/@tsconfig/node10": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", - "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@tsconfig/node12": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", - "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", - "dev": true, - "license": "MIT" - }, - "node_modules/@tsconfig/node14": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", - "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", - "dev": true, - "license": "MIT" - }, - "node_modules/@tsconfig/node16": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", - "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/body-parser": { - "version": "1.19.5", - "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", - "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/connect": "*", - "@types/node": "*" - } - }, - "node_modules/@types/connect": { - "version": "3.4.38", - "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", - "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/express": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.0.tgz", - "integrity": "sha512-DvZriSMehGHL1ZNLzi6MidnsDhUZM/x2pRdDIKdwbUNqqwHxMlRdkxtn6/EPKyqKpHqTl/4nRZsRNLpZxZRpPQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/body-parser": "*", - "@types/express-serve-static-core": "^5.0.0", - "@types/qs": "*", - "@types/serve-static": "*" - } - }, - "node_modules/@types/express-serve-static-core": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.0.1.tgz", - "integrity": "sha512-CRICJIl0N5cXDONAdlTv5ShATZ4HEwk6kDDIW2/w9qOWKg+NU/5F8wYRWCrONad0/UKkloNSmmyN/wX4rtpbVA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*", - "@types/qs": "*", - "@types/range-parser": "*", - "@types/send": "*" - } - }, - "node_modules/@types/express-session": { - "version": "1.18.1", - "resolved": "https://registry.npmjs.org/@types/express-session/-/express-session-1.18.1.tgz", - "integrity": "sha512-S6TkD/lljxDlQ2u/4A70luD8/ZxZcrU5pQwI1rVXCiaVIywoFgbA+PIUNDjPhQpPdK0dGleLtYc/y7XWBfclBg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/express": "*" - } - }, - "node_modules/@types/http-errors": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", - "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/mime": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", - "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/node": { - "version": "22.8.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.8.1.tgz", - "integrity": "sha512-k6Gi8Yyo8EtrNtkHXutUu2corfDf9su95VYVP10aGYMMROM6SAItZi0w1XszA6RtWTHSVp5OeFof37w0IEqCQg==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~6.19.8" - } - }, - "node_modules/@types/qs": { - "version": "6.9.16", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.16.tgz", - "integrity": "sha512-7i+zxXdPD0T4cKDuxCUXJ4wHcsJLwENa6Z3dCu8cfCK743OGy5Nu1RmAGqDPsoTDINVEcdXKRvR/zre+P2Ku1A==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/range-parser": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", - "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/send": { - "version": "0.17.4", - "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", - "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/mime": "^1", - "@types/node": "*" - } - }, - "node_modules/@types/serve-static": { - "version": "1.15.7", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz", - "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/http-errors": "*", - "@types/node": "*", - "@types/send": "*" - } - }, - "node_modules/accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", - "dev": true, - "license": "MIT", - "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/acorn": { - "version": "8.13.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.13.0.tgz", - "integrity": "sha512-8zSiw54Oxrdym50NlZ9sUusyO1Z1ZchgRLWRaK6c86XJFClyCgFKetdowBg5bKxyp/u+CDBJG4Mpp0m3HLZl9w==", - "dev": true, - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-walk": { - "version": "8.3.4", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", - "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "acorn": "^8.11.0" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/ansi-regex": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", - "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/arg": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true, - "license": "MIT" - }, - "node_modules/array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", - "dev": true, - "license": "MIT" - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/body-parser": { - "version": "1.20.3", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", - "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", - "dev": true, - "license": "MIT", - "dependencies": { - "bytes": "3.1.2", - "content-type": "~1.0.5", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.13.0", - "raw-body": "2.5.2", - "type-is": "~1.6.18", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/call-bind": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", - "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "set-function-length": "^1.2.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" - }, - "node_modules/content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "safe-buffer": "5.2.1" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/content-type": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", - "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/create-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/define-data-property": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", - "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "gopd": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/dotenv": { - "version": "16.4.5", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", - "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://dotenvx.com" - } - }, - "node_modules/eastasianwidth": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "dev": true, - "license": "MIT" - }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", - "dev": true, - "license": "MIT" - }, - "node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true, - "license": "MIT" - }, - "node_modules/encodeurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/es-define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", - "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "get-intrinsic": "^1.2.4" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", - "dev": true, - "license": "MIT" - }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/express": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", - "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", - "dev": true, - "license": "MIT", - "dependencies": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "1.20.3", - "content-disposition": "0.5.4", - "content-type": "~1.0.4", - "cookie": "0.7.1", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "2.0.0", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "1.3.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "merge-descriptors": "1.0.3", - "methods": "~1.1.2", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.12", - "proxy-addr": "~2.0.7", - "qs": "6.13.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "0.19.0", - "serve-static": "1.16.2", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - }, - "engines": { - "node": ">= 0.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/express-session": { - "version": "1.18.1", - "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.18.1.tgz", - "integrity": "sha512-a5mtTqEaZvBCL9A9aqkrtfz+3SMDhOVUnjafjo+s7A9Txkq+SVX2DLvSp1Zrv4uCXa3lMSK3viWnh9Gg07PBUA==", - "license": "MIT", - "dependencies": { - "cookie": "0.7.2", - "cookie-signature": "1.0.7", - "debug": "2.6.9", - "depd": "~2.0.0", - "on-headers": "~1.0.2", - "parseurl": "~1.3.3", - "safe-buffer": "5.2.1", - "uid-safe": "~2.1.5" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/express-session/node_modules/cookie": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", - "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/express-session/node_modules/cookie-signature": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", - "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==", - "license": "MIT" - }, - "node_modules/finalhandler": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", - "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "2.6.9", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "statuses": "2.0.1", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/foreground-child": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", - "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==", - "dev": true, - "license": "ISC", - "dependencies": { - "cross-spawn": "^7.0.0", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-intrinsic": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", - "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "hasown": "^2.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/glob": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.0.tgz", - "integrity": "sha512-9UiX/Bl6J2yaBbxKoEBRm4Cipxgok8kQYcOPEhScPwebu2I0HoQOuYdIO6S3hLuWoZgpDpwQZMzTFxgpkyT76g==", - "dev": true, - "license": "ISC", - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^4.0.1", - "minimatch": "^10.0.0", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^2.0.0" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/gopd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", - "dev": true, - "license": "MIT", - "dependencies": { - "get-intrinsic": "^1.1.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-property-descriptors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", - "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-define-property": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-proto": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", - "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dev": true, - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, - "license": "ISC" - }, - "node_modules/jackspeak": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.0.2.tgz", - "integrity": "sha512-bZsjR/iRjl1Nk1UkjGpAzLNfQtzuijhn2g+pbZb98HQ1Gk8vM9hfbxeMBP+M2/UUdwj0RqGG3mlvk2MsAqwvEw==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/jose": { - "version": "5.9.6", - "resolved": "https://registry.npmjs.org/jose/-/jose-5.9.6.tgz", - "integrity": "sha512-AMlnetc9+CV9asI19zHmrgS/WYsWUwCn2R7RzlbJWD7F9eWYUTGyBmU9o6PxngtLGOiDGPRu+Uc4fhKzbpteZQ==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/panva" - } - }, - "node_modules/lru-cache": { - "version": "11.0.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.0.1.tgz", - "integrity": "sha512-CgeuL5uom6j/ZVrg7G/+1IXqRY8JXX4Hghfy5YE0EhoYQWvndP1kufu58cmZLNIDKnRhZrXfdS9urVWx98AipQ==", - "dev": true, - "license": "ISC", - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/make-error": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true, - "license": "ISC" - }, - "node_modules/media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/merge-descriptors": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", - "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "dev": true, - "license": "MIT", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dev": true, - "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/minimatch": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz", - "integrity": "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" - }, - "node_modules/negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/oauth4webapi": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/oauth4webapi/-/oauth4webapi-3.1.4.tgz", - "integrity": "sha512-eVfN3nZNbok2s/ROifO0UAc5G8nRoLSbrcKJ09OqmucgnhXEfdIQOR4gq1eJH1rN3gV7rNw62bDEgftsgFtBEg==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/panva" - } - }, - "node_modules/object-inspect": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", - "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "dev": true, - "license": "MIT", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/on-headers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", - "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/openid-client": { - "version": "6.1.7", - "resolved": "https://registry.npmjs.org/openid-client/-/openid-client-6.1.7.tgz", - "integrity": "sha512-JfY/KvQgOutmG2P+oVNKInE7zIh+im1MQOaO7g5CtNnTWMociA563WweiEMKfR9ry9XG3K2HGvj9wEqhCQkPMg==", - "license": "MIT", - "dependencies": { - "jose": "^5.9.6", - "oauth4webapi": "^3.1.4" - }, - "funding": { - "url": "https://github.com/sponsors/panva" - } - }, - "node_modules/package-json-from-dist": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", - "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", - "dev": true, - "license": "BlueOak-1.0.0" - }, - "node_modules/parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-scurry": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz", - "integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "lru-cache": "^11.0.0", - "minipass": "^7.1.2" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/path-to-regexp": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", - "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/prisma": { - "version": "5.21.1", - "resolved": "https://registry.npmjs.org/prisma/-/prisma-5.21.1.tgz", - "integrity": "sha512-PB+Iqzld/uQBPaaw2UVIk84kb0ITsLajzsxzsadxxl54eaU5Gyl2/L02ysivHxK89t7YrfQJm+Ggk37uvM70oQ==", - "devOptional": true, - "hasInstallScript": true, - "license": "Apache-2.0", - "dependencies": { - "@prisma/engines": "5.21.1" - }, - "bin": { - "prisma": "build/index.js" - }, - "engines": { - "node": ">=16.13" - }, - "optionalDependencies": { - "fsevents": "2.3.3" - } - }, - "node_modules/property-expr": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/property-expr/-/property-expr-2.0.6.tgz", - "integrity": "sha512-SVtmxhRE/CGkn3eZY1T6pC8Nln6Fr/lu1mKSgRud0eC73whjGfoAogbn78LkD8aFL0zz3bAFerKSnOl7NlErBA==", - "license": "MIT" - }, - "node_modules/proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", - "dev": true, - "license": "MIT", - "dependencies": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/qs": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", - "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "side-channel": "^1.0.6" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/random-bytes": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", - "integrity": "sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/raw-body": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", - "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", - "dev": true, - "license": "MIT", - "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/rimraf": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-6.0.1.tgz", - "integrity": "sha512-9dkvaxAsk/xNXSJzMgFqqMCuFgt2+KsOFek3TMLfo8NCPfWpBmqwyNn5Y+NX56QUYfCtsyhF3ayiboEoUmJk/A==", - "dev": true, - "license": "ISC", - "dependencies": { - "glob": "^11.0.0", - "package-json-from-dist": "^1.0.0" - }, - "bin": { - "rimraf": "dist/esm/bin.mjs" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true, - "license": "MIT" - }, - "node_modules/send": { - "version": "0.19.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", - "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "2.4.1", - "range-parser": "~1.2.1", - "statuses": "2.0.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/send/node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/send/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/serve-static": { - "version": "1.16.2", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", - "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", - "dev": true, - "license": "MIT", - "dependencies": { - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.19.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/set-function-length": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", - "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", - "dev": true, - "license": "MIT", - "dependencies": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", - "dev": true, - "license": "ISC" - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/side-channel": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", - "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.4", - "object-inspect": "^1.13.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/string-width-cjs": { - "name": "string-width", - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width-cjs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width-cjs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/string-width-cjs/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/strip-ansi-cjs": { - "name": "strip-ansi", - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/tiny-case": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/tiny-case/-/tiny-case-1.0.3.tgz", - "integrity": "sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q==", - "license": "MIT" - }, - "node_modules/toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.6" - } - }, - "node_modules/toposort": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz", - "integrity": "sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg==", - "license": "MIT" - }, - "node_modules/ts-node": { - "version": "10.9.2", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", - "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@cspotcode/source-map-support": "^0.8.0", - "@tsconfig/node10": "^1.0.7", - "@tsconfig/node12": "^1.0.7", - "@tsconfig/node14": "^1.0.0", - "@tsconfig/node16": "^1.0.2", - "acorn": "^8.4.1", - "acorn-walk": "^8.1.1", - "arg": "^4.1.0", - "create-require": "^1.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "v8-compile-cache-lib": "^3.0.1", - "yn": "3.1.1" - }, - "bin": { - "ts-node": "dist/bin.js", - "ts-node-cwd": "dist/bin-cwd.js", - "ts-node-esm": "dist/bin-esm.js", - "ts-node-script": "dist/bin-script.js", - "ts-node-transpile-only": "dist/bin-transpile.js", - "ts-script": "dist/bin-script-deprecated.js" - }, - "peerDependencies": { - "@swc/core": ">=1.2.50", - "@swc/wasm": ">=1.2.50", - "@types/node": "*", - "typescript": ">=2.7" - }, - "peerDependenciesMeta": { - "@swc/core": { - "optional": true - }, - "@swc/wasm": { - "optional": true - } - } - }, - "node_modules/type-fest": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", - "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=12.20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "dev": true, - "license": "MIT", - "dependencies": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/typescript": { - "version": "5.6.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", - "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/uid-safe": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", - "integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==", - "license": "MIT", - "dependencies": { - "random-bytes": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/undici-types": { - "version": "6.19.8", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", - "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", - "dev": true, - "license": "MIT" - }, - "node_modules/unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/v8-compile-cache-lib": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", - "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", - "dev": true, - "license": "MIT" - }, - "node_modules/vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/wrap-ansi": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs": { - "name": "wrap-ansi", - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/wrap-ansi-cjs/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/yn": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/yup": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/yup/-/yup-1.4.0.tgz", - "integrity": "sha512-wPbgkJRCqIf+OHyiTBQoJiP5PFuAXaWiJK6AmYkzQAh5/c2K9hzSApBZG5wV9KoKSePF7sAxmNSvh/13YHkFDg==", - "license": "MIT", - "dependencies": { - "property-expr": "^2.0.5", - "tiny-case": "^1.0.3", - "toposort": "^2.0.2", - "type-fest": "^2.19.0" - } - } - } + "name": "server", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "server", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "@prisma/client": "^5.21.1", + "dotenv": "^16.4.5", + "express-session": "^1.18.1", + "jsonwebtoken": "^9.0.2", + "jwk-to-pem": "^2.0.7", + "openid-client": "^6.1.7", + "yup": "^1.4.0" + }, + "devDependencies": { + "@types/bcrypt": "^5.0.2", + "@types/express": "^5.0.0", + "@types/express-session": "^1.18.1", + "@types/jsonwebtoken": "^9.0.7", + "@types/jwk-to-pem": "^2.0.3", + "@types/node": "^22.8.1", + "express": "^4.21.1", + "prisma": "^5.21.1", + "rimraf": "^6.0.1", + "ts-node": "^10.9.2", + "typescript": "^5.6.3" + } + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@prisma/client": { + "version": "5.21.1", + "resolved": "https://registry.npmjs.org/@prisma/client/-/client-5.21.1.tgz", + "integrity": "sha512-3n+GgbAZYjaS/k0M03yQsQfR1APbr411r74foknnsGpmhNKBG49VuUkxIU6jORgvJPChoD4WC4PqoHImN1FP0w==", + "hasInstallScript": true, + "license": "Apache-2.0", + "engines": { + "node": ">=16.13" + }, + "peerDependencies": { + "prisma": "*" + }, + "peerDependenciesMeta": { + "prisma": { + "optional": true + } + } + }, + "node_modules/@prisma/debug": { + "version": "5.21.1", + "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-5.21.1.tgz", + "integrity": "sha512-uY8SAhcnORhvgtOrNdvWS98Aq/nkQ9QDUxrWAgW8XrCZaI3j2X7zb7Xe6GQSh6xSesKffFbFlkw0c2luHQviZA==", + "devOptional": true, + "license": "Apache-2.0" + }, + "node_modules/@prisma/engines": { + "version": "5.21.1", + "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-5.21.1.tgz", + "integrity": "sha512-hGVTldUkIkTwoV8//hmnAAiAchi4oMEKD3aW5H2RrnI50tTdwza7VQbTTAyN3OIHWlK5DVg6xV7X8N/9dtOydA==", + "devOptional": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "5.21.1", + "@prisma/engines-version": "5.21.1-1.bf0e5e8a04cada8225617067eaa03d041e2bba36", + "@prisma/fetch-engine": "5.21.1", + "@prisma/get-platform": "5.21.1" + } + }, + "node_modules/@prisma/engines-version": { + "version": "5.21.1-1.bf0e5e8a04cada8225617067eaa03d041e2bba36", + "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-5.21.1-1.bf0e5e8a04cada8225617067eaa03d041e2bba36.tgz", + "integrity": "sha512-qvnEflL0//lh44S/T9NcvTMxfyowNeUxTunPcDfKPjyJNrCNf2F1zQLcUv5UHAruECpX+zz21CzsC7V2xAeM7Q==", + "devOptional": true, + "license": "Apache-2.0" + }, + "node_modules/@prisma/fetch-engine": { + "version": "5.21.1", + "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-5.21.1.tgz", + "integrity": "sha512-70S31vgpCGcp9J+mh/wHtLCkVezLUqe/fGWk3J3JWZIN7prdYSlr1C0niaWUyNK2VflLXYi8kMjAmSxUVq6WGQ==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "5.21.1", + "@prisma/engines-version": "5.21.1-1.bf0e5e8a04cada8225617067eaa03d041e2bba36", + "@prisma/get-platform": "5.21.1" + } + }, + "node_modules/@prisma/get-platform": { + "version": "5.21.1", + "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-5.21.1.tgz", + "integrity": "sha512-sRxjL3Igst3ct+e8ya/x//cDXmpLbZQ5vfps2N4tWl4VGKQAmym77C/IG/psSMsQKszc8uFC/q1dgmKFLUgXZQ==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "5.21.1" + } + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", + "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/bcrypt": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@types/bcrypt/-/bcrypt-5.0.2.tgz", + "integrity": "sha512-6atioO8Y75fNcbmj0G7UjI9lXN2pQ/IGJ2FWT4a/btd0Lk9lQalHLKhkgKVZ3r+spnmWUKfbMi1GEe9wyHQfNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/body-parser": { + "version": "1.19.5", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", + "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/express": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.0.tgz", + "integrity": "sha512-DvZriSMehGHL1ZNLzi6MidnsDhUZM/x2pRdDIKdwbUNqqwHxMlRdkxtn6/EPKyqKpHqTl/4nRZsRNLpZxZRpPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^5.0.0", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.0.1.tgz", + "integrity": "sha512-CRICJIl0N5cXDONAdlTv5ShATZ4HEwk6kDDIW2/w9qOWKg+NU/5F8wYRWCrONad0/UKkloNSmmyN/wX4rtpbVA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/express-session": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/@types/express-session/-/express-session-1.18.1.tgz", + "integrity": "sha512-S6TkD/lljxDlQ2u/4A70luD8/ZxZcrU5pQwI1rVXCiaVIywoFgbA+PIUNDjPhQpPdK0dGleLtYc/y7XWBfclBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/express": "*" + } + }, + "node_modules/@types/http-errors": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", + "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/jsonwebtoken": { + "version": "9.0.7", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.7.tgz", + "integrity": "sha512-ugo316mmTYBl2g81zDFnZ7cfxlut3o+/EQdaP7J8QN2kY6lJ22hmQYCK5EHcJHbrW+dkCGSCPgbG8JtYj6qSrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/jwk-to-pem": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/jwk-to-pem/-/jwk-to-pem-2.0.3.tgz", + "integrity": "sha512-I/WFyFgk5GrNbkpmt14auGO3yFK1Wt4jXzkLuI+fDBNtO5ZI2rbymyGd6bKzfSBEuyRdM64ZUwxU1+eDcPSOEQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "22.8.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.8.1.tgz", + "integrity": "sha512-k6Gi8Yyo8EtrNtkHXutUu2corfDf9su95VYVP10aGYMMROM6SAItZi0w1XszA6RtWTHSVp5OeFof37w0IEqCQg==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.19.8" + } + }, + "node_modules/@types/qs": { + "version": "6.9.16", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.16.tgz", + "integrity": "sha512-7i+zxXdPD0T4cKDuxCUXJ4wHcsJLwENa6Z3dCu8cfCK743OGy5Nu1RmAGqDPsoTDINVEcdXKRvR/zre+P2Ku1A==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/send": { + "version": "0.17.4", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", + "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.7", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz", + "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "*" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.13.0.tgz", + "integrity": "sha512-8zSiw54Oxrdym50NlZ9sUusyO1Z1ZchgRLWRaK6c86XJFClyCgFKetdowBg5bKxyp/u+CDBJG4Mpp0m3HLZl9w==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", + "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true, + "license": "MIT" + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/asn1.js": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz", + "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==", + "license": "MIT", + "dependencies": { + "bn.js": "^4.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0", + "safer-buffer": "^2.1.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/bn.js": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.1.tgz", + "integrity": "sha512-k8TVBiPkPJT9uHLdOKfFpqcfprwBFOAAXXozRubr7R7PfIuKvQlzcI4M0pALeqXN09vdaMbUdUj+pass+uULAg==", + "license": "MIT" + }, + "node_modules/body-parser": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.13.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==", + "license": "MIT" + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "license": "BSD-3-Clause" + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/dotenv": { + "version": "16.4.5", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", + "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "dev": true, + "license": "MIT" + }, + "node_modules/elliptic": { + "version": "6.6.1", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.6.1.tgz", + "integrity": "sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g==", + "license": "MIT", + "dependencies": { + "bn.js": "^4.11.9", + "brorand": "^1.1.0", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.1", + "inherits": "^2.0.4", + "minimalistic-assert": "^1.0.1", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "dev": true, + "license": "MIT" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.3", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.7.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.3.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.12", + "proxy-addr": "~2.0.7", + "qs": "6.13.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.19.0", + "serve-static": "1.16.2", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express-session": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.18.1.tgz", + "integrity": "sha512-a5mtTqEaZvBCL9A9aqkrtfz+3SMDhOVUnjafjo+s7A9Txkq+SVX2DLvSp1Zrv4uCXa3lMSK3viWnh9Gg07PBUA==", + "license": "MIT", + "dependencies": { + "cookie": "0.7.2", + "cookie-signature": "1.0.7", + "debug": "2.6.9", + "depd": "~2.0.0", + "on-headers": "~1.0.2", + "parseurl": "~1.3.3", + "safe-buffer": "5.2.1", + "uid-safe": "~2.1.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/express-session/node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express-session/node_modules/cookie-signature": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", + "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==", + "license": "MIT" + }, + "node_modules/finalhandler": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/foreground-child": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", + "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/glob": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.0.tgz", + "integrity": "sha512-9UiX/Bl6J2yaBbxKoEBRm4Cipxgok8kQYcOPEhScPwebu2I0HoQOuYdIO6S3hLuWoZgpDpwQZMzTFxgpkyT76g==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^4.0.1", + "minimatch": "^10.0.0", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^2.0.0" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hash.js": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hmac-drbg": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==", + "license": "MIT", + "dependencies": { + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/jackspeak": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.0.2.tgz", + "integrity": "sha512-bZsjR/iRjl1Nk1UkjGpAzLNfQtzuijhn2g+pbZb98HQ1Gk8vM9hfbxeMBP+M2/UUdwj0RqGG3mlvk2MsAqwvEw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/jose": { + "version": "5.9.6", + "resolved": "https://registry.npmjs.org/jose/-/jose-5.9.6.tgz", + "integrity": "sha512-AMlnetc9+CV9asI19zHmrgS/WYsWUwCn2R7RzlbJWD7F9eWYUTGyBmU9o6PxngtLGOiDGPRu+Uc4fhKzbpteZQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/jsonwebtoken": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", + "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", + "license": "MIT", + "dependencies": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jsonwebtoken/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jwk-to-pem": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/jwk-to-pem/-/jwk-to-pem-2.0.7.tgz", + "integrity": "sha512-cSVphrmWr6reVchuKQZdfSs4U9c5Y4hwZggPoz6cbVnTpAVgGRpEuQng86IyqLeGZlhTh+c4MAreB6KbdQDKHQ==", + "license": "Apache-2.0", + "dependencies": { + "asn1.js": "^5.3.0", + "elliptic": "^6.6.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "license": "MIT", + "dependencies": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", + "license": "MIT" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "license": "MIT" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", + "license": "MIT" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", + "license": "MIT" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "license": "MIT" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "license": "MIT" + }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.0.1.tgz", + "integrity": "sha512-CgeuL5uom6j/ZVrg7G/+1IXqRY8JXX4Hghfy5YE0EhoYQWvndP1kufu58cmZLNIDKnRhZrXfdS9urVWx98AipQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true, + "license": "ISC" + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true, + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "license": "ISC" + }, + "node_modules/minimalistic-crypto-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==", + "license": "MIT" + }, + "node_modules/minimatch": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz", + "integrity": "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/oauth4webapi": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/oauth4webapi/-/oauth4webapi-3.1.4.tgz", + "integrity": "sha512-eVfN3nZNbok2s/ROifO0UAc5G8nRoLSbrcKJ09OqmucgnhXEfdIQOR4gq1eJH1rN3gV7rNw62bDEgftsgFtBEg==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/object-inspect": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", + "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/openid-client": { + "version": "6.1.7", + "resolved": "https://registry.npmjs.org/openid-client/-/openid-client-6.1.7.tgz", + "integrity": "sha512-JfY/KvQgOutmG2P+oVNKInE7zIh+im1MQOaO7g5CtNnTWMociA563WweiEMKfR9ry9XG3K2HGvj9wEqhCQkPMg==", + "license": "MIT", + "dependencies": { + "jose": "^5.9.6", + "oauth4webapi": "^3.1.4" + }, + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-scurry": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz", + "integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-to-regexp": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/prisma": { + "version": "5.21.1", + "resolved": "https://registry.npmjs.org/prisma/-/prisma-5.21.1.tgz", + "integrity": "sha512-PB+Iqzld/uQBPaaw2UVIk84kb0ITsLajzsxzsadxxl54eaU5Gyl2/L02ysivHxK89t7YrfQJm+Ggk37uvM70oQ==", + "devOptional": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/engines": "5.21.1" + }, + "bin": { + "prisma": "build/index.js" + }, + "engines": { + "node": ">=16.13" + }, + "optionalDependencies": { + "fsevents": "2.3.3" + } + }, + "node_modules/property-expr": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/property-expr/-/property-expr-2.0.6.tgz", + "integrity": "sha512-SVtmxhRE/CGkn3eZY1T6pC8Nln6Fr/lu1mKSgRud0eC73whjGfoAogbn78LkD8aFL0zz3bAFerKSnOl7NlErBA==", + "license": "MIT" + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/qs": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/random-bytes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", + "integrity": "sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/rimraf": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-6.0.1.tgz", + "integrity": "sha512-9dkvaxAsk/xNXSJzMgFqqMCuFgt2+KsOFek3TMLfo8NCPfWpBmqwyNn5Y+NX56QUYfCtsyhF3ayiboEoUmJk/A==", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^11.0.0", + "package-json-from-dist": "^1.0.0" + }, + "bin": { + "rimraf": "dist/esm/bin.mjs" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/serve-static": { + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.19.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "dev": true, + "license": "ISC" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/tiny-case": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-case/-/tiny-case-1.0.3.tgz", + "integrity": "sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q==", + "license": "MIT" + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/toposort": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz", + "integrity": "sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg==", + "license": "MIT" + }, + "node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typescript": { + "version": "5.6.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", + "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/uid-safe": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", + "integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==", + "license": "MIT", + "dependencies": { + "random-bytes": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/undici-types": { + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", + "dev": true, + "license": "MIT" + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true, + "license": "MIT" + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/yup": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/yup/-/yup-1.4.0.tgz", + "integrity": "sha512-wPbgkJRCqIf+OHyiTBQoJiP5PFuAXaWiJK6AmYkzQAh5/c2K9hzSApBZG5wV9KoKSePF7sAxmNSvh/13YHkFDg==", + "license": "MIT", + "dependencies": { + "property-expr": "^2.0.5", + "tiny-case": "^1.0.3", + "toposort": "^2.0.2", + "type-fest": "^2.19.0" + } + } + } } diff --git a/server/package.json b/server/package.json index f9c8916..8b08106 100644 --- a/server/package.json +++ b/server/package.json @@ -1,36 +1,42 @@ { - "name": "server", - "version": "1.0.0", - "main": "dist/index.ts", - "prisma": { - "seed": "ts-node prisma/seed.ts" - }, - "scripts": { - "start": "node dist/index.js", - "dev": "ts-node src/index.ts", - "clean": "rimraf dist", - "build": "npm run clean && npx tsc", - "test": "echo \"Error: no test specified\" && exit 1" - }, - "keywords": [], - "author": "", - "license": "ISC", - "description": "", - "devDependencies": { - "@types/express": "^5.0.0", - "@types/express-session": "^1.18.1", - "@types/node": "^22.8.1", - "express": "^4.21.1", - "prisma": "^5.21.1", - "rimraf": "^6.0.1", - "ts-node": "^10.9.2", - "typescript": "^5.6.3" - }, - "dependencies": { - "@prisma/client": "^5.21.1", - "dotenv": "^16.4.5", - "express-session": "^1.18.1", - "openid-client": "^6.1.7", - "yup": "^1.4.0" - } + "name": "server", + "version": "1.0.0", + "main": "dist/index.ts", + "prisma": { + "seed": "ts-node prisma/seed.ts" + }, + "scripts": { + "start": "node dist/index.js", + "dev": "node --import ./loader-register.mjs src/index.ts", + "clean": "rimraf dist", + "build": "npm run clean && npx tsc", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "description": "", + "devDependencies": { + "@types/bcrypt": "^5.0.2", + "@types/express": "^5.0.0", + "@types/express-session": "^1.18.1", + "@types/jsonwebtoken": "^9.0.7", + "@types/jwk-to-pem": "^2.0.3", + "@types/node": "^22.8.1", + "express": "^4.21.1", + "prisma": "^5.21.1", + "rimraf": "^6.0.1", + "ts-node": "^10.9.2", + "typescript": "^5.6.3" + }, + "dependencies": { + "@prisma/client": "^5.21.1", + "dotenv": "^16.4.5", + "express-session": "^1.18.1", + "jsonwebtoken": "^9.0.2", + "jwk-to-pem": "^2.0.7", + "openid-client": "^6.1.7", + "yup": "^1.4.0" + }, + "type": "module" } diff --git a/server/prisma/migrations/20241217081529_clean_db/migration.sql b/server/prisma/migrations/20241217081529_clean_db/migration.sql new file mode 100644 index 0000000..8c8ed21 --- /dev/null +++ b/server/prisma/migrations/20241217081529_clean_db/migration.sql @@ -0,0 +1,8 @@ +/* + Warnings: + + - You are about to drop the `PKCESession` table. If the table is not empty, all the data it contains will be lost. + +*/ +-- DropTable +DROP TABLE "PKCESession"; diff --git a/server/prisma/schema.prisma b/server/prisma/schema.prisma index cbffeb6..b3c49a1 100644 --- a/server/prisma/schema.prisma +++ b/server/prisma/schema.prisma @@ -82,10 +82,3 @@ enum ApplicationStatus { shortlisted rejected } - -model PKCESession { - id Int @id @default(autoincrement()) - state String @unique - code_verifier String - created_at DateTime @default(now()) -} \ No newline at end of file diff --git a/server/src/index.ts b/server/src/index.ts index b31c4e1..e45b0f5 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -1,57 +1,24 @@ import express, { Request, Response} from 'express'; -import session from 'express-session'; -import applicantRoutes from './routes/applicant'; -import applicationRoutes from './routes/application' -import adminRoutes from './routes/admin'; -import jobRoutes from './routes/job'; -import authRoutes from './routes/auth'; -import assert from "assert"; +import applicantRoutes from './routes/applicant.js'; +import applicationRoutes from './routes/application.js' +import adminRoutes from './routes/admin.js'; +import jobRoutes from './routes/job.js'; +import authRoutes from './routes/auth.js'; +import assert from 'assert'; import dotenv from 'dotenv'; -// Extend SessionData inline -declare module 'express-session' { - interface SessionData { - code_verifier?: string; - state?: string; - } -} - // Assertions for type safety assert(process.env.PORT, "Environment variable PORT must be defined."); assert(process.env.HOST, "Environment variable HOST must be defined."); -assert(process.env.SESSION_SECRET, "Environment variable SESSION_SECRET must be defined."); // Declarations & Configurations dotenv.config(); const app = express(); const PORT = parseInt(process.env.PORT, 10); const HOST = process.env.HOST; -const SESSION_SECRET = process.env.SESSION_SECRET; - -app.use(express.json()); -app.use('/applicant', applicantRoutes); -app.use('/admin', adminRoutes); -app.use('/job', jobRoutes); -app.use('/application', applicationRoutes); -app.use('/auth', authRoutes); - -app.get('/', (_req: Request, res: Response) => { - res.send('Hello, TypeScript Express!'); -}); - - -// Configure session middleware -app.use( - session({ - secret: SESSION_SECRET, - resave: false, - saveUninitialized: false, - cookie: { secure: false }, // Set to true in production - }) -); // Handling middleware errors -app.use((err: any, _req: Request, res: Response) => { +app.use((err: any, _req: Request, res: Response, next: Function) => { if (err.name === 'UnauthorizedError') { // Handle authentication errors (e.g., invalid or missing token) console.error('Authentication error:', err.message); @@ -62,6 +29,19 @@ app.use((err: any, _req: Request, res: Response) => { res.status(500).send('Something went wrong'); }); +// Routes +app.use(express.json()); +app.use('/applicant', applicantRoutes); +app.use('/admin', adminRoutes); +app.use('/job', jobRoutes); +app.use('/application', applicationRoutes); +app.use('/auth', authRoutes); + +// Test Route +app.get('/', (_req: Request, res: Response) => { + res.send('Hello, TypeScript Express!'); +}); + app.listen(PORT, HOST, () => { console.log(`Server running at http://${HOST}:${PORT}`); }); \ No newline at end of file diff --git a/server/src/routes/admin.ts b/server/src/routes/admin.ts index d16549b..35fe30a 100644 --- a/server/src/routes/admin.ts +++ b/server/src/routes/admin.ts @@ -1,7 +1,7 @@ import { Router, Request, Response } from "express"; import { PrismaClient } from "@prisma/client"; -import { createAdminSchema, editAdminSchema } from "../validators/admin"; -import { logRequest } from "../utils/logUtil"; +import { createAdminSchema, editAdminSchema } from "../validators/admin.js"; +import { logRequest } from "../utils/logUtil.js"; import * as yup from 'yup'; const prisma = new PrismaClient(); diff --git a/server/src/routes/applicant.ts b/server/src/routes/applicant.ts index 231dc44..c110dd7 100644 --- a/server/src/routes/applicant.ts +++ b/server/src/routes/applicant.ts @@ -1,7 +1,7 @@ import { Router, Request, Response } from "express"; import { PrismaClient } from "@prisma/client"; -import { createApplicantSchema, editApplicantSchema } from "../validators/applicant"; -import { logRequest } from "../utils/logUtil"; +import { createApplicantSchema, editApplicantSchema } from "../validators/applicant.js"; +import { logRequest } from "../utils/logUtil.js"; import * as yup from 'yup'; const prisma = new PrismaClient(); diff --git a/server/src/routes/application.ts b/server/src/routes/application.ts index 233b1ba..5d947fc 100644 --- a/server/src/routes/application.ts +++ b/server/src/routes/application.ts @@ -1,7 +1,7 @@ import { Router, Request, Response } from "express"; import { PrismaClient } from "@prisma/client"; -import { createApplicationSchema, editApplicationSchema } from "../validators/application"; -import { logRequest } from "../utils/logUtil"; +import { createApplicationSchema, editApplicationSchema } from "../validators/application.js"; +import { logRequest } from "../utils/logUtil.js"; import * as yup from 'yup'; const prisma = new PrismaClient(); diff --git a/server/src/routes/auth.ts b/server/src/routes/auth.ts index 2c9a197..440951d 100644 --- a/server/src/routes/auth.ts +++ b/server/src/routes/auth.ts @@ -1,64 +1,79 @@ import { Router, Request, Response } from "express"; +import { PrismaClient } from "@prisma/client"; import * as client from "openid-client"; +import session from 'express-session'; import jwt from "jsonwebtoken"; import assert from "assert"; +import { logRequest } from "../utils/logUtil.js"; +import { decodeGoogleIdToken } from "../utils/authUtil.js"; -assert( - process.env.GOOGLE_URL, - "Environment variable GOOGLE_URL must be defined." -); -assert( - process.env.GOOGLE_CLIENT_ID, - "Environment variable GOOGLE_CLIENT_ID must be defined." -); -assert( - process.env.GOOGLE_CLIENT_SECRET, - "Environment variable GOOGLE_CLIENT_SECRET must be defined." -); -assert( - process.env.JWT_SECRET, - "Environment variable JWT_SECRET must be defined." -); +// Assertions for type safety +assert(process.env.GOOGLE_URL, "Environment variable GOOGLE_URL must be defined."); +assert(process.env.GOOGLE_CLIENT_ID, "Environment variable GOOGLE_CLIENT_ID must be defined."); +assert(process.env.GOOGLE_CLIENT_SECRET, "Environment variable GOOGLE_CLIENT_SECRET must be defined."); +assert(process.env.JWT_SECRET, "Environment variable JWT_SECRET must be defined."); +assert(process.env.SESSION_SECRET, "Environment variable SESSION_SECRET must be defined."); const router = Router(); +const prisma = new PrismaClient(); + +// Google Login Configuration const server: URL = new URL(process.env.GOOGLE_URL); // Authorization Server's Issuer Identifier const client_id: string = process.env.GOOGLE_CLIENT_ID; // Client identifier at the Authorization Server const client_secret: string = process.env.GOOGLE_CLIENT_SECRET; // Client Secret const jwt_secret: string = process.env.JWT_SECRET; // JWT Secret +const session_secret: string = process.env.SESSION_SECRET; // SESSION Secret let config!: client.Configuration; +// Extend SessionData inline +declare module 'express-session' { + interface SessionData { + code_verifier?: string; + state?: string; + } +} + +// Create session +const sessionMiddleware = session({ + secret: session_secret, + resave: false, + saveUninitialized: false, + cookie: { secure: false }, // Set to true in production +}); + (async () => { // Discover Google's OpenID configuration - const client = await import("openid-client"); config = await client.discovery(server, client_id, client_secret); console.log("Google configuration discovered"); })(); -router.get("/login", async (req: Request, res: Response) => { +router.get("/login", sessionMiddleware, async (req: Request, res: Response) => { + logRequest(req); const code_verifier = client.randomPKCECodeVerifier(); const state = client.randomState(); - // Store codeVerifier and state in the session + // Store code_verifier and state in the session req.session.code_verifier = code_verifier; req.session.state = state; // Create parameters const code_challenge = await client.calculatePKCECodeChallenge(code_verifier); const parameters: Record = { - redirect_uri: `${req.protocol}://${req.get("host")}/callback`, - scope: "openid email", + redirect_uri: `${req.protocol}://${req.get("host")}/auth/callback`, + scope: "email profile", code_challenge: code_challenge, code_challenge_method: "S256", + state: state, }; - // now redirect the user to redirectTo.href + // Redirect the user to redirectTo.href const redirectTo: URL = client.buildAuthorizationUrl(config, parameters); - console.log("redirecting to", redirectTo.href); + console.log("Redirecting User to Google Sign-In Page"); res.redirect(redirectTo.href); }); -router.get("/callback", async (req: Request, res: Response) => { +router.get("/callback", sessionMiddleware, async (req: Request, res: Response) => { try { const currentUrl = new URL( `${req.protocol}://${req.get("host")}${req.originalUrl}` @@ -79,51 +94,57 @@ router.get("/callback", async (req: Request, res: Response) => { expectedState: req.session.state, } ); - console.log("Token Response:", tokenResponse); - - // // Get the user information - // const userInfoResponse = await fetch('https://openidconnect.googleapis.com/v1/userinfo', { - // method: 'GET', - // headers: { - // Authorization: `Bearer ${tokenResponse.access_token}`, - // }, - // }); - - // const userInfo = await userInfoResponse.json(); - - // console.log('User Info:', userInfo); - // Return the user's email as the response - // res.json({ - // message: 'Login successful', - // email: userInfo.email, - // }); - - // Decode the ID token to get the user email - const idToken = tokenResponse.id_token; - assert(idToken) - const decodedIdToken = jwt.decode(idToken) as { email?: string }; - - const email = decodedIdToken?.email; - if (!email) { - throw new Error("Failed to extract email from ID token"); - } - - console.log("User Email:", email); - - // Generate JWT for the application - const payload = { email, role: "applicant" }; - const appToken = jwt.sign(payload, jwt_secret, { noTimestamp: true }); - - res.json({ token: appToken }); + console.log("Token Received"); - // Clean up the session after use - req.session.destroy(() => { - console.log("Session destroyed after successful login"); + // Decode the ID token to get the user email and name + const id_token = tokenResponse.id_token; + if (!id_token) { + throw new Error("OAuth 2.0/OIDC token response does not contain id token") + } + const decoded_id_token = await decodeGoogleIdToken(id_token, client_id) as { + email?: string; + name?: string; + }; + const email = decoded_id_token?.email; + if (!email) { + throw new Error("Failed to extract email from ID token"); + } + const name = decoded_id_token?.name; + if (!name) { + throw new Error("Failed to extract name from ID token"); + } + + // Add applicant to database if needed + const existingApplicant = await prisma.applicant.findUnique({ + where: { email: email }, // Query based on email + }); + if (!existingApplicant) { + console.log(`Creating applicant with email: ${email}`); + await prisma.applicant.create({ + data: { + username: name, + email: email, + }, + }); + res.status(201); + } + + // Generate JWT for the applicant. No Expiry on JWT + const payload = { email: email, role: "applicant" }; + const token = jwt.sign(payload, jwt_secret, { noTimestamp: true }); + res.json({ + token: token, + username: name, + email: email }); + console.log(`Applicant with email: ${email} logged in`); + + // Clean up the session after use + req.session.destroy(() => {}); } catch (error) { console.error("Error during callback:", error); res.status(500).json({ error: "Login failed" }); } }); -export default router; \ No newline at end of file +export default router; diff --git a/server/src/routes/job.ts b/server/src/routes/job.ts index 3864f56..969edc6 100644 --- a/server/src/routes/job.ts +++ b/server/src/routes/job.ts @@ -1,7 +1,7 @@ import { Router, Request, Response } from "express"; import { PrismaClient } from "@prisma/client"; -import { createJobSchema, editJobSchema } from "../validators/job"; -import { logRequest } from "../utils/logUtil"; +import { createJobSchema, editJobSchema } from "../validators/job.js"; +import { logRequest } from "../utils/logUtil.js"; import * as yup from 'yup'; const prisma = new PrismaClient(); diff --git a/server/src/utils/authUtil.ts b/server/src/utils/authUtil.ts new file mode 100644 index 0000000..20235f1 --- /dev/null +++ b/server/src/utils/authUtil.ts @@ -0,0 +1,70 @@ +import jwt, { JwtPayload } from "jsonwebtoken"; +import assert from "assert"; +import jwkToPem from "jwk-to-pem"; + +// Function to decrypt server's JWT +export function decrypt_jwt(token: string) { + assert(process.env.JWT_SECRET, "Environment variable JWT_SECRET must be defined."); + const jwt_secret: string = process.env.JWT_SECRET; // JWT Secret + const decoded = jwt.verify(token, jwt_secret) as { + email?: string; + role?: string; + }; + + if (!decoded.email || !decoded.role) { + throw new Error("JWT supplied has missing fields") + } + return decoded.email, decoded.role +} + +// Function to verify and decode Google ID Token +export async function decodeGoogleIdToken(idToken: string, clientId: string) { + // Step 1: Fetch Google's public keys + const keys = await getGooglePublicKeys(); + + // Step 2: Decode the token header to get 'kid' + const decodedHeader = jwt.decode(idToken, { complete: true }); + if ( + !decodedHeader || + typeof decodedHeader === "string" || + !decodedHeader.header.kid + ) { + throw new Error("Invalid token header"); + } + const kid = decodedHeader.header.kid; + + // Step 3: Find the corresponding public key + const publicKey = getKeyFromJWKS(keys, kid); + if (!publicKey) { + throw new Error("Unable to find matching public key"); + } + + // Step 4: Verify the token + const payload = jwt.verify(idToken, publicKey, { + algorithms: ["RS256"], + audience: clientId, + issuer: "https://accounts.google.com", + }) as JwtPayload; + return payload; +} + +// Helper Function + +// Function to fetch Google JWKS using fetch +async function getGooglePublicKeys(): Promise { + const GOOGLE_JWKS_URL = "https://www.googleapis.com/oauth2/v3/certs"; + const response = await fetch(GOOGLE_JWKS_URL); + if (!response.ok) { + throw new Error(`Failed to fetch JWKS: ${response.statusText}`); + } + const { keys } = await response.json(); + return keys; +} + +// Function to find the public key that matches the 'kid' +function getKeyFromJWKS(keys: any[], kid: string): string | undefined { + const key = keys.find((k) => k.kid === kid); + if (!key) throw new Error("No matching key found"); + return jwkToPem(key); // Convert the JWKS key to PEM format +} + diff --git a/server/tsconfig.json b/server/tsconfig.json index c38dab0..9a5a473 100644 --- a/server/tsconfig.json +++ b/server/tsconfig.json @@ -1,9 +1,8 @@ { "compilerOptions": { "target": "es6", - // "module": "commonjs", - "module": "ESNext", // Use modern ES module syntax - "moduleResolution": "Node", // Node.js resolution for imports + "module": "NodeNext", + "moduleResolution": "NodeNext", "outDir": "./dist", "strict": true, "esModuleInterop": true, From 3349eb797ec32b22cfe1369d1cefd3fbedbfec1e Mon Sep 17 00:00:00 2001 From: NgZiXin Date: Tue, 17 Dec 2024 16:51:07 +0800 Subject: [PATCH 13/24] Clean up --- server/package-lock.json | 11 ----------- server/package.json | 1 - server/src/utils/authUtil.ts | 8 ++++---- 3 files changed, 4 insertions(+), 16 deletions(-) diff --git a/server/package-lock.json b/server/package-lock.json index 03b6bd7..2c5342c 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -18,7 +18,6 @@ "yup": "^1.4.0" }, "devDependencies": { - "@types/bcrypt": "^5.0.2", "@types/express": "^5.0.0", "@types/express-session": "^1.18.1", "@types/jsonwebtoken": "^9.0.7", @@ -186,16 +185,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/bcrypt": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/@types/bcrypt/-/bcrypt-5.0.2.tgz", - "integrity": "sha512-6atioO8Y75fNcbmj0G7UjI9lXN2pQ/IGJ2FWT4a/btd0Lk9lQalHLKhkgKVZ3r+spnmWUKfbMi1GEe9wyHQfNQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@types/body-parser": { "version": "1.19.5", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", diff --git a/server/package.json b/server/package.json index 8b08106..687e21b 100644 --- a/server/package.json +++ b/server/package.json @@ -17,7 +17,6 @@ "license": "ISC", "description": "", "devDependencies": { - "@types/bcrypt": "^5.0.2", "@types/express": "^5.0.0", "@types/express-session": "^1.18.1", "@types/jsonwebtoken": "^9.0.7", diff --git a/server/src/utils/authUtil.ts b/server/src/utils/authUtil.ts index 20235f1..e0d5938 100644 --- a/server/src/utils/authUtil.ts +++ b/server/src/utils/authUtil.ts @@ -19,10 +19,10 @@ export function decrypt_jwt(token: string) { // Function to verify and decode Google ID Token export async function decodeGoogleIdToken(idToken: string, clientId: string) { - // Step 1: Fetch Google's public keys + // Fetch Google's public keys const keys = await getGooglePublicKeys(); - // Step 2: Decode the token header to get 'kid' + // Decode the token header to get 'kid' const decodedHeader = jwt.decode(idToken, { complete: true }); if ( !decodedHeader || @@ -33,13 +33,13 @@ export async function decodeGoogleIdToken(idToken: string, clientId: string) { } const kid = decodedHeader.header.kid; - // Step 3: Find the corresponding public key + // Find the corresponding public key const publicKey = getKeyFromJWKS(keys, kid); if (!publicKey) { throw new Error("Unable to find matching public key"); } - // Step 4: Verify the token + // Verify the token const payload = jwt.verify(idToken, publicKey, { algorithms: ["RS256"], audience: clientId, From 5bbb7a08b1c7c53abad97cd03689d350bbbad4ea Mon Sep 17 00:00:00 2001 From: NgZiXin Date: Thu, 19 Dec 2024 13:06:10 +0800 Subject: [PATCH 14/24] Refactor for extensibility --- server/src/auth/callbackHandler.ts | 87 ++++++++++++ server/src/auth/clients/googleClient.ts | 91 +++++++++++++ server/src/auth/loginHandler.ts | 30 +++++ server/src/middleware/sessionMiddleware.ts | 24 ++++ server/src/routes/auth.ts | 146 +-------------------- server/src/utils/authUtil.ts | 53 +------- 6 files changed, 239 insertions(+), 192 deletions(-) create mode 100644 server/src/auth/callbackHandler.ts create mode 100644 server/src/auth/clients/googleClient.ts create mode 100644 server/src/auth/loginHandler.ts create mode 100644 server/src/middleware/sessionMiddleware.ts diff --git a/server/src/auth/callbackHandler.ts b/server/src/auth/callbackHandler.ts new file mode 100644 index 0000000..979a260 --- /dev/null +++ b/server/src/auth/callbackHandler.ts @@ -0,0 +1,87 @@ +import { Request, Response } from "express"; +import * as client from "openid-client"; +import jwt, { JwtPayload } from "jsonwebtoken"; +import assert from "assert"; +import { PrismaClient } from "@prisma/client"; + +assert( + process.env.JWT_SECRET, + "Environment variable JWT_SECRET must be defined." +); + +const prisma = new PrismaClient(); +const jwt_secret: string = process.env.JWT_SECRET; // JWT Secret + +export async function callbackHandler( + req: Request, + res: Response, + config: client.Configuration, + decodeFunction: (id_token: string) => Promise +) { + const currentUrl = new URL( + `${req.protocol}://${req.get("host")}${req.originalUrl}` + ); + + // Exchange the authorization code for tokens + if (!req.session.code_verifier || !req.session.state) { + res + .status(400) + .json({ error: "Session is invalid or missing required attributes" }); + return; + } + const tokenResponse = await client.authorizationCodeGrant( + config, + currentUrl, + { + pkceCodeVerifier: req.session.code_verifier, // Retrieve from session + expectedState: req.session.state, + } + ); + console.log("Token Received"); + + // Decode the ID token to get the user email and name + const id_token = tokenResponse.id_token; + if (!id_token) { + throw new Error("OAuth 2.0/OIDC token response does not contain id token"); + } + const decoded_id_token = (await decodeFunction(id_token)) as { + email?: string; + name?: string; + }; + const email = decoded_id_token?.email; + if (!email) { + throw new Error("Failed to extract email from ID token"); + } + const name = decoded_id_token?.name; + if (!name) { + throw new Error("Failed to extract name from ID token"); + } + + // Add applicant to database if needed + const existingApplicant = await prisma.applicant.findUnique({ + where: { email: email }, // Query based on email + }); + if (!existingApplicant) { + console.log(`Creating applicant with email: ${email}`); + await prisma.applicant.create({ + data: { + username: name, + email: email, + }, + }); + res.status(201); + } + + // Generate JWT for the applicant. No Expiry on JWT + const payload = { email: email, role: "applicant" }; + const token = jwt.sign(payload, jwt_secret, { noTimestamp: true }); + res.json({ + token: token, + username: name, + email: email, + }); + console.log(`Applicant with email: ${email} logged in`); + + // Clean up the session after use + req.session.destroy(() => {}); +} diff --git a/server/src/auth/clients/googleClient.ts b/server/src/auth/clients/googleClient.ts new file mode 100644 index 0000000..be863b4 --- /dev/null +++ b/server/src/auth/clients/googleClient.ts @@ -0,0 +1,91 @@ +import { Request, Response } from "express"; +import * as client from "openid-client"; +import assert from "assert"; +import jwt, { JwtPayload } from "jsonwebtoken"; +import jwkToPem from "jwk-to-pem"; +import { loginHandler } from "../loginHandler.js"; +import { callbackHandler } from "../callbackHandler.js"; + +// Assertions for type safety +assert( + process.env.GOOGLE_URL, + "Environment variable GOOGLE_URL must be defined." +); +assert( + process.env.GOOGLE_CLIENT_ID, + "Environment variable GOOGLE_CLIENT_ID must be defined." +); +assert( + process.env.GOOGLE_CLIENT_SECRET, + "Environment variable GOOGLE_CLIENT_SECRET must be defined." +); + +// Google Login Configuration +const server: URL = new URL(process.env.GOOGLE_URL); // Authorization Server's Issuer Identifier +const client_id: string = process.env.GOOGLE_CLIENT_ID; // Client identifier at the Authorization Server +const client_secret: string = process.env.GOOGLE_CLIENT_SECRET; // Client Secret +let config!: client.Configuration; + +(async () => { + config = await client.discovery(server, client_id, client_secret); + console.log("Google configuration discovered"); +})(); + +export async function googleLoginHandler( + req: Request, + res: Response, + redirect_uri: string +) { + try { + loginHandler(req, res, redirect_uri, config); + console.log("Redirected User to Google Sign-In Page"); + } catch (error) { + console.error("Error during Google login:", error); + res.status(500).json({ error: "Failed to initiate login process" }); + } +} + +export async function googleCallBackHandler(req: Request, res: Response) { + try { + callbackHandler(req, res, config, decodeGoogleIdToken); + console.log("Callback succesful"); + } catch (error) { + console.error("Error during callback from google:", error); + res.status(500).json({ error: "Login failed" }); + } +} + +// Function to verify and decode Google ID Token +async function decodeGoogleIdToken(idToken: string): Promise { + // Fetch Google's public keys + const GOOGLE_JWKS_URL = "https://www.googleapis.com/oauth2/v3/certs"; + const response = await fetch(GOOGLE_JWKS_URL); + if (!response.ok) { + throw new Error(`Failed to fetch JWKS: ${response.statusText}`); + } + const { keys } = await response.json(); + + // Decode the token header to get 'kid' + const decodedHeader = jwt.decode(idToken, { complete: true }); + if ( + !decodedHeader || + typeof decodedHeader === "string" || + !decodedHeader.header.kid + ) { + throw new Error("Invalid token header"); + } + const kid = decodedHeader.header.kid; + + // Find the corresponding public key + const publicKey_JWS = keys.find((k: any) => k.kid === kid); + const publicKey = jwkToPem(publicKey_JWS); // Convert the JWKS key to PEM format + if (!publicKey) throw new Error("No matching public key found"); + + // Verify the token + const payload = jwt.verify(idToken, publicKey, { + algorithms: ["RS256"], + audience: client_id, + issuer: "https://accounts.google.com", + }) as JwtPayload; + return payload; +} diff --git a/server/src/auth/loginHandler.ts b/server/src/auth/loginHandler.ts new file mode 100644 index 0000000..9699f66 --- /dev/null +++ b/server/src/auth/loginHandler.ts @@ -0,0 +1,30 @@ +import { Request, Response } from "express"; +import * as client from "openid-client"; + +export async function loginHandler( + req: Request, + res: Response, + redirect_uri: string, + config: client.Configuration +) { + const code_verifier = client.randomPKCECodeVerifier(); + const state = client.randomState(); + + // Store code_verifier and state in the session + req.session.code_verifier = code_verifier; + req.session.state = state; + + // Create parameters + const code_challenge = await client.calculatePKCECodeChallenge(code_verifier); + const parameters: Record = { + redirect_uri: redirect_uri, + scope: "email profile", + code_challenge: code_challenge, + code_challenge_method: "S256", + state: state, + }; + + // Redirect the user to redirectTo.href + const redirectTo: URL = client.buildAuthorizationUrl(config, parameters); + res.redirect(redirectTo.href); +} diff --git a/server/src/middleware/sessionMiddleware.ts b/server/src/middleware/sessionMiddleware.ts new file mode 100644 index 0000000..25e7118 --- /dev/null +++ b/server/src/middleware/sessionMiddleware.ts @@ -0,0 +1,24 @@ +import session from 'express-session'; +import assert from "assert"; + +assert(process.env.SESSION_SECRET, "Environment variable SESSION_SECRET must be defined."); + +const session_secret: string = process.env.SESSION_SECRET; // SESSION Secret + +// Extend SessionData inline +declare module 'express-session' { + interface SessionData { + code_verifier?: string; + state?: string; + } +} + +// Create session +const sessionMiddleware = session({ + secret: session_secret, + resave: false, + saveUninitialized: false, + cookie: { secure: false }, // Set to true in production +}); + +export { sessionMiddleware }; \ No newline at end of file diff --git a/server/src/routes/auth.ts b/server/src/routes/auth.ts index 440951d..ec7ee02 100644 --- a/server/src/routes/auth.ts +++ b/server/src/routes/auth.ts @@ -1,150 +1,16 @@ import { Router, Request, Response } from "express"; -import { PrismaClient } from "@prisma/client"; -import * as client from "openid-client"; -import session from 'express-session'; -import jwt from "jsonwebtoken"; -import assert from "assert"; +import { sessionMiddleware } from "../middleware/sessionMiddleware.js"; import { logRequest } from "../utils/logUtil.js"; -import { decodeGoogleIdToken } from "../utils/authUtil.js"; - -// Assertions for type safety -assert(process.env.GOOGLE_URL, "Environment variable GOOGLE_URL must be defined."); -assert(process.env.GOOGLE_CLIENT_ID, "Environment variable GOOGLE_CLIENT_ID must be defined."); -assert(process.env.GOOGLE_CLIENT_SECRET, "Environment variable GOOGLE_CLIENT_SECRET must be defined."); -assert(process.env.JWT_SECRET, "Environment variable JWT_SECRET must be defined."); -assert(process.env.SESSION_SECRET, "Environment variable SESSION_SECRET must be defined."); +import { googleCallBackHandler, googleLoginHandler } from "../auth/clients/googleClient.js"; const router = Router(); -const prisma = new PrismaClient(); - -// Google Login Configuration -const server: URL = new URL(process.env.GOOGLE_URL); // Authorization Server's Issuer Identifier -const client_id: string = process.env.GOOGLE_CLIENT_ID; // Client identifier at the Authorization Server -const client_secret: string = process.env.GOOGLE_CLIENT_SECRET; // Client Secret -const jwt_secret: string = process.env.JWT_SECRET; // JWT Secret -const session_secret: string = process.env.SESSION_SECRET; // SESSION Secret - -let config!: client.Configuration; - -// Extend SessionData inline -declare module 'express-session' { - interface SessionData { - code_verifier?: string; - state?: string; - } -} -// Create session -const sessionMiddleware = session({ - secret: session_secret, - resave: false, - saveUninitialized: false, - cookie: { secure: false }, // Set to true in production -}); - -(async () => { - // Discover Google's OpenID configuration - config = await client.discovery(server, client_id, client_secret); - console.log("Google configuration discovered"); -})(); - -router.get("/login", sessionMiddleware, async (req: Request, res: Response) => { +router.get("/google/login", sessionMiddleware, async (req: Request, res: Response) => { logRequest(req); - const code_verifier = client.randomPKCECodeVerifier(); - const state = client.randomState(); - - // Store code_verifier and state in the session - req.session.code_verifier = code_verifier; - req.session.state = state; - - // Create parameters - const code_challenge = await client.calculatePKCECodeChallenge(code_verifier); - const parameters: Record = { - redirect_uri: `${req.protocol}://${req.get("host")}/auth/callback`, - scope: "email profile", - code_challenge: code_challenge, - code_challenge_method: "S256", - state: state, - }; - - // Redirect the user to redirectTo.href - const redirectTo: URL = client.buildAuthorizationUrl(config, parameters); - console.log("Redirecting User to Google Sign-In Page"); - res.redirect(redirectTo.href); + const redirect_uri = `${req.protocol}://${req.get("host")}/auth/google/callback`; + googleLoginHandler(req, res, redirect_uri); }); -router.get("/callback", sessionMiddleware, async (req: Request, res: Response) => { - try { - const currentUrl = new URL( - `${req.protocol}://${req.get("host")}${req.originalUrl}` - ); - - // Exchange the authorization code for tokens - if (!req.session.code_verifier || !req.session.state) { - res - .status(400) - .json({ error: "Session is invalid or missing required attributes" }); - return; - } - const tokenResponse = await client.authorizationCodeGrant( - config, - currentUrl, - { - pkceCodeVerifier: req.session.code_verifier, // Retrieve from session - expectedState: req.session.state, - } - ); - console.log("Token Received"); - - // Decode the ID token to get the user email and name - const id_token = tokenResponse.id_token; - if (!id_token) { - throw new Error("OAuth 2.0/OIDC token response does not contain id token") - } - const decoded_id_token = await decodeGoogleIdToken(id_token, client_id) as { - email?: string; - name?: string; - }; - const email = decoded_id_token?.email; - if (!email) { - throw new Error("Failed to extract email from ID token"); - } - const name = decoded_id_token?.name; - if (!name) { - throw new Error("Failed to extract name from ID token"); - } - - // Add applicant to database if needed - const existingApplicant = await prisma.applicant.findUnique({ - where: { email: email }, // Query based on email - }); - if (!existingApplicant) { - console.log(`Creating applicant with email: ${email}`); - await prisma.applicant.create({ - data: { - username: name, - email: email, - }, - }); - res.status(201); - } - - // Generate JWT for the applicant. No Expiry on JWT - const payload = { email: email, role: "applicant" }; - const token = jwt.sign(payload, jwt_secret, { noTimestamp: true }); - res.json({ - token: token, - username: name, - email: email - }); - console.log(`Applicant with email: ${email} logged in`); - - // Clean up the session after use - req.session.destroy(() => {}); - } catch (error) { - console.error("Error during callback:", error); - res.status(500).json({ error: "Login failed" }); - } -}); +router.get("/google/callback", sessionMiddleware, googleCallBackHandler); export default router; diff --git a/server/src/utils/authUtil.ts b/server/src/utils/authUtil.ts index e0d5938..8a06505 100644 --- a/server/src/utils/authUtil.ts +++ b/server/src/utils/authUtil.ts @@ -1,6 +1,5 @@ -import jwt, { JwtPayload } from "jsonwebtoken"; +import jwt from "jsonwebtoken"; import assert from "assert"; -import jwkToPem from "jwk-to-pem"; // Function to decrypt server's JWT export function decrypt_jwt(token: string) { @@ -17,54 +16,4 @@ export function decrypt_jwt(token: string) { return decoded.email, decoded.role } -// Function to verify and decode Google ID Token -export async function decodeGoogleIdToken(idToken: string, clientId: string) { - // Fetch Google's public keys - const keys = await getGooglePublicKeys(); - - // Decode the token header to get 'kid' - const decodedHeader = jwt.decode(idToken, { complete: true }); - if ( - !decodedHeader || - typeof decodedHeader === "string" || - !decodedHeader.header.kid - ) { - throw new Error("Invalid token header"); - } - const kid = decodedHeader.header.kid; - - // Find the corresponding public key - const publicKey = getKeyFromJWKS(keys, kid); - if (!publicKey) { - throw new Error("Unable to find matching public key"); - } - - // Verify the token - const payload = jwt.verify(idToken, publicKey, { - algorithms: ["RS256"], - audience: clientId, - issuer: "https://accounts.google.com", - }) as JwtPayload; - return payload; -} - -// Helper Function - -// Function to fetch Google JWKS using fetch -async function getGooglePublicKeys(): Promise { - const GOOGLE_JWKS_URL = "https://www.googleapis.com/oauth2/v3/certs"; - const response = await fetch(GOOGLE_JWKS_URL); - if (!response.ok) { - throw new Error(`Failed to fetch JWKS: ${response.statusText}`); - } - const { keys } = await response.json(); - return keys; -} - -// Function to find the public key that matches the 'kid' -function getKeyFromJWKS(keys: any[], kid: string): string | undefined { - const key = keys.find((k) => k.kid === kid); - if (!key) throw new Error("No matching key found"); - return jwkToPem(key); // Convert the JWKS key to PEM format -} From 433689a584e7e6082beb3f3893ace56c7ff7b17d Mon Sep 17 00:00:00 2001 From: NgZiXin Date: Tue, 24 Dec 2024 00:24:44 +0800 Subject: [PATCH 15/24] Add linkedin login feature --- server/src/auth/callbackHandler.ts | 27 ++++++----- server/src/auth/clients/googleClient.ts | 54 ++++----------------- server/src/auth/clients/linkedinClient.ts | 57 +++++++++++++++++++++++ server/src/auth/loginHandler.ts | 4 +- server/src/routes/auth.ts | 11 +++++ 5 files changed, 94 insertions(+), 59 deletions(-) create mode 100644 server/src/auth/clients/linkedinClient.ts diff --git a/server/src/auth/callbackHandler.ts b/server/src/auth/callbackHandler.ts index 979a260..33142c4 100644 --- a/server/src/auth/callbackHandler.ts +++ b/server/src/auth/callbackHandler.ts @@ -1,6 +1,6 @@ import { Request, Response } from "express"; import * as client from "openid-client"; -import jwt, { JwtPayload } from "jsonwebtoken"; +import jwt from "jsonwebtoken"; import assert from "assert"; import { PrismaClient } from "@prisma/client"; @@ -12,39 +12,44 @@ assert( const prisma = new PrismaClient(); const jwt_secret: string = process.env.JWT_SECRET; // JWT Secret -export async function callbackHandler( +export default async function callbackHandler( req: Request, res: Response, - config: client.Configuration, - decodeFunction: (id_token: string) => Promise + config: client.Configuration ) { const currentUrl = new URL( `${req.protocol}://${req.get("host")}${req.originalUrl}` ); - // Exchange the authorization code for tokens + // Exchange the authorization code for tokens if (!req.session.code_verifier || !req.session.state) { res .status(400) .json({ error: "Session is invalid or missing required attributes" }); return; } + + const checks: Record = { + expectedState: req.session.state, + }; + + if (config.serverMetadata().supportsPKCE()) { + checks.pkceCodeVerifier = req.session.code_verifier; // Retrieve from session + } + const tokenResponse = await client.authorizationCodeGrant( config, currentUrl, - { - pkceCodeVerifier: req.session.code_verifier, // Retrieve from session - expectedState: req.session.state, - } + checks ); - console.log("Token Received"); // Decode the ID token to get the user email and name const id_token = tokenResponse.id_token; if (!id_token) { throw new Error("OAuth 2.0/OIDC token response does not contain id token"); } - const decoded_id_token = (await decodeFunction(id_token)) as { + + const decoded_id_token = jwt.decode(id_token) as { email?: string; name?: string; }; diff --git a/server/src/auth/clients/googleClient.ts b/server/src/auth/clients/googleClient.ts index be863b4..d29769e 100644 --- a/server/src/auth/clients/googleClient.ts +++ b/server/src/auth/clients/googleClient.ts @@ -1,16 +1,13 @@ import { Request, Response } from "express"; import * as client from "openid-client"; import assert from "assert"; -import jwt, { JwtPayload } from "jsonwebtoken"; -import jwkToPem from "jwk-to-pem"; -import { loginHandler } from "../loginHandler.js"; -import { callbackHandler } from "../callbackHandler.js"; +import loginHandler from "../loginHandler.js"; +import callbackHandler from "../callbackHandler.js"; + +// High-level declarations +const issuer: string = "https://accounts.google.com"; // Assertions for type safety -assert( - process.env.GOOGLE_URL, - "Environment variable GOOGLE_URL must be defined." -); assert( process.env.GOOGLE_CLIENT_ID, "Environment variable GOOGLE_CLIENT_ID must be defined." @@ -21,7 +18,7 @@ assert( ); // Google Login Configuration -const server: URL = new URL(process.env.GOOGLE_URL); // Authorization Server's Issuer Identifier +const server: URL = new URL(issuer); // Authorization Server's Issuer Identifier const client_id: string = process.env.GOOGLE_CLIENT_ID; // Client identifier at the Authorization Server const client_secret: string = process.env.GOOGLE_CLIENT_SECRET; // Client Secret let config!: client.Configuration; @@ -37,7 +34,7 @@ export async function googleLoginHandler( redirect_uri: string ) { try { - loginHandler(req, res, redirect_uri, config); + await loginHandler(req, res, redirect_uri, config); console.log("Redirected User to Google Sign-In Page"); } catch (error) { console.error("Error during Google login:", error); @@ -47,45 +44,10 @@ export async function googleLoginHandler( export async function googleCallBackHandler(req: Request, res: Response) { try { - callbackHandler(req, res, config, decodeGoogleIdToken); + await callbackHandler(req, res, config); console.log("Callback succesful"); } catch (error) { console.error("Error during callback from google:", error); res.status(500).json({ error: "Login failed" }); } } - -// Function to verify and decode Google ID Token -async function decodeGoogleIdToken(idToken: string): Promise { - // Fetch Google's public keys - const GOOGLE_JWKS_URL = "https://www.googleapis.com/oauth2/v3/certs"; - const response = await fetch(GOOGLE_JWKS_URL); - if (!response.ok) { - throw new Error(`Failed to fetch JWKS: ${response.statusText}`); - } - const { keys } = await response.json(); - - // Decode the token header to get 'kid' - const decodedHeader = jwt.decode(idToken, { complete: true }); - if ( - !decodedHeader || - typeof decodedHeader === "string" || - !decodedHeader.header.kid - ) { - throw new Error("Invalid token header"); - } - const kid = decodedHeader.header.kid; - - // Find the corresponding public key - const publicKey_JWS = keys.find((k: any) => k.kid === kid); - const publicKey = jwkToPem(publicKey_JWS); // Convert the JWKS key to PEM format - if (!publicKey) throw new Error("No matching public key found"); - - // Verify the token - const payload = jwt.verify(idToken, publicKey, { - algorithms: ["RS256"], - audience: client_id, - issuer: "https://accounts.google.com", - }) as JwtPayload; - return payload; -} diff --git a/server/src/auth/clients/linkedinClient.ts b/server/src/auth/clients/linkedinClient.ts new file mode 100644 index 0000000..d44371d --- /dev/null +++ b/server/src/auth/clients/linkedinClient.ts @@ -0,0 +1,57 @@ +import { Request, Response } from "express"; +import * as client from "openid-client"; +import assert from "assert"; +import loginHandler from "../loginHandler.js"; +import callbackHandler from "../callbackHandler.js"; + +// High-level declarations +const issuer: string = "https://www.linkedin.com/oauth"; + +// Assertions for type safety +assert( + process.env.LINKEDIN_CLIENT_ID, + "Environment variable LINKEDIN_CLIENT_ID must be defined." +); +assert( + process.env.LINKEDIN_CLIENT_SECRET, + "Environment variable LINKEDIN_CLIENT_SECRET must be defined." +); + +// LinkedIn Login Configuration +const server: URL = new URL(issuer); // Authorization Server's Issuer Identifier +const client_id: string = process.env.LINKEDIN_CLIENT_ID; // Client identifier at the Authorization Server +const client_secret: string = process.env.LINKEDIN_CLIENT_SECRET; // Client Secret +let config!: client.Configuration; + +(async () => { + try { + config = await client.discovery(server, client_id, client_secret); + console.log("LinkedIn configuration discovered"); + } catch (error) { + console.error("Failed to discover LinkedIn configuration:", error); + } +})(); + +export async function linkedinLoginHandler( + req: Request, + res: Response, + redirect_uri: string +) { + try { + await loginHandler(req, res, redirect_uri, config); + console.log("Redirected User to LinkedIn Sign-In Page"); + } catch (error) { + console.error("Error during LinkedIn login:", error); + res.status(500).json({ error: "Failed to initiate login process" }); + } +} + +export async function linkedinCallBackHandler(req: Request, res: Response) { + try { + await callbackHandler(req, res, config); + console.log("Callback succesful"); + } catch (error) { + console.error("Error during callback from linkedin:", error); + res.status(500).json({ error: "Login failed" }); + } +} diff --git a/server/src/auth/loginHandler.ts b/server/src/auth/loginHandler.ts index 9699f66..f6245bb 100644 --- a/server/src/auth/loginHandler.ts +++ b/server/src/auth/loginHandler.ts @@ -1,7 +1,7 @@ import { Request, Response } from "express"; import * as client from "openid-client"; -export async function loginHandler( +export default async function loginHandler( req: Request, res: Response, redirect_uri: string, @@ -18,7 +18,7 @@ export async function loginHandler( const code_challenge = await client.calculatePKCECodeChallenge(code_verifier); const parameters: Record = { redirect_uri: redirect_uri, - scope: "email profile", + scope: "openid email profile", code_challenge: code_challenge, code_challenge_method: "S256", state: state, diff --git a/server/src/routes/auth.ts b/server/src/routes/auth.ts index ec7ee02..dbc8f7b 100644 --- a/server/src/routes/auth.ts +++ b/server/src/routes/auth.ts @@ -2,9 +2,11 @@ import { Router, Request, Response } from "express"; import { sessionMiddleware } from "../middleware/sessionMiddleware.js"; import { logRequest } from "../utils/logUtil.js"; import { googleCallBackHandler, googleLoginHandler } from "../auth/clients/googleClient.js"; +import { linkedinCallBackHandler, linkedinLoginHandler } from "../auth/clients/linkedinClient.js"; const router = Router(); +// Google Login & Callback router.get("/google/login", sessionMiddleware, async (req: Request, res: Response) => { logRequest(req); const redirect_uri = `${req.protocol}://${req.get("host")}/auth/google/callback`; @@ -13,4 +15,13 @@ router.get("/google/login", sessionMiddleware, async (req: Request, res: Respons router.get("/google/callback", sessionMiddleware, googleCallBackHandler); +// Linkedin Login & Callback +router.get("/linkedin/login", sessionMiddleware, async (req: Request, res: Response) => { + logRequest(req); + const redirect_uri = `${req.protocol}://${req.get("host")}/auth/linkedin/callback`; + linkedinLoginHandler(req, res, redirect_uri); +}); + +router.get("/linkedin/callback", sessionMiddleware, linkedinCallBackHandler); + export default router; From a1afb67b9dd5d9863b461b370fd7834a57be9587 Mon Sep 17 00:00:00 2001 From: NgZiXin Date: Tue, 24 Dec 2024 00:59:19 +0800 Subject: [PATCH 16/24] Add env.md --- server/env.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 server/env.md diff --git a/server/env.md b/server/env.md new file mode 100644 index 0000000..f1642c0 --- /dev/null +++ b/server/env.md @@ -0,0 +1,24 @@ +# Example of environment file (For testing purpose, you guys can have my oauth id and secrets) + +# Server configuration +PORT=3000 +HOST=localhost + +# Server secret +SESSION_SECRET="sessionSecrettt" +JWT_SECRET="jwtSecrettt" + +# Database configuration (Configure with your own db) +DATABASE_URL="postgresql://postgres:somePassword@localhost:5432/fintech_test?schema=public" + +# Google oauth configuration +GOOGLE_CLIENT_ID="1001300138315-9uoebvefjghadqto1tvqa7agqv16gu2g.apps.googleusercontent.com" +GOOGLE_CLIENT_SECRET="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" + +# Linkedin oauth configuration +LINKEDIN_CLIENT_ID="86ubf590mi26zb" +LINKEDIN_CLIENT_SECRET="WPL_AP1.pLcDrMd0C8JyM71q.STlaMw==" + +# Github oauth configuration +GITHUB_CLIENT_ID="Ov23li5QHI82UQJLAX1H" +GITHUB_CLIENT_SECRET="4ae735b59aaa09a1b2badb698d156c24ca819596" From 05d39143e39f8dd92709d5d1f2e0a17a075f3223 Mon Sep 17 00:00:00 2001 From: Luoqi Date: Tue, 24 Dec 2024 13:21:18 +0800 Subject: [PATCH 17/24] add frontend files add applicant and admin to seed add positionsAvailable and requirements to job schema add data fetching to landing page add data fetching to positions page add form validation to position application form enable form submission for position application --- package-lock.json | 173 ++++++++++++- package.json | 5 +- server/package-lock.json | 45 +++- server/package.json | 4 + server/prisma/schema.prisma | 2 + server/prisma/seed.ts | 168 ++++++++++++- server/src/index.ts | 7 +- server/src/routes/applicant.ts | 8 +- server/src/routes/job.ts | 17 +- server/src/validators/job.ts | 2 + src/app/applicant/page.tsx | 2 +- src/app/confirmation/page.tsx | 14 +- src/app/layout.tsx | 6 +- src/app/page.tsx | 114 +++++---- src/app/positions/[id]/page.tsx | 255 +++++++++++++++++--- src/app/signin/page.tsx | 1 + src/app/signup/page.tsx | 2 + src/components/ReactQueryClientProvider.tsx | 16 ++ src/lib/types.ts | 15 ++ src/lib/validation/applicationSchema.ts | 30 +++ 20 files changed, 778 insertions(+), 108 deletions(-) create mode 100644 src/components/ReactQueryClientProvider.tsx create mode 100644 src/lib/types.ts create mode 100644 src/lib/validation/applicationSchema.ts diff --git a/package-lock.json b/package-lock.json index 9de2f67..9d1a5b2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,10 +8,13 @@ "name": "sd_careers_platform", "version": "0.1.0", "dependencies": { + "bcryptjs": "^2.4.3", "flowbite-react": "^0.10.2", "next": "14.2.15", "react": "^18", - "react-dom": "^18" + "react-dom": "^18", + "react-query": "^3.39.3", + "yup": "^1.6.1" }, "devDependencies": { "@eslint/js": "^9.16.0", @@ -43,6 +46,17 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@babel/runtime": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.0.tgz", + "integrity": "sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", @@ -1229,6 +1243,19 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "license": "MIT" }, + "node_modules/bcryptjs": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz", + "integrity": "sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ==" + }, + "node_modules/big-integer": { + "version": "1.6.52", + "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.52.tgz", + "integrity": "sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==", + "engines": { + "node": ">=0.6" + } + }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", @@ -1245,7 +1272,6 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", @@ -1264,6 +1290,21 @@ "node": ">=8" } }, + "node_modules/broadcast-channel": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/broadcast-channel/-/broadcast-channel-3.7.0.tgz", + "integrity": "sha512-cIAKJXAxGJceNZGTZSBzMxzyOn72cVgPnKx4dc6LRjQgbaJUQqhy5rzL3zbMxkMWsGKkv2hSFkPRMEXfoMZ2Mg==", + "dependencies": { + "@babel/runtime": "^7.7.2", + "detect-node": "^2.1.0", + "js-sha3": "0.8.0", + "microseconds": "0.2.0", + "nano-time": "1.0.0", + "oblivious-set": "1.0.0", + "rimraf": "3.0.2", + "unload": "2.2.0" + } + }, "node_modules/busboy": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", @@ -1430,7 +1471,6 @@ "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true, "license": "MIT" }, "node_modules/cross-spawn": { @@ -1642,6 +1682,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/detect-node": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", + "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==" + }, "node_modules/didyoumean": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", @@ -2581,7 +2626,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true, "license": "ISC" }, "node_modules/fsevents": { @@ -2930,7 +2974,6 @@ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", - "dev": true, "license": "ISC", "dependencies": { "once": "^1.3.0", @@ -2941,7 +2984,6 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true, "license": "ISC" }, "node_modules/internal-slot": { @@ -3441,6 +3483,11 @@ "jiti": "bin/jiti.js" } }, + "node_modules/js-sha3": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz", + "integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==" + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -3610,6 +3657,15 @@ "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", "license": "ISC" }, + "node_modules/match-sorter": { + "version": "6.3.4", + "resolved": "https://registry.npmjs.org/match-sorter/-/match-sorter-6.3.4.tgz", + "integrity": "sha512-jfZW7cWS5y/1xswZo8VBOdudUiSd9nifYRWphc9M5D/ee4w4AoXLgBEdRbgVaxbMuagBPeUC5y2Hi8DO6o9aDg==", + "dependencies": { + "@babel/runtime": "^7.23.8", + "remove-accents": "0.5.0" + } + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -3632,6 +3688,11 @@ "node": ">=8.6" } }, + "node_modules/microseconds": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/microseconds/-/microseconds-0.2.0.tgz", + "integrity": "sha512-n7DHHMjR1avBbSpsTBj6fmMGh2AGrifVV4e+WYc3Q9lO+xnSZ3NyhcBND3vzzatt05LFhoKFRxrIyklmLlUtyA==" + }, "node_modules/mini-svg-data-uri": { "version": "1.4.4", "resolved": "https://registry.npmjs.org/mini-svg-data-uri/-/mini-svg-data-uri-1.4.4.tgz", @@ -3645,7 +3706,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" @@ -3691,6 +3751,14 @@ "thenify-all": "^1.0.0" } }, + "node_modules/nano-time": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/nano-time/-/nano-time-1.0.0.tgz", + "integrity": "sha512-flnngywOoQ0lLQOTRNexn2gGSNuM9bKj9RZAWSzhQ+UJYaAFG9bac4DW9VHjUAzrOaIcajHybCTHe/bkvozQqA==", + "dependencies": { + "big-integer": "^1.6.16" + } + }, "node_modules/nanoid": { "version": "3.3.7", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", @@ -3947,11 +4015,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/oblivious-set": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/oblivious-set/-/oblivious-set-1.0.0.tgz", + "integrity": "sha512-z+pI07qxo4c2CulUHCDf9lcqDlMSo72N/4rLUpRXf6fu+q8vjt8y0xS+Tlf8NTJDdTXHbdeO1n3MlbctwEoXZw==" + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, "license": "ISC", "dependencies": { "wrappy": "1" @@ -4034,7 +4106,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -4294,6 +4365,11 @@ "react-is": "^16.13.1" } }, + "node_modules/property-expr": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/property-expr/-/property-expr-2.0.6.tgz", + "integrity": "sha512-SVtmxhRE/CGkn3eZY1T6pC8Nln6Fr/lu1mKSgRud0eC73whjGfoAogbn78LkD8aFL0zz3bAFerKSnOl7NlErBA==" + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -4365,6 +4441,31 @@ "dev": true, "license": "MIT" }, + "node_modules/react-query": { + "version": "3.39.3", + "resolved": "https://registry.npmjs.org/react-query/-/react-query-3.39.3.tgz", + "integrity": "sha512-nLfLz7GiohKTJDuT4us4X3h/8unOh+00MLb2yJoGTPjxKs2bc1iDhkNx2bd5MKklXnOD3NrVZ+J2UXujA5In4g==", + "dependencies": { + "@babel/runtime": "^7.5.5", + "broadcast-channel": "^3.4.1", + "match-sorter": "^6.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + } + } + }, "node_modules/read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", @@ -4408,6 +4509,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" + }, "node_modules/regexp.prototype.flags": { "version": "1.5.3", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.3.tgz", @@ -4427,6 +4533,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/remove-accents": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/remove-accents/-/remove-accents-0.5.0.tgz", + "integrity": "sha512-8g3/Otx1eJaVD12e31UbJj1YzdtVvzH85HV7t+9MJYk/u3XmkOUJ5Ys9wQrf9PCPK8+xn4ymzqYCiZl6QWKn+A==" + }, "node_modules/resolve": { "version": "1.22.8", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", @@ -4479,7 +4590,6 @@ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", "deprecated": "Rimraf versions prior to v4 are no longer supported", - "dev": true, "license": "ISC", "dependencies": { "glob": "^7.1.3" @@ -4496,7 +4606,6 @@ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", "deprecated": "Glob versions prior to v9 are no longer supported", - "dev": true, "license": "ISC", "dependencies": { "fs.realpath": "^1.0.0", @@ -5090,6 +5199,11 @@ "node": ">=0.8" } }, + "node_modules/tiny-case": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-case/-/tiny-case-1.0.3.tgz", + "integrity": "sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q==" + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -5102,6 +5216,11 @@ "node": ">=8.0" } }, + "node_modules/toposort": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz", + "integrity": "sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg==" + }, "node_modules/ts-api-utils": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", @@ -5307,6 +5426,15 @@ "dev": true, "license": "MIT" }, + "node_modules/unload": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/unload/-/unload-2.2.0.tgz", + "integrity": "sha512-B60uB5TNBLtN6/LsgAf3udH9saB5p7gqJwcFfbOEZ8BcBHnGwCf6G/TGiEqkRAxX7zAFIUtzdrXQSdL3Q/wqNA==", + "dependencies": { + "@babel/runtime": "^7.6.2", + "detect-node": "^2.0.4" + } + }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -5529,7 +5657,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true, "license": "ISC" }, "node_modules/yaml": { @@ -5556,6 +5683,28 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/yup": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/yup/-/yup-1.6.1.tgz", + "integrity": "sha512-JED8pB50qbA4FOkDol0bYF/p60qSEDQqBD0/qeIrUCG1KbPBIQ776fCUNb9ldbPcSTxA69g/47XTo4TqWiuXOA==", + "dependencies": { + "property-expr": "^2.0.5", + "tiny-case": "^1.0.3", + "toposort": "^2.0.2", + "type-fest": "^2.19.0" + } + }, + "node_modules/yup/node_modules/type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } } } } diff --git a/package.json b/package.json index 9c242be..d5dc14c 100644 --- a/package.json +++ b/package.json @@ -9,10 +9,13 @@ "lint": "next lint" }, "dependencies": { + "bcryptjs": "^2.4.3", "flowbite-react": "^0.10.2", "next": "14.2.15", "react": "^18", - "react-dom": "^18" + "react-dom": "^18", + "react-query": "^3.39.3", + "yup": "^1.6.1" }, "devDependencies": { "@eslint/js": "^9.16.0", diff --git a/server/package-lock.json b/server/package-lock.json index 814abb7..12d1909 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -10,10 +10,14 @@ "license": "ISC", "dependencies": { "@prisma/client": "^5.21.1", + "bcryptjs": "^2.4.3", + "cors": "^2.8.5", "dotenv": "^16.4.5", "yup": "^1.4.0" }, "devDependencies": { + "@types/bcryptjs": "^2.4.6", + "@types/cors": "^2.8.17", "@types/express": "^5.0.0", "@types/node": "^22.8.1", "express": "^4.21.1", @@ -178,6 +182,12 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/bcryptjs": { + "version": "2.4.6", + "resolved": "https://registry.npmjs.org/@types/bcryptjs/-/bcryptjs-2.4.6.tgz", + "integrity": "sha512-9xlo6R2qDs5uixm0bcIqCeMCE6HiQsIyel9KQySStiyqNl2tnj2mP3DX1Nf56MD6KMenNNlBBsy3LJ7gUEQPXQ==", + "dev": true + }, "node_modules/@types/body-parser": { "version": "1.19.5", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", @@ -199,6 +209,15 @@ "@types/node": "*" } }, + "node_modules/@types/cors": { + "version": "2.8.17", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz", + "integrity": "sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/express": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.0.tgz", @@ -373,6 +392,11 @@ "dev": true, "license": "MIT" }, + "node_modules/bcryptjs": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz", + "integrity": "sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ==" + }, "node_modules/body-parser": { "version": "1.20.3", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", @@ -498,6 +522,18 @@ "dev": true, "license": "MIT" }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/create-require": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", @@ -1101,6 +1137,14 @@ "node": ">= 0.6" } }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/object-inspect": { "version": "1.13.2", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", @@ -1704,7 +1748,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.8" diff --git a/server/package.json b/server/package.json index 2be5905..b531223 100644 --- a/server/package.json +++ b/server/package.json @@ -17,6 +17,8 @@ "license": "ISC", "description": "", "devDependencies": { + "@types/bcryptjs": "^2.4.6", + "@types/cors": "^2.8.17", "@types/express": "^5.0.0", "@types/node": "^22.8.1", "express": "^4.21.1", @@ -27,6 +29,8 @@ }, "dependencies": { "@prisma/client": "^5.21.1", + "bcryptjs": "^2.4.3", + "cors": "^2.8.5", "dotenv": "^16.4.5", "yup": "^1.4.0" } diff --git a/server/prisma/schema.prisma b/server/prisma/schema.prisma index d247e32..741eba8 100644 --- a/server/prisma/schema.prisma +++ b/server/prisma/schema.prisma @@ -42,6 +42,8 @@ model Job { description String semester String deadline DateTime + positionsAvailable Int + requirements String status JobStatus created_by Int diff --git a/server/prisma/seed.ts b/server/prisma/seed.ts index c30e00f..441f586 100644 --- a/server/prisma/seed.ts +++ b/server/prisma/seed.ts @@ -1,4 +1,4 @@ -import { PrismaClient } from '@prisma/client'; +import { PrismaClient, JobStatus } from '@prisma/client'; const prisma = new PrismaClient(); @@ -38,8 +38,174 @@ async function populate_department() { console.log({ ml, sd, internal, quant }); } +async function populate_admin() { + const admin = await prisma.admin.upsert({ + where: { admin_id: 1 }, + update: {}, + create: { + username: 'admin', + email: 'admin@example.com', + password: 'saltandhash', + } + }); + console.log({ admin }); +} + +async function populate_applicant() { + const applicant = await prisma.applicant.upsert({ + where: { applicant_id: 1 }, + update: {}, + create: { + username: 'joe', + email: 'joe@gmail.com', + password: 'saltandhashbrowns', + } + }); + console.log({ applicant }); +} + +async function populate_jobs() { + + const jobs = [ + { + title: "UI/UX Designer", + department_id: 2, + semester: "Semester 1", + positionsAvailable: 5, + description: "As a UIUX designer, you will learn from and work closely with a team of designers, engineers, and design lead to create a fit-for-purpose, convenient, and engaging user interface for applications NUS Fintech Society would build.", + requirements: "You love tech and love to design!", + deadline: new Date("2024-12-31"), + status: JobStatus.open, + created_by: 1, + }, + { + title: "Software Engineer", + department_id: 2, + semester: "Semester 1", + positionsAvailable: 10, + description: "As a software engineer, you will be involved in the entire software development lifecycle, from requirements gathering to deployment and maintenance. You will work with a team of talented engineers to build innovative solutions for NUS Fintech Society.", + requirements: "Strong programming skills in at least one language (e.g., Python, Java, C++)", + deadline: new Date("2024-12-31"), + status: JobStatus.open, + created_by: 1, + }, + { + title: "Data Scientist", + department_id: 1, + semester: "Semester 2", + positionsAvailable: 4, + description: "As a data scientist, you will leverage your data analysis and machine learning skills to extract valuable insights from large datasets. You will work on projects such as fraud detection, risk assessment, and algorithmic trading.", + requirements: "Experience with data analysis tools (e.g., Python, R, SQL) and machine learning frameworks (e.g., TensorFlow, PyTorch)", + deadline: new Date("2024-12-31"), + status: JobStatus.open, + created_by: 1, + }, + { + title: "Financial Analyst", + department_id: 4, + semester: "Semester 2", + positionsAvailable: 4, + description: "As a financial analyst, you will analyze financial data, prepare financial reports, and provide insights to support decision-making. You will work on projects such as valuation, investment analysis, and portfolio management.", + requirements: "Strong understanding of financial concepts and experience with financial modeling tools (e.g., Excel, Python)", + deadline: new Date("2024-12-31"), + status: JobStatus.open, + created_by: 1, + }, + { + title: "Marketing Specialist", + department_id: 3, + semester: "Semester 1", + positionsAvailable: 4, + description: "As a marketing specialist, you will develop and implement marketing strategies to promote NUS Fintech Society and its initiatives. You will work on projects such as social media campaigns, content creation, and event planning.", + requirements: "Strong communication and interpersonal skills, experience with social media and digital marketing tools", + deadline: new Date("2024-12-31"), + status: JobStatus.open, + created_by: 1, + }, + { + title: "Blockchain Developer", + department_id: 4, + semester: "Semester 2", + positionsAvailable: 2, + description: "As a blockchain developer, you will work on developing and deploying blockchain-based applications. You will gain hands-on experience with blockchain technologies such as Ethereum, Hyperledger, and Corda.", + requirements: "Strong programming skills in Solidity, Python, or Java, understanding of blockchain concepts and protocols", + deadline: new Date("2024-12-31"), + status: JobStatus.open, + created_by: 1, + }, + { + title: "Cybersecurity Analyst", + department_id: 1, + semester: "Semester 1", + positionsAvailable: 2, + description: "As a cybersecurity analyst, you will identify, assess, and mitigate cybersecurity risks. You will work on projects such as vulnerability assessments, penetration testing, and incident response.", + requirements: "Understanding of cybersecurity principles, experience with security tools (e.g., Kali Linux, Metasploit), knowledge of networking concepts", + deadline: new Date("2024-12-31"), + status: JobStatus.open, + created_by: 1, + }, + { + title: "Quantitative Analyst", + department_id: 4, + semester: "Semester 2", + positionsAvailable: 2, + description: "As a quantitative analyst, you will develop quantitative models to analyze financial markets and make investment decisions. You will work on projects such as algorithmic trading, risk modeling, and portfolio optimization.", + requirements: "Strong mathematical and statistical skills, programming skills in Python or R, knowledge of financial modeling techniques", + deadline: new Date("2024-12-31"), + status: JobStatus.open, + created_by: 1, + }, + { + title: "Product Manager", + department_id: 2, + semester: "Semester 1", + positionsAvailable: 2, + description: "As a product manager, you will define product strategy, manage product development, and launch new products. You will work closely with engineering, design, and marketing teams to bring products to market.", + requirements: "Strong product sense, excellent communication and leadership skills, experience with product management methodologies (e.g., Agile, Scrum)", + deadline: new Date("2024-12-31"), + status: JobStatus.open, + created_by: 1, + }, + { + title: "User Experience Researcher", + department_id: 2, + semester: "Semester 1", + positionsAvailable: 2, + description: "As a user experience researcher, you will conduct user research to understand user needs and behaviors. You will use qualitative and quantitative research methods to inform design decisions.", + requirements: "Strong understanding of user research methodologies, experience with user testing and surveys, excellent communication and analytical skills", + deadline: new Date("2024-12-31"), + status: JobStatus.open, + created_by: 1, + }]; + + let idCounter = 1; + jobs.map(async (job) => { + const jobData = await prisma.job.upsert({ + where: { job_id: idCounter++ }, + update: {}, + create: { + title: job.title, + department_id: job.department_id, + semester: job.semester, + positionsAvailable: job.positionsAvailable, + description: job.description, + requirements: job.requirements, + deadline: job.deadline, + status: job.status, + created_by: job.created_by, + }, + }); + return jobData; + }); + + console.log({ jobs }); +} + async function main() { await populate_department(); + await populate_admin(); + await populate_jobs(); + await populate_applicant(); } main() diff --git a/server/src/index.ts b/server/src/index.ts index 1444719..3acdc35 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -1,9 +1,10 @@ -import express, { Request, Response} from 'express'; +import express, { NextFunction, Request, Response} from 'express'; import applicantRoutes from './routes/applicant'; import applicationRoutes from './routes/application' import adminRoutes from './routes/admin'; import jobRoutes from './routes/job'; import dotenv from 'dotenv'; +import cors from 'cors'; dotenv.config(); @@ -11,6 +12,7 @@ const app = express(); const PORT = parseInt(process.env.PORT || '3000', 10); const HOST = process.env.HOST || "localhost"; +app.use(cors()); app.use(express.json()); app.use('/applicant', applicantRoutes); app.use('/admin', adminRoutes); @@ -22,7 +24,8 @@ app.get('/', (req: Request, res: Response) => { }); // Add this error handling middleware -app.use((err: Error, req: Request, res: Response) => { +// eslint-disable-next-line @typescript-eslint/no-unused-vars +app.use((err: Error, req: Request, res: Response, next: NextFunction) => { console.error(err.stack); res.status(500).send('Something went wrong'); }); diff --git a/server/src/routes/applicant.ts b/server/src/routes/applicant.ts index 231dc44..f2caed6 100644 --- a/server/src/routes/applicant.ts +++ b/server/src/routes/applicant.ts @@ -3,6 +3,7 @@ import { PrismaClient } from "@prisma/client"; import { createApplicantSchema, editApplicantSchema } from "../validators/applicant"; import { logRequest } from "../utils/logUtil"; import * as yup from 'yup'; +import bcrypt from 'bcryptjs'; const prisma = new PrismaClient(); const router = Router(); @@ -17,9 +18,14 @@ router.post("/", async (req: Request, res: Response) => { stripUnknown: true, // Required to prevent mass assignment vulnerabilities }); + const hashedPassword = await bcrypt.hash(validatedData.password, 10); + console.log(`Creating applicant with email: ${validatedData.email}`); const applicant = await prisma.applicant.create({ - data: validatedData, + data: { + ...validatedData, + password: hashedPassword, + } }); console.log(`Applicant created with ID: ${applicant.applicant_id}`); diff --git a/server/src/routes/job.ts b/server/src/routes/job.ts index 3864f56..12f315f 100644 --- a/server/src/routes/job.ts +++ b/server/src/routes/job.ts @@ -44,7 +44,15 @@ router.get("/", async (_req: Request, res: Response) => { logRequest(_req); try { console.log("Fetching all jobs"); - const jobs = await prisma.job.findMany(); + const jobs = await prisma.job.findMany({ + include: { + department: { + select: { + department_name: true, + }, + }, + }, + }); res.json(jobs); } catch (error) { console.error("Error fetching jobs:", error); @@ -60,6 +68,13 @@ router.get("/:id", async (req: Request, res: Response) => { console.log(`Fetching job with ID: ${id}`); const job = await prisma.job.findUnique({ where: { job_id: Number(id) }, + include: { + department: { + select: { + department_name: true, + }, + }, + }, }); if (job) { console.log(`Job found: ${job.title}`); diff --git a/server/src/validators/job.ts b/server/src/validators/job.ts index 127122b..e1c1185 100644 --- a/server/src/validators/job.ts +++ b/server/src/validators/job.ts @@ -10,6 +10,8 @@ export const createJobSchema = yup.object().shape({ .required('Department ID is required'), description: yup.string().required('Description is required'), semester: yup.string().required('Semester is required'), + positionsAvailable: yup.number().required('Positions available is required'), + requirements: yup.string().required('Requirements is required'), deadline: yup.date().required('Deadline is required'), status: yup .mixed<'open' | 'closed'>() diff --git a/src/app/applicant/page.tsx b/src/app/applicant/page.tsx index d52208c..c83da9a 100644 --- a/src/app/applicant/page.tsx +++ b/src/app/applicant/page.tsx @@ -5,7 +5,7 @@ import Footer from "@/components/Footer" import Carousel from "@/components/Carousel"; import { JobDataType } from "@/lib/positions/job-data"; import Link from "next/link"; -import { useState } from "react"; +import React, { useState } from "react"; const inputData = { "ui-ux-designer": { diff --git a/src/app/confirmation/page.tsx b/src/app/confirmation/page.tsx index e5b3ce7..5b3d708 100644 --- a/src/app/confirmation/page.tsx +++ b/src/app/confirmation/page.tsx @@ -1,10 +1,16 @@ "use client"; import checkmark from "@/app/components/confirmation/checkmark.png"; - +import React from "react"; import Image from "next/image"; +import { useSearchParams } from "next/navigation"; + export default function ConfirmationPage() { + const searchParams = useSearchParams(); + const jobTitle = searchParams.get("jobTitle"); + const department = searchParams.get("department"); + const semester = searchParams.get("semester"); return (
@@ -35,9 +41,9 @@ export default function ConfirmationPage() { {/* Job Role Title */}
-

UI/UX Designer

-

Software Development - Semester 1

-

1-5 Positions Available

+

{jobTitle}

+

{department}

+

{semester}

diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 6d2cde4..749372e 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,8 +1,10 @@ import type { Metadata } from "next"; import localFont from "next/font/local"; import "./globals.css"; +import React from "react"; import {Montserrat, Roboto} from 'next/font/google'; +import ReactQueryClientProvider from "@/components/ReactQueryClientProvider"; const montserrat = Montserrat({ subsets: ['latin'], @@ -48,7 +50,9 @@ export default function RootLayout({ ${geistMono.variable} antialiased`} > - {children} + + {children} + ); diff --git a/src/app/page.tsx b/src/app/page.tsx index e964b99..092a83c 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -3,10 +3,12 @@ import NavBar from "@/components/Navbar"; import Footer from "@/components/Footer" import Link from "next/link"; -import { JobData, JobDataType } from "@/lib/positions/job-data"; -import { useState } from "react"; +import { JobData } from "@/lib/positions/job-data"; +import React, { useState } from "react"; import Image from "next/image"; import excoPic from "@/app/components/landing_page/Fintech_Exco.png"; +import { useQuery } from "react-query"; +import { Job } from "@/lib/types"; // Landing page export default function JobApplication() { @@ -16,15 +18,31 @@ export default function JobApplication() { const [rolePerPage, setRolePerPage] = useState(MIN_ROLE_PER_PAGE); const [selectedDepartment, setSelectedDepartment] = useState("All"); - const handleDepartmentChange = (event) => { + const { data: availableJobs, isLoading, isError } = useQuery( + ["jobs"], + async () => { + console.log(`fetching from: ${process.env.NEXT_PUBLIC_API_URL}`); + const response = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/job`); + if (!response.ok) { + throw new Error("Network response was not ok"); + } + return response.json(); + }); + + if (isLoading) return
Loading...
; + if (isError) return
Error fetching data
; + + const handleDepartmentChange = (event: React.ChangeEvent) => { setSelectedDepartment(event.target.value); }; - const filteredRoutes = availableRoutes.filter((jobPosition) => { + const filteredJobs = availableJobs.filter((jobPosition: Job) => { if (selectedDepartment === "All") return true; - return JobData[jobPosition].department === selectedDepartment; + return jobPosition.department.department_name === selectedDepartment; }); + + return (
@@ -49,62 +67,69 @@ export default function JobApplication() {
- {/*This is not the actual implementation. It's just some random placeholder*/} -
- { - filteredRoutes.slice(0, rolePerPage).map((jobPosition) => ( - - )) - } -
- {/*see more button*/} - {(rolePerPage < maxRole) &&
- -
} + {!isLoading && !isError && ( + <> + {/*This is not the actual implementation. It's just some random placeholder*/} +
+ {filteredJobs.slice(0, rolePerPage).map((jobPosition: Job) => ( + + ))} +
+ + {/*see more button*/} + {rolePerPage < maxRole && ( +
+ +
+ )} - {/*see less button*/} - {(rolePerPage >= maxRole) &&
- -
} + {/*see less button*/} + {rolePerPage >= maxRole && ( +
+ +
+ )} + + )}
); } -function JobCard(props: JobCardProps) { - const route = props.route; - const jobData: JobDataType = JobData[route]; - const position = 0; // FIX: replace this value +function JobCard({ job }: { job: Job }) { return ( -
- +
+
-
{jobData.title}
+
{job.title}
- {jobData.department} {jobData.semester} + {job.department.department_name} {job.semester}
-
{position}
+
{job.positionsAvailable}
- Position + + {job.positionsAvailable === 1 ? "Position" : "Positions"} + Available
@@ -114,6 +139,3 @@ function JobCard(props: JobCardProps) { ) } -interface JobCardProps { - route: string; -} diff --git a/src/app/positions/[id]/page.tsx b/src/app/positions/[id]/page.tsx index f6c7971..a32be41 100644 --- a/src/app/positions/[id]/page.tsx +++ b/src/app/positions/[id]/page.tsx @@ -1,20 +1,35 @@ "use client" import { useParams } from "next/navigation"; -import { useState } from "react"; +import React, { useState } from "react"; import NavBar from "@/components/Navbar"; import Footer from "@/components/Footer" import PositionNotFound from "../not-found"; -import { JobDataType, JobData } from "@/lib/positions/job-data"; import Link from "next/link"; - +import { useQuery } from "react-query"; +import { Job } from "@/lib/types"; +import { useRouter } from "next/navigation"; +import { frontEndApplicationSchema } from "@/lib/validation/applicationSchema"; +import { ValidationError } from "yup"; export default function JobApplication() { const params = useParams(); - const jobData: JobDataType = JobData[params.id]; + const { data: jobData, isLoading, isError } = useQuery( + ["jobs", params.id], + async () => { + console.log(`fetching from: ${process.env.NEXT_PUBLIC_API_URL}`); + const response = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/job/${params.id}`); + if (!response.ok) { + throw new Error("Network response was not ok"); + } + return response.json(); + }); const [activeTab, setActiveTab] = useState("overview"); + if (isLoading) return
Loading...
; + if (isError) return
Error...
; + if (jobData !== undefined) { return ( @@ -26,7 +41,7 @@ export default function JobApplication() {
{jobData.title}
- {jobData.department} | {jobData.semester} | {jobData.positionsAvailable} + {jobData.department.department_name} | {jobData.semester} | {jobData.positionsAvailable}
@@ -61,7 +76,7 @@ export default function JobApplication() { } -function Overview(jobData: JobDataType) { +function Overview(jobData: Job) { return (
@@ -76,38 +91,204 @@ function Overview(jobData: JobDataType) { ) } -function Application(jobData: JobDataType) { +function Application(jobData: Job) { + const [fullName, setFullName] = useState(""); + const [faculty, setFaculty] = useState(""); + const [major, setMajor] = useState(""); + const [yearOfStudy, setYearOfStudy] = useState(""); + const [linkedinUrl, setLinkedinUrl] = useState(""); + const [resumeLink, setResumeLink] = useState(""); + const [telegramHandle, setTelegramHandle] = useState(""); + const [phoneNumber, setPhoneNumber] = useState(""); + const [reason, setReason] = useState(""); + const router = useRouter(); + + const [errors, setErrors] = useState>({}); + + const handleSubmit = async (event: React.FormEvent) => { + event.preventDefault(); + + const newApplication = { + applicant_id: 1, // Hardcoded for now, replace with actual user ID + job_id: jobData.job_id, + status: "submitted", // Default status for new applications, change if needed + name: fullName, + telegram: telegramHandle, + phone: phoneNumber, + year: yearOfStudy ? Number(yearOfStudy) : 0, + major: major, + faculty: faculty, + linkedin_url: linkedinUrl, + resume_url: resumeLink, + applicant_desc: reason, + }; + + try { + // Validate input first + await frontEndApplicationSchema.validate(newApplication, { abortEarly: false }); + + // Send application to backend + const response = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/application`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(newApplication), + }); + + // Some error occured during the request + if (!response.ok) { + console.log(response); + throw new Error("Network response was not ok"); + } + + const data = await response.json(); + console.log("Application created:", data); + // Handle successful application creation (navigate to confirmation page) + router.push(`/confirmation?jobTitle=${jobData.title}&department=${jobData.department.department_name}&semester=${jobData.semester}`); + } catch (error) { + // Handle validation error + if (error instanceof ValidationError) { + const validationErrors: Record = {}; + error.inner.forEach((e) => { + if (e.path) { + validationErrors[e.path] = e.message; + } + }); + setErrors(validationErrors); + } + console.error("Error creating application:", error); + } +}; + return ( -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- -
- -
+
+
+ + setFullName(e.target.value)} + className="w-full p-2 border border-gray-300 rounded" + required + /> + {errors.name &&

{errors.name}

} +
+ +
+ + setFaculty(e.target.value)} + className="w-full p-2 border border-gray-300 rounded" + required + /> + {errors.faculty &&

{errors.faculty}

} +
+ +
+ + setMajor(e.target.value)} + className="w-full p-2 border border-gray-300 rounded" + required + /> + {errors.major &&

{errors.major}

} +
+ +
+ + + {errors.year &&

{errors.year}

} +
+ +
+ + setTelegramHandle(e.target.value)} + className="w-full p-2 border border-gray-300 rounded" + required + /> + {errors.telegram &&

{errors.telegram}

} +
+ +
+ + setPhoneNumber(e.target.value)} + className="w-full p-2 border border-gray-300 rounded" + required + /> + {errors.phone &&

{errors.phone}

} +
+ +
+ + setLinkedinUrl(e.target.value)} + className="w-full p-2 border border-gray-300 rounded" + required + /> + {errors.linkedin_url &&

{errors.linkedin_url}

} +
+ +
+ + setResumeLink(e.target.value)} + className="w-full p-2 border border-gray-300 rounded" + required + /> + {errors.resume_url &&

{errors.resume_url}

} +
+ +
+ + setReason(e.target.value)} + className="w-full p-2 border border-gray-300 rounded" + required + /> + {errors.applicant_desc &&

{errors.applicant_desc}

} +
+ +
+ +
+
) } diff --git a/src/app/signin/page.tsx b/src/app/signin/page.tsx index 8fdfeb5..788f5ea 100644 --- a/src/app/signin/page.tsx +++ b/src/app/signin/page.tsx @@ -1,6 +1,7 @@ "use client"; import Link from "next/link"; +import React from "react"; export default function SignInPage() { return ( diff --git a/src/app/signup/page.tsx b/src/app/signup/page.tsx index ee9abbd..65697c0 100644 --- a/src/app/signup/page.tsx +++ b/src/app/signup/page.tsx @@ -41,6 +41,8 @@ export default function SignInPage() { id="confirmpassword" type="password" required + value={confirmPassword} + onChange={(e) => setConfirmPassword(e.target.value)} className="w-3/5 rounded-2xl border-gray-200 bg-gray-200 focus:ring-blue-900 focus:border-blue-900" />
diff --git a/src/components/ReactQueryClientProvider.tsx b/src/components/ReactQueryClientProvider.tsx new file mode 100644 index 0000000..a742885 --- /dev/null +++ b/src/components/ReactQueryClientProvider.tsx @@ -0,0 +1,16 @@ +"use client"; + +import React from 'react'; +import { QueryClient, QueryClientProvider } from 'react-query'; + +const queryClient = new QueryClient(); + +const ReactQueryClientProvider = ({ children }: { children: React.ReactNode }) => { + return ( + + {children} + + ); +}; + +export default ReactQueryClientProvider; \ No newline at end of file diff --git a/src/lib/types.ts b/src/lib/types.ts new file mode 100644 index 0000000..2508c19 --- /dev/null +++ b/src/lib/types.ts @@ -0,0 +1,15 @@ +export type Job = { + created_by: number; + deadline: Date; + department_id: number; + department: { + department_name: string; + } + description: string; + job_id: number; + positionsAvailable: number; + requirements: string; + semester: string; + status: string; + title: string; + } \ No newline at end of file diff --git a/src/lib/validation/applicationSchema.ts b/src/lib/validation/applicationSchema.ts new file mode 100644 index 0000000..3c6c776 --- /dev/null +++ b/src/lib/validation/applicationSchema.ts @@ -0,0 +1,30 @@ +import * as yup from "yup"; + +export const frontEndApplicationSchema = yup.object().shape({ + // Replicate only the rules you need on the client + status: yup + .mixed<"submitted" | "shortlisted" | "rejected">() + .oneOf(["submitted", "shortlisted", "rejected"], + "Status must be one of: submitted, shortlisted, rejected") + .optional(), + name: yup.string().optional(), + telegram: yup + .string() + .matches(/^@/, "Telegram handle must start with '@'") + .optional(), + phone: yup + .string() + .matches(/^\d{8}$/, "Phone number must be exactly 8 digits") + .optional(), + year: yup + .number() + .integer() + .min(1, "Year must be between 1 and 4") + .max(4, "Year must be between 1 and 4") + .optional(), + major: yup.string().required("Major is required"), + faculty: yup.string().required("Faculty is required"), + linkedin_url: yup.string().url("LinkedIn URL must be a valid URL").optional(), + resume_url: yup.string().url("Resume URL must be a valid URL").optional(), + applicant_desc: yup.string().required("Applicant description is required"), +}); \ No newline at end of file From 992a082cdb3ef0b9b52ae4d07b56e04be8998163 Mon Sep 17 00:00:00 2001 From: NgZiXin Date: Thu, 26 Dec 2024 00:09:20 +0800 Subject: [PATCH 18/24] Integrate with frontend --- .env | 1 + env.md | 3 + package-lock.json | 130 +++++ package.json | 3 +- server/env.md | 5 +- server/loader-register.mjs | 5 - server/package-lock.json | 492 ++++++++++++++++++ server/package.json | 9 +- .../migration.sql | 10 + server/prisma/seed.ts | 2 - server/src/auth/callbackHandler.ts | 19 +- server/src/auth/clients/googleClient.ts | 31 +- server/src/auth/clients/linkedinClient.ts | 36 +- server/src/auth/loginHandler.ts | 7 +- server/src/middleware/sessionMiddleware.ts | 1 + server/src/routes/applicant.ts | 4 +- server/src/routes/auth.ts | 73 ++- server/src/validators/auth.ts | 5 + src/app/confirmation/page.tsx | 19 +- src/app/page.tsx | 6 +- src/app/positions/[id]/page.tsx | 14 +- src/app/signin/page.tsx | 152 ++++-- src/app/signin/success/page.tsx | 58 +++ src/utils/getCookie.tsx | 16 + src/utils/handleResponse.tsx | 9 + 25 files changed, 937 insertions(+), 173 deletions(-) create mode 100644 .env create mode 100644 env.md delete mode 100644 server/loader-register.mjs create mode 100644 server/prisma/migrations/20241224084204_merge_with_main/migration.sql create mode 100644 server/src/validators/auth.ts create mode 100644 src/app/signin/success/page.tsx create mode 100644 src/utils/getCookie.tsx create mode 100644 src/utils/handleResponse.tsx diff --git a/.env b/.env new file mode 100644 index 0000000..634d553 --- /dev/null +++ b/.env @@ -0,0 +1 @@ +NEXT_PUBLIC_API_URL=http://localhost:3000 \ No newline at end of file diff --git a/env.md b/env.md new file mode 100644 index 0000000..1aa54ad --- /dev/null +++ b/env.md @@ -0,0 +1,3 @@ +# Example of .env file required + +NEXT_PUBLIC_API_URL=http://localhost:3001 \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 35e7cea..86d515b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "0.1.0", "dependencies": { "bcryptjs": "^2.4.3", + "cookie": "^1.0.2", "flowbite-react": "^0.10.2", "next": "14.2.15", "react": "^18", @@ -332,6 +333,126 @@ "glob": "10.3.10" } }, + "node_modules/@next/swc-darwin-arm64": { + "version": "14.2.15", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.15.tgz", + "integrity": "sha512-Rvh7KU9hOUBnZ9TJ28n2Oa7dD9cvDBKua9IKx7cfQQ0GoYUwg9ig31O2oMwH3wm+pE3IkAQ67ZobPfEgurPZIA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-darwin-x64": { + "version": "14.2.15", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.15.tgz", + "integrity": "sha512-5TGyjFcf8ampZP3e+FyCax5zFVHi+Oe7sZyaKOngsqyaNEpOgkKB3sqmymkZfowy3ufGA/tUgDPPxpQx931lHg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-gnu": { + "version": "14.2.15", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.15.tgz", + "integrity": "sha512-3Bwv4oc08ONiQ3FiOLKT72Q+ndEMyLNsc/D3qnLMbtUYTQAmkx9E/JRu0DBpHxNddBmNT5hxz1mYBphJ3mfrrw==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-musl": { + "version": "14.2.15", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.15.tgz", + "integrity": "sha512-k5xf/tg1FBv/M4CMd8S+JL3uV9BnnRmoe7F+GWC3DxkTCD9aewFRH1s5rJ1zkzDa+Do4zyN8qD0N8c84Hu96FQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-gnu": { + "version": "14.2.15", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.15.tgz", + "integrity": "sha512-kE6q38hbrRbKEkkVn62reLXhThLRh6/TvgSP56GkFNhU22TbIrQDEMrO7j0IcQHcew2wfykq8lZyHFabz0oBrA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-musl": { + "version": "14.2.15", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.15.tgz", + "integrity": "sha512-PZ5YE9ouy/IdO7QVJeIcyLn/Rc4ml9M2G4y3kCM9MNf1YKvFY4heg3pVa/jQbMro+tP6yc4G2o9LjAz1zxD7tQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-arm64-msvc": { + "version": "14.2.15", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.15.tgz", + "integrity": "sha512-2raR16703kBvYEQD9HNLyb0/394yfqzmIeyp2nDzcPV4yPjqNUG3ohX6jX00WryXz6s1FXpVhsCo3i+g4RUX+g==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-ia32-msvc": { + "version": "14.2.15", + "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.15.tgz", + "integrity": "sha512-fyTE8cklgkyR1p03kJa5zXEaZ9El+kDNM5A+66+8evQS5e/6v0Gk28LqA0Jet8gKSOyP+OTm/tJHzMlGdQerdQ==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, "node_modules/@next/swc-win32-x64-msvc": { "version": "14.2.15", "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.15.tgz", @@ -1345,6 +1466,15 @@ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "license": "MIT" }, + "node_modules/cookie": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz", + "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", diff --git a/package.json b/package.json index d5dc14c..c0fe0de 100644 --- a/package.json +++ b/package.json @@ -3,13 +3,14 @@ "version": "0.1.0", "private": true, "scripts": { - "dev": "next dev", + "dev": "next dev -p 3001", "build": "next build", "start": "next start", "lint": "next lint" }, "dependencies": { "bcryptjs": "^2.4.3", + "cookie": "^1.0.2", "flowbite-react": "^0.10.2", "next": "14.2.15", "react": "^18", diff --git a/server/env.md b/server/env.md index f1642c0..1b9ce5f 100644 --- a/server/env.md +++ b/server/env.md @@ -1,7 +1,7 @@ # Example of environment file (For testing purpose, you guys can have my oauth id and secrets) # Server configuration -PORT=3000 +PORT=3001 HOST=localhost # Server secret @@ -19,6 +19,3 @@ GOOGLE_CLIENT_SECRET="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" LINKEDIN_CLIENT_ID="86ubf590mi26zb" LINKEDIN_CLIENT_SECRET="WPL_AP1.pLcDrMd0C8JyM71q.STlaMw==" -# Github oauth configuration -GITHUB_CLIENT_ID="Ov23li5QHI82UQJLAX1H" -GITHUB_CLIENT_SECRET="4ae735b59aaa09a1b2badb698d156c24ca819596" diff --git a/server/loader-register.mjs b/server/loader-register.mjs deleted file mode 100644 index 9cef40e..0000000 --- a/server/loader-register.mjs +++ /dev/null @@ -1,5 +0,0 @@ -import { register } from "node:module"; -import { pathToFileURL } from "node:url"; - -// Script for registering ts-node loader -register("ts-node/esm", pathToFileURL("./")); \ No newline at end of file diff --git a/server/package-lock.json b/server/package-lock.json index cf042f4..da43cd6 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -31,6 +31,7 @@ "prisma": "^5.21.1", "rimraf": "^6.0.1", "ts-node": "^10.9.2", + "tsx": "^4.19.2", "typescript": "^5.6.3" } }, @@ -47,6 +48,414 @@ "node": ">=12" } }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.23.1.tgz", + "integrity": "sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.23.1.tgz", + "integrity": "sha512-uz6/tEy2IFm9RYOyvKl88zdzZfwEfKZmnX9Cj1BHjeSGNuGLuMD1kR8y5bteYmwqKm1tj8m4cb/aKEorr6fHWQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.23.1.tgz", + "integrity": "sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.23.1.tgz", + "integrity": "sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.23.1.tgz", + "integrity": "sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.23.1.tgz", + "integrity": "sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.23.1.tgz", + "integrity": "sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.23.1.tgz", + "integrity": "sha512-lK1eJeyk1ZX8UklqFd/3A60UuZ/6UVfGT2LuGo3Wp4/z7eRTRYY+0xOu2kpClP+vMTi9wKOfXi2vjUpO1Ro76g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.23.1.tgz", + "integrity": "sha512-CXXkzgn+dXAPs3WBwE+Kvnrf4WECwBdfjfeYHpMeVxWE0EceB6vhWGShs6wi0IYEqMSIzdOF1XjQ/Mkm5d7ZdQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.23.1.tgz", + "integrity": "sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.23.1.tgz", + "integrity": "sha512-VTN4EuOHwXEkXzX5nTvVY4s7E/Krz7COC8xkftbbKRYAl96vPiUssGkeMELQMOnLOJ8k3BY1+ZY52tttZnHcXQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.23.1.tgz", + "integrity": "sha512-Vx09LzEoBa5zDnieH8LSMRToj7ir/Jeq0Gu6qJ/1GcBq9GkfoEAoXvLiW1U9J1qE/Y/Oyaq33w5p2ZWrNNHNEw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.23.1.tgz", + "integrity": "sha512-nrFzzMQ7W4WRLNUOU5dlWAqa6yVeI0P78WKGUo7lg2HShq/yx+UYkeNSE0SSfSure0SqgnsxPvmAUu/vu0E+3Q==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.23.1.tgz", + "integrity": "sha512-dKN8fgVqd0vUIjxuJI6P/9SSSe/mB9rvA98CSH2sJnlZ/OCZWO1DJvxj8jvKTfYUdGfcq2dDxoKaC6bHuTlgcw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.23.1.tgz", + "integrity": "sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.23.1.tgz", + "integrity": "sha512-9ygs73tuFCe6f6m/Tb+9LtYxWR4c9yg7zjt2cYkjDbDpV/xVn+68cQxMXCjUpYwEkze2RcU/rMnfIXNRFmSoDw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.23.1.tgz", + "integrity": "sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.23.1.tgz", + "integrity": "sha512-aevEkCNu7KlPRpYLjwmdcuNz6bDFiE7Z8XC4CPqExjTvrHugh28QzUXVOZtiYghciKUacNktqxdpymplil1beA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.23.1.tgz", + "integrity": "sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.23.1.tgz", + "integrity": "sha512-aY2gMmKmPhxfU+0EdnN+XNtGbjfQgwZj43k8G3fyrDM/UdZww6xrWxmDkuz2eCZchqVeABjV5BpildOrUbBTqA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.23.1.tgz", + "integrity": "sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.23.1.tgz", + "integrity": "sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.23.1.tgz", + "integrity": "sha512-BcaL0Vn6QwCwre3Y717nVHZbAa4UBEigzFm6VdsVdT/MbZ38xoj1X9HPkZhbmaBGUD1W8vxAfffbDe8bA6AKnQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.23.1.tgz", + "integrity": "sha512-BHpFFeslkWrXWyUPnbKm+xYYVYruCinGcftSBaa8zoF9hZO4BcSCFUvHVTtzpIY6YzUnYtuEhZ+C9iEXjxnasg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -771,6 +1180,46 @@ "node": ">= 0.4" } }, + "node_modules/esbuild": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.23.1.tgz", + "integrity": "sha512-VVNz/9Sa0bs5SELtn3f7qhJCDPCF5oMEl5cO9/SSinpE9hbPVvxbd572HH5AKiP7WD8INO53GgfDDhRjkylHEg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.23.1", + "@esbuild/android-arm": "0.23.1", + "@esbuild/android-arm64": "0.23.1", + "@esbuild/android-x64": "0.23.1", + "@esbuild/darwin-arm64": "0.23.1", + "@esbuild/darwin-x64": "0.23.1", + "@esbuild/freebsd-arm64": "0.23.1", + "@esbuild/freebsd-x64": "0.23.1", + "@esbuild/linux-arm": "0.23.1", + "@esbuild/linux-arm64": "0.23.1", + "@esbuild/linux-ia32": "0.23.1", + "@esbuild/linux-loong64": "0.23.1", + "@esbuild/linux-mips64el": "0.23.1", + "@esbuild/linux-ppc64": "0.23.1", + "@esbuild/linux-riscv64": "0.23.1", + "@esbuild/linux-s390x": "0.23.1", + "@esbuild/linux-x64": "0.23.1", + "@esbuild/netbsd-x64": "0.23.1", + "@esbuild/openbsd-arm64": "0.23.1", + "@esbuild/openbsd-x64": "0.23.1", + "@esbuild/sunos-x64": "0.23.1", + "@esbuild/win32-arm64": "0.23.1", + "@esbuild/win32-ia32": "0.23.1", + "@esbuild/win32-x64": "0.23.1" + } + }, "node_modules/escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -970,6 +1419,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-tsconfig": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.8.1.tgz", + "integrity": "sha512-k9PN+cFBmaLWtVz29SkUoqU5O0slLuHJXt/2P+tMVFT+phsSGXGkp9t3rQIqdz0e+06EHNGs3oM6ZX1s2zHxRg==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, "node_modules/glob": { "version": "11.0.0", "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.0.tgz", @@ -1614,6 +2076,16 @@ "node": ">= 0.8" } }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, "node_modules/rimraf": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-6.0.1.tgz", @@ -1990,6 +2462,26 @@ } } }, + "node_modules/tsx": { + "version": "4.19.2", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.19.2.tgz", + "integrity": "sha512-pOUl6Vo2LUq/bSa8S5q7b91cgNSjctn9ugq/+Mvow99qW6x/UZYwzxy/3NmqoT66eHYfCVvFvACC58UBPFf28g==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.23.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, "node_modules/type-fest": { "version": "2.19.0", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", diff --git a/server/package.json b/server/package.json index 9b9715e..0a17143 100644 --- a/server/package.json +++ b/server/package.json @@ -3,11 +3,11 @@ "version": "1.0.0", "main": "dist/index.ts", "prisma": { - "seed": "ts-node prisma/seed.ts" + "seed": "tsx prisma/seed.ts" }, "scripts": { "start": "node dist/index.js", - "dev": "node --import ./loader-register.mjs src/index.ts", + "dev": "tsx src/index.ts", "clean": "rimraf dist", "build": "npm run clean && npx tsc", "test": "echo \"Error: no test specified\" && exit 1" @@ -28,14 +28,15 @@ "prisma": "^5.21.1", "rimraf": "^6.0.1", "ts-node": "^10.9.2", + "tsx": "^4.19.2", "typescript": "^5.6.3" }, "dependencies": { "@prisma/client": "^5.21.1", - "dotenv": "^16.4.5", - "express-session": "^1.18.1", "bcryptjs": "^2.4.3", "cors": "^2.8.5", + "dotenv": "^16.4.5", + "express-session": "^1.18.1", "jsonwebtoken": "^9.0.2", "jwk-to-pem": "^2.0.7", "openid-client": "^6.1.7", diff --git a/server/prisma/migrations/20241224084204_merge_with_main/migration.sql b/server/prisma/migrations/20241224084204_merge_with_main/migration.sql new file mode 100644 index 0000000..a9dc98f --- /dev/null +++ b/server/prisma/migrations/20241224084204_merge_with_main/migration.sql @@ -0,0 +1,10 @@ +/* + Warnings: + + - Added the required column `positionsAvailable` to the `Job` table without a default value. This is not possible if the table is not empty. + - Added the required column `requirements` to the `Job` table without a default value. This is not possible if the table is not empty. + +*/ +-- AlterTable +ALTER TABLE "Job" ADD COLUMN "positionsAvailable" INTEGER NOT NULL, +ADD COLUMN "requirements" TEXT NOT NULL; diff --git a/server/prisma/seed.ts b/server/prisma/seed.ts index 441f586..c30f62f 100644 --- a/server/prisma/seed.ts +++ b/server/prisma/seed.ts @@ -45,7 +45,6 @@ async function populate_admin() { create: { username: 'admin', email: 'admin@example.com', - password: 'saltandhash', } }); console.log({ admin }); @@ -58,7 +57,6 @@ async function populate_applicant() { create: { username: 'joe', email: 'joe@gmail.com', - password: 'saltandhashbrowns', } }); console.log({ applicant }); diff --git a/server/src/auth/callbackHandler.ts b/server/src/auth/callbackHandler.ts index 33142c4..347a808 100644 --- a/server/src/auth/callbackHandler.ts +++ b/server/src/auth/callbackHandler.ts @@ -22,11 +22,8 @@ export default async function callbackHandler( ); // Exchange the authorization code for tokens - if (!req.session.code_verifier || !req.session.state) { - res - .status(400) - .json({ error: "Session is invalid or missing required attributes" }); - return; + if (!req.session.code_verifier || !req.session.state || !req.session.redirect_to_frontend) { + throw new Error("Session is invalid or missing required attributes" ); } const checks: Record = { @@ -80,13 +77,9 @@ export default async function callbackHandler( // Generate JWT for the applicant. No Expiry on JWT const payload = { email: email, role: "applicant" }; const token = jwt.sign(payload, jwt_secret, { noTimestamp: true }); - res.json({ - token: token, - username: name, - email: email, - }); - console.log(`Applicant with email: ${email} logged in`); - // Clean up the session after use - req.session.destroy(() => {}); + const redirect_uri = `${req.session.redirect_to_frontend}?token=${token}&username=${name}&email=${email}`; + res.redirect(redirect_uri); + console.log(`Applicant with email: ${email} logged in`); + req.session.destroy(() => {}); // Clean up the session after use } diff --git a/server/src/auth/clients/googleClient.ts b/server/src/auth/clients/googleClient.ts index d29769e..a02ebe0 100644 --- a/server/src/auth/clients/googleClient.ts +++ b/server/src/auth/clients/googleClient.ts @@ -21,33 +21,14 @@ assert( const server: URL = new URL(issuer); // Authorization Server's Issuer Identifier const client_id: string = process.env.GOOGLE_CLIENT_ID; // Client identifier at the Authorization Server const client_secret: string = process.env.GOOGLE_CLIENT_SECRET; // Client Secret -let config!: client.Configuration; -(async () => { - config = await client.discovery(server, client_id, client_secret); - console.log("Google configuration discovered"); -})(); +let config: client.Configuration | null = null; // Cache (Singleton Pattern) -export async function googleLoginHandler( - req: Request, - res: Response, - redirect_uri: string -) { - try { - await loginHandler(req, res, redirect_uri, config); - console.log("Redirected User to Google Sign-In Page"); - } catch (error) { - console.error("Error during Google login:", error); - res.status(500).json({ error: "Failed to initiate login process" }); +export default async function getGoogleConfig() { + if (!config) { + config = await client.discovery(server, client_id, client_secret); + console.log("Google configuration discovered"); } -} -export async function googleCallBackHandler(req: Request, res: Response) { - try { - await callbackHandler(req, res, config); - console.log("Callback succesful"); - } catch (error) { - console.error("Error during callback from google:", error); - res.status(500).json({ error: "Login failed" }); - } + return config; } diff --git a/server/src/auth/clients/linkedinClient.ts b/server/src/auth/clients/linkedinClient.ts index d44371d..187f7db 100644 --- a/server/src/auth/clients/linkedinClient.ts +++ b/server/src/auth/clients/linkedinClient.ts @@ -1,8 +1,5 @@ -import { Request, Response } from "express"; import * as client from "openid-client"; import assert from "assert"; -import loginHandler from "../loginHandler.js"; -import callbackHandler from "../callbackHandler.js"; // High-level declarations const issuer: string = "https://www.linkedin.com/oauth"; @@ -21,37 +18,14 @@ assert( const server: URL = new URL(issuer); // Authorization Server's Issuer Identifier const client_id: string = process.env.LINKEDIN_CLIENT_ID; // Client identifier at the Authorization Server const client_secret: string = process.env.LINKEDIN_CLIENT_SECRET; // Client Secret -let config!: client.Configuration; -(async () => { - try { +let config: client.Configuration | null = null; // Cache (Singleton Pattern) + +export default async function getLinkedInConfig() { + if (!config) { config = await client.discovery(server, client_id, client_secret); console.log("LinkedIn configuration discovered"); - } catch (error) { - console.error("Failed to discover LinkedIn configuration:", error); } -})(); -export async function linkedinLoginHandler( - req: Request, - res: Response, - redirect_uri: string -) { - try { - await loginHandler(req, res, redirect_uri, config); - console.log("Redirected User to LinkedIn Sign-In Page"); - } catch (error) { - console.error("Error during LinkedIn login:", error); - res.status(500).json({ error: "Failed to initiate login process" }); - } -} - -export async function linkedinCallBackHandler(req: Request, res: Response) { - try { - await callbackHandler(req, res, config); - console.log("Callback succesful"); - } catch (error) { - console.error("Error during callback from linkedin:", error); - res.status(500).json({ error: "Login failed" }); - } + return config; } diff --git a/server/src/auth/loginHandler.ts b/server/src/auth/loginHandler.ts index f6245bb..0b0d067 100644 --- a/server/src/auth/loginHandler.ts +++ b/server/src/auth/loginHandler.ts @@ -1,4 +1,6 @@ import { Request, Response } from "express"; +import { logRequest } from "../utils/logUtil.js"; +import { loginOpenIdConnectSchema } from "../validators/auth.js"; import * as client from "openid-client"; export default async function loginHandler( @@ -7,10 +9,13 @@ export default async function loginHandler( redirect_uri: string, config: client.Configuration ) { + logRequest(req); + const redirect_to_frontend = (await loginOpenIdConnectSchema.validate(req.query)).redirect_to_frontend; const code_verifier = client.randomPKCECodeVerifier(); const state = client.randomState(); - // Store code_verifier and state in the session + // Store values in the session + req.session.redirect_to_frontend = redirect_to_frontend; req.session.code_verifier = code_verifier; req.session.state = state; diff --git a/server/src/middleware/sessionMiddleware.ts b/server/src/middleware/sessionMiddleware.ts index 25e7118..62578be 100644 --- a/server/src/middleware/sessionMiddleware.ts +++ b/server/src/middleware/sessionMiddleware.ts @@ -10,6 +10,7 @@ declare module 'express-session' { interface SessionData { code_verifier?: string; state?: string; + redirect_to_frontend?: string; } } diff --git a/server/src/routes/applicant.ts b/server/src/routes/applicant.ts index 15359e6..6d123a9 100644 --- a/server/src/routes/applicant.ts +++ b/server/src/routes/applicant.ts @@ -18,13 +18,13 @@ router.post("/", async (req: Request, res: Response) => { stripUnknown: true, // Required to prevent mass assignment vulnerabilities }); - const hashedPassword = await bcrypt.hash(validatedData.password, 10); + // const hashedPassword = await bcrypt.hash(validatedData.password, 10); console.log(`Creating applicant with email: ${validatedData.email}`); const applicant = await prisma.applicant.create({ data: { ...validatedData, - password: hashedPassword, + // password: hashedPassword, } }); diff --git a/server/src/routes/auth.ts b/server/src/routes/auth.ts index dbc8f7b..23fbec1 100644 --- a/server/src/routes/auth.ts +++ b/server/src/routes/auth.ts @@ -1,27 +1,70 @@ import { Router, Request, Response } from "express"; import { sessionMiddleware } from "../middleware/sessionMiddleware.js"; -import { logRequest } from "../utils/logUtil.js"; -import { googleCallBackHandler, googleLoginHandler } from "../auth/clients/googleClient.js"; -import { linkedinCallBackHandler, linkedinLoginHandler } from "../auth/clients/linkedinClient.js"; +import getGoogleConfig from "../auth/clients/googleClient.js"; +import getLinkedInConfig from "../auth/clients/linkedinClient.js"; +import loginHandler from "../auth/loginHandler.js"; +import callbackHandler from "../auth/callbackHandler.js"; const router = Router(); // Google Login & Callback -router.get("/google/login", sessionMiddleware, async (req: Request, res: Response) => { - logRequest(req); - const redirect_uri = `${req.protocol}://${req.get("host")}/auth/google/callback`; - googleLoginHandler(req, res, redirect_uri); -}); +router.get( + "/google/login", + sessionMiddleware, + async (req: Request, res: Response) => { + try { + const config = await getGoogleConfig() + const redirect_uri = `${req.protocol}://${req.get("host")}/auth/google/callback`; + loginHandler(req, res, redirect_uri, config); + } catch (error) { + console.error("Error during Google login:", error); + res.status(500).json({ error: "Failed to initiate login process" }); + } + } +); -router.get("/google/callback", sessionMiddleware, googleCallBackHandler); +router.get( + "/google/callback", + sessionMiddleware, + async (req: Request, res: Response) => { + try { + const config = await getGoogleConfig() + await callbackHandler(req, res, config); + } catch (error) { + console.error("Error during callback from Google:", error); + res.status(500).json({ error: "Login failed" }); + } + } +); // Linkedin Login & Callback -router.get("/linkedin/login", sessionMiddleware, async (req: Request, res: Response) => { - logRequest(req); - const redirect_uri = `${req.protocol}://${req.get("host")}/auth/linkedin/callback`; - linkedinLoginHandler(req, res, redirect_uri); -}); +router.get( + "/linkedin/login", + sessionMiddleware, + async (req: Request, res: Response) => { + try { + const config = await getLinkedInConfig() + const redirect_uri = `${req.protocol}://${req.get("host")}/auth/linkedin/callback`; + loginHandler(req, res, redirect_uri, config); + } catch (error) { + console.error("Error during LinkedIn login:", error); + res.status(500).json({ error: "Failed to initiate login process" }); + } + } +); -router.get("/linkedin/callback", sessionMiddleware, linkedinCallBackHandler); +router.get( + "/linkedin/callback", + sessionMiddleware, + async (req: Request, res: Response) => { + try { + const config = await getLinkedInConfig() + await callbackHandler(req, res, config); + } catch (error) { + console.error("Error during callback from LinkedIn:", error); + res.status(500).json({ error: "Login failed" }); + } + } +); export default router; diff --git a/server/src/validators/auth.ts b/server/src/validators/auth.ts new file mode 100644 index 0000000..e59c9fb --- /dev/null +++ b/server/src/validators/auth.ts @@ -0,0 +1,5 @@ +import * as yup from 'yup'; + +export const loginOpenIdConnectSchema = yup.object().shape({ + redirect_to_frontend: yup.string().required('Redirect URI is required'), +}); diff --git a/src/app/confirmation/page.tsx b/src/app/confirmation/page.tsx index 5b3d708..834c268 100644 --- a/src/app/confirmation/page.tsx +++ b/src/app/confirmation/page.tsx @@ -1,17 +1,21 @@ "use client"; import checkmark from "@/app/components/confirmation/checkmark.png"; -import React from "react"; +import React, { useState, useEffect } from "react"; import Image from "next/image"; import { useSearchParams } from "next/navigation"; - +import getCookie from "@/utils/getCookie"; export default function ConfirmationPage() { const searchParams = useSearchParams(); const jobTitle = searchParams.get("jobTitle"); const department = searchParams.get("department"); const semester = searchParams.get("semester"); + const [username, setUsername] = useState(null); + useEffect(() => { + setUsername(getCookie("username")); + }, []); return (
{/* Header Area */} @@ -35,8 +39,15 @@ export default function ConfirmationPage() { About Us -
-
Welcome Back Shawn!
+
{" "} + {username ? ( +
+ Welcome Back{" "} + {username}! +
+ ) : ( + <> + )} {/* Job Role Title */} diff --git a/src/app/page.tsx b/src/app/page.tsx index 092a83c..be7b47a 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -9,6 +9,7 @@ import Image from "next/image"; import excoPic from "@/app/components/landing_page/Fintech_Exco.png"; import { useQuery } from "react-query"; import { Job } from "@/lib/types"; +import { handleResponse } from "@/utils/handleResponse"; // Landing page export default function JobApplication() { @@ -23,10 +24,7 @@ export default function JobApplication() { async () => { console.log(`fetching from: ${process.env.NEXT_PUBLIC_API_URL}`); const response = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/job`); - if (!response.ok) { - throw new Error("Network response was not ok"); - } - return response.json(); + return (await handleResponse(response)); }); if (isLoading) return
Loading...
; diff --git a/src/app/positions/[id]/page.tsx b/src/app/positions/[id]/page.tsx index a32be41..47884a6 100644 --- a/src/app/positions/[id]/page.tsx +++ b/src/app/positions/[id]/page.tsx @@ -11,6 +11,7 @@ import { Job } from "@/lib/types"; import { useRouter } from "next/navigation"; import { frontEndApplicationSchema } from "@/lib/validation/applicationSchema"; import { ValidationError } from "yup"; +import { handleResponse } from "@/utils/handleResponse"; export default function JobApplication() { const params = useParams(); @@ -19,10 +20,7 @@ export default function JobApplication() { async () => { console.log(`fetching from: ${process.env.NEXT_PUBLIC_API_URL}`); const response = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/job/${params.id}`); - if (!response.ok) { - throw new Error("Network response was not ok"); - } - return response.json(); + return (await handleResponse(response)); }); const [activeTab, setActiveTab] = useState("overview"); @@ -136,13 +134,7 @@ function Application(jobData: Job) { body: JSON.stringify(newApplication), }); - // Some error occured during the request - if (!response.ok) { - console.log(response); - throw new Error("Network response was not ok"); - } - - const data = await response.json(); + const data = await handleResponse(response); console.log("Application created:", data); // Handle successful application creation (navigate to confirmation page) router.push(`/confirmation?jobTitle=${jobData.title}&department=${jobData.department.department_name}&semester=${jobData.semester}`); diff --git a/src/app/signin/page.tsx b/src/app/signin/page.tsx index 788f5ea..7e30391 100644 --- a/src/app/signin/page.tsx +++ b/src/app/signin/page.tsx @@ -1,60 +1,110 @@ "use client"; import Link from "next/link"; -import React from "react"; +import { usePathname } from "next/navigation"; +import React, { useState, useEffect } from "react"; export default function SignInPage() { - return ( + const pathname = usePathname(); + const [redirectToFrontend, setRedirectToFrontend] = useState(""); + + const handleGoogleSignIn = () => { + window.location.href = `${process.env.NEXT_PUBLIC_API_URL}/auth/google/login?redirect_to_frontend=${redirectToFrontend}`; + }; + + const handleLinkedInSignIn = () => { + window.location.href = `${process.env.NEXT_PUBLIC_API_URL}/auth/linkedIn/login?redirect_to_frontend=${redirectToFrontend}`; + }; + + useEffect(() => { + setRedirectToFrontend(`${window.location.origin}${pathname}/success`); + }); + + return ( + <> + {redirectToFrontend ? (
-
-

Sign In

-
-
- - -
-
- - -
-
- - -
-
- -
-
-
- Don’t have an account?{' '} - - Sign Up - -
+
+ +

+ Sign In +

+
+
+ + +
+
+ + +
+
+ + +
+
+ +
+
+
+ or +
+
+
+ + +
+
+
+ Don’t have an account?{" "} + + Sign Up + +
+
- ); + ) : ( + <> + )}{" "} + + ); } diff --git a/src/app/signin/success/page.tsx b/src/app/signin/success/page.tsx new file mode 100644 index 0000000..63c302c --- /dev/null +++ b/src/app/signin/success/page.tsx @@ -0,0 +1,58 @@ +"use client"; + +import React, { useEffect, useState } from "react"; +import Link from "next/link"; +import { useRouter, useSearchParams } from "next/navigation"; + +export default function SignInSuccessPage() { + const router = useRouter(); + const searchParams = useSearchParams(); + const [isCookiesSet, setIsCookiesSet] = useState(false); + + useEffect(() => { + const token = searchParams.get("token"); + const username = searchParams.get("username"); + const email = searchParams.get("email"); + + if (!token || !username || !email) { + throw new Error("Missing query fields"); + } + + {/*Note: Browser cookies currently set to have no expiry date */} + document.cookie = `token=${encodeURIComponent(token)}; path=/;`; + document.cookie = `username=${encodeURIComponent(username)}; path=/;`; + document.cookie = `email=${encodeURIComponent(email)}; path=/;`; + + console.log(`${username} logged in successfully`); + setIsCookiesSet(true); + + const timer = setTimeout(() => { + router.push("/"); // Automatic redirect after 5 seconds + }, 5000); + + return () => clearTimeout(timer); + }, []); + + return ( +
+
+

+ You have signed in successfully. +

+

+ Automatically redirecting you to homepage... +

+ {isCookiesSet ? ( +

+ + click me + {" "} + if not redirected +

+ ) : ( + <> + )} +
+
+ ); +} diff --git a/src/utils/getCookie.tsx b/src/utils/getCookie.tsx new file mode 100644 index 0000000..d97cf1f --- /dev/null +++ b/src/utils/getCookie.tsx @@ -0,0 +1,16 @@ +{/*NOTE: Only can be used inside an useEffect hook*/} +export default function getCookie(name: string): string | null { + + if (typeof document === "undefined") { + return null; // Return null during Server Side Rendering (SSR) + } + + const cookies = document.cookie.split("; "); // Split cookies into key-value pairs + for (let i = 0; i < cookies.length; i++) { + const [key, value] = cookies[i].split("="); // Split each pair into key and value + if (key === name) { + return decodeURIComponent(value); // Return decoded value + } + } + return null; // Return null if not found +} diff --git a/src/utils/handleResponse.tsx b/src/utils/handleResponse.tsx new file mode 100644 index 0000000..d428c90 --- /dev/null +++ b/src/utils/handleResponse.tsx @@ -0,0 +1,9 @@ +export const handleResponse = async (response: Response) => { + if (!response.ok) { + // Try parsing JSON error; fallback to status text if it fails + const errorMessage = (await response.json().catch(() => null))?.error || + `${response.status} ${response.statusText}`; + throw new Error(errorMessage); + } + return response.json(); // Return the parsed JSON response + }; \ No newline at end of file From c67e09aedfb4900dfbc1ce86c642386201b923c5 Mon Sep 17 00:00:00 2001 From: NgZiXin Date: Thu, 26 Dec 2024 00:11:47 +0800 Subject: [PATCH 19/24] Remove redundant package --- package-lock.json | 10 ---------- package.json | 1 - 2 files changed, 11 deletions(-) diff --git a/package-lock.json b/package-lock.json index 86d515b..502597b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,6 @@ "version": "0.1.0", "dependencies": { "bcryptjs": "^2.4.3", - "cookie": "^1.0.2", "flowbite-react": "^0.10.2", "next": "14.2.15", "react": "^18", @@ -1466,15 +1465,6 @@ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "license": "MIT" }, - "node_modules/cookie": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz", - "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==", - "license": "MIT", - "engines": { - "node": ">=18" - } - }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", diff --git a/package.json b/package.json index c0fe0de..1e1d60d 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,6 @@ }, "dependencies": { "bcryptjs": "^2.4.3", - "cookie": "^1.0.2", "flowbite-react": "^0.10.2", "next": "14.2.15", "react": "^18", From f2f055f18d747547a76170739643ca877e5d95de Mon Sep 17 00:00:00 2001 From: NgZiXin Date: Thu, 26 Dec 2024 00:41:39 +0800 Subject: [PATCH 20/24] Add ReadMe --- server/ReadMe.md | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 server/ReadMe.md diff --git a/server/ReadMe.md b/server/ReadMe.md new file mode 100644 index 0000000..53db498 --- /dev/null +++ b/server/ReadMe.md @@ -0,0 +1,48 @@ +## User Guide + +### Pre-requisite +Ensure you are in the **server directory** before proceeding. + +--- + +### Step 1: Install Dependencies +Install all required dependencies by running the following command: +```bash npm install` + +--- + +### Step 2: Create `.env` File +1. Create a `.env` file in the **server directory**. +2. Use the details outlined in **env.md** as a reference. + +Note: Ensure your **local PostgreSQL database** is set up before proceeding. + +--- + +### Step 3: Push Database Schema +Push the database schema to your PostgreSQL database: +```bash npx prisma db push` + +--- + +### Step 4: Migrate Database +Apply database migrations: +```bash npx prisma migrate dev` + +--- + +### Step 5: Seed Database +Seed the database with initial data: +```bash npx prisma db seed` + +--- + +### Step 6: Run Backend Server +Start the backend server: +```bash npm run dev` + +--- + +### Notes +- Ensure your PostgreSQL server is running before executing database commands. +- If any errors occur, check the logs and verify your `.env` configurations. From bc8308bdb0c4cdb49792ce7557a79ee22522fca6 Mon Sep 17 00:00:00 2001 From: NgZiXin Date: Thu, 26 Dec 2024 00:43:55 +0800 Subject: [PATCH 21/24] Fix bug --- server/ReadMe.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/server/ReadMe.md b/server/ReadMe.md index 53db498..eca4a8d 100644 --- a/server/ReadMe.md +++ b/server/ReadMe.md @@ -7,7 +7,7 @@ Ensure you are in the **server directory** before proceeding. ### Step 1: Install Dependencies Install all required dependencies by running the following command: -```bash npm install` +```bash npm install``` --- @@ -21,25 +21,25 @@ Note: Ensure your **local PostgreSQL database** is set up before proceeding. ### Step 3: Push Database Schema Push the database schema to your PostgreSQL database: -```bash npx prisma db push` +```bash npx prisma db push``` --- ### Step 4: Migrate Database Apply database migrations: -```bash npx prisma migrate dev` +```bash npx prisma migrate dev``` --- ### Step 5: Seed Database Seed the database with initial data: -```bash npx prisma db seed` +```bash npx prisma db seed``` --- ### Step 6: Run Backend Server Start the backend server: -```bash npm run dev` +```bash npm run dev``` --- From 44a31d10bbf617bd782d7367eeaa9be039df360d Mon Sep 17 00:00:00 2001 From: NgZiXin Date: Thu, 26 Dec 2024 00:44:48 +0800 Subject: [PATCH 22/24] Fix ReadMe formtting issues --- server/ReadMe.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/server/ReadMe.md b/server/ReadMe.md index eca4a8d..1cd415e 100644 --- a/server/ReadMe.md +++ b/server/ReadMe.md @@ -7,7 +7,7 @@ Ensure you are in the **server directory** before proceeding. ### Step 1: Install Dependencies Install all required dependencies by running the following command: -```bash npm install``` +``` npm install``` --- @@ -21,25 +21,25 @@ Note: Ensure your **local PostgreSQL database** is set up before proceeding. ### Step 3: Push Database Schema Push the database schema to your PostgreSQL database: -```bash npx prisma db push``` +``` npx prisma db push``` --- ### Step 4: Migrate Database Apply database migrations: -```bash npx prisma migrate dev``` +``` npx prisma migrate dev``` --- ### Step 5: Seed Database Seed the database with initial data: -```bash npx prisma db seed``` +``` npx prisma db seed``` --- ### Step 6: Run Backend Server Start the backend server: -```bash npm run dev``` +``` npm run dev``` --- From 599461a020baf9acdac3bdede2955e54be14fc47 Mon Sep 17 00:00:00 2001 From: Jack Date: Thu, 26 Dec 2024 01:00:59 +0800 Subject: [PATCH 23/24] set up interviews schema and routes and seed data --- prisma/seed.ts | 1 + server/prisma/schema.prisma | 76 ++++++++++++------- server/prisma/seed.ts | 113 +++++++++++++++++++++++------ server/src/index.ts | 4 +- server/src/routes/interview.ts | 113 +++++++++++++++++++++++++++++ server/src/validators/interview.ts | 40 ++++++++++ 6 files changed, 296 insertions(+), 51 deletions(-) create mode 100644 prisma/seed.ts create mode 100644 server/src/routes/interview.ts create mode 100644 server/src/validators/interview.ts diff --git a/prisma/seed.ts b/prisma/seed.ts new file mode 100644 index 0000000..0519ecb --- /dev/null +++ b/prisma/seed.ts @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/server/prisma/schema.prisma b/server/prisma/schema.prisma index 23363e1..0e51d63 100644 --- a/server/prisma/schema.prisma +++ b/server/prisma/schema.prisma @@ -22,7 +22,7 @@ model Admin { email String @unique // Relation to Job - jobs Job[] + jobs Job[] } model Department { @@ -30,45 +30,46 @@ model Department { department_name String @unique // Relation to Job - jobs Job[] + jobs Job[] } model Job { - job_id Int @id @default(autoincrement()) - title String - department_id Int - description String - semester String - deadline DateTime + job_id Int @id @default(autoincrement()) + title String + department_id Int + description String + semester String + deadline DateTime positionsAvailable Int - requirements String - status JobStatus - created_by Int + requirements String + status JobStatus + created_by Int // Relations - department Department @relation(fields: [department_id], references: [department_id]) - admin Admin @relation(fields: [created_by], references: [admin_id]) + department Department @relation(fields: [department_id], references: [department_id]) + admin Admin @relation(fields: [created_by], references: [admin_id]) applications Application[] } model Application { - application_id Int @id @default(autoincrement()) - applicant_id Int - job_id Int - status ApplicationStatus - name String - telegram String - phone String - year Int - major String - faculty String - linkedin_url String? // optional - resume_url String - applicant_desc String + application_id Int @id @default(autoincrement()) + applicant_id Int + job_id Int + status ApplicationStatus + name String + telegram String + phone String + year Int + major String + faculty String + linkedin_url String? // optional + resume_url String + applicant_desc String // Relations - applicant Applicant @relation(fields: [applicant_id], references: [applicant_id]) - job Job @relation(fields: [job_id], references: [job_id]) + applicant Applicant @relation(fields: [applicant_id], references: [applicant_id]) + job Job @relation(fields: [job_id], references: [job_id]) + interview Interview? // Composite Constraints @@unique([applicant_id, job_id]) @@ -84,3 +85,22 @@ enum ApplicationStatus { shortlisted rejected } + +enum InterviewStatus { + scheduled + completed + canceled +} + +model Interview { + interview_id Int @id @default(autoincrement()) + application_id Int @unique + interview_dateTime DateTime + interview_URL String + interview_decision String? + interview_notes String? + status InterviewStatus + + // Relation + application Application @relation(fields: [application_id], references: [application_id]) +} diff --git a/server/prisma/seed.ts b/server/prisma/seed.ts index c30f62f..fccf4c5 100644 --- a/server/prisma/seed.ts +++ b/server/prisma/seed.ts @@ -1,7 +1,9 @@ -import { PrismaClient, JobStatus } from '@prisma/client'; +import { PrismaClient, JobStatus, InterviewStatus, ApplicationStatus } from '@prisma/client'; const prisma = new PrismaClient(); + + async function populate_department() { const ml = await prisma.department.upsert({ where: { department_name: 'ML' }, @@ -62,6 +64,7 @@ async function populate_applicant() { console.log({ applicant }); } + async function populate_jobs() { const jobs = [ @@ -70,11 +73,11 @@ async function populate_jobs() { department_id: 2, semester: "Semester 1", positionsAvailable: 5, - description: "As a UIUX designer, you will learn from and work closely with a team of designers, engineers, and design lead to create a fit-for-purpose, convenient, and engaging user interface for applications NUS Fintech Society would build.", + description: "As a UIUX designer, you will learn from and work closely with a team of designers, engineers, and design lead to create a fit-for-purpose, convenient, and engaging user interface for applications NUS Fintech Society would build.", requirements: "You love tech and love to design!", deadline: new Date("2024-12-31"), status: JobStatus.open, - created_by: 1, + created_by: 1, }, { title: "Software Engineer", @@ -176,34 +179,100 @@ async function populate_jobs() { created_by: 1, }]; - let idCounter = 1; - jobs.map(async (job) => { - const jobData = await prisma.job.upsert({ - where: { job_id: idCounter++ }, - update: {}, - create: { - title: job.title, - department_id: job.department_id, - semester: job.semester, - positionsAvailable: job.positionsAvailable, - description: job.description, - requirements: job.requirements, - deadline: job.deadline, - status: job.status, - created_by: job.created_by, - }, - }); - return jobData; - }); + let idCounter = 1; + jobs.map(async (job) => { + const jobData = await prisma.job.upsert({ + where: { job_id: idCounter++ }, + update: {}, + create: { + title: job.title, + department_id: job.department_id, + semester: job.semester, + positionsAvailable: job.positionsAvailable, + description: job.description, + requirements: job.requirements, + deadline: job.deadline, + status: job.status, + created_by: job.created_by, + }, + }); + return jobData; + }); console.log({ jobs }); } +async function populate_application() { + const applications = [ + { + applicant_id: 1, + job_id: 1, + status: ApplicationStatus.submitted, + name: "Joe Smith", + telegram: "@joesmith", + phone: "91234567", + year: 2, + major: "Computer Science", + faculty: "Computing", + linkedin_url: "https://linkedin.com/in/joesmith", + resume_url: "https://example.com/resume/joe", + applicant_desc: "Passionate about software development with experience in web technologies." + }, + { + applicant_id: 1, + job_id: 2, + status: ApplicationStatus.shortlisted, + name: "Joe Smith", + telegram: "@joesmith", + phone: "91234567", + year: 2, + major: "Computer Science", + faculty: "Computing", + linkedin_url: "https://linkedin.com/in/joesmith", + resume_url: "https://example.com/resume/joe", + applicant_desc: "Interested in full-stack development and system design." + } + ]; + + for (let i = 0; i < applications.length; i++) { + const application = await prisma.application.upsert({ + where: { application_id: i + 1 }, + update: {}, + create: applications[i] + }); + console.log({ application }); + } +} + +async function populate_interview() { + const interviews = [ + { + application_id: 1, + interview_dateTime: new Date("2024-04-15T10:00:00Z"), + interview_URL: "https://zoom.us/meeting1", + interview_decision: null, + interview_notes: null, + status: InterviewStatus.scheduled + } + ]; + + for (let i = 0; i < interviews.length; i++) { + const interview = await prisma.interview.upsert({ + where: { interview_id: i + 1 }, + update: {}, + create: interviews[i] + }); + console.log({ interview }); + } +} + async function main() { await populate_department(); await populate_admin(); await populate_jobs(); await populate_applicant(); + await populate_application(); + await populate_interview(); } main() diff --git a/server/src/index.ts b/server/src/index.ts index cfe168a..1962a8f 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -1,9 +1,10 @@ -import express, { NextFunction, Request, Response} from 'express'; +import express, { NextFunction, Request, Response } from 'express'; import applicantRoutes from './routes/applicant.js'; import applicationRoutes from './routes/application.js' import adminRoutes from './routes/admin.js'; import jobRoutes from './routes/job.js'; import authRoutes from './routes/auth.js'; +import interviewRoutes from './routes/interview.js'; import assert from 'assert'; import dotenv from 'dotenv'; import cors from 'cors'; @@ -39,6 +40,7 @@ app.use('/admin', adminRoutes); app.use('/job', jobRoutes); app.use('/application', applicationRoutes); app.use('/auth', authRoutes); +app.use('/interview', interviewRoutes); // Test Route app.get('/', (_req: Request, res: Response) => { diff --git a/server/src/routes/interview.ts b/server/src/routes/interview.ts new file mode 100644 index 0000000..b37fbe3 --- /dev/null +++ b/server/src/routes/interview.ts @@ -0,0 +1,113 @@ +import { Router, Request, Response } from "express"; +import { PrismaClient } from "@prisma/client"; +import { createInterviewSchema, editInterviewSchema } from "../validators/interview.js"; +import { logRequest } from "../utils/logUtil.js"; +import * as yup from 'yup'; + +const prisma = new PrismaClient(); +const router = Router(); + +// Create a new interview +router.post("/", async (req: Request, res: Response) => { + logRequest(req); + try { + console.log(`Validating input data`); + const validatedData = await createInterviewSchema.validate(req.body, { + abortEarly: false, + stripUnknown: true, + }); + + console.log(`Creating interview for application ID: ${validatedData.application_id}`); + const interview = await prisma.interview.create({ + data: validatedData, + }); + + console.log(`Interview created with ID: ${interview.interview_id}`); + res.status(201).json(interview); + } catch (error) { + if (error instanceof yup.ValidationError) { + res.status(400).json({ + error: "Validation error", + details: error.errors, + }); + } else { + console.error("Error creating interview:", error); + res.status(400).json({ error: "Error creating interview" }); + } + } +}); + +// Get all interviews +router.get("/", async (_req: Request, res: Response) => { + logRequest(_req); + try { + console.log("Fetching all interviews"); + const interviews = await prisma.interview.findMany({ + include: { + application: true, + }, + }); + res.json(interviews); + } catch (error) { + console.error("Error fetching interviews:", error); + res.status(500).json({ error: "Error fetching interviews" }); + } +}); + +// Get a single interview by ID +router.get("/:id", async (req: Request, res: Response) => { + logRequest(req); + const { id } = req.params; + try { + console.log(`Fetching interview with ID: ${id}`); + const interview = await prisma.interview.findUnique({ + where: { interview_id: Number(id) }, + include: { + application: true, + }, + }); + if (interview) { + res.json(interview); + } else { + console.warn(`Interview with ID ${id} not found`); + res.status(404).json({ error: "Interview not found" }); + } + } catch (error) { + console.error("Error fetching interview:", error); + res.status(500).json({ error: "Error fetching interview" }); + } +}); + +// Update an interview by ID +router.put("/:id", async (req: Request, res: Response) => { + logRequest(req); + const { id } = req.params; + try { + console.log(`Validating input data`); + const validatedData = await editInterviewSchema.validate(req.body, { + abortEarly: false, + stripUnknown: true, + }); + + console.log(`Updating interview with ID: ${id}`); + const updatedInterview = await prisma.interview.update({ + where: { interview_id: Number(id) }, + data: validatedData, + }); + + console.log(`Interview with ID ${id} updated`); + res.json(updatedInterview); + } catch (error) { + if (error instanceof yup.ValidationError) { + res.status(400).json({ + error: "Validation error", + details: error.errors, + }); + } else { + console.error("Error updating interview:", error); + res.status(400).json({ error: "Error updating interview" }); + } + } +}); + +export default router; \ No newline at end of file diff --git a/server/src/validators/interview.ts b/server/src/validators/interview.ts new file mode 100644 index 0000000..459572d --- /dev/null +++ b/server/src/validators/interview.ts @@ -0,0 +1,40 @@ +import * as yup from 'yup'; + +export const createInterviewSchema = yup.object().shape({ + application_id: yup + .number() + .integer() + .positive() + .required('Application ID is required'), + interview_dateTime: yup + .date() + .required('Interview date and time is required'), + interview_URL: yup + .string() + .url('Interview URL must be a valid URL') + .required('Interview URL is required'), + interview_decision: yup + .string() + .optional(), + interview_notes: yup + .string() + .optional(), + status: yup + .mixed<'scheduled' | 'completed' | 'canceled'>() + .oneOf(['scheduled', 'completed', 'canceled'], 'Status must be one of: scheduled, completed, canceled') + .required('Status is required'), +}); + +export const editInterviewSchema = yup.object().shape({ + interview_dateTime: yup.date().optional(), + interview_URL: yup + .string() + .url('Interview URL must be a valid URL') + .optional(), + interview_decision: yup.string().optional(), + interview_notes: yup.string().optional(), + status: yup + .mixed<'scheduled' | 'completed' | 'canceled'>() + .oneOf(['scheduled', 'completed', 'canceled'], 'Status must be one of: scheduled, completed, canceled') + .optional(), +}); \ No newline at end of file From 492e7af32e23c0db941baba3abdd11f4e4f03ad6 Mon Sep 17 00:00:00 2001 From: Jack Date: Thu, 26 Dec 2024 01:11:34 +0800 Subject: [PATCH 24/24] removed file created by accident --- prisma/seed.ts | 1 - 1 file changed, 1 deletion(-) delete mode 100644 prisma/seed.ts diff --git a/prisma/seed.ts b/prisma/seed.ts deleted file mode 100644 index 0519ecb..0000000 --- a/prisma/seed.ts +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file