diff --git a/src/controllers/expenseController.js b/src/controllers/expenseController.js index 45dd31cb..45460b6a 100644 --- a/src/controllers/expenseController.js +++ b/src/controllers/expenseController.js @@ -1,101 +1,91 @@ const expenseService = require('../services/expenseService'); -const userService = require('../services/userService'); module.exports = { async getAll(req, res) { - const { userId, categories, from, to } = req.query; - const numberUserId = parseInt(userId); - - const expenses = await expenseService.getAllFiltered({ - userId: numberUserId, - categories, - from, - to, - }); - - res.status(200).send(expenses.map(expenseService.format)); + try { + const { userId, categories, from, to } = req.query; + const numberUserId = +userId; + const validCategories = categories ? [categories].flat() : null; + + const expenses = await expenseService.getAllFiltered({ + userId: numberUserId, + categories: validCategories, + from, + to, + }); + + res.status(200).send(expenses.map(expenseService.format)); + } catch (error) { + res.status(500).send('Server error'); + } }, async getOne(req, res) { - const id = parseInt(req.params.id); + try { + const id = +req.params.id; - const expense = await expenseService.getById(id); + const expense = await expenseService.getById(id); - if (!expense) { - res.sendStatus(404); + if (!expense) { + res.status(404).send('No expenses found'); - return; - } + return; + } - res.status(200).send(expenseService.format(expense)); + res.status(200).send(expenseService.format(expense)); + } catch (error) { + res.status(500).send('Server error'); + } }, async create(req, res) { - const { userId, spentAt, title, amount, category, note } = req.body; - - const foundUser = await userService.getById(userId); - - if (!userId || !spentAt || !title || !amount || !category || !foundUser) { - res.sendStatus(400); - - return; + try { + const { userId, spentAt, title, amount, category, note } = req.body; + + const expense = await expenseService.create({ + userId, + spentAt, + title, + amount, + category, + note, + }); + + res.status(201).send(expenseService.format(expense)); + } catch (error) { + res.status(500).send('Server error'); } - - const expense = await expenseService.create({ - userId, - spentAt, - title, - amount, - category, - note, - }); - - res.status(201).send(expenseService.format(expense)); }, async update(req, res) { - const currentId = parseInt(req.params.id); - const { id, userId, spentAt, title, amount, category, note } = req.body; - - const foundExpense = await expenseService.getById(currentId); - - if (!foundExpense) { - res.sendStatus(404); - - return; - } - - if (!id && !userId && !spentAt && !title && !amount && !category && !note) { - res.sendStatus(400); - - return; + try { + const currentId = +req.params.id; + const { id, userId, spentAt, title, amount, category, note } = req.body; + + await expenseService.update({ + currentId, + id, + userId, + spentAt, + title, + amount, + category, + note, + }); + + const updatedExpense = await expenseService.getById(id ?? currentId); + + res.status(200).send(expenseService.format(updatedExpense)); + } catch (error) { + res.status(500).send('Server error'); } - - await expenseService.update({ - currentId, - id, - userId, - spentAt, - title, - amount, - category, - note, - }); - - const updatedExpense = await expenseService.getById(id ?? currentId); - - res.status(200).send(expenseService.format(updatedExpense)); }, async remove(req, res) { - const id = parseInt(req.params.id); - - const foundExpense = await expenseService.getById(id); + try { + const id = +req.params.id; - if (!foundExpense) { - res.sendStatus(404); + await expenseService.remove(id); - return; + res.sendStatus(204); + } catch (error) { + res.status(500).send('Server error'); } - - await expenseService.remove(id); - - res.sendStatus(204); }, }; diff --git a/src/controllers/userController.js b/src/controllers/userController.js index 5a38e754..808383f6 100644 --- a/src/controllers/userController.js +++ b/src/controllers/userController.js @@ -2,74 +2,66 @@ const userService = require('../services/userService'); module.exports = { async getAll(_req, res) { - const users = await userService.getAll(); + try { + const users = await userService.getAll(); - res.status(200).send(users); + res.status(200).send(users); + } catch (error) { + res.status(500).send('Server error'); + } }, async getOne(req, res) { - const id = parseInt(req.params.id); + try { + const id = +req.params.id; - const user = await userService.getById(id); + const user = await userService.getById(id); - if (!user) { - res.sendStatus(404); + if (!user) { + res.sendStatus(404); - return; - } + return; + } - res.status(200).send(user); + res.status(200).send(user); + } catch (error) { + res.status(500).send('Server error'); + } }, async create(req, res) { - const { name } = req.body; + try { + const { name } = req.body; - if (!name) { - res.sendStatus(400); + const user = await userService.create({ name }); - return; + res.status(201).send(user); + } catch (error) { + res.status(500).send('Server error'); } - - const user = await userService.create({ name }); - - res.status(201).send(user); }, async update(req, res) { - const currentId = parseInt(req.params.id); - - const { name, id } = req.body; - - if (!name && !id) { - res.sendStatus(400); + try { + const currentId = +req.params.id; - return; - } + const { name, id } = req.body; - const foundUser = await userService.getById(currentId); + await userService.update({ currentId, id, name }); - if (!foundUser) { - res.sendStatus(404); + const updatedUser = await userService.getById(id ?? currentId); - return; + res.status(200).send(updatedUser); + } catch (error) { + res.status(500).send('Server error'); } - - await userService.update({ currentId, id, name }); - - const updatedUser = await userService.getById(id ?? currentId); - - res.status(200).send(updatedUser); }, async remove(req, res) { - const id = parseInt(req.params.id); + try { + const id = +req.params.id; - const foundUser = await userService.getById(id); + await userService.remove(id); - if (!foundUser) { - res.sendStatus(404); - - return; + res.sendStatus(204); + } catch (error) { + res.status(500).send('Server error'); } - - await userService.remove(id); - - res.sendStatus(204); }, }; diff --git a/src/middleware/expense/index.js b/src/middleware/expense/index.js new file mode 100644 index 00000000..4c466502 --- /dev/null +++ b/src/middleware/expense/index.js @@ -0,0 +1,9 @@ +const { validateCreateBody } = require('./validateCreateBody'); +const { validateGetAllQuery } = require('./validateGetAllQuery'); +const { validateUpdateBody } = require('./validateUpdateBody'); + +module.exports = { + validateGetAllQuery, + validateCreateBody, + validateUpdateBody, +}; diff --git a/src/middleware/expense/validateCreateBody.js b/src/middleware/expense/validateCreateBody.js new file mode 100644 index 00000000..ecd70d71 --- /dev/null +++ b/src/middleware/expense/validateCreateBody.js @@ -0,0 +1,42 @@ +const { + isValidId, + isValidDate, + isValidString, +} = require('../../utils/validators'); + +module.exports.validateCreateBody = (req, res, next) => { + const { userId, spentAt, title, amount, category, note } = req.body; + const errors = []; + + if (!isValidId(userId)) { + errors.push('Invalid property userId in the request body'); + } + + if (!isValidDate(new Date(spentAt))) { + errors.push('Invalid property spentAt in the request body'); + } + + if (!isValidString(title)) { + errors.push('Invalid property title in the request body'); + } + + if (isNaN(+amount)) { + errors.push('Invalid property amount in the request body'); + } + + if (!isValidString(category)) { + errors.push('Invalid property category in the request body'); + } + + if (note && !isValidString(note)) { + errors.push('Invalid property note in the request body'); + } + + if (errors.length) { + res.status(400).send(errors); + + return; + } + + next(); +}; diff --git a/src/middleware/expense/validateGetAllQuery.js b/src/middleware/expense/validateGetAllQuery.js new file mode 100644 index 00000000..c399ac45 --- /dev/null +++ b/src/middleware/expense/validateGetAllQuery.js @@ -0,0 +1,40 @@ +const { + isValidId, + isValidDate, + isValidString, +} = require('../../utils/validators'); + +module.exports.validateGetAllQuery = (req, res, next) => { + const { userId, categories, from, to } = req.query; + const errors = []; + + if (userId && !isValidId(userId)) { + errors.push('Invalid "userId" query'); + } + + if (from && !isValidDate(new Date(from))) { + errors.push('Invalid "from" query'); + } + + if (from && !isValidDate(new Date(to))) { + errors.push('Invalid "to" query'); + } + + if (categories) { + const isValid = Array.isArray(categories) + ? categories.every(isValidString) + : isValidString(categories); + + if (!isValid) { + errors.push('Invalid "categories" query'); + } + } + + if (errors.length) { + res.status(400).send(errors); + + return; + } + + next(); +}; diff --git a/src/middleware/expense/validateUpdateBody.js b/src/middleware/expense/validateUpdateBody.js new file mode 100644 index 00000000..928d9863 --- /dev/null +++ b/src/middleware/expense/validateUpdateBody.js @@ -0,0 +1,46 @@ +const { + isValidId, + isValidDate, + isValidString, +} = require('../../utils/validators'); + +module.exports.validateUpdateBody = (req, res, next) => { + const { id, userId, spentAt, title, amount, category, note } = req.body; + const errors = []; + + if (id && !isValidId(id)) { + errors.push('Invalid property id in the request body'); + } + + if (userId && !isValidId(userId)) { + errors.push('Invalid property userId in the request body'); + } + + if (spentAt && !isValidDate(new Date(spentAt))) { + errors.push('Invalid property spentAt in the request body'); + } + + if (title && !isValidString(title)) { + errors.push('Invalid property title in the request body'); + } + + if (amount && isNaN(+amount)) { + errors.push('Invalid property amount in the request body'); + } + + if (category && !isValidString(category)) { + errors.push('Invalid property category in the request body'); + } + + if (note && !isValidString(note)) { + errors.push('Invalid property note in the request body'); + } + + if (errors.length) { + res.status(400).send(errors); + + return; + } + + next(); +}; diff --git a/src/middleware/shared/checkIfEntityExistsByBodyFieldId.js b/src/middleware/shared/checkIfEntityExistsByBodyFieldId.js new file mode 100644 index 00000000..919c334b --- /dev/null +++ b/src/middleware/shared/checkIfEntityExistsByBodyFieldId.js @@ -0,0 +1,18 @@ +module.exports.checkIfEntityExistsByBodyFieldId = + (model, fieldName) => async (req, res, next) => { + try { + const id = +req.body[fieldName]; + + const entity = await model.findByPk(id); + + if (!entity) { + res.status(404).send(`No items were found with the id ${id}`); + + return; + } + + next(); + } catch (error) { + res.status(500).send('Internal error'); + } + }; diff --git a/src/middleware/shared/checkIfEntityExistsByOptionalBodyFieldId.js b/src/middleware/shared/checkIfEntityExistsByOptionalBodyFieldId.js new file mode 100644 index 00000000..d8fe1099 --- /dev/null +++ b/src/middleware/shared/checkIfEntityExistsByOptionalBodyFieldId.js @@ -0,0 +1,24 @@ +module.exports.checkIfEntityExistsByOptionalBodyFieldId = + (model, fieldName) => async (req, res, next) => { + try { + const id = +req.body[fieldName]; + + if (!id) { + next(); + + return; + } + + const entity = await model.findByPk(id); + + if (!entity) { + res.status(404).send(`No items were found with the id ${id}`); + + return; + } + + next(); + } catch (error) { + res.status(500).send('Internal error'); + } + }; diff --git a/src/middleware/shared/checkIfEntityExistsByParamId.js b/src/middleware/shared/checkIfEntityExistsByParamId.js new file mode 100644 index 00000000..aa6fff56 --- /dev/null +++ b/src/middleware/shared/checkIfEntityExistsByParamId.js @@ -0,0 +1,18 @@ +module.exports.checkIfEntityExistsByParamId = + (model) => async (req, res, next) => { + try { + const id = +req.params.id; + + const entity = await model.findByPk(id); + + if (!entity) { + res.status(404).send(`No items were found with the id ${id}`); + + return; + } + + next(); + } catch (error) { + res.status(500).send('Internal error'); + } + }; diff --git a/src/middleware/shared/index.js b/src/middleware/shared/index.js new file mode 100644 index 00000000..9d179c9f --- /dev/null +++ b/src/middleware/shared/index.js @@ -0,0 +1,17 @@ +const { validateParamId } = require('./validateParamId'); +const { + checkIfEntityExistsByParamId, +} = require('./checkIfEntityExistsByParamId'); +const { + checkIfEntityExistsByBodyFieldId, +} = require('./checkIfEntityExistsByBodyFieldId'); +const { + checkIfEntityExistsByOptionalBodyFieldId, +} = require('./checkIfEntityExistsByOptionalBodyFieldId'); + +module.exports = { + validateParamId, + checkIfEntityExistsByParamId, + checkIfEntityExistsByBodyFieldId, + checkIfEntityExistsByOptionalBodyFieldId, +}; diff --git a/src/middleware/shared/validateParamId.js b/src/middleware/shared/validateParamId.js new file mode 100644 index 00000000..581520b5 --- /dev/null +++ b/src/middleware/shared/validateParamId.js @@ -0,0 +1,13 @@ +const { isValidId } = require('../../utils/validators'); + +module.exports.validateParamId = (req, res, next) => { + const { id } = req.params; + + if (!isValidId(id)) { + res.status(400).send('Invalid id format'); + + return; + } + + next(); +}; diff --git a/src/middleware/user/index.js b/src/middleware/user/index.js new file mode 100644 index 00000000..fe810c25 --- /dev/null +++ b/src/middleware/user/index.js @@ -0,0 +1,7 @@ +const { validateCreateBody } = require('./validateCreateBody'); +const { validateUpdateBody } = require('./validateUpdateBody'); + +module.exports = { + validateCreateBody, + validateUpdateBody, +}; diff --git a/src/middleware/user/validateCreateBody.js b/src/middleware/user/validateCreateBody.js new file mode 100644 index 00000000..59f8031c --- /dev/null +++ b/src/middleware/user/validateCreateBody.js @@ -0,0 +1,13 @@ +const { isValidString } = require('../../utils/validators'); + +module.exports.validateCreateBody = (req, res, next) => { + const { name } = req.body; + + if (!isValidString(name)) { + res.status(400).send('Invalid property name in the request body'); + + return; + } + + next(); +}; diff --git a/src/middleware/user/validateUpdateBody.js b/src/middleware/user/validateUpdateBody.js new file mode 100644 index 00000000..7b450c78 --- /dev/null +++ b/src/middleware/user/validateUpdateBody.js @@ -0,0 +1,26 @@ +const { isValidId, isValidString } = require('../../utils/validators'); + +module.exports.validateUpdateBody = (req, res, next) => { + const { name, id } = req.body; + const errors = []; + + if (!name && !id) { + errors.push('No data to update'); + } + + if (name && !isValidString(name)) { + errors.push('Invalid property name in the request body'); + } + + if (id && !isValidId(id)) { + errors.push('Invalid property id in the request body'); + } + + if (errors.length) { + res.status(400).send(errors); + + return; + } + + next(); +}; diff --git a/src/routes/expenseRoute.js b/src/routes/expenseRoute.js index edba639d..24b8bb9c 100644 --- a/src/routes/expenseRoute.js +++ b/src/routes/expenseRoute.js @@ -1,12 +1,54 @@ const { Router } = require('express'); + const expenseController = require('../controllers/expenseController'); +const { User } = require('../models/User.model'); +const { Expense } = require('../models/Expense.model'); +const { + validateGetAllQuery, + validateCreateBody, + validateUpdateBody, +} = require('../middleware/expense'); +const { + validateParamId, + checkIfEntityExistsByBodyFieldId, + checkIfEntityExistsByParamId, + checkIfEntityExistsByOptionalBodyFieldId, +} = require('../middleware/shared'); const router = Router(); -router.get('/', expenseController.getAll); -router.get('/:id', expenseController.getOne); -router.post('/', expenseController.create); -router.patch('/:id', expenseController.update); -router.delete('/:id', expenseController.remove); +const checkIfUserExistsByUserId = checkIfEntityExistsByBodyFieldId( + User, + 'userId', +); +const checkIfUserExistsByOptionalUserId = + checkIfEntityExistsByOptionalBodyFieldId(User, 'userId'); +const checkIfExpenseExistsByParamId = checkIfEntityExistsByParamId(Expense); + +router.get('/', validateGetAllQuery, expenseController.getAll); +router.get('/:id', validateParamId, expenseController.getOne); + +router.post( + '/', + validateCreateBody, + checkIfUserExistsByUserId, + expenseController.create, +); + +router.patch( + '/:id', + validateParamId, + checkIfExpenseExistsByParamId, + validateUpdateBody, + checkIfUserExistsByOptionalUserId, + expenseController.update, +); + +router.delete( + '/:id', + validateParamId, + checkIfExpenseExistsByParamId, + expenseController.remove, +); module.exports.expenseRouter = router; diff --git a/src/routes/userRoute.js b/src/routes/userRoute.js index 0516364a..378dafbd 100644 --- a/src/routes/userRoute.js +++ b/src/routes/userRoute.js @@ -1,13 +1,37 @@ const { Router } = require('express'); const userController = require('../controllers/userController'); +const { User } = require('../models/User.model'); +const { + checkIfEntityExistsByParamId, + validateParamId, +} = require('../middleware/shared'); +const { + validateCreateBody, + validateUpdateBody, +} = require('../middleware/user'); const router = Router(); +const checkIfUserExistsByParamId = checkIfEntityExistsByParamId(User); + router.get('/', userController.getAll); -router.get('/:id', userController.getOne); -router.post('/', userController.create); -router.patch('/:id', userController.update); -router.delete('/:id', userController.remove); +router.get('/:id', validateParamId, userController.getOne); +router.post('/', validateCreateBody, userController.create); + +router.patch( + '/:id', + validateParamId, + checkIfUserExistsByParamId, + validateUpdateBody, + userController.update, +); + +router.delete( + '/:id', + validateParamId, + checkIfUserExistsByParamId, + userController.remove, +); module.exports.userRoute = router; diff --git a/src/utils/validators.js b/src/utils/validators.js new file mode 100644 index 00000000..76f0e5c1 --- /dev/null +++ b/src/utils/validators.js @@ -0,0 +1,7 @@ +module.exports.isValidId = (id) => Boolean(id && !isNaN(+id)); + +module.exports.isValidString = (name) => + Boolean(typeof name === 'string' && name.trim().length && name.length <= 255); + +module.exports.isValidDate = (date) => + Boolean(date.toString() !== 'Invalid Date');