chore: create updateEntry

This commit is contained in:
Alexandre Bodin 2024-04-05 11:33:35 +02:00
parent 5e5bcf8c8f
commit 2b548ef558
6 changed files with 77 additions and 79 deletions

View File

@ -137,11 +137,9 @@ describe('history-version service', () => {
contentType: { contentType: {
uid: 'api::article.article', uid: 'api::article.article',
}, },
args: [ params: {
{ locale: 'fr',
locale: 'fr', },
},
],
}; };
const next = jest.fn((context) => ({ ...context, documentId: 'document-id' })); const next = jest.fn((context) => ({ ...context, documentId: 'document-id' }));
@ -154,7 +152,8 @@ describe('history-version service', () => {
expect(next).toHaveBeenCalled(); expect(next).toHaveBeenCalled();
// Ensure we're only storing the data we need in the database // 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', locale: 'fr',
populate: { populate: {
component: { component: {

View File

@ -308,7 +308,7 @@ const createHistoryService = ({ strapi }: { strapi: Core.Strapi }) => {
if ('documentId' in entry) { if ('documentId' in entry) {
relatedEntry = await strapi relatedEntry = await strapi
.documents(attributeSchema.target) .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 // For media assets, only the id is available, double check that we have it
} else if ('id' in entry) { } else if ('id' in entry) {

View File

@ -105,7 +105,8 @@ describe('Default Service', () => {
expect(dbInstance.findOne).toHaveBeenCalledWith(); expect(dbInstance.findOne).toHaveBeenCalledWith();
expect(documentService.update).toHaveBeenCalledWith(1, { expect(documentService.update).toHaveBeenCalledWith({
documentId: 1,
data: input, data: input,
status: 'published', status: 'published',
}); });
@ -137,7 +138,7 @@ describe('Default Service', () => {
expect(dbInstance.findOne).toHaveBeenCalledWith(); expect(dbInstance.findOne).toHaveBeenCalledWith();
expect(documentService.delete).toHaveBeenCalledWith(1, { status: 'published' }); expect(documentService.delete).toHaveBeenCalledWith({ documentId: 1, status: 'published' });
}); });
}); });
}); });

View File

@ -19,8 +19,6 @@ type ComponentBody = {
[key: string]: ComponentValue | DynamicZoneValue; [key: string]: ComponentValue | DynamicZoneValue;
}; };
const isDialectMySQL = () => strapi.db?.dialect.client === 'mysql';
function omitComponentData( function omitComponentData(
contentType: Struct.ContentTypeSchema, contentType: Struct.ContentTypeSchema,
data: Modules.EntityService.Params.Data.Input<Struct.ContentTypeSchema['uid']> data: Modules.EntityService.Params.Data.Input<Struct.ContentTypeSchema['uid']>
@ -83,11 +81,9 @@ const createComponents = async <
} }
// MySQL/MariaDB can cause deadlocks here if concurrency higher than 1 // MySQL/MariaDB can cause deadlocks here if concurrency higher than 1
const components = (await async.map( const components: RepeatableComponentValue = await async.map(componentValue, (value: any) =>
componentValue, createComponent(componentUID, value)
(value: any) => createComponent(componentUID, value), );
{ concurrency: isDialectMySQL() && !strapi.db?.inTransaction() ? 1 : Infinity }
)) as RepeatableComponentValue;
componentBody[attributeName] = components.map(({ id }) => { componentBody[attributeName] = components.map(({ id }) => {
return { return {
@ -103,6 +99,7 @@ const createComponents = async <
componentUID, componentUID,
componentValue as Modules.EntityService.Params.Data.Input<UID.Component> componentValue as Modules.EntityService.Params.Data.Input<UID.Component>
); );
componentBody[attributeName] = { componentBody[attributeName] = {
id: component.id, id: component.id,
__pivot: { __pivot: {
@ -140,8 +137,7 @@ const createComponents = async <
// MySQL/MariaDB can cause deadlocks here if concurrency higher than 1 // MySQL/MariaDB can cause deadlocks here if concurrency higher than 1
componentBody[attributeName] = await async.map( componentBody[attributeName] = await async.map(
dynamiczoneValues, dynamiczoneValues,
createDynamicZoneComponents, createDynamicZoneComponents
{ concurrency: isDialectMySQL() && !strapi.db?.inTransaction() ? 1 : Infinity }
); );
continue; continue;
@ -200,11 +196,9 @@ const updateComponents = async <
} }
// MySQL/MariaDB can cause deadlocks here if concurrency higher than 1 // MySQL/MariaDB can cause deadlocks here if concurrency higher than 1
const components = (await async.map( const components: RepeatableComponentValue = await async.map(componentValue, (value: any) =>
componentValue, updateOrCreateComponent(componentUID, value)
(value: any) => updateOrCreateComponent(componentUID, value), );
{ concurrency: isDialectMySQL() && !strapi.db?.inTransaction() ? 1 : Infinity }
)) as RepeatableComponentValue;
componentBody[attributeName] = components.filter(_.negate(_.isNil)).map(({ id }) => { componentBody[attributeName] = components.filter(_.negate(_.isNil)).map(({ id }) => {
return { return {
@ -235,21 +229,17 @@ const updateComponents = async <
} }
// MySQL/MariaDB can cause deadlocks here if concurrency higher than 1 // MySQL/MariaDB can cause deadlocks here if concurrency higher than 1
componentBody[attributeName] = await async.map( componentBody[attributeName] = await async.map(dynamiczoneValues, async (value: any) => {
dynamiczoneValues, const { id } = await updateOrCreateComponent(value.__component, value);
async (value: any) => {
const { id } = await updateOrCreateComponent(value.__component, value);
return { return {
id, id,
__component: value.__component, __component: value.__component,
__pivot: { __pivot: {
field: attributeName, field: attributeName,
}, },
}; };
}, });
{ concurrency: isDialectMySQL() && !strapi.db?.inTransaction() ? 1 : Infinity }
);
} }
} }
@ -379,20 +369,14 @@ const deleteComponents = async <TUID extends UID.Schema, TEntity extends Data.En
if (attribute.type === 'component') { if (attribute.type === 'component') {
const { component: componentUID } = attribute; const { component: componentUID } = attribute;
// MySQL/MariaDB can cause deadlocks here if concurrency higher than 1 // MySQL/MariaDB can cause deadlocks here if concurrency higher than 1
await async.map( await async.map(_.castArray(value), (subValue: any) =>
_.castArray(value), deleteComponent(componentUID, subValue)
(subValue: any) => deleteComponent(componentUID, subValue),
{
concurrency: isDialectMySQL() && !strapi.db?.inTransaction() ? 1 : Infinity,
}
); );
} else { } else {
// delete dynamic zone components // delete dynamic zone components
// MySQL/MariaDB can cause deadlocks here if concurrency higher than 1 // MySQL/MariaDB can cause deadlocks here if concurrency higher than 1
await async.map( await async.map(_.castArray(value), (subValue: any) =>
_.castArray(value), deleteComponent(subValue.__component, subValue)
(subValue: any) => deleteComponent(subValue.__component, subValue),
{ concurrency: isDialectMySQL() && !strapi.db?.inTransaction() ? 1 : Infinity }
); );
} }
@ -469,8 +453,21 @@ const deleteComponent = async <TUID extends UID.Component>(
await strapi.db.query(uid).delete({ where: { id: componentToDelete.id } }); await strapi.db.query(uid).delete({ where: { id: componentToDelete.id } });
}; };
const assignComponentData = <TUID extends UID.ContentType>(
data: Modules.EntityService.Params.Data.Input<TUID>,
componentData: ComponentBody,
{
contentType,
}: {
contentType: Schema.ContentType<TUID>;
}
) => {
return Object.assign(omitComponentData(contentType, data), componentData);
};
export { export {
omitComponentData, omitComponentData,
assignComponentData,
getComponents, getComponents,
createComponents, createComponents,
updateComponents, updateComponents,

View File

@ -29,11 +29,10 @@ const createEntriesService = (uid: UID.ContentType) => {
// Component handling // Component handling
const componentData = await components.createComponents(uid, validData); const componentData = await components.createComponents(uid, validData);
const contentTypeWithoutComponentData = components.omitComponentData(contentType, validData); const dataWithComponents = components.assignComponentData(validData, componentData, {
const entryData = applyTransforms( contentType,
Object.assign(contentTypeWithoutComponentData, componentData) as any, });
{ contentType } const entryData = applyTransforms(dataWithComponents, { contentType });
);
const doc = await strapi.db.query(uid).create({ ...query, data: entryData }); 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 }); 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 { return {
create: createEntry, create: createEntry,
delete: deleteEntry, delete: deleteEntry,
update: updateEntry,
}; };
}; };

View File

@ -5,18 +5,15 @@ import { async, contentTypes as contentTypesUtils } from '@strapi/utils';
import { wrapInTransaction, type RepositoryFactoryMethod } from './common'; import { wrapInTransaction, type RepositoryFactoryMethod } from './common';
import * as DP from './draft-and-publish'; import * as DP from './draft-and-publish';
import * as i18n from './internationalization'; import * as i18n from './internationalization';
import { transformParamsDocumentId } from './transform/id-transform';
import * as components from './components'; import * as components from './components';
import { createEntriesService } from './entries';
import { createEntriesService } from './entries';
import { pickSelectionParams } from './params'; import { pickSelectionParams } from './params';
import { applyTransforms } from './attributes';
import entityValidator from '../entity-validator';
import { createDocumentId } from '../../utils/transform-content-types-to-models'; import { createDocumentId } from '../../utils/transform-content-types-to-models';
import { getDeepPopulate } from './utils/populate'; import { getDeepPopulate } from './utils/populate';
import { transformData } from './transform/data'; import { transformData } from './transform/data';
import { transformParamsToQuery } from './transform/query'; import { transformParamsToQuery } from './transform/query';
import { transformParamsDocumentId } from './transform/id-transform';
export const createContentTypeRepository: RepositoryFactoryMethod = (uid) => { export const createContentTypeRepository: RepositoryFactoryMethod = (uid) => {
const contentType = strapi.contentType(uid); const contentType = strapi.contentType(uid);
@ -167,7 +164,6 @@ export const createContentTypeRepository: RepositoryFactoryMethod = (uid) => {
const query = transformParamsToQuery(uid, pickSelectionParams(restParams || {}) as any); const query = transformParamsToQuery(uid, pickSelectionParams(restParams || {}) as any);
// Validation // Validation
const model = strapi.contentType(uid);
// Find if document exists // Find if document exists
const entryToUpdate = await strapi.db const entryToUpdate = await strapi.db
.query(uid) .query(uid)
@ -175,27 +171,7 @@ export const createContentTypeRepository: RepositoryFactoryMethod = (uid) => {
let updatedDraft = null; let updatedDraft = null;
if (entryToUpdate) { if (entryToUpdate) {
const validData = await entityValidator.validateEntityUpdate( updatedDraft = await entries.update(entryToUpdate, queryParams);
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 });
} }
if (!updatedDraft) { if (!updatedDraft) {