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 */}
+
+
+
+ );
+}
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 */}
-
- {/* 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