From 4b80f4b83b646f9dc0074602bb19f321b7f8bbb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9EBAS8243=E2=80=9C?= <gerd.embruch@uni-hamburg.de> Date: Sat, 10 Aug 2024 13:20:10 +0200 Subject: [PATCH] added routes for user handling --- __tests__/manualREST/users.rest | 34 ++++++++++ controllers/User.js | 110 ++++++++++++++++++++++++++++++-- routes/users.js | 40 +++++++++++- utils/handleDB.js | 16 +++++ validationSchemes/User.js | 14 ++++ 5 files changed, 207 insertions(+), 7 deletions(-) diff --git a/__tests__/manualREST/users.rest b/__tests__/manualREST/users.rest index a7f44ca..f644c77 100644 --- a/__tests__/manualREST/users.rest +++ b/__tests__/manualREST/users.rest @@ -15,6 +15,8 @@ @token = {{login.response.body.accessToken}} @token = {{refreshJWT.response.body.accessToken}} +@userId = {{login.response.body.document.id}} + ################# # HANDLE SIGNUP ################# @@ -79,6 +81,38 @@ Accept: application/json # @name logout delete {{host}}/auth +################# +# HANDLE PROFILE +################# +### get one +# @name getOne +GET {{host}}/users/{{userId}} +Authorization: Bearer {{token}} +Accept: application/json + +### get multiple +# @name getMultiple +GET {{host}}/users +Authorization: Bearer {{token}} +Accept: application/json + +### UPDATE +PATCH {{host}}/users/{{userId}} +content-type: application/json +Authorization: Bearer {{token}} + +{ + "name": "{{name}}", + "username": "{{username}}", + "email": "{{email}}" +} + + +### DELETE +DELETE {{host}}/users/{{userId}} +content-type: application/json +Authorization: Bearer {{token}} + ################# # HANDLE CHANGES ################# diff --git a/controllers/User.js b/controllers/User.js index ac1df7c..10cc72b 100644 --- a/controllers/User.js +++ b/controllers/User.js @@ -1,5 +1,5 @@ import User from "../models/User.js"; -import { createRecord, findOneRecord } from "../utils/handleDB.js"; +import { createRecord, deleteOneRecord, findOneRecord, findRecordByID, findRecords } from "../utils/handleDB.js"; import { prefillDocumentObject, hideConfidentialFields } from '../utils/handleSchemes.js'; /** ******************************************************* @@ -12,9 +12,6 @@ export const createUser = async (req, res, next) => { if (isArtilleryAgent) { req.body.verified = true; } - // console.log("🚀 ~ createUser ~ isArtilleryAgent:", isArtilleryAgent); - // return res.status(200).json({ message: 'tmp abort', isArtilleryAgent }); - // create user object const newRecord = await createRecord(User, prefillDocumentObject(User, req.body)); // remember document but remove confidential info @@ -32,6 +29,82 @@ export const createUser = async (req, res, next) => { }; +/** ******************************************************* + * GET ONE + */ +export const getUser = (req, res, next) => { + return res.json(req.requestedDocument); +}; + +/** ******************************************************* + * GET MULTIPLE + */ +export const getUsers = async (req, res, next) => { + try { + const users = await findRecords(User, {}); + return res.json(users); + } catch (error) { + next(error); + } +}; + + +/** ******************************************************* + * UPDATE ONE + */ +export const updateUser = async (req, res, next) => { + + // check if user is allowed to change data + // if not self editing + if (global.currentUserId !== req.requestedDocument.id) { + // check for current users role + if (!(global.currentUserRole >= 2)) return res.status(403).json({ message: 'Access forbidden' }); + } + + // filter req data by schema field names + const newData = prefillDocumentObject(User, req.body); + // drop password if empty to prevent setting empty + if (!newData.password) delete newData.password; + // merge new data into document + Object.assign(req.requestedDocument, newData); + // try saving + try { + const updatedUser = await req.requestedDocument.save({ new: true }); + const document = hideConfidentialFields(User, updatedUser); + // return msg incl. document + res.json({ msg: 'User successfully updated', document }); + } catch (error) { + next(error); + } +}; + +/** ******************************************************* + * DELETE ONE + */ +export const deleteUser = async (req, res, next) => { + // check if user is allowed to change data + // if not self editing + if (global.currentUserId !== req.params.id) { + // check for current users role + if (!(global.currentUserRole >= 2)) return res.status(403).json({ message: 'Access forbidden' }); + } + + try { + // delete document + console.log("🚀 ~ deleteUser ~ req.params.id:", req.params.id); + const document = await deleteOneRecord(User, req.params.id); + // return msg incl. document + return res.status(200).json({ msg: 'User successfully deleted', document }); + } catch (error) { + console.error(error); + next(error); + } +}; + + +/** ******************************************************* + ####################### FUNCTIONS ####################### + ******************************************************* */ /** ******************************************************* * FIND USER BY MAIL @@ -52,3 +125,32 @@ export const prefetchUserByEmail = async (req, res, next) => { next(error); } }; + + +/** + * get a document based on ID from request param + * save this to req.requestedDocument for further computing + * + * @param req request data + * @param res response, sended to the user + * @param next simply tell the code to go on with next function + * + */ +export const prefetchUser = async (req, res, next) => { + + try { + if (!req.params.id) return res.status(400).json({ msg: 'No ID given' }); + // search for matching document + // FIX Mongoose .populate() für createdBy & updatedBy (updatedBy wird bei Update überschrieben und ist nicht länger populated) + const user = await findRecordByID(User, req.params.id); + // if no matching document was found + if (!user._id) return res.status(404).json({ message: 'Cannot find user' }); + // remember document in res + req.requestedDocument = user; + // going on with the rest of the code + next(); + } catch (error) { + // on error + next(error); + } +}; \ No newline at end of file diff --git a/routes/users.js b/routes/users.js index bda9262..39ac37a 100644 --- a/routes/users.js +++ b/routes/users.js @@ -1,8 +1,9 @@ import { Router } from "express"; -import { createUser } from '../controllers/User.js'; -import { sendVerificationEmail } from '../controllers/Auth.js'; -import { createUserSchema } from "../validationSchemes/User.js"; +import { createUser, deleteUser, getUser, getUsers, prefetchUser, updateUser } from '../controllers/User.js'; +import { gateKeeper, sendVerificationEmail } from '../controllers/Auth.js'; +import { createUserSchema, updateUserSchema } from "../validationSchemes/User.js"; import { validate } from "../utils/handleValidations.js"; +import { verifyAccessToken } from "../utils/handleTokens.js"; const router = Router(); @@ -20,5 +21,38 @@ const router = Router(); */ router.post('/', validate(createUserSchema), createUser, sendVerificationEmail); +/** + * GET ONE + * @header {authorization} Bearer [required] access token + * + * @prop {string} id [required] id of the user to fetch + * + */ +router.get('/:id', verifyAccessToken, prefetchUser, getUser); + +/** + * GET MULTIPLE + * @header {authorization} Bearer [required] access token + * + */ +router.get('/', verifyAccessToken, gateKeeper, getUsers); + + +/** + * UPDATE ONE + * @header {authorization} Bearer [required] access token + * + * @prop {string} id [required] id of the user to fetch + */ +router.patch('/:id', verifyAccessToken, validate(updateUserSchema), prefetchUser, updateUser); + + +/** + * DELETE ONE + * @header {authorization} Bearer [required] access token + * + * @prop {string} id [required] id of the user to fetch + */ +router.delete('/:id', verifyAccessToken, prefetchUser, deleteUser); export default router; \ No newline at end of file diff --git a/utils/handleDB.js b/utils/handleDB.js index 6455dd2..12df065 100644 --- a/utils/handleDB.js +++ b/utils/handleDB.js @@ -142,6 +142,22 @@ export const updateOneRecord = async (newData) => { } }; +/** + * delete one record + * + * @param {mongoose model} model [required] model to search the record in + * @param {mongoose document} record [required] a mongoose document + * + * @return {mongoose document} deleted document + */ +export const deleteOneRecord = async (model, id) => { + try { + return await model.deleteOne({ _id: id }); + } catch (error) { + throw error; + } +}; + /** * Find a document by id and update it * diff --git a/validationSchemes/User.js b/validationSchemes/User.js index c482930..9b5499c 100644 --- a/validationSchemes/User.js +++ b/validationSchemes/User.js @@ -14,3 +14,17 @@ export const createUserSchema = z.object({ message: "Passwords don't match", path: ["confirmPassword"], }); + +// UPDATE +export const updateUserSchema = z.object({ + name: z.string().min(1), + username: z.string().min(1), + email: z.string().email(), + password: z.string().refine((val) => val && isStrongPassword(val), { + message: 'This field must be min 6 characters long and contain uppercase, lowercase, number, specialchar.', + }).nullish().or(z.literal('')), + confirmPassword: z.string().nullish().or(z.literal('')), +}).refine((data) => data.password === data.confirmPassword, { + message: "Passwords don't match", + path: ["confirmPassword"], +}); -- GitLab