From 17c2d40711712f6168c160f042eda05c223a8d18 Mon Sep 17 00:00:00 2001 From: Pratham Makwana Date: Mon, 27 Oct 2025 18:08:14 +0530 Subject: [PATCH] week-5 complete --- week-5/backend/db/index.js | 33 ++- week-5/backend/index.js | 31 ++- week-5/backend/middleware/user.js | 32 ++- week-5/backend/package-lock.json | 372 +++++++++++++++++++++++++++++- week-5/backend/package.json | 8 +- week-5/backend/routes/todo.js | 79 ++++++- week-5/backend/routes/user.js | 66 +++++- week-5/frontend/index.html | 51 +++- week-5/frontend/script.js | 187 ++++++++++++++- week-5/frontend/style.css | 120 +++++++++- 10 files changed, 966 insertions(+), 13 deletions(-) diff --git a/week-5/backend/db/index.js b/week-5/backend/db/index.js index 675aa4cb5..cbe58da6b 100644 --- a/week-5/backend/db/index.js +++ b/week-5/backend/db/index.js @@ -1 +1,32 @@ -// start writing from here \ No newline at end of file +// start writing from here +import mongoose from 'mongoose'; +import dotenv from 'dotenv'; + +dotenv.config(); + +const connectDB = async () => { + try { + await mongoose.connect(process.env.MONGO_URI); + console.log('MongoDB connected successfully'); + } catch (error) { + console.error('MongoDB connection failed:', error.message); + process.exit(1); + } +}; + +export default connectDB; + +const userSchema = new mongoose.Schema({ + username: { type: String, required: true , unique: true }, + password: { type: String, required: true }, +}, { timestamps: true }); + +const todoSchema = new mongoose.Schema({ + userId: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true }, + title: { type: String, required: true }, + completed: { type: Boolean, default: false }, +}, { timestamps: true }); + +export const User = mongoose.model('User', userSchema); +export const Todo = mongoose.model('Todo', todoSchema); + diff --git a/week-5/backend/index.js b/week-5/backend/index.js index 2fd17dfad..cae40652d 100644 --- a/week-5/backend/index.js +++ b/week-5/backend/index.js @@ -1 +1,30 @@ -// start writing from here \ No newline at end of file +// start writing from here +import express from 'express'; +import dotenv from 'dotenv'; +import cors from 'cors'; +import connectDB from './db/index.js'; +import userRoutes from './routes/user.js'; +import todoRoutes from './routes/todo.js'; + +dotenv.config(); +const app = express(); +const PORT = process.env.PORT || 3000; + +app.use(express.json()); +app.use(cors({ + origin: 'http://localhost:64128', // your frontend origin + methods: ['GET', 'POST', 'PUT', 'DELETE'], + credentials: true +})); + +connectDB(); + +app.get('/', (req, res) => { + res.send('Welcome to the Todo API'); +}); + +app.use('/api/user', userRoutes); +app.use('/api/todo', todoRoutes); + + +app.listen(PORT, () => console.log('Server running on port 3000')); diff --git a/week-5/backend/middleware/user.js b/week-5/backend/middleware/user.js index 675aa4cb5..7a81569f5 100644 --- a/week-5/backend/middleware/user.js +++ b/week-5/backend/middleware/user.js @@ -1 +1,31 @@ -// start writing from here \ No newline at end of file +// start writing from here +import jwt from 'jsonwebtoken'; +import dotenv from 'dotenv'; + +dotenv.config(); +const SECRET = process.env.JWT_SECRET || 'default_secret'; + +/** + * Middleware to verify JWT token and authenticate user + */ +export const authenticateJwt = (req, res, next) => { + const authHeader = req.headers.authorization; + + // ✅ 1. Check if Authorization header exists + if (!authHeader || !authHeader.startsWith('Bearer ')) { + return res.status(401).json({ message: 'Authorization header missing or malformed' }); + } + + const token = authHeader.split(' ')[1]; + + // ✅ 2. Verify the token + try { + const decoded = jwt.verify(token, SECRET); + req.userId = decoded.userId; // attach the decoded userId to the request + next(); + } catch (error) { + return res.status(403).json({ message: 'Invalid or expired token' }); + } +}; + +export { SECRET }; diff --git a/week-5/backend/package-lock.json b/week-5/backend/package-lock.json index 8598f9788..1c71d3d29 100644 --- a/week-5/backend/package-lock.json +++ b/week-5/backend/package-lock.json @@ -9,11 +9,13 @@ "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.6.3", + "nodemon": "^3.1.10" } }, "node_modules/@mongodb-js/saslprep": { @@ -53,12 +55,57 @@ "node": ">= 0.6" } }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmmirror.com/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "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.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT" + }, + "node_modules/bcrypt": { + "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/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.npmmirror.com/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "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,6 +129,28 @@ "npm": "1.2.8000 || >= 1.4.16" } }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmmirror.com/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "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", @@ -122,6 +191,36 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmmirror.com/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "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.npmmirror.com/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "license": "MIT" + }, "node_modules/content-disposition": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", @@ -159,8 +258,9 @@ }, "node_modules/cors": { "version": "2.8.5", - "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "resolved": "https://registry.npmmirror.com/cors/-/cors-2.8.5.tgz", "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "license": "MIT", "dependencies": { "object-assign": "^4", "vary": "^1" @@ -315,6 +415,18 @@ "node": ">= 0.10.0" } }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmmirror.com/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "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 +461,20 @@ "node": ">= 0.6" } }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "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 +501,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmmirror.com/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "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 +524,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "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 +603,12 @@ "node": ">=0.10.0" } }, + "node_modules/ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", + "license": "ISC" + }, "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", @@ -470,6 +623,48 @@ "node": ">= 0.10" } }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmmirror.com/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmmirror.com/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "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,6 +817,18 @@ "node": ">= 0.6" } }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "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", @@ -763,6 +970,86 @@ "node": ">= 0.6" } }, + "node_modules/node-addon-api": { + "version": "8.5.0", + "resolved": "https://registry.npmmirror.com/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.npmmirror.com/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.10", + "resolved": "https://registry.npmmirror.com/nodemon/-/nodemon-3.1.10.tgz", + "integrity": "sha512-WDjw3pJ0/0jMFmyNDp3gvY2YizjLmmOUQo6DEBY+JgdvW/yQ9mEeSw6H5ythl5Ny2ytb7f9C2nIbjSxMNzbJXw==", + "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.npmmirror.com/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "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.npmmirror.com/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "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 +1093,18 @@ "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.npmmirror.com/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "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 +1118,12 @@ "node": ">= 0.10" } }, + "node_modules/pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmmirror.com/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", + "license": "MIT" + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -864,6 +1169,18 @@ "node": ">= 0.8" } }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmmirror.com/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "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 +1311,18 @@ "integrity": "sha512-Rtlj66/b0ICeFzYTuNvX/EF1igRbbnGSvEyT79McoZa/DeGhMyC5pWKOEsZKnpkqtSeovd5FL/bjHWC3CIIvCQ==", "license": "MIT" }, + "node_modules/simple-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + "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 +1340,30 @@ "node": ">= 0.8" } }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "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,6 +1372,15 @@ "node": ">=0.6" } }, + "node_modules/touch": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/touch/-/touch-3.1.1.tgz", + "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", + "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", @@ -1043,6 +1405,12 @@ "node": ">= 0.6" } }, + "node_modules/undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmmirror.com/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", + "license": "MIT" + }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", diff --git a/week-5/backend/package.json b/week-5/backend/package.json index 006b08316..82010351f 100644 --- a/week-5/backend/package.json +++ b/week-5/backend/package.json @@ -2,18 +2,22 @@ "name": "backend", "version": "1.0.0", "main": "index.js", + "type": "module", "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.6.3", + "nodemon": "^3.1.10" } } diff --git a/week-5/backend/routes/todo.js b/week-5/backend/routes/todo.js index 4be9f96ed..ee47f3145 100644 --- a/week-5/backend/routes/todo.js +++ b/week-5/backend/routes/todo.js @@ -1 +1,78 @@ -// start writing your code from here \ No newline at end of file +// start writing your code from here +import express from 'express'; +import { Todo } from '../db/index.js'; +import { authenticateJwt } from '../middleware/user.js'; + +const router = express.Router(); +router.use(authenticateJwt); + +// -------------------- CREATE TODO -------------------- +router.post('/', async (req, res) => { + authenticateJwt + const { title } = req.body; + + if (!title || typeof title !== 'string' || !title.trim()) { + return res.status(400).json({ msg: 'Invalid title.' }); + } + + try { + const newTodo = await Todo.create({ + title: title.trim(), + completed: false, + userId: req.userId, + }); + + res.status(201).json({ + msg: 'Todo created successfully', + todo: newTodo, + }); + } catch (error) { + res.status(500).json({ + msg: 'Error creating todo', + ...(process.env.NODE_ENV === 'development' && { error: error.message }), + }); + } +}); + +// -------------------- GET TODOS -------------------- +router.get('/', async (req, res) => { + try { + const todos = await Todo.find({ userId: req.userId }).sort({ createdAt: -1 }); + res.json({ todos }); + } catch (error) { + res.status(500).json({ + msg: 'Error fetching todos', + ...(process.env.NODE_ENV === 'development' && { error: error.message }), + }); + } +}); + +// -------------------- UPDATE TODO -------------------- +router.put('/:id', async (req, res) => { + const { id } = req.params; + const { completed } = req.body; + + if (typeof completed !== 'boolean') { + return res.status(400).json({ msg: 'You must provide a valid completed status.' }); + } + + try { + const result = await Todo.updateOne( + { _id: id, userId: req.userId }, + { completed } + ); + + if (result.matchedCount === 0) { + return res.status(404).json({ msg: 'Todo not found or unauthorized.' }); + } + + res.json({ msg: 'Todo updated successfully.' }); + } catch (error) { + res.status(500).json({ + msg: 'Error updating todo', + ...(process.env.NODE_ENV === 'development' && { error: error.message }), + }); + } +}); + +export default router; diff --git a/week-5/backend/routes/user.js b/week-5/backend/routes/user.js index 4be9f96ed..83fa63a8c 100644 --- a/week-5/backend/routes/user.js +++ b/week-5/backend/routes/user.js @@ -1 +1,65 @@ -// start writing your code from here \ No newline at end of file +// start writing your code from here +import express from 'express'; +import jwt from 'jsonwebtoken'; +import bcrypt from 'bcrypt'; +import dotenv from 'dotenv'; +import { authenticateJwt } from '../middleware/user.js'; +import { User } from '../db/index.js'; + +dotenv.config(); +const router = express.Router(); +const SECRET = process.env.JWT_SECRET || 'default_secret'; + +// -------------------- SIGNUP -------------------- +router.post('/signup', async (req, res) => { + const { username, password } = req.body; + + if (!username || !password) { + return res.status(400).json({ message: 'Username and password are required' }); + } + + try { + const existingUser = await User.findOne({ username }); + if (existingUser) { + return res.status(403).json({ message: 'User already exists' }); + } + + const hashedPassword = await bcrypt.hash(password, 10); + const newUser = new User({ username, password: hashedPassword }); + await newUser.save(); + + const token = jwt.sign({ userId: newUser._id }, SECRET, { expiresIn: '1h' }); + res.status(201).json({ message: 'User created successfully', token }); + } catch (error) { + console.error('Error during signup:', error); + res.status(500).json({ message: error.message }); + } +}); + +// -------------------- SIGNIN -------------------- +router.post('/signin', async (req, res) => { + const { username, password } = req.body; + + if (!username || !password) { + return res.status(400).json({ message: 'Username and password are required' }); + } + + try { + const user = await User.findOne({ username }); + if (!user) { + return 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 username or 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' }); + } +}); + +export default router; diff --git a/week-5/frontend/index.html b/week-5/frontend/index.html index 794d1dd78..a64cb0435 100644 --- a/week-5/frontend/index.html +++ b/week-5/frontend/index.html @@ -1,13 +1,60 @@ + Taskify Frontend + - - +
+ + +
+

Signup

+
+ + + + + +
+

+ Already have an account? Sign In +

+
+ + + + + +

+
+ + + \ No newline at end of file diff --git a/week-5/frontend/script.js b/week-5/frontend/script.js index 722bb62ed..02dfad950 100644 --- a/week-5/frontend/script.js +++ b/week-5/frontend/script.js @@ -1 +1,186 @@ -// start from here \ No newline at end of file +let isSigningUp = false; +let isAddingTodo = false; + +// Signup Form Submission +document.getElementById('signup-form').addEventListener('submit', async (e) => { + e.preventDefault(); + if (isSigningUp) return; + isSigningUp = true; + + const username = document.getElementById('signup-username').value; + const password = document.getElementById('signup-password').value; + + try { + const response = await fetch('http://localhost:3000/api/user/signup', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ username, password }), + }); + const result = await response.json(); + isSigningUp = false; + + if (response.ok) { + document.getElementById('response-message').innerText = result.message || 'Signup successful, please sign in'; + document.getElementById('signup-container').style.display = 'none'; + document.getElementById('signin-container').style.display = 'block'; + } else { + document.getElementById('response-message').innerText = result.message || 'Signup failed'; + } + } catch (error) { + isSigningUp = false; + document.getElementById('response-message').innerText = 'Error during signup'; + } +}); + +// Signin Form Submission + +document.getElementById('signin-form').addEventListener('submit', async (e) => { + e.preventDefault(); + + const username = document.getElementById('signin-username').value; + const password = document.getElementById('signin-password').value; + + try { + const response = await fetch('http://localhost:3000/api/user/signin', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ username, password }), + }); + const result = await response.json(); + + if (response.ok) { + localStorage.setItem('token', result.token); + document.getElementById('signin-container').style.display = 'none'; + document.getElementById('todo-container').style.display = 'block'; + document.getElementById('response-message').innerHTML = + `Logged in successfully. Logout`; + loadTodos(); + + // Add event listener for the logout link + document.getElementById('logout-link').addEventListener('click', (e) => { + e.preventDefault(); + localStorage.removeItem('token'); // Clear token + document.getElementById('todo-container').style.display = 'none'; + document.getElementById('signin-container').style.display = 'block'; + document.getElementById('response-message').innerText = ''; + }); + } else { + document.getElementById('response-message').innerText = result.message || 'Signin failed'; + } + } catch (error) { + document.getElementById('response-message').innerText = 'Error during signin'; + } +}); + + +// Adding Todo on Form Submission +document.getElementById('todo-form').addEventListener('submit', async (e) => { + e.preventDefault(); + if (isAddingTodo) return; + isAddingTodo = true; + + const todoInput = document.getElementById('todo-input'); + const todoText = todoInput.value.trim(); + if (!todoText) { + isAddingTodo = false; + return; + } + + const token = localStorage.getItem('token'); + + try { + const response = await fetch('http://localhost:3000/api/todo', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${token}`, + }, + body: JSON.stringify({ title: todoText }), + }); + const result = await response.json(); + isAddingTodo = false; + + if (response.ok) { + todoInput.value = ''; + loadTodos(); + } else { + console.error(result.msg); + } + } catch (error) { + isAddingTodo = false; + console.error('Error adding todo:', error); + } +}); + +// Load Todos +async function loadTodos() { + const token = localStorage.getItem('token'); + try { + const response = await fetch('http://localhost:3000/api/todo', { + headers: { + 'Authorization': `Bearer ${token}`, + }, + }); + const { todos } = await response.json(); + const todoList = document.getElementById('todo-list'); + todoList.innerHTML = ''; + + todos.forEach(todo => { + const li = document.createElement('li'); + li.textContent = todo.title; + + if (todo.completed) { + li.style.textDecoration = 'line-through'; + } + + const completeButton = document.createElement('button'); + completeButton.textContent = 'Complete'; + completeButton.onclick = () => { + completeTodo(todo._id, !todo.completed); + }; + + if (!todo.completed) { + li.appendChild(completeButton); + } + + todoList.appendChild(li); + }); + } catch (error) { + console.error('Error loading todos:', error); + } +} + +// Complete Todo +async function completeTodo(id, completed) { + const token = localStorage.getItem('token'); + try { + await fetch(`http://localhost:3000/api/todo/${id}`, { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${token}`, + }, + body: JSON.stringify({ completed }), + }); + loadTodos(); + } catch (error) { + console.error('Error completing todo:', error); + } +} + +// Toggle between Signup and Signin +document.getElementById('show-signin').addEventListener('click', (e) => { + e.preventDefault(); + document.getElementById('signup-container').style.display = 'none'; + document.getElementById('signin-container').style.display = 'block'; +}); + +document.getElementById('show-signup').addEventListener('click', (e) => { + e.preventDefault(); + document.getElementById('signin-container').style.display = 'none'; + document.getElementById('signup-container').style.display = 'block'; +}); diff --git a/week-5/frontend/style.css b/week-5/frontend/style.css index 9bff184c6..6267cbd35 100644 --- a/week-5/frontend/style.css +++ b/week-5/frontend/style.css @@ -1 +1,119 @@ - /* start from here */ \ No newline at end of file +body { + font-family: Arial, sans-serif; + margin: 0; + padding: 0; + background: + linear-gradient(135deg, rgba(147, 112, 219, 0.6), rgba(138, 43, 226, 0.6)), + radial-gradient(circle at 54% 113%, rgba(108, 38, 255, 0.3), rgba(242, 89, 255, 0)), + radial-gradient(circle at 28% 0, rgba(213, 189, 245, 0.3), rgba(250, 247, 254, 0)); + color: #8A2BE2; + min-height: 100vh; + display: flex; + justify-content: center; + align-items: center; +} + +.container { + background: rgba(255, 255, 255, 0.1); + border-radius: 20px; + padding: 30px; + width: 100%; + max-width: 400px; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); +} + +h2 { + margin-top: 0; + margin-bottom: 20px; + font-size: 24px; +} + +form { + display: flex; + flex-direction: column; +} + +label { + margin-bottom: 5px; + font-size: 14px; +} + +input[type="text"], +input[type="password"], +#todo-input { + padding: 12px; + margin-bottom: 15px; + border: none; + border-radius: 10px; + background: rgba(255, 255, 255, 0.2); + color: white; +} + +input::placeholder { + color: rgba(255, 255, 255, 0.7); +} + +button { + padding: 12px; + border: none; + border-radius: 10px; + background-color: #8A2BE2; + color: white; + cursor: pointer; + transition: background-color 0.3s; +} + +button:hover { + background-color: #9370DB; +} + +#response-message { + margin-top: 20px; + text-align: center; + color: #FFD700; +} + +a { + color: #FFD700; + text-decoration: none; +} + +a:hover { + text-decoration: underline; +} + +/* New styles for the todo list */ +#todo-list { + list-style: none; + padding: 0; +} + +#todo-list li { + display: flex; + justify-content: space-between; + align-items: center; + padding: 10px; + background: rgba(255, 255, 255, 0.2); + border-radius: 10px; + margin-bottom: 10px; +} + +#todo-list li span { + flex-grow: 1; +} + +#todo-list li button { + padding: 6px 12px; + background-color: #FFF; + transition: background-color 0.3s; + color: black; +} + +#todo-list li button:hover { + background-color: #e0e0e0; +} + +#response-message a { + color: #FFD700; /* Adjust color for better visibility */ + text-decoration: underline; +}