diff --git a/src/routes/folders.ts b/src/routes/folders.ts index b8208addf1..42baf311e2 100644 --- a/src/routes/folders.ts +++ b/src/routes/folders.ts @@ -5,7 +5,6 @@ import validateQuery from '../middleware/validate-query'; import useCollection from '../middleware/use-collection'; import * as FoldersService from '../services/folders'; import * as ActivityService from '../services/activity'; -import * as PayloadService from '../services/payload'; const router = express.Router(); @@ -13,8 +12,7 @@ router.post( '/', useCollection('directus_folders'), asyncHandler(async (req, res) => { - const payload = await PayloadService.processValues('create', req.collection, req.body); - const record = await FoldersService.createFolder(payload, req.sanitizedQuery); + const record = await FoldersService.createFolder(req.body, req.sanitizedQuery); ActivityService.createActivity({ action: ActivityService.Action.CREATE, @@ -55,11 +53,9 @@ router.patch( '/:pk', useCollection('directus_folders'), asyncHandler(async (req, res) => { - const payload = await PayloadService.processValues('create', req.collection, req.body); - const record = await FoldersService.updateFolder( req.params.pk, - payload, + req.body, req.sanitizedQuery ); diff --git a/src/routes/items.ts b/src/routes/items.ts index 75df3e9af3..ff566f4324 100644 --- a/src/routes/items.ts +++ b/src/routes/items.ts @@ -6,7 +6,6 @@ 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 * as PayloadService from '../services/payload'; import * as ActivityService from '../services/activity'; const router = express.Router(); @@ -16,8 +15,7 @@ router.post( validateCollection, validateSingleton, asyncHandler(async (req, res) => { - const payload = await PayloadService.processValues('create', req.collection, req.body); - const item = await createItem(req.params.collection, payload); + const item = await createItem(req.params.collection, req.body); ActivityService.createActivity({ action: ActivityService.Action.CREATE, @@ -67,8 +65,7 @@ router.patch( '/:collection/:pk', validateCollection, asyncHandler(async (req, res) => { - const payload = await PayloadService.processValues('update', req.collection, req.body); - const item = await updateItem(req.params.collection, req.params.pk, payload); + const item = await updateItem(req.params.collection, req.params.pk, req.body); ActivityService.createActivity({ action: ActivityService.Action.UPDATE, diff --git a/src/services/files.ts b/src/services/files.ts index 3642513d17..ad2fe5330c 100644 --- a/src/services/files.ts +++ b/src/services/files.ts @@ -1,7 +1,6 @@ import { Query } from '../types/query'; import * as ItemsService from './items'; import storage from '../storage'; -import * as PayloadService from './payload'; import database from '../database'; import logger from '../logger'; import sharp from 'sharp'; @@ -9,14 +8,19 @@ import { parse as parseICC } from 'icc'; import parseEXIF from 'exif-reader'; import parseIPTC from '../utils/parse-iptc'; import path from 'path'; -import { SYSTEM_ASSET_WHITELIST } from '../constants'; +import { v4 as uuidv4 } from 'uuid'; export const createFile = async ( data: Record, stream: NodeJS.ReadableStream, query?: Query ) => { - const payload = await PayloadService.processValues('create', 'directus_files', data); + const id = uuidv4(); + + const payload: Record = { + ...data, + id, + }; payload.filename_disk = payload.id + path.extname(payload.filename_disk); @@ -55,29 +59,7 @@ export const readFiles = async (query: Query) => { }; export const readFile = async (pk: string | number, query: Query) => { - const file = await ItemsService.readItem('directus_files', pk, query); - - const { asset_allowlist: assetAllowlist } = - (await database.select('asset_allowlist').from('directus_settings').first()) || {}; - - const assetSizes = [...SYSTEM_ASSET_WHITELIST, ...(assetAllowlist || [])]; - - file.links = { - asset_url: new URL(`/assets/${file.id}`, process.env.PUBLIC_URL), - /** @TODO confirm is public url is set before returning */ - original_url: new URL( - file.filename_disk, - process.env[`STORAGE_${file.storage.toUpperCase()}_PUBLIC_URL`] - ), - thumbnails: assetSizes.map((size) => { - return { - ...size, - url: new URL(`/assets/${file.id}?key=${size.key}`, process.env.PUBLIC_URL), - }; - }), - }; - - return file; + return await ItemsService.readItem('directus_files', pk, query); }; // @todo Add query support @@ -87,8 +69,6 @@ export const updateFile = async ( stream?: NodeJS.ReadableStream, query?: Query ) => { - const payload = await PayloadService.processValues('update', 'directus_files', data); - /** * @TODO * Handle changes in storage adapter -> going from local to S3 needs to delete from one, upload to the other @@ -110,7 +90,7 @@ export const updateFile = async ( await storage.disk(file.storage).put(file.filename_disk, stream as any); } - return await ItemsService.updateItem('directus_files', pk, payload, query); + return await ItemsService.updateItem('directus_files', pk, data, query); }; export const deleteFile = async (pk: string | number) => { diff --git a/src/services/items.ts b/src/services/items.ts index 1e0ca03fc8..e19b8017ad 100644 --- a/src/services/items.ts +++ b/src/services/items.ts @@ -2,14 +2,16 @@ import database, { schemaInspector } from '../database'; import { Query } from '../types/query'; import runAST from '../database/run-ast'; import getAST from '../utils/get-ast'; +import * as PayloadService from './payload'; export const createItem = async ( collection: string, data: Record, query: Query = {} ) => { + const payload = await PayloadService.processValues('create', collection, data); const primaryKeyField = await schemaInspector.primary(collection); - const result = await database(collection).insert(data).returning(primaryKeyField); + const result = await database(collection).insert(payload).returning(primaryKeyField); return readItem(collection, result[0], query); }; @@ -19,7 +21,7 @@ export const readItems = async >( ): Promise => { const ast = await getAST(collection, query); const records = await runAST(ast); - return records; + return await PayloadService.processValues('read', collection, records); }; export const readItem = async ( @@ -43,7 +45,7 @@ export const readItem = async ( const ast = await getAST(collection, query); const records = await runAST(ast); - return records[0]; + return await PayloadService.processValues('read', collection, records[0]); }; export const updateItem = async ( @@ -52,9 +54,10 @@ export const updateItem = async ( data: Record, query: Query = {} ) => { + const payload = await PayloadService.processValues('create', collection, data); const primaryKeyField = await schemaInspector.primary(collection); const result = await database(collection) - .update(data) + .update(payload) .where({ [primaryKeyField]: pk }) .returning('id'); return readItem(collection, result[0], query); diff --git a/src/services/payload.ts b/src/services/payload.ts index cfa28f3f87..81266a57a1 100644 --- a/src/services/payload.ts +++ b/src/services/payload.ts @@ -9,6 +9,8 @@ import { v4 as uuidv4 } from 'uuid'; import database from '../database'; import { clone } from 'lodash'; +type Operation = 'create' | 'read' | 'update'; + /** * Process and update all the special fields in the given payload * @@ -18,11 +20,15 @@ import { clone } from 'lodash'; * @returns The updated payload */ export const processValues = async ( - operation: 'create' | 'update', + operation: Operation, collection: string, - payload: Record + payload: Record | Record[] ) => { - const processedPayload = clone(payload); + let processedPayload = clone(payload); + + if (Array.isArray(payload) === false) { + processedPayload = [processedPayload]; + } const specialFieldsInCollection = await database .select('field', 'special') @@ -30,8 +36,19 @@ export const processValues = async ( .where({ collection: collection }) .whereNotNull('special'); - for (const field of specialFieldsInCollection) { - processedPayload[field.field] = await processField(field, processedPayload, operation); + await Promise.all( + processedPayload.map(async (record: any) => { + await Promise.all( + specialFieldsInCollection.map(async (field) => { + record[field.field] = await processField(field, processedPayload, operation); + }) + ); + }) + ); + + // Return the payload in it's original format + if (Array.isArray(payload) === false) { + return processedPayload[0]; } return processedPayload; @@ -40,24 +57,47 @@ export const processValues = async ( async function processField( field: Pick, payload: Record, - operation: 'create' | 'update' + operation: Operation ) { switch (field.special) { case 'hash': - return await genHash(payload[field.field]); + return await genHash(operation, payload[field.field]); case 'uuid': - return await genUUID(operation); + return await genUUID(operation, payload[field.field]); + case 'file-links': + return await genFileLinks(operation, payload[field.field]); + default: + return payload[field.field]; } } -async function genHash(value?: string | number) { +/** + * @note The following functions can be called _a lot_. Make sure to utilize some form of caching + * if you have to rely on heavy operations + */ + +async function genHash(operation: Operation, value?: any) { if (!value) return; - return await argon2.hash(String(value)); + if (operation === 'create' || operation === 'update') { + return await argon2.hash(String(value)); + } + + return value; } -async function genUUID(operation: 'create' | 'update') { - if (operation === 'create') { +async function genUUID(operation: Operation, value?: any) { + if (operation === 'create' && !value) { return uuidv4(); } + + return value; +} + +async function genFileLinks(operation: Operation, value?: any) { + if (operation === 'read') { + return 'test'; + } + + return value; } diff --git a/src/services/users.ts b/src/services/users.ts index d672a4ccca..8411687aa1 100644 --- a/src/services/users.ts +++ b/src/services/users.ts @@ -4,12 +4,10 @@ import jwt from 'jsonwebtoken'; import { sendInviteMail } from '../mail'; import database from '../database'; import argon2 from 'argon2'; -import * as PayloadService from '../services/payload'; import { InvalidPayloadException } from '../exceptions'; export const createUser = async (data: Record, query?: Query) => { - const payload = await PayloadService.processValues('create', 'directus_users', data); - return await ItemsService.createItem('directus_users', payload, query); + return await ItemsService.createItem('directus_users', data, query); }; export const readUsers = async (query?: Query) => { @@ -35,12 +33,7 @@ export const deleteUser = async (pk: string | number) => { }; export const inviteUser = async (email: string, role: string) => { - const userPayload = await PayloadService.processValues('create', 'directus_users', { - email, - role, - status: 'invited', - }); - await createUser(userPayload); + await createUser({ email, role, status: 'invited' }); const payload = { email }; const token = jwt.sign(payload, process.env.SECRET, { expiresIn: '7d' });