From d9223bd66bb99470d90344256b41a07207231813 Mon Sep 17 00:00:00 2001 From: harish-pasupuleti Date: Fri, 1 Aug 2025 01:06:55 +0530 Subject: [PATCH 1/3] feature: added full backend functionality for Sign-Up and Sign-In with JWT --- .env.sample | 1 + Backend/.env | 3 + Backend/.env.sample | 3 + Backend/index.js | 29 +++ Backend/middleware/auth.js | 15 ++ Backend/models/Goal.js | 66 ++++++ Backend/models/Transaction.js | 48 ++++ Backend/models/User.js | 20 ++ {server => Backend}/package-lock.json | 290 +++++++++++++---------- {server => Backend}/package.json | 16 +- Backend/routes/auth.js | 38 ++++ Backend/routes/goals.js | 145 ++++++++++++ Backend/routes/transactions.js | 146 ++++++++++++ server/.env.sample | 8 - server/.gitignore | 120 ---------- server/app.js | 20 -- server/controllers/user.controllers.js | 147 ------------ server/db/index.js | 16 -- server/index.js | 20 -- server/middlewares/auth.middleware.js | 32 --- server/models/user.models.js | 67 ------ server/routers/user.routers.js | 17 -- server/utils/ApiError.js | 29 --- server/utils/ApiResponse.js | 10 - server/utils/asyncHandler.js | 8 - src/components/AuthModal.jsx | 155 +++++++++++++ src/components/Dashboard.jsx | 52 +++-- src/components/HomePage.jsx | 131 +++++------ src/components/TransactionContext.jsx | 304 +++++++++++++++++++++---- 29 files changed, 1201 insertions(+), 755 deletions(-) create mode 100644 .env.sample create mode 100644 Backend/.env create mode 100644 Backend/.env.sample create mode 100644 Backend/index.js create mode 100644 Backend/middleware/auth.js create mode 100644 Backend/models/Goal.js create mode 100644 Backend/models/Transaction.js create mode 100644 Backend/models/User.js rename {server => Backend}/package-lock.json (90%) rename {server => Backend}/package.json (57%) create mode 100644 Backend/routes/auth.js create mode 100644 Backend/routes/goals.js create mode 100644 Backend/routes/transactions.js delete mode 100644 server/.env.sample delete mode 100644 server/.gitignore delete mode 100644 server/app.js delete mode 100644 server/controllers/user.controllers.js delete mode 100644 server/db/index.js delete mode 100644 server/index.js delete mode 100644 server/middlewares/auth.middleware.js delete mode 100644 server/models/user.models.js delete mode 100644 server/routers/user.routers.js delete mode 100644 server/utils/ApiError.js delete mode 100644 server/utils/ApiResponse.js delete mode 100644 server/utils/asyncHandler.js create mode 100644 src/components/AuthModal.jsx diff --git a/.env.sample b/.env.sample new file mode 100644 index 0000000..36c235c --- /dev/null +++ b/.env.sample @@ -0,0 +1 @@ +VITE_API_URL= # Base URL for your API, e.g., http://localhost:5000/api \ No newline at end of file diff --git a/Backend/.env b/Backend/.env new file mode 100644 index 0000000..2f03c1c --- /dev/null +++ b/Backend/.env @@ -0,0 +1,3 @@ +MONGODB_URL="mongodb+srv://harishpasupuleti18:dltvMaOlYvh7iEwd@cluster0.k21edhb.mongodb.net/?retryWrites=true&w=majority&appName=Cluster0" +JWT_SECRET=123 +Frontend_URL=http://localhost:5173 \ No newline at end of file diff --git a/Backend/.env.sample b/Backend/.env.sample new file mode 100644 index 0000000..d7ca2e3 --- /dev/null +++ b/Backend/.env.sample @@ -0,0 +1,3 @@ +MONGODB_URL="" # Your MongoDB connection instance.JWT_SECRET=123 +JWT_SECRET= # Your JWT secret key +Frontend_URL= # URL of your frontend application, e.g., http://localhost:5173 \ No newline at end of file diff --git a/Backend/index.js b/Backend/index.js new file mode 100644 index 0000000..a7deeed --- /dev/null +++ b/Backend/index.js @@ -0,0 +1,29 @@ +const express = require('express'); +const mongoose = require('mongoose'); +const dotenv = require('dotenv'); +const goals = require('./routes/goals'); +const transactions = require('./routes/transactions.js'); +const cors = require('cors'); +const { urlencoded } = require('express'); +dotenv.config(); + + +const app = express(); +app.use(express.json()); +app.use(cors({ + origin: process.env.FRONTEND_URL, + credentials: true, +})); +app.use(urlencoded({ extended: true })); + + +mongoose.connect(process.env.MONGODB_URL) + .then(() => console.log('MongoDB connected')) + .catch(err => console.error(err)); + +app.use('/api/auth', require('./routes/auth')); +app.use('/api/goals', goals); +app.use('/api/transactions', transactions); + +const PORT = process.env.PORT || 5000; +app.listen(PORT, () => console.log(`Server running on port ${PORT}`)); diff --git a/Backend/middleware/auth.js b/Backend/middleware/auth.js new file mode 100644 index 0000000..b614501 --- /dev/null +++ b/Backend/middleware/auth.js @@ -0,0 +1,15 @@ +const jwt = require('jsonwebtoken'); +const User = require('../models/User.js'); + +module.exports = async (req, res, next) => { + const token = req.header('Authorization')?.split(' ')[1]; + if (!token) return res.status(401).json({ msg: 'No token, authorization denied' }); + + try { + const decoded = jwt.verify(token, process.env.JWT_SECRET); + req.user = await User.findById(decoded.id).select('-password'); + next(); + } catch (err) { + res.status(401).json({ msg: 'Token is not valid' }); + } +}; diff --git a/Backend/models/Goal.js b/Backend/models/Goal.js new file mode 100644 index 0000000..5bfc45a --- /dev/null +++ b/Backend/models/Goal.js @@ -0,0 +1,66 @@ +const mongoose = require('mongoose'); + +const goalSchema = new mongoose.Schema({ + userId: { + type: mongoose.Schema.Types.ObjectId, + ref: 'User', + required: true + }, + name: { + type: String, + required: true, + trim: true, + maxlength: 100 + }, + targetAmount: { + type: Number, + required: true, + min: 0.01 + }, + currentAmount: { + type: Number, + default: 0, + min: 0 + }, + isCompleted: { + type: Boolean, + default: false + }, + completedAt: { + type: Date, + default: null + } +}, { + timestamps: true +}); + +// Virtual to calculate progress percentage +goalSchema.virtual('progressPercentage').get(function() { + return Math.min((this.currentAmount / this.targetAmount) * 100, 100); +}); + +// Virtual to calculate remaining amount +goalSchema.virtual('remainingAmount').get(function() { + return Math.max(this.targetAmount - this.currentAmount, 0); +}); + +// Middleware to update completion status +goalSchema.pre('save', function(next) { + if (this.currentAmount >= this.targetAmount && !this.isCompleted) { + this.isCompleted = true; + this.completedAt = new Date(); + } else if (this.currentAmount < this.targetAmount && this.isCompleted) { + this.isCompleted = false; + this.completedAt = null; + } + next(); +}); + +// Include virtuals when converting to JSON +goalSchema.set('toJSON', { virtuals: true }); +goalSchema.set('toObject', { virtuals: true }); + +// Index for better query performance +goalSchema.index({ userId: 1, createdAt: -1 }); + +module.exports = mongoose.model('Goal', goalSchema); \ No newline at end of file diff --git a/Backend/models/Transaction.js b/Backend/models/Transaction.js new file mode 100644 index 0000000..d32f08b --- /dev/null +++ b/Backend/models/Transaction.js @@ -0,0 +1,48 @@ +// models/Transaction.js +const mongoose = require('mongoose'); + +const transactionSchema = new mongoose.Schema({ + userId: { + type: mongoose.Schema.Types.ObjectId, + ref: 'User', + required: true + }, + amount: { + type: Number, + required: true, + min: 0 + }, + category: { + type: String, + required: true, + trim: true + }, + type: { + type: String, + required: true, + enum: ['Income', 'Expense'] + }, + date: { + type: Date, + required: true, + default: Date.now + }, + note: { + type: String, + trim: true, + default: '' + }, + goalId: { + type: mongoose.Schema.Types.ObjectId, + ref: 'Goal', + default: null + } +}, { + timestamps: true +}); + +// Index for better query performance +transactionSchema.index({ userId: 1, date: -1 }); +transactionSchema.index({ userId: 1, goalId: 1 }); + +module.exports = mongoose.model('Transaction', transactionSchema); \ No newline at end of file diff --git a/Backend/models/User.js b/Backend/models/User.js new file mode 100644 index 0000000..f67a8f7 --- /dev/null +++ b/Backend/models/User.js @@ -0,0 +1,20 @@ +const mongoose = require('mongoose'); +const bcrypt = require('bcryptjs'); + +const UserSchema = new mongoose.Schema({ + name: String, + email: { type: String, required: true, unique: true }, + password: { type: String, required: true } +}); + +UserSchema.pre('save', async function(next) { + if (!this.isModified('password')) return next(); + this.password = await bcrypt.hash(this.password, 10); + next(); +}); + +UserSchema.methods.comparePassword = async function(password) { + return await bcrypt.compare(password, this.password); +}; + +module.exports = mongoose.model('User', UserSchema); diff --git a/server/package-lock.json b/Backend/package-lock.json similarity index 90% rename from server/package-lock.json rename to Backend/package-lock.json index 9a6b924..870622a 100644 --- a/server/package-lock.json +++ b/Backend/package-lock.json @@ -1,23 +1,20 @@ { - "name": "server", + "name": "backend", "version": "1.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "server", + "name": "backend", "version": "1.0.0", "license": "ISC", "dependencies": { - "bcrypt": "^6.0.0", - "cookie-parser": "^1.4.7", + "bcryptjs": "^3.0.2", "cors": "^2.8.5", - "dotenv": "^17.2.0", + "dotenv": "^17.2.1", "express": "^5.1.0", "jsonwebtoken": "^9.0.2", - "mongoose": "^8.16.4" - }, - "devDependencies": { + "mongoose": "^8.17.0", "nodemon": "^3.1.10" } }, @@ -25,6 +22,7 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.3.0.tgz", "integrity": "sha512-zlayKCsIjYb7/IdfqxorK5+xUMyi4vOKcFy10wKJYc63NSdKI8mNME+uJqfatkPmOSMMUiojrL58IePKBm3gvQ==", + "license": "MIT", "dependencies": { "sparse-bitfield": "^3.0.3" } @@ -32,12 +30,14 @@ "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==" + "integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==", + "license": "MIT" }, "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==", + "license": "MIT", "dependencies": { "@types/webidl-conversions": "*" } @@ -46,6 +46,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" @@ -58,7 +59,7 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, + "license": "ISC", "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" @@ -71,26 +72,22 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true + "license": "MIT" }, - "node_modules/bcrypt": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-6.0.0.tgz", - "integrity": "sha512-cU8v/EGSrnH+HnxV2z0J7/blxH8gq7Xh2JFT6Aroax7UohdmiJJlxApMxtKfuI7z68NvvVcmR78k2LbT6efhRg==", - "hasInstallScript": true, - "dependencies": { - "node-addon-api": "^8.3.0", - "node-gyp-build": "^4.8.4" - }, - "engines": { - "node": ">= 18" + "node_modules/bcryptjs": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-3.0.2.tgz", + "integrity": "sha512-k38b3XOZKv60C4E2hVsXTolJWfkGRMbILBIe2IBITXciy5bOsTKot5kDrf3ZfufQtQOUN5mXceUEpU1rTl9Uog==", + "license": "BSD-3-Clause", + "bin": { + "bcrypt": "bin/bcrypt" } }, "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, + "license": "MIT", "engines": { "node": ">=8" }, @@ -102,6 +99,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", + "license": "MIT", "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", @@ -121,7 +119,7 @@ "version": "1.1.12", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dev": true, + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -131,7 +129,7 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, + "license": "MIT", "dependencies": { "fill-range": "^7.1.1" }, @@ -143,6 +141,7 @@ "version": "6.10.4", "resolved": "https://registry.npmjs.org/bson/-/bson-6.10.4.tgz", "integrity": "sha512-WIsKqkSC0ABoBJuT1LEX+2HEvNmNKKgnTAyd0fL8qzK4SH2i9NXg+t08YtdZp/V9IZ33cxe3iV4yM0qg8lMQng==", + "license": "Apache-2.0", "engines": { "node": ">=16.20.1" } @@ -150,12 +149,14 @@ "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==" + "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==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -164,6 +165,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" @@ -176,6 +178,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" @@ -191,7 +194,7 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "dev": true, + "license": "MIT", "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", @@ -215,12 +218,13 @@ "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true + "license": "MIT" }, "node_modules/content-disposition": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", + "license": "MIT", "dependencies": { "safe-buffer": "5.2.1" }, @@ -232,6 +236,7 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -240,31 +245,25 @@ "version": "0.7.2", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", "engines": { "node": ">= 0.6" } }, - "node_modules/cookie-parser": { - "version": "1.4.7", - "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.7.tgz", - "integrity": "sha512-nGUvgXnotP3BsjiLX2ypbQnWoGUPIIfHQNZkkC668ntrzGWEZVW70HDEB1qnNGMicPje6EttlIgzo51YSwNQGw==", - "dependencies": { - "cookie": "0.7.2", - "cookie-signature": "1.0.6" - }, + "node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", "engines": { - "node": ">= 0.8.0" + "node": ">=6.6.0" } }, - "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" @@ -277,6 +276,7 @@ "version": "4.4.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "license": "MIT", "dependencies": { "ms": "^2.1.3" }, @@ -293,14 +293,16 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", "engines": { "node": ">= 0.8" } }, "node_modules/dotenv": { - "version": "17.2.0", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.0.tgz", - "integrity": "sha512-Q4sgBT60gzd0BB0lSyYD3xM4YxrXA9y4uBDof1JNYGzOXrQdQ6yX+7XIAqoFOGQFOTK1D3Hts5OllpxMDZFONQ==", + "version": "17.2.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.1.tgz", + "integrity": "sha512-kQhDYKZecqnM0fCnzI5eIv5L4cAe/iRI+HqMbO/hbRdTAeXDG+M9FjipUxNfbARuEg4iHIbhnhs78BCHNbSxEQ==", + "license": "BSD-2-Clause", "engines": { "node": ">=12" }, @@ -312,6 +314,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", @@ -325,6 +328,7 @@ "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" } @@ -332,12 +336,14 @@ "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==" + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" }, "node_modules/encodeurl": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -346,6 +352,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", "engines": { "node": ">= 0.4" } @@ -354,6 +361,7 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", "engines": { "node": ">= 0.4" } @@ -362,6 +370,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", "dependencies": { "es-errors": "^1.3.0" }, @@ -372,12 +381,14 @@ "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==" + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" }, "node_modules/etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -386,6 +397,7 @@ "version": "5.1.0", "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", + "license": "MIT", "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.0", @@ -423,19 +435,11 @@ "url": "https://opencollective.com/express" } }, - "node_modules/express/node_modules/cookie-signature": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", - "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", - "engines": { - "node": ">=6.6.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, + "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" }, @@ -447,6 +451,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", + "license": "MIT", "dependencies": { "debug": "^4.4.0", "encodeurl": "^2.0.0", @@ -463,6 +468,7 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -471,6 +477,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -479,8 +486,8 @@ "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, "hasInstallScript": true, + "license": "MIT", "optional": true, "os": [ "darwin" @@ -493,6 +500,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -501,6 +509,7 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", @@ -524,6 +533,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" @@ -536,7 +546,7 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, + "license": "ISC", "dependencies": { "is-glob": "^4.0.1" }, @@ -548,6 +558,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -559,7 +570,7 @@ "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, + "license": "MIT", "engines": { "node": ">=4" } @@ -568,6 +579,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -579,6 +591,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", "dependencies": { "function-bind": "^1.1.2" }, @@ -590,6 +603,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "license": "MIT", "dependencies": { "depd": "2.0.0", "inherits": "2.0.4", @@ -605,6 +619,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -613,6 +628,7 @@ "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" }, @@ -624,17 +640,19 @@ "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 + "license": "ISC" }, "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" }, "node_modules/ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", "engines": { "node": ">= 0.10" } @@ -643,7 +661,7 @@ "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, + "license": "MIT", "dependencies": { "binary-extensions": "^2.0.0" }, @@ -655,7 +673,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -664,7 +682,7 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, + "license": "MIT", "dependencies": { "is-extglob": "^2.1.1" }, @@ -676,7 +694,7 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, + "license": "MIT", "engines": { "node": ">=0.12.0" } @@ -684,12 +702,14 @@ "node_modules/is-promise": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", - "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==" + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "license": "MIT" }, "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", @@ -711,6 +731,7 @@ "version": "1.4.2", "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.2.tgz", "integrity": "sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw==", + "license": "MIT", "dependencies": { "buffer-equal-constant-time": "^1.0.1", "ecdsa-sig-formatter": "1.0.11", @@ -721,6 +742,7 @@ "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" @@ -730,6 +752,7 @@ "version": "2.6.3", "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.6.3.tgz", "integrity": "sha512-C3iHfuGUXK2u8/ipq9LfjFfXFxAZMQJJq7vLS45r3D9Y2xQ/m4S8zaR4zMLFWh9AsNPXmcFfUDhTEO8UIC/V6Q==", + "license": "Apache-2.0", "engines": { "node": ">=12.0.0" } @@ -737,42 +760,50 @@ "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==" + "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==" + "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==" + "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==" + "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==" + "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==" + "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==" + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "license": "MIT" }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", "engines": { "node": ">= 0.4" } @@ -781,6 +812,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -788,12 +820,14 @@ "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==" + "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==", + "license": "MIT" }, "node_modules/merge-descriptors": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "license": "MIT", "engines": { "node": ">=18" }, @@ -805,6 +839,7 @@ "version": "1.54.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -813,6 +848,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + "license": "MIT", "dependencies": { "mime-db": "^1.54.0" }, @@ -824,7 +860,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, + "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -833,9 +869,10 @@ } }, "node_modules/mongodb": { - "version": "6.17.0", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.17.0.tgz", - "integrity": "sha512-neerUzg/8U26cgruLysKEjJvoNSXhyID3RvzvdcpsIi2COYM3FS3o9nlH7fxFtefTb942dX3W9i37oPfCVj4wA==", + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.18.0.tgz", + "integrity": "sha512-fO5ttN9VC8P0F5fqtQmclAkgXZxbIkYRTUi1j8JO6IYwvamkhtYDilJr35jOPELR49zqCJgXZWwCtW7B+TM8vQ==", + "license": "Apache-2.0", "dependencies": { "@mongodb-js/saslprep": "^1.1.9", "bson": "^6.10.4", @@ -881,19 +918,21 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.2.tgz", "integrity": "sha512-rMO7CGo/9BFwyZABcKAWL8UJwH/Kc2x0g72uhDWzG48URRax5TCIcJ7Rc3RZqffZzO/Gwff/jyKwCU9TN8gehA==", + "license": "Apache-2.0", "dependencies": { "@types/whatwg-url": "^11.0.2", "whatwg-url": "^14.1.0 || ^13.0.0" } }, "node_modules/mongoose": { - "version": "8.16.4", - "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.16.4.tgz", - "integrity": "sha512-jslgdQ8pY2vcNSKPv3Dbi5ogo/NT8zcvf6kPDyD8Sdsjsa1at3AFAF0F5PT+jySPGSPbvlNaQ49nT9h+Kx2UDA==", + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.17.0.tgz", + "integrity": "sha512-mxW6TBPHViORfNYOFXCVOnT4d5aRr+CgDxTs1ViYXfuHzNpkelgJQrQa+Lz6hofoEQISnKlXv1L3ZnHyJRkhfA==", + "license": "MIT", "dependencies": { "bson": "^6.10.4", "kareem": "2.6.3", - "mongodb": "~6.17.0", + "mongodb": "~6.18.0", "mpath": "0.9.0", "mquery": "5.0.0", "ms": "2.1.3", @@ -911,6 +950,7 @@ "version": "0.9.0", "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.9.0.tgz", "integrity": "sha512-ikJRQTk8hw5DEoFVxHG1Gn9T/xcjtdnOKIU1JTmGjZZlg9LST2mBLmcX3/ICIbgJydT2GOc15RnNy5mHmzfSew==", + "license": "MIT", "engines": { "node": ">=4.0.0" } @@ -919,6 +959,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/mquery/-/mquery-5.0.0.tgz", "integrity": "sha512-iQMncpmEK8R8ncT8HJGsGc9Dsp8xcgYMVSbs5jgnm1lFHTZqMJTUWTDx1LBO8+mK3tPNZWFLBghQEIOULSTHZg==", + "license": "MIT", "dependencies": { "debug": "4.x" }, @@ -929,39 +970,23 @@ "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==" + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" }, "node_modules/negotiator": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "license": "MIT", "engines": { "node": ">= 0.6" } }, - "node_modules/node-addon-api": { - "version": "8.5.0", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.5.0.tgz", - "integrity": "sha512-/bRZty2mXUIFY/xU5HLvveNHlswNJej+RnxBjOMkidWfwZzgTbPG1E3K5TOxRLOR+5hX7bSofy8yf1hZevMS8A==", - "engines": { - "node": "^18 || ^20 || >= 21" - } - }, - "node_modules/node-gyp-build": { - "version": "4.8.4", - "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", - "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==", - "bin": { - "node-gyp-build": "bin.js", - "node-gyp-build-optional": "optional.js", - "node-gyp-build-test": "build-test.js" - } - }, "node_modules/nodemon": { "version": "3.1.10", "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.10.tgz", "integrity": "sha512-WDjw3pJ0/0jMFmyNDp3gvY2YizjLmmOUQo6DEBY+JgdvW/yQ9mEeSw6H5ythl5Ny2ytb7f9C2nIbjSxMNzbJXw==", - "dev": true, + "license": "MIT", "dependencies": { "chokidar": "^3.5.2", "debug": "^4", @@ -989,7 +1014,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -998,6 +1023,7 @@ "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" } @@ -1006,6 +1032,7 @@ "version": "1.13.4", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -1017,6 +1044,7 @@ "version": "2.4.1", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", "dependencies": { "ee-first": "1.1.1" }, @@ -1028,6 +1056,7 @@ "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" } @@ -1036,6 +1065,7 @@ "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -1044,6 +1074,7 @@ "version": "8.2.0", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz", "integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==", + "license": "MIT", "engines": { "node": ">=16" } @@ -1052,7 +1083,7 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, + "license": "MIT", "engines": { "node": ">=8.6" }, @@ -1064,6 +1095,7 @@ "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==", + "license": "MIT", "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" @@ -1076,12 +1108,13 @@ "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 + "license": "MIT" }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "license": "MIT", "engines": { "node": ">=6" } @@ -1090,6 +1123,7 @@ "version": "6.14.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "license": "BSD-3-Clause", "dependencies": { "side-channel": "^1.1.0" }, @@ -1104,6 +1138,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -1112,6 +1147,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz", "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==", + "license": "MIT", "dependencies": { "bytes": "3.1.2", "http-errors": "2.0.0", @@ -1126,7 +1162,7 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, + "license": "MIT", "dependencies": { "picomatch": "^2.2.1" }, @@ -1138,6 +1174,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "license": "MIT", "dependencies": { "debug": "^4.4.0", "depd": "^2.0.0", @@ -1166,17 +1203,20 @@ "type": "consulting", "url": "https://feross.org/support" } - ] + ], + "license": "MIT" }, "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" }, "node_modules/semver": { "version": "7.7.2", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "license": "ISC", "bin": { "semver": "bin/semver.js" }, @@ -1188,6 +1228,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", + "license": "MIT", "dependencies": { "debug": "^4.3.5", "encodeurl": "^2.0.0", @@ -1209,6 +1250,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", + "license": "MIT", "dependencies": { "encodeurl": "^2.0.0", "escape-html": "^1.0.3", @@ -1222,12 +1264,14 @@ "node_modules/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" }, "node_modules/side-channel": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3", @@ -1246,6 +1290,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3" @@ -1261,6 +1306,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", @@ -1278,6 +1324,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", @@ -1295,13 +1342,14 @@ "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==" + "integrity": "sha512-Rtlj66/b0ICeFzYTuNvX/EF1igRbbnGSvEyT79McoZa/DeGhMyC5pWKOEsZKnpkqtSeovd5FL/bjHWC3CIIvCQ==", + "license": "MIT" }, "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, + "license": "MIT", "dependencies": { "semver": "^7.5.3" }, @@ -1313,6 +1361,7 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==", + "license": "MIT", "dependencies": { "memory-pager": "^1.0.2" } @@ -1321,6 +1370,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -1329,7 +1379,7 @@ "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, + "license": "MIT", "dependencies": { "has-flag": "^3.0.0" }, @@ -1341,7 +1391,7 @@ "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, + "license": "MIT", "dependencies": { "is-number": "^7.0.0" }, @@ -1353,6 +1403,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", "engines": { "node": ">=0.6" } @@ -1361,7 +1412,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", - "dev": true, + "license": "ISC", "bin": { "nodetouch": "bin/nodetouch.js" } @@ -1370,6 +1421,7 @@ "version": "5.1.1", "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", + "license": "MIT", "dependencies": { "punycode": "^2.3.1" }, @@ -1381,6 +1433,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "license": "MIT", "dependencies": { "content-type": "^1.0.5", "media-typer": "^1.1.0", @@ -1394,12 +1447,13 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", - "dev": true + "license": "MIT" }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -1408,6 +1462,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -1416,6 +1471,7 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "license": "BSD-2-Clause", "engines": { "node": ">=12" } @@ -1424,6 +1480,7 @@ "version": "14.2.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", + "license": "MIT", "dependencies": { "tr46": "^5.1.0", "webidl-conversions": "^7.0.0" @@ -1435,7 +1492,8 @@ "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==" + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" } } } diff --git a/server/package.json b/Backend/package.json similarity index 57% rename from server/package.json rename to Backend/package.json index 662c03c..2ae18d1 100644 --- a/server/package.json +++ b/Backend/package.json @@ -1,26 +1,22 @@ { - "name": "server", + "name": "backend", "version": "1.0.0", "main": "index.js", - "type": "module", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", - "dev": "nodemon --watch . index.js" + "start": "node index.js", + "dev": "nodemon index.js" }, - "keywords": [], "author": "", "license": "ISC", "description": "", "dependencies": { - "bcrypt": "^6.0.0", - "cookie-parser": "^1.4.7", + "bcryptjs": "^3.0.2", "cors": "^2.8.5", - "dotenv": "^17.2.0", + "dotenv": "^17.2.1", "express": "^5.1.0", "jsonwebtoken": "^9.0.2", - "mongoose": "^8.16.4" - }, - "devDependencies": { + "mongoose": "^8.17.0", "nodemon": "^3.1.10" } } diff --git a/Backend/routes/auth.js b/Backend/routes/auth.js new file mode 100644 index 0000000..f9b496d --- /dev/null +++ b/Backend/routes/auth.js @@ -0,0 +1,38 @@ +const express = require('express'); +const router = express.Router(); +const User = require('../../Backend/models/User'); +const jwt = require('jsonwebtoken'); +const bcrypt = require('bcryptjs'); + +router.post('/signup', async (req, res) => { + const { name, email, password } = req.body; + try { + let user = await User.findOne({ email }); + if (user) return res.status(400).json({ msg: 'User already exists' }); + + user = new User({ name, email, password }); + await user.save(); + + const token = jwt.sign({ id: user._id }, process.env.JWT_SECRET, { expiresIn: '7d' }); + res.json({ token, user: { id: user._id, name: user.name, email: user.email } }); + } catch (err) { + res.status(500).send('Server error'); + } +}); + +router.post('/signin', async (req, res) => { + const { email, password } = req.body; + try { + const user = await User.findOne({ email }); + if (!user || !(await user.comparePassword(password))) { + return res.status(400).json({ msg: 'Invalid credentials' }); + } + + const token = jwt.sign({ id: user._id }, process.env.JWT_SECRET, { expiresIn: '7d' }); + res.json({ token, user: { id: user._id, name: user.name, email: user.email } }); + } catch (err) { + res.status(500).send('Server error'); + } +}); + +module.exports = router; diff --git a/Backend/routes/goals.js b/Backend/routes/goals.js new file mode 100644 index 0000000..01fe4a3 --- /dev/null +++ b/Backend/routes/goals.js @@ -0,0 +1,145 @@ +const express = require('express'); +const router = express.Router(); +const auth = require('../middleware/auth.js'); +const Goal = require('../models/Goal'); +const Transaction = require('../models/Transaction'); + +// POST /api/goals - Create a new goal +router.post('/', auth, async (req, res) => { + try { + const { name, targetAmount } = req.body; + + // Validate input + if (!name || !targetAmount) { + return res.status(400).json({ error: 'Name and target amount are required' }); + } + + if (Number(targetAmount) <= 0) { + return res.status(400).json({ error: 'Target amount must be greater than 0' }); + } + + const goal = new Goal({ + userId: req.user.id, + name, + targetAmount: Number(targetAmount), + currentAmount: 0 + }); + + await goal.save(); + res.status(201).json(goal); + } catch (err) { + console.error('Error saving goal:', err); + res.status(500).json({ error: 'Server error while saving goal' }); + } +}); + +// GET /api/goals - Get all goals for the authenticated user +router.get('/', auth, async (req, res) => { + try { + const goals = await Goal.find({ userId: req.user.id }) + .sort({ createdAt: -1 }); // Sort by creation date, newest first + + if (!goals || goals.length === 0) { + return res.status(404).json({ error: 'No goals found' }); + } + + res.status(200).json(goals); + } catch (err) { + console.error('Error fetching goals:', err); + res.status(500).json({ error: 'Server error while fetching goals' }); + } +}); + +// PUT /api/goals/:id - Update a goal +router.put('/:id', auth, async (req, res) => { + try { + const updates = req.body; + + // Validate currentAmount if provided + if (updates.currentAmount !== undefined && Number(updates.currentAmount) < 0) { + return res.status(400).json({ error: 'Current amount cannot be negative' }); + } + + // Validate targetAmount if provided + if (updates.targetAmount !== undefined && Number(updates.targetAmount) <= 0) { + return res.status(400).json({ error: 'Target amount must be greater than 0' }); + } + + const goal = await Goal.findOneAndUpdate( + { _id: req.params.id, userId: req.user.id }, + updates, + { new: true, runValidators: true } + ); + + if (!goal) { + return res.status(404).json({ error: 'Goal not found' }); + } + + res.status(200).json(goal); + } catch (err) { + console.error('Error updating goal:', err); + res.status(500).json({ error: 'Server error while updating goal' }); + } +}); + +// DELETE /api/goals/:id - Delete a goal and all related transactions +router.delete('/:id', auth, async (req, res) => { + try { + // Find the goal first + const goal = await Goal.findOne({ + _id: req.params.id, + userId: req.user.id + }); + + if (!goal) { + return res.status(404).json({ error: 'Goal not found' }); + } + + // Delete all transactions related to this goal + const deleteResult = await Transaction.deleteMany({ + goalId: req.params.id, + userId: req.user.id + }); + + // Delete the goal + await Goal.findOneAndDelete({ + _id: req.params.id, + userId: req.user.id + }); + + res.status(200).json({ + message: 'Goal deleted successfully', + deletedTransactions: deleteResult.deletedCount + }); + } catch (err) { + console.error('Error deleting goal:', err); + res.status(500).json({ error: 'Server error while deleting goal' }); + } +}); + +// GET /api/goals/:id/transactions - Get all transactions for a specific goal +router.get('/:id/transactions', auth, async (req, res) => { + try { + // First verify the goal exists and belongs to the user + const goal = await Goal.findOne({ + _id: req.params.id, + userId: req.user.id + }); + + if (!goal) { + return res.status(404).json({ error: 'Goal not found' }); + } + + const transactions = await Transaction.find({ + goalId: req.params.id, + userId: req.user.id + }).sort({ date: -1 }); + + res.status(200).json(transactions); + } catch (err) { + console.error('Error fetching goal transactions:', err); + res.status(500).json({ error: 'Server error while fetching goal transactions' }); + } +}); + +module.exports = router; \ No newline at end of file diff --git a/Backend/routes/transactions.js b/Backend/routes/transactions.js new file mode 100644 index 0000000..5b35ac3 --- /dev/null +++ b/Backend/routes/transactions.js @@ -0,0 +1,146 @@ +const express = require('express'); +const router = express.Router(); +const auth = require('../middleware/auth.js'); +const Transaction = require('../models/Transaction'); +const Goal = require('../models/Goal'); + +// POST /api/transactions - Add a new transaction +router.post('/', auth, async (req, res) => { + try { + const { amount, category, type, date, note, goalId } = req.body; + + // Basic validation + if (!amount || !category || !type || !date) { + return res.status(400).json({ error: 'Missing required fields' }); + } + + // Validate and convert date + const transactionDate = date ? new Date(date) : new Date(); + + if (isNaN(transactionDate.getTime())) { + return res.status(400).json({ error: 'Invalid date format' }); + } + + const transaction = new Transaction({ + userId: req.user.id, + amount: Number(amount), + category, + type, + date: transactionDate, + note, + goalId: goalId || null + }); + + await transaction.save(); + res.status(201).json(transaction); + } catch (err) { + console.error('Error creating transaction:', err); + res.status(500).json({ error: 'Server error while saving transaction' }); + } +}); + +// GET /api/transactions - Get all transactions for the authenticated user +router.get('/', auth, async (req, res) => { + try { + const transactions = await Transaction.find({ userId: req.user.id }) + .sort({ date: -1 }); // Sort by date, newest first + + if (!transactions || transactions.length === 0) { + return res.status(404).json({ error: 'No transactions found' }); + } + + res.status(200).json(transactions); + } catch (err) { + console.error('Error fetching transactions:', err); + res.status(500).json({ error: 'Server error while fetching transactions' }); + } +}); + +// DELETE /api/transactions/:id - Delete a transaction +router.delete('/:id', auth, async (req, res) => { + try { + // Find the transaction first to check if it's related to a goal + const transaction = await Transaction.findOne({ + _id: req.params.id, + userId: req.user.id + }); + + if (!transaction) { + return res.status(404).json({ error: 'Transaction not found' }); + } + + // If this transaction was a contribution to a goal, update the goal's current amount + if (transaction.goalId) { + await Goal.findOneAndUpdate( + { _id: transaction.goalId, userId: req.user.id }, + { $inc: { currentAmount: -transaction.amount } }, + { new: true } + ); + } + + // Delete the transaction + await Transaction.findOneAndDelete({ + _id: req.params.id, + userId: req.user.id + }); + + res.status(200).json({ message: 'Transaction deleted successfully' }); + } catch (err) { + console.error('Error deleting transaction:', err); + res.status(500).json({ error: 'Server error while deleting transaction' }); + } +}); + +// PUT /api/transactions/:id - Update a transaction +router.put('/:id', auth, async (req, res) => { + try { + const { amount, category, type, date, note, goalId } = req.body; + + // Find the existing transaction + const existingTransaction = await Transaction.findOne({ + _id: req.params.id, + userId: req.user.id + }); + + if (!existingTransaction) { + return res.status(404).json({ error: 'Transaction not found' }); + } + + // If the transaction was previously linked to a goal, reverse its contribution + if (existingTransaction.goalId) { + await Goal.findOneAndUpdate( + { _id: existingTransaction.goalId, userId: req.user.id }, + { $inc: { currentAmount: -existingTransaction.amount } } + ); + } + + // Update the transaction + const updatedTransaction = await Transaction.findOneAndUpdate( + { _id: req.params.id, userId: req.user.id }, + { + amount: Number(amount), + category, + type, + date: date ? new Date(date) : existingTransaction.date, + note, + goalId: goalId || null + }, + { new: true } + ); + + // If the updated transaction is linked to a goal, add its contribution + if (updatedTransaction.goalId) { + await Goal.findOneAndUpdate( + { _id: updatedTransaction.goalId, userId: req.user.id }, + { $inc: { currentAmount: updatedTransaction.amount } } + ); + } + + res.status(200).json(updatedTransaction); + } catch (err) { + console.error('Error updating transaction:', err); + res.status(500).json({ error: 'Server error while updating transaction' }); + } +}); + +module.exports = router; \ No newline at end of file diff --git a/server/.env.sample b/server/.env.sample deleted file mode 100644 index 13ff683..0000000 --- a/server/.env.sample +++ /dev/null @@ -1,8 +0,0 @@ -MONGODB_URL="" # Your MongoDB connection instance. -DB_NAME="" # DB name ex: SmartLog -CORS_ORIGIN=* # add the frontend origin -PORT=8000 -ACCESS_TOKEN_SECRET= # your access token secret here for encryption. -ACCESS_TOKEN_EXPIRY= # ex: 1d -REFRESH_TOKEN_SECRET= # your refresh token secret here for encryption. -REFRESH_TOKEN_EXPIRY= # ex: 10d \ No newline at end of file diff --git a/server/.gitignore b/server/.gitignore deleted file mode 100644 index b277f17..0000000 --- a/server/.gitignore +++ /dev/null @@ -1,120 +0,0 @@ -# Logs -logs -*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* -lerna-debug.log* -.pnpm-debug.log* - -# Diagnostic reports (https://nodejs.org/api/report.html) -report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json - -# Runtime data -pids -*.pid -*.seed -*.pid.lock - -# Directory for instrumented libs generated by jscoverage/JSCover -lib-cov - -# Coverage directory used by tools like istanbul -coverage -*.lcov - -# nyc test coverage -.nyc_output - -# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) -.grunt - -# Bower dependency directory (https://bower.io/) -bower_components - -# node-waf configuration -.lock-wscript - -# Compiled binary addons (https://nodejs.org/api/addons.html) -build/Release - -# Dependency directories -node_modules/ -jspm_packages/ - -# Snowpack dependency directory (https://snowpack.dev/) -web_modules/ - -# TypeScript cache -*.tsbuildinfo - -# Optional npm cache directory -.npm - -# Optional eslint cache -.eslintcache - -# Microbundle cache -.rpt2_cache/ -.rts2_cache_cjs/ -.rts2_cache_es/ -.rts2_cache_umd/ - -# Optional REPL history -.node_repl_history - -# Output of 'npm pack' -*.tgz - -# Yarn Integrity file -.yarn-integrity - -# dotenv environment variables file -.env -.env.test -.env.production - -# parcel-bundler cache (https://parceljs.org/) -.cache -.parcel-cache - -# Next.js build output -.next -out - -# Nuxt.js build / generate output -.nuxt -dist - -# Gatsby files -.cache/ -# Comment in the public line in if your project uses Gatsby and not Next.js -# https://nextjs.org/blog/next-9-1#public-directory-support -# public - -# vuepress build output -.vuepress/dist - -# Serverless directories -.serverless/ - -# FuseBox cache -.fusebox/ - -# DynamoDB Local files -.dynamodb/ - -# TernJS port file -.tern-port - -# Stores VSCode versions used for testing VSCode extensions -.vscode-test - -# yarn v2 -.yarn/cache -.yarn/unplugged -.yarn/build-state.yml -.yarn/install-state.gz -.pnp.* - -# End of https://mrkandreev.name/snippets/gitignore-generator/#Node \ No newline at end of file diff --git a/server/app.js b/server/app.js deleted file mode 100644 index 8033926..0000000 --- a/server/app.js +++ /dev/null @@ -1,20 +0,0 @@ -import express from "express"; -import cookieParser from "cookie-parser"; -import cors from "cors"; - -const app = express(); -app.use(cors({ credentials: true, origin: process.env.CORS_ORIGIN })); -app.use(express.json({ limit: "16kb" })); -app.use(express.urlencoded({ extended: true })); -app.use(cookieParser()); - -// Health Checkup -app.get("/is-up", (req, res) => { - res.send("Hello World!"); -}); - -// All the routes here -import userRoutes from "./routers/user.routers.js"; -app.use("/api/v1/users", userRoutes); - -export default app; diff --git a/server/controllers/user.controllers.js b/server/controllers/user.controllers.js deleted file mode 100644 index e9447f3..0000000 --- a/server/controllers/user.controllers.js +++ /dev/null @@ -1,147 +0,0 @@ -import ApiError from "../utils/ApiError.js"; -import ApiResponse from "../utils/ApiResponse.js"; -import asyncHandler from "../utils/asyncHandler.js"; -import { User } from "../models/user.models.js"; - -const generateAccessAndRefreshToken = async (user) => { - const accessToken = await user.generateAccessToken(); - const refreshToken = await user.generateRefreshToken(); - user.refreshToken = refreshToken; - // Skip validation since only partial fields are updated (other required fields may be missing) - await user.save({ - validateBeforeSave: false, - }); - - return { accessToken: accessToken, refreshToken: refreshToken }; -}; - -const registerUser = asyncHandler(async (req, res) => { - const { username, email, password, fullName } = req.body; - - // Checking if the each and every field that it's not empty - if ( - [username, email, password, fullName].some((field) => field?.trim() === "") - ) { - //if Any of the field is empty it will throw and exception - throw new ApiError(400, "All the fields are required"); - } - const isUserExists = await User.findOne({ $or: [{ username }, { email }] }); - if (isUserExists) { - // Just a simple if else check if both the name and email have already been created and throws error based on the condition - - // if (isUserExists.username === username && isUserExists.email === email) { - // throw new ApiError( - // 401, - // "User with the following username or email exists" - // ); - // } else if ( - // isUserExists.username === username && - // !isUserExists.email === email - // ) { - // throw new ApiError(401, "User with the following username exists"); - // } else throw new ApiError(401, "User with the following email exists"); - - // ***** More cleaner version ***** // - - let conflictField = []; - - if (isUserExists.username === username.toLowerCase()) - conflictField.push("username"); - if (isUserExists.email === email) conflictField.push("email"); - - const message = `User with this ${conflictField.join( - " and " - )} already exists`; - throw new ApiError(401, message); - } - - //If not already exists create a new user with their credentials - const user = await User.create({ - username: username.toLowerCase(), - email, - password, - fullName, - }); - if (!user) { - throw new ApiError( - 500, - "Something went wrong while creating user in the database" - ); - } - - const createdUser = await User.findById(user._id).select( - "-password -refreshToken" - ); - - //Fallback checking if for any reason the user has been deleted - - if (!createdUser) { - throw new ApiError( - 500, - "Something went wrong while fetching user from the database" - ); - } - return res - .status(201) - .json(new ApiResponse(200, createdUser, "User registered successfully")); -}); - -const loginUser = asyncHandler(async (req, res) => { - const { username, email, password } = req.body; - //Any one field is required - if (!username && !email) { - throw new ApiError(400, "Username or email is required"); - } - const user = await User.findOne({ - $or: [{ email }, { username }], - }); - - if (!user) { - throw new ApiError(404, "No user with the current username or email"); - } - - const isPasswordCorrect = await user.isPasswordCorrect(password); - - if (!isPasswordCorrect) { - throw new ApiError(401, "Invalid password"); - } - //Generating user access and refresh tokens - const { accessToken, refreshToken } = await generateAccessAndRefreshToken( - user - ); - - const loggedInUser = await User.findById(user._id).select( - "-password -refreshToken" - ); - const options = { - httpOnly: true, - secure: true, - }; - return res - .status(200) - .cookie("accessToken", accessToken, options) - .cookie("refreshToken", refreshToken, options) - .json(new ApiResponse(200, loggedInUser, "User logged in successfully")); -}); - -const logoutUser = asyncHandler(async (req, res) => { - await User.findByIdAndUpdate( - req.user._id, - { $set: { refreshToken: undefined } } - - // Returns the updated document - // { new: true } - ); - const options = { - httpOnly: true, - secure: true, - }; - - return res - .status(200) - .clearCookie("accessToken", options) - .clearCookie("refreshToken", options) - .json(new ApiResponse(200, {}, "User logged out")); -}); - -export { registerUser, loginUser, logoutUser }; diff --git a/server/db/index.js b/server/db/index.js deleted file mode 100644 index 13548fa..0000000 --- a/server/db/index.js +++ /dev/null @@ -1,16 +0,0 @@ -import mongoose from "mongoose"; - -const connectDB = async () => { - try { - const connectionInstance = await mongoose.connect( - `${process.env.MONGODB_URL}/${process.env.DB_NAME}` - ); - console.log( - `\n MongoDB connected !! DB HOST: ${connectionInstance.connection.host}` - ); - } catch (error) { - console.log("MongoDB connection error", error); - } -}; - -export default connectDB; diff --git a/server/index.js b/server/index.js deleted file mode 100644 index 2069531..0000000 --- a/server/index.js +++ /dev/null @@ -1,20 +0,0 @@ -import dotenv from "dotenv"; -import connectDB from "./db/index.js"; -import app from "./app.js"; - -//To read all the constraints from .env file -dotenv.config(); -connectDB() - .then(() => { - app.on("error", (err) => { - console.log(err); - throw err; - }); - app.listen(process.env.PORT || 8000, () => - console.log(`Server is running at the port ${process.env.PORT}`) - ); - }) - .catch((err) => { - console.log("MongoDB connection failed!!", err); - process.exit(1); - }); diff --git a/server/middlewares/auth.middleware.js b/server/middlewares/auth.middleware.js deleted file mode 100644 index c53bcab..0000000 --- a/server/middlewares/auth.middleware.js +++ /dev/null @@ -1,32 +0,0 @@ -import { User } from "../models/user.models.js"; -import asyncHandler from "../utils/asyncHandler.js"; -import ApiError from "../utils/ApiError.js"; -import jwt from "jsonwebtoken"; - -export const verifyUser = asyncHandler(async (req, res, next) => { - try { - // Fetching tokens either from cookies or header - const token = - req.cookies?.accessToken || - req.header("Authorization")?.replace("Bearer ", ""); - - if (!token) { - throw new ApiError(401, "Unauthorized request"); - } - - // Decoded token contains id, email and username - // This might throw error so wrap it in a try catch - const decodedToken = jwt.verify(token, process.env.ACCESS_TOKEN_SECRET); - - const user = await User.findById(decodedToken?._id).select( - "-password -refreshToken" - ); - if (!user) { - throw new ApiError(401, "Invalid Access Token"); - } - req.user = user; - next(); - } catch (error) { - throw new ApiError(401, error?.message || "Invalid access token"); - } -}); diff --git a/server/models/user.models.js b/server/models/user.models.js deleted file mode 100644 index 6583879..0000000 --- a/server/models/user.models.js +++ /dev/null @@ -1,67 +0,0 @@ -import mongoose, { Schema } from "mongoose"; -import jwt from "jsonwebtoken"; -import bcrypt from "bcrypt"; - -const userSchema = new Schema({ - username: { - type: String, - required: true, - unique: true, - lowercase: true, - trim: true, - index: true, - }, - email: { - type: String, - required: true, - unique: true, - trim: true, - index: true, - }, - fullName: { - type: String, - required: true, - trim: true, - index: true, - }, - refreshToken: { - type: String, - }, - password: { - type: String, - required: [true, "Password is required"], - }, -}); - -userSchema.pre("save", async function (next) { - if (!this.isModified("password")) return next(); - this.password = await bcrypt.hash(this.password, 10); - next(); -}); - -userSchema.methods.isPasswordCorrect = async function (password) { - return await bcrypt.compare(password, this.password); -}; - -userSchema.methods.generateAccessToken = async function () { - return jwt.sign( - { - _id: this._id, - email: this.email, - username: this.username, - }, - process.env.ACCESS_TOKEN_SECRET, - { expiresIn: process.env.ACCESS_TOKEN_EXPIRY } - ); -}; -userSchema.methods.generateRefreshToken = async function () { - return jwt.sign( - { - _id: this._id, - }, - process.env.REFRESH_TOKEN_SECRET, - { expiresIn: process.env.REFRESH_TOKEN_EXPIRY } - ); -}; - -export const User = mongoose.model("User", userSchema); diff --git a/server/routers/user.routers.js b/server/routers/user.routers.js deleted file mode 100644 index 6c58dd9..0000000 --- a/server/routers/user.routers.js +++ /dev/null @@ -1,17 +0,0 @@ -import { Router } from "express"; -import { - loginUser, - logoutUser, - registerUser, -} from "../controllers/user.controllers.js"; -import { verifyUser } from "../middlewares/auth.middleware.js"; - -const router = Router(); - -router.post("/sign-up", registerUser); -router.post("/sign-in", loginUser); - -// use router.use(middleware) if there are more routes which required middleware -router.post("/logout", verifyUser, logoutUser); - -export default router; diff --git a/server/utils/ApiError.js b/server/utils/ApiError.js deleted file mode 100644 index 769f210..0000000 --- a/server/utils/ApiError.js +++ /dev/null @@ -1,29 +0,0 @@ -class ApiError extends Error { - constructor( - statusCode, - message = "Something went wrong", - errors = [], - stack = "" - ) { - super(message); - this.statusCode = statusCode; - this.message = message; - this.data = null; - this.success = false; - this.errors = errors; - - /** - * If a stack trace is manually provided (e.g., when wrapping another error), - * use that stack trace. Otherwise, capture the current stack trace starting - * from where this error was created, and exclude the constructor from it. - */ - - if (stack) { - this.stack = stack; - } else { - Error.captureStackTrace(this, this.constructor); - } - } -} - -export default ApiError; diff --git a/server/utils/ApiResponse.js b/server/utils/ApiResponse.js deleted file mode 100644 index 2bceb15..0000000 --- a/server/utils/ApiResponse.js +++ /dev/null @@ -1,10 +0,0 @@ -class ApiResponse { - constructor(statusCode, data, message = "Success") { - this.statusCode = statusCode; - this.data = data; - this.message = message; - this.success = statusCode < 400; - } -} - -export default ApiResponse; diff --git a/server/utils/asyncHandler.js b/server/utils/asyncHandler.js deleted file mode 100644 index 514b9ee..0000000 --- a/server/utils/asyncHandler.js +++ /dev/null @@ -1,8 +0,0 @@ -// Wrapper function to handle asynchronous route handlers and forward errors to Express -const asycnHandler = (fn) => { - return (req, res, next) => { - Promise.resolve(fn(req, res, next)).catch((err) => next(err)); - }; -}; - -export default asycnHandler; diff --git a/src/components/AuthModal.jsx b/src/components/AuthModal.jsx new file mode 100644 index 0000000..84cb6cb --- /dev/null +++ b/src/components/AuthModal.jsx @@ -0,0 +1,155 @@ +import React, { use, useState } from 'react'; +import { useNavigate } from 'react-router-dom'; + +export default function AuthModal({ onClose }) { + const [isSignUp, setIsSignUp] = useState(false); + const [form, setForm] = useState({ name: '', email: '', password: '' }); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(''); + const navigate=useNavigate(); + + const API_BASE_URL = `${import.meta.env.VITE_API_URL}/api/auth`; + + const handleChange = (e) => { + setForm({ ...form, [e.target.name]: e.target.value }); + }; + + const handleSubmit = async (e) => { + e.preventDefault(); + setLoading(true); + setError(''); + + try { + const endpoint = isSignUp ? '/signup' : '/signin'; + const response = await fetch(`${API_BASE_URL}${endpoint}`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(form), + }); + + const data = await response.json(); + + if (!response.ok) { + throw new Error(data.msg || 'Something went wrong'); + } + + // Save JWT token to localStorage + localStorage.setItem('token', data.token); + localStorage.setItem('user', JSON.stringify(data.user)); + + // Optionally: redirect or show success toast + console.log('Authenticated:', data); + + onClose(); + navigate('/dashboard'); + } catch (err) { + setError(err.message); + } finally { + setLoading(false); + } + }; + + return ( +
+
+ {/* Close Button */} + + + {/* Title */} +

+ {isSignUp ? 'Create an Account' : 'Welcome Back'} +

+ + {/* Toggle Buttons */} +
+ + +
+ + {/* Form */} +
+ {isSignUp && ( +
+ + +
+ )} + +
+ + +
+ +
+ + +
+ + {error &&

{error}

} + + +
+
+
+ ); +} diff --git a/src/components/Dashboard.jsx b/src/components/Dashboard.jsx index 9eb39ac..a6a0a13 100644 --- a/src/components/Dashboard.jsx +++ b/src/components/Dashboard.jsx @@ -1,4 +1,4 @@ -import { Plus, TrendingUp, TrendingDown, Wallet, IndianRupee, Calendar, Tag, Filter, Search, Eye, EyeOff, ChevronDown, ChevronLeft, ChevronRight, Trash2, Download, Moon, Sun, Target } from "lucide-react"; +import { Plus, TrendingUp, TrendingDown, Wallet, IndianRupee, Calendar, Tag, Filter, Search, Eye, EyeOff, ChevronDown, ChevronLeft, ChevronRight, Trash2, Download, Moon, Sun, Target, LogOut } from "lucide-react"; import { useTransactions } from "./TransactionContext"; import { useCurrency } from "./CurrencyContext"; import { useState, useEffect } from "react"; @@ -8,7 +8,7 @@ import Footer from "./Footer"; // Download imports import { PDFDownloadLink } from "@react-pdf/renderer"; import TransactionPDF from "./TransactionPDF"; -import { Link } from "react-router-dom"; +import { Link, useNavigate } from "react-router-dom"; export default function Dashboard() { const { transactions, income, expense, setTransactions, deleteTransaction } = useTransactions(); @@ -18,7 +18,7 @@ export default function Dashboard() { const [isVisible, setIsVisible] = useState(false); const [hoveredCard, setHoveredCard] = useState(null); const [showBalance, setShowBalance] = useState(true); - + const navigate = useNavigate(); const [animatedValues, setAnimatedValues] = useState({ income: 0, expense: 0, balance: 0 }); const { currency, locale, setCurrency, setLocale } = useCurrency(); @@ -57,6 +57,14 @@ export default function Dashboard() { setDarkMode(!darkMode); }; + // Logout function + const handleLogout = () => { + localStorage.removeItem('token'); + localStorage.removeItem('user'); + // Redirect to home page + navigate('/'); + }; + // Apply dark mode class to body useEffect(() => { if (darkMode) { @@ -208,17 +216,33 @@ export default function Dashboard() {

- {/* Dark Mode Toggle Button */} - + {/* Theme Toggle and Logout Buttons */} +
+ {/* Dark Mode Toggle Button */} + + + {/* Logout Button */} + +
diff --git a/src/components/HomePage.jsx b/src/components/HomePage.jsx index 12d2261..7ef9391 100644 --- a/src/components/HomePage.jsx +++ b/src/components/HomePage.jsx @@ -1,73 +1,72 @@ import { useState, useEffect } from 'react'; -import { IndianRupee, TrendingUp, PieChart, BarChart3, Sparkles, ArrowRight, Plus } from 'lucide-react'; -import { useNavigate } from 'react-router-dom'; +import { + IndianRupee, + TrendingUp, + PieChart, + BarChart3, + Sparkles, + ArrowRight, +} from 'lucide-react'; import { motion } from 'framer-motion'; import Footer from './Footer'; +import AuthModal from './AuthModal'; export default function HomePage() { const [isVisible, setIsVisible] = useState(false); const [hoveredFeature, setHoveredFeature] = useState(null); - const navigate = useNavigate(); + const [showAuthModal, setShowAuthModal] = useState(false); useEffect(() => { setIsVisible(true); }, []); const handleGetStarted = () => { - navigate('/dashboard'); + setShowAuthModal(true); }; const features = [ - { - icon: IndianRupee, - title: 'Track Expenses', - desc: 'Monitor every rupee', - color: 'from-blue-400 to-blue-500', - bgColor: 'bg-blue-500' + { + icon: IndianRupee, + title: 'Track Expenses', + desc: 'Monitor every rupee', + bgColor: 'bg-blue-500', }, - { - icon: TrendingUp, - title: 'Smart Analytics', - desc: 'Insights that matter', - color: 'from-purple-400 to-pink-500', - bgColor: 'bg-gradient-to-br from-purple-400 to-pink-500' + { + icon: TrendingUp, + title: 'Smart Analytics', + desc: 'Insights that matter', + bgColor: 'bg-gradient-to-br from-purple-400 to-pink-500', }, - { - icon: BarChart3, - title: 'Budget Goals', - desc: 'Stay on track', - color: 'from-pink-400 to-red-500', - bgColor: 'bg-gradient-to-br from-pink-400 to-red-500' + { + icon: BarChart3, + title: 'Budget Goals', + desc: 'Stay on track', + bgColor: 'bg-gradient-to-br from-pink-400 to-red-500', + }, + { + icon: PieChart, + title: 'Visual Reports', + desc: 'See your spending', + bgColor: 'bg-gradient-to-br from-green-400 to-blue-500', }, - { - icon: PieChart, - title: 'Visual Reports', - desc: 'See your spending', - color: 'from-green-400 to-blue-500', - bgColor: 'bg-gradient-to-br from-green-400 to-blue-500' - } ]; return (
- {/* Main Content Section - SmartLog Branding Left, Device Mockup Right */} + {/* Hero Section */}
- {/* Left Side - SmartLog Branding */}
- {/* SmartLog Logo */}
-
-

SmartLog

-

- Transform your financial future with AI-powered insights, beautiful visualizations, and smart automation. -

-
+

SmartLog

+

+ Transform your financial future with AI-powered insights, + beautiful visualizations, and smart automation. +

-
- - {/* Right Side - Device Mockup Image */}
- SmartLog Mobile and Laptop Mockup
- {/* Bottom Section - Feature Boxes */} + {/* Features Section */}
{features.map((feature, index) => { const Icon = feature.icon; - const isHovered = hoveredFeature === index; return ( setHoveredFeature(index)} - onMouseLeave={() => setHoveredFeature(null)} + className="group p-8 rounded-3xl bg-white shadow-xl hover:shadow-2xl transition-all duration-500 border border-gray-100" > - +
- +

{feature.title}

{feature.desc} @@ -143,7 +121,10 @@ export default function HomePage() {

+ {/* Auth Modal */} + {showAuthModal && setShowAuthModal(false)} />} +
); -} \ No newline at end of file +} diff --git a/src/components/TransactionContext.jsx b/src/components/TransactionContext.jsx index c319bcf..3d73a1d 100644 --- a/src/components/TransactionContext.jsx +++ b/src/components/TransactionContext.jsx @@ -12,58 +12,236 @@ const getTodaysDate = () => { return `${day}/${month}/${year}`; }; +// Convert ISO date string to DD/MM/YYYY format +const formatDateFromISO = (isoString) => { + if (!isoString) return getTodaysDate(); + + const date = new Date(isoString); + const day = String(date.getDate()).padStart(2, "0"); + const month = String(date.getMonth() + 1).padStart(2, "0"); + const year = date.getFullYear(); + return `${day}/${month}/${year}`; +}; + +// Convert DD/MM/YYYY format to ISO string for backend +const formatDateToISO = (dateString) => { + if (!dateString) return new Date().toISOString(); + + const [day, month, year] = dateString.split('/'); + const date = new Date(year, month - 1, day); + return date.toISOString(); +}; + +// API utility functions +const apiCall = async (url, options = {}) => { + const token = localStorage.getItem('token'); + + const config = { + headers: { + 'Content-Type': 'application/json', + ...(token && { 'Authorization': `Bearer ${token}` }), + ...options.headers, + }, + ...options, + }; + const apiUrl = import.meta.env.VITE_API_URL; + const response = await fetch(`${apiUrl}${url}`, config); + if (!response.ok) { + const error = await response.json(); + throw new Error(error.error || `HTTP error! status: ${response.status}`); + } + + return response.json(); +}; + export const TransactionProvider = ({ children }) => { - const [transactions, setTransactions] = useState(() => { - const saved = localStorage.getItem('transactions'); - return saved ? JSON.parse(saved) : []; - }); + const [transactions, setTransactions] = useState([]); + const [goals, setGoals] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + // Fetch transactions from API + const fetchTransactions = async () => { + try { + const data = await apiCall('/api/transactions'); + // Format dates from ISO to DD/MM/YYYY + const formattedTransactions = data.map(transaction => ({ + ...transaction, + date: formatDateFromISO(transaction.date) + })); + setTransactions(formattedTransactions); + setError(null); + } catch (err) { + console.error('Error fetching transactions:', err); + if (err.message.includes('No transactions found')) { + setTransactions([]); // Handle empty state + setError(null); + } else { + setError(err.message); + } + } + }; - const [goals, setGoals] = useState(() => { - const saved = localStorage.getItem('goals'); - return saved ? JSON.parse(saved) : []; - }) + // Fetch goals from API + const fetchGoals = async () => { + try { + const data = await apiCall('/api/goals'); + setGoals(data); + setError(null); + } catch (err) { + console.error('Error fetching goals:', err); + if (err.message.includes('No goals found')) { + setGoals([]); // Handle empty state + setError(null); + } else { + setError(err.message); + } + } + }; + // Load data on component mount useEffect(() => { - localStorage.setItem('transactions', JSON.stringify(transactions)); - }, [transactions]); + const loadData = async () => { + setLoading(true); + try { + await Promise.all([fetchTransactions(), fetchGoals()]); + } catch (err) { + console.error('Error loading data:', err); + } finally { + setLoading(false); + } + }; - useEffect(() => { - localStorage.setItem('goals', JSON.stringify(goals)); - }, [goals]); + loadData(); + }, []); - const addTransaction = (transaction) => { - setTransactions([transaction, ...transactions]); + // Add transaction to API and update local state + const addTransaction = async (transaction) => { + try { + // Convert date to ISO format for backend + const transactionForBackend = { + ...transaction, + date: formatDateToISO(transaction.date) + }; + + const newTransaction = await apiCall('/api/transactions', { + method: 'POST', + body: JSON.stringify(transactionForBackend), + }); + + // Format the returned transaction date for frontend + const formattedTransaction = { + ...newTransaction, + date: formatDateFromISO(newTransaction.date) + }; + + setTransactions(prev => [formattedTransaction, ...prev]); + setError(null); + return formattedTransaction; + } catch (err) { + console.error('Error adding transaction:', err); + setError(err.message); + throw err; + } }; - const addGoal = (goal) => { - setGoals([goal, ...goals]); - } + // Add goal to API and update local state + const addGoal = async (goal) => { + try { + const newGoal = await apiCall('/api/goals', { + method: 'POST', + body: JSON.stringify(goal), + }); + + setGoals(prev => [newGoal, ...prev]); + setError(null); + return newGoal; + } catch (err) { + console.error('Error adding goal:', err); + setError(err.message); + throw err; + } + }; - const deleteTransaction = (transactionId) => { - // 1. Find the transaction to be deleted - const transactionToDelete = transactions.find(t => t.id === transactionId); + // Delete transaction (you'll need to add DELETE endpoint to your backend) + const deleteTransaction = async (transactionId) => { + try { + // Find the transaction to be deleted + const transactionToDelete = transactions.find(t => t._id === transactionId); + + if (!transactionToDelete) return; - if (!transactionToDelete) return; // Exit if transaction not found + // Delete from API first + await apiCall(`/api/transactions/${transactionId}`, { + method: 'DELETE', + }); - // 2. Check if it was a contribution to a goal - if (transactionToDelete.goalId) { - // 3. If yes, "refund" the amount from the goal - const updatedGoals = goals.map(goal => { - if (goal.id === transactionToDelete.goalId) { - return { - ...goal, - currentAmount: goal.currentAmount - transactionToDelete.amount - }; + // If it was a contribution to a goal, update the goal + if (transactionToDelete.goalId) { + const updatedGoals = goals.map(goal => { + if (goal._id === transactionToDelete.goalId) { + return { + ...goal, + currentAmount: goal.currentAmount - transactionToDelete.amount + }; + } + return goal; + }); + setGoals(updatedGoals); + + // You might also want to update the goal in the backend + const goalToUpdate = updatedGoals.find(g => g._id === transactionToDelete.goalId); + if (goalToUpdate) { + await apiCall(`/api/goals/${goalToUpdate._id}`, { + method: 'PUT', + body: JSON.stringify({ currentAmount: goalToUpdate.currentAmount }), + }); } - return goal; - }); - setGoals(updatedGoals); + } + + // Update local state + setTransactions(prev => prev.filter(t => t._id !== transactionId)); + setError(null); + } catch (err) { + console.error('Error deleting transaction:', err); + setError(err.message); + throw err; } + }; + + // Delete goal and related transactions + const deleteGoal = async (goalId) => { + try { + // Find all transactions related to this goal + const relatedTransactions = transactions.filter(t => t.goalId === goalId); + + // Delete the goal from API first + await apiCall(`/api/goals/${goalId}`, { + method: 'DELETE', + }); - // 4. Finally, delete the transaction itself - setTransactions(prev => prev.filter(t => t.id !== transactionId)); + // Delete all related transactions from API + for (const transaction of relatedTransactions) { + await apiCall(`/api/transactions/${transaction._id}`, { + method: 'DELETE', + }); + } + + // Update local state - remove goal + setGoals(prev => prev.filter(g => g._id !== goalId)); + + // Update local state - remove related transactions + setTransactions(prev => prev.filter(t => t.goalId !== goalId)); + + setError(null); + } catch (err) { + console.error('Error deleting goal:', err); + setError(err.message); + throw err; + } }; + // Calculate income and expense from current transactions const income = transactions .filter(t => t.type === 'Income') .reduce((sum, t) => sum + Number(t.amount), 0); @@ -72,20 +250,31 @@ export const TransactionProvider = ({ children }) => { .filter(t => t.type === 'Expense') .reduce((sum, t) => sum + Number(t.amount), 0); - const contributeToGoal = (goalId, amount) => { + // Contribute to goal + const contributeToGoal = async (goalId, amount) => { + try { let goalName = ''; - + + // Update goal locally first const updatedGoals = goals.map(goal => { - if (goal.id === goalId) { - goalName = goal.name; + if (goal._id === goalId) { + goalName = goal.name; return { ...goal, currentAmount: goal.currentAmount + amount }; } return goal; }); + setGoals(updatedGoals); + // Update goal in backend + const goalToUpdate = updatedGoals.find(g => g._id === goalId); + await apiCall(`/api/goals/${goalId}`, { + method: 'PUT', + body: JSON.stringify({ currentAmount: goalToUpdate.currentAmount }), + }); + + // Create contribution transaction const contributionTransaction = { - id: Date.now(), note: `Contribution to "${goalName}"`, amount: amount, type: 'Expense', @@ -93,13 +282,36 @@ export const TransactionProvider = ({ children }) => { date: getTodaysDate(), goalId: goalId }; - addTransaction(contributionTransaction); - }; + + await addTransaction(contributionTransaction); + setError(null); + } catch (err) { + console.error('Error contributing to goal:', err); + setError(err.message); + throw err; + } + }; + + const value = { + transactions, + goals, + loading, + error, + setTransactions, + setGoals, + addTransaction, + addGoal, + deleteTransaction, + deleteGoal, + contributeToGoal, + income, + expense, + fetchTransactions, + fetchGoals, + }; return ( - + {children} ); From 171e2cc37594d5b2eb6d5d2702c25c318bda7fd7 Mon Sep 17 00:00:00 2001 From: harish-pasupuleti <133561454+harish-pasupuleti@users.noreply.github.com> Date: Fri, 1 Aug 2025 01:14:43 +0530 Subject: [PATCH 2/3] Update .env --- Backend/.env | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Backend/.env b/Backend/.env index 2f03c1c..76894b3 100644 --- a/Backend/.env +++ b/Backend/.env @@ -1,3 +1,3 @@ -MONGODB_URL="mongodb+srv://harishpasupuleti18:dltvMaOlYvh7iEwd@cluster0.k21edhb.mongodb.net/?retryWrites=true&w=majority&appName=Cluster0" +MONGODB_URL="mongodb+srv://@cluster0.k21edhb.mongodb.net/?retryWrites=true&w=majority&appName=Cluster0" JWT_SECRET=123 -Frontend_URL=http://localhost:5173 \ No newline at end of file +Frontend_URL=http://localhost:5173 From ae5d4cab2e1c8ad55fbefede7ec5d34f6996f864 Mon Sep 17 00:00:00 2001 From: harish-pasupuleti <133561454+harish-pasupuleti@users.noreply.github.com> Date: Fri, 8 Aug 2025 19:15:32 +0530 Subject: [PATCH 3/3] Delete Backend/.env --- Backend/.env | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 Backend/.env diff --git a/Backend/.env b/Backend/.env deleted file mode 100644 index 76894b3..0000000 --- a/Backend/.env +++ /dev/null @@ -1,3 +0,0 @@ -MONGODB_URL="mongodb+srv://@cluster0.k21edhb.mongodb.net/?retryWrites=true&w=majority&appName=Cluster0" -JWT_SECRET=123 -Frontend_URL=http://localhost:5173