@strapi/strapi 4.13.0-beta.0 → 4.13.0
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 +13 -5
- package/lib/Strapi.js +11 -0
- package/lib/commands/actions/import/action.js +4 -4
- package/lib/commands/actions/import/command.js +1 -1
- package/lib/commands/actions/transfer/action.js +3 -6
- package/lib/commands/actions/transfer/command.js +1 -1
- package/lib/commands/actions/watch-admin/action.js +1 -1
- package/lib/commands/builders/admin.js +1 -1
- package/lib/commands/utils/data-transfer.js +39 -1
- package/lib/core/loaders/index.js +1 -0
- package/lib/core/loaders/plugins/get-enabled-plugins.js +40 -13
- package/lib/core/loaders/sanitizers.js +1 -1
- package/lib/core/loaders/validators.js +5 -0
- package/lib/core/registries/validators.js +26 -0
- package/lib/core-api/controller/collection-type.js +14 -8
- package/lib/core-api/controller/index.js +15 -3
- package/lib/core-api/controller/single-type.js +2 -0
- package/lib/factories.d.ts +10 -13
- package/lib/factories.js +12 -2
- package/lib/services/entity-service/index.d.ts +1 -100
- package/lib/services/entity-service/index.js +1 -0
- package/lib/services/entity-service/types/index.d.ts +228 -0
- package/lib/services/entity-service/types/params/attributes.d.ts +146 -0
- package/lib/services/entity-service/types/params/data.d.ts +4 -0
- package/lib/services/entity-service/types/params/fields.d.ts +74 -0
- package/lib/services/entity-service/types/params/filters/index.d.ts +75 -0
- package/lib/services/entity-service/types/params/filters/operators.d.ts +30 -0
- package/lib/services/entity-service/types/params/index.d.ts +79 -0
- package/lib/services/entity-service/types/params/pagination.d.ts +13 -0
- package/lib/services/entity-service/types/params/populate.d.ts +130 -0
- package/lib/services/entity-service/types/params/publication-state.d.ts +38 -0
- package/lib/services/entity-service/types/params/search.d.ts +1 -0
- package/lib/services/entity-service/types/params/sort.d.ts +106 -0
- package/lib/services/entity-service/types/plugin.d.ts +11 -0
- package/lib/services/entity-service/types/result.d.ts +205 -0
- package/lib/types/core/attributes/common.d.ts +13 -10
- package/lib/types/core/attributes/component.d.ts +3 -0
- package/lib/types/core/attributes/date-time.d.ts +2 -1
- package/lib/types/core/attributes/dynamic-zone.d.ts +10 -7
- package/lib/types/core/attributes/media.d.ts +8 -0
- package/lib/types/core/attributes/relation.d.ts +69 -28
- package/lib/types/core/attributes/time.d.ts +1 -1
- package/lib/types/core/attributes/timestamp.d.ts +1 -1
- package/lib/types/core/attributes/uid.d.ts +7 -17
- package/lib/types/core/attributes/utils.d.ts +61 -6
- package/lib/types/core/common/index.d.ts +12 -0
- package/lib/types/core/common/uid.d.ts +12 -16
- package/lib/types/core/index.d.ts +1 -0
- package/lib/types/core/plugins/index.d.ts +16 -0
- package/lib/types/core/schemas/index.d.ts +1 -0
- package/lib/types/core/strapi/index.d.ts +7 -2
- package/lib/types/core-api/controller.d.ts +12 -10
- package/lib/types/index.d.ts +2 -0
- package/lib/types/shared/entity-service.d.ts +1 -0
- package/lib/types/shared/index.d.ts +2 -0
- package/lib/types/shared/plugins.d.ts +3 -0
- package/lib/types/utils/expression.d.ts +29 -6
- package/lib/types/utils/guard.d.ts +14 -1
- package/lib/types/utils/index.d.ts +8 -0
- package/lib/types/utils/object.d.ts +35 -1
- package/lib/types/utils/string.d.ts +18 -0
- package/package.json +17 -16
package/README.md
CHANGED
|
@@ -1,9 +1,17 @@
|
|
|
1
1
|
<p align="center">
|
|
2
|
-
<a href="https://strapi.io
|
|
3
|
-
<
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
2
|
+
<a href="https://strapi.io">
|
|
3
|
+
<picture>
|
|
4
|
+
<source
|
|
5
|
+
srcset="https://strapi.io/assets/strapi-logo-dark.svg"
|
|
6
|
+
media="(prefers-color-scheme: dark)"
|
|
7
|
+
width="318px"
|
|
8
|
+
/>
|
|
9
|
+
<img
|
|
10
|
+
src="https://strapi.io/assets/strapi-logo-light.svg"
|
|
11
|
+
alt="Strapi logo"
|
|
12
|
+
width="318px"
|
|
13
|
+
/>
|
|
14
|
+
</picture>
|
|
7
15
|
</a>
|
|
8
16
|
</p>
|
|
9
17
|
|
package/lib/Strapi.js
CHANGED
|
@@ -46,6 +46,7 @@ const loaders = require('./core/loaders');
|
|
|
46
46
|
const { destroyOnSignal } = require('./utils/signals');
|
|
47
47
|
const getNumberOfDynamicZones = require('./services/utils/dynamic-zones');
|
|
48
48
|
const sanitizersRegistry = require('./core/registries/sanitizers');
|
|
49
|
+
const validatorsRegistry = require('./core/registries/validators');
|
|
49
50
|
const convertCustomFieldType = require('./utils/convert-custom-field-type');
|
|
50
51
|
|
|
51
52
|
// TODO: move somewhere else
|
|
@@ -99,6 +100,7 @@ class Strapi {
|
|
|
99
100
|
this.container.register('auth', createAuth(this));
|
|
100
101
|
this.container.register('content-api', createContentAPI(this));
|
|
101
102
|
this.container.register('sanitizers', sanitizersRegistry(this));
|
|
103
|
+
this.container.register('validators', validatorsRegistry(this));
|
|
102
104
|
|
|
103
105
|
// Create a mapping of every useful directory (for the app, dist and static directories)
|
|
104
106
|
this.dirs = utils.getDirs(rootDirs, { strapi: this });
|
|
@@ -212,6 +214,10 @@ class Strapi {
|
|
|
212
214
|
return this.container.get('sanitizers');
|
|
213
215
|
}
|
|
214
216
|
|
|
217
|
+
get validators() {
|
|
218
|
+
return this.container.get('validators');
|
|
219
|
+
}
|
|
220
|
+
|
|
215
221
|
async start() {
|
|
216
222
|
try {
|
|
217
223
|
if (!this.isLoaded) {
|
|
@@ -370,6 +376,10 @@ class Strapi {
|
|
|
370
376
|
await loaders.loadSanitizers(this);
|
|
371
377
|
}
|
|
372
378
|
|
|
379
|
+
async loadValidators() {
|
|
380
|
+
await loaders.loadValidators(this);
|
|
381
|
+
}
|
|
382
|
+
|
|
373
383
|
registerInternalHooks() {
|
|
374
384
|
this.container.get('hooks').set('strapi::content-types.beforeSync', createAsyncParallelHook());
|
|
375
385
|
this.container.get('hooks').set('strapi::content-types.afterSync', createAsyncParallelHook());
|
|
@@ -382,6 +392,7 @@ class Strapi {
|
|
|
382
392
|
await Promise.all([
|
|
383
393
|
this.loadApp(),
|
|
384
394
|
this.loadSanitizers(),
|
|
395
|
+
this.loadValidators(),
|
|
385
396
|
this.loadPlugins(),
|
|
386
397
|
this.loadAdmin(),
|
|
387
398
|
this.loadAPIs(),
|
|
@@ -23,6 +23,7 @@ const {
|
|
|
23
23
|
getTransferTelemetryPayload,
|
|
24
24
|
setSignalHandler,
|
|
25
25
|
getDiffHandler,
|
|
26
|
+
parseRestoreFromOptions,
|
|
26
27
|
} = require('../../utils/data-transfer');
|
|
27
28
|
const { exitWith } = require('../../utils/helpers');
|
|
28
29
|
|
|
@@ -71,10 +72,9 @@ module.exports = async (opts) => {
|
|
|
71
72
|
},
|
|
72
73
|
autoDestroy: false,
|
|
73
74
|
strategy: opts.conflictStrategy || DEFAULT_CONFLICT_STRATEGY,
|
|
74
|
-
restore:
|
|
75
|
-
entities: { exclude: DEFAULT_IGNORED_CONTENT_TYPES },
|
|
76
|
-
},
|
|
75
|
+
restore: parseRestoreFromOptions(opts),
|
|
77
76
|
};
|
|
77
|
+
|
|
78
78
|
const destination = createLocalStrapiDestinationProvider(destinationOptions);
|
|
79
79
|
|
|
80
80
|
/**
|
|
@@ -86,7 +86,7 @@ module.exports = async (opts) => {
|
|
|
86
86
|
exclude: opts.exclude,
|
|
87
87
|
only: opts.only,
|
|
88
88
|
throttle: opts.throttle,
|
|
89
|
-
|
|
89
|
+
transforms: {
|
|
90
90
|
links: [
|
|
91
91
|
{
|
|
92
92
|
filter(link) {
|
|
@@ -89,7 +89,7 @@ module.exports = ({ command }) => {
|
|
|
89
89
|
.hook(
|
|
90
90
|
'preAction',
|
|
91
91
|
getCommanderConfirmMessage(
|
|
92
|
-
'The import will delete
|
|
92
|
+
'The import will delete your existing data! Are you sure you want to proceed?',
|
|
93
93
|
{ failMessage: 'Import process aborted' }
|
|
94
94
|
)
|
|
95
95
|
)
|
|
@@ -25,6 +25,7 @@ const {
|
|
|
25
25
|
setSignalHandler,
|
|
26
26
|
getDiffHandler,
|
|
27
27
|
getAssetsBackupHandler,
|
|
28
|
+
parseRestoreFromOptions,
|
|
28
29
|
} = require('../../utils/data-transfer');
|
|
29
30
|
const { exitWith } = require('../../utils/helpers');
|
|
30
31
|
|
|
@@ -88,9 +89,7 @@ module.exports = async (opts) => {
|
|
|
88
89
|
destination = createLocalStrapiDestinationProvider({
|
|
89
90
|
getStrapi: () => strapi,
|
|
90
91
|
strategy: 'restore',
|
|
91
|
-
restore:
|
|
92
|
-
entities: { exclude: DEFAULT_IGNORED_CONTENT_TYPES },
|
|
93
|
-
},
|
|
92
|
+
restore: parseRestoreFromOptions(opts),
|
|
94
93
|
});
|
|
95
94
|
}
|
|
96
95
|
// if URL provided, set up a remote destination provider
|
|
@@ -106,9 +105,7 @@ module.exports = async (opts) => {
|
|
|
106
105
|
token: opts.toToken,
|
|
107
106
|
},
|
|
108
107
|
strategy: 'restore',
|
|
109
|
-
restore:
|
|
110
|
-
entities: { exclude: DEFAULT_IGNORED_CONTENT_TYPES },
|
|
111
|
-
},
|
|
108
|
+
restore: parseRestoreFromOptions(opts),
|
|
112
109
|
});
|
|
113
110
|
}
|
|
114
111
|
|
|
@@ -105,7 +105,7 @@ module.exports = ({ command }) => {
|
|
|
105
105
|
}
|
|
106
106
|
|
|
107
107
|
await getCommanderConfirmMessage(
|
|
108
|
-
'The transfer will delete
|
|
108
|
+
'The transfer will delete existing data from the remote Strapi! Are you sure you want to proceed?',
|
|
109
109
|
{ failMessage: 'Transfer process aborted' }
|
|
110
110
|
)(thisCommand);
|
|
111
111
|
}
|
|
@@ -16,7 +16,7 @@ module.exports = async ({ browser }) => {
|
|
|
16
16
|
serveAdminPanel: false,
|
|
17
17
|
});
|
|
18
18
|
|
|
19
|
-
const plugins = await getEnabledPlugins(strapiInstance);
|
|
19
|
+
const plugins = await getEnabledPlugins(strapiInstance, { client: true });
|
|
20
20
|
|
|
21
21
|
const { adminPath } = getConfigUrls(strapiInstance.config, true);
|
|
22
22
|
|
|
@@ -20,7 +20,7 @@ module.exports = async ({ buildDestDir, forceBuild = true, optimization, srcDir
|
|
|
20
20
|
serveAdminPanel: false,
|
|
21
21
|
});
|
|
22
22
|
|
|
23
|
-
const plugins = await getEnabledPlugins(strapiInstance);
|
|
23
|
+
const plugins = await getEnabledPlugins(strapiInstance, { client: true });
|
|
24
24
|
|
|
25
25
|
const env = strapiInstance.config.get('environment');
|
|
26
26
|
const { serverUrl, adminPath } = getConfigUrls(strapiInstance.config, true);
|
|
@@ -291,7 +291,8 @@ const getDiffHandler = (engine, { force, action }) => {
|
|
|
291
291
|
if (
|
|
292
292
|
uid === 'admin::workflow' ||
|
|
293
293
|
uid === 'admin::workflow-stage' ||
|
|
294
|
-
endPath?.startsWith('strapi_stage')
|
|
294
|
+
endPath?.startsWith('strapi_stage') ||
|
|
295
|
+
endPath?.startsWith('strapi_assignee')
|
|
295
296
|
) {
|
|
296
297
|
workflowsStatus = diff.kind;
|
|
297
298
|
}
|
|
@@ -369,6 +370,41 @@ const getAssetsBackupHandler = (engine, { force, action }) => {
|
|
|
369
370
|
};
|
|
370
371
|
};
|
|
371
372
|
|
|
373
|
+
const shouldSkipStage = (opts, dataKind) => {
|
|
374
|
+
if (opts.exclude?.includes(dataKind)) {
|
|
375
|
+
return true;
|
|
376
|
+
}
|
|
377
|
+
if (opts.only) {
|
|
378
|
+
return !opts.only.includes(dataKind);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
return false;
|
|
382
|
+
};
|
|
383
|
+
|
|
384
|
+
// Based on exclude/only from options, create the restore object to match
|
|
385
|
+
const parseRestoreFromOptions = (opts) => {
|
|
386
|
+
const entitiesOptions = {
|
|
387
|
+
exclude: DEFAULT_IGNORED_CONTENT_TYPES,
|
|
388
|
+
include: undefined,
|
|
389
|
+
};
|
|
390
|
+
|
|
391
|
+
// if content is not included, send an empty array for include
|
|
392
|
+
if ((opts.only && !opts.only.includes('content')) || opts.exclude?.includes('content')) {
|
|
393
|
+
entitiesOptions.include = [];
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
const restoreConfig = {
|
|
397
|
+
entities: entitiesOptions,
|
|
398
|
+
assets: !shouldSkipStage(opts, 'files'),
|
|
399
|
+
configuration: {
|
|
400
|
+
webhooks: !shouldSkipStage(opts, 'config'),
|
|
401
|
+
coreStore: !shouldSkipStage(opts, 'config'),
|
|
402
|
+
},
|
|
403
|
+
};
|
|
404
|
+
|
|
405
|
+
return restoreConfig;
|
|
406
|
+
};
|
|
407
|
+
|
|
372
408
|
module.exports = {
|
|
373
409
|
loadersFactory,
|
|
374
410
|
buildTransferTable,
|
|
@@ -386,4 +422,6 @@ module.exports = {
|
|
|
386
422
|
setSignalHandler,
|
|
387
423
|
getDiffHandler,
|
|
388
424
|
getAssetsBackupHandler,
|
|
425
|
+
shouldSkipStage,
|
|
426
|
+
parseRestoreFromOptions,
|
|
389
427
|
};
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
const { dirname, join, resolve } = require('path');
|
|
4
4
|
const { statSync, existsSync } = require('fs');
|
|
5
5
|
const _ = require('lodash');
|
|
6
|
-
const { get,
|
|
6
|
+
const { get, pickBy, defaultsDeep, map, prop, pipe } = require('lodash/fp');
|
|
7
7
|
const { isKebabCase } = require('@strapi/utils');
|
|
8
8
|
const getUserPluginsConfig = require('./get-user-plugins-config');
|
|
9
9
|
|
|
@@ -21,31 +21,58 @@ const validatePluginName = (pluginName) => {
|
|
|
21
21
|
}
|
|
22
22
|
};
|
|
23
23
|
|
|
24
|
+
/**
|
|
25
|
+
* @typedef {Object} PluginInstallDeclaration
|
|
26
|
+
* @property {boolean} enabled
|
|
27
|
+
* @property {string=} resolve
|
|
28
|
+
* @property {boolean=} isModule - whether the plugin is a node module or a local plugin
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* @typedef {Object} DetailedDeclaration
|
|
33
|
+
* @property {boolean} enabled
|
|
34
|
+
* @property {string=} pathToPlugin
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* @type {(declaration: PluginInstallDeclaration | boolean) => DetailedDeclaration}
|
|
39
|
+
*/
|
|
24
40
|
const toDetailedDeclaration = (declaration) => {
|
|
25
41
|
if (typeof declaration === 'boolean') {
|
|
26
42
|
return { enabled: declaration };
|
|
27
43
|
}
|
|
28
44
|
|
|
29
|
-
const detailedDeclaration =
|
|
30
|
-
|
|
31
|
-
|
|
45
|
+
const detailedDeclaration = {
|
|
46
|
+
enabled: declaration.enabled,
|
|
47
|
+
};
|
|
32
48
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
} catch (e) {
|
|
36
|
-
pathToPlugin = resolve(strapi.dirs.app.root, declaration.resolve);
|
|
49
|
+
if (declaration?.resolve) {
|
|
50
|
+
let pathToPlugin = '';
|
|
37
51
|
|
|
38
|
-
|
|
39
|
-
|
|
52
|
+
if (declaration.isModule) {
|
|
53
|
+
/**
|
|
54
|
+
* we only want the node_module here, not the package.json
|
|
55
|
+
*/
|
|
56
|
+
pathToPlugin = join(declaration.resolve, '..');
|
|
57
|
+
} else {
|
|
58
|
+
try {
|
|
59
|
+
pathToPlugin = dirname(require.resolve(declaration.resolve));
|
|
60
|
+
} catch (e) {
|
|
61
|
+
pathToPlugin = resolve(strapi.dirs.app.root, declaration.resolve);
|
|
62
|
+
|
|
63
|
+
if (!existsSync(pathToPlugin) || !statSync(pathToPlugin).isDirectory()) {
|
|
64
|
+
throw new Error(`${declaration.resolve} couldn't be resolved`);
|
|
65
|
+
}
|
|
40
66
|
}
|
|
41
67
|
}
|
|
42
68
|
|
|
43
69
|
detailedDeclaration.pathToPlugin = pathToPlugin;
|
|
44
70
|
}
|
|
71
|
+
|
|
45
72
|
return detailedDeclaration;
|
|
46
73
|
};
|
|
47
74
|
|
|
48
|
-
const getEnabledPlugins = async (strapi) => {
|
|
75
|
+
const getEnabledPlugins = async (strapi, { client } = { client: false }) => {
|
|
49
76
|
const internalPlugins = {};
|
|
50
77
|
for (const dep of INTERNAL_PLUGINS) {
|
|
51
78
|
const packagePath = join(dep, 'package.json');
|
|
@@ -53,7 +80,7 @@ const getEnabledPlugins = async (strapi) => {
|
|
|
53
80
|
|
|
54
81
|
validatePluginName(packageInfo.strapi.name);
|
|
55
82
|
internalPlugins[packageInfo.strapi.name] = {
|
|
56
|
-
...toDetailedDeclaration({ enabled: true, resolve: packagePath }),
|
|
83
|
+
...toDetailedDeclaration({ enabled: true, resolve: packagePath, isModule: client }),
|
|
57
84
|
info: packageInfo.strapi,
|
|
58
85
|
};
|
|
59
86
|
}
|
|
@@ -73,7 +100,7 @@ const getEnabledPlugins = async (strapi) => {
|
|
|
73
100
|
if (isStrapiPlugin(packageInfo)) {
|
|
74
101
|
validatePluginName(packageInfo.strapi.name);
|
|
75
102
|
installedPlugins[packageInfo.strapi.name] = {
|
|
76
|
-
...toDetailedDeclaration({ enabled: true, resolve: packagePath }),
|
|
103
|
+
...toDetailedDeclaration({ enabled: true, resolve: packagePath, isModule: client }),
|
|
77
104
|
info: {
|
|
78
105
|
...packageInfo.strapi,
|
|
79
106
|
packageName: packageInfo.name,
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const _ = require('lodash');
|
|
4
|
+
|
|
5
|
+
const validatorsRegistry = () => {
|
|
6
|
+
const validators = {};
|
|
7
|
+
|
|
8
|
+
return {
|
|
9
|
+
get(path) {
|
|
10
|
+
return _.get(validators, path, []);
|
|
11
|
+
},
|
|
12
|
+
add(path, sanitizer) {
|
|
13
|
+
this.get(path).push(sanitizer);
|
|
14
|
+
return this;
|
|
15
|
+
},
|
|
16
|
+
set(path, value = []) {
|
|
17
|
+
_.set(validators, path, value);
|
|
18
|
+
return this;
|
|
19
|
+
},
|
|
20
|
+
has(path) {
|
|
21
|
+
return _.has(validators, path);
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
module.exports = validatorsRegistry;
|
|
@@ -19,7 +19,9 @@ const createCollectionTypeController = ({ contentType }) => {
|
|
|
19
19
|
* @return {Object|Array}
|
|
20
20
|
*/
|
|
21
21
|
async find(ctx) {
|
|
22
|
+
await this.validateQuery(ctx);
|
|
22
23
|
const sanitizedQuery = await this.sanitizeQuery(ctx);
|
|
24
|
+
|
|
23
25
|
const { results, pagination } = await strapi.service(uid).find(sanitizedQuery);
|
|
24
26
|
const sanitizedResults = await this.sanitizeOutput(results, ctx);
|
|
25
27
|
return this.transformResponse(sanitizedResults, { pagination });
|
|
@@ -32,9 +34,10 @@ const createCollectionTypeController = ({ contentType }) => {
|
|
|
32
34
|
*/
|
|
33
35
|
async findOne(ctx) {
|
|
34
36
|
const { id } = ctx.params;
|
|
35
|
-
|
|
37
|
+
await this.validateQuery(ctx);
|
|
38
|
+
const sanitizedQuery = await this.sanitizeQuery(ctx);
|
|
36
39
|
|
|
37
|
-
const entity = await strapi.service(uid).findOne(id,
|
|
40
|
+
const entity = await strapi.service(uid).findOne(id, sanitizedQuery);
|
|
38
41
|
const sanitizedEntity = await this.sanitizeOutput(entity, ctx);
|
|
39
42
|
|
|
40
43
|
return this.transformResponse(sanitizedEntity);
|
|
@@ -46,7 +49,8 @@ const createCollectionTypeController = ({ contentType }) => {
|
|
|
46
49
|
* @return {Object}
|
|
47
50
|
*/
|
|
48
51
|
async create(ctx) {
|
|
49
|
-
|
|
52
|
+
await this.validateQuery(ctx);
|
|
53
|
+
const sanitizedQuery = await this.sanitizeQuery(ctx);
|
|
50
54
|
|
|
51
55
|
const { data, files } = parseBody(ctx);
|
|
52
56
|
|
|
@@ -58,7 +62,7 @@ const createCollectionTypeController = ({ contentType }) => {
|
|
|
58
62
|
|
|
59
63
|
const entity = await strapi
|
|
60
64
|
.service(uid)
|
|
61
|
-
.create({ ...
|
|
65
|
+
.create({ ...sanitizedQuery, data: sanitizedInputData, files });
|
|
62
66
|
const sanitizedEntity = await this.sanitizeOutput(entity, ctx);
|
|
63
67
|
|
|
64
68
|
return this.transformResponse(sanitizedEntity);
|
|
@@ -71,7 +75,8 @@ const createCollectionTypeController = ({ contentType }) => {
|
|
|
71
75
|
*/
|
|
72
76
|
async update(ctx) {
|
|
73
77
|
const { id } = ctx.params;
|
|
74
|
-
|
|
78
|
+
await this.validateQuery(ctx);
|
|
79
|
+
const sanitizedQuery = await this.sanitizeQuery(ctx);
|
|
75
80
|
|
|
76
81
|
const { data, files } = parseBody(ctx);
|
|
77
82
|
|
|
@@ -83,7 +88,7 @@ const createCollectionTypeController = ({ contentType }) => {
|
|
|
83
88
|
|
|
84
89
|
const entity = await strapi
|
|
85
90
|
.service(uid)
|
|
86
|
-
.update(id, { ...
|
|
91
|
+
.update(id, { ...sanitizedQuery, data: sanitizedInputData, files });
|
|
87
92
|
const sanitizedEntity = await this.sanitizeOutput(entity, ctx);
|
|
88
93
|
|
|
89
94
|
return this.transformResponse(sanitizedEntity);
|
|
@@ -96,9 +101,10 @@ const createCollectionTypeController = ({ contentType }) => {
|
|
|
96
101
|
*/
|
|
97
102
|
async delete(ctx) {
|
|
98
103
|
const { id } = ctx.params;
|
|
99
|
-
|
|
104
|
+
await this.validateQuery(ctx);
|
|
105
|
+
const sanitizedQuery = await this.sanitizeQuery(ctx);
|
|
100
106
|
|
|
101
|
-
const entity = await strapi.service(uid).delete(id,
|
|
107
|
+
const entity = await strapi.service(uid).delete(id, sanitizedQuery);
|
|
102
108
|
const sanitizedEntity = await this.sanitizeOutput(entity, ctx);
|
|
103
109
|
|
|
104
110
|
return this.transformResponse(sanitizedEntity);
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const { getOr } = require('lodash/fp');
|
|
4
4
|
|
|
5
|
-
const { contentTypes, sanitize } = require('@strapi/utils');
|
|
5
|
+
const { contentTypes, sanitize, validate } = require('@strapi/utils');
|
|
6
6
|
|
|
7
7
|
const { transformResponse } = require('./transform');
|
|
8
8
|
const createSingleTypeController = require('./single-type');
|
|
@@ -24,17 +24,29 @@ const createController = ({ contentType }) => {
|
|
|
24
24
|
return sanitize.contentAPI.output(data, contentType, { auth });
|
|
25
25
|
},
|
|
26
26
|
|
|
27
|
-
sanitizeInput(data, ctx) {
|
|
27
|
+
async sanitizeInput(data, ctx) {
|
|
28
28
|
const auth = getAuthFromKoaContext(ctx);
|
|
29
29
|
|
|
30
30
|
return sanitize.contentAPI.input(data, contentType, { auth });
|
|
31
31
|
},
|
|
32
32
|
|
|
33
|
-
sanitizeQuery(ctx) {
|
|
33
|
+
async sanitizeQuery(ctx) {
|
|
34
34
|
const auth = getAuthFromKoaContext(ctx);
|
|
35
35
|
|
|
36
36
|
return sanitize.contentAPI.query(ctx.query, contentType, { auth });
|
|
37
37
|
},
|
|
38
|
+
|
|
39
|
+
async validateQuery(ctx) {
|
|
40
|
+
const auth = getAuthFromKoaContext(ctx);
|
|
41
|
+
|
|
42
|
+
return validate.contentAPI.query(ctx.query, contentType, { auth });
|
|
43
|
+
},
|
|
44
|
+
|
|
45
|
+
async validateInput(data, ctx) {
|
|
46
|
+
const auth = getAuthFromKoaContext(ctx);
|
|
47
|
+
|
|
48
|
+
return validate.contentAPI.input(data, contentType, { auth });
|
|
49
|
+
},
|
|
38
50
|
};
|
|
39
51
|
|
|
40
52
|
let ctrl;
|
|
@@ -18,7 +18,9 @@ const createSingleTypeController = ({ contentType }) => {
|
|
|
18
18
|
* @return {Object|Array}
|
|
19
19
|
*/
|
|
20
20
|
async find(ctx) {
|
|
21
|
+
await this.validateQuery(ctx);
|
|
21
22
|
const sanitizedQuery = await this.sanitizeQuery(ctx);
|
|
23
|
+
|
|
22
24
|
const entity = await strapi.service(uid).find(sanitizedQuery);
|
|
23
25
|
|
|
24
26
|
const sanitizedEntity = await this.sanitizeOutput(entity, ctx);
|
package/lib/factories.d.ts
CHANGED
|
@@ -1,21 +1,18 @@
|
|
|
1
|
-
import type { Common, CoreApi
|
|
2
|
-
|
|
3
|
-
type WithStrapiCallback<T> = <S extends { strapi: Strapi }>(params: S) => T;
|
|
1
|
+
import type { Common, CoreApi } from '@strapi/strapi';
|
|
4
2
|
|
|
5
3
|
export declare function createCoreRouter<T extends Common.UID.ContentType>(
|
|
6
4
|
uid: T,
|
|
7
5
|
cfg?: CoreApi.Router.RouterConfig<T>
|
|
8
6
|
): () => CoreApi.Router.Router;
|
|
9
7
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
S extends Partial<CoreApi.Controller.Extendable<T>>
|
|
13
|
-
>(
|
|
8
|
+
// TODO: Find a cleaner way to expose methods for customization
|
|
9
|
+
export declare function createCoreController<T extends Common.UID.ContentType>(
|
|
14
10
|
uid: T,
|
|
15
|
-
config?:
|
|
16
|
-
): () =>
|
|
11
|
+
config?: any
|
|
12
|
+
): () => any;
|
|
17
13
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
14
|
+
// TODO: Find a cleaner way to expose methods for customization
|
|
15
|
+
export declare function createCoreService<T extends Common.UID.ContentType>(
|
|
16
|
+
uid: T,
|
|
17
|
+
config?: any
|
|
18
|
+
): () => any;
|
package/lib/factories.js
CHANGED
|
@@ -6,10 +6,20 @@ const { createController } = require('./core-api/controller');
|
|
|
6
6
|
const { createService } = require('./core-api/service');
|
|
7
7
|
const { createRoutes } = require('./core-api/routes');
|
|
8
8
|
|
|
9
|
+
// Content type is proxied to allow for dynamic content type updates
|
|
10
|
+
const getContentTypeProxy = (strapi, uid) => {
|
|
11
|
+
return new Proxy(strapi.contentType(uid), {
|
|
12
|
+
get(target, prop) {
|
|
13
|
+
const contentType = strapi.contentType(uid);
|
|
14
|
+
return contentType[prop];
|
|
15
|
+
},
|
|
16
|
+
});
|
|
17
|
+
};
|
|
18
|
+
|
|
9
19
|
const createCoreController = (uid, cfg = {}) => {
|
|
10
20
|
return ({ strapi }) => {
|
|
11
21
|
const baseController = createController({
|
|
12
|
-
contentType: strapi
|
|
22
|
+
contentType: getContentTypeProxy(strapi, uid),
|
|
13
23
|
});
|
|
14
24
|
|
|
15
25
|
const userCtrl = typeof cfg === 'function' ? cfg({ strapi }) : cfg;
|
|
@@ -28,7 +38,7 @@ const createCoreController = (uid, cfg = {}) => {
|
|
|
28
38
|
const createCoreService = (uid, cfg = {}) => {
|
|
29
39
|
return ({ strapi }) => {
|
|
30
40
|
const baseService = createService({
|
|
31
|
-
contentType: strapi
|
|
41
|
+
contentType: getContentTypeProxy(strapi, uid),
|
|
32
42
|
});
|
|
33
43
|
|
|
34
44
|
const userService = typeof cfg === 'function' ? cfg({ strapi }) : cfg;
|
|
@@ -1,100 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import { Strapi } from '../../';
|
|
3
|
-
|
|
4
|
-
type EntityServiceAction =
|
|
5
|
-
| 'findMany'
|
|
6
|
-
| 'findPage'
|
|
7
|
-
| 'findWithRelationCountsPage'
|
|
8
|
-
| 'findWithRelationCounts'
|
|
9
|
-
| 'findOne'
|
|
10
|
-
| 'count'
|
|
11
|
-
| 'create'
|
|
12
|
-
| 'update'
|
|
13
|
-
| 'delete';
|
|
14
|
-
|
|
15
|
-
type PaginationInfo = {
|
|
16
|
-
page: number;
|
|
17
|
-
pageSize: number;
|
|
18
|
-
pageCount: number;
|
|
19
|
-
total: number;
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
type Params<T> = {
|
|
23
|
-
fields?: (keyof T)[];
|
|
24
|
-
filters?: any;
|
|
25
|
-
_q?: string;
|
|
26
|
-
populate?: any;
|
|
27
|
-
sort?: any;
|
|
28
|
-
start?: number;
|
|
29
|
-
limit?: number;
|
|
30
|
-
page?: number;
|
|
31
|
-
pageSize?: number;
|
|
32
|
-
publicationState?: string;
|
|
33
|
-
data?: any;
|
|
34
|
-
files?: any;
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
export interface EntityService {
|
|
38
|
-
uploadFiles<K extends keyof AllTypes, T extends AllTypes[K]>(uid: K, entity, files);
|
|
39
|
-
wrapParams<K extends keyof AllTypes, T extends AllTypes[K]>(
|
|
40
|
-
params: Params<T>,
|
|
41
|
-
{ uid: K, action: EntityServiceAction }
|
|
42
|
-
);
|
|
43
|
-
|
|
44
|
-
findMany<K extends keyof AllTypes, T extends AllTypes[K]>(
|
|
45
|
-
uid: K,
|
|
46
|
-
params: Params<T>
|
|
47
|
-
): Promise<T[]>;
|
|
48
|
-
findPage<K extends keyof AllTypes, T extends AllTypes[K]>(
|
|
49
|
-
uid: K,
|
|
50
|
-
params: Params<T>
|
|
51
|
-
): Promise<{
|
|
52
|
-
results: T[];
|
|
53
|
-
pagination: PaginationInfo;
|
|
54
|
-
}>;
|
|
55
|
-
|
|
56
|
-
findWithRelationCountsPage<K extends keyof AllTypes, T extends AllTypes[K]>(
|
|
57
|
-
uid: K,
|
|
58
|
-
params: Params<T>
|
|
59
|
-
): Promise<{
|
|
60
|
-
results: T[];
|
|
61
|
-
pagination: PaginationInfo;
|
|
62
|
-
}>;
|
|
63
|
-
|
|
64
|
-
findWithRelationCounts<K extends keyof AllTypes, T extends AllTypes[K]>(
|
|
65
|
-
uid: K,
|
|
66
|
-
params: Params<T>
|
|
67
|
-
): Promise<T[]>;
|
|
68
|
-
|
|
69
|
-
findOne<K extends keyof AllTypes, T extends AllTypes[K]>(
|
|
70
|
-
uid: K,
|
|
71
|
-
entityId: ID,
|
|
72
|
-
params: Params<T>
|
|
73
|
-
): Promise<T>;
|
|
74
|
-
|
|
75
|
-
count<K extends keyof AllTypes, T extends AllTypes[K]>(uid: K, params: Params<T>): Promise<any>;
|
|
76
|
-
create<K extends keyof AllTypes, T extends AllTypes[K]>(uid: K, params: Params<T>): Promise<any>;
|
|
77
|
-
update<K extends keyof AllTypes, T extends AllTypes[K]>(
|
|
78
|
-
uid: K,
|
|
79
|
-
entityId: ID,
|
|
80
|
-
params: Params<T>
|
|
81
|
-
): Promise<any>;
|
|
82
|
-
delete<K extends keyof AllTypes, T extends AllTypes[K]>(
|
|
83
|
-
uid: K,
|
|
84
|
-
entityId: ID,
|
|
85
|
-
params: Params<T>
|
|
86
|
-
): Promise<any>;
|
|
87
|
-
clone<K extends keyof AllTypes, T extends AllTypes[K]>(
|
|
88
|
-
uid: K,
|
|
89
|
-
cloneId: ID,
|
|
90
|
-
params: Params<T>
|
|
91
|
-
): Promise<any>;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
export default function (opts: {
|
|
95
|
-
strapi: Strapi;
|
|
96
|
-
db: Database;
|
|
97
|
-
// TODO: define types
|
|
98
|
-
eventHub: any;
|
|
99
|
-
entityValidator: any;
|
|
100
|
-
}): EntityService;
|
|
1
|
+
export * from './types';
|
|
@@ -172,6 +172,7 @@ const createDefaultImplementation = ({ strapi, db, eventHub, entityValidator })
|
|
|
172
172
|
data: entityData,
|
|
173
173
|
});
|
|
174
174
|
|
|
175
|
+
// TODO: do all of this in a transaction to avoid a race condition where entity is created then deleted before we do findOne again
|
|
175
176
|
// TODO: upload the files then set the links in the entity like with compo to avoid making too many queries
|
|
176
177
|
if (files && Object.keys(files).length > 0) {
|
|
177
178
|
await this.uploadFiles(uid, Object.assign(entityData, entity), files);
|