diff --git a/controllers/chats-controller.js b/controllers/chats-controller.js index 0c236c8..024cc57 100644 --- a/controllers/chats-controller.js +++ b/controllers/chats-controller.js @@ -1,16 +1,6 @@ -const dotenv = require('dotenv'); -dotenv.config(); - -const { validationResult } = require('express-validator'); const chatMessageService = require('../services/chat-message-service'); -const { ErrorHandler } = require('../util/error-handler'); - -exports.getChatMessagesByRoom = async (req, res, next) => { - const errors = validationResult(req); - if (!errors.isEmpty()) { - return next(new ErrorHandler(422, 'Invalid input passed, please check it')); - } +exports.getChatMessagesByRoom = async (req, res) => { const skip = req.query.skip ? +req.query.skip : 0; const limit = req.query.limit ? +req.query.limit : 20; @@ -20,9 +10,8 @@ exports.getChatMessagesByRoom = async (req, res, next) => { skip ); - res.status(200) - .json({ - messages: messages?.data || [], - total: messages?.total || 0, - }); + res.status(200).json({ + messages: messages?.data || [], + total: messages?.total || 0, + }); }; diff --git a/controllers/user-messages-controller.js b/controllers/user-messages-controller.js new file mode 100644 index 0000000..f15acdc --- /dev/null +++ b/controllers/user-messages-controller.js @@ -0,0 +1,57 @@ +const websocketService = require('../services/websocket-service'); +const chatMessageService = require('../services/chat-message-service'); +const eventService = require('../services/event-service'); +const _ = require('lodash'); + +exports.getMessagesByUser = async (req, res) => { + const skip = req.query.skip ? +req.query.skip : 0; + const limit = req.query.limit ? +req.query.limit : 20; + + const messages = await chatMessageService.getLatestChatMessagesByUserId( + req.user._id, + limit, + skip + ); + + res.status(200).json({ + messages: messages?.data || [], + total: messages?.total || 0, + }); +}; + +exports.setMessageRead = async (req, res) => { + const { id } = req.params; + const requestingUser = req.user; + await chatMessageService.setMessageRead(id, requestingUser); + return res.status(200).send(); +}; + +exports.sendMessage = async (req, res) => { + // console.log('sending a test message'); + + // // SEND OASIS EVENT RESOLVED + // setTimeout(async () => { + // const event = await eventService.getEvent('61792b2bc986908179b4e4b5'); + // event.bookmarks.map(async (u) => { + // await websocketService.emitBetResolveNotification(u, event, event.bets[0], 0, 100); + // }); + // }, 2000); + + // // SEND ELON EVENT RESOLVED + // setTimeout(async () => { + // const event = await eventService.getEvent('617950d40a97aa8c14679fd0'); + // event.bookmarks.map(async (u) => { + // await websocketService.emitBetResolveNotification(u, event, event.bets[0], 0, 0); + // }); + // }, 4500); + + // // SEND UEFA CANCELLED + // setTimeout(async () => { + // const event = await eventService.getEvent('61797d87fce07aa1dfc00b5c'); + // event.bookmarks.map(async (u) => { + // await websocketService.emitEventCancelNotification(u, event, event.bets[0]); + // }); + // }, 18000); + + return res.status(200).send({}); +}; diff --git a/helper/index.js b/helper/index.js index 750622a..e639ac5 100644 --- a/helper/index.js +++ b/helper/index.js @@ -1,4 +1,11 @@ +/** + * @param {} req + * returns if the user is an admin and if a userId url param was passed and equal + * to the logged in user. + * TODO: replace with utils/auth.isUserAdminOrSelf + */ exports.isAdmin = (req) => !(req.user.admin === false && req.params.userId !== req.user.id); + exports.generate = (n) => { const add = 1; let max = 12 - add; diff --git a/index.js b/index.js index 45eb227..ef76dcf 100644 --- a/index.js +++ b/index.js @@ -24,13 +24,7 @@ let mongoURL = process.env.DB_CONNECTION; const corsOptions = { origin: '*', credentials: true, - allowedMethods: [ - 'GET', - 'PUT', - 'POST', - 'PATCH', - 'DELETE', - ], + allowedMethods: ['GET', 'PUT', 'POST', 'PATCH', 'DELETE'], allowedHeaders: [ 'Origin', 'X-Requested-With', @@ -41,7 +35,7 @@ const corsOptions = { ], exposedHeaders: ['Content-Length'], preflightContinue: false, -} +}; // Connection to Database async function connectMongoDB() { @@ -84,10 +78,6 @@ async function main() { // Import cors const cors = require('cors'); - // Import middleware for jwt verification - const passport = require('passport'); - require('./util/auth'); - // Initialise server using express const server = express(); const httpServer = http.createServer(server); @@ -135,9 +125,13 @@ async function main() { subClient.subscribe('message'); - // Giving server ability to parse json + // Jwt verification + const passport = require('passport'); + const auth = require('./util/auth'); + auth.setPassportStrategies(); server.use(passport.initialize()); server.use(passport.session()); + server.use(auth.evaluateIsAdmin); adminService.buildRouter(); server.use(adminService.getRootPath(), adminService.getRouter()); @@ -163,11 +157,11 @@ async function main() { const chatRoutes = require('./routes/users/chat-routes'); const notificationEventsRoutes = require('./routes/users/notification-events-routes'); const authRoutes = require('./routes/auth/auth-routes'); + const userMessagesRoutes = require('./routes/users/user-messages-routes'); const auth0ShowcaseRoutes = require('./routes/auth0-showcase-routes'); server.use(auth0ShowcaseRoutes); - // Using Routes server.use('/api/event', eventRoutes); server.use('/api/event', passport.authenticate('jwt', { session: false }), secureEventRoutes); @@ -180,9 +174,14 @@ async function main() { secureBetTemplateRoute ); server.use('/webhooks/twitch/', twitchWebhook); - server.use('/api/chat', chatRoutes); + server.use('/api/chat', passport.authenticate('jwt', { session: false }), chatRoutes); server.use('/api/notification-events', notificationEventsRoutes); server.use('/api/auth', authRoutes); + server.use( + '/api/user-messages', + passport.authenticate('jwt', { session: false }), + userMessagesRoutes + ); // Error handler middleware // eslint-disable-next-line no-unused-vars diff --git a/package-lock.json b/package-lock.json index 512f484..1596a34 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2789,9 +2789,9 @@ } }, "@wallfair.io/wallfair-commons": { - "version": "1.7.11", - "resolved": "https://registry.npmjs.org/@wallfair.io/wallfair-commons/-/wallfair-commons-1.7.11.tgz", - "integrity": "sha512-z4KnbpFq7NIelVQtcfC7jhHSQtZLNF2TNNKIQXkE/4VsCBuDsEGsXdOFc/VO66WgrudNxQt9pjQik4ehoICRCA==" + "version": "1.7.17", + "resolved": "https://registry.npmjs.org/@wallfair.io/wallfair-commons/-/wallfair-commons-1.7.17.tgz", + "integrity": "sha512-KiSxd2R8uJ4wGYA68pSz5CPFycRh6zan1TYk6fTauFXhCfb0p+qN/M/bNJv95mAE4ayvtuPZp10LYPnkSSA+kw==" }, "abbrev": { "version": "1.1.1", diff --git a/routes/users/chat-routes.js b/routes/users/chat-routes.js index 0037855..2eeb90b 100644 --- a/routes/users/chat-routes.js +++ b/routes/users/chat-routes.js @@ -1,11 +1,12 @@ const router = require('express').Router(); const { check } = require('express-validator'); const chatsController = require('../../controllers/chats-controller'); +const { validateRequest } = require('../../util/request-validator'); router.get( '/chat-messages/:roomId', - [check('roomId').notEmpty()], - chatsController.getChatMessagesByRoom, + [check('roomId').notEmpty(), check('roomId').isMongoId()], + validateRequest(chatsController.getChatMessagesByRoom) ); module.exports = router; diff --git a/routes/users/user-messages-routes.js b/routes/users/user-messages-routes.js new file mode 100644 index 0000000..feb9e46 --- /dev/null +++ b/routes/users/user-messages-routes.js @@ -0,0 +1,16 @@ +const router = require('express').Router(); +const { check } = require('express-validator'); +const { validateRequest } = require('../../util/request-validator'); +const controller = require('../../controllers/user-messages-controller'); + +router.get('/', controller.getMessagesByUser); + +router.put('/:id/read', [check('id').isMongoId()], validateRequest(controller.setMessageRead)); + +router.post( + '/', + [check('userId').exists(), check('userId'.isMongoId)], + validateRequest(controller.sendMessage) +); + +module.exports = router; diff --git a/services/admin-service.js b/services/admin-service.js index 02b363c..80780cc 100644 --- a/services/admin-service.js +++ b/services/admin-service.js @@ -11,6 +11,7 @@ const AdminBro = require('admin-bro'); const AdminBroExpress = require('@admin-bro/express'); const AdminBroMongoose = require('@admin-bro/mongoose'); const { Router } = require('express'); +const _ = require('lodash'); AdminBro.registerAdapter(AdminBroMongoose); @@ -148,14 +149,12 @@ exports.initialize = function () { if (dbBet) { const event = await eventService.getEvent(dbBet.event); - + if (event.bookmarks) { + // union of users who placed a bet and have bookmarked the event + userIds = _.union(userIds, event.bookmarks); + } for (const userId of userIds) { - websocketService.emitEventCancelNotification( - userId, - dbBet.event, - event.name, - dbBet.reasonOfCancellation - ); + websocketService.emitEventCancelNotification(userId, event, dbBet); } } @@ -191,7 +190,7 @@ exports.initialize = function () { const indexOutcome = request.fields.index; const { evidenceActual } = request.fields; const { evidenceDescription } = request.fields; - + let stillToNotifyUsersIds = event.bookmarks; if (bet.status !== 'active' && bet.status !== 'closed') { context.record.params.message = 'Event can only be resolved if it is active or closed'; @@ -256,15 +255,22 @@ exports.initialize = function () { await userService.increaseAmountWon(userId, winToken); // send notification to this user - websocketService.emitBetResolveNotification( + await websocketService.emitBetResolveNotification( userId, - id, - bet.marketQuestion, - bet.outcomes[indexOutcome].name, + event, + bet, Math.round(investedValues[userId]), - event.previewImageUrl, winToken ); + stillToNotifyUsersIds = stillToNotifyUsersIds.filter((u) => u != userId); + } + + if (stillToNotifyUsersIds) { + // the users who bookmarked but didn't place a bet + stillToNotifyUsersIds.map( + async (u) => + await websocketService.emitEventResolvedNotification(u, event, bet) + ); } } return { diff --git a/services/bet-service.js b/services/bet-service.js index c5290b5..41f4fc9 100644 --- a/services/bet-service.js +++ b/services/bet-service.js @@ -9,10 +9,8 @@ const { toScaledBigInt, fromScaledBigInt } = require('../util/number-helper'); const { calculateAllBetsStatus, filterPublishedBets } = require('../services/event-service'); exports.listBets = async (q) => { - return Bet.find(q).populate('event') - .map(calculateAllBetsStatus) - .map(filterPublishedBets); -} + return Bet.find(q).populate('event').map(calculateAllBetsStatus).map(filterPublishedBets); +}; exports.filterBets = async ( type = 'all', @@ -24,7 +22,7 @@ exports.filterBets = async ( status = 'active', published = true, resolved = false, - canceled = false, + canceled = false ) => { const eventQuery = {}; const betQuery = {}; @@ -64,7 +62,6 @@ exports.filterBets = async ( return result; }; - exports.editBet = async (betId, betData) => { const updatedEvent = await Bet.findByIdAndUpdate(betId, betData, { new: true }); return updatedEvent; @@ -132,7 +129,7 @@ exports.placeBet = async (userId, betId, amount, outcome, minOutcomeTokens) => { producer: 'user', producerId: userId, data: { bet, trade: response.trade, user, event }, - broadcast: true + broadcast: true, }); return response; } catch (err) { @@ -200,7 +197,7 @@ exports.refundUserHistory = async (bet, session) => { bet, userIds, }, - broadcast: true + broadcast: true, }); return userIds; @@ -253,7 +250,7 @@ exports.resolve = async ({ producer: 'system', producerId: 'notification-service', data: { bet, event }, - broadcast: true + broadcast: true, }); } catch (err) { console.debug(err); @@ -297,18 +294,16 @@ exports.resolve = async ({ producer: 'system', producerId: 'notification-service', data: { bet, event, userId, winToken: winToken.toString() }, - broadcast: true + broadcast: true, }); // send notification to this user websocketService.emitBetResolveNotification( // eslint-disable-line no-unsafe-finally userId, - betId, - bet.marketQuestion, - bet.outcomes[outcomeIndex].name, + event, + bet, +investedValues[userId], - event.previewImageUrl, winToken ); } @@ -349,14 +344,9 @@ exports.cancel = async (bet, cancellationReason) => { reasonOfCancellation: dbBet.reasonOfCancellation, previewImageUrl: event.previewImageUrl, }, - broadcast: true + broadcast: true, }); - websocketService.emitEventCancelNotification( - userId, - dbBet.event, - event.name, - dbBet.reasonOfCancellation - ); + websocketService.emitEventCancelNotification(userId, event, dbBet); } } diff --git a/services/chat-message-service.js b/services/chat-message-service.js index 2bbd075..b911c35 100644 --- a/services/chat-message-service.js +++ b/services/chat-message-service.js @@ -1,6 +1,6 @@ const mongoose = require('mongoose'); -// Import ChatMessage model const { ChatMessage } = require('@wallfair.io/wallfair-commons').models; +const { ForbiddenError, NotFoundError } = require('../util/error-handler'); exports.getChatMessagesByEvent = async (eventId) => ChatMessage.find({ roomId: eventId }); @@ -24,9 +24,9 @@ exports.getLatestChatMessagesByRoom = async (roomId, limit = 100, skip = 0) => { $group: { _id: null, - count: { $sum: 1 } - } - } + count: { $sum: 1 }, + }, + }, ], data: [ { $skip: skip }, @@ -53,19 +53,83 @@ exports.getLatestChatMessagesByRoom = async (roomId, limit = 100, skip = 0) => }, }, }, - } - ] - } + }, + ], + }, }, - { $unwind: "$total" }, + { $unwind: '$total' }, { $project: { - total: "$total.count", - data: "$data", + total: '$total.count', + data: '$data', }, }, - ]).exec().then(items => items[0]); + ]) + .exec() + .then((items) => items[0]); -exports.createChatMessage = async (data) => ChatMessage.create(data); +exports.createChatMessage = async (type, userId, roomId, message, payload) => + ChatMessage.create({ + type, + userId, + roomId, + message, + payload, + }); exports.saveChatMessage = async (chatMessage) => chatMessage.save(); + +exports.getLatestChatMessagesByUserId = async (userId, limit = 100, skip = 0) => + ChatMessage.aggregate([ + { + $match: { userId: mongoose.Types.ObjectId(userId), read: { $exists: false } }, + }, + { $sort: { date: -1 } }, + { + $facet: { + total: [ + { + $group: { + _id: null, + count: { $sum: 1 }, + }, + }, + ], + data: [ + { $skip: skip }, + { $limit: limit }, + { + $project: { + userId: 1, + roomId: 1, + type: 1, + message: 1, + date: 1, + payload: 1, + }, + }, + ], + }, + }, + { $unwind: '$total' }, + { + $project: { + total: '$total.count', + data: '$data', + }, + }, + ]) + .exec() + .then((items) => items[0]); + +exports.setMessageRead = async (messageId, requestingUser) => { + const message = await ChatMessage.findById(messageId); + if (!message) { + throw new NotFoundError(); + } + if (!requestingUser?.admin && message.userId.toString() !== requestingUser._id.toString()) { + throw new ForbiddenError(); + } + message.read = new Date(); + await message.save(message); +}; diff --git a/services/websocket-service.js b/services/websocket-service.js index e6d6e48..5178a27 100644 --- a/services/websocket-service.js +++ b/services/websocket-service.js @@ -20,7 +20,9 @@ exports.handleChatMessage = async function (socket, data, userId) { console.debug(LOG_TAG, `user ${userId} sends message "${message}" to room ${roomId}`); - await persist(data); + if (data) { + await ChatMessageService.createChatMessage(data); + } emitToAllByEventId(roomId, 'chatMessage', responseData); } catch (error) { @@ -71,7 +73,7 @@ exports.emitBetStarted = async (bet) => { eventId: event.id, bet, type: 'BET_STARTED', - } + }; emitToAllByEventId(event.id, 'BET_STARTED', payload); }; @@ -131,59 +133,63 @@ exports.emitBetCreatedByEventId = async (eventId, userId, betId, title) => { await handleBetMessage(eventId, 'betCreated', betCreationData); }; +const handleBetMessage = async (eventId, emitEventName, data) => { + emitToAllByEventId(eventId, emitEventName, data); +}; + exports.emitEventStartNotification = (userId, eventId, eventName) => { console.log(userId, eventId, eventName); // const message = `The event ${eventName} begins in 60s. Place your token.`; // emitToAllByUserId(userId, 'notification', { type: notificationTypes.EVENT_START, eventId, message }); }; -exports.emitBetResolveNotification = ( - userId, - betId, - betQuestion, - betOutcome, - amountTraded, - eventPhotoUrl, - tokensWon -) => { - let message = `The bet ${betQuestion} was resolved. The outcome is ${betOutcome}. You traded ${amountTraded} WFAIR.`; - if (tokensWon > 0) { - message += ` You won ${tokensWon} WFAIR.`; - } - - emitToAllByUserId(userId, 'notification', { - type: notificationTypes.EVENT_RESOLVE, - betId, - message, - betQuestion, - betOutcome, +exports.emitBetResolveNotification = async (userId, event, bet, amountTraded, tokensWon) => { + const outcome = + tokensWon > 0 ? `Don't forget to cash out!` : `Unfortunately you haven't won this time.`; + const message = `Your favourite [event] has finished. ${outcome}`; + await emitUserMessage(notificationTypes.EVENT_RESOLVE, userId, message, { + imageUrl: event.previewImageUrl, + eventId: event.id, + eventName: event.name, + eventSlug: event.slug, + betId: bet.id, + betQuestion: bet?.marketQuestion, amountTraded, - eventPhotoUrl, tokensWon, }); }; -exports.emitEventCancelNotification = (userId, eventId, eventName, cancellationDescription) => { - console.log(userId, eventId, eventName, cancellationDescription); - const message = `The event ${eventName} was cancelled due to ${cancellationDescription}.`; - emitToAllByUserId(userId, 'notification', { type: notificationTypes.EVENT_CANCEL, eventId, message }); +exports.emitEventResolvedNotification = async (userId, event, bet) => { + const message = `Your favourite [event] has finished.`; + await emitUserMessage(notificationTypes.EVENT_RESOLVE, userId, message, { + imageUrl: event?.previewImageUrl, + eventId: event?.id, + eventName: event?.name, + eventSlug: event?.slug, + betQuestion: bet?.marketQuestion, + }); }; -const handleBetMessage = async (eventId, emitEventName, data) => { - // await persist(data); TODO: Check if we need to persist these types of messages - emitToAllByEventId(eventId, emitEventName, data); -}; +exports.emitEventCancelNotification = async (userId, event, bet) => { + let { reasonOfCancellation, marketQuestion } = bet; -const persist = async (data) => { - if (data) { - const chatMessage = await ChatMessageService.createChatMessage(data); - await ChatMessageService.saveChatMessage(chatMessage); + let message = `Your favourite [event] was cancelled`; + if (reasonOfCancellation) { + message = message + ': ' + reasonOfCancellation; + } else { + message = message + '.'; } + await emitUserMessage(notificationTypes.EVENT_CANCEL, userId, message, { + imageUrl: event?.previewImageUrl, + eventId: event?.id, + eventName: event?.name, + eventSlug: event?.slug, + betQuestion: marketQuestion, + }); }; const emitToAllByEventId = (eventId, emitEventName, data) => { console.debug(LOG_TAG, `emitting event "${emitEventName}" to all in event room ${eventId}`); - // io.of('/').to(eventId.toString()).emit(emitEventName, data); pubClient.publish( 'message', JSON.stringify({ @@ -194,15 +200,24 @@ const emitToAllByEventId = (eventId, emitEventName, data) => { ); }; -const emitToAllByUserId = (userId, emitEventName, data) => { - console.debug(LOG_TAG, `emitting event "${emitEventName}" to all in user room ${userId}`); - // io.of('/').to(userId.toString()).emit(emitEventName, {date: new Date(), ...data}); +/** + * Creates and pushes over the websocket a UserMessage. + */ +const emitUserMessage = async (type, userId, message, payload) => { + await ChatMessageService.createChatMessage(type, userId, null, message, payload); + if (!userId) { + console.error( + LOG_TAG, + 'websocket-service: emitUserMessage was called without a userId, skipping it.' + ); + return; + } pubClient.publish( 'message', JSON.stringify({ to: userId.toString(), - event: emitEventName, - data: { date: new Date(), ...data }, + event: 'notification', + data: { type, userId, message, ...payload }, }) ); }; diff --git a/util/auth.js b/util/auth.js index ed87a93..1085623 100644 --- a/util/auth.js +++ b/util/auth.js @@ -4,40 +4,69 @@ const ExtractJWT = require('passport-jwt').ExtractJwt; // Import User Service const userService = require('../services/user-service'); -passport.use( - 'jwt', - new JWTstrategy( - { - secretOrKey: process.env.JWT_KEY, - jwtFromRequest: ExtractJWT.fromAuthHeaderAsBearerToken(), - }, - async (token, done) => { - try { - const user = await userService.getUserById(token.userId); - return done(null, user); - } catch (error) { - done(error); +exports.setPassportStrategies = () => { + passport.use( + 'jwt', + new JWTstrategy( + { + secretOrKey: process.env.JWT_KEY, + jwtFromRequest: ExtractJWT.fromAuthHeaderAsBearerToken(), + }, + async (token, done) => { + try { + const user = await userService.getUserById(token.userId); + return done(null, user); + } catch (error) { + done(error); + } } - } - ) -); -passport.use( - 'jwt_admin', - new JWTstrategy( - { - secretOrKey: process.env.JWT_KEY, - jwtFromRequest: ExtractJWT.fromAuthHeaderAsBearerToken(), - }, - async (token, done) => { - try { - let user = await userService.getUserById(token.userId); - if (!user.admin) { - user = undefined; + ) + ); + passport.use( + 'jwt_admin', + new JWTstrategy( + { + secretOrKey: process.env.JWT_KEY, + jwtFromRequest: ExtractJWT.fromAuthHeaderAsBearerToken(), + }, + async (token, done) => { + try { + let user = await userService.getUserById(token.userId); + if (!user.admin) { + user = undefined; + } + return done(null, user); + } catch (error) { + done(error); } - return done(null, user); - } catch (error) { - done(error); } + ) + ); +}; + +/** + * Adds req.isAdmin that indicates if the logged in user + * is an admin + */ +exports.evaluateIsAdmin = (req, res, next) => { + return passport.authenticate('jwt', { session: false }, function (err, user) { + if (err) { + console.log(err); } - ) -); + req.isAdmin = !err && user && user.admin; + next(); + })(req, res, next); +}; + +/** + * Returns if the current logged in user is allowed to perform an action on a userId + * provided in the request querystring or body + * @param {} req an http request + * @param {} userPropName optional name of property to look for + */ +exports.isUserAdminOrSelf = (req, userPropName = 'userId') => { + if (req.isAdmin) return true; + const actionableUserId = + req.param[userPropName] || req.query[userPropName] || req.query['user-id']; + return req.user?.id?.toString() === actionableUserId?.toString(); +}; diff --git a/util/error-handler.js b/util/error-handler.js index e842497..1a6f422 100644 --- a/util/error-handler.js +++ b/util/error-handler.js @@ -1,26 +1,48 @@ -const logger = require('../util/logger') +const logger = require('../util/logger'); class ErrorHandler extends Error { - constructor(statusCode, message) { + constructor(statusCode, message, errors) { super(); this.statusCode = statusCode; this.message = message; + this.errors = errors; } } const handleError = (err, res) => { - const { statusCode = 500, message } = err; - - logger.error(err) + const { statusCode = 500, message, errors } = err; + logger.error(err); return res.status(statusCode).json({ status: 'error', statusCode, message, + errors, }); }; +class ForbiddenError extends ErrorHandler { + constructor() { + super(403, 'The credentials provided are insufficient to access the requested resource'); + } +} + +class NotFoundError extends ErrorHandler { + constructor() { + super(404, "The requested resource wasn't found"); + } +} + +class ValidationError extends ErrorHandler { + constructor(errors) { + super(422, 'Invalid input passed, please check it.', errors); + } +} + module.exports = { ErrorHandler, handleError, + ForbiddenError, + NotFoundError, + ValidationError, }; diff --git a/util/request-validator.js b/util/request-validator.js new file mode 100644 index 0000000..fe96dfb --- /dev/null +++ b/util/request-validator.js @@ -0,0 +1,14 @@ +const { validationResult } = require('express-validator'); +const { ValidationError } = require('./error-handler'); + +/** + * Checks if the request is valid, and eventually returns an http 422 error + */ +exports.validateRequest = (nextController) => (req, res, next) => { + const result = validationResult(req); + if (result.isEmpty()) { + return nextController(req, res, next); + } + + next(new ValidationError(result.array())); +};