From db6d43988dff979d1687039e4804e011bb116c24 Mon Sep 17 00:00:00 2001 From: rijkvanzanten Date: Thu, 25 Jun 2020 17:59:11 -0400 Subject: [PATCH] Change payload middleware to service --- package-lock.json | 5 --- package.json | 1 - src/app.ts | 2 - src/loaders.ts | 33 ---------------- src/middleware/init-loaders.ts | 14 ------- src/middleware/process-payload.ts | 63 ------------------------------- src/routes/items.ts | 9 +++-- src/services/payload.ts | 62 ++++++++++++++++++++++++++++++ src/services/users.ts | 8 +++- 9 files changed, 74 insertions(+), 123 deletions(-) delete mode 100644 src/loaders.ts delete mode 100644 src/middleware/init-loaders.ts delete mode 100644 src/middleware/process-payload.ts create mode 100644 src/services/payload.ts diff --git a/package-lock.json b/package-lock.json index 15d5616134..3f88056850 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1107,11 +1107,6 @@ "assert-plus": "^1.0.0" } }, - "dataloader": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/dataloader/-/dataloader-2.0.0.tgz", - "integrity": "sha512-YzhyDAwA4TaQIhM5go+vCLmU0UikghC/t9DTQYZR2M/UvZ1MdOhPezSDZcjj9uqQJOMqjLcpWtyW2iNINdlatQ==" - }, "date-utils": { "version": "1.2.21", "resolved": "https://registry.npmjs.org/date-utils/-/date-utils-1.2.21.tgz", diff --git a/package.json b/package.json index 0a865183ac..fd57d832cf 100644 --- a/package.json +++ b/package.json @@ -66,7 +66,6 @@ "atob": "^2.1.2", "bcrypt": "^5.0.0", "body-parser": "^1.19.0", - "dataloader": "^2.0.0", "dotenv": "^8.2.0", "express": "^4.17.1", "express-async-handler": "^1.1.4", diff --git a/src/app.ts b/src/app.ts index c07c0e673a..cfec9db7be 100644 --- a/src/app.ts +++ b/src/app.ts @@ -7,7 +7,6 @@ import logger from 'express-pino-logger'; import { errorHandler } from './error'; -import initLoaders from './middleware/init-loaders'; import extractToken from './middleware/extract-token'; import authenticate from './middleware/authenticate'; @@ -33,7 +32,6 @@ const app = express() .disable('x-powered-by') .use(logger()) .use(bodyParser.json()) - .use(initLoaders) .use(extractToken) .use(authenticate) .use('/activity', activityRouter) diff --git a/src/loaders.ts b/src/loaders.ts deleted file mode 100644 index fdeefd22ce..0000000000 --- a/src/loaders.ts +++ /dev/null @@ -1,33 +0,0 @@ -/** - * Loaders are individual DataLoader instances that can be used to query often used information more - * efficiently. This is relied on for fetching field information for example, seeing that's an - * operation we'll do often in various middleware checks - */ - -import DataLoader from 'dataloader'; -import { FieldInfo } from './types/field'; -import database from './database'; - -async function getCollections(keys: string[]) { - const records = await database - .select('*') - .from('directus_collections') - .whereIn('collection', keys); - - return keys.map((key) => records.find((collection) => collection.collection === key)); -} - -export default function createSystemLoaders() { - return { - collections: new DataLoader(getCollections), - fieldsByCollection: new DataLoader((collections: string[]) => - database - .select('*') - .from('directus_fields') - .whereIn('collection', collections) - .then((rows) => - collections.map((collection) => rows.filter((x) => x.collection === collection)) - ) - ), - }; -} diff --git a/src/middleware/init-loaders.ts b/src/middleware/init-loaders.ts deleted file mode 100644 index 5d61de7e8a..0000000000 --- a/src/middleware/init-loaders.ts +++ /dev/null @@ -1,14 +0,0 @@ -/** - * Sets up the data loaders for this individual request. This allows us to reuse the same system - * loaders in the other middleware and route handlers - */ - -import { RequestHandler } from 'express'; -import createSystemLoaders from '../loaders'; - -const initLoaders: RequestHandler = (req, res, next) => { - req.loaders = createSystemLoaders(); - next(); -}; - -export default initLoaders; diff --git a/src/middleware/process-payload.ts b/src/middleware/process-payload.ts deleted file mode 100644 index dbca919bcd..0000000000 --- a/src/middleware/process-payload.ts +++ /dev/null @@ -1,63 +0,0 @@ -/** - * Will check the fields system table for any special operations that need to be done on the field - * value, this can include hashing the value or generating a UUID - */ - -import { RequestHandler } from 'express'; -import asyncHandler from 'express-async-handler'; -import { FieldInfo } from '../types/field'; -import bcrypt from 'bcrypt'; -import { v4 as uuidv4 } from 'uuid'; - -type Operation = 'create' | 'update'; - -/** - * @TODO - * - * Move this out of the middleware into a service - */ - -const processPayload = (operation: Operation) => { - const middleware: RequestHandler = asyncHandler(async (req, res, next) => { - const fieldsInCollection = await req.loaders.fieldsByCollection.load(req.collection); - - const specialFields = fieldsInCollection.filter((field) => { - if (field instanceof Error) return false; - return field.special !== null; - }); - - for (const field of specialFields) { - req.body[field.field] = await processField(req.collection, field, req.body, operation); - } - - next(); - }); - - return middleware; -}; - -async function processField( - collection: string, - field: FieldInfo, - payload: Record, - operation: Operation -) { - switch (field.special) { - case 'hash': - return await genHash(payload[field.field]); - case 'uuid': - return await genUUID(operation); - } -} - -async function genHash(value: string | number) { - return await bcrypt.hash(value, Number(process.env.SALT_ROUNDS)); -} - -async function genUUID(operation: Operation) { - if (operation === 'create') { - return uuidv4(); - } -} - -export default processPayload; diff --git a/src/routes/items.ts b/src/routes/items.ts index 1d0c4fbeec..dfecda47be 100644 --- a/src/routes/items.ts +++ b/src/routes/items.ts @@ -6,7 +6,7 @@ import validateCollection from '../middleware/validate-collection'; import validateSingleton from '../middleware/validate-singleton'; import validateQuery from '../middleware/validate-query'; import * as MetaService from '../services/meta'; -import processPayload from '../middleware/process-payload'; +import * as PayloadService from '../services/payload'; const router = express.Router(); @@ -14,9 +14,9 @@ router.post( '/:collection', validateCollection, validateSingleton, - processPayload('create'), asyncHandler(async (req, res) => { - await createItem(req.params.collection, req.body); + const payload = await PayloadService.processValues('create', req.collection, req.body); + await createItem(req.params.collection, payload); res.status(200).end(); }) ); @@ -55,7 +55,8 @@ router.patch( '/:collection/:pk', validateCollection, asyncHandler(async (req, res) => { - await updateItem(req.params.collection, req.params.pk, req.body); + const payload = await PayloadService.processValues('update', req.collection, req.body); + await updateItem(req.params.collection, req.params.pk, payload); return res.status(200).end(); }) ); diff --git a/src/services/payload.ts b/src/services/payload.ts new file mode 100644 index 0000000000..5a99765781 --- /dev/null +++ b/src/services/payload.ts @@ -0,0 +1,62 @@ +/** + * # PayloadService + * + * Process a given payload for a collection to ensure the special fields (hash, uuid, date etc) are + * handled correctly. + */ + +import { FieldInfo } from '../types/field'; +import bcrypt from 'bcrypt'; +import { v4 as uuidv4 } from 'uuid'; +import database from '../database'; +import { clone } from 'lodash'; + +/** + * Process and update all the special fields in the given payload + * + * @param collection Collection the payload goes in + * @param operation If this is on create or on update + * @param payload The actual payload itself + * @returns The updated payload + */ +export const processValues = async ( + operation: 'create' | 'update', + collection: string, + payload: Record +) => { + const processedPayload = clone(payload); + const specialFieldsInCollection = await database + .select('field', 'special') + .from('directus_fields') + .where({ collection: collection }) + .whereNotNull('special'); + + for (const field of specialFieldsInCollection) { + processedPayload[field.field] = await processField(field, processedPayload, operation); + } + + return processedPayload; +}; + +async function processField( + field: FieldInfo, + payload: Record, + operation: 'create' | 'update' +) { + switch (field.special) { + case 'hash': + return await genHash(payload[field.field]); + case 'uuid': + return await genUUID(operation); + } +} + +async function genHash(value: string | number) { + return await bcrypt.hash(value, Number(process.env.SALT_ROUNDS)); +} + +async function genUUID(operation: 'create' | 'update') { + if (operation === 'create') { + return uuidv4(); + } +} diff --git a/src/services/users.ts b/src/services/users.ts index 6448632d4a..a2669356a9 100644 --- a/src/services/users.ts +++ b/src/services/users.ts @@ -5,6 +5,7 @@ import { sendInviteMail } from '../mail'; import database from '../database'; import APIError, { ErrorCode } from '../error'; import bcrypt from 'bcrypt'; +import * as PayloadService from '../services/payload'; export const createUser = async (data: Record, query?: Query) => { return await ItemsService.createItem('directus_users', data, query); @@ -27,7 +28,12 @@ export const deleteUser = async (pk: string | number) => { }; export const inviteUser = async (email: string, role: string) => { - await createUser({ email, role, status: 'invited' }); + const userPayload = await PayloadService.processValues('create', 'directus_users', { + email, + role, + status: 'invited', + }); + await createUser(userPayload); const payload = { email }; const token = jwt.sign(payload, process.env.SECRET, { expiresIn: '7d' });