Merge pull request #11031 from strapi/v4/entity-service-refactor

Refacto entity service API to be more consistent with the DB layer
This commit is contained in:
Alexandre BODIN 2021-09-22 14:33:39 +02:00 committed by GitHub
commit 872e7317ca
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 244 additions and 167 deletions

View File

@ -36,9 +36,8 @@ describe('Content-Manager', () => {
expect(strapi.entityService.update).toBeCalledWith(uid, entity.id, { expect(strapi.entityService.update).toBeCalledWith(uid, entity.id, {
data: { published_at: expect.any(Date) }, data: { published_at: expect.any(Date) },
params: {
populate: {}, populate: {},
},
}); });
}); });
}); });
@ -66,9 +65,8 @@ describe('Content-Manager', () => {
expect(strapi.entityService.update).toHaveBeenCalledWith(uid, entity.id, { expect(strapi.entityService.update).toHaveBeenCalledWith(uid, entity.id, {
data: { published_at: null }, data: { published_at: null },
params: {
populate: {}, populate: {},
},
}); });
}); });
}); });

View File

@ -0,0 +1,17 @@
interface EntityManager {
assocCreatorRoles(): any;
find(): any;
findPage(): any;
findWithRelationCounts(): any;
count(): any;
findOne(): any;
findOneWithCreatorRoles(): any;
create(): any;
update(): any;
delete(): any;
deleteMany(): any;
publish(): any;
unpublish(): any;
}
export default function(opts: { strapi: Strapi }): EntityManager;

View File

@ -10,7 +10,7 @@ const { ENTRY_PUBLISH, ENTRY_UNPUBLISH } = strapiUtils.webhook.webhookEvents;
const omitPublishedAtField = omit(PUBLISHED_AT_ATTRIBUTE); const omitPublishedAtField = omit(PUBLISHED_AT_ATTRIBUTE);
const emitEvent = (event, fn) => async (entity, model) => { const wrapWithEmitEvent = (event, fn) => async (entity, model) => {
const result = await fn(entity, model); const result = await fn(entity, model);
const modelDef = strapi.getModel(model); const modelDef = strapi.getModel(model);
@ -92,6 +92,9 @@ const getBasePopulate = (uid, populate) => {
}); });
}; };
/**
* @type {import('./entity-manager').default}
*/
module.exports = ({ strapi }) => ({ module.exports = ({ strapi }) => ({
async assocCreatorRoles(entity) { async assocCreatorRoles(entity) {
if (!entity) { if (!entity) {
@ -105,31 +108,31 @@ module.exports = ({ strapi }) => ({
find(opts, uid, populate) { find(opts, uid, populate) {
const params = { ...opts, populate: getDeepPopulate(uid, populate) }; const params = { ...opts, populate: getDeepPopulate(uid, populate) };
return strapi.entityService.find(uid, { params }); return strapi.entityService.findMany(uid, params);
}, },
findPage(opts, uid, populate) { findPage(opts, uid, populate) {
const params = { ...opts, populate: getBasePopulate(uid, populate) }; const params = { ...opts, populate: getBasePopulate(uid, populate) };
return strapi.entityService.findPage(uid, { params }); return strapi.entityService.findPage(uid, params);
}, },
findWithRelationCounts(opts, uid, populate) { findWithRelationCounts(opts, uid, populate) {
const params = { ...opts, populate: getBasePopulate(uid, populate) }; const params = { ...opts, populate: getBasePopulate(uid, populate) };
return strapi.entityService.findWithRelationCounts(uid, { params }); return strapi.entityService.findWithRelationCounts(uid, params);
}, },
count(opts, uid) { count(opts, uid) {
const params = { ...opts }; const params = { ...opts };
return strapi.entityService.count(uid, { params }); return strapi.entityService.count(uid, params);
}, },
async findOne(id, uid, populate) { async findOne(id, uid, populate) {
const params = { populate: getDeepPopulate(uid, populate) }; const params = { populate: getDeepPopulate(uid, populate) };
return strapi.entityService.findOne(uid, id, { params }); return strapi.entityService.findOne(uid, id, params);
}, },
async findOneWithCreatorRoles(id, uid, populate) { async findOneWithCreatorRoles(id, uid, populate) {
@ -150,33 +153,33 @@ module.exports = ({ strapi }) => ({
publishData[PUBLISHED_AT_ATTRIBUTE] = null; publishData[PUBLISHED_AT_ATTRIBUTE] = null;
} }
const params = { populate: getDeepPopulate(uid) }; const params = { data: publishData, populate: getDeepPopulate(uid) };
return strapi.entityService.create(uid, { params, data: publishData }); return strapi.entityService.create(uid, params);
}, },
update(entity, body, uid) { update(entity, body, uid) {
const publishData = omitPublishedAtField(body); const publishData = omitPublishedAtField(body);
const params = { populate: getDeepPopulate(uid) }; const params = { data: publishData, populate: getDeepPopulate(uid) };
return strapi.entityService.update(uid, entity.id, { params, data: publishData }); return strapi.entityService.update(uid, entity.id, params);
}, },
delete(entity, uid) { delete(entity, uid) {
const params = { populate: getDeepPopulate(uid) }; const params = { populate: getDeepPopulate(uid) };
return strapi.entityService.delete(uid, entity.id, { params }); return strapi.entityService.delete(uid, entity.id, params);
}, },
// FIXME: handle relations // FIXME: handle relations
deleteMany(opts, uid) { deleteMany(opts, uid) {
const params = { ...opts }; const params = { ...opts };
return strapi.entityService.deleteMany(uid, { params }); return strapi.entityService.deleteMany(uid, params);
}, },
publish: emitEvent(ENTRY_PUBLISH, async (entity, uid) => { publish: wrapWithEmitEvent(ENTRY_PUBLISH, async (entity, uid) => {
if (entity[PUBLISHED_AT_ATTRIBUTE]) { if (entity[PUBLISHED_AT_ATTRIBUTE]) {
throw strapi.errors.badRequest('already.published'); throw strapi.errors.badRequest('already.published');
} }
@ -186,20 +189,20 @@ module.exports = ({ strapi }) => ({
const data = { [PUBLISHED_AT_ATTRIBUTE]: new Date() }; const data = { [PUBLISHED_AT_ATTRIBUTE]: new Date() };
const params = { populate: getDeepPopulate(uid) }; const params = { data, populate: getDeepPopulate(uid) };
return strapi.entityService.update(uid, entity.id, { params, data }); return strapi.entityService.update(uid, entity.id, params);
}), }),
unpublish: emitEvent(ENTRY_UNPUBLISH, (entity, uid) => { unpublish: wrapWithEmitEvent(ENTRY_UNPUBLISH, (entity, uid) => {
if (!entity[PUBLISHED_AT_ATTRIBUTE]) { if (!entity[PUBLISHED_AT_ATTRIBUTE]) {
throw strapi.errors.badRequest('already.draft'); throw strapi.errors.badRequest('already.draft');
} }
const data = { [PUBLISHED_AT_ATTRIBUTE]: null }; const data = { [PUBLISHED_AT_ATTRIBUTE]: null };
const params = { populate: getDeepPopulate(uid) }; const params = { data, populate: getDeepPopulate(uid) };
return strapi.entityService.update(uid, entity.id, { params, data }); return strapi.entityService.update(uid, entity.id, params);
}), }),
}); });

View File

@ -63,7 +63,7 @@ describe('Default Service', () => {
test('Creates data when no entity is found', async () => { test('Creates data when no entity is found', async () => {
const strapi = { const strapi = {
entityService: { entityService: {
find: jest.fn(() => Promise.resolve(null)), findMany: jest.fn(() => Promise.resolve(null)),
create: jest.fn(() => Promise.resolve({ id: 1 })), create: jest.fn(() => Promise.resolve({ id: 1 })),
}, },
query() { query() {
@ -81,8 +81,8 @@ describe('Default Service', () => {
const input = {}; const input = {};
await service.createOrUpdate(input); await service.createOrUpdate(input);
expect(strapi.entityService.find).toHaveBeenCalledWith('testModel', { expect(strapi.entityService.findMany).toHaveBeenCalledWith('testModel', {
params: { publicationState: 'live' }, publicationState: 'live',
}); });
expect(strapi.entityService.create).toHaveBeenCalledWith('testModel', { data: input }); expect(strapi.entityService.create).toHaveBeenCalledWith('testModel', { data: input });
@ -91,7 +91,7 @@ describe('Default Service', () => {
test('Updates data when entity is found', async () => { test('Updates data when entity is found', async () => {
const strapi = { const strapi = {
entityService: { entityService: {
find: jest.fn(() => Promise.resolve({ id: 1 })), findMany: jest.fn(() => Promise.resolve({ id: 1 })),
update: jest.fn(() => Promise.resolve({ id: 1 })), update: jest.fn(() => Promise.resolve({ id: 1 })),
}, },
query() { query() {
@ -109,9 +109,9 @@ describe('Default Service', () => {
const input = {}; const input = {};
await service.createOrUpdate(input); await service.createOrUpdate(input);
expect(strapi.entityService.find).toHaveBeenCalledWith('testModel', { expect(strapi.entityService.findMany).toHaveBeenCalledWith('testModel', {
populate: undefined, populate: undefined,
params: { publicationState: 'live' }, publicationState: 'live',
}); });
expect(strapi.entityService.update).toHaveBeenCalledWith('testModel', 1, { expect(strapi.entityService.update).toHaveBeenCalledWith('testModel', 1, {
@ -122,7 +122,7 @@ describe('Default Service', () => {
test('Delete data when entity is found', async () => { test('Delete data when entity is found', async () => {
const strapi = { const strapi = {
entityService: { entityService: {
find: jest.fn(() => Promise.resolve({ id: 1 })), findMany: jest.fn(() => Promise.resolve({ id: 1 })),
delete: jest.fn(() => Promise.resolve({ id: 1 })), delete: jest.fn(() => Promise.resolve({ id: 1 })),
}, },
}; };
@ -136,9 +136,9 @@ describe('Default Service', () => {
await service.delete(); await service.delete();
expect(strapi.entityService.find).toHaveBeenCalledWith('testModel', { expect(strapi.entityService.findMany).toHaveBeenCalledWith('testModel', {
populate: undefined, populate: undefined,
params: { publicationState: 'live' }, publicationState: 'live',
}); });
expect(strapi.entityService.delete).toHaveBeenCalledWith('testModel', 1); expect(strapi.entityService.delete).toHaveBeenCalledWith('testModel', 1);

View File

@ -16,7 +16,7 @@ const createCollectionTypeController = ({ service, sanitize, transformResponse }
async find(ctx) { async find(ctx) {
const { query } = ctx; const { query } = ctx;
const { results, pagination } = await service.find({ params: query }); const { results, pagination } = await service.find(query);
return transformResponse(sanitize(results), { pagination }); return transformResponse(sanitize(results), { pagination });
}, },
@ -30,7 +30,7 @@ const createCollectionTypeController = ({ service, sanitize, transformResponse }
const { id } = ctx.params; const { id } = ctx.params;
const { query } = ctx; const { query } = ctx;
const entity = await service.findOne(id, { params: query }); const entity = await service.findOne(id, query);
return transformResponse(sanitize(entity)); return transformResponse(sanitize(entity));
}, },
@ -45,7 +45,7 @@ const createCollectionTypeController = ({ service, sanitize, transformResponse }
const { data, files } = parseBody(ctx); const { data, files } = parseBody(ctx);
const entity = await service.create({ params: query, data, files }); const entity = await service.create({ ...query, data, files });
return transformResponse(sanitize(entity)); return transformResponse(sanitize(entity));
}, },
@ -61,7 +61,7 @@ const createCollectionTypeController = ({ service, sanitize, transformResponse }
const { data, files } = parseBody(ctx); const { data, files } = parseBody(ctx);
const entity = await service.update(id, { params: query, data, files }); const entity = await service.update(id, { ...query, data, files });
return transformResponse(sanitize(entity)); return transformResponse(sanitize(entity));
}, },
@ -75,7 +75,7 @@ const createCollectionTypeController = ({ service, sanitize, transformResponse }
const { id } = ctx.params; const { id } = ctx.params;
const { query } = ctx; const { query } = ctx;
const entity = await service.delete(id, { params: query }); const entity = await service.delete(id, query);
return transformResponse(sanitize(entity)); return transformResponse(sanitize(entity));
}, },
}; };

View File

@ -14,7 +14,7 @@ const createSingleTypeController = ({ service, sanitize, transformResponse }) =>
*/ */
async find(ctx) { async find(ctx) {
const { query } = ctx; const { query } = ctx;
const entity = await service.find({ params: query }); const entity = await service.find(query);
return transformResponse(sanitize(entity)); return transformResponse(sanitize(entity));
}, },
@ -27,7 +27,7 @@ const createSingleTypeController = ({ service, sanitize, transformResponse }) =>
const { query } = ctx.request; const { query } = ctx.request;
const { data, files } = parseBody(ctx); const { data, files } = parseBody(ctx);
const entity = await service.createOrUpdate({ params: query, data, files }); const entity = await service.createOrUpdate({ ...query, data, files });
return transformResponse(sanitize(entity)); return transformResponse(sanitize(entity));
}, },
@ -35,7 +35,7 @@ const createSingleTypeController = ({ service, sanitize, transformResponse }) =>
async delete(ctx) { async delete(ctx) {
const { query } = ctx; const { query } = ctx;
const entity = await service.delete({ params: query }); const entity = await service.delete(query);
return transformResponse(sanitize(entity)); return transformResponse(sanitize(entity));
}, },
}; };

View File

@ -28,19 +28,18 @@ const createCollectionTypeService = ({ model, strapi, utils }) => {
const { sanitizeInput, getFetchParams } = utils; const { sanitizeInput, getFetchParams } = utils;
return { return {
async find(opts = {}) { async find(params = {}) {
const params = getFetchParams(opts.params); const fetchParams = getFetchParams(params);
const paginationInfo = getPaginationInfo(params); const paginationInfo = getPaginationInfo(fetchParams);
const results = await strapi.entityService.find(uid, { const results = await strapi.entityService.findMany(uid, {
params: { ...params, ...convertPagedToStartLimit(paginationInfo) }, ...fetchParams,
...convertPagedToStartLimit(paginationInfo),
}); });
if (shouldCount(params)) { if (shouldCount(fetchParams)) {
const count = await strapi.entityService.count(uid, { const count = await strapi.entityService.count(uid, { ...fetchParams, ...paginationInfo });
params: { ...params, ...paginationInfo },
});
return { return {
results, results,
@ -54,30 +53,30 @@ const createCollectionTypeService = ({ model, strapi, utils }) => {
}; };
}, },
findOne(entityId, opts = {}) { findOne(entityId, params = {}) {
const params = getFetchParams(opts.params); return strapi.entityService.findOne(uid, entityId, getFetchParams(params));
return strapi.entityService.findOne(uid, entityId, { params });
}, },
create({ params, data, files } = {}) { create(params = {}) {
const { data } = params;
const sanitizedData = sanitizeInput(data); const sanitizedData = sanitizeInput(data);
if (hasDraftAndPublish(model)) { if (hasDraftAndPublish(model)) {
setPublishedAt(sanitizedData); setPublishedAt(sanitizedData);
} }
return strapi.entityService.create(uid, { params, data: sanitizedData, files }); return strapi.entityService.create(uid, { ...params, data: sanitizedData });
}, },
update(entityId, { params, data, files } = {}) { update(entityId, params = {}) {
const { data } = params;
const sanitizedData = sanitizeInput(data); const sanitizedData = sanitizeInput(data);
return strapi.entityService.update(uid, entityId, { params, data: sanitizedData, files }); return strapi.entityService.update(uid, entityId, { ...params, data: sanitizedData });
}, },
delete(entityId, { params } = {}) { delete(entityId, params = {}) {
return strapi.entityService.delete(uid, entityId, { params }); return strapi.entityService.delete(uid, entityId, params);
}, },
}; };
}; };

View File

@ -13,9 +13,8 @@ const createSingleTypeService = ({ model, strapi, utils }) => {
* *
* @return {Promise} * @return {Promise}
*/ */
find({ params } = {}) { find(params = {}) {
const normalizedParams = getFetchParams(params); return strapi.entityService.findMany(uid, getFetchParams(params));
return strapi.entityService.find(uid, { params: normalizedParams });
}, },
/** /**
@ -23,9 +22,10 @@ const createSingleTypeService = ({ model, strapi, utils }) => {
* *
* @return {Promise} * @return {Promise}
*/ */
async createOrUpdate({ params, data, files } = {}) { async createOrUpdate(params = {}) {
const entity = await this.find({ params }); const entity = await this.find(params);
const { data } = params;
const sanitizedData = sanitizeInput(data); const sanitizedData = sanitizeInput(data);
if (!entity) { if (!entity) {
@ -34,14 +34,10 @@ const createSingleTypeService = ({ model, strapi, utils }) => {
throw strapi.errors.badRequest('singleType.alreadyExists'); throw strapi.errors.badRequest('singleType.alreadyExists');
} }
return strapi.entityService.create(uid, { params, data: sanitizedData, files }); return strapi.entityService.create(uid, { ...params, data: sanitizedData });
} else {
return strapi.entityService.update(uid, entity.id, {
params,
data: sanitizedData,
files,
});
} }
return strapi.entityService.update(uid, entity.id, { ...params, data: sanitizedData });
}, },
/** /**
@ -49,8 +45,8 @@ const createSingleTypeService = ({ model, strapi, utils }) => {
* *
* @return {Promise} * @return {Promise}
*/ */
async delete({ params } = {}) { async delete(params = {}) {
const entity = await this.find({ params }); const entity = await this.find(params);
if (!entity) return; if (!entity) return;

View File

@ -1,40 +1,14 @@
import { Database } from '@strapi/database'; import { Database } from '@strapi/database';
import { Strapi } from './Strapi'; import { EntityService } from './services/entity-service';
import { Strapi as StrapiClass } from './Strapi';
type ID = number | string; interface StrapiInterface extends StrapiClass {
interface Options<T> {
params: Params<T>;
}
interface Params<T> {
fields: (keyof T)[];
}
interface EntityService {
uploadFiles<T extends keyof AllTypes>(uid: T);
wrapOptions<T extends keyof AllTypes>(uid: T);
find<T extends keyof AllTypes>(uid: T): Promise<AllTypes[T][]>;
findPage<T extends keyof AllTypes>(uid: T): Promise<any>;
findWithRelationCounts<T extends keyof AllTypes>(uid: T): Promise<any>;
findOne<T extends keyof AllTypes>(
uid: T,
id: ID,
opts: Options<AllTypes[T]>
): Promise<AllTypes[T]>;
count<T extends keyof AllTypes>(uid: T): Promise<any>;
create<T extends keyof AllTypes>(uid: T): Promise<any>;
update<T extends keyof AllTypes>(uid: T): Promise<any>;
delete<T extends keyof AllTypes>(uid: T): Promise<any>;
}
interface StrapiInterface extends Strapi {
query: Database['query']; query: Database['query'];
entityService: EntityService; entityService: EntityService;
} }
export type Strapi = StrapiInterface;
declare global { declare global {
interface AllTypes {} interface AllTypes {}
} }
@ -44,5 +18,9 @@ declare global {
strapi: StrapiInterface; strapi: StrapiInterface;
} }
export type Strapi = StrapiInterface;
const strapi: StrapiInterface; const strapi: StrapiInterface;
} }
export default function(opts): Strapi;

View File

@ -1,8 +1,8 @@
'use strict'; 'use strict';
const { EventEmitter } = require('events'); const { EventEmitter } = require('events');
const createEntityService = require('../entity-service'); const createEntityService = require('../');
const entityValidator = require('../entity-validator'); const entityValidator = require('../../entity-validator');
describe('Entity service', () => { describe('Entity service', () => {
global.strapi = { global.strapi = {
@ -15,7 +15,7 @@ describe('Entity service', () => {
}; };
describe('Decorator', () => { describe('Decorator', () => {
test.each(['create', 'update', 'find', 'findOne', 'delete', 'count', 'findPage'])( test.each(['create', 'update', 'findMany', 'findOne', 'delete', 'count', 'findPage'])(
'Can decorate', 'Can decorate',
async method => { async method => {
const instance = createEntityService({ const instance = createEntityService({
@ -65,7 +65,7 @@ describe('Entity service', () => {
eventHub: new EventEmitter(), eventHub: new EventEmitter(),
}); });
const result = await instance.find('test-model'); const result = await instance.findMany('test-model');
expect(fakeStrapi.getModel).toHaveBeenCalledTimes(1); expect(fakeStrapi.getModel).toHaveBeenCalledTimes(1);
expect(fakeStrapi.getModel).toHaveBeenCalledWith('test-model'); expect(fakeStrapi.getModel).toHaveBeenCalledWith('test-model');

View File

@ -0,0 +1,91 @@
import { Database } from '@strapi/database';
import { Strapi } from '../../';
type ID = number | string;
type EntityServiceAction =
| 'findMany'
| 'findPage'
| 'findWithRelationCounts'
| 'findOne'
| 'count'
| 'create'
| 'update'
| 'delete';
type PaginationInfo = {
page: number;
pageSize: number;
pageCount: number;
total: number;
};
type Params<T> = {
fields?: (keyof T)[];
filters?: any;
_q?: string;
populate?: any;
sort?: any;
start?: number;
limit?: number;
page?: number;
pageSize?: number;
publicationState?: string;
data?: any;
files?: any;
};
interface EntityService {
uploadFiles<K extends keyof AllTypes, T extends AllTypes[K]>(uid: K, entity, files);
wrapParams<K extends keyof AllTypes, T extends AllTypes[K]>(
params: Params<T>,
{ uid: K, action: EntityServiceAction }
);
findMany<K extends keyof AllTypes, T extends AllTypes[K]>(
uid: K,
params: Params<T>
): Promise<T[]>;
findPage<K extends keyof AllTypes, T extends AllTypes[K]>(
uid: K,
params: Params<T>
): Promise<{
results: T[];
pagination: PaginationInfo;
}>;
findWithRelationCounts<K extends keyof AllTypes, T extends AllTypes[K]>(
uid: K,
params: Params<T>
): Promise<{
results: T[];
pagination: PaginationInfo;
}>;
findOne<K extends keyof AllTypes, T extends AllTypes[K]>(
uid: K,
entityId: ID,
params: Params<T>
): Promise<T>;
count<K extends keyof AllTypes, T extends AllTypes[K]>(uid: K, params: Params<T>): Promise<any>;
create<K extends keyof AllTypes, T extends AllTypes[K]>(uid: K, params: Params<T>): Promise<any>;
update<K extends keyof AllTypes, T extends AllTypes[K]>(
uid: K,
entityId: ID,
params: Params<T>
): Promise<any>;
delete<K extends keyof AllTypes, T extends AllTypes[K]>(
uid: K,
entityId: ID,
params: Params<T>
): Promise<any>;
}
export default function(opts: {
strapi: Strapi;
db: Database;
// TODO: define types
eventHub: any;
entityValidator: any;
}): EntityService;

View File

@ -45,10 +45,13 @@ module.exports = ctx => {
return service; return service;
}; };
/**
* @type {import('.').default}
*/
const createDefaultImplementation = ({ strapi, db, eventHub, entityValidator }) => ({ const createDefaultImplementation = ({ strapi, db, eventHub, entityValidator }) => ({
uploadFiles, uploadFiles,
async wrapOptions(options = {}) { async wrapParams(options = {}) {
return options; return options;
}, },
@ -61,13 +64,12 @@ const createDefaultImplementation = ({ strapi, db, eventHub, entityValidator })
}); });
}, },
// TODO: rename to findMany async findMany(uid, opts) {
async find(uid, opts) {
const { kind } = strapi.getModel(uid); const { kind } = strapi.getModel(uid);
const { params } = await this.wrapOptions(opts, { uid, action: 'find' }); const wrappedParams = await this.wrapParams(opts, { uid, action: 'findMany' });
const query = transformParamsToQuery(uid, params); const query = transformParamsToQuery(uid, wrappedParams);
if (kind === 'singleType') { if (kind === 'singleType') {
return db.query(uid).findOne(query); return db.query(uid).findOne(query);
@ -77,9 +79,9 @@ const createDefaultImplementation = ({ strapi, db, eventHub, entityValidator })
}, },
async findPage(uid, opts) { async findPage(uid, opts) {
const { params } = await this.wrapOptions(opts, { uid, action: 'findPage' }); const wrappedParams = await this.wrapParams(opts, { uid, action: 'findPage' });
const query = transformParamsToQuery(uid, params); const query = transformParamsToQuery(uid, wrappedParams);
return db.query(uid).findPage(query); return db.query(uid).findPage(query);
}, },
@ -88,9 +90,9 @@ const createDefaultImplementation = ({ strapi, db, eventHub, entityValidator })
async findWithRelationCounts(uid, opts) { async findWithRelationCounts(uid, opts) {
const model = strapi.getModel(uid); const model = strapi.getModel(uid);
const { params } = await this.wrapOptions(opts, { uid, action: 'findWithRelationCounts' }); const wrappedParams = await this.wrapParams(opts, { uid, action: 'findWithRelationCounts' });
const query = transformParamsToQuery(uid, params); const query = transformParamsToQuery(uid, wrappedParams);
const { attributes } = model; const { attributes } = model;
@ -121,23 +123,24 @@ const createDefaultImplementation = ({ strapi, db, eventHub, entityValidator })
}, },
async findOne(uid, entityId, opts) { async findOne(uid, entityId, opts) {
const { params } = await this.wrapOptions(opts, { uid, action: 'findOne' }); const wrappedParams = await this.wrapParams(opts, { uid, action: 'findOne' });
const query = transformParamsToQuery(uid, pickSelectionParams(params)); const query = transformParamsToQuery(uid, pickSelectionParams(wrappedParams));
return db.query(uid).findOne({ ...query, where: { id: entityId } }); return db.query(uid).findOne({ ...query, where: { id: entityId } });
}, },
async count(uid, opts) { async count(uid, opts) {
const { params } = await this.wrapOptions(opts, { uid, action: 'count' }); const wrappedParams = await this.wrapParams(opts, { uid, action: 'count' });
const query = transformParamsToQuery(uid, params); const query = transformParamsToQuery(uid, wrappedParams);
return db.query(uid).count(query); return db.query(uid).count(query);
}, },
async create(uid, opts) { async create(uid, opts) {
const { params, data, files } = await this.wrapOptions(opts, { uid, action: 'create' }); const wrappedParams = await this.wrapParams(opts, { uid, action: 'create' });
const { data, files } = wrappedParams;
const model = strapi.getModel(uid); const model = strapi.getModel(uid);
@ -145,7 +148,7 @@ const createDefaultImplementation = ({ strapi, db, eventHub, entityValidator })
const validData = await entityValidator.validateEntityCreation(model, data, { isDraft }); const validData = await entityValidator.validateEntityCreation(model, data, { isDraft });
// select / populate // select / populate
const query = transformParamsToQuery(uid, pickSelectionParams(params)); const query = transformParamsToQuery(uid, pickSelectionParams(wrappedParams));
// TODO: wrap into transaction // TODO: wrap into transaction
const componentData = await createComponents(uid, validData); const componentData = await createComponents(uid, validData);
@ -159,7 +162,7 @@ const createDefaultImplementation = ({ strapi, db, eventHub, entityValidator })
// FIXME: upload in components // FIXME: upload in components
if (files && Object.keys(files).length > 0) { if (files && Object.keys(files).length > 0) {
await this.uploadFiles(uid, entity, files); await this.uploadFiles(uid, entity, files);
entity = await this.findOne(uid, entity.id, { params }); entity = await this.findOne(uid, entity.id, wrappedParams);
} }
this.emitEvent(uid, ENTRY_CREATE, entity); this.emitEvent(uid, ENTRY_CREATE, entity);
@ -168,7 +171,8 @@ const createDefaultImplementation = ({ strapi, db, eventHub, entityValidator })
}, },
async update(uid, entityId, opts) { async update(uid, entityId, opts) {
const { params, data, files } = await this.wrapOptions(opts, { uid, action: 'update' }); const wrappedParams = await this.wrapParams(opts, { uid, action: 'update' });
const { data, files } = wrappedParams;
const model = strapi.getModel(uid); const model = strapi.getModel(uid);
@ -184,7 +188,7 @@ const createDefaultImplementation = ({ strapi, db, eventHub, entityValidator })
isDraft, isDraft,
}); });
const query = transformParamsToQuery(uid, pickSelectionParams(params)); const query = transformParamsToQuery(uid, pickSelectionParams(wrappedParams));
// TODO: wrap in transaction // TODO: wrap in transaction
const componentData = await updateComponents(uid, entityToUpdate, validData); const componentData = await updateComponents(uid, entityToUpdate, validData);
@ -199,7 +203,7 @@ const createDefaultImplementation = ({ strapi, db, eventHub, entityValidator })
// FIXME: upload in components // FIXME: upload in components
if (files && Object.keys(files).length > 0) { if (files && Object.keys(files).length > 0) {
await this.uploadFiles(uid, entity, files); await this.uploadFiles(uid, entity, files);
entity = await this.findOne(uid, entity.id, { params }); entity = await this.findOne(uid, entity.id, wrappedParams);
} }
this.emitEvent(uid, ENTRY_UPDATE, entity); this.emitEvent(uid, ENTRY_UPDATE, entity);
@ -208,10 +212,10 @@ const createDefaultImplementation = ({ strapi, db, eventHub, entityValidator })
}, },
async delete(uid, entityId, opts) { async delete(uid, entityId, opts) {
const { params } = await this.wrapOptions(opts, { uid, action: 'delete' }); const wrappedParams = await this.wrapParams(opts, { uid, action: 'delete' });
// select / populate // select / populate
const query = transformParamsToQuery(uid, pickSelectionParams(params)); const query = transformParamsToQuery(uid, pickSelectionParams(wrappedParams));
const entityToDelete = await db.query(uid).findOne({ const entityToDelete = await db.query(uid).findOne({
...query, ...query,
@ -232,10 +236,10 @@ const createDefaultImplementation = ({ strapi, db, eventHub, entityValidator })
// FIXME: used only for the CM to be removed // FIXME: used only for the CM to be removed
async deleteMany(uid, opts) { async deleteMany(uid, opts) {
const { params } = await this.wrapOptions(opts, { uid, action: 'delete' }); const wrappedParams = await this.wrapParams(opts, { uid, action: 'delete' });
// select / populate // select / populate
const query = transformParamsToQuery(uid, params); const query = transformParamsToQuery(uid, wrappedParams);
return db.query(uid).deleteMany(query); return db.query(uid).deleteMany(query);
}, },

View File

@ -22,8 +22,8 @@ const transformParamsToQuery = (uid, params = {}) => {
const query = {}; const query = {};
const { const {
start,
page, page,
start,
pageSize, pageSize,
limit, limit,
sort, sort,

View File

@ -36,9 +36,7 @@ const validateLocaleCreation = async (ctx, next) => {
body.locale = entityLocale; body.locale = entityLocale;
if (modelDef.kind === 'singleType') { if (modelDef.kind === 'singleType') {
const entity = await strapi.entityService.find(modelDef.uid, { const entity = await strapi.entityService.findMany(modelDef.uid, { locale: entityLocale });
params: { locale: entityLocale },
});
ctx.request.query.locale = body.locale; ctx.request.query.locale = body.locale;

View File

@ -65,54 +65,54 @@ describe('Entity service decorator', () => {
syncNonLocalizedAttributes.mockClear(); syncNonLocalizedAttributes.mockClear();
}); });
describe('wrapOptions', () => { describe('wrapParams', () => {
test('Calls original wrapOptions', async () => { test('Calls original wrapParams', async () => {
const defaultService = { const defaultService = {
wrapOptions: jest.fn(() => Promise.resolve('li')), wrapParams: jest.fn(() => Promise.resolve('li')),
}; };
const service = decorator(defaultService); const service = decorator(defaultService);
const input = { populate: ['test'] }; const input = { populate: ['test'] };
await service.wrapOptions(input, { uid: 'test-model' }); await service.wrapParams(input, { uid: 'test-model' });
expect(defaultService.wrapOptions).toHaveBeenCalledWith(input, { uid: 'test-model' }); expect(defaultService.wrapParams).toHaveBeenCalledWith(input, { uid: 'test-model' });
}); });
test('Does not wrap options if model is not localized', async () => { test('Does not wrap options if model is not localized', async () => {
const defaultService = { const defaultService = {
wrapOptions: jest.fn(opts => Promise.resolve(opts)), wrapParams: jest.fn(opts => Promise.resolve(opts)),
}; };
const service = decorator(defaultService); const service = decorator(defaultService);
const input = { populate: ['test'] }; const input = { populate: ['test'] };
const output = await service.wrapOptions(input, { uid: 'non-localized-model' }); const output = await service.wrapParams(input, { uid: 'non-localized-model' });
expect(output).toStrictEqual(input); expect(output).toStrictEqual(input);
}); });
test('does not change non params options', async () => { test('does not change non params options', async () => {
const defaultService = { const defaultService = {
wrapOptions: jest.fn(opts => Promise.resolve(opts)), wrapParams: jest.fn(opts => Promise.resolve(opts)),
}; };
const service = decorator(defaultService); const service = decorator(defaultService);
const input = { populate: ['test'] }; const input = { populate: ['test'] };
const output = await service.wrapOptions(input, { uid: 'test-model' }); const output = await service.wrapParams(input, { uid: 'test-model' });
expect(output.populate).toStrictEqual(input.populate); expect(output.populate).toStrictEqual(input.populate);
}); });
test('Adds locale param', async () => { test('Adds locale param', async () => {
const defaultService = { const defaultService = {
wrapOptions: jest.fn(opts => Promise.resolve(opts)), wrapParams: jest.fn(opts => Promise.resolve(opts)),
}; };
const service = decorator(defaultService); const service = decorator(defaultService);
const input = { populate: ['test'] }; const input = { populate: ['test'] };
const output = await service.wrapOptions(input, { uid: 'test-model' }); const output = await service.wrapParams(input, { uid: 'test-model' });
expect(output).toMatchObject({ params: { filters: { $and: [{ locale: 'en' }] } } }); expect(output).toMatchObject({ filters: { $and: [{ locale: 'en' }] } });
}); });
const testData = [ const testData = [
@ -130,32 +130,30 @@ describe('Entity service decorator', () => {
"Doesn't add locale param when the params contain id or id_in - %s", "Doesn't add locale param when the params contain id or id_in - %s",
async (action, params) => { async (action, params) => {
const defaultService = { const defaultService = {
wrapOptions: jest.fn(opts => Promise.resolve(opts)), wrapParams: jest.fn(opts => Promise.resolve(opts)),
}; };
const service = decorator(defaultService); const service = decorator(defaultService);
const input = Object.assign({ populate: ['test'], params }); const input = Object.assign({ populate: ['test'], ...params });
const output = await service.wrapOptions(input, { uid: 'test-model', action }); const output = await service.wrapParams(input, { uid: 'test-model', action });
expect(output).toEqual({ populate: ['test'], params }); expect(output).toEqual({ populate: ['test'], ...params });
} }
); );
test('Replaces _locale param', async () => { test('Replaces _locale param', async () => {
const defaultService = { const defaultService = {
wrapOptions: jest.fn(opts => Promise.resolve(opts)), wrapParams: jest.fn(opts => Promise.resolve(opts)),
}; };
const service = decorator(defaultService); const service = decorator(defaultService);
const input = { const input = {
params: { locale: 'fr',
locale: 'fr',
},
populate: ['test'], populate: ['test'],
}; };
const output = await service.wrapOptions(input, { uid: 'test-model' }); const output = await service.wrapParams(input, { uid: 'test-model' });
expect(output).toMatchObject({ params: { filters: { $and: [{ locale: 'fr' }] } } }); expect(output).toMatchObject({ filters: { $and: [{ locale: 'fr' }] } });
}); });
}); });

View File

@ -91,23 +91,18 @@ const decorator = service => ({
* @param {object} ctx - Query context * @param {object} ctx - Query context
* @param {object} ctx.model - Model that is being used * @param {object} ctx.model - Model that is being used
*/ */
async wrapOptions(opts = {}, ctx = {}) { async wrapParams(params = {}, ctx = {}) {
const wrappedOptions = await service.wrapOptions.call(this, opts, ctx); const wrappedParams = await service.wrapParams.call(this, params, ctx);
const model = strapi.getModel(ctx.uid); const model = strapi.getModel(ctx.uid);
const { isLocalizedContentType } = getService('content-types'); const { isLocalizedContentType } = getService('content-types');
if (!isLocalizedContentType(model)) { if (!isLocalizedContentType(model)) {
return wrappedOptions; return wrappedParams;
} }
const { params } = opts; return wrapParams(params, ctx);
return {
...wrappedOptions,
params: await wrapParams(params, ctx),
};
}, },
/** /**