From 14bcb70ea84ce03aa9be285faffc5b610636b447 Mon Sep 17 00:00:00 2001 From: ANAS Date: Mon, 11 Nov 2024 16:04:13 +0530 Subject: [PATCH 01/30] Implement global error handling middleware --- .vscode/settings.json | 6 +--- src/index.ts | 3 ++ src/middlewares/globalErrorHandler.ts | 51 +++++++++++++++++++++++++++ src/utils/error/customError.ts | 14 ++++++++ src/utils/error/errorCatch.ts | 9 +++++ src/utils/error/handleErrors.ts | 37 +++++++++++++++++++ 6 files changed, 115 insertions(+), 5 deletions(-) create mode 100644 src/middlewares/globalErrorHandler.ts create mode 100644 src/utils/error/customError.ts create mode 100644 src/utils/error/errorCatch.ts create mode 100644 src/utils/error/handleErrors.ts diff --git a/.vscode/settings.json b/.vscode/settings.json index 5d7cb19..4f37590 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,7 +1,3 @@ { - "editor.defaultFormatter": "biomejs.biome", - "cSpell.words": ["signin", "Signup"], - "[jsonc]": { - "editor.defaultFormatter": "esbenp.prettier-vscode" - } + "editor.defaultFormatter": "biomejs.biome" } diff --git a/src/index.ts b/src/index.ts index 782b739..adf14ee 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,7 @@ import dotenv from "dotenv"; import express from "express"; import mongoose from "mongoose"; +import { globalErrorHandler } from "./middlewares/globalErrorHandler"; const app = express(); @@ -14,6 +15,8 @@ app.get("/", (_req, res) => { res.send("Hello World!"); }); +app.use(globalErrorHandler); + mongoose .connect(process.env.MONGO_URI || "") .then(() => { diff --git a/src/middlewares/globalErrorHandler.ts b/src/middlewares/globalErrorHandler.ts new file mode 100644 index 0000000..c8f55fd --- /dev/null +++ b/src/middlewares/globalErrorHandler.ts @@ -0,0 +1,51 @@ +import type { NextFunction, Request, Response } from "express"; +import { CustomError } from "../utils/error/customError"; +import { castErrorHandler, duplicateKeyErrorHandler, validationErrorHandler } from "../utils/error/handleErrors"; + +const errorResponse = (error: CustomError, res: Response) => { + res.status(error.statusCode).json({ + status: error.status, + statusCode: error.statusCode, + message: error.message, + }); +}; + +export const globalErrorHandler = ( + // biome-ignore lint/suspicious/noExplicitAny: the error can be multiple types + error: any, + _req: Request, + res: Response, + _next: NextFunction, +) => { + console.log(error); + + if (error instanceof CustomError) { + errorResponse(error, res); + return; + } + + if (error.name === "CastError") { + const castError = castErrorHandler(error); + errorResponse(castError, res); + return; + } + + if (error.code === 11000) { + const duplicateKeyError = duplicateKeyErrorHandler(error); + errorResponse(duplicateKeyError, res); + return; + } + + if (error.name === "ValidationError") { + const validationError = validationErrorHandler(error); + errorResponse(validationError, res); + return; + } + + // default error if none of the above match + res.status(500).json({ + status: "fail", + statusCode: 500, + message: error?.message || "Something went wrong", + }); +}; diff --git a/src/utils/error/customError.ts b/src/utils/error/customError.ts new file mode 100644 index 0000000..c78eabe --- /dev/null +++ b/src/utils/error/customError.ts @@ -0,0 +1,14 @@ +export class CustomError extends Error { + // all custom errors will extend this class + statusCode: number; + status: string; + isOperational: boolean; + constructor(message: string, statusCode = 500) { + super(message); + this.statusCode = statusCode; + this.status = statusCode >= 400 && statusCode < 500 ? "fail" : "error"; + this.isOperational = true; + + Error.captureStackTrace(this, this.constructor); + } +} diff --git a/src/utils/error/errorCatch.ts b/src/utils/error/errorCatch.ts new file mode 100644 index 0000000..115f995 --- /dev/null +++ b/src/utils/error/errorCatch.ts @@ -0,0 +1,9 @@ +import type { NextFunction, Request, Response } from "express"; + +export const errorCatch = ( + func: (req: Request, res: Response, next: NextFunction) => Promise +) => { + return (req: Request, res: Response, next: NextFunction) => { + func(req, res, next).catch((err) => next(err)); + }; +}; diff --git a/src/utils/error/handleErrors.ts b/src/utils/error/handleErrors.ts new file mode 100644 index 0000000..b000f2a --- /dev/null +++ b/src/utils/error/handleErrors.ts @@ -0,0 +1,37 @@ +import type { CastError } from "mongoose"; +import { CustomError } from "./customError"; + +const castErrorHandler = (err: CastError) => { + // cast error means that the value is not of the correct type defined in the schema + const msg = `Invalid value for ${err.path}: ${err.value}!`; + return new CustomError(msg, 400); +}; + +const duplicateKeyErrorHandler = (err: { + keyValue: Record; +}) => { + const field = Object.keys(err.keyValue)[0]; + const value = err.keyValue[field]; + + if (field === "email") { + return new CustomError("This email is already registered! ", 400); + } + + if (field === "username") { + return new CustomError("Username is already taken!", 400); + } + + return new CustomError(`${value} already exists!`, 400); +}; + +const validationErrorHandler = (err: { + errors: { [key: string]: { message: string } }; +}) => { + const errors = Object.values(err.errors).map((val) => val.message); + const errorMessages = errors.join(". "); + const msg = `Invalid input data: ${errorMessages}`; + + return new CustomError(msg, 400); +}; + +export { castErrorHandler, duplicateKeyErrorHandler, validationErrorHandler }; From a68e265ed81a05fdae2e834270b4427b478d35f9 Mon Sep 17 00:00:00 2001 From: ANAS Date: Mon, 11 Nov 2024 16:52:22 +0530 Subject: [PATCH 02/30] configure zod validation and standard response Class --- package-lock.json | 11 ++++++++++- package.json | 3 ++- src/middlewares/zodValidation.ts | 17 +++++++++++++++++ src/utils/error/errorCatch.ts | 10 ++++------ src/utils/standardResponse.ts | 15 +++++++++++++++ src/utils/zodSchemas.ts | 15 +++++++++++++++ 6 files changed, 63 insertions(+), 8 deletions(-) create mode 100644 src/middlewares/zodValidation.ts create mode 100644 src/utils/standardResponse.ts create mode 100644 src/utils/zodSchemas.ts diff --git a/package-lock.json b/package-lock.json index 118d81e..f02e7c9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,8 @@ "dependencies": { "dotenv": "^16.4.5", "express": "^4.21.1", - "mongoose": "^8.8.1" + "mongoose": "^8.8.1", + "zod": "^3.23.8" }, "devDependencies": { "@biomejs/biome": "^1.9.4", @@ -1722,6 +1723,14 @@ "engines": { "node": ">=6" } + }, + "node_modules/zod": { + "version": "3.23.8", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz", + "integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } } } } diff --git a/package.json b/package.json index a6dbdc7..fa09632 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,8 @@ "dependencies": { "dotenv": "^16.4.5", "express": "^4.21.1", - "mongoose": "^8.8.1" + "mongoose": "^8.8.1", + "zod": "^3.23.8" }, "devDependencies": { "@biomejs/biome": "^1.9.4", diff --git a/src/middlewares/zodValidation.ts b/src/middlewares/zodValidation.ts new file mode 100644 index 0000000..e6b60b1 --- /dev/null +++ b/src/middlewares/zodValidation.ts @@ -0,0 +1,17 @@ +import type { Request, Response, NextFunction } from "express"; +import { type AnyZodObject, ZodError } from "zod"; +import { CustomError } from "../utils/error/customError"; + +export function validateData(schema: AnyZodObject) { + return (req: Request, _res: Response, next: NextFunction) => { + try { + schema.parse(req.body); + next(); + } catch (error) { + if (error instanceof ZodError) { + throw new CustomError("Invalid data", 400); + } + throw new CustomError("Error when validating data", 400); + } + }; +} diff --git a/src/utils/error/errorCatch.ts b/src/utils/error/errorCatch.ts index 115f995..ebbbd24 100644 --- a/src/utils/error/errorCatch.ts +++ b/src/utils/error/errorCatch.ts @@ -1,9 +1,7 @@ import type { NextFunction, Request, Response } from "express"; -export const errorCatch = ( - func: (req: Request, res: Response, next: NextFunction) => Promise -) => { - return (req: Request, res: Response, next: NextFunction) => { - func(req, res, next).catch((err) => next(err)); - }; +export const errorCatch = (func: (req: Request, res: Response, next: NextFunction) => Promise) => { + return (req: Request, res: Response, next: NextFunction) => { + func(req, res, next).catch((err) => next(err)); + }; }; diff --git a/src/utils/standardResponse.ts b/src/utils/standardResponse.ts new file mode 100644 index 0000000..2bcfcc6 --- /dev/null +++ b/src/utils/standardResponse.ts @@ -0,0 +1,15 @@ +export class StandardResponse { + // all responses will extend this class + statusCode: number; + status: string; + message: string; + // biome-ignore lint/suspicious/noExplicitAny: + data: any; + // biome-ignore lint/suspicious/noExplicitAny: response data can be any type + constructor(message: string, data: any, statusCode = 200) { + this.statusCode = statusCode; + this.status = "success"; + this.message = message; + this.data = data; + } +} diff --git a/src/utils/zodSchemas.ts b/src/utils/zodSchemas.ts new file mode 100644 index 0000000..46a84f4 --- /dev/null +++ b/src/utils/zodSchemas.ts @@ -0,0 +1,15 @@ +import { z } from "zod"; + +const loginSchema = z.object({ + email: z.string(), + password: z.string(), +}); + +const registerSchema = z.object({ + firstName: z.string().min(3), + lastName: z.string().optional(), + email: z.string().email(), + password: z.string().min(4), +}); + +export { loginSchema, registerSchema }; From ce2e6a755b38b8a4d5b57cb06bdd892763dd04b0 Mon Sep 17 00:00:00 2001 From: Sahad Date: Tue, 12 Nov 2024 09:49:07 +0530 Subject: [PATCH 03/30] add signin and signup functionality (#2) * create userSchema * create signin and signup controller --- package-lock.json | 716 +++++++++++++++++++++++++++++- package.json | 4 + src/controllers/authController.ts | 60 +++ src/index.ts | 22 +- src/models/userModel.ts | 33 ++ src/routes/authRoutes.ts | 13 + 6 files changed, 831 insertions(+), 17 deletions(-) create mode 100644 src/controllers/authController.ts create mode 100644 src/models/userModel.ts create mode 100644 src/routes/authRoutes.ts diff --git a/package-lock.json b/package-lock.json index f02e7c9..9fc25dc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,14 +9,18 @@ "version": "1.0.0", "license": "ISC", "dependencies": { + "bcrypt": "^5.1.1", "dotenv": "^16.4.5", "express": "^4.21.1", + "jsonwebtoken": "^9.0.2", "mongoose": "^8.8.1", "zod": "^3.23.8" }, "devDependencies": { "@biomejs/biome": "^1.9.4", + "@types/bcrypt": "^5.0.2", "@types/express": "5.0.0", + "@types/jsonwebtoken": "^9.0.7", "@types/node": "^22.9.0", "nodemon": "^3.1.7", "ts-node": "^10.9.2", @@ -215,6 +219,26 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, + "node_modules/@mapbox/node-pre-gyp": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz", + "integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==", + "license": "BSD-3-Clause", + "dependencies": { + "detect-libc": "^2.0.0", + "https-proxy-agent": "^5.0.0", + "make-dir": "^3.1.0", + "node-fetch": "^2.6.7", + "nopt": "^5.0.0", + "npmlog": "^5.0.1", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.11" + }, + "bin": { + "node-pre-gyp": "bin/node-pre-gyp" + } + }, "node_modules/@mongodb-js/saslprep": { "version": "1.1.9", "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.1.9.tgz", @@ -247,6 +271,16 @@ "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", "dev": true }, + "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", @@ -296,6 +330,16 @@ "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", "dev": true }, + "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/mime": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", @@ -357,6 +401,12 @@ "@types/webidl-conversions": "*" } }, + "node_modules/abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "license": "ISC" + }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -393,6 +443,50 @@ "node": ">=0.4.0" } }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/agent-base/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/agent-base/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/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/anymatch": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", @@ -406,6 +500,26 @@ "node": ">= 8" } }, + "node_modules/aproba": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", + "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==", + "license": "ISC" + }, + "node_modules/are-we-there-yet": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", + "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", + "deprecated": "This package is no longer supported.", + "license": "ISC", + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/arg": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", @@ -420,8 +534,21 @@ "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 + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "node_modules/bcrypt": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.1.1.tgz", + "integrity": "sha512-AGBHOG5hPYZ5Xl9KXzU5iKq9516yEmvCKDg3ecP5kX2aB6UqTeXZxk2ELnDgDm6BQSMlLt9rDB4LoSMx0rYwww==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "@mapbox/node-pre-gyp": "^1.0.11", + "node-addon-api": "^5.0.0" + }, + "engines": { + "node": ">= 10.0.0" + } }, "node_modules/binary-extensions": { "version": "2.3.0", @@ -462,7 +589,6 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -488,6 +614,12 @@ "node": ">=16.20.1" } }, + "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", @@ -538,11 +670,34 @@ "fsevents": "~2.3.2" } }, + "node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "license": "ISC", + "bin": { + "color-support": "bin.js" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + }, + "node_modules/console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", + "license": "ISC" }, "node_modules/content-disposition": { "version": "0.5.4", @@ -606,6 +761,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", + "license": "MIT" + }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -623,6 +784,15 @@ "npm": "1.2.8000 || >= 1.4.16" } }, + "node_modules/detect-libc": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", + "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, "node_modules/diff": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", @@ -643,11 +813,26 @@ "url": "https://dotenvx.com" } }, + "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==" }, + "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==", + "license": "MIT" + }, "node_modules/encodeurl": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", @@ -774,6 +959,36 @@ "node": ">= 0.6" } }, + "node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "license": "ISC" + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -796,6 +1011,27 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/gauge": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", + "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", + "deprecated": "This package is no longer supported.", + "license": "ISC", + "dependencies": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.2", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.1", + "object-assign": "^4.1.1", + "signal-exit": "^3.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.2" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/get-intrinsic": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", @@ -814,6 +1050,27 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/glob": { + "version": "7.2.3", + "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", + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/glob-parent": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", @@ -879,6 +1136,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", + "license": "ISC" + }, "node_modules/hasown": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", @@ -905,6 +1168,42 @@ "node": ">= 0.8" } }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/https-proxy-agent/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/https-proxy-agent/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/iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -922,6 +1221,17 @@ "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", "dev": true }, + "node_modules/inflight": { + "version": "1.0.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.", + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", @@ -956,6 +1266,15 @@ "node": ">=0.10.0" } }, + "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==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", @@ -977,6 +1296,55 @@ "node": ">=0.12.0" } }, + "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/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/kareem": { "version": "2.6.3", "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.6.3.tgz", @@ -985,6 +1353,72 @@ "node": ">=12.0.0" } }, + "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/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "license": "MIT", + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/make-error": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", @@ -1054,7 +1488,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, "dependencies": { "brace-expansion": "^1.1.7" }, @@ -1062,6 +1495,52 @@ "node": "*" } }, + "node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "license": "ISC", + "engines": { + "node": ">=8" + } + }, + "node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "license": "MIT", + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "license": "MIT", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/mongodb": { "version": "6.10.0", "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.10.0.tgz", @@ -1195,6 +1674,54 @@ "node": ">= 0.6" } }, + "node_modules/node-addon-api": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz", + "integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==", + "license": "MIT" + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-fetch/node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, + "node_modules/node-fetch/node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, + "node_modules/node-fetch/node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/nodemon": { "version": "3.1.7", "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.7.tgz", @@ -1246,6 +1773,21 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true }, + "node_modules/nopt": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", + "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "license": "ISC", + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -1255,6 +1797,28 @@ "node": ">=0.10.0" } }, + "node_modules/npmlog": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", + "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", + "deprecated": "This package is no longer supported.", + "license": "ISC", + "dependencies": { + "are-we-there-yet": "^2.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^3.0.0", + "set-blocking": "^2.0.0" + } + }, + "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==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/object-inspect": { "version": "1.13.3", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz", @@ -1277,6 +1841,15 @@ "node": ">= 0.8" } }, + "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==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -1285,6 +1858,15 @@ "node": ">= 0.8" } }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/path-to-regexp": { "version": "0.1.10", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", @@ -1364,6 +1946,20 @@ "node": ">= 0.8" } }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -1376,6 +1972,22 @@ "node": ">=8.10.0" } }, + "node_modules/rimraf": { + "version": "3.0.2", + "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", + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "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", @@ -1404,7 +2016,6 @@ "version": "7.6.3", "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", - "dev": true, "bin": { "semver": "bin/semver.js" }, @@ -1462,6 +2073,12 @@ "node": ">= 0.8.0" } }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", + "license": "ISC" + }, "node_modules/set-function-length": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", @@ -1505,6 +2122,12 @@ "resolved": "https://registry.npmjs.org/sift/-/sift-17.1.3.tgz", "integrity": "sha512-Rtlj66/b0ICeFzYTuNvX/EF1igRbbnGSvEyT79McoZa/DeGhMyC5pWKOEsZKnpkqtSeovd5FL/bjHWC3CIIvCQ==" }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "license": "ISC" + }, "node_modules/simple-update-notifier": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", @@ -1533,6 +2156,41 @@ "node": ">= 0.8" } }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "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==", + "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/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -1545,6 +2203,23 @@ "node": ">=4" } }, + "node_modules/tar": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "license": "ISC", + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -1673,6 +2348,12 @@ "node": ">= 0.8" } }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, "node_modules/utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", @@ -1715,6 +2396,27 @@ "node": ">=16" } }, + "node_modules/wide-align": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "license": "ISC", + "dependencies": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC" + }, "node_modules/yn": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", diff --git a/package.json b/package.json index fa09632..b263ab8 100644 --- a/package.json +++ b/package.json @@ -12,14 +12,18 @@ "author": "", "license": "ISC", "dependencies": { + "bcrypt": "^5.1.1", "dotenv": "^16.4.5", "express": "^4.21.1", + "jsonwebtoken": "^9.0.2", "mongoose": "^8.8.1", "zod": "^3.23.8" }, "devDependencies": { "@biomejs/biome": "^1.9.4", + "@types/bcrypt": "^5.0.2", "@types/express": "5.0.0", + "@types/jsonwebtoken": "^9.0.7", "@types/node": "^22.9.0", "nodemon": "^3.1.7", "ts-node": "^10.9.2", diff --git a/src/controllers/authController.ts b/src/controllers/authController.ts new file mode 100644 index 0000000..ee420b4 --- /dev/null +++ b/src/controllers/authController.ts @@ -0,0 +1,60 @@ +import type { Request, Response } from "express"; +import bcrypt from "bcrypt"; +import { User } from "../models/userModel"; +import { StandardResponse } from "../utils/standardResponse"; +import { CustomError } from "../utils/error/customError"; +import jwt from "jsonwebtoken"; + +//Register user +export const register = async (req: Request, res: Response) => { + const { firstName, lastName, email, password } = req.body; + + const saltRounds = Number(process.env.SALT_ROUNDS || "10"); + const hashedPassword = await bcrypt.hash(password, saltRounds); + + const user = new User({ + firstName, + lastName, + email, + password: hashedPassword, + }); + await user.save(); + + res.status(201).json(new StandardResponse("User created successfully", user)); +}; + +//Login user +export const login = async (req: Request, res: Response) => { + const { email, password } = req.body; + + const user = await User.findOne({ email }); + + if (!user) { + throw new CustomError("User not found", 400); + } + + if (user) { + const isPasswordCorrect = await bcrypt.compare(password, user.password); + if (!isPasswordCorrect) { + throw new CustomError("Invalid credentials", 400); + } + const token = jwt.sign( + { + id: user._id, + }, + process.env.JWT_SECRET_KEY || "", + { + expiresIn: "1d", + } + ); + + const response = { + firstName: user.firstName, + lastName: user.lastName, + email: user.email, + token, + }; + + res.status(200).json(new StandardResponse("Login successful", response)); + } +}; diff --git a/src/index.ts b/src/index.ts index adf14ee..70ba092 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,6 +2,7 @@ import dotenv from "dotenv"; import express from "express"; import mongoose from "mongoose"; import { globalErrorHandler } from "./middlewares/globalErrorHandler"; +import authRoutes from "./routes/authRoutes"; const app = express(); @@ -12,18 +13,19 @@ const port = process.env.PORT || 3001; app.use(express.json()); app.get("/", (_req, res) => { - res.send("Hello World!"); + res.send("Hello World!"); }); +app.use("/auth", authRoutes); app.use(globalErrorHandler); mongoose - .connect(process.env.MONGO_URI || "") - .then(() => { - app.listen(port, () => { - console.log(`Server is running on port ${port}`); - }); - }) - .catch((err) => { - console.error(err); - }); + .connect(process.env.MONGO_URI || "") + .then(() => { + app.listen(port, () => { + console.log(`Server is running on port ${port}`); + }); + }) + .catch((err) => { + console.error(err); + }); diff --git a/src/models/userModel.ts b/src/models/userModel.ts new file mode 100644 index 0000000..ac95940 --- /dev/null +++ b/src/models/userModel.ts @@ -0,0 +1,33 @@ +import mongoose, { type Document, Schema } from "mongoose"; + +export interface IUser extends Document { + firstName: string; + lastName: string; + email: string; + password: string; +} + +const userSchema: Schema = new Schema( + { + firstName: { + type: String, + required: true, + }, + lastName: { + type: String, + required: true, + }, + email: { + type: String, + required: true, + unique: true, + }, + password: { + type: String, + required: true, + }, + }, + { timestamps: true } +); + +export const User = mongoose.model("User", userSchema); diff --git a/src/routes/authRoutes.ts b/src/routes/authRoutes.ts new file mode 100644 index 0000000..9a64ac4 --- /dev/null +++ b/src/routes/authRoutes.ts @@ -0,0 +1,13 @@ +import express from "express"; +import { login, register } from "../controllers/authController"; +import { errorCatch } from "../utils/error/errorCatch"; +import { validateData } from "../middlewares/zodValidation"; +import { loginSchema, registerSchema } from "../utils/zodSchemas"; + +const router = express.Router(); + +//Register user +router.post("/register", validateData(registerSchema), errorCatch(register)); +router.post("/login", validateData(loginSchema), errorCatch(login)); + +export default router; From c8f400b031cd3083278999f90afa7cdfa4780ef9 Mon Sep 17 00:00:00 2001 From: Sinan Date: Tue, 12 Nov 2024 09:57:31 +0530 Subject: [PATCH 04/30] Add Mongoose Schemas (#3) * created mongoose shemas * updated chat schema --- src/models/chatModel.ts | 32 ++++++++++++++++++++++++++++++++ src/models/listModel.ts | 24 ++++++++++++++++++++++++ src/models/spaceModel.ts | 26 ++++++++++++++++++++++++++ src/models/taskModel.ts | 22 ++++++++++++++++++++++ src/models/workspaceModel.ts | 24 ++++++++++++++++++++++++ 5 files changed, 128 insertions(+) create mode 100644 src/models/chatModel.ts create mode 100644 src/models/listModel.ts create mode 100644 src/models/spaceModel.ts create mode 100644 src/models/taskModel.ts create mode 100644 src/models/workspaceModel.ts diff --git a/src/models/chatModel.ts b/src/models/chatModel.ts new file mode 100644 index 0000000..7285b71 --- /dev/null +++ b/src/models/chatModel.ts @@ -0,0 +1,32 @@ +import mongoose, { type Document, Schema, type Types } from "mongoose"; + +interface IMessage { + content: string; + timestamp: Date; + sender: Types.ObjectId; +} + +interface IChat extends Document { + workspaceId: Types.ObjectId; + messages: IMessage[]; + createdAt: Date; + updatedAt: Date; +} + +const chatSchema: Schema = new Schema({ + workspaceId: { + type: Schema.Types.ObjectId, + ref: "workspace", + required: true, + }, + messages: [ + { + content: { type: String, required: true }, + timestamp: { type: Date, required: true }, + sender: { type: Schema.Types.ObjectId, ref: "user", required: true }, + }, + ], +}); + +const Chat = mongoose.model("Chat", chatSchema); +export default Chat; diff --git a/src/models/listModel.ts b/src/models/listModel.ts new file mode 100644 index 0000000..e48e674 --- /dev/null +++ b/src/models/listModel.ts @@ -0,0 +1,24 @@ +import mongoose, { type Document, Schema, type Types } from "mongoose"; + +interface IList extends Document { + spaceId: Types.ObjectId; + name: string; + description?: string; + color?: string[]; + task: Types.ObjectId[]; + createdAt: Date; + updatedAt: Date; +} + +const listsSchema: Schema = new Schema({ + spaceId: { type: Schema.Types.ObjectId, ref: "space", required: true }, + name: { type: String, required: true }, + description: { type: String }, + color: { type: [String] }, + task: [{ type: Schema.Types.ObjectId, ref: "task" }], + createdAt: { type: Date, required: true }, + updatedAt: { type: Date, required: true }, +}); + +const List = mongoose.model("List", listsSchema); +export default List; diff --git a/src/models/spaceModel.ts b/src/models/spaceModel.ts new file mode 100644 index 0000000..7c6cf0b --- /dev/null +++ b/src/models/spaceModel.ts @@ -0,0 +1,26 @@ +import mongoose, { type Document, Schema, type Types } from "mongoose"; + +interface ISpace extends Document { + workspaceId: Types.ObjectId; + name: string; + description?: string; + lists: Types.ObjectId[]; + createdAt: Date; + updatedAt: Date; +} + +const spaceSchema: Schema = new Schema({ + workspaceId: { + type: Schema.Types.ObjectId, + ref: "workspace", + required: true, + }, + name: { type: String, required: true }, + description: { type: String }, + lists: [{ type: Schema.Types.ObjectId, ref: "lists", required: true }], + createdAt: { type: Date, required: true }, + updatedAt: { type: Date, required: true }, +}); + +const Space = mongoose.model("Space", spaceSchema); +export default Space; diff --git a/src/models/taskModel.ts b/src/models/taskModel.ts new file mode 100644 index 0000000..7cca6e9 --- /dev/null +++ b/src/models/taskModel.ts @@ -0,0 +1,22 @@ +import mongoose, { type Document, Schema, type Types } from "mongoose"; + +interface ITask extends Document { + listId: Types.ObjectId; + title: string; + description?: string; + dueDate?: Date; + createdAt: Date; + updatedAt: Date; +} + +const taskSchema: Schema = new Schema({ + listId: { type: Schema.Types.ObjectId, ref: "list", required: true }, + title: { type: String, required: true }, + description: { type: String }, + dueDate: { type: Date }, + createdAt: { type: Date, required: true }, + updatedAt: { type: Date, required: true }, +}); + +const Task = mongoose.model("Task", taskSchema); +export default Task; diff --git a/src/models/workspaceModel.ts b/src/models/workspaceModel.ts new file mode 100644 index 0000000..07dcc52 --- /dev/null +++ b/src/models/workspaceModel.ts @@ -0,0 +1,24 @@ +import mongoose, { type Document, Schema } from "mongoose"; + +interface IWorkspace extends Document { + name: string; + description?: string; + members: mongoose.Schema.Types.ObjectId[]; + spaces: mongoose.Schema.Types.ObjectId[]; + createdAt: Date; + updatedAt: Date; +} + +const workspaceSchema: Schema = new Schema({ + name: { type: String, required: true }, + description: { type: String }, + members: [ + { type: mongoose.Schema.Types.ObjectId, ref: "user", required: true }, + ], + spaces: [{ type: mongoose.Schema.Types.ObjectId, ref: "space" }], + createdAt: { type: Date, required: true }, + updatedAt: { type: Date, required: true }, +}); + +const Workspace = mongoose.model("Workspace", workspaceSchema); +export default Workspace; From bfafcaf2e11d16e18e04cd14c980d73ae7f5bf7d Mon Sep 17 00:00:00 2001 From: Anas P <128923778+anaspxr@users.noreply.github.com> Date: Tue, 12 Nov 2024 16:10:26 +0530 Subject: [PATCH 05/30] Authentication and Authorization updates (#6) * create verify token middleware for authorization * Add OTP verification functionality with node mailer * Implement forgot and reset password functionality --- package-lock.json | 35 +++ package.json | 4 + src/controllers/authController.ts | 296 ++++++++++++++++++++++---- src/index.ts | 20 +- src/middlewares/globalErrorHandler.ts | 1 + src/middlewares/verifyToken.ts | 18 ++ src/models/otpModel.ts | 17 ++ src/models/userModel.ts | 53 ++--- src/routes/authRoutes.ts | 8 +- src/types/interfaces.ts | 6 + src/utils/error/customError.ts | 4 +- src/utils/error/handleErrors.ts | 8 - src/utils/mailSender.ts | 32 +++ src/utils/standardResponse.ts | 4 +- src/utils/zodSchemas.ts | 7 +- 15 files changed, 420 insertions(+), 93 deletions(-) create mode 100644 src/middlewares/verifyToken.ts create mode 100644 src/models/otpModel.ts create mode 100644 src/types/interfaces.ts create mode 100644 src/utils/mailSender.ts diff --git a/package-lock.json b/package-lock.json index 9fc25dc..70016c3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,8 @@ "express": "^4.21.1", "jsonwebtoken": "^9.0.2", "mongoose": "^8.8.1", + "nodemailer": "^6.9.16", + "otp-generator": "^4.0.1", "zod": "^3.23.8" }, "devDependencies": { @@ -22,6 +24,8 @@ "@types/express": "5.0.0", "@types/jsonwebtoken": "^9.0.7", "@types/node": "^22.9.0", + "@types/nodemailer": "^6.4.16", + "@types/otp-generator": "^4.0.2", "nodemon": "^3.1.7", "ts-node": "^10.9.2", "typescript": "^5.6.3" @@ -355,6 +359,21 @@ "undici-types": "~6.19.8" } }, + "node_modules/@types/nodemailer": { + "version": "6.4.16", + "resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-6.4.16.tgz", + "integrity": "sha512-uz6hN6Pp0upXMcilM61CoKyjT7sskBoOWpptkjjJp8jIMlTdc3xG01U7proKkXzruMS4hS0zqtHNkNPFB20rKQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/otp-generator": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/otp-generator/-/otp-generator-4.0.2.tgz", + "integrity": "sha512-9+qqWzuFb332hXPbLgjUyOXlbcaTQkmkmqQjTduvNuOmPV5fW+iLv70JsVEhdUy0DWi4kY34++HDCaWl6N0AYg==", + "dev": true + }, "node_modules/@types/qs": { "version": "6.9.17", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.17.tgz", @@ -1722,6 +1741,14 @@ "webidl-conversions": "^3.0.0" } }, + "node_modules/nodemailer": { + "version": "6.9.16", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.16.tgz", + "integrity": "sha512-psAuZdTIRN08HKVd/E8ObdV6NO7NTBY3KsC30F7M4H1OnmLCUNaS56FpYxyb26zWLSyYF9Ozch9KYHhHegsiOQ==", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/nodemon": { "version": "3.1.7", "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.7.tgz", @@ -1850,6 +1877,14 @@ "wrappy": "1" } }, + "node_modules/otp-generator": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/otp-generator/-/otp-generator-4.0.1.tgz", + "integrity": "sha512-2TJ52vUftA0+J3eque4wwVtpaL4/NdIXDL0gFWFJFVUAZwAN7+9tltMhL7GCNYaHJtuONoier8Hayyj4HLbSag==", + "engines": { + "node": ">=14.10.0" + } + }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", diff --git a/package.json b/package.json index b263ab8..8ecf18f 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,8 @@ "express": "^4.21.1", "jsonwebtoken": "^9.0.2", "mongoose": "^8.8.1", + "nodemailer": "^6.9.16", + "otp-generator": "^4.0.1", "zod": "^3.23.8" }, "devDependencies": { @@ -25,6 +27,8 @@ "@types/express": "5.0.0", "@types/jsonwebtoken": "^9.0.7", "@types/node": "^22.9.0", + "@types/nodemailer": "^6.4.16", + "@types/otp-generator": "^4.0.2", "nodemon": "^3.1.7", "ts-node": "^10.9.2", "typescript": "^5.6.3" diff --git a/src/controllers/authController.ts b/src/controllers/authController.ts index ee420b4..424a0ca 100644 --- a/src/controllers/authController.ts +++ b/src/controllers/authController.ts @@ -4,57 +4,263 @@ import { User } from "../models/userModel"; import { StandardResponse } from "../utils/standardResponse"; import { CustomError } from "../utils/error/customError"; import jwt from "jsonwebtoken"; +import mailSender from "../utils/mailSender"; +import { Otp } from "../models/otpModel"; +import otpGenerator from "otp-generator"; //Register user -export const register = async (req: Request, res: Response) => { - const { firstName, lastName, email, password } = req.body; +const register = async (req: Request, res: Response) => { + const { firstName, lastName, email, password } = req.body; - const saltRounds = Number(process.env.SALT_ROUNDS || "10"); - const hashedPassword = await bcrypt.hash(password, saltRounds); + const saltRounds = Number(process.env.SALT_ROUNDS || "10"); + const hashedPassword = await bcrypt.hash(password, saltRounds); - const user = new User({ - firstName, - lastName, - email, - password: hashedPassword, - }); - await user.save(); + const existingUser = await User.findOne({ email }); - res.status(201).json(new StandardResponse("User created successfully", user)); + if (existingUser) { + if (!existingUser.verified) throw new CustomError("User already exists but not verified", 400, "USER_NOT_VERIFIED"); + + throw new CustomError("Email already exists", 400); + } + + const user = new User({ + firstName, + lastName, + email, + password: hashedPassword, + }); + await user.save(); + + const otp = otpGenerator.generate(6, { upperCaseAlphabets: false, specialChars: false, lowerCaseAlphabets: false }); + + const otpDoc = new Otp({ + email, + otp, + }); + + await otpDoc.save(); + + mailSender( + email, + "Verify your email", + ` +
+

Verify your email

+

Please enter the following OTP to verify your email:

+

${otp}

+

expires in 10 minutes

+

Thank you for registering with us!

+
+ `, + ); + + res.status(201).json( + new StandardResponse("Otp send successfully!", { + email: user.email, + firstName: user.firstName, + lastName: user.lastName, + }), + ); }; //Login user -export const login = async (req: Request, res: Response) => { - const { email, password } = req.body; - - const user = await User.findOne({ email }); - - if (!user) { - throw new CustomError("User not found", 400); - } - - if (user) { - const isPasswordCorrect = await bcrypt.compare(password, user.password); - if (!isPasswordCorrect) { - throw new CustomError("Invalid credentials", 400); - } - const token = jwt.sign( - { - id: user._id, - }, - process.env.JWT_SECRET_KEY || "", - { - expiresIn: "1d", - } - ); - - const response = { - firstName: user.firstName, - lastName: user.lastName, - email: user.email, - token, - }; - - res.status(200).json(new StandardResponse("Login successful", response)); - } +const login = async (req: Request, res: Response) => { + const { email, password } = req.body; + + const user = await User.findOne({ email }); + + if (!user) { + throw new CustomError("User not found", 400); + } + + if (user) { + const isPasswordCorrect = await bcrypt.compare(password, user.password); + if (!isPasswordCorrect) { + throw new CustomError("Invalid credentials", 400); + } + + if (!user.verified) { + throw new CustomError("User not verified", 400, "USER_NOT_VERIFIED"); + } + + const token = jwt.sign( + { + id: user._id, + }, + process.env.JWT_SECRET_KEY || "", + { + expiresIn: "1d", + }, + ); + + const response = { + firstName: user.firstName, + lastName: user.lastName, + email: user.email, + token, + }; + + res.status(200).json(new StandardResponse("Login successful", response)); + } }; + +// Verify OTP for email verification +const verifyOtp = async (req: Request, res: Response) => { + const { email, otp } = req.body; + + const user = await User.findOne({ email }); + + if (!user) { + throw new CustomError("User not found", 400); + } + + if (user.verified) { + throw new CustomError("User already verified", 400, "USER_ALREADY_VERIFIED"); + } + const otpDoc = await Otp.findOne({ email }); + + if (!otpDoc) { + throw new CustomError("Email not found", 400); + } + + // Check if OTP is expired + const currentTime = new Date().getTime(); + const otpUpdatedTime = new Date(otpDoc.updatedAt).getTime(); + const timeDifference = (currentTime - otpUpdatedTime) / 1000 / 60; + + if (timeDifference > 10) { + // 10 minutes + throw new CustomError("OTP expired", 400); + } + + if (otp !== otpDoc.otp) { + throw new CustomError("Invalid OTP", 400); + } + + await Promise.all([user.updateOne({ verified: true }), otpDoc.deleteOne()]); + + res.status(200).json(new StandardResponse("Email verified successfully!")); +}; + +// Resend OTP +const reSendOtp = async (req: Request, res: Response) => { + if (!req.body.email) { + throw new CustomError("Email is required", 400); + } + + const { email } = req.body; + + const existingUser = await User.findOne({ email }); + + if (!existingUser) { + throw new CustomError("User not found", 400); + } + + if (existingUser.verified) { + throw new CustomError("User already verified", 400, "USER_ALREADY_VERIFIED"); + } + + const otp = otpGenerator.generate(6, { upperCaseAlphabets: false, specialChars: false, lowerCaseAlphabets: false }); + + const otpDoc = await Otp.findOne({ email }); + + mailSender( + email, + "Verify your email", + ` +
+

Verify your email

+

Please enter the following OTP to verify your email:

+

${otp}

+

expires in 10 minutes

+

Thank you for registering with us!

+
+ `, + ); + + if (otpDoc) { + await otpDoc.updateOne({ otp }); + } else { + const newOtp = new Otp({ + email, + otp, + }); + + await newOtp.save(); + } + + res.status(200).json( + new StandardResponse("Otp send successfully!", { + email, + }), + ); +}; + +// forgot password +const forgotPassword = async (req: Request, res: Response) => { + const { email } = req.body; + + if (!email) throw new CustomError("Email is required", 400); + + const user = await User.findOne({ email }); + + if (!user) { + throw new CustomError("User not found", 400); + } + + const resetToken = jwt.sign( + { + id: user._id, + }, + process.env.JWT_SECRET_KEY || "", + { + expiresIn: "10m", + }, + ); + + mailSender( + email, + "Reset your password", + ` +
+

Reset your password

+

Please click on the following link to reset your password:

+ Reset password +

expires in 10 minutes

+

Don't share this link with anyone!!

+

Thank you!

+
+ `, + ); + + res.status(200).json(new StandardResponse("Reset link sent successfully!")); +}; + +// reset the password using the token +const resetPassword = async (req: Request, res: Response) => { + const token = req.params.token; + + if (!token) throw new CustomError("Invalid token", 400); + + const { newPassword } = req.body; + + if (!newPassword) throw new CustomError("New Password is required", 400); + + const decoded = jwt.verify(token, process.env.JWT_SECRET_KEY || "") as { id: string }; + console.log(decoded); + + const user = await User.findById(decoded.id); + + if (!user) { + throw new CustomError("User not found", 400); + } + + const saltRounds = Number(process.env.SALT_ROUNDS || "10"); + const hashedPassword = await bcrypt.hash(newPassword, saltRounds); + + await user.updateOne({ password: hashedPassword }); + + res.status(200).json(new StandardResponse("Password reset successfully!")); +}; + +export { register, login, reSendOtp, verifyOtp, forgotPassword, resetPassword }; diff --git a/src/index.ts b/src/index.ts index 70ba092..fa8d126 100644 --- a/src/index.ts +++ b/src/index.ts @@ -13,19 +13,19 @@ const port = process.env.PORT || 3001; app.use(express.json()); app.get("/", (_req, res) => { - res.send("Hello World!"); + res.send("Hello World!"); }); app.use("/auth", authRoutes); app.use(globalErrorHandler); mongoose - .connect(process.env.MONGO_URI || "") - .then(() => { - app.listen(port, () => { - console.log(`Server is running on port ${port}`); - }); - }) - .catch((err) => { - console.error(err); - }); + .connect(process.env.MONGO_URI || "") + .then(() => { + app.listen(port, () => { + console.log(`Server is running on port ${port}`); + }); + }) + .catch((err) => { + console.error(err); + }); diff --git a/src/middlewares/globalErrorHandler.ts b/src/middlewares/globalErrorHandler.ts index c8f55fd..742638a 100644 --- a/src/middlewares/globalErrorHandler.ts +++ b/src/middlewares/globalErrorHandler.ts @@ -7,6 +7,7 @@ const errorResponse = (error: CustomError, res: Response) => { status: error.status, statusCode: error.statusCode, message: error.message, + errorCode: error.errorCode, }); }; diff --git a/src/middlewares/verifyToken.ts b/src/middlewares/verifyToken.ts new file mode 100644 index 0000000..8d3af6a --- /dev/null +++ b/src/middlewares/verifyToken.ts @@ -0,0 +1,18 @@ +import type { NextFunction, Request, Response } from "express"; +import jwt from "jsonwebtoken"; +import type { CustomRequest } from "../types/interfaces"; +import { CustomError } from "../utils/error/customError"; + +export const verifyToken = (req: Request, _res: Response, next: NextFunction) => { + const token = req.header("Authorization")?.split(" ")[1]; + if (!token) { + throw new CustomError("Not authenticated", 401); + } + try { + const verified = jwt.verify(token, process.env.JWT_SECRET_KEY || ""); + (req as CustomRequest).user = verified; + next(); + } catch (_err) { + throw new CustomError("Invalid token", 401); + } +}; diff --git a/src/models/otpModel.ts b/src/models/otpModel.ts new file mode 100644 index 0000000..d36c460 --- /dev/null +++ b/src/models/otpModel.ts @@ -0,0 +1,17 @@ +import { type Document, model, Schema } from "mongoose"; + +interface IOtp extends Document { + email: string; + otp: string; + updatedAt: Date; +} + +const otpSchema: Schema = new Schema( + { + email: { type: String, required: true }, + otp: { type: String, required: true }, + }, + { timestamps: true }, +); + +export const Otp = model("Otp", otpSchema); diff --git a/src/models/userModel.ts b/src/models/userModel.ts index ac95940..56cb667 100644 --- a/src/models/userModel.ts +++ b/src/models/userModel.ts @@ -1,33 +1,38 @@ import mongoose, { type Document, Schema } from "mongoose"; export interface IUser extends Document { - firstName: string; - lastName: string; - email: string; - password: string; + firstName: string; + lastName: string; + email: string; + password: string; + verified: boolean; } const userSchema: Schema = new Schema( - { - firstName: { - type: String, - required: true, - }, - lastName: { - type: String, - required: true, - }, - email: { - type: String, - required: true, - unique: true, - }, - password: { - type: String, - required: true, - }, - }, - { timestamps: true } + { + firstName: { + type: String, + required: true, + }, + lastName: { + type: String, + required: true, + }, + email: { + type: String, + required: true, + unique: true, + }, + password: { + type: String, + required: true, + }, + verified: { + type: Boolean, + default: false, + }, + }, + { timestamps: true }, ); export const User = mongoose.model("User", userSchema); diff --git a/src/routes/authRoutes.ts b/src/routes/authRoutes.ts index 9a64ac4..4a68399 100644 --- a/src/routes/authRoutes.ts +++ b/src/routes/authRoutes.ts @@ -1,13 +1,17 @@ import express from "express"; -import { login, register } from "../controllers/authController"; +import { forgotPassword, login, register, reSendOtp, resetPassword, verifyOtp } from "../controllers/authController"; import { errorCatch } from "../utils/error/errorCatch"; import { validateData } from "../middlewares/zodValidation"; -import { loginSchema, registerSchema } from "../utils/zodSchemas"; +import { loginSchema, otpSchema, registerSchema } from "../utils/zodSchemas"; const router = express.Router(); //Register user router.post("/register", validateData(registerSchema), errorCatch(register)); router.post("/login", validateData(loginSchema), errorCatch(login)); +router.post("/verifyotp", validateData(otpSchema), errorCatch(verifyOtp)); +router.post("/resendotp", errorCatch(reSendOtp)); +router.post("/forgotpassword", errorCatch(forgotPassword)); +router.post("/resetpassword/:token", errorCatch(resetPassword)); export default router; diff --git a/src/types/interfaces.ts b/src/types/interfaces.ts new file mode 100644 index 0000000..bd22cd0 --- /dev/null +++ b/src/types/interfaces.ts @@ -0,0 +1,6 @@ +import type { Request } from "express"; +import type { JwtPayload } from "jsonwebtoken"; + +export interface CustomRequest extends Request { + user?: JwtPayload | string; +} diff --git a/src/utils/error/customError.ts b/src/utils/error/customError.ts index c78eabe..303131a 100644 --- a/src/utils/error/customError.ts +++ b/src/utils/error/customError.ts @@ -2,11 +2,13 @@ export class CustomError extends Error { // all custom errors will extend this class statusCode: number; status: string; + errorCode?: string; isOperational: boolean; - constructor(message: string, statusCode = 500) { + constructor(message: string, statusCode = 500, errorCode?: string) { super(message); this.statusCode = statusCode; this.status = statusCode >= 400 && statusCode < 500 ? "fail" : "error"; + this.errorCode = errorCode; this.isOperational = true; Error.captureStackTrace(this, this.constructor); diff --git a/src/utils/error/handleErrors.ts b/src/utils/error/handleErrors.ts index b000f2a..768aa46 100644 --- a/src/utils/error/handleErrors.ts +++ b/src/utils/error/handleErrors.ts @@ -13,14 +13,6 @@ const duplicateKeyErrorHandler = (err: { const field = Object.keys(err.keyValue)[0]; const value = err.keyValue[field]; - if (field === "email") { - return new CustomError("This email is already registered! ", 400); - } - - if (field === "username") { - return new CustomError("Username is already taken!", 400); - } - return new CustomError(`${value} already exists!`, 400); }; diff --git a/src/utils/mailSender.ts b/src/utils/mailSender.ts new file mode 100644 index 0000000..30f2629 --- /dev/null +++ b/src/utils/mailSender.ts @@ -0,0 +1,32 @@ +import nodemailer from "nodemailer"; +import { CustomError } from "./error/customError"; + +const mailSender = async (email: string, title: string, body: string) => { + try { + // Create a Transporter to send emails + const transporter = nodemailer.createTransport({ + service: "gmail", + host: "smtp.gmail.com", + port: 587, + secure: false, + auth: { + user: process.env.MAIL_USER, + pass: process.env.MAIL_PASS, + }, + }); + + // Send emails to users + const info = await transporter.sendMail({ + from: "DOQUE", + to: email, + subject: title, + html: body, + }); + return info; + } catch (_error) { + console.log(_error); + throw new CustomError("Error when sending Email", 500); + } +}; + +export default mailSender; diff --git a/src/utils/standardResponse.ts b/src/utils/standardResponse.ts index 2bcfcc6..623e073 100644 --- a/src/utils/standardResponse.ts +++ b/src/utils/standardResponse.ts @@ -4,9 +4,9 @@ export class StandardResponse { status: string; message: string; // biome-ignore lint/suspicious/noExplicitAny: - data: any; + data?: any; // biome-ignore lint/suspicious/noExplicitAny: response data can be any type - constructor(message: string, data: any, statusCode = 200) { + constructor(message: string, data?: any, statusCode = 200) { this.statusCode = statusCode; this.status = "success"; this.message = message; diff --git a/src/utils/zodSchemas.ts b/src/utils/zodSchemas.ts index 46a84f4..a5848f3 100644 --- a/src/utils/zodSchemas.ts +++ b/src/utils/zodSchemas.ts @@ -12,4 +12,9 @@ const registerSchema = z.object({ password: z.string().min(4), }); -export { loginSchema, registerSchema }; +const otpSchema = z.object({ + email: z.string().email(), + otp: z.string(), +}); + +export { loginSchema, registerSchema, otpSchema }; From ed195859fb0795f1b1d769793c097ebaa110e093 Mon Sep 17 00:00:00 2001 From: Sahad Date: Tue, 12 Nov 2024 16:54:14 +0530 Subject: [PATCH 06/30] Space CRUD functionality (#7) * created controllers and routes for space * add middleware for routes --- .vscode/settings.json | 5 +- src/controllers/authController.ts | 404 +++++++++++++++-------------- src/controllers/spaceController.ts | 62 +++++ src/index.ts | 2 + src/models/spaceModel.ts | 31 +-- src/routes/spaceRoutes.ts | 33 +++ src/utils/error/handleErrors.ts | 24 +- src/utils/zodSchemas.ts | 24 +- 8 files changed, 356 insertions(+), 229 deletions(-) create mode 100644 src/controllers/spaceController.ts create mode 100644 src/routes/spaceRoutes.ts diff --git a/.vscode/settings.json b/.vscode/settings.json index 4f37590..b048cd8 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,6 @@ { - "editor.defaultFormatter": "biomejs.biome" + "editor.defaultFormatter": "biomejs.biome", + "[typescript]": { + "editor.defaultFormatter": "biomejs.biome" + } } diff --git a/src/controllers/authController.ts b/src/controllers/authController.ts index 424a0ca..bb3a5e9 100644 --- a/src/controllers/authController.ts +++ b/src/controllers/authController.ts @@ -10,40 +10,49 @@ import otpGenerator from "otp-generator"; //Register user const register = async (req: Request, res: Response) => { - const { firstName, lastName, email, password } = req.body; - - const saltRounds = Number(process.env.SALT_ROUNDS || "10"); - const hashedPassword = await bcrypt.hash(password, saltRounds); - - const existingUser = await User.findOne({ email }); - - if (existingUser) { - if (!existingUser.verified) throw new CustomError("User already exists but not verified", 400, "USER_NOT_VERIFIED"); - - throw new CustomError("Email already exists", 400); - } - - const user = new User({ - firstName, - lastName, - email, - password: hashedPassword, - }); - await user.save(); - - const otp = otpGenerator.generate(6, { upperCaseAlphabets: false, specialChars: false, lowerCaseAlphabets: false }); - - const otpDoc = new Otp({ - email, - otp, - }); - - await otpDoc.save(); - - mailSender( - email, - "Verify your email", - ` + const { firstName, lastName, email, password } = req.body; + + const saltRounds = Number(process.env.SALT_ROUNDS || "10"); + const hashedPassword = await bcrypt.hash(password, saltRounds); + + const existingUser = await User.findOne({ email }); + + if (existingUser) { + if (!existingUser.verified) + throw new CustomError( + "User already exists but not verified", + 400, + "USER_NOT_VERIFIED" + ); + + throw new CustomError("Email already exists", 400); + } + + const user = new User({ + firstName, + lastName, + email, + password: hashedPassword, + }); + await user.save(); + + const otp = otpGenerator.generate(6, { + upperCaseAlphabets: false, + specialChars: false, + lowerCaseAlphabets: false, + }); + + const otpDoc = new Otp({ + email, + otp, + }); + + await otpDoc.save(); + + mailSender( + email, + "Verify your email", + `

Verify your email

Please enter the following OTP to verify your email:

@@ -51,123 +60,136 @@ const register = async (req: Request, res: Response) => {

expires in 10 minutes

Thank you for registering with us!

- `, - ); - - res.status(201).json( - new StandardResponse("Otp send successfully!", { - email: user.email, - firstName: user.firstName, - lastName: user.lastName, - }), - ); + ` + ); + + res.status(201).json( + new StandardResponse("Otp send successfully!", { + email: user.email, + firstName: user.firstName, + lastName: user.lastName, + }) + ); }; //Login user const login = async (req: Request, res: Response) => { - const { email, password } = req.body; - - const user = await User.findOne({ email }); - - if (!user) { - throw new CustomError("User not found", 400); - } - - if (user) { - const isPasswordCorrect = await bcrypt.compare(password, user.password); - if (!isPasswordCorrect) { - throw new CustomError("Invalid credentials", 400); - } - - if (!user.verified) { - throw new CustomError("User not verified", 400, "USER_NOT_VERIFIED"); - } - - const token = jwt.sign( - { - id: user._id, - }, - process.env.JWT_SECRET_KEY || "", - { - expiresIn: "1d", - }, - ); - - const response = { - firstName: user.firstName, - lastName: user.lastName, - email: user.email, - token, - }; - - res.status(200).json(new StandardResponse("Login successful", response)); - } + const { email, password } = req.body; + + const user = await User.findOne({ email }); + + if (!user) { + throw new CustomError("User not found", 400); + } + + if (user) { + const isPasswordCorrect = await bcrypt.compare(password, user.password); + if (!isPasswordCorrect) { + throw new CustomError("Invalid credentials", 400); + } + + if (!user.verified) { + throw new CustomError("User not verified", 400, "USER_NOT_VERIFIED"); + } + + const token = jwt.sign( + { + id: user._id, + }, + process.env.JWT_SECRET_KEY || "", + { + expiresIn: "1d", + } + ); + + const response = { + userId: user._id, + firstName: user.firstName, + lastName: user.lastName, + email: user.email, + token, + }; + + res.status(200).json(new StandardResponse("Login successful", response)); + } }; // Verify OTP for email verification const verifyOtp = async (req: Request, res: Response) => { - const { email, otp } = req.body; + const { email, otp } = req.body; - const user = await User.findOne({ email }); + const user = await User.findOne({ email }); - if (!user) { - throw new CustomError("User not found", 400); - } + if (!user) { + throw new CustomError("User not found", 400); + } - if (user.verified) { - throw new CustomError("User already verified", 400, "USER_ALREADY_VERIFIED"); - } - const otpDoc = await Otp.findOne({ email }); + if (user.verified) { + throw new CustomError( + "User already verified", + 400, + "USER_ALREADY_VERIFIED" + ); + } + const otpDoc = await Otp.findOne({ email }); - if (!otpDoc) { - throw new CustomError("Email not found", 400); - } + if (!otpDoc) { + throw new CustomError("Email not found", 400); + } - // Check if OTP is expired - const currentTime = new Date().getTime(); - const otpUpdatedTime = new Date(otpDoc.updatedAt).getTime(); - const timeDifference = (currentTime - otpUpdatedTime) / 1000 / 60; + // Check if OTP is expired + const currentTime = new Date().getTime(); + const otpUpdatedTime = new Date(otpDoc.updatedAt).getTime(); + const timeDifference = (currentTime - otpUpdatedTime) / 1000 / 60; - if (timeDifference > 10) { - // 10 minutes - throw new CustomError("OTP expired", 400); - } + if (timeDifference > 10) { + // 10 minutes + throw new CustomError("OTP expired", 400); + } - if (otp !== otpDoc.otp) { - throw new CustomError("Invalid OTP", 400); - } + if (otp !== otpDoc.otp) { + throw new CustomError("Invalid OTP", 400); + } - await Promise.all([user.updateOne({ verified: true }), otpDoc.deleteOne()]); + await Promise.all([user.updateOne({ verified: true }), otpDoc.deleteOne()]); - res.status(200).json(new StandardResponse("Email verified successfully!")); + res.status(200).json(new StandardResponse("Email verified successfully!")); }; // Resend OTP const reSendOtp = async (req: Request, res: Response) => { - if (!req.body.email) { - throw new CustomError("Email is required", 400); - } - - const { email } = req.body; - - const existingUser = await User.findOne({ email }); - - if (!existingUser) { - throw new CustomError("User not found", 400); - } - - if (existingUser.verified) { - throw new CustomError("User already verified", 400, "USER_ALREADY_VERIFIED"); - } - - const otp = otpGenerator.generate(6, { upperCaseAlphabets: false, specialChars: false, lowerCaseAlphabets: false }); - - const otpDoc = await Otp.findOne({ email }); - - mailSender( - email, - "Verify your email", - ` + if (!req.body.email) { + throw new CustomError("Email is required", 400); + } + + const { email } = req.body; + + const existingUser = await User.findOne({ email }); + + if (!existingUser) { + throw new CustomError("User not found", 400); + } + + if (existingUser.verified) { + throw new CustomError( + "User already verified", + 400, + "USER_ALREADY_VERIFIED" + ); + } + + const otp = otpGenerator.generate(6, { + upperCaseAlphabets: false, + specialChars: false, + lowerCaseAlphabets: false, + }); + + const otpDoc = await Otp.findOne({ email }); + + mailSender( + email, + "Verify your email", + `

Verify your email

Please enter the following OTP to verify your email:

@@ -175,53 +197,53 @@ const reSendOtp = async (req: Request, res: Response) => {

expires in 10 minutes

Thank you for registering with us!

- `, - ); - - if (otpDoc) { - await otpDoc.updateOne({ otp }); - } else { - const newOtp = new Otp({ - email, - otp, - }); - - await newOtp.save(); - } - - res.status(200).json( - new StandardResponse("Otp send successfully!", { - email, - }), - ); + ` + ); + + if (otpDoc) { + await otpDoc.updateOne({ otp }); + } else { + const newOtp = new Otp({ + email, + otp, + }); + + await newOtp.save(); + } + + res.status(200).json( + new StandardResponse("Otp send successfully!", { + email, + }) + ); }; // forgot password const forgotPassword = async (req: Request, res: Response) => { - const { email } = req.body; - - if (!email) throw new CustomError("Email is required", 400); - - const user = await User.findOne({ email }); - - if (!user) { - throw new CustomError("User not found", 400); - } - - const resetToken = jwt.sign( - { - id: user._id, - }, - process.env.JWT_SECRET_KEY || "", - { - expiresIn: "10m", - }, - ); - - mailSender( - email, - "Reset your password", - ` + const { email } = req.body; + + if (!email) throw new CustomError("Email is required", 400); + + const user = await User.findOne({ email }); + + if (!user) { + throw new CustomError("User not found", 400); + } + + const resetToken = jwt.sign( + { + id: user._id, + }, + process.env.JWT_SECRET_KEY || "", + { + expiresIn: "10m", + } + ); + + mailSender( + email, + "Reset your password", + `

Reset your password

Please click on the following link to reset your password:

@@ -230,37 +252,39 @@ const forgotPassword = async (req: Request, res: Response) => {

Don't share this link with anyone!!

Thank you!

- `, - ); + ` + ); - res.status(200).json(new StandardResponse("Reset link sent successfully!")); + res.status(200).json(new StandardResponse("Reset link sent successfully!")); }; // reset the password using the token const resetPassword = async (req: Request, res: Response) => { - const token = req.params.token; + const token = req.params.token; - if (!token) throw new CustomError("Invalid token", 400); + if (!token) throw new CustomError("Invalid token", 400); - const { newPassword } = req.body; + const { newPassword } = req.body; - if (!newPassword) throw new CustomError("New Password is required", 400); + if (!newPassword) throw new CustomError("New Password is required", 400); - const decoded = jwt.verify(token, process.env.JWT_SECRET_KEY || "") as { id: string }; - console.log(decoded); + const decoded = jwt.verify(token, process.env.JWT_SECRET_KEY || "") as { + id: string; + }; + console.log(decoded); - const user = await User.findById(decoded.id); + const user = await User.findById(decoded.id); - if (!user) { - throw new CustomError("User not found", 400); - } + if (!user) { + throw new CustomError("User not found", 400); + } - const saltRounds = Number(process.env.SALT_ROUNDS || "10"); - const hashedPassword = await bcrypt.hash(newPassword, saltRounds); + const saltRounds = Number(process.env.SALT_ROUNDS || "10"); + const hashedPassword = await bcrypt.hash(newPassword, saltRounds); - await user.updateOne({ password: hashedPassword }); + await user.updateOne({ password: hashedPassword }); - res.status(200).json(new StandardResponse("Password reset successfully!")); + res.status(200).json(new StandardResponse("Password reset successfully!")); }; export { register, login, reSendOtp, verifyOtp, forgotPassword, resetPassword }; diff --git a/src/controllers/spaceController.ts b/src/controllers/spaceController.ts new file mode 100644 index 0000000..f9dedc6 --- /dev/null +++ b/src/controllers/spaceController.ts @@ -0,0 +1,62 @@ +import type { Request, Response } from "express"; +import { Space } from "../models/spaceModel"; +import { StandardResponse } from "../utils/standardResponse"; +import { CustomError } from "../utils/error/customError"; + +//create a new space +export const createSpace = async (req: Request, res: Response) => { + const { name, description, workspaceId } = req.body; + + const newSpace = new Space({ + name, + description, + workspaceId, + lists: [], + }); + + await newSpace.save(); + + res.status(201).json(new StandardResponse("Space created successfully", newSpace, 201)); +}; + +//get all spaces +export const getAllSpaces = async (req: Request, res: Response) => { + const { workspaceId } = req.query; + if (!workspaceId) { + throw new CustomError("Workspace ID is required"); + } + + const spaces = await Space.find({ workspaceId }); + res.status(200).json(new StandardResponse("Spaces fetched successfully", spaces, 200)); +}; + +//get a specific space by id +export const getSpaceById = async (req: Request, res: Response) => { + const { id } = req.params; + const space = await Space.findById(id); + if (!space) { + throw new CustomError("Space not found"); + } + res.status(200).json(new StandardResponse("Space fetched successfully", space, 200)); +}; + +//update a specific space by id +export const updateSpaceById = async (req: Request, res: Response) => { + const { id } = req.params; + const { name, description } = req.body; + const updatedSpace = await Space.findByIdAndUpdate(id, { name, description }, { new: true }); + if (!updatedSpace) { + throw new CustomError("Space not found"); + } + res.status(200).json(new StandardResponse("Space updated successfully", updatedSpace, 200)); +}; + +//delete a specific space by id +export const deleteSpaceById = async (req: Request, res: Response) => { + const { id } = req.params; + const deletedSpace = await Space.findByIdAndDelete(id); + if (!deletedSpace) { + throw new CustomError("Space not found"); + } + res.status(200).json(new StandardResponse("Space deleted successfully", deletedSpace, 200)); +}; diff --git a/src/index.ts b/src/index.ts index fa8d126..7cd5c02 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,6 +3,7 @@ import express from "express"; import mongoose from "mongoose"; import { globalErrorHandler } from "./middlewares/globalErrorHandler"; import authRoutes from "./routes/authRoutes"; +import spaceRoutes from "./routes/spaceRoutes"; const app = express(); @@ -17,6 +18,7 @@ app.get("/", (_req, res) => { }); app.use("/auth", authRoutes); +app.use("/space", spaceRoutes); app.use(globalErrorHandler); mongoose diff --git a/src/models/spaceModel.ts b/src/models/spaceModel.ts index 7c6cf0b..681ba67 100644 --- a/src/models/spaceModel.ts +++ b/src/models/spaceModel.ts @@ -4,23 +4,20 @@ interface ISpace extends Document { workspaceId: Types.ObjectId; name: string; description?: string; - lists: Types.ObjectId[]; - createdAt: Date; - updatedAt: Date; + lists: Schema.Types.ObjectId[]; } - -const spaceSchema: Schema = new Schema({ - workspaceId: { - type: Schema.Types.ObjectId, - ref: "workspace", - required: true, +const spaceSchema: Schema = new Schema( + { + workspaceId: { + type: Schema.Types.ObjectId, + ref: "workspace", + required: true, + }, + name: { type: String, required: true }, + description: { type: String }, + lists: [{ type: Schema.Types.ObjectId, ref: "lists", optional: true }], }, - name: { type: String, required: true }, - description: { type: String }, - lists: [{ type: Schema.Types.ObjectId, ref: "lists", required: true }], - createdAt: { type: Date, required: true }, - updatedAt: { type: Date, required: true }, -}); + { timestamps: true } +); -const Space = mongoose.model("Space", spaceSchema); -export default Space; +export const Space = mongoose.model("Space", spaceSchema); diff --git a/src/routes/spaceRoutes.ts b/src/routes/spaceRoutes.ts new file mode 100644 index 0000000..b0d2d4c --- /dev/null +++ b/src/routes/spaceRoutes.ts @@ -0,0 +1,33 @@ +import express from "express"; +import { validateData } from "../middlewares/zodValidation"; +import { spaceSchema } from "../utils/zodSchemas"; +import { errorCatch } from "../utils/error/errorCatch"; +import { + createSpace, + deleteSpaceById, + getAllSpaces, + getSpaceById, + updateSpaceById, +} from "../controllers/spaceController"; +import { verifyToken } from "../middlewares/verifyToken"; + +const router = express.Router(); + +//Space routes +router.post( + "/", + verifyToken, + validateData(spaceSchema), + errorCatch(createSpace) +); +router.get("/", verifyToken, errorCatch(getAllSpaces)); +router.get("/:id", verifyToken, errorCatch(getSpaceById)); +router.put( + "/:id", + verifyToken, + validateData(spaceSchema), + errorCatch(updateSpaceById) +); +router.delete("/:id", verifyToken, errorCatch(deleteSpaceById)); + +export default router; diff --git a/src/utils/error/handleErrors.ts b/src/utils/error/handleErrors.ts index 768aa46..815a3dd 100644 --- a/src/utils/error/handleErrors.ts +++ b/src/utils/error/handleErrors.ts @@ -2,28 +2,28 @@ import type { CastError } from "mongoose"; import { CustomError } from "./customError"; const castErrorHandler = (err: CastError) => { - // cast error means that the value is not of the correct type defined in the schema - const msg = `Invalid value for ${err.path}: ${err.value}!`; - return new CustomError(msg, 400); + // cast error means that the value is not of the correct type defined in the schema + const msg = `Invalid value for ${err.path}: ${err.value}!`; + return new CustomError(msg, 400); }; const duplicateKeyErrorHandler = (err: { - keyValue: Record; + keyValue: Record; }) => { - const field = Object.keys(err.keyValue)[0]; - const value = err.keyValue[field]; + const field = Object.keys(err.keyValue)[0]; + const value = err.keyValue[field]; - return new CustomError(`${value} already exists!`, 400); + return new CustomError(`${value} already exists!`, 400); }; const validationErrorHandler = (err: { - errors: { [key: string]: { message: string } }; + errors: { [key: string]: { message: string } }; }) => { - const errors = Object.values(err.errors).map((val) => val.message); - const errorMessages = errors.join(". "); - const msg = `Invalid input data: ${errorMessages}`; + const errors = Object.values(err.errors).map((val) => val.message); + const errorMessages = errors.join(". "); + const msg = `Invalid input data: ${errorMessages}`; - return new CustomError(msg, 400); + return new CustomError(msg, 400); }; export { castErrorHandler, duplicateKeyErrorHandler, validationErrorHandler }; diff --git a/src/utils/zodSchemas.ts b/src/utils/zodSchemas.ts index a5848f3..59b041b 100644 --- a/src/utils/zodSchemas.ts +++ b/src/utils/zodSchemas.ts @@ -1,20 +1,26 @@ import { z } from "zod"; const loginSchema = z.object({ - email: z.string(), - password: z.string(), + email: z.string(), + password: z.string(), }); const registerSchema = z.object({ - firstName: z.string().min(3), - lastName: z.string().optional(), - email: z.string().email(), - password: z.string().min(4), + firstName: z.string().min(3), + lastName: z.string().optional(), + email: z.string().email(), + password: z.string().min(4), }); const otpSchema = z.object({ - email: z.string().email(), - otp: z.string(), + email: z.string().email(), + otp: z.string(), }); -export { loginSchema, registerSchema, otpSchema }; +const spaceSchema = z.object({ + name: z.string().min(3), + description: z.string().optional(), + workspaceId: z.string().optional(), +}); + +export { loginSchema, registerSchema, otpSchema, spaceSchema }; From cea731938e0b03c995b03df7a554f1bd0bd7bbf8 Mon Sep 17 00:00:00 2001 From: Fasalu Rahman <145885597+fa-salu@users.noreply.github.com> Date: Thu, 14 Nov 2024 09:33:57 +0530 Subject: [PATCH 07/30] Admin functionality (#4) * admin authentication * get all user for admin * - get all workspace and spaces - get workspace by id - add user block * user model updated --- .vscode/settings.json | 11 ++- src/controllers/admin/adminAuthController.ts | 40 +++++++++ src/controllers/admin/adminController.ts | 88 ++++++++++++++++++++ src/index.ts | 2 + src/middlewares/admin/adminAuthenticate.ts | 28 +++++++ src/models/admin/adminModel.ts | 23 +++++ src/models/userModel.ts | 5 ++ src/routes/admin/adminRoutes.ts | 22 +++++ src/utils/zodSchemas.ts | 29 ++++--- 9 files changed, 232 insertions(+), 16 deletions(-) create mode 100644 src/controllers/admin/adminAuthController.ts create mode 100644 src/controllers/admin/adminController.ts create mode 100644 src/middlewares/admin/adminAuthenticate.ts create mode 100644 src/models/admin/adminModel.ts create mode 100644 src/routes/admin/adminRoutes.ts diff --git a/.vscode/settings.json b/.vscode/settings.json index b048cd8..6cb095f 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,6 +1,9 @@ { - "editor.defaultFormatter": "biomejs.biome", - "[typescript]": { - "editor.defaultFormatter": "biomejs.biome" - } + "editor.defaultFormatter": "biomejs.biome", + "[typescript]": { + "editor.defaultFormatter": "biomejs.biome" + }, + "[dotenv]": { + "editor.defaultFormatter": "foxundermoon.shell-format" + } } diff --git a/src/controllers/admin/adminAuthController.ts b/src/controllers/admin/adminAuthController.ts new file mode 100644 index 0000000..0e05eef --- /dev/null +++ b/src/controllers/admin/adminAuthController.ts @@ -0,0 +1,40 @@ +import type { Request, Response } from "express"; +import jwt from "jsonwebtoken"; +import bcrypt from "bcrypt"; +import { CustomError } from "../../utils/error/customError"; +import { StandardResponse } from "../../utils/standardResponse"; + +export const adminLogin = async (req: Request, res: Response) => { + const { email, password } = req.body; + console.log(email, password); + + const ADMIN_KEY = process.env.ADMIN_KEY || ""; + const ADMIN_PASSWORD = process.env.ADMIN_PASSWORD || ""; + + if (email !== ADMIN_KEY) { + throw new CustomError("Admin not found", 400); + } + + const isPasswordCorrect = await bcrypt.compare(password, ADMIN_PASSWORD); + if (!isPasswordCorrect) { + throw new CustomError("Invalid admin credentials", 400); + } + + const token = jwt.sign( + { + role: "admin", + }, + process.env.JWT_SECRET_KEY || "", + { + expiresIn: "1d", + }, + ); + + const response = { + email: ADMIN_KEY, + role: "admin", + token, + }; + + res.status(200).json(new StandardResponse("Admin login successful", response)); +}; diff --git a/src/controllers/admin/adminController.ts b/src/controllers/admin/adminController.ts new file mode 100644 index 0000000..c2b248e --- /dev/null +++ b/src/controllers/admin/adminController.ts @@ -0,0 +1,88 @@ +import type { Response } from "express"; +import { User } from "../../models/userModel"; +import { CustomError } from "../../utils/error/customError"; +import { StandardResponse } from "../../utils/standardResponse"; +import type { CustomRequest } from "../../types/interfaces"; +import Workspace from "../../models/workspaceModel"; +import mongoose from "mongoose"; + +export const getAllUsers = async (req: CustomRequest, res: Response) => { + if (!req.user) { + throw new CustomError("Unauthorized access", 401); + } + + const users = await User.find().select("-password"); + + res.status(200).json(new StandardResponse("Users retrieved successfully", users)); +}; + +export const blockUser = async (req: CustomRequest, res: Response) => { + const { userId } = req.params; + const { isBlocked } = req.query; + console.log("param", userId); + + if (isBlocked !== "true" && isBlocked !== "false") { + throw new CustomError("Invalid query parameter for 'isBlocked'", 400); + } + + const user = await User.findById(userId); + console.log("find", user); + + if (!user) { + throw new CustomError("User not found", 404); + } + + user.isBlocked = isBlocked === "true"; + + await user.save(); + + res + .status(200) + .json(new StandardResponse(`User ${isBlocked === "true" ? "blocked" : "unblocked"} successfully`, user)); +}; + +export const getAllWorkspacesWithSpaces = async (req: CustomRequest, res: Response) => { + if (!req.user) { + throw new CustomError("Unauthorized access", 401); + } + + const workspacesWithSpaces = await Workspace.aggregate([ + { + $lookup: { + from: "spaces", + localField: "_id", + foreignField: "workspaceId", + as: "spaces", + }, + }, + ]); + + res.status(200).json(new StandardResponse("Workspaces with spaces retrieved successfully", workspacesWithSpaces)); +}; + +export const getWorkspaceById = async (req: CustomRequest, res: Response) => { + const { workspaceId } = req.params; + + if (!req.user) { + throw new CustomError("Unauthorized access", 401); + } + const workspaceWithSpaces = await Workspace.aggregate([ + { + $match: { _id: new mongoose.Types.ObjectId(workspaceId) }, + }, + { + $lookup: { + from: "spaces", + localField: "_id", + foreignField: "workspaceId", + as: "spaces", + }, + }, + ]); + + if (!workspaceWithSpaces.length) { + throw new CustomError("Workspace not found", 404); + } + + res.status(200).json(new StandardResponse("Workspace with spaces retrieved successfully", workspaceWithSpaces[0])); +}; diff --git a/src/index.ts b/src/index.ts index 7cd5c02..bcb262b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,6 +4,7 @@ import mongoose from "mongoose"; import { globalErrorHandler } from "./middlewares/globalErrorHandler"; import authRoutes from "./routes/authRoutes"; import spaceRoutes from "./routes/spaceRoutes"; +import adminRoutes from "./routes/admin/adminRoutes"; const app = express(); @@ -19,6 +20,7 @@ app.get("/", (_req, res) => { app.use("/auth", authRoutes); app.use("/space", spaceRoutes); +app.use("/admin", adminRoutes); app.use(globalErrorHandler); mongoose diff --git a/src/middlewares/admin/adminAuthenticate.ts b/src/middlewares/admin/adminAuthenticate.ts new file mode 100644 index 0000000..95ad2dc --- /dev/null +++ b/src/middlewares/admin/adminAuthenticate.ts @@ -0,0 +1,28 @@ +import type { Response, NextFunction } from "express"; +import jwt from "jsonwebtoken"; +import type { CustomRequest } from "../../types/interfaces"; +import { CustomError } from "../../utils/error/customError"; + +export const adminAuthenticate = (req: CustomRequest, res: Response, next: NextFunction): void => { + const token = req.headers.authorization?.split(" ")[1]; + + if (!token) { + res.status(401).json({ message: "Authorization token is missing" }); + return; + } + + try { + const decoded = jwt.verify(token, process.env.JWT_SECRET_KEY || "") as jwt.JwtPayload; + + if (decoded.role !== "admin") { + throw new CustomError("Access denied. Admins only.", 403); + } + + req.user = decoded; + + next(); + // biome-ignore lint/correctness/noUnusedVariables: + } catch (error) { + res.status(401).json({ message: "Invalid or expired token" }); + } +}; diff --git a/src/models/admin/adminModel.ts b/src/models/admin/adminModel.ts new file mode 100644 index 0000000..40e2d38 --- /dev/null +++ b/src/models/admin/adminModel.ts @@ -0,0 +1,23 @@ +import mongoose, { type Document, Schema } from "mongoose"; + +export interface IAdmin extends Document { + email: string; + password: string; +} + +const adminSchema: Schema = new Schema( + { + email: { + type: String, + required: true, + unique: true, + }, + password: { + type: String, + required: true, + }, + }, + { timestamps: true }, +); + +export const Admin = mongoose.model("Admin", adminSchema); diff --git a/src/models/userModel.ts b/src/models/userModel.ts index 56cb667..7e603f9 100644 --- a/src/models/userModel.ts +++ b/src/models/userModel.ts @@ -6,6 +6,7 @@ export interface IUser extends Document { email: string; password: string; verified: boolean; + isBlocked: boolean; } const userSchema: Schema = new Schema( @@ -31,6 +32,10 @@ const userSchema: Schema = new Schema( type: Boolean, default: false, }, + isBlocked: { + type: Boolean, + default: false, + }, }, { timestamps: true }, ); diff --git a/src/routes/admin/adminRoutes.ts b/src/routes/admin/adminRoutes.ts new file mode 100644 index 0000000..0831b0a --- /dev/null +++ b/src/routes/admin/adminRoutes.ts @@ -0,0 +1,22 @@ +import express from "express"; +import { errorCatch } from "../../utils/error/errorCatch"; +import { validateData } from "../../middlewares/zodValidation"; +import { adminLoginSchema } from "../../utils/zodSchemas"; +import { adminLogin } from "../../controllers/admin/adminAuthController"; +import { + blockUser, + getAllUsers, + getAllWorkspacesWithSpaces, + getWorkspaceById, +} from "../../controllers/admin/adminController"; +import { adminAuthenticate } from "../../middlewares/admin/adminAuthenticate"; + +const router = express.Router(); + +router.post("/login", validateData(adminLoginSchema), errorCatch(adminLogin)); +router.get("/users", adminAuthenticate, errorCatch(getAllUsers)); +router.patch("/blockuser/:userId", adminAuthenticate, errorCatch(blockUser)); +router.get("/workspaces", adminAuthenticate, errorCatch(getAllWorkspacesWithSpaces)); +router.get("/workspace/:workspaceId", adminAuthenticate, errorCatch(getWorkspaceById)); + +export default router; diff --git a/src/utils/zodSchemas.ts b/src/utils/zodSchemas.ts index 59b041b..5458240 100644 --- a/src/utils/zodSchemas.ts +++ b/src/utils/zodSchemas.ts @@ -1,26 +1,31 @@ import { z } from "zod"; const loginSchema = z.object({ - email: z.string(), - password: z.string(), + email: z.string(), + password: z.string(), }); const registerSchema = z.object({ - firstName: z.string().min(3), - lastName: z.string().optional(), - email: z.string().email(), - password: z.string().min(4), + firstName: z.string().min(3), + lastName: z.string().optional(), + email: z.string().email(), + password: z.string().min(4), }); const otpSchema = z.object({ - email: z.string().email(), - otp: z.string(), + email: z.string().email(), + otp: z.string(), }); const spaceSchema = z.object({ - name: z.string().min(3), - description: z.string().optional(), - workspaceId: z.string().optional(), + name: z.string().min(3), + description: z.string().optional(), + workspaceId: z.string().optional(), }); -export { loginSchema, registerSchema, otpSchema, spaceSchema }; +const adminLoginSchema = z.object({ + email: z.string().email(), + password: z.string().min(6), +}); + +export { loginSchema, registerSchema, otpSchema, spaceSchema, adminLoginSchema }; From 9f1993d3e920db72f53a8e752a4264418fe183fa Mon Sep 17 00:00:00 2001 From: Sinan Date: Thu, 14 Nov 2024 09:54:04 +0530 Subject: [PATCH 08/30] chat controller completed (#9) --- src/controllers/chatController.ts | 64 +++++++++++++++++++++++++++++++ src/index.ts | 2 + src/models/chatModel.ts | 8 ++-- src/routes/chatRoutes.ts | 14 +++++++ src/utils/zodSchemas.ts | 6 ++- 5 files changed, 89 insertions(+), 5 deletions(-) create mode 100644 src/controllers/chatController.ts create mode 100644 src/routes/chatRoutes.ts diff --git a/src/controllers/chatController.ts b/src/controllers/chatController.ts new file mode 100644 index 0000000..149ec7f --- /dev/null +++ b/src/controllers/chatController.ts @@ -0,0 +1,64 @@ +import type { Request, Response } from "express"; +import { CustomError } from "../utils/error/customError"; +import { StandardResponse } from "../utils/standardResponse"; +import Chat from "../models/chatModel"; + +export const createMessages = async (req: Request, res: Response) => { + const { workspaceId } = req.params; + const { content } = req.body; + const userId = "6733105ddf0de189dc9866d9"; + + if (!workspaceId) { + throw new CustomError("Workspace not found", 400); + } + + const message = new Chat({ + workspace: workspaceId, + messages: [ + { + content: content, + sender: userId, + timestamp: Date.now(), + }, + ], + }); + + message.save(); + + res.status(201).json(new StandardResponse(" messages successful sended", message)); +}; + +export const getMessages = async (req: Request, res: Response) => { + const { workspaceId } = req.params; + const messages = await Chat.find({ workspace: workspaceId }).populate("messages.sender"); + + if (messages.length === 0) { + throw new CustomError("Messages not found", 400); + } + + res.status(200).json(new StandardResponse("Fetched messages successful", messages)); +}; + +export const deleteChat = async (req: Request, res: Response) => { + const { workspaceId } = req.params; + + const cleartChat = await Chat.deleteMany({ workspace: workspaceId }); + + if (!cleartChat) { + throw new CustomError("Message not found", 404); + } + + res.status(204).json(new StandardResponse("Successfully clear the chats", {})); +}; + +export const deleteMessage = async (req: Request, res: Response) => { + const { messageId } = req.params; + + const deletedMessage = await Chat.findByIdAndDelete(messageId); + + if (!deletedMessage) { + throw new CustomError("Message or workspace not found", 404); + } + + res.status(200).json(new StandardResponse("Message deleted successfully", {})); +}; diff --git a/src/index.ts b/src/index.ts index bcb262b..837cfe5 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5,6 +5,7 @@ import { globalErrorHandler } from "./middlewares/globalErrorHandler"; import authRoutes from "./routes/authRoutes"; import spaceRoutes from "./routes/spaceRoutes"; import adminRoutes from "./routes/admin/adminRoutes"; +import chatRoutes from "./routes/chatRoutes"; const app = express(); @@ -21,6 +22,7 @@ app.get("/", (_req, res) => { app.use("/auth", authRoutes); app.use("/space", spaceRoutes); app.use("/admin", adminRoutes); +app.use("/chat", chatRoutes); app.use(globalErrorHandler); mongoose diff --git a/src/models/chatModel.ts b/src/models/chatModel.ts index 7285b71..996381e 100644 --- a/src/models/chatModel.ts +++ b/src/models/chatModel.ts @@ -7,23 +7,23 @@ interface IMessage { } interface IChat extends Document { - workspaceId: Types.ObjectId; + workspace: Types.ObjectId; messages: IMessage[]; createdAt: Date; updatedAt: Date; } const chatSchema: Schema = new Schema({ - workspaceId: { + workspace: { type: Schema.Types.ObjectId, - ref: "workspace", + ref: "Workspace", required: true, }, messages: [ { content: { type: String, required: true }, timestamp: { type: Date, required: true }, - sender: { type: Schema.Types.ObjectId, ref: "user", required: true }, + sender: { type: Schema.Types.ObjectId, ref: "User", required: true }, }, ], }); diff --git a/src/routes/chatRoutes.ts b/src/routes/chatRoutes.ts new file mode 100644 index 0000000..b0aaacd --- /dev/null +++ b/src/routes/chatRoutes.ts @@ -0,0 +1,14 @@ +import express from "express"; +import { errorCatch } from "../utils/error/errorCatch"; +import { createMessages, deleteChat, deleteMessage, getMessages } from "../controllers/chatController"; +import { validateData } from "../middlewares/zodValidation"; +import { chatSchema } from "../utils/zodSchemas"; + +const router = express.Router(); + +router.post("/workspaces/:workspaceId/messages", validateData(chatSchema), errorCatch(createMessages)); +router.get("/workspaces/:workspaceId/messages", errorCatch(getMessages)); +router.delete("/workspaces/:workspaceId/messages", errorCatch(deleteChat)); +router.delete("/workspaces/:workspaceId/messages/:messageId", errorCatch(deleteMessage)); + +export default router; diff --git a/src/utils/zodSchemas.ts b/src/utils/zodSchemas.ts index 5458240..42cb862 100644 --- a/src/utils/zodSchemas.ts +++ b/src/utils/zodSchemas.ts @@ -28,4 +28,8 @@ const adminLoginSchema = z.object({ password: z.string().min(6), }); -export { loginSchema, registerSchema, otpSchema, spaceSchema, adminLoginSchema }; +const chatSchema = z.object({ + content: z.string().min(1), +}); + +export { loginSchema, registerSchema, otpSchema, spaceSchema, adminLoginSchema, chatSchema }; From 2596a8f65d116c05c4bee1b5d3e70b57de32d242 Mon Sep 17 00:00:00 2001 From: Sinan Date: Thu, 14 Nov 2024 11:54:41 +0530 Subject: [PATCH 09/30] search feature completed (#11) --- src/controllers/searchController.ts | 25 +++++++++++++++++++++++++ src/index.ts | 2 ++ src/routes/searchRoutes.ts | 12 ++++++++++++ 3 files changed, 39 insertions(+) create mode 100644 src/controllers/searchController.ts create mode 100644 src/routes/searchRoutes.ts diff --git a/src/controllers/searchController.ts b/src/controllers/searchController.ts new file mode 100644 index 0000000..25ada4f --- /dev/null +++ b/src/controllers/searchController.ts @@ -0,0 +1,25 @@ +import type { Request, Response } from "express"; +import { CustomError } from "../utils/error/customError"; +import { StandardResponse } from "../utils/standardResponse"; +import Workspace from "../models/workspaceModel"; + +export const searchWorkspace = async (req: Request, res: Response) => { + const { query } = req.query; + + if (!query || typeof query !== "string") { + throw new CustomError("Query parameter is required and should be a string", 400); + } + const workspaces = await Workspace.aggregate([ + { + $match: { + name: { $regex: query, $options: "i" }, + }, + }, + ]); + + if (!workspaces || workspaces.length === 0) { + throw new CustomError("Workspace not found", 404); + } + + res.status(200).json(new StandardResponse("Workspaces retrieved successfully", workspaces)); +}; diff --git a/src/index.ts b/src/index.ts index 837cfe5..8bfd009 100644 --- a/src/index.ts +++ b/src/index.ts @@ -6,6 +6,7 @@ import authRoutes from "./routes/authRoutes"; import spaceRoutes from "./routes/spaceRoutes"; import adminRoutes from "./routes/admin/adminRoutes"; import chatRoutes from "./routes/chatRoutes"; +import searchRoutes from "./routes/searchRoutes"; const app = express(); @@ -23,6 +24,7 @@ app.use("/auth", authRoutes); app.use("/space", spaceRoutes); app.use("/admin", adminRoutes); app.use("/chat", chatRoutes); +app.use("/search", searchRoutes); app.use(globalErrorHandler); mongoose diff --git a/src/routes/searchRoutes.ts b/src/routes/searchRoutes.ts new file mode 100644 index 0000000..d40ecd2 --- /dev/null +++ b/src/routes/searchRoutes.ts @@ -0,0 +1,12 @@ +import express from "express"; +import { verifyToken } from "../middlewares/verifyToken"; +import { errorCatch } from "../utils/error/errorCatch"; +import { searchWorkspace } from "../controllers/searchController"; + +const router = express.Router(); + +router.use(verifyToken); + +router.get("/", errorCatch(searchWorkspace)); + +export default router; From 233f52298a6036b285661eb33f2bac0d211d8410 Mon Sep 17 00:00:00 2001 From: Anas P <128923778+anaspxr@users.noreply.github.com> Date: Fri, 15 Nov 2024 15:41:41 +0530 Subject: [PATCH 10/30] workspace functionality (#13) * get , create, update deleted the workspace and get , updated the invited members * workspace feature completed * fix the middleware and mail sender utility errors * complete the workspace functionalities --------- Co-authored-by: sinan --- src/controllers/authController.ts | 415 ++++++++++----------- src/controllers/workspaceController.ts | 157 ++++++++ src/index.ts | 4 +- src/middlewares/admin/adminAuthenticate.ts | 7 +- src/middlewares/verifyToken.ts | 17 +- src/models/chatModel.ts | 43 +-- src/models/listModel.ts | 33 +- src/models/spaceModel.ts | 30 +- src/models/taskModel.ts | 27 +- src/models/userModel.ts | 5 + src/models/workspaceModel.ts | 37 +- src/routes/chatRoutes.ts | 3 +- src/routes/workspaceRoutes.ts | 30 ++ src/types/interfaces.ts | 11 +- src/utils/workspaceUtils.ts | 25 ++ src/utils/zodSchemas.ts | 23 +- 16 files changed, 556 insertions(+), 311 deletions(-) create mode 100644 src/controllers/workspaceController.ts create mode 100644 src/routes/workspaceRoutes.ts create mode 100644 src/utils/workspaceUtils.ts diff --git a/src/controllers/authController.ts b/src/controllers/authController.ts index bb3a5e9..c220662 100644 --- a/src/controllers/authController.ts +++ b/src/controllers/authController.ts @@ -10,49 +10,44 @@ import otpGenerator from "otp-generator"; //Register user const register = async (req: Request, res: Response) => { - const { firstName, lastName, email, password } = req.body; - - const saltRounds = Number(process.env.SALT_ROUNDS || "10"); - const hashedPassword = await bcrypt.hash(password, saltRounds); - - const existingUser = await User.findOne({ email }); - - if (existingUser) { - if (!existingUser.verified) - throw new CustomError( - "User already exists but not verified", - 400, - "USER_NOT_VERIFIED" - ); - - throw new CustomError("Email already exists", 400); - } - - const user = new User({ - firstName, - lastName, - email, - password: hashedPassword, - }); - await user.save(); - - const otp = otpGenerator.generate(6, { - upperCaseAlphabets: false, - specialChars: false, - lowerCaseAlphabets: false, - }); - - const otpDoc = new Otp({ - email, - otp, - }); - - await otpDoc.save(); - - mailSender( - email, - "Verify your email", - ` + const { firstName, lastName, email, password } = req.body; + + const saltRounds = Number(process.env.SALT_ROUNDS || "10"); + const hashedPassword = await bcrypt.hash(password, saltRounds); + + const existingUser = await User.findOne({ email }); + + if (existingUser) { + if (!existingUser.verified) throw new CustomError("User already exists but not verified", 400, "USER_NOT_VERIFIED"); + + throw new CustomError("Email already exists", 400); + } + + const user = new User({ + firstName, + lastName, + email, + password: hashedPassword, + }); + await user.save(); + + const otp = otpGenerator.generate(6, { + upperCaseAlphabets: false, + specialChars: false, + lowerCaseAlphabets: false, + }); + + const otpDoc = new Otp({ + email, + otp, + }); + + await otpDoc.save(); + + await mailSender( + email, + "Verify your email", + `

Verify your email

Please enter the following OTP to verify your email:

@@ -60,136 +55,128 @@ const register = async (req: Request, res: Response) => {

expires in 10 minutes

Thank you for registering with us!

- ` - ); - - res.status(201).json( - new StandardResponse("Otp send successfully!", { - email: user.email, - firstName: user.firstName, - lastName: user.lastName, - }) - ); + `, + ); + + res.status(201).json( + new StandardResponse("Otp send successfully!", { + email: user.email, + firstName: user.firstName, + lastName: user.lastName, + }), + ); }; //Login user const login = async (req: Request, res: Response) => { - const { email, password } = req.body; - - const user = await User.findOne({ email }); - - if (!user) { - throw new CustomError("User not found", 400); - } - - if (user) { - const isPasswordCorrect = await bcrypt.compare(password, user.password); - if (!isPasswordCorrect) { - throw new CustomError("Invalid credentials", 400); - } - - if (!user.verified) { - throw new CustomError("User not verified", 400, "USER_NOT_VERIFIED"); - } - - const token = jwt.sign( - { - id: user._id, - }, - process.env.JWT_SECRET_KEY || "", - { - expiresIn: "1d", - } - ); - - const response = { - userId: user._id, - firstName: user.firstName, - lastName: user.lastName, - email: user.email, - token, - }; - - res.status(200).json(new StandardResponse("Login successful", response)); - } + const { email, password } = req.body; + + const user = await User.findOne({ email }); + + if (!user) { + throw new CustomError("User not found", 400); + } + + if (user) { + const isPasswordCorrect = await bcrypt.compare(password, user.password); + if (!isPasswordCorrect) { + throw new CustomError("Invalid credentials", 400); + } + + if (!user.verified) { + throw new CustomError("User not verified", 400, "USER_NOT_VERIFIED"); + } + + const token = jwt.sign( + { + id: user._id, + }, + process.env.JWT_SECRET_KEY || "", + { + expiresIn: "1d", + }, + ); + + const response = { + userId: user._id, + firstName: user.firstName, + lastName: user.lastName, + email: user.email, + token, + }; + + res.status(200).json(new StandardResponse("Login successful", response)); + } }; // Verify OTP for email verification const verifyOtp = async (req: Request, res: Response) => { - const { email, otp } = req.body; + const { email, otp } = req.body; - const user = await User.findOne({ email }); + const user = await User.findOne({ email }); - if (!user) { - throw new CustomError("User not found", 400); - } + if (!user) { + throw new CustomError("User not found", 400); + } - if (user.verified) { - throw new CustomError( - "User already verified", - 400, - "USER_ALREADY_VERIFIED" - ); - } - const otpDoc = await Otp.findOne({ email }); + if (user.verified) { + throw new CustomError("User already verified", 400, "USER_ALREADY_VERIFIED"); + } + const otpDoc = await Otp.findOne({ email }); - if (!otpDoc) { - throw new CustomError("Email not found", 400); - } + if (!otpDoc) { + throw new CustomError("Email not found", 400); + } - // Check if OTP is expired - const currentTime = new Date().getTime(); - const otpUpdatedTime = new Date(otpDoc.updatedAt).getTime(); - const timeDifference = (currentTime - otpUpdatedTime) / 1000 / 60; + // Check if OTP is expired + const currentTime = new Date().getTime(); + const otpUpdatedTime = new Date(otpDoc.updatedAt).getTime(); + const timeDifference = (currentTime - otpUpdatedTime) / 1000 / 60; - if (timeDifference > 10) { - // 10 minutes - throw new CustomError("OTP expired", 400); - } + if (timeDifference > 10) { + // 10 minutes + throw new CustomError("OTP expired", 400); + } - if (otp !== otpDoc.otp) { - throw new CustomError("Invalid OTP", 400); - } + if (otp !== otpDoc.otp) { + throw new CustomError("Invalid OTP", 400); + } - await Promise.all([user.updateOne({ verified: true }), otpDoc.deleteOne()]); + await Promise.all([user.updateOne({ verified: true }), otpDoc.deleteOne()]); - res.status(200).json(new StandardResponse("Email verified successfully!")); + res.status(200).json(new StandardResponse("Email verified successfully!")); }; // Resend OTP const reSendOtp = async (req: Request, res: Response) => { - if (!req.body.email) { - throw new CustomError("Email is required", 400); - } - - const { email } = req.body; - - const existingUser = await User.findOne({ email }); - - if (!existingUser) { - throw new CustomError("User not found", 400); - } - - if (existingUser.verified) { - throw new CustomError( - "User already verified", - 400, - "USER_ALREADY_VERIFIED" - ); - } - - const otp = otpGenerator.generate(6, { - upperCaseAlphabets: false, - specialChars: false, - lowerCaseAlphabets: false, - }); - - const otpDoc = await Otp.findOne({ email }); - - mailSender( - email, - "Verify your email", - ` + if (!req.body.email) { + throw new CustomError("Email is required", 400); + } + + const { email } = req.body; + + const existingUser = await User.findOne({ email }); + + if (!existingUser) { + throw new CustomError("User not found", 400); + } + + if (existingUser.verified) { + throw new CustomError("User already verified", 400, "USER_ALREADY_VERIFIED"); + } + + const otp = otpGenerator.generate(6, { + upperCaseAlphabets: false, + specialChars: false, + lowerCaseAlphabets: false, + }); + + const otpDoc = await Otp.findOne({ email }); + + await mailSender( + email, + "Verify your email", + `

Verify your email

Please enter the following OTP to verify your email:

@@ -197,53 +184,53 @@ const reSendOtp = async (req: Request, res: Response) => {

expires in 10 minutes

Thank you for registering with us!

- ` - ); - - if (otpDoc) { - await otpDoc.updateOne({ otp }); - } else { - const newOtp = new Otp({ - email, - otp, - }); - - await newOtp.save(); - } - - res.status(200).json( - new StandardResponse("Otp send successfully!", { - email, - }) - ); + `, + ); + + if (otpDoc) { + await otpDoc.updateOne({ otp }); + } else { + const newOtp = new Otp({ + email, + otp, + }); + + await newOtp.save(); + } + + res.status(200).json( + new StandardResponse("Otp send successfully!", { + email, + }), + ); }; // forgot password const forgotPassword = async (req: Request, res: Response) => { - const { email } = req.body; - - if (!email) throw new CustomError("Email is required", 400); - - const user = await User.findOne({ email }); - - if (!user) { - throw new CustomError("User not found", 400); - } - - const resetToken = jwt.sign( - { - id: user._id, - }, - process.env.JWT_SECRET_KEY || "", - { - expiresIn: "10m", - } - ); - - mailSender( - email, - "Reset your password", - ` + const { email } = req.body; + + if (!email) throw new CustomError("Email is required", 400); + + const user = await User.findOne({ email }); + + if (!user) { + throw new CustomError("User not found", 400); + } + + const resetToken = jwt.sign( + { + id: user._id, + }, + process.env.JWT_SECRET_KEY || "", + { + expiresIn: "10m", + }, + ); + + await mailSender( + email, + "Reset your password", + `

Reset your password

Please click on the following link to reset your password:

@@ -252,39 +239,39 @@ const forgotPassword = async (req: Request, res: Response) => {

Don't share this link with anyone!!

Thank you!

- ` - ); + `, + ); - res.status(200).json(new StandardResponse("Reset link sent successfully!")); + res.status(200).json(new StandardResponse("Reset link sent successfully!")); }; // reset the password using the token const resetPassword = async (req: Request, res: Response) => { - const token = req.params.token; + const token = req.params.token; - if (!token) throw new CustomError("Invalid token", 400); + if (!token) throw new CustomError("Invalid token", 400); - const { newPassword } = req.body; + const { newPassword } = req.body; - if (!newPassword) throw new CustomError("New Password is required", 400); + if (!newPassword) throw new CustomError("New Password is required", 400); - const decoded = jwt.verify(token, process.env.JWT_SECRET_KEY || "") as { - id: string; - }; - console.log(decoded); + const decoded = jwt.verify(token, process.env.JWT_SECRET_KEY || "") as { + id: string; + }; + console.log(decoded); - const user = await User.findById(decoded.id); + const user = await User.findById(decoded.id); - if (!user) { - throw new CustomError("User not found", 400); - } + if (!user) { + throw new CustomError("User not found", 400); + } - const saltRounds = Number(process.env.SALT_ROUNDS || "10"); - const hashedPassword = await bcrypt.hash(newPassword, saltRounds); + const saltRounds = Number(process.env.SALT_ROUNDS || "10"); + const hashedPassword = await bcrypt.hash(newPassword, saltRounds); - await user.updateOne({ password: hashedPassword }); + await user.updateOne({ password: hashedPassword }); - res.status(200).json(new StandardResponse("Password reset successfully!")); + res.status(200).json(new StandardResponse("Password reset successfully!")); }; export { register, login, reSendOtp, verifyOtp, forgotPassword, resetPassword }; diff --git a/src/controllers/workspaceController.ts b/src/controllers/workspaceController.ts new file mode 100644 index 0000000..4e74a6e --- /dev/null +++ b/src/controllers/workspaceController.ts @@ -0,0 +1,157 @@ +import type { Request, Response } from "express"; +import type { CustomRequest } from "../types/interfaces"; +import type { ObjectId } from "mongoose"; +import Workspace from "../models/workspaceModel"; +import { StandardResponse } from "../utils/standardResponse"; +import { CustomError } from "../utils/error/customError"; +import { hasAccess, sendInvitationEmail } from "../utils/workspaceUtils"; +import { User } from "../models/userModel"; + +export const createWorkspace = async (req: CustomRequest, res: Response) => { + const { name, description, visibility = "private" } = req.body; + + const userId = req.user?.id; + + const workspace = new Workspace({ + name, + description, + visibility, + members: [userId], + createdBy: userId, + }); + + await workspace.save(); + + res.status(201).json(new StandardResponse("workspace created successfully", workspace)); +}; + +export const getActiveWorkspaces = async (req: CustomRequest, res: Response) => { + const userId = req.user?.id; + + console.log(userId); + + const workspaces = await Workspace.find({ + "members.userId": userId, + }); + + const memberWorkspaces = workspaces.filter((workspace) => + workspace.members.some((member) => member.toString() === userId), + ); + + const pendingWorkspaces = workspaces.filter((workspace) => + workspace.pendingMembers.some((member) => member.toString() === userId), + ); + + res.status(200).json( + new StandardResponse("fetched active workspaces successfully", { + memberWorkspaces, + pendingWorkspaces, + }), + ); +}; + +export const getWorkspaceById = async (req: CustomRequest, res: Response) => { + const { id } = req.params; + + const workspace = await Workspace.findById(id); + + if (!workspace) { + throw new CustomError("Workspace not found", 404); + } + + if (workspace.visibility === "private") await hasAccess(workspace, req.user?.id || ""); + + res.status(200).json(new StandardResponse("fetched workspace successfully", workspace)); +}; + +export const updateWorkspace = async (req: CustomRequest, res: Response) => { + const { id } = req.params; + + const updateWorkspace = req.body; + + const workspace = await Workspace.findById(id); + + if (!workspace) { + throw new CustomError("Workspace not found", 404); + } + + await hasAccess(workspace, req.user?.id || ""); + + const updated = await workspace.updateOne({ $set: updateWorkspace }, { new: true }); + + res.status(200).json(new StandardResponse("Updated workspace successfully", updated)); +}; + +export const deleteWorkspace = async (req: CustomRequest, res: Response) => { + const { id } = req.params; + + //here also deleting the space , list and task + + const workspace = await Workspace.findById(id); + + if (!workspace) { + throw new CustomError("Workspace not found", 404); + } + + await hasAccess(workspace, req.user?.id || ""); + + await workspace.deleteOne(); + + res.status(204).json(new StandardResponse("Deleted workspace successfully")); +}; + +export const getInvitedMembers = async (req: Request, res: Response) => { + const { id } = req.params; + const workspace = await Workspace.findById(id).populate("members", "_id firstName lastName image email"); + + if (!workspace) { + throw new CustomError("Workspace not found", 404); + } + + const members = workspace.members; + + const pendingMembers = workspace.pendingMembers; + + res.status(200).json(new StandardResponse("Fetched members successfully", { members, pendingMembers })); +}; + +export const acceptInvitation = async (req: CustomRequest, res: Response) => { + const { id } = req.params; + const userId = req.user?.id; + const workspace = await Workspace.findById(id); + + if (!workspace) { + throw new CustomError("Workspace not found", 404); + } + + const user = await User.findById(userId); + const member = workspace.pendingMembers.find((member) => member === user?.email); + + if (!member) throw new CustomError("User was not invited to the workspace or invitation already accepted", 400); + + workspace.members.push(user?.id); + await workspace.save(); + res.status(200).json(new StandardResponse("Accepted invitation", workspace)); +}; + +export const inviteMember = async (req: Request, res: Response) => { + const { email } = req.body; + const { id } = req.params; + + const workspace = await Workspace.findById(id); + + if (!workspace) { + throw new CustomError("Workspace not found", 404); + } + + const alreadyInvited = workspace.pendingMembers.includes(email); + if (alreadyInvited) throw new CustomError("User already invited", 400); + + workspace.pendingMembers.push(email); + await sendInvitationEmail(email, workspace); + await workspace.save(); + + console.log(alreadyInvited); + + res.status(200).json(new StandardResponse(`Invitation sent to ${email} successfully.`, { email, workspaceId: id })); +}; diff --git a/src/index.ts b/src/index.ts index 8bfd009..70eed3b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -7,7 +7,7 @@ import spaceRoutes from "./routes/spaceRoutes"; import adminRoutes from "./routes/admin/adminRoutes"; import chatRoutes from "./routes/chatRoutes"; import searchRoutes from "./routes/searchRoutes"; - +import workspaceRoutes from "./routes/workspaceRoutes"; const app = express(); dotenv.config(); @@ -25,6 +25,8 @@ app.use("/space", spaceRoutes); app.use("/admin", adminRoutes); app.use("/chat", chatRoutes); app.use("/search", searchRoutes); +app.use("/workspace", workspaceRoutes); + app.use(globalErrorHandler); mongoose diff --git a/src/middlewares/admin/adminAuthenticate.ts b/src/middlewares/admin/adminAuthenticate.ts index 95ad2dc..4d48ae2 100644 --- a/src/middlewares/admin/adminAuthenticate.ts +++ b/src/middlewares/admin/adminAuthenticate.ts @@ -1,6 +1,6 @@ import type { Response, NextFunction } from "express"; import jwt from "jsonwebtoken"; -import type { CustomRequest } from "../../types/interfaces"; +import type { CustomRequest, JwtDecoded } from "../../types/interfaces"; import { CustomError } from "../../utils/error/customError"; export const adminAuthenticate = (req: CustomRequest, res: Response, next: NextFunction): void => { @@ -12,7 +12,7 @@ export const adminAuthenticate = (req: CustomRequest, res: Response, next: NextF } try { - const decoded = jwt.verify(token, process.env.JWT_SECRET_KEY || "") as jwt.JwtPayload; + const decoded = jwt.verify(token, process.env.JWT_SECRET_KEY || "") as JwtDecoded; if (decoded.role !== "admin") { throw new CustomError("Access denied. Admins only.", 403); @@ -21,8 +21,7 @@ export const adminAuthenticate = (req: CustomRequest, res: Response, next: NextF req.user = decoded; next(); - // biome-ignore lint/correctness/noUnusedVariables: } catch (error) { - res.status(401).json({ message: "Invalid or expired token" }); + next(error); } }; diff --git a/src/middlewares/verifyToken.ts b/src/middlewares/verifyToken.ts index 8d3af6a..d87ca85 100644 --- a/src/middlewares/verifyToken.ts +++ b/src/middlewares/verifyToken.ts @@ -1,18 +1,23 @@ -import type { NextFunction, Request, Response } from "express"; +import type { NextFunction, Response } from "express"; import jwt from "jsonwebtoken"; -import type { CustomRequest } from "../types/interfaces"; +import type { CustomRequest, JwtDecoded } from "../types/interfaces"; import { CustomError } from "../utils/error/customError"; +import { User } from "../models/userModel"; -export const verifyToken = (req: Request, _res: Response, next: NextFunction) => { +export const verifyToken = async (req: CustomRequest, _res: Response, next: NextFunction) => { const token = req.header("Authorization")?.split(" ")[1]; if (!token) { throw new CustomError("Not authenticated", 401); } try { const verified = jwt.verify(token, process.env.JWT_SECRET_KEY || ""); - (req as CustomRequest).user = verified; + req.user = verified as JwtDecoded; + const userExists = await User.findById(req.user.id); + if (!userExists || userExists.isBlocked) { + throw new CustomError("User not found or blocked", 404); + } next(); - } catch (_err) { - throw new CustomError("Invalid token", 401); + } catch (error) { + next(error); } }; diff --git a/src/models/chatModel.ts b/src/models/chatModel.ts index 996381e..b5a94ea 100644 --- a/src/models/chatModel.ts +++ b/src/models/chatModel.ts @@ -1,32 +1,33 @@ import mongoose, { type Document, Schema, type Types } from "mongoose"; interface IMessage { - content: string; - timestamp: Date; - sender: Types.ObjectId; + content: string; + timestamp: Date; + sender: Types.ObjectId; } interface IChat extends Document { - workspace: Types.ObjectId; - messages: IMessage[]; - createdAt: Date; - updatedAt: Date; + workspaceId: Types.ObjectId; + messages: IMessage[]; } -const chatSchema: Schema = new Schema({ - workspace: { - type: Schema.Types.ObjectId, - ref: "Workspace", - required: true, - }, - messages: [ - { - content: { type: String, required: true }, - timestamp: { type: Date, required: true }, - sender: { type: Schema.Types.ObjectId, ref: "User", required: true }, - }, - ], -}); +const chatSchema: Schema = new Schema( + { + workspaceId: { + type: Schema.Types.ObjectId, + ref: "workspace", + required: true, + }, + messages: [ + { + content: { type: String, required: true }, + timestamp: { type: Date, required: true }, + sender: { type: Schema.Types.ObjectId, ref: "user", required: true }, + }, + ], + }, + { timestamps: true }, +); const Chat = mongoose.model("Chat", chatSchema); export default Chat; diff --git a/src/models/listModel.ts b/src/models/listModel.ts index e48e674..6dda294 100644 --- a/src/models/listModel.ts +++ b/src/models/listModel.ts @@ -1,24 +1,25 @@ import mongoose, { type Document, Schema, type Types } from "mongoose"; interface IList extends Document { - spaceId: Types.ObjectId; - name: string; - description?: string; - color?: string[]; - task: Types.ObjectId[]; - createdAt: Date; - updatedAt: Date; + spaceId: Types.ObjectId; + name: string; + description?: string; + color?: string[]; + task: Types.ObjectId[]; + createdAt: Date; + updatedAt: Date; } -const listsSchema: Schema = new Schema({ - spaceId: { type: Schema.Types.ObjectId, ref: "space", required: true }, - name: { type: String, required: true }, - description: { type: String }, - color: { type: [String] }, - task: [{ type: Schema.Types.ObjectId, ref: "task" }], - createdAt: { type: Date, required: true }, - updatedAt: { type: Date, required: true }, -}); +const listsSchema: Schema = new Schema( + { + spaceId: { type: Schema.Types.ObjectId, ref: "space", required: true }, + name: { type: String, required: true }, + description: { type: String }, + color: { type: [String] }, + task: [{ type: Schema.Types.ObjectId, ref: "task" }], + }, + { timestamps: true }, +); const List = mongoose.model("List", listsSchema); export default List; diff --git a/src/models/spaceModel.ts b/src/models/spaceModel.ts index 681ba67..dafba2a 100644 --- a/src/models/spaceModel.ts +++ b/src/models/spaceModel.ts @@ -1,23 +1,23 @@ import mongoose, { type Document, Schema, type Types } from "mongoose"; interface ISpace extends Document { - workspaceId: Types.ObjectId; - name: string; - description?: string; - lists: Schema.Types.ObjectId[]; + workspaceId: Types.ObjectId; + name: string; + description?: string; + lists: Schema.Types.ObjectId[]; } const spaceSchema: Schema = new Schema( - { - workspaceId: { - type: Schema.Types.ObjectId, - ref: "workspace", - required: true, - }, - name: { type: String, required: true }, - description: { type: String }, - lists: [{ type: Schema.Types.ObjectId, ref: "lists", optional: true }], - }, - { timestamps: true } + { + workspaceId: { + type: Schema.Types.ObjectId, + ref: "workspace", + required: true, + }, + name: { type: String, required: true }, + description: { type: String }, + lists: [{ type: Schema.Types.ObjectId, ref: "lists", optional: true }], + }, + { timestamps: true }, ); export const Space = mongoose.model("Space", spaceSchema); diff --git a/src/models/taskModel.ts b/src/models/taskModel.ts index 7cca6e9..218733b 100644 --- a/src/models/taskModel.ts +++ b/src/models/taskModel.ts @@ -1,22 +1,21 @@ import mongoose, { type Document, Schema, type Types } from "mongoose"; interface ITask extends Document { - listId: Types.ObjectId; - title: string; - description?: string; - dueDate?: Date; - createdAt: Date; - updatedAt: Date; + listId: Types.ObjectId; + title: string; + description?: string; + dueDate?: Date; } -const taskSchema: Schema = new Schema({ - listId: { type: Schema.Types.ObjectId, ref: "list", required: true }, - title: { type: String, required: true }, - description: { type: String }, - dueDate: { type: Date }, - createdAt: { type: Date, required: true }, - updatedAt: { type: Date, required: true }, -}); +const taskSchema: Schema = new Schema( + { + listId: { type: Schema.Types.ObjectId, ref: "list", required: true }, + title: { type: String, required: true }, + description: { type: String }, + dueDate: { type: Date }, + }, + { timestamps: true }, +); const Task = mongoose.model("Task", taskSchema); export default Task; diff --git a/src/models/userModel.ts b/src/models/userModel.ts index 7e603f9..03cf3bb 100644 --- a/src/models/userModel.ts +++ b/src/models/userModel.ts @@ -4,6 +4,7 @@ export interface IUser extends Document { firstName: string; lastName: string; email: string; + image?: string; password: string; verified: boolean; isBlocked: boolean; @@ -24,6 +25,10 @@ const userSchema: Schema = new Schema( required: true, unique: true, }, + image: { + type: String, + required: false, + }, password: { type: String, required: true, diff --git a/src/models/workspaceModel.ts b/src/models/workspaceModel.ts index 07dcc52..6040695 100644 --- a/src/models/workspaceModel.ts +++ b/src/models/workspaceModel.ts @@ -1,24 +1,27 @@ import mongoose, { type Document, Schema } from "mongoose"; -interface IWorkspace extends Document { - name: string; - description?: string; - members: mongoose.Schema.Types.ObjectId[]; - spaces: mongoose.Schema.Types.ObjectId[]; - createdAt: Date; - updatedAt: Date; +export interface IWorkspace extends Document { + name: string; + description?: string; + members: mongoose.Schema.Types.ObjectId[]; + pendingMembers: string[]; + spaces: mongoose.Schema.Types.ObjectId[]; + visibility: string; + createdBy: mongoose.Schema.Types.ObjectId; } -const workspaceSchema: Schema = new Schema({ - name: { type: String, required: true }, - description: { type: String }, - members: [ - { type: mongoose.Schema.Types.ObjectId, ref: "user", required: true }, - ], - spaces: [{ type: mongoose.Schema.Types.ObjectId, ref: "space" }], - createdAt: { type: Date, required: true }, - updatedAt: { type: Date, required: true }, -}); +const workspaceSchema: Schema = new Schema( + { + name: { type: String, required: true }, + description: { type: String }, + members: [{ type: mongoose.Schema.Types.ObjectId, ref: "User" }], + pendingMembers: [{ type: String }], + spaces: [{ type: mongoose.Schema.Types.ObjectId, ref: "Space" }], + visibility: { type: String, default: "private", required: true }, + createdBy: { type: mongoose.Schema.Types.ObjectId, ref: "User", required: true }, + }, + { timestamps: true }, +); const Workspace = mongoose.model("Workspace", workspaceSchema); export default Workspace; diff --git a/src/routes/chatRoutes.ts b/src/routes/chatRoutes.ts index b0aaacd..d066284 100644 --- a/src/routes/chatRoutes.ts +++ b/src/routes/chatRoutes.ts @@ -3,9 +3,10 @@ import { errorCatch } from "../utils/error/errorCatch"; import { createMessages, deleteChat, deleteMessage, getMessages } from "../controllers/chatController"; import { validateData } from "../middlewares/zodValidation"; import { chatSchema } from "../utils/zodSchemas"; +import { verifyToken } from "../middlewares/verifyToken"; const router = express.Router(); - +router.use(verifyToken); router.post("/workspaces/:workspaceId/messages", validateData(chatSchema), errorCatch(createMessages)); router.get("/workspaces/:workspaceId/messages", errorCatch(getMessages)); router.delete("/workspaces/:workspaceId/messages", errorCatch(deleteChat)); diff --git a/src/routes/workspaceRoutes.ts b/src/routes/workspaceRoutes.ts new file mode 100644 index 0000000..cba8619 --- /dev/null +++ b/src/routes/workspaceRoutes.ts @@ -0,0 +1,30 @@ +import express from "express"; +import { validateData } from "../middlewares/zodValidation"; +import { inviteSchema, workspaceSchema } from "../utils/zodSchemas"; +import { errorCatch } from "../utils/error/errorCatch"; +import { + acceptInvitation, + createWorkspace, + deleteWorkspace, + getActiveWorkspaces, + getInvitedMembers, + getWorkspaceById, + inviteMember, + updateWorkspace, +} from "../controllers/workspaceController"; +import { verifyToken } from "../middlewares/verifyToken"; + +const router = express.Router(); + +router.use(verifyToken); + +router.post("/", validateData(workspaceSchema), errorCatch(createWorkspace)); +router.get("/", errorCatch(getActiveWorkspaces)); +router.get("/:id", errorCatch(getWorkspaceById)); +router.put("/:id", validateData(workspaceSchema), errorCatch(updateWorkspace)); +router.delete("/:id", errorCatch(deleteWorkspace)); +router.get("/:id/invited-members", errorCatch(getInvitedMembers)); +router.patch("/:id/accept-invitation", errorCatch(acceptInvitation)); +router.post("/:id/invite", validateData(inviteSchema), errorCatch(inviteMember)); + +export default router; diff --git a/src/types/interfaces.ts b/src/types/interfaces.ts index bd22cd0..3940aad 100644 --- a/src/types/interfaces.ts +++ b/src/types/interfaces.ts @@ -2,5 +2,14 @@ import type { Request } from "express"; import type { JwtPayload } from "jsonwebtoken"; export interface CustomRequest extends Request { - user?: JwtPayload | string; + user?: { + id: string; + } & JwtPayload; } + +export type JwtDecoded = { + id: string; + iat: number; + exp: number; + role: "user" | "admin"; +}; diff --git a/src/utils/workspaceUtils.ts b/src/utils/workspaceUtils.ts new file mode 100644 index 0000000..cae4859 --- /dev/null +++ b/src/utils/workspaceUtils.ts @@ -0,0 +1,25 @@ +import type { IWorkspace } from "../models/workspaceModel"; +import { CustomError } from "./error/customError"; +import mailSender from "./mailSender"; + +export const hasAccess = async (workspace: IWorkspace, userId: string) => { + // to find if the user is a member of the workspace. If not, throw an error + if (workspace.members.every((member) => member.toString() !== userId)) { + throw new CustomError("You are not a member of this workspace", 403); + } +}; + +export const sendInvitationEmail = async (email: string, workspace: IWorkspace) => { + const title = "Invitation to Join Workspace"; + const body = ` +

Hello,

+

You have been invited to join the workspace: ${workspace.name}.

+

Click the link below to accept the invitation:

+ Accept Invitation +

If you didn't request this invitation, please ignore this email.

+

Best Regards,

+

Team DOQUE

+ `; + const info = await mailSender(email, title, body); + return info; +}; diff --git a/src/utils/zodSchemas.ts b/src/utils/zodSchemas.ts index 42cb862..9ab8c79 100644 --- a/src/utils/zodSchemas.ts +++ b/src/utils/zodSchemas.ts @@ -32,4 +32,25 @@ const chatSchema = z.object({ content: z.string().min(1), }); -export { loginSchema, registerSchema, otpSchema, spaceSchema, adminLoginSchema, chatSchema }; +const workspaceSchema = z + .object({ + name: z.string(), + description: z.string().optional(), + visibility: z.string().optional(), + }) + .strict(); + +const inviteSchema = z.object({ + email: z.string().email(), +}); + +export { + loginSchema, + registerSchema, + otpSchema, + spaceSchema, + adminLoginSchema, + chatSchema, + workspaceSchema, + inviteSchema, +}; From 9842ec02f18c1efe01fc69309613b72cbe026a28 Mon Sep 17 00:00:00 2001 From: Fasalu Rahman <145885597+fa-salu@users.noreply.github.com> Date: Fri, 15 Nov 2024 17:02:00 +0530 Subject: [PATCH 11/30] list features (#14) * - create list - list update - get all lists - delete list --- src/controllers/listControllers.ts | 90 ++++++++++++++++++++++++++++++ src/models/listModel.ts | 24 ++++---- src/models/spaceModel.ts | 2 +- src/routes/listRoutes.ts | 15 +++++ src/utils/zodSchemas.ts | 9 +++ 5 files changed, 129 insertions(+), 11 deletions(-) create mode 100644 src/controllers/listControllers.ts create mode 100644 src/routes/listRoutes.ts diff --git a/src/controllers/listControllers.ts b/src/controllers/listControllers.ts new file mode 100644 index 0000000..8a1bc65 --- /dev/null +++ b/src/controllers/listControllers.ts @@ -0,0 +1,90 @@ +import type { Request, Response } from "express"; +import { CustomError } from "../utils/error/customError"; +import { StandardResponse } from "../utils/standardResponse"; +import { Space } from "../models/spaceModel"; +import List from "../models/listModel"; + +export const createList = async (req: Request, res: Response) => { + const spaceId = req.params.spaceId; + const { name, description, color, task } = req.body; + + const space = await Space.findById(spaceId); + if (!space) { + throw new CustomError("space not found", 404); + } + + const newList = new List({ + spaceId, + name, + description, + color, + task: task || [], + }); + + await newList.save(); + + res.status(200).json(new StandardResponse("List Created Successfully", newList, 201)); +}; + +export const getAllLists = async (req: Request, res: Response) => { + const { spaceId } = req.params; + + const lists = await List.find({ spaceId }); + + if (!lists || lists.length === 0) { + throw new CustomError("No lists found for this space", 404); + } + + res.status(200).json(new StandardResponse("Lists Retrieved Successfully", lists, 200)); +}; + +export const updateList = async (req: Request, res: Response) => { + const { spaceId, listId } = req.params; + const { name, description, color } = req.body; + + try { + const space = await Space.findById(spaceId); + if (!space) { + throw new CustomError("Space not found", 404); + } + + const list = await List.findOne({ _id: listId, spaceId }); + if (!list) { + throw new CustomError("List not found in the specified space", 404); + } + + if (name) list.name = name; + if (description) list.description = description; + if (color) list.color = color; + + await list.save(); + + res.status(200).json(new StandardResponse("List Updated Successfully", list, 200)); + } catch (error) { + const errorMessage = (error as Error).message || "Unknown error"; + res.status(500).json(new CustomError("Failed to update list", 500, errorMessage)); + } +}; + +export const deleteList = async (req: Request, res: Response) => { + const { spaceId, listId } = req.params; + + try { + const space = await Space.findById(spaceId); + if (!space) { + throw new CustomError("Space not found", 404); + } + + const list = await List.findOne({ _id: listId, spaceId }); + if (!list) { + throw new CustomError("List not found in the specified space", 404); + } + + await List.findByIdAndDelete(listId); + + res.status(200).json(new StandardResponse("List Deleted Successfully", null, 200)); + } catch (error) { + const errorMessage = (error as Error).message || "Unknown error"; + res.status(500).json(new CustomError("Failed to delete list", 500, errorMessage)); + } +}; diff --git a/src/models/listModel.ts b/src/models/listModel.ts index 6dda294..3c5fdf7 100644 --- a/src/models/listModel.ts +++ b/src/models/listModel.ts @@ -10,16 +10,20 @@ interface IList extends Document { updatedAt: Date; } -const listsSchema: Schema = new Schema( - { - spaceId: { type: Schema.Types.ObjectId, ref: "space", required: true }, - name: { type: String, required: true }, - description: { type: String }, - color: { type: [String] }, - task: [{ type: Schema.Types.ObjectId, ref: "task" }], - }, - { timestamps: true }, -); +const listsSchema: Schema = new Schema({ + spaceId: { type: Schema.Types.ObjectId, ref: "space", required: true }, + name: { type: String, required: true }, + description: { type: String }, + color: { type: String }, + task: [{ type: Schema.Types.ObjectId, ref: "task" }], + createdAt: { type: Date, default: Date.now(), required: true }, + updatedAt: { type: Date, default: Date.now(), required: true }, +}); + +listsSchema.pre("save", function (next) { + this.updatedAt = new Date(); + next(); +}); const List = mongoose.model("List", listsSchema); export default List; diff --git a/src/models/spaceModel.ts b/src/models/spaceModel.ts index dafba2a..904daaf 100644 --- a/src/models/spaceModel.ts +++ b/src/models/spaceModel.ts @@ -15,7 +15,7 @@ const spaceSchema: Schema = new Schema( }, name: { type: String, required: true }, description: { type: String }, - lists: [{ type: Schema.Types.ObjectId, ref: "lists", optional: true }], + lists: [{ type: Schema.Types.ObjectId, ref: "List", optional: true }], }, { timestamps: true }, ); diff --git a/src/routes/listRoutes.ts b/src/routes/listRoutes.ts new file mode 100644 index 0000000..edb7e9f --- /dev/null +++ b/src/routes/listRoutes.ts @@ -0,0 +1,15 @@ +import express from "express"; +import { validateData } from "../middlewares/zodValidation"; +import { listSchema } from "../utils/zodSchemas"; +import { errorCatch } from "../utils/error/errorCatch"; +import { createList, deleteList, getAllLists, updateList } from "../controllers/listControllers"; +import { verifyToken } from "../middlewares/verifyToken"; + +const router = express.Router(); + +router.post("/:spaceId/lists", verifyToken, validateData(listSchema), errorCatch(createList)); +router.get("/:spaceId/lists", verifyToken, errorCatch(getAllLists)); +router.put("/:spaceId/lists/:listId", verifyToken, errorCatch(updateList)); +router.delete("/:spaceId/lists/:listId", verifyToken, errorCatch(deleteList)); + +export default router; diff --git a/src/utils/zodSchemas.ts b/src/utils/zodSchemas.ts index 9ab8c79..5d74a25 100644 --- a/src/utils/zodSchemas.ts +++ b/src/utils/zodSchemas.ts @@ -23,6 +23,14 @@ const spaceSchema = z.object({ workspaceId: z.string().optional(), }); +const listSchema = z.object({ + name: z.string().min(3, "List name must be at least 3 characters"), + description: z.string().optional(), + color: z.string().optional(), + task: z.array(z.string()).optional(), + spaceId: z.string().optional(), +}); + const adminLoginSchema = z.object({ email: z.string().email(), password: z.string().min(6), @@ -49,6 +57,7 @@ export { registerSchema, otpSchema, spaceSchema, + listSchema, adminLoginSchema, chatSchema, workspaceSchema, From 85e8143c980301d4a2912c5a57d2f05ea212ee2d Mon Sep 17 00:00:00 2001 From: ANAS Date: Sat, 16 Nov 2024 09:59:56 +0530 Subject: [PATCH 12/30] fix: fix the empty active workspace --- src/controllers/workspaceController.ts | 19 ++++++------------- src/middlewares/verifyToken.ts | 8 ++++---- src/middlewares/zodValidation.ts | 2 +- 3 files changed, 11 insertions(+), 18 deletions(-) diff --git a/src/controllers/workspaceController.ts b/src/controllers/workspaceController.ts index 4e74a6e..4f1b6c2 100644 --- a/src/controllers/workspaceController.ts +++ b/src/controllers/workspaceController.ts @@ -1,6 +1,5 @@ import type { Request, Response } from "express"; import type { CustomRequest } from "../types/interfaces"; -import type { ObjectId } from "mongoose"; import Workspace from "../models/workspaceModel"; import { StandardResponse } from "../utils/standardResponse"; import { CustomError } from "../utils/error/customError"; @@ -28,23 +27,17 @@ export const createWorkspace = async (req: CustomRequest, res: Response) => { export const getActiveWorkspaces = async (req: CustomRequest, res: Response) => { const userId = req.user?.id; - console.log(userId); - - const workspaces = await Workspace.find({ - "members.userId": userId, + const activeWorkspaces = await Workspace.find({ + members: { $in: [userId] }, }); - const memberWorkspaces = workspaces.filter((workspace) => - workspace.members.some((member) => member.toString() === userId), - ); - - const pendingWorkspaces = workspaces.filter((workspace) => - workspace.pendingMembers.some((member) => member.toString() === userId), - ); + const pendingWorkspaces = await Workspace.find({ + pendingMembers: { $in: [userId] }, + }); res.status(200).json( new StandardResponse("fetched active workspaces successfully", { - memberWorkspaces, + activeWorkspaces, pendingWorkspaces, }), ); diff --git a/src/middlewares/verifyToken.ts b/src/middlewares/verifyToken.ts index d87ca85..36258b6 100644 --- a/src/middlewares/verifyToken.ts +++ b/src/middlewares/verifyToken.ts @@ -5,11 +5,11 @@ import { CustomError } from "../utils/error/customError"; import { User } from "../models/userModel"; export const verifyToken = async (req: CustomRequest, _res: Response, next: NextFunction) => { - const token = req.header("Authorization")?.split(" ")[1]; - if (!token) { - throw new CustomError("Not authenticated", 401); - } try { + const token = req.header("Authorization")?.split(" ")[1]; + if (!token) { + throw new CustomError("Not authenticated", 401); + } const verified = jwt.verify(token, process.env.JWT_SECRET_KEY || ""); req.user = verified as JwtDecoded; const userExists = await User.findById(req.user.id); diff --git a/src/middlewares/zodValidation.ts b/src/middlewares/zodValidation.ts index e6b60b1..fe63bfb 100644 --- a/src/middlewares/zodValidation.ts +++ b/src/middlewares/zodValidation.ts @@ -9,7 +9,7 @@ export function validateData(schema: AnyZodObject) { next(); } catch (error) { if (error instanceof ZodError) { - throw new CustomError("Invalid data", 400); + throw new CustomError(`Invalid data, ${error.errors[0].path}: ${error.errors[0].message}`, 400); } throw new CustomError("Error when validating data", 400); } From a037090a1af6cafbfe8afb82a5be3af35c7a2e87 Mon Sep 17 00:00:00 2001 From: Abhijith V <142149141+abhijithjithu0007@users.noreply.github.com> Date: Sat, 16 Nov 2024 13:59:43 +0530 Subject: [PATCH 13/30] Feature/usersettings (#16) * merged * created controller for user * fixed bugs --- src/controllers/authController.ts | 415 +++++++++++++++--------------- src/controllers/userController.ts | 39 +++ src/index.ts | 22 +- src/models/userModel.ts | 4 + src/routes/userRoutes.ts | 18 ++ src/utils/zodSchemas.ts | 78 +++--- 6 files changed, 330 insertions(+), 246 deletions(-) create mode 100644 src/controllers/userController.ts create mode 100644 src/routes/userRoutes.ts diff --git a/src/controllers/authController.ts b/src/controllers/authController.ts index c220662..8197d7f 100644 --- a/src/controllers/authController.ts +++ b/src/controllers/authController.ts @@ -10,44 +10,49 @@ import otpGenerator from "otp-generator"; //Register user const register = async (req: Request, res: Response) => { - const { firstName, lastName, email, password } = req.body; - - const saltRounds = Number(process.env.SALT_ROUNDS || "10"); - const hashedPassword = await bcrypt.hash(password, saltRounds); - - const existingUser = await User.findOne({ email }); - - if (existingUser) { - if (!existingUser.verified) throw new CustomError("User already exists but not verified", 400, "USER_NOT_VERIFIED"); - - throw new CustomError("Email already exists", 400); - } - - const user = new User({ - firstName, - lastName, - email, - password: hashedPassword, - }); - await user.save(); - - const otp = otpGenerator.generate(6, { - upperCaseAlphabets: false, - specialChars: false, - lowerCaseAlphabets: false, - }); - - const otpDoc = new Otp({ - email, - otp, - }); - - await otpDoc.save(); - - await mailSender( - email, - "Verify your email", - ` + const { firstName, lastName, email, password } = req.body; + + const saltRounds = Number(process.env.SALT_ROUNDS || "10"); + const hashedPassword = await bcrypt.hash(password, saltRounds); + + const existingUser = await User.findOne({ email }); + + if (existingUser) { + if (!existingUser.verified) + throw new CustomError( + "User already exists but not verified", + 400, + "USER_NOT_VERIFIED" + ); + + throw new CustomError("Email already exists", 400); + } + + const user = new User({ + firstName, + lastName, + email, + password: hashedPassword, + }); + await user.save(); + + const otp = otpGenerator.generate(6, { + upperCaseAlphabets: false, + specialChars: false, + lowerCaseAlphabets: false, + }); + + const otpDoc = new Otp({ + email, + otp, + }); + + await otpDoc.save(); + + await mailSender( + email, + "Verify your email", + `

Verify your email

Please enter the following OTP to verify your email:

@@ -55,128 +60,136 @@ const register = async (req: Request, res: Response) => {

expires in 10 minutes

Thank you for registering with us!

- `, - ); - - res.status(201).json( - new StandardResponse("Otp send successfully!", { - email: user.email, - firstName: user.firstName, - lastName: user.lastName, - }), - ); + ` + ); + + res.status(201).json( + new StandardResponse("Otp send successfully!", { + email: user.email, + firstName: user.firstName, + lastName: user.lastName, + }) + ); }; //Login user const login = async (req: Request, res: Response) => { - const { email, password } = req.body; - - const user = await User.findOne({ email }); - - if (!user) { - throw new CustomError("User not found", 400); - } - - if (user) { - const isPasswordCorrect = await bcrypt.compare(password, user.password); - if (!isPasswordCorrect) { - throw new CustomError("Invalid credentials", 400); - } - - if (!user.verified) { - throw new CustomError("User not verified", 400, "USER_NOT_VERIFIED"); - } - - const token = jwt.sign( - { - id: user._id, - }, - process.env.JWT_SECRET_KEY || "", - { - expiresIn: "1d", - }, - ); - - const response = { - userId: user._id, - firstName: user.firstName, - lastName: user.lastName, - email: user.email, - token, - }; - - res.status(200).json(new StandardResponse("Login successful", response)); - } + const { email, password } = req.body; + + const user = await User.findOne({ email }); + + if (!user) { + throw new CustomError("User not found", 400); + } + + if (user) { + const isPasswordCorrect = await bcrypt.compare(password, user.password); + if (!isPasswordCorrect) { + throw new CustomError("Invalid credentials", 400); + } + + if (!user.verified) { + throw new CustomError("User not verified", 400, "USER_NOT_VERIFIED"); + } + + const token = jwt.sign( + { + id: user._id, + }, + process.env.JWT_SECRET_KEY || "", + { + expiresIn: "1d", + } + ); + + const response = { + userId: user._id, + firstName: user.firstName, + lastName: user.lastName, + email: user.email, + token, + }; + + res.status(200).json(new StandardResponse("Login successful", response)); + } }; // Verify OTP for email verification const verifyOtp = async (req: Request, res: Response) => { - const { email, otp } = req.body; + const { email, otp } = req.body; - const user = await User.findOne({ email }); + const user = await User.findOne({ email }); - if (!user) { - throw new CustomError("User not found", 400); - } + if (!user) { + throw new CustomError("User not found", 400); + } - if (user.verified) { - throw new CustomError("User already verified", 400, "USER_ALREADY_VERIFIED"); - } - const otpDoc = await Otp.findOne({ email }); + if (user.verified) { + throw new CustomError( + "User already verified", + 400, + "USER_ALREADY_VERIFIED" + ); + } + const otpDoc = await Otp.findOne({ email }); - if (!otpDoc) { - throw new CustomError("Email not found", 400); - } + if (!otpDoc) { + throw new CustomError("Email not found", 400); + } - // Check if OTP is expired - const currentTime = new Date().getTime(); - const otpUpdatedTime = new Date(otpDoc.updatedAt).getTime(); - const timeDifference = (currentTime - otpUpdatedTime) / 1000 / 60; + // Check if OTP is expired + const currentTime = new Date().getTime(); + const otpUpdatedTime = new Date(otpDoc.updatedAt).getTime(); + const timeDifference = (currentTime - otpUpdatedTime) / 1000 / 60; - if (timeDifference > 10) { - // 10 minutes - throw new CustomError("OTP expired", 400); - } + if (timeDifference > 10) { + // 10 minutes + throw new CustomError("OTP expired", 400); + } - if (otp !== otpDoc.otp) { - throw new CustomError("Invalid OTP", 400); - } + if (otp !== otpDoc.otp) { + throw new CustomError("Invalid OTP", 400); + } - await Promise.all([user.updateOne({ verified: true }), otpDoc.deleteOne()]); + await Promise.all([user.updateOne({ verified: true }), otpDoc.deleteOne()]); - res.status(200).json(new StandardResponse("Email verified successfully!")); + res.status(200).json(new StandardResponse("Email verified successfully!")); }; // Resend OTP const reSendOtp = async (req: Request, res: Response) => { - if (!req.body.email) { - throw new CustomError("Email is required", 400); - } - - const { email } = req.body; - - const existingUser = await User.findOne({ email }); - - if (!existingUser) { - throw new CustomError("User not found", 400); - } - - if (existingUser.verified) { - throw new CustomError("User already verified", 400, "USER_ALREADY_VERIFIED"); - } - - const otp = otpGenerator.generate(6, { - upperCaseAlphabets: false, - specialChars: false, - lowerCaseAlphabets: false, - }); - - const otpDoc = await Otp.findOne({ email }); - - await mailSender( - email, - "Verify your email", - ` + if (!req.body.email) { + throw new CustomError("Email is required", 400); + } + + const { email } = req.body; + + const existingUser = await User.findOne({ email }); + + if (!existingUser) { + throw new CustomError("User not found", 400); + } + + if (existingUser.verified) { + throw new CustomError( + "User already verified", + 400, + "USER_ALREADY_VERIFIED" + ); + } + + const otp = otpGenerator.generate(6, { + upperCaseAlphabets: false, + specialChars: false, + lowerCaseAlphabets: false, + }); + + const otpDoc = await Otp.findOne({ email }); + + await mailSender( + email, + "Verify your email", + `

Verify your email

Please enter the following OTP to verify your email:

@@ -184,53 +197,53 @@ const reSendOtp = async (req: Request, res: Response) => {

expires in 10 minutes

Thank you for registering with us!

- `, - ); - - if (otpDoc) { - await otpDoc.updateOne({ otp }); - } else { - const newOtp = new Otp({ - email, - otp, - }); - - await newOtp.save(); - } - - res.status(200).json( - new StandardResponse("Otp send successfully!", { - email, - }), - ); + ` + ); + + if (otpDoc) { + await otpDoc.updateOne({ otp }); + } else { + const newOtp = new Otp({ + email, + otp, + }); + + await newOtp.save(); + } + + res.status(200).json( + new StandardResponse("Otp send successfully!", { + email, + }) + ); }; // forgot password const forgotPassword = async (req: Request, res: Response) => { - const { email } = req.body; - - if (!email) throw new CustomError("Email is required", 400); - - const user = await User.findOne({ email }); - - if (!user) { - throw new CustomError("User not found", 400); - } - - const resetToken = jwt.sign( - { - id: user._id, - }, - process.env.JWT_SECRET_KEY || "", - { - expiresIn: "10m", - }, - ); - - await mailSender( - email, - "Reset your password", - ` + const { email } = req.body; + + if (!email) throw new CustomError("Email is required", 400); + + const user = await User.findOne({ email }); + + if (!user) { + throw new CustomError("User not found", 400); + } + + const resetToken = jwt.sign( + { + id: user._id, + }, + process.env.JWT_SECRET_KEY || "", + { + expiresIn: "10m", + } + ); + + await mailSender( + email, + "Reset your password", + `

Reset your password

Please click on the following link to reset your password:

@@ -239,39 +252,39 @@ const forgotPassword = async (req: Request, res: Response) => {

Don't share this link with anyone!!

Thank you!

- `, - ); + ` + ); - res.status(200).json(new StandardResponse("Reset link sent successfully!")); + res.status(200).json(new StandardResponse("Reset link sent successfully!")); }; // reset the password using the token const resetPassword = async (req: Request, res: Response) => { - const token = req.params.token; + const token = req.params.token; - if (!token) throw new CustomError("Invalid token", 400); + if (!token) throw new CustomError("Invalid token", 400); - const { newPassword } = req.body; + const { newPassword } = req.body; - if (!newPassword) throw new CustomError("New Password is required", 400); + if (!newPassword) throw new CustomError("New Password is required", 400); - const decoded = jwt.verify(token, process.env.JWT_SECRET_KEY || "") as { - id: string; - }; - console.log(decoded); + const decoded = jwt.verify(token, process.env.JWT_SECRET_KEY || "") as { + id: string; + }; + console.log(decoded); - const user = await User.findById(decoded.id); + const user = await User.findById(decoded.id); - if (!user) { - throw new CustomError("User not found", 400); - } + if (!user) { + throw new CustomError("User not found", 400); + } - const saltRounds = Number(process.env.SALT_ROUNDS || "10"); - const hashedPassword = await bcrypt.hash(newPassword, saltRounds); + const saltRounds = Number(process.env.SALT_ROUNDS || "10"); + const hashedPassword = await bcrypt.hash(newPassword, saltRounds); - await user.updateOne({ password: hashedPassword }); + await user.updateOne({ password: hashedPassword }); - res.status(200).json(new StandardResponse("Password reset successfully!")); + res.status(200).json(new StandardResponse("Password reset successfully!")); }; export { register, login, reSendOtp, verifyOtp, forgotPassword, resetPassword }; diff --git a/src/controllers/userController.ts b/src/controllers/userController.ts new file mode 100644 index 0000000..6cc96df --- /dev/null +++ b/src/controllers/userController.ts @@ -0,0 +1,39 @@ +import type { Request, Response } from "express"; +import { User } from "../models/userModel"; +import { StandardResponse } from "../utils/standardResponse"; +import { CustomError } from "../utils/error/customError"; +import { CustomRequest } from "../types/interfaces"; + +export const getUserById = async (req: CustomRequest, res: Response) => { + const userId = req.user?.id; + const user = await User.findById(userId); + + if (!user) { + throw new CustomError("User not found"); + } + + res + .status(200) + .json(new StandardResponse("Profile fetched successfully", user)); +}; + +export const updatedUserProfile = async (req: Request, res: Response) => { + const userId = req.params.userId; + const { firstName, lastName, phoneNumber, image } = req.body; + + const user = await User.findById(userId); + + if (!user) { + throw new CustomError("User not found"); + } + + const updatedProfile = await User.findByIdAndUpdate( + userId, + { firstName, lastName, phoneNumber, image }, + { new: true } + ); + + res + .status(200) + .json(new StandardResponse("User Updated successfully", updatedProfile)); +}; diff --git a/src/index.ts b/src/index.ts index 70eed3b..5bf94ee 100644 --- a/src/index.ts +++ b/src/index.ts @@ -7,6 +7,7 @@ import spaceRoutes from "./routes/spaceRoutes"; import adminRoutes from "./routes/admin/adminRoutes"; import chatRoutes from "./routes/chatRoutes"; import searchRoutes from "./routes/searchRoutes"; +import userRoutes from "./routes/userRoutes"; import workspaceRoutes from "./routes/workspaceRoutes"; const app = express(); @@ -17,7 +18,7 @@ const port = process.env.PORT || 3001; app.use(express.json()); app.get("/", (_req, res) => { - res.send("Hello World!"); + res.send("Hello World!"); }); app.use("/auth", authRoutes); @@ -25,17 +26,18 @@ app.use("/space", spaceRoutes); app.use("/admin", adminRoutes); app.use("/chat", chatRoutes); app.use("/search", searchRoutes); +app.use(userRoutes); app.use("/workspace", workspaceRoutes); app.use(globalErrorHandler); mongoose - .connect(process.env.MONGO_URI || "") - .then(() => { - app.listen(port, () => { - console.log(`Server is running on port ${port}`); - }); - }) - .catch((err) => { - console.error(err); - }); + .connect(process.env.MONGO_URI || "") + .then(() => { + app.listen(port, () => { + console.log(`Server is running on port ${port}`); + }); + }) + .catch((err) => { + console.error(err); + }); diff --git a/src/models/userModel.ts b/src/models/userModel.ts index 03cf3bb..e05d1d8 100644 --- a/src/models/userModel.ts +++ b/src/models/userModel.ts @@ -33,6 +33,10 @@ const userSchema: Schema = new Schema( type: String, required: true, }, + phoneNumber: { + type: String, + required: false, + }, verified: { type: Boolean, default: false, diff --git a/src/routes/userRoutes.ts b/src/routes/userRoutes.ts new file mode 100644 index 0000000..fde517c --- /dev/null +++ b/src/routes/userRoutes.ts @@ -0,0 +1,18 @@ +import express from "express"; +import { getUserById, updatedUserProfile } from "../controllers/userController"; +import { verifyToken } from "../middlewares/verifyToken"; +import { errorCatch } from "../utils/error/errorCatch"; +import { editUserDetails } from "../utils/zodSchemas"; +import { validateData } from "../middlewares/zodValidation"; + +const router = express.Router(); + +router.get("/userprofile/:userId", verifyToken, errorCatch(getUserById)); +router.put( + "/userprofile/:userId", + verifyToken, + validateData(editUserDetails), + errorCatch(updatedUserProfile) +); + +export default router; diff --git a/src/utils/zodSchemas.ts b/src/utils/zodSchemas.ts index 5d74a25..3894685 100644 --- a/src/utils/zodSchemas.ts +++ b/src/utils/zodSchemas.ts @@ -1,65 +1,73 @@ import { z } from "zod"; const loginSchema = z.object({ - email: z.string(), - password: z.string(), + email: z.string(), + password: z.string(), }); const registerSchema = z.object({ - firstName: z.string().min(3), - lastName: z.string().optional(), - email: z.string().email(), - password: z.string().min(4), + firstName: z.string().min(3), + lastName: z.string().optional(), + email: z.string().email(), + password: z.string().min(4), }); const otpSchema = z.object({ - email: z.string().email(), - otp: z.string(), + email: z.string().email(), + otp: z.string(), }); const spaceSchema = z.object({ - name: z.string().min(3), - description: z.string().optional(), - workspaceId: z.string().optional(), + name: z.string().min(3), + description: z.string().optional(), + workspaceId: z.string().optional(), }); const listSchema = z.object({ - name: z.string().min(3, "List name must be at least 3 characters"), - description: z.string().optional(), - color: z.string().optional(), - task: z.array(z.string()).optional(), - spaceId: z.string().optional(), + name: z.string().min(3, "List name must be at least 3 characters"), + description: z.string().optional(), + color: z.string().optional(), + task: z.array(z.string()).optional(), + spaceId: z.string().optional(), }); const adminLoginSchema = z.object({ - email: z.string().email(), - password: z.string().min(6), + email: z.string().email(), + password: z.string().min(6), }); const chatSchema = z.object({ - content: z.string().min(1), + content: z.string().min(1), +}); + +const editUserDetails = z.object({ + firstName: z.string().min(3), + lastName: z.string().optional(), + phoneNumber: z.string().optional(), + image: z.string().optional(), }); const workspaceSchema = z - .object({ - name: z.string(), - description: z.string().optional(), - visibility: z.string().optional(), - }) - .strict(); + .object({ + name: z.string(), + description: z.string().optional(), + visibility: z.string().optional(), + }) + .strict(); const inviteSchema = z.object({ - email: z.string().email(), + email: z.string().email(), }); export { - loginSchema, - registerSchema, - otpSchema, - spaceSchema, - listSchema, - adminLoginSchema, - chatSchema, - workspaceSchema, - inviteSchema, + loginSchema, + registerSchema, + otpSchema, + spaceSchema, + listSchema, + adminLoginSchema, + chatSchema, + workspaceSchema, + inviteSchema, + editUserDetails, }; From e5388f640a6082b2065aba186b39be0ce5739b63 Mon Sep 17 00:00:00 2001 From: Abhijith V <142149141+abhijithjithu0007@users.noreply.github.com> Date: Mon, 18 Nov 2024 10:33:15 +0530 Subject: [PATCH 14/30] Added task endpoints(#17) * created task route * developed task section api --------- Co-authored-by: ANAS --- src/controllers/taskController.ts | 78 +++++++++++++++++++++++++++++++ src/index.ts | 4 +- src/models/taskModel.ts | 3 ++ src/routes/spaceRoutes.ts | 24 +++------- src/routes/taskRoutes.ts | 46 ++++++++++++++++++ src/utils/zodSchemas.ts | 22 ++++++++- 6 files changed, 157 insertions(+), 20 deletions(-) create mode 100644 src/controllers/taskController.ts create mode 100644 src/routes/taskRoutes.ts diff --git a/src/controllers/taskController.ts b/src/controllers/taskController.ts new file mode 100644 index 0000000..478dbe8 --- /dev/null +++ b/src/controllers/taskController.ts @@ -0,0 +1,78 @@ +import type { Response } from "express"; +import Task from "../models/taskModel"; +import type { CustomRequest } from "../types/interfaces"; +import { StandardResponse } from "../utils/standardResponse"; +import { CustomError } from "../utils/error/customError"; + +export const createTask = async (req: CustomRequest, res: Response) => { + const { title, description, dueDate, priority, assignedTo, listId } = + req.body; + + const task = new Task({ + listId, + title, + description, + dueDate, + priority, + assignedTo, + }); + + await task.save(); + + res.status(201).json(new StandardResponse("task created successfully", task)); +}; + +export const getAllTasks = async (req: CustomRequest, res: Response) => { + const { listId } = req.params; + const tasks = await Task.find({ listId }); + + if (tasks.length === 0) { + throw new CustomError("Task retrieved succesfully", 404); + } + res + .status(200) + .json(new StandardResponse("Task retrieved succesfully", tasks, 200)); +}; + +export const updateTask = async (req: CustomRequest, res: Response) => { + const { taskId } = req.params; + const { title, description, dueDate, priority, assignedTo, listId } = + req.body; + + const task = await Task.findByIdAndUpdate( + taskId, + { title, description, dueDate, priority, assignedTo, listId }, + { new: true } + ); + + if (!task) { + throw new CustomError("Task not found", 404); + } + + res.status(200).json(new StandardResponse("Task updated successfully", task)); +}; + +export const deleteTask = async (req: CustomRequest, res: Response) => { + const { taskId } = req.params; + + const task = await Task.findByIdAndDelete(taskId); + + if (!task) { + throw new CustomError("Task not found", 404); + } + + res.status(200).json(new StandardResponse("Task deleted successfully")); +}; + +export const moveTask = async (req: CustomRequest, res: Response) => { + const { taskId } = req.params; + const { listId } = req.body; + + const task = await Task.findByIdAndUpdate(taskId, { listId }, { new: true }); + + if (!task) { + throw new CustomError("Task not found", 404); + } + + res.status(200).json(new StandardResponse("Task moved successfully", task)); +}; diff --git a/src/index.ts b/src/index.ts index 5bf94ee..d41e5a6 100644 --- a/src/index.ts +++ b/src/index.ts @@ -7,6 +7,8 @@ import spaceRoutes from "./routes/spaceRoutes"; import adminRoutes from "./routes/admin/adminRoutes"; import chatRoutes from "./routes/chatRoutes"; import searchRoutes from "./routes/searchRoutes"; +import listRoutes from "./routes/listRoutes"; +import taskRoutes from "./routes/taskRoutes"; import userRoutes from "./routes/userRoutes"; import workspaceRoutes from "./routes/workspaceRoutes"; const app = express(); @@ -22,7 +24,7 @@ app.get("/", (_req, res) => { }); app.use("/auth", authRoutes); -app.use("/space", spaceRoutes); +app.use("/space", spaceRoutes, listRoutes, taskRoutes); app.use("/admin", adminRoutes); app.use("/chat", chatRoutes); app.use("/search", searchRoutes); diff --git a/src/models/taskModel.ts b/src/models/taskModel.ts index 218733b..c757d60 100644 --- a/src/models/taskModel.ts +++ b/src/models/taskModel.ts @@ -5,6 +5,7 @@ interface ITask extends Document { title: string; description?: string; dueDate?: Date; + assignedTo?: Types.ObjectId; } const taskSchema: Schema = new Schema( @@ -13,9 +14,11 @@ const taskSchema: Schema = new Schema( title: { type: String, required: true }, description: { type: String }, dueDate: { type: Date }, + assignedTo: [{ type: Schema.Types.ObjectId, ref: "user" }], }, { timestamps: true }, ); const Task = mongoose.model("Task", taskSchema); + export default Task; diff --git a/src/routes/spaceRoutes.ts b/src/routes/spaceRoutes.ts index b0d2d4c..55209e1 100644 --- a/src/routes/spaceRoutes.ts +++ b/src/routes/spaceRoutes.ts @@ -3,31 +3,21 @@ import { validateData } from "../middlewares/zodValidation"; import { spaceSchema } from "../utils/zodSchemas"; import { errorCatch } from "../utils/error/errorCatch"; import { - createSpace, - deleteSpaceById, - getAllSpaces, - getSpaceById, - updateSpaceById, + createSpace, + deleteSpaceById, + getAllSpaces, + getSpaceById, + updateSpaceById, } from "../controllers/spaceController"; import { verifyToken } from "../middlewares/verifyToken"; const router = express.Router(); //Space routes -router.post( - "/", - verifyToken, - validateData(spaceSchema), - errorCatch(createSpace) -); +router.post("/", verifyToken, validateData(spaceSchema), errorCatch(createSpace)); router.get("/", verifyToken, errorCatch(getAllSpaces)); router.get("/:id", verifyToken, errorCatch(getSpaceById)); -router.put( - "/:id", - verifyToken, - validateData(spaceSchema), - errorCatch(updateSpaceById) -); +router.put("/:id", verifyToken, validateData(spaceSchema), errorCatch(updateSpaceById)); router.delete("/:id", verifyToken, errorCatch(deleteSpaceById)); export default router; diff --git a/src/routes/taskRoutes.ts b/src/routes/taskRoutes.ts new file mode 100644 index 0000000..cc7806f --- /dev/null +++ b/src/routes/taskRoutes.ts @@ -0,0 +1,46 @@ +import { Router } from "express"; +import { verifyToken } from "../middlewares/verifyToken"; +import { validateData } from "../middlewares/zodValidation"; +import { createTasksSchema, updateTaskSchema } from "../utils/zodSchemas"; +import { + createTask, + deleteTask, + getAllTasks, + moveTask, + updateTask, +} from "../controllers/taskController"; +import { errorCatch } from "../utils/error/errorCatch"; + +const router = Router(); + +router.use(verifyToken); + +router.get( + "/:spaceId/lists/:listId/tasks", + verifyToken, + errorCatch(getAllTasks) +); + +router.post( + "/:spaceId/lists/:listId/tasks", + verifyToken, + validateData(createTasksSchema), + errorCatch(createTask) +); +router.put( + "/:spaceId/lists/:listId/tasks/:taskId", + verifyToken, + validateData(updateTaskSchema), + errorCatch(updateTask) +); +router.delete( + "/:spaceId/lists/:listId/tasks/:taskId", + verifyToken, + errorCatch(deleteTask) +); +router.patch( + "/:spaceId/lists/:listId/tasks/:taskId", + verifyToken, + errorCatch(moveTask) +); +export default router; diff --git a/src/utils/zodSchemas.ts b/src/utils/zodSchemas.ts index 3894685..e56e7a2 100644 --- a/src/utils/zodSchemas.ts +++ b/src/utils/zodSchemas.ts @@ -19,15 +19,14 @@ const otpSchema = z.object({ const spaceSchema = z.object({ name: z.string().min(3), + workspaceId: z.string(), description: z.string().optional(), - workspaceId: z.string().optional(), }); const listSchema = z.object({ name: z.string().min(3, "List name must be at least 3 characters"), description: z.string().optional(), color: z.string().optional(), - task: z.array(z.string()).optional(), spaceId: z.string().optional(), }); @@ -59,6 +58,23 @@ const inviteSchema = z.object({ email: z.string().email(), }); +const createTasksSchema = z.object({ + title: z.string().min(3, "Task name must be at least 3 characters"), + description: z.string().optional(), + listId: z.string(), + dueDate: z.date().optional(), + priority: z.string().optional(), + assignedTo: z.string().optional(), +}); +const updateTaskSchema = z.object({ + title: z.string().optional(), + description: z.string().optional(), + listId: z.string(), + dueDate: z.date().optional(), + priority: z.string().optional(), + assignedTo: z.string().optional(), +}); + export { loginSchema, registerSchema, @@ -70,4 +86,6 @@ export { workspaceSchema, inviteSchema, editUserDetails, + createTasksSchema, + updateTaskSchema, }; From 7b46fd5ba8c3c300da5a63d5cd0b0482ad6859e7 Mon Sep 17 00:00:00 2001 From: Sahad Date: Tue, 19 Nov 2024 09:39:22 +0530 Subject: [PATCH 15/30] update responses after connecting to frontend (#18) * update response msg * update endpoints * Merge branch 'beta' of https://github.com/bridgeonAcademic/DOQUE-REST-API into feature/api-connection * update routes and controller for connecting to frontend --- package-lock.json | 28 +- package.json | 2 + src/controllers/authController.ts | 415 ++++++++++++------------- src/controllers/chatController.ts | 9 +- src/controllers/spaceController.ts | 3 +- src/controllers/workspaceController.ts | 6 +- src/index.ts | 36 ++- src/routes/authRoutes.ts | 2 +- src/routes/userRoutes.ts | 9 +- src/routes/workspaceRoutes.ts | 4 +- src/utils/mailSender.ts | 2 + src/utils/zodSchemas.ts | 106 +++---- 12 files changed, 317 insertions(+), 305 deletions(-) diff --git a/package-lock.json b/package-lock.json index 70016c3..72d3650 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,9 @@ "version": "1.0.0", "license": "ISC", "dependencies": { + "@types/cors": "^2.8.17", "bcrypt": "^5.1.1", + "cors": "^2.8.5", "dotenv": "^16.4.5", "express": "^4.21.1", "jsonwebtoken": "^9.0.2", @@ -304,6 +306,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==", + "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", @@ -354,7 +365,6 @@ "version": "22.9.0", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.9.0.tgz", "integrity": "sha512-vuyHg81vvWA1Z1ELfvLko2c8f34gyA0zaic0+Rllc5lbCnbSyuvb2Oxpm6TAUAC/2xZN3QGqxBNggD1nNR2AfQ==", - "dev": true, "dependencies": { "undici-types": "~6.19.8" } @@ -750,6 +760,19 @@ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" }, + "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==", + "license": "MIT", + "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", @@ -2372,8 +2395,7 @@ "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 + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==" }, "node_modules/unpipe": { "version": "1.0.0", diff --git a/package.json b/package.json index 8ecf18f..f4b0b2b 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,9 @@ "author": "", "license": "ISC", "dependencies": { + "@types/cors": "^2.8.17", "bcrypt": "^5.1.1", + "cors": "^2.8.5", "dotenv": "^16.4.5", "express": "^4.21.1", "jsonwebtoken": "^9.0.2", diff --git a/src/controllers/authController.ts b/src/controllers/authController.ts index 8197d7f..c220662 100644 --- a/src/controllers/authController.ts +++ b/src/controllers/authController.ts @@ -10,49 +10,44 @@ import otpGenerator from "otp-generator"; //Register user const register = async (req: Request, res: Response) => { - const { firstName, lastName, email, password } = req.body; - - const saltRounds = Number(process.env.SALT_ROUNDS || "10"); - const hashedPassword = await bcrypt.hash(password, saltRounds); - - const existingUser = await User.findOne({ email }); - - if (existingUser) { - if (!existingUser.verified) - throw new CustomError( - "User already exists but not verified", - 400, - "USER_NOT_VERIFIED" - ); - - throw new CustomError("Email already exists", 400); - } - - const user = new User({ - firstName, - lastName, - email, - password: hashedPassword, - }); - await user.save(); - - const otp = otpGenerator.generate(6, { - upperCaseAlphabets: false, - specialChars: false, - lowerCaseAlphabets: false, - }); - - const otpDoc = new Otp({ - email, - otp, - }); - - await otpDoc.save(); - - await mailSender( - email, - "Verify your email", - ` + const { firstName, lastName, email, password } = req.body; + + const saltRounds = Number(process.env.SALT_ROUNDS || "10"); + const hashedPassword = await bcrypt.hash(password, saltRounds); + + const existingUser = await User.findOne({ email }); + + if (existingUser) { + if (!existingUser.verified) throw new CustomError("User already exists but not verified", 400, "USER_NOT_VERIFIED"); + + throw new CustomError("Email already exists", 400); + } + + const user = new User({ + firstName, + lastName, + email, + password: hashedPassword, + }); + await user.save(); + + const otp = otpGenerator.generate(6, { + upperCaseAlphabets: false, + specialChars: false, + lowerCaseAlphabets: false, + }); + + const otpDoc = new Otp({ + email, + otp, + }); + + await otpDoc.save(); + + await mailSender( + email, + "Verify your email", + `

Verify your email

Please enter the following OTP to verify your email:

@@ -60,136 +55,128 @@ const register = async (req: Request, res: Response) => {

expires in 10 minutes

Thank you for registering with us!

- ` - ); - - res.status(201).json( - new StandardResponse("Otp send successfully!", { - email: user.email, - firstName: user.firstName, - lastName: user.lastName, - }) - ); + `, + ); + + res.status(201).json( + new StandardResponse("Otp send successfully!", { + email: user.email, + firstName: user.firstName, + lastName: user.lastName, + }), + ); }; //Login user const login = async (req: Request, res: Response) => { - const { email, password } = req.body; - - const user = await User.findOne({ email }); - - if (!user) { - throw new CustomError("User not found", 400); - } - - if (user) { - const isPasswordCorrect = await bcrypt.compare(password, user.password); - if (!isPasswordCorrect) { - throw new CustomError("Invalid credentials", 400); - } - - if (!user.verified) { - throw new CustomError("User not verified", 400, "USER_NOT_VERIFIED"); - } - - const token = jwt.sign( - { - id: user._id, - }, - process.env.JWT_SECRET_KEY || "", - { - expiresIn: "1d", - } - ); - - const response = { - userId: user._id, - firstName: user.firstName, - lastName: user.lastName, - email: user.email, - token, - }; - - res.status(200).json(new StandardResponse("Login successful", response)); - } + const { email, password } = req.body; + + const user = await User.findOne({ email }); + + if (!user) { + throw new CustomError("User not found", 400); + } + + if (user) { + const isPasswordCorrect = await bcrypt.compare(password, user.password); + if (!isPasswordCorrect) { + throw new CustomError("Invalid credentials", 400); + } + + if (!user.verified) { + throw new CustomError("User not verified", 400, "USER_NOT_VERIFIED"); + } + + const token = jwt.sign( + { + id: user._id, + }, + process.env.JWT_SECRET_KEY || "", + { + expiresIn: "1d", + }, + ); + + const response = { + userId: user._id, + firstName: user.firstName, + lastName: user.lastName, + email: user.email, + token, + }; + + res.status(200).json(new StandardResponse("Login successful", response)); + } }; // Verify OTP for email verification const verifyOtp = async (req: Request, res: Response) => { - const { email, otp } = req.body; + const { email, otp } = req.body; - const user = await User.findOne({ email }); + const user = await User.findOne({ email }); - if (!user) { - throw new CustomError("User not found", 400); - } + if (!user) { + throw new CustomError("User not found", 400); + } - if (user.verified) { - throw new CustomError( - "User already verified", - 400, - "USER_ALREADY_VERIFIED" - ); - } - const otpDoc = await Otp.findOne({ email }); + if (user.verified) { + throw new CustomError("User already verified", 400, "USER_ALREADY_VERIFIED"); + } + const otpDoc = await Otp.findOne({ email }); - if (!otpDoc) { - throw new CustomError("Email not found", 400); - } + if (!otpDoc) { + throw new CustomError("Email not found", 400); + } - // Check if OTP is expired - const currentTime = new Date().getTime(); - const otpUpdatedTime = new Date(otpDoc.updatedAt).getTime(); - const timeDifference = (currentTime - otpUpdatedTime) / 1000 / 60; + // Check if OTP is expired + const currentTime = new Date().getTime(); + const otpUpdatedTime = new Date(otpDoc.updatedAt).getTime(); + const timeDifference = (currentTime - otpUpdatedTime) / 1000 / 60; - if (timeDifference > 10) { - // 10 minutes - throw new CustomError("OTP expired", 400); - } + if (timeDifference > 10) { + // 10 minutes + throw new CustomError("OTP expired", 400); + } - if (otp !== otpDoc.otp) { - throw new CustomError("Invalid OTP", 400); - } + if (otp !== otpDoc.otp) { + throw new CustomError("Invalid OTP", 400); + } - await Promise.all([user.updateOne({ verified: true }), otpDoc.deleteOne()]); + await Promise.all([user.updateOne({ verified: true }), otpDoc.deleteOne()]); - res.status(200).json(new StandardResponse("Email verified successfully!")); + res.status(200).json(new StandardResponse("Email verified successfully!")); }; // Resend OTP const reSendOtp = async (req: Request, res: Response) => { - if (!req.body.email) { - throw new CustomError("Email is required", 400); - } - - const { email } = req.body; - - const existingUser = await User.findOne({ email }); - - if (!existingUser) { - throw new CustomError("User not found", 400); - } - - if (existingUser.verified) { - throw new CustomError( - "User already verified", - 400, - "USER_ALREADY_VERIFIED" - ); - } - - const otp = otpGenerator.generate(6, { - upperCaseAlphabets: false, - specialChars: false, - lowerCaseAlphabets: false, - }); - - const otpDoc = await Otp.findOne({ email }); - - await mailSender( - email, - "Verify your email", - ` + if (!req.body.email) { + throw new CustomError("Email is required", 400); + } + + const { email } = req.body; + + const existingUser = await User.findOne({ email }); + + if (!existingUser) { + throw new CustomError("User not found", 400); + } + + if (existingUser.verified) { + throw new CustomError("User already verified", 400, "USER_ALREADY_VERIFIED"); + } + + const otp = otpGenerator.generate(6, { + upperCaseAlphabets: false, + specialChars: false, + lowerCaseAlphabets: false, + }); + + const otpDoc = await Otp.findOne({ email }); + + await mailSender( + email, + "Verify your email", + `

Verify your email

Please enter the following OTP to verify your email:

@@ -197,53 +184,53 @@ const reSendOtp = async (req: Request, res: Response) => {

expires in 10 minutes

Thank you for registering with us!

- ` - ); - - if (otpDoc) { - await otpDoc.updateOne({ otp }); - } else { - const newOtp = new Otp({ - email, - otp, - }); - - await newOtp.save(); - } - - res.status(200).json( - new StandardResponse("Otp send successfully!", { - email, - }) - ); + `, + ); + + if (otpDoc) { + await otpDoc.updateOne({ otp }); + } else { + const newOtp = new Otp({ + email, + otp, + }); + + await newOtp.save(); + } + + res.status(200).json( + new StandardResponse("Otp send successfully!", { + email, + }), + ); }; // forgot password const forgotPassword = async (req: Request, res: Response) => { - const { email } = req.body; - - if (!email) throw new CustomError("Email is required", 400); - - const user = await User.findOne({ email }); - - if (!user) { - throw new CustomError("User not found", 400); - } - - const resetToken = jwt.sign( - { - id: user._id, - }, - process.env.JWT_SECRET_KEY || "", - { - expiresIn: "10m", - } - ); - - await mailSender( - email, - "Reset your password", - ` + const { email } = req.body; + + if (!email) throw new CustomError("Email is required", 400); + + const user = await User.findOne({ email }); + + if (!user) { + throw new CustomError("User not found", 400); + } + + const resetToken = jwt.sign( + { + id: user._id, + }, + process.env.JWT_SECRET_KEY || "", + { + expiresIn: "10m", + }, + ); + + await mailSender( + email, + "Reset your password", + `

Reset your password

Please click on the following link to reset your password:

@@ -252,39 +239,39 @@ const forgotPassword = async (req: Request, res: Response) => {

Don't share this link with anyone!!

Thank you!

- ` - ); + `, + ); - res.status(200).json(new StandardResponse("Reset link sent successfully!")); + res.status(200).json(new StandardResponse("Reset link sent successfully!")); }; // reset the password using the token const resetPassword = async (req: Request, res: Response) => { - const token = req.params.token; + const token = req.params.token; - if (!token) throw new CustomError("Invalid token", 400); + if (!token) throw new CustomError("Invalid token", 400); - const { newPassword } = req.body; + const { newPassword } = req.body; - if (!newPassword) throw new CustomError("New Password is required", 400); + if (!newPassword) throw new CustomError("New Password is required", 400); - const decoded = jwt.verify(token, process.env.JWT_SECRET_KEY || "") as { - id: string; - }; - console.log(decoded); + const decoded = jwt.verify(token, process.env.JWT_SECRET_KEY || "") as { + id: string; + }; + console.log(decoded); - const user = await User.findById(decoded.id); + const user = await User.findById(decoded.id); - if (!user) { - throw new CustomError("User not found", 400); - } + if (!user) { + throw new CustomError("User not found", 400); + } - const saltRounds = Number(process.env.SALT_ROUNDS || "10"); - const hashedPassword = await bcrypt.hash(newPassword, saltRounds); + const saltRounds = Number(process.env.SALT_ROUNDS || "10"); + const hashedPassword = await bcrypt.hash(newPassword, saltRounds); - await user.updateOne({ password: hashedPassword }); + await user.updateOne({ password: hashedPassword }); - res.status(200).json(new StandardResponse("Password reset successfully!")); + res.status(200).json(new StandardResponse("Password reset successfully!")); }; export { register, login, reSendOtp, verifyOtp, forgotPassword, resetPassword }; diff --git a/src/controllers/chatController.ts b/src/controllers/chatController.ts index 149ec7f..5af41c8 100644 --- a/src/controllers/chatController.ts +++ b/src/controllers/chatController.ts @@ -1,12 +1,13 @@ import type { Request, Response } from "express"; import { CustomError } from "../utils/error/customError"; +import type { CustomRequest } from "../types/interfaces"; import { StandardResponse } from "../utils/standardResponse"; import Chat from "../models/chatModel"; -export const createMessages = async (req: Request, res: Response) => { +export const createMessages = async (req: CustomRequest, res: Response) => { const { workspaceId } = req.params; const { content } = req.body; - const userId = "6733105ddf0de189dc9866d9"; + const userId = req.user?.id; if (!workspaceId) { throw new CustomError("Workspace not found", 400); @@ -42,9 +43,9 @@ export const getMessages = async (req: Request, res: Response) => { export const deleteChat = async (req: Request, res: Response) => { const { workspaceId } = req.params; - const cleartChat = await Chat.deleteMany({ workspace: workspaceId }); + const clearChat = await Chat.deleteMany({ workspace: workspaceId }); - if (!cleartChat) { + if (!clearChat) { throw new CustomError("Message not found", 404); } diff --git a/src/controllers/spaceController.ts b/src/controllers/spaceController.ts index f9dedc6..7c2116d 100644 --- a/src/controllers/spaceController.ts +++ b/src/controllers/spaceController.ts @@ -5,7 +5,8 @@ import { CustomError } from "../utils/error/customError"; //create a new space export const createSpace = async (req: Request, res: Response) => { - const { name, description, workspaceId } = req.body; + const { name, description } = req.body; + const { workspaceId } = req.query; const newSpace = new Space({ name, diff --git a/src/controllers/workspaceController.ts b/src/controllers/workspaceController.ts index 4f1b6c2..1603951 100644 --- a/src/controllers/workspaceController.ts +++ b/src/controllers/workspaceController.ts @@ -72,7 +72,7 @@ export const updateWorkspace = async (req: CustomRequest, res: Response) => { const updated = await workspace.updateOne({ $set: updateWorkspace }, { new: true }); - res.status(200).json(new StandardResponse("Updated workspace successfully", updated)); + res.status(200).json(new StandardResponse(" Workspace updated successfully", updated)); }; export const deleteWorkspace = async (req: CustomRequest, res: Response) => { @@ -90,7 +90,7 @@ export const deleteWorkspace = async (req: CustomRequest, res: Response) => { await workspace.deleteOne(); - res.status(204).json(new StandardResponse("Deleted workspace successfully")); + res.status(204).json(new StandardResponse("Workspace deleted successfully")); }; export const getInvitedMembers = async (req: Request, res: Response) => { @@ -105,7 +105,7 @@ export const getInvitedMembers = async (req: Request, res: Response) => { const pendingMembers = workspace.pendingMembers; - res.status(200).json(new StandardResponse("Fetched members successfully", { members, pendingMembers })); + res.status(200).json(new StandardResponse("Members fetched Successfully", { members, pendingMembers })); }; export const acceptInvitation = async (req: CustomRequest, res: Response) => { diff --git a/src/index.ts b/src/index.ts index d41e5a6..ab7d4a9 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,7 @@ import dotenv from "dotenv"; import express from "express"; import mongoose from "mongoose"; +import cors from "cors"; import { globalErrorHandler } from "./middlewares/globalErrorHandler"; import authRoutes from "./routes/authRoutes"; import spaceRoutes from "./routes/spaceRoutes"; @@ -20,26 +21,27 @@ const port = process.env.PORT || 3001; app.use(express.json()); app.get("/", (_req, res) => { - res.send("Hello World!"); + res.send("Hello World!"); }); +app.use(cors({ origin: process.env.CLIENT_URL, credentials: true })); -app.use("/auth", authRoutes); -app.use("/space", spaceRoutes, listRoutes, taskRoutes); -app.use("/admin", adminRoutes); -app.use("/chat", chatRoutes); -app.use("/search", searchRoutes); -app.use(userRoutes); -app.use("/workspace", workspaceRoutes); +app.use("/api/auth", authRoutes); +app.use("/api/space", spaceRoutes, listRoutes, taskRoutes); +app.use("/api/admin", adminRoutes); +app.use("/api/chat", chatRoutes); +app.use("/api/search", searchRoutes); +app.use("/api/userprofile", userRoutes); +app.use("/api/workspace", workspaceRoutes); app.use(globalErrorHandler); mongoose - .connect(process.env.MONGO_URI || "") - .then(() => { - app.listen(port, () => { - console.log(`Server is running on port ${port}`); - }); - }) - .catch((err) => { - console.error(err); - }); + .connect(process.env.MONGO_URI || "") + .then(() => { + app.listen(port, () => { + console.log(`Server is running on port ${port}`); + }); + }) + .catch((err) => { + console.error(err); + }); diff --git a/src/routes/authRoutes.ts b/src/routes/authRoutes.ts index 4a68399..f4fdcb1 100644 --- a/src/routes/authRoutes.ts +++ b/src/routes/authRoutes.ts @@ -12,6 +12,6 @@ router.post("/login", validateData(loginSchema), errorCatch(login)); router.post("/verifyotp", validateData(otpSchema), errorCatch(verifyOtp)); router.post("/resendotp", errorCatch(reSendOtp)); router.post("/forgotpassword", errorCatch(forgotPassword)); -router.post("/resetpassword/:token", errorCatch(resetPassword)); +router.patch("/resetpassword/:token", errorCatch(resetPassword)); export default router; diff --git a/src/routes/userRoutes.ts b/src/routes/userRoutes.ts index fde517c..e67a2f6 100644 --- a/src/routes/userRoutes.ts +++ b/src/routes/userRoutes.ts @@ -7,12 +7,7 @@ import { validateData } from "../middlewares/zodValidation"; const router = express.Router(); -router.get("/userprofile/:userId", verifyToken, errorCatch(getUserById)); -router.put( - "/userprofile/:userId", - verifyToken, - validateData(editUserDetails), - errorCatch(updatedUserProfile) -); +router.get("/:userId", verifyToken, errorCatch(getUserById)); +router.put("/:userId", verifyToken, validateData(editUserDetails), errorCatch(updatedUserProfile)); export default router; diff --git a/src/routes/workspaceRoutes.ts b/src/routes/workspaceRoutes.ts index cba8619..cd4b6a0 100644 --- a/src/routes/workspaceRoutes.ts +++ b/src/routes/workspaceRoutes.ts @@ -23,8 +23,8 @@ router.get("/", errorCatch(getActiveWorkspaces)); router.get("/:id", errorCatch(getWorkspaceById)); router.put("/:id", validateData(workspaceSchema), errorCatch(updateWorkspace)); router.delete("/:id", errorCatch(deleteWorkspace)); -router.get("/:id/invited-members", errorCatch(getInvitedMembers)); -router.patch("/:id/accept-invitation", errorCatch(acceptInvitation)); router.post("/:id/invite", validateData(inviteSchema), errorCatch(inviteMember)); +router.patch("/:id/accept-invitation", errorCatch(acceptInvitation)); +router.get("/:id/invited-members", errorCatch(getInvitedMembers)); export default router; diff --git a/src/utils/mailSender.ts b/src/utils/mailSender.ts index 30f2629..3939663 100644 --- a/src/utils/mailSender.ts +++ b/src/utils/mailSender.ts @@ -2,6 +2,8 @@ import nodemailer from "nodemailer"; import { CustomError } from "./error/customError"; const mailSender = async (email: string, title: string, body: string) => { + console.log(process.env.MAIL_USER); + try { // Create a Transporter to send emails const transporter = nodemailer.createTransport({ diff --git a/src/utils/zodSchemas.ts b/src/utils/zodSchemas.ts index e56e7a2..4dd7c78 100644 --- a/src/utils/zodSchemas.ts +++ b/src/utils/zodSchemas.ts @@ -1,91 +1,91 @@ import { z } from "zod"; const loginSchema = z.object({ - email: z.string(), - password: z.string(), + email: z.string(), + password: z.string(), }); const registerSchema = z.object({ - firstName: z.string().min(3), - lastName: z.string().optional(), - email: z.string().email(), - password: z.string().min(4), + firstName: z.string().min(3), + lastName: z.string().optional(), + email: z.string().email(), + password: z.string().min(4), }); const otpSchema = z.object({ - email: z.string().email(), - otp: z.string(), + email: z.string().email(), + otp: z.string(), }); const spaceSchema = z.object({ - name: z.string().min(3), - workspaceId: z.string(), - description: z.string().optional(), + name: z.string().min(3), + workspaceId: z.string(), + description: z.string().optional(), }); const listSchema = z.object({ - name: z.string().min(3, "List name must be at least 3 characters"), - description: z.string().optional(), - color: z.string().optional(), - spaceId: z.string().optional(), + name: z.string().min(3, "List name must be at least 3 characters"), + description: z.string().optional(), + color: z.string().optional(), + spaceId: z.string().optional(), }); const adminLoginSchema = z.object({ - email: z.string().email(), - password: z.string().min(6), + email: z.string().email(), + password: z.string().min(6), }); const chatSchema = z.object({ - content: z.string().min(1), + content: z.string().min(1), }); const editUserDetails = z.object({ - firstName: z.string().min(3), - lastName: z.string().optional(), - phoneNumber: z.string().optional(), - image: z.string().optional(), + firstName: z.string().min(3), + lastName: z.string().optional(), + phoneNumber: z.string().optional(), + image: z.string().optional(), }); const workspaceSchema = z - .object({ - name: z.string(), - description: z.string().optional(), - visibility: z.string().optional(), - }) - .strict(); + .object({ + name: z.string(), + description: z.string().optional(), + visibility: z.string().optional(), + }) + .strict(); const inviteSchema = z.object({ - email: z.string().email(), + email: z.string().email(), }); const createTasksSchema = z.object({ - title: z.string().min(3, "Task name must be at least 3 characters"), - description: z.string().optional(), - listId: z.string(), - dueDate: z.date().optional(), - priority: z.string().optional(), - assignedTo: z.string().optional(), + title: z.string().min(3, "Task name must be at least 3 characters"), + description: z.string().optional(), + listId: z.string(), + dueDate: z.date().optional(), + priority: z.string().optional(), + assignedTo: z.string().optional(), }); const updateTaskSchema = z.object({ - title: z.string().optional(), - description: z.string().optional(), - listId: z.string(), - dueDate: z.date().optional(), - priority: z.string().optional(), - assignedTo: z.string().optional(), + title: z.string().optional(), + description: z.string().optional(), + listId: z.string(), + dueDate: z.date().optional(), + priority: z.string().optional(), + assignedTo: z.string().optional(), }); export { - loginSchema, - registerSchema, - otpSchema, - spaceSchema, - listSchema, - adminLoginSchema, - chatSchema, - workspaceSchema, - inviteSchema, - editUserDetails, - createTasksSchema, - updateTaskSchema, + loginSchema, + registerSchema, + otpSchema, + spaceSchema, + listSchema, + adminLoginSchema, + chatSchema, + workspaceSchema, + inviteSchema, + editUserDetails, + createTasksSchema, + updateTaskSchema, }; From 6cfb8acc0ea6650f38131d7065286e7c82419dbc Mon Sep 17 00:00:00 2001 From: ANAS Date: Tue, 19 Nov 2024 15:59:12 +0530 Subject: [PATCH 16/30] refactor: improve workspace and space creation logic --- src/controllers/authController.ts | 1 - src/controllers/spaceController.ts | 17 +++++++++-- src/controllers/userController.ts | 42 ++++++++++++-------------- src/controllers/workspaceController.ts | 12 +++++--- src/utils/mailSender.ts | 2 -- src/utils/workspaceUtils.ts | 7 ++++- src/utils/zodSchemas.ts | 1 - 7 files changed, 47 insertions(+), 35 deletions(-) diff --git a/src/controllers/authController.ts b/src/controllers/authController.ts index c220662..f288879 100644 --- a/src/controllers/authController.ts +++ b/src/controllers/authController.ts @@ -258,7 +258,6 @@ const resetPassword = async (req: Request, res: Response) => { const decoded = jwt.verify(token, process.env.JWT_SECRET_KEY || "") as { id: string; }; - console.log(decoded); const user = await User.findById(decoded.id); diff --git a/src/controllers/spaceController.ts b/src/controllers/spaceController.ts index 7c2116d..e1f7113 100644 --- a/src/controllers/spaceController.ts +++ b/src/controllers/spaceController.ts @@ -2,12 +2,23 @@ import type { Request, Response } from "express"; import { Space } from "../models/spaceModel"; import { StandardResponse } from "../utils/standardResponse"; import { CustomError } from "../utils/error/customError"; +import Workspace from "../models/workspaceModel"; //create a new space export const createSpace = async (req: Request, res: Response) => { const { name, description } = req.body; const { workspaceId } = req.query; + if (!workspaceId) { + throw new CustomError("Workspace ID is required"); + } + + const workspace = await Workspace.findById(workspaceId); + + if (!workspace) { + throw new CustomError("Workspace not found"); + } + const newSpace = new Space({ name, description, @@ -15,7 +26,7 @@ export const createSpace = async (req: Request, res: Response) => { lists: [], }); - await newSpace.save(); + await Promise.all([newSpace.save(), workspace.updateOne({ $push: { spaces: newSpace._id } })]); res.status(201).json(new StandardResponse("Space created successfully", newSpace, 201)); }; @@ -55,9 +66,11 @@ export const updateSpaceById = async (req: Request, res: Response) => { //delete a specific space by id export const deleteSpaceById = async (req: Request, res: Response) => { const { id } = req.params; + const deletedSpace = await Space.findByIdAndDelete(id); if (!deletedSpace) { throw new CustomError("Space not found"); } - res.status(200).json(new StandardResponse("Space deleted successfully", deletedSpace, 200)); + await Workspace.findByIdAndUpdate(deletedSpace.workspaceId, { $pull: { spaces: id } }); + res.status(200).json(new StandardResponse("Space deleted successfully")); }; diff --git a/src/controllers/userController.ts b/src/controllers/userController.ts index 6cc96df..f02ae42 100644 --- a/src/controllers/userController.ts +++ b/src/controllers/userController.ts @@ -2,38 +2,34 @@ import type { Request, Response } from "express"; import { User } from "../models/userModel"; import { StandardResponse } from "../utils/standardResponse"; import { CustomError } from "../utils/error/customError"; -import { CustomRequest } from "../types/interfaces"; +import type { CustomRequest } from "../types/interfaces"; export const getUserById = async (req: CustomRequest, res: Response) => { - const userId = req.user?.id; - const user = await User.findById(userId); + const userId = req.user?.id; + const user = await User.findById(userId); - if (!user) { - throw new CustomError("User not found"); - } + if (!user) { + throw new CustomError("User not found"); + } - res - .status(200) - .json(new StandardResponse("Profile fetched successfully", user)); + res.status(200).json(new StandardResponse("Profile fetched successfully", user)); }; export const updatedUserProfile = async (req: Request, res: Response) => { - const userId = req.params.userId; - const { firstName, lastName, phoneNumber, image } = req.body; + const userId = req.params.userId; + const { firstName, lastName, phoneNumber, image } = req.body; - const user = await User.findById(userId); + const user = await User.findById(userId); - if (!user) { - throw new CustomError("User not found"); - } + if (!user) { + throw new CustomError("User not found"); + } - const updatedProfile = await User.findByIdAndUpdate( - userId, - { firstName, lastName, phoneNumber, image }, - { new: true } - ); + const updatedProfile = await User.findByIdAndUpdate( + userId, + { firstName, lastName, phoneNumber, image }, + { new: true }, + ); - res - .status(200) - .json(new StandardResponse("User Updated successfully", updatedProfile)); + res.status(200).json(new StandardResponse("User Updated successfully", updatedProfile)); }; diff --git a/src/controllers/workspaceController.ts b/src/controllers/workspaceController.ts index 1603951..8e515d8 100644 --- a/src/controllers/workspaceController.ts +++ b/src/controllers/workspaceController.ts @@ -68,7 +68,7 @@ export const updateWorkspace = async (req: CustomRequest, res: Response) => { throw new CustomError("Workspace not found", 404); } - await hasAccess(workspace, req.user?.id || ""); + await hasAccess(workspace, req.user?.id || "", "owner"); const updated = await workspace.updateOne({ $set: updateWorkspace }, { new: true }); @@ -86,7 +86,7 @@ export const deleteWorkspace = async (req: CustomRequest, res: Response) => { throw new CustomError("Workspace not found", 404); } - await hasAccess(workspace, req.user?.id || ""); + await hasAccess(workspace, req.user?.id || "", "owner"); await workspace.deleteOne(); @@ -118,11 +118,15 @@ export const acceptInvitation = async (req: CustomRequest, res: Response) => { } const user = await User.findById(userId); - const member = workspace.pendingMembers.find((member) => member === user?.email); + if (!user) throw new CustomError("User not found", 404); + const member = workspace.pendingMembers.find((member) => member === user.email); if (!member) throw new CustomError("User was not invited to the workspace or invitation already accepted", 400); + if (workspace.members.includes(user?.id)) throw new CustomError("User already a member of the workspace", 400); + workspace.members.push(user?.id); + workspace.pendingMembers.splice(workspace.pendingMembers.indexOf(user.email), 1); await workspace.save(); res.status(200).json(new StandardResponse("Accepted invitation", workspace)); }; @@ -144,7 +148,5 @@ export const inviteMember = async (req: Request, res: Response) => { await sendInvitationEmail(email, workspace); await workspace.save(); - console.log(alreadyInvited); - res.status(200).json(new StandardResponse(`Invitation sent to ${email} successfully.`, { email, workspaceId: id })); }; diff --git a/src/utils/mailSender.ts b/src/utils/mailSender.ts index 3939663..30f2629 100644 --- a/src/utils/mailSender.ts +++ b/src/utils/mailSender.ts @@ -2,8 +2,6 @@ import nodemailer from "nodemailer"; import { CustomError } from "./error/customError"; const mailSender = async (email: string, title: string, body: string) => { - console.log(process.env.MAIL_USER); - try { // Create a Transporter to send emails const transporter = nodemailer.createTransport({ diff --git a/src/utils/workspaceUtils.ts b/src/utils/workspaceUtils.ts index cae4859..b6b19dd 100644 --- a/src/utils/workspaceUtils.ts +++ b/src/utils/workspaceUtils.ts @@ -2,8 +2,13 @@ import type { IWorkspace } from "../models/workspaceModel"; import { CustomError } from "./error/customError"; import mailSender from "./mailSender"; -export const hasAccess = async (workspace: IWorkspace, userId: string) => { +export const hasAccess = async (workspace: IWorkspace, userId: string, role: "owner" | "user" = "user") => { // to find if the user is a member of the workspace. If not, throw an error + + if (role === "owner" && workspace.createdBy.toString() !== userId) { + throw new CustomError("You do not have permission to do this!", 403); + } + if (workspace.members.every((member) => member.toString() !== userId)) { throw new CustomError("You are not a member of this workspace", 403); } diff --git a/src/utils/zodSchemas.ts b/src/utils/zodSchemas.ts index 4dd7c78..3e101e2 100644 --- a/src/utils/zodSchemas.ts +++ b/src/utils/zodSchemas.ts @@ -19,7 +19,6 @@ const otpSchema = z.object({ const spaceSchema = z.object({ name: z.string().min(3), - workspaceId: z.string(), description: z.string().optional(), }); From ee264e0e9dd01e0ebe4643871637029c4c0bb8a5 Mon Sep 17 00:00:00 2001 From: ANAS Date: Wed, 20 Nov 2024 12:19:22 +0530 Subject: [PATCH 17/30] refactor space, list and task controllers for integrating with frontend --- src/controllers/listControllers.ts | 6 +- src/controllers/spaceController.ts | 98 ++++++++++++++++++++++++++++-- src/controllers/taskController.ts | 91 ++++++++++++++------------- src/models/taskModel.ts | 2 + src/routes/taskRoutes.ts | 41 +++---------- src/utils/zodSchemas.ts | 13 ++-- 6 files changed, 160 insertions(+), 91 deletions(-) diff --git a/src/controllers/listControllers.ts b/src/controllers/listControllers.ts index 8a1bc65..0c9d351 100644 --- a/src/controllers/listControllers.ts +++ b/src/controllers/listControllers.ts @@ -9,6 +9,7 @@ export const createList = async (req: Request, res: Response) => { const { name, description, color, task } = req.body; const space = await Space.findById(spaceId); + if (!space) { throw new CustomError("space not found", 404); } @@ -21,7 +22,7 @@ export const createList = async (req: Request, res: Response) => { task: task || [], }); - await newList.save(); + await Promise.all([newList.save(), space.updateOne({ $push: { lists: newList._id } })]); res.status(200).json(new StandardResponse("List Created Successfully", newList, 201)); }; @@ -71,6 +72,7 @@ export const deleteList = async (req: Request, res: Response) => { try { const space = await Space.findById(spaceId); + if (!space) { throw new CustomError("Space not found", 404); } @@ -80,7 +82,7 @@ export const deleteList = async (req: Request, res: Response) => { throw new CustomError("List not found in the specified space", 404); } - await List.findByIdAndDelete(listId); + await Promise.all([list.deleteOne(), space.updateOne({ $pull: { lists: listId } })]); res.status(200).json(new StandardResponse("List Deleted Successfully", null, 200)); } catch (error) { diff --git a/src/controllers/spaceController.ts b/src/controllers/spaceController.ts index e1f7113..04a68f8 100644 --- a/src/controllers/spaceController.ts +++ b/src/controllers/spaceController.ts @@ -3,6 +3,7 @@ import { Space } from "../models/spaceModel"; import { StandardResponse } from "../utils/standardResponse"; import { CustomError } from "../utils/error/customError"; import Workspace from "../models/workspaceModel"; +import mongoose from "mongoose"; //create a new space export const createSpace = async (req: Request, res: Response) => { @@ -34,22 +35,111 @@ export const createSpace = async (req: Request, res: Response) => { //get all spaces export const getAllSpaces = async (req: Request, res: Response) => { const { workspaceId } = req.query; + if (!workspaceId) { throw new CustomError("Workspace ID is required"); } - const spaces = await Space.find({ workspaceId }); + const spaces = await Space.aggregate([ + { $match: { workspaceId: new mongoose.Types.ObjectId(workspaceId as string) } }, // Filter spaces by workspaceId + { + $lookup: { + from: "lists", + localField: "_id", + foreignField: "spaceId", + as: "lists", + }, + }, + { + $unwind: { + path: "$lists", + preserveNullAndEmptyArrays: true, + }, + }, + { + $lookup: { + from: "tasks", + localField: "lists._id", + foreignField: "listId", + as: "lists.tasks", + }, + }, + { + $group: { + _id: "$_id", + name: { $first: "$name" }, + description: { $first: "$description" }, + workspaceId: { $first: "$workspaceId" }, + lists: { + $push: { + _id: "$lists._id", + name: "$lists.name", + tasks: "$lists.tasks", + }, + }, + }, + }, + ]); + res.status(200).json(new StandardResponse("Spaces fetched successfully", spaces, 200)); }; //get a specific space by id export const getSpaceById = async (req: Request, res: Response) => { const { id } = req.params; - const space = await Space.findById(id); - if (!space) { + + const space = await Space.aggregate([ + { $match: { _id: new mongoose.Types.ObjectId(id) } }, // Match the space + { + $lookup: { + from: "lists", // Collection name for lists + localField: "_id", + foreignField: "spaceId", // Assuming lists have a `spaceId` field + as: "lists", + }, + }, + { + $lookup: { + from: "tasks", // Collection name for tasks + localField: "lists._id", + foreignField: "listId", // Assuming tasks have a `listId` field + as: "tasks", + }, + }, + { + $addFields: { + lists: { + $map: { + input: "$lists", + as: "list", + in: { + $mergeObjects: [ + "$$list", + { + tasks: { + $filter: { + input: "$tasks", + as: "task", + cond: { $eq: ["$$task.listId", "$$list._id"] }, + }, + }, + }, + ], + }, + }, + }, + }, + }, + { $project: { tasks: 0 } }, // Remove the top-level tasks array + ]); + + console.log(space); + + if (!space.length) { throw new CustomError("Space not found"); } - res.status(200).json(new StandardResponse("Space fetched successfully", space, 200)); + + res.status(200).json(new StandardResponse("Space fetched successfully", space[0], 200)); }; //update a specific space by id diff --git a/src/controllers/taskController.ts b/src/controllers/taskController.ts index 478dbe8..47410cb 100644 --- a/src/controllers/taskController.ts +++ b/src/controllers/taskController.ts @@ -5,74 +5,73 @@ import { StandardResponse } from "../utils/standardResponse"; import { CustomError } from "../utils/error/customError"; export const createTask = async (req: CustomRequest, res: Response) => { - const { title, description, dueDate, priority, assignedTo, listId } = - req.body; + const { title, description, dueDate, priority, assignedTo } = req.body; + const { listId } = req.params; - const task = new Task({ - listId, - title, - description, - dueDate, - priority, - assignedTo, - }); + const task = new Task({ + listId, + title, + description, + dueDate, + priority, + assignedTo, + }); - await task.save(); + await task.save(); - res.status(201).json(new StandardResponse("task created successfully", task)); + res.status(201).json(new StandardResponse("task created successfully", task)); }; -export const getAllTasks = async (req: CustomRequest, res: Response) => { - const { listId } = req.params; - const tasks = await Task.find({ listId }); +export const getTaskById = async (req: CustomRequest, res: Response) => { + const { taskId } = req.params; - if (tasks.length === 0) { - throw new CustomError("Task retrieved succesfully", 404); - } - res - .status(200) - .json(new StandardResponse("Task retrieved succesfully", tasks, 200)); + const task = await Task.findById(taskId); + + if (!task) { + throw new CustomError("Task not found", 404); + } + + res.status(200).json(new StandardResponse("Task retrieved succesfully", task, 200)); }; export const updateTask = async (req: CustomRequest, res: Response) => { - const { taskId } = req.params; - const { title, description, dueDate, priority, assignedTo, listId } = - req.body; + const { taskId } = req.params; + const { title, description, dueDate, priority, assignedTo, listId } = req.body; - const task = await Task.findByIdAndUpdate( - taskId, - { title, description, dueDate, priority, assignedTo, listId }, - { new: true } - ); + const task = await Task.findByIdAndUpdate( + taskId, + { title, description, dueDate, priority, assignedTo, listId }, + { new: true }, + ); - if (!task) { - throw new CustomError("Task not found", 404); - } + if (!task) { + throw new CustomError("Task not found", 404); + } - res.status(200).json(new StandardResponse("Task updated successfully", task)); + res.status(200).json(new StandardResponse("Task updated successfully", task)); }; export const deleteTask = async (req: CustomRequest, res: Response) => { - const { taskId } = req.params; + const { taskId } = req.params; - const task = await Task.findByIdAndDelete(taskId); + const task = await Task.findByIdAndDelete(taskId); - if (!task) { - throw new CustomError("Task not found", 404); - } + if (!task) { + throw new CustomError("Task not found", 404); + } - res.status(200).json(new StandardResponse("Task deleted successfully")); + res.status(200).json(new StandardResponse("Task deleted successfully")); }; export const moveTask = async (req: CustomRequest, res: Response) => { - const { taskId } = req.params; - const { listId } = req.body; + const { taskId } = req.params; + const { targetListId } = req.body; - const task = await Task.findByIdAndUpdate(taskId, { listId }, { new: true }); + const task = await Task.findByIdAndUpdate(taskId, { listId: targetListId }, { new: true }); - if (!task) { - throw new CustomError("Task not found", 404); - } + if (!task) { + throw new CustomError("Task not found", 404); + } - res.status(200).json(new StandardResponse("Task moved successfully", task)); + res.status(200).json(new StandardResponse("Task moved successfully", task)); }; diff --git a/src/models/taskModel.ts b/src/models/taskModel.ts index c757d60..8dc32c1 100644 --- a/src/models/taskModel.ts +++ b/src/models/taskModel.ts @@ -5,6 +5,7 @@ interface ITask extends Document { title: string; description?: string; dueDate?: Date; + priority?: string; assignedTo?: Types.ObjectId; } @@ -13,6 +14,7 @@ const taskSchema: Schema = new Schema( listId: { type: Schema.Types.ObjectId, ref: "list", required: true }, title: { type: String, required: true }, description: { type: String }, + priority: { type: String, default: "medium" }, dueDate: { type: Date }, assignedTo: [{ type: Schema.Types.ObjectId, ref: "user" }], }, diff --git a/src/routes/taskRoutes.ts b/src/routes/taskRoutes.ts index cc7806f..78cf1d4 100644 --- a/src/routes/taskRoutes.ts +++ b/src/routes/taskRoutes.ts @@ -2,45 +2,22 @@ import { Router } from "express"; import { verifyToken } from "../middlewares/verifyToken"; import { validateData } from "../middlewares/zodValidation"; import { createTasksSchema, updateTaskSchema } from "../utils/zodSchemas"; -import { - createTask, - deleteTask, - getAllTasks, - moveTask, - updateTask, -} from "../controllers/taskController"; +import { createTask, deleteTask, getTaskById, moveTask, updateTask } from "../controllers/taskController"; import { errorCatch } from "../utils/error/errorCatch"; const router = Router(); router.use(verifyToken); -router.get( - "/:spaceId/lists/:listId/tasks", - verifyToken, - errorCatch(getAllTasks) -); +router.get("/:spaceId/lists/:listId/tasks/:taskId", verifyToken, errorCatch(getTaskById)); -router.post( - "/:spaceId/lists/:listId/tasks", - verifyToken, - validateData(createTasksSchema), - errorCatch(createTask) -); +router.post("/:spaceId/lists/:listId/tasks", verifyToken, validateData(createTasksSchema), errorCatch(createTask)); router.put( - "/:spaceId/lists/:listId/tasks/:taskId", - verifyToken, - validateData(updateTaskSchema), - errorCatch(updateTask) -); -router.delete( - "/:spaceId/lists/:listId/tasks/:taskId", - verifyToken, - errorCatch(deleteTask) -); -router.patch( - "/:spaceId/lists/:listId/tasks/:taskId", - verifyToken, - errorCatch(moveTask) + "/:spaceId/lists/:listId/tasks/:taskId", + verifyToken, + validateData(updateTaskSchema), + errorCatch(updateTask), ); +router.delete("/:spaceId/lists/:listId/tasks/:taskId", verifyToken, errorCatch(deleteTask)); +router.patch("/:spaceId/lists/:listId/tasks/:taskId", verifyToken, errorCatch(moveTask)); export default router; diff --git a/src/utils/zodSchemas.ts b/src/utils/zodSchemas.ts index 3e101e2..518ec12 100644 --- a/src/utils/zodSchemas.ts +++ b/src/utils/zodSchemas.ts @@ -1,4 +1,4 @@ -import { z } from "zod"; +import { string, z } from "zod"; const loginSchema = z.object({ email: z.string(), @@ -60,18 +60,17 @@ const inviteSchema = z.object({ const createTasksSchema = z.object({ title: z.string().min(3, "Task name must be at least 3 characters"), description: z.string().optional(), - listId: z.string(), - dueDate: z.date().optional(), + dueDate: z.string().optional(), priority: z.string().optional(), - assignedTo: z.string().optional(), + assignedTo: z.array(z.string()).optional(), }); const updateTaskSchema = z.object({ title: z.string().optional(), description: z.string().optional(), - listId: z.string(), - dueDate: z.date().optional(), + listId: z.string().optional(), + dueDate: z.string().optional(), priority: z.string().optional(), - assignedTo: z.string().optional(), + assignedTo: z.array(z.string()).optional(), }); export { From aea03991e267fbb31e7ff2c5084fdd6e64acfd7b Mon Sep 17 00:00:00 2001 From: Fasalu Rahman <145885597+fa-salu@users.noreply.github.com> Date: Thu, 21 Nov 2024 09:29:00 +0530 Subject: [PATCH 18/30] Feature/list (#21) * populate members deatails with workspace * api update for integration * update space schema and admin , space controller --- src/controllers/admin/adminAuthController.ts | 9 +-- src/controllers/admin/adminController.ts | 85 +++++++++++++++++--- src/controllers/spaceController.ts | 2 - src/models/userModel.ts | 4 + src/utils/zodSchemas.ts | 2 + 5 files changed, 81 insertions(+), 21 deletions(-) diff --git a/src/controllers/admin/adminAuthController.ts b/src/controllers/admin/adminAuthController.ts index 0e05eef..34048b8 100644 --- a/src/controllers/admin/adminAuthController.ts +++ b/src/controllers/admin/adminAuthController.ts @@ -6,7 +6,6 @@ import { StandardResponse } from "../../utils/standardResponse"; export const adminLogin = async (req: Request, res: Response) => { const { email, password } = req.body; - console.log(email, password); const ADMIN_KEY = process.env.ADMIN_KEY || ""; const ADMIN_PASSWORD = process.env.ADMIN_PASSWORD || ""; @@ -30,11 +29,5 @@ export const adminLogin = async (req: Request, res: Response) => { }, ); - const response = { - email: ADMIN_KEY, - role: "admin", - token, - }; - - res.status(200).json(new StandardResponse("Admin login successful", response)); + res.status(200).json(new StandardResponse(token)); }; diff --git a/src/controllers/admin/adminController.ts b/src/controllers/admin/adminController.ts index c2b248e..e20032b 100644 --- a/src/controllers/admin/adminController.ts +++ b/src/controllers/admin/adminController.ts @@ -13,32 +13,43 @@ export const getAllUsers = async (req: CustomRequest, res: Response) => { const users = await User.find().select("-password"); - res.status(200).json(new StandardResponse("Users retrieved successfully", users)); + const usersWithActiveWorkspaces = await Promise.all( + users.map(async (user) => { + const activeWorkspaces = await Workspace.find({ + members: { $in: [user._id] }, + }); + + return { + ...user.toObject(), + activeWorkspaces, + }; + }), + ); + + res + .status(200) + .json(new StandardResponse("Users with active workspaces retrieved successfully", usersWithActiveWorkspaces)); }; export const blockUser = async (req: CustomRequest, res: Response) => { const { userId } = req.params; - const { isBlocked } = req.query; - console.log("param", userId); + const { action } = req.query; - if (isBlocked !== "true" && isBlocked !== "false") { - throw new CustomError("Invalid query parameter for 'isBlocked'", 400); + if (action !== "block" && action !== "unblock") { + throw new CustomError("Invalid query parameter for 'action'", 400); } const user = await User.findById(userId); - console.log("find", user); if (!user) { throw new CustomError("User not found", 404); } - user.isBlocked = isBlocked === "true"; + user.isBlocked = action === "block"; await user.save(); - res - .status(200) - .json(new StandardResponse(`User ${isBlocked === "true" ? "blocked" : "unblocked"} successfully`, user)); + res.status(200).json(new StandardResponse(`User ${action === "block" ? "blocked" : "unblocked"} successfully`, user)); }; export const getAllWorkspacesWithSpaces = async (req: CustomRequest, res: Response) => { @@ -46,6 +57,10 @@ export const getAllWorkspacesWithSpaces = async (req: CustomRequest, res: Respon throw new CustomError("Unauthorized access", 401); } + const { page = 1, limit = 10 } = req.query; + + const skip = (Number(page) - 1) * Number(limit); + const workspacesWithSpaces = await Workspace.aggregate([ { $lookup: { @@ -55,9 +70,24 @@ export const getAllWorkspacesWithSpaces = async (req: CustomRequest, res: Respon as: "spaces", }, }, + { + $facet: { + data: [{ $skip: skip }, { $limit: Number(limit) }], + totalCount: [{ $count: "count" }], + }, + }, ]); - res.status(200).json(new StandardResponse("Workspaces with spaces retrieved successfully", workspacesWithSpaces)); + const total = workspacesWithSpaces[0]?.totalCount[0]?.count || 0; + + res.status(200).json( + new StandardResponse("Workspaces with spaces retrieved successfully", { + data: workspacesWithSpaces[0]?.data || [], + total, + page: Number(page), + limit: Number(limit), + }), + ); }; export const getWorkspaceById = async (req: CustomRequest, res: Response) => { @@ -66,6 +96,7 @@ export const getWorkspaceById = async (req: CustomRequest, res: Response) => { if (!req.user) { throw new CustomError("Unauthorized access", 401); } + const workspaceWithSpaces = await Workspace.aggregate([ { $match: { _id: new mongoose.Types.ObjectId(workspaceId) }, @@ -78,6 +109,38 @@ export const getWorkspaceById = async (req: CustomRequest, res: Response) => { as: "spaces", }, }, + { + $lookup: { + from: "users", + localField: "members", + foreignField: "_id", + as: "members", + pipeline: [ + { + $project: { + password: 0, + __v: 0, + }, + }, + ], + }, + }, + { + $lookup: { + from: "users", + localField: "pendingMembers", + foreignField: "_id", + as: "pendingMembers", + pipeline: [ + { + $project: { + password: 0, + __v: 0, + }, + }, + ], + }, + }, ]); if (!workspaceWithSpaces.length) { diff --git a/src/controllers/spaceController.ts b/src/controllers/spaceController.ts index 04a68f8..6b53285 100644 --- a/src/controllers/spaceController.ts +++ b/src/controllers/spaceController.ts @@ -133,8 +133,6 @@ export const getSpaceById = async (req: Request, res: Response) => { { $project: { tasks: 0 } }, // Remove the top-level tasks array ]); - console.log(space); - if (!space.length) { throw new CustomError("Space not found"); } diff --git a/src/models/userModel.ts b/src/models/userModel.ts index e05d1d8..e678c13 100644 --- a/src/models/userModel.ts +++ b/src/models/userModel.ts @@ -37,6 +37,10 @@ const userSchema: Schema = new Schema( type: String, required: false, }, + status: { + type: String, + default: "active", + }, verified: { type: Boolean, default: false, diff --git a/src/utils/zodSchemas.ts b/src/utils/zodSchemas.ts index 518ec12..becbdaf 100644 --- a/src/utils/zodSchemas.ts +++ b/src/utils/zodSchemas.ts @@ -10,6 +10,7 @@ const registerSchema = z.object({ lastName: z.string().optional(), email: z.string().email(), password: z.string().min(4), + image: z.string().optional(), }); const otpSchema = z.object({ @@ -26,6 +27,7 @@ const listSchema = z.object({ name: z.string().min(3, "List name must be at least 3 characters"), description: z.string().optional(), color: z.string().optional(), + task: z.array(z.string()).optional(), spaceId: z.string().optional(), }); From 02f18bd6f0f555eb062ef1c90138785da5583f8b Mon Sep 17 00:00:00 2001 From: Sinan Date: Fri, 22 Nov 2024 10:51:17 +0530 Subject: [PATCH 19/30] web socket completed (#22) --- package-lock.json | 208 ++++++++++++++++++++++++++++++ package.json | 2 + src/controllers/chatController.ts | 9 +- src/index.ts | 4 +- src/models/chatModel.ts | 6 +- src/socket/socket.ts | 64 +++++++++ 6 files changed, 286 insertions(+), 7 deletions(-) create mode 100644 src/socket/socket.ts diff --git a/package-lock.json b/package-lock.json index 72d3650..b1148b3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,6 +18,7 @@ "mongoose": "^8.8.1", "nodemailer": "^6.9.16", "otp-generator": "^4.0.1", + "socket.io": "^4.8.1", "zod": "^3.23.8" }, "devDependencies": { @@ -28,6 +29,7 @@ "@types/node": "^22.9.0", "@types/nodemailer": "^6.4.16", "@types/otp-generator": "^4.0.2", + "@types/socket.io": "^3.0.2", "nodemon": "^3.1.7", "ts-node": "^10.9.2", "typescript": "^5.6.3" @@ -253,6 +255,11 @@ "sparse-bitfield": "^3.0.3" } }, + "node_modules/@socket.io/component-emitter": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", + "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==" + }, "node_modules/@tsconfig/node10": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", @@ -306,6 +313,11 @@ "@types/node": "*" } }, + "node_modules/@types/cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==" + }, "node_modules/@types/cors": { "version": "2.8.17", "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz", @@ -417,6 +429,16 @@ "@types/send": "*" } }, + "node_modules/@types/socket.io": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/socket.io/-/socket.io-3.0.2.tgz", + "integrity": "sha512-pu0sN9m5VjCxBZVK8hW37ZcMe8rjn4HHggBN5CbaRTvFwv5jOmuIRZEuddsBPa9Th0ts0SIo3Niukq+95cMBbQ==", + "deprecated": "This is a stub types definition. socket.io provides its own type definitions, so you do not need this installed.", + "dev": true, + "dependencies": { + "socket.io": "*" + } + }, "node_modules/@types/webidl-conversions": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz", @@ -565,6 +587,14 @@ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, + "node_modules/base64id": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", + "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", + "engines": { + "node": "^4.5.0 || >= 5.9" + } + }, "node_modules/bcrypt": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.1.1.tgz", @@ -883,6 +913,63 @@ "node": ">= 0.8" } }, + "node_modules/engine.io": { + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.2.tgz", + "integrity": "sha512-gmNvsYi9C8iErnZdVcJnvCpSKbWTt1E8+JZo8b+daLninywUWi5NQ5STSHZ9rFjFO7imNcvb8Pc5pe/wMR5xEw==", + "dependencies": { + "@types/cookie": "^0.4.1", + "@types/cors": "^2.8.12", + "@types/node": ">=10.0.0", + "accepts": "~1.3.4", + "base64id": "2.0.0", + "cookie": "~0.7.2", + "cors": "~2.8.5", + "debug": "~4.3.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.17.1" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/engine.io-parser": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz", + "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/engine.io/node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/engine.io/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/engine.io/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==" + }, "node_modules/es-define-property": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", @@ -2198,6 +2285,107 @@ "node": ">=10" } }, + "node_modules/socket.io": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.1.tgz", + "integrity": "sha512-oZ7iUCxph8WYRHHcjBEc9unw3adt5CmSNlppj/5Q4k2RIrhl8Z5yY2Xr4j9zj0+wzVZ0bxmYoGSzKJnRl6A4yg==", + "dependencies": { + "accepts": "~1.3.4", + "base64id": "~2.0.0", + "cors": "~2.8.5", + "debug": "~4.3.2", + "engine.io": "~6.6.0", + "socket.io-adapter": "~2.5.2", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/socket.io-adapter": { + "version": "2.5.5", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.5.tgz", + "integrity": "sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==", + "dependencies": { + "debug": "~4.3.4", + "ws": "~8.17.1" + } + }, + "node_modules/socket.io-adapter/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socket.io-adapter/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==" + }, + "node_modules/socket.io-parser": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", + "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-parser/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socket.io-parser/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==" + }, + "node_modules/socket.io/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socket.io/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==" + }, "node_modules/sparse-bitfield": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", @@ -2468,6 +2656,26 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "license": "ISC" }, + "node_modules/ws": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", diff --git a/package.json b/package.json index f4b0b2b..8cd04ed 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "mongoose": "^8.8.1", "nodemailer": "^6.9.16", "otp-generator": "^4.0.1", + "socket.io": "^4.8.1", "zod": "^3.23.8" }, "devDependencies": { @@ -31,6 +32,7 @@ "@types/node": "^22.9.0", "@types/nodemailer": "^6.4.16", "@types/otp-generator": "^4.0.2", + "@types/socket.io": "^3.0.2", "nodemon": "^3.1.7", "ts-node": "^10.9.2", "typescript": "^5.6.3" diff --git a/src/controllers/chatController.ts b/src/controllers/chatController.ts index 5af41c8..5a4f267 100644 --- a/src/controllers/chatController.ts +++ b/src/controllers/chatController.ts @@ -13,6 +13,8 @@ export const createMessages = async (req: CustomRequest, res: Response) => { throw new CustomError("Workspace not found", 400); } + // console.log(workspaceId, "aaa"); + const message = new Chat({ workspace: workspaceId, messages: [ @@ -31,9 +33,12 @@ export const createMessages = async (req: CustomRequest, res: Response) => { export const getMessages = async (req: Request, res: Response) => { const { workspaceId } = req.params; - const messages = await Chat.find({ workspace: workspaceId }).populate("messages.sender"); + console.log(workspaceId); + + const messages = await Chat.find({ workspaceId: workspaceId }).populate("messages.sender"); + console.log(messages); - if (messages.length === 0) { + if (messages.length < 1) { throw new CustomError("Messages not found", 400); } diff --git a/src/index.ts b/src/index.ts index ab7d4a9..de0f8d4 100644 --- a/src/index.ts +++ b/src/index.ts @@ -12,7 +12,7 @@ import listRoutes from "./routes/listRoutes"; import taskRoutes from "./routes/taskRoutes"; import userRoutes from "./routes/userRoutes"; import workspaceRoutes from "./routes/workspaceRoutes"; -const app = express(); +import { app, server } from "./socket/socket"; dotenv.config(); @@ -38,7 +38,7 @@ app.use(globalErrorHandler); mongoose .connect(process.env.MONGO_URI || "") .then(() => { - app.listen(port, () => { + server.listen(port, () => { console.log(`Server is running on port ${port}`); }); }) diff --git a/src/models/chatModel.ts b/src/models/chatModel.ts index b5a94ea..c57edfd 100644 --- a/src/models/chatModel.ts +++ b/src/models/chatModel.ts @@ -2,7 +2,7 @@ import mongoose, { type Document, Schema, type Types } from "mongoose"; interface IMessage { content: string; - timestamp: Date; + timestamp: Date | number; sender: Types.ObjectId; } @@ -15,14 +15,14 @@ const chatSchema: Schema = new Schema( { workspaceId: { type: Schema.Types.ObjectId, - ref: "workspace", + ref: "Workspace", required: true, }, messages: [ { content: { type: String, required: true }, timestamp: { type: Date, required: true }, - sender: { type: Schema.Types.ObjectId, ref: "user", required: true }, + sender: { type: Schema.Types.ObjectId, ref: "User", required: true }, }, ], }, diff --git a/src/socket/socket.ts b/src/socket/socket.ts new file mode 100644 index 0000000..f6b5113 --- /dev/null +++ b/src/socket/socket.ts @@ -0,0 +1,64 @@ +import Chat from "../models/chatModel"; +import { createServer } from "node:http"; +import { Server } from "socket.io"; +import express from "express"; + +export const app = express(); + +export const server = createServer(app); +const io = new Server(server, { + cors: { + origin: "http://localhost:3000", + methods: ["GET", "POST"], + }, +}); + +io.on("connection", async (socket) => { + console.log("A user connected"); + + socket.on("sendMessage", async (message) => { + try { + console.log("Message received:", message); + + // Check if a chat already exists for the given workspaceId + const chat = await Chat.findOne({ workspaceId: message?.workSpaceId }); + + if (chat) { + // If chat exists, update the messages array + chat.messages.push({ + content: message?.content, + timestamp: Date.now(), + sender: message?.sender, + }); + + const updatedChat = await chat.save(); + const populatedChat = await updatedChat.populate("messages.sender"); + + console.log("Chat updated:", populatedChat); + io.emit("receiveMessage", populatedChat); + } else { + // If no chat exists, create a new chat + const newChat = new Chat({ + workspaceId: message?.workSpaceId, + messages: [ + { + content: message?.content, + timestamp: Date.now(), + sender: message?.sender, + }, + ], + }); + + const savedMessage = await (await newChat.save()).populate("messages.sender"); + console.log("Message saved:", savedMessage); + io.emit("receiveMessage", savedMessage); + } + } catch (err) { + console.error("Error saving message to database:", err); + } + }); + + socket.on("disconnect", () => { + console.log("User disconnected"); + }); +}); From 52f3911b8e989ec68b9fc2067f11c2f35e649be8 Mon Sep 17 00:00:00 2001 From: Sinan Date: Fri, 22 Nov 2024 14:33:31 +0530 Subject: [PATCH 20/30] user controller updated (#23) * web socket completed * updated userController --- src/controllers/chatController.ts | 8 ++------ src/controllers/userController.ts | 11 +++++++++++ src/routes/userRoutes.ts | 3 ++- src/utils/workspaceUtils.ts | 2 +- 4 files changed, 16 insertions(+), 8 deletions(-) diff --git a/src/controllers/chatController.ts b/src/controllers/chatController.ts index 5a4f267..e6f4e61 100644 --- a/src/controllers/chatController.ts +++ b/src/controllers/chatController.ts @@ -13,8 +13,6 @@ export const createMessages = async (req: CustomRequest, res: Response) => { throw new CustomError("Workspace not found", 400); } - // console.log(workspaceId, "aaa"); - const message = new Chat({ workspace: workspaceId, messages: [ @@ -33,12 +31,10 @@ export const createMessages = async (req: CustomRequest, res: Response) => { export const getMessages = async (req: Request, res: Response) => { const { workspaceId } = req.params; - console.log(workspaceId); - const messages = await Chat.find({ workspaceId: workspaceId }).populate("messages.sender"); - console.log(messages); + const messages = await Chat.findOne({ workspaceId: workspaceId }).populate("messages.sender"); - if (messages.length < 1) { + if (!messages) { throw new CustomError("Messages not found", 400); } diff --git a/src/controllers/userController.ts b/src/controllers/userController.ts index f02ae42..6a67f86 100644 --- a/src/controllers/userController.ts +++ b/src/controllers/userController.ts @@ -33,3 +33,14 @@ export const updatedUserProfile = async (req: Request, res: Response) => { res.status(200).json(new StandardResponse("User Updated successfully", updatedProfile)); }; + +export const getAllUsers = async (req: Request, res: Response) => { + const users = await User.find(); + console.log(users); + + if (!users || users.length < 1) { + throw new CustomError("Users not found"); + } + + res.status(200).json(new StandardResponse("User Updated successfully", users)); +}; diff --git a/src/routes/userRoutes.ts b/src/routes/userRoutes.ts index e67a2f6..83dd42e 100644 --- a/src/routes/userRoutes.ts +++ b/src/routes/userRoutes.ts @@ -1,5 +1,5 @@ import express from "express"; -import { getUserById, updatedUserProfile } from "../controllers/userController"; +import { getAllUsers, getUserById, updatedUserProfile } from "../controllers/userController"; import { verifyToken } from "../middlewares/verifyToken"; import { errorCatch } from "../utils/error/errorCatch"; import { editUserDetails } from "../utils/zodSchemas"; @@ -9,5 +9,6 @@ const router = express.Router(); router.get("/:userId", verifyToken, errorCatch(getUserById)); router.put("/:userId", verifyToken, validateData(editUserDetails), errorCatch(updatedUserProfile)); +router.get("/", verifyToken, errorCatch(getAllUsers)); export default router; diff --git a/src/utils/workspaceUtils.ts b/src/utils/workspaceUtils.ts index b6b19dd..6a7b19e 100644 --- a/src/utils/workspaceUtils.ts +++ b/src/utils/workspaceUtils.ts @@ -20,7 +20,7 @@ export const sendInvitationEmail = async (email: string, workspace: IWorkspace)

Hello,

You have been invited to join the workspace: ${workspace.name}.

Click the link below to accept the invitation:

- Accept Invitation + Accept Invitation

If you didn't request this invitation, please ignore this email.

Best Regards,

Team DOQUE

From ba44a4b14b87ccdbe76eadc3b0d8a7045b76e5df Mon Sep 17 00:00:00 2001 From: Sinan Date: Sat, 23 Nov 2024 09:20:45 +0530 Subject: [PATCH 21/30] Chat feature with websocket (#25) * web socket completed * updated userController * updated workspaceController --- src/controllers/authController.ts | 3 ++- src/controllers/workspaceController.ts | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/controllers/authController.ts b/src/controllers/authController.ts index f288879..2ebeb64 100644 --- a/src/controllers/authController.ts +++ b/src/controllers/authController.ts @@ -10,7 +10,7 @@ import otpGenerator from "otp-generator"; //Register user const register = async (req: Request, res: Response) => { - const { firstName, lastName, email, password } = req.body; + const { firstName, lastName, email, image, password } = req.body; const saltRounds = Number(process.env.SALT_ROUNDS || "10"); const hashedPassword = await bcrypt.hash(password, saltRounds); @@ -26,6 +26,7 @@ const register = async (req: Request, res: Response) => { const user = new User({ firstName, lastName, + image, email, password: hashedPassword, }); diff --git a/src/controllers/workspaceController.ts b/src/controllers/workspaceController.ts index 8e515d8..ebbf40c 100644 --- a/src/controllers/workspaceController.ts +++ b/src/controllers/workspaceController.ts @@ -29,7 +29,7 @@ export const getActiveWorkspaces = async (req: CustomRequest, res: Response) => const activeWorkspaces = await Workspace.find({ members: { $in: [userId] }, - }); + }).populate("createdBy"); const pendingWorkspaces = await Workspace.find({ pendingMembers: { $in: [userId] }, From 620fcb2c5213e1c37ac8a5659cbb16c706da138c Mon Sep 17 00:00:00 2001 From: Abhijith V <142149141+abhijithjithu0007@users.noreply.github.com> Date: Sat, 23 Nov 2024 09:52:56 +0530 Subject: [PATCH 22/30] Feature/task (#26) * created task route * developed task section api --------- Co-authored-by: ANAS --- src/controllers/taskController.ts | 91 ++++++++++---------- src/controllers/userController.ts | 132 ++++++++++++++++++++++++------ src/index.ts | 20 ++--- src/models/taskModel.ts | 30 +++---- src/routes/taskRoutes.ts | 41 ++++++++-- src/routes/userRoutes.ts | 19 ++++- src/utils/zodSchemas.ts | 106 ++++++++++++------------ 7 files changed, 284 insertions(+), 155 deletions(-) diff --git a/src/controllers/taskController.ts b/src/controllers/taskController.ts index 47410cb..08aa715 100644 --- a/src/controllers/taskController.ts +++ b/src/controllers/taskController.ts @@ -5,73 +5,80 @@ import { StandardResponse } from "../utils/standardResponse"; import { CustomError } from "../utils/error/customError"; export const createTask = async (req: CustomRequest, res: Response) => { - const { title, description, dueDate, priority, assignedTo } = req.body; - const { listId } = req.params; + const { title, description, dueDate, priority, assignedTo } = req.body; + const { listId } = req.params; - const task = new Task({ - listId, - title, - description, - dueDate, - priority, - assignedTo, - }); + const task = new Task({ + listId, + title, + description, + dueDate, + priority, + assignedTo, + }); - await task.save(); + await task.save(); - res.status(201).json(new StandardResponse("task created successfully", task)); + res.status(201).json(new StandardResponse("task created successfully", task)); }; export const getTaskById = async (req: CustomRequest, res: Response) => { - const { taskId } = req.params; + const { taskId } = req.params; - const task = await Task.findById(taskId); + const task = await Task.findById(taskId); - if (!task) { - throw new CustomError("Task not found", 404); - } + if (!task) { + throw new CustomError("Task not found", 404); + } - res.status(200).json(new StandardResponse("Task retrieved succesfully", task, 200)); + res + .status(200) + .json(new StandardResponse("Task retrieved succesfully", task, 200)); }; export const updateTask = async (req: CustomRequest, res: Response) => { - const { taskId } = req.params; - const { title, description, dueDate, priority, assignedTo, listId } = req.body; + const { taskId } = req.params; + const { title, description, dueDate, priority, assignedTo, listId } = + req.body; - const task = await Task.findByIdAndUpdate( - taskId, - { title, description, dueDate, priority, assignedTo, listId }, - { new: true }, - ); + const task = await Task.findByIdAndUpdate( + taskId, + { title, description, dueDate, priority, assignedTo, listId }, + { new: true } + ); - if (!task) { - throw new CustomError("Task not found", 404); - } + if (!task) { + throw new CustomError("Task not found", 404); + } - res.status(200).json(new StandardResponse("Task updated successfully", task)); + res.status(200).json(new StandardResponse("Task updated successfully", task)); }; export const deleteTask = async (req: CustomRequest, res: Response) => { - const { taskId } = req.params; + const { taskId } = req.params; - const task = await Task.findByIdAndDelete(taskId); + const task = await Task.findByIdAndDelete(taskId); - if (!task) { - throw new CustomError("Task not found", 404); - } + if (!task) { + throw new CustomError("Task not found", 404); + } - res.status(200).json(new StandardResponse("Task deleted successfully")); + res.status(200).json(new StandardResponse("Task deleted successfully")); }; export const moveTask = async (req: CustomRequest, res: Response) => { - const { taskId } = req.params; - const { targetListId } = req.body; + const { taskId } = req.params; + const { targetListId } = req.body; - const task = await Task.findByIdAndUpdate(taskId, { listId: targetListId }, { new: true }); + const task = await Task.findByIdAndUpdate( + taskId, + { listId: targetListId }, + { new: true } + ); - if (!task) { - throw new CustomError("Task not found", 404); - } + if (!task) { + throw new CustomError("Task not found", 404); + } - res.status(200).json(new StandardResponse("Task moved successfully", task)); + res.status(200).json(new StandardResponse("Task moved successfully", task)); }; diff --git a/src/controllers/userController.ts b/src/controllers/userController.ts index 6a67f86..a6aa8c4 100644 --- a/src/controllers/userController.ts +++ b/src/controllers/userController.ts @@ -3,44 +3,128 @@ import { User } from "../models/userModel"; import { StandardResponse } from "../utils/standardResponse"; import { CustomError } from "../utils/error/customError"; import type { CustomRequest } from "../types/interfaces"; +import Task from "../models/taskModel"; +import mongoose from "mongoose"; export const getUserById = async (req: CustomRequest, res: Response) => { - const userId = req.user?.id; - const user = await User.findById(userId); + const userId = req.user?.id; + const user = await User.findById(userId); - if (!user) { - throw new CustomError("User not found"); - } + if (!user) { + throw new CustomError("User not found"); + } - res.status(200).json(new StandardResponse("Profile fetched successfully", user)); + res + .status(200) + .json(new StandardResponse("Profile fetched successfully", user)); }; export const updatedUserProfile = async (req: Request, res: Response) => { - const userId = req.params.userId; - const { firstName, lastName, phoneNumber, image } = req.body; + const userId = req.params.userId; + const { firstName, lastName, phoneNumber, image } = req.body; - const user = await User.findById(userId); + const user = await User.findById(userId); - if (!user) { - throw new CustomError("User not found"); - } + if (!user) { + throw new CustomError("User not found"); + } - const updatedProfile = await User.findByIdAndUpdate( - userId, - { firstName, lastName, phoneNumber, image }, - { new: true }, - ); + const updatedProfile = await User.findByIdAndUpdate( + userId, + { firstName, lastName, phoneNumber, image }, + { new: true } + ); - res.status(200).json(new StandardResponse("User Updated successfully", updatedProfile)); + res + .status(200) + .json(new StandardResponse("User Updated successfully", updatedProfile)); }; export const getAllUsers = async (req: Request, res: Response) => { - const users = await User.find(); - console.log(users); + const users = await User.find(); - if (!users || users.length < 1) { - throw new CustomError("Users not found"); - } + if (!users || users.length < 1) { + throw new CustomError("Users not found"); + } - res.status(200).json(new StandardResponse("User Updated successfully", users)); + res + .status(200) + .json(new StandardResponse("User Updated successfully", users)); +}; + +export const taskAssignedToUser = async (req: Request, res: Response) => { + const { userId } = req.params; + + if (!userId) { + throw new CustomError("User ID is required", 400); + } + + try { + const tasks = await Task.find({ assignedTo: userId }); + + if (!tasks || tasks.length === 0) { + throw new CustomError("No tasks found for the specified user", 404); + } + + const tasksWithDetails = await Task.aggregate([ + { + $match: { + _id: { $in: tasks.map((task) => task._id) }, + }, + }, + { + $lookup: { + from: "lists", + localField: "listId", + foreignField: "_id", + as: "listDetails", + }, + }, + { $unwind: "$listDetails" }, + { + $lookup: { + from: "spaces", + localField: "listDetails.spaceId", + foreignField: "_id", + as: "spaceDetails", + }, + }, + { $unwind: "$spaceDetails" }, + { + $project: { + _id: 0, + task: { + id: "$_id", + name: "$title", + priority: "$priority", + dueDate: "$dueDate", + }, + list: { + id: "$listDetails._id", + name: "$listDetails.name", + }, + space: { + id: "$spaceDetails._id", + name: "$spaceDetails.name", + }, + }, + }, + ]); + + if (!tasksWithDetails || tasksWithDetails.length === 0) { + throw new CustomError("No tasks found with list and space details", 404); + } + + res + .status(200) + .json( + new StandardResponse( + "Tasks assigned to the user retrieved successfully", + tasksWithDetails + ) + ); + } catch (error) { + console.error("Error retrieving tasks:", error); + throw new CustomError("Internal server error", 500); + } }; diff --git a/src/index.ts b/src/index.ts index de0f8d4..9f93b5f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -21,7 +21,7 @@ const port = process.env.PORT || 3001; app.use(express.json()); app.get("/", (_req, res) => { - res.send("Hello World!"); + res.send("Hello World!"); }); app.use(cors({ origin: process.env.CLIENT_URL, credentials: true })); @@ -36,12 +36,12 @@ app.use("/api/workspace", workspaceRoutes); app.use(globalErrorHandler); mongoose - .connect(process.env.MONGO_URI || "") - .then(() => { - server.listen(port, () => { - console.log(`Server is running on port ${port}`); - }); - }) - .catch((err) => { - console.error(err); - }); + .connect(process.env.MONGO_URI || "") + .then(() => { + server.listen(port, () => { + console.log(`Server is running on port ${port}`); + }); + }) + .catch((err) => { + console.error(err); + }); diff --git a/src/models/taskModel.ts b/src/models/taskModel.ts index 8dc32c1..55dc47b 100644 --- a/src/models/taskModel.ts +++ b/src/models/taskModel.ts @@ -1,24 +1,24 @@ import mongoose, { type Document, Schema, type Types } from "mongoose"; interface ITask extends Document { - listId: Types.ObjectId; - title: string; - description?: string; - dueDate?: Date; - priority?: string; - assignedTo?: Types.ObjectId; + listId: Types.ObjectId; + title: string; + description?: string; + dueDate?: Date; + priority?: string; + assignedTo?: Types.ObjectId; } const taskSchema: Schema = new Schema( - { - listId: { type: Schema.Types.ObjectId, ref: "list", required: true }, - title: { type: String, required: true }, - description: { type: String }, - priority: { type: String, default: "medium" }, - dueDate: { type: Date }, - assignedTo: [{ type: Schema.Types.ObjectId, ref: "user" }], - }, - { timestamps: true }, + { + listId: { type: Schema.Types.ObjectId, ref: "list", required: true }, + title: { type: String, required: true }, + description: { type: String }, + priority: { type: String, default: "medium" }, + dueDate: { type: Date }, + assignedTo: [{ type: Schema.Types.ObjectId, ref: "user" }], + }, + { timestamps: true } ); const Task = mongoose.model("Task", taskSchema); diff --git a/src/routes/taskRoutes.ts b/src/routes/taskRoutes.ts index 78cf1d4..6353e95 100644 --- a/src/routes/taskRoutes.ts +++ b/src/routes/taskRoutes.ts @@ -2,22 +2,45 @@ import { Router } from "express"; import { verifyToken } from "../middlewares/verifyToken"; import { validateData } from "../middlewares/zodValidation"; import { createTasksSchema, updateTaskSchema } from "../utils/zodSchemas"; -import { createTask, deleteTask, getTaskById, moveTask, updateTask } from "../controllers/taskController"; +import { + createTask, + deleteTask, + getTaskById, + moveTask, + updateTask, +} from "../controllers/taskController"; import { errorCatch } from "../utils/error/errorCatch"; const router = Router(); router.use(verifyToken); -router.get("/:spaceId/lists/:listId/tasks/:taskId", verifyToken, errorCatch(getTaskById)); +router.get( + "/:spaceId/lists/:listId/tasks/:taskId", + verifyToken, + errorCatch(getTaskById) +); -router.post("/:spaceId/lists/:listId/tasks", verifyToken, validateData(createTasksSchema), errorCatch(createTask)); +router.post( + "/:spaceId/lists/:listId/tasks", + verifyToken, + validateData(createTasksSchema), + errorCatch(createTask) +); router.put( - "/:spaceId/lists/:listId/tasks/:taskId", - verifyToken, - validateData(updateTaskSchema), - errorCatch(updateTask), + "/:spaceId/lists/:listId/tasks/:taskId", + verifyToken, + validateData(updateTaskSchema), + errorCatch(updateTask) +); +router.delete( + "/:spaceId/lists/:listId/tasks/:taskId", + verifyToken, + errorCatch(deleteTask) +); +router.patch( + "/:spaceId/lists/:listId/tasks/:taskId", + verifyToken, + errorCatch(moveTask) ); -router.delete("/:spaceId/lists/:listId/tasks/:taskId", verifyToken, errorCatch(deleteTask)); -router.patch("/:spaceId/lists/:listId/tasks/:taskId", verifyToken, errorCatch(moveTask)); export default router; diff --git a/src/routes/userRoutes.ts b/src/routes/userRoutes.ts index 83dd42e..f2babf8 100644 --- a/src/routes/userRoutes.ts +++ b/src/routes/userRoutes.ts @@ -1,5 +1,10 @@ import express from "express"; -import { getAllUsers, getUserById, updatedUserProfile } from "../controllers/userController"; +import { + getAllUsers, + getUserById, + taskAssignedToUser, + updatedUserProfile, +} from "../controllers/userController"; import { verifyToken } from "../middlewares/verifyToken"; import { errorCatch } from "../utils/error/errorCatch"; import { editUserDetails } from "../utils/zodSchemas"; @@ -8,7 +13,17 @@ import { validateData } from "../middlewares/zodValidation"; const router = express.Router(); router.get("/:userId", verifyToken, errorCatch(getUserById)); -router.put("/:userId", verifyToken, validateData(editUserDetails), errorCatch(updatedUserProfile)); +router.put( + "/:userId", + verifyToken, + validateData(editUserDetails), + errorCatch(updatedUserProfile) +); router.get("/", verifyToken, errorCatch(getAllUsers)); +router.get( + "/:userId/assigned-tasks", + verifyToken, + errorCatch(taskAssignedToUser) +); export default router; diff --git a/src/utils/zodSchemas.ts b/src/utils/zodSchemas.ts index becbdaf..fe81110 100644 --- a/src/utils/zodSchemas.ts +++ b/src/utils/zodSchemas.ts @@ -1,91 +1,91 @@ import { string, z } from "zod"; const loginSchema = z.object({ - email: z.string(), - password: z.string(), + email: z.string(), + password: z.string(), }); const registerSchema = z.object({ - firstName: z.string().min(3), - lastName: z.string().optional(), - email: z.string().email(), - password: z.string().min(4), - image: z.string().optional(), + firstName: z.string().min(3), + lastName: z.string().optional(), + email: z.string().email(), + password: z.string().min(4), + image: z.string().optional(), }); const otpSchema = z.object({ - email: z.string().email(), - otp: z.string(), + email: z.string().email(), + otp: z.string(), }); const spaceSchema = z.object({ - name: z.string().min(3), - description: z.string().optional(), + name: z.string().min(3), + description: z.string().optional(), }); const listSchema = z.object({ - name: z.string().min(3, "List name must be at least 3 characters"), - description: z.string().optional(), - color: z.string().optional(), - task: z.array(z.string()).optional(), - spaceId: z.string().optional(), + name: z.string().min(3, "List name must be at least 3 characters"), + description: z.string().optional(), + color: z.string().optional(), + task: z.array(z.string()).optional(), + spaceId: z.string().optional(), }); const adminLoginSchema = z.object({ - email: z.string().email(), - password: z.string().min(6), + email: z.string().email(), + password: z.string().min(6), }); const chatSchema = z.object({ - content: z.string().min(1), + content: z.string().min(1), }); const editUserDetails = z.object({ - firstName: z.string().min(3), - lastName: z.string().optional(), - phoneNumber: z.string().optional(), - image: z.string().optional(), + firstName: z.string().min(3), + lastName: z.string().optional(), + phoneNumber: z.string().optional(), + image: z.string().optional(), }); const workspaceSchema = z - .object({ - name: z.string(), - description: z.string().optional(), - visibility: z.string().optional(), - }) - .strict(); + .object({ + name: z.string(), + description: z.string().optional(), + visibility: z.string().optional(), + }) + .strict(); const inviteSchema = z.object({ - email: z.string().email(), + email: z.string().email(), }); const createTasksSchema = z.object({ - title: z.string().min(3, "Task name must be at least 3 characters"), - description: z.string().optional(), - dueDate: z.string().optional(), - priority: z.string().optional(), - assignedTo: z.array(z.string()).optional(), + title: z.string().min(3, "Task name must be at least 3 characters"), + description: z.string().optional(), + dueDate: z.string().optional(), + priority: z.string().optional(), + assignedTo: z.array(z.string()).optional(), }); const updateTaskSchema = z.object({ - title: z.string().optional(), - description: z.string().optional(), - listId: z.string().optional(), - dueDate: z.string().optional(), - priority: z.string().optional(), - assignedTo: z.array(z.string()).optional(), + title: z.string().optional(), + description: z.string().optional(), + listId: z.string().optional(), + dueDate: z.string().optional(), + priority: z.string().optional(), + assignedTo: z.array(z.string()).optional(), }); export { - loginSchema, - registerSchema, - otpSchema, - spaceSchema, - listSchema, - adminLoginSchema, - chatSchema, - workspaceSchema, - inviteSchema, - editUserDetails, - createTasksSchema, - updateTaskSchema, + loginSchema, + registerSchema, + otpSchema, + spaceSchema, + listSchema, + adminLoginSchema, + chatSchema, + workspaceSchema, + inviteSchema, + editUserDetails, + createTasksSchema, + updateTaskSchema, }; From 37a3e3a6d8180fc100f7475dd0c5f6804bb59dcf Mon Sep 17 00:00:00 2001 From: Sahad Date: Sat, 23 Nov 2024 10:32:47 +0530 Subject: [PATCH 23/30] Feature/payment integration (#24) * integrate payment using stripe * install socket --- package-lock.json | 25 +++++++ package.json | 2 + src/controllers/paymentControllers.ts | 101 ++++++++++++++++++++++++++ src/index.ts | 3 +- src/models/paymentModel.ts | 26 +++++++ src/models/userModel.ts | 5 ++ src/routes/paymentRoutes.ts | 12 +++ 7 files changed, 173 insertions(+), 1 deletion(-) create mode 100644 src/controllers/paymentControllers.ts create mode 100644 src/models/paymentModel.ts create mode 100644 src/routes/paymentRoutes.ts diff --git a/package-lock.json b/package-lock.json index b1148b3..338349b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "license": "ISC", "dependencies": { "@types/cors": "^2.8.17", + "@types/stripe": "^8.0.416", "bcrypt": "^5.1.1", "cors": "^2.8.5", "dotenv": "^16.4.5", @@ -19,6 +20,7 @@ "nodemailer": "^6.9.16", "otp-generator": "^4.0.1", "socket.io": "^4.8.1", + "stripe": "^17.4.0", "zod": "^3.23.8" }, "devDependencies": { @@ -439,6 +441,15 @@ "socket.io": "*" } }, + "node_modules/@types/stripe": { + "version": "8.0.416", + "resolved": "https://registry.npmjs.org/@types/stripe/-/stripe-8.0.416.tgz", + "integrity": "sha512-LDA574j7g30dg4R+SI1JIpkS+rkIuXgbe6+/qlf62avd7ZNntbbl2DYZwAIj9CfJYVh7FG/PLeoNB5OXTsEehg==", + "license": "MIT", + "dependencies": { + "stripe": "*" + } + }, "node_modules/@types/webidl-conversions": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz", @@ -878,6 +889,7 @@ "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" }, @@ -2437,6 +2449,19 @@ "node": ">=8" } }, + "node_modules/stripe": { + "version": "17.4.0", + "resolved": "https://registry.npmjs.org/stripe/-/stripe-17.4.0.tgz", + "integrity": "sha512-sQQGZguPxe7/QYXJKtDpfzT2OAH9F8nyE2SOsVdTU793iiU33/dpaKgWaJEGJm8396Yy/6NvTLblgdHlueGLhA==", + "license": "MIT", + "dependencies": { + "@types/node": ">=8.1.0", + "qs": "^6.11.0" + }, + "engines": { + "node": ">=12.*" + } + }, "node_modules/supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", diff --git a/package.json b/package.json index 8cd04ed..daf0449 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "license": "ISC", "dependencies": { "@types/cors": "^2.8.17", + "@types/stripe": "^8.0.416", "bcrypt": "^5.1.1", "cors": "^2.8.5", "dotenv": "^16.4.5", @@ -21,6 +22,7 @@ "mongoose": "^8.8.1", "nodemailer": "^6.9.16", "otp-generator": "^4.0.1", + "stripe": "^17.4.0", "socket.io": "^4.8.1", "zod": "^3.23.8" }, diff --git a/src/controllers/paymentControllers.ts b/src/controllers/paymentControllers.ts new file mode 100644 index 0000000..0314ad5 --- /dev/null +++ b/src/controllers/paymentControllers.ts @@ -0,0 +1,101 @@ +import type { NextFunction, Request, Response } from "express"; +import { CustomError } from "../utils/error/customError"; +import { StandardResponse } from "../utils/standardResponse"; +import { PaymentModel } from "../models/paymentModel"; +import Stripe from "stripe"; +import type { CustomRequest } from "../types/interfaces"; +import { User } from "../models/userModel"; + +// Create Payment Intent +export const createPaymentIntent = async (req: CustomRequest, res: Response, next: NextFunction): Promise => { + const stripe = new Stripe(process.env.STRIPE_SECRET_KEY || ""); + const userId = req.user?.id; + const { amount, subscription, currency } = req.body; + + try { + const session = await stripe.checkout.sessions.create({ + payment_method_types: ["card"], + ui_mode: "embedded", + currency: currency || "inr", + mode: "payment", + line_items: [ + { + price_data: { + currency: currency || "inr", + product_data: { + name: subscription || "Subscription", + }, + unit_amount: amount * 100, + }, + quantity: 1, + }, + ], + return_url: `${process.env.CLIENT_URL}/paymentsuccess?session_id={CHECKOUT_SESSION_ID}`, + }); + + const newPayment = new PaymentModel({ + currency, + status: "pending", + userId, + amount, + subscription, + }); + newPayment.sessionId = session.id; + await newPayment.save(); + + res + .status(200) + .json(new StandardResponse("Payment intent created successfully", { clientSecret: session.client_secret })); + } catch (error) { + console.error("Error creating payment intent:", error); + next(new CustomError((error as Error).message || "Failed to create payment intent", 500)); + } +}; + +// Get Payment Details +export const getPaymentDetails = async (req: CustomRequest, res: Response, next: NextFunction): Promise => { + try { + const { sessionId } = req.params; + + console.log(sessionId); + + const payment = await PaymentModel.findOne({ sessionId }); + + if (!payment) { + throw new CustomError("Payment not found", 404); + } + + res.status(200).json(new StandardResponse("Payment details fetched successfully", payment)); + } catch (error) { + next(new CustomError((error as Error).message || "Failed to fetch payment details", 500)); + } +}; + +//payment success +export const paymentSuccess = async (req: Request, res: Response, next: NextFunction): Promise => { + const { sessionId } = req.params; + + const payment = await PaymentModel.findOne({ sessionId }); + + if (!payment) { + throw new CustomError("Payment not found", 404); + } + + if (payment.status === "paid") { + throw new CustomError("Payment has already been processed", 400, "PAYMENT_ALREADY_PROCESSED"); + } + + payment.status = "paid"; + await payment.save(); + + const user = await User.findById(payment.userId); + + if (!user) { + throw new CustomError("User not found", 404); + } + + user.subscription = payment.subscription; + await user.save(); + + res.status(200).json(new StandardResponse("Payment processed successfully", { payment })); +}; diff --git a/src/index.ts b/src/index.ts index 9f93b5f..6c836a5 100644 --- a/src/index.ts +++ b/src/index.ts @@ -12,6 +12,7 @@ import listRoutes from "./routes/listRoutes"; import taskRoutes from "./routes/taskRoutes"; import userRoutes from "./routes/userRoutes"; import workspaceRoutes from "./routes/workspaceRoutes"; +import paymentRoutes from "./routes/paymentRoutes"; import { app, server } from "./socket/socket"; dotenv.config(); @@ -32,7 +33,7 @@ app.use("/api/chat", chatRoutes); app.use("/api/search", searchRoutes); app.use("/api/userprofile", userRoutes); app.use("/api/workspace", workspaceRoutes); - +app.use("/api/payment", paymentRoutes); app.use(globalErrorHandler); mongoose diff --git a/src/models/paymentModel.ts b/src/models/paymentModel.ts new file mode 100644 index 0000000..6f0f8de --- /dev/null +++ b/src/models/paymentModel.ts @@ -0,0 +1,26 @@ +import type { Document } from "mongoose"; +import mongoose, { Schema } from "mongoose"; + +export interface PaymentDocument extends Document { + amount: number; + currency: string; + status: string; + sessionId: string | null; + userId: string; + subscription: string; + createdAt: Date; +} + +const PaymentSchema = new Schema( + { + amount: { type: Number, required: true }, + currency: { type: String, required: true }, + status: { type: String, required: true }, + userId: { type: String, required: true }, + subscription: { type: String, required: true }, + sessionId: { type: String, required: true }, + }, + { timestamps: true }, +); + +export const PaymentModel = mongoose.model("Payment", PaymentSchema); diff --git a/src/models/userModel.ts b/src/models/userModel.ts index e678c13..c7c81b5 100644 --- a/src/models/userModel.ts +++ b/src/models/userModel.ts @@ -6,6 +6,7 @@ export interface IUser extends Document { email: string; image?: string; password: string; + subscription?: string | null; verified: boolean; isBlocked: boolean; } @@ -41,6 +42,10 @@ const userSchema: Schema = new Schema( type: String, default: "active", }, + subscription: { + type: String, + default: null, + }, verified: { type: Boolean, default: false, diff --git a/src/routes/paymentRoutes.ts b/src/routes/paymentRoutes.ts new file mode 100644 index 0000000..f9a33d5 --- /dev/null +++ b/src/routes/paymentRoutes.ts @@ -0,0 +1,12 @@ +import express from "express"; +import { verifyToken } from "../middlewares/verifyToken"; +import { createPaymentIntent, getPaymentDetails, paymentSuccess } from "../controllers/paymentControllers"; +import { errorCatch } from "../utils/error/errorCatch"; + +const router = express.Router(); + +router.post("/create-payment-intent", verifyToken, createPaymentIntent); +router.get("/payment-details/:sessionId", verifyToken, getPaymentDetails); +router.put("/success/:sessionId", verifyToken, errorCatch(paymentSuccess)); + +export default router; From 567114a29fd8f58cd3efb5f71dcfdda1e959547b Mon Sep 17 00:00:00 2001 From: Sinan Date: Sat, 23 Nov 2024 12:16:47 +0530 Subject: [PATCH 24/30] chatController updated (#27) --- src/controllers/chatController.ts | 8 +- src/controllers/userController.ts | 200 ++++++++++++++---------------- src/routes/userRoutes.ts | 20 +-- src/socket/socket.ts | 7 -- src/utils/mailSender.ts | 1 - 5 files changed, 103 insertions(+), 133 deletions(-) diff --git a/src/controllers/chatController.ts b/src/controllers/chatController.ts index e6f4e61..b5c264d 100644 --- a/src/controllers/chatController.ts +++ b/src/controllers/chatController.ts @@ -35,7 +35,13 @@ export const getMessages = async (req: Request, res: Response) => { const messages = await Chat.findOne({ workspaceId: workspaceId }).populate("messages.sender"); if (!messages) { - throw new CustomError("Messages not found", 400); + const newChat = new Chat({ + workspaceId: workspaceId, + messages: [], + }); + + res.status(200).json(new StandardResponse("Fetched messages successful", newChat)); + return; } res.status(200).json(new StandardResponse("Fetched messages successful", messages)); diff --git a/src/controllers/userController.ts b/src/controllers/userController.ts index a6aa8c4..afb2299 100644 --- a/src/controllers/userController.ts +++ b/src/controllers/userController.ts @@ -4,127 +4,113 @@ import { StandardResponse } from "../utils/standardResponse"; import { CustomError } from "../utils/error/customError"; import type { CustomRequest } from "../types/interfaces"; import Task from "../models/taskModel"; -import mongoose from "mongoose"; export const getUserById = async (req: CustomRequest, res: Response) => { - const userId = req.user?.id; - const user = await User.findById(userId); + const userId = req.user?.id; + const user = await User.findById(userId); - if (!user) { - throw new CustomError("User not found"); - } + if (!user) { + throw new CustomError("User not found"); + } - res - .status(200) - .json(new StandardResponse("Profile fetched successfully", user)); + res.status(200).json(new StandardResponse("Profile fetched successfully", user)); }; export const updatedUserProfile = async (req: Request, res: Response) => { - const userId = req.params.userId; - const { firstName, lastName, phoneNumber, image } = req.body; + const userId = req.params.userId; + const { firstName, lastName, phoneNumber, image } = req.body; - const user = await User.findById(userId); + const user = await User.findById(userId); - if (!user) { - throw new CustomError("User not found"); - } + if (!user) { + throw new CustomError("User not found"); + } - const updatedProfile = await User.findByIdAndUpdate( - userId, - { firstName, lastName, phoneNumber, image }, - { new: true } - ); + const updatedProfile = await User.findByIdAndUpdate( + userId, + { firstName, lastName, phoneNumber, image }, + { new: true }, + ); - res - .status(200) - .json(new StandardResponse("User Updated successfully", updatedProfile)); + res.status(200).json(new StandardResponse("User Updated successfully", updatedProfile)); }; -export const getAllUsers = async (req: Request, res: Response) => { - const users = await User.find(); +export const taskAssignedToUser = async (req: Request, res: Response) => { + const { userId } = req.params; + + if (!userId) { + throw new CustomError("User ID is required", 400); + } + + try { + const tasks = await Task.find({ assignedTo: userId }); + + if (!tasks || tasks.length === 0) { + throw new CustomError("No tasks found for the specified user", 404); + } + + const tasksWithDetails = await Task.aggregate([ + { + $match: { + _id: { $in: tasks.map((task) => task._id) }, + }, + }, + { + $lookup: { + from: "lists", + localField: "listId", + foreignField: "_id", + as: "listDetails", + }, + }, + { $unwind: "$listDetails" }, + { + $lookup: { + from: "spaces", + localField: "listDetails.spaceId", + foreignField: "_id", + as: "spaceDetails", + }, + }, + { $unwind: "$spaceDetails" }, + { + $project: { + _id: 0, + task: { + id: "$_id", + name: "$title", + priority: "$priority", + dueDate: "$dueDate", + }, + list: { + id: "$listDetails._id", + name: "$listDetails.name", + }, + space: { + id: "$spaceDetails._id", + name: "$spaceDetails.name", + }, + }, + }, + ]); + + if (!tasksWithDetails || tasksWithDetails.length === 0) { + throw new CustomError("No tasks found with list and space details", 404); + } + + res.status(200).json(new StandardResponse("Tasks assigned to the user retrieved successfully", tasksWithDetails)); + } catch (error) { + console.error("Error retrieving tasks:", error); + throw new CustomError("Internal server error", 500); + } +}; - if (!users || users.length < 1) { - throw new CustomError("Users not found"); - } +export const getAllUsers = async (req: Request, res: Response) => { + const users = await User.find(); - res - .status(200) - .json(new StandardResponse("User Updated successfully", users)); -}; + if (!users || users.length < 1) { + throw new CustomError("Users not found"); + } -export const taskAssignedToUser = async (req: Request, res: Response) => { - const { userId } = req.params; - - if (!userId) { - throw new CustomError("User ID is required", 400); - } - - try { - const tasks = await Task.find({ assignedTo: userId }); - - if (!tasks || tasks.length === 0) { - throw new CustomError("No tasks found for the specified user", 404); - } - - const tasksWithDetails = await Task.aggregate([ - { - $match: { - _id: { $in: tasks.map((task) => task._id) }, - }, - }, - { - $lookup: { - from: "lists", - localField: "listId", - foreignField: "_id", - as: "listDetails", - }, - }, - { $unwind: "$listDetails" }, - { - $lookup: { - from: "spaces", - localField: "listDetails.spaceId", - foreignField: "_id", - as: "spaceDetails", - }, - }, - { $unwind: "$spaceDetails" }, - { - $project: { - _id: 0, - task: { - id: "$_id", - name: "$title", - priority: "$priority", - dueDate: "$dueDate", - }, - list: { - id: "$listDetails._id", - name: "$listDetails.name", - }, - space: { - id: "$spaceDetails._id", - name: "$spaceDetails.name", - }, - }, - }, - ]); - - if (!tasksWithDetails || tasksWithDetails.length === 0) { - throw new CustomError("No tasks found with list and space details", 404); - } - - res - .status(200) - .json( - new StandardResponse( - "Tasks assigned to the user retrieved successfully", - tasksWithDetails - ) - ); - } catch (error) { - console.error("Error retrieving tasks:", error); - throw new CustomError("Internal server error", 500); - } + res.status(200).json(new StandardResponse("User fetched successfully", users)); }; diff --git a/src/routes/userRoutes.ts b/src/routes/userRoutes.ts index f2babf8..83e1a0c 100644 --- a/src/routes/userRoutes.ts +++ b/src/routes/userRoutes.ts @@ -1,10 +1,5 @@ import express from "express"; -import { - getAllUsers, - getUserById, - taskAssignedToUser, - updatedUserProfile, -} from "../controllers/userController"; +import { getAllUsers, getUserById, taskAssignedToUser, updatedUserProfile } from "../controllers/userController"; import { verifyToken } from "../middlewares/verifyToken"; import { errorCatch } from "../utils/error/errorCatch"; import { editUserDetails } from "../utils/zodSchemas"; @@ -13,17 +8,8 @@ import { validateData } from "../middlewares/zodValidation"; const router = express.Router(); router.get("/:userId", verifyToken, errorCatch(getUserById)); -router.put( - "/:userId", - verifyToken, - validateData(editUserDetails), - errorCatch(updatedUserProfile) -); +router.put("/:userId", verifyToken, validateData(editUserDetails), errorCatch(updatedUserProfile)); router.get("/", verifyToken, errorCatch(getAllUsers)); -router.get( - "/:userId/assigned-tasks", - verifyToken, - errorCatch(taskAssignedToUser) -); +router.get("/:userId/assigned-tasks", verifyToken, errorCatch(taskAssignedToUser)); export default router; diff --git a/src/socket/socket.ts b/src/socket/socket.ts index f6b5113..3042b56 100644 --- a/src/socket/socket.ts +++ b/src/socket/socket.ts @@ -18,13 +18,9 @@ io.on("connection", async (socket) => { socket.on("sendMessage", async (message) => { try { - console.log("Message received:", message); - - // Check if a chat already exists for the given workspaceId const chat = await Chat.findOne({ workspaceId: message?.workSpaceId }); if (chat) { - // If chat exists, update the messages array chat.messages.push({ content: message?.content, timestamp: Date.now(), @@ -34,10 +30,8 @@ io.on("connection", async (socket) => { const updatedChat = await chat.save(); const populatedChat = await updatedChat.populate("messages.sender"); - console.log("Chat updated:", populatedChat); io.emit("receiveMessage", populatedChat); } else { - // If no chat exists, create a new chat const newChat = new Chat({ workspaceId: message?.workSpaceId, messages: [ @@ -50,7 +44,6 @@ io.on("connection", async (socket) => { }); const savedMessage = await (await newChat.save()).populate("messages.sender"); - console.log("Message saved:", savedMessage); io.emit("receiveMessage", savedMessage); } } catch (err) { diff --git a/src/utils/mailSender.ts b/src/utils/mailSender.ts index 30f2629..cc27832 100644 --- a/src/utils/mailSender.ts +++ b/src/utils/mailSender.ts @@ -24,7 +24,6 @@ const mailSender = async (email: string, title: string, body: string) => { }); return info; } catch (_error) { - console.log(_error); throw new CustomError("Error when sending Email", 500); } }; From ad6491ac504797cb1a71d4f205a1e876167a9c08 Mon Sep 17 00:00:00 2001 From: fa-salu Date: Sat, 23 Nov 2024 13:45:01 +0530 Subject: [PATCH 25/30] add newUser field to login resoonse --- src/controllers/authController.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/controllers/authController.ts b/src/controllers/authController.ts index 2ebeb64..14b8c0d 100644 --- a/src/controllers/authController.ts +++ b/src/controllers/authController.ts @@ -7,6 +7,7 @@ import jwt from "jsonwebtoken"; import mailSender from "../utils/mailSender"; import { Otp } from "../models/otpModel"; import otpGenerator from "otp-generator"; +import Workspace from "../models/workspaceModel"; //Register user const register = async (req: Request, res: Response) => { @@ -98,11 +99,14 @@ const login = async (req: Request, res: Response) => { }, ); + const workspaces = await Workspace.find({ createdBy: user.id }); + const response = { userId: user._id, firstName: user.firstName, lastName: user.lastName, email: user.email, + newUser: workspaces.length === 0, token, }; From f31d5057b791d163e3857cd79768feb408442ffe Mon Sep 17 00:00:00 2001 From: fa-salu Date: Tue, 26 Nov 2024 15:39:59 +0530 Subject: [PATCH 26/30] subscrtion statics add for admin dashboard --- src/controllers/admin/adminController.ts | 14 ++++++++++++++ src/models/userModel.ts | 2 +- src/routes/admin/adminRoutes.ts | 2 ++ 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/controllers/admin/adminController.ts b/src/controllers/admin/adminController.ts index e20032b..f5256bc 100644 --- a/src/controllers/admin/adminController.ts +++ b/src/controllers/admin/adminController.ts @@ -5,6 +5,7 @@ import { StandardResponse } from "../../utils/standardResponse"; import type { CustomRequest } from "../../types/interfaces"; import Workspace from "../../models/workspaceModel"; import mongoose from "mongoose"; +import { PaymentModel } from "../../models/paymentModel"; export const getAllUsers = async (req: CustomRequest, res: Response) => { if (!req.user) { @@ -149,3 +150,16 @@ export const getWorkspaceById = async (req: CustomRequest, res: Response) => { res.status(200).json(new StandardResponse("Workspace with spaces retrieved successfully", workspaceWithSpaces[0])); }; + +export const getAllSubscription = async (req: CustomRequest, res: Response) => { + if (!req.user) { + throw new CustomError("Unauthorized access", 401); + } + + const subscription = await PaymentModel.find(); + + if (!subscription.length) { + throw new CustomError("No subscription found", 404); + } + res.status(200).json(new StandardResponse("Subscription retrieved successfully", subscription)); +}; diff --git a/src/models/userModel.ts b/src/models/userModel.ts index c7c81b5..c28d695 100644 --- a/src/models/userModel.ts +++ b/src/models/userModel.ts @@ -44,7 +44,7 @@ const userSchema: Schema = new Schema( }, subscription: { type: String, - default: null, + default: "free", }, verified: { type: Boolean, diff --git a/src/routes/admin/adminRoutes.ts b/src/routes/admin/adminRoutes.ts index 0831b0a..9bb0ea2 100644 --- a/src/routes/admin/adminRoutes.ts +++ b/src/routes/admin/adminRoutes.ts @@ -5,6 +5,7 @@ import { adminLoginSchema } from "../../utils/zodSchemas"; import { adminLogin } from "../../controllers/admin/adminAuthController"; import { blockUser, + getAllSubscription, getAllUsers, getAllWorkspacesWithSpaces, getWorkspaceById, @@ -18,5 +19,6 @@ router.get("/users", adminAuthenticate, errorCatch(getAllUsers)); router.patch("/blockuser/:userId", adminAuthenticate, errorCatch(blockUser)); router.get("/workspaces", adminAuthenticate, errorCatch(getAllWorkspacesWithSpaces)); router.get("/workspace/:workspaceId", adminAuthenticate, errorCatch(getWorkspaceById)); +router.get("/subscription", adminAuthenticate, errorCatch(getAllSubscription)); export default router; From e5ba87727a0ff1c5bef9b98c2d792e4b22833ea9 Mon Sep 17 00:00:00 2001 From: ANAS Date: Wed, 27 Nov 2024 09:48:47 +0530 Subject: [PATCH 27/30] update scripts for deployment and add dist to .gitignore --- .gitignore | 3 +- package.json | 81 +++++++++++++++++----------------- tsconfig.json | 119 +++++--------------------------------------------- 3 files changed, 54 insertions(+), 149 deletions(-) diff --git a/.gitignore b/.gitignore index e14d388..4ab2dc8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ node_modules .env -logs \ No newline at end of file +logs +dist \ No newline at end of file diff --git a/package.json b/package.json index daf0449..712dff9 100644 --- a/package.json +++ b/package.json @@ -1,42 +1,43 @@ { - "name": "doque-server", - "version": "1.0.0", - "main": "src/index.ts", - "scripts": { - "start": "ts-node src/index.ts", - "dev": "nodemon src/index.ts", - "lint": "biome check src", - "format": "biome check --write src" - }, - "keywords": [], - "author": "", - "license": "ISC", - "dependencies": { - "@types/cors": "^2.8.17", - "@types/stripe": "^8.0.416", - "bcrypt": "^5.1.1", - "cors": "^2.8.5", - "dotenv": "^16.4.5", - "express": "^4.21.1", - "jsonwebtoken": "^9.0.2", - "mongoose": "^8.8.1", - "nodemailer": "^6.9.16", - "otp-generator": "^4.0.1", - "stripe": "^17.4.0", - "socket.io": "^4.8.1", - "zod": "^3.23.8" - }, - "devDependencies": { - "@biomejs/biome": "^1.9.4", - "@types/bcrypt": "^5.0.2", - "@types/express": "5.0.0", - "@types/jsonwebtoken": "^9.0.7", - "@types/node": "^22.9.0", - "@types/nodemailer": "^6.4.16", - "@types/otp-generator": "^4.0.2", - "@types/socket.io": "^3.0.2", - "nodemon": "^3.1.7", - "ts-node": "^10.9.2", - "typescript": "^5.6.3" - } + "name": "doque-server", + "version": "1.0.0", + "main": "src/index.ts", + "scripts": { + "build": "tsc", + "start": "node dist/index.js", + "dev": "nodemon src/index.ts", + "lint": "biome check src", + "format": "biome check --write src" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "@types/cors": "^2.8.17", + "@types/stripe": "^8.0.416", + "bcrypt": "^5.1.1", + "cors": "^2.8.5", + "dotenv": "^16.4.5", + "express": "^4.21.1", + "jsonwebtoken": "^9.0.2", + "mongoose": "^8.8.1", + "nodemailer": "^6.9.16", + "otp-generator": "^4.0.1", + "stripe": "^17.4.0", + "socket.io": "^4.8.1", + "zod": "^3.23.8" + }, + "devDependencies": { + "@biomejs/biome": "^1.9.4", + "@types/bcrypt": "^5.0.2", + "@types/express": "5.0.0", + "@types/jsonwebtoken": "^9.0.7", + "@types/node": "^22.9.0", + "@types/nodemailer": "^6.4.16", + "@types/otp-generator": "^4.0.2", + "@types/socket.io": "^3.0.2", + "nodemon": "^3.1.7", + "ts-node": "^10.9.2", + "typescript": "^5.6.3" + } } diff --git a/tsconfig.json b/tsconfig.json index 56a8ab8..ead38c1 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,110 +1,13 @@ { - "compilerOptions": { - /* Visit https://aka.ms/tsconfig to read more about this file */ - - /* Projects */ - // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ - // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ - // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ - // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ - // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ - // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ - - /* Language and Environment */ - "target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ - // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ - // "jsx": "preserve", /* Specify what JSX code is generated. */ - // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */ - // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ - // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ - // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ - // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ - // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ - // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ - // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ - // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ - - /* Modules */ - "module": "commonjs", /* Specify what module code is generated. */ - // "rootDir": "./", /* Specify the root folder within your source files. */ - // "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */ - // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ - // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ - // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ - // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ - // "types": [], /* Specify type package names to be included without being referenced in a source file. */ - // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ - // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ - // "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */ - // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */ - // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */ - // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */ - // "noUncheckedSideEffectImports": true, /* Check side effect imports. */ - // "resolveJsonModule": true, /* Enable importing .json files. */ - // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */ - // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ - - /* JavaScript Support */ - // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ - // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ - // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ - - /* Emit */ - // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ - // "declarationMap": true, /* Create sourcemaps for d.ts files. */ - // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ - // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ - // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ - // "noEmit": true, /* Disable emitting files from a compilation. */ - // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ - // "outDir": "./", /* Specify an output folder for all emitted files. */ - // "removeComments": true, /* Disable emitting comments. */ - // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ - // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ - // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ - // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ - // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ - // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ - // "newLine": "crlf", /* Set the newline character for emitting files. */ - // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ - // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ - // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ - // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ - // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ - - /* Interop Constraints */ - // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ - // "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */ - // "isolatedDeclarations": true, /* Require sufficient annotation on exports so other tools can trivially generate declaration files. */ - // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ - "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ - // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ - "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ - - /* Type Checking */ - "strict": true, /* Enable all strict type-checking options. */ - // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ - // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ - // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ - // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ - // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ - // "strictBuiltinIteratorReturn": true, /* Built-in iterators are instantiated with a 'TReturn' type of 'undefined' instead of 'any'. */ - // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ - // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ - // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ - // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ - // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ - // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ - // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ - // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ - // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ - // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ - // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ - // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ - // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ - - /* Completeness */ - // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ - "skipLibCheck": true /* Skip type checking all .d.ts files. */ - } + "compilerOptions": { + "target": "ES6", + "module": "commonjs", + "rootDir": "./src", + "outDir": "./dist", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true + }, + "include": ["src/**/*.ts"], + "exclude": ["node_modules"] } From 239467253f409abdae8bfe0eb174cbd332f39cc9 Mon Sep 17 00:00:00 2001 From: ANAS Date: Wed, 27 Nov 2024 10:13:28 +0530 Subject: [PATCH 28/30] refactor: conditionally log errors and messages in dev mode --- src/controllers/paymentControllers.ts | 3 --- src/controllers/userController.ts | 1 - src/middlewares/globalErrorHandler.ts | 2 +- src/socket/socket.ts | 5 ++--- 4 files changed, 3 insertions(+), 8 deletions(-) diff --git a/src/controllers/paymentControllers.ts b/src/controllers/paymentControllers.ts index 0314ad5..4e19503 100644 --- a/src/controllers/paymentControllers.ts +++ b/src/controllers/paymentControllers.ts @@ -47,7 +47,6 @@ export const createPaymentIntent = async (req: CustomRequest, res: Response, nex .status(200) .json(new StandardResponse("Payment intent created successfully", { clientSecret: session.client_secret })); } catch (error) { - console.error("Error creating payment intent:", error); next(new CustomError((error as Error).message || "Failed to create payment intent", 500)); } }; @@ -57,8 +56,6 @@ export const getPaymentDetails = async (req: CustomRequest, res: Response, next: try { const { sessionId } = req.params; - console.log(sessionId); - const payment = await PaymentModel.findOne({ sessionId }); if (!payment) { diff --git a/src/controllers/userController.ts b/src/controllers/userController.ts index afb2299..a324e93 100644 --- a/src/controllers/userController.ts +++ b/src/controllers/userController.ts @@ -100,7 +100,6 @@ export const taskAssignedToUser = async (req: Request, res: Response) => { res.status(200).json(new StandardResponse("Tasks assigned to the user retrieved successfully", tasksWithDetails)); } catch (error) { - console.error("Error retrieving tasks:", error); throw new CustomError("Internal server error", 500); } }; diff --git a/src/middlewares/globalErrorHandler.ts b/src/middlewares/globalErrorHandler.ts index 742638a..08de8f9 100644 --- a/src/middlewares/globalErrorHandler.ts +++ b/src/middlewares/globalErrorHandler.ts @@ -18,7 +18,7 @@ export const globalErrorHandler = ( res: Response, _next: NextFunction, ) => { - console.log(error); + if (process.env.NODE_ENV === "development") console.log(error); if (error instanceof CustomError) { errorResponse(error, res); diff --git a/src/socket/socket.ts b/src/socket/socket.ts index 3042b56..b656131 100644 --- a/src/socket/socket.ts +++ b/src/socket/socket.ts @@ -14,8 +14,7 @@ const io = new Server(server, { }); io.on("connection", async (socket) => { - console.log("A user connected"); - + if (process.env.NODE_ENV === "development") console.log("User connected"); socket.on("sendMessage", async (message) => { try { const chat = await Chat.findOne({ workspaceId: message?.workSpaceId }); @@ -52,6 +51,6 @@ io.on("connection", async (socket) => { }); socket.on("disconnect", () => { - console.log("User disconnected"); + if (process.env.NODE_ENV === "development") console.log("User disconnected"); }); }); From 88e7ea175f46178d7bb0b99f4931f39649c80fad Mon Sep 17 00:00:00 2001 From: sinan Date: Wed, 27 Nov 2024 10:39:15 +0530 Subject: [PATCH 29/30] updated search controller to only include user's worksapce --- src/controllers/searchController.ts | 9 +++++++-- src/socket/socket.ts | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/controllers/searchController.ts b/src/controllers/searchController.ts index 25ada4f..f09344d 100644 --- a/src/controllers/searchController.ts +++ b/src/controllers/searchController.ts @@ -1,18 +1,23 @@ -import type { Request, Response } from "express"; +import type { Response } from "express"; import { CustomError } from "../utils/error/customError"; import { StandardResponse } from "../utils/standardResponse"; import Workspace from "../models/workspaceModel"; +import type { CustomRequest } from "../types/interfaces"; +import mongoose from "mongoose"; -export const searchWorkspace = async (req: Request, res: Response) => { +export const searchWorkspace = async (req: CustomRequest, res: Response) => { + const userId = req.user?.id; const { query } = req.query; if (!query || typeof query !== "string") { throw new CustomError("Query parameter is required and should be a string", 400); } + const workspaces = await Workspace.aggregate([ { $match: { name: { $regex: query, $options: "i" }, + members: { $in: [new mongoose.Types.ObjectId(userId)] }, }, }, ]); diff --git a/src/socket/socket.ts b/src/socket/socket.ts index b656131..9662150 100644 --- a/src/socket/socket.ts +++ b/src/socket/socket.ts @@ -8,7 +8,7 @@ export const app = express(); export const server = createServer(app); const io = new Server(server, { cors: { - origin: "http://localhost:3000", + origin: process.env.CLIENT_URL, methods: ["GET", "POST"], }, }); From 7485f8810eaa279f0918d6a3e6db4b8efafb0db9 Mon Sep 17 00:00:00 2001 From: Sinan Date: Thu, 28 Nov 2024 10:14:05 +0530 Subject: [PATCH 30/30] fix socket bug (#31) --- package-lock.json | 5452 +++++++++++++++++++++--------------------- src/socket/socket.ts | 12 +- 2 files changed, 2736 insertions(+), 2728 deletions(-) diff --git a/package-lock.json b/package-lock.json index 338349b..5faf782 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,2728 +1,2728 @@ { - "name": "doque-server", - "version": "1.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "doque-server", - "version": "1.0.0", - "license": "ISC", - "dependencies": { - "@types/cors": "^2.8.17", - "@types/stripe": "^8.0.416", - "bcrypt": "^5.1.1", - "cors": "^2.8.5", - "dotenv": "^16.4.5", - "express": "^4.21.1", - "jsonwebtoken": "^9.0.2", - "mongoose": "^8.8.1", - "nodemailer": "^6.9.16", - "otp-generator": "^4.0.1", - "socket.io": "^4.8.1", - "stripe": "^17.4.0", - "zod": "^3.23.8" - }, - "devDependencies": { - "@biomejs/biome": "^1.9.4", - "@types/bcrypt": "^5.0.2", - "@types/express": "5.0.0", - "@types/jsonwebtoken": "^9.0.7", - "@types/node": "^22.9.0", - "@types/nodemailer": "^6.4.16", - "@types/otp-generator": "^4.0.2", - "@types/socket.io": "^3.0.2", - "nodemon": "^3.1.7", - "ts-node": "^10.9.2", - "typescript": "^5.6.3" - } - }, - "node_modules/@biomejs/biome": { - "version": "1.9.4", - "resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-1.9.4.tgz", - "integrity": "sha512-1rkd7G70+o9KkTn5KLmDYXihGoTaIGO9PIIN2ZB7UJxFrWw04CZHPYiMRjYsaDvVV7hP1dYNRLxSANLaBFGpog==", - "dev": true, - "hasInstallScript": true, - "bin": { - "biome": "bin/biome" - }, - "engines": { - "node": ">=14.21.3" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/biome" - }, - "optionalDependencies": { - "@biomejs/cli-darwin-arm64": "1.9.4", - "@biomejs/cli-darwin-x64": "1.9.4", - "@biomejs/cli-linux-arm64": "1.9.4", - "@biomejs/cli-linux-arm64-musl": "1.9.4", - "@biomejs/cli-linux-x64": "1.9.4", - "@biomejs/cli-linux-x64-musl": "1.9.4", - "@biomejs/cli-win32-arm64": "1.9.4", - "@biomejs/cli-win32-x64": "1.9.4" - } - }, - "node_modules/@biomejs/cli-darwin-arm64": { - "version": "1.9.4", - "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-1.9.4.tgz", - "integrity": "sha512-bFBsPWrNvkdKrNCYeAp+xo2HecOGPAy9WyNyB/jKnnedgzl4W4Hb9ZMzYNbf8dMCGmUdSavlYHiR01QaYR58cw==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=14.21.3" - } - }, - "node_modules/@biomejs/cli-darwin-x64": { - "version": "1.9.4", - "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-1.9.4.tgz", - "integrity": "sha512-ngYBh/+bEedqkSevPVhLP4QfVPCpb+4BBe2p7Xs32dBgs7rh9nY2AIYUL6BgLw1JVXV8GlpKmb/hNiuIxfPfZg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=14.21.3" - } - }, - "node_modules/@biomejs/cli-linux-arm64": { - "version": "1.9.4", - "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-1.9.4.tgz", - "integrity": "sha512-fJIW0+LYujdjUgJJuwesP4EjIBl/N/TcOX3IvIHJQNsAqvV2CHIogsmA94BPG6jZATS4Hi+xv4SkBBQSt1N4/g==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=14.21.3" - } - }, - "node_modules/@biomejs/cli-linux-arm64-musl": { - "version": "1.9.4", - "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-1.9.4.tgz", - "integrity": "sha512-v665Ct9WCRjGa8+kTr0CzApU0+XXtRgwmzIf1SeKSGAv+2scAlW6JR5PMFo6FzqqZ64Po79cKODKf3/AAmECqA==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=14.21.3" - } - }, - "node_modules/@biomejs/cli-linux-x64": { - "version": "1.9.4", - "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-1.9.4.tgz", - "integrity": "sha512-lRCJv/Vi3Vlwmbd6K+oQ0KhLHMAysN8lXoCI7XeHlxaajk06u7G+UsFSO01NAs5iYuWKmVZjmiOzJ0OJmGsMwg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=14.21.3" - } - }, - "node_modules/@biomejs/cli-linux-x64-musl": { - "version": "1.9.4", - "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-1.9.4.tgz", - "integrity": "sha512-gEhi/jSBhZ2m6wjV530Yy8+fNqG8PAinM3oV7CyO+6c3CEh16Eizm21uHVsyVBEB6RIM8JHIl6AGYCv6Q6Q9Tg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=14.21.3" - } - }, - "node_modules/@biomejs/cli-win32-arm64": { - "version": "1.9.4", - "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-1.9.4.tgz", - "integrity": "sha512-tlbhLk+WXZmgwoIKwHIHEBZUwxml7bRJgk0X2sPyNR3S93cdRq6XulAZRQJ17FYGGzWne0fgrXBKpl7l4M87Hg==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=14.21.3" - } - }, - "node_modules/@biomejs/cli-win32-x64": { - "version": "1.9.4", - "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-1.9.4.tgz", - "integrity": "sha512-8Y5wMhVIPaWe6jw2H+KlEm4wP/f7EW3810ZLmDlrEEy5KvBsb9ECEfu/kMWD484ijfQ8+nIi0giMgu9g1UAuuA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=14.21.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, - "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, - "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 - }, - "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, - "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - }, - "node_modules/@mapbox/node-pre-gyp": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz", - "integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==", - "license": "BSD-3-Clause", - "dependencies": { - "detect-libc": "^2.0.0", - "https-proxy-agent": "^5.0.0", - "make-dir": "^3.1.0", - "node-fetch": "^2.6.7", - "nopt": "^5.0.0", - "npmlog": "^5.0.1", - "rimraf": "^3.0.2", - "semver": "^7.3.5", - "tar": "^6.1.11" - }, - "bin": { - "node-pre-gyp": "bin/node-pre-gyp" - } - }, - "node_modules/@mongodb-js/saslprep": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.1.9.tgz", - "integrity": "sha512-tVkljjeEaAhCqTzajSdgbQ6gE6f3oneVwa3iXR6csiEwXXOFsiC6Uh9iAjAhXPtqa/XMDHWjjeNH/77m/Yq2dw==", - "dependencies": { - "sparse-bitfield": "^3.0.3" - } - }, - "node_modules/@socket.io/component-emitter": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", - "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==" - }, - "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 - }, - "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 - }, - "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 - }, - "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 - }, - "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, - "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, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/cookie": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", - "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==" - }, - "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==", - "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, - "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, - "dependencies": { - "@types/node": "*", - "@types/qs": "*", - "@types/range-parser": "*", - "@types/send": "*" - } - }, - "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 - }, - "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/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 - }, - "node_modules/@types/node": { - "version": "22.9.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.9.0.tgz", - "integrity": "sha512-vuyHg81vvWA1Z1ELfvLko2c8f34gyA0zaic0+Rllc5lbCnbSyuvb2Oxpm6TAUAC/2xZN3QGqxBNggD1nNR2AfQ==", - "dependencies": { - "undici-types": "~6.19.8" - } - }, - "node_modules/@types/nodemailer": { - "version": "6.4.16", - "resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-6.4.16.tgz", - "integrity": "sha512-uz6hN6Pp0upXMcilM61CoKyjT7sskBoOWpptkjjJp8jIMlTdc3xG01U7proKkXzruMS4hS0zqtHNkNPFB20rKQ==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/otp-generator": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@types/otp-generator/-/otp-generator-4.0.2.tgz", - "integrity": "sha512-9+qqWzuFb332hXPbLgjUyOXlbcaTQkmkmqQjTduvNuOmPV5fW+iLv70JsVEhdUy0DWi4kY34++HDCaWl6N0AYg==", - "dev": true - }, - "node_modules/@types/qs": { - "version": "6.9.17", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.17.tgz", - "integrity": "sha512-rX4/bPcfmvxHDv0XjfJELTTr+iB+tn032nPILqHm5wbthUUUuVtNGGqzhya9XUxjTP8Fpr0qYgSZZKxGY++svQ==", - "dev": true - }, - "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 - }, - "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, - "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, - "dependencies": { - "@types/http-errors": "*", - "@types/node": "*", - "@types/send": "*" - } - }, - "node_modules/@types/socket.io": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@types/socket.io/-/socket.io-3.0.2.tgz", - "integrity": "sha512-pu0sN9m5VjCxBZVK8hW37ZcMe8rjn4HHggBN5CbaRTvFwv5jOmuIRZEuddsBPa9Th0ts0SIo3Niukq+95cMBbQ==", - "deprecated": "This is a stub types definition. socket.io provides its own type definitions, so you do not need this installed.", - "dev": true, - "dependencies": { - "socket.io": "*" - } - }, - "node_modules/@types/stripe": { - "version": "8.0.416", - "resolved": "https://registry.npmjs.org/@types/stripe/-/stripe-8.0.416.tgz", - "integrity": "sha512-LDA574j7g30dg4R+SI1JIpkS+rkIuXgbe6+/qlf62avd7ZNntbbl2DYZwAIj9CfJYVh7FG/PLeoNB5OXTsEehg==", - "license": "MIT", - "dependencies": { - "stripe": "*" - } - }, - "node_modules/@types/webidl-conversions": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz", - "integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==" - }, - "node_modules/@types/whatwg-url": { - "version": "11.0.5", - "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-11.0.5.tgz", - "integrity": "sha512-coYR071JRaHa+xoEvvYqvnIHaVqaYrLPbsufM9BF63HkwI5Lgmy2QR8Q5K/lYDYo5AK82wOvSOS0UsLTpTG7uQ==", - "dependencies": { - "@types/webidl-conversions": "*" - } - }, - "node_modules/abbrev": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", - "license": "ISC" - }, - "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==", - "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/acorn": { - "version": "8.14.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", - "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", - "dev": true, - "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, - "dependencies": { - "acorn": "^8.11.0" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "license": "MIT", - "dependencies": { - "debug": "4" - }, - "engines": { - "node": ">= 6.0.0" - } - }, - "node_modules/agent-base/node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/agent-base/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/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/aproba": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", - "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==", - "license": "ISC" - }, - "node_modules/are-we-there-yet": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", - "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", - "deprecated": "This package is no longer supported.", - "license": "ISC", - "dependencies": { - "delegates": "^1.0.0", - "readable-stream": "^3.6.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/arg": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true - }, - "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==" - }, - "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==" - }, - "node_modules/base64id": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", - "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", - "engines": { - "node": "^4.5.0 || >= 5.9" - } - }, - "node_modules/bcrypt": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.1.1.tgz", - "integrity": "sha512-AGBHOG5hPYZ5Xl9KXzU5iKq9516yEmvCKDg3ecP5kX2aB6UqTeXZxk2ELnDgDm6BQSMlLt9rDB4LoSMx0rYwww==", - "hasInstallScript": true, - "license": "MIT", - "dependencies": { - "@mapbox/node-pre-gyp": "^1.0.11", - "node-addon-api": "^5.0.0" - }, - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/binary-extensions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", - "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "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==", - "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": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/bson": { - "version": "6.9.0", - "resolved": "https://registry.npmjs.org/bson/-/bson-6.9.0.tgz", - "integrity": "sha512-X9hJeyeM0//Fus+0pc5dSUMhhrrmWwQUtdavaQeF3Ta6m69matZkGWV/MrBcnwUeLC8W9kwwc2hfkZgUuCX3Ig==", - "engines": { - "node": ">=16.20.1" - } - }, - "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==", - "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==", - "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/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "dev": true, - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/chownr": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", - "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "node_modules/color-support": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", - "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", - "license": "ISC", - "bin": { - "color-support": "bin.js" - } - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" - }, - "node_modules/console-control-strings": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", - "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", - "license": "ISC" - }, - "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==", - "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==", - "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==", - "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==" - }, - "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==", - "license": "MIT", - "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", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "dev": true - }, - "node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "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==", - "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/delegates": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", - "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", - "license": "MIT" - }, - "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==", - "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==", - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/detect-libc": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", - "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", - "license": "Apache-2.0", - "engines": { - "node": ">=8" - } - }, - "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, - "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/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==" - }, - "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==", - "license": "MIT" - }, - "node_modules/encodeurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/engine.io": { - "version": "6.6.2", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.2.tgz", - "integrity": "sha512-gmNvsYi9C8iErnZdVcJnvCpSKbWTt1E8+JZo8b+daLninywUWi5NQ5STSHZ9rFjFO7imNcvb8Pc5pe/wMR5xEw==", - "dependencies": { - "@types/cookie": "^0.4.1", - "@types/cors": "^2.8.12", - "@types/node": ">=10.0.0", - "accepts": "~1.3.4", - "base64id": "2.0.0", - "cookie": "~0.7.2", - "cors": "~2.8.5", - "debug": "~4.3.1", - "engine.io-parser": "~5.2.1", - "ws": "~8.17.1" - }, - "engines": { - "node": ">=10.2.0" - } - }, - "node_modules/engine.io-parser": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz", - "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==", - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/engine.io/node_modules/cookie": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", - "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/engine.io/node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/engine.io/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==" - }, - "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==", - "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==", - "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==" - }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", - "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==", - "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/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/finalhandler": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", - "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", - "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==", - "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==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fs-minipass": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", - "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", - "license": "ISC", - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/fs-minipass/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "license": "ISC" - }, - "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, - "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==", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/gauge": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", - "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", - "deprecated": "This package is no longer supported.", - "license": "ISC", - "dependencies": { - "aproba": "^1.0.3 || ^2.0.0", - "color-support": "^1.1.2", - "console-control-strings": "^1.0.0", - "has-unicode": "^2.0.1", - "object-assign": "^4.1.1", - "signal-exit": "^3.0.0", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1", - "wide-align": "^1.1.2" - }, - "engines": { - "node": ">=10" - } - }, - "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==", - "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": "7.2.3", - "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", - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/gopd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", - "dependencies": { - "get-intrinsic": "^1.1.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "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==", - "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==", - "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==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-unicode": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", - "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", - "license": "ISC" - }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "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==", - "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/https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", - "license": "MIT", - "dependencies": { - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/https-proxy-agent/node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/https-proxy-agent/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/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ignore-by-default": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", - "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", - "dev": true - }, - "node_modules/inflight": { - "version": "1.0.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.", - "license": "ISC", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "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==", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "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==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "engines": { - "node": ">=0.12.0" - } - }, - "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/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/kareem": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.6.3.tgz", - "integrity": "sha512-C3iHfuGUXK2u8/ipq9LfjFfXFxAZMQJJq7vLS45r3D9Y2xQ/m4S8zaR4zMLFWh9AsNPXmcFfUDhTEO8UIC/V6Q==", - "engines": { - "node": ">=12.0.0" - } - }, - "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/make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "license": "MIT", - "dependencies": { - "semver": "^6.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/make-dir/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "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 - }, - "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==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/memory-pager": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", - "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==" - }, - "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==", - "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==", - "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==", - "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==", - "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==", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/minipass": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", - "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", - "license": "ISC", - "engines": { - "node": ">=8" - } - }, - "node_modules/minizlib": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", - "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", - "license": "MIT", - "dependencies": { - "minipass": "^3.0.0", - "yallist": "^4.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/minizlib/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "license": "MIT", - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/mongodb": { - "version": "6.10.0", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.10.0.tgz", - "integrity": "sha512-gP9vduuYWb9ZkDM546M+MP2qKVk5ZG2wPF63OvSRuUbqCR+11ZCAE1mOfllhlAG0wcoJY5yDL/rV3OmYEwXIzg==", - "dependencies": { - "@mongodb-js/saslprep": "^1.1.5", - "bson": "^6.7.0", - "mongodb-connection-string-url": "^3.0.0" - }, - "engines": { - "node": ">=16.20.1" - }, - "peerDependencies": { - "@aws-sdk/credential-providers": "^3.188.0", - "@mongodb-js/zstd": "^1.1.0", - "gcp-metadata": "^5.2.0", - "kerberos": "^2.0.1", - "mongodb-client-encryption": ">=6.0.0 <7", - "snappy": "^7.2.2", - "socks": "^2.7.1" - }, - "peerDependenciesMeta": { - "@aws-sdk/credential-providers": { - "optional": true - }, - "@mongodb-js/zstd": { - "optional": true - }, - "gcp-metadata": { - "optional": true - }, - "kerberos": { - "optional": true - }, - "mongodb-client-encryption": { - "optional": true - }, - "snappy": { - "optional": true - }, - "socks": { - "optional": true - } - } - }, - "node_modules/mongodb-connection-string-url": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.1.tgz", - "integrity": "sha512-XqMGwRX0Lgn05TDB4PyG2h2kKO/FfWJyCzYQbIhXUxz7ETt0I/FqHjUeqj37irJ+Dl1ZtU82uYyj14u2XsZKfg==", - "dependencies": { - "@types/whatwg-url": "^11.0.2", - "whatwg-url": "^13.0.0" - } - }, - "node_modules/mongoose": { - "version": "8.8.1", - "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.8.1.tgz", - "integrity": "sha512-l7DgeY1szT98+EKU8GYnga5WnyatAu+kOQ2VlVX1Mxif6A0Umt0YkSiksCiyGxzx8SPhGe9a53ND1GD4yVDrPA==", - "dependencies": { - "bson": "^6.7.0", - "kareem": "2.6.3", - "mongodb": "~6.10.0", - "mpath": "0.9.0", - "mquery": "5.0.0", - "ms": "2.1.3", - "sift": "17.1.3" - }, - "engines": { - "node": ">=16.20.1" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mongoose" - } - }, - "node_modules/mongoose/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==" - }, - "node_modules/mpath": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.9.0.tgz", - "integrity": "sha512-ikJRQTk8hw5DEoFVxHG1Gn9T/xcjtdnOKIU1JTmGjZZlg9LST2mBLmcX3/ICIbgJydT2GOc15RnNy5mHmzfSew==", - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/mquery": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/mquery/-/mquery-5.0.0.tgz", - "integrity": "sha512-iQMncpmEK8R8ncT8HJGsGc9Dsp8xcgYMVSbs5jgnm1lFHTZqMJTUWTDx1LBO8+mK3tPNZWFLBghQEIOULSTHZg==", - "dependencies": { - "debug": "4.x" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/mquery/node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/mquery/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==" - }, - "node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, - "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==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/node-addon-api": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz", - "integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==", - "license": "MIT" - }, - "node_modules/node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", - "license": "MIT", - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, - "node_modules/node-fetch/node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", - "license": "MIT" - }, - "node_modules/node-fetch/node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", - "license": "BSD-2-Clause" - }, - "node_modules/node-fetch/node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "license": "MIT", - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, - "node_modules/nodemailer": { - "version": "6.9.16", - "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.16.tgz", - "integrity": "sha512-psAuZdTIRN08HKVd/E8ObdV6NO7NTBY3KsC30F7M4H1OnmLCUNaS56FpYxyb26zWLSyYF9Ozch9KYHhHegsiOQ==", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/nodemon": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.7.tgz", - "integrity": "sha512-hLj7fuMow6f0lbB0cD14Lz2xNjwsyruH251Pk4t/yIitCFJbmY1myuLlHm/q06aST4jg6EgAh74PIBBrRqpVAQ==", - "dev": true, - "dependencies": { - "chokidar": "^3.5.2", - "debug": "^4", - "ignore-by-default": "^1.0.1", - "minimatch": "^3.1.2", - "pstree.remy": "^1.1.8", - "semver": "^7.5.3", - "simple-update-notifier": "^2.0.0", - "supports-color": "^5.5.0", - "touch": "^3.1.0", - "undefsafe": "^2.0.5" - }, - "bin": { - "nodemon": "bin/nodemon.js" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/nodemon" - } - }, - "node_modules/nodemon/node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", - "dev": true, - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/nodemon/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 - }, - "node_modules/nopt": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", - "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", - "license": "ISC", - "dependencies": { - "abbrev": "1" - }, - "bin": { - "nopt": "bin/nopt.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npmlog": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", - "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", - "deprecated": "This package is no longer supported.", - "license": "ISC", - "dependencies": { - "are-we-there-yet": "^2.0.0", - "console-control-strings": "^1.1.0", - "gauge": "^3.0.0", - "set-blocking": "^2.0.0" - } - }, - "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==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-inspect": { - "version": "1.13.3", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz", - "integrity": "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==", - "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==", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "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==", - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/otp-generator": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/otp-generator/-/otp-generator-4.0.1.tgz", - "integrity": "sha512-2TJ52vUftA0+J3eque4wwVtpaL4/NdIXDL0gFWFJFVUAZwAN7+9tltMhL7GCNYaHJtuONoier8Hayyj4HLbSag==", - "engines": { - "node": ">=14.10.0" - } - }, - "node_modules/parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "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==" - }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "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==", - "dependencies": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/pstree.remy": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", - "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", - "dev": true - }, - "node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "engines": { - "node": ">=6" - } - }, - "node_modules/qs": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", - "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", - "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==", - "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==", - "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/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "license": "MIT", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/rimraf": { - "version": "3.0.2", - "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", - "license": "ISC", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "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" - } - ] - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" - }, - "node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", - "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==", - "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==", - "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==" - }, - "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==", - "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-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", - "license": "ISC" - }, - "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==", - "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==" - }, - "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==", - "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/sift": { - "version": "17.1.3", - "resolved": "https://registry.npmjs.org/sift/-/sift-17.1.3.tgz", - "integrity": "sha512-Rtlj66/b0ICeFzYTuNvX/EF1igRbbnGSvEyT79McoZa/DeGhMyC5pWKOEsZKnpkqtSeovd5FL/bjHWC3CIIvCQ==" - }, - "node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "license": "ISC" - }, - "node_modules/simple-update-notifier": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", - "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", - "dev": true, - "dependencies": { - "semver": "^7.5.3" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/socket.io": { - "version": "4.8.1", - "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.1.tgz", - "integrity": "sha512-oZ7iUCxph8WYRHHcjBEc9unw3adt5CmSNlppj/5Q4k2RIrhl8Z5yY2Xr4j9zj0+wzVZ0bxmYoGSzKJnRl6A4yg==", - "dependencies": { - "accepts": "~1.3.4", - "base64id": "~2.0.0", - "cors": "~2.8.5", - "debug": "~4.3.2", - "engine.io": "~6.6.0", - "socket.io-adapter": "~2.5.2", - "socket.io-parser": "~4.2.4" - }, - "engines": { - "node": ">=10.2.0" - } - }, - "node_modules/socket.io-adapter": { - "version": "2.5.5", - "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.5.tgz", - "integrity": "sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==", - "dependencies": { - "debug": "~4.3.4", - "ws": "~8.17.1" - } - }, - "node_modules/socket.io-adapter/node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/socket.io-adapter/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==" - }, - "node_modules/socket.io-parser": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", - "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", - "dependencies": { - "@socket.io/component-emitter": "~3.1.0", - "debug": "~4.3.1" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/socket.io-parser/node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/socket.io-parser/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==" - }, - "node_modules/socket.io/node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/socket.io/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==" - }, - "node_modules/sparse-bitfield": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", - "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==", - "dependencies": { - "memory-pager": "^1.0.2" - } - }, - "node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, - "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==", - "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/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/stripe": { - "version": "17.4.0", - "resolved": "https://registry.npmjs.org/stripe/-/stripe-17.4.0.tgz", - "integrity": "sha512-sQQGZguPxe7/QYXJKtDpfzT2OAH9F8nyE2SOsVdTU793iiU33/dpaKgWaJEGJm8396Yy/6NvTLblgdHlueGLhA==", - "license": "MIT", - "dependencies": { - "@types/node": ">=8.1.0", - "qs": "^6.11.0" - }, - "engines": { - "node": ">=12.*" - } - }, - "node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/tar": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", - "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", - "license": "ISC", - "dependencies": { - "chownr": "^2.0.0", - "fs-minipass": "^2.0.0", - "minipass": "^5.0.0", - "minizlib": "^2.1.1", - "mkdirp": "^1.0.3", - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "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==", - "engines": { - "node": ">=0.6" - } - }, - "node_modules/touch": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", - "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", - "dev": true, - "bin": { - "nodetouch": "bin/nodetouch.js" - } - }, - "node_modules/tr46": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-4.1.1.tgz", - "integrity": "sha512-2lv/66T7e5yNyhAAC4NaKe5nVavzuGJQVVtRYLyQ2OI8tsJ61PMLlelehb0wi2Hx6+hT/OJUWZcw8MjlSRnxvw==", - "dependencies": { - "punycode": "^2.3.0" - }, - "engines": { - "node": ">=14" - } - }, - "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, - "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==", - "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, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/undefsafe": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", - "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", - "dev": true - }, - "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==" - }, - "node_modules/unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "license": "MIT" - }, - "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==", - "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 - }, - "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==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/webidl-conversions": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", - "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", - "engines": { - "node": ">=12" - } - }, - "node_modules/whatwg-url": { - "version": "13.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-13.0.0.tgz", - "integrity": "sha512-9WWbymnqj57+XEuqADHrCJ2eSXzn8WXIW/YSGaZtb2WKAInQ6CHfaUUcTyyver0p8BDg5StLQq8h1vtZuwmOig==", - "dependencies": { - "tr46": "^4.1.1", - "webidl-conversions": "^7.0.0" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/wide-align": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", - "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", - "license": "ISC", - "dependencies": { - "string-width": "^1.0.2 || 2 || 3 || 4" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "license": "ISC" - }, - "node_modules/ws": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", - "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "license": "ISC" - }, - "node_modules/yn": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/zod": { - "version": "3.23.8", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz", - "integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==", - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } - } - } + "name": "doque-server", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "doque-server", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "@types/cors": "^2.8.17", + "@types/stripe": "^8.0.416", + "bcrypt": "^5.1.1", + "cors": "^2.8.5", + "dotenv": "^16.4.5", + "express": "^4.21.1", + "jsonwebtoken": "^9.0.2", + "mongoose": "^8.8.1", + "nodemailer": "^6.9.16", + "otp-generator": "^4.0.1", + "socket.io": "^4.8.1", + "stripe": "^17.4.0", + "zod": "^3.23.8" + }, + "devDependencies": { + "@biomejs/biome": "^1.9.4", + "@types/bcrypt": "^5.0.2", + "@types/express": "5.0.0", + "@types/jsonwebtoken": "^9.0.7", + "@types/node": "^22.9.0", + "@types/nodemailer": "^6.4.16", + "@types/otp-generator": "^4.0.2", + "@types/socket.io": "^3.0.2", + "nodemon": "^3.1.7", + "ts-node": "^10.9.2", + "typescript": "^5.6.3" + } + }, + "node_modules/@biomejs/biome": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-1.9.4.tgz", + "integrity": "sha512-1rkd7G70+o9KkTn5KLmDYXihGoTaIGO9PIIN2ZB7UJxFrWw04CZHPYiMRjYsaDvVV7hP1dYNRLxSANLaBFGpog==", + "dev": true, + "hasInstallScript": true, + "bin": { + "biome": "bin/biome" + }, + "engines": { + "node": ">=14.21.3" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/biome" + }, + "optionalDependencies": { + "@biomejs/cli-darwin-arm64": "1.9.4", + "@biomejs/cli-darwin-x64": "1.9.4", + "@biomejs/cli-linux-arm64": "1.9.4", + "@biomejs/cli-linux-arm64-musl": "1.9.4", + "@biomejs/cli-linux-x64": "1.9.4", + "@biomejs/cli-linux-x64-musl": "1.9.4", + "@biomejs/cli-win32-arm64": "1.9.4", + "@biomejs/cli-win32-x64": "1.9.4" + } + }, + "node_modules/@biomejs/cli-darwin-arm64": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-1.9.4.tgz", + "integrity": "sha512-bFBsPWrNvkdKrNCYeAp+xo2HecOGPAy9WyNyB/jKnnedgzl4W4Hb9ZMzYNbf8dMCGmUdSavlYHiR01QaYR58cw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-darwin-x64": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-1.9.4.tgz", + "integrity": "sha512-ngYBh/+bEedqkSevPVhLP4QfVPCpb+4BBe2p7Xs32dBgs7rh9nY2AIYUL6BgLw1JVXV8GlpKmb/hNiuIxfPfZg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-arm64": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-1.9.4.tgz", + "integrity": "sha512-fJIW0+LYujdjUgJJuwesP4EjIBl/N/TcOX3IvIHJQNsAqvV2CHIogsmA94BPG6jZATS4Hi+xv4SkBBQSt1N4/g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-arm64-musl": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-1.9.4.tgz", + "integrity": "sha512-v665Ct9WCRjGa8+kTr0CzApU0+XXtRgwmzIf1SeKSGAv+2scAlW6JR5PMFo6FzqqZ64Po79cKODKf3/AAmECqA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-x64": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-1.9.4.tgz", + "integrity": "sha512-lRCJv/Vi3Vlwmbd6K+oQ0KhLHMAysN8lXoCI7XeHlxaajk06u7G+UsFSO01NAs5iYuWKmVZjmiOzJ0OJmGsMwg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-x64-musl": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-1.9.4.tgz", + "integrity": "sha512-gEhi/jSBhZ2m6wjV530Yy8+fNqG8PAinM3oV7CyO+6c3CEh16Eizm21uHVsyVBEB6RIM8JHIl6AGYCv6Q6Q9Tg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-win32-arm64": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-1.9.4.tgz", + "integrity": "sha512-tlbhLk+WXZmgwoIKwHIHEBZUwxml7bRJgk0X2sPyNR3S93cdRq6XulAZRQJ17FYGGzWne0fgrXBKpl7l4M87Hg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-win32-x64": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-1.9.4.tgz", + "integrity": "sha512-8Y5wMhVIPaWe6jw2H+KlEm4wP/f7EW3810ZLmDlrEEy5KvBsb9ECEfu/kMWD484ijfQ8+nIi0giMgu9g1UAuuA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=14.21.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, + "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, + "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 + }, + "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, + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@mapbox/node-pre-gyp": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz", + "integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==", + "license": "BSD-3-Clause", + "dependencies": { + "detect-libc": "^2.0.0", + "https-proxy-agent": "^5.0.0", + "make-dir": "^3.1.0", + "node-fetch": "^2.6.7", + "nopt": "^5.0.0", + "npmlog": "^5.0.1", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.11" + }, + "bin": { + "node-pre-gyp": "bin/node-pre-gyp" + } + }, + "node_modules/@mongodb-js/saslprep": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.1.9.tgz", + "integrity": "sha512-tVkljjeEaAhCqTzajSdgbQ6gE6f3oneVwa3iXR6csiEwXXOFsiC6Uh9iAjAhXPtqa/XMDHWjjeNH/77m/Yq2dw==", + "dependencies": { + "sparse-bitfield": "^3.0.3" + } + }, + "node_modules/@socket.io/component-emitter": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", + "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==" + }, + "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 + }, + "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 + }, + "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 + }, + "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 + }, + "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, + "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, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==" + }, + "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==", + "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, + "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, + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "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 + }, + "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/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 + }, + "node_modules/@types/node": { + "version": "22.9.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.9.0.tgz", + "integrity": "sha512-vuyHg81vvWA1Z1ELfvLko2c8f34gyA0zaic0+Rllc5lbCnbSyuvb2Oxpm6TAUAC/2xZN3QGqxBNggD1nNR2AfQ==", + "dependencies": { + "undici-types": "~6.19.8" + } + }, + "node_modules/@types/nodemailer": { + "version": "6.4.16", + "resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-6.4.16.tgz", + "integrity": "sha512-uz6hN6Pp0upXMcilM61CoKyjT7sskBoOWpptkjjJp8jIMlTdc3xG01U7proKkXzruMS4hS0zqtHNkNPFB20rKQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/otp-generator": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/otp-generator/-/otp-generator-4.0.2.tgz", + "integrity": "sha512-9+qqWzuFb332hXPbLgjUyOXlbcaTQkmkmqQjTduvNuOmPV5fW+iLv70JsVEhdUy0DWi4kY34++HDCaWl6N0AYg==", + "dev": true + }, + "node_modules/@types/qs": { + "version": "6.9.17", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.17.tgz", + "integrity": "sha512-rX4/bPcfmvxHDv0XjfJELTTr+iB+tn032nPILqHm5wbthUUUuVtNGGqzhya9XUxjTP8Fpr0qYgSZZKxGY++svQ==", + "dev": true + }, + "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 + }, + "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, + "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, + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "*" + } + }, + "node_modules/@types/socket.io": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/socket.io/-/socket.io-3.0.2.tgz", + "integrity": "sha512-pu0sN9m5VjCxBZVK8hW37ZcMe8rjn4HHggBN5CbaRTvFwv5jOmuIRZEuddsBPa9Th0ts0SIo3Niukq+95cMBbQ==", + "deprecated": "This is a stub types definition. socket.io provides its own type definitions, so you do not need this installed.", + "dev": true, + "dependencies": { + "socket.io": "*" + } + }, + "node_modules/@types/stripe": { + "version": "8.0.416", + "resolved": "https://registry.npmjs.org/@types/stripe/-/stripe-8.0.416.tgz", + "integrity": "sha512-LDA574j7g30dg4R+SI1JIpkS+rkIuXgbe6+/qlf62avd7ZNntbbl2DYZwAIj9CfJYVh7FG/PLeoNB5OXTsEehg==", + "license": "MIT", + "dependencies": { + "stripe": "*" + } + }, + "node_modules/@types/webidl-conversions": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz", + "integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==" + }, + "node_modules/@types/whatwg-url": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-11.0.5.tgz", + "integrity": "sha512-coYR071JRaHa+xoEvvYqvnIHaVqaYrLPbsufM9BF63HkwI5Lgmy2QR8Q5K/lYDYo5AK82wOvSOS0UsLTpTG7uQ==", + "dependencies": { + "@types/webidl-conversions": "*" + } + }, + "node_modules/abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "license": "ISC" + }, + "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==", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.14.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", + "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", + "dev": true, + "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, + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/agent-base/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/agent-base/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/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/aproba": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", + "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==", + "license": "ISC" + }, + "node_modules/are-we-there-yet": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", + "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", + "deprecated": "This package is no longer supported.", + "license": "ISC", + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, + "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==" + }, + "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==" + }, + "node_modules/base64id": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", + "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", + "engines": { + "node": "^4.5.0 || >= 5.9" + } + }, + "node_modules/bcrypt": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.1.1.tgz", + "integrity": "sha512-AGBHOG5hPYZ5Xl9KXzU5iKq9516yEmvCKDg3ecP5kX2aB6UqTeXZxk2ELnDgDm6BQSMlLt9rDB4LoSMx0rYwww==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "@mapbox/node-pre-gyp": "^1.0.11", + "node-addon-api": "^5.0.0" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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==", + "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": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/bson": { + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/bson/-/bson-6.9.0.tgz", + "integrity": "sha512-X9hJeyeM0//Fus+0pc5dSUMhhrrmWwQUtdavaQeF3Ta6m69matZkGWV/MrBcnwUeLC8W9kwwc2hfkZgUuCX3Ig==", + "engines": { + "node": ">=16.20.1" + } + }, + "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==", + "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==", + "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/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "license": "ISC", + "bin": { + "color-support": "bin.js" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + }, + "node_modules/console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", + "license": "ISC" + }, + "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==", + "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==", + "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==", + "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==" + }, + "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==", + "license": "MIT", + "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", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "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==", + "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/delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", + "license": "MIT" + }, + "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==", + "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==", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/detect-libc": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", + "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "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, + "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/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==" + }, + "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==", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/engine.io": { + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.2.tgz", + "integrity": "sha512-gmNvsYi9C8iErnZdVcJnvCpSKbWTt1E8+JZo8b+daLninywUWi5NQ5STSHZ9rFjFO7imNcvb8Pc5pe/wMR5xEw==", + "dependencies": { + "@types/cookie": "^0.4.1", + "@types/cors": "^2.8.12", + "@types/node": ">=10.0.0", + "accepts": "~1.3.4", + "base64id": "2.0.0", + "cookie": "~0.7.2", + "cors": "~2.8.5", + "debug": "~4.3.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.17.1" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/engine.io-parser": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz", + "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/engine.io/node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/engine.io/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/engine.io/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==" + }, + "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==", + "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==", + "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==" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "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==", + "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/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "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==", + "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==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "license": "ISC" + }, + "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, + "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==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gauge": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", + "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", + "deprecated": "This package is no longer supported.", + "license": "ISC", + "dependencies": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.2", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.1", + "object-assign": "^4.1.1", + "signal-exit": "^3.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.2" + }, + "engines": { + "node": ">=10" + } + }, + "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==", + "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": "7.2.3", + "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", + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "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==", + "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==", + "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==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", + "license": "ISC" + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "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==", + "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/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/https-proxy-agent/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/https-proxy-agent/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/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", + "dev": true + }, + "node_modules/inflight": { + "version": "1.0.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.", + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "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==", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "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==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "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/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/kareem": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.6.3.tgz", + "integrity": "sha512-C3iHfuGUXK2u8/ipq9LfjFfXFxAZMQJJq7vLS45r3D9Y2xQ/m4S8zaR4zMLFWh9AsNPXmcFfUDhTEO8UIC/V6Q==", + "engines": { + "node": ">=12.0.0" + } + }, + "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/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "license": "MIT", + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "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 + }, + "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==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/memory-pager": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", + "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==" + }, + "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==", + "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==", + "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==", + "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==", + "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==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "license": "ISC", + "engines": { + "node": ">=8" + } + }, + "node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "license": "MIT", + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "license": "MIT", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mongodb": { + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.10.0.tgz", + "integrity": "sha512-gP9vduuYWb9ZkDM546M+MP2qKVk5ZG2wPF63OvSRuUbqCR+11ZCAE1mOfllhlAG0wcoJY5yDL/rV3OmYEwXIzg==", + "dependencies": { + "@mongodb-js/saslprep": "^1.1.5", + "bson": "^6.7.0", + "mongodb-connection-string-url": "^3.0.0" + }, + "engines": { + "node": ">=16.20.1" + }, + "peerDependencies": { + "@aws-sdk/credential-providers": "^3.188.0", + "@mongodb-js/zstd": "^1.1.0", + "gcp-metadata": "^5.2.0", + "kerberos": "^2.0.1", + "mongodb-client-encryption": ">=6.0.0 <7", + "snappy": "^7.2.2", + "socks": "^2.7.1" + }, + "peerDependenciesMeta": { + "@aws-sdk/credential-providers": { + "optional": true + }, + "@mongodb-js/zstd": { + "optional": true + }, + "gcp-metadata": { + "optional": true + }, + "kerberos": { + "optional": true + }, + "mongodb-client-encryption": { + "optional": true + }, + "snappy": { + "optional": true + }, + "socks": { + "optional": true + } + } + }, + "node_modules/mongodb-connection-string-url": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.1.tgz", + "integrity": "sha512-XqMGwRX0Lgn05TDB4PyG2h2kKO/FfWJyCzYQbIhXUxz7ETt0I/FqHjUeqj37irJ+Dl1ZtU82uYyj14u2XsZKfg==", + "dependencies": { + "@types/whatwg-url": "^11.0.2", + "whatwg-url": "^13.0.0" + } + }, + "node_modules/mongoose": { + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.8.1.tgz", + "integrity": "sha512-l7DgeY1szT98+EKU8GYnga5WnyatAu+kOQ2VlVX1Mxif6A0Umt0YkSiksCiyGxzx8SPhGe9a53ND1GD4yVDrPA==", + "dependencies": { + "bson": "^6.7.0", + "kareem": "2.6.3", + "mongodb": "~6.10.0", + "mpath": "0.9.0", + "mquery": "5.0.0", + "ms": "2.1.3", + "sift": "17.1.3" + }, + "engines": { + "node": ">=16.20.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mongoose" + } + }, + "node_modules/mongoose/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==" + }, + "node_modules/mpath": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.9.0.tgz", + "integrity": "sha512-ikJRQTk8hw5DEoFVxHG1Gn9T/xcjtdnOKIU1JTmGjZZlg9LST2mBLmcX3/ICIbgJydT2GOc15RnNy5mHmzfSew==", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/mquery": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/mquery/-/mquery-5.0.0.tgz", + "integrity": "sha512-iQMncpmEK8R8ncT8HJGsGc9Dsp8xcgYMVSbs5jgnm1lFHTZqMJTUWTDx1LBO8+mK3tPNZWFLBghQEIOULSTHZg==", + "dependencies": { + "debug": "4.x" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/mquery/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/mquery/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==" + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "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==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-addon-api": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz", + "integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==", + "license": "MIT" + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-fetch/node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, + "node_modules/node-fetch/node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, + "node_modules/node-fetch/node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/nodemailer": { + "version": "6.9.16", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.16.tgz", + "integrity": "sha512-psAuZdTIRN08HKVd/E8ObdV6NO7NTBY3KsC30F7M4H1OnmLCUNaS56FpYxyb26zWLSyYF9Ozch9KYHhHegsiOQ==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/nodemon": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.7.tgz", + "integrity": "sha512-hLj7fuMow6f0lbB0cD14Lz2xNjwsyruH251Pk4t/yIitCFJbmY1myuLlHm/q06aST4jg6EgAh74PIBBrRqpVAQ==", + "dev": true, + "dependencies": { + "chokidar": "^3.5.2", + "debug": "^4", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.1.2", + "pstree.remy": "^1.1.8", + "semver": "^7.5.3", + "simple-update-notifier": "^2.0.0", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "bin": { + "nodemon": "bin/nodemon.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nodemon" + } + }, + "node_modules/nodemon/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/nodemon/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 + }, + "node_modules/nopt": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", + "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "license": "ISC", + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npmlog": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", + "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", + "deprecated": "This package is no longer supported.", + "license": "ISC", + "dependencies": { + "are-we-there-yet": "^2.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^3.0.0", + "set-blocking": "^2.0.0" + } + }, + "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==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz", + "integrity": "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==", + "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==", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "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==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/otp-generator": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/otp-generator/-/otp-generator-4.0.1.tgz", + "integrity": "sha512-2TJ52vUftA0+J3eque4wwVtpaL4/NdIXDL0gFWFJFVUAZwAN7+9tltMhL7GCNYaHJtuONoier8Hayyj4HLbSag==", + "engines": { + "node": ">=14.10.0" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "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==" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "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==", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", + "dev": true + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/qs": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "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==", + "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==", + "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/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "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", + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "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" + } + ] + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "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==", + "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==", + "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==" + }, + "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==", + "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-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", + "license": "ISC" + }, + "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==", + "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==" + }, + "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==", + "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/sift": { + "version": "17.1.3", + "resolved": "https://registry.npmjs.org/sift/-/sift-17.1.3.tgz", + "integrity": "sha512-Rtlj66/b0ICeFzYTuNvX/EF1igRbbnGSvEyT79McoZa/DeGhMyC5pWKOEsZKnpkqtSeovd5FL/bjHWC3CIIvCQ==" + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "license": "ISC" + }, + "node_modules/simple-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + "dev": true, + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/socket.io": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.1.tgz", + "integrity": "sha512-oZ7iUCxph8WYRHHcjBEc9unw3adt5CmSNlppj/5Q4k2RIrhl8Z5yY2Xr4j9zj0+wzVZ0bxmYoGSzKJnRl6A4yg==", + "dependencies": { + "accepts": "~1.3.4", + "base64id": "~2.0.0", + "cors": "~2.8.5", + "debug": "~4.3.2", + "engine.io": "~6.6.0", + "socket.io-adapter": "~2.5.2", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/socket.io-adapter": { + "version": "2.5.5", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.5.tgz", + "integrity": "sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==", + "dependencies": { + "debug": "~4.3.4", + "ws": "~8.17.1" + } + }, + "node_modules/socket.io-adapter/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socket.io-adapter/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==" + }, + "node_modules/socket.io-parser": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", + "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-parser/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socket.io-parser/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==" + }, + "node_modules/socket.io/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socket.io/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==" + }, + "node_modules/sparse-bitfield": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", + "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==", + "dependencies": { + "memory-pager": "^1.0.2" + } + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "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==", + "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/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/stripe": { + "version": "17.4.0", + "resolved": "https://registry.npmjs.org/stripe/-/stripe-17.4.0.tgz", + "integrity": "sha512-sQQGZguPxe7/QYXJKtDpfzT2OAH9F8nyE2SOsVdTU793iiU33/dpaKgWaJEGJm8396Yy/6NvTLblgdHlueGLhA==", + "license": "MIT", + "dependencies": { + "@types/node": ">=8.1.0", + "qs": "^6.11.0" + }, + "engines": { + "node": ">=12.*" + } + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/tar": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "license": "ISC", + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "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==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/touch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", + "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", + "dev": true, + "bin": { + "nodetouch": "bin/nodetouch.js" + } + }, + "node_modules/tr46": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-4.1.1.tgz", + "integrity": "sha512-2lv/66T7e5yNyhAAC4NaKe5nVavzuGJQVVtRYLyQ2OI8tsJ61PMLlelehb0wi2Hx6+hT/OJUWZcw8MjlSRnxvw==", + "dependencies": { + "punycode": "^2.3.0" + }, + "engines": { + "node": ">=14" + } + }, + "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, + "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==", + "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, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", + "dev": true + }, + "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==" + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "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==", + "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 + }, + "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==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-url": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-13.0.0.tgz", + "integrity": "sha512-9WWbymnqj57+XEuqADHrCJ2eSXzn8WXIW/YSGaZtb2WKAInQ6CHfaUUcTyyver0p8BDg5StLQq8h1vtZuwmOig==", + "dependencies": { + "tr46": "^4.1.1", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/wide-align": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "license": "ISC", + "dependencies": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/ws": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC" + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/zod": { + "version": "3.23.8", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz", + "integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + } + } } diff --git a/src/socket/socket.ts b/src/socket/socket.ts index 9662150..e30dd4d 100644 --- a/src/socket/socket.ts +++ b/src/socket/socket.ts @@ -2,7 +2,9 @@ import Chat from "../models/chatModel"; import { createServer } from "node:http"; import { Server } from "socket.io"; import express from "express"; +import dotenv from "dotenv"; +dotenv.config(); export const app = express(); export const server = createServer(app); @@ -15,6 +17,12 @@ const io = new Server(server, { io.on("connection", async (socket) => { if (process.env.NODE_ENV === "development") console.log("User connected"); + + socket.on("joinWorkspace", (workSpaceId) => { + socket.join(workSpaceId); + if (process.env.NODE_ENV === "development") console.log(`User joined workspace: ${workSpaceId}`); + }); + socket.on("sendMessage", async (message) => { try { const chat = await Chat.findOne({ workspaceId: message?.workSpaceId }); @@ -29,7 +37,7 @@ io.on("connection", async (socket) => { const updatedChat = await chat.save(); const populatedChat = await updatedChat.populate("messages.sender"); - io.emit("receiveMessage", populatedChat); + io.to(message?.workSpaceId).emit("receiveMessage", populatedChat); } else { const newChat = new Chat({ workspaceId: message?.workSpaceId, @@ -43,7 +51,7 @@ io.on("connection", async (socket) => { }); const savedMessage = await (await newChat.save()).populate("messages.sender"); - io.emit("receiveMessage", savedMessage); + io.to(message?.workSpaceId).emit("receiveMessage", savedMessage); } } catch (err) { console.error("Error saving message to database:", err);