@strapi/strapi 4.4.0-beta.1 → 4.4.0-beta.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/Strapi.js CHANGED
@@ -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 createContentAPI = require('./services/content-api');
25
+ const createCustomFields = require('./services/custom-fields');
26
26
  const createUpdateNotifier = require('./utils/update-notifier');
27
27
  const createStartupLogger = require('./utils/startup-logger');
28
28
  const { LIFECYCLES } = require('./utils/lifecycles');
@@ -35,12 +35,14 @@ 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');
38
39
  const createConfigProvider = require('./core/registries/config');
39
40
  const apisRegistry = require('./core/registries/apis');
40
41
  const bootstrap = require('./core/bootstrap');
41
42
  const loaders = require('./core/loaders');
42
43
  const { destroyOnSignal } = require('./utils/signals');
43
44
  const sanitizersRegistry = require('./core/registries/sanitizers');
45
+ const convertCustomFieldType = require('./utils/convert-custom-field-type');
44
46
 
45
47
  // TODO: move somewhere else
46
48
  const draftAndPublishSync = require('./migrations/draft-publish');
@@ -75,7 +77,7 @@ class Strapi {
75
77
  // Load the app configuration from the dist directory
76
78
  const appConfig = loadConfiguration({ appDir: rootDirs.app, distDir: rootDirs.dist }, opts);
77
79
 
78
- // Instantiate the Strapi container
80
+ // Instanciate the Strapi container
79
81
  this.container = createContainer(this);
80
82
 
81
83
  // Register every Strapi registry in the container
@@ -88,9 +90,9 @@ class Strapi {
88
90
  this.container.register('controllers', controllersRegistry(this));
89
91
  this.container.register('modules', modulesRegistry(this));
90
92
  this.container.register('plugins', pluginsRegistry(this));
93
+ this.container.register('custom-fields', customFieldsRegistry(this));
91
94
  this.container.register('apis', apisRegistry(this));
92
95
  this.container.register('auth', createAuth(this));
93
- this.container.register('content-api', createContentAPI(this));
94
96
  this.container.register('sanitizers', sanitizersRegistry(this));
95
97
 
96
98
  // Create a mapping of every useful directory (for the app, dist and static directories)
@@ -100,7 +102,7 @@ class Strapi {
100
102
  this.isLoaded = false;
101
103
  this.reload = this.reload();
102
104
 
103
- // Instantiate the Koa app & the HTTP server
105
+ // Instanciate the Koa app & the HTTP server
104
106
  this.server = createServer(this);
105
107
 
106
108
  // Strapi utils instanciation
@@ -111,6 +113,8 @@ class Strapi {
111
113
  this.cron = createCronService();
112
114
  this.telemetry = createTelemetry(this);
113
115
 
116
+ this.customFields = createCustomFields(this);
117
+
114
118
  createUpdateNotifier(this).notify();
115
119
  }
116
120
 
@@ -190,10 +194,6 @@ class Strapi {
190
194
  return this.container.get('auth');
191
195
  }
192
196
 
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
  }
@@ -383,6 +383,8 @@ class Strapi {
383
383
  this.telemetry.register();
384
384
 
385
385
  await this.runLifecyclesFunctions(LIFECYCLES.REGISTER);
386
+ // NOTE: Swap type customField for underlying data type
387
+ convertCustomFieldType(this);
386
388
 
387
389
  return this;
388
390
  }
@@ -451,8 +453,6 @@ class Strapi {
451
453
  await this.server.initMiddlewares();
452
454
  await this.server.initRouting();
453
455
 
454
- await this.contentAPI.permissions.registerActions();
455
-
456
456
  await this.runLifecyclesFunctions(LIFECYCLES.BOOTSTRAP);
457
457
 
458
458
  this.cron.start();
@@ -17,7 +17,7 @@ const passwordValidator = yup
17
17
  const adminCreateSchema = yup.object().shape({
18
18
  email: emailValidator,
19
19
  password: passwordValidator,
20
- firstname: yup.string().required('First name is required'),
20
+ firstname: yup.string().trim().required('First name is required'),
21
21
  lastname: yup.string(),
22
22
  });
23
23
 
@@ -0,0 +1,54 @@
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;
@@ -32,7 +32,6 @@ const createAuthentication = () => {
32
32
 
33
33
  return this;
34
34
  },
35
-
36
35
  async authenticate(ctx, next) {
37
36
  const { route } = ctx.state;
38
37
 
@@ -48,7 +47,7 @@ const createAuthentication = () => {
48
47
  for (const strategy of strategiesToUse) {
49
48
  const result = await strategy.authenticate(ctx);
50
49
 
51
- const { authenticated = false, credentials, ability = null, error = null } = result || {};
50
+ const { authenticated = false, error = null, credentials } = result || {};
52
51
 
53
52
  if (error !== null) {
54
53
  return ctx.unauthorized(error);
@@ -59,7 +58,6 @@ const createAuthentication = () => {
59
58
  ctx.state.auth = {
60
59
  strategy,
61
60
  credentials,
62
- ability,
63
61
  };
64
62
 
65
63
  return next();
@@ -68,7 +66,6 @@ const createAuthentication = () => {
68
66
 
69
67
  return ctx.unauthorized('Missing or invalid credentials');
70
68
  },
71
-
72
69
  async verify(auth, config = {}) {
73
70
  if (config === false) {
74
71
  return;
@@ -0,0 +1,11 @@
1
+ 'use strict';
2
+
3
+ const createCustomFields = (strapi) => {
4
+ return {
5
+ register(customField) {
6
+ strapi.container.get('custom-fields').add(customField);
7
+ },
8
+ };
9
+ };
10
+
11
+ module.exports = createCustomFields;
@@ -5,6 +5,28 @@ import type { StringMap } from './utils';
5
5
  import type { GenericController } from '../../../core-api/controller'
6
6
  import type { GenericService } from '../../../core-api/service'
7
7
 
8
+ // TODO move custom fields types to a separate file
9
+ interface CustomFieldServerOptions {
10
+ /**
11
+ * The name of the custom field
12
+ */
13
+ name: string;
14
+
15
+ /**
16
+ * The name of the plugin creating the custom field
17
+ */
18
+ plugin?: string;
19
+
20
+ /**
21
+ * The existing Strapi data type the custom field uses
22
+ */
23
+ type: string;
24
+ }
25
+
26
+ interface CustomFields {
27
+ register: (customFields: CustomFieldServerOptions[] | CustomFieldServerOptions) => void;
28
+ }
29
+
8
30
  /**
9
31
  * The Strapi interface implemented by the main Strapi class.
10
32
  */
@@ -24,11 +46,6 @@ export interface Strapi {
24
46
  */
25
47
  readonly auth: any;
26
48
 
27
- /**
28
- * Getter for the Strapi content API container
29
- */
30
- readonly contentAPI: any;
31
-
32
49
  /**
33
50
  * Getter for the Strapi sanitizers container
34
51
  */
@@ -70,6 +87,13 @@ export interface Strapi {
70
87
  */
71
88
  contentType(uid: string): any;
72
89
 
90
+ /**
91
+ * The custom fields registry
92
+ *
93
+ * It returns the custom fields interface
94
+ */
95
+ readonly customFields: CustomFields;
96
+
73
97
  /**
74
98
  * Getter for the Strapi policies container
75
99
  *
@@ -200,7 +224,7 @@ export interface Strapi {
200
224
  /**
201
225
  * Restart the server and reload all the configuration.
202
226
  * It re-runs all the lifecycles phases.
203
- *
227
+ *
204
228
  * @example
205
229
  * ``` ts
206
230
  * setImmediate(() => strapi.reload());
@@ -228,13 +252,13 @@ export interface Strapi {
228
252
  /**
229
253
  * Opent he administration panel in a browser if the option is enabled.
230
254
  * You can disable it using the admin.autoOpen configuration variable.
231
- *
255
+ *
232
256
  * Note: It only works in development envs.
233
257
  */
234
258
  openAdmin(options: { isInitialized: boolean }): Promise<void>;
235
259
 
236
260
  /**
237
- * Load the admin panel server logic into the server code and initialize its configuration.
261
+ * Load the admin panel server logic into the server code and initialize its configuration.
238
262
  */
239
263
  loadAdmin(): Promise<void>;
240
264
 
@@ -293,7 +317,7 @@ export interface Strapi {
293
317
  container: any;
294
318
 
295
319
  /**
296
- * References to all the directories handled by Strapi
320
+ * References to all the directories handled by Strapi
297
321
  */
298
322
  dirs: StrapiDirectories;
299
323
 
@@ -328,7 +352,7 @@ export interface Strapi {
328
352
  startupLogger: any;
329
353
 
330
354
  /**
331
- * Strapi logger used to send errors, warning or information messages
355
+ * Strapi logger used to send errors, warning or information messages
332
356
  */
333
357
  log: any;
334
358
 
@@ -361,7 +385,7 @@ export interface Strapi {
361
385
  /**
362
386
  * Entity Service instance
363
387
  */
364
- entityService: any;
388
+ entityService: any;
365
389
  }
366
390
 
367
391
  export interface Lifecycles {
@@ -394,4 +418,4 @@ export interface StrapiDirectories {
394
418
  middlewares: string;
395
419
  config: string;
396
420
  };
397
- }
421
+ }
@@ -0,0 +1,22 @@
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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@strapi/strapi",
3
- "version": "4.4.0-beta.1",
3
+ "version": "4.4.0-beta.3",
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,18 +80,17 @@
80
80
  "dependencies": {
81
81
  "@koa/cors": "3.4.1",
82
82
  "@koa/router": "10.1.1",
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",
83
+ "@strapi/admin": "4.4.0-beta.3",
84
+ "@strapi/database": "4.4.0-beta.3",
85
+ "@strapi/generate-new": "4.4.0-beta.3",
86
+ "@strapi/generators": "4.4.0-beta.3",
87
+ "@strapi/logger": "4.4.0-beta.3",
88
+ "@strapi/plugin-content-manager": "4.4.0-beta.3",
89
+ "@strapi/plugin-content-type-builder": "4.4.0-beta.3",
90
+ "@strapi/plugin-email": "4.4.0-beta.3",
91
+ "@strapi/plugin-upload": "4.4.0-beta.3",
92
+ "@strapi/typescript-utils": "4.4.0-beta.3",
93
+ "@strapi/utils": "4.4.0-beta.3",
95
94
  "bcryptjs": "2.4.3",
96
95
  "boxen": "5.1.2",
97
96
  "chalk": "4.1.2",
@@ -140,5 +139,5 @@
140
139
  "node": ">=14.19.1 <=18.x.x",
141
140
  "npm": ">=6.0.0"
142
141
  },
143
- "gitHead": "cae16f7f259fa4473a55e8fea57839cda98f34ae"
142
+ "gitHead": "baad89e67844d972ef53a6f1b0839a361bf968c0"
144
143
  }
@@ -1,74 +0,0 @@
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;
@@ -1,5 +0,0 @@
1
- 'use strict';
2
-
3
- const permissions = require('@strapi/permissions');
4
-
5
- module.exports = ({ providers }) => permissions.engine.new({ providers });
@@ -1,148 +0,0 @@
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
- };
@@ -1,19 +0,0 @@
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
- };
@@ -1,19 +0,0 @@
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
- };
@@ -1,9 +0,0 @@
1
- 'use strict';
2
-
3
- const createActionProvider = require('./action');
4
- const createConditionProvider = require('./condition');
5
-
6
- module.exports = {
7
- createActionProvider,
8
- createConditionProvider,
9
- };