├── .env.example ├── .gitignore ├── models ├── task.js └── user.js ├── package-lock.json ├── package.json ├── routes └── users.js ├── server.js └── vercel.json /.env.example: -------------------------------------------------------------------------------- 1 | MONGODB_URL= 2 | PORT= -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### Node ### 2 | # Logs 3 | logs 4 | *.log 5 | npm-debug.log* 6 | yarn-debug.log* 7 | yarn-error.log* 8 | lerna-debug.log* 9 | .pnpm-debug.log* 10 | 11 | # Diagnostic reports (https://nodejs.org/api/report.html) 12 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 13 | 14 | # Runtime data 15 | pids 16 | *.pid 17 | *.seed 18 | *.pid.lock 19 | 20 | # Directory for instrumented libs generated by jscoverage/JSCover 21 | lib-cov 22 | 23 | # Coverage directory used by tools like istanbul 24 | coverage 25 | *.lcov 26 | 27 | # nyc test coverage 28 | .nyc_output 29 | 30 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 31 | .grunt 32 | 33 | # Bower dependency directory (https://bower.io/) 34 | bower_components 35 | 36 | # node-waf configuration 37 | .lock-wscript 38 | 39 | # Compiled binary addons (https://nodejs.org/api/addons.html) 40 | build/Release 41 | 42 | # Dependency directories 43 | node_modules/ 44 | jspm_packages/ 45 | 46 | # Snowpack dependency directory (https://snowpack.dev/) 47 | web_modules/ 48 | 49 | # TypeScript cache 50 | *.tsbuildinfo 51 | 52 | # Optional npm cache directory 53 | .npm 54 | 55 | # Optional eslint cache 56 | .eslintcache 57 | 58 | # Optional stylelint cache 59 | .stylelintcache 60 | 61 | # Microbundle cache 62 | .rpt2_cache/ 63 | .rts2_cache_cjs/ 64 | .rts2_cache_es/ 65 | .rts2_cache_umd/ 66 | 67 | # Optional REPL history 68 | .node_repl_history 69 | 70 | # Output of 'npm pack' 71 | *.tgz 72 | 73 | # Yarn Integrity file 74 | .yarn-integrity 75 | 76 | # dotenv environment variable files 77 | .env 78 | .env.development.local 79 | .env.test.local 80 | .env.production.local 81 | .env.local 82 | 83 | # parcel-bundler cache (https://parceljs.org/) 84 | .cache 85 | .parcel-cache 86 | 87 | # Next.js build output 88 | .next 89 | out 90 | 91 | # Nuxt.js build / generate output 92 | .nuxt 93 | dist 94 | 95 | # Gatsby files 96 | .cache/ 97 | # Comment in the public line in if your project uses Gatsby and not Next.js 98 | # https://nextjs.org/blog/next-9-1#public-directory-support 99 | # public 100 | 101 | # vuepress build output 102 | .vuepress/dist 103 | 104 | # vuepress v2.x temp and cache directory 105 | .temp 106 | 107 | # Docusaurus cache and generated files 108 | .docusaurus 109 | 110 | # Serverless directories 111 | .serverless/ 112 | 113 | # FuseBox cache 114 | .fusebox/ 115 | 116 | # DynamoDB Local files 117 | .dynamodb/ 118 | 119 | # TernJS port file 120 | .tern-port 121 | 122 | # Stores VSCode versions used for testing VSCode extensions 123 | .vscode-test 124 | 125 | # yarn v2 126 | .yarn/cache 127 | .yarn/unplugged 128 | .yarn/build-state.yml 129 | .yarn/install-state.gz 130 | .pnp.* 131 | 132 | ### Node Patch ### 133 | # Serverless Webpack directories 134 | .webpack/ 135 | 136 | # Optional stylelint cache 137 | 138 | # SvelteKit build / generate output 139 | .svelte-kit 140 | -------------------------------------------------------------------------------- /models/task.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | const Schema = mongoose.Schema; 3 | 4 | const taskSchema = new mongoose.Schema({ 5 | title: { 6 | type: String, 7 | required: true, 8 | minlength: 3, 9 | maxlength: 50, 10 | }, 11 | description: { 12 | type: String, 13 | required: true, 14 | minlength: 10, 15 | maxlength: 500, 16 | }, 17 | assignedTo: { 18 | type: mongoose.Schema.Types.ObjectId, 19 | ref: "User", 20 | required: true, 21 | }, 22 | }); 23 | 24 | const Task = mongoose.model("Task", taskSchema); 25 | 26 | module.exports = Task; -------------------------------------------------------------------------------- /models/user.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | const Schema = mongoose.Schema; 3 | 4 | const userSchema = new Schema({ 5 | name: { 6 | type: String, 7 | required: true, 8 | minlength: 3, 9 | maxlength: 50, 10 | }, 11 | email: { 12 | type: String, 13 | required: true, 14 | minlength: 5, 15 | maxlength: 255, 16 | unique: true, 17 | }, 18 | password: { 19 | type: String, 20 | required: true, 21 | minlength: 6, 22 | maxlength: 1024, 23 | }, 24 | isAdmin: { 25 | type: Boolean, 26 | default: false, 27 | }, 28 | }); 29 | 30 | const User = mongoose.model("User", userSchema); 31 | 32 | module.exports = User; 33 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "task-management-backend", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "server.js", 6 | "scripts": { 7 | "dev": "NODE_ENV=development node server.js", 8 | "start": "NODE_ENV=production node server.js", 9 | "test": "echo \"Error: no test specified\" && exit 1" 10 | }, 11 | "author": "Amil Abdullazadeh", 12 | "license": "ISC", 13 | "dependencies": { 14 | "bcrypt": "^5.1.0", 15 | "body-parser": "^1.20.2", 16 | "cookie-parser": "^1.4.6", 17 | "cors": "^2.8.5", 18 | "dotenv": "^16.0.3", 19 | "express": "^4.18.2", 20 | "http-errors": "^2.0.0", 21 | "jsonwebtoken": "^9.0.0", 22 | "mongoose": "^7.0.0", 23 | "morgan": "^1.10.0", 24 | "nodemon": "^2.0.21", 25 | "path": "^0.12.7", 26 | "vercel": "^28.16.12" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /routes/users.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const bcrypt = require("bcrypt"); 3 | const jwt = require("jsonwebtoken"); 4 | const User = require("../models/user") 5 | const Task = require("../models/task") 6 | 7 | const router = express.Router(); 8 | 9 | //! LOGIN, REGISTER => AUTH 10 | router.post("/login", async (req, res) => { 11 | 12 | // { 13 | // "name": "Test Admin", 14 | // "email": "test@admin.com", 15 | // "password": "Test123@", 16 | // "isAdmin": true 17 | // } 18 | 19 | // { 20 | // "name": "Test U”ser, 21 | // "email": “user@admin.com", 22 | // "password": “user123@", 23 | // "isAdmin": false 24 | // } 25 | 26 | const { email, password } = req.body; 27 | 28 | const user = await User.findOne({ email }); 29 | if (!user) return res.status(400).send("Invalid email or password."); 30 | 31 | const isValidPassword = await bcrypt.compare(password, user.password); 32 | if (!isValidPassword) 33 | return res.status(400).send("Invalid email or password."); 34 | 35 | const token = jwt.sign( 36 | { _id: user._id, isAdmin: user.isAdmin }, 37 | "jwtPrivateKey" 38 | ); 39 | res.send(token); 40 | }); 41 | 42 | router.post("/register", async (req, res) => { 43 | const { name, email, password, isAdmin } = req.body; 44 | 45 | // Check if the email is already registered 46 | let user = await User.findOne({ email }); 47 | if (user) return res.status(400).send("User already registered."); 48 | 49 | // Create a new user 50 | user = new User({ 51 | name, 52 | email, 53 | password: await bcrypt.hash(password, 10), 54 | isAdmin, 55 | }); 56 | 57 | await user.save(); 58 | 59 | // Send a response with the new user 60 | res.send(user); 61 | }); 62 | 63 | //! GET, POST => USERS 64 | router.post("/users", async (req, res) => { 65 | const { username, password } = req.body; 66 | 67 | const existingUser = await User.findOne({ username }); 68 | if (existingUser) return res.status(400).send("Username already taken."); 69 | 70 | const user = new User({ 71 | username, 72 | password: await bcrypt.hash(password, 10), 73 | }); 74 | 75 | await user.save(); 76 | 77 | res.send(user); 78 | }); 79 | 80 | router.get("/users", async (req, res) => { 81 | const users = await User.find(); 82 | res.send(users); 83 | }); 84 | 85 | //! GET, POST => TASKS 86 | router.post("/tasks", async (req, res) => { 87 | const { title, description, assignedTo } = req.body; 88 | 89 | const task = new Task({ 90 | title, 91 | description, 92 | assignedTo, 93 | }); 94 | 95 | await task.save(); 96 | 97 | res.send(task); 98 | }); 99 | 100 | router.get("/tasks", async (req, res) => { 101 | const tasks = await Task.find().populate("assignedTo", "username"); 102 | res.send(tasks); 103 | }); 104 | 105 | module.exports = router; 106 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | const express = require("express"); 3 | const cors = require("cors"); 4 | const bodyParser = require("body-parser"); 5 | const dotenv = require("dotenv"); 6 | const users = require("./routes/users"); 7 | 8 | dotenv.config(); 9 | 10 | const app = express(); 11 | 12 | const whitelist = ["*"]; 13 | 14 | app.use((req, res, next) => { 15 | const origin = req.get("referer"); 16 | const isWhitelisted = whitelist.find((w) => origin && origin.includes(w)); 17 | if (isWhitelisted) { 18 | res.setHeader("Access-Control-Allow-Origin", "*"); 19 | res.setHeader( 20 | "Access-Control-Allow-Methods", 21 | "GET, POST, OPTIONS, PUT, PATCH, DELETE" 22 | ); 23 | res.setHeader( 24 | "Access-Control-Allow-Headers", 25 | "X-Requested-With,Content-Type,Authorization" 26 | ); 27 | res.setHeader("Access-Control-Allow-Credentials", true); 28 | } 29 | // Pass to next layer of middleware 30 | if (req.method === "OPTIONS") res.sendStatus(200); 31 | else next(); 32 | }); 33 | 34 | const setContext = (req, res, next) => { 35 | if (!req.context) req.context = {}; 36 | next(); 37 | }; 38 | 39 | app.use(setContext); 40 | 41 | mongoose 42 | .connect(process.env.MONGODB_URL, { 43 | useNewUrlParser: true, 44 | useUnifiedTopology: true, 45 | }) 46 | .then(() => console.log("Connected to MongoDB...")) 47 | .catch((err) => console.error("Could not connect to MongoDB...", err)); 48 | 49 | app.use(bodyParser.json()); 50 | app.use("/api", users); 51 | 52 | const port = process.env.PORT || 4000; 53 | app.listen(port, () => console.log(`Listening on port ${port}`)); 54 | 55 | module.exports = app; -------------------------------------------------------------------------------- /vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 2, 3 | "builds": [ 4 | { 5 | "src": "server.js", 6 | "use": "@vercel/node" 7 | } 8 | ], 9 | "routes": [ 10 | { 11 | "src": "/(.*)", 12 | "dest": "server.js" 13 | } 14 | ] 15 | } 16 | --------------------------------------------------------------------------------