diff --git a/week-2/week-2-async-js/solutions/easy/1-counter.js b/week-2/week-2-async-js/solutions/easy/1-counter.js
index da2806780..80cade2d8 100644
--- a/week-2/week-2-async-js/solutions/easy/1-counter.js
+++ b/week-2/week-2-async-js/solutions/easy/1-counter.js
@@ -1,9 +1,13 @@
let counter = 0;
+// const updateCounter = () => {
+// counter++;
+// console.log(counter);
+// };
-const updateCounter = () => {
+function updateCounter() {
counter++;
console.log(counter);
-};
+}
setInterval(updateCounter, 1000);
diff --git a/week-2/week-2-async-js/solutions/easy/2-counter.js b/week-2/week-2-async-js/solutions/easy/2-counter.js
index 95896a264..38e3a69b7 100644
--- a/week-2/week-2-async-js/solutions/easy/2-counter.js
+++ b/week-2/week-2-async-js/solutions/easy/2-counter.js
@@ -1,10 +1,16 @@
let counter = 0;
-const updateCounter = () => {
- counter++;
- console.log(counter);
+// const updateCounter = () => {
+// counter++;
+// console.log(counter);
+
+// setTimeout(updateCounter, 1000);
+// };
+function updateCounter() {
+ counter++;
+ console.log(counter);
setTimeout(updateCounter, 1000);
-};
+}
updateCounter();
diff --git a/week-2/week-2-async-js/solutions/easy/example.txt b/week-2/week-2-async-js/solutions/easy/example.txt
index a542373a3..5dd01c177 100644
--- a/week-2/week-2-async-js/solutions/easy/example.txt
+++ b/week-2/week-2-async-js/solutions/easy/example.txt
@@ -1 +1 @@
-Hello, world!!!
\ No newline at end of file
+Hello, world!
\ No newline at end of file
diff --git a/week-2/week-2-async-js/solutions/hard/1-promisify-setTimeout.js b/week-2/week-2-async-js/solutions/hard/1-promisify-setTimeout.js
index 1362af622..94f15cc7a 100644
--- a/week-2/week-2-async-js/solutions/hard/1-promisify-setTimeout.js
+++ b/week-2/week-2-async-js/solutions/hard/1-promisify-setTimeout.js
@@ -7,4 +7,14 @@ function wait(n) {
return p;
}
-module.exports = wait;
\ No newline at end of file
+module.exports = wait;
+
+function randomAfter3S(resolve, n) {
+ setTimeout(resolve, n*1000);
+}
+
+function callbackAfter3S() {
+ console.log("promise succeeded after 3 seconds!")
+}
+p = new Promise((resolve) => randomAfter3S(resolve, 3));
+p.then(callbackAfter3S);
\ No newline at end of file
diff --git a/week-2/week-2-async-js/solutions/hard/2-sleep-completely.js b/week-2/week-2-async-js/solutions/hard/2-sleep-completely.js
index 42e80a1a4..3cb056fea 100644
--- a/week-2/week-2-async-js/solutions/hard/2-sleep-completely.js
+++ b/week-2/week-2-async-js/solutions/hard/2-sleep-completely.js
@@ -6,4 +6,16 @@ function sleep(milliseconds) {
});
}
-module.exports = sleep;
\ No newline at end of file
+module.exports = sleep;
+
+function randomAfterSleep(resolve, n) {
+ let startTime = new Date().getTime();
+ while (new Date().getTime() < startTime + n*1000);
+ resolve();
+}
+
+function callbackAfterSleep() {
+ console.log("promise succeeded after sleeping for 3 seconds!")
+}
+p = new Promise((resolve) => randomAfterSleep(resolve, 3));
+p.then(callbackAfterSleep);
\ No newline at end of file
diff --git a/week-2/week-2-async-js/solutions/hard/3-promise-all.js b/week-2/week-2-async-js/solutions/hard/3-promise-all.js
index 5ca428d5a..3ce15a9a6 100644
--- a/week-2/week-2-async-js/solutions/hard/3-promise-all.js
+++ b/week-2/week-2-async-js/solutions/hard/3-promise-all.js
@@ -25,5 +25,53 @@ function wait1(t) {
return totalTime;
}
- module.exports = calculateTime;
-
\ No newline at end of file
+module.exports = calculateTime;
+
+
+// function randomAfter1S(resolve, n) {
+// setTimeout(resolve, n*1000);
+// }
+
+function callbackAfter1S() {
+ console.log("promise succeeded after 1 seconds!")
+}
+let p1 = new Promise((resolve) => randomAfterNS(resolve, 1));
+p1.then(callbackAfter1S);
+
+
+
+// function randomAfter2S(resolve, n) {
+// setTimeout(resolve, n*1000);
+// }
+
+function callbackAfter2S() {
+ console.log("promise succeeded after 2 seconds!")
+}
+let p2 = new Promise((resolve) => randomAfterNS(resolve, 2));
+p2.then(callbackAfter2S);
+
+
+
+// function randomAfter3S(resolve, n) {
+// setTimeout(resolve, n*1000);
+// }
+
+function callbackAfter3S() {
+ console.log("promise succeeded after 3 seconds!")
+}
+let p3 = new Promise((resolve) => randomAfterNS(resolve, 3));
+p3.then(callbackAfter3S);
+
+console.log("hello")
+
+function randomAfterNS(resolve, n) {
+ setTimeout(resolve, n*1000);
+}
+
+const startTime = Date.now();
+
+function callback() {
+ console.log("all promises succeeded after " + (Date.now() - startTime)/1000 + " seconds!")
+}
+
+Promise.all([p1, p2, p3]).then(callback);
\ No newline at end of file
diff --git a/week-2/week-2-async-js/solutions/hard/4-promise-chain.js b/week-2/week-2-async-js/solutions/hard/4-promise-chain.js
index f3608d78d..92756d918 100644
--- a/week-2/week-2-async-js/solutions/hard/4-promise-chain.js
+++ b/week-2/week-2-async-js/solutions/hard/4-promise-chain.js
@@ -32,4 +32,93 @@ function call(t1, t2, t3) {
});
}
-module.exports = calculateTime;
\ No newline at end of file
+module.exports = calculateTime;
+
+// function randomAfter1S(resolve, n) {
+// setTimeout(resolve, n*1000);
+// }
+
+function callbackAfter1S() {
+ console.log("promise succeeded after 1 seconds!")
+}
+let p1 = new Promise((resolve) => randomAfterNS(resolve, 1));
+// p1.then(callbackAfter1S);
+
+
+
+// function randomAfter2S(resolve, n) {
+// setTimeout(resolve, n*1000);
+// }
+
+function callbackAfter2S() {
+ console.log("promise succeeded after 2 seconds!")
+}
+// let p2 = new Promise((resolve) => randomAfterNS(resolve, 2));
+// p2.then(callbackAfter2S);
+
+
+
+// function randomAfter3S(resolve, n) {
+// setTimeout(resolve, n*1000);
+// }
+
+function callbackAfter3S() {
+ console.log("promise succeeded after 3 seconds!")
+}
+// let p3 = new Promise((resolve) => randomAfterNS(resolve, 3));
+// p3.then(callbackAfter3S);
+
+console.log("hello")
+
+function randomAfterNS(resolve, n) {
+ setTimeout(resolve, n*1000);
+}
+
+const startTime = Date.now();
+
+function callback() {
+ console.log("all promises succeeded after " + (Date.now() - startTime)/1000 + " seconds!")
+}
+
+p1
+ .then(() => {
+ callbackAfter1S();
+ return new Promise((resolve) => randomAfterNS(resolve, 2));
+ })
+ .then(() => {
+ callbackAfter2S();
+ return new Promise((resolve) => randomAfterNS(resolve, 3));
+ })
+ .then(() => {
+ callbackAfter3S();
+ callback();
+ });
+
+
+p1 = new Promise((resolve) => randomAfterNS(resolve, 1));
+p1.then(() => {
+ callbackAfter1S();
+ p2 = new Promise((resolve) => randomAfterNS(resolve, 2));
+ p2.then(() => {
+ callbackAfter2S();
+ p3 = new Promise((resolve) => randomAfterNS(resolve, 3));
+ p3.then(() => {
+ callbackAfter3S();
+ callback();
+ })
+ })
+ })
+
+ async function solve() {
+ await new Promise((resolve) => randomAfterNS(resolve, 1));
+ callbackAfter1S();
+ await new Promise((resolve) => randomAfterNS(resolve, 2));
+ callbackAfter2S();
+ await new Promise((resolve) => randomAfterNS(resolve, 3));
+ callbackAfter3S();
+ callback();
+ }
+
+ solve()
+
+ console.log("whatsup")
\ No newline at end of file
diff --git "a/week-3/easy/The-Pok\303\251mon/index.html" "b/week-3/easy/The-Pok\303\251mon/index.html"
new file mode 100644
index 000000000..a8a7ab2f4
--- /dev/null
+++ "b/week-3/easy/The-Pok\303\251mon/index.html"
@@ -0,0 +1,16 @@
+
+
+
+
+ index
+
+
+
+ Hello world
+ Start Pokemon
+
+
+
+
+
+
\ No newline at end of file
diff --git "a/week-3/easy/The-Pok\303\251mon/script.js" "b/week-3/easy/The-Pok\303\251mon/script.js"
new file mode 100644
index 000000000..dc19e10c4
--- /dev/null
+++ "b/week-3/easy/The-Pok\303\251mon/script.js"
@@ -0,0 +1,50 @@
+let score = 0;
+
+function startPokemon() {
+ document.querySelector("#input").innerHTML = ""
+ document.querySelector("#pokemon-list").innerHTML = ""
+ document.querySelector("#input").appendChild(submitPokemonId())
+}
+
+function submitPokemonId() {
+ const div = document.createElement("div");
+ const form = document.createElement("form");
+ const label = document.createElement("label");
+ const input = document.createElement("input");
+ const button = document.createElement("button");
+ label.setAttribute("for", "pokemonId")
+ label.innerHTML = "Enter pokemon id"
+ input.setAttribute("id", "pokemonId")
+ input.setAttribute("placeholder", "Enter pokemon id")
+ button.innerHTML = "Submit"
+ button.type = "button";
+ button.addEventListener("click", () => pokemonComponent(input.value));
+ form.appendChild(label)
+ form.appendChild(input)
+ form.appendChild(button)
+ div.appendChild(form)
+ return div
+}
+
+async function pokemonComponent(id) {
+ console.log(id)
+ document.querySelector("#pokemon-list").innerHTML = ""
+ try {
+ const response = await fetch("https://pokeapi.co/api/v2/pokemon-form/" + id + "/");
+ const pokemonData = await response.json(); // <-- JSON stored here
+ const h1 = document.createElement("h1");
+ const imgFront = document.createElement("img");
+ const imgBack = document.createElement("img");
+ h1.innerHTML = pokemonData.pokemon.name;
+ imgFront.setAttribute("src", pokemonData.sprites.front_default)
+ imgBack.setAttribute("src", pokemonData.sprites.back_default)
+ document.querySelector("#pokemon-list").appendChild(h1)
+ document.querySelector("#pokemon-list").appendChild(imgFront)
+ document.querySelector("#pokemon-list").appendChild(imgBack)
+ } catch (err) {
+ console.error("Error fetching Pokémon data:", err);
+ }
+}
+
+// Attach event listener in JavaScript (module-safe)
+document.getElementById("start-btn").addEventListener("click", startPokemon);
\ No newline at end of file
diff --git a/week-3/easy/bg-color-changer/index.html b/week-3/easy/bg-color-changer/index.html
new file mode 100644
index 000000000..48a7cabae
--- /dev/null
+++ b/week-3/easy/bg-color-changer/index.html
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+ index
+
+
+
+
+ Hello world
+
+ Red
+ Blue
+ Green
+ hellooooo
+
+
+
+
\ No newline at end of file
diff --git a/week-3/easy/bg-color-changer/script.js b/week-3/easy/bg-color-changer/script.js
new file mode 100644
index 000000000..642b06c3c
--- /dev/null
+++ b/week-3/easy/bg-color-changer/script.js
@@ -0,0 +1,12 @@
+function changeBackground(colour) {
+ document.querySelector("body").style.backgroundColor = colour
+ render();
+}
+
+// react
+function render() {
+
+}
+
+// THIS makes the default appear immediately
+render();
\ No newline at end of file
diff --git a/week-3/easy/quiz-app/index.html b/week-3/easy/quiz-app/index.html
new file mode 100644
index 000000000..7382f4a96
--- /dev/null
+++ b/week-3/easy/quiz-app/index.html
@@ -0,0 +1,35 @@
+
+
+
+
+ index
+
+
+
+ Hello world
+
+
+ Start Quiz
+
+
+
+
+
+
\ No newline at end of file
diff --git a/week-3/easy/quiz-app/script.js b/week-3/easy/quiz-app/script.js
new file mode 100644
index 000000000..8c4ea93d6
--- /dev/null
+++ b/week-3/easy/quiz-app/script.js
@@ -0,0 +1,99 @@
+import { quizData } from "./data.js";
+
+let score = 0;
+
+function startQuiz() {
+ score = 0;
+ render(0);
+}
+
+function submitAnswer(i) {
+ calculateScore(document.getElementById(quizData[i].correct).checked)
+ i = i + 1;
+ if (i == quizData.length) {
+ document.querySelector("#quiz-list").innerHTML = "Your final score is " + score + " out of " + quizData.length;
+ }
+ else
+ render(i)
+}
+
+function calculateScore(i) {
+ score = score + i;
+ console.log("score")
+ console.log(score)
+}
+
+function quizComponent(i) {
+ const div = document.createElement("div");
+ const h1 = document.createElement("h1");
+ const form = document.createElement("form");
+ const label1 = document.createElement("label");
+ const label2 = document.createElement("label");
+ const label3 = document.createElement("label");
+ const label4 = document.createElement("label");
+ const input1 = document.createElement("input");
+ const input2 = document.createElement("input");
+ const input3 = document.createElement("input");
+ const input4 = document.createElement("input");
+ const button = document.createElement("button");
+ h1.innerHTML = quizData[i].question;
+ button.innerHTML = "Submit"
+ button.type = "button";
+ button.addEventListener("click", () => submitAnswer(i));
+
+ input1.setAttribute("type", "radio")
+ input1.setAttribute("name", "selection")
+ input1.setAttribute("value", "a")
+ input1.setAttribute("id", "a");
+
+ input2.setAttribute("type", "radio")
+ input2.setAttribute("name", "selection")
+ input2.setAttribute("value", "b")
+ input2.setAttribute("id", "b");
+
+ input3.setAttribute("type", "radio")
+ input3.setAttribute("name", "selection")
+ input3.setAttribute("value", "c")
+ input3.setAttribute("id", "c");
+
+ input4.setAttribute("type", "radio")
+ input4.setAttribute("name", "selection")
+ input4.setAttribute("value", "d")
+ input4.setAttribute("id", "d");
+
+ label1.setAttribute("for", "a")
+ label2.setAttribute("for", "b")
+ label3.setAttribute("for", "c")
+ label4.setAttribute("for", "d")
+
+ div.appendChild(h1)
+ label1.innerHTML = quizData[i].a;
+ label2.innerHTML = quizData[i].b;
+ label3.innerHTML = quizData[i].c;
+ label4.innerHTML = quizData[i].d;
+ label1.appendChild(input1)
+ label2.appendChild(input2)
+ label3.appendChild(input3)
+ label4.appendChild(input4)
+ form.appendChild(label1)
+ form.appendChild(document.createElement("br"))
+ form.appendChild(label2)
+ form.appendChild(document.createElement("br"))
+ form.appendChild(label3)
+ form.appendChild(document.createElement("br"))
+ form.appendChild(label4)
+ form.appendChild(document.createElement("br"))
+ form.appendChild(button)
+ div.appendChild(form)
+ return div
+
+}
+
+// react
+function render(i) {
+ document.querySelector("#quiz-list").innerHTML = ""
+ document.querySelector("#quiz-list").appendChild(quizComponent(i))
+}
+
+// Attach event listener in JavaScript (module-safe)
+document.getElementById("start-btn").addEventListener("click", startQuiz);
\ No newline at end of file
diff --git a/week-5/README.md b/week-5/README.md
index b447375dc..4dec5e74d 100644
--- a/week-5/README.md
+++ b/week-5/README.md
@@ -20,14 +20,16 @@ cp .env.example .env
3. run the server.
```
-npm run dev
+C:\Users\tavis\OneDrive\Desktop\cohort\assignments\week-5\solution\backend> npm run dev
```
start building.
#### Frontend Setup
- go inside week-5/frontend and run:
-
+```
+C:\Users\tavis\OneDrive\Desktop\cohort\assignments\week-5\solution\frontend> serve .
+```
### Reference UI:

diff --git a/week-5/package-lock.json b/week-5/package-lock.json
new file mode 100644
index 000000000..493386486
--- /dev/null
+++ b/week-5/package-lock.json
@@ -0,0 +1,6 @@
+{
+ "name": "week-5",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {}
+}
diff --git a/week-5/solution/backend/middleware/user.js b/week-5/solution/backend/middleware/user.js
index 4ed77ad15..404fa1e60 100644
--- a/week-5/solution/backend/middleware/user.js
+++ b/week-5/solution/backend/middleware/user.js
@@ -2,6 +2,12 @@ const jwt = require('jsonwebtoken');
const SECRET = process.env.SECRET || 'secret000';
const authenticateJwt = (req, res, next) => {
+ /*
+ receive req.headers from script.js (submit button of todo)
+ {
+ Authorization: "Bearer abcdefghijklmnopqrstuvwxyz" (from local storage)
+ }
+ */
const authHeader = req.headers.authorization;
if (authHeader) {
const token = authHeader.split(' ')[1];
diff --git a/week-5/solution/backend/package-lock.json b/week-5/solution/backend/package-lock.json
index 8598f9788..20c22c522 100644
--- a/week-5/solution/backend/package-lock.json
+++ b/week-5/solution/backend/package-lock.json
@@ -9,17 +9,22 @@
"version": "1.0.0",
"license": "ISC",
"dependencies": {
+ "bcrypt": "^6.0.0",
"cors": "^2.8.5",
"dotenv": "^16.4.5",
"express": "^4.21.0",
"jsonwebtoken": "^9.0.2",
- "mongoose": "^8.6.3"
+ "mongoose": "^8.20.1",
+ "zod": "^4.1.13"
+ },
+ "devDependencies": {
+ "nodemon": "^3.1.11"
}
},
"node_modules/@mongodb-js/saslprep": {
- "version": "1.1.9",
- "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.1.9.tgz",
- "integrity": "sha512-tVkljjeEaAhCqTzajSdgbQ6gE6f3oneVwa3iXR6csiEwXXOFsiC6Uh9iAjAhXPtqa/XMDHWjjeNH/77m/Yq2dw==",
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.3.2.tgz",
+ "integrity": "sha512-QgA5AySqB27cGTXBFmnpifAi7HxoGUeezwo6p9dI03MuDB6Pp33zgclqVb6oVK3j6I9Vesg0+oojW2XxB59SGg==",
"license": "MIT",
"dependencies": {
"sparse-bitfield": "^3.0.3"
@@ -53,12 +58,60 @@
"node": ">= 0.6"
}
},
+ "node_modules/anymatch": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
+ "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "normalize-path": "^3.0.0",
+ "picomatch": "^2.0.4"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
"node_modules/array-flatten": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
"integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
"license": "MIT"
},
+ "node_modules/balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/bcrypt": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-6.0.0.tgz",
+ "integrity": "sha512-cU8v/EGSrnH+HnxV2z0J7/blxH8gq7Xh2JFT6Aroax7UohdmiJJlxApMxtKfuI7z68NvvVcmR78k2LbT6efhRg==",
+ "hasInstallScript": true,
+ "license": "MIT",
+ "dependencies": {
+ "node-addon-api": "^8.3.0",
+ "node-gyp-build": "^4.8.4"
+ },
+ "engines": {
+ "node": ">= 18"
+ }
+ },
+ "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"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/body-parser": {
"version": "1.20.3",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz",
@@ -82,10 +135,34 @@
"npm": "1.2.8000 || >= 1.4.16"
}
},
+ "node_modules/brace-expansion": {
+ "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"
+ }
+ },
+ "node_modules/braces": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
+ "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fill-range": "^7.1.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/bson": {
- "version": "6.8.0",
- "resolved": "https://registry.npmjs.org/bson/-/bson-6.8.0.tgz",
- "integrity": "sha512-iOJg8pr7wq2tg/zSlCCHMi3hMm5JTOxLTagf3zxhcenHsFp+c6uOs6K7W5UE7A4QIJGtqh/ZovFNMP4mOPJynQ==",
+ "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"
@@ -122,6 +199,38 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/chokidar": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
+ "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "anymatch": "~3.1.2",
+ "braces": "~3.0.2",
+ "glob-parent": "~5.1.2",
+ "is-binary-path": "~2.1.0",
+ "is-glob": "~4.0.1",
+ "normalize-path": "~3.0.0",
+ "readdirp": "~3.6.0"
+ },
+ "engines": {
+ "node": ">= 8.10.0"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/concat-map": {
+ "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": "0.5.4",
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
@@ -315,6 +424,19 @@
"node": ">= 0.10.0"
}
},
+ "node_modules/fill-range": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
+ "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "to-regex-range": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/finalhandler": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz",
@@ -349,6 +471,21 @@
"node": ">= 0.6"
}
},
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
"node_modules/function-bind": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
@@ -375,6 +512,19 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
"node_modules/gopd": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
@@ -386,6 +536,16 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/has-flag": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
+ "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/has-property-descriptors": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
@@ -456,6 +616,13 @@
"node": ">=0.10.0"
}
},
+ "node_modules/ignore-by-default": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz",
+ "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==",
+ "dev": true,
+ "license": "ISC"
+ },
"node_modules/inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
@@ -470,6 +637,52 @@
"node": ">= 0.10"
}
},
+ "node_modules/is-binary-path": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
+ "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "binary-extensions": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-glob": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-extglob": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-number": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.12.0"
+ }
+ },
"node_modules/jsonwebtoken": {
"version": "9.0.2",
"resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz",
@@ -622,26 +835,39 @@
"node": ">= 0.6"
}
},
+ "node_modules/minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
"node_modules/mongodb": {
- "version": "6.8.0",
- "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.8.0.tgz",
- "integrity": "sha512-HGQ9NWDle5WvwMnrvUxsFYPd3JEbqD3RgABHBQRuoCEND0qzhsd0iH5ypHsf1eJ+sXmvmyKpP+FLOKY8Il7jMw==",
+ "version": "6.20.0",
+ "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.20.0.tgz",
+ "integrity": "sha512-Tl6MEIU3K4Rq3TSHd+sZQqRBoGlFsOgNrH5ltAcFBV62Re3Fd+FcaVf8uSEQFOJ51SDowDVttBTONMfoYWrWlQ==",
"license": "Apache-2.0",
"dependencies": {
- "@mongodb-js/saslprep": "^1.1.5",
- "bson": "^6.7.0",
- "mongodb-connection-string-url": "^3.0.0"
+ "@mongodb-js/saslprep": "^1.3.0",
+ "bson": "^6.10.4",
+ "mongodb-connection-string-url": "^3.0.2"
},
"engines": {
"node": ">=16.20.1"
},
"peerDependencies": {
"@aws-sdk/credential-providers": "^3.188.0",
- "@mongodb-js/zstd": "^1.1.0",
+ "@mongodb-js/zstd": "^1.1.0 || ^2.0.0",
"gcp-metadata": "^5.2.0",
"kerberos": "^2.0.1",
"mongodb-client-encryption": ">=6.0.0 <7",
- "snappy": "^7.2.2",
+ "snappy": "^7.3.2",
"socks": "^2.7.1"
},
"peerDependenciesMeta": {
@@ -669,23 +895,24 @@
}
},
"node_modules/mongodb-connection-string-url": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.1.tgz",
- "integrity": "sha512-XqMGwRX0Lgn05TDB4PyG2h2kKO/FfWJyCzYQbIhXUxz7ETt0I/FqHjUeqj37irJ+Dl1ZtU82uYyj14u2XsZKfg==",
+ "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": "^13.0.0"
+ "whatwg-url": "^14.1.0 || ^13.0.0"
}
},
"node_modules/mongoose": {
- "version": "8.6.3",
- "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.6.3.tgz",
- "integrity": "sha512-++yRmm7hjMbqVA/8WeiygTnEfrFbiy+OBjQi49GFJIvCQuSYE56myyQWo4j5hbpcHjhHQU8NukMNGTwAWFWjIw==",
+ "version": "8.20.1",
+ "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.20.1.tgz",
+ "integrity": "sha512-G+n3maddlqkQrP1nXxsI0q20144OSo+pe+HzRRGqaC4yK3FLYKqejqB9cbIi+SX7eoRsnG23LHGYNp8n7mWL2Q==",
+ "license": "MIT",
"dependencies": {
- "bson": "^6.7.0",
+ "bson": "^6.10.4",
"kareem": "2.6.3",
- "mongodb": "6.8.0",
+ "mongodb": "~6.20.0",
"mpath": "0.9.0",
"mquery": "5.0.0",
"ms": "2.1.3",
@@ -763,6 +990,90 @@
"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==",
+ "license": "MIT",
+ "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==",
+ "license": "MIT",
+ "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.11",
+ "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.11.tgz",
+ "integrity": "sha512-is96t8F/1//UHAjNPHpbsNY46ELPpftGUoSVNXwUfMk/qdjSylYrWSu1XavVTBOn526kFiOR733ATgNBCQyH0g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "chokidar": "^3.5.2",
+ "debug": "^4",
+ "ignore-by-default": "^1.0.1",
+ "minimatch": "^3.1.2",
+ "pstree.remy": "^1.1.8",
+ "semver": "^7.5.3",
+ "simple-update-notifier": "^2.0.0",
+ "supports-color": "^5.5.0",
+ "touch": "^3.1.0",
+ "undefsafe": "^2.0.5"
+ },
+ "bin": {
+ "nodemon": "bin/nodemon.js"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/nodemon"
+ }
+ },
+ "node_modules/nodemon/node_modules/debug": {
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/nodemon/node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/normalize-path": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
+ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
@@ -806,6 +1117,19 @@
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz",
"integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w=="
},
+ "node_modules/picomatch": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
+ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
"node_modules/proxy-addr": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
@@ -819,6 +1143,13 @@
"node": ">= 0.10"
}
},
+ "node_modules/pstree.remy": {
+ "version": "1.1.8",
+ "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz",
+ "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/punycode": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
@@ -864,6 +1195,19 @@
"node": ">= 0.8"
}
},
+ "node_modules/readdirp": {
+ "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"
+ },
+ "engines": {
+ "node": ">=8.10.0"
+ }
+ },
"node_modules/safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
@@ -994,6 +1338,19 @@
"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"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/sparse-bitfield": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz",
@@ -1011,6 +1368,32 @@
"node": ">= 0.8"
}
},
+ "node_modules/supports-color": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "has-flag": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/to-regex-range": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-number": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=8.0"
+ }
+ },
"node_modules/toidentifier": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
@@ -1019,16 +1402,26 @@
"node": ">=0.6"
}
},
+ "node_modules/touch": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz",
+ "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "nodetouch": "bin/nodetouch.js"
+ }
+ },
"node_modules/tr46": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/tr46/-/tr46-4.1.1.tgz",
- "integrity": "sha512-2lv/66T7e5yNyhAAC4NaKe5nVavzuGJQVVtRYLyQ2OI8tsJ61PMLlelehb0wi2Hx6+hT/OJUWZcw8MjlSRnxvw==",
+ "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.0"
+ "punycode": "^2.3.1"
},
"engines": {
- "node": ">=14"
+ "node": ">=18"
}
},
"node_modules/type-is": {
@@ -1043,6 +1436,13 @@
"node": ">= 0.6"
}
},
+ "node_modules/undefsafe": {
+ "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",
@@ -1079,16 +1479,25 @@
}
},
"node_modules/whatwg-url": {
- "version": "13.0.0",
- "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-13.0.0.tgz",
- "integrity": "sha512-9WWbymnqj57+XEuqADHrCJ2eSXzn8WXIW/YSGaZtb2WKAInQ6CHfaUUcTyyver0p8BDg5StLQq8h1vtZuwmOig==",
+ "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": "^4.1.1",
+ "tr46": "^5.1.0",
"webidl-conversions": "^7.0.0"
},
"engines": {
- "node": ">=16"
+ "node": ">=18"
+ }
+ },
+ "node_modules/zod": {
+ "version": "4.1.13",
+ "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.13.tgz",
+ "integrity": "sha512-AvvthqfqrAhNH9dnfmrfKzX5upOdjUVJYFqNSlkmGf64gRaTzlPwz99IHYnVs28qYAybvAlBV+H7pn0saFY4Ig==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/colinhacks"
}
}
}
diff --git a/week-5/solution/backend/package.json b/week-5/solution/backend/package.json
index 006b08316..ca7d5459b 100644
--- a/week-5/solution/backend/package.json
+++ b/week-5/solution/backend/package.json
@@ -3,17 +3,23 @@
"version": "1.0.0",
"main": "index.js",
"scripts": {
- "test": "echo \"Error: no test specified\" && exit 1"
+ "start": "node index.js",
+ "dev": "nodemon index.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"description": "",
"dependencies": {
+ "bcrypt": "^6.0.0",
"cors": "^2.8.5",
"dotenv": "^16.4.5",
"express": "^4.21.0",
"jsonwebtoken": "^9.0.2",
- "mongoose": "^8.6.3"
+ "mongoose": "^8.20.1",
+ "zod": "^4.1.13"
+ },
+ "devDependencies": {
+ "nodemon": "^3.1.11"
}
}
diff --git a/week-5/solution/backend/routes/todo.js b/week-5/solution/backend/routes/todo.js
index cd8671c59..a9ed70fa7 100644
--- a/week-5/solution/backend/routes/todo.js
+++ b/week-5/solution/backend/routes/todo.js
@@ -3,11 +3,19 @@ const { Todo } = require("../db");
const { authenticateJwt } = require("../middleware/user");
const router = express.Router();
+// authenticateJwt middleware before every todo route
router.use(authenticateJwt);
router.post("/", async (req, res) => {
+ /*
+ receive req.body from script.js (submit button of todo)
+ {
+ title: "go to gym" (from todo-input input box)
+ }
+ */
const createPayload = req.body;
console.log(req.userId);
+ // req.userId from authenticateJwt middleware
if (!createPayload.title) {
return res.status(400).json({
@@ -36,6 +44,7 @@ router.post("/", async (req, res) => {
});
router.get("/", async (req, res) => {
+ // req.userId from authenticateJwt middleware
try {
const todos = await Todo.find({ userId: req.userId });
diff --git a/week-5/solution/backend/routes/user.js b/week-5/solution/backend/routes/user.js
index d058e32a7..0525cace8 100644
--- a/week-5/solution/backend/routes/user.js
+++ b/week-5/solution/backend/routes/user.js
@@ -1,40 +1,76 @@
const express = require('express');
const jwt = require('jsonwebtoken');
+const bcrypt = require('bcrypt');
+const { z } = require('zod');
const { authenticateJwt, SECRET } = require("../middleware/user");
const { User } = require("../db");
const router = express.Router();
+//const app = express()
+// app.use(express.json())
router.post('/signup', async (req, res) => {
+ const requiredBody = z.object({
+ username: z.email(),
+ password: z.string().min(3).max(30)
+ })
+ const result = requiredBody.safeParse(req.body)
+
+ if (!result.success) {
+ res.json({
+ message: "Incorrect format: " + result.error.issues[0].message
+ })
+ return
+ }
+ /*
+ receive req.body from script.js (submit button of signup)
+ {
+ username: username (from signup-username),
+ password: password (from signup-password)
+ }
+ */
const { username, password } = req.body;
+ // check if username already exists
try {
const user = await User.findOne({ username });
if (user) {
return res.status(403).json({ message: 'User already exists' });
}
- const newUser = new User({ username, password });
+ const hashedPassword = await bcrypt.hash(password, 5);
+ const newUser = new User({ username, password: hashedPassword });
await newUser.save();
+ /*
+ const user = await User.create({
+ username: username,
+ password: password
+ })
+ */
const token = jwt.sign({ userId: newUser._id }, SECRET, { expiresIn: '1h' });
res.json({ message: 'User created successfully', token });
} catch (error) {
- res.status(500).json({ message: 'Error creating user', error });
+ res.status(500).json({ message: 'Error creating user', error: error.message });
}
});
router.post('/signin', async (req, res) => {
const { username, password } = req.body;
try {
- const user = await User.findOne({ username, password });
- if (user) {
- const token = jwt.sign({ userId: user._id }, SECRET, { expiresIn: '1h' });
+ const user = await User.findOne({ username });
+ if (!user) {
+ return res.status(403).json({ message: "Invalid username" });
+ }
- res.json({ message: 'Logged in successfully', token });
- } else {
- res.status(403).json({ message: 'Invalid username or password' });
+ const isPasswordValid = await bcrypt.compare(password, user.password);
+ if (!isPasswordValid) {
+ return res.status(403).json({ message: "Invalid password" });
}
+
+ const token = jwt.sign({ userId: user._id }, SECRET, { expiresIn: "1h" });
+ res.json({ message: "Logged in successfully", token });
+
} catch (error) {
- res.status(500).json({ message: 'Error signing in', error });
+ res.status(500).json({ message: "Error signing in", error: error.message });
}
});
diff --git a/week-5/solution/frontend/index.html b/week-5/solution/frontend/index.html
index a64cb0435..ee3e65775 100644
--- a/week-5/solution/frontend/index.html
+++ b/week-5/solution/frontend/index.html
@@ -20,9 +20,16 @@ Signup
Password:
Signup
+
Already have an account? Sign In
+
@@ -34,9 +41,16 @@ Signin
Password:
Signin
+
Don't have an account? Sign Up
+
@@ -46,8 +60,10 @@ Your Todo List
Add Todo
-
-
+
+ Logout
diff --git a/week-5/solution/frontend/package-lock.json b/week-5/solution/frontend/package-lock.json
new file mode 100644
index 000000000..65e317057
--- /dev/null
+++ b/week-5/solution/frontend/package-lock.json
@@ -0,0 +1,46 @@
+{
+ "name": "frontend",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "dependencies": {
+ "bcrypt": "^6.0.0"
+ }
+ },
+ "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,
+ "license": "MIT",
+ "dependencies": {
+ "node-addon-api": "^8.3.0",
+ "node-gyp-build": "^4.8.4"
+ },
+ "engines": {
+ "node": ">= 18"
+ }
+ },
+ "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==",
+ "license": "MIT",
+ "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==",
+ "license": "MIT",
+ "bin": {
+ "node-gyp-build": "bin.js",
+ "node-gyp-build-optional": "optional.js",
+ "node-gyp-build-test": "build-test.js"
+ }
+ }
+ }
+}
diff --git a/week-5/solution/frontend/package.json b/week-5/solution/frontend/package.json
new file mode 100644
index 000000000..8da26fb6d
--- /dev/null
+++ b/week-5/solution/frontend/package.json
@@ -0,0 +1,5 @@
+{
+ "dependencies": {
+ "bcrypt": "^6.0.0"
+ }
+}
diff --git a/week-5/solution/frontend/script.js b/week-5/solution/frontend/script.js
index 0ad89d693..c19466473 100644
--- a/week-5/solution/frontend/script.js
+++ b/week-5/solution/frontend/script.js
@@ -11,19 +11,34 @@ document.getElementById('signup-form').addEventListener('submit', async (e) => {
const password = document.getElementById('signup-password').value;
try {
- const response = await fetch('http://localhost:3000/user/signup', {
+ const response = await fetch('http://localhost:3003/user/signup', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ username, password }),
+ /*
+ send req.body to http://localhost:3003/user/signup
+ {
+ username: username (from signup-username),
+ password: password (from signup-password)
+ }
+ */
});
const result = await response.json();
+ /*
+ receive response.json() and response.status() from http://localhost:3003/user/signup
+ {
+ "message": "User created successfully/User already exists/Error creating user",
+ "token": "abcdefghijklmnopqrstuvwxyz"
+ }
+ */
isSigningUp = false;
if (response.ok) {
document.getElementById('response-message').innerText = result.message || 'Signup successful, please sign in';
document.getElementById('signup-container').style.display = 'none';
+ // The CSS property display: none; is used to completely hide an HTML element from the document.
document.getElementById('signin-container').style.display = 'block';
} else {
document.getElementById('response-message').innerText = result.message || 'Signup failed';
@@ -43,21 +58,36 @@ document.getElementById('signin-form').addEventListener('submit', async (e) => {
const password = document.getElementById('signin-password').value;
try {
- const response = await fetch('http://localhost:3000/user/signin', {
+ const response = await fetch('http://localhost:3003/user/signin', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ username, password }),
});
+ /*
+ send req.body to http://localhost:3003/user/signup
+ {
+ username: username (from signup-username),
+ password: password (from signup-password)
+ }
+ */
const result = await response.json();
+ /*
+ response.json()
+ {
+ "message": "Logged in successfully/Invalid username or password/Error signing in",
+ "token": "abcdefghijklmnopqrstuvwxyz"
+ }
+ */
if (response.ok) {
localStorage.setItem('token', result.token);
document.getElementById('signin-container').style.display = 'none';
+ // The CSS property display: none; is used to completely hide an HTML element from the document.
document.getElementById('todo-container').style.display = 'block';
document.getElementById('response-message').innerHTML =
- `Logged in successfully. Logout `;
+ `Logged in successfully.`;
loadTodos();
// Add event listener for the logout link
@@ -65,6 +95,7 @@ document.getElementById('signin-form').addEventListener('submit', async (e) => {
e.preventDefault();
localStorage.removeItem('token'); // Clear token
document.getElementById('todo-container').style.display = 'none';
+ // The CSS property display: none; is used to completely hide an HTML element from the document.
document.getElementById('signin-container').style.display = 'block';
document.getElementById('response-message').innerText = '';
});
@@ -92,8 +123,9 @@ document.getElementById('todo-form').addEventListener('submit', async (e) => {
const token = localStorage.getItem('token');
+ // router.post
try {
- const response = await fetch('http://localhost:3000/todo', {
+ const response = await fetch('http://localhost:3003/todo', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
@@ -101,11 +133,31 @@ document.getElementById('todo-form').addEventListener('submit', async (e) => {
},
body: JSON.stringify({ title: todoText }),
});
+ /*
+ send req.headers to http://localhost:3003/user/todo
+ {
+ Authorization: "Bearer abcdefghijklmnopqrstuvwxyz" (from local storage)
+ }
+ send req.body to http://localhost:3003/user/todo
+ {
+ title: "go to gym" (from todo-input input box)
+ }
+ */
const result = await response.json();
+ /*
+ response.json()
+ {
+ msg: "Todo created",
+ todo: newTodo where newTodo contains {
+ title: "go to gym",
+ completed: false,
+ userId: req.userId,
+ }
+ */
isAddingTodo = false;
if (response.ok) {
- todoInput.value = '';
+ todoInput.value = ''; // clear todo input box
loadTodos();
} else {
console.error(result.msg);
@@ -120,12 +172,19 @@ document.getElementById('todo-form').addEventListener('submit', async (e) => {
async function loadTodos() {
const token = localStorage.getItem('token');
try {
- const response = await fetch('http://localhost:3000/todo', {
+ // router.get
+ const response = await fetch('http://localhost:3003/todo', {
headers: {
'Authorization': `Bearer ${token}`,
},
});
const { todos } = await response.json();
+ /*
+ response.json()
+ {
+ todos: todos
+ }
+ */
const todoList = document.getElementById('todo-list');
todoList.innerHTML = '';
@@ -158,7 +217,7 @@ async function loadTodos() {
async function completeTodo(id, completed) {
const token = localStorage.getItem('token');
try {
- await fetch(`http://localhost:3000/todo/${id}`, {
+ await fetch(`http://localhost:3003/todo/${id}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
@@ -166,6 +225,16 @@ async function completeTodo(id, completed) {
},
body: JSON.stringify({ completed }),
});
+ /*
+ send req.headers to http://localhost:3003/user/todo/{todo._id}
+ {
+ Authorization: "Bearer abcdefghijklmnopqrstuvwxyz" (from local storage)
+ }
+ send req.body to http://localhost:3003/user/todo/{todo._id}
+ {
+ completed: !todo.completed
+ }
+ */
loadTodos();
} catch (error) {
console.error('Error completing todo:', error);
@@ -176,11 +245,13 @@ async function completeTodo(id, completed) {
document.getElementById('show-signin').addEventListener('click', (e) => {
e.preventDefault();
document.getElementById('signup-container').style.display = 'none';
+ // The CSS property display: none; is used to completely hide an HTML element from the document.
document.getElementById('signin-container').style.display = 'block';
});
document.getElementById('show-signup').addEventListener('click', (e) => {
e.preventDefault();
document.getElementById('signin-container').style.display = 'none';
+ // The CSS property display: none; is used to completely hide an HTML element from the document.
document.getElementById('signup-container').style.display = 'block';
});
diff --git a/week-5/solution/package-lock.json b/week-5/solution/package-lock.json
new file mode 100644
index 000000000..c7fa1c889
--- /dev/null
+++ b/week-5/solution/package-lock.json
@@ -0,0 +1,6 @@
+{
+ "name": "solution",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {}
+}
diff --git a/week-7/client/src/App.jsx b/week-7/client/src/App.jsx
index 8cc387045..9a24d9666 100644
--- a/week-7/client/src/App.jsx
+++ b/week-7/client/src/App.jsx
@@ -1,13 +1,52 @@
-// firstly, Don't get overwhelmed and if you are then go with client-easy.
-import Home from "./pages/Home"
+import React, { useState, useEffect } from "react";
+import Home from "./pages/Home";
+import AdminHome from "./pages/AdminHome";
+
function App() {
+ const [isAdminView, setIsAdminView] = useState(false);
+
+ // Automatically set view based on login state
+ useEffect(() => {
+ const adminToken = localStorage.getItem("adminToken");
+ const userToken = localStorage.getItem("token");
+
+ if (adminToken) {
+ setIsAdminView(true); // admin logged in → show admin view
+ } else if (userToken) {
+ setIsAdminView(false); // user logged in → show user view
+ }
+ }, []); // runs only once when app loads
+
+ const toggleView = () => {
+ setIsAdminView(prev => !prev);
+ };
return (
- <>
- {/* start writing from here */}
-
- >
- )
+
+
Online Courses Platform
+
+ {/* Toggle button */}
+
+ Switch to {isAdminView ? "User" : "Admin"} View
+
+
+ {/* Render view */}
+
+
+ );
}
-export default App
+export default App;
\ No newline at end of file
diff --git a/week-7/client/src/components/AdminCourses.jsx b/week-7/client/src/components/AdminCourses.jsx
new file mode 100644
index 000000000..3fe8ac127
--- /dev/null
+++ b/week-7/client/src/components/AdminCourses.jsx
@@ -0,0 +1,208 @@
+import React, { useState, useEffect } from "react";
+import axios from "axios";
+
+const AdminCourses = () => {
+ const [courses, setCourses] = useState([]);
+ const [message, setMessage] = useState("");
+ const [form, setForm] = useState({
+ title: "",
+ description: "",
+ imageUrl: "",
+ price: "",
+ });
+ const [editingCourseId, setEditingCourseId] = useState(null);
+
+ const token = localStorage.getItem("adminToken");
+
+ useEffect(() => {
+ fetchCourses();
+ }, []);
+
+ const fetchCourses = async () => {
+ if (!token) return;
+ try {
+ const res = await axios.get("http://localhost:3000/admin/courses", {
+ headers: { Authorization: `Bearer ${token}` },
+ });
+ setCourses(res.data.courses);
+ } catch (err) {
+ console.error(err);
+ }
+ };
+
+ const handleChange = (e) => {
+ setForm({ ...form, [e.target.name]: e.target.value });
+ };
+
+ const createCourse = async () => {
+ if (!token) return setMessage("Please login as admin");
+ try {
+ const res = await axios.post(
+ "http://localhost:3000/admin/courses",
+ form,
+ { headers: { Authorization: `Bearer ${token}` } }
+ );
+ setMessage(res.data.message);
+ setForm({ title: "", description: "", imageUrl: "", price: "" });
+ fetchCourses();
+ } catch (err) {
+ setMessage(err.response?.data?.message || "Error creating course");
+ }
+ };
+
+ const startEdit = (course) => {
+ setEditingCourseId(course._id);
+ setForm({
+ title: course.title,
+ description: course.description,
+ imageUrl: course.imageUrl,
+ price: course.price,
+ });
+ };
+
+ const updateCourse = async () => {
+ if (!token || !editingCourseId) return;
+ try {
+ const res = await axios.put(
+ `http://localhost:3000/admin/courses/${editingCourseId}`,
+ form,
+ { headers: { Authorization: `Bearer ${token}` } }
+ );
+ setMessage(res.data.message);
+ setEditingCourseId(null);
+ setForm({ title: "", description: "", imageUrl: "", price: "" });
+ fetchCourses();
+ } catch (err) {
+ setMessage(err.response?.data?.message || "Error updating course");
+ }
+ };
+
+ return (
+
+ );
+};
+
+const styles = {
+ container: {
+ border: "1px solid #ccc",
+ padding: "20px",
+ borderRadius: "8px",
+ maxWidth: "600px",
+ margin: "0 auto",
+ backgroundColor: "#f9f9f9",
+ },
+ heading: {
+ textAlign: "center",
+ color: "#333",
+ },
+ subHeading: {
+ marginTop: "20px",
+ color: "#555",
+ },
+ message: {
+ color: "green",
+ fontWeight: "bold",
+ },
+ form: {
+ display: "flex",
+ flexDirection: "column",
+ gap: "10px",
+ marginBottom: "20px",
+ },
+ input: {
+ padding: "8px",
+ borderRadius: "5px",
+ border: "1px solid #ccc",
+ fontSize: "14px",
+ },
+ button: {
+ padding: "10px",
+ backgroundColor: "#4caf50",
+ color: "white",
+ border: "none",
+ borderRadius: "5px",
+ cursor: "pointer",
+ fontWeight: "bold",
+ },
+ courseList: {
+ listStyleType: "none",
+ padding: 0,
+ },
+ courseItem: {
+ display: "flex",
+ justifyContent: "space-between",
+ alignItems: "center",
+ padding: "8px 0",
+ borderBottom: "1px solid #ddd",
+ },
+ editButton: {
+ padding: "5px 10px",
+ backgroundColor: "#2196f3",
+ color: "white",
+ border: "none",
+ borderRadius: "4px",
+ cursor: "pointer",
+ },
+};
+
+export default AdminCourses;
\ No newline at end of file
diff --git a/week-7/client/src/components/AdminLogin.jsx b/week-7/client/src/components/AdminLogin.jsx
new file mode 100644
index 000000000..94bde31be
--- /dev/null
+++ b/week-7/client/src/components/AdminLogin.jsx
@@ -0,0 +1,89 @@
+import React, { useState } from "react";
+import axios from "axios";
+
+const AdminLogin = () => {
+ const [formData, setFormData] = useState({
+ email: "",
+ password: ""
+ });
+
+ const [message, setMessage] = useState("");
+
+ function handleChange(e) {
+ setFormData({ ...formData, [e.target.name]: e.target.value });
+ }
+
+ async function handleLogin() {
+ try {
+ const response = await axios.post(
+ "http://localhost:3000/admin/login",
+ formData
+ );
+
+ setMessage(response.data.message);
+
+ if (response.data.token) {
+ localStorage.setItem("adminToken", response.data.token);
+ window.location.reload(); // simple way to refresh Home.jsx state
+ }
+ } catch (error) {
+ setMessage(error.response?.data?.message || "Login failed");
+ }
+ }
+
+ return (
+
+
Login
+
+
+
+
+
+
+ Login
+
+
+ {message &&
{message}
}
+
+ );
+};
+
+const styles = {
+ container: {
+ display: "flex",
+ flexDirection: "column",
+ gap: "10px",
+ width: "100%",
+ marginTop: "10px",
+ },
+ input: {
+ padding: "10px",
+ border: "1px solid #ccc",
+ borderRadius: "5px",
+ },
+ button: {
+ padding: "10px",
+ background: "#2196F3",
+ color: "white",
+ border: "none",
+ borderRadius: "5px",
+ cursor: "pointer",
+ fontWeight: "bold",
+ }
+};
+
+export default AdminLogin;
\ No newline at end of file
diff --git a/week-7/client/src/components/AdminRegister.jsx b/week-7/client/src/components/AdminRegister.jsx
new file mode 100644
index 000000000..a7a7cee2d
--- /dev/null
+++ b/week-7/client/src/components/AdminRegister.jsx
@@ -0,0 +1,106 @@
+import React, { useState } from "react";
+import axios from "axios";
+
+const AdminRegister = () => {
+ const [formData, setFormData] = useState({
+ email: "",
+ password: "",
+ firstName: "",
+ lastName: "",
+ });
+
+ const [message, setMessage] = useState("");
+
+ function handleChange(e) {
+ setFormData({ ...formData, [e.target.name]: e.target.value });
+ }
+
+ async function handleRegister() {
+ try {
+ const response = await axios.post("http://localhost:3000/admin/signup", formData);
+
+ setMessage(response.data.message);
+
+ if (response.data.token) {
+ localStorage.setItem("adminToken", response.data.token);
+ window.location.reload(); // simple way to refresh Home.jsx state
+ }
+ } catch (error) {
+ setMessage(error.response?.data?.message || "Error occurred");
+ }
+ }
+
+ return (
+
+ );
+};
+
+const styles = {
+ container: {
+ display: "flex",
+ flexDirection: "column",
+ gap: "10px",
+ width: "100%",
+ marginTop: "10px",
+ },
+ input: {
+ padding: "10px",
+ border: "1px solid #ccc",
+ borderRadius: "5px",
+ },
+ button: {
+ padding: "10px",
+ background: "#4CAF50",
+ color: "white",
+ border: "none",
+ borderRadius: "5px",
+ cursor: "pointer",
+ fontWeight: "bold",
+ },
+};
+
+export default AdminRegister;
\ No newline at end of file
diff --git a/week-7/client/src/components/Courses.jsx b/week-7/client/src/components/Courses.jsx
index cfbbaee98..a15d5a1f6 100644
--- a/week-7/client/src/components/Courses.jsx
+++ b/week-7/client/src/components/Courses.jsx
@@ -1,11 +1,118 @@
-// courses code here
-import React from 'react'
+import React, { useEffect, useState } from "react";
+import axios from "axios";
-// use axios here, similar to register and login
const Courses = () => {
+ const [courses, setCourses] = useState([]);
+ const [purchased, setPurchased] = useState([]);
+ const [message, setMessage] = useState("");
+
+ // get token from localStorage
+ const token = localStorage.getItem("token");
+
+ // fetch all courses
+ const fetchCourses = async () => {
+ try {
+ const res = await axios.get("http://localhost:3000/users/courses");
+ setCourses(res.data.courses);
+ } catch (err) {
+ console.error("Error fetching courses:", err);
+ }
+ };
+
+ // fetch purchased courses
+ const fetchPurchasedCourses = async () => {
+ if (!token) return;
+ try {
+ const res = await axios.get("http://localhost:3000/users/purchasedCourses", {
+ headers: { Authorization: `Bearer ${token}` },
+ });
+ setPurchased(res.data.coursesData.map(c => c._id));
+ } catch (err) {
+ console.error("Error fetching purchased courses:", err);
+ }
+ };
+
+ useEffect(() => {
+ fetchCourses();
+ fetchPurchasedCourses();
+ }, []);
+
+ // purchase a course
+ const purchaseCourse = async (courseId) => {
+ if (!token) {
+ setMessage("Please login to purchase courses");
+ return;
+ }
+
+ try {
+ const res = await axios.post(
+ `http://localhost:3000/users/courses/${courseId}`,
+ {},
+ { headers: { Authorization: `Bearer ${token}` } }
+ );
+ setMessage(res.data.message);
+ setPurchased([...purchased, courseId]);
+ } catch (err) {
+ setMessage(err.response?.data?.message || "Purchase failed");
+ }
+ };
+
return (
- Courses
- )
-}
+
+
Available Courses
+ {message &&
{message}
}
+
+ {courses.map(course => (
+
+
{course.title}
+
{course.description}
+
Price: ${course.price}
+ {purchased.includes(course._id) ? (
+
+ Purchased
+
+ ) : (
+
purchaseCourse(course._id)}
+ style={styles.purchaseButton}
+ >
+ Purchase
+
+ )}
+
+ ))}
+
+
+ );
+};
+
+const styles = {
+ courseContainer: {
+ display: "flex",
+ gap: "20px",
+ flexWrap: "wrap",
+ },
+ courseCard: {
+ border: "1px solid #ccc",
+ borderRadius: "10px",
+ padding: "15px",
+ width: "250px",
+ },
+ purchaseButton: {
+ background: "#4CAF50",
+ color: "white",
+ border: "none",
+ padding: "8px 12px",
+ borderRadius: "5px",
+ cursor: "pointer",
+ },
+ purchasedButton: {
+ background: "#aaa",
+ color: "white",
+ border: "none",
+ padding: "8px 12px",
+ borderRadius: "5px",
+ },
+};
-export default Courses
\ No newline at end of file
+export default Courses;
\ No newline at end of file
diff --git a/week-7/client/src/components/Login.jsx b/week-7/client/src/components/Login.jsx
index 0948cf6e0..08804aaad 100644
--- a/week-7/client/src/components/Login.jsx
+++ b/week-7/client/src/components/Login.jsx
@@ -1,15 +1,89 @@
-// login code here
-import axios from 'axios';
-import React from 'react'
+import React, { useState } from "react";
+import axios from "axios";
const Login = () => {
- // call the functions onClick of button.
- async function handleLogin() {
- const resposne = await axios.post(); // // if you don't know about axios, give it a read https://axios-http.com/docs/intro
+ const [formData, setFormData] = useState({
+ email: "",
+ password: ""
+ });
+
+ const [message, setMessage] = useState("");
+
+ function handleChange(e) {
+ setFormData({ ...formData, [e.target.name]: e.target.value });
+ }
+
+ async function handleLogin() {
+ try {
+ const response = await axios.post(
+ "http://localhost:3000/users/login",
+ formData
+ );
+
+ setMessage(response.data.message);
+
+ if (response.data.token) {
+ localStorage.setItem("token", response.data.token);
+ window.location.reload(); // simple way to refresh Home.jsx state
+ }
+ } catch (error) {
+ setMessage(error.response?.data?.message || "Login failed");
}
- return (
- Login
- )
-}
+ }
+
+ return (
+
+
Login
+
+
+
+
+
+
+ Login
+
+
+ {message &&
{message}
}
+
+ );
+};
+
+const styles = {
+ container: {
+ display: "flex",
+ flexDirection: "column",
+ gap: "10px",
+ width: "100%",
+ marginTop: "10px",
+ },
+ input: {
+ padding: "10px",
+ border: "1px solid #ccc",
+ borderRadius: "5px",
+ },
+ button: {
+ padding: "10px",
+ background: "#2196F3",
+ color: "white",
+ border: "none",
+ borderRadius: "5px",
+ cursor: "pointer",
+ fontWeight: "bold",
+ }
+};
-export default Login
\ No newline at end of file
+export default Login;
\ No newline at end of file
diff --git a/week-7/client/src/components/Register.jsx b/week-7/client/src/components/Register.jsx
index c143c3f6e..f5528ac97 100644
--- a/week-7/client/src/components/Register.jsx
+++ b/week-7/client/src/components/Register.jsx
@@ -1,16 +1,106 @@
-// register code here
-import React from 'react'
-import axios from "axios"
-
+import React, { useState } from "react";
+import axios from "axios";
const Register = () => {
- // call the functions onClick of button.
- async function handleRegister() {
- const resposne = await axios.post(); // if you don't know about axios, give it a read https://axios-http.com/docs/intro
+ const [formData, setFormData] = useState({
+ email: "",
+ password: "",
+ firstName: "",
+ lastName: "",
+ });
+
+ const [message, setMessage] = useState("");
+
+ function handleChange(e) {
+ setFormData({ ...formData, [e.target.name]: e.target.value });
+ }
+
+ async function handleRegister() {
+ try {
+ const response = await axios.post("http://localhost:3000/users/signup", formData);
+
+ setMessage(response.data.message);
+
+ if (response.data.token) {
+ localStorage.setItem("token", response.data.token);
+ window.location.reload(); // simple way to refresh Home.jsx state
}
- return (
- Register
- )
-}
+ } catch (error) {
+ setMessage(error.response?.data?.message || "Error occurred");
+ }
+ }
+
+ return (
+
+ );
+};
+
+const styles = {
+ container: {
+ display: "flex",
+ flexDirection: "column",
+ gap: "10px",
+ width: "100%",
+ marginTop: "10px",
+ },
+ input: {
+ padding: "10px",
+ border: "1px solid #ccc",
+ borderRadius: "5px",
+ },
+ button: {
+ padding: "10px",
+ background: "#4CAF50",
+ color: "white",
+ border: "none",
+ borderRadius: "5px",
+ cursor: "pointer",
+ fontWeight: "bold",
+ },
+};
-export default Register
\ No newline at end of file
+export default Register;
\ No newline at end of file
diff --git a/week-7/client/src/pages/AdminHome.jsx b/week-7/client/src/pages/AdminHome.jsx
new file mode 100644
index 000000000..d10ce15d1
--- /dev/null
+++ b/week-7/client/src/pages/AdminHome.jsx
@@ -0,0 +1,71 @@
+import React, { useState, useEffect } from "react";
+import AdminLogin from "../components/AdminLogin";
+import AdminRegister from "../components/AdminRegister";
+import AdminCourses from "../components/AdminCourses";
+
+const AdminHome = () => {
+ const [isLoggedIn, setIsLoggedIn] = useState(false);
+
+ useEffect(() => {
+ const token = localStorage.getItem("adminToken");
+ setIsLoggedIn(!!token);
+ }, []);
+ /*
+ Runs once when the component loads.
+ Looks for adminToken in localStorage.
+ If it exists → isLoggedIn = true
+ If not → isLoggedIn = false
+ */
+
+ const handleLogout = () => {
+ localStorage.removeItem("adminToken");
+ setIsLoggedIn(false);
+ };
+ /*
+ Removes the admin token from local storage.
+ Updates isLoggedIn to false, which re-renders the component and shows login/register forms again.
+ */
+
+ /*
+ Admin logged in (isLoggedIn === true):
+ Logout button
+ AdminCourses component (to manage or view courses)
+
+ Admin not logged in (isLoggedIn === false):
+ AdminRegister and AdminLogin forms side by side
+ */
+ return (
+
+
Admin Dashboard
+
+ {isLoggedIn ? (
+
+ ) : (
+
+ )}
+
+ );
+};
+
+const styles = {
+ authContainer: { display: "flex", gap: "20px" },
+ logoutButton: {
+ padding: "8px 12px",
+ background: "#f44336",
+ color: "white",
+ border: "none",
+ borderRadius: "5px",
+ cursor: "pointer",
+ marginBottom: "15px",
+ },
+};
+
+export default AdminHome;
\ No newline at end of file
diff --git a/week-7/client/src/pages/Home.jsx b/week-7/client/src/pages/Home.jsx
index 94691be96..8e7624364 100644
--- a/week-7/client/src/pages/Home.jsx
+++ b/week-7/client/src/pages/Home.jsx
@@ -1,16 +1,79 @@
-// implement the home page UI here.
-import React from 'react'
-
-// compoents imports
-import Login from '../components/Login'
-import Register from '../components/Register'
-import Courses from '../components/Courses'
+import React, { useState, useEffect } from "react";
+import Login from "../components/Login";
+import Register from "../components/Register";
+import Courses from "../components/Courses";
const Home = () => {
+ const [isLoggedIn, setIsLoggedIn] = useState(false);
+ // isLoggedIn state keeps track of whether the user is logged in.
+ // Initially, it is false (user is not logged in).
+
+ useEffect(() => {
+ const token = localStorage.getItem("token");
+ setIsLoggedIn(!!token);
+ }, []);
+ /*
+ useEffect runs once when the component mounts ([] dependency array).
+ Checks localStorage for a token:
+ If a token exists → user is considered logged in → isLoggedIn becomes true
+ If no token → isLoggedIn remains false
+ */
+
+ const handleLogout = () => {
+ localStorage.removeItem("token");
+ setIsLoggedIn(false);
+ };
+ /*
+ Removes the token from local storage.
+ Sets isLoggedIn to false, which will re-render the component and show the login/register forms again.
+ */
+
+ /*
+ Logged in (isLoggedIn === true):
+ Shows a Logout button
+ Shows the component (user can view courses)
+
+ Not logged in (isLoggedIn === false):
+ Shows Register and Login forms side by side (styled with flex)
+ */
return (
- // write home page UI code here
- Home
- )
-}
+
+
User Dashboard
+
+ {isLoggedIn ? (
+
+
+ Logout
+
+
+
+ ) : (
+ <>
+
+
+
+
+
+ {/* Public Courses view */}
+ {/* fetchCourses() inside Courses will load courses from /users/courses */}
+
+ >
+ )}
+
+ );
+};
+
+const styles = {
+ authContainer: { display: "flex", gap: "20px" },
+ logoutButton: {
+ padding: "8px 12px",
+ background: "#f44336",
+ color: "white",
+ border: "none",
+ borderRadius: "5px",
+ cursor: "pointer",
+ marginBottom: "15px",
+ },
+};
-export default Home
\ No newline at end of file
+export default Home;
\ No newline at end of file
diff --git a/week-7/server/.env.example b/week-7/server/.env.example
index 6c797aa93..780972744 100644
--- a/week-7/server/.env.example
+++ b/week-7/server/.env.example
@@ -1,2 +1,6 @@
-JWT_SECRET=""
-PORT=
\ No newline at end of file
+JWT_USER_SECRET=""
+JWT_ADMIN_SECRET=""
+PORT=
+MONGO_URI=mongodb://localhost:27017/coursera-app
+// netstat -ano | findstr :3000
+// taskkill /PID 12345 /F
\ No newline at end of file
diff --git a/week-7/server/db/db.js b/week-7/server/db/db.js
new file mode 100644
index 000000000..428d628a1
--- /dev/null
+++ b/week-7/server/db/db.js
@@ -0,0 +1,51 @@
+// Define mongoose schemas
+
+const mongoose = require("mongoose");
+const ObjectId = mongoose.Types.ObjectId;
+
+const userSchema = new mongoose.Schema({
+ // userSchema here
+ email: { type: String, unique: true },
+ password: String,
+ firstName: String,
+ lastName: String,
+});
+
+const adminSchema = new mongoose.Schema({
+ // adminSchema here
+ email: { type: String, unique: true },
+ password: String,
+ firstName: String,
+ lastName: String,
+});
+
+const courseSchema = new mongoose.Schema({
+ // courseSchema here
+ title: String,
+ description: String,
+ price: Number,
+ imageUrl: String,
+ creatorId: ObjectId,
+});
+
+const purchaseSchema = new mongoose.Schema({
+ userId: ObjectId,
+ courseId: ObjectId,
+});
+
+// Define mongoose models
+const User = mongoose.model("User", userSchema);
+const Admin = mongoose.model("Admin", adminSchema);
+const Course = mongoose.model("Course", courseSchema);
+const Purchase = mongoose.model("Purchase", purchaseSchema);
+
+const authMiddleware = (req, res, next) => {
+ // authMiddleware logic here
+};
+
+module.exports = {
+ User,
+ Admin,
+ Course,
+ Purchase,
+};
diff --git a/week-7/server/middleware/admin.js b/week-7/server/middleware/admin.js
new file mode 100644
index 000000000..e050af04b
--- /dev/null
+++ b/week-7/server/middleware/admin.js
@@ -0,0 +1,29 @@
+const jwt = require("jsonwebtoken");
+const SECRET = process.env.JWT_ADMIN_SECRET || "secret000";
+
+const authenticateAdminJwt = (req, res, next) => {
+ /*
+ receive req.headers from script.js (submit button of todo)
+ {
+ Authorization: "Bearer abcdefghijklmnopqrstuvwxyz" (from local storage)
+ }
+ */
+ const authHeader = req.headers.authorization;
+ if (authHeader) {
+ const token = authHeader.split(" ")[1];
+ jwt.verify(token, SECRET, (err, user) => {
+ if (err) {
+ return res.status(403).json({ message: "Forbidden: Invalid token" });
+ }
+ req.userId = user.userId;
+ next();
+ });
+ } else {
+ res.status(401).json({ message: "Unauthorized: No token provided" });
+ }
+};
+
+module.exports = {
+ authenticateAdminJwt,
+ SECRET,
+};
diff --git a/week-7/server/middleware/user.js b/week-7/server/middleware/user.js
new file mode 100644
index 000000000..2fc546aa5
--- /dev/null
+++ b/week-7/server/middleware/user.js
@@ -0,0 +1,29 @@
+const jwt = require("jsonwebtoken");
+const SECRET = process.env.JWT_USER_SECRET || "secret000";
+
+const authenticateUserJwt = (req, res, next) => {
+ /*
+ receive req.headers from script.js (submit button of todo)
+ {
+ Authorization: "Bearer abcdefghijklmnopqrstuvwxyz" (from local storage)
+ }
+ */
+ const authHeader = req.headers.authorization;
+ if (authHeader) {
+ const token = authHeader.split(" ")[1];
+ jwt.verify(token, SECRET, (err, user) => {
+ if (err) {
+ return res.status(403).json({ message: "Forbidden: Invalid token" });
+ }
+ req.userId = user.userId;
+ next();
+ });
+ } else {
+ res.status(401).json({ message: "Unauthorized: No token provided" });
+ }
+};
+
+module.exports = {
+ authenticateUserJwt,
+ SECRET,
+};
diff --git a/week-7/server/package-lock.json b/week-7/server/package-lock.json
index 5f1a5b874..6ff61a60f 100644
--- a/week-7/server/package-lock.json
+++ b/week-7/server/package-lock.json
@@ -9,17 +9,19 @@
"version": "1.0.0",
"license": "ISC",
"dependencies": {
+ "bcrypt": "^6.0.0",
"cors": "^2.8.5",
"dotenv": "^16.4.5",
- "express": "^4.21.0",
- "jsonwebtoken": "^9.0.2",
- "mongoose": "^8.6.2"
+ "express": "^4.22.1",
+ "jsonwebtoken": "^9.0.3",
+ "mongoose": "^8.6.2",
+ "zod": "^4.1.13"
}
},
"node_modules/@mongodb-js/saslprep": {
- "version": "1.1.9",
- "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.1.9.tgz",
- "integrity": "sha512-tVkljjeEaAhCqTzajSdgbQ6gE6f3oneVwa3iXR6csiEwXXOFsiC6Uh9iAjAhXPtqa/XMDHWjjeNH/77m/Yq2dw==",
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.3.2.tgz",
+ "integrity": "sha512-QgA5AySqB27cGTXBFmnpifAi7HxoGUeezwo6p9dI03MuDB6Pp33zgclqVb6oVK3j6I9Vesg0+oojW2XxB59SGg==",
"license": "MIT",
"dependencies": {
"sparse-bitfield": "^3.0.3"
@@ -59,6 +61,20 @@
"integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
"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,
+ "license": "MIT",
+ "dependencies": {
+ "node-addon-api": "^8.3.0",
+ "node-gyp-build": "^4.8.4"
+ },
+ "engines": {
+ "node": ">= 18"
+ }
+ },
"node_modules/body-parser": {
"version": "1.20.3",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz",
@@ -84,9 +100,9 @@
}
},
"node_modules/bson": {
- "version": "6.8.0",
- "resolved": "https://registry.npmjs.org/bson/-/bson-6.8.0.tgz",
- "integrity": "sha512-iOJg8pr7wq2tg/zSlCCHMi3hMm5JTOxLTagf3zxhcenHsFp+c6uOs6K7W5UE7A4QIJGtqh/ZovFNMP4mOPJynQ==",
+ "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"
@@ -107,17 +123,27 @@
"node": ">= 0.8"
}
},
- "node_modules/call-bind": {
- "version": "1.0.7",
- "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz",
- "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==",
+ "node_modules/call-bind-apply-helpers": {
+ "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-define-property": "^1.0.0",
"es-errors": "^1.3.0",
- "function-bind": "^1.1.2",
- "get-intrinsic": "^1.2.4",
- "set-function-length": "^1.2.1"
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/call-bound": {
+ "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"
},
"engines": {
"node": ">= 0.4"
@@ -148,9 +174,9 @@
}
},
"node_modules/cookie": {
- "version": "0.6.0",
- "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz",
- "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==",
+ "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"
@@ -184,23 +210,6 @@
"ms": "2.0.0"
}
},
- "node_modules/define-data-property": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
- "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==",
- "license": "MIT",
- "dependencies": {
- "es-define-property": "^1.0.0",
- "es-errors": "^1.3.0",
- "gopd": "^1.0.1"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
"node_modules/depd": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
@@ -232,6 +241,20 @@
"url": "https://dotenvx.com"
}
},
+ "node_modules/dunder-proto": {
+ "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",
+ "gopd": "^1.2.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
"node_modules/ecdsa-sig-formatter": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
@@ -257,13 +280,10 @@
}
},
"node_modules/es-define-property": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz",
- "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==",
+ "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",
- "dependencies": {
- "get-intrinsic": "^1.2.4"
- },
"engines": {
"node": ">= 0.4"
}
@@ -277,6 +297,18 @@
"node": ">= 0.4"
}
},
+ "node_modules/es-object-atoms": {
+ "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"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
"node_modules/escape-html": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
@@ -293,45 +325,64 @@
}
},
"node_modules/express": {
- "version": "4.21.0",
- "resolved": "https://registry.npmjs.org/express/-/express-4.21.0.tgz",
- "integrity": "sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng==",
+ "version": "4.22.1",
+ "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz",
+ "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==",
"license": "MIT",
"dependencies": {
"accepts": "~1.3.8",
"array-flatten": "1.1.1",
- "body-parser": "1.20.3",
- "content-disposition": "0.5.4",
+ "body-parser": "~1.20.3",
+ "content-disposition": "~0.5.4",
"content-type": "~1.0.4",
- "cookie": "0.6.0",
- "cookie-signature": "1.0.6",
+ "cookie": "~0.7.1",
+ "cookie-signature": "~1.0.6",
"debug": "2.6.9",
"depd": "2.0.0",
"encodeurl": "~2.0.0",
"escape-html": "~1.0.3",
"etag": "~1.8.1",
- "finalhandler": "1.3.1",
- "fresh": "0.5.2",
- "http-errors": "2.0.0",
+ "finalhandler": "~1.3.1",
+ "fresh": "~0.5.2",
+ "http-errors": "~2.0.0",
"merge-descriptors": "1.0.3",
"methods": "~1.1.2",
- "on-finished": "2.4.1",
+ "on-finished": "~2.4.1",
"parseurl": "~1.3.3",
- "path-to-regexp": "0.1.10",
+ "path-to-regexp": "~0.1.12",
"proxy-addr": "~2.0.7",
- "qs": "6.13.0",
+ "qs": "~6.14.0",
"range-parser": "~1.2.1",
"safe-buffer": "5.2.1",
- "send": "0.19.0",
- "serve-static": "1.16.2",
+ "send": "~0.19.0",
+ "serve-static": "~1.16.2",
"setprototypeof": "1.2.0",
- "statuses": "2.0.1",
+ "statuses": "~2.0.1",
"type-is": "~1.6.18",
"utils-merge": "1.0.1",
"vary": "~1.1.2"
},
"engines": {
"node": ">= 0.10.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/express/node_modules/qs": {
+ "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"
+ },
+ "engines": {
+ "node": ">=0.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/finalhandler": {
@@ -380,16 +431,21 @@
}
},
"node_modules/get-intrinsic": {
- "version": "1.2.4",
- "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz",
- "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==",
+ "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",
"es-errors": "^1.3.0",
+ "es-object-atoms": "^1.1.1",
"function-bind": "^1.1.2",
- "has-proto": "^1.0.1",
- "has-symbols": "^1.0.3",
- "hasown": "^2.0.0"
+ "get-proto": "^1.0.1",
+ "gopd": "^1.2.0",
+ "has-symbols": "^1.1.0",
+ "hasown": "^2.0.2",
+ "math-intrinsics": "^1.1.0"
},
"engines": {
"node": ">= 0.4"
@@ -398,34 +454,23 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/gopd": {
+ "node_modules/get-proto": {
"version": "1.0.1",
- "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
- "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==",
- "license": "MIT",
- "dependencies": {
- "get-intrinsic": "^1.1.3"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/has-property-descriptors": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
- "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
+ "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
+ "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
"license": "MIT",
"dependencies": {
- "es-define-property": "^1.0.0"
+ "dunder-proto": "^1.0.1",
+ "es-object-atoms": "^1.0.0"
},
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
+ "engines": {
+ "node": ">= 0.4"
}
},
- "node_modules/has-proto": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz",
- "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==",
+ "node_modules/gopd": {
+ "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"
@@ -435,9 +480,9 @@
}
},
"node_modules/has-symbols": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
- "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
+ "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"
@@ -502,12 +547,12 @@
}
},
"node_modules/jsonwebtoken": {
- "version": "9.0.2",
- "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz",
- "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==",
+ "version": "9.0.3",
+ "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz",
+ "integrity": "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==",
"license": "MIT",
"dependencies": {
- "jws": "^3.2.2",
+ "jws": "^4.0.1",
"lodash.includes": "^4.3.0",
"lodash.isboolean": "^3.0.3",
"lodash.isinteger": "^4.0.4",
@@ -530,23 +575,23 @@
"license": "MIT"
},
"node_modules/jwa": {
- "version": "1.4.1",
- "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz",
- "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==",
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz",
+ "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==",
"license": "MIT",
"dependencies": {
- "buffer-equal-constant-time": "1.0.1",
+ "buffer-equal-constant-time": "^1.0.1",
"ecdsa-sig-formatter": "1.0.11",
"safe-buffer": "^5.0.1"
}
},
"node_modules/jws": {
- "version": "3.2.2",
- "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz",
- "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==",
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz",
+ "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==",
"license": "MIT",
"dependencies": {
- "jwa": "^1.4.1",
+ "jwa": "^2.0.1",
"safe-buffer": "^5.0.1"
}
},
@@ -601,6 +646,15 @@
"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"
+ }
+ },
"node_modules/media-typer": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
@@ -668,25 +722,25 @@
}
},
"node_modules/mongodb": {
- "version": "6.8.0",
- "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.8.0.tgz",
- "integrity": "sha512-HGQ9NWDle5WvwMnrvUxsFYPd3JEbqD3RgABHBQRuoCEND0qzhsd0iH5ypHsf1eJ+sXmvmyKpP+FLOKY8Il7jMw==",
+ "version": "6.20.0",
+ "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.20.0.tgz",
+ "integrity": "sha512-Tl6MEIU3K4Rq3TSHd+sZQqRBoGlFsOgNrH5ltAcFBV62Re3Fd+FcaVf8uSEQFOJ51SDowDVttBTONMfoYWrWlQ==",
"license": "Apache-2.0",
"dependencies": {
- "@mongodb-js/saslprep": "^1.1.5",
- "bson": "^6.7.0",
- "mongodb-connection-string-url": "^3.0.0"
+ "@mongodb-js/saslprep": "^1.3.0",
+ "bson": "^6.10.4",
+ "mongodb-connection-string-url": "^3.0.2"
},
"engines": {
"node": ">=16.20.1"
},
"peerDependencies": {
"@aws-sdk/credential-providers": "^3.188.0",
- "@mongodb-js/zstd": "^1.1.0",
+ "@mongodb-js/zstd": "^1.1.0 || ^2.0.0",
"gcp-metadata": "^5.2.0",
"kerberos": "^2.0.1",
"mongodb-client-encryption": ">=6.0.0 <7",
- "snappy": "^7.2.2",
+ "snappy": "^7.3.2",
"socks": "^2.7.1"
},
"peerDependenciesMeta": {
@@ -714,24 +768,24 @@
}
},
"node_modules/mongodb-connection-string-url": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.1.tgz",
- "integrity": "sha512-XqMGwRX0Lgn05TDB4PyG2h2kKO/FfWJyCzYQbIhXUxz7ETt0I/FqHjUeqj37irJ+Dl1ZtU82uYyj14u2XsZKfg==",
+ "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": "^13.0.0"
+ "whatwg-url": "^14.1.0 || ^13.0.0"
}
},
"node_modules/mongoose": {
- "version": "8.6.2",
- "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.6.2.tgz",
- "integrity": "sha512-ErbDVvuUzUfyQpXvJ6sXznmZDICD8r6wIsa0VKjJtB6/LZncqwUn5Um040G1BaNo6L3Jz+xItLSwT0wZmSmUaQ==",
+ "version": "8.20.2",
+ "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.20.2.tgz",
+ "integrity": "sha512-U0TPupnqBOAI3p9H9qdShX8/nJUBylliRcHFKuhbewEkM7Y0qc9BbrQR9h4q6+1easoZqej7cq2Ee36AZ0gMzQ==",
"license": "MIT",
"dependencies": {
- "bson": "^6.7.0",
+ "bson": "^6.10.4",
"kareem": "2.6.3",
- "mongodb": "6.8.0",
+ "mongodb": "~6.20.0",
"mpath": "0.9.0",
"mquery": "5.0.0",
"ms": "2.1.3",
@@ -810,6 +864,26 @@
"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==",
+ "license": "MIT",
+ "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==",
+ "license": "MIT",
+ "bin": {
+ "node-gyp-build": "bin.js",
+ "node-gyp-build-optional": "optional.js",
+ "node-gyp-build-test": "build-test.js"
+ }
+ },
"node_modules/object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
@@ -820,9 +894,9 @@
}
},
"node_modules/object-inspect": {
- "version": "1.13.2",
- "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz",
- "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==",
+ "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"
@@ -853,9 +927,9 @@
}
},
"node_modules/path-to-regexp": {
- "version": "0.1.10",
- "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz",
- "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==",
+ "version": "0.1.12",
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz",
+ "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==",
"license": "MIT"
},
"node_modules/proxy-addr": {
@@ -1011,39 +1085,76 @@
"node": ">= 0.8.0"
}
},
- "node_modules/set-function-length": {
- "version": "1.2.2",
- "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
- "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==",
+ "node_modules/setprototypeof": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
+ "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": {
- "define-data-property": "^1.1.4",
"es-errors": "^1.3.0",
- "function-bind": "^1.1.2",
- "get-intrinsic": "^1.2.4",
- "gopd": "^1.0.1",
- "has-property-descriptors": "^1.0.2"
+ "object-inspect": "^1.13.3",
+ "side-channel-list": "^1.0.0",
+ "side-channel-map": "^1.0.1",
+ "side-channel-weakmap": "^1.0.2"
},
"engines": {
"node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/setprototypeof": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
- "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
- "license": "ISC"
+ "node_modules/side-channel-list": {
+ "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"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
},
- "node_modules/side-channel": {
- "version": "1.0.6",
- "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz",
- "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==",
+ "node_modules/side-channel-map": {
+ "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",
+ "get-intrinsic": "^1.2.5",
+ "object-inspect": "^1.13.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-weakmap": {
+ "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-bind": "^1.0.7",
+ "call-bound": "^1.0.2",
"es-errors": "^1.3.0",
- "get-intrinsic": "^1.2.4",
- "object-inspect": "^1.13.1"
+ "get-intrinsic": "^1.2.5",
+ "object-inspect": "^1.13.3",
+ "side-channel-map": "^1.0.1"
},
"engines": {
"node": ">= 0.4"
@@ -1086,15 +1197,15 @@
}
},
"node_modules/tr46": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/tr46/-/tr46-4.1.1.tgz",
- "integrity": "sha512-2lv/66T7e5yNyhAAC4NaKe5nVavzuGJQVVtRYLyQ2OI8tsJ61PMLlelehb0wi2Hx6+hT/OJUWZcw8MjlSRnxvw==",
+ "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.0"
+ "punycode": "^2.3.1"
},
"engines": {
- "node": ">=14"
+ "node": ">=18"
}
},
"node_modules/type-is": {
@@ -1147,16 +1258,25 @@
}
},
"node_modules/whatwg-url": {
- "version": "13.0.0",
- "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-13.0.0.tgz",
- "integrity": "sha512-9WWbymnqj57+XEuqADHrCJ2eSXzn8WXIW/YSGaZtb2WKAInQ6CHfaUUcTyyver0p8BDg5StLQq8h1vtZuwmOig==",
+ "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": "^4.1.1",
+ "tr46": "^5.1.0",
"webidl-conversions": "^7.0.0"
},
"engines": {
- "node": ">=16"
+ "node": ">=18"
+ }
+ },
+ "node_modules/zod": {
+ "version": "4.1.13",
+ "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.13.tgz",
+ "integrity": "sha512-AvvthqfqrAhNH9dnfmrfKzX5upOdjUVJYFqNSlkmGf64gRaTzlPwz99IHYnVs28qYAybvAlBV+H7pn0saFY4Ig==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/colinhacks"
}
}
}
diff --git a/week-7/server/package.json b/week-7/server/package.json
index ed08190c4..8d1b3b780 100644
--- a/week-7/server/package.json
+++ b/week-7/server/package.json
@@ -3,17 +3,20 @@
"version": "1.0.0",
"main": "index.js",
"scripts": {
- "test": "echo \"Error: no test specified\" && exit 1"
+ "start": "node server.js",
+ "dev": "nodemon server.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"description": "",
"dependencies": {
+ "bcrypt": "^6.0.0",
"cors": "^2.8.5",
"dotenv": "^16.4.5",
- "express": "^4.21.0",
- "jsonwebtoken": "^9.0.2",
- "mongoose": "^8.6.2"
+ "express": "^4.22.1",
+ "jsonwebtoken": "^9.0.3",
+ "mongoose": "^8.6.2",
+ "zod": "^4.1.13"
}
}
diff --git a/week-7/server/routes/admin.js b/week-7/server/routes/admin.js
new file mode 100644
index 000000000..5ffcf0cd4
--- /dev/null
+++ b/week-7/server/routes/admin.js
@@ -0,0 +1,185 @@
+// Admin routes
+
+const { Router } = require("express");
+const adminRouter = Router();
+const { Admin, Course } = require("../db/db");
+const { z } = require("zod");
+const bcrypt = require("bcrypt");
+const jwt = require("jsonwebtoken");
+const dotenv = require("dotenv");
+dotenv.config();
+const { SECRET, authenticateAdminJwt } = require("../middleware/admin");
+
+adminRouter.post("/signup", async (req, res) => {
+ // logic to sign up admin
+ const requiredBody = z.object({
+ email: z.email(),
+ password: z.string().min(3).max(30),
+ firstName: z.string(),
+ lastName: z.string(),
+ });
+ const result = requiredBody.safeParse(req.body);
+
+ if (!result.success) {
+ res.json({
+ message: "Incorrect format: " + result.error.issues[0].message,
+ });
+ return;
+ }
+ /*
+ receive req.body from script.js (submit button of signup)
+ {
+ email: email (from signup-email),
+ password: password (from signup-password),
+ firstName: firstName,
+ lastName: lastName
+ }
+ */
+ const { email, password, firstName, lastName } = req.body;
+ // check if email already exists
+ try {
+ const user = await Admin.findOne({ email });
+ if (user) {
+ return res.status(403).json({ message: "Admin already exists" });
+ }
+
+ const hashedPassword = await bcrypt.hash(password, 5);
+ const newAdmin = new Admin({
+ email,
+ password: hashedPassword,
+ firstName,
+ lastName,
+ });
+ await newAdmin.save();
+ /*
+ const user = await Admin.create({
+ email: email,
+ password: password,
+ firstName: firstName,
+ lastName, lastName
+ })
+ */
+
+ const token = jwt.sign({ userId: newAdmin._id }, SECRET, {
+ expiresIn: "1h",
+ });
+ res.json({ message: "Admin created successfully", token });
+ } catch (error) {
+ res
+ .status(500)
+ .json({ message: "Error creating user", error: error.message });
+ }
+});
+
+adminRouter.post("/login", async (req, res) => {
+ // logic to log in admin
+ const { email, password } = req.body;
+ try {
+ const user = await Admin.findOne({ email });
+ if (!user) {
+ return res.status(403).json({ message: "Invalid email" });
+ }
+
+ const isPasswordValid = await bcrypt.compare(password, user.password);
+ if (!isPasswordValid) {
+ return res.status(403).json({ message: "Invalid password" });
+ }
+
+ const token = jwt.sign({ userId: user._id }, SECRET, { expiresIn: "1h" });
+ res.json({ message: "Logged in successfully", token });
+ } catch (error) {
+ res.status(500).json({ message: "Error signing in", error: error.message });
+ }
+});
+
+adminRouter.post("/courses", authenticateAdminJwt, async (req, res) => {
+ // logic to create a course
+ const adminId = req.userId;
+ const { title, description, imageUrl, price } = req.body;
+
+ try {
+ const course = await Course.findOne({ title });
+ if (course) {
+ return res.status(403).json({ message: "Course already exists" });
+ }
+
+ const newCourse = await Course.create({
+ title: title,
+ description: description,
+ imageUrl: imageUrl,
+ price: price,
+ creatorId: adminId,
+ });
+
+ res.json({
+ message: "Course created successfully",
+ courseId: newCourse._id,
+ });
+ } catch (error) {
+ res
+ .status(500)
+ .json({ message: "Error creating course", error: error.message });
+ }
+});
+
+adminRouter.put(
+ "/courses/:courseId",
+ authenticateAdminJwt,
+ async (req, res) => {
+ // logic to edit a course
+ const adminId = req.userId;
+ const courseId = req.params.courseId;
+ const { title, description, imageUrl, price } = req.body;
+
+ const course = await Course.findOne({
+ _id: courseId,
+ });
+
+ if (!course) {
+ return res.status(404).json({
+ message: "Course not found",
+ });
+ }
+
+ const updatedCourse = await Course.findOneAndUpdate(
+ {
+ _id: courseId,
+ creatorId: adminId,
+ },
+ {
+ title: title,
+ description: description,
+ imageUrl: imageUrl,
+ price: price,
+ },
+ );
+
+ if (!updatedCourse) {
+ return res.status(404).json({
+ message: "Course not found or not owned by admin",
+ });
+ }
+
+ res.json({
+ message: "Course updated",
+ courseId: updatedCourse._id,
+ });
+ },
+);
+
+adminRouter.get("/courses", authenticateAdminJwt, async (req, res) => {
+ // logic to get all courses
+ const adminId = req.userId;
+
+ const courses = await Course.find({
+ creatorId: adminId,
+ });
+
+ res.json({
+ courses: courses,
+ });
+});
+
+module.exports = {
+ adminRouter: adminRouter,
+};
diff --git a/week-7/server/routes/user.js b/week-7/server/routes/user.js
new file mode 100644
index 000000000..fb75e91cb
--- /dev/null
+++ b/week-7/server/routes/user.js
@@ -0,0 +1,145 @@
+// User routes
+
+const { Router } = require("express");
+const userRouter = Router();
+const { User, Purchase, Course } = require("../db/db");
+const { z } = require('zod');
+const bcrypt = require('bcrypt');
+const jwt = require('jsonwebtoken');
+const dotenv = require("dotenv");
+dotenv.config();
+const { SECRET, authenticateUserJwt } = require("../middleware/user")
+
+userRouter.post('/signup', async (req, res) => {
+ // logic to sign up user
+ const requiredBody = z.object({
+ email: z.email(),
+ password: z.string().min(3).max(30),
+ firstName: z.string(),
+ lastName: z.string()
+ })
+ const result = requiredBody.safeParse(req.body)
+
+ if (!result.success) {
+ res.json({
+ message: "Incorrect format: " + result.error.issues[0].message
+ })
+ return
+ }
+ /*
+ receive req.body from script.js (submit button of signup)
+ {
+ email: email (from signup-email),
+ password: password (from signup-password),
+ firstName: firstName,
+ lastName: lastName
+ }
+ */
+ const { email, password, firstName, lastName } = req.body;
+ // check if email already exists
+ try {
+ const user = await User.findOne({ email });
+ if (user) {
+ return res.status(403).json({ message: 'User already exists' });
+ }
+
+ const hashedPassword = await bcrypt.hash(password, 5);
+ const newUser = new User({ email, password: hashedPassword, firstName, lastName });
+ await newUser.save();
+ /*
+ const user = await User.create({
+ email: email,
+ password: password,
+ firstName: firstName,
+ lastName, lastName
+ })
+ */
+
+ const token = jwt.sign({ userId: newUser._id }, SECRET, { expiresIn: '1h' });
+ res.json({ message: 'User created successfully', token });
+ } catch (error) {
+ res.status(500).json({ message: 'Error creating user', error: error.message });
+ }
+});
+
+userRouter.post('/login', async (req, res) => {
+ // logic to log in user
+ const { email, password } = req.body;
+ try {
+ const user = await User.findOne({ email });
+ if (!user) {
+ return res.status(403).json({ message: "Invalid email" });
+ }
+
+ const isPasswordValid = await bcrypt.compare(password, user.password);
+ if (!isPasswordValid) {
+ return res.status(403).json({ message: "Invalid password" });
+ }
+
+ const token = jwt.sign({ userId: user._id }, SECRET, { expiresIn: "1h" });
+ // Inspect -> Application -> Local Storage
+ res.json({ message: "Logged in successfully", token });
+
+ } catch (error) {
+ res.status(500).json({ message: "Error signing in", error: error.message });
+ }
+});
+
+userRouter.get('/courses', async (req, res) => {
+ // logic to list all courses
+ const courses = await Course.find({});
+
+ res.json({
+ courses: courses
+ })
+});
+
+userRouter.post('/courses/:courseId', authenticateUserJwt, async (req, res) => {
+ // logic to purchase a course
+ const userId = req.userId
+ const courseId = req.params.courseId
+
+ const course = await Course.findOne({
+ _id: courseId
+ });
+
+ if (!course) {
+ return res.status(404).json({
+ message: "Course not found"
+ });
+ }
+
+ try {
+ const newPurchase = await Purchase.create({
+ userId: userId,
+ courseId: courseId
+ })
+
+ res.json({ message: 'Course purchased successfully', courseId: newPurchase._id });
+ } catch (error) {
+ res.status(500).json({ message: 'Error creating course', error: error.message });
+ }
+
+});
+
+userRouter.get('/purchasedCourses', authenticateUserJwt, async (req, res) => {
+ // logic to view purchased courses
+ const userId = req.userId
+
+ const purchasedCourses = await Purchase.find({
+ userId: userId,
+ });
+
+ const coursesData = await Course.find({
+ _id: { $in: purchasedCourses.map(x => x.courseId)}
+ })
+
+ res.json({
+ courses: purchasedCourses,
+ coursesData: coursesData
+ });
+});
+
+module.exports = {
+ userRouter: userRouter
+}
\ No newline at end of file
diff --git a/week-7/server/server.js b/week-7/server/server.js
index de3d9f638..1bc052317 100644
--- a/week-7/server/server.js
+++ b/week-7/server/server.js
@@ -3,82 +3,25 @@ const express = require('express');
const jwt = require('jsonwebtoken');
const mongoose = require('mongoose');
const dotenv = require("dotenv");
+const cors = require("cors");
+const { userRouter } = require('./routes/user');
+const { adminRouter } = require('./routes/admin');
+const { connectToDatabase } = require("./db/db");
dotenv.config();
+// Dotenv is a zero-dependency module that loads environment variables from a .env file into process.env
const app = express();
+app.use(cors());
app.use(express.json());
-const secret = process.env.JWT_SECRERT; // This should be in an environment variable in a real application
-const port = process.env.PORT;
+const secret = process.env.JWT_SECRET; // This should be in an environment variable in a real application
+const PORT = process.env.PORT || 3000;
-// Define mongoose schemas
-const userSchema = new mongoose.Schema({
- // userSchema here
-});
-
-const adminSchema = new mongoose.Schema({
-// adminSchema here
-});
-
-const courseSchema = new mongoose.Schema({
-// courseSchema here
-});
-
-// Define mongoose models
-const User = mongoose.model('User', userSchema);
-const Admin = mongoose.model('Admin', adminSchema);
-const Course = mongoose.model('Course', courseSchema);
-
-const authMiddleware = (req, res, next) => {
-// authMiddleware logic here
-};
-
-// Connect to MongoDB
-mongoose.connect('');
-
-
-// Admin routes
-app.post('/admin/signup', (req, res) => {
- // logic to sign up admin
-});
-
-app.post('/admin/login', (req, res) => {
- // logic to log in admin
-});
-
-app.post('/admin/courses', (req, res) => {
- // logic to create a course
-});
-
-app.put('/admin/courses/:courseId', (req, res) => {
- // logic to edit a course
-});
-
-app.get('/admin/courses', (req, res) => {
- // logic to get all courses
-});
-
-// User routes
-app.post('/users/signup', (req, res) => {
- // logic to sign up user
-});
+app.use("/users", userRouter);
+app.use("/admin", adminRouter);
-app.post('/users/login', (req, res) => {
- // logic to log in user
+app.listen(PORT, () => {
+ // Connect to MongoDB
+ mongoose.connect(process.env.MONGO_URI);
+ console.log(`Server running on port ${PORT}`);
});
-
-app.get('/users/courses', (req, res) => {
- // logic to list all courses
-});
-
-app.post('/users/courses/:courseId', (req, res) => {
- // logic to purchase a course
-});
-
-app.get('/users/purchasedCourses', (req, res) => {
- // logic to view purchased courses
-});
-
-app.listen(port, () => {
- console.log('Server is listening on port 3000');
-});
\ No newline at end of file