@strapi/strapi 4.13.0-beta.0 → 4.13.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.
Files changed (62) hide show
  1. package/README.md +13 -5
  2. package/lib/Strapi.js +11 -0
  3. package/lib/commands/actions/import/action.js +4 -4
  4. package/lib/commands/actions/import/command.js +1 -1
  5. package/lib/commands/actions/transfer/action.js +3 -6
  6. package/lib/commands/actions/transfer/command.js +1 -1
  7. package/lib/commands/actions/watch-admin/action.js +1 -1
  8. package/lib/commands/builders/admin.js +1 -1
  9. package/lib/commands/utils/data-transfer.js +39 -1
  10. package/lib/core/loaders/index.js +1 -0
  11. package/lib/core/loaders/plugins/get-enabled-plugins.js +40 -13
  12. package/lib/core/loaders/sanitizers.js +1 -1
  13. package/lib/core/loaders/validators.js +5 -0
  14. package/lib/core/registries/validators.js +26 -0
  15. package/lib/core-api/controller/collection-type.js +14 -8
  16. package/lib/core-api/controller/index.js +15 -3
  17. package/lib/core-api/controller/single-type.js +2 -0
  18. package/lib/factories.d.ts +10 -13
  19. package/lib/factories.js +12 -2
  20. package/lib/services/entity-service/index.d.ts +1 -100
  21. package/lib/services/entity-service/index.js +1 -0
  22. package/lib/services/entity-service/types/index.d.ts +228 -0
  23. package/lib/services/entity-service/types/params/attributes.d.ts +146 -0
  24. package/lib/services/entity-service/types/params/data.d.ts +4 -0
  25. package/lib/services/entity-service/types/params/fields.d.ts +74 -0
  26. package/lib/services/entity-service/types/params/filters/index.d.ts +75 -0
  27. package/lib/services/entity-service/types/params/filters/operators.d.ts +30 -0
  28. package/lib/services/entity-service/types/params/index.d.ts +79 -0
  29. package/lib/services/entity-service/types/params/pagination.d.ts +13 -0
  30. package/lib/services/entity-service/types/params/populate.d.ts +130 -0
  31. package/lib/services/entity-service/types/params/publication-state.d.ts +38 -0
  32. package/lib/services/entity-service/types/params/search.d.ts +1 -0
  33. package/lib/services/entity-service/types/params/sort.d.ts +106 -0
  34. package/lib/services/entity-service/types/plugin.d.ts +11 -0
  35. package/lib/services/entity-service/types/result.d.ts +205 -0
  36. package/lib/types/core/attributes/common.d.ts +13 -10
  37. package/lib/types/core/attributes/component.d.ts +3 -0
  38. package/lib/types/core/attributes/date-time.d.ts +2 -1
  39. package/lib/types/core/attributes/dynamic-zone.d.ts +10 -7
  40. package/lib/types/core/attributes/media.d.ts +8 -0
  41. package/lib/types/core/attributes/relation.d.ts +69 -28
  42. package/lib/types/core/attributes/time.d.ts +1 -1
  43. package/lib/types/core/attributes/timestamp.d.ts +1 -1
  44. package/lib/types/core/attributes/uid.d.ts +7 -17
  45. package/lib/types/core/attributes/utils.d.ts +61 -6
  46. package/lib/types/core/common/index.d.ts +12 -0
  47. package/lib/types/core/common/uid.d.ts +12 -16
  48. package/lib/types/core/index.d.ts +1 -0
  49. package/lib/types/core/plugins/index.d.ts +16 -0
  50. package/lib/types/core/schemas/index.d.ts +1 -0
  51. package/lib/types/core/strapi/index.d.ts +7 -2
  52. package/lib/types/core-api/controller.d.ts +12 -10
  53. package/lib/types/index.d.ts +2 -0
  54. package/lib/types/shared/entity-service.d.ts +1 -0
  55. package/lib/types/shared/index.d.ts +2 -0
  56. package/lib/types/shared/plugins.d.ts +3 -0
  57. package/lib/types/utils/expression.d.ts +29 -6
  58. package/lib/types/utils/guard.d.ts +14 -1
  59. package/lib/types/utils/index.d.ts +8 -0
  60. package/lib/types/utils/object.d.ts +35 -1
  61. package/lib/types/utils/string.d.ts +18 -0
  62. package/package.json +17 -16
package/README.md CHANGED
@@ -1,9 +1,17 @@
1
1
  <p align="center">
2
- <a href="https://strapi.io/#gh-light-mode-only">
3
- <img src="https://strapi.io/assets/strapi-logo-dark.svg" width="318px" alt="Strapi logo" />
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" />
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
- rules: {
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 all assets and data in your database. Are you sure you want to proceed?',
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 all the remote Strapi assets and its database. Are you sure you want to proceed?',
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
  };
@@ -9,4 +9,5 @@ module.exports = {
9
9
  loadPlugins: require('./plugins'),
10
10
  loadAdmin: require('./admin'),
11
11
  loadSanitizers: require('./sanitizers'),
12
+ loadValidators: require('./validators'),
12
13
  };
@@ -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, has, pick, pickBy, defaultsDeep, map, prop, pipe } = require('lodash/fp');
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 = pick(['enabled'], declaration);
30
- if (has('resolve', declaration)) {
31
- let pathToPlugin = '';
45
+ const detailedDeclaration = {
46
+ enabled: declaration.enabled,
47
+ };
32
48
 
33
- try {
34
- pathToPlugin = dirname(require.resolve(declaration.resolve));
35
- } catch (e) {
36
- pathToPlugin = resolve(strapi.dirs.app.root, declaration.resolve);
49
+ if (declaration?.resolve) {
50
+ let pathToPlugin = '';
37
51
 
38
- if (!existsSync(pathToPlugin) || !statSync(pathToPlugin).isDirectory()) {
39
- throw new Error(`${declaration.resolve} couldn't be resolved`);
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,
@@ -1,5 +1,5 @@
1
1
  'use strict';
2
2
 
3
3
  module.exports = (strapi) => {
4
- strapi.container.get('sanitizers').set('content-api', { input: [], output: [] });
4
+ strapi.container.get('sanitizers').set('content-api', { input: [], output: [], query: [] });
5
5
  };
@@ -0,0 +1,5 @@
1
+ 'use strict';
2
+
3
+ module.exports = (strapi) => {
4
+ strapi.container.get('validators').set('content-api', { input: [], query: [] });
5
+ };
@@ -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
- const { query } = ctx;
37
+ await this.validateQuery(ctx);
38
+ const sanitizedQuery = await this.sanitizeQuery(ctx);
36
39
 
37
- const entity = await strapi.service(uid).findOne(id, query);
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
- const { query } = ctx.request;
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({ ...query, data: sanitizedInputData, files });
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
- const { query } = ctx.request;
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, { ...query, data: sanitizedInputData, files });
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
- const { query } = ctx;
104
+ await this.validateQuery(ctx);
105
+ const sanitizedQuery = await this.sanitizeQuery(ctx);
100
106
 
101
- const entity = await strapi.service(uid).delete(id, query);
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);
@@ -1,21 +1,18 @@
1
- import type { Common, CoreApi, Strapi } from '@strapi/strapi';
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
- export declare function createCoreController<
11
- T extends Common.UID.ContentType,
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?: WithStrapiCallback<S> | S
16
- ): () => Required<S & CoreApi.Controller.ContentType<T>>;
11
+ config?: any
12
+ ): () => any;
17
13
 
18
- export declare function createCoreService<
19
- T extends Common.UID.ContentType,
20
- S extends Partial<CoreApi.Service.Extendable<T>>
21
- >(uid: T, config?: WithStrapiCallback<S> | S): () => Required<S & CoreApi.Service.ContentType<T>>;
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.contentType(uid),
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.contentType(uid),
41
+ contentType: getContentTypeProxy(strapi, uid),
32
42
  });
33
43
 
34
44
  const userService = typeof cfg === 'function' ? cfg({ strapi }) : cfg;
@@ -1,100 +1 @@
1
- import type { Database, ID } from '@strapi/database';
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);