UNPKG

@directus/api

Version:

Directus is a real-time API and App dashboard for managing SQL database content

135 lines (134 loc) 6.18 kB
import { isObject } from '@directus/utils'; import Joi from 'joi'; import { cloneDeep } from 'lodash-es'; const alterationSchema = Joi.object({ create: Joi.array().items(Joi.object().unknown()), update: Joi.array().items(Joi.object().unknown()), delete: Joi.array().items(Joi.string(), Joi.number()), }); export function mergeVersionsRaw(item, versionData) { const result = cloneDeep(item); for (const versionRecord of versionData) { for (const key of Object.keys(versionRecord)) { result[key] = versionRecord[key]; } } return result; } export function mergeVersionsRecursive(item, versionData, collection, schema) { if (versionData.length === 0) return item; return recursiveMerging(item, versionData, collection, schema); } function recursiveMerging(data, versionData, collection, schema) { const result = cloneDeep(data); const relations = getRelations(collection, schema); for (const versionRecord of versionData) { if (!isObject(versionRecord)) { continue; } for (const key of Object.keys(data)) { if (key in versionRecord === false) { continue; } const currentValue = data[key]; const newValue = versionRecord[key]; if (typeof newValue !== 'object' || newValue === null) { // primitive type substitution, json and non relational array values are handled in the next check result[key] = newValue; continue; } if (key in relations === false) { // check for m2a exception if (isManyToAnyCollection(collection, schema) && key === 'item') { const item = addMissingKeys(isObject(currentValue) ? currentValue : {}, newValue); result[key] = recursiveMerging(item, [newValue], data['collection'], schema); } else { // item is not a relation result[key] = newValue; } continue; } const { error } = alterationSchema.validate(newValue); if (error) { if (typeof newValue === 'object' && key in relations) { const newItem = !currentValue || typeof currentValue !== 'object' ? newValue : currentValue; result[key] = recursiveMerging(newItem, [newValue], relations[key], schema); } continue; } const alterations = newValue; const currentPrimaryKeyField = schema.collections[collection].primary; const relatedPrimaryKeyField = schema.collections[relations[key]].primary; const mergedRelation = []; if (Array.isArray(currentValue)) { if (alterations.delete.length > 0) { for (const currentItem of currentValue) { const currentId = typeof currentItem === 'object' ? currentItem[currentPrimaryKeyField] : currentItem; if (alterations.delete.includes(currentId) === false) { mergedRelation.push(currentItem); } } } else { mergedRelation.push(...currentValue); } if (alterations.update.length > 0) { for (const updatedItem of alterations.update) { // find existing item to update const itemIndex = mergedRelation.findIndex((currentItem) => currentItem[relatedPrimaryKeyField] === updatedItem[currentPrimaryKeyField]); if (itemIndex === -1) { // check for raw primary keys const pkIndex = mergedRelation.findIndex((currentItem) => currentItem === updatedItem[currentPrimaryKeyField]); if (pkIndex === -1) { // nothing to update so add the item as is mergedRelation.push(updatedItem); } else { mergedRelation[pkIndex] = updatedItem; } continue; } const item = addMissingKeys(mergedRelation[itemIndex], updatedItem); mergedRelation[itemIndex] = recursiveMerging(item, [updatedItem], relations[key], schema); } } } if (alterations.create.length > 0) { for (const createdItem of alterations.create) { const item = addMissingKeys({}, createdItem); mergedRelation.push(recursiveMerging(item, [createdItem], relations[key], schema)); } } result[key] = mergedRelation; } } return result; } function addMissingKeys(item, edits) { const result = { ...item }; for (const key of Object.keys(edits)) { if (key in item === false) { result[key] = null; } } return result; } function isManyToAnyCollection(collection, schema) { const relation = schema.relations.find((relation) => relation.collection === collection && relation.meta?.many_collection === collection); if (!relation || !relation.meta?.one_field || !relation.related_collection) return false; return Boolean(schema.collections[relation.related_collection]?.fields[relation.meta.one_field]?.special.includes('m2a')); } function getRelations(collection, schema) { return schema.relations.reduce((result, relation) => { if (relation.related_collection === collection && relation.meta?.one_field) { result[relation.meta.one_field] = relation.collection; } if (relation.collection === collection && relation.related_collection) { result[relation.field] = relation.related_collection; } return result; }, {}); }