@strapi/strapi 4.0.0-next.9 → 4.0.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.
Files changed (142) hide show
  1. package/README.md +14 -14
  2. package/bin/strapi.js +37 -6
  3. package/lib/Strapi.js +140 -72
  4. package/lib/commands/build.js +21 -11
  5. package/lib/commands/console.js +1 -1
  6. package/lib/commands/content-types/list.js +22 -0
  7. package/lib/commands/controllers/list.js +22 -0
  8. package/lib/commands/develop.js +24 -27
  9. package/lib/commands/generate-template.js +4 -5
  10. package/lib/commands/hooks/list.js +22 -0
  11. package/lib/commands/middlewares/list.js +22 -0
  12. package/lib/commands/new.js +3 -1
  13. package/lib/commands/policies/list.js +22 -0
  14. package/lib/commands/routes/list.js +28 -0
  15. package/lib/commands/services/list.js +22 -0
  16. package/lib/commands/watchAdmin.js +18 -9
  17. package/lib/core/app-configuration/index.js +3 -19
  18. package/lib/core/bootstrap.js +3 -34
  19. package/lib/core/domain/content-type/index.js +3 -7
  20. package/lib/core/domain/module/index.js +8 -6
  21. package/lib/core/domain/module/validation.js +1 -4
  22. package/lib/core/loaders/admin.js +2 -2
  23. package/lib/core/loaders/apis.js +7 -7
  24. package/lib/core/loaders/components.js +3 -5
  25. package/lib/core/loaders/index.js +1 -0
  26. package/lib/core/loaders/middlewares.js +23 -123
  27. package/lib/core/loaders/plugins/get-enabled-plugins.js +55 -19
  28. package/lib/core/loaders/plugins/get-user-plugins-config.js +37 -0
  29. package/lib/core/loaders/plugins/index.js +30 -16
  30. package/lib/core/loaders/policies.js +1 -1
  31. package/lib/core/loaders/src-index.js +39 -0
  32. package/lib/core/registries/apis.js +2 -16
  33. package/lib/core/registries/content-types.js +50 -6
  34. package/lib/core/registries/controllers.d.ts +7 -0
  35. package/lib/core/registries/controllers.js +74 -3
  36. package/lib/core/registries/hooks.d.ts +20 -0
  37. package/lib/core/registries/hooks.js +87 -0
  38. package/lib/core/registries/middlewares.d.ts +5 -0
  39. package/lib/core/registries/middlewares.js +61 -2
  40. package/lib/core/registries/modules.js +3 -3
  41. package/lib/core/registries/plugins.js +2 -2
  42. package/lib/core/registries/policies.d.ts +9 -0
  43. package/lib/core/registries/policies.js +57 -6
  44. package/lib/core/registries/services.d.ts +7 -0
  45. package/lib/core/registries/services.js +71 -15
  46. package/lib/core-api/controller/collection-type.js +38 -11
  47. package/lib/core-api/controller/index.d.ts +25 -0
  48. package/lib/core-api/controller/index.js +30 -11
  49. package/lib/core-api/controller/single-type.js +26 -7
  50. package/lib/core-api/controller/transform.js +28 -3
  51. package/lib/core-api/routes/index.js +71 -0
  52. package/lib/core-api/service/collection-type.js +22 -27
  53. package/lib/core-api/service/index.d.ts +21 -0
  54. package/lib/core-api/service/index.js +9 -19
  55. package/lib/core-api/service/pagination.js +16 -16
  56. package/lib/core-api/service/single-type.js +17 -20
  57. package/lib/factories.d.ts +48 -0
  58. package/lib/factories.js +84 -0
  59. package/lib/index.d.ts +10 -31
  60. package/lib/index.js +5 -1
  61. package/lib/middlewares/body.js +33 -0
  62. package/lib/middlewares/compression.js +8 -0
  63. package/lib/middlewares/cors.js +58 -0
  64. package/lib/middlewares/errors.js +40 -0
  65. package/lib/middlewares/favicon.js +19 -0
  66. package/lib/middlewares/index.d.ts +5 -0
  67. package/lib/middlewares/index.js +30 -116
  68. package/lib/middlewares/ip.js +8 -0
  69. package/lib/middlewares/logger.js +27 -0
  70. package/lib/middlewares/powered-by.js +20 -0
  71. package/lib/middlewares/public/index.js +72 -77
  72. package/lib/middlewares/query.js +46 -0
  73. package/lib/middlewares/response-time.js +15 -0
  74. package/lib/middlewares/responses.js +19 -0
  75. package/lib/middlewares/security.js +51 -0
  76. package/lib/middlewares/session/index.js +6 -6
  77. package/lib/migrations/draft-publish.js +57 -0
  78. package/lib/services/auth/index.js +87 -0
  79. package/lib/services/core-store.js +64 -49
  80. package/lib/services/cron.js +54 -0
  81. package/lib/services/entity-service/attributes/index.js +31 -0
  82. package/lib/services/entity-service/attributes/transforms.js +20 -0
  83. package/lib/services/entity-service/components.js +39 -15
  84. package/lib/services/entity-service/index.d.ts +91 -0
  85. package/lib/services/entity-service/index.js +118 -60
  86. package/lib/services/entity-service/params.js +48 -81
  87. package/lib/services/entity-validator/index.js +76 -43
  88. package/lib/services/entity-validator/validators.js +131 -43
  89. package/lib/services/errors.js +77 -0
  90. package/lib/services/fs.js +1 -1
  91. package/lib/services/metrics/index.js +38 -36
  92. package/lib/services/server/admin-api.js +14 -0
  93. package/lib/services/server/api.js +36 -0
  94. package/lib/services/server/compose-endpoint.js +141 -0
  95. package/lib/services/server/content-api.js +16 -0
  96. package/lib/{server.js → services/server/http-server.js} +0 -0
  97. package/lib/services/server/index.js +127 -0
  98. package/lib/services/server/koa.js +64 -0
  99. package/lib/services/server/middleware.js +122 -0
  100. package/lib/services/server/policy.js +32 -0
  101. package/lib/services/server/register-middlewares.js +110 -0
  102. package/lib/services/server/register-routes.js +106 -0
  103. package/lib/services/server/routing.js +120 -0
  104. package/lib/services/webhook-runner.js +1 -1
  105. package/lib/utils/ee.js +3 -3
  106. package/lib/utils/get-dirs.js +17 -0
  107. package/lib/utils/index.js +2 -0
  108. package/lib/utils/signals.js +24 -0
  109. package/lib/utils/update-notifier/index.js +2 -1
  110. package/package.json +94 -93
  111. package/lib/core/app-configuration/load-functions.js +0 -28
  112. package/lib/core-api/index.js +0 -39
  113. package/lib/middlewares/boom/defaults.json +0 -5
  114. package/lib/middlewares/boom/index.js +0 -147
  115. package/lib/middlewares/cors/index.js +0 -66
  116. package/lib/middlewares/cron/defaults.json +0 -5
  117. package/lib/middlewares/cron/index.js +0 -43
  118. package/lib/middlewares/favicon/defaults.json +0 -7
  119. package/lib/middlewares/favicon/index.js +0 -32
  120. package/lib/middlewares/gzip/defaults.json +0 -6
  121. package/lib/middlewares/gzip/index.js +0 -19
  122. package/lib/middlewares/helmet/defaults.json +0 -18
  123. package/lib/middlewares/helmet/index.js +0 -9
  124. package/lib/middlewares/ip/defaults.json +0 -7
  125. package/lib/middlewares/ip/index.js +0 -25
  126. package/lib/middlewares/language/defaults.json +0 -9
  127. package/lib/middlewares/language/index.js +0 -40
  128. package/lib/middlewares/logger/defaults.json +0 -5
  129. package/lib/middlewares/logger/index.js +0 -37
  130. package/lib/middlewares/parser/defaults.json +0 -11
  131. package/lib/middlewares/parser/index.js +0 -72
  132. package/lib/middlewares/poweredBy/defaults.json +0 -5
  133. package/lib/middlewares/poweredBy/index.js +0 -16
  134. package/lib/middlewares/public/defaults.json +0 -8
  135. package/lib/middlewares/responseTime/defaults.json +0 -5
  136. package/lib/middlewares/responseTime/index.js +0 -25
  137. package/lib/middlewares/responses/defaults.json +0 -5
  138. package/lib/middlewares/responses/index.js +0 -18
  139. package/lib/middlewares/router/defaults.json +0 -7
  140. package/lib/middlewares/router/index.js +0 -72
  141. package/lib/middlewares/router/utils/compose-endpoint.js +0 -169
  142. package/lib/utils/get-prefixed-dependencies.js +0 -7
@@ -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,32 +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
- // TODO: rename to findMany
65
- async find(uid, opts) {
105
+ async findMany(uid, opts) {
66
106
  const { kind } = strapi.getModel(uid);
67
107
 
68
- const { params } = await this.wrapOptions(opts, { uid, action: 'find' });
108
+ const wrappedParams = await this.wrapParams(opts, { uid, action: 'findMany' });
69
109
 
70
- const query = transformParamsToQuery(uid, params);
110
+ const query = transformParamsToQuery(uid, wrappedParams);
71
111
 
72
112
  if (kind === 'singleType') {
73
113
  return db.query(uid).findOne(query);
@@ -77,42 +117,20 @@ const createDefaultImplementation = ({ strapi, db, eventHub, entityValidator })
77
117
  },
78
118
 
79
119
  async findPage(uid, opts) {
80
- const { params } = await this.wrapOptions(opts, { uid, action: 'findPage' });
120
+ const wrappedParams = await this.wrapParams(opts, { uid, action: 'findPage' });
81
121
 
82
- const query = transformParamsToQuery(uid, params);
122
+ const query = transformParamsToQuery(uid, wrappedParams);
83
123
 
84
124
  return db.query(uid).findPage(query);
85
125
  },
86
126
 
87
127
  // TODO: streamline the logic based on the populate option
88
128
  async findWithRelationCounts(uid, opts) {
89
- const model = strapi.getModel(uid);
90
-
91
- const { params } = await this.wrapOptions(opts, { uid, action: 'findWithRelationCounts' });
129
+ const wrappedParams = await this.wrapParams(opts, { uid, action: 'findWithRelationCounts' });
92
130
 
93
- const query = transformParamsToQuery(uid, params);
131
+ const query = transformParamsToQuery(uid, wrappedParams);
94
132
 
95
- const { attributes } = model;
96
-
97
- const populate = (query.populate || []).reduce((populate, attributeName) => {
98
- const attribute = attributes[attributeName];
99
-
100
- if (
101
- MANY_RELATIONS.includes(attribute.relation) &&
102
- contentTypesUtils.isVisibleAttribute(model, attributeName)
103
- ) {
104
- populate[attributeName] = { count: true };
105
- } else {
106
- populate[attributeName] = true;
107
- }
108
-
109
- return populate;
110
- }, {});
111
-
112
- const { results, pagination } = await db.query(uid).findPage({
113
- ...query,
114
- populate,
115
- });
133
+ const { results, pagination } = await db.query(uid).findPage(query);
116
134
 
117
135
  return {
118
136
  results,
@@ -121,23 +139,24 @@ const createDefaultImplementation = ({ strapi, db, eventHub, entityValidator })
121
139
  },
122
140
 
123
141
  async findOne(uid, entityId, opts) {
124
- const { params } = await this.wrapOptions(opts, { uid, action: 'findOne' });
142
+ const wrappedParams = await this.wrapParams(opts, { uid, action: 'findOne' });
125
143
 
126
- const query = transformParamsToQuery(uid, pickSelectionParams(params));
144
+ const query = transformParamsToQuery(uid, pickSelectionParams(wrappedParams));
127
145
 
128
146
  return db.query(uid).findOne({ ...query, where: { id: entityId } });
129
147
  },
130
148
 
131
149
  async count(uid, opts) {
132
- const { params } = await this.wrapOptions(opts, { uid, action: 'count' });
150
+ const wrappedParams = await this.wrapParams(opts, { uid, action: 'count' });
133
151
 
134
- const query = transformParamsToQuery(uid, params);
152
+ const query = transformParamsToQuery(uid, wrappedParams);
135
153
 
136
154
  return db.query(uid).count(query);
137
155
  },
138
156
 
139
157
  async create(uid, opts) {
140
- 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;
141
160
 
142
161
  const model = strapi.getModel(uid);
143
162
 
@@ -145,30 +164,33 @@ const createDefaultImplementation = ({ strapi, db, eventHub, entityValidator })
145
164
  const validData = await entityValidator.validateEntityCreation(model, data, { isDraft });
146
165
 
147
166
  // select / populate
148
- const query = transformParamsToQuery(uid, pickSelectionParams(params));
167
+ const query = transformParamsToQuery(uid, pickSelectionParams(wrappedParams));
149
168
 
150
169
  // TODO: wrap into transaction
151
170
  const componentData = await createComponents(uid, validData);
152
171
 
153
172
  let entity = await db.query(uid).create({
154
173
  ...query,
155
- data: Object.assign(omitComponentData(model, validData), componentData),
174
+ data: creationPipeline(Object.assign(omitComponentData(model, validData), componentData), {
175
+ contentType: model,
176
+ }),
156
177
  });
157
178
 
158
179
  // TODO: upload the files then set the links in the entity like with compo to avoid making too many queries
159
180
  // FIXME: upload in components
160
181
  if (files && Object.keys(files).length > 0) {
161
182
  await this.uploadFiles(uid, entity, files);
162
- entity = await this.findOne(uid, entity.id, { params });
183
+ entity = await this.findOne(uid, entity.id, wrappedParams);
163
184
  }
164
185
 
165
- this.emitEvent(uid, ENTRY_CREATE, entity);
186
+ await this.emitEvent(uid, ENTRY_CREATE, entity);
166
187
 
167
188
  return entity;
168
189
  },
169
190
 
170
191
  async update(uid, entityId, opts) {
171
- 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;
172
194
 
173
195
  const model = strapi.getModel(uid);
174
196
 
@@ -180,11 +202,16 @@ const createDefaultImplementation = ({ strapi, db, eventHub, entityValidator })
180
202
 
181
203
  const isDraft = contentTypesUtils.isDraft(entityToUpdate, model);
182
204
 
183
- const validData = await entityValidator.validateEntityUpdate(model, data, {
184
- isDraft,
185
- });
205
+ const validData = await entityValidator.validateEntityUpdate(
206
+ model,
207
+ data,
208
+ {
209
+ isDraft,
210
+ },
211
+ entityToUpdate
212
+ );
186
213
 
187
- const query = transformParamsToQuery(uid, pickSelectionParams(params));
214
+ const query = transformParamsToQuery(uid, pickSelectionParams(wrappedParams));
188
215
 
189
216
  // TODO: wrap in transaction
190
217
  const componentData = await updateComponents(uid, entityToUpdate, validData);
@@ -192,26 +219,28 @@ const createDefaultImplementation = ({ strapi, db, eventHub, entityValidator })
192
219
  let entity = await db.query(uid).update({
193
220
  ...query,
194
221
  where: { id: entityId },
195
- data: Object.assign(omitComponentData(model, validData), componentData),
222
+ data: updatePipeline(Object.assign(omitComponentData(model, validData), componentData), {
223
+ contentType: model,
224
+ }),
196
225
  });
197
226
 
198
227
  // TODO: upload the files then set the links in the entity like with compo to avoid making too many queries
199
228
  // FIXME: upload in components
200
229
  if (files && Object.keys(files).length > 0) {
201
230
  await this.uploadFiles(uid, entity, files);
202
- entity = await this.findOne(uid, entity.id, { params });
231
+ entity = await this.findOne(uid, entity.id, wrappedParams);
203
232
  }
204
233
 
205
- this.emitEvent(uid, ENTRY_UPDATE, entity);
234
+ await this.emitEvent(uid, ENTRY_UPDATE, entity);
206
235
 
207
236
  return entity;
208
237
  },
209
238
 
210
239
  async delete(uid, entityId, opts) {
211
- const { params } = await this.wrapOptions(opts, { uid, action: 'delete' });
240
+ const wrappedParams = await this.wrapParams(opts, { uid, action: 'delete' });
212
241
 
213
242
  // select / populate
214
- const query = transformParamsToQuery(uid, pickSelectionParams(params));
243
+ const query = transformParamsToQuery(uid, pickSelectionParams(wrappedParams));
215
244
 
216
245
  const entityToDelete = await db.query(uid).findOne({
217
246
  ...query,
@@ -225,18 +254,47 @@ const createDefaultImplementation = ({ strapi, db, eventHub, entityValidator })
225
254
  await deleteComponents(uid, entityToDelete);
226
255
  await db.query(uid).delete({ where: { id: entityToDelete.id } });
227
256
 
228
- this.emitEvent(uid, ENTRY_DELETE, entityToDelete);
257
+ await this.emitEvent(uid, ENTRY_DELETE, entityToDelete);
229
258
 
230
259
  return entityToDelete;
231
260
  },
232
261
 
233
262
  // FIXME: used only for the CM to be removed
234
263
  async deleteMany(uid, opts) {
235
- const { params } = await this.wrapOptions(opts, { uid, action: 'delete' });
264
+ const wrappedParams = await this.wrapParams(opts, { uid, action: 'delete' });
236
265
 
237
266
  // select / populate
238
- const query = transformParamsToQuery(uid, params);
267
+ const query = transformParamsToQuery(uid, wrappedParams);
239
268
 
240
269
  return db.query(uid).deleteMany(query);
241
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
+ },
242
300
  });
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
- const { pick } = require('lodash/fp');
3
+ const { pick, isNil, toNumber, isInteger } = require('lodash/fp');
4
+ const { PaginationError } = require('@strapi/utils').errors;
4
5
 
5
6
  const {
6
7
  convertSortQueryParams,
@@ -9,118 +10,84 @@ const {
9
10
  convertPopulateQueryParams,
10
11
  convertFiltersQueryParams,
11
12
  convertFieldsQueryParams,
13
+ convertPublicationStateParams,
12
14
  } = require('@strapi/utils/lib/convert-query-params');
13
15
 
14
- const { contentTypes: contentTypesUtils } = require('@strapi/utils');
15
-
16
- const { PUBLISHED_AT_ATTRIBUTE } = contentTypesUtils.constants;
16
+ const pickSelectionParams = pick(['fields', 'populate']);
17
17
 
18
- // TODO: check invalid values / add defaults ....
19
- const transformParamsToQuery = (uid, params = {}) => {
20
- const model = strapi.getModel(uid);
18
+ const transformParamsToQuery = (uid, params) => {
19
+ // NOTE: can be a CT, a Compo or nothing in the case of polymorphism (DZ & morph relations)
20
+ const type = strapi.getModel(uid);
21
21
 
22
22
  const query = {};
23
23
 
24
- const {
25
- start,
26
- page,
27
- pageSize,
28
- limit,
29
- sort,
30
- filters,
31
- fields,
32
- populate,
33
- publicationState,
34
- _q,
35
- _where,
36
- ...rest
37
- } = params;
38
-
39
- if (_q) {
40
- query._q = _q;
41
- }
42
-
43
- if (page) {
44
- query.page = Number(page);
45
- }
46
-
47
- if (pageSize) {
48
- query.pageSize = Number(pageSize);
49
- }
24
+ const { _q, sort, filters, fields, populate, page, pageSize, start, limit } = params;
50
25
 
51
- if (start) {
52
- query.offset = convertStartQueryParams(start);
53
- }
54
-
55
- if (limit) {
56
- query.limit = convertLimitQueryParams(limit);
26
+ if (!isNil(_q)) {
27
+ query._q = _q;
57
28
  }
58
29
 
59
- if (sort) {
30
+ if (!isNil(sort)) {
60
31
  query.orderBy = convertSortQueryParams(sort);
61
32
  }
62
33
 
63
- if (filters) {
34
+ if (!isNil(filters)) {
64
35
  query.where = convertFiltersQueryParams(filters);
65
36
  }
66
37
 
67
- if (_where) {
68
- query.where = {
69
- $and: [_where].concat(query.where || []),
70
- };
71
- }
72
-
73
- if (fields) {
38
+ if (!isNil(fields)) {
74
39
  query.select = convertFieldsQueryParams(fields);
75
40
  }
76
41
 
77
- if (populate) {
42
+ if (!isNil(populate)) {
78
43
  query.populate = convertPopulateQueryParams(populate);
79
44
  }
80
45
 
81
- // TODO: move to convert-query-params ?
82
- if (publicationState && contentTypesUtils.hasDraftAndPublish(model)) {
83
- const { publicationState = 'live' } = params;
46
+ const isPagePagination = !isNil(page) || !isNil(pageSize);
47
+ const isOffsetPagination = !isNil(start) || !isNil(limit);
84
48
 
85
- const liveClause = {
86
- [PUBLISHED_AT_ATTRIBUTE]: {
87
- $notNull: true,
88
- },
89
- };
49
+ if (isPagePagination && isOffsetPagination) {
50
+ throw new PaginationError(
51
+ 'Invalid pagination attributes. You cannot use page and offset pagination in the same query'
52
+ );
53
+ }
90
54
 
91
- if (publicationState === 'live') {
92
- query.where = {
93
- $and: [liveClause].concat(query.where || []),
94
- };
55
+ if (!isNil(page)) {
56
+ const pageVal = toNumber(page);
95
57
 
96
- // TODO: propagate nested publicationState filter somehow
58
+ if (!isInteger(pageVal) || pageVal <= 0) {
59
+ throw new PaginationError(
60
+ `Invalid 'page' parameter. Expected an integer > 0, received: ${page}`
61
+ );
97
62
  }
63
+
64
+ query.page = pageVal;
98
65
  }
99
66
 
100
- const finalQuery = {
101
- ...convertOldQuery(rest),
102
- ...query,
103
- };
67
+ if (!isNil(pageSize)) {
68
+ const pageSizeVal = toNumber(pageSize);
104
69
 
105
- return finalQuery;
106
- };
70
+ if (!isInteger(pageSizeVal) || pageSizeVal <= 0) {
71
+ throw new PaginationError(
72
+ `Invalid 'pageSize' parameter. Expected an integer > 0, received: ${page}`
73
+ );
74
+ }
107
75
 
108
- // TODO: to remove once the front is migrated
109
- const convertOldQuery = params => {
110
- const obj = {};
76
+ query.pageSize = pageSizeVal;
77
+ }
111
78
 
112
- Object.keys(params).forEach(key => {
113
- if (key.startsWith('_')) {
114
- obj[key.slice(1)] = params[key];
115
- } else {
116
- obj[key] = params[key];
117
- }
118
- });
79
+ if (!isNil(start)) {
80
+ query.offset = convertStartQueryParams(start);
81
+ }
119
82
 
120
- return obj;
121
- };
83
+ if (!isNil(limit)) {
84
+ query.limit = convertLimitQueryParams(limit);
85
+ }
122
86
 
123
- const pickSelectionParams = pick(['fields', 'populate']);
87
+ convertPublicationStateParams(type, params, query);
88
+
89
+ return query;
90
+ };
124
91
 
125
92
  module.exports = {
126
93
  transformParamsToQuery,