@strapi/strapi 4.0.0-beta.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 (143) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +144 -0
  3. package/bin/strapi.js +186 -0
  4. package/lib/Strapi.js +470 -0
  5. package/lib/commands/admin-reset.js +51 -0
  6. package/lib/commands/build.js +56 -0
  7. package/lib/commands/configurationDump.js +50 -0
  8. package/lib/commands/configurationRestore.js +169 -0
  9. package/lib/commands/console.js +26 -0
  10. package/lib/commands/develop.js +157 -0
  11. package/lib/commands/generate-template.js +97 -0
  12. package/lib/commands/install.js +48 -0
  13. package/lib/commands/new.js +11 -0
  14. package/lib/commands/start.js +8 -0
  15. package/lib/commands/uninstall.js +68 -0
  16. package/lib/commands/watchAdmin.js +45 -0
  17. package/lib/container.js +45 -0
  18. package/lib/core/app-configuration/config-loader.js +20 -0
  19. package/lib/core/app-configuration/index.js +75 -0
  20. package/lib/core/app-configuration/load-config-file.js +43 -0
  21. package/lib/core/app-configuration/load-functions.js +28 -0
  22. package/lib/core/bootstrap.js +60 -0
  23. package/lib/core/domain/component/index.js +24 -0
  24. package/lib/core/domain/component/validator.js +29 -0
  25. package/lib/core/domain/content-type/index.js +140 -0
  26. package/lib/core/domain/content-type/validator.js +64 -0
  27. package/lib/core/domain/module/index.js +106 -0
  28. package/lib/core/domain/module/validation.js +36 -0
  29. package/lib/core/loaders/admin.js +16 -0
  30. package/lib/core/loaders/apis.js +157 -0
  31. package/lib/core/loaders/components.js +41 -0
  32. package/lib/core/loaders/index.js +11 -0
  33. package/lib/core/loaders/middlewares.js +86 -0
  34. package/lib/core/loaders/plugins/get-enabled-plugins.js +100 -0
  35. package/lib/core/loaders/plugins/index.js +109 -0
  36. package/lib/core/loaders/policies.js +28 -0
  37. package/lib/core/loaders/src-index.js +38 -0
  38. package/lib/core/registries/apis.js +43 -0
  39. package/lib/core/registries/config.js +21 -0
  40. package/lib/core/registries/content-types.js +53 -0
  41. package/lib/core/registries/controllers.js +43 -0
  42. package/lib/core/registries/hooks.js +37 -0
  43. package/lib/core/registries/middlewares.js +30 -0
  44. package/lib/core/registries/modules.js +44 -0
  45. package/lib/core/registries/plugins.js +28 -0
  46. package/lib/core/registries/policies.js +38 -0
  47. package/lib/core/registries/services.js +58 -0
  48. package/lib/core/utils.js +35 -0
  49. package/lib/core-api/controller/collection-type.js +84 -0
  50. package/lib/core-api/controller/index.js +26 -0
  51. package/lib/core-api/controller/single-type.js +44 -0
  52. package/lib/core-api/controller/transform.js +97 -0
  53. package/lib/core-api/index.js +39 -0
  54. package/lib/core-api/service/collection-type.js +84 -0
  55. package/lib/core-api/service/index.js +55 -0
  56. package/lib/core-api/service/pagination.js +125 -0
  57. package/lib/core-api/service/single-type.js +58 -0
  58. package/lib/index.d.ts +26 -0
  59. package/lib/index.js +3 -0
  60. package/lib/load/filepath-to-prop-path.js +22 -0
  61. package/lib/load/glob.js +15 -0
  62. package/lib/load/index.js +9 -0
  63. package/lib/load/load-files.js +56 -0
  64. package/lib/load/package-path.js +9 -0
  65. package/lib/middlewares/cors/index.js +66 -0
  66. package/lib/middlewares/error/defaults.json +5 -0
  67. package/lib/middlewares/error/index.js +147 -0
  68. package/lib/middlewares/favicon/defaults.json +7 -0
  69. package/lib/middlewares/favicon/index.js +31 -0
  70. package/lib/middlewares/gzip/defaults.json +6 -0
  71. package/lib/middlewares/gzip/index.js +19 -0
  72. package/lib/middlewares/helmet/defaults.json +18 -0
  73. package/lib/middlewares/helmet/index.js +9 -0
  74. package/lib/middlewares/index.js +120 -0
  75. package/lib/middlewares/ip/defaults.json +7 -0
  76. package/lib/middlewares/ip/index.js +25 -0
  77. package/lib/middlewares/logger/defaults.json +5 -0
  78. package/lib/middlewares/logger/index.js +37 -0
  79. package/lib/middlewares/parser/defaults.json +11 -0
  80. package/lib/middlewares/parser/index.js +75 -0
  81. package/lib/middlewares/poweredBy/defaults.json +5 -0
  82. package/lib/middlewares/poweredBy/index.js +16 -0
  83. package/lib/middlewares/public/assets/images/group_people_1.png +0 -0
  84. package/lib/middlewares/public/assets/images/group_people_2.png +0 -0
  85. package/lib/middlewares/public/assets/images/group_people_3.png +0 -0
  86. package/lib/middlewares/public/assets/images/logo_login.png +0 -0
  87. package/lib/middlewares/public/defaults.json +8 -0
  88. package/lib/middlewares/public/index.html +66 -0
  89. package/lib/middlewares/public/index.js +130 -0
  90. package/lib/middlewares/public/serve-static.js +23 -0
  91. package/lib/middlewares/responseTime/defaults.json +5 -0
  92. package/lib/middlewares/responseTime/index.js +25 -0
  93. package/lib/middlewares/responses/defaults.json +5 -0
  94. package/lib/middlewares/responses/index.js +19 -0
  95. package/lib/middlewares/router/defaults.json +7 -0
  96. package/lib/middlewares/router/index.js +97 -0
  97. package/lib/middlewares/session/defaults.json +18 -0
  98. package/lib/middlewares/session/index.js +140 -0
  99. package/lib/migrations/draft-publish.js +57 -0
  100. package/lib/services/auth/index.js +92 -0
  101. package/lib/services/core-store.js +145 -0
  102. package/lib/services/cron.js +54 -0
  103. package/lib/services/entity-service/components.js +365 -0
  104. package/lib/services/entity-service/index.d.ts +91 -0
  105. package/lib/services/entity-service/index.js +244 -0
  106. package/lib/services/entity-service/params.js +145 -0
  107. package/lib/services/entity-validator/index.js +187 -0
  108. package/lib/services/entity-validator/validators.js +123 -0
  109. package/lib/services/event-hub.js +15 -0
  110. package/lib/services/fs.js +58 -0
  111. package/lib/services/metrics/index.js +104 -0
  112. package/lib/services/metrics/is-truthy.js +9 -0
  113. package/lib/services/metrics/middleware.js +33 -0
  114. package/lib/services/metrics/rate-limiter.js +27 -0
  115. package/lib/services/metrics/sender.js +76 -0
  116. package/lib/services/metrics/stringify-deep.js +22 -0
  117. package/lib/services/server/admin-api.js +14 -0
  118. package/lib/services/server/api.js +32 -0
  119. package/lib/services/server/compose-endpoint.js +112 -0
  120. package/lib/services/server/content-api.js +16 -0
  121. package/lib/services/server/http-server.js +64 -0
  122. package/lib/services/server/index.js +108 -0
  123. package/lib/services/server/middleware.js +28 -0
  124. package/lib/services/server/policy.js +34 -0
  125. package/lib/services/server/routing.js +107 -0
  126. package/lib/services/utils/upload-files.js +79 -0
  127. package/lib/services/webhook-runner.js +155 -0
  128. package/lib/services/webhook-store.js +91 -0
  129. package/lib/services/worker-queue.js +58 -0
  130. package/lib/utils/addSlash.js +10 -0
  131. package/lib/utils/ee.js +123 -0
  132. package/lib/utils/get-dirs.js +15 -0
  133. package/lib/utils/get-prefixed-dependencies.js +7 -0
  134. package/lib/utils/index.js +11 -0
  135. package/lib/utils/is-initialized.js +23 -0
  136. package/lib/utils/open-browser.js +12 -0
  137. package/lib/utils/resources/key.pub +9 -0
  138. package/lib/utils/run-checks.js +22 -0
  139. package/lib/utils/startup-logger.js +90 -0
  140. package/lib/utils/success.js +31 -0
  141. package/lib/utils/update-notifier/index.js +97 -0
  142. package/lib/utils/url-from-segments.js +12 -0
  143. package/package.json +133 -0
@@ -0,0 +1,365 @@
1
+ 'use strict';
2
+
3
+ const _ = require('lodash');
4
+ const { has, prop, omit, toString } = require('lodash/fp');
5
+
6
+ const { contentTypes: contentTypesUtils } = require('@strapi/utils');
7
+
8
+ const omitComponentData = (contentType, data) => {
9
+ const { attributes } = contentType;
10
+ const componentAttributes = Object.keys(attributes).filter(attributeName =>
11
+ contentTypesUtils.isComponentAttribute(attributes[attributeName])
12
+ );
13
+
14
+ return omit(componentAttributes, data);
15
+ };
16
+
17
+ // NOTE: we could generalize the logic to allow CRUD of relation directly in the DB layer
18
+ const createComponents = async (uid, data) => {
19
+ const { attributes } = strapi.getModel(uid);
20
+
21
+ const componentBody = {};
22
+
23
+ for (const attributeName in attributes) {
24
+ const attribute = attributes[attributeName];
25
+
26
+ if (!has(attributeName, data) || !contentTypesUtils.isComponentAttribute(attribute)) {
27
+ continue;
28
+ }
29
+
30
+ if (attribute.type === 'component') {
31
+ const { component: componentUID, repeatable = false } = attribute;
32
+
33
+ const componentValue = data[attributeName];
34
+
35
+ if (componentValue === null) {
36
+ continue;
37
+ }
38
+
39
+ if (repeatable === true) {
40
+ if (!Array.isArray(componentValue)) {
41
+ throw new Error('Expected an array to create repeatable component');
42
+ }
43
+
44
+ const components = await Promise.all(
45
+ componentValue.map(value => createComponent(componentUID, value))
46
+ );
47
+
48
+ // TODO: add order
49
+ componentBody[attributeName] = components.map(({ id }, idx) => {
50
+ return {
51
+ id,
52
+ __pivot: {
53
+ order: idx + 1,
54
+ field: attributeName,
55
+ component_type: componentUID,
56
+ },
57
+ };
58
+ });
59
+ } else {
60
+ const component = await createComponent(componentUID, componentValue);
61
+ componentBody[attributeName] = {
62
+ id: component.id,
63
+ __pivot: {
64
+ order: 1,
65
+ field: attributeName,
66
+ component_type: componentUID,
67
+ },
68
+ };
69
+ }
70
+
71
+ continue;
72
+ }
73
+
74
+ if (attribute.type === 'dynamiczone') {
75
+ const dynamiczoneValues = data[attributeName];
76
+
77
+ if (!Array.isArray(dynamiczoneValues)) {
78
+ throw new Error('Expected an array to create repeatable component');
79
+ }
80
+
81
+ componentBody[attributeName] = await Promise.all(
82
+ dynamiczoneValues.map(async (value, idx) => {
83
+ const { id } = await createComponent(value.__component, value);
84
+ return {
85
+ id,
86
+ __component: value.__component,
87
+ __pivot: {
88
+ order: idx + 1,
89
+ field: attributeName,
90
+ },
91
+ };
92
+ })
93
+ );
94
+
95
+ continue;
96
+ }
97
+ }
98
+
99
+ return componentBody;
100
+ };
101
+
102
+ /*
103
+ delete old components
104
+ create or update
105
+ */
106
+ const updateComponents = async (uid, entityToUpdate, data) => {
107
+ const { attributes } = strapi.getModel(uid);
108
+
109
+ const componentBody = {};
110
+
111
+ for (const attributeName in attributes) {
112
+ const attribute = attributes[attributeName];
113
+
114
+ if (!has(attributeName, data)) {
115
+ continue;
116
+ }
117
+
118
+ if (attribute.type === 'component') {
119
+ const { component: componentUID, repeatable = false } = attribute;
120
+
121
+ const componentValue = data[attributeName];
122
+
123
+ await deleteOldComponents(uid, componentUID, entityToUpdate, attributeName, componentValue);
124
+
125
+ if (repeatable === true) {
126
+ if (!Array.isArray(componentValue)) {
127
+ throw new Error('Expected an array to create repeatable component');
128
+ }
129
+
130
+ const components = await Promise.all(
131
+ componentValue.map(value => updateOrCreateComponent(componentUID, value))
132
+ );
133
+
134
+ componentBody[attributeName] = components.filter(_.negate(_.isNil)).map(({ id }, idx) => {
135
+ return {
136
+ id,
137
+ __pivot: {
138
+ order: idx + 1,
139
+ field: attributeName,
140
+ component_type: componentUID,
141
+ },
142
+ };
143
+ });
144
+ } else {
145
+ const component = await updateOrCreateComponent(componentUID, componentValue);
146
+ componentBody[attributeName] = component && {
147
+ id: component.id,
148
+ __pivot: {
149
+ order: 1,
150
+ field: attributeName,
151
+ component_type: componentUID,
152
+ },
153
+ };
154
+ }
155
+
156
+ continue;
157
+ }
158
+
159
+ if (attribute.type === 'dynamiczone') {
160
+ const dynamiczoneValues = data[attributeName];
161
+
162
+ await deleteOldDZComponents(uid, entityToUpdate, attributeName, dynamiczoneValues);
163
+
164
+ if (!Array.isArray(dynamiczoneValues)) {
165
+ throw new Error('Expected an array to create repeatable component');
166
+ }
167
+
168
+ componentBody[attributeName] = await Promise.all(
169
+ dynamiczoneValues.map(async (value, idx) => {
170
+ const { id } = await updateOrCreateComponent(value.__component, value);
171
+
172
+ return {
173
+ id,
174
+ __component: value.__component,
175
+ __pivot: {
176
+ order: idx + 1,
177
+ field: attributeName,
178
+ },
179
+ };
180
+ })
181
+ );
182
+
183
+ continue;
184
+ }
185
+ }
186
+
187
+ return componentBody;
188
+ };
189
+
190
+ const deleteOldComponents = async (
191
+ uid,
192
+ componentUID,
193
+ entityToUpdate,
194
+ attributeName,
195
+ componentValue
196
+ ) => {
197
+ const previousValue = await strapi.query(uid).load(entityToUpdate, attributeName);
198
+
199
+ const idsToKeep = _.castArray(componentValue)
200
+ .filter(has('id'))
201
+ .map(prop('id'))
202
+ .map(toString);
203
+
204
+ const allIds = _.castArray(previousValue)
205
+ .filter(has('id'))
206
+ .map(prop('id'))
207
+ .map(toString);
208
+
209
+ idsToKeep.forEach(id => {
210
+ if (!allIds.includes(id)) {
211
+ const err = new Error(
212
+ `Some of the provided components in ${attributeName} are not related to the entity`
213
+ );
214
+ err.status = 400;
215
+ throw err;
216
+ }
217
+ });
218
+
219
+ const idsToDelete = _.difference(allIds, idsToKeep);
220
+
221
+ if (idsToDelete.length > 0) {
222
+ for (const idToDelete of idsToDelete) {
223
+ await deleteComponent(componentUID, { id: idToDelete });
224
+ }
225
+ }
226
+ };
227
+
228
+ const deleteOldDZComponents = async (uid, entityToUpdate, attributeName, dynamiczoneValues) => {
229
+ const previousValue = await strapi.query(uid).load(entityToUpdate, attributeName);
230
+
231
+ const idsToKeep = _.castArray(dynamiczoneValues)
232
+ .filter(has('id'))
233
+ .map(({ id, __component }) => ({
234
+ id: toString(id),
235
+ __component,
236
+ }));
237
+
238
+ const allIds = _.castArray(previousValue)
239
+ .filter(has('id'))
240
+ .map(({ id, __component }) => ({
241
+ id: toString(id),
242
+ __component,
243
+ }));
244
+
245
+ idsToKeep.forEach(({ id, __component }) => {
246
+ if (!allIds.find(el => el.id === id && el.__component === __component)) {
247
+ const err = new Error(
248
+ `Some of the provided components in ${attributeName} are not related to the entity`
249
+ );
250
+ err.status = 400;
251
+ throw err;
252
+ }
253
+ });
254
+
255
+ const idsToDelete = allIds.reduce((acc, { id, __component }) => {
256
+ if (!idsToKeep.find(el => el.id === id && el.__component === __component)) {
257
+ acc.push({ id, __component });
258
+ }
259
+
260
+ return acc;
261
+ }, []);
262
+
263
+ if (idsToDelete.length > 0) {
264
+ for (const idToDelete of idsToDelete) {
265
+ const { id, __component } = idToDelete;
266
+ await deleteComponent(__component, { id });
267
+ }
268
+ }
269
+ };
270
+
271
+ const deleteComponents = async (uid, entityToDelete) => {
272
+ const { attributes } = strapi.getModel(uid);
273
+
274
+ for (const attributeName in attributes) {
275
+ const attribute = attributes[attributeName];
276
+
277
+ if (attribute.type === 'component') {
278
+ const { component: componentUID } = attribute;
279
+
280
+ const value = await strapi.query(uid).load(entityToDelete, attributeName);
281
+
282
+ if (!value) {
283
+ continue;
284
+ }
285
+
286
+ if (Array.isArray(value)) {
287
+ await Promise.all(value.map(subValue => deleteComponent(componentUID, subValue)));
288
+ } else {
289
+ await deleteComponent(componentUID, value);
290
+ }
291
+
292
+ continue;
293
+ }
294
+
295
+ if (attribute.type === 'dynamiczone') {
296
+ const value = await strapi.query(uid).load(entityToDelete, attributeName);
297
+
298
+ if (!value) {
299
+ continue;
300
+ }
301
+
302
+ if (Array.isArray(value)) {
303
+ await Promise.all(value.map(subValue => deleteComponent(subValue.__component, subValue)));
304
+ }
305
+
306
+ continue;
307
+ }
308
+ }
309
+ };
310
+
311
+ /***************************
312
+ Component queries
313
+ ***************************/
314
+
315
+ // components can have nested compos so this must be recursive
316
+ const createComponent = async (uid, data) => {
317
+ const model = strapi.getModel(uid);
318
+
319
+ const componentData = await createComponents(uid, data);
320
+
321
+ return strapi.query(uid).create({
322
+ data: Object.assign(omitComponentData(model, data), componentData),
323
+ });
324
+ };
325
+
326
+ // components can have nested compos so this must be recursive
327
+ const updateComponent = async (uid, componentToUpdate, data) => {
328
+ const model = strapi.getModel(uid);
329
+
330
+ const componentData = await updateComponents(uid, componentToUpdate, data);
331
+
332
+ return strapi.query(uid).update({
333
+ where: {
334
+ id: componentToUpdate.id,
335
+ },
336
+ data: Object.assign(omitComponentData(model, data), componentData),
337
+ });
338
+ };
339
+
340
+ const updateOrCreateComponent = (componentUID, value) => {
341
+ if (value === null) {
342
+ return null;
343
+ }
344
+
345
+ // update
346
+ if (has('id', value)) {
347
+ // TODO: verify the compo is associated with the entity
348
+ return updateComponent(componentUID, { id: value.id }, value);
349
+ }
350
+
351
+ // create
352
+ return createComponent(componentUID, value);
353
+ };
354
+
355
+ const deleteComponent = async (uid, componentToDelete) => {
356
+ await deleteComponents(uid, componentToDelete);
357
+ await strapi.query(uid).delete({ where: { id: componentToDelete.id } });
358
+ };
359
+
360
+ module.exports = {
361
+ omitComponentData,
362
+ createComponents,
363
+ updateComponents,
364
+ deleteComponents,
365
+ };
@@ -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
+ 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;
@@ -0,0 +1,244 @@
1
+ 'use strict';
2
+
3
+ const delegate = require('delegates');
4
+ const { pipe } = require('lodash/fp');
5
+
6
+ const {
7
+ sanitizeEntity,
8
+ webhook: webhookUtils,
9
+ contentTypes: contentTypesUtils,
10
+ } = require('@strapi/utils');
11
+ const uploadFiles = require('../utils/upload-files');
12
+
13
+ const {
14
+ omitComponentData,
15
+ createComponents,
16
+ updateComponents,
17
+ deleteComponents,
18
+ } = require('./components');
19
+ const {
20
+ transformCommonParams,
21
+ transformPaginationParams,
22
+ transformParamsToQuery,
23
+ pickSelectionParams,
24
+ } = require('./params');
25
+
26
+ // TODO: those should be strapi events used by the webhooks not the other way arround
27
+ const { ENTRY_CREATE, ENTRY_UPDATE, ENTRY_DELETE } = webhookUtils.webhookEvents;
28
+
29
+ module.exports = ctx => {
30
+ const implementation = createDefaultImplementation(ctx);
31
+
32
+ const service = {
33
+ implementation,
34
+ decorate(decorator) {
35
+ if (typeof decorator !== 'function') {
36
+ throw new Error(`Decorator must be a function, received ${typeof decorator}`);
37
+ }
38
+
39
+ this.implementation = Object.assign({}, this.implementation, decorator(this.implementation));
40
+ return this;
41
+ },
42
+ };
43
+
44
+ const delegator = delegate(service, 'implementation');
45
+
46
+ // delegate every method in implementation
47
+ Object.keys(service.implementation).forEach(key => delegator.method(key));
48
+
49
+ return service;
50
+ };
51
+
52
+ /**
53
+ * @type {import('.').default}
54
+ */
55
+ const createDefaultImplementation = ({ strapi, db, eventHub, entityValidator }) => ({
56
+ uploadFiles,
57
+
58
+ async wrapParams(options = {}) {
59
+ return options;
60
+ },
61
+
62
+ emitEvent(uid, event, entity) {
63
+ const model = strapi.getModel(uid);
64
+
65
+ eventHub.emit(event, {
66
+ model: model.modelName,
67
+ entry: sanitizeEntity(entity, { model }),
68
+ });
69
+ },
70
+
71
+ async findMany(uid, opts) {
72
+ const { kind } = strapi.getModel(uid);
73
+
74
+ const wrappedParams = await this.wrapParams(opts, { uid, action: 'findMany' });
75
+
76
+ const query = transformParamsToQuery(uid, wrappedParams);
77
+
78
+ if (kind === 'singleType') {
79
+ return db.query(uid).findOne(query);
80
+ }
81
+
82
+ return db.query(uid).findMany(query);
83
+ },
84
+
85
+ async findPage(uid, opts) {
86
+ const wrappedParams = await this.wrapParams(opts, { uid, action: 'findPage' });
87
+
88
+ const query = transformParamsToQuery(uid, wrappedParams);
89
+
90
+ return db.query(uid).findPage(query);
91
+ },
92
+
93
+ // TODO: streamline the logic based on the populate option
94
+ async findWithRelationCounts(uid, opts) {
95
+ const wrappedParams = await this.wrapParams(opts, { uid, action: 'findWithRelationCounts' });
96
+
97
+ const query = transformParamsToQuery(uid, wrappedParams);
98
+
99
+ const { results, pagination } = await db.query(uid).findPage(query);
100
+
101
+ return {
102
+ results,
103
+ pagination,
104
+ };
105
+ },
106
+
107
+ async findOne(uid, entityId, opts) {
108
+ const wrappedParams = await this.wrapParams(opts, { uid, action: 'findOne' });
109
+
110
+ const query = transformParamsToQuery(uid, pickSelectionParams(wrappedParams));
111
+
112
+ return db.query(uid).findOne({ ...query, where: { id: entityId } });
113
+ },
114
+
115
+ async count(uid, opts) {
116
+ const wrappedParams = await this.wrapParams(opts, { uid, action: 'count' });
117
+
118
+ const query = transformParamsToQuery(uid, wrappedParams);
119
+
120
+ return db.query(uid).count(query);
121
+ },
122
+
123
+ async create(uid, opts) {
124
+ const wrappedParams = await this.wrapParams(opts, { uid, action: 'create' });
125
+ const { data, files } = wrappedParams;
126
+
127
+ const model = strapi.getModel(uid);
128
+
129
+ const isDraft = contentTypesUtils.isDraft(data, model);
130
+ const validData = await entityValidator.validateEntityCreation(model, data, { isDraft });
131
+
132
+ // select / populate
133
+ const query = transformParamsToQuery(uid, pickSelectionParams(wrappedParams));
134
+
135
+ // TODO: wrap into transaction
136
+ const componentData = await createComponents(uid, validData);
137
+
138
+ let entity = await db.query(uid).create({
139
+ ...query,
140
+ data: Object.assign(omitComponentData(model, validData), componentData),
141
+ });
142
+
143
+ // TODO: upload the files then set the links in the entity like with compo to avoid making too many queries
144
+ // FIXME: upload in components
145
+ if (files && Object.keys(files).length > 0) {
146
+ await this.uploadFiles(uid, entity, files);
147
+ entity = await this.findOne(uid, entity.id, wrappedParams);
148
+ }
149
+
150
+ this.emitEvent(uid, ENTRY_CREATE, entity);
151
+
152
+ return entity;
153
+ },
154
+
155
+ async update(uid, entityId, opts) {
156
+ const wrappedParams = await this.wrapParams(opts, { uid, action: 'update' });
157
+ const { data, files } = wrappedParams;
158
+
159
+ const model = strapi.getModel(uid);
160
+
161
+ const entityToUpdate = await db.query(uid).findOne({ where: { id: entityId } });
162
+
163
+ if (!entityToUpdate) {
164
+ return null;
165
+ }
166
+
167
+ const isDraft = contentTypesUtils.isDraft(entityToUpdate, model);
168
+
169
+ const validData = await entityValidator.validateEntityUpdate(model, data, {
170
+ isDraft,
171
+ });
172
+
173
+ const query = transformParamsToQuery(uid, pickSelectionParams(wrappedParams));
174
+
175
+ // TODO: wrap in transaction
176
+ const componentData = await updateComponents(uid, entityToUpdate, validData);
177
+
178
+ let entity = await db.query(uid).update({
179
+ ...query,
180
+ where: { id: entityId },
181
+ data: Object.assign(omitComponentData(model, validData), componentData),
182
+ });
183
+
184
+ // TODO: upload the files then set the links in the entity like with compo to avoid making too many queries
185
+ // FIXME: upload in components
186
+ if (files && Object.keys(files).length > 0) {
187
+ await this.uploadFiles(uid, entity, files);
188
+ entity = await this.findOne(uid, entity.id, wrappedParams);
189
+ }
190
+
191
+ this.emitEvent(uid, ENTRY_UPDATE, entity);
192
+
193
+ return entity;
194
+ },
195
+
196
+ async delete(uid, entityId, opts) {
197
+ const wrappedParams = await this.wrapParams(opts, { uid, action: 'delete' });
198
+
199
+ // select / populate
200
+ const query = transformParamsToQuery(uid, pickSelectionParams(wrappedParams));
201
+
202
+ const entityToDelete = await db.query(uid).findOne({
203
+ ...query,
204
+ where: { id: entityId },
205
+ });
206
+
207
+ if (!entityToDelete) {
208
+ return null;
209
+ }
210
+
211
+ await deleteComponents(uid, entityToDelete);
212
+ await db.query(uid).delete({ where: { id: entityToDelete.id } });
213
+
214
+ this.emitEvent(uid, ENTRY_DELETE, entityToDelete);
215
+
216
+ return entityToDelete;
217
+ },
218
+
219
+ // FIXME: used only for the CM to be removed
220
+ async deleteMany(uid, opts) {
221
+ const wrappedParams = await this.wrapParams(opts, { uid, action: 'delete' });
222
+
223
+ // select / populate
224
+ const query = transformParamsToQuery(uid, wrappedParams);
225
+
226
+ return db.query(uid).deleteMany(query);
227
+ },
228
+
229
+ load(uid, entity, field, params) {
230
+ const { attributes } = strapi.getModel(uid);
231
+
232
+ const attribute = attributes[field];
233
+
234
+ const loadParams =
235
+ attribute.type === 'relation'
236
+ ? transformParamsToQuery(attribute.target, params)
237
+ : pipe(
238
+ transformCommonParams,
239
+ transformPaginationParams
240
+ )(params);
241
+
242
+ return db.query(uid).load(entity, field, loadParams);
243
+ },
244
+ });