diff --git a/packages/core/content-manager/server/src/history/services/__tests__/history.test.ts b/packages/core/content-manager/server/src/history/services/__tests__/history.test.ts index eebccadd8d..26c7090dd8 100644 --- a/packages/core/content-manager/server/src/history/services/__tests__/history.test.ts +++ b/packages/core/content-manager/server/src/history/services/__tests__/history.test.ts @@ -137,11 +137,9 @@ describe('history-version service', () => { contentType: { uid: 'api::article.article', }, - args: [ - { - locale: 'fr', - }, - ], + params: { + locale: 'fr', + }, }; const next = jest.fn((context) => ({ ...context, documentId: 'document-id' })); @@ -154,7 +152,8 @@ describe('history-version service', () => { expect(next).toHaveBeenCalled(); // Ensure we're only storing the data we need in the database - expect(mockFindOne).toHaveBeenLastCalledWith('document-id', { + expect(mockFindOne).toHaveBeenLastCalledWith({ + documentId: 'document-id', locale: 'fr', populate: { component: { diff --git a/packages/core/content-manager/server/src/history/services/history.ts b/packages/core/content-manager/server/src/history/services/history.ts index 69c3ba8956..4c89f70c07 100644 --- a/packages/core/content-manager/server/src/history/services/history.ts +++ b/packages/core/content-manager/server/src/history/services/history.ts @@ -308,7 +308,7 @@ const createHistoryService = ({ strapi }: { strapi: Core.Strapi }) => { if ('documentId' in entry) { relatedEntry = await strapi .documents(attributeSchema.target) - .findOne(entry.documentId, { locale: entry.locale || undefined }); + .findOne({ documentId: entry.documentId, locale: entry.locale || undefined }); } // For media assets, only the id is available, double check that we have it } else if ('id' in entry) { diff --git a/packages/core/core/src/core-api/service/__tests__/index.test.ts b/packages/core/core/src/core-api/service/__tests__/index.test.ts index 4ff6fc31f7..fbe6b2032d 100644 --- a/packages/core/core/src/core-api/service/__tests__/index.test.ts +++ b/packages/core/core/src/core-api/service/__tests__/index.test.ts @@ -105,7 +105,8 @@ describe('Default Service', () => { expect(dbInstance.findOne).toHaveBeenCalledWith(); - expect(documentService.update).toHaveBeenCalledWith(1, { + expect(documentService.update).toHaveBeenCalledWith({ + documentId: 1, data: input, status: 'published', }); @@ -137,7 +138,7 @@ describe('Default Service', () => { expect(dbInstance.findOne).toHaveBeenCalledWith(); - expect(documentService.delete).toHaveBeenCalledWith(1, { status: 'published' }); + expect(documentService.delete).toHaveBeenCalledWith({ documentId: 1, status: 'published' }); }); }); }); diff --git a/packages/core/core/src/services/document-service/components.ts b/packages/core/core/src/services/document-service/components.ts index 1133700f50..74a844a678 100644 --- a/packages/core/core/src/services/document-service/components.ts +++ b/packages/core/core/src/services/document-service/components.ts @@ -19,8 +19,6 @@ type ComponentBody = { [key: string]: ComponentValue | DynamicZoneValue; }; -const isDialectMySQL = () => strapi.db?.dialect.client === 'mysql'; - function omitComponentData( contentType: Struct.ContentTypeSchema, data: Modules.EntityService.Params.Data.Input @@ -83,11 +81,9 @@ const createComponents = async < } // MySQL/MariaDB can cause deadlocks here if concurrency higher than 1 - const components = (await async.map( - componentValue, - (value: any) => createComponent(componentUID, value), - { concurrency: isDialectMySQL() && !strapi.db?.inTransaction() ? 1 : Infinity } - )) as RepeatableComponentValue; + const components: RepeatableComponentValue = await async.map(componentValue, (value: any) => + createComponent(componentUID, value) + ); componentBody[attributeName] = components.map(({ id }) => { return { @@ -103,6 +99,7 @@ const createComponents = async < componentUID, componentValue as Modules.EntityService.Params.Data.Input ); + componentBody[attributeName] = { id: component.id, __pivot: { @@ -140,8 +137,7 @@ const createComponents = async < // MySQL/MariaDB can cause deadlocks here if concurrency higher than 1 componentBody[attributeName] = await async.map( dynamiczoneValues, - createDynamicZoneComponents, - { concurrency: isDialectMySQL() && !strapi.db?.inTransaction() ? 1 : Infinity } + createDynamicZoneComponents ); continue; @@ -200,11 +196,9 @@ const updateComponents = async < } // MySQL/MariaDB can cause deadlocks here if concurrency higher than 1 - const components = (await async.map( - componentValue, - (value: any) => updateOrCreateComponent(componentUID, value), - { concurrency: isDialectMySQL() && !strapi.db?.inTransaction() ? 1 : Infinity } - )) as RepeatableComponentValue; + const components: RepeatableComponentValue = await async.map(componentValue, (value: any) => + updateOrCreateComponent(componentUID, value) + ); componentBody[attributeName] = components.filter(_.negate(_.isNil)).map(({ id }) => { return { @@ -235,21 +229,17 @@ const updateComponents = async < } // MySQL/MariaDB can cause deadlocks here if concurrency higher than 1 - componentBody[attributeName] = await async.map( - dynamiczoneValues, - async (value: any) => { - const { id } = await updateOrCreateComponent(value.__component, value); + componentBody[attributeName] = await async.map(dynamiczoneValues, async (value: any) => { + const { id } = await updateOrCreateComponent(value.__component, value); - return { - id, - __component: value.__component, - __pivot: { - field: attributeName, - }, - }; - }, - { concurrency: isDialectMySQL() && !strapi.db?.inTransaction() ? 1 : Infinity } - ); + return { + id, + __component: value.__component, + __pivot: { + field: attributeName, + }, + }; + }); } } @@ -379,20 +369,14 @@ const deleteComponents = async deleteComponent(componentUID, subValue), - { - concurrency: isDialectMySQL() && !strapi.db?.inTransaction() ? 1 : Infinity, - } + await async.map(_.castArray(value), (subValue: any) => + deleteComponent(componentUID, subValue) ); } else { // delete dynamic zone components // MySQL/MariaDB can cause deadlocks here if concurrency higher than 1 - await async.map( - _.castArray(value), - (subValue: any) => deleteComponent(subValue.__component, subValue), - { concurrency: isDialectMySQL() && !strapi.db?.inTransaction() ? 1 : Infinity } + await async.map(_.castArray(value), (subValue: any) => + deleteComponent(subValue.__component, subValue) ); } @@ -469,8 +453,21 @@ const deleteComponent = async ( await strapi.db.query(uid).delete({ where: { id: componentToDelete.id } }); }; +const assignComponentData = ( + data: Modules.EntityService.Params.Data.Input, + componentData: ComponentBody, + { + contentType, + }: { + contentType: Schema.ContentType; + } +) => { + return Object.assign(omitComponentData(contentType, data), componentData); +}; + export { omitComponentData, + assignComponentData, getComponents, createComponents, updateComponents, diff --git a/packages/core/core/src/services/document-service/entries.ts b/packages/core/core/src/services/document-service/entries.ts index c63841ac90..68a20d07e9 100644 --- a/packages/core/core/src/services/document-service/entries.ts +++ b/packages/core/core/src/services/document-service/entries.ts @@ -29,11 +29,10 @@ const createEntriesService = (uid: UID.ContentType) => { // Component handling const componentData = await components.createComponents(uid, validData); - const contentTypeWithoutComponentData = components.omitComponentData(contentType, validData); - const entryData = applyTransforms( - Object.assign(contentTypeWithoutComponentData, componentData) as any, - { contentType } - ); + const dataWithComponents = components.assignComponentData(validData, componentData, { + contentType, + }); + const entryData = applyTransforms(dataWithComponents, { contentType }); const doc = await strapi.db.query(uid).create({ ...query, data: entryData }); @@ -48,9 +47,35 @@ const createEntriesService = (uid: UID.ContentType) => { await components.deleteComponents(uid, componentsToDelete as any, { loadComponents: false }); } + async function updateEntry(entryToUpdate: any, params = {} as any) { + const { data, ...restParams } = await transformParamsDocumentId(uid, params); + const query = transformParamsToQuery(uid, pickSelectionParams(restParams) as any); // select / populate + + const validData = await entityValidator.validateEntityUpdate( + contentType, + data, + { + isDraft: !params?.data?.publishedAt, // Always update the draft version + locale: params?.locale, + }, + entryToUpdate + ); + // Component handling + const componentData = await components.updateComponents(uid, entryToUpdate, validData as any); + const dataWithComponents = components.assignComponentData(validData, componentData, { + contentType, + }); + const entryData = applyTransforms(dataWithComponents, { contentType }); + + return strapi.db + .query(uid) + .update({ ...query, where: { id: entryToUpdate.id }, data: entryData }); + } + return { create: createEntry, delete: deleteEntry, + update: updateEntry, }; }; diff --git a/packages/core/core/src/services/document-service/repository.ts b/packages/core/core/src/services/document-service/repository.ts index 571f0c2124..81cee5253c 100644 --- a/packages/core/core/src/services/document-service/repository.ts +++ b/packages/core/core/src/services/document-service/repository.ts @@ -5,18 +5,15 @@ import { async, contentTypes as contentTypesUtils } from '@strapi/utils'; import { wrapInTransaction, type RepositoryFactoryMethod } from './common'; import * as DP from './draft-and-publish'; import * as i18n from './internationalization'; -import { transformParamsDocumentId } from './transform/id-transform'; - import * as components from './components'; -import { createEntriesService } from './entries'; +import { createEntriesService } from './entries'; import { pickSelectionParams } from './params'; -import { applyTransforms } from './attributes'; -import entityValidator from '../entity-validator'; import { createDocumentId } from '../../utils/transform-content-types-to-models'; import { getDeepPopulate } from './utils/populate'; import { transformData } from './transform/data'; import { transformParamsToQuery } from './transform/query'; +import { transformParamsDocumentId } from './transform/id-transform'; export const createContentTypeRepository: RepositoryFactoryMethod = (uid) => { const contentType = strapi.contentType(uid); @@ -167,7 +164,6 @@ export const createContentTypeRepository: RepositoryFactoryMethod = (uid) => { const query = transformParamsToQuery(uid, pickSelectionParams(restParams || {}) as any); // Validation - const model = strapi.contentType(uid); // Find if document exists const entryToUpdate = await strapi.db .query(uid) @@ -175,27 +171,7 @@ export const createContentTypeRepository: RepositoryFactoryMethod = (uid) => { let updatedDraft = null; if (entryToUpdate) { - const validData = await entityValidator.validateEntityUpdate( - model, - // @ts-expect-error we need type guard to assert that data has the valid type - data, - { - isDraft: !queryParams?.data?.publishedAt, // Always update the draft version - locale: queryParams?.locale, - }, - entryToUpdate - ); - - // Component handling - const componentData = await updateComponents(entryToUpdate, validData as any); - const entryData = applyTransforms( - Object.assign(omitComponentData(validData), componentData as any), - { contentType: model } - ); - - updatedDraft = await strapi.db - .query(uid) - .update({ ...query, where: { id: entryToUpdate.id }, data: entryData }); + updatedDraft = await entries.update(entryToUpdate, queryParams); } if (!updatedDraft) {