mirror of
https://github.com/directus/directus.git
synced 2026-01-29 14:48:02 -05:00
Change payload middleware to service
This commit is contained in:
5
package-lock.json
generated
5
package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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<string, FieldInfo[]>((collections: string[]) =>
|
||||
database
|
||||
.select('*')
|
||||
.from('directus_fields')
|
||||
.whereIn('collection', collections)
|
||||
.then((rows) =>
|
||||
collections.map((collection) => rows.filter((x) => x.collection === collection))
|
||||
)
|
||||
),
|
||||
};
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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<string, any>,
|
||||
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;
|
||||
@@ -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();
|
||||
})
|
||||
);
|
||||
|
||||
62
src/services/payload.ts
Normal file
62
src/services/payload.ts
Normal file
@@ -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<string, any>
|
||||
) => {
|
||||
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<string, any>,
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -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<string, any>, 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' });
|
||||
|
||||
Reference in New Issue
Block a user