From d84a0f9c87276c5d5376a3610663a1f793a583b7 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Mon, 27 May 2024 11:57:25 -0700 Subject: [PATCH 1/3] Started update user route --- Server/controllers/authController.js | 42 +++++++++++++++- Server/db/MongoDB.js | 26 ++++++++++ Server/index.js | 12 ++--- Server/middleware/verifyOwnership.js | 1 + Server/models/user.js | 71 +++++++++++++--------------- Server/routes/authRoute.js | 6 +++ Server/validation/joi.js | 13 ++++- 7 files changed, 125 insertions(+), 46 deletions(-) diff --git a/Server/controllers/authController.js b/Server/controllers/authController.js index a3a43ba0d..3d3dde796 100644 --- a/Server/controllers/authController.js +++ b/Server/controllers/authController.js @@ -1,5 +1,10 @@ const express = require("express"); -const { registerValidation, loginValidation } = require("../validation/joi"); +const { + registerValidation, + loginValidation, + editUserParamValidation, + editUserBodyValidation, +} = require("../validation/joi"); const logger = require("../utils/logger"); require("dotenv").config(); var jwt = require("jsonwebtoken"); @@ -107,4 +112,37 @@ const loginController = async (req, res, next) => { } }; -module.exports = { registerController, loginController }; +const userEditController = async (req, res, next) => { + try { + await editUserParamValidation.validateAsync(req.params); + await editUserBodyValidation.validateAsync(req.body); + } catch (error) { + error.status = 422; + error.service = SERVICE_NAME; + error.message = error.details[0].message; + next(error); + return; + } + + if (req.params.userId !== req.user._id.toString()) { + const error = new Error("Unauthorized access"); + error.status = 401; + error.service = SERVICE_NAME; + next(error); + return; + } + + //Find and update user + try { + const updatedUser = await req.db.updateUser(req, res); + res + .status(200) + .json({ success: true, msg: "User updated", data: updatedUser }); + } catch (error) { + error.service = SERVICE_NAME; + next(error); + return; + } +}; + +module.exports = { registerController, loginController, userEditController }; diff --git a/Server/db/MongoDB.js b/Server/db/MongoDB.js index 44b734954..a2e45f5d6 100644 --- a/Server/db/MongoDB.js +++ b/Server/db/MongoDB.js @@ -55,6 +55,31 @@ const getUserByEmail = async (req, res) => { } }; +/** + * Update a user by ID + * @async + * @param {Express.Request} req + * @param {Express.Response} res + * @returns {Promise} + * @throws {Error} + */ + +const updateUser = async (req, res) => { + const candidateUserId = req.params.userId; + const candidateUser = req.body; + + try { + const updatedUser = await user.findByIdAndUpdate( + candidateUserId, + candidateUser, + { new: true } // Returns updated user instead of pre-update user + ); + return updatedUser; + } catch (error) { + throw error; + } +}; + /** * Get all monitors * @async @@ -173,6 +198,7 @@ module.exports = { connect, insertUser, getUserByEmail, + updateUser, getAllMonitors, getMonitorById, getMonitorsByUserId, diff --git a/Server/index.js b/Server/index.js index db3be459c..63d919b8d 100644 --- a/Server/index.js +++ b/Server/index.js @@ -63,12 +63,6 @@ app.use((req, res, next) => { app.use("/api/v1/auth", authRouter); app.use("/api/v1/monitors", verifyJWT, monitorRouter); -/** - * Error handler middleware - * MUST be called after all routes - */ -app.use(handleErrors); - // Testing email service // app.use('/sendEmail', async (req, res) => { // const response = sendEmail(['veysel.boybay@bluewavelabs.ca'], 'Testing email service', '

Testing Bluewavelabs

'); @@ -86,4 +80,10 @@ app.use("/api/v1/healthy", (req, res) => { } }); +/** + * Error handler middleware + * Should be called last + */ +app.use(handleErrors); + connectDbAndRunServer(app, db); diff --git a/Server/middleware/verifyOwnership.js b/Server/middleware/verifyOwnership.js index 00f7b0787..409303fa4 100644 --- a/Server/middleware/verifyOwnership.js +++ b/Server/middleware/verifyOwnership.js @@ -19,6 +19,7 @@ const verifyOwnership = (Model, paramName) => { // If the userID does not match the document's userID, return a 403 error if (userId.toString() !== doc.userId.toString()) { + console.log("boom"); const error = new Error("Unauthorized access"); error.status = 403; throw error; diff --git a/Server/models/user.js b/Server/models/user.js index d90b484bb..7f3945354 100644 --- a/Server/models/user.js +++ b/Server/models/user.js @@ -1,44 +1,41 @@ const mongoose = require("mongoose"); const bcrypt = require("bcrypt"); -const UserSchema = mongoose.Schema({ - firstname: { - type: String, - required: true, +const UserSchema = mongoose.Schema( + { + firstname: { + type: String, + required: true, + }, + lastname: { + type: String, + required: true, + }, + email: { + type: String, + required: true, + unique: true, + }, + password: { + type: String, + required: true, + }, + profilePicUrl: { + type: String, + }, + isActive: { + type: Boolean, + default: true, + }, + isVerified: { + type: Boolean, + default: false, + }, }, - lastname: { - type: String, - required: true, - }, - email: { - type: String, - required: true, - unique: true, - }, - password: { - type: String, - required: true, - }, - profilePicUrl: { - type: String, - }, - isActive: { - type: Boolean, - default: true, - }, - isVerified: { - type: Boolean, - default: false, - }, - updated_at: { - type: Date, - default: Date.now, - }, - created_at: { - type: Date, - default: Date.now, - }, -}); + { + timestamps: true, + } +); UserSchema.pre("save", async function (next) { if (!this.isModified("password")) { diff --git a/Server/routes/authRoute.js b/Server/routes/authRoute.js index 48a173b92..94f0ce5cc 100644 --- a/Server/routes/authRoute.js +++ b/Server/routes/authRoute.js @@ -1,10 +1,16 @@ const router = require("express").Router(); +const user = require("../models/user"); +const { verifyJWT } = require("../middleware/verifyJWT"); +const { verifyOwnership } = require("../middleware/verifyOwnership"); + const { registerController, loginController, + userEditController, } = require("../controllers/authController"); router.post("/register", registerController); router.post("/login", loginController); +router.post("/user/:userId", verifyJWT, userEditController); module.exports = router; diff --git a/Server/validation/joi.js b/Server/validation/joi.js index ecc19253e..399c243dc 100644 --- a/Server/validation/joi.js +++ b/Server/validation/joi.js @@ -1,5 +1,4 @@ const joi = require("joi"); -const user = require("../models/user"); const loginValidation = joi.object({ email: joi.string().email().required(), @@ -30,10 +29,22 @@ const monitorValidation = joi.object({ interval: joi.number(), }); +const editUserParamValidation = joi.object({ + userId: joi.string().required(), +}); + +const editUserBodyValidation = joi.object({ + firstname: joi.string(), + lastname: joi.string(), + profilePicUrl: joi.string(), +}); + module.exports = { loginValidation, registerValidation, getMonitorByIdValidation, getMonitorsByUserIdValidation, monitorValidation, + editUserParamValidation, + editUserBodyValidation, }; From 78abdd7f704cf405d825e7b845da88e0a9304dbd Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Mon, 27 May 2024 12:03:32 -0700 Subject: [PATCH 2/3] Fixed bug in error handler, finished implementing update user endpoint --- Server/controllers/authController.js | 1 - Server/db/MongoDB.js | 2 +- Server/middleware/handleErrors.js | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/Server/controllers/authController.js b/Server/controllers/authController.js index 3d3dde796..cc476ab54 100644 --- a/Server/controllers/authController.js +++ b/Server/controllers/authController.js @@ -132,7 +132,6 @@ const userEditController = async (req, res, next) => { return; } - //Find and update user try { const updatedUser = await req.db.updateUser(req, res); res diff --git a/Server/db/MongoDB.js b/Server/db/MongoDB.js index a2e45f5d6..b3b269600 100644 --- a/Server/db/MongoDB.js +++ b/Server/db/MongoDB.js @@ -69,7 +69,7 @@ const updateUser = async (req, res) => { const candidateUser = req.body; try { - const updatedUser = await user.findByIdAndUpdate( + const updatedUser = await UserModel.findByIdAndUpdate( candidateUserId, candidateUser, { new: true } // Returns updated user instead of pre-update user diff --git a/Server/middleware/handleErrors.js b/Server/middleware/handleErrors.js index 78764c1e1..a20db8571 100644 --- a/Server/middleware/handleErrors.js +++ b/Server/middleware/handleErrors.js @@ -3,7 +3,7 @@ const logger = require("../utils/logger"); const handleErrors = (error, req, res, next) => { const status = error.status || 500; const message = error.message || "Something went wrong"; - const service = error.errorService || "Unknown service"; + const service = error.service || "Unknown service"; logger.error(error.message, { service: service }); res.status(status).json({ success: false, msg: message }); From 7672cd55300ef6676d92fdd266c279e491a66470 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Mon, 27 May 2024 12:27:57 -0700 Subject: [PATCH 3/3] Removed password from returned user, updated readme --- README.md | 62 ++++++++++++++++++++++++++++++++++++++ Server/db/MongoDB.js | 2 +- Server/routes/authRoute.js | 2 -- 3 files changed, 63 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index a8e1b1f32..63f5625aa 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ BlueWave uptime monitoring application 4. [Endpoints](#endpoints) - POST [/api/v1/auth/register](#post-register) - POST [/api/v1/auth/login](#post-login) + - POST [/api/v1/auth/user/{userId}](#post-auth-user-edit-id) - GET [/api/v1/monitors](#get-monitors) - GET [/api/v1/monitor/{id}](#get-monitor-id) - GET [/api/v1/monitors/user/{userId}](#get-monitors-user-userid) @@ -222,6 +223,67 @@ curl --request POST \ --- +
+POST/api/v1/auth/user/{userId} + +###### Method/Headers + +> | Method/Headers | Value | +> | -------------- | ----- | +> | Method | POST | + +##### Body + +> | Name | Type | Notes | +> | ------------- | -------- | ----------- | +> | firstname | `string` | | +> | lastname | `string` | | +> | profilePicUrl | `string` | | +> | password | `string` | Min 8 chars | + +###### Response Payload + +> | Type | Notes | +> | ------ | ------------------------ | +> | `User` | Returns the updated user | + +##### Sample CURL request + +``` +curl --request POST \ + --url http://localhost:5000/api/v1/auth/user/6654d156634754f789e1f10e \ + --header 'Authorization: ' \ + --header 'Content-Type: application/json' \ + --data '{ + "firstname": "First Name", + "lastname: "Last Name" +}' +``` + +###### Sample Response + +```json +{ + "success": true, + "msg": "User updated", + "data": { + "_id": "6654d156634754f789e1f10e", + "firstname": "First Name", + "lastname": "Last Name", + "email": "me@gmail.com", + "isActive": true, + "isVerified": false, + "createdAt": "2024-05-27T18:30:46.358Z", + "updatedAt": "2024-05-27T19:21:51.747Z", + "__v": 0 + } +} +``` + +
+ +--- +
GET /api/v1/monitors diff --git a/Server/db/MongoDB.js b/Server/db/MongoDB.js index b3b269600..71b6fe44a 100644 --- a/Server/db/MongoDB.js +++ b/Server/db/MongoDB.js @@ -73,7 +73,7 @@ const updateUser = async (req, res) => { candidateUserId, candidateUser, { new: true } // Returns updated user instead of pre-update user - ); + ).select("-password"); return updatedUser; } catch (error) { throw error; diff --git a/Server/routes/authRoute.js b/Server/routes/authRoute.js index 94f0ce5cc..637e4bf5e 100644 --- a/Server/routes/authRoute.js +++ b/Server/routes/authRoute.js @@ -1,7 +1,5 @@ const router = require("express").Router(); -const user = require("../models/user"); const { verifyJWT } = require("../middleware/verifyJWT"); -const { verifyOwnership } = require("../middleware/verifyOwnership"); const { registerController,