@strapi/strapi 4.0.0-next.6 → 4.0.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.
Files changed (166) hide show
  1. package/README.md +12 -12
  2. package/bin/strapi.js +41 -60
  3. package/lib/Strapi.js +234 -114
  4. package/lib/commands/build.js +16 -6
  5. package/lib/commands/console.js +1 -1
  6. package/lib/commands/content-types/list.js +22 -0
  7. package/lib/commands/develop.js +17 -18
  8. package/lib/commands/generate-template.js +4 -5
  9. package/lib/commands/hooks/list.js +22 -0
  10. package/lib/commands/middlewares/list.js +22 -0
  11. package/lib/commands/new.js +3 -1
  12. package/lib/commands/policies/list.js +22 -0
  13. package/lib/commands/routes/list.js +28 -0
  14. package/lib/commands/services/list.js +22 -0
  15. package/lib/commands/watchAdmin.js +18 -8
  16. package/lib/container.js +6 -6
  17. package/lib/core/app-configuration/config-loader.js +1 -37
  18. package/lib/core/app-configuration/index.js +6 -46
  19. package/lib/core/app-configuration/load-config-file.js +43 -0
  20. package/lib/core/bootstrap.js +5 -117
  21. package/lib/core/domain/component/index.js +24 -0
  22. package/lib/core/domain/component/validator.js +29 -0
  23. package/lib/core/domain/content-type/index.js +140 -0
  24. package/lib/core/domain/content-type/validator.js +64 -0
  25. package/lib/core/domain/module/index.js +108 -0
  26. package/lib/core/domain/module/validation.js +33 -0
  27. package/lib/core/loaders/admin.js +16 -0
  28. package/lib/core/loaders/apis.js +159 -0
  29. package/lib/core/{load-components.js → loaders/components.js} +5 -7
  30. package/lib/core/loaders/index.js +11 -0
  31. package/lib/core/loaders/middlewares.js +36 -0
  32. package/lib/core/loaders/plugins/get-enabled-plugins.js +116 -0
  33. package/lib/core/loaders/plugins/index.js +123 -0
  34. package/lib/core/loaders/policies.js +28 -0
  35. package/lib/core/loaders/src-index.js +39 -0
  36. package/lib/core/registries/apis.js +29 -0
  37. package/lib/core/{app-configuration/config-provider.js → registries/config.js} +4 -11
  38. package/lib/core/registries/content-types.js +97 -0
  39. package/lib/core/registries/controllers.d.ts +7 -0
  40. package/lib/core/registries/controllers.js +114 -0
  41. package/lib/core/registries/hooks.d.ts +20 -0
  42. package/lib/core/registries/hooks.js +87 -0
  43. package/lib/core/registries/middlewares.d.ts +5 -0
  44. package/lib/core/registries/middlewares.js +89 -0
  45. package/lib/core/registries/modules.js +44 -0
  46. package/lib/core/registries/plugins.js +28 -0
  47. package/lib/core/registries/policies.d.ts +9 -0
  48. package/lib/core/registries/policies.js +89 -0
  49. package/lib/core/registries/services.d.ts +7 -0
  50. package/lib/core/registries/services.js +114 -0
  51. package/lib/core/utils.js +35 -0
  52. package/lib/core-api/controller/collection-type.js +45 -26
  53. package/lib/core-api/controller/index.d.ts +25 -0
  54. package/lib/core-api/controller/index.js +33 -11
  55. package/lib/core-api/controller/single-type.js +29 -15
  56. package/lib/core-api/controller/transform.js +62 -6
  57. package/lib/core-api/routes/index.js +71 -0
  58. package/lib/core-api/service/collection-type.js +43 -21
  59. package/lib/core-api/service/index.d.ts +21 -0
  60. package/lib/core-api/service/index.js +8 -67
  61. package/lib/core-api/service/pagination.js +130 -0
  62. package/lib/core-api/service/single-type.js +17 -19
  63. package/lib/factories.d.ts +48 -0
  64. package/lib/factories.js +84 -0
  65. package/lib/index.d.ts +10 -31
  66. package/lib/index.js +5 -1
  67. package/lib/middlewares/body.js +33 -0
  68. package/lib/middlewares/compression.js +8 -0
  69. package/lib/middlewares/cors.js +58 -0
  70. package/lib/middlewares/errors.js +40 -0
  71. package/lib/middlewares/favicon.js +19 -0
  72. package/lib/middlewares/index.d.ts +5 -0
  73. package/lib/middlewares/index.js +30 -117
  74. package/lib/middlewares/ip.js +8 -0
  75. package/lib/middlewares/logger.js +27 -0
  76. package/lib/middlewares/powered-by.js +20 -0
  77. package/lib/middlewares/public/index.js +98 -73
  78. package/lib/middlewares/query.js +46 -0
  79. package/lib/middlewares/response-time.js +15 -0
  80. package/lib/middlewares/responses.js +19 -0
  81. package/lib/middlewares/security.js +51 -0
  82. package/lib/middlewares/session/index.js +6 -6
  83. package/lib/migrations/draft-publish.js +57 -0
  84. package/lib/services/auth/index.js +87 -0
  85. package/lib/services/core-store.js +64 -51
  86. package/lib/services/cron.js +54 -0
  87. package/lib/services/entity-service/attributes/index.js +31 -0
  88. package/lib/services/entity-service/attributes/transforms.js +20 -0
  89. package/lib/services/entity-service/components.js +39 -15
  90. package/lib/services/entity-service/index.d.ts +91 -0
  91. package/lib/services/entity-service/index.js +120 -59
  92. package/lib/services/entity-service/params.js +52 -94
  93. package/lib/services/entity-validator/index.js +76 -43
  94. package/lib/services/entity-validator/validators.js +129 -43
  95. package/lib/services/errors.js +77 -0
  96. package/lib/{core → services}/fs.js +10 -2
  97. package/lib/services/metrics/index.js +41 -38
  98. package/lib/services/metrics/sender.js +2 -2
  99. package/lib/services/server/admin-api.js +14 -0
  100. package/lib/services/server/api.js +36 -0
  101. package/lib/services/server/compose-endpoint.js +141 -0
  102. package/lib/services/server/content-api.js +16 -0
  103. package/lib/{server.js → services/server/http-server.js} +0 -0
  104. package/lib/services/server/index.js +127 -0
  105. package/lib/services/server/koa.js +64 -0
  106. package/lib/services/server/middleware.js +122 -0
  107. package/lib/services/server/policy.js +32 -0
  108. package/lib/services/server/register-middlewares.js +110 -0
  109. package/lib/services/server/register-routes.js +106 -0
  110. package/lib/services/server/routing.js +120 -0
  111. package/lib/services/utils/upload-files.js +1 -1
  112. package/lib/services/webhook-runner.js +1 -1
  113. package/lib/utils/ee.js +3 -3
  114. package/lib/utils/get-dirs.js +17 -0
  115. package/lib/utils/index.js +2 -0
  116. package/lib/utils/is-initialized.js +1 -1
  117. package/lib/utils/run-checks.js +0 -15
  118. package/lib/utils/signals.js +24 -0
  119. package/lib/utils/startup-logger.js +2 -2
  120. package/lib/utils/update-notifier/index.js +3 -2
  121. package/package.json +93 -96
  122. package/lib/commands/generate.js +0 -76
  123. package/lib/core/index.js +0 -17
  124. package/lib/core/load-apis.js +0 -20
  125. package/lib/core/load-extensions.js +0 -71
  126. package/lib/core/load-functions.js +0 -21
  127. package/lib/core/load-middlewares.js +0 -130
  128. package/lib/core/load-modules.js +0 -55
  129. package/lib/core/load-plugins.js +0 -68
  130. package/lib/core/load-policies.js +0 -36
  131. package/lib/core/walk.js +0 -27
  132. package/lib/core-api/index.js +0 -39
  133. package/lib/load/check-reserved-filename.js +0 -10
  134. package/lib/load/load-config-files.js +0 -22
  135. package/lib/load/require-file-parse.js +0 -15
  136. package/lib/middlewares/boom/defaults.json +0 -5
  137. package/lib/middlewares/boom/index.js +0 -147
  138. package/lib/middlewares/cors/index.js +0 -66
  139. package/lib/middlewares/cron/defaults.json +0 -5
  140. package/lib/middlewares/cron/index.js +0 -43
  141. package/lib/middlewares/favicon/defaults.json +0 -7
  142. package/lib/middlewares/favicon/index.js +0 -32
  143. package/lib/middlewares/gzip/defaults.json +0 -6
  144. package/lib/middlewares/gzip/index.js +0 -19
  145. package/lib/middlewares/helmet/defaults.json +0 -18
  146. package/lib/middlewares/helmet/index.js +0 -9
  147. package/lib/middlewares/ip/defaults.json +0 -7
  148. package/lib/middlewares/ip/index.js +0 -25
  149. package/lib/middlewares/language/defaults.json +0 -9
  150. package/lib/middlewares/language/index.js +0 -40
  151. package/lib/middlewares/logger/defaults.json +0 -5
  152. package/lib/middlewares/logger/index.js +0 -37
  153. package/lib/middlewares/parser/defaults.json +0 -11
  154. package/lib/middlewares/parser/index.js +0 -71
  155. package/lib/middlewares/poweredBy/defaults.json +0 -5
  156. package/lib/middlewares/poweredBy/index.js +0 -16
  157. package/lib/middlewares/public/defaults.json +0 -8
  158. package/lib/middlewares/responseTime/defaults.json +0 -5
  159. package/lib/middlewares/responseTime/index.js +0 -25
  160. package/lib/middlewares/responses/defaults.json +0 -5
  161. package/lib/middlewares/responses/index.js +0 -18
  162. package/lib/middlewares/router/defaults.json +0 -7
  163. package/lib/middlewares/router/index.js +0 -64
  164. package/lib/middlewares/router/utils/composeEndpoint.js +0 -25
  165. package/lib/middlewares/router/utils/routerChecker.js +0 -92
  166. package/lib/utils/get-prefixed-dependencies.js +0 -7
@@ -0,0 +1,54 @@
1
+ 'use strict';
2
+
3
+ const { Job } = require('node-schedule');
4
+ const { isFunction } = require('lodash/fp');
5
+
6
+ const createCronService = () => {
7
+ let jobsSpecs = [];
8
+
9
+ return {
10
+ add(tasks = {}) {
11
+ for (const taskExpression in tasks) {
12
+ const taskValue = tasks[taskExpression];
13
+
14
+ let fn;
15
+ let options;
16
+ if (isFunction(taskValue)) {
17
+ fn = taskValue.bind(tasks);
18
+ options = taskExpression;
19
+ } else if (isFunction(taskValue.task)) {
20
+ fn = taskValue.task.bind(taskValue);
21
+ options = taskValue.options;
22
+ } else {
23
+ throw new Error(
24
+ `Could not schedule a cron job for "${taskExpression}": no function found.`
25
+ );
26
+ }
27
+
28
+ const fnWithStrapi = (...args) => fn({ strapi }, ...args);
29
+
30
+ const job = new Job(null, fnWithStrapi);
31
+ jobsSpecs.push({ job, options });
32
+ }
33
+ return this;
34
+ },
35
+ start() {
36
+ if (!strapi.config.get('server.cron.enabled')) {
37
+ return;
38
+ }
39
+ jobsSpecs.forEach(({ job, options }) => job.schedule(options));
40
+ return this;
41
+ },
42
+ stop() {
43
+ jobsSpecs.forEach(({ job }) => job.cancel());
44
+ return this;
45
+ },
46
+ destroy() {
47
+ this.stop();
48
+ jobsSpecs = [];
49
+ return this;
50
+ },
51
+ };
52
+ };
53
+
54
+ module.exports = createCronService;
@@ -0,0 +1,31 @@
1
+ 'use strict';
2
+
3
+ const transforms = require('./transforms');
4
+
5
+ const applyTransforms = (data, context) => {
6
+ const { contentType } = context;
7
+
8
+ const entries = Object.entries(data);
9
+
10
+ for (const [attributeName, value] of entries) {
11
+ const attribute = contentType.attributes[attributeName];
12
+
13
+ if (!attribute) {
14
+ continue;
15
+ }
16
+
17
+ const transform = transforms[attribute.type];
18
+
19
+ if (transform) {
20
+ const attributeContext = { ...context, attributeName, attribute };
21
+
22
+ data[attributeName] = transform(value, attributeContext);
23
+ }
24
+ }
25
+
26
+ return data;
27
+ };
28
+
29
+ module.exports = {
30
+ applyTransforms,
31
+ };
@@ -0,0 +1,20 @@
1
+ 'use strict';
2
+
3
+ const { getOr, toNumber, isString, isBuffer } = require('lodash/fp');
4
+ const bcrypt = require('bcryptjs');
5
+
6
+ const transforms = {
7
+ password(value, context) {
8
+ const { attribute } = context;
9
+
10
+ if (!isString(value) && !isBuffer(value)) {
11
+ return value;
12
+ }
13
+
14
+ const rounds = toNumber(getOr(10, 'encryption.rounds', attribute));
15
+
16
+ return bcrypt.hashSync(value, rounds);
17
+ },
18
+ };
19
+
20
+ module.exports = transforms;
@@ -1,9 +1,10 @@
1
1
  'use strict';
2
2
 
3
3
  const _ = require('lodash');
4
- const { has, prop, omit } = require('lodash/fp');
4
+ const { has, prop, omit, toString } = require('lodash/fp');
5
5
 
6
6
  const { contentTypes: contentTypesUtils } = require('@strapi/utils');
7
+ const { ApplicationError } = require('@strapi/utils').errors;
7
8
 
8
9
  const omitComponentData = (contentType, data) => {
9
10
  const { attributes } = contentType;
@@ -131,11 +132,26 @@ const updateComponents = async (uid, entityToUpdate, data) => {
131
132
  componentValue.map(value => updateOrCreateComponent(componentUID, value))
132
133
  );
133
134
 
134
- // TODO: add order
135
- componentBody[attributeName] = components.filter(_.negate(_.isNil)).map(({ id }) => id);
135
+ componentBody[attributeName] = components.filter(_.negate(_.isNil)).map(({ id }, idx) => {
136
+ return {
137
+ id,
138
+ __pivot: {
139
+ order: idx + 1,
140
+ field: attributeName,
141
+ component_type: componentUID,
142
+ },
143
+ };
144
+ });
136
145
  } else {
137
146
  const component = await updateOrCreateComponent(componentUID, componentValue);
138
- componentBody[attributeName] = component && component.id;
147
+ componentBody[attributeName] = component && {
148
+ id: component.id,
149
+ __pivot: {
150
+ order: 1,
151
+ field: attributeName,
152
+ component_type: componentUID,
153
+ },
154
+ };
139
155
  }
140
156
 
141
157
  continue;
@@ -151,9 +167,17 @@ const updateComponents = async (uid, entityToUpdate, data) => {
151
167
  }
152
168
 
153
169
  componentBody[attributeName] = await Promise.all(
154
- dynamiczoneValues.map(async value => {
170
+ dynamiczoneValues.map(async (value, idx) => {
155
171
  const { id } = await updateOrCreateComponent(value.__component, value);
156
- return { id, __component: value.__component };
172
+
173
+ return {
174
+ id,
175
+ __component: value.__component,
176
+ __pivot: {
177
+ order: idx + 1,
178
+ field: attributeName,
179
+ },
180
+ };
157
181
  })
158
182
  );
159
183
 
@@ -175,19 +199,19 @@ const deleteOldComponents = async (
175
199
 
176
200
  const idsToKeep = _.castArray(componentValue)
177
201
  .filter(has('id'))
178
- .map(prop('id'));
202
+ .map(prop('id'))
203
+ .map(toString);
179
204
 
180
205
  const allIds = _.castArray(previousValue)
181
206
  .filter(has('id'))
182
- .map(prop('id'));
207
+ .map(prop('id'))
208
+ .map(toString);
183
209
 
184
210
  idsToKeep.forEach(id => {
185
211
  if (!allIds.includes(id)) {
186
- const err = new Error(
212
+ throw new ApplicationError(
187
213
  `Some of the provided components in ${attributeName} are not related to the entity`
188
214
  );
189
- err.status = 400;
190
- throw err;
191
215
  }
192
216
  });
193
217
 
@@ -206,14 +230,14 @@ const deleteOldDZComponents = async (uid, entityToUpdate, attributeName, dynamic
206
230
  const idsToKeep = _.castArray(dynamiczoneValues)
207
231
  .filter(has('id'))
208
232
  .map(({ id, __component }) => ({
209
- id,
233
+ id: toString(id),
210
234
  __component,
211
235
  }));
212
236
 
213
237
  const allIds = _.castArray(previousValue)
214
238
  .filter(has('id'))
215
239
  .map(({ id, __component }) => ({
216
- id,
240
+ id: toString(id),
217
241
  __component,
218
242
  }));
219
243
 
@@ -293,7 +317,7 @@ const createComponent = async (uid, data) => {
293
317
 
294
318
  const componentData = await createComponents(uid, data);
295
319
 
296
- return await strapi.query(uid).create({
320
+ return strapi.query(uid).create({
297
321
  data: Object.assign(omitComponentData(model, data), componentData),
298
322
  });
299
323
  };
@@ -304,7 +328,7 @@ const updateComponent = async (uid, componentToUpdate, data) => {
304
328
 
305
329
  const componentData = await updateComponents(uid, componentToUpdate, data);
306
330
 
307
- return await strapi.query(uid).update({
331
+ return strapi.query(uid).update({
308
332
  where: {
309
333
  id: componentToUpdate.id,
310
334
  },
@@ -0,0 +1,91 @@
1
+ import { Database } from '@strapi/database';
2
+ import { Strapi } from '../../';
3
+
4
+ type ID = number | string;
5
+
6
+ type EntityServiceAction =
7
+ | 'findMany'
8
+ | 'findPage'
9
+ | 'findWithRelationCounts'
10
+ | 'findOne'
11
+ | 'count'
12
+ | 'create'
13
+ | 'update'
14
+ | 'delete';
15
+
16
+ type PaginationInfo = {
17
+ page: number;
18
+ pageSize: number;
19
+ pageCount: number;
20
+ total: number;
21
+ };
22
+
23
+ type Params<T> = {
24
+ fields?: (keyof T)[];
25
+ filters?: any;
26
+ _q?: string;
27
+ populate?: any;
28
+ sort?: any;
29
+ start?: number;
30
+ limit?: number;
31
+ page?: number;
32
+ pageSize?: number;
33
+ publicationState?: string;
34
+ data?: any;
35
+ files?: any;
36
+ };
37
+
38
+ export interface EntityService {
39
+ uploadFiles<K extends keyof AllTypes, T extends AllTypes[K]>(uid: K, entity, files);
40
+ wrapParams<K extends keyof AllTypes, T extends AllTypes[K]>(
41
+ params: Params<T>,
42
+ { uid: K, action: EntityServiceAction }
43
+ );
44
+
45
+ findMany<K extends keyof AllTypes, T extends AllTypes[K]>(
46
+ uid: K,
47
+ params: Params<T>
48
+ ): Promise<T[]>;
49
+ findPage<K extends keyof AllTypes, T extends AllTypes[K]>(
50
+ uid: K,
51
+ params: Params<T>
52
+ ): Promise<{
53
+ results: T[];
54
+ pagination: PaginationInfo;
55
+ }>;
56
+
57
+ findWithRelationCounts<K extends keyof AllTypes, T extends AllTypes[K]>(
58
+ uid: K,
59
+ params: Params<T>
60
+ ): Promise<{
61
+ results: T[];
62
+ pagination: PaginationInfo;
63
+ }>;
64
+
65
+ findOne<K extends keyof AllTypes, T extends AllTypes[K]>(
66
+ uid: K,
67
+ entityId: ID,
68
+ params: Params<T>
69
+ ): Promise<T>;
70
+
71
+ count<K extends keyof AllTypes, T extends AllTypes[K]>(uid: K, params: Params<T>): Promise<any>;
72
+ create<K extends keyof AllTypes, T extends AllTypes[K]>(uid: K, params: Params<T>): Promise<any>;
73
+ update<K extends keyof AllTypes, T extends AllTypes[K]>(
74
+ uid: K,
75
+ entityId: ID,
76
+ params: Params<T>
77
+ ): Promise<any>;
78
+ delete<K extends keyof AllTypes, T extends AllTypes[K]>(
79
+ uid: K,
80
+ entityId: ID,
81
+ params: Params<T>
82
+ ): Promise<any>;
83
+ }
84
+
85
+ export default function(opts: {
86
+ strapi: Strapi;
87
+ db: Database;
88
+ // TODO: define types
89
+ eventHub: any;
90
+ entityValidator: any;
91
+ }): EntityService;
@@ -1,12 +1,18 @@
1
1
  'use strict';
2
2
 
3
+ const _ = require('lodash');
3
4
  const delegate = require('delegates');
4
5
  const {
5
- sanitizeEntity,
6
+ InvalidTimeError,
7
+ InvalidDateError,
8
+ InvalidDateTimeError,
9
+ } = require('@strapi/database').errors;
10
+ const {
6
11
  webhook: webhookUtils,
7
12
  contentTypes: contentTypesUtils,
8
- relations: relationsUtils,
13
+ sanitize,
9
14
  } = require('@strapi/utils');
15
+ const { ValidationError } = require('@strapi/utils').errors;
10
16
  const uploadFiles = require('../utils/upload-files');
11
17
 
12
18
  const {
@@ -16,12 +22,21 @@ const {
16
22
  deleteComponents,
17
23
  } = require('./components');
18
24
  const { transformParamsToQuery, pickSelectionParams } = require('./params');
19
-
20
- const { MANY_RELATIONS } = relationsUtils.constants;
25
+ const { applyTransforms } = require('./attributes');
21
26
 
22
27
  // TODO: those should be strapi events used by the webhooks not the other way arround
23
28
  const { ENTRY_CREATE, ENTRY_UPDATE, ENTRY_DELETE } = webhookUtils.webhookEvents;
24
29
 
30
+ const databaseErrorsToTransform = [InvalidTimeError, InvalidDateTimeError, InvalidDateError];
31
+
32
+ const creationPipeline = (data, context) => {
33
+ return applyTransforms(data, context);
34
+ };
35
+
36
+ const updatePipeline = (data, context) => {
37
+ return applyTransforms(data, context);
38
+ };
39
+
25
40
  module.exports = ctx => {
26
41
  const implementation = createDefaultImplementation(ctx);
27
42
 
@@ -42,31 +57,57 @@ module.exports = ctx => {
42
57
  // delegate every method in implementation
43
58
  Object.keys(service.implementation).forEach(key => delegator.method(key));
44
59
 
60
+ // wrap methods to handle Database Errors
61
+ service.decorate(oldService => {
62
+ const newService = _.mapValues(
63
+ oldService,
64
+ (method, methodName) =>
65
+ async function(...args) {
66
+ try {
67
+ return await oldService[methodName].call(this, ...args);
68
+ } catch (error) {
69
+ if (
70
+ databaseErrorsToTransform.some(errorToTransform => error instanceof errorToTransform)
71
+ ) {
72
+ throw new ValidationError(error.message);
73
+ }
74
+ throw error;
75
+ }
76
+ }
77
+ );
78
+
79
+ return newService;
80
+ });
81
+
45
82
  return service;
46
83
  };
47
84
 
85
+ /**
86
+ * @type {import('.').default}
87
+ */
48
88
  const createDefaultImplementation = ({ strapi, db, eventHub, entityValidator }) => ({
49
89
  uploadFiles,
50
90
 
51
- async wrapOptions(options = {}) {
91
+ async wrapParams(options = {}) {
52
92
  return options;
53
93
  },
54
94
 
55
- emitEvent(uid, event, entity) {
95
+ async emitEvent(uid, event, entity) {
56
96
  const model = strapi.getModel(uid);
97
+ const sanitizedEntity = await sanitize.sanitizers.defaultSanitizeOutput(model, entity);
57
98
 
58
99
  eventHub.emit(event, {
59
100
  model: model.modelName,
60
- entry: sanitizeEntity(entity, { model }),
101
+ entry: sanitizedEntity,
61
102
  });
62
103
  },
63
104
 
64
- async find(uid, opts) {
105
+ async findMany(uid, opts) {
65
106
  const { kind } = strapi.getModel(uid);
66
107
 
67
- const { params } = await this.wrapOptions(opts, { uid, action: 'find' });
108
+ const wrappedParams = await this.wrapParams(opts, { uid, action: 'findMany' });
68
109
 
69
- const query = transformParamsToQuery(uid, params);
110
+ const query = transformParamsToQuery(uid, wrappedParams);
70
111
 
71
112
  if (kind === 'singleType') {
72
113
  return db.query(uid).findOne(query);
@@ -76,41 +117,20 @@ const createDefaultImplementation = ({ strapi, db, eventHub, entityValidator })
76
117
  },
77
118
 
78
119
  async findPage(uid, opts) {
79
- const { params } = await this.wrapOptions(opts, { uid, action: 'findPage' });
120
+ const wrappedParams = await this.wrapParams(opts, { uid, action: 'findPage' });
80
121
 
81
- const query = transformParamsToQuery(uid, params);
122
+ const query = transformParamsToQuery(uid, wrappedParams);
82
123
 
83
124
  return db.query(uid).findPage(query);
84
125
  },
85
126
 
127
+ // TODO: streamline the logic based on the populate option
86
128
  async findWithRelationCounts(uid, opts) {
87
- const model = strapi.getModel(uid);
88
-
89
- const { params } = await this.wrapOptions(opts, { uid, action: 'findWithRelationCounts' });
129
+ const wrappedParams = await this.wrapParams(opts, { uid, action: 'findWithRelationCounts' });
90
130
 
91
- const query = transformParamsToQuery(uid, params);
131
+ const query = transformParamsToQuery(uid, wrappedParams);
92
132
 
93
- const { attributes } = model;
94
-
95
- const populate = (query.populate || []).reduce((populate, attributeName) => {
96
- const attribute = attributes[attributeName];
97
-
98
- if (
99
- MANY_RELATIONS.includes(attribute.relation) &&
100
- contentTypesUtils.isVisibleAttribute(model, attributeName)
101
- ) {
102
- populate[attributeName] = { count: true };
103
- } else {
104
- populate[attributeName] = true;
105
- }
106
-
107
- return populate;
108
- }, {});
109
-
110
- const { results, pagination } = await db.query(uid).findPage({
111
- ...query,
112
- populate,
113
- });
133
+ const { results, pagination } = await db.query(uid).findPage(query);
114
134
 
115
135
  return {
116
136
  results,
@@ -119,23 +139,24 @@ const createDefaultImplementation = ({ strapi, db, eventHub, entityValidator })
119
139
  },
120
140
 
121
141
  async findOne(uid, entityId, opts) {
122
- const { params } = await this.wrapOptions(opts, { uid, action: 'findOne' });
142
+ const wrappedParams = await this.wrapParams(opts, { uid, action: 'findOne' });
123
143
 
124
- const query = transformParamsToQuery(uid, pickSelectionParams(params));
144
+ const query = transformParamsToQuery(uid, pickSelectionParams(wrappedParams));
125
145
 
126
146
  return db.query(uid).findOne({ ...query, where: { id: entityId } });
127
147
  },
128
148
 
129
149
  async count(uid, opts) {
130
- const { params } = await this.wrapOptions(opts, { uid, action: 'count' });
150
+ const wrappedParams = await this.wrapParams(opts, { uid, action: 'count' });
131
151
 
132
- const query = transformParamsToQuery(uid, params);
152
+ const query = transformParamsToQuery(uid, wrappedParams);
133
153
 
134
154
  return db.query(uid).count(query);
135
155
  },
136
156
 
137
157
  async create(uid, opts) {
138
- const { params, data, files } = await this.wrapOptions(opts, { uid, action: 'create' });
158
+ const wrappedParams = await this.wrapParams(opts, { uid, action: 'create' });
159
+ const { data, files } = wrappedParams;
139
160
 
140
161
  const model = strapi.getModel(uid);
141
162
 
@@ -143,30 +164,33 @@ const createDefaultImplementation = ({ strapi, db, eventHub, entityValidator })
143
164
  const validData = await entityValidator.validateEntityCreation(model, data, { isDraft });
144
165
 
145
166
  // select / populate
146
- const query = transformParamsToQuery(uid, pickSelectionParams(params));
167
+ const query = transformParamsToQuery(uid, pickSelectionParams(wrappedParams));
147
168
 
148
169
  // TODO: wrap into transaction
149
170
  const componentData = await createComponents(uid, validData);
150
171
 
151
172
  let entity = await db.query(uid).create({
152
173
  ...query,
153
- data: Object.assign(omitComponentData(model, validData), componentData),
174
+ data: creationPipeline(Object.assign(omitComponentData(model, validData), componentData), {
175
+ contentType: model,
176
+ }),
154
177
  });
155
178
 
156
179
  // TODO: upload the files then set the links in the entity like with compo to avoid making too many queries
157
180
  // FIXME: upload in components
158
181
  if (files && Object.keys(files).length > 0) {
159
182
  await this.uploadFiles(uid, entity, files);
160
- entity = await this.findOne(uid, entity.id, { params });
183
+ entity = await this.findOne(uid, entity.id, wrappedParams);
161
184
  }
162
185
 
163
- this.emitEvent(uid, ENTRY_CREATE, entity);
186
+ await this.emitEvent(uid, ENTRY_CREATE, entity);
164
187
 
165
188
  return entity;
166
189
  },
167
190
 
168
191
  async update(uid, entityId, opts) {
169
- const { params, data, files } = await this.wrapOptions(opts, { uid, action: 'update' });
192
+ const wrappedParams = await this.wrapParams(opts, { uid, action: 'update' });
193
+ const { data, files } = wrappedParams;
170
194
 
171
195
  const model = strapi.getModel(uid);
172
196
 
@@ -178,11 +202,16 @@ const createDefaultImplementation = ({ strapi, db, eventHub, entityValidator })
178
202
 
179
203
  const isDraft = contentTypesUtils.isDraft(entityToUpdate, model);
180
204
 
181
- const validData = await entityValidator.validateEntityUpdate(model, data, {
182
- isDraft,
183
- });
205
+ const validData = await entityValidator.validateEntityUpdate(
206
+ model,
207
+ data,
208
+ {
209
+ isDraft,
210
+ },
211
+ entityToUpdate
212
+ );
184
213
 
185
- const query = transformParamsToQuery(uid, pickSelectionParams(params));
214
+ const query = transformParamsToQuery(uid, pickSelectionParams(wrappedParams));
186
215
 
187
216
  // TODO: wrap in transaction
188
217
  const componentData = await updateComponents(uid, entityToUpdate, validData);
@@ -190,26 +219,28 @@ const createDefaultImplementation = ({ strapi, db, eventHub, entityValidator })
190
219
  let entity = await db.query(uid).update({
191
220
  ...query,
192
221
  where: { id: entityId },
193
- data: Object.assign(omitComponentData(model, validData), componentData),
222
+ data: updatePipeline(Object.assign(omitComponentData(model, validData), componentData), {
223
+ contentType: model,
224
+ }),
194
225
  });
195
226
 
196
227
  // TODO: upload the files then set the links in the entity like with compo to avoid making too many queries
197
228
  // FIXME: upload in components
198
229
  if (files && Object.keys(files).length > 0) {
199
230
  await this.uploadFiles(uid, entity, files);
200
- entity = await this.findOne(uid, entity.id, { params });
231
+ entity = await this.findOne(uid, entity.id, wrappedParams);
201
232
  }
202
233
 
203
- this.emitEvent(uid, ENTRY_UPDATE, entity);
234
+ await this.emitEvent(uid, ENTRY_UPDATE, entity);
204
235
 
205
236
  return entity;
206
237
  },
207
238
 
208
239
  async delete(uid, entityId, opts) {
209
- const { params } = await this.wrapOptions(opts, { uid, action: 'delete' });
240
+ const wrappedParams = await this.wrapParams(opts, { uid, action: 'delete' });
210
241
 
211
242
  // select / populate
212
- const query = transformParamsToQuery(uid, pickSelectionParams(params));
243
+ const query = transformParamsToQuery(uid, pickSelectionParams(wrappedParams));
213
244
 
214
245
  const entityToDelete = await db.query(uid).findOne({
215
246
  ...query,
@@ -223,17 +254,47 @@ const createDefaultImplementation = ({ strapi, db, eventHub, entityValidator })
223
254
  await deleteComponents(uid, entityToDelete);
224
255
  await db.query(uid).delete({ where: { id: entityToDelete.id } });
225
256
 
226
- this.emitEvent(uid, ENTRY_DELETE, entityToDelete);
257
+ await this.emitEvent(uid, ENTRY_DELETE, entityToDelete);
227
258
 
228
259
  return entityToDelete;
229
260
  },
230
261
 
262
+ // FIXME: used only for the CM to be removed
231
263
  async deleteMany(uid, opts) {
232
- const { params } = await this.wrapOptions(opts, { uid, action: 'delete' });
264
+ const wrappedParams = await this.wrapParams(opts, { uid, action: 'delete' });
233
265
 
234
266
  // select / populate
235
- const query = transformParamsToQuery(uid, params);
267
+ const query = transformParamsToQuery(uid, wrappedParams);
236
268
 
237
269
  return db.query(uid).deleteMany(query);
238
270
  },
271
+
272
+ load(uid, entity, field, params = {}) {
273
+ const { attributes } = strapi.getModel(uid);
274
+
275
+ const attribute = attributes[field];
276
+
277
+ const loadParams = {};
278
+
279
+ switch (attribute.type) {
280
+ case 'relation': {
281
+ Object.assign(loadParams, transformParamsToQuery(attribute.target, params));
282
+ break;
283
+ }
284
+ case 'component': {
285
+ Object.assign(loadParams, transformParamsToQuery(attribute.component, params));
286
+ break;
287
+ }
288
+ case 'dynamiczone': {
289
+ Object.assign(loadParams, transformParamsToQuery(null, params));
290
+ break;
291
+ }
292
+ case 'media': {
293
+ Object.assign(loadParams, transformParamsToQuery('plugin::upload.file', params));
294
+ break;
295
+ }
296
+ }
297
+
298
+ return db.query(uid).load(entity, field, loadParams);
299
+ },
239
300
  });