diff --git a/src/services/activity.ts b/src/services/activity.ts index d96c7c034a..aded494c81 100644 --- a/src/services/activity.ts +++ b/src/services/activity.ts @@ -12,7 +12,8 @@ export enum Action { } export const createActivity = async (data: Record, query?: Query) => { - return await ItemsService.createItem('directus_activity', data, query); + const primaryKey = await ItemsService.createItem('directus_activity', data); + return await ItemsService.readItem('directus_activity', primaryKey, query); }; export const readActivities = async (query?: Query) => { diff --git a/src/services/folders.ts b/src/services/folders.ts index 446eff2370..aa7d2417f3 100644 --- a/src/services/folders.ts +++ b/src/services/folders.ts @@ -2,7 +2,8 @@ import { Query } from '../types/query'; import * as ItemsService from './items'; export const createFolder = async (data: Record, query: Query) => { - return await ItemsService.createItem('directus_folders', data, query); + const primaryKey = await ItemsService.createItem('directus_folders', data); + return await ItemsService.readItem('directus_folders', primaryKey, query); }; export const readFolders = async (query: Query) => { @@ -18,7 +19,8 @@ export const updateFolder = async ( data: Record, query: Query ) => { - return await ItemsService.updateItem('directus_folders', pk, data, query); + const primaryKey = await ItemsService.updateItem('directus_folders', pk, data); + return await ItemsService.readItem('directus_folders', primaryKey, query); }; export const deleteFolder = async (pk: string | number) => { diff --git a/src/services/items.ts b/src/services/items.ts index d127165f88..bcce640fa7 100644 --- a/src/services/items.ts +++ b/src/services/items.ts @@ -3,6 +3,7 @@ import { Query } from '../types/query'; import runAST from '../database/run-ast'; import getAST from '../utils/get-ast'; import * as PayloadService from './payload'; +import { pick } from 'lodash'; export const createItem = async (collection: string, data: Record) => { let payload = await PayloadService.processValues('create', collection, data); @@ -10,11 +11,24 @@ export const createItem = async (collection: string, data: Record) payload = await PayloadService.processM2O(collection, payload); const primaryKeyField = await schemaInspector.primary(collection); - const result = await database(collection).insert(payload).returning(primaryKeyField); - // payload process o2m + // Only insert the values that actually save to an existing column. This ensures we ignore aliases etc + const columns = await schemaInspector.columns(collection); + const primaryKeys = await database(collection) + .insert( + pick( + payload, + columns.map(({ column }) => column) + ) + ) + .returning(primaryKeyField); - return result[0]; // pk + // This allows the o2m values to be populated correctly + payload[primaryKeyField] = primaryKeys[0]; + + await PayloadService.processO2M(collection, payload); + + return primaryKeys[0]; }; export const readItems = async >( @@ -55,14 +69,25 @@ export const updateItem = async ( pk: number | string, data: Record ) => { - const payload = await PayloadService.processValues('create', collection, data); + let payload = await PayloadService.processValues('create', collection, data); + + payload = await PayloadService.processM2O(collection, payload); + const primaryKeyField = await schemaInspector.primary(collection); - const result = await database(collection) - .update(payload) + + // Only insert the values that actually save to an existing column. This ensures we ignore aliases etc + const columns = await schemaInspector.columns(collection); + const primaryKeys = await database(collection) + .update( + pick( + payload, + columns.map(({ column }) => column) + ) + ) .where({ [primaryKeyField]: pk }) .returning(primaryKeyField); - return result[0]; // pk + return primaryKeys[0]; }; export const deleteItem = async (collection: string, pk: number | string) => { diff --git a/src/services/payload.ts b/src/services/payload.ts index 498986d578..addf3d3743 100644 --- a/src/services/payload.ts +++ b/src/services/payload.ts @@ -119,10 +119,9 @@ async function processField( /** * 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 | Record[] -) => { +export const processM2O = async (collection: string, payload: Record) => { + const payloadClone = clone(payload); + const relations = await database .select('*') .from('directus_relations') @@ -131,15 +130,15 @@ export const processM2O = async ( // Only process related records that are actually in the payload const relationsToProcess = relations.filter((relation) => { return ( - payload.hasOwnProperty(relation.field_many) && - typeof payload[relation.field_many] === 'object' + 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 = payload[relation.field_many]; + const relatedRecord = payloadClone[relation.field_many]; const hasPrimaryKey = relatedRecord.hasOwnProperty(relation.primary_one); let relatedPrimaryKey: string | number; @@ -159,9 +158,64 @@ export const processM2O = async ( } // Overwrite the nested object with just the primary key, so the parent level can be saved correctly - payload[relation.field_many] = relatedPrimaryKey; + payloadClone[relation.field_many] = relatedPrimaryKey; }) ); - return payload; + return payloadClone; +}; + +export const processO2M = async (collection: string, payload: Record) => { + const payloadClone = clone(payload); + + const relations = await database + .select('*') + .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; }; diff --git a/src/types/relation.ts b/src/types/relation.ts index 3042536071..7bfdc49099 100644 --- a/src/types/relation.ts +++ b/src/types/relation.ts @@ -1,7 +1,10 @@ export type Relation = { id: number; + collection_many: string; field_many: string; + primary_many: string; + collection_one: string; field_one: string; primary_one: string;