@strapi/strapi 4.4.0-alpha.0 → 4.4.0-beta.1
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/README.md +17 -8
- package/lib/Strapi.js +10 -10
- package/lib/commands/configurationDump.js +2 -2
- package/lib/core/loaders/apis.js +1 -1
- package/lib/core/loaders/plugins/get-enabled-plugins.js +3 -1
- package/lib/core/loaders/plugins/index.js +5 -5
- package/lib/core/registries/content-types.js +2 -2
- package/lib/core/registries/controllers.js +2 -2
- package/lib/core/registries/hooks.js +1 -1
- package/lib/core/registries/middlewares.js +2 -2
- package/lib/core/registries/policies.js +1 -1
- package/lib/core/registries/services.js +2 -2
- package/lib/core-api/controller/transform.js +1 -1
- package/lib/services/auth/index.js +4 -1
- package/lib/services/content-api/index.js +74 -0
- package/lib/services/content-api/permissions/engine.js +5 -0
- package/lib/services/content-api/permissions/index.js +148 -0
- package/lib/services/content-api/permissions/providers/action.js +19 -0
- package/lib/services/content-api/permissions/providers/condition.js +19 -0
- package/lib/services/content-api/permissions/providers/index.js +9 -0
- package/lib/services/cron.js +1 -1
- package/lib/services/entity-service/components.js +28 -8
- package/lib/services/entity-service/index.js +9 -2
- package/lib/services/entity-validator/index.js +22 -17
- package/lib/services/metrics/middleware.js +1 -1
- package/lib/services/metrics/sender.js +2 -2
- package/lib/services/server/register-routes.js +2 -2
- package/lib/services/utils/upload-files.js +1 -1
- package/lib/services/worker-queue.js +2 -2
- package/lib/types/core/strapi/index.d.ts +5 -0
- package/lib/utils/addSlash.js +6 -5
- package/package.json +15 -14
- package/lib/core/registries/custom-fields.js +0 -54
- package/lib/services/custom-fields.js +0 -11
- package/lib/utils/convert-custom-field-type.js +0 -22
package/README.md
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
<p align="center">
|
|
2
|
-
<a href="https://strapi.io">
|
|
2
|
+
<a href="https://strapi.io/#gh-light-mode-only">
|
|
3
3
|
<img src="https://strapi.io/assets/strapi-logo-dark.svg" width="318px" alt="Strapi logo" />
|
|
4
4
|
</a>
|
|
5
|
+
<a href="https://strapi.io/#gh-dark-mode-only">
|
|
6
|
+
<img src="https://strapi.io/assets/strapi-logo-light.svg" width="318px" alt="Strapi logo" />
|
|
7
|
+
</a>
|
|
5
8
|
</p>
|
|
9
|
+
|
|
6
10
|
<h3 align="center">API creation made simple, secure and fast.</h3>
|
|
7
11
|
<p align="center">The most advanced open-source headless CMS to build powerful APIs with no effort.</p>
|
|
8
12
|
<p align="center"><a href="https://strapi.io/demo">Try live demo</a></p>
|
|
@@ -18,6 +22,9 @@
|
|
|
18
22
|
<a href="https://discord.strapi.io">
|
|
19
23
|
<img src="https://img.shields.io/discord/811989166782021633?label=Discord" alt="Strapi on Discord" />
|
|
20
24
|
</a>
|
|
25
|
+
<a href="https://github.com/strapi/strapi/actions/workflows/nightly.yml">
|
|
26
|
+
<img src="https://github.com/strapi/strapi/actions/workflows/nightly.yml/badge.svg" alt="Strapi Nightly Release Build Status" />
|
|
27
|
+
</a>
|
|
21
28
|
</p>
|
|
22
29
|
|
|
23
30
|
<br>
|
|
@@ -79,15 +86,17 @@ Complete installation requirements can be found in the documentation under <a hr
|
|
|
79
86
|
|
|
80
87
|
**Node:**
|
|
81
88
|
|
|
82
|
-
- NodeJS >=
|
|
89
|
+
- NodeJS >= 14 <= 18
|
|
83
90
|
- NPM >= 6.x
|
|
84
91
|
|
|
85
92
|
**Database:**
|
|
86
93
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
94
|
+
| Database | Minimum | Recommended |
|
|
95
|
+
| ---------- | ------- | ----------- |
|
|
96
|
+
| MySQL | 5.7.8 | 8.0 |
|
|
97
|
+
| MariaDB | 10.3 | 10.6 |
|
|
98
|
+
| PostgreSQL | 11.0 | 14.0 |
|
|
99
|
+
| SQLite | 3 | 3 |
|
|
91
100
|
|
|
92
101
|
**We recommend always using the latest version of Strapi to start your new projects**.
|
|
93
102
|
|
|
@@ -114,7 +123,7 @@ For general help using Strapi, please refer to [the official Strapi documentatio
|
|
|
114
123
|
- [Discord](https://discord.strapi.io) (For live discussion with the Community and Strapi team)
|
|
115
124
|
- [GitHub](https://github.com/strapi/strapi) (Bug reports, Contributions)
|
|
116
125
|
- [Community Forum](https://forum.strapi.io) (Questions and Discussions)
|
|
117
|
-
- [
|
|
126
|
+
- [Feedback section](https://feedback.strapi.io) (Roadmap, Feature requests)
|
|
118
127
|
- [Twitter](https://twitter.com/strapijs) (Get the news fast)
|
|
119
128
|
- [Facebook](https://www.facebook.com/Strapi-616063331867161)
|
|
120
129
|
- [YouTube Channel](https://www.youtube.com/strapi) (Learn from Video Tutorials)
|
|
@@ -125,7 +134,7 @@ Follow our [migration guides](https://docs.strapi.io/developer-docs/latest/updat
|
|
|
125
134
|
|
|
126
135
|
## Roadmap
|
|
127
136
|
|
|
128
|
-
Check out our [roadmap](https://feedback.strapi.io
|
|
137
|
+
Check out our [roadmap](https://feedback.strapi.io) to get informed of the latest features released and the upcoming ones. You may also give us insights and vote for a specific feature.
|
|
129
138
|
|
|
130
139
|
## Documentation
|
|
131
140
|
|
package/lib/Strapi.js
CHANGED
|
@@ -22,7 +22,7 @@ const createCronService = require('./services/cron');
|
|
|
22
22
|
const entityValidator = require('./services/entity-validator');
|
|
23
23
|
const createTelemetry = require('./services/metrics');
|
|
24
24
|
const createAuth = require('./services/auth');
|
|
25
|
-
const
|
|
25
|
+
const createContentAPI = require('./services/content-api');
|
|
26
26
|
const createUpdateNotifier = require('./utils/update-notifier');
|
|
27
27
|
const createStartupLogger = require('./utils/startup-logger');
|
|
28
28
|
const { LIFECYCLES } = require('./utils/lifecycles');
|
|
@@ -35,14 +35,12 @@ const hooksRegistry = require('./core/registries/hooks');
|
|
|
35
35
|
const controllersRegistry = require('./core/registries/controllers');
|
|
36
36
|
const modulesRegistry = require('./core/registries/modules');
|
|
37
37
|
const pluginsRegistry = require('./core/registries/plugins');
|
|
38
|
-
const customFieldsRegistry = require('./core/registries/custom-fields');
|
|
39
38
|
const createConfigProvider = require('./core/registries/config');
|
|
40
39
|
const apisRegistry = require('./core/registries/apis');
|
|
41
40
|
const bootstrap = require('./core/bootstrap');
|
|
42
41
|
const loaders = require('./core/loaders');
|
|
43
42
|
const { destroyOnSignal } = require('./utils/signals');
|
|
44
43
|
const sanitizersRegistry = require('./core/registries/sanitizers');
|
|
45
|
-
const convertCustomFieldType = require('./utils/convert-custom-field-type');
|
|
46
44
|
|
|
47
45
|
// TODO: move somewhere else
|
|
48
46
|
const draftAndPublishSync = require('./migrations/draft-publish');
|
|
@@ -77,7 +75,7 @@ class Strapi {
|
|
|
77
75
|
// Load the app configuration from the dist directory
|
|
78
76
|
const appConfig = loadConfiguration({ appDir: rootDirs.app, distDir: rootDirs.dist }, opts);
|
|
79
77
|
|
|
80
|
-
//
|
|
78
|
+
// Instantiate the Strapi container
|
|
81
79
|
this.container = createContainer(this);
|
|
82
80
|
|
|
83
81
|
// Register every Strapi registry in the container
|
|
@@ -90,9 +88,9 @@ class Strapi {
|
|
|
90
88
|
this.container.register('controllers', controllersRegistry(this));
|
|
91
89
|
this.container.register('modules', modulesRegistry(this));
|
|
92
90
|
this.container.register('plugins', pluginsRegistry(this));
|
|
93
|
-
this.container.register('custom-fields', customFieldsRegistry(this));
|
|
94
91
|
this.container.register('apis', apisRegistry(this));
|
|
95
92
|
this.container.register('auth', createAuth(this));
|
|
93
|
+
this.container.register('content-api', createContentAPI(this));
|
|
96
94
|
this.container.register('sanitizers', sanitizersRegistry(this));
|
|
97
95
|
|
|
98
96
|
// Create a mapping of every useful directory (for the app, dist and static directories)
|
|
@@ -102,7 +100,7 @@ class Strapi {
|
|
|
102
100
|
this.isLoaded = false;
|
|
103
101
|
this.reload = this.reload();
|
|
104
102
|
|
|
105
|
-
//
|
|
103
|
+
// Instantiate the Koa app & the HTTP server
|
|
106
104
|
this.server = createServer(this);
|
|
107
105
|
|
|
108
106
|
// Strapi utils instanciation
|
|
@@ -113,8 +111,6 @@ class Strapi {
|
|
|
113
111
|
this.cron = createCronService();
|
|
114
112
|
this.telemetry = createTelemetry(this);
|
|
115
113
|
|
|
116
|
-
this.customFields = createCustomFields(this);
|
|
117
|
-
|
|
118
114
|
createUpdateNotifier(this).notify();
|
|
119
115
|
}
|
|
120
116
|
|
|
@@ -194,6 +190,10 @@ class Strapi {
|
|
|
194
190
|
return this.container.get('auth');
|
|
195
191
|
}
|
|
196
192
|
|
|
193
|
+
get contentAPI() {
|
|
194
|
+
return this.container.get('content-api');
|
|
195
|
+
}
|
|
196
|
+
|
|
197
197
|
get sanitizers() {
|
|
198
198
|
return this.container.get('sanitizers');
|
|
199
199
|
}
|
|
@@ -451,6 +451,8 @@ class Strapi {
|
|
|
451
451
|
await this.server.initMiddlewares();
|
|
452
452
|
await this.server.initRouting();
|
|
453
453
|
|
|
454
|
+
await this.contentAPI.permissions.registerActions();
|
|
455
|
+
|
|
454
456
|
await this.runLifecyclesFunctions(LIFECYCLES.BOOTSTRAP);
|
|
455
457
|
|
|
456
458
|
this.cron.start();
|
|
@@ -460,8 +462,6 @@ class Strapi {
|
|
|
460
462
|
|
|
461
463
|
async load() {
|
|
462
464
|
await this.register();
|
|
463
|
-
// Swap type customField for underlying data type
|
|
464
|
-
convertCustomFieldType(this);
|
|
465
465
|
await this.bootstrap();
|
|
466
466
|
|
|
467
467
|
this.isLoaded = true;
|
|
@@ -9,7 +9,7 @@ const CHUNK_SIZE = 100;
|
|
|
9
9
|
* Will dump configurations to a file or stdout
|
|
10
10
|
* @param {string} file filepath to use as output
|
|
11
11
|
*/
|
|
12
|
-
module.exports = async
|
|
12
|
+
module.exports = async ({ file: filePath, pretty }) => {
|
|
13
13
|
const output = filePath ? fs.createWriteStream(filePath) : process.stdout;
|
|
14
14
|
|
|
15
15
|
const appContext = await strapi.compile();
|
|
@@ -21,7 +21,7 @@ module.exports = async function ({ file: filePath, pretty }) {
|
|
|
21
21
|
|
|
22
22
|
const pageCount = Math.ceil(count / CHUNK_SIZE);
|
|
23
23
|
|
|
24
|
-
for (let page = 0; page < pageCount; page
|
|
24
|
+
for (let page = 0; page < pageCount; page += 1) {
|
|
25
25
|
const results = await app
|
|
26
26
|
.query('strapi::core-store')
|
|
27
27
|
.findMany({ limit: CHUNK_SIZE, offset: page * CHUNK_SIZE, orderBy: 'key' });
|
package/lib/core/loaders/apis.js
CHANGED
|
@@ -59,7 +59,9 @@ const getEnabledPlugins = async (strapi) => {
|
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
const installedPlugins = {};
|
|
62
|
-
|
|
62
|
+
const dependencies = strapi.config.get('info.dependencies', {});
|
|
63
|
+
|
|
64
|
+
for (const dep of Object.keys(dependencies)) {
|
|
63
65
|
const packagePath = join(dep, 'package.json');
|
|
64
66
|
let packageInfo;
|
|
65
67
|
try {
|
|
@@ -34,10 +34,10 @@ const applyUserExtension = async (plugins) => {
|
|
|
34
34
|
const extendedSchemas = await loadFiles(extensionsDir, '**/content-types/**/schema.json');
|
|
35
35
|
const strapiServers = await loadFiles(extensionsDir, '**/strapi-server.js');
|
|
36
36
|
|
|
37
|
-
for (const pluginName
|
|
37
|
+
for (const pluginName of Object.keys(plugins)) {
|
|
38
38
|
const plugin = plugins[pluginName];
|
|
39
39
|
// first: load json schema
|
|
40
|
-
for (const ctName
|
|
40
|
+
for (const ctName of Object.keys(plugin.contentTypes)) {
|
|
41
41
|
const extendedSchema = get([pluginName, 'content-types', ctName, 'schema'], extendedSchemas);
|
|
42
42
|
if (extendedSchema) {
|
|
43
43
|
plugin.contentTypes[ctName].schema = {
|
|
@@ -57,7 +57,7 @@ const applyUserExtension = async (plugins) => {
|
|
|
57
57
|
const applyUserConfig = async (plugins) => {
|
|
58
58
|
const userPluginsConfig = await getUserPluginsConfig();
|
|
59
59
|
|
|
60
|
-
for (const pluginName
|
|
60
|
+
for (const pluginName of Object.keys(plugins)) {
|
|
61
61
|
const plugin = plugins[pluginName];
|
|
62
62
|
const userPluginConfig = getOr({}, `${pluginName}.config`, userPluginsConfig);
|
|
63
63
|
const defaultConfig =
|
|
@@ -82,7 +82,7 @@ const loadPlugins = async (strapi) => {
|
|
|
82
82
|
|
|
83
83
|
strapi.config.set('enabledPlugins', enabledPlugins);
|
|
84
84
|
|
|
85
|
-
for (const pluginName
|
|
85
|
+
for (const pluginName of Object.keys(enabledPlugins)) {
|
|
86
86
|
const enabledPlugin = enabledPlugins[pluginName];
|
|
87
87
|
|
|
88
88
|
const serverEntrypointPath = join(enabledPlugin.pathToPlugin, 'strapi-server.js');
|
|
@@ -100,7 +100,7 @@ const loadPlugins = async (strapi) => {
|
|
|
100
100
|
await applyUserConfig(plugins);
|
|
101
101
|
await applyUserExtension(plugins);
|
|
102
102
|
|
|
103
|
-
for (const pluginName
|
|
103
|
+
for (const pluginName of Object.keys(plugins)) {
|
|
104
104
|
strapi.container.get('plugins').add(pluginName, plugins[pluginName]);
|
|
105
105
|
}
|
|
106
106
|
};
|
|
@@ -5,7 +5,7 @@ const { createContentType } = require('../domain/content-type');
|
|
|
5
5
|
const { addNamespace, hasNamespace } = require('../utils');
|
|
6
6
|
|
|
7
7
|
const validateKeySameToSingularName = (contentTypes) => {
|
|
8
|
-
for (const ctName
|
|
8
|
+
for (const ctName of Object.keys(contentTypes)) {
|
|
9
9
|
const contentType = contentTypes[ctName];
|
|
10
10
|
|
|
11
11
|
if (ctName !== contentType.schema.info.singularName) {
|
|
@@ -63,7 +63,7 @@ const contentTypesRegistry = () => {
|
|
|
63
63
|
add(namespace, newContentTypes) {
|
|
64
64
|
validateKeySameToSingularName(newContentTypes);
|
|
65
65
|
|
|
66
|
-
for (const rawCtName
|
|
66
|
+
for (const rawCtName of Object.keys(newContentTypes)) {
|
|
67
67
|
const uid = addNamespace(rawCtName, namespace);
|
|
68
68
|
|
|
69
69
|
if (has(uid, contentTypes)) {
|
|
@@ -48,7 +48,7 @@ const controllersRegistry = () => {
|
|
|
48
48
|
const filteredControllers = pickBy((_, uid) => hasNamespace(uid, namespace))(controllers);
|
|
49
49
|
|
|
50
50
|
const map = {};
|
|
51
|
-
for (const uid
|
|
51
|
+
for (const uid of Object.keys(filteredControllers)) {
|
|
52
52
|
Object.defineProperty(map, uid, {
|
|
53
53
|
enumerable: true,
|
|
54
54
|
get: () => {
|
|
@@ -78,7 +78,7 @@ const controllersRegistry = () => {
|
|
|
78
78
|
* @returns
|
|
79
79
|
*/
|
|
80
80
|
add(namespace, newControllers) {
|
|
81
|
-
for (const controllerName
|
|
81
|
+
for (const controllerName of Object.keys(newControllers)) {
|
|
82
82
|
const controller = newControllers[controllerName];
|
|
83
83
|
const uid = addNamespace(controllerName, namespace);
|
|
84
84
|
|
|
@@ -54,8 +54,8 @@ const middlewaresRegistry = () => {
|
|
|
54
54
|
* @param {{ [key: string]: Middleware }} newMiddlewares
|
|
55
55
|
* @returns
|
|
56
56
|
*/
|
|
57
|
-
add(namespace, rawMiddlewares) {
|
|
58
|
-
for (const middlewareName
|
|
57
|
+
add(namespace, rawMiddlewares = {}) {
|
|
58
|
+
for (const middlewareName of Object.keys(rawMiddlewares)) {
|
|
59
59
|
const middleware = rawMiddlewares[middlewareName];
|
|
60
60
|
const uid = addNamespace(middlewareName, namespace);
|
|
61
61
|
|
|
@@ -55,7 +55,7 @@ const policiesRegistry = () => {
|
|
|
55
55
|
* @returns
|
|
56
56
|
*/
|
|
57
57
|
add(namespace, newPolicies) {
|
|
58
|
-
for (const policyName
|
|
58
|
+
for (const policyName of Object.keys(newPolicies)) {
|
|
59
59
|
const policy = newPolicies[policyName];
|
|
60
60
|
const uid = addNamespace(policyName, namespace);
|
|
61
61
|
|
|
@@ -48,7 +48,7 @@ const servicesRegistry = (strapi) => {
|
|
|
48
48
|
|
|
49
49
|
// create lazy accessor to avoid instantiating the services;
|
|
50
50
|
const map = {};
|
|
51
|
-
for (const uid
|
|
51
|
+
for (const uid of Object.keys(filteredServices)) {
|
|
52
52
|
Object.defineProperty(map, uid, {
|
|
53
53
|
enumerable: true,
|
|
54
54
|
get: () => {
|
|
@@ -78,7 +78,7 @@ const servicesRegistry = (strapi) => {
|
|
|
78
78
|
* @returns
|
|
79
79
|
*/
|
|
80
80
|
add(namespace, newServices) {
|
|
81
|
-
for (const serviceName
|
|
81
|
+
for (const serviceName of Object.keys(newServices)) {
|
|
82
82
|
const service = newServices[serviceName];
|
|
83
83
|
const uid = addNamespace(serviceName, namespace);
|
|
84
84
|
|
|
@@ -32,6 +32,7 @@ const createAuthentication = () => {
|
|
|
32
32
|
|
|
33
33
|
return this;
|
|
34
34
|
},
|
|
35
|
+
|
|
35
36
|
async authenticate(ctx, next) {
|
|
36
37
|
const { route } = ctx.state;
|
|
37
38
|
|
|
@@ -47,7 +48,7 @@ const createAuthentication = () => {
|
|
|
47
48
|
for (const strategy of strategiesToUse) {
|
|
48
49
|
const result = await strategy.authenticate(ctx);
|
|
49
50
|
|
|
50
|
-
const { authenticated = false,
|
|
51
|
+
const { authenticated = false, credentials, ability = null, error = null } = result || {};
|
|
51
52
|
|
|
52
53
|
if (error !== null) {
|
|
53
54
|
return ctx.unauthorized(error);
|
|
@@ -58,6 +59,7 @@ const createAuthentication = () => {
|
|
|
58
59
|
ctx.state.auth = {
|
|
59
60
|
strategy,
|
|
60
61
|
credentials,
|
|
62
|
+
ability,
|
|
61
63
|
};
|
|
62
64
|
|
|
63
65
|
return next();
|
|
@@ -66,6 +68,7 @@ const createAuthentication = () => {
|
|
|
66
68
|
|
|
67
69
|
return ctx.unauthorized('Missing or invalid credentials');
|
|
68
70
|
},
|
|
71
|
+
|
|
69
72
|
async verify(auth, config = {}) {
|
|
70
73
|
if (config === false) {
|
|
71
74
|
return;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const _ = require('lodash');
|
|
4
|
+
const instantiatePermissionsUtilities = require('./permissions');
|
|
5
|
+
|
|
6
|
+
const transformRoutePrefixFor = (pluginName) => (route) => {
|
|
7
|
+
const prefix = route.config && route.config.prefix;
|
|
8
|
+
const path = prefix !== undefined ? `${prefix}${route.path}` : `/${pluginName}${route.path}`;
|
|
9
|
+
|
|
10
|
+
return {
|
|
11
|
+
...route,
|
|
12
|
+
path,
|
|
13
|
+
};
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Create a content API container that holds logic, tools and utils. (eg: permissions, ...)
|
|
18
|
+
*/
|
|
19
|
+
const createContentAPI = (strapi) => {
|
|
20
|
+
const getRoutesMap = async () => {
|
|
21
|
+
const routesMap = {};
|
|
22
|
+
|
|
23
|
+
_.forEach(strapi.api, (api, apiName) => {
|
|
24
|
+
const routes = _.flatMap(api.routes, (route) => {
|
|
25
|
+
if (_.has(route, 'routes')) {
|
|
26
|
+
return route.routes;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return route;
|
|
30
|
+
}).filter((route) => route.info.type === 'content-api');
|
|
31
|
+
|
|
32
|
+
if (routes.length === 0) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const apiPrefix = strapi.config.get('api.rest.prefix');
|
|
37
|
+
routesMap[`api::${apiName}`] = routes.map((route) => ({
|
|
38
|
+
...route,
|
|
39
|
+
path: `${apiPrefix}${route.path}`,
|
|
40
|
+
}));
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
_.forEach(strapi.plugins, (plugin, pluginName) => {
|
|
44
|
+
const transformPrefix = transformRoutePrefixFor(pluginName);
|
|
45
|
+
|
|
46
|
+
const routes = _.flatMap(plugin.routes, (route) => {
|
|
47
|
+
if (_.has(route, 'routes')) {
|
|
48
|
+
return route.routes.map(transformPrefix);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return transformPrefix(route);
|
|
52
|
+
}).filter((route) => route.info.type === 'content-api');
|
|
53
|
+
|
|
54
|
+
if (routes.length === 0) {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const apiPrefix = strapi.config.get('api.rest.prefix');
|
|
59
|
+
routesMap[`plugin::${pluginName}`] = routes.map((route) => ({
|
|
60
|
+
...route,
|
|
61
|
+
path: `${apiPrefix}${route.path}`,
|
|
62
|
+
}));
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
return routesMap;
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
permissions: instantiatePermissionsUtilities(strapi),
|
|
70
|
+
getRoutesMap,
|
|
71
|
+
};
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
module.exports = createContentAPI;
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const _ = require('lodash');
|
|
4
|
+
const { createActionProvider, createConditionProvider } = require('./providers');
|
|
5
|
+
const createPermissionEngine = require('./engine');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Creates an handler that checks if the permission's action exists in the action registry
|
|
9
|
+
*/
|
|
10
|
+
const createValidatePermissionHandler =
|
|
11
|
+
(actionProvider) =>
|
|
12
|
+
({ permission }) => {
|
|
13
|
+
const action = actionProvider.get(permission.action);
|
|
14
|
+
|
|
15
|
+
// If the action isn't registered into the action provider, then ignore the permission and warn the user
|
|
16
|
+
if (!action) {
|
|
17
|
+
strapi.log.debug(
|
|
18
|
+
`Unknown action "${permission.action}" supplied when registering a new permission`
|
|
19
|
+
);
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Create instances of providers and permission engine for the core content-API service.
|
|
26
|
+
* Also, expose utilities to get informations about available actions and such.
|
|
27
|
+
*
|
|
28
|
+
* @param {Strapi.Strapi} strapi
|
|
29
|
+
*/
|
|
30
|
+
module.exports = (strapi) => {
|
|
31
|
+
// NOTE: Here we define both an action and condition provider,
|
|
32
|
+
// but at the moment, we're only using the action one.
|
|
33
|
+
const providers = {
|
|
34
|
+
action: createActionProvider(),
|
|
35
|
+
condition: createConditionProvider(),
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Get a tree representation of the available Content API actions
|
|
40
|
+
* based on the methods of the Content API controllers.
|
|
41
|
+
*
|
|
42
|
+
* @note Only actions bound to a content-API route are returned.
|
|
43
|
+
*
|
|
44
|
+
* @return {{ [api: string]: { [controller: string]: string[] }}}
|
|
45
|
+
*/
|
|
46
|
+
const getActionsMap = () => {
|
|
47
|
+
const actionMap = {};
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Check if a controller's action is bound to the
|
|
51
|
+
* content-api by looking at a potential __type__ symbol
|
|
52
|
+
*
|
|
53
|
+
* @param {object} action
|
|
54
|
+
*
|
|
55
|
+
* @return {boolean}
|
|
56
|
+
*/
|
|
57
|
+
const isContentApi = (action) => {
|
|
58
|
+
if (!_.has(action, Symbol.for('__type__'))) {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return action[Symbol.for('__type__')].includes('content-api');
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Register actions from a specific API source into the result tree
|
|
67
|
+
*
|
|
68
|
+
* @param {{ [apiName]: { controllers: { [controller]: object } }}} apis The API container
|
|
69
|
+
* @param {string} source The prefix to use in front the API name
|
|
70
|
+
*
|
|
71
|
+
* @return {void}
|
|
72
|
+
*/
|
|
73
|
+
const registerAPIsActions = (apis, source) => {
|
|
74
|
+
_.forEach(apis, (api, apiName) => {
|
|
75
|
+
const controllers = _.reduce(
|
|
76
|
+
api.controllers,
|
|
77
|
+
(acc, controller, controllerName) => {
|
|
78
|
+
const contentApiActions = _.pickBy(controller, isContentApi);
|
|
79
|
+
|
|
80
|
+
if (_.isEmpty(contentApiActions)) {
|
|
81
|
+
return acc;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
acc[controllerName] = Object.keys(contentApiActions);
|
|
85
|
+
|
|
86
|
+
return acc;
|
|
87
|
+
},
|
|
88
|
+
{}
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
if (!_.isEmpty(controllers)) {
|
|
92
|
+
actionMap[`${source}::${apiName}`] = { controllers };
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
registerAPIsActions(strapi.api, 'api');
|
|
98
|
+
registerAPIsActions(strapi.plugins, 'plugin');
|
|
99
|
+
|
|
100
|
+
return actionMap;
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Register all the content-API's controllers actions into the action provider.
|
|
105
|
+
* This method make use of the {@link getActionsMap} to generate the list of actions to register.
|
|
106
|
+
*
|
|
107
|
+
* @return {void}
|
|
108
|
+
*/
|
|
109
|
+
const registerActions = async () => {
|
|
110
|
+
const actionsMap = getActionsMap();
|
|
111
|
+
|
|
112
|
+
// For each API
|
|
113
|
+
for (const [api, value] of Object.entries(actionsMap)) {
|
|
114
|
+
const { controllers } = value;
|
|
115
|
+
|
|
116
|
+
// Register controllers methods as actions
|
|
117
|
+
for (const [controller, actions] of Object.entries(controllers)) {
|
|
118
|
+
// Register each action individually
|
|
119
|
+
await Promise.all(
|
|
120
|
+
actions.map((action) => {
|
|
121
|
+
const actionUID = `${api}.${controller}.${action}`;
|
|
122
|
+
|
|
123
|
+
return providers.action.register(actionUID, {
|
|
124
|
+
api,
|
|
125
|
+
controller,
|
|
126
|
+
action,
|
|
127
|
+
uid: actionUID,
|
|
128
|
+
});
|
|
129
|
+
})
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
// Create an instance of a content-API permission engine
|
|
136
|
+
// and binds a custom validation handler to it
|
|
137
|
+
const engine = createPermissionEngine({ providers }).on(
|
|
138
|
+
'before-format::validate.permission',
|
|
139
|
+
createValidatePermissionHandler(providers.action)
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
return {
|
|
143
|
+
engine,
|
|
144
|
+
providers,
|
|
145
|
+
registerActions,
|
|
146
|
+
getActionsMap,
|
|
147
|
+
};
|
|
148
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { providerFactory } = require('@strapi/utils');
|
|
4
|
+
|
|
5
|
+
module.exports = (options = {}) => {
|
|
6
|
+
const provider = providerFactory(options);
|
|
7
|
+
|
|
8
|
+
return {
|
|
9
|
+
...provider,
|
|
10
|
+
|
|
11
|
+
async register(action, payload) {
|
|
12
|
+
if (strapi.isLoaded) {
|
|
13
|
+
throw new Error(`You can't register new actions outside the bootstrap function.`);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return provider.register(action, payload);
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { providerFactory } = require('@strapi/utils');
|
|
4
|
+
|
|
5
|
+
module.exports = (options = {}) => {
|
|
6
|
+
const provider = providerFactory(options);
|
|
7
|
+
|
|
8
|
+
return {
|
|
9
|
+
...provider,
|
|
10
|
+
|
|
11
|
+
async register(condition) {
|
|
12
|
+
if (strapi.isLoaded) {
|
|
13
|
+
throw new Error(`You can't register new conditions outside the bootstrap function.`);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return provider.register(condition.name, condition);
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
};
|
package/lib/services/cron.js
CHANGED
|
@@ -5,6 +5,7 @@ const { has, prop, omit, toString } = require('lodash/fp');
|
|
|
5
5
|
|
|
6
6
|
const { contentTypes: contentTypesUtils } = require('@strapi/utils');
|
|
7
7
|
const { ApplicationError } = require('@strapi/utils').errors;
|
|
8
|
+
const { getComponentAttributes } = require('@strapi/utils').contentTypes;
|
|
8
9
|
|
|
9
10
|
const omitComponentData = (contentType, data) => {
|
|
10
11
|
const { attributes } = contentType;
|
|
@@ -17,11 +18,11 @@ const omitComponentData = (contentType, data) => {
|
|
|
17
18
|
|
|
18
19
|
// NOTE: we could generalize the logic to allow CRUD of relation directly in the DB layer
|
|
19
20
|
const createComponents = async (uid, data) => {
|
|
20
|
-
const { attributes } = strapi.getModel(uid);
|
|
21
|
+
const { attributes = {} } = strapi.getModel(uid);
|
|
21
22
|
|
|
22
23
|
const componentBody = {};
|
|
23
24
|
|
|
24
|
-
for (const attributeName
|
|
25
|
+
for (const attributeName of Object.keys(attributes)) {
|
|
25
26
|
const attribute = attributes[attributeName];
|
|
26
27
|
|
|
27
28
|
if (!has(attributeName, data) || !contentTypesUtils.isComponentAttribute(attribute)) {
|
|
@@ -100,16 +101,28 @@ const createComponents = async (uid, data) => {
|
|
|
100
101
|
return componentBody;
|
|
101
102
|
};
|
|
102
103
|
|
|
104
|
+
/**
|
|
105
|
+
* @param {str} uid
|
|
106
|
+
* @param {object} entity
|
|
107
|
+
* @return {Promise<{uid: string, entity: object}>}
|
|
108
|
+
*/
|
|
109
|
+
const getComponents = async (uid, entity) => {
|
|
110
|
+
const componentAttributes = getComponentAttributes(strapi.getModel(uid));
|
|
111
|
+
|
|
112
|
+
if (_.isEmpty(componentAttributes)) return {};
|
|
113
|
+
return strapi.query(uid).load(entity, componentAttributes);
|
|
114
|
+
};
|
|
115
|
+
|
|
103
116
|
/*
|
|
104
117
|
delete old components
|
|
105
118
|
create or update
|
|
106
119
|
*/
|
|
107
120
|
const updateComponents = async (uid, entityToUpdate, data) => {
|
|
108
|
-
const { attributes } = strapi.getModel(uid);
|
|
121
|
+
const { attributes = {} } = strapi.getModel(uid);
|
|
109
122
|
|
|
110
123
|
const componentBody = {};
|
|
111
124
|
|
|
112
|
-
for (const attributeName
|
|
125
|
+
for (const attributeName of Object.keys(attributes)) {
|
|
113
126
|
const attribute = attributes[attributeName];
|
|
114
127
|
|
|
115
128
|
if (!has(attributeName, data)) {
|
|
@@ -262,15 +275,18 @@ const deleteOldDZComponents = async (uid, entityToUpdate, attributeName, dynamic
|
|
|
262
275
|
};
|
|
263
276
|
|
|
264
277
|
const deleteComponents = async (uid, entityToDelete) => {
|
|
265
|
-
const { attributes } = strapi.getModel(uid);
|
|
278
|
+
const { attributes = {} } = strapi.getModel(uid);
|
|
266
279
|
|
|
267
|
-
for (const attributeName
|
|
280
|
+
for (const attributeName of Object.keys(attributes)) {
|
|
268
281
|
const attribute = attributes[attributeName];
|
|
269
282
|
|
|
270
283
|
if (attribute.type === 'component') {
|
|
271
284
|
const { component: componentUID } = attribute;
|
|
272
285
|
|
|
273
|
-
|
|
286
|
+
// Load attribute value if it's not already loaded
|
|
287
|
+
const value =
|
|
288
|
+
entityToDelete[attributeName] ||
|
|
289
|
+
(await strapi.query(uid).load(entityToDelete, attributeName));
|
|
274
290
|
|
|
275
291
|
if (!value) {
|
|
276
292
|
continue;
|
|
@@ -286,7 +302,9 @@ const deleteComponents = async (uid, entityToDelete) => {
|
|
|
286
302
|
}
|
|
287
303
|
|
|
288
304
|
if (attribute.type === 'dynamiczone') {
|
|
289
|
-
const value =
|
|
305
|
+
const value =
|
|
306
|
+
entityToDelete[attributeName] ||
|
|
307
|
+
(await strapi.query(uid).load(entityToDelete, attributeName));
|
|
290
308
|
|
|
291
309
|
if (!value) {
|
|
292
310
|
continue;
|
|
@@ -352,7 +370,9 @@ const deleteComponent = async (uid, componentToDelete) => {
|
|
|
352
370
|
|
|
353
371
|
module.exports = {
|
|
354
372
|
omitComponentData,
|
|
373
|
+
getComponents,
|
|
355
374
|
createComponents,
|
|
356
375
|
updateComponents,
|
|
357
376
|
deleteComponents,
|
|
377
|
+
deleteComponent,
|
|
358
378
|
};
|
|
@@ -14,6 +14,7 @@ const uploadFiles = require('../utils/upload-files');
|
|
|
14
14
|
|
|
15
15
|
const {
|
|
16
16
|
omitComponentData,
|
|
17
|
+
getComponents,
|
|
17
18
|
createComponents,
|
|
18
19
|
updateComponents,
|
|
19
20
|
deleteComponents,
|
|
@@ -213,8 +214,10 @@ const createDefaultImplementation = ({ strapi, db, eventHub, entityValidator })
|
|
|
213
214
|
return null;
|
|
214
215
|
}
|
|
215
216
|
|
|
217
|
+
const componentsToDelete = await getComponents(uid, entityToDelete);
|
|
218
|
+
|
|
216
219
|
await db.query(uid).delete({ where: { id: entityToDelete.id } });
|
|
217
|
-
await deleteComponents(uid, entityToDelete);
|
|
220
|
+
await deleteComponents(uid, { ...entityToDelete, ...componentsToDelete });
|
|
218
221
|
|
|
219
222
|
await this.emitEvent(uid, ENTRY_DELETE, entityToDelete);
|
|
220
223
|
|
|
@@ -234,8 +237,12 @@ const createDefaultImplementation = ({ strapi, db, eventHub, entityValidator })
|
|
|
234
237
|
return null;
|
|
235
238
|
}
|
|
236
239
|
|
|
240
|
+
const componentsToDelete = await Promise.all(
|
|
241
|
+
entitiesToDelete.map((entityToDelete) => getComponents(uid, entityToDelete))
|
|
242
|
+
);
|
|
243
|
+
|
|
237
244
|
const deletedEntities = await db.query(uid).deleteMany(query);
|
|
238
|
-
await Promise.all(
|
|
245
|
+
await Promise.all(componentsToDelete.map((compos) => deleteComponents(uid, compos)));
|
|
239
246
|
|
|
240
247
|
// Trigger webhooks. One for each entity
|
|
241
248
|
await Promise.all(entitiesToDelete.map((entity) => this.emitEvent(uid, ENTRY_DELETE, entity)));
|
|
@@ -14,51 +14,56 @@ const { isMediaAttribute, isScalarAttribute, getWritableAttributes } = strapiUti
|
|
|
14
14
|
const { ValidationError } = strapiUtils.errors;
|
|
15
15
|
|
|
16
16
|
const addMinMax = (validator, { attr, updatedAttribute }) => {
|
|
17
|
+
let nextValidator = validator;
|
|
18
|
+
|
|
17
19
|
if (
|
|
18
20
|
Number.isInteger(attr.min) &&
|
|
19
21
|
(attr.required || (Array.isArray(updatedAttribute.value) && updatedAttribute.value.length > 0))
|
|
20
22
|
) {
|
|
21
|
-
|
|
23
|
+
nextValidator = nextValidator.min(attr.min);
|
|
22
24
|
}
|
|
23
25
|
if (Number.isInteger(attr.max)) {
|
|
24
|
-
|
|
26
|
+
nextValidator = nextValidator.max(attr.max);
|
|
25
27
|
}
|
|
26
|
-
return
|
|
28
|
+
return nextValidator;
|
|
27
29
|
};
|
|
28
30
|
|
|
29
|
-
const addRequiredValidation =
|
|
30
|
-
(
|
|
31
|
-
|
|
31
|
+
const addRequiredValidation = (createOrUpdate) => {
|
|
32
|
+
return (validator, { attr: { required } }) => {
|
|
33
|
+
let nextValidator = validator;
|
|
32
34
|
if (required) {
|
|
33
35
|
if (createOrUpdate === 'creation') {
|
|
34
|
-
|
|
36
|
+
nextValidator = nextValidator.notNil();
|
|
35
37
|
} else if (createOrUpdate === 'update') {
|
|
36
|
-
|
|
38
|
+
nextValidator = nextValidator.notNull();
|
|
37
39
|
}
|
|
38
40
|
} else {
|
|
39
|
-
|
|
41
|
+
nextValidator = nextValidator.nullable();
|
|
40
42
|
}
|
|
41
|
-
return
|
|
43
|
+
return nextValidator;
|
|
42
44
|
};
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const addDefault = (createOrUpdate) => {
|
|
48
|
+
return (validator, { attr }) => {
|
|
49
|
+
let nextValidator = validator;
|
|
43
50
|
|
|
44
|
-
const addDefault =
|
|
45
|
-
(createOrUpdate) =>
|
|
46
|
-
(validator, { attr }) => {
|
|
47
51
|
if (createOrUpdate === 'creation') {
|
|
48
52
|
if (
|
|
49
53
|
((attr.type === 'component' && attr.repeatable) || attr.type === 'dynamiczone') &&
|
|
50
54
|
!attr.required
|
|
51
55
|
) {
|
|
52
|
-
|
|
56
|
+
nextValidator = nextValidator.default([]);
|
|
53
57
|
} else {
|
|
54
|
-
|
|
58
|
+
nextValidator = nextValidator.default(attr.default);
|
|
55
59
|
}
|
|
56
60
|
} else {
|
|
57
|
-
|
|
61
|
+
nextValidator = nextValidator.default(undefined);
|
|
58
62
|
}
|
|
59
63
|
|
|
60
|
-
return
|
|
64
|
+
return nextValidator;
|
|
61
65
|
};
|
|
66
|
+
};
|
|
62
67
|
|
|
63
68
|
const preventCast = (validator) => validator.transform((val, originalVal) => originalVal);
|
|
64
69
|
|
|
@@ -45,12 +45,12 @@ module.exports = (strapi) => {
|
|
|
45
45
|
environment: strapi.config.environment,
|
|
46
46
|
os: os.type(),
|
|
47
47
|
osPlatform: os.platform(),
|
|
48
|
+
osArch: os.arch(),
|
|
48
49
|
osRelease: os.release(),
|
|
49
|
-
nodeVersion: process.
|
|
50
|
+
nodeVersion: process.versions.node,
|
|
50
51
|
docker: process.env.DOCKER || isDocker(),
|
|
51
52
|
isCI: ciEnv.isCI,
|
|
52
53
|
version: strapi.config.get('info.strapi'),
|
|
53
|
-
strapiVersion: strapi.config.get('info.strapi'),
|
|
54
54
|
projectType: isEE ? 'Enterprise' : 'Community',
|
|
55
55
|
useTypescriptOnServer: isUsingTypeScriptSync(serverRootPath),
|
|
56
56
|
useTypescriptOnAdmin: isUsingTypeScriptSync(adminRootPath),
|
|
@@ -50,7 +50,7 @@ const registerAdminRoutes = (strapi) => {
|
|
|
50
50
|
* @param {import('../../').Strapi} strapi
|
|
51
51
|
*/
|
|
52
52
|
const registerPluginRoutes = (strapi) => {
|
|
53
|
-
for (const pluginName
|
|
53
|
+
for (const pluginName of Object.keys(strapi.plugins)) {
|
|
54
54
|
const plugin = strapi.plugins[pluginName];
|
|
55
55
|
|
|
56
56
|
const generateRouteScope = createRouteScopeGenerator(`plugin::${pluginName}`);
|
|
@@ -86,7 +86,7 @@ const registerPluginRoutes = (strapi) => {
|
|
|
86
86
|
* @param {import('../../').Strapi} strapi
|
|
87
87
|
*/
|
|
88
88
|
const registerAPIRoutes = (strapi) => {
|
|
89
|
-
for (const apiName
|
|
89
|
+
for (const apiName of Object.keys(strapi.api)) {
|
|
90
90
|
const api = strapi.api[apiName];
|
|
91
91
|
|
|
92
92
|
const generateRouteScope = createRouteScopeGenerator(`api::${apiName}`);
|
|
@@ -27,7 +27,7 @@ module.exports = async (uid, entity, files) => {
|
|
|
27
27
|
let tmpModel = modelDef;
|
|
28
28
|
let modelUID = uid;
|
|
29
29
|
|
|
30
|
-
for (let i = 0; i < path.length; i
|
|
30
|
+
for (let i = 0; i < path.length; i += 1) {
|
|
31
31
|
if (!tmpModel) return {};
|
|
32
32
|
const part = path[i];
|
|
33
33
|
const attr = tmpModel.attributes[part];
|
|
@@ -26,7 +26,7 @@ module.exports = class WorkerQueue {
|
|
|
26
26
|
enqueue(payload) {
|
|
27
27
|
debug('Enqueue event in worker queue');
|
|
28
28
|
if (this.running < this.concurrency) {
|
|
29
|
-
this.running
|
|
29
|
+
this.running += 1;
|
|
30
30
|
this.execute(payload);
|
|
31
31
|
} else {
|
|
32
32
|
this.queue.unshift(payload);
|
|
@@ -40,7 +40,7 @@ module.exports = class WorkerQueue {
|
|
|
40
40
|
if (payload) {
|
|
41
41
|
this.execute(payload);
|
|
42
42
|
} else {
|
|
43
|
-
this.running
|
|
43
|
+
this.running -= 1;
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
46
|
|
package/lib/utils/addSlash.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
module.exports = (path) => {
|
|
4
|
-
|
|
5
|
-
if (
|
|
4
|
+
let tmpPath = path;
|
|
5
|
+
if (typeof tmpPath !== 'string') throw new Error('admin.url must be a string');
|
|
6
|
+
if (tmpPath === '' || tmpPath === '/') return '/';
|
|
6
7
|
|
|
7
|
-
if (
|
|
8
|
-
if (
|
|
9
|
-
return
|
|
8
|
+
if (tmpPath[0] !== '/') tmpPath = `/${tmpPath}`;
|
|
9
|
+
if (tmpPath[tmpPath.length - 1] !== '/') tmpPath += '/';
|
|
10
|
+
return tmpPath;
|
|
10
11
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@strapi/strapi",
|
|
3
|
-
"version": "4.4.0-
|
|
3
|
+
"version": "4.4.0-beta.1",
|
|
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",
|
|
@@ -80,17 +80,18 @@
|
|
|
80
80
|
"dependencies": {
|
|
81
81
|
"@koa/cors": "3.4.1",
|
|
82
82
|
"@koa/router": "10.1.1",
|
|
83
|
-
"@strapi/admin": "4.4.0-
|
|
84
|
-
"@strapi/database": "4.4.0-
|
|
85
|
-
"@strapi/generate-new": "4.4.0-
|
|
86
|
-
"@strapi/generators": "4.4.0-
|
|
87
|
-
"@strapi/logger": "4.4.0-
|
|
88
|
-
"@strapi/
|
|
89
|
-
"@strapi/plugin-content-
|
|
90
|
-
"@strapi/plugin-
|
|
91
|
-
"@strapi/plugin-
|
|
92
|
-
"@strapi/
|
|
93
|
-
"@strapi/utils": "4.4.0-
|
|
83
|
+
"@strapi/admin": "4.4.0-beta.1",
|
|
84
|
+
"@strapi/database": "4.4.0-beta.1",
|
|
85
|
+
"@strapi/generate-new": "4.4.0-beta.1",
|
|
86
|
+
"@strapi/generators": "4.4.0-beta.1",
|
|
87
|
+
"@strapi/logger": "4.4.0-beta.1",
|
|
88
|
+
"@strapi/permissions": "4.4.0-beta.1",
|
|
89
|
+
"@strapi/plugin-content-manager": "4.4.0-beta.1",
|
|
90
|
+
"@strapi/plugin-content-type-builder": "4.4.0-beta.1",
|
|
91
|
+
"@strapi/plugin-email": "4.4.0-beta.1",
|
|
92
|
+
"@strapi/plugin-upload": "4.4.0-beta.1",
|
|
93
|
+
"@strapi/typescript-utils": "4.4.0-beta.1",
|
|
94
|
+
"@strapi/utils": "4.4.0-beta.1",
|
|
94
95
|
"bcryptjs": "2.4.3",
|
|
95
96
|
"boxen": "5.1.2",
|
|
96
97
|
"chalk": "4.1.2",
|
|
@@ -136,8 +137,8 @@
|
|
|
136
137
|
"typescript": "4.6.2"
|
|
137
138
|
},
|
|
138
139
|
"engines": {
|
|
139
|
-
"node": ">=14.19.1 <=
|
|
140
|
+
"node": ">=14.19.1 <=18.x.x",
|
|
140
141
|
"npm": ">=6.0.0"
|
|
141
142
|
},
|
|
142
|
-
"gitHead": "
|
|
143
|
+
"gitHead": "cae16f7f259fa4473a55e8fea57839cda98f34ae"
|
|
143
144
|
}
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const { has } = require('lodash/fp');
|
|
4
|
-
const validators = require('../../services/entity-validator/validators');
|
|
5
|
-
|
|
6
|
-
const customFieldsRegistry = strapi => {
|
|
7
|
-
const customFields = {};
|
|
8
|
-
|
|
9
|
-
return {
|
|
10
|
-
getAll() {
|
|
11
|
-
return customFields;
|
|
12
|
-
},
|
|
13
|
-
get(customField) {
|
|
14
|
-
const registeredCustomField = customFields[customField];
|
|
15
|
-
if (!registeredCustomField) {
|
|
16
|
-
throw new Error(`Could not find Custom Field: ${customField}`);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
return registeredCustomField;
|
|
20
|
-
},
|
|
21
|
-
add(customField) {
|
|
22
|
-
const customFieldList = Array.isArray(customField) ? customField : [customField];
|
|
23
|
-
|
|
24
|
-
for (const cf of customFieldList) {
|
|
25
|
-
if (!has('name', cf) || !has('type', cf)) {
|
|
26
|
-
throw new Error(`Custom fields require a 'name' and 'type' key`);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const { name, plugin, type } = cf;
|
|
30
|
-
if (!has(type, validators)) {
|
|
31
|
-
throw new Error(
|
|
32
|
-
`Custom field type: '${type}' is not a valid Strapi type or it can't be used with a Custom Field`
|
|
33
|
-
);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
const isValidObjectKey = /^(?![0-9])[a-zA-Z0-9$_-]+$/g;
|
|
37
|
-
if (!isValidObjectKey.test(name)) {
|
|
38
|
-
throw new Error(`Custom field name: '${name}' is not a valid object key`);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// When no plugin is specified, or it isn't found in Strapi, default to global
|
|
42
|
-
const uid = strapi.plugin(plugin) ? `plugin::${plugin}.${name}` : `global::${name}`;
|
|
43
|
-
|
|
44
|
-
if (has(uid, customFields)) {
|
|
45
|
-
throw new Error(`Custom field: '${uid}' has already been registered`);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
customFields[uid] = cf;
|
|
49
|
-
}
|
|
50
|
-
},
|
|
51
|
-
};
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
module.exports = customFieldsRegistry;
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const convertCustomFieldType = strapi => {
|
|
4
|
-
const allContentTypeSchemaAttributes = Object.values(strapi.contentTypes).map(
|
|
5
|
-
schema => schema.attributes
|
|
6
|
-
);
|
|
7
|
-
const allComponentSchemaAttributes = Object.values(strapi.components).map(
|
|
8
|
-
schema => schema.attributes
|
|
9
|
-
);
|
|
10
|
-
const allSchemasAttributes = [...allContentTypeSchemaAttributes, ...allComponentSchemaAttributes];
|
|
11
|
-
|
|
12
|
-
for (const schemaAttrbutes of allSchemasAttributes) {
|
|
13
|
-
for (const attribute of Object.values(schemaAttrbutes)) {
|
|
14
|
-
if (attribute.type === 'customField') {
|
|
15
|
-
const customField = strapi.container.get('custom-fields').get(attribute.customField);
|
|
16
|
-
attribute.type = customField.type;
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
module.exports = convertCustomFieldType;
|