diff --git a/.changeset/brave-eyes-mate.md b/.changeset/brave-eyes-mate.md new file mode 100644 index 00000000..7933eda9 --- /dev/null +++ b/.changeset/brave-eyes-mate.md @@ -0,0 +1,5 @@ +--- +"strapi-plugin-webtools": patch +--- + +fix: wrap the bulk generate service in a db transaction to prevent #280 diff --git a/.changeset/silver-grapes-see.md b/.changeset/silver-grapes-see.md new file mode 100644 index 00000000..aa2b22c5 --- /dev/null +++ b/.changeset/silver-grapes-see.md @@ -0,0 +1,5 @@ +--- +"strapi-plugin-webtools": patch +--- + +fix: make sure error notifications are shown with type 'danger' instead of 'warning' diff --git a/packages/core/admin/screens/List/components/TableRow/index.tsx b/packages/core/admin/screens/List/components/TableRow/index.tsx index edb21305..74103c6a 100644 --- a/packages/core/admin/screens/List/components/TableRow/index.tsx +++ b/packages/core/admin/screens/List/components/TableRow/index.tsx @@ -56,7 +56,7 @@ const TableRow: FC = ({ }) .catch(() => { if (onDelete) onDelete(); - toggleNotification({ type: 'warning', message: formatMessage({ id: 'notification.error' }) }); + toggleNotification({ type: 'danger', message: formatMessage({ id: 'notification.error' }) }); }); }; diff --git a/packages/core/admin/screens/List/index.tsx b/packages/core/admin/screens/List/index.tsx index 1fe973d5..85396361 100644 --- a/packages/core/admin/screens/List/index.tsx +++ b/packages/core/admin/screens/List/index.tsx @@ -53,7 +53,7 @@ const List = () => { toggleNotification({ type: 'success', message: formatMessage({ id: 'webtools.success.url-alias.generate', defaultMessage: response.data.message }) }); }) .catch(() => { - toggleNotification({ type: 'warning', message: formatMessage({ id: 'notification.error' }) }); + toggleNotification({ type: 'danger', message: formatMessage({ id: 'notification.error' }) }); }); await queryClient.invalidateQueries('url-alias'); diff --git a/packages/core/admin/screens/Patterns/CreatePage/index.tsx b/packages/core/admin/screens/Patterns/CreatePage/index.tsx index 6e6d8336..985a4ccf 100644 --- a/packages/core/admin/screens/Patterns/CreatePage/index.tsx +++ b/packages/core/admin/screens/Patterns/CreatePage/index.tsx @@ -63,7 +63,7 @@ const CreatePatternPage = () => { setSubmitting(false); } catch (err) { toggleNotification({ - type: 'warning', + type: 'danger', message: formatMessage({ id: 'notification.error' }), }); setSubmitting(false); diff --git a/packages/core/admin/screens/Patterns/EditPage/index.tsx b/packages/core/admin/screens/Patterns/EditPage/index.tsx index a21c8938..c1f90b03 100644 --- a/packages/core/admin/screens/Patterns/EditPage/index.tsx +++ b/packages/core/admin/screens/Patterns/EditPage/index.tsx @@ -61,7 +61,7 @@ const EditPatternPage = () => { setSubmitting(false); } catch (err) { toggleNotification({ - type: 'warning', + type: 'danger', message: formatMessage({ id: 'notification.error' }), }); setSubmitting(false); diff --git a/packages/core/admin/screens/Patterns/ListPage/components/TableBody/index.tsx b/packages/core/admin/screens/Patterns/ListPage/components/TableBody/index.tsx index 21bcda5a..edaa096f 100644 --- a/packages/core/admin/screens/Patterns/ListPage/components/TableBody/index.tsx +++ b/packages/core/admin/screens/Patterns/ListPage/components/TableBody/index.tsx @@ -31,7 +31,7 @@ const TableBody: React.FC = ({ patterns, contentTypes }) => { toggleNotification({ type: 'success', message: formatMessage({ id: 'webtools.settings.success.delete' }) }); }) .catch(() => { - toggleNotification({ type: 'warning', message: formatMessage({ id: 'notification.error' }) }); + toggleNotification({ type: 'danger', message: formatMessage({ id: 'notification.error' }) }); }); }; diff --git a/packages/core/server/services/bulk-generate.ts b/packages/core/server/services/bulk-generate.ts index 1cb84fbd..c3722743 100644 --- a/packages/core/server/services/bulk-generate.ts +++ b/packages/core/server/services/bulk-generate.ts @@ -20,104 +20,107 @@ const generateUrlAliases = async (params: GenerateParams): Promise => { // Map over all the types sent in the request. await Promise.all(types.map(async (type) => { - if (generationType === 'all') { - // Delete all the URL aliases for the given type. - await strapi.db.query('plugin::webtools.url-alias').deleteMany({ - where: { contenttype: type }, - }); - } + await strapi.db.transaction(async () => { + if (generationType === 'all') { + // Delete all the URL aliases for the given type. + await strapi.db.query('plugin::webtools.url-alias').deleteMany({ + where: { contenttype: type }, + }); + } - if (generationType === 'only_generated') { - // Delete all the auto generated URL aliases of the given type. - await strapi.db.query('plugin::webtools.url-alias').deleteMany({ - where: { contenttype: type, generated: true }, - }); - } + if (generationType === 'only_generated') { + // Delete all the auto generated URL aliases of the given type. + await strapi.db.query('plugin::webtools.url-alias').deleteMany({ + where: { contenttype: type, generated: true }, + }); + } - let relations: string[] = []; + let relations: string[] = []; - // Get all relations for the type from all patterns for all languages. - const urlPatterns = await getPluginService('url-pattern').findByUid(type); - urlPatterns.forEach((urlPattern) => { - const languageRelations = getPluginService('url-pattern').getRelationsFromPattern(urlPattern); - relations = [...relations, ...languageRelations]; - }); - - // Query all the entities of the type that do not have a corresponding URL alias. - const entities = await strapi.documents(type as 'api::test.test').findMany({ - filters: { url_alias: null }, - populate: { - ...relations.reduce((obj, key) => ({ ...obj, [key]: {} }), {}), - localizations: { - populate: { - ...relations.reduce((obj, key) => ({ ...obj, [key]: {} }), {}), - }, - }, - }, - }); - - /** - * @todo - * We should do a Promise.all(entities.map()) here to speed up the process. - * Using that method we can create all the URL aliases in parallel. - * Currently this is not possible due to the duplicateCheck function. - * Race conditions can occur when two entities have the same URL path. - */ - // eslint-disable-next-line no-restricted-syntax - for (const entity of entities) { - // FIXME: just filter the `urlPatterns` we already have. - // eslint-disable-next-line no-await-in-loop - const entityUrlPatterns = await getPluginService('url-pattern').findByUid(type, entity.locale); - const resolvedPath = getPluginService('url-pattern').resolvePattern(type, entity, entityUrlPatterns[0]); - - // eslint-disable-next-line no-await-in-loop - const newUrlAlias = await strapi.documents('plugin::webtools.url-alias').create({ - data: { - url_path: resolvedPath, - generated: true, - contenttype: type, - locale: entity.locale, - }, + // Get all relations for the type from all patterns for all languages. + const urlPatterns = await getPluginService('url-pattern').findByUid(type); + urlPatterns.forEach((urlPattern) => { + const languageRelations = getPluginService('url-pattern').getRelationsFromPattern(urlPattern); + relations = [...relations, ...languageRelations]; }); - // eslint-disable-next-line no-await-in-loop - await strapi.documents(type as 'api::test.test').update({ - locale: entity.locale, - documentId: entity.documentId, - data: { - url_alias: [newUrlAlias.documentId], + // Query all the entities of the type that do not have a corresponding URL alias. + const entities = await strapi.documents(type as 'api::test.test').findMany({ + filters: { url_alias: null }, + populate: { + ...relations.reduce((obj, key) => ({ ...obj, [key]: {} }), {}), + localizations: { + populate: { + ...relations.reduce((obj, key) => ({ ...obj, [key]: {} }), {}), + }, + }, }, }); - // eslint-disable-next-line no-await-in-loop - await Promise.all(entity.localizations.map(async (loc) => { - const patterns = await getPluginService('url-pattern').findByUid(type, loc.locale); - const path = getPluginService('url-pattern').resolvePattern(type, loc, patterns[0]); - - const alias = await strapi.documents('plugin::webtools.url-alias').update({ - documentId: newUrlAlias.documentId, - locale: loc.locale, + /** + * @todo + * We should do a Promise.all(entities.map()) here to speed up the process. + * Using that method we can create all the URL aliases in parallel. + * Currently this is not possible due to the duplicateCheck function. + * Race conditions can occur when two entities have the same URL path. + */ + // eslint-disable-next-line no-restricted-syntax + for (const entity of entities) { + // FIXME: just filter the `urlPatterns` we already have. + // eslint-disable-next-line no-await-in-loop + const entityUrlPatterns = await getPluginService('url-pattern').findByUid(type, entity.locale); + const resolvedPath = getPluginService('url-pattern').resolvePattern(type, entity, entityUrlPatterns[0]); + + // eslint-disable-next-line no-await-in-loop + const newUrlAlias = await strapi.documents('plugin::webtools.url-alias').create({ data: { - url_path: path, + url_path: resolvedPath, generated: true, contenttype: type, locale: entity.locale, }, }); + // eslint-disable-next-line no-await-in-loop await strapi.documents(type as 'api::test.test').update({ + locale: entity.locale, documentId: entity.documentId, - locale: loc.locale, data: { - url_alias: [alias.documentId], + url_alias: [newUrlAlias.documentId], }, }); - })); - generatedCount += 1; - } + // eslint-disable-next-line no-await-in-loop + await Promise.all(entity.localizations.map(async (loc) => { + const patterns = await getPluginService('url-pattern').findByUid(type, loc.locale); + const path = getPluginService('url-pattern').resolvePattern(type, loc, patterns[0]); + + const alias = await strapi.documents('plugin::webtools.url-alias').update({ + documentId: newUrlAlias.documentId, + locale: loc.locale, + data: { + url_path: path, + generated: true, + contenttype: type, + locale: entity.locale, + }, + }); + + await strapi.documents(type as 'api::test.test').update({ + documentId: entity.documentId, + locale: loc.locale, + data: { + url_alias: [alias.documentId], + }, + }); + })); + + generatedCount += 1; + } + }); })); + return generatedCount; };