Init factories for controllers

This commit is contained in:
Alexandre Bodin 2021-11-08 16:05:19 +01:00
parent 3a31a96067
commit f4507e1cb0
18 changed files with 260 additions and 111 deletions

View File

@ -0,0 +1,17 @@
const { createCoreController } = require('@strapi/strapi').factories;
module.exports = createCoreController('api::address.address', ({ strapi }) => ({
async find(ctx) {
const { query } = ctx;
const { results, pagination } = await strapi.service(uid).find(query);
const sanitizedResults = await this.sanitizeOutput(results, ctx);
return this.transformResponse(sanitizedResults, { pagination });
},
async findOne(ctx) {
// use the parent controller
return Object.getPrototypeOf(this).findOne(ctx);
},
}));

View File

@ -0,0 +1,12 @@
const { createCoreService } = require('@strapi/strapi').factories;
module.exports = createCoreService('api::address.address', {
find() {
return {
results: [],
pagination: {
foo: 'bar',
},
};
},
});

View File

@ -1,15 +0,0 @@
'use strict';
/**
* A set of functions called "actions" for `temp`
*/
module.exports = {
// exampleAction: async (ctx, next) => {
// try {
// ctx.body = 'ok';
// } catch (err) {
// ctx.body = err;
// }
// }
};

View File

@ -9,12 +9,7 @@ const { parseBody } = require('./transform');
*
* Returns a collection type controller to handle default core-api actions
*/
const createCollectionTypeController = ({
service,
sanitizeInput,
sanitizeOutput,
transformResponse,
}) => {
const createCollectionTypeController = ({ service }) => {
return {
/**
* Retrieve records.
@ -25,9 +20,9 @@ const createCollectionTypeController = ({
const { query } = ctx;
const { results, pagination } = await service.find(query);
const sanitizedResults = await sanitizeOutput(results, ctx);
const sanitizedResults = await this.sanitizeOutput(results, ctx);
return transformResponse(sanitizedResults, { pagination });
return this.transformResponse(sanitizedResults, { pagination });
},
/**
@ -40,9 +35,9 @@ const createCollectionTypeController = ({
const { query } = ctx;
const entity = await service.findOne(id, query);
const sanitizedEntity = await sanitizeOutput(entity, ctx);
const sanitizedEntity = await this.sanitizeOutput(entity, ctx);
return transformResponse(sanitizedEntity);
return this.transformResponse(sanitizedEntity);
},
/**
@ -59,12 +54,12 @@ const createCollectionTypeController = ({
throw new ValidationError('Missing "data" payload in the request body');
}
const sanitizedInputData = await sanitizeInput(data, ctx);
const sanitizedInputData = await this.sanitizeInput(data, ctx);
const entity = await service.create({ ...query, data: sanitizedInputData, files });
const sanitizedEntity = await sanitizeOutput(entity, ctx);
const sanitizedEntity = await this.sanitizeOutput(entity, ctx);
return transformResponse(sanitizedEntity);
return this.transformResponse(sanitizedEntity);
},
/**
@ -82,12 +77,12 @@ const createCollectionTypeController = ({
throw new ValidationError('Missing "data" payload in the request body');
}
const sanitizedInputData = await sanitizeInput(data, ctx);
const sanitizedInputData = await this.sanitizeInput(data, ctx);
const entity = await service.update(id, { ...query, data: sanitizedInputData, files });
const sanitizedEntity = await sanitizeOutput(entity, ctx);
const sanitizedEntity = await this.sanitizeOutput(entity, ctx);
return transformResponse(sanitizedEntity);
return this.transformResponse(sanitizedEntity);
},
/**
@ -100,9 +95,9 @@ const createCollectionTypeController = ({
const { query } = ctx;
const entity = await service.delete(id, query);
const sanitizedEntity = await sanitizeOutput(entity, ctx);
const sanitizedEntity = await this.sanitizeOutput(entity, ctx);
return transformResponse(sanitizedEntity);
return this.transformResponse(sanitizedEntity);
},
};
};

View File

@ -10,31 +10,37 @@ const createCollectionTypeController = require('./collection-type');
const getAuthFromKoaContext = getOr({}, 'state.auth');
module.exports = ({ service, model }) => {
module.exports = ({ service, contentType }) => {
const ctx = {
model,
contentType,
service,
};
const proto = {
transformResponse(data, meta) {
return transformResponse(data, meta, { contentType: model });
return transformResponse(data, meta, { contentType });
},
sanitizeOutput(data, ctx) {
const auth = getAuthFromKoaContext(ctx);
return sanitize.contentAPI.output(data, strapi.getModel(model.uid), { auth });
return sanitize.contentAPI.output(data, contentType, { auth });
},
sanitizeInput(data, ctx) {
const auth = getAuthFromKoaContext(ctx);
return sanitize.contentAPI.input(data, strapi.getModel(model.uid), { auth });
return sanitize.contentAPI.input(data, contentType, { auth });
},
};
if (contentTypes.isSingleType(model)) {
return createSingleTypeController(ctx);
let ctrl;
if (contentTypes.isSingleType(contentType)) {
ctrl = createSingleTypeController(ctx);
} else {
ctrl = createCollectionTypeController(ctx);
}
return createCollectionTypeController(ctx);
return Object.assign(Object.create(proto), ctrl);
};

View File

@ -8,12 +8,7 @@ const { parseBody } = require('./transform');
/**
* Returns a single type controller to handle default core-api actions
*/
const createSingleTypeController = ({
service,
sanitizeInput,
sanitizeOutput,
transformResponse,
}) => {
const createSingleTypeController = ({ service }) => {
return {
/**
* Retrieve single type content
@ -24,9 +19,9 @@ const createSingleTypeController = ({
const { query } = ctx;
const entity = await service.find(query);
const sanitizedEntity = await sanitizeOutput(entity, ctx);
const sanitizedEntity = await this.sanitizeOutput(entity, ctx);
return transformResponse(sanitizedEntity);
return this.transformResponse(sanitizedEntity);
},
/**
@ -42,21 +37,21 @@ const createSingleTypeController = ({
throw new ValidationError('Missing "data" payload in the request body');
}
const sanitizedInputData = await sanitizeInput(data, ctx);
const sanitizedInputData = await this.sanitizeInput(data, ctx);
const entity = await service.createOrUpdate({ ...query, data: sanitizedInputData, files });
const sanitizedEntity = await sanitizeOutput(entity, ctx);
const sanitizedEntity = await this.sanitizeOutput(entity, ctx);
return transformResponse(sanitizedEntity);
return this.transformResponse(sanitizedEntity);
},
async delete(ctx) {
const { query } = ctx;
const entity = await service.delete(query);
const sanitizedEntity = await sanitizeOutput(entity, ctx);
const sanitizedEntity = await this.sanitizeOutput(entity, ctx);
return transformResponse(sanitizedEntity);
return this.transformResponse(sanitizedEntity);
},
};
};

View File

@ -3,8 +3,6 @@
*/
'use strict';
const _ = require('lodash');
const createController = require('./controller');
const { createService } = require('./service');
@ -17,16 +15,15 @@ const { createService } = require('./service');
* @param {object} opts.strapi strapi
* @returns {object} controller & service
*/
function createCoreApi({ api, model, strapi }) {
const { modelName } = model;
function createCoreApi({ api, contentType, strapi }) {
const { modelName } = contentType;
// find corresponding service and controller
const userService = _.get(api, ['services', modelName], {});
const userController = _.get(api, ['controllers', modelName], {});
const userService = api.service(modelName);
const userController = api.controller(modelName);
const service = Object.assign(createService({ model, strapi }), userService);
const controller = Object.assign(createController({ service, model }), userController);
const service = Object.assign(createService({ contentType, strapi }), userService);
const controller = Object.assign(createController({ service, contentType }), userController);
return {
service,

View File

@ -22,14 +22,12 @@ const setPublishedAt = data => {
*
* Returns a collection type service to handle default core-api actions
*/
const createCollectionTypeService = ({ model, strapi, utils }) => {
const { uid } = model;
const { getFetchParams } = utils;
const createCollectionTypeService = ({ contentType, strapi }) => {
const { uid } = contentType;
return {
async find(params = {}) {
const fetchParams = getFetchParams(params);
const fetchParams = this.getFetchParams(params);
const paginationInfo = getPaginationInfo(fetchParams);
@ -54,13 +52,13 @@ const createCollectionTypeService = ({ model, strapi, utils }) => {
},
findOne(entityId, params = {}) {
return strapi.entityService.findOne(uid, entityId, getFetchParams(params));
return strapi.entityService.findOne(uid, entityId, this.getFetchParams(params));
},
create(params = {}) {
const { data } = params;
if (hasDraftAndPublish(model)) {
if (hasDraftAndPublish(contentType)) {
setPublishedAt(data);
}

View File

@ -13,14 +13,18 @@ const createCollectionTypeService = require('./collection-type');
* @param {{ model: object, strapi: object }} context
* @returns {object}
*/
const createService = ({ model, strapi }) => {
const utils = createUtils({ model });
const createService = ({ contentType, strapi }) => {
const proto = { getFetchParams };
if (isSingleType(model)) {
return createSingleTypeService({ model, strapi, utils });
let service;
if (isSingleType(contentType)) {
service = createSingleTypeService({ contentType, strapi });
} else {
service = createCollectionTypeService({ contentType, strapi });
}
return createCollectionTypeService({ model, strapi, utils });
return Object.assign(Object.create(proto), service);
};
/**
@ -35,13 +39,6 @@ const getFetchParams = (params = {}) => {
};
};
/**
* Mixins
*/
const createUtils = () => ({
getFetchParams,
});
module.exports = {
createService,
getFetchParams,

View File

@ -5,9 +5,8 @@ const { ValidationError } = require('@strapi/utils').errors;
/**
* Returns a single type service to handle default core-api actions
*/
const createSingleTypeService = ({ model, strapi, utils }) => {
const { uid } = model;
const { getFetchParams } = utils;
const createSingleTypeService = ({ contentType, strapi }) => {
const { uid } = contentType;
return {
/**
@ -16,7 +15,7 @@ const createSingleTypeService = ({ model, strapi, utils }) => {
* @return {Promise}
*/
find(params = {}) {
return strapi.entityService.findMany(uid, getFetchParams(params));
return strapi.entityService.findMany(uid, this.getFetchParams(params));
},
/**

View File

@ -1,7 +1,8 @@
'use strict';
const { has } = require('lodash/fp');
const { createCoreApi } = require('../../core-api');
const { createService } = require('../../core-api/service');
const createController = require('../../core-api/controller');
const apisRegistry = strapi => {
const apis = {};
@ -18,22 +19,27 @@ const apisRegistry = strapi => {
throw new Error(`API ${apiName} has already been registered.`);
}
const apiInstance = strapi.container.get('modules').add(`api::${apiName}`, apiConfig);
const api = strapi.container.get('modules').add(`api::${apiName}`, apiConfig);
for (const ctName in apiInstance.contentTypes || {}) {
const contentType = apiInstance.contentTypes[ctName];
for (const ctName in api.contentTypes || {}) {
const contentType = api.contentTypes[ctName];
const { service, controller } = createCoreApi({
model: contentType,
api: apiInstance,
strapi,
});
const uid = `api::${apiName}.${ctName}`;
strapi.container.get('services').set(`api::${apiName}.${ctName}`, service);
strapi.container.get('controllers').set(`api::${apiName}.${ctName}`, controller);
if (!has(contentType.modelName, api.services)) {
const service = createService({ contentType, strapi });
strapi.container.get('services').set(uid, service);
}
if (!has(contentType.modelName, api.controllers)) {
const service = strapi.container.get('services').get(uid);
const controller = createController({ contentType, service });
strapi.container.get('controllers').set(uid, controller);
}
}
apis[apiName] = apiInstance;
apis[apiName] = api;
return apis[apiName];
},

View File

@ -0,0 +1,7 @@
import { Strapi } from '../../';
export type Controller = {
[key: string]: (...args: any) => any;
};
export type ControllerFactory = ({ strapi: Strapi }) => Controller;

View File

@ -3,20 +3,80 @@
const { pickBy, has } = require('lodash/fp');
const { addNamespace, hasNamespace } = require('../utils');
/**
* @typedef {import('./controllers').Controller} Controller
* @typedef {import('./controllers').ControllerFactory} ControllerFactory
*/
const controllersRegistry = () => {
const controllers = {};
const instances = {};
return {
/**
* Returns this list of registered controllers uids
* @returns {string[]}
*/
keys() {
return Object.keys(controllers);
},
/**
* Returns the instance of a controller. Instantiate the controller if not already done
* @param {string} uid
* @returns {Controller}
*/
get(uid) {
return controllers[uid];
if (instances[uid]) {
return instances[uid];
}
const controller = controllers[uid];
if (controller) {
instances[uid] = typeof controller === 'function' ? controller({ strapi }) : controller;
return instances[uid];
}
},
/**
* Returns a map with all the controller in a namespace
* @param {string} namespace
* @returns {{ [key: string]: Controller }}
*/
getAll(namespace) {
return pickBy((_, uid) => hasNamespace(uid, namespace))(controllers);
const filteredControllers = pickBy((_, uid) => hasNamespace(uid, namespace))(controllers);
const map = {};
for (const uid in filteredControllers) {
Object.defineProperty(map, uid, {
enumerable: true,
get: () => {
return this.get(uid);
},
});
}
return map;
},
/**
* Registers a controller
* @param {string} uid
* @param {Controller} controller
*/
set(uid, value) {
controllers[uid] = value;
delete instances[uid];
return this;
},
/**
* Registers a map of controllers for a specific namespace
* @param {string} namespace
* @param {{ [key: string]: Controller|ControllerFactory }} newControllers
* @returns
*/
add(namespace, newControllers) {
for (const controllerName in newControllers) {
const controller = newControllers[controllerName];
@ -27,15 +87,26 @@ const controllersRegistry = () => {
}
controllers[uid] = controller;
}
return this;
},
/**
* Wraps a controller to extend it
* @param {string} uid
* @param {(controller: Controller) => Controller} extendFn
*/
extend(controllerUID, extendFn) {
const currentController = this.get(controllerUID);
if (!currentController) {
throw new Error(`Controller ${controllerUID} doesn't exist`);
}
const newController = extendFn(currentController);
controllers[controllerUID] = newController;
instances[controllerUID] = newController;
return this;
},
};
};

View File

@ -1,6 +1,5 @@
'use strict';
const _ = require('lodash');
const { pickBy, has } = require('lodash/fp');
const { addNamespace, hasNamespace } = require('../utils');
@ -37,8 +36,6 @@ const servicesRegistry = strapi => {
instantiatedServices[uid] = typeof service === 'function' ? service({ strapi }) : service;
return instantiatedServices[uid];
}
return undefined;
},
/**
@ -49,16 +46,28 @@ const servicesRegistry = strapi => {
getAll(namespace) {
const filteredServices = pickBy((_, uid) => hasNamespace(uid, namespace))(services);
return _.mapValues(filteredServices, (service, serviceUID) => this.get(serviceUID));
// create lazy accessor to avoid instantiating the services;
const map = {};
for (const uid in filteredServices) {
Object.defineProperty(map, uid, {
enumerable: true,
get: () => {
return this.get(uid);
},
});
}
return map;
},
/**
* Registers a service
* @param {string} uid
* @param {Service|ServiceFactory} service
* @param {Service} service
*/
set(uid, service) {
instantiatedServices[uid] = service;
services[uid] = service;
delete instantiatedServices[uid];
return this;
},

View File

@ -0,0 +1,48 @@
'use strict';
const createController = require('./core-api/controller');
const { createService } = require('./core-api/service');
const createCoreController = (uid, cfg) => {
return ({ strapi }) => {
const deps = {
service: strapi.service(uid),
contentType: strapi.contentType(uid),
};
const baseController = createController(deps);
let userCtrl = typeof cfg === 'function' ? cfg({ strapi }) : cfg;
// TODO: can only extend the defined action so we can add some without creating breaking
return Object.assign(
Object.create(baseController),
{
get coreController() {
return baseController;
},
},
userCtrl
);
};
};
const createCoreService = (uid, cfg) => {
return ({ strapi }) => {
const deps = {
contentType: strapi.contentType(uid),
};
const baseService = createService(deps);
let userCtrl = typeof cfg === 'function' ? cfg({ strapi }) : cfg;
return Object.assign(baseService, userCtrl);
};
};
module.exports = {
createCoreController,
createCoreService,
};

View File

@ -1,3 +1,7 @@
'use strict';
module.exports = require('./Strapi');
const Strapi = require('./Strapi');
Strapi.factories = require('./factories');
module.exports = Strapi;

View File

@ -182,10 +182,9 @@ const addCreateLocalizationAction = contentType => {
strapi.api[apiName].routes[modelName].routes.push(localizationRoute);
strapi.container.get('controllers').extend(`api::${apiName}.${modelName}`, controller => {
return {
...controller,
return Object.assign(controller, {
createLocalization: createLocalizationHandler(contentType),
};
});
});
};

View File

@ -40,9 +40,13 @@ module.exports = ({ strapi }) => ({
};
_.forEach(strapi.api, (api, apiName) => {
console.log(api.controllers);
const controllers = _.reduce(
api.controllers,
(acc, controller, controllerName) => {
console.log(controllerName, controller);
const contentApiActions = _.pickBy(controller, isContentApi);
if (_.isEmpty(contentApiActions)) {