@strapi/strapi 4.0.0-beta.13 → 4.0.0-beta.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/Strapi.js CHANGED
@@ -463,13 +463,13 @@ class Strapi {
463
463
  await this.container.get('modules')[lifecycleName]();
464
464
 
465
465
  // user
466
- const userLifecycleFunction = this.app[lifecycleName];
466
+ const userLifecycleFunction = this.app && this.app[lifecycleName];
467
467
  if (isFunction(userLifecycleFunction)) {
468
468
  await userLifecycleFunction({ strapi: this });
469
469
  }
470
470
 
471
471
  // admin
472
- const adminLifecycleFunction = this.admin[lifecycleName];
472
+ const adminLifecycleFunction = this.admin && this.admin[lifecycleName];
473
473
  if (isFunction(adminLifecycleFunction)) {
474
474
  await adminLifecycleFunction({ strapi: this });
475
475
  }
@@ -1,5 +1,7 @@
1
1
  'use strict';
2
2
 
3
+ const { generateNewApp } = require('@strapi/generate-new');
4
+
3
5
  /**
4
6
  * `$ strapi new`
5
7
  *
@@ -7,5 +9,5 @@
7
9
  */
8
10
 
9
11
  module.exports = function(...args) {
10
- return require('@strapi/generate-new')(...args);
12
+ return generateNewApp(...args);
11
13
  };
@@ -61,7 +61,9 @@ const createModule = (namespace, rawModule, strapi) => {
61
61
  strapi.container.get('controllers').add(namespace, rawModule.controllers);
62
62
  strapi.container.get('config').set(uidToPath(namespace), rawModule.config);
63
63
  },
64
- routes: rawModule.routes,
64
+ get routes() {
65
+ return rawModule.routes;
66
+ },
65
67
  config(path, defaultValue) {
66
68
  return strapi.container.get('config').get(`${uidToPath(namespace)}.${path}`, defaultValue);
67
69
  },
@@ -1,6 +1,5 @@
1
1
  'use strict';
2
2
 
3
- const _ = require('lodash');
4
3
  const { yup } = require('@strapi/utils');
5
4
 
6
5
  const strapiServerSchema = yup
@@ -14,9 +13,7 @@ const strapiServerSchema = yup
14
13
  if (Array.isArray(value)) {
15
14
  return yup.array();
16
15
  } else {
17
- const shape = _.mapValues(value, () => yup.object({ routes: yup.array().required() }));
18
-
19
- return yup.object(shape);
16
+ return yup.object();
20
17
  }
21
18
  }),
22
19
  controllers: yup.object(),
@@ -51,10 +51,10 @@ const getEnabledPlugins = async strapi => {
51
51
  const packageInfo = require(packagePath);
52
52
 
53
53
  validatePluginName(packageInfo.strapi.name);
54
- internalPlugins[packageInfo.strapi.name] = toDetailedDeclaration({
55
- enabled: true,
56
- resolve: packagePath,
57
- });
54
+ internalPlugins[packageInfo.strapi.name] = {
55
+ ...toDetailedDeclaration({ enabled: true, resolve: packagePath }),
56
+ info: packageInfo.strapi,
57
+ };
58
58
  }
59
59
 
60
60
  const installedPlugins = {};
@@ -64,10 +64,10 @@ const getEnabledPlugins = async strapi => {
64
64
 
65
65
  if (isStrapiPlugin(packageInfo)) {
66
66
  validatePluginName(packageInfo.strapi.name);
67
- installedPlugins[packageInfo.strapi.name] = toDetailedDeclaration({
68
- enabled: true,
69
- resolve: packagePath,
70
- });
67
+ installedPlugins[packageInfo.strapi.name] = {
68
+ ...toDetailedDeclaration({ enabled: true, resolve: packagePath }),
69
+ info: packageInfo.strapi,
70
+ };
71
71
  }
72
72
  }
73
73
 
@@ -79,7 +79,23 @@ const getEnabledPlugins = async strapi => {
79
79
 
80
80
  _.forEach(userPluginsConfig, (declaration, pluginName) => {
81
81
  validatePluginName(pluginName);
82
- declaredPlugins[pluginName] = toDetailedDeclaration(declaration);
82
+
83
+ declaredPlugins[pluginName] = {
84
+ ...toDetailedDeclaration(declaration),
85
+ info: {},
86
+ };
87
+
88
+ const { pathToPlugin } = declaredPlugins[pluginName];
89
+
90
+ // for manually resolved plugins
91
+ if (pathToPlugin) {
92
+ const packagePath = join(pathToPlugin, 'package.json');
93
+ const packageInfo = require(packagePath);
94
+
95
+ if (isStrapiPlugin(packageInfo)) {
96
+ declaredPlugins[pluginName].info = packageInfo.strapi || {};
97
+ }
98
+ }
83
99
  });
84
100
 
85
101
  const declaredPluginsResolves = map(prop('pathToPlugin'), declaredPlugins);
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  const { join } = require('path');
4
- const { existsSync } = require('fs');
4
+ const fse = require('fs-extra');
5
5
  const { defaultsDeep, getOr, get } = require('lodash/fp');
6
6
  const { env } = require('@strapi/utils');
7
7
  const loadConfigFile = require('../../app-configuration/load-config-file');
@@ -26,7 +26,7 @@ const defaultPlugin = {
26
26
 
27
27
  const applyUserExtension = async plugins => {
28
28
  const extensionsDir = strapi.dirs.extensions;
29
- if (!existsSync(extensionsDir)) {
29
+ if (!(await fse.pathExists(extensionsDir))) {
30
30
  return;
31
31
  }
32
32
 
@@ -61,9 +61,9 @@ const formatContentTypes = plugins => {
61
61
  }
62
62
  };
63
63
 
64
- const applyUserConfig = plugins => {
64
+ const applyUserConfig = async plugins => {
65
65
  const userPluginConfigPath = join(strapi.dirs.config, 'plugins.js');
66
- const userPluginsConfig = existsSync(userPluginConfigPath)
66
+ const userPluginsConfig = (await fse.pathExists(userPluginConfigPath))
67
67
  ? loadConfigFile(userPluginConfigPath)
68
68
  : {};
69
69
 
@@ -90,14 +90,24 @@ const loadPlugins = async strapi => {
90
90
 
91
91
  const enabledPlugins = await getEnabledPlugins(strapi);
92
92
 
93
+ strapi.config.set('enabledPlugins', enabledPlugins);
94
+
93
95
  for (const pluginName in enabledPlugins) {
94
96
  const enabledPlugin = enabledPlugins[pluginName];
95
- const pluginServer = loadConfigFile(join(enabledPlugin.pathToPlugin, 'strapi-server.js'));
97
+
98
+ const serverEntrypointPath = join(enabledPlugin.pathToPlugin, 'strapi-server.js');
99
+
100
+ // only load plugins with a server entrypoint
101
+ if (!(await fse.pathExists(serverEntrypointPath))) {
102
+ continue;
103
+ }
104
+
105
+ const pluginServer = loadConfigFile(serverEntrypointPath);
96
106
  plugins[pluginName] = defaultsDeep(defaultPlugin, pluginServer);
97
107
  }
98
108
 
99
109
  // TODO: validate plugin format
100
- applyUserConfig(plugins);
110
+ await applyUserConfig(plugins);
101
111
  await applyUserExtension(plugins);
102
112
  formatContentTypes(plugins);
103
113
 
@@ -1,7 +1,6 @@
1
1
  'use strict';
2
2
 
3
3
  const { has } = require('lodash/fp');
4
- const { createCoreApi } = require('../../core-api');
5
4
 
6
5
  const apisRegistry = strapi => {
7
6
  const apis = {};
@@ -18,22 +17,9 @@ const apisRegistry = strapi => {
18
17
  throw new Error(`API ${apiName} has already been registered.`);
19
18
  }
20
19
 
21
- const apiInstance = strapi.container.get('modules').add(`api::${apiName}`, apiConfig);
20
+ const api = strapi.container.get('modules').add(`api::${apiName}`, apiConfig);
22
21
 
23
- for (const ctName in apiInstance.contentTypes || {}) {
24
- const contentType = apiInstance.contentTypes[ctName];
25
-
26
- const { service, controller } = createCoreApi({
27
- model: contentType,
28
- api: apiInstance,
29
- strapi,
30
- });
31
-
32
- strapi.container.get('services').set(`api::${apiName}.${ctName}`, service);
33
- strapi.container.get('controllers').set(`api::${apiName}.${ctName}`, controller);
34
- }
35
-
36
- apis[apiName] = apiInstance;
22
+ apis[apiName] = api;
37
23
 
38
24
  return apis[apiName];
39
25
  },
@@ -0,0 +1,7 @@
1
+ import { Strapi } from '../../';
2
+
3
+ export type Controller = {
4
+ [key: string]: (...args: any) => any;
5
+ };
6
+
7
+ export type ControllerFactory = ({ strapi: Strapi }) => Controller;
@@ -3,20 +3,80 @@
3
3
  const { pickBy, has } = require('lodash/fp');
4
4
  const { addNamespace, hasNamespace } = require('../utils');
5
5
 
6
+ /**
7
+ * @typedef {import('./controllers').Controller} Controller
8
+ * @typedef {import('./controllers').ControllerFactory} ControllerFactory
9
+ */
10
+
6
11
  const controllersRegistry = () => {
7
12
  const controllers = {};
13
+ const instances = {};
8
14
 
9
15
  return {
16
+ /**
17
+ * Returns this list of registered controllers uids
18
+ * @returns {string[]}
19
+ */
20
+ keys() {
21
+ return Object.keys(controllers);
22
+ },
23
+
24
+ /**
25
+ * Returns the instance of a controller. Instantiate the controller if not already done
26
+ * @param {string} uid
27
+ * @returns {Controller}
28
+ */
10
29
  get(uid) {
11
- return controllers[uid];
30
+ if (instances[uid]) {
31
+ return instances[uid];
32
+ }
33
+
34
+ const controller = controllers[uid];
35
+
36
+ if (controller) {
37
+ instances[uid] = typeof controller === 'function' ? controller({ strapi }) : controller;
38
+ return instances[uid];
39
+ }
12
40
  },
41
+
42
+ /**
43
+ * Returns a map with all the controller in a namespace
44
+ * @param {string} namespace
45
+ * @returns {{ [key: string]: Controller }}
46
+ */
13
47
  getAll(namespace) {
14
- return pickBy((_, uid) => hasNamespace(uid, namespace))(controllers);
48
+ const filteredControllers = pickBy((_, uid) => hasNamespace(uid, namespace))(controllers);
49
+
50
+ const map = {};
51
+ for (const uid in filteredControllers) {
52
+ Object.defineProperty(map, uid, {
53
+ enumerable: true,
54
+ get: () => {
55
+ return this.get(uid);
56
+ },
57
+ });
58
+ }
59
+
60
+ return map;
15
61
  },
62
+
63
+ /**
64
+ * Registers a controller
65
+ * @param {string} uid
66
+ * @param {Controller} controller
67
+ */
16
68
  set(uid, value) {
17
69
  controllers[uid] = value;
70
+ delete instances[uid];
18
71
  return this;
19
72
  },
73
+
74
+ /**
75
+ * Registers a map of controllers for a specific namespace
76
+ * @param {string} namespace
77
+ * @param {{ [key: string]: Controller|ControllerFactory }} newControllers
78
+ * @returns
79
+ */
20
80
  add(namespace, newControllers) {
21
81
  for (const controllerName in newControllers) {
22
82
  const controller = newControllers[controllerName];
@@ -27,15 +87,26 @@ const controllersRegistry = () => {
27
87
  }
28
88
  controllers[uid] = controller;
29
89
  }
90
+
30
91
  return this;
31
92
  },
93
+
94
+ /**
95
+ * Wraps a controller to extend it
96
+ * @param {string} uid
97
+ * @param {(controller: Controller) => Controller} extendFn
98
+ */
32
99
  extend(controllerUID, extendFn) {
33
100
  const currentController = this.get(controllerUID);
101
+
34
102
  if (!currentController) {
35
103
  throw new Error(`Controller ${controllerUID} doesn't exist`);
36
104
  }
105
+
37
106
  const newController = extendFn(currentController);
38
- controllers[controllerUID] = newController;
107
+ instances[controllerUID] = newController;
108
+
109
+ return this;
39
110
  },
40
111
  };
41
112
  };
@@ -1,6 +1,5 @@
1
1
  'use strict';
2
2
 
3
- const _ = require('lodash');
4
3
  const { pickBy, has } = require('lodash/fp');
5
4
  const { addNamespace, hasNamespace } = require('../utils');
6
5
 
@@ -37,8 +36,6 @@ const servicesRegistry = strapi => {
37
36
  instantiatedServices[uid] = typeof service === 'function' ? service({ strapi }) : service;
38
37
  return instantiatedServices[uid];
39
38
  }
40
-
41
- return undefined;
42
39
  },
43
40
 
44
41
  /**
@@ -49,16 +46,28 @@ const servicesRegistry = strapi => {
49
46
  getAll(namespace) {
50
47
  const filteredServices = pickBy((_, uid) => hasNamespace(uid, namespace))(services);
51
48
 
52
- return _.mapValues(filteredServices, (service, serviceUID) => this.get(serviceUID));
49
+ // create lazy accessor to avoid instantiating the services;
50
+ const map = {};
51
+ for (const uid in filteredServices) {
52
+ Object.defineProperty(map, uid, {
53
+ enumerable: true,
54
+ get: () => {
55
+ return this.get(uid);
56
+ },
57
+ });
58
+ }
59
+
60
+ return map;
53
61
  },
54
62
 
55
63
  /**
56
64
  * Registers a service
57
65
  * @param {string} uid
58
- * @param {Service|ServiceFactory} service
66
+ * @param {Service} service
59
67
  */
60
68
  set(uid, service) {
61
- instantiatedServices[uid] = service;
69
+ services[uid] = service;
70
+ delete instantiatedServices[uid];
62
71
  return this;
63
72
  },
64
73
 
@@ -9,12 +9,9 @@ const { parseBody } = require('./transform');
9
9
  *
10
10
  * Returns a collection type controller to handle default core-api actions
11
11
  */
12
- const createCollectionTypeController = ({
13
- service,
14
- sanitizeInput,
15
- sanitizeOutput,
16
- transformResponse,
17
- }) => {
12
+ const createCollectionTypeController = ({ contentType }) => {
13
+ const { uid } = contentType;
14
+
18
15
  return {
19
16
  /**
20
17
  * Retrieve records.
@@ -24,10 +21,10 @@ const createCollectionTypeController = ({
24
21
  async find(ctx) {
25
22
  const { query } = ctx;
26
23
 
27
- const { results, pagination } = await service.find(query);
28
- const sanitizedResults = await sanitizeOutput(results, ctx);
24
+ const { results, pagination } = await strapi.service(uid).find(query);
25
+ const sanitizedResults = await this.sanitizeOutput(results, ctx);
29
26
 
30
- return transformResponse(sanitizedResults, { pagination });
27
+ return this.transformResponse(sanitizedResults, { pagination });
31
28
  },
32
29
 
33
30
  /**
@@ -39,10 +36,10 @@ const createCollectionTypeController = ({
39
36
  const { id } = ctx.params;
40
37
  const { query } = ctx;
41
38
 
42
- const entity = await service.findOne(id, query);
43
- const sanitizedEntity = await sanitizeOutput(entity, ctx);
39
+ const entity = await strapi.service(uid).findOne(id, query);
40
+ const sanitizedEntity = await this.sanitizeOutput(entity, ctx);
44
41
 
45
- return transformResponse(sanitizedEntity);
42
+ return this.transformResponse(sanitizedEntity);
46
43
  },
47
44
 
48
45
  /**
@@ -59,12 +56,14 @@ const createCollectionTypeController = ({
59
56
  throw new ValidationError('Missing "data" payload in the request body');
60
57
  }
61
58
 
62
- const sanitizedInputData = await sanitizeInput(data, ctx);
59
+ const sanitizedInputData = await this.sanitizeInput(data, ctx);
63
60
 
64
- const entity = await service.create({ ...query, data: sanitizedInputData, files });
65
- const sanitizedEntity = await sanitizeOutput(entity, ctx);
61
+ const entity = await strapi
62
+ .service(uid)
63
+ .create({ ...query, data: sanitizedInputData, files });
64
+ const sanitizedEntity = await this.sanitizeOutput(entity, ctx);
66
65
 
67
- return transformResponse(sanitizedEntity);
66
+ return this.transformResponse(sanitizedEntity);
68
67
  },
69
68
 
70
69
  /**
@@ -82,12 +81,14 @@ const createCollectionTypeController = ({
82
81
  throw new ValidationError('Missing "data" payload in the request body');
83
82
  }
84
83
 
85
- const sanitizedInputData = await sanitizeInput(data, ctx);
84
+ const sanitizedInputData = await this.sanitizeInput(data, ctx);
86
85
 
87
- const entity = await service.update(id, { ...query, data: sanitizedInputData, files });
88
- const sanitizedEntity = await sanitizeOutput(entity, ctx);
86
+ const entity = await strapi
87
+ .service(uid)
88
+ .update(id, { ...query, data: sanitizedInputData, files });
89
+ const sanitizedEntity = await this.sanitizeOutput(entity, ctx);
89
90
 
90
- return transformResponse(sanitizedEntity);
91
+ return this.transformResponse(sanitizedEntity);
91
92
  },
92
93
 
93
94
  /**
@@ -99,10 +100,10 @@ const createCollectionTypeController = ({
99
100
  const { id } = ctx.params;
100
101
  const { query } = ctx;
101
102
 
102
- const entity = await service.delete(id, query);
103
- const sanitizedEntity = await sanitizeOutput(entity, ctx);
103
+ const entity = await strapi.service(uid).delete(id, query);
104
+ const sanitizedEntity = await this.sanitizeOutput(entity, ctx);
104
105
 
105
- return transformResponse(sanitizedEntity);
106
+ return this.transformResponse(sanitizedEntity);
106
107
  },
107
108
  };
108
109
  };
@@ -0,0 +1,25 @@
1
+ import { Context } from 'koa';
2
+
3
+ type Response = object;
4
+
5
+ interface BaseController {
6
+ transformResponse(data: object, meta: object): object;
7
+ sanitizeOutput(data: object, ctx: Context): Promise<object>;
8
+ sanitizeInput(data: object, ctx: Context): Promise<object>;
9
+ }
10
+
11
+ export interface SingleTypeController extends BaseController {
12
+ find(ctx: Context): Promise<Response>;
13
+ update(ctx: Context): Promise<Response>;
14
+ delete(ctx: Context): Promise<Response>;
15
+ }
16
+
17
+ export interface CollectionTypeController extends BaseController {
18
+ find(ctx: Context): Promise<Response>;
19
+ findOne(ctx: Context): Promise<Response>;
20
+ create(ctx: Context): Promise<Response>;
21
+ update(ctx: Context): Promise<Response>;
22
+ delete(ctx: Context): Promise<Response>;
23
+ }
24
+
25
+ export type Controller = SingleTypeController | CollectionTypeController;
@@ -10,31 +10,36 @@ const createCollectionTypeController = require('./collection-type');
10
10
 
11
11
  const getAuthFromKoaContext = getOr({}, 'state.auth');
12
12
 
13
- module.exports = ({ service, model }) => {
14
- const ctx = {
15
- model,
16
- service,
13
+ const createController = ({ contentType }) => {
14
+ const ctx = { contentType };
17
15
 
16
+ const proto = {
18
17
  transformResponse(data, meta) {
19
- return transformResponse(data, meta, { contentType: model });
18
+ return transformResponse(data, meta, { contentType });
20
19
  },
21
20
 
22
21
  sanitizeOutput(data, ctx) {
23
22
  const auth = getAuthFromKoaContext(ctx);
24
23
 
25
- return sanitize.contentAPI.output(data, strapi.getModel(model.uid), { auth });
24
+ return sanitize.contentAPI.output(data, contentType, { auth });
26
25
  },
27
26
 
28
27
  sanitizeInput(data, ctx) {
29
28
  const auth = getAuthFromKoaContext(ctx);
30
29
 
31
- return sanitize.contentAPI.input(data, strapi.getModel(model.uid), { auth });
30
+ return sanitize.contentAPI.input(data, contentType, { auth });
32
31
  },
33
32
  };
34
33
 
35
- if (contentTypes.isSingleType(model)) {
36
- return createSingleTypeController(ctx);
34
+ let ctrl;
35
+
36
+ if (contentTypes.isSingleType(contentType)) {
37
+ ctrl = createSingleTypeController(ctx);
38
+ } else {
39
+ ctrl = createCollectionTypeController(ctx);
37
40
  }
38
41
 
39
- return createCollectionTypeController(ctx);
42
+ return Object.assign(Object.create(proto), ctrl);
40
43
  };
44
+
45
+ module.exports = { createController };
@@ -8,12 +8,9 @@ const { parseBody } = require('./transform');
8
8
  /**
9
9
  * Returns a single type controller to handle default core-api actions
10
10
  */
11
- const createSingleTypeController = ({
12
- service,
13
- sanitizeInput,
14
- sanitizeOutput,
15
- transformResponse,
16
- }) => {
11
+ const createSingleTypeController = ({ contentType }) => {
12
+ const { uid } = contentType;
13
+
17
14
  return {
18
15
  /**
19
16
  * Retrieve single type content
@@ -23,10 +20,10 @@ const createSingleTypeController = ({
23
20
  async find(ctx) {
24
21
  const { query } = ctx;
25
22
 
26
- const entity = await service.find(query);
27
- const sanitizedEntity = await sanitizeOutput(entity, ctx);
23
+ const entity = await strapi.service(uid).find(query);
24
+ const sanitizedEntity = await this.sanitizeOutput(entity, ctx);
28
25
 
29
- return transformResponse(sanitizedEntity);
26
+ return this.transformResponse(sanitizedEntity);
30
27
  },
31
28
 
32
29
  /**
@@ -42,21 +39,23 @@ const createSingleTypeController = ({
42
39
  throw new ValidationError('Missing "data" payload in the request body');
43
40
  }
44
41
 
45
- const sanitizedInputData = await sanitizeInput(data, ctx);
42
+ const sanitizedInputData = await this.sanitizeInput(data, ctx);
46
43
 
47
- const entity = await service.createOrUpdate({ ...query, data: sanitizedInputData, files });
48
- const sanitizedEntity = await sanitizeOutput(entity, ctx);
44
+ const entity = await strapi
45
+ .service(uid)
46
+ .createOrUpdate({ ...query, data: sanitizedInputData, files });
47
+ const sanitizedEntity = await this.sanitizeOutput(entity, ctx);
49
48
 
50
- return transformResponse(sanitizedEntity);
49
+ return this.transformResponse(sanitizedEntity);
51
50
  },
52
51
 
53
52
  async delete(ctx) {
54
53
  const { query } = ctx;
55
54
 
56
- const entity = await service.delete(query);
57
- const sanitizedEntity = await sanitizeOutput(entity, ctx);
55
+ const entity = await strapi.service(uid).delete(query);
56
+ const sanitizedEntity = await this.sanitizeOutput(entity, ctx);
58
57
 
59
- return transformResponse(sanitizedEntity);
58
+ return this.transformResponse(sanitizedEntity);
60
59
  },
61
60
  };
62
61
  };
@@ -0,0 +1,71 @@
1
+ 'use strict';
2
+
3
+ const { isSingleType } = require('@strapi/utils').contentTypes;
4
+
5
+ const createRoutes = ({ contentType }) => {
6
+ if (isSingleType(contentType)) {
7
+ return getSingleTypeRoutes(contentType);
8
+ }
9
+
10
+ return getCollectionTypeRoutes(contentType);
11
+ };
12
+
13
+ const getSingleTypeRoutes = ({ uid, info }) => {
14
+ return {
15
+ find: {
16
+ method: 'GET',
17
+ path: `/${info.singularName}`,
18
+ handler: `${uid}.find`,
19
+ config: {},
20
+ },
21
+ update: {
22
+ method: 'PUT',
23
+ path: `/${info.singularName}`,
24
+ handler: `${uid}.update`,
25
+ config: {},
26
+ },
27
+ delete: {
28
+ method: 'DELETE',
29
+ path: `/${info.singularName}`,
30
+ handler: `${uid}.delete`,
31
+ config: {},
32
+ },
33
+ };
34
+ };
35
+
36
+ const getCollectionTypeRoutes = ({ uid, info }) => {
37
+ return {
38
+ find: {
39
+ method: 'GET',
40
+ path: `/${info.pluralName}`,
41
+ handler: `${uid}.find`,
42
+ config: {},
43
+ },
44
+ findOne: {
45
+ method: 'GET',
46
+ path: `/${info.pluralName}/:id`,
47
+ handler: `${uid}.findOne`,
48
+ config: {},
49
+ },
50
+ create: {
51
+ method: 'POST',
52
+ path: `/${info.pluralName}`,
53
+ handler: `${uid}.create`,
54
+ config: {},
55
+ },
56
+ update: {
57
+ method: 'PUT',
58
+ path: `/${info.pluralName}/:id`,
59
+ handler: `${uid}.update`,
60
+ config: {},
61
+ },
62
+ delete: {
63
+ method: 'DELETE',
64
+ path: `/${info.pluralName}/:id`,
65
+ handler: `${uid}.delete`,
66
+ config: {},
67
+ },
68
+ };
69
+ };
70
+
71
+ module.exports = { createRoutes };
@@ -22,14 +22,12 @@ const setPublishedAt = data => {
22
22
  *
23
23
  * Returns a collection type service to handle default core-api actions
24
24
  */
25
- const createCollectionTypeService = ({ model, strapi, utils }) => {
26
- const { uid } = model;
27
-
28
- const { getFetchParams } = utils;
25
+ const createCollectionTypeService = ({ contentType }) => {
26
+ const { uid } = contentType;
29
27
 
30
28
  return {
31
29
  async find(params = {}) {
32
- const fetchParams = getFetchParams(params);
30
+ const fetchParams = this.getFetchParams(params);
33
31
 
34
32
  const paginationInfo = getPaginationInfo(fetchParams);
35
33
 
@@ -54,13 +52,13 @@ const createCollectionTypeService = ({ model, strapi, utils }) => {
54
52
  },
55
53
 
56
54
  findOne(entityId, params = {}) {
57
- return strapi.entityService.findOne(uid, entityId, getFetchParams(params));
55
+ return strapi.entityService.findOne(uid, entityId, this.getFetchParams(params));
58
56
  },
59
57
 
60
58
  create(params = {}) {
61
59
  const { data } = params;
62
60
 
63
- if (hasDraftAndPublish(model)) {
61
+ if (hasDraftAndPublish(contentType)) {
64
62
  setPublishedAt(data);
65
63
  }
66
64
 
@@ -0,0 +1,21 @@
1
+ type Entity = object;
2
+
3
+ interface BaseService {
4
+ getFetchParams(params: object): object;
5
+ }
6
+
7
+ export interface SingleTypeService extends BaseService {
8
+ find(params: object): Promise<Entity>;
9
+ createOrUpdate(params: object): Promise<Entity>;
10
+ delete(params: object): Promise<Entity>;
11
+ }
12
+
13
+ export interface CollectionTypeService extends BaseService {
14
+ find(params: object): Promise<Entity[]>;
15
+ findOne(params: object): Promise<Entity>;
16
+ create(params: object): Promise<Entity>;
17
+ update(params: object): Promise<Entity>;
18
+ delete(params: object): Promise<Entity>;
19
+ }
20
+
21
+ export type Service = SingleTypeService | CollectionTypeService;
@@ -13,14 +13,18 @@ const createCollectionTypeService = require('./collection-type');
13
13
  * @param {{ model: object, strapi: object }} context
14
14
  * @returns {object}
15
15
  */
16
- const createService = ({ model, strapi }) => {
17
- const utils = createUtils({ model });
16
+ const createService = ({ contentType }) => {
17
+ const proto = { getFetchParams };
18
18
 
19
- if (isSingleType(model)) {
20
- return createSingleTypeService({ model, strapi, utils });
19
+ let service;
20
+
21
+ if (isSingleType(contentType)) {
22
+ service = createSingleTypeService({ contentType });
23
+ } else {
24
+ service = createCollectionTypeService({ contentType });
21
25
  }
22
26
 
23
- return createCollectionTypeService({ model, strapi, utils });
27
+ return Object.assign(Object.create(proto), service);
24
28
  };
25
29
 
26
30
  /**
@@ -35,13 +39,6 @@ const getFetchParams = (params = {}) => {
35
39
  };
36
40
  };
37
41
 
38
- /**
39
- * Mixins
40
- */
41
- const createUtils = () => ({
42
- getFetchParams,
43
- });
44
-
45
42
  module.exports = {
46
43
  createService,
47
44
  getFetchParams,
@@ -5,10 +5,12 @@ const { ValidationError } = require('@strapi/utils').errors;
5
5
  /**
6
6
  * Returns a single type service to handle default core-api actions
7
7
  */
8
- const createSingleTypeService = ({ model, strapi, utils }) => {
9
- const { uid } = model;
10
- const { getFetchParams } = utils;
8
+ const createSingleTypeService = ({ contentType }) => {
9
+ const { uid } = contentType;
11
10
 
11
+ /**
12
+ * @type {import('./').SingleTypeService}
13
+ */
12
14
  return {
13
15
  /**
14
16
  * Returns singleType content
@@ -16,7 +18,7 @@ const createSingleTypeService = ({ model, strapi, utils }) => {
16
18
  * @return {Promise}
17
19
  */
18
20
  find(params = {}) {
19
- return strapi.entityService.findMany(uid, getFetchParams(params));
21
+ return strapi.entityService.findMany(uid, this.getFetchParams(params));
20
22
  },
21
23
 
22
24
  /**
@@ -0,0 +1,48 @@
1
+ import { Service } from './core-api/service';
2
+ import { Controller } from './core-api/controller';
3
+ import { Middleware } from './middlewares';
4
+ import { Policy } from './core/registries/policies';
5
+
6
+ type ControllerConfig = Controller;
7
+
8
+ type ServiceConfig = Service;
9
+
10
+ type HandlerConfig = {
11
+ auth: false | { scope: string[] };
12
+ policies: Array<string | Policy>;
13
+ middlewares: Array<string | Middleware>;
14
+ };
15
+
16
+ type SingleTypeRouterConfig = {
17
+ find: HandlerConfig;
18
+ update: HandlerConfig;
19
+ delete: HandlerConfig;
20
+ };
21
+
22
+ type CollectionTypeRouterConfig = {
23
+ find: HandlerConfig;
24
+ findOne: HandlerConfig;
25
+ create: HandlerConfig;
26
+ update: HandlerConfig;
27
+ delete: HandlerConfig;
28
+ };
29
+
30
+ type RouterConfig = {
31
+ prefix: string;
32
+ only: string[];
33
+ except: string[];
34
+ config: SingleTypeRouterConfig | CollectionTypeRouterConfig;
35
+ };
36
+
37
+ interface Route {
38
+ method: string;
39
+ path: string;
40
+ }
41
+ interface Router {
42
+ prefix: string;
43
+ routes: Route[];
44
+ }
45
+
46
+ export function createCoreRouter(uid: string, cfg: RouterConfig): () => Router;
47
+ export function createCoreController(uid: string, cfg: ControllerConfig): () => Controller;
48
+ export function createCoreService(uid: string, cfg: ServiceConfig): () => Service;
@@ -0,0 +1,84 @@
1
+ 'use strict';
2
+
3
+ const { pipe, omit, pick } = require('lodash/fp');
4
+
5
+ const { createController } = require('./core-api/controller');
6
+ const { createService } = require('./core-api/service');
7
+ const { createRoutes } = require('./core-api/routes');
8
+
9
+ const createCoreController = (uid, cfg = {}) => {
10
+ return ({ strapi }) => {
11
+ const baseController = createController({
12
+ contentType: strapi.contentType(uid),
13
+ });
14
+
15
+ let userCtrl = typeof cfg === 'function' ? cfg({ strapi }) : cfg;
16
+
17
+ for (const methodName of Object.keys(baseController)) {
18
+ if (userCtrl[methodName] === undefined) {
19
+ userCtrl[methodName] = baseController[methodName];
20
+ }
21
+ }
22
+
23
+ Object.setPrototypeOf(userCtrl, baseController);
24
+ return userCtrl;
25
+ };
26
+ };
27
+
28
+ const createCoreService = (uid, cfg = {}) => {
29
+ return ({ strapi }) => {
30
+ const baseService = createService({
31
+ contentType: strapi.contentType(uid),
32
+ });
33
+
34
+ let userService = typeof cfg === 'function' ? cfg({ strapi }) : cfg;
35
+
36
+ for (const methodName of Object.keys(baseService)) {
37
+ if (userService[methodName] === undefined) {
38
+ userService[methodName] = baseService[methodName];
39
+ }
40
+ }
41
+
42
+ Object.setPrototypeOf(userService, baseService);
43
+ return userService;
44
+ };
45
+ };
46
+
47
+ const createCoreRouter = (uid, cfg = {}) => {
48
+ const { prefix, config = {}, only, except } = cfg;
49
+ let routes;
50
+
51
+ return {
52
+ get prefix() {
53
+ return prefix;
54
+ },
55
+ get routes() {
56
+ if (!routes) {
57
+ const contentType = strapi.contentType(uid);
58
+
59
+ const defaultRoutes = createRoutes({ contentType });
60
+
61
+ Object.keys(defaultRoutes).forEach(routeName => {
62
+ const defaultRoute = defaultRoutes[routeName];
63
+
64
+ Object.assign(defaultRoute.config, config[routeName] || {});
65
+ });
66
+
67
+ const selectedRoutes = pipe(
68
+ routes => (except ? omit(except, routes) : routes),
69
+ routes => (only ? pick(only, routes) : routes)
70
+ )(defaultRoutes);
71
+
72
+ routes = Object.values(selectedRoutes);
73
+ }
74
+
75
+ return routes;
76
+ },
77
+ };
78
+ };
79
+
80
+ module.exports = {
81
+ createCoreController,
82
+ createCoreService,
83
+ createCoreRouter,
84
+ };
package/lib/index.d.ts CHANGED
@@ -2,6 +2,7 @@ import { Database } from '@strapi/database';
2
2
  import { EntityService } from './services/entity-service';
3
3
  import { Strapi as StrapiClass } from './Strapi';
4
4
 
5
+ export * as factories from './factories';
5
6
  interface StrapiInterface extends StrapiClass {
6
7
  query: Database['query'];
7
8
  entityService: EntityService;
package/lib/index.js CHANGED
@@ -1,3 +1,7 @@
1
1
  'use strict';
2
2
 
3
- module.exports = require('./Strapi');
3
+ const Strapi = require('./Strapi');
4
+
5
+ Strapi.factories = require('./factories');
6
+
7
+ module.exports = Strapi;
@@ -2,3 +2,4 @@ import { Strapi } from '../';
2
2
  import { Middleware } from 'koa';
3
3
 
4
4
  export type MiddlewareFactory = (config: any, ctx: { strapi: Strapi }) => Middleware | null;
5
+ export type Middleware = Middleware;
@@ -14,6 +14,7 @@ const defaults = {
14
14
  'connect-src': ["'self'", 'https:'],
15
15
  'img-src': ["'self'", 'data:', 'blob:'],
16
16
  'media-src': ["'self'", 'data:', 'blob:'],
17
+ upgradeInsecureRequests: null,
17
18
  },
18
19
  },
19
20
  xssFilter: false,
@@ -35,7 +35,7 @@ type Params<T> = {
35
35
  files?: any;
36
36
  };
37
37
 
38
- interface EntityService {
38
+ export interface EntityService {
39
39
  uploadFiles<K extends keyof AllTypes, T extends AllTypes[K]>(uid: K, entity, files);
40
40
  wrapParams<K extends keyof AllTypes, T extends AllTypes[K]>(
41
41
  params: Params<T>,
@@ -89,17 +89,30 @@ module.exports = strapi => {
89
89
  };
90
90
 
91
91
  const getController = (name, { pluginName, apiName }, strapi) => {
92
+ let ctrl;
93
+
92
94
  if (pluginName) {
93
95
  if (pluginName === 'admin') {
94
- return strapi.controller(`admin::${name}`);
96
+ ctrl = strapi.controller(`admin::${name}`);
97
+ } else {
98
+ ctrl = strapi.plugin(pluginName).controller(name);
95
99
  }
96
-
97
- return strapi.plugin(pluginName).controller(name);
98
100
  } else if (apiName) {
99
- return strapi.controller(`api::${apiName}.${name}`);
101
+ ctrl = strapi.controller(`api::${apiName}.${name}`);
102
+ }
103
+
104
+ if (!ctrl) {
105
+ return strapi.controller(name);
100
106
  }
101
107
 
102
- return strapi.controller(name);
108
+ return ctrl;
109
+ };
110
+
111
+ const extractHandlerParts = name => {
112
+ const controllerName = name.slice(0, name.lastIndexOf('.'));
113
+ const actionName = name.slice(name.lastIndexOf('.') + 1);
114
+
115
+ return { controllerName, actionName };
103
116
  };
104
117
 
105
118
  const getAction = (route, strapi) => {
@@ -110,7 +123,7 @@ const getAction = (route, strapi) => {
110
123
  return handler;
111
124
  }
112
125
 
113
- const [controllerName, actionName] = trim(handler).split('.');
126
+ const { controllerName, actionName } = extractHandlerParts(trim(handler));
114
127
 
115
128
  const controller = getController(controllerName, { pluginName, apiName }, strapi);
116
129
 
@@ -6,12 +6,10 @@ const createRouteScopeGenerator = namespace => route => {
6
6
  const prefix = namespace.endsWith('::') ? namespace : `${namespace}.`;
7
7
 
8
8
  if (typeof route.handler === 'string') {
9
- const [controller, action] = route.handler.split('.');
10
-
11
9
  _.defaultsDeep(route, {
12
10
  config: {
13
11
  auth: {
14
- scope: [`${prefix}${controller}.${action}`],
12
+ scope: [`${route.handler.startsWith(prefix) ? '' : prefix}${route.handler}`],
15
13
  },
16
14
  },
17
15
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@strapi/strapi",
3
- "version": "4.0.0-beta.13",
3
+ "version": "4.0.0-beta.17",
4
4
  "description": "An open source headless CMS solution to create and manage your own API. It provides a powerful dashboard and features to make your life easier. Databases supported: MySQL, MariaDB, PostgreSQL, SQLite",
5
5
  "keywords": [
6
6
  "strapi",
@@ -79,16 +79,16 @@
79
79
  "dependencies": {
80
80
  "@koa/cors": "3.1.0",
81
81
  "@koa/router": "10.1.1",
82
- "@strapi/admin": "4.0.0-beta.13",
83
- "@strapi/database": "4.0.0-beta.13",
84
- "@strapi/generate-new": "4.0.0-beta.13",
85
- "@strapi/generators": "4.0.0-beta.13",
86
- "@strapi/logger": "4.0.0-beta.13",
87
- "@strapi/plugin-content-manager": "4.0.0-beta.13",
88
- "@strapi/plugin-content-type-builder": "4.0.0-beta.13",
89
- "@strapi/plugin-email": "4.0.0-beta.13",
90
- "@strapi/plugin-upload": "4.0.0-beta.13",
91
- "@strapi/utils": "4.0.0-beta.13",
82
+ "@strapi/admin": "4.0.0-beta.17",
83
+ "@strapi/database": "4.0.0-beta.17",
84
+ "@strapi/generate-new": "4.0.0-beta.17",
85
+ "@strapi/generators": "4.0.0-beta.17",
86
+ "@strapi/logger": "4.0.0-beta.17",
87
+ "@strapi/plugin-content-manager": "4.0.0-beta.17",
88
+ "@strapi/plugin-content-type-builder": "4.0.0-beta.17",
89
+ "@strapi/plugin-email": "4.0.0-beta.17",
90
+ "@strapi/plugin-upload": "4.0.0-beta.17",
91
+ "@strapi/utils": "4.0.0-beta.17",
92
92
  "bcryptjs": "2.4.3",
93
93
  "boxen": "5.1.2",
94
94
  "chalk": "4.1.2",
@@ -134,5 +134,5 @@
134
134
  "node": ">=12.x.x <=16.x.x",
135
135
  "npm": ">=6.0.0"
136
136
  },
137
- "gitHead": "3a31a96067aa0a74cabf51282215820ba46dd447"
137
+ "gitHead": "8f9cf3803464d3dbfc8c1090c16bb5f53a60c6c3"
138
138
  }
@@ -1,39 +0,0 @@
1
- /**
2
- * Core API
3
- */
4
- 'use strict';
5
-
6
- const _ = require('lodash');
7
-
8
- const createController = require('./controller');
9
- const { createService } = require('./service');
10
-
11
- /**
12
- * Returns a service and a controller built based on the content type passed
13
- *
14
- * @param {object} opts options
15
- * @param {object} opts.api api
16
- * @param {object} opts.model model
17
- * @param {object} opts.strapi strapi
18
- * @returns {object} controller & service
19
- */
20
- function createCoreApi({ api, model, strapi }) {
21
- const { modelName } = model;
22
-
23
- // find corresponding service and controller
24
- const userService = _.get(api, ['services', modelName], {});
25
- const userController = _.get(api, ['controllers', modelName], {});
26
-
27
- const service = Object.assign(createService({ model, strapi }), userService);
28
-
29
- const controller = Object.assign(createController({ service, model }), userController);
30
-
31
- return {
32
- service,
33
- controller,
34
- };
35
- }
36
-
37
- module.exports = {
38
- createCoreApi,
39
- };