@strapi/strapi 4.0.0-beta.12 → 4.0.0-beta.16
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 +3 -3
- package/lib/commands/new.js +3 -1
- package/lib/core/domain/module/index.js +3 -1
- package/lib/core/domain/module/validation.js +1 -4
- package/lib/core/loaders/plugins/get-enabled-plugins.js +25 -9
- package/lib/core/loaders/plugins/index.js +16 -6
- package/lib/core/registries/apis.js +2 -16
- package/lib/core/registries/controllers.d.ts +7 -0
- package/lib/core/registries/controllers.js +74 -3
- package/lib/core/registries/services.js +15 -6
- package/lib/core-api/controller/collection-type.js +37 -23
- package/lib/core-api/controller/index.d.ts +25 -0
- package/lib/core-api/controller/index.js +15 -10
- package/lib/core-api/controller/single-type.js +23 -16
- package/lib/core-api/routes/index.js +71 -0
- package/lib/core-api/service/collection-type.js +5 -7
- package/lib/core-api/service/index.d.ts +21 -0
- package/lib/core-api/service/index.js +9 -12
- package/lib/core-api/service/single-type.js +6 -4
- package/lib/factories.d.ts +48 -0
- package/lib/factories.js +84 -0
- package/lib/index.d.ts +1 -0
- package/lib/index.js +5 -1
- package/lib/middlewares/errors.js +1 -1
- package/lib/middlewares/index.d.ts +1 -0
- package/lib/middlewares/security.js +7 -2
- package/lib/services/entity-service/index.d.ts +1 -1
- package/lib/services/entity-service/index.js +8 -7
- package/lib/services/entity-validator/index.js +10 -1
- package/lib/services/errors.js +8 -3
- package/lib/services/server/compose-endpoint.js +19 -6
- package/lib/services/server/register-routes.js +1 -3
- package/package.json +12 -12
- package/lib/core-api/index.js +0 -39
package/lib/Strapi.js
CHANGED
|
@@ -366,7 +366,7 @@ class Strapi {
|
|
|
366
366
|
this.telemetry.bootstrap();
|
|
367
367
|
|
|
368
368
|
let oldContentTypes;
|
|
369
|
-
if (await this.db.
|
|
369
|
+
if (await this.db.getSchemaConnection().hasTable(coreStoreModel.collectionName)) {
|
|
370
370
|
oldContentTypes = await this.store.get({
|
|
371
371
|
type: 'strapi',
|
|
372
372
|
name: 'content_types',
|
|
@@ -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
|
}
|
package/lib/commands/new.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
-
|
|
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] =
|
|
55
|
-
enabled: true,
|
|
56
|
-
|
|
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] =
|
|
68
|
-
enabled: true,
|
|
69
|
-
|
|
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
|
-
|
|
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
|
|
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 (!
|
|
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 =
|
|
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
|
-
|
|
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
|
|
20
|
+
const api = strapi.container.get('modules').add(`api::${apiName}`, apiConfig);
|
|
22
21
|
|
|
23
|
-
|
|
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
|
},
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
66
|
+
* @param {Service} service
|
|
59
67
|
*/
|
|
60
68
|
set(uid, service) {
|
|
61
|
-
|
|
69
|
+
services[uid] = service;
|
|
70
|
+
delete instantiatedServices[uid];
|
|
62
71
|
return this;
|
|
63
72
|
},
|
|
64
73
|
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const { isObject } = require('lodash/fp');
|
|
4
|
+
const { ValidationError } = require('@strapi/utils').errors;
|
|
5
|
+
|
|
3
6
|
const { parseBody } = require('./transform');
|
|
4
7
|
|
|
5
8
|
/**
|
|
6
9
|
*
|
|
7
10
|
* Returns a collection type controller to handle default core-api actions
|
|
8
11
|
*/
|
|
9
|
-
const createCollectionTypeController = ({
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
sanitizeOutput,
|
|
13
|
-
transformResponse,
|
|
14
|
-
}) => {
|
|
12
|
+
const createCollectionTypeController = ({ contentType }) => {
|
|
13
|
+
const { uid } = contentType;
|
|
14
|
+
|
|
15
15
|
return {
|
|
16
16
|
/**
|
|
17
17
|
* Retrieve records.
|
|
@@ -21,10 +21,10 @@ const createCollectionTypeController = ({
|
|
|
21
21
|
async find(ctx) {
|
|
22
22
|
const { query } = ctx;
|
|
23
23
|
|
|
24
|
-
const { results, pagination } = await service.find(query);
|
|
25
|
-
const sanitizedResults = await sanitizeOutput(results, ctx);
|
|
24
|
+
const { results, pagination } = await strapi.service(uid).find(query);
|
|
25
|
+
const sanitizedResults = await this.sanitizeOutput(results, ctx);
|
|
26
26
|
|
|
27
|
-
return transformResponse(sanitizedResults, { pagination });
|
|
27
|
+
return this.transformResponse(sanitizedResults, { pagination });
|
|
28
28
|
},
|
|
29
29
|
|
|
30
30
|
/**
|
|
@@ -36,10 +36,10 @@ const createCollectionTypeController = ({
|
|
|
36
36
|
const { id } = ctx.params;
|
|
37
37
|
const { query } = ctx;
|
|
38
38
|
|
|
39
|
-
const entity = await service.findOne(id, query);
|
|
40
|
-
const sanitizedEntity = await sanitizeOutput(entity, ctx);
|
|
39
|
+
const entity = await strapi.service(uid).findOne(id, query);
|
|
40
|
+
const sanitizedEntity = await this.sanitizeOutput(entity, ctx);
|
|
41
41
|
|
|
42
|
-
return transformResponse(sanitizedEntity);
|
|
42
|
+
return this.transformResponse(sanitizedEntity);
|
|
43
43
|
},
|
|
44
44
|
|
|
45
45
|
/**
|
|
@@ -51,12 +51,19 @@ const createCollectionTypeController = ({
|
|
|
51
51
|
const { query } = ctx.request;
|
|
52
52
|
|
|
53
53
|
const { data, files } = parseBody(ctx);
|
|
54
|
-
const sanitizedInputData = await sanitizeInput(data, ctx);
|
|
55
54
|
|
|
56
|
-
|
|
57
|
-
|
|
55
|
+
if (!isObject(data)) {
|
|
56
|
+
throw new ValidationError('Missing "data" payload in the request body');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const sanitizedInputData = await this.sanitizeInput(data, ctx);
|
|
58
60
|
|
|
59
|
-
|
|
61
|
+
const entity = await strapi
|
|
62
|
+
.service(uid)
|
|
63
|
+
.create({ ...query, data: sanitizedInputData, files });
|
|
64
|
+
const sanitizedEntity = await this.sanitizeOutput(entity, ctx);
|
|
65
|
+
|
|
66
|
+
return this.transformResponse(sanitizedEntity);
|
|
60
67
|
},
|
|
61
68
|
|
|
62
69
|
/**
|
|
@@ -69,12 +76,19 @@ const createCollectionTypeController = ({
|
|
|
69
76
|
const { query } = ctx.request;
|
|
70
77
|
|
|
71
78
|
const { data, files } = parseBody(ctx);
|
|
72
|
-
const sanitizedInputData = await sanitizeInput(data, ctx);
|
|
73
79
|
|
|
74
|
-
|
|
75
|
-
|
|
80
|
+
if (!isObject(data)) {
|
|
81
|
+
throw new ValidationError('Missing "data" payload in the request body');
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const sanitizedInputData = await this.sanitizeInput(data, ctx);
|
|
85
|
+
|
|
86
|
+
const entity = await strapi
|
|
87
|
+
.service(uid)
|
|
88
|
+
.update(id, { ...query, data: sanitizedInputData, files });
|
|
89
|
+
const sanitizedEntity = await this.sanitizeOutput(entity, ctx);
|
|
76
90
|
|
|
77
|
-
return transformResponse(sanitizedEntity);
|
|
91
|
+
return this.transformResponse(sanitizedEntity);
|
|
78
92
|
},
|
|
79
93
|
|
|
80
94
|
/**
|
|
@@ -86,10 +100,10 @@ const createCollectionTypeController = ({
|
|
|
86
100
|
const { id } = ctx.params;
|
|
87
101
|
const { query } = ctx;
|
|
88
102
|
|
|
89
|
-
const entity = await service.delete(id, query);
|
|
90
|
-
const sanitizedEntity = await sanitizeOutput(entity, ctx);
|
|
103
|
+
const entity = await strapi.service(uid).delete(id, query);
|
|
104
|
+
const sanitizedEntity = await this.sanitizeOutput(entity, ctx);
|
|
91
105
|
|
|
92
|
-
return transformResponse(sanitizedEntity);
|
|
106
|
+
return this.transformResponse(sanitizedEntity);
|
|
93
107
|
},
|
|
94
108
|
};
|
|
95
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
|
-
|
|
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
|
|
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,
|
|
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,
|
|
30
|
+
return sanitize.contentAPI.input(data, contentType, { auth });
|
|
32
31
|
},
|
|
33
32
|
};
|
|
34
33
|
|
|
35
|
-
|
|
36
|
-
|
|
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
|
|
42
|
+
return Object.assign(Object.create(proto), ctrl);
|
|
40
43
|
};
|
|
44
|
+
|
|
45
|
+
module.exports = { createController };
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const { isObject } = require('lodash/fp');
|
|
4
|
+
const { ValidationError } = require('@strapi/utils').errors;
|
|
5
|
+
|
|
3
6
|
const { parseBody } = require('./transform');
|
|
4
7
|
|
|
5
8
|
/**
|
|
6
9
|
* Returns a single type controller to handle default core-api actions
|
|
7
10
|
*/
|
|
8
|
-
const createSingleTypeController = ({
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
sanitizeOutput,
|
|
12
|
-
transformResponse,
|
|
13
|
-
}) => {
|
|
11
|
+
const createSingleTypeController = ({ contentType }) => {
|
|
12
|
+
const { uid } = contentType;
|
|
13
|
+
|
|
14
14
|
return {
|
|
15
15
|
/**
|
|
16
16
|
* Retrieve single type content
|
|
@@ -20,10 +20,10 @@ const createSingleTypeController = ({
|
|
|
20
20
|
async find(ctx) {
|
|
21
21
|
const { query } = ctx;
|
|
22
22
|
|
|
23
|
-
const entity = await service.find(query);
|
|
24
|
-
const sanitizedEntity = await sanitizeOutput(entity, ctx);
|
|
23
|
+
const entity = await strapi.service(uid).find(query);
|
|
24
|
+
const sanitizedEntity = await this.sanitizeOutput(entity, ctx);
|
|
25
25
|
|
|
26
|
-
return transformResponse(sanitizedEntity);
|
|
26
|
+
return this.transformResponse(sanitizedEntity);
|
|
27
27
|
},
|
|
28
28
|
|
|
29
29
|
/**
|
|
@@ -34,21 +34,28 @@ const createSingleTypeController = ({
|
|
|
34
34
|
async update(ctx) {
|
|
35
35
|
const { query } = ctx.request;
|
|
36
36
|
const { data, files } = parseBody(ctx);
|
|
37
|
-
const sanitizedInputData = await sanitizeInput(data, ctx);
|
|
38
37
|
|
|
39
|
-
|
|
40
|
-
|
|
38
|
+
if (!isObject(data)) {
|
|
39
|
+
throw new ValidationError('Missing "data" payload in the request body');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const sanitizedInputData = await this.sanitizeInput(data, ctx);
|
|
43
|
+
|
|
44
|
+
const entity = await strapi
|
|
45
|
+
.service(uid)
|
|
46
|
+
.createOrUpdate({ ...query, data: sanitizedInputData, files });
|
|
47
|
+
const sanitizedEntity = await this.sanitizeOutput(entity, ctx);
|
|
41
48
|
|
|
42
|
-
return transformResponse(sanitizedEntity);
|
|
49
|
+
return this.transformResponse(sanitizedEntity);
|
|
43
50
|
},
|
|
44
51
|
|
|
45
52
|
async delete(ctx) {
|
|
46
53
|
const { query } = ctx;
|
|
47
54
|
|
|
48
|
-
const entity = await service.delete(query);
|
|
49
|
-
const sanitizedEntity = await sanitizeOutput(entity, ctx);
|
|
55
|
+
const entity = await strapi.service(uid).delete(query);
|
|
56
|
+
const sanitizedEntity = await this.sanitizeOutput(entity, ctx);
|
|
50
57
|
|
|
51
|
-
return transformResponse(sanitizedEntity);
|
|
58
|
+
return this.transformResponse(sanitizedEntity);
|
|
52
59
|
},
|
|
53
60
|
};
|
|
54
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 = ({
|
|
26
|
-
const { uid } =
|
|
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(
|
|
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 = ({
|
|
17
|
-
const
|
|
16
|
+
const createService = ({ contentType }) => {
|
|
17
|
+
const proto = { getFetchParams };
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
let service;
|
|
20
|
+
|
|
21
|
+
if (isSingleType(contentType)) {
|
|
22
|
+
service = createSingleTypeService({ contentType });
|
|
23
|
+
} else {
|
|
24
|
+
service = createCollectionTypeService({ contentType });
|
|
21
25
|
}
|
|
22
26
|
|
|
23
|
-
return
|
|
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 = ({
|
|
9
|
-
const { uid } =
|
|
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;
|
package/lib/factories.js
ADDED
|
@@ -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
|
@@ -12,6 +12,8 @@ const defaults = {
|
|
|
12
12
|
useDefaults: true,
|
|
13
13
|
directives: {
|
|
14
14
|
'connect-src': ["'self'", 'https:'],
|
|
15
|
+
'img-src': ["'self'", 'data:', 'blob:'],
|
|
16
|
+
'media-src': ["'self'", 'data:', 'blob:'],
|
|
15
17
|
},
|
|
16
18
|
},
|
|
17
19
|
xssFilter: false,
|
|
@@ -30,12 +32,15 @@ const defaults = {
|
|
|
30
32
|
module.exports = config => (ctx, next) => {
|
|
31
33
|
let helmetConfig = defaultsDeep(defaults, config);
|
|
32
34
|
|
|
33
|
-
if (
|
|
35
|
+
if (
|
|
36
|
+
ctx.method === 'GET' &&
|
|
37
|
+
['/graphql', '/documentation'].some(str => ctx.path.startsWith(str))
|
|
38
|
+
) {
|
|
34
39
|
helmetConfig = merge(helmetConfig, {
|
|
35
40
|
contentSecurityPolicy: {
|
|
36
41
|
directives: {
|
|
37
42
|
'script-src': ["'self'", "'unsafe-inline'", 'cdn.jsdelivr.net'],
|
|
38
|
-
'img-src': ["'self'", 'data:', 'cdn.jsdelivr.net'],
|
|
43
|
+
'img-src': ["'self'", 'data:', 'cdn.jsdelivr.net', 'strapi.io'],
|
|
39
44
|
},
|
|
40
45
|
},
|
|
41
46
|
});
|
|
@@ -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>,
|
|
@@ -8,9 +8,9 @@ const {
|
|
|
8
8
|
InvalidDateTimeError,
|
|
9
9
|
} = require('@strapi/database').errors;
|
|
10
10
|
const {
|
|
11
|
-
sanitize,
|
|
12
11
|
webhook: webhookUtils,
|
|
13
12
|
contentTypes: contentTypesUtils,
|
|
13
|
+
sanitize,
|
|
14
14
|
} = require('@strapi/utils');
|
|
15
15
|
const { ValidationError } = require('@strapi/utils').errors;
|
|
16
16
|
const uploadFiles = require('../utils/upload-files');
|
|
@@ -92,12 +92,13 @@ const createDefaultImplementation = ({ strapi, db, eventHub, entityValidator })
|
|
|
92
92
|
return options;
|
|
93
93
|
},
|
|
94
94
|
|
|
95
|
-
emitEvent(uid, event, entity) {
|
|
95
|
+
async emitEvent(uid, event, entity) {
|
|
96
96
|
const model = strapi.getModel(uid);
|
|
97
|
+
const sanitizedEntity = await sanitize.sanitizers.defaultSanitizeOutput(model, entity);
|
|
97
98
|
|
|
98
99
|
eventHub.emit(event, {
|
|
99
100
|
model: model.modelName,
|
|
100
|
-
entry:
|
|
101
|
+
entry: sanitizedEntity,
|
|
101
102
|
});
|
|
102
103
|
},
|
|
103
104
|
|
|
@@ -182,7 +183,7 @@ const createDefaultImplementation = ({ strapi, db, eventHub, entityValidator })
|
|
|
182
183
|
entity = await this.findOne(uid, entity.id, wrappedParams);
|
|
183
184
|
}
|
|
184
185
|
|
|
185
|
-
this.emitEvent(uid, ENTRY_CREATE, entity);
|
|
186
|
+
await this.emitEvent(uid, ENTRY_CREATE, entity);
|
|
186
187
|
|
|
187
188
|
return entity;
|
|
188
189
|
},
|
|
@@ -225,7 +226,7 @@ const createDefaultImplementation = ({ strapi, db, eventHub, entityValidator })
|
|
|
225
226
|
entity = await this.findOne(uid, entity.id, wrappedParams);
|
|
226
227
|
}
|
|
227
228
|
|
|
228
|
-
this.emitEvent(uid, ENTRY_UPDATE, entity);
|
|
229
|
+
await this.emitEvent(uid, ENTRY_UPDATE, entity);
|
|
229
230
|
|
|
230
231
|
return entity;
|
|
231
232
|
},
|
|
@@ -248,7 +249,7 @@ const createDefaultImplementation = ({ strapi, db, eventHub, entityValidator })
|
|
|
248
249
|
await deleteComponents(uid, entityToDelete);
|
|
249
250
|
await db.query(uid).delete({ where: { id: entityToDelete.id } });
|
|
250
251
|
|
|
251
|
-
this.emitEvent(uid, ENTRY_DELETE, entityToDelete);
|
|
252
|
+
await this.emitEvent(uid, ENTRY_DELETE, entityToDelete);
|
|
252
253
|
|
|
253
254
|
return entityToDelete;
|
|
254
255
|
},
|
|
@@ -263,7 +264,7 @@ const createDefaultImplementation = ({ strapi, db, eventHub, entityValidator })
|
|
|
263
264
|
return db.query(uid).deleteMany(query);
|
|
264
265
|
},
|
|
265
266
|
|
|
266
|
-
load(uid, entity, field, params) {
|
|
267
|
+
load(uid, entity, field, params = {}) {
|
|
267
268
|
const { attributes } = strapi.getModel(uid);
|
|
268
269
|
|
|
269
270
|
const attribute = attributes[field];
|
|
@@ -4,12 +4,13 @@
|
|
|
4
4
|
*/
|
|
5
5
|
'use strict';
|
|
6
6
|
|
|
7
|
-
const { has, assoc, prop } = require('lodash/fp');
|
|
7
|
+
const { has, assoc, prop, isObject } = require('lodash/fp');
|
|
8
8
|
const strapiUtils = require('@strapi/utils');
|
|
9
9
|
const validators = require('./validators');
|
|
10
10
|
|
|
11
11
|
const { yup, validateYupSchema } = strapiUtils;
|
|
12
12
|
const { isMediaAttribute, isScalarAttribute, getWritableAttributes } = strapiUtils.contentTypes;
|
|
13
|
+
const { ValidationError } = strapiUtils.errors;
|
|
13
14
|
|
|
14
15
|
const addMinMax = (attr, validator, data) => {
|
|
15
16
|
if (Number.isInteger(attr.min) && (attr.required || (Array.isArray(data) && data.length > 0))) {
|
|
@@ -173,6 +174,14 @@ const createModelValidator = createOrUpdate => (model, data, { isDraft }) => {
|
|
|
173
174
|
};
|
|
174
175
|
|
|
175
176
|
const createValidateEntity = createOrUpdate => async (model, data, { isDraft = false } = {}) => {
|
|
177
|
+
if (!isObject(data)) {
|
|
178
|
+
const { displayName } = model.info;
|
|
179
|
+
|
|
180
|
+
throw new ValidationError(
|
|
181
|
+
`Invalid payload submitted for the ${createOrUpdate} of an entity of type ${displayName}. Expected an object, but got ${typeof data}`
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
|
|
176
185
|
const validator = createModelValidator(createOrUpdate)(model, data, { isDraft }).required();
|
|
177
186
|
return validateYupSchema(validator, { strict: false, abortEarly: false })(data);
|
|
178
187
|
};
|
package/lib/services/errors.js
CHANGED
|
@@ -60,9 +60,14 @@ const formatHttpError = error => {
|
|
|
60
60
|
};
|
|
61
61
|
};
|
|
62
62
|
|
|
63
|
-
const formatInternalError =
|
|
64
|
-
const
|
|
65
|
-
|
|
63
|
+
const formatInternalError = error => {
|
|
64
|
+
const httpError = createError(error);
|
|
65
|
+
|
|
66
|
+
if (httpError.expose) {
|
|
67
|
+
return formatHttpError(httpError);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return formatHttpError(createError(httpError.status || 500));
|
|
66
71
|
};
|
|
67
72
|
|
|
68
73
|
module.exports = {
|
|
@@ -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
|
-
|
|
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
|
-
|
|
101
|
+
ctrl = strapi.controller(`api::${apiName}.${name}`);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (!ctrl) {
|
|
105
|
+
return strapi.controller(name);
|
|
100
106
|
}
|
|
101
107
|
|
|
102
|
-
return
|
|
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
|
|
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}${
|
|
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.
|
|
3
|
+
"version": "4.0.0-beta.16",
|
|
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.
|
|
83
|
-
"@strapi/database": "4.0.0-beta.
|
|
84
|
-
"@strapi/generate-new": "4.0.0-beta.
|
|
85
|
-
"@strapi/generators": "4.0.0-beta.
|
|
86
|
-
"@strapi/logger": "4.0.0-beta.
|
|
87
|
-
"@strapi/plugin-content-manager": "4.0.0-beta.
|
|
88
|
-
"@strapi/plugin-content-type-builder": "4.0.0-beta.
|
|
89
|
-
"@strapi/plugin-email": "4.0.0-beta.
|
|
90
|
-
"@strapi/plugin-upload": "4.0.0-beta.
|
|
91
|
-
"@strapi/utils": "4.0.0-beta.
|
|
82
|
+
"@strapi/admin": "4.0.0-beta.16",
|
|
83
|
+
"@strapi/database": "4.0.0-beta.16",
|
|
84
|
+
"@strapi/generate-new": "4.0.0-beta.16",
|
|
85
|
+
"@strapi/generators": "4.0.0-beta.16",
|
|
86
|
+
"@strapi/logger": "4.0.0-beta.16",
|
|
87
|
+
"@strapi/plugin-content-manager": "4.0.0-beta.16",
|
|
88
|
+
"@strapi/plugin-content-type-builder": "4.0.0-beta.16",
|
|
89
|
+
"@strapi/plugin-email": "4.0.0-beta.16",
|
|
90
|
+
"@strapi/plugin-upload": "4.0.0-beta.16",
|
|
91
|
+
"@strapi/utils": "4.0.0-beta.16",
|
|
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": "
|
|
137
|
+
"gitHead": "71bdfa34637832e8e78a6cf1b57c8c6dbadf133d"
|
|
138
138
|
}
|
package/lib/core-api/index.js
DELETED
|
@@ -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
|
-
};
|