From 905e8b770c396bc111007b9f669355d7414346c3 Mon Sep 17 00:00:00 2001 From: Marc-Roig Date: Thu, 14 Dec 2023 15:29:06 +0100 Subject: [PATCH 1/5] feat: extend i18n attributes to every content type --- packages/plugins/i18n/server/src/register.ts | 46 ++++++++++---------- 1 file changed, 22 insertions(+), 24 deletions(-) diff --git a/packages/plugins/i18n/server/src/register.ts b/packages/plugins/i18n/server/src/register.ts index 2209686f31..75422a6ab9 100644 --- a/packages/plugins/i18n/server/src/register.ts +++ b/packages/plugins/i18n/server/src/register.ts @@ -9,7 +9,7 @@ import enableContentType from './migrations/content-type/enable'; import disableContentType from './migrations/content-type/disable'; export default ({ strapi }: { strapi: Strapi }) => { - extendLocalizedContentTypes(strapi); + extendContentTypes(strapi); addContentManagerLocaleMiddleware(strapi); addContentTypeSyncHooks(strapi); }; @@ -46,37 +46,35 @@ const addContentTypeSyncHooks = (strapi: Strapi) => { }; /** - * Adds locale and localization fields to localized content types + * Adds locale and localization fields to all content types + * Even if content type is not localized, it will have these fields * @param {Strapi} strapi */ -const extendLocalizedContentTypes = (strapi: Strapi) => { - const contentTypeService = getService('content-types'); +const extendContentTypes = (strapi: Strapi) => { const coreApiService = getService('core-api'); Object.values(strapi.contentTypes).forEach((contentType) => { - if (contentTypeService.isLocalizedContentType(contentType)) { - const { attributes } = contentType; + const { attributes } = contentType; - _.set(attributes, 'localizations', { - writable: true, - private: false, - configurable: false, - visible: false, - type: 'relation', - relation: 'oneToMany', - target: contentType.uid, - }); + _.set(attributes, 'localizations', { + writable: true, + private: false, + configurable: false, + visible: false, + type: 'relation', + relation: 'oneToMany', + target: contentType.uid, + }); - _.set(attributes, 'locale', { - writable: true, - private: false, - configurable: false, - visible: false, - type: 'string', - }); + _.set(attributes, 'locale', { + writable: true, + private: false, + configurable: false, + visible: false, + type: 'string', + }); - coreApiService.addCreateLocalizationAction(contentType); - } + coreApiService.addCreateLocalizationAction(contentType); }); if (strapi.plugin('graphql')) { From 856dfd8ee5ae20ceb7ef5f0f19fc8aa598382fa1 Mon Sep 17 00:00:00 2001 From: Marc-Roig Date: Wed, 10 Jan 2024 11:01:21 +0100 Subject: [PATCH 2/5] fix: ignore locale field in api tests --- .../core/strapi/api/validate-query/validate-query.test.api.js | 1 + packages/plugins/users-permissions/server/bootstrap/index.js | 2 ++ 2 files changed, 3 insertions(+) diff --git a/api-tests/core/strapi/api/validate-query/validate-query.test.api.js b/api-tests/core/strapi/api/validate-query/validate-query.test.api.js index 2edca04407..8d0d22f777 100644 --- a/api-tests/core/strapi/api/validate-query/validate-query.test.api.js +++ b/api-tests/core/strapi/api/validate-query/validate-query.test.api.js @@ -745,6 +745,7 @@ describe('Core API - Validate', () => { const allDocumentFields = [ // TODO: Document id should not be in attributes 'documentId', + 'locale', 'name', 'name_non_searchable', 'misc', diff --git a/packages/plugins/users-permissions/server/bootstrap/index.js b/packages/plugins/users-permissions/server/bootstrap/index.js index f0bf234ed6..6659b05f79 100644 --- a/packages/plugins/users-permissions/server/bootstrap/index.js +++ b/packages/plugins/users-permissions/server/bootstrap/index.js @@ -115,6 +115,8 @@ const userSchemaAdditions = () => { 'publishedAt', 'strapi_stage', 'strapi_assignee', + 'locale', + 'localizations', ]; return currentSchema.filter((key) => !(ignoreDiffs.includes(key) || defaultSchema.includes(key))); From fc38bbf22331728938933fe93e2e84a669a908fa Mon Sep 17 00:00:00 2001 From: Marc-Roig Date: Wed, 10 Jan 2024 11:31:48 +0100 Subject: [PATCH 3/5] fix: ignore localizations field in non localized ct --- api-tests/core/content-manager/search.test.api.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/api-tests/core/content-manager/search.test.api.js b/api-tests/core/content-manager/search.test.api.js index 09150e21ec..6dd672220b 100644 --- a/api-tests/core/content-manager/search.test.api.js +++ b/api-tests/core/content-manager/search.test.api.js @@ -181,7 +181,10 @@ describe('Search query', () => { expect(Array.isArray(res.body.results)).toBe(true); expect(res.body.results.length).toBe(data.beds.length); - expect(res.body.results.map(omit(CREATOR_FIELDS))).toEqual(expect.arrayContaining(data.beds)); + // TODO V5: Filter out i18n fields if content type is not localized + expect(res.body.results.map(omit([...CREATOR_FIELDS, 'localizations']))).toEqual( + expect.arrayContaining(data.beds) + ); }); test('search with special characters', async () => { From ed37011ec03bdea5a1ca14af51530bcedffe60dd Mon Sep 17 00:00:00 2001 From: Marc-Roig Date: Wed, 10 Jan 2024 16:55:13 +0100 Subject: [PATCH 4/5] feat: ignore locale parameter on non localized content types --- .../document-service/create.test.api.ts | 19 ++++++++++- .../document-service/find-one.test.api.ts | 13 +++++++- .../resources/fixtures/author.json | 16 +++++++++ .../resources/fixtures/index.js | 1 + .../resources/schemas/author.js | 26 +++++++++++++++ .../resources/schemas/index.js | 1 + .../resources/types/contentTypes.d.ts | 33 +++++++++++++++++++ .../strapi/document-service/utils/index.ts | 15 +++++++++ .../middlewares/defaults/locales.ts | 12 +++++-- 9 files changed, 132 insertions(+), 4 deletions(-) create mode 100644 api-tests/core/strapi/document-service/resources/fixtures/author.json create mode 100644 api-tests/core/strapi/document-service/resources/schemas/author.js diff --git a/api-tests/core/strapi/document-service/create.test.api.ts b/api-tests/core/strapi/document-service/create.test.api.ts index 2d3dbdc07f..27891088ad 100644 --- a/api-tests/core/strapi/document-service/create.test.api.ts +++ b/api-tests/core/strapi/document-service/create.test.api.ts @@ -2,7 +2,7 @@ import { LoadedStrapi } from '@strapi/types'; import { createTestSetup, destroyTestSetup } from '../../../utils/builder-helper'; import { testInTransaction } from '../../../utils/index'; import resources from './resources/index'; -import { ARTICLE_UID, findArticlesDb } from './utils'; +import { ARTICLE_UID, findArticlesDb, AUTHOR_UID } from './utils'; describe('Document Service', () => { let testUtils; @@ -118,5 +118,22 @@ describe('Document Service', () => { }); }) ); + + it( + 'ignores locale parameter on non-localized content type', + testInTransaction(async () => { + const author = await strapi.documents(AUTHOR_UID).create({ + // Should be ignored on non-localized content types + locale: 'fr', + data: { name: 'Author' }, + }); + + // verify that the returned document was updated + expect(author).toMatchObject({ + name: 'Author', + locale: null, // should be null, as it is not a localized content type + }); + }) + ); }); }); diff --git a/api-tests/core/strapi/document-service/find-one.test.api.ts b/api-tests/core/strapi/document-service/find-one.test.api.ts index cc434edc8f..b3c7712627 100644 --- a/api-tests/core/strapi/document-service/find-one.test.api.ts +++ b/api-tests/core/strapi/document-service/find-one.test.api.ts @@ -1,7 +1,7 @@ import { LoadedStrapi } from '@strapi/types'; import { createTestSetup, destroyTestSetup } from '../../../utils/builder-helper'; import resources from './resources/index'; -import { ARTICLE_UID, findArticleDb } from './utils'; +import { ARTICLE_UID, findArticleDb, AUTHOR_UID, findAuthorDb } from './utils'; describe('Document Service', () => { let testUtils; @@ -55,6 +55,17 @@ describe('Document Service', () => { expect(article).toMatchObject(articleDb); }); + it('ignores locale parameter on non-localized content type', async () => { + const authorDb = await findAuthorDb({ name: 'Author1-Draft' }); + + // Locale should be ignored on non-localized content types + const author = await strapi.documents(AUTHOR_UID).findOne(authorDb.documentId, { + locale: 'en', + }); + + expect(author).toMatchObject(authorDb); + }); + it.todo('ignores pagination parameters'); }); }); diff --git a/api-tests/core/strapi/document-service/resources/fixtures/author.json b/api-tests/core/strapi/document-service/resources/fixtures/author.json new file mode 100644 index 0000000000..1b6b31ebb8 --- /dev/null +++ b/api-tests/core/strapi/document-service/resources/fixtures/author.json @@ -0,0 +1,16 @@ +[ + { + "id": 1, + "documentId": "Author1", + "name": "Author1-Draft", + "publishedAt": null, + "locale": null + }, + { + "id": 2, + "documentId": "Author2", + "name": "Author2-Draft", + "publishedAt": null, + "locale": null + } +] diff --git a/api-tests/core/strapi/document-service/resources/fixtures/index.js b/api-tests/core/strapi/document-service/resources/fixtures/index.js index 3aa6887375..2be48d0281 100644 --- a/api-tests/core/strapi/document-service/resources/fixtures/index.js +++ b/api-tests/core/strapi/document-service/resources/fixtures/index.js @@ -5,5 +5,6 @@ module.exports = { // Make sure this is sorted by order to create them 'api::category.category': require('./category.json'), 'api::article.article': require('./article.json'), + 'api::author.author': require('./author.json'), }, }; diff --git a/api-tests/core/strapi/document-service/resources/schemas/author.js b/api-tests/core/strapi/document-service/resources/schemas/author.js new file mode 100644 index 0000000000..0dd1967764 --- /dev/null +++ b/api-tests/core/strapi/document-service/resources/schemas/author.js @@ -0,0 +1,26 @@ +'use strict'; + +module.exports = { + kind: 'collectionType', + collectionName: 'authors', + singularName: 'author', + pluralName: 'authors', + displayName: 'Author', + description: '', + draftAndPublish: true, + pluginOptions: { + i18n: { + localized: false, + }, + }, + attributes: { + name: { + type: 'string', + pluginOptions: { + i18n: { + localized: false, + }, + }, + }, + }, +}; diff --git a/api-tests/core/strapi/document-service/resources/schemas/index.js b/api-tests/core/strapi/document-service/resources/schemas/index.js index 58f79f29a2..22f93339a9 100644 --- a/api-tests/core/strapi/document-service/resources/schemas/index.js +++ b/api-tests/core/strapi/document-service/resources/schemas/index.js @@ -4,6 +4,7 @@ module.exports = { 'content-types': { 'api::category.category': require('./category'), 'api::article.article': require('./article'), + 'api::author.author': require('./author'), }, components: { 'article.comp': require('./comp'), diff --git a/api-tests/core/strapi/document-service/resources/types/contentTypes.d.ts b/api-tests/core/strapi/document-service/resources/types/contentTypes.d.ts index b3dac703dc..575f73cc4e 100644 --- a/api-tests/core/strapi/document-service/resources/types/contentTypes.d.ts +++ b/api-tests/core/strapi/document-service/resources/types/contentTypes.d.ts @@ -618,6 +618,38 @@ export interface ApiArticleArticle extends Schema.CollectionType { }; } +export interface ApiAuthorAuthor extends Schema.CollectionType { + collectionName: 'authors'; + info: { + singularName: 'author'; + pluralName: 'authors'; + displayName: 'Author'; + description: ''; + }; + pluginOptions: { + i18n: { + localized: false; + }; + }; + attributes: { + name: Attribute.String & + Attribute.SetPluginOptions<{ + i18n: { + localized: false; + }; + }>; + createdAt: Attribute.DateTime; + updatedAt: Attribute.DateTime; + publishedAt: Attribute.DateTime; + createdBy: Attribute.Relation<'api::author.author', 'oneToOne', 'admin::user'> & + Attribute.Private; + updatedBy: Attribute.Relation<'api::author.author', 'oneToOne', 'admin::user'> & + Attribute.Private; + localizations: Attribute.Relation<'api::author.author', 'oneToMany', 'api::author.author'>; + locale: Attribute.String; + }; +} + export interface ApiCategoryCategory extends Schema.CollectionType { collectionName: 'categories'; info: { @@ -674,6 +706,7 @@ declare module '@strapi/types' { 'plugin::users-permissions.role': PluginUsersPermissionsRole; 'plugin::users-permissions.user': PluginUsersPermissionsUser; 'api::article.article': ApiArticleArticle; + 'api::author.author': ApiAuthorAuthor; 'api::category.category': ApiCategoryCategory; } } diff --git a/api-tests/core/strapi/document-service/utils/index.ts b/api-tests/core/strapi/document-service/utils/index.ts index 7dc926ee10..cf45d10ac0 100644 --- a/api-tests/core/strapi/document-service/utils/index.ts +++ b/api-tests/core/strapi/document-service/utils/index.ts @@ -1,5 +1,8 @@ import { Attribute } from '@strapi/strapi'; +export const AUTHOR_UID = 'api::author.author'; +export type Author = Attribute.GetAll & { documentId: string; id: number }; + export const ARTICLE_UID = 'api::article.article'; export type Article = Attribute.GetAll & { documentId: string; id: number }; @@ -14,3 +17,15 @@ export const findArticlesDb = async (where: any) => { export const findPublishedArticlesDb = async (documentId) => { return findArticlesDb({ documentId, publishedAt: { $notNull: true } }); }; + +export const findAuthorDb = async (where: any) => { + return (await strapi.query(AUTHOR_UID).findOne({ where })) as Author | undefined; +}; + +export const findAuthorsDb = async (where: any) => { + return (await strapi.query(AUTHOR_UID).findMany({ where })) as Author[]; +}; + +export const findPublishedAuthorsDb = async (documentId) => { + return findAuthorsDb({ documentId, publishedAt: { $notNull: true } }); +}; diff --git a/packages/core/core/src/services/document-service/middlewares/defaults/locales.ts b/packages/core/core/src/services/document-service/middlewares/defaults/locales.ts index 53d078edfb..58cbf90504 100644 --- a/packages/core/core/src/services/document-service/middlewares/defaults/locales.ts +++ b/packages/core/core/src/services/document-service/middlewares/defaults/locales.ts @@ -1,13 +1,19 @@ // TODO: Move to i18n -import { Documents } from '@strapi/types'; +import { Documents, Common } from '@strapi/types'; type Middleware = Documents.Middleware.Middleware; +const isLocalizedContentType = (uid: Common.UID.Schema) => { + const model = strapi.getModel(uid); + return strapi.plugin('i18n').service('content-types').isLocalizedContentType(model); +}; + export const defaultLocale: Middleware = async (ctx, next) => { + if (!isLocalizedContentType(ctx.uid)) return next(ctx); if (!ctx.params) ctx.params = {}; - // Default to en (TODO: Load default locale from db in i18n) if (!ctx.params.locale) { + // Default to en (TODO: Load default locale from db in i18n) ctx.params.locale = 'en'; } @@ -18,6 +24,7 @@ export const defaultLocale: Middleware = async (ctx, next) => { * Add locale lookup query to the params */ export const localeToLookup: Middleware = async (ctx, next) => { + if (!isLocalizedContentType(ctx.uid)) return next(ctx); if (!ctx.params) ctx.params = {}; const lookup = ctx.params.lookup || {}; @@ -34,6 +41,7 @@ export const localeToLookup: Middleware = async (ctx, next) => { * Translate locale status parameter into the data that will be saved */ export const localeToData: Middleware = async (ctx, next) => { + if (!isLocalizedContentType(ctx.uid)) return next(ctx); if (!ctx.params) ctx.params = {}; const data = ctx.params.data || {}; From 03f38a5baf806991ce1784dea209206013581e48 Mon Sep 17 00:00:00 2001 From: Marc-Roig Date: Fri, 12 Jan 2024 09:57:16 +0100 Subject: [PATCH 5/5] fix: release all content types as now d&p is always enabled --- .../admin/src/components/CMReleasesContainer.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/core/content-releases/admin/src/components/CMReleasesContainer.tsx b/packages/core/content-releases/admin/src/components/CMReleasesContainer.tsx index 1b089fb075..415ac7811b 100644 --- a/packages/core/content-releases/admin/src/components/CMReleasesContainer.tsx +++ b/packages/core/content-releases/admin/src/components/CMReleasesContainer.tsx @@ -276,10 +276,8 @@ export const CMReleasesContainer = () => { /** * - Impossible to add entry to release before it exists - * - Content types without draft and publish cannot add entries to release - * TODO v5: All contentTypes will have draft and publish enabled */ - if (isCreatingEntry || !contentType?.options?.draftAndPublish) { + if (isCreatingEntry) { return null; }