diff --git a/packages/strapi-plugin-i18n/services/entity-service-decorator.js b/packages/strapi-plugin-i18n/services/entity-service-decorator.js index 25ed7621c2..8442a4e5fe 100644 --- a/packages/strapi-plugin-i18n/services/entity-service-decorator.js +++ b/packages/strapi-plugin-i18n/services/entity-service-decorator.js @@ -15,10 +15,6 @@ const SINGLE_ENTRY_ACTIONS = ['findOne', 'update', 'delete']; const wrapParams = async (params = {}, ctx = {}) => { const { action } = ctx; - if (has('id', params) && SINGLE_ENTRY_ACTIONS.includes(action)) { - return params; - } - if (has(LOCALE_QUERY_FILTER, params)) { if (params[LOCALE_QUERY_FILTER] === 'all') { return omit(LOCALE_QUERY_FILTER, params); @@ -30,6 +26,10 @@ const wrapParams = async (params = {}, ctx = {}) => { }; } + if (has('id', params) && SINGLE_ENTRY_ACTIONS.includes(action)) { + return params; + } + const { getDefaultLocale } = getService('locales'); return { diff --git a/packages/strapi/lib/core-api/__tests__/service.test.js b/packages/strapi/lib/core-api/__tests__/service.test.js index c0f208a81a..1053f76f87 100644 --- a/packages/strapi/lib/core-api/__tests__/service.test.js +++ b/packages/strapi/lib/core-api/__tests__/service.test.js @@ -1,7 +1,7 @@ 'use strict'; const _ = require('lodash'); -const createService = require('../service'); +const { createService, getFetchParams } = require('../service'); const maxLimit = 50; const defaultLimit = 20; @@ -69,6 +69,9 @@ describe('Default Service', () => { find: jest.fn(() => Promise.resolve(null)), create: jest.fn(() => Promise.resolve({ id: 1 })), }, + query() { + return { count() {} }; + }, }; const model = { @@ -102,6 +105,9 @@ describe('Default Service', () => { find: jest.fn(() => Promise.resolve({ id: 1 })), update: jest.fn(() => Promise.resolve({ id: 1 })), }, + query() { + return { count() {} }; + }, }; const model = { @@ -194,7 +200,7 @@ describe('getFetchParams', () => { ['1000 if _limit=1000 and no max allowed limit is set', { _limit: 1000 }, 1000], ])('Sets _limit parameter to %s', (description, input, expected) => { strapi.config.api.rest.maxLimit = input.maxLimit; - expect(createService.getFetchParams({ _limit: input._limit })).toMatchObject({ + expect(getFetchParams({ _limit: input._limit })).toMatchObject({ _limit: expected, }); }); diff --git a/packages/strapi/lib/core-api/controller.js b/packages/strapi/lib/core-api/controller.js index f5e91d6df6..8f7e785414 100644 --- a/packages/strapi/lib/core-api/controller.js +++ b/packages/strapi/lib/core-api/controller.js @@ -43,12 +43,14 @@ const createSingleTypeController = ({ model, service }) => { * @return {Object} */ async update(ctx) { + const { body, query } = ctx.request; + let entity; if (ctx.is('multipart')) { const { data, files } = parseMultipartData(ctx); - entity = await service.createOrUpdate(data, { files }); + entity = await service.createOrUpdate(data, { files, query }); } else { - entity = await service.createOrUpdate(ctx.request.body); + entity = await service.createOrUpdate(body, { query }); } return sanitize(entity); diff --git a/packages/strapi/lib/core-api/index.js b/packages/strapi/lib/core-api/index.js index 183a272f9c..05a8152128 100644 --- a/packages/strapi/lib/core-api/index.js +++ b/packages/strapi/lib/core-api/index.js @@ -6,7 +6,7 @@ const _ = require('lodash'); const createController = require('./controller'); -const createService = require('./service'); +const { createService } = require('./service'); /** * Returns a service and a controller built based on the content type passed diff --git a/packages/strapi/lib/core-api/service.js b/packages/strapi/lib/core-api/service.js deleted file mode 100644 index 1c898da736..0000000000 --- a/packages/strapi/lib/core-api/service.js +++ /dev/null @@ -1,245 +0,0 @@ -'use strict'; - -const _ = require('lodash'); -const utils = require('strapi-utils'); -const { - contentTypes: { - hasDraftAndPublish, - constants: { PUBLISHED_AT_ATTRIBUTE, DP_PUB_STATE_LIVE }, - }, -} = require('strapi-utils'); - -/** - * Default limit values from config - * @return {{maxLimit: number, defaultLimit: number}} - */ -const getLimitConfigDefaults = () => ({ - defaultLimit: _.toNumber(strapi.config.get('api.rest.defaultLimit', 100)), - maxLimit: _.toNumber(strapi.config.get('api.rest.maxLimit')) || null, -}); - -const getLimitParam = params => { - const { defaultLimit, maxLimit } = getLimitConfigDefaults(); - if (params._limit === undefined) { - return defaultLimit; - } - - const limit = _.toNumber(params._limit); - // if there is max limit set and params._limit exceeds this number, return configured max limit - if (maxLimit && (limit === -1 || limit > maxLimit)) { - return maxLimit; - } - - return limit; -}; - -const getFetchParams = (params = {}) => { - const defaultParams = {}; - - Object.assign(defaultParams, { - _publicationState: DP_PUB_STATE_LIVE, - }); - - return { - ...defaultParams, - ...params, - _limit: getLimitParam(params), - }; -}; - -/** - * default service - * - */ -const createCoreService = ({ model, strapi }) => { - const serviceFactory = - model.kind === 'singleType' ? createSingleTypeService : createCollectionTypeService; - - return serviceFactory({ model, strapi }); -}; - -/** - * Mixins - */ -const createUtils = ({ model }) => { - const { getNonWritableAttributes } = utils.contentTypes; - - return { - // make sure too keep the call to getNonWritableAttributes dynamic - sanitizeInput: data => _.omit(data, getNonWritableAttributes(model)), - }; -}; - -/** - * Returns a single type service to handle default core-api actions - */ -const createSingleTypeService = ({ model, strapi }) => { - const { modelName } = model; - const { sanitizeInput } = createUtils({ model }); - - return { - /** - * Returns single type content - * - * @return {Promise} - */ - find(params, populate) { - return strapi.entityService.find( - { params: getFetchParams(params), populate }, - { model: modelName } - ); - }, - - /** - * Creates or update the single- type content - * - * @return {Promise} - */ - async createOrUpdate(data, { files } = {}) { - const entity = await this.find(); - const sanitizedData = sanitizeInput(data); - - if (!entity) { - return strapi.entityService.create({ data: sanitizedData, files }, { model: modelName }); - } else { - return strapi.entityService.update( - { - params: { - id: entity.id, - }, - data: sanitizedData, - files, - }, - { model: modelName } - ); - } - }, - - /** - * Deletes the single type content - * - * @return {Promise} - */ - async delete() { - const entity = await this.find(); - - if (!entity) return; - - return strapi.entityService.delete({ params: { id: entity.id } }, { model: modelName }); - }, - }; -}; - -/** - * - * Returns a collection type service to handle default core-api actions - */ -const createCollectionTypeService = ({ model, strapi }) => { - const { modelName } = model; - const { sanitizeInput } = createUtils({ model }); - - return { - /** - * Promise to fetch all records - * - * @return {Promise} - */ - find(params, populate) { - return strapi.entityService.find( - { params: getFetchParams(params), populate }, - { model: modelName } - ); - }, - - /** - * Promise to fetch record - * - * @return {Promise} - */ - - findOne(params, populate) { - return strapi.entityService.findOne( - { params: getFetchParams(params), populate }, - { model: modelName } - ); - }, - - /** - * Promise to count record - * - * @return {Promise} - */ - - count(params) { - return strapi.entityService.count({ params: getFetchParams(params) }, { model: modelName }); - }, - - /** - * Promise to add record - * - * @return {Promise} - */ - - create(data, { files } = {}) { - const sanitizedData = sanitizeInput(data); - if (hasDraftAndPublish(model)) { - sanitizedData[PUBLISHED_AT_ATTRIBUTE] = _.get( - sanitizedData, - PUBLISHED_AT_ATTRIBUTE, - new Date() - ); - } - return strapi.entityService.create({ data: sanitizedData, files }, { model: modelName }); - }, - - /** - * Promise to edit record - * - * @return {Promise} - */ - - update(params, data, { files } = {}) { - const sanitizedData = sanitizeInput(data); - return strapi.entityService.update( - { params, data: sanitizedData, files }, - { model: modelName } - ); - }, - - /** - * Promise to delete a record - * - * @return {Promise} - */ - - delete(params) { - return strapi.entityService.delete({ params }, { model: modelName }); - }, - - /** - * Promise to search records - * - * @return {Promise} - */ - - search(params) { - return strapi.entityService.search({ params }, { model: modelName }); - }, - - /** - * Promise to count searched records - * - * @return {Promise} - */ - countSearch(params) { - return strapi.entityService.countSearch( - { params: getFetchParams(params) }, - { model: modelName } - ); - }, - }; -}; - -module.exports = createCoreService; - -module.exports.getFetchParams = getFetchParams; diff --git a/packages/strapi/lib/core-api/service/collection-type.js b/packages/strapi/lib/core-api/service/collection-type.js new file mode 100644 index 0000000000..8d9ef71694 --- /dev/null +++ b/packages/strapi/lib/core-api/service/collection-type.js @@ -0,0 +1,122 @@ +'use strict'; + +const { propOr } = require('lodash/fp'); + +const { + hasDraftAndPublish, + constants: { PUBLISHED_AT_ATTRIBUTE }, +} = require('strapi-utils').contentTypes; + +const setPublishedAt = data => { + data[PUBLISHED_AT_ATTRIBUTE] = propOr(new Date(), PUBLISHED_AT_ATTRIBUTE, data); +}; + +/** + * + * Returns a collection type service to handle default core-api actions + */ +const createCollectionTypeService = ({ model, strapi, utils }) => { + const { modelName } = model; + + const { sanitizeInput, getFetchParams } = utils; + + return { + /** + * Promise to fetch all records + * + * @return {Promise} + */ + find(params, populate) { + return strapi.entityService.find( + { params: getFetchParams(params), populate }, + { model: modelName } + ); + }, + + /** + * Promise to fetch record + * + * @return {Promise} + */ + + findOne(params, populate) { + return strapi.entityService.findOne( + { params: getFetchParams(params), populate }, + { model: modelName } + ); + }, + + /** + * Promise to count record + * + * @return {Promise} + */ + + count(params) { + return strapi.entityService.count({ params: getFetchParams(params) }, { model: modelName }); + }, + + /** + * Promise to add record + * + * @return {Promise} + */ + + create(data, { files } = {}) { + const sanitizedData = sanitizeInput(data); + if (hasDraftAndPublish(model)) { + setPublishedAt(sanitizedData); + } + + return strapi.entityService.create({ data: sanitizedData, files }, { model: modelName }); + }, + + /** + * Promise to edit record + * + * @return {Promise} + */ + + update(params, data, { files } = {}) { + const sanitizedData = sanitizeInput(data); + return strapi.entityService.update( + { params, data: sanitizedData, files }, + { model: modelName } + ); + }, + + /** + * Promise to delete a record + * + * @return {Promise} + */ + + delete(params) { + return strapi.entityService.delete({ params }, { model: modelName }); + }, + + /** + * Promise to search records + * + * @return {Promise} + */ + + search(params) { + return strapi.entityService.search({ params }, { model: modelName }); + }, + + /** + * Promise to count searched records + * + * @return {Promise} + */ + countSearch(params) { + return strapi.entityService.countSearch( + { params: getFetchParams(params) }, + { model: modelName } + ); + }, + }; +}; + +module.exports = createCollectionTypeService; diff --git a/packages/strapi/lib/core-api/service/index.js b/packages/strapi/lib/core-api/service/index.js new file mode 100644 index 0000000000..33b284dfcb --- /dev/null +++ b/packages/strapi/lib/core-api/service/index.js @@ -0,0 +1,81 @@ +'use strict'; + +const _ = require('lodash'); + +const { + isSingleType, + getNonWritableAttributes, + constants: { DP_PUB_STATE_LIVE }, +} = require('strapi-utils').contentTypes; + +const createSingleTypeService = require('./single-type'); +const createCollectionTypeService = require('./collection-type'); + +/** + * Returns a core api for the provided model + * @param {{ model: object, strapi: object }} context + * @returns {object} + */ +const createService = ({ model, strapi }) => { + const utils = createUtils({ model }); + + if (isSingleType(model)) { + return createSingleTypeService({ model, strapi, utils }); + } + + return createCollectionTypeService({ model, strapi, utils }); +}; + +/** + * Default limit values from config + * @return {{maxLimit: number, defaultLimit: number}} + */ +const getLimitConfigDefaults = () => ({ + defaultLimit: _.toNumber(strapi.config.get('api.rest.defaultLimit', 100)), + maxLimit: _.toNumber(strapi.config.get('api.rest.maxLimit')) || null, +}); + +const getLimitParam = params => { + const { defaultLimit, maxLimit } = getLimitConfigDefaults(); + + if (params._limit === undefined) { + return defaultLimit; + } + + const limit = _.toNumber(params._limit); + // if there is max limit set and params._limit exceeds this number, return configured max limit + if (maxLimit && (limit === -1 || limit > maxLimit)) { + return maxLimit; + } + + return limit; +}; + +/** + * Create default fetch params + * @param {*} params + * @returns + */ +const getFetchParams = (params = {}) => { + return { + _publicationState: DP_PUB_STATE_LIVE, + ...params, + _limit: getLimitParam(params), + }; +}; + +/** + * Mixins + */ +const createUtils = ({ model }) => { + return { + // make sure too keep the call to getNonWritableAttributes dynamic + sanitizeInput: data => _.omit(data, getNonWritableAttributes(model)), + getFetchParams, + }; +}; + +module.exports = { + createService, + getFetchParams, +}; diff --git a/packages/strapi/lib/core-api/service/single-type.js b/packages/strapi/lib/core-api/service/single-type.js new file mode 100644 index 0000000000..a82abf65df --- /dev/null +++ b/packages/strapi/lib/core-api/service/single-type.js @@ -0,0 +1,68 @@ +'use strict'; + +/** + * Returns a single type service to handle default core-api actions + */ +const createSingleTypeService = ({ model, strapi, utils }) => { + const { modelName } = model; + const { sanitizeInput, getFetchParams } = utils; + + return { + /** + * Returns single type content + * + * @return {Promise} + */ + find(params, populate) { + return strapi.entityService.find( + { params: getFetchParams(params), populate }, + { model: modelName } + ); + }, + + /** + * Creates or update the single- type content + * + * @return {Promise} + */ + async createOrUpdate(data, { files, query } = {}) { + const entity = await this.find(query); + const sanitizedData = sanitizeInput(data); + + if (!entity) { + const count = await strapi.query(modelName).count(); + if (count >= 1) { + throw strapi.errors.badRequest('singleType.alreadyExists'); + } + + return strapi.entityService.create({ data: sanitizedData, files }, { model: modelName }); + } else { + return strapi.entityService.update( + { + params: { + id: entity.id, + }, + data: sanitizedData, + files, + }, + { model: modelName } + ); + } + }, + + /** + * Deletes the single type content + * + * @return {Promise} + */ + async delete() { + const entity = await this.find(); + + if (!entity) return; + + return strapi.entityService.delete({ params: { id: entity.id } }, { model: modelName }); + }, + }; +}; + +module.exports = createSingleTypeService;