diff --git a/server/app.js b/server/app.js index dfe51f49..953d90f4 100644 --- a/server/app.js +++ b/server/app.js @@ -1,12 +1,18 @@ -const express = require("express"); -const morgan = require("morgan"); -const cookieParser = require("cookie-parser"); +import express from "express"; +import morgan from "morgan"; +import cookieParser from "cookie-parser"; +import cors from "cors"; +import mongoose from "mongoose" const PORT = 5005; // STATIC DATA // Devs Team - Import the provided files with JSON data of students and cohorts here: // ... - +// const cohorts = require("./cohorts.json") +// const students = require("./students.json"); +import studentModel from "./models/student.model.js"; +import cohortModel from "./models/cohort.model.js" +import authRoutes from "./routes/auth.routes.js" // INITIALIZE EXPRESS APP - https://expressjs.com/en/4x/api.html#express const app = express(); @@ -15,21 +21,185 @@ const app = express(); // MIDDLEWARE // Research Team - Set up CORS middleware here: // ... +app.use(cors({ origin: ["http://localhost:5173"] })); + app.use(express.json()); app.use(morgan("dev")); app.use(express.static("public")); app.use(express.urlencoded({ extended: false })); app.use(cookieParser()); +app.use("/auth", authRoutes) +mongoose.connect('mongodb://localhost:27017/cohort-tools-api'). +then(()=>{ + console.log("connected to db") +}) +.catch ((err) =>console.log(err)) // ROUTES - https://expressjs.com/en/starter/basic-routing.html // Devs Team - Start working on the routes here: // ... -app.get("/docs", (req, res) => { + +app.get("/api/docs", (req, res) => { res.sendFile(__dirname + "/views/docs.html"); }); +// Cohort Routes + +// POST /api/cohorts - Creates a new cohort +app.post("/api/cohorts", async (req, res, next)=> { + try { + const getCohorts= await cohortModel.create(req.body) + res.status(201).json(getCohorts) + } catch (err) { + next(err); + } +} +) + +// GET /api/cohorts - Retrieves all of the cohorts in the database collection +app.get("/api/cohorts", async (req, res, next)=>{ + try { + const cohorts = await cohortModel.find() + res.status(200).json(cohorts); + } + catch (err) { + next(err); + }; +}) + + +// GET /api/cohorts/:cohortId - Retrieves a specific cohort by id +app.get("/api/cohorts/:cohortId", async (req, res, next) => { + try{ + const cohort = await cohortModel.findById(req.params.cohortId) + if (!cohort) { + return res.status(404).json({ message: "cohort not found" }) + } + res.status(200).json(cohort) + } + catch (err) { + next(err); + } +} +) + + +// PUT /api/cohorts/:cohortId - Updates a specific cohort by id +app.put("/api/cohorts/:cohortId",async (req,res, next) => { + try{ + const cohort = await cohortModel.findByIdAndUpdate(req.params.cohortId, req.body, { new: true }); + res.status(200).json(cohort) + } + catch (err) { + next(err); + } + }) + +// DELETE /api/cohorts/:cohortId - Deletes a specific cohort by id +app.delete("/api/cohorts/:cohortId",async (req, res, next) => { + try{ + const cohort = await cohortModel.findByIdAndDelete(req.params.cohortId) + res.status(200).json(cohort) + } + catch (err) { + next(err) + } + }) + + +// Student Routes + +// POST /api/students - Creates a new student +app.post("/api/students", async (req, res, next)=> { + try { + const student = await studentModel.create(req.body) + res.status(201).json(student) + } catch (err) { + next(err) + } +} +) + + +// GET /api/students - Retrieves all of the students in the database collection +app.get("/api/students", async (req, res, next) => { + try { + const students = await studentModel.find().populate("cohort"); + res.status(200).json(students); + } catch (err) { + next(err) + } +}) + +// GET /api/students/cohort/:cohortId - Retrieves all of the students for a given cohort + +app.get("/api/students/cohort/:cohortId", async (req, res, next) => { + try{ + const students = await studentModel.find({ cohort: req.params.cohortId }).populate("cohort"); + res.status(200).json(students) + } + catch (err) { + next(err) + + } + }) +// GET /api/students/:studentId - Retrieves a specific student by id +app.get("/api/students/:studentId",async (req, res, next) => { + try{ + const students = await studentModel.findById(req.params.studentId).populate("cohort"); + res.status(200).json(students) + } + catch (err) { + next(err) + } + }) + +// PUT /api/students/:studentId - Updates a specific student by id +app.put("/api/students/:studentId",async (req, res, next) => { + try{ + const students = await studentModel.findByIdAndUpdate(req.params.studentId, req.body, { new: true }); + res.status(200).json(students) + } + catch (err) { + next(err) + } + }) + +// DELETE /api/students/:studentId - Deletes a specific student by id +app.delete("/api/students/:studentId", async (req,res, next) => { + try{ + const students = await studentModel.findByIdAndDelete(req.params.studentId) + res.status(200).json(students) + } + catch (err) { + next(err) + } + }) + +app.use((req, res, next) => { +res.status(404).json({ message: "This route does not exist" }); +}); + + +app.use((err, req, res, next) => { + console.error("Error:", err.name, "-", err.message); + + if (err.name === "CastError") { + return res.status(400).json({ message: "Invalid ID format" }); + } + + if (err.name === "ValidationError") { + return res.status(400).json({ message: err.message }); + } + + res.status(500).json({ message: "Internal server error" }); +}); + + + + // START SERVER app.listen(PORT, () => { console.log(`Server listening on port ${PORT}`); diff --git a/server/middleware/jwt.middleware.js b/server/middleware/jwt.middleware.js new file mode 100644 index 00000000..16cdde04 --- /dev/null +++ b/server/middleware/jwt.middleware.js @@ -0,0 +1,22 @@ +import jwt from "jsonwebtoken"; + +const isAuthenticated = (req, res, next) => { + try { + + const token = req.headers.authorization?.split(" ")[1]; + + if (!token) { + return res.status(401).json({ message: "No token provided" }); + } + + const payload = jwt.verify(token, "t0k3n$ecr3t"); + + req.payload = payload; + next(); + + } catch (error) { + return res.status(401).json({ message: "Invalid or expired token" }); + } +}; + +export default isAuthenticated; diff --git a/server/models/cohort.model.js b/server/models/cohort.model.js new file mode 100644 index 00000000..1006228b --- /dev/null +++ b/server/models/cohort.model.js @@ -0,0 +1,54 @@ +import {Schema, model} from "mongoose"; + +const cohortSchema = new Schema( + { + cohortSlug: { + type: String, + required: true, + unique: true, + }, + cohortName: { + type: String, + required: true, + }, + program: { + type: String, + enum: ["Web Dev", "UX/UI", "Data Analytics", "Cybersecurity"], + }, + format: { + type: String, + enum: ["Full Time", "Part Time"], + }, + campus: { + type: String, + enum: ["Madrid", "Barcelona", "Miami", "Paris", "Berlin", "Amsterdam", "Lisbon", "Remote"], + }, + startDate:{ + type: Date, + default: Date.now(), + }, + endDate:{ + type: Date, + }, + inProgress: { + type: Boolean, + default: false, + }, + programManager: { + type: String, + required: true, + }, + leadTeacher: { + type: String, + required: true, + }, + totalHours: { + type: Number, + default: 360, + } + } +) + +const cohortModel = model('cohort', cohortSchema) + +export default cohortModel \ No newline at end of file diff --git a/server/models/student.model.js b/server/models/student.model.js new file mode 100644 index 00000000..e15b45d2 --- /dev/null +++ b/server/models/student.model.js @@ -0,0 +1,55 @@ +import {Schema, model} from "mongoose"; + +const studentSchema = new Schema( + { + firstName: { + type: String, + required: true, + }, + lastName: { + type: String, + required: true, + }, + email: { + type: String, + required: true, + unique: true, + }, + phone: { + type: String, + required: true, + }, + linkedinUrl: { + type: String, + default: "", + }, + languages:{ + type: [String], + enum: ["English", "Spanish", "French", "German", "Portuguese", "Dutch", "Other"], + }, + program:{ + type: String, + 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", + }, + projects: { + type: [String], + default: [], + } + } +) + +const studentModel = model('student', studentSchema) + +export default studentModel \ No newline at end of file diff --git a/server/models/user.model.js b/server/models/user.model.js new file mode 100644 index 00000000..0190c614 --- /dev/null +++ b/server/models/user.model.js @@ -0,0 +1,21 @@ +import { Schema, model } from "mongoose"; + +const userSchema = new Schema({ + username: { + type: String, + required: true, + unique: true, + }, + email: { + type: String, + required: true, + }, + password: { + type: String, + required: true, + }, +}); + +const userModel = model("user", userSchema); + +export default userModel \ No newline at end of file diff --git a/server/package-lock.json b/server/package-lock.json index 23e2f78a..dfc8422e 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -9,12 +9,66 @@ "version": "1.0.0", "license": "ISC", "dependencies": { + "bcryptjs": "^3.0.3", "cookie-parser": "^1.4.6", + "cors": "^2.8.6", "express": "^4.18.2", + "express-jwt": "^8.5.1", + "jsonwebtoken": "^9.0.3", + "mongoose": "^9.2.2", "morgan": "^1.10.0", "nodemon": "^3.0.1" } }, + "node_modules/@mongodb-js/saslprep": { + "version": "1.4.6", + "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.4.6.tgz", + "integrity": "sha512-y+x3H1xBZd38n10NZF/rEBlvDOOMQ6LKUTHqr8R9VkJ+mmQOYtJFxIlkkK8fZrtOiL6VixbOBWMbZGBdal3Z1g==", + "license": "MIT", + "dependencies": { + "sparse-bitfield": "^3.0.3" + } + }, + "node_modules/@types/jsonwebtoken": { + "version": "9.0.10", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.10.tgz", + "integrity": "sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==", + "license": "MIT", + "dependencies": { + "@types/ms": "*", + "@types/node": "*" + } + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "25.3.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.3.3.tgz", + "integrity": "sha512-DpzbrH7wIcBaJibpKo9nnSQL0MTRdnWttGyE5haGwK86xgMOkFLp7vEyfQPGLOJh5wNYiJ3V9PmUMDhV9u8kkQ==", + "license": "MIT", + "dependencies": { + "undici-types": "~7.18.0" + } + }, + "node_modules/@types/webidl-conversions": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz", + "integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==", + "license": "MIT" + }, + "node_modules/@types/whatwg-url": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-13.0.0.tgz", + "integrity": "sha512-N8WXpbE6Wgri7KUSvrmQcqrMllKZ9uxkYWMt+mCSGwNc0Hsw9VQTW7ApqI4XNrx6/SaM2QQJCzMPDEXE058s+Q==", + "license": "MIT", + "dependencies": { + "@types/webidl-conversions": "*" + } + }, "node_modules/abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", @@ -70,6 +124,15 @@ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, + "node_modules/bcryptjs": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-3.0.3.tgz", + "integrity": "sha512-GlF5wPWnSa/X5LKM1o0wz0suXIINz1iHRLvTS+sLyi7XPbe5ycmYI3DlZqVGZZtDgl4DmasFg7gOB3JYbphV5g==", + "license": "BSD-3-Clause", + "bin": { + "bcrypt": "bin/bcrypt" + } + }, "node_modules/binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", @@ -121,6 +184,21 @@ "node": ">=8" } }, + "node_modules/bson": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/bson/-/bson-7.2.0.tgz", + "integrity": "sha512-YCEo7KjMlbNlyHhz7zAZNDpIpQbd+wOEHJYezv0nMYTn4x31eIUM2yomNNubclAt63dObUzKHWsBLJ9QcZNSnQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=20.19.0" + } + }, + "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 +294,23 @@ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" }, + "node_modules/cors": { + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", + "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -241,6 +336,15 @@ "npm": "1.2.8000 || >= 1.4.16" } }, + "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", @@ -308,6 +412,26 @@ "node": ">= 0.10.0" } }, + "node_modules/express-jwt": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/express-jwt/-/express-jwt-8.5.1.tgz", + "integrity": "sha512-Dv6QjDLpR2jmdb8M6XQXiCcpEom7mK8TOqnr0/TngDKsG2DHVkO8+XnVxkJVN7BuS1I3OrGw6N8j5DaaGgkDRQ==", + "license": "MIT", + "dependencies": { + "@types/jsonwebtoken": "^9", + "express-unless": "^2.1.3", + "jsonwebtoken": "^9.0.0" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/express-unless": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/express-unless/-/express-unless-2.1.3.tgz", + "integrity": "sha512-wj4tLMyCVYuIIKHGt0FhCtIViBcwzWejX0EjNxveAa6dG+0XBCQhMbx+PnkLkFCxLC69qoFrxds4pIyL88inaQ==", + "license": "MIT" + }, "node_modules/express/node_modules/cookie": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", @@ -526,6 +650,106 @@ "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/kareem": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/kareem/-/kareem-3.2.0.tgz", + "integrity": "sha512-VS8MWZz/cT+SqBCpVfNN4zoVz5VskR3N4+sTmUXme55e9avQHntpwpNq0yjnosISXqwJ3AQVjlbI4Dyzv//JtA==", + "license": "Apache-2.0", + "engines": { + "node": ">=18.0.0" + } + }, + "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", @@ -545,6 +769,12 @@ "node": ">= 0.6" } }, + "node_modules/memory-pager": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", + "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==", + "license": "MIT" + }, "node_modules/merge-descriptors": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", @@ -599,6 +829,92 @@ "node": "*" } }, + "node_modules/mongodb": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-7.0.0.tgz", + "integrity": "sha512-vG/A5cQrvGGvZm2mTnCSz1LUcbOPl83hfB6bxULKQ8oFZauyox/2xbZOoGNl+64m8VBrETkdGCDBdOsCr3F3jg==", + "license": "Apache-2.0", + "dependencies": { + "@mongodb-js/saslprep": "^1.3.0", + "bson": "^7.0.0", + "mongodb-connection-string-url": "^7.0.0" + }, + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@aws-sdk/credential-providers": "^3.806.0", + "@mongodb-js/zstd": "^7.0.0", + "gcp-metadata": "^7.0.1", + "kerberos": "^7.0.0", + "mongodb-client-encryption": ">=7.0.0 <7.1.0", + "snappy": "^7.3.2", + "socks": "^2.8.6" + }, + "peerDependenciesMeta": { + "@aws-sdk/credential-providers": { + "optional": true + }, + "@mongodb-js/zstd": { + "optional": true + }, + "gcp-metadata": { + "optional": true + }, + "kerberos": { + "optional": true + }, + "mongodb-client-encryption": { + "optional": true + }, + "snappy": { + "optional": true + }, + "socks": { + "optional": true + } + } + }, + "node_modules/mongodb-connection-string-url": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-7.0.1.tgz", + "integrity": "sha512-h0AZ9A7IDVwwHyMxmdMXKy+9oNlF0zFoahHiX3vQ8e3KFcSP3VmsmfvtRSuLPxmyv2vjIDxqty8smTgie/SNRQ==", + "license": "Apache-2.0", + "dependencies": { + "@types/whatwg-url": "^13.0.0", + "whatwg-url": "^14.1.0" + }, + "engines": { + "node": ">=20.19.0" + } + }, + "node_modules/mongoose": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-9.2.2.tgz", + "integrity": "sha512-e06XdPPlH/L9aEq4vcnqIz5AxFFdfhlqrmymYDO7fZwnqwVp/u8pAH/cCEvvpXg0VlV0Tt5qwu6RUk8lhu6ifg==", + "license": "MIT", + "dependencies": { + "kareem": "3.2.0", + "mongodb": "~7.0", + "mpath": "0.9.0", + "mquery": "6.0.0", + "ms": "2.1.3", + "sift": "17.1.3" + }, + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mongoose" + } + }, + "node_modules/mongoose/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/morgan": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz", @@ -625,6 +941,24 @@ "node": ">= 0.8" } }, + "node_modules/mpath": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.9.0.tgz", + "integrity": "sha512-ikJRQTk8hw5DEoFVxHG1Gn9T/xcjtdnOKIU1JTmGjZZlg9LST2mBLmcX3/ICIbgJydT2GOc15RnNy5mHmzfSew==", + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/mquery": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/mquery/-/mquery-6.0.0.tgz", + "integrity": "sha512-b2KQNsmgtkscfeDgkYMcWGn9vZI9YoXh802VDEwE6qc50zxBFQ0Oo8ROkawbPAsXCY1/Z1yp0MagqsZStPWJjw==", + "license": "MIT", + "engines": { + "node": ">=20.19.0" + } + }, "node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -700,6 +1034,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", @@ -768,6 +1111,15 @@ "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==" }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/qs": { "version": "6.11.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", @@ -913,6 +1265,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/sift": { + "version": "17.1.3", + "resolved": "https://registry.npmjs.org/sift/-/sift-17.1.3.tgz", + "integrity": "sha512-Rtlj66/b0ICeFzYTuNvX/EF1igRbbnGSvEyT79McoZa/DeGhMyC5pWKOEsZKnpkqtSeovd5FL/bjHWC3CIIvCQ==", + "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", @@ -924,6 +1282,15 @@ "node": ">=10" } }, + "node_modules/sparse-bitfield": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", + "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==", + "license": "MIT", + "dependencies": { + "memory-pager": "^1.0.2" + } + }, "node_modules/statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", @@ -973,6 +1340,18 @@ "nodetouch": "bin/nodetouch.js" } }, + "node_modules/tr46": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", + "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", @@ -990,6 +1369,12 @@ "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==" }, + "node_modules/undici-types": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "license": "MIT" + }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -1014,6 +1399,28 @@ "node": ">= 0.8" } }, + "node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-url": { + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", + "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", + "license": "MIT", + "dependencies": { + "tr46": "^5.1.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", diff --git a/server/package.json b/server/package.json index 7aa0847c..14c2afa1 100644 --- a/server/package.json +++ b/server/package.json @@ -1,4 +1,5 @@ { + "type": "module", "name": "cohort-tools-api", "version": "1.0.0", "description": "ExpressJS API for the Cohort Tools app", @@ -18,8 +19,13 @@ }, "homepage": "https://github.com/ironhack-labs/cohort-tools-project#readme", "dependencies": { + "bcryptjs": "^3.0.3", "cookie-parser": "^1.4.6", + "cors": "^2.8.6", "express": "^4.18.2", + "express-jwt": "^8.5.1", + "jsonwebtoken": "^9.0.3", + "mongoose": "^9.2.2", "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..aa150529 --- /dev/null +++ b/server/routes/auth.routes.js @@ -0,0 +1,103 @@ +import express from "express" +const router = express.Router() +import bcrypt from "bcryptjs"; +import jwt from "jsonwebtoken"; +import userModel from "../models/user.model.js" +import isAuthenticated from "../middleware/jwt.middleware.js"; + +// POST /auth/signup - Creates a new user in the database +router.post("/signup", async (req, res)=>{ + try { + const {username, email, password} = req.body + if (!username || !email || !password){ + return res.status(400).json({message: "Please provide all info"}) + } + + const foundUser = await userModel.findOne({ $or: [{email}, {username}]}) + if (foundUser) { + return res.status(400).json({message: "Email of username already taken"} ) + } + + if (!password.match("^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!@$ %^&*-]).{8,}$",)){ +return res.status(400).json({message: "Password needs at least 8 characters, and numbers"}) + } + + const salts = await bcrypt.genSalt(12) + const hashedPassword = await bcrypt.hash(password, salts) + + const createdUser = await userModel.create({ + username, email, password: hashedPassword + }) + + +return res.status(201).json({message: "User created", createdUser}) + + } catch (error) { + console.log(error) + return res.status(500).json(error) + } +} +) + + +// POST /auth/login - Checks the sent email and password and, if email and password are correct returns a JWT +router.post("/login", async (req, res) => { + try { + const { email, username, password } = req.body + + if (!(email || username) || !password) { + return res.status(400).json({ message: "Please provide all info" }) + } + + const foundUser = await userModel.findOne({ $or: [{ email }, { username }] }) + if (!foundUser) { + return res.status(400).json({ message: "User doesn't exist" }) + } + + // const isValid = await bcrypt.compare(password, foundUser.password) + + if (!bcrypt.compareSync(password, foundUser.password)) { + return res.status(400).json({ message: "Incorrect password" }) + } + + const payload = { + _id: foundUser._id, + username: foundUser.username, + email: foundUser.email, + } + + const authToken = jwt.sign(payload, "t0k3n$ecr3t", { + expiresIn: "6h", + algorithm: "HS256", + }) + + return res.status(200).json({ message: "Successfuly logged in", authToken }) + } catch (error) { + console.log(error) + return res.status(500).json(error) + } +}) + +// GET /auth/verify - Verifies that the JWT sent by the client is valid +router.get("/verify", isAuthenticated, (req, res) => { + console.log(req.auth) + + res.status(200).json("verified") +}) + +// GET /api/users/:id - Retrieves a specific user by id. The route should be protected by the authentication middleware. +router.get ("/api/users/:id", isAuthenticated, async (req, res)=>{ + + try{ + if (isAuthenticated){ +const user = await userModel.findById(req.params.id).select("-password"); +res.status(200).json(user) + } + } + catch (error){ +console.log(error) +res.status(500).json(error) + } + +}) +export default router \ No newline at end of file