diff --git a/client/package-lock.json b/client/package-lock.json index 03ddba3c..bed18962 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -78,6 +78,7 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.21.8.tgz", "integrity": "sha512-YeM22Sondbo523Sz0+CirSPnbj9bG3P0CdHcBZdqUuaeOaYEFbOLoGU7lebvGP6P5J/WE9wOn7u7C4J9HvS1xQ==", "dev": true, + "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.21.4", @@ -989,6 +990,7 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", "dev": true, + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -1248,6 +1250,7 @@ "url": "https://tidelift.com/funding/github/npm/browserslist" } ], + "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001449", "electron-to-chromium": "^1.4.284", @@ -1669,6 +1672,7 @@ "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.41.0.tgz", "integrity": "sha512-WQDQpzGBOP5IrXPo4Hc0814r4/v2rrIsB0rhT7jtunIalgg6gYXWhRMOejVO8yH21T/FGaxjmFjBMNqcIlmH1Q==", "dev": true, + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.4.0", @@ -3233,6 +3237,7 @@ "url": "https://github.com/sponsors/ai" } ], + "peer": true, "dependencies": { "nanoid": "^3.3.6", "picocolors": "^1.0.0", @@ -3420,6 +3425,7 @@ "version": "18.2.0", "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", + "peer": true, "dependencies": { "loose-envify": "^1.1.0" }, @@ -3431,6 +3437,7 @@ "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", + "peer": true, "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.0" @@ -4052,6 +4059,7 @@ "resolved": "https://registry.npmjs.org/vite/-/vite-4.3.8.tgz", "integrity": "sha512-uYB8PwN7hbMrf4j1xzGDk/lqjsZvCDbt/JC5dyfxc19Pg8kRm14LinK/uq+HSLNswZEoKmweGdtpbnxRtrAXiQ==", "dev": true, + "peer": true, "dependencies": { "esbuild": "^0.17.5", "postcss": "^8.4.23", @@ -4226,6 +4234,7 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.21.8.tgz", "integrity": "sha512-YeM22Sondbo523Sz0+CirSPnbj9bG3P0CdHcBZdqUuaeOaYEFbOLoGU7lebvGP6P5J/WE9wOn7u7C4J9HvS1xQ==", "dev": true, + "peer": true, "requires": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.21.4", @@ -4799,7 +4808,8 @@ "version": "8.8.2", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", - "dev": true + "dev": true, + "peer": true }, "acorn-jsx": { "version": "5.3.2", @@ -4982,6 +4992,7 @@ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.5.tgz", "integrity": "sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w==", "dev": true, + "peer": true, "requires": { "caniuse-lite": "^1.0.30001449", "electron-to-chromium": "^1.4.284", @@ -5296,6 +5307,7 @@ "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.41.0.tgz", "integrity": "sha512-WQDQpzGBOP5IrXPo4Hc0814r4/v2rrIsB0rhT7jtunIalgg6gYXWhRMOejVO8yH21T/FGaxjmFjBMNqcIlmH1Q==", "dev": true, + "peer": true, "requires": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.4.0", @@ -6397,6 +6409,7 @@ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.27.tgz", "integrity": "sha512-gY/ACJtJPSmUFPDCHtX78+01fHa64FaU4zaaWfuh1MhGJISufJAH4cun6k/8fwsHYeK4UQmENQK+tRLCFJE8JQ==", "dev": true, + "peer": true, "requires": { "nanoid": "^3.3.6", "picocolors": "^1.0.0", @@ -6509,6 +6522,7 @@ "version": "18.2.0", "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", + "peer": true, "requires": { "loose-envify": "^1.1.0" } @@ -6517,6 +6531,7 @@ "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", + "peer": true, "requires": { "loose-envify": "^1.1.0", "scheduler": "^0.23.0" @@ -6953,6 +6968,7 @@ "resolved": "https://registry.npmjs.org/vite/-/vite-4.3.8.tgz", "integrity": "sha512-uYB8PwN7hbMrf4j1xzGDk/lqjsZvCDbt/JC5dyfxc19Pg8kRm14LinK/uq+HSLNswZEoKmweGdtpbnxRtrAXiQ==", "dev": true, + "peer": true, "requires": { "esbuild": "^0.17.5", "fsevents": "~2.3.2", diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000..68060633 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,6 @@ +{ + "name": "cohort-tools-project", + "lockfileVersion": 3, + "requires": true, + "packages": {} +} diff --git a/server/.env.sample b/server/.env.sample deleted file mode 100644 index 0042a604..00000000 --- a/server/.env.sample +++ /dev/null @@ -1 +0,0 @@ -PORT=5005 \ No newline at end of file diff --git a/server/app.js b/server/app.js index dfe51f49..fe7ccd4d 100644 --- a/server/app.js +++ b/server/app.js @@ -1,36 +1,201 @@ +require("dotenv").config(); + const express = require("express"); const morgan = require("morgan"); const cookieParser = require("cookie-parser"); + const PORT = 5005; // STATIC DATA // Devs Team - Import the provided files with JSON data of students and cohorts here: // ... - // INITIALIZE EXPRESS APP - https://expressjs.com/en/4x/api.html#express const app = express(); - // MIDDLEWARE + // Research Team - Set up CORS middleware here: -// ... +const cors = require("cors"); +const mongoose = require("mongoose"); +const CohortModel = require("./models/Cohort.model"); + +const StudentModel = require("./models/Student.model"); +mongoose + .connect("mongodb://localhost:27017/cohort-tools-api") + .then(() => { + console.log("Connected to the DB, Nice work!"); + }) + .catch((err) => console.log(err)); + +app.use(cors()); + app.use(express.json()); app.use(morgan("dev")); app.use(express.static("public")); app.use(express.urlencoded({ extended: false })); app.use(cookieParser()); - // ROUTES - https://expressjs.com/en/starter/basic-routing.html // Devs Team - Start working on the routes here: + +//Creates a new student +app.post("/api/students", (req, res) => { + StudentModel.create(req.body) + .then((data) => { + console.log("student created", data); + res.status(201).json(data); + }) + .catch((err) => { + console.log(err); + res.status(500).json({ errorMessage: err }); + }); +}); + // ... app.get("/docs", (req, res) => { res.sendFile(__dirname + "/views/docs.html"); }); +//Retrieves all of the students in the database collection +app.get("/api/students", async (req, res) => { + try { + const data = await StudentModel.find().populate("cohort"); + console.log("student found", data); + res.status(200).json(data); + } catch (err) { + console.log(err); + res.status(500).json({ errorMessage: err }); + } +}); + +// Retrieves all students from a specific cohort +app.get("/api/students/cohort/:cohortId", async (req, res) => { + const { cohortId } = req.params; + + try { + const students = await StudentModel.find({ cohort: cohortId }).populate( + "cohort" + ); + + res.status(200).json(students); + } catch (err) { + console.log(err); + res.status(500).json({ errorMessage: err }); + } +}); + +// Updates a specific student by id +app.put("/api/students/:studentId", async (req, res) => { + const { studentId } = req.params; + + try { + const updatedStudent = await StudentModel.findByIdAndUpdate( + studentId, + req.body, + { new: true } + ); + + res.status(200).json(updatedStudent); + } catch (err) { + console.log(err); + res.status(500).json({ errorMessage: err }); + } +}); + +app.get("/api/students/:studentId", async (req, res) => { + const { studentId } = req.params; + + try { + const student = await StudentModel.findById(studentId).populate("cohort"); + + res.status(200).json(student); + } catch (err) { + console.log(err); + res.status(500).json({ errorMessage: err }); + } +}); + +// Deletes a specific student by id +app.delete("/api/students/:studentId", async (req, res) => { + const { studentId } = req.params; + + try { + const deletedStudent = await StudentModel.findByIdAndDelete(studentId); + + res.status(200).json(deletedStudent); + } catch (err) { + console.log(err); + res.status(500).json({ errorMessage: err }); + } +}); + +// Creates a new cohort +app.post("/api/cohorts", async (req, res) => { + try { + const newCohort = await CohortModel.create(req.body); + res.status(201).json(newCohort); + } catch (err) { + console.log(err); + res.status(500).json({ errorMessage: err }); + } +}); +// Retrieves all cohorts +app.get("/api/cohorts", async (req, res) => { + try { + const cohorts = await CohortModel.find(); + res.status(200).json(cohorts); + } catch (err) { + console.log(err); + res.status(500).json({ errorMessage: err }); + } +}); +// Retrieves a specific cohort by id +app.get("/api/cohorts/:cohortId", async (req, res) => { + const { cohortId } = req.params; + + try { + const cohort = await CohortModel.findById(cohortId); + res.status(200).json(cohort); + } catch (err) { + console.log(err); + res.status(500).json({ errorMessage: err }); + } +}); +// Updates a specific cohort by id +app.put("/api/cohorts/:cohortId", async (req, res) => { + const { cohortId } = req.params; + + try { + const updatedCohort = await CohortModel.findByIdAndUpdate( + cohortId, + req.body, + { new: true } + ); + + res.status(200).json(updatedCohort); + } catch (err) { + console.log(err); + res.status(500).json({ errorMessage: err }); + } +}); +// Deletes a specific cohort by id +app.delete("/api/cohorts/:cohortId", async (req, res) => { + const { cohortId } = req.params; + + try { + const deletedCohort = await CohortModel.findByIdAndDelete(cohortId); + res.status(200).json(deletedCohort); + } catch (err) { + console.log(err); + res.status(500).json({ errorMessage: err }); + } +}); + +const authRoutes = require("./routes/auth.routes"); +app.use("/auth", authRoutes); // START SERVER app.listen(PORT, () => { console.log(`Server listening on port ${PORT}`); -}); \ No newline at end of file +}); diff --git a/server/middlewares/jwt.middleware.js b/server/middlewares/jwt.middleware.js new file mode 100644 index 00000000..23af2566 --- /dev/null +++ b/server/middlewares/jwt.middleware.js @@ -0,0 +1,24 @@ +const jwt = require("jsonwebtoken"); + +function isAuthenticated(req, res, next) { + if ( + req.headers.authorization.split(" ")[0] === "Bearer" && + req.headers.authorization.split(" ")[1] + ) { + const theTokenInHeaders = req.headers.authorization.split(" ")[1]; + try { + const decodedToken = jwt.verify( + theTokenInHeaders, + process.env.TOKEN_SECRET + ); + req.payload = decodedToken; + next(); + } catch (error) { + res.status(403).json({ errorMessage: "Invalid Token" }); + } + } else { + res.status(403).json({ errorMessage: "Headers Malformed" }); + } +} + +module.exports = { isAuthenticated }; diff --git a/server/models/Cohort.model.js b/server/models/Cohort.model.js new file mode 100644 index 00000000..77585f30 --- /dev/null +++ b/server/models/Cohort.model.js @@ -0,0 +1,78 @@ +const { Schema, model } = require("mongoose"); + +const cohortSchema = new Schema({ + cohortSlug: { + type: String, + required: true, + unique: true, + trim: true, + }, + + cohortName: { + type: String, + required: true, + trim: true, + }, + + program: { + type: String, + required: true, + enum: ["Web Dev", "UX/UI", "Data Analytics", "Cybersecurity"], + }, + + format: { + type: String, + required: true, + enum: ["Full Time", "Part Time"], + }, + + campus: { + type: String, + required: true, + enum: [ + "Madrid", + "Barcelona", + "Miami", + "Paris", + "Berlin", + "Amsterdam", + "Lisbon", + "Remote", + ], + }, + + startDate: { + type: Date, + default: Date.now, + }, + + endDate: { + type: Date, + required: true, + }, + + inProgress: { + type: Boolean, + default: false, + }, + + programManager: { + type: String, + required: true, + trim: true, + }, + + leadTeacher: { + type: String, + required: true, + trim: true, + }, + + totalHours: { + type: Number, + default: 360, + min: 1, + }, +}); +const CohortModel = model("Cohort", cohortSchema); +module.exports = CohortModel; diff --git a/server/models/Student.model.js b/server/models/Student.model.js new file mode 100644 index 00000000..fd0817bc --- /dev/null +++ b/server/models/Student.model.js @@ -0,0 +1,84 @@ +const { Schema, model } = require("mongoose"); +const studentSchema = new Schema({ + firstName: { + type: String, + required: true, + trim: true, + }, + + lastName: { + type: String, + required: true, + trim: true, + }, + + email: { + type: String, + required: true, + unique: true, + lowercase: true, + trim: true, + match: [/^\S+@\S+\.\S+$/, "Invalid email format"], + }, + + phone: { + type: String, + required: true, + trim: true, + }, + + linkedinUrl: { + type: String, + default: "", + trim: true, + }, + + languages: { + type: [ + { + type: String, + enum: [ + "English", + "Spanish", + "French", + "German", + "Portuguese", + "Dutch", + "Other", + ], + }, + ], + default: [], + }, + + program: { + type: String, + required: true, + enum: ["Web Dev", "UX/UI", "Data Analytics", "Cybersecurity"], + }, + + background: { + type: String, + default: "", + }, + + image: { + type: String, + default: "https://i.imgur.com/r8bo8u7.png", + }, + + cohort: { + type: Schema.Types.ObjectId, + ref: "Cohort", + required: true, + }, + + projects: [ + { + type: Schema.Types.ObjectId, + ref: "Project", + }, + ], +}); +const StudentModel = model("student", studentSchema); +module.exports = StudentModel; diff --git a/server/models/User.model.js b/server/models/User.model.js new file mode 100644 index 00000000..d83009a6 --- /dev/null +++ b/server/models/User.model.js @@ -0,0 +1,39 @@ +const { Schema, model } = require("mongoose"); + +// TODO: Please make sure you edit the User model to whatever makes sense in this case +const userSchema = new Schema( + { + email: { + type: String, + required: [true, "Email is required."], + unique: true, + lowercase: true, + trim: true, + }, + password: { + type: String, + required: [true, "Password is required."], + }, + username: { + type: String, + required: [true, "Username is required."], + }, + profilePicture: { + type: String, + default: + "https://media.istockphoto.com/id/1496602447/photo/human.jpg?s=2048x2048&w=is&k=20&c=gNMd_7mQn5nTCUfu-0iv0G6jayEANRPtFa52wAh03lM=", + }, + pets: { + type: [Schema.Types.ObjectId], + ref: "Pet", + }, + }, + { + // this second object adds extra properties: `createdAt` and `updatedAt` + timestamps: true, + } +); + +const User = model("User", userSchema); + +module.exports = User; diff --git a/server/package-lock.json b/server/package-lock.json index 23e2f78a..e3c56915 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -9,8 +9,12 @@ "version": "1.0.0", "license": "ISC", "dependencies": { + "bcrypt": "^6.0.0", "cookie-parser": "^1.4.6", + "cors": "^2.8.5", + "dotenv": "^17.2.3", "express": "^4.18.2", + "jsonwebtoken": "^9.0.3", "morgan": "^1.10.0", "nodemon": "^3.0.1" } @@ -70,6 +74,20 @@ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, + "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.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", @@ -121,6 +139,12 @@ "node": ">=8" } }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "license": "BSD-3-Clause" + }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -216,6 +240,19 @@ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -241,6 +278,27 @@ "npm": "1.2.8000 || >= 1.4.16" } }, + "node_modules/dotenv": { + "version": "17.2.3", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.3.tgz", + "integrity": "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -526,6 +584,97 @@ "node": ">=0.12.0" } }, + "node_modules/jsonwebtoken": { + "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": "^4.0.1", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jsonwebtoken/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==", + "license": "MIT" + }, + "node_modules/jwa": { + "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", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "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": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", + "license": "MIT" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "license": "MIT" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", + "license": "MIT" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", + "license": "MIT" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "license": "MIT" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "license": "MIT" + }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "license": "MIT" + }, "node_modules/lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -638,6 +787,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/nodemon": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.0.1.tgz", @@ -700,6 +869,15 @@ "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", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/object-inspect": { "version": "1.12.3", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", diff --git a/server/package.json b/server/package.json index 7aa0847c..e2538f82 100644 --- a/server/package.json +++ b/server/package.json @@ -18,8 +18,12 @@ }, "homepage": "https://github.com/ironhack-labs/cohort-tools-project#readme", "dependencies": { + "bcrypt": "^6.0.0", "cookie-parser": "^1.4.6", + "cors": "^2.8.5", + "dotenv": "^17.2.3", "express": "^4.18.2", + "jsonwebtoken": "^9.0.3", "morgan": "^1.10.0", "nodemon": "^3.0.1" } diff --git a/server/routes/auth.routes.js b/server/routes/auth.routes.js new file mode 100644 index 00000000..0e0cf55e --- /dev/null +++ b/server/routes/auth.routes.js @@ -0,0 +1,63 @@ +const router = require("express").Router(); +const UserModel = require("../models/User.model"); +const bcryptjs = require("bcrypt"); +const jwt = require("jsonwebtoken"); +const { isAuthenticated } = require("../middlewares/jwt.middleware"); +router.post("/signup", async (req, res) => { + const { email, password } = req.body; + try { + const userAlreadyInDB = await UserModel.findOne({ email }); + if (userAlreadyInDB) { + res.status(403).json({ errorMessage: "Email already taken" }); + } else { + const theSalt = bcryptjs.genSaltSync(12); + const theHashedPassword = bcryptjs.hashSync(password, theSalt); + const hashedUser = { + ...req.body, + password: theHashedPassword, + }; + const createdUser = await UserModel.create(hashedUser); + res.status(201).json(createdUser); + } + } catch (error) { + console.log(error); + res.status(500).json({ errorMessage: error }); + } +}); + +router.post("/login", async (req, res) => { + try { + const { email, password } = req.body; + const userAlreadyInDB = await UserModel.findOne({ email }); + if (!userAlreadyInDB) { + res.status(403).json({ errorMessage: "Email no in DB" }); + } else { + const doesPasswordsMatch = bcryptjs.compareSync( + password, + userAlreadyInDB.password + ); + if (!doesPasswordsMatch) { + res.status(403).json({ errorMessage: "Incorrect Password" }); + } else { + const payLoad = { _id: userAlreadyInDB._id }; + const authToken = jwt.sign(payLoad, process.env.TOKEN_SECRET, { + algorithm: "HS256", + expiresIn: "6h", + }); + + res + .status(200) + .json({ errorMessage: "you are now logged in", authToken }); + } + } + } catch (error) { + console.log(error); + res.status(500).json({ errorMessage: error }); + } +}); + +router.get("/verify", isAuthenticated, (req, res) => { + res.status(200).json({ message: "Token is valid" }); +}); + +module.exports = router;