diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 67ed587c..a8eab914 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -49,9 +49,11 @@ jobs: - name: Install playground dependencies run: yarn playground:install - name: Build the playground - run: NODE_ENV=ci yarn playground:build + run: yarn playground:build + - name: Run unit tests + run: yarn test:unit - name: Run integration tests - run: NODE_ENV=ci yarn test:integration + run: yarn test:integration - name: Upload coverage to Codecov uses: codecov/codecov-action@v2 with: diff --git a/package.json b/package.json index b2bedf59..40fa72c9 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "playground:build": "cd playground && yarn build", "playground:start": "cd playground && yarn start", "playground:develop": "cd playground && yarn develop", + "test:unit": "ENV_PATH=./playground/.env jest --verbose --runInBand", "test:integration": "ENV_PATH=./playground/.env jest --verbose --runInBand --forceExit --testMatch '**/healthcheck.test.js'" }, "dependencies": { diff --git a/packages/core/server/admin-api/__tests__/query-layer-decorator.test.ts b/packages/core/server/admin-api/__tests__/query-layer-decorator.test.ts new file mode 100644 index 00000000..ae747337 --- /dev/null +++ b/packages/core/server/admin-api/__tests__/query-layer-decorator.test.ts @@ -0,0 +1,175 @@ +import request from 'supertest'; +// @ts-ignore +// eslint-disable-next-line import/no-relative-packages +import { setupStrapi, stopStrapi } from '../../../../../playground/tests/helpers'; + +beforeAll(async () => { + await setupStrapi(); +}); + +afterAll(async () => { + await stopStrapi(); +}); + +describe('Query layer decorator', () => { + it('Create - Should generate a new URL alias', async () => { + const page = await strapi.entityService.create("api::test.test", { + data: { + title: 'Some amazing new page', + }, + populate: ['url_alias'] + }); + + expect(page).toHaveProperty('url_alias.url_path', '/page/some-amazing-new-page'); + expect(page).toHaveProperty('url_alias.generated', true); + expect(page).toHaveProperty('url_alias.contenttype', 'api::test.test'); + }); + + it('Create - Should re-generate a pre-created URL alias if generated is set to true', async () => { + const alias = await strapi.entityService.create("plugin::webtools.url-alias", { + data: { + url_path: '/generated-pre-created-path', + generated: true, + contenttype: 'api::test.test', + }, + }); + + const page = await strapi.entityService.create("api::test.test", { + data: { + title: 'Generated amazing new page', + url_alias: alias.id, + }, + populate: ['url_alias'] + }); + + expect(page).toHaveProperty('url_alias.id', alias.id); + expect(page).toHaveProperty('url_alias.url_path', '/page/generated-amazing-new-page'); + expect(page).toHaveProperty('url_alias.generated', true); + expect(page).toHaveProperty('url_alias.contenttype', 'api::test.test'); + }); + + it('Create - Should not re-generate a pre-created URL alias if generated is set to false', async () => { + const alias = await strapi.entityService.create("plugin::webtools.url-alias", { + data: { + url_path: '/pre-created-path', + generated: false, + contenttype: 'api::test.test', + }, + }); + + const page = await strapi.entityService.create("api::test.test", { + data: { + title: 'Some amazing new page', + url_alias: alias.id, + }, + populate: ['url_alias'] + }); + + expect(page).toHaveProperty('url_alias.id', alias.id); + expect(page).toHaveProperty('url_alias.url_path', '/pre-created-path'); + expect(page).toHaveProperty('url_alias.generated', false); + }); + + it('Update - Should generate a new URL alias if none is present', async () => { + const page = await strapi.entityService.create("api::test.test", { + data: { + title: 'Some about to be updated new page', + }, + populate: ['url_alias'] + }); + + const oldAliasId = page.url_alias.id; + + // Delete the created url alias to make sure none is present + // at the time of running the .update() query. + await strapi.entityService.delete("plugin::webtools.url-alias", oldAliasId); + + const updatedPage = await strapi.entityService.update("api::test.test", page.id, { + data: { + // @ts-ignore + title: 'Some updated page', + }, + populate: ['url_alias'] + }); + + expect(updatedPage?.url_alias?.id).not.toBe(oldAliasId); + expect(updatedPage).toHaveProperty('url_alias.url_path', '/page/some-updated-page'); + expect(updatedPage).toHaveProperty('url_alias.generated', true); + expect(updatedPage).toHaveProperty('url_alias.contenttype', 'api::test.test'); + }); + + it('Update - Should re-generate an existing URL alias if generated is set to true', async () => { + const page = await strapi.entityService.create("api::test.test", { + data: { + title: 'Some about to be updated new page', + }, + populate: ['url_alias'] + }); + + expect(page).toHaveProperty('url_alias.url_path', '/page/some-about-to-be-updated-new-page') + expect(page).toHaveProperty('url_alias.generated', true); + + const updatedPage = await strapi.entityService.update("api::test.test", page.id, { + data: { + // @ts-ignore + title: 'Some updated page with overwritten url alias', + }, + populate: ['url_alias'] + }); + + expect(updatedPage).toHaveProperty('url_alias.id', page.url_alias.id); + expect(updatedPage).toHaveProperty('url_alias.url_path', '/page/some-updated-page-with-overwritten-url-alias'); + expect(updatedPage).toHaveProperty('url_alias.generated', true); + }); + + it('Update - Should not re-generate an existing URL alias if generated is set to false', async () => { + const alias = await strapi.entityService.create("plugin::webtools.url-alias", { + data: { + url_path: '/path-should-not-update', + generated: false, + contenttype: 'api::test.test', + }, + }); + + const page = await strapi.entityService.create("api::test.test", { + data: { + title: 'Some about to be updated new page', + url_alias: alias.id, + }, + populate: ['url_alias'] + }); + + expect(page).toHaveProperty('url_alias.url_path', '/path-should-not-update') + expect(page).toHaveProperty('url_alias.generated', false); + + const updatedPage = await strapi.entityService.update("api::test.test", page.id, { + data: { + // @ts-ignore + title: 'Some updated page', + }, + populate: ['url_alias'] + }); + + expect(updatedPage).toHaveProperty('url_alias.id', page.url_alias.id); + expect(updatedPage).toHaveProperty('url_alias.url_path', '/path-should-not-update'); + expect(updatedPage).toHaveProperty('url_alias.generated', false); + }); + + it('Delete - Should delete the corresponding URL alias as wel', async () => { + const page = await strapi.entityService.create("api::test.test", { + data: { + title: 'Some about to be deleted new page', + }, + populate: ['url_alias'] + }); + + expect(page).toHaveProperty('url_alias.url_path', '/page/some-about-to-be-deleted-new-page') + expect(page).toHaveProperty('url_alias.generated', true); + + await strapi.entityService.delete("api::test.test", page.id); + + const alias = await strapi.entityService.findOne("plugin::webtools.url-alias", page.url_alias.id); + + expect(alias).toBe(null); + }); +}); diff --git a/packages/core/server/content-api/__tests__/core.test.ts b/packages/core/server/content-api/__tests__/core.test.ts new file mode 100644 index 00000000..a74b3e40 --- /dev/null +++ b/packages/core/server/content-api/__tests__/core.test.ts @@ -0,0 +1,115 @@ +import request from 'supertest'; +// @ts-ignore +// eslint-disable-next-line import/no-relative-packages +import { setupStrapi, stopStrapi } from '../../../../../playground/tests/helpers'; + +beforeAll(async () => { + await setupStrapi(); +}); + +afterAll(async () => { + await stopStrapi(); +}); + +describe('Core controller - Router', () => { + it('Should return a 200 if a page was found', async () => { + const page = await request(strapi.server.httpServer) + .get('/api/webtools/router?path=/page/published-test-page') + .expect(200) + .then((data) => data.body); + + expect(page).toHaveProperty('data.attributes.title', 'Published test page'); + expect(page).toHaveProperty('data.attributes.contentType', 'api::test.test'); + }); + + it('Should return a 404 if no page was found', async () => { + const page = await request(strapi.server.httpServer) + .get('/api/webtools/router?path=/page/does-not-exist') + .expect(404) + .then((data) => data.body); + + expect(page).toHaveProperty('data', null); + expect(page).toHaveProperty('error.status', 404); + }); + + it('Should return a 403 if the user has insufficient rights', async () => { + const page = await request(strapi.server.httpServer) + .get('/api/webtools/router?path=/private-category/published') + .expect(403) + .then((data) => data.body); + + expect(page).toHaveProperty('data', null); + expect(page).toHaveProperty('error.status', 403); + }); + + it('Should fetch a draft entries by default', async () => { + const page = await request(strapi.server.httpServer) + .get('/api/webtools/router?path=/page/unpublished-test-page') + .expect(200) + .then((data) => data.body); + + expect(page).toHaveProperty('data.attributes.title', 'Unpublished test page'); + }); + + it('Should not fetch draft entries with publicationState set to live', async () => { + const page = await request(strapi.server.httpServer) + .get('/api/webtools/router?path=/page/unpublished-test-page&publicationState=live') + .expect(404) + .then((data) => data.body); + }); + + it('Should allow query parameters for population', async () => { + const page = await request(strapi.server.httpServer) + .get('/api/webtools/router?path=/page/unpublished-test-page&publicationState=preview&populate=*') + .expect(200) + .then((data) => data.body); + + expect(page.data.attributes.category.data).not.toBe(null); + }); + + it('Should sanitize populated relations without the "find" permission', async () => { + const page = await request(strapi.server.httpServer) + .get('/api/webtools/router?path=/page/published-test-page&populate=*') + .expect(200) + .then((data) => data.body); + + expect(page).not.toHaveProperty('data.attributes.private-category'); + }); + + it('Should not sanitize unpublished populated relations', async () => { + const page = await request(strapi.server.httpServer) + .get('/api/webtools/router?path=/page/published-test-page&populate=*') + .expect(200) + .then((data) => data.body); + + expect(page.data.attributes.category.data).not.toBe(null); + }); + + it('Should sanitize unpublished populated relations with publicationState set to live', async () => { + const page = await request(strapi.server.httpServer) + .get('/api/webtools/router?path=/page/published-test-page&populate=*&publicationState=live') + .expect(200) + .then((data) => data.body); + + expect(page.data.attributes.category.data).toBe(null); + }); + + it('Should allow query parameters for field selection', async () => { + const page = await request(strapi.server.httpServer) + .get('/api/webtools/router?path=/page/published-test-page') + .expect(200) + .then((data) => data.body); + + expect(page).toHaveProperty("data.attributes.createdAt"); + + const filteredPage = await request(strapi.server.httpServer) + .get('/api/webtools/router?path=/page/published-test-page&fields[0]=title') + .expect(200) + .then((data) => data.body); + + expect(filteredPage).not.toHaveProperty("data.attributes.createdAt"); + expect(page).toHaveProperty("data.attributes.title"); + }); + + // it('Should allow query parameters for localization', async () => {}); +}); diff --git a/playground/src/api/category/content-types/category/schema.json b/playground/src/api/category/content-types/category/schema.json new file mode 100644 index 00000000..637ea5f1 --- /dev/null +++ b/playground/src/api/category/content-types/category/schema.json @@ -0,0 +1,29 @@ +{ + "kind": "collectionType", + "collectionName": "categories", + "info": { + "singularName": "category", + "pluralName": "categories", + "displayName": "Category", + "description": "" + }, + "options": { + "draftAndPublish": true + }, + "pluginOptions": { + "webtools": { + "enabled": true + } + }, + "attributes": { + "test": { + "type": "relation", + "relation": "oneToOne", + "target": "api::test.test", + "inversedBy": "category" + }, + "title": { + "type": "string" + } + } +} diff --git a/playground/src/api/category/controllers/category.ts b/playground/src/api/category/controllers/category.ts new file mode 100644 index 00000000..4f9f5213 --- /dev/null +++ b/playground/src/api/category/controllers/category.ts @@ -0,0 +1,7 @@ +/** + * category controller + */ + +import { factories } from '@strapi/strapi' + +export default factories.createCoreController('api::category.category'); diff --git a/playground/src/api/category/routes/category.ts b/playground/src/api/category/routes/category.ts new file mode 100644 index 00000000..29daf72f --- /dev/null +++ b/playground/src/api/category/routes/category.ts @@ -0,0 +1,7 @@ +/** + * category router + */ + +import { factories } from '@strapi/strapi'; + +export default factories.createCoreRouter('api::category.category'); diff --git a/playground/src/api/category/services/category.ts b/playground/src/api/category/services/category.ts new file mode 100644 index 00000000..b4d79e27 --- /dev/null +++ b/playground/src/api/category/services/category.ts @@ -0,0 +1,7 @@ +/** + * category service + */ + +import { factories } from '@strapi/strapi'; + +export default factories.createCoreService('api::category.category'); diff --git a/playground/src/api/private-category/content-types/private-category/schema.json b/playground/src/api/private-category/content-types/private-category/schema.json new file mode 100644 index 00000000..1706d632 --- /dev/null +++ b/playground/src/api/private-category/content-types/private-category/schema.json @@ -0,0 +1,29 @@ +{ + "kind": "collectionType", + "collectionName": "private_categories", + "info": { + "singularName": "private-category", + "pluralName": "private-categories", + "displayName": "Private category", + "description": "" + }, + "options": { + "draftAndPublish": true + }, + "pluginOptions": { + "webtools": { + "enabled": true + } + }, + "attributes": { + "title": { + "type": "string" + }, + "test": { + "type": "relation", + "relation": "oneToOne", + "target": "api::test.test", + "mappedBy": "private_category" + } + } +} diff --git a/playground/src/api/private-category/controllers/private-category.ts b/playground/src/api/private-category/controllers/private-category.ts new file mode 100644 index 00000000..a0fa152e --- /dev/null +++ b/playground/src/api/private-category/controllers/private-category.ts @@ -0,0 +1,7 @@ +/** + * private-category controller + */ + +import { factories } from '@strapi/strapi' + +export default factories.createCoreController('api::private-category.private-category'); diff --git a/playground/src/api/private-category/routes/private-category.ts b/playground/src/api/private-category/routes/private-category.ts new file mode 100644 index 00000000..27cbfc7e --- /dev/null +++ b/playground/src/api/private-category/routes/private-category.ts @@ -0,0 +1,7 @@ +/** + * private-category router + */ + +import { factories } from '@strapi/strapi'; + +export default factories.createCoreRouter('api::private-category.private-category'); diff --git a/playground/src/api/private-category/services/private-category.ts b/playground/src/api/private-category/services/private-category.ts new file mode 100644 index 00000000..50e5adce --- /dev/null +++ b/playground/src/api/private-category/services/private-category.ts @@ -0,0 +1,7 @@ +/** + * private-category service + */ + +import { factories } from '@strapi/strapi'; + +export default factories.createCoreService('api::private-category.private-category'); diff --git a/playground/src/api/test/content-types/test/schema.json b/playground/src/api/test/content-types/test/schema.json index a7541e3d..ceb9bcf2 100644 --- a/playground/src/api/test/content-types/test/schema.json +++ b/playground/src/api/test/content-types/test/schema.json @@ -26,6 +26,18 @@ "localized": true } } + }, + "category": { + "type": "relation", + "relation": "oneToOne", + "target": "api::category.category", + "mappedBy": "test" + }, + "private_category": { + "type": "relation", + "relation": "oneToOne", + "target": "api::private-category.private-category", + "inversedBy": "test" } } } diff --git a/playground/src/index.ts b/playground/src/index.ts index 9df633d6..6990256d 100644 --- a/playground/src/index.ts +++ b/playground/src/index.ts @@ -17,5 +17,118 @@ module.exports = { * This gives you an opportunity to set up your data model, * run jobs, or perform some special logic. */ - async bootstrap({ strapi }: { strapi: Strapi }) {}, + async bootstrap({ strapi }: { strapi: Strapi }) { + // Seed the database with some test data for the integration tests. + if (process.env.NODE_ENV === 'test') { + // Give the public role some permissions to test with + const roles = await strapi + .service('plugin::users-permissions.role') + .find(); + + const publicId = roles.filter((role) => role.type === 'public')[0]?.id; + + if (publicId) { + const publicRole = await strapi + .service('plugin::users-permissions.role') + .findOne(publicId); + + publicRole.permissions['plugin::webtools'] = { + controllers: { + core: { + router: { enabled: true }, + }, + 'url-alias': { + find: { enabled: true }, + }, + }, + }; + + publicRole.permissions['api::test'] = { + controllers: { + test: { + find: { enabled: true }, + }, + }, + }; + + publicRole.permissions['api::category'] = { + controllers: { + category: { + find: { enabled: true }, + }, + }, + }; + + await strapi + .service('plugin::users-permissions.role') + .updateRole(publicRole.id, publicRole); + } + + strapi.entityService.create('plugin::webtools.url-pattern', { + data: { + pattern: '/page/[title]', + label: 'Test API pattern', + code: 'test_api_pattern', + contenttype: 'api::test.test', + languages: [], + } + }); + + strapi.entityService.create('plugin::webtools.url-pattern', { + data: { + pattern: '/category/[title]', + label: 'Category API pattern', + code: 'category_api_pattern', + contenttype: 'api::category.category', + languages: [], + } + }); + + strapi.entityService.create('plugin::webtools.url-pattern', { + data: { + pattern: '/private-category/[title]', + label: 'Private category API pattern', + code: 'private_category_api_pattern', + contenttype: 'api::private-category.private-category', + languages: [], + } + }); + + const privateCategory = await strapi.entityService.create('api::private-category.private-category', { + data: { + title: 'Published', + publishedAt: new Date(), + } + }); + + const publishedCategory = await strapi.entityService.create('api::category.category', { + data: { + title: 'Published category', + publishedAt: new Date(), + } + }); + + const unpublishedCategory = await strapi.entityService.create('api::category.category', { + data: { + title: 'Unpublished category', + } + }); + + strapi.entityService.create('api::test.test', { + data: { + title: 'Published test page', + publishedAt: new Date(), + category: unpublishedCategory.id, + private_category: privateCategory.id, + } + }); + + strapi.entityService.create('api::test.test', { + data: { + title: 'Unpublished test page', + category: publishedCategory.id, + } + }); + } + }, }; diff --git a/playground/types/generated/contentTypes.d.ts b/playground/types/generated/contentTypes.d.ts index 241fde51..763ffd32 100644 --- a/playground/types/generated/contentTypes.d.ts +++ b/playground/types/generated/contentTypes.d.ts @@ -362,62 +362,6 @@ export interface AdminTransferTokenPermission extends Schema.CollectionType { }; } -export interface ApiTestTest extends Schema.CollectionType { - collectionName: 'tests'; - info: { - singularName: 'test'; - pluralName: 'tests'; - displayName: 'test'; - description: ''; - }; - options: { - draftAndPublish: true; - }; - pluginOptions: { - webtools: { - enabled: true; - }; - i18n: { - localized: true; - }; - }; - attributes: { - title: Attribute.String & - Attribute.SetPluginOptions<{ - i18n: { - localized: true; - }; - }>; - createdAt: Attribute.DateTime; - updatedAt: Attribute.DateTime; - publishedAt: Attribute.DateTime; - createdBy: Attribute.Relation<'api::test.test', 'oneToOne', 'admin::user'> & - Attribute.Private; - updatedBy: Attribute.Relation<'api::test.test', 'oneToOne', 'admin::user'> & - Attribute.Private; - url_alias: Attribute.Relation< - 'api::test.test', - 'oneToOne', - 'plugin::webtools.url-alias' - > & - Attribute.Unique & - Attribute.SetPluginOptions<{ - i18n: { - localized: true; - }; - }>; - sitemap_exclude: Attribute.Boolean & - Attribute.Private & - Attribute.DefaultTo; - localizations: Attribute.Relation< - 'api::test.test', - 'oneToMany', - 'api::test.test' - >; - locale: Attribute.String; - }; -} - export interface PluginUploadFile extends Schema.CollectionType { collectionName: 'files'; info: { @@ -459,9 +403,12 @@ export interface PluginUploadFile extends Schema.CollectionType { folderPath: Attribute.String & Attribute.Required & Attribute.Private & - Attribute.SetMinMax<{ - min: 1; - }>; + Attribute.SetMinMax< + { + min: 1; + }, + number + >; createdAt: Attribute.DateTime; updatedAt: Attribute.DateTime; createdBy: Attribute.Relation< @@ -500,9 +447,12 @@ export interface PluginUploadFolder extends Schema.CollectionType { attributes: { name: Attribute.String & Attribute.Required & - Attribute.SetMinMax<{ - min: 1; - }>; + Attribute.SetMinMax< + { + min: 1; + }, + number + >; pathId: Attribute.Integer & Attribute.Required & Attribute.Unique; parent: Attribute.Relation< 'plugin::upload.folder', @@ -521,9 +471,12 @@ export interface PluginUploadFolder extends Schema.CollectionType { >; path: Attribute.String & Attribute.Required & - Attribute.SetMinMax<{ - min: 1; - }>; + Attribute.SetMinMax< + { + min: 1; + }, + number + >; createdAt: Attribute.DateTime; updatedAt: Attribute.DateTime; createdBy: Attribute.Relation< @@ -541,6 +494,104 @@ export interface PluginUploadFolder extends Schema.CollectionType { }; } +export interface PluginContentReleasesRelease extends Schema.CollectionType { + collectionName: 'strapi_releases'; + info: { + singularName: 'release'; + pluralName: 'releases'; + displayName: 'Release'; + }; + options: { + draftAndPublish: false; + }; + pluginOptions: { + 'content-manager': { + visible: false; + }; + 'content-type-builder': { + visible: false; + }; + }; + attributes: { + name: Attribute.String & Attribute.Required; + releasedAt: Attribute.DateTime; + actions: Attribute.Relation< + 'plugin::content-releases.release', + 'oneToMany', + 'plugin::content-releases.release-action' + >; + createdAt: Attribute.DateTime; + updatedAt: Attribute.DateTime; + createdBy: Attribute.Relation< + 'plugin::content-releases.release', + 'oneToOne', + 'admin::user' + > & + Attribute.Private; + updatedBy: Attribute.Relation< + 'plugin::content-releases.release', + 'oneToOne', + 'admin::user' + > & + Attribute.Private; + sitemap_exclude: Attribute.Boolean & + Attribute.Private & + Attribute.DefaultTo; + }; +} + +export interface PluginContentReleasesReleaseAction + extends Schema.CollectionType { + collectionName: 'strapi_release_actions'; + info: { + singularName: 'release-action'; + pluralName: 'release-actions'; + displayName: 'Release Action'; + }; + options: { + draftAndPublish: false; + }; + pluginOptions: { + 'content-manager': { + visible: false; + }; + 'content-type-builder': { + visible: false; + }; + }; + attributes: { + type: Attribute.Enumeration<['publish', 'unpublish']> & Attribute.Required; + entry: Attribute.Relation< + 'plugin::content-releases.release-action', + 'morphToOne' + >; + contentType: Attribute.String & Attribute.Required; + locale: Attribute.String; + release: Attribute.Relation< + 'plugin::content-releases.release-action', + 'manyToOne', + 'plugin::content-releases.release' + >; + createdAt: Attribute.DateTime; + updatedAt: Attribute.DateTime; + createdBy: Attribute.Relation< + 'plugin::content-releases.release-action', + 'oneToOne', + 'admin::user' + > & + Attribute.Private; + updatedBy: Attribute.Relation< + 'plugin::content-releases.release-action', + 'oneToOne', + 'admin::user' + > & + Attribute.Private; + sitemap_exclude: Attribute.Boolean & + Attribute.Private & + Attribute.DefaultTo; + }; +} + export interface PluginWebtoolsUrlAlias extends Schema.CollectionType { collectionName: 'wt_url_alias'; info: { @@ -696,10 +747,13 @@ export interface PluginI18NLocale extends Schema.CollectionType { }; attributes: { name: Attribute.String & - Attribute.SetMinMax<{ - min: 1; - max: 50; - }>; + Attribute.SetMinMax< + { + min: 1; + max: 50; + }, + number + >; code: Attribute.String & Attribute.Unique; createdAt: Attribute.DateTime; updatedAt: Attribute.DateTime; @@ -872,6 +926,183 @@ export interface PluginUsersPermissionsUser extends Schema.CollectionType { }; } +export interface ApiCategoryCategory extends Schema.CollectionType { + collectionName: 'categories'; + info: { + singularName: 'category'; + pluralName: 'categories'; + displayName: 'Category'; + description: ''; + }; + options: { + draftAndPublish: true; + }; + pluginOptions: { + webtools: { + enabled: true; + }; + }; + attributes: { + test: Attribute.Relation< + 'api::category.category', + 'oneToOne', + 'api::test.test' + >; + title: Attribute.String; + createdAt: Attribute.DateTime; + updatedAt: Attribute.DateTime; + publishedAt: Attribute.DateTime; + createdBy: Attribute.Relation< + 'api::category.category', + 'oneToOne', + 'admin::user' + > & + Attribute.Private; + updatedBy: Attribute.Relation< + 'api::category.category', + 'oneToOne', + 'admin::user' + > & + Attribute.Private; + url_alias: Attribute.Relation< + 'api::category.category', + 'oneToOne', + 'plugin::webtools.url-alias' + > & + Attribute.Unique & + Attribute.SetPluginOptions<{ + i18n: { + localized: true; + }; + }>; + sitemap_exclude: Attribute.Boolean & + Attribute.Private & + Attribute.DefaultTo; + }; +} + +export interface ApiPrivateCategoryPrivateCategory + extends Schema.CollectionType { + collectionName: 'private_categories'; + info: { + singularName: 'private-category'; + pluralName: 'private-categories'; + displayName: 'Private category'; + description: ''; + }; + options: { + draftAndPublish: true; + }; + pluginOptions: { + webtools: { + enabled: true; + }; + }; + attributes: { + title: Attribute.String; + test: Attribute.Relation< + 'api::private-category.private-category', + 'oneToOne', + 'api::test.test' + >; + createdAt: Attribute.DateTime; + updatedAt: Attribute.DateTime; + publishedAt: Attribute.DateTime; + createdBy: Attribute.Relation< + 'api::private-category.private-category', + 'oneToOne', + 'admin::user' + > & + Attribute.Private; + updatedBy: Attribute.Relation< + 'api::private-category.private-category', + 'oneToOne', + 'admin::user' + > & + Attribute.Private; + url_alias: Attribute.Relation< + 'api::private-category.private-category', + 'oneToOne', + 'plugin::webtools.url-alias' + > & + Attribute.Unique & + Attribute.SetPluginOptions<{ + i18n: { + localized: true; + }; + }>; + sitemap_exclude: Attribute.Boolean & + Attribute.Private & + Attribute.DefaultTo; + }; +} + +export interface ApiTestTest extends Schema.CollectionType { + collectionName: 'tests'; + info: { + singularName: 'test'; + pluralName: 'tests'; + displayName: 'test'; + description: ''; + }; + options: { + draftAndPublish: true; + }; + pluginOptions: { + webtools: { + enabled: true; + }; + i18n: { + localized: true; + }; + }; + attributes: { + title: Attribute.String & + Attribute.SetPluginOptions<{ + i18n: { + localized: true; + }; + }>; + category: Attribute.Relation< + 'api::test.test', + 'oneToOne', + 'api::category.category' + >; + private_category: Attribute.Relation< + 'api::test.test', + 'oneToOne', + 'api::private-category.private-category' + >; + createdAt: Attribute.DateTime; + updatedAt: Attribute.DateTime; + publishedAt: Attribute.DateTime; + createdBy: Attribute.Relation<'api::test.test', 'oneToOne', 'admin::user'> & + Attribute.Private; + updatedBy: Attribute.Relation<'api::test.test', 'oneToOne', 'admin::user'> & + Attribute.Private; + url_alias: Attribute.Relation< + 'api::test.test', + 'oneToOne', + 'plugin::webtools.url-alias' + > & + Attribute.Unique & + Attribute.SetPluginOptions<{ + i18n: { + localized: true; + }; + }>; + sitemap_exclude: Attribute.Boolean & + Attribute.Private & + Attribute.DefaultTo; + localizations: Attribute.Relation< + 'api::test.test', + 'oneToMany', + 'api::test.test' + >; + locale: Attribute.String; + }; +} + declare module '@strapi/types' { export module Shared { export interface ContentTypes { @@ -882,9 +1113,10 @@ declare module '@strapi/types' { 'admin::api-token-permission': AdminApiTokenPermission; 'admin::transfer-token': AdminTransferToken; 'admin::transfer-token-permission': AdminTransferTokenPermission; - 'api::test.test': ApiTestTest; 'plugin::upload.file': PluginUploadFile; 'plugin::upload.folder': PluginUploadFolder; + 'plugin::content-releases.release': PluginContentReleasesRelease; + 'plugin::content-releases.release-action': PluginContentReleasesReleaseAction; 'plugin::webtools.url-alias': PluginWebtoolsUrlAlias; 'plugin::webtools.url-pattern': PluginWebtoolsUrlPattern; 'plugin::webtools-addon-sitemap.sitemap': PluginWebtoolsAddonSitemapSitemap; @@ -892,6 +1124,9 @@ declare module '@strapi/types' { 'plugin::users-permissions.permission': PluginUsersPermissionsPermission; 'plugin::users-permissions.role': PluginUsersPermissionsRole; 'plugin::users-permissions.user': PluginUsersPermissionsUser; + 'api::category.category': ApiCategoryCategory; + 'api::private-category.private-category': ApiPrivateCategoryPrivateCategory; + 'api::test.test': ApiTestTest; } } }