mirror of
https://github.com/directus/directus.git
synced 2026-02-06 15:04:59 -05:00
222 lines
5.7 KiB
TypeScript
222 lines
5.7 KiB
TypeScript
/**
|
|
* Process a given payload for a collection to ensure the special fields (hash, uuid, date etc) are
|
|
* handled correctly.
|
|
*/
|
|
|
|
import { System } from '../types/field';
|
|
import argon2 from 'argon2';
|
|
import { v4 as uuidv4 } from 'uuid';
|
|
import database from '../database';
|
|
import { clone } from 'lodash';
|
|
import { File } from '../types/files';
|
|
import { Relation } from '../types/relation';
|
|
import * as ItemsService from './items';
|
|
|
|
type Operation = 'create' | 'read' | 'update';
|
|
|
|
type Transformers = {
|
|
[type: string]: (
|
|
operation: Operation,
|
|
value: any,
|
|
payload: Record<string, any>
|
|
) => Promise<any>;
|
|
};
|
|
|
|
/**
|
|
* @todo allow this to be extended
|
|
*/
|
|
const transformers: Transformers = {
|
|
async hash(operation, value) {
|
|
if (!value) return;
|
|
|
|
if (operation === 'create' || operation === 'update') {
|
|
return await argon2.hash(String(value));
|
|
}
|
|
|
|
return value;
|
|
},
|
|
async uuid(operation, value) {
|
|
if (operation === 'create' && !value) {
|
|
return uuidv4();
|
|
}
|
|
|
|
return value;
|
|
},
|
|
async 'file-info'(operation, value, payload: File) {
|
|
if (operation === 'read' && payload) {
|
|
return {
|
|
asset_url: new URL(`/assets/${payload.id}`, process.env.PUBLIC_URL),
|
|
};
|
|
}
|
|
|
|
// This is an non-existing column, so there isn't any data to save
|
|
return undefined;
|
|
},
|
|
};
|
|
|
|
/**
|
|
* 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: Operation,
|
|
collection: string,
|
|
payload: Record<string, any> | Record<string, any>[]
|
|
) => {
|
|
let processedPayload = clone(payload);
|
|
|
|
if (Array.isArray(payload) === false) {
|
|
processedPayload = [processedPayload];
|
|
}
|
|
|
|
const specialFieldsInCollection = await database
|
|
.select('field', 'special')
|
|
.from<System>('directus_fields')
|
|
.where({ collection: collection })
|
|
.whereNotNull('special');
|
|
|
|
await Promise.all(
|
|
processedPayload.map(async (record: any) => {
|
|
await Promise.all(
|
|
specialFieldsInCollection.map(async (field) => {
|
|
record[field.field] = await processField(field, record, operation);
|
|
})
|
|
);
|
|
})
|
|
);
|
|
|
|
/** @TODO
|
|
*
|
|
* - Make config.ts file in root
|
|
* - Have it cache settings / env vars a la graphql/dataloader (memory-cache)
|
|
* - Have it have a function to reload env vars
|
|
*/
|
|
|
|
// Return the payload in it's original format
|
|
if (Array.isArray(payload) === false) {
|
|
return processedPayload[0];
|
|
}
|
|
|
|
return processedPayload;
|
|
};
|
|
|
|
async function processField(
|
|
field: Pick<System, 'field' | 'special'>,
|
|
payload: Record<string, any>,
|
|
operation: Operation
|
|
) {
|
|
if (transformers.hasOwnProperty(field.special)) {
|
|
return await transformers[field.special](operation, payload[field.field], payload);
|
|
}
|
|
|
|
return payload[field.field];
|
|
}
|
|
|
|
/**
|
|
* Recursively checks for nested relational items, and saves them bottom up, to ensure we have IDs etc ready
|
|
*/
|
|
export const processM2O = async (collection: string, payload: Record<string, any>) => {
|
|
const payloadClone = clone(payload);
|
|
|
|
const relations = await database
|
|
.select<Relation[]>('*')
|
|
.from('directus_relations')
|
|
.where({ collection_many: collection });
|
|
|
|
// Only process related records that are actually in the payload
|
|
const relationsToProcess = relations.filter((relation) => {
|
|
return (
|
|
payloadClone.hasOwnProperty(relation.field_many) &&
|
|
typeof payloadClone[relation.field_many] === 'object'
|
|
);
|
|
});
|
|
|
|
// Save all nested m2o records
|
|
await Promise.all(
|
|
relationsToProcess.map(async (relation) => {
|
|
const relatedRecord = payloadClone[relation.field_many];
|
|
const hasPrimaryKey = relatedRecord.hasOwnProperty(relation.primary_one);
|
|
|
|
let relatedPrimaryKey: string | number;
|
|
|
|
if (hasPrimaryKey) {
|
|
relatedPrimaryKey = relatedRecord[relation.primary_one];
|
|
await ItemsService.updateItem(
|
|
relation.collection_one,
|
|
relatedPrimaryKey,
|
|
relatedRecord
|
|
);
|
|
} else {
|
|
relatedPrimaryKey = await ItemsService.createItem(
|
|
relation.collection_one,
|
|
relatedRecord
|
|
);
|
|
}
|
|
|
|
// Overwrite the nested object with just the primary key, so the parent level can be saved correctly
|
|
payloadClone[relation.field_many] = relatedPrimaryKey;
|
|
})
|
|
);
|
|
|
|
return payloadClone;
|
|
};
|
|
|
|
export const processO2M = async (collection: string, payload: Record<string, any>) => {
|
|
const payloadClone = clone(payload);
|
|
|
|
const relations = await database
|
|
.select<Relation[]>('*')
|
|
.from('directus_relations')
|
|
.where({ collection_one: collection });
|
|
|
|
// Only process related records that are actually in the payload
|
|
const relationsToProcess = relations.filter((relation) => {
|
|
return (
|
|
payloadClone.hasOwnProperty(relation.field_one) &&
|
|
Array.isArray(payloadClone[relation.field_one])
|
|
);
|
|
});
|
|
|
|
// Save all nested o2m records
|
|
await Promise.all(
|
|
relationsToProcess.map(async (relation) => {
|
|
const relatedRecords = payloadClone[relation.field_one];
|
|
|
|
await Promise.all(
|
|
relatedRecords.map(async (relatedRecord: any, index: number) => {
|
|
relatedRecord[relation.field_many] = payloadClone[relation.primary_one];
|
|
|
|
const hasPrimaryKey = relatedRecord.hasOwnProperty(relation.primary_many);
|
|
|
|
let relatedPrimaryKey: string | number;
|
|
|
|
if (hasPrimaryKey) {
|
|
relatedPrimaryKey = relatedRecord[relation.primary_many];
|
|
|
|
await ItemsService.updateItem(
|
|
relation.collection_many,
|
|
relatedPrimaryKey,
|
|
relatedRecord
|
|
);
|
|
} else {
|
|
relatedPrimaryKey = await ItemsService.createItem(
|
|
relation.collection_many,
|
|
relatedRecord
|
|
);
|
|
}
|
|
|
|
relatedRecord[relation.primary_many] = relatedPrimaryKey;
|
|
|
|
payloadClone[relation.field_one][index] = relatedRecord;
|
|
})
|
|
);
|
|
})
|
|
);
|
|
|
|
return payloadClone;
|
|
};
|