diff --git a/README.md b/README.md index 2206cd4079..cd4316975b 100644 --- a/README.md +++ b/README.md @@ -91,10 +91,12 @@ Complete installation requirements can be found in the documentation under = 5.7.8 -- MariaDB >= 10.2.7 -- PostgreSQL >= 10 -- SQLite >= 3 +| Database | Minimum | Recommended | +| ---------- | ------- | ----------- | +| MySQL | 5.7.8 | 8.0 | +| MariaDB | 10.3 | 10.6 | +| PostgreSQL | 11.0 | 14.0 | +| SQLite | 3 | 3 | **We recommend always using the latest version of Strapi to start your new projects**. diff --git a/packages/core/admin/admin/src/translations/es.json b/packages/core/admin/admin/src/translations/es.json index a0cabcf3a7..5cbe0d33a3 100644 --- a/packages/core/admin/admin/src/translations/es.json +++ b/packages/core/admin/admin/src/translations/es.json @@ -759,7 +759,7 @@ "global.table.header.roles": "Roles", "global.table.header.username": "Nombre de usuario", "global.type": "Tipo", - "global.users": "Useuarios", + "global.users": "Usuarios", "notification.warning.404": "404 - No Encontrado" } diff --git a/packages/core/database/lib/entity-manager.js b/packages/core/database/lib/entity-manager.js index e074b27408..7d492e4b71 100644 --- a/packages/core/database/lib/entity-manager.js +++ b/packages/core/database/lib/entity-manager.js @@ -776,7 +776,6 @@ const createEntityManager = (db) => { } }, - // TODO: support multiple relations at once with the populate syntax // TODO: add lifecycle events async populate(uid, entity, populate) { const entry = await this.findOne(uid, { @@ -788,30 +787,37 @@ const createEntityManager = (db) => { return { ...entity, ...entry }; }, - // TODO: support multiple relations at once with the populate syntax // TODO: add lifecycle events - async load(uid, entity, field, params) { + async load(uid, entity, fields, params) { const { attributes } = db.metadata.get(uid); - const attribute = attributes[field]; + const fieldsArr = _.castArray(fields); + fieldsArr.forEach((field) => { + const attribute = attributes[field]; - if (!attribute || attribute.type !== 'relation') { - throw new Error('Invalid load. Expected a relational attribute'); - } + if (!attribute || attribute.type !== 'relation') { + throw new Error(`Invalid load. Expected ${field} to be a relational attribute`); + } + }); const entry = await this.findOne(uid, { select: ['id'], where: { id: entity.id }, - populate: { - [field]: params || true, - }, + populate: fieldsArr.reduce((acc, field) => { + acc[field] = params || true; + return acc; + }, {}), }); if (!entry) { return null; } - return entry[field]; + if (Array.isArray(fields)) { + return _.pick(fields, entry); + } + + return entry[fields]; }, // cascading diff --git a/packages/core/strapi/README.md b/packages/core/strapi/README.md index 575ab98e86..cd4316975b 100644 --- a/packages/core/strapi/README.md +++ b/packages/core/strapi/README.md @@ -1,8 +1,12 @@

- + Strapi logo + + Strapi logo +

+

API creation made simple, secure and fast.

The most advanced open-source headless CMS to build powerful APIs with no effort.

Try live demo

@@ -18,6 +22,9 @@ Strapi on Discord + + Strapi Nightly Release Build Status +


@@ -79,15 +86,17 @@ Complete installation requirements can be found in the documentation under = 12 <= 16 +- NodeJS >= 14 <= 16 - NPM >= 6.x **Database:** -- MySQL >= 5.7.8 -- MariaDB >= 10.2.7 -- PostgreSQL >= 10 -- SQLite >= 3 +| Database | Minimum | Recommended | +| ---------- | ------- | ----------- | +| MySQL | 5.7.8 | 8.0 | +| MariaDB | 10.3 | 10.6 | +| PostgreSQL | 11.0 | 14.0 | +| SQLite | 3 | 3 | **We recommend always using the latest version of Strapi to start your new projects**. @@ -114,7 +123,7 @@ For general help using Strapi, please refer to [the official Strapi documentatio - [Discord](https://discord.strapi.io) (For live discussion with the Community and Strapi team) - [GitHub](https://github.com/strapi/strapi) (Bug reports, Contributions) - [Community Forum](https://forum.strapi.io) (Questions and Discussions) -- [Roadmap & Feature Requests](https://feedback.strapi.io/) +- [Feedback section](https://feedback.strapi.io) (Roadmap, Feature requests) - [Twitter](https://twitter.com/strapijs) (Get the news fast) - [Facebook](https://www.facebook.com/Strapi-616063331867161) - [YouTube Channel](https://www.youtube.com/strapi) (Learn from Video Tutorials) @@ -125,7 +134,7 @@ Follow our [migration guides](https://docs.strapi.io/developer-docs/latest/updat ## Roadmap -Check out our [roadmap](https://feedback.strapi.io/) to get informed of the latest features released and the upcoming ones. You may also give us insights and vote for a specific feature. +Check out our [roadmap](https://feedback.strapi.io) to get informed of the latest features released and the upcoming ones. You may also give us insights and vote for a specific feature. ## Documentation diff --git a/packages/core/strapi/lib/services/entity-service/components.js b/packages/core/strapi/lib/services/entity-service/components.js index 8b11ee31ca..17cd90c823 100644 --- a/packages/core/strapi/lib/services/entity-service/components.js +++ b/packages/core/strapi/lib/services/entity-service/components.js @@ -5,6 +5,7 @@ const { has, prop, omit, toString } = require('lodash/fp'); const { contentTypes: contentTypesUtils } = require('@strapi/utils'); const { ApplicationError } = require('@strapi/utils').errors; +const { getComponentAttributes } = require('@strapi/utils').contentTypes; const omitComponentData = (contentType, data) => { const { attributes } = contentType; @@ -100,6 +101,18 @@ const createComponents = async (uid, data) => { return componentBody; }; +/** + * @param {str} uid + * @param {object} entity + * @return {Promise<{uid: string, entity: object}>} + */ +const getComponents = async (uid, entity) => { + const componentAttributes = getComponentAttributes(strapi.getModel(uid)); + + if (_.isEmpty(componentAttributes)) return {}; + return strapi.query(uid).load(entity, componentAttributes); +}; + /* delete old components create or update @@ -270,7 +283,10 @@ const deleteComponents = async (uid, entityToDelete) => { if (attribute.type === 'component') { const { component: componentUID } = attribute; - const value = await strapi.query(uid).load(entityToDelete, attributeName); + // Load attribute value if it's not already loaded + const value = + entityToDelete[attributeName] || + (await strapi.query(uid).load(entityToDelete, attributeName)); if (!value) { continue; @@ -286,7 +302,9 @@ const deleteComponents = async (uid, entityToDelete) => { } if (attribute.type === 'dynamiczone') { - const value = await strapi.query(uid).load(entityToDelete, attributeName); + const value = + entityToDelete[attributeName] || + (await strapi.query(uid).load(entityToDelete, attributeName)); if (!value) { continue; @@ -352,7 +370,9 @@ const deleteComponent = async (uid, componentToDelete) => { module.exports = { omitComponentData, + getComponents, createComponents, updateComponents, deleteComponents, + deleteComponent, }; diff --git a/packages/core/strapi/lib/services/entity-service/index.js b/packages/core/strapi/lib/services/entity-service/index.js index f5bdffdb68..54277520c3 100644 --- a/packages/core/strapi/lib/services/entity-service/index.js +++ b/packages/core/strapi/lib/services/entity-service/index.js @@ -14,6 +14,7 @@ const uploadFiles = require('../utils/upload-files'); const { omitComponentData, + getComponents, createComponents, updateComponents, deleteComponents, @@ -213,8 +214,10 @@ const createDefaultImplementation = ({ strapi, db, eventHub, entityValidator }) return null; } + const componentsToDelete = await getComponents(uid, entityToDelete); + await db.query(uid).delete({ where: { id: entityToDelete.id } }); - await deleteComponents(uid, entityToDelete); + await deleteComponents(uid, { ...entityToDelete, ...componentsToDelete }); await this.emitEvent(uid, ENTRY_DELETE, entityToDelete); @@ -234,8 +237,12 @@ const createDefaultImplementation = ({ strapi, db, eventHub, entityValidator }) return null; } + const componentsToDelete = await Promise.all( + entitiesToDelete.map((entityToDelete) => getComponents(uid, entityToDelete)) + ); + const deletedEntities = await db.query(uid).deleteMany(query); - await Promise.all(entitiesToDelete.map((entity) => deleteComponents(uid, entity))); + await Promise.all(componentsToDelete.map((compos) => deleteComponents(uid, compos))); // Trigger webhooks. One for each entity await Promise.all(entitiesToDelete.map((entity) => this.emitEvent(uid, ENTRY_DELETE, entity))); diff --git a/packages/core/strapi/tests/api/basic-dp-compo.test.e2e.js b/packages/core/strapi/tests/api/basic-dp-compo.test.e2e.js index f6238e1895..7a53b1ad8b 100644 --- a/packages/core/strapi/tests/api/basic-dp-compo.test.e2e.js +++ b/packages/core/strapi/tests/api/basic-dp-compo.test.e2e.js @@ -158,6 +158,15 @@ describe('Core API - Basic + compo + draftAndPublish', () => { data.productsWithCompoAndDP.shift(); }); + describe('database state', () => { + test('components have been removed from the database', async () => { + const dbComponents = await strapi.db + .query('default.compo') + .findMany({ name: 'compo name updated' }); + expect(dbComponents).toHaveLength(0); + }); + }); + describe('validation', () => { test('Cannot create product with compo - compo required', async () => { const product = { diff --git a/packages/core/utils/lib/content-types.js b/packages/core/utils/lib/content-types.js index 89a019ba09..6c8e530102 100644 --- a/packages/core/utils/lib/content-types.js +++ b/packages/core/utils/lib/content-types.js @@ -106,6 +106,20 @@ const isPrivateAttribute = (model = {}, attributeName) => { const isScalarAttribute = (attribute) => { return !['media', 'component', 'relation', 'dynamiczone'].includes(attribute.type); }; +const isMediaAttribute = (attribute) => attribute.type === 'media'; +const isRelationalAttribute = (attribute) => attribute.type === 'relation'; +const isComponentAttribute = (attribute) => ['component', 'dynamiczone'].includes(attribute.type); + +const getComponentAttributes = (schema) => { + return _.reduce( + schema.attributes, + (acc, attr, attrName) => { + if (isComponentAttribute(attr)) acc.push(attrName); + return acc; + }, + [] + ); +}; const getScalarAttributes = (schema) => { return _.reduce( @@ -118,10 +132,6 @@ const getScalarAttributes = (schema) => { ); }; -const isMediaAttribute = (attribute) => attribute.type === 'media'; -const isRelationalAttribute = (attribute) => attribute.type === 'relation'; -const isComponentAttribute = (attribute) => ['component', 'dynamiczone'].includes(attribute.type); - /** * Checks if an attribute is of type `type` * @param {object} attribute @@ -152,6 +162,7 @@ module.exports = { isPrivateAttribute, constants, getNonWritableAttributes, + getComponentAttributes, getScalarAttributes, getWritableAttributes, isWritableAttribute, diff --git a/packages/plugins/users-permissions/documentation/content-api.yaml b/packages/plugins/users-permissions/documentation/content-api.yaml index caf659266a..e87520b446 100644 --- a/packages/plugins/users-permissions/documentation/content-api.yaml +++ b/packages/plugins/users-permissions/documentation/content-api.yaml @@ -102,6 +102,13 @@ paths: tags: - Users-Permissions - Auth summary: Default Callback from provider auth + parameters: + - name: provider + in: path + required: true + description: Provider name + schema: + type: string responses: 200: description: Returns a jwt token and user info @@ -196,15 +203,16 @@ paths: application/json: schema: type: object + required: + - password + - currentPassword + - passwordConfirmation properties: password: - required: true type: string currentPassword: - required: true type: string passwordConfirmation: - required: true type: string responses: 200: @@ -219,7 +227,6 @@ paths: application/json: schema: $ref: '#/components/schemas/Error' - /auth/email-confirmation: get: tags: @@ -228,7 +235,8 @@ paths: parameters: - in: query name: confirmation - type: string + schema: + type: string description: confirmation token received by email responses: 301: @@ -319,7 +327,6 @@ paths: application/json: schema: $ref: '#/components/schemas/Error' - /users-permissions/roles: get: tags: @@ -364,7 +371,7 @@ paths: - Users-Permissions - Users & Roles summary: Create a role requestBody: - $ref: '#/components/schemas/Users-Permissions-RoleRequest' + $ref: '#/components/requestBodies/Users-Permissions-RoleRequest' responses: 200: description: Returns ok if the role was create @@ -391,10 +398,13 @@ paths: parameters: - in: path name: id - type: string + required: true + schema: + type: string description: role Id responses: 200: + description: Returns the role content: application/json: schema: @@ -431,10 +441,12 @@ paths: parameters: - in: path name: role - type: string + required: true + schema: + type: string description: role Id requestBody: - $ref: '#/components/schemas/Users-Permissions-RoleRequest' + $ref: '#/components/requestBodies/Users-Permissions-RoleRequest' responses: 200: description: Returns ok if the role was udpated @@ -460,7 +472,9 @@ paths: parameters: - in: path name: role - type: string + required: true + schema: + type: string description: role Id responses: 200: @@ -487,7 +501,7 @@ paths: summary: Get list of users responses: 200: - summary: Returns an array of users + description: Returns an array of users content: application/json: schema: @@ -520,17 +534,17 @@ paths: application/json: schema: type: object + required: + - username + - email + - password properties: email: type: string - required: true username: type: string - required: true password: type: string - required: true - example: username: foo email: foo@strapi.io @@ -569,7 +583,6 @@ paths: application/json: schema: $ref: '#/components/schemas/Error' - /users/{id}: get: tags: @@ -578,10 +591,13 @@ paths: parameters: - in: path name: id - type: string + required: true + schema: + type: string description: user Id responses: 200: + description: Returns a user content: application/json: schema: @@ -601,7 +617,6 @@ paths: application/json: schema: $ref: '#/components/schemas/Error' - put: tags: - Users-Permissions - Users & Roles @@ -609,7 +624,9 @@ paths: parameters: - in: path name: id - type: string + required: true + schema: + type: string description: user Id requestBody: required: true @@ -617,17 +634,17 @@ paths: application/json: schema: type: object + required: + - username + - email + - password properties: email: type: string - required: true username: type: string - required: true password: type: string - required: true - example: username: foo email: foo@strapi.io @@ -666,13 +683,19 @@ paths: application/json: schema: $ref: '#/components/schemas/Error' - delete: tags: - Users-Permissions - Users & Roles summary: Delete a user + parameters: + - in: path + name: id + required: true + schema: + type: string + description: user Id responses: - 200: + "200": description: Returns deleted user info content: application/json: @@ -817,6 +840,10 @@ components: policy: type: string + parameters: + responses: + examples: + requestBodies: Users-Permissions-RoleRequest: required: true content: @@ -842,7 +869,3 @@ components: find: enabled: true - parameters: - responses: - examples: - requestBodies: diff --git a/packages/providers/upload-aws-s3/package.json b/packages/providers/upload-aws-s3/package.json index 5941876141..58538968ae 100644 --- a/packages/providers/upload-aws-s3/package.json +++ b/packages/providers/upload-aws-s3/package.json @@ -37,7 +37,7 @@ "test": "echo \"no tests yet\"" }, "dependencies": { - "aws-sdk": "2.1188.0", + "aws-sdk": "2.1208.0", "lodash": "4.17.21" }, "engines": { diff --git a/yarn.lock b/yarn.lock index 277105be11..d452568320 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7493,10 +7493,10 @@ available-typed-arrays@^1.0.5: resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz#92f95616501069d07d10edb2fc37d3e1c65123b7" integrity sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw== -aws-sdk@2.1188.0: - version "2.1188.0" - resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.1188.0.tgz#94b710948ef2924093a8d6fe42443a792385afa2" - integrity sha512-4KXwjRjbCzU1luTOeH+ded92H51I4UuHaZzx2EI+JA0II1+q48heTxFlFd7yp7jGz9UwjPb6k12Jv1W3r0JWxA== +aws-sdk@2.1208.0: + version "2.1208.0" + resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.1208.0.tgz#7dc8c877652d2b1ea126a3c256157c1cbd2e20e2" + integrity sha512-Wyq9TJyvRZMcHmcGwmOJag5/94m+Gq3BHcK2klwFvgUf1OWWJc4OYqmi90d7qJ09ydTeGGMeodNJildQdkOrYQ== dependencies: buffer "4.9.2" events "1.1.1"