@strapi/plugin-graphql 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 (122) hide show
  1. package/README.md +1 -1
  2. package/admin/src/index.js +0 -8
  3. package/package.json +39 -32
  4. package/server/bootstrap.js +148 -0
  5. package/server/config/default-config.js +13 -0
  6. package/server/config/index.js +7 -0
  7. package/server/format-graphql-error.js +50 -0
  8. package/server/services/builders/dynamic-zones.js +97 -0
  9. package/server/services/builders/entity-meta.js +7 -0
  10. package/server/services/builders/entity.js +43 -0
  11. package/server/services/builders/enums.js +24 -0
  12. package/server/services/builders/filters/content-type.js +93 -0
  13. package/server/services/builders/filters/index.js +7 -0
  14. package/server/services/builders/filters/operators/and.js +15 -0
  15. package/server/services/builders/filters/operators/between.js +15 -0
  16. package/server/services/builders/filters/operators/contains.js +13 -0
  17. package/server/services/builders/filters/operators/containsi.js +13 -0
  18. package/server/services/builders/filters/operators/ends-with.js +13 -0
  19. package/server/services/builders/filters/operators/eq.js +23 -0
  20. package/server/services/builders/filters/operators/gt.js +13 -0
  21. package/server/services/builders/filters/operators/gte.js +13 -0
  22. package/server/services/builders/filters/operators/in.js +15 -0
  23. package/server/services/builders/filters/operators/index.js +38 -0
  24. package/server/services/builders/filters/operators/lt.js +13 -0
  25. package/server/services/builders/filters/operators/lte.js +13 -0
  26. package/server/services/builders/filters/operators/ne.js +13 -0
  27. package/server/services/builders/filters/operators/not-contains.js +13 -0
  28. package/server/services/builders/filters/operators/not-containsi.js +13 -0
  29. package/server/services/builders/filters/operators/not-in.js +15 -0
  30. package/server/services/builders/filters/operators/not-null.js +13 -0
  31. package/server/services/builders/filters/operators/not.js +19 -0
  32. package/server/services/builders/filters/operators/null.js +13 -0
  33. package/server/services/builders/filters/operators/or.js +15 -0
  34. package/server/services/builders/filters/operators/starts-with.js +13 -0
  35. package/server/services/builders/generic-morph.js +41 -0
  36. package/server/services/builders/index.js +92 -0
  37. package/server/services/builders/input.js +121 -0
  38. package/server/services/builders/mutations/collection-type.js +191 -0
  39. package/server/services/builders/mutations/index.js +9 -0
  40. package/server/services/builders/mutations/single-type.js +141 -0
  41. package/server/services/builders/queries/collection-type.js +120 -0
  42. package/server/services/builders/queries/index.js +9 -0
  43. package/server/services/builders/queries/single-type.js +70 -0
  44. package/server/services/builders/relation-response-collection.js +35 -0
  45. package/server/services/builders/resolvers/association.js +85 -0
  46. package/server/services/builders/resolvers/component.js +18 -0
  47. package/server/services/builders/resolvers/dynamic-zone.js +9 -0
  48. package/server/services/builders/resolvers/index.js +18 -0
  49. package/server/services/builders/resolvers/mutation.js +33 -0
  50. package/server/services/builders/resolvers/query.js +19 -0
  51. package/server/services/builders/response-collection.js +43 -0
  52. package/server/services/builders/response.js +32 -0
  53. package/server/services/builders/type.js +364 -0
  54. package/server/services/builders/utils.js +134 -0
  55. package/server/services/constants.js +147 -0
  56. package/server/services/content-api/index.js +179 -0
  57. package/server/services/content-api/policy.js +60 -0
  58. package/server/services/content-api/register-functions/collection-type.js +72 -0
  59. package/server/services/content-api/register-functions/component.js +15 -0
  60. package/server/services/content-api/register-functions/content-type/dynamic-zones.js +36 -0
  61. package/server/services/content-api/register-functions/content-type/enums.js +33 -0
  62. package/server/services/content-api/register-functions/content-type/filters.js +15 -0
  63. package/server/services/content-api/register-functions/content-type/index.js +13 -0
  64. package/server/services/content-api/register-functions/content-type/inputs.js +21 -0
  65. package/server/services/content-api/register-functions/index.js +22 -0
  66. package/server/services/content-api/register-functions/internals.js +13 -0
  67. package/server/services/content-api/register-functions/polymorphic.js +69 -0
  68. package/server/services/content-api/register-functions/scalars.js +14 -0
  69. package/server/services/content-api/register-functions/single-type.js +72 -0
  70. package/server/services/content-api/wrap-resolvers.js +144 -0
  71. package/server/services/extension/extension.js +95 -0
  72. package/server/services/extension/index.js +5 -0
  73. package/server/services/extension/shadow-crud-manager.js +159 -0
  74. package/server/services/format/index.js +7 -0
  75. package/server/services/format/return-types.js +27 -0
  76. package/server/services/index.js +21 -0
  77. package/server/services/internals/args/index.js +11 -0
  78. package/server/services/internals/args/pagination.js +19 -0
  79. package/server/services/internals/args/publication-state.js +12 -0
  80. package/server/services/internals/args/sort.js +10 -0
  81. package/server/services/internals/helpers/get-enabled-scalars.js +15 -0
  82. package/server/services/internals/helpers/index.js +7 -0
  83. package/server/services/internals/index.js +13 -0
  84. package/server/services/internals/scalars/index.js +18 -0
  85. package/server/services/internals/scalars/time.js +36 -0
  86. package/server/services/internals/types/error.js +34 -0
  87. package/server/services/internals/types/filters.js +39 -0
  88. package/server/services/internals/types/index.js +29 -0
  89. package/server/services/internals/types/pagination.js +24 -0
  90. package/server/services/internals/types/publication-state.js +24 -0
  91. package/server/services/internals/types/response-collection-meta.js +38 -0
  92. package/server/services/type-registry.js +104 -0
  93. package/server/services/utils/attributes.js +84 -0
  94. package/server/services/utils/index.js +11 -0
  95. package/server/services/utils/mappers/entity-to-response-entity.js +12 -0
  96. package/server/services/utils/mappers/graphql-filters-to-strapi-query.js +109 -0
  97. package/server/services/utils/mappers/graphql-scalar-to-operators.js +17 -0
  98. package/server/services/utils/mappers/index.js +13 -0
  99. package/server/services/utils/mappers/strapi-scalar-to-graphql-scalar.js +25 -0
  100. package/server/services/utils/naming.js +287 -0
  101. package/strapi-admin.js +3 -0
  102. package/strapi-server.js +13 -0
  103. package/admin/src/assets/images/logo.svg +0 -38
  104. package/config/routes.json +0 -3
  105. package/config/schema.graphql +0 -1
  106. package/config/settings.json +0 -12
  107. package/controllers/GraphQL.js +0 -9
  108. package/hooks/graphql/defaults.json +0 -5
  109. package/hooks/graphql/index.js +0 -174
  110. package/hooks/graphql/load-config.js +0 -42
  111. package/services/build-aggregation.js +0 -565
  112. package/services/data-loaders.js +0 -55
  113. package/services/naming.js +0 -15
  114. package/services/resolvers-builder.js +0 -204
  115. package/services/schema-definitions.js +0 -131
  116. package/services/schema-generator.js +0 -178
  117. package/services/shadow-crud.js +0 -612
  118. package/services/type-builder.js +0 -311
  119. package/services/utils.js +0 -200
  120. package/types/dynamiczoneScalar.js +0 -40
  121. package/types/publication-state.js +0 -16
  122. package/types/time.js +0 -26
@@ -0,0 +1,18 @@
1
+ 'use strict';
2
+
3
+ const associationResolvers = require('./association');
4
+ const queriesResolvers = require('./query');
5
+ const mutationsResolvers = require('./mutation');
6
+ const componentResolvers = require('./component');
7
+ const dynamicZoneResolvers = require('./dynamic-zone');
8
+
9
+ module.exports = context => ({
10
+ // Generics
11
+ ...associationResolvers(context),
12
+
13
+ // Builders
14
+ ...mutationsResolvers(context),
15
+ ...queriesResolvers(context),
16
+ ...componentResolvers(context),
17
+ ...dynamicZoneResolvers(context),
18
+ });
@@ -0,0 +1,33 @@
1
+ 'use strict';
2
+
3
+ const { pick } = require('lodash/fp');
4
+
5
+ const pickCreateArgs = pick(['params', 'data', 'files']);
6
+
7
+ module.exports = ({ strapi }) => ({
8
+ buildMutationsResolvers({ contentType }) {
9
+ const { uid } = contentType;
10
+
11
+ return {
12
+ async create(parent, args) {
13
+ // todo[v4]: Might be interesting to generate dynamic yup schema to validate payloads with more complex checks (on top of graphql validation)
14
+ const params = pickCreateArgs(args);
15
+
16
+ // todo[v4]: Sanitize args to only keep params / data / files (or do it in the base resolver)
17
+ return strapi.entityService.create(uid, params);
18
+ },
19
+
20
+ async update(parent, args) {
21
+ const { id, data } = args;
22
+
23
+ return strapi.entityService.update(uid, id, { data });
24
+ },
25
+
26
+ async delete(parent, args) {
27
+ const { id, ...rest } = args;
28
+
29
+ return strapi.entityService.delete(uid, id, rest);
30
+ },
31
+ };
32
+ },
33
+ });
@@ -0,0 +1,19 @@
1
+ 'use strict';
2
+
3
+ const { omit } = require('lodash/fp');
4
+
5
+ module.exports = ({ strapi }) => ({
6
+ buildQueriesResolvers({ contentType }) {
7
+ const { uid } = contentType;
8
+
9
+ return {
10
+ async find(parent, args) {
11
+ return strapi.entityService.findMany(uid, args);
12
+ },
13
+
14
+ async findOne(parent, args) {
15
+ return strapi.entityService.findOne(uid, args.id, omit('id', args));
16
+ },
17
+ };
18
+ },
19
+ });
@@ -0,0 +1,43 @@
1
+ 'use strict';
2
+
3
+ const { objectType, nonNull } = require('nexus');
4
+ const { defaultTo, prop, pipe } = require('lodash/fp');
5
+
6
+ module.exports = ({ strapi }) => {
7
+ const { naming } = strapi.plugin('graphql').service('utils');
8
+ const { RESPONSE_COLLECTION_META_TYPE_NAME } = strapi.plugin('graphql').service('constants');
9
+
10
+ return {
11
+ /**
12
+ * Build a type definition for a content API collection response for a given content type
13
+ * @param {object} contentType The content type which will be used to build its content API response definition
14
+ * @return {NexusObjectTypeDef}
15
+ */
16
+ buildResponseCollectionDefinition(contentType) {
17
+ const name = naming.getEntityResponseCollectionName(contentType);
18
+ const entityName = naming.getEntityName(contentType);
19
+
20
+ return objectType({
21
+ name,
22
+
23
+ definition(t) {
24
+ t.nonNull.list.field('data', {
25
+ type: nonNull(entityName),
26
+
27
+ resolve: pipe(
28
+ prop('nodes'),
29
+ defaultTo([])
30
+ ),
31
+ });
32
+
33
+ t.nonNull.field('meta', {
34
+ type: RESPONSE_COLLECTION_META_TYPE_NAME,
35
+
36
+ // Pass down the args stored in the source object
37
+ resolve: prop('info'),
38
+ });
39
+ },
40
+ });
41
+ },
42
+ };
43
+ };
@@ -0,0 +1,32 @@
1
+ 'use strict';
2
+
3
+ const { objectType } = require('nexus');
4
+ const { prop } = require('lodash/fp');
5
+
6
+ module.exports = ({ strapi }) => {
7
+ const { naming } = strapi.plugin('graphql').service('utils');
8
+
9
+ return {
10
+ /**
11
+ * Build a type definition for a content API response for a given content type
12
+ * @param {object} contentType The content type which will be used to build its content API response definition
13
+ * @return {NexusObjectTypeDef}
14
+ */
15
+ buildResponseDefinition(contentType) {
16
+ const name = naming.getEntityResponseName(contentType);
17
+ const entityName = naming.getEntityName(contentType);
18
+
19
+ return objectType({
20
+ name,
21
+
22
+ definition(t) {
23
+ t.field('data', {
24
+ type: entityName,
25
+
26
+ resolve: prop('value'),
27
+ });
28
+ },
29
+ });
30
+ },
31
+ };
32
+ };
@@ -0,0 +1,364 @@
1
+ 'use strict';
2
+
3
+ const { isArray, isString, isUndefined, constant } = require('lodash/fp');
4
+ const { objectType } = require('nexus');
5
+
6
+ const { contentTypes } = require('@strapi/utils');
7
+
8
+ /**
9
+ * @typedef TypeBuildersOptions
10
+ *
11
+ * @property {ObjectDefinitionBlock} builder
12
+ * @property {string} attributeName
13
+ * @property {object} attribute
14
+ * @property {object} contentType
15
+ * @property {object} context
16
+ * @property {object} context.strapi
17
+ * @property {object} context.registry
18
+ */
19
+ module.exports = context => {
20
+ const { strapi } = context;
21
+
22
+ const getGraphQLService = strapi.plugin('graphql').service;
23
+
24
+ const extension = getGraphQLService('extension');
25
+
26
+ /**
27
+ * Add a scalar attribute to the type definition
28
+ *
29
+ * The attribute is added based on a simple association between a Strapi
30
+ * type and a GraphQL type (the map is defined in `strapiTypeToGraphQLScalar`)
31
+ *
32
+ * @param {TypeBuildersOptions} options
33
+ */
34
+ const addScalarAttribute = ({ builder, attributeName, attribute }) => {
35
+ const { mappers } = getGraphQLService('utils');
36
+
37
+ const gqlType = mappers.strapiScalarToGraphQLScalar(attribute.type);
38
+
39
+ builder.field(attributeName, { type: gqlType });
40
+ };
41
+
42
+ /**
43
+ * Add a component attribute to the type definition
44
+ *
45
+ * The attribute is added by fetching the component's type
46
+ * name and using it as the attribute's type
47
+ *
48
+ * @param {TypeBuildersOptions} options
49
+ */
50
+ const addComponentAttribute = ({ builder, attributeName, contentType, attribute }) => {
51
+ const { naming } = getGraphQLService('utils');
52
+ const { getContentTypeArgs } = getGraphQLService('builders').utils;
53
+ const { buildComponentResolver } = getGraphQLService('builders').get('content-api');
54
+
55
+ const type = naming.getComponentNameFromAttribute(attribute);
56
+
57
+ if (attribute.repeatable) {
58
+ builder = builder.list;
59
+ }
60
+
61
+ const targetComponent = strapi.getModel(attribute.component);
62
+
63
+ const resolve = buildComponentResolver({
64
+ contentTypeUID: contentType.uid,
65
+ attributeName,
66
+ strapi,
67
+ });
68
+
69
+ const args = getContentTypeArgs(targetComponent, { multiple: !!attribute.repeatable });
70
+
71
+ builder.field(attributeName, { type, resolve, args });
72
+ };
73
+
74
+ /**
75
+ * Add a dynamic zone attribute to the type definition
76
+ *
77
+ * The attribute is added by fetching the dynamic zone's
78
+ * type name and using it as the attribute's type
79
+ *
80
+ * @param {TypeBuildersOptions} options
81
+ */
82
+ const addDynamicZoneAttribute = ({ builder, attributeName, contentType }) => {
83
+ const { naming } = getGraphQLService('utils');
84
+ const { ERROR_CODES } = getGraphQLService('constants');
85
+ const { buildDynamicZoneResolver } = getGraphQLService('builders').get('content-api');
86
+
87
+ const { components } = contentType.attributes[attributeName];
88
+
89
+ const isEmpty = components.length === 0;
90
+ const type = naming.getDynamicZoneName(contentType, attributeName);
91
+
92
+ const resolve = isEmpty
93
+ ? // If the dynamic zone don't have any component, then return an error payload
94
+ constant({
95
+ code: ERROR_CODES.emptyDynamicZone,
96
+ message: `This dynamic zone don't have any component attached to it`,
97
+ })
98
+ : // Else, return a classic dynamic-zone resolver
99
+ buildDynamicZoneResolver({
100
+ contentTypeUID: contentType.uid,
101
+ attributeName,
102
+ });
103
+
104
+ builder.list.field(attributeName, { type, resolve });
105
+ };
106
+
107
+ /**
108
+ * Add an enum attribute to the type definition
109
+ *
110
+ * The attribute is added by fetching the enum's type
111
+ * name and using it as the attribute's type
112
+ *
113
+ * @param {TypeBuildersOptions} options
114
+ */
115
+ const addEnumAttribute = ({ builder, attributeName, contentType }) => {
116
+ const { naming } = getGraphQLService('utils');
117
+
118
+ const type = naming.getEnumName(contentType, attributeName);
119
+
120
+ builder.field(attributeName, { type });
121
+ };
122
+
123
+ /**
124
+ * Add a media attribute to the type definition
125
+ * @param {TypeBuildersOptions} options
126
+ */
127
+ const addMediaAttribute = options => {
128
+ const { naming } = getGraphQLService('utils');
129
+ const { getContentTypeArgs } = getGraphQLService('builders').utils;
130
+ const { buildAssociationResolver } = getGraphQLService('builders').get('content-api');
131
+ const extension = getGraphQLService('extension');
132
+
133
+ let { builder } = options;
134
+ const { attributeName, attribute, contentType } = options;
135
+ const fileUID = 'plugin::upload.file';
136
+
137
+ if (extension.shadowCRUD(fileUID).isDisabled()) {
138
+ return;
139
+ }
140
+
141
+ const fileContentType = strapi.contentTypes[fileUID];
142
+
143
+ const resolve = buildAssociationResolver({
144
+ contentTypeUID: contentType.uid,
145
+ attributeName,
146
+ strapi,
147
+ });
148
+
149
+ const args = attribute.multiple ? getContentTypeArgs(fileContentType) : undefined;
150
+ const type = attribute.multiple
151
+ ? naming.getRelationResponseCollectionName(fileContentType)
152
+ : naming.getEntityResponseName(fileContentType);
153
+
154
+ builder.field(attributeName, { type, resolve, args });
155
+ };
156
+
157
+ /**
158
+ * Add a polymorphic relational attribute to the type definition
159
+ * @param {TypeBuildersOptions} options
160
+ */
161
+ const addPolymorphicRelationalAttribute = options => {
162
+ const { GENERIC_MORPH_TYPENAME } = getGraphQLService('constants');
163
+ const { naming } = getGraphQLService('utils');
164
+ const { buildAssociationResolver } = getGraphQLService('builders').get('content-api');
165
+
166
+ let { builder } = options;
167
+ const { attributeName, attribute, contentType } = options;
168
+
169
+ const { target } = attribute;
170
+ const isToManyRelation = attribute.relation.endsWith('Many');
171
+
172
+ if (isToManyRelation) {
173
+ builder = builder.list;
174
+ }
175
+ // todo[v4]: How to handle polymorphic relation w/ entity response collection types?
176
+ // -> Currently return raw polymorphic entities
177
+
178
+ const resolve = buildAssociationResolver({
179
+ contentTypeUID: contentType.uid,
180
+ attributeName,
181
+ strapi,
182
+ });
183
+
184
+ // If there is no specific target specified, then use the GenericMorph type
185
+ if (isUndefined(target)) {
186
+ builder.field(attributeName, {
187
+ type: GENERIC_MORPH_TYPENAME,
188
+ resolve,
189
+ });
190
+ }
191
+
192
+ // If the target is an array of string, resolve the associated morph type and use it
193
+ else if (isArray(target) && target.every(isString)) {
194
+ const type = naming.getMorphRelationTypeName(contentType, attributeName);
195
+
196
+ builder.field(attributeName, { type, resolve });
197
+ }
198
+ };
199
+
200
+ /**
201
+ * Add a regular relational attribute to the type definition
202
+ * @param {TypeBuildersOptions} options
203
+ */
204
+ const addRegularRelationalAttribute = options => {
205
+ const { naming } = getGraphQLService('utils');
206
+ const { getContentTypeArgs } = getGraphQLService('builders').utils;
207
+ const { buildAssociationResolver } = getGraphQLService('builders').get('content-api');
208
+ const extension = getGraphQLService('extension');
209
+
210
+ let { builder } = options;
211
+ const { attributeName, attribute, contentType } = options;
212
+
213
+ if (extension.shadowCRUD(attribute.target).isDisabled()) {
214
+ return;
215
+ }
216
+
217
+ const isToManyRelation = attribute.relation.endsWith('Many');
218
+
219
+ const resolve = buildAssociationResolver({
220
+ contentTypeUID: contentType.uid,
221
+ attributeName,
222
+ strapi,
223
+ });
224
+
225
+ const targetContentType = strapi.getModel(attribute.target);
226
+
227
+ const type = isToManyRelation
228
+ ? naming.getRelationResponseCollectionName(targetContentType)
229
+ : naming.getEntityResponseName(targetContentType);
230
+
231
+ const args = isToManyRelation ? getContentTypeArgs(targetContentType) : undefined;
232
+
233
+ const resolverPath = `${naming.getTypeName(contentType)}.${attributeName}`;
234
+ const resolverScope = `${targetContentType.uid}.find`;
235
+
236
+ extension.use({ resolversConfig: { [resolverPath]: { auth: { scope: [resolverScope] } } } });
237
+
238
+ builder.field(attributeName, { type, resolve, args });
239
+ };
240
+
241
+ const isNotPrivate = contentType => attributeName => {
242
+ return !contentTypes.isPrivateAttribute(contentType, attributeName);
243
+ };
244
+
245
+ const isNotDisabled = contentType => attributeName => {
246
+ return extension
247
+ .shadowCRUD(contentType.uid)
248
+ .field(attributeName)
249
+ .hasOutputEnabled();
250
+ };
251
+
252
+ return {
253
+ /**
254
+ * Create a type definition for a given content type
255
+ * @param contentType - The content type used to created the definition
256
+ * @return {NexusObjectTypeDef}
257
+ */
258
+ buildTypeDefinition(contentType) {
259
+ const utils = getGraphQLService('utils');
260
+
261
+ const { getComponentName, getTypeName } = utils.naming;
262
+ const {
263
+ isStrapiScalar,
264
+ isComponent,
265
+ isDynamicZone,
266
+ isEnumeration,
267
+ isMedia,
268
+ isMorphRelation,
269
+ isRelation,
270
+ } = utils.attributes;
271
+
272
+ const { attributes, modelType } = contentType;
273
+
274
+ const attributesKey = Object.keys(attributes);
275
+
276
+ const name = (modelType === 'component' ? getComponentName : getTypeName).call(
277
+ null,
278
+ contentType
279
+ );
280
+
281
+ return objectType({
282
+ name,
283
+
284
+ definition(t) {
285
+ if (modelType === 'component' && isNotDisabled(contentType)('id')) {
286
+ t.nonNull.id('id');
287
+ }
288
+
289
+ /** Attributes
290
+ *
291
+ * Attributes can be of 7 different kind:
292
+ * - Scalar
293
+ * - Component
294
+ * - Dynamic Zone
295
+ * - Enum
296
+ * - Media
297
+ * - Polymorphic Relations
298
+ * - Regular Relations
299
+ *
300
+ * Here, we iterate over each non-private attribute
301
+ * and add it to the type definition based on its type
302
+ */
303
+ attributesKey
304
+ // Ignore private attributes
305
+ .filter(isNotPrivate(contentType))
306
+ // Ignore disabled fields (from extension service)
307
+ .filter(isNotDisabled(contentType))
308
+ // Add each attribute to the type definition
309
+ .forEach(attributeName => {
310
+ const attribute = attributes[attributeName];
311
+
312
+ // We create a copy of the builder (t) to apply custom
313
+ // rules only on the current attribute (eg: nonNull, list, ...)
314
+ let builder = t;
315
+
316
+ if (attribute.required) {
317
+ builder = builder.nonNull;
318
+ }
319
+
320
+ /**
321
+ * @type {TypeBuildersOptions}
322
+ */
323
+ const options = { builder, attributeName, attribute, contentType, context };
324
+
325
+ // Scalars
326
+ if (isStrapiScalar(attribute)) {
327
+ addScalarAttribute(options);
328
+ }
329
+
330
+ // Components
331
+ else if (isComponent(attribute)) {
332
+ addComponentAttribute(options);
333
+ }
334
+
335
+ // Dynamic Zones
336
+ else if (isDynamicZone(attribute)) {
337
+ addDynamicZoneAttribute(options);
338
+ }
339
+
340
+ // Enums
341
+ else if (isEnumeration(attribute)) {
342
+ addEnumAttribute(options);
343
+ }
344
+
345
+ // Media
346
+ else if (isMedia(attribute)) {
347
+ addMediaAttribute(options);
348
+ }
349
+
350
+ // Polymorphic Relations
351
+ else if (isMorphRelation(attribute)) {
352
+ addPolymorphicRelationalAttribute(options);
353
+ }
354
+
355
+ // Regular Relations
356
+ else if (isRelation(attribute)) {
357
+ addRegularRelationalAttribute(options);
358
+ }
359
+ });
360
+ },
361
+ });
362
+ },
363
+ };
364
+ };
@@ -0,0 +1,134 @@
1
+ 'use strict';
2
+
3
+ const { entries, mapValues, omit } = require('lodash/fp');
4
+ const {
5
+ pagination: { withDefaultPagination },
6
+ contentTypes: { hasDraftAndPublish },
7
+ } = require('@strapi/utils');
8
+
9
+ module.exports = ({ strapi }) => {
10
+ const { service: getService } = strapi.plugin('graphql');
11
+
12
+ return {
13
+ /**
14
+ * Get every args for a given content type
15
+ * @param {object} contentType
16
+ * @param {object} options
17
+ * @param {boolean} options.multiple
18
+ * @return {object}
19
+ */
20
+ getContentTypeArgs(contentType, { multiple = true } = {}) {
21
+ const { naming } = getService('utils');
22
+ const { args } = getService('internals');
23
+
24
+ const { kind, modelType } = contentType;
25
+
26
+ // Components
27
+ if (modelType === 'component') {
28
+ if (!multiple) return {};
29
+
30
+ return {
31
+ filters: naming.getFiltersInputTypeName(contentType),
32
+ pagination: args.PaginationArg,
33
+ sort: args.SortArg,
34
+ };
35
+ }
36
+
37
+ // Collection Types
38
+ else if (kind === 'collectionType') {
39
+ if (!multiple) {
40
+ return { id: 'ID' };
41
+ }
42
+
43
+ const params = {
44
+ filters: naming.getFiltersInputTypeName(contentType),
45
+ pagination: args.PaginationArg,
46
+ sort: args.SortArg,
47
+ };
48
+
49
+ if (hasDraftAndPublish(contentType)) {
50
+ Object.assign(params, { publicationState: args.PublicationStateArg });
51
+ }
52
+
53
+ return params;
54
+ }
55
+
56
+ // Single Types
57
+ else if (kind === 'singleType') {
58
+ const params = {};
59
+
60
+ if (hasDraftAndPublish(contentType)) {
61
+ Object.assign(params, { publicationState: args.PublicationStateArg });
62
+ }
63
+
64
+ return params;
65
+ }
66
+ },
67
+
68
+ /**
69
+ * Filter an object entries and keep only those whose value is a unique scalar attribute
70
+ * @param {object} attributes
71
+ * @return {Object<string, object>}
72
+ */
73
+ getUniqueScalarAttributes(attributes) {
74
+ const { isStrapiScalar } = getService('utils').attributes;
75
+
76
+ const uniqueAttributes = entries(attributes).filter(
77
+ ([, attribute]) => isStrapiScalar(attribute) && attribute.unique
78
+ );
79
+
80
+ return Object.fromEntries(uniqueAttributes);
81
+ },
82
+
83
+ /**
84
+ * Map each value from an attribute to a FiltersInput type name
85
+ * @param {object} attributes - The attributes object to transform
86
+ * @return {Object<string, string>}
87
+ */
88
+ scalarAttributesToFiltersMap: mapValues(attribute => {
89
+ const { mappers, naming } = getService('utils');
90
+
91
+ const gqlScalar = mappers.strapiScalarToGraphQLScalar(attribute.type);
92
+
93
+ return naming.getScalarFilterInputTypeName(gqlScalar);
94
+ }),
95
+
96
+ /**
97
+ * Apply basic transform to GQL args
98
+ */
99
+ transformArgs(args, { contentType, usePagination = false } = {}) {
100
+ const { mappers } = getService('utils');
101
+ const { config } = strapi.plugin('graphql');
102
+ const { pagination = {}, filters = {} } = args;
103
+
104
+ // Init
105
+ const newArgs = omit(['pagination', 'filters'], args);
106
+
107
+ // Pagination
108
+ if (usePagination) {
109
+ const defaultLimit = config('defaultLimit');
110
+ const maxLimit = config('maxLimit');
111
+
112
+ Object.assign(
113
+ newArgs,
114
+ withDefaultPagination(pagination, {
115
+ maxLimit,
116
+ defaults: {
117
+ offset: { limit: defaultLimit },
118
+ page: { pageSize: defaultLimit },
119
+ },
120
+ })
121
+ );
122
+ }
123
+
124
+ // Filters
125
+ if (args.filters) {
126
+ Object.assign(newArgs, {
127
+ filters: mappers.graphQLFiltersToStrapiQuery(filters, contentType),
128
+ });
129
+ }
130
+
131
+ return newArgs;
132
+ },
133
+ };
134
+ };