@strapi/plugin-graphql 4.0.0-next.2 → 4.0.0-next.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/admin/src/translations/zh-Hans.json +4 -0
- package/package.json +17 -15
- package/server/bootstrap.js +124 -0
- package/server/services/builders/dynamic-zones.js +96 -0
- package/server/services/builders/entity-meta.js +7 -0
- package/server/services/builders/entity.js +43 -0
- package/server/services/builders/enums.js +24 -0
- package/server/services/builders/filters/content-type.js +84 -0
- package/server/services/builders/filters/index.js +7 -0
- package/server/services/builders/filters/operators/and.js +15 -0
- package/server/services/builders/filters/operators/between.js +15 -0
- package/server/services/builders/filters/operators/contains.js +13 -0
- package/server/services/builders/filters/operators/containsi.js +13 -0
- package/server/services/builders/filters/operators/ends-with.js +13 -0
- package/server/services/builders/filters/operators/eq.js +19 -0
- package/server/services/builders/filters/operators/gt.js +13 -0
- package/server/services/builders/filters/operators/gte.js +13 -0
- package/server/services/builders/filters/operators/in.js +15 -0
- package/server/services/builders/filters/operators/index.js +38 -0
- package/server/services/builders/filters/operators/lt.js +13 -0
- package/server/services/builders/filters/operators/lte.js +13 -0
- package/server/services/builders/filters/operators/ne.js +13 -0
- package/server/services/builders/filters/operators/not-contains.js +13 -0
- package/server/services/builders/filters/operators/not-containsi.js +13 -0
- package/server/services/builders/filters/operators/not-in.js +15 -0
- package/server/services/builders/filters/operators/not-null.js +13 -0
- package/server/services/builders/filters/operators/not.js +19 -0
- package/server/services/builders/filters/operators/null.js +13 -0
- package/server/services/builders/filters/operators/or.js +15 -0
- package/server/services/builders/filters/operators/starts-with.js +13 -0
- package/server/services/builders/generic-morph.js +41 -0
- package/server/services/builders/index.js +92 -0
- package/server/services/builders/input.js +118 -0
- package/server/services/builders/mutations/collection-type.js +170 -0
- package/server/services/builders/mutations/index.js +9 -0
- package/server/services/builders/mutations/single-type.js +135 -0
- package/server/services/builders/queries/collection-type.js +120 -0
- package/server/services/builders/queries/index.js +9 -0
- package/server/services/builders/queries/single-type.js +70 -0
- package/server/services/builders/relation-response-collection.js +35 -0
- package/server/services/builders/resolvers/association.js +64 -0
- package/server/services/builders/resolvers/component.js +14 -0
- package/server/services/builders/resolvers/dynamic-zone.js +9 -0
- package/server/services/builders/resolvers/index.js +18 -0
- package/server/services/builders/resolvers/mutation.js +33 -0
- package/server/services/builders/resolvers/query.js +19 -0
- package/server/services/builders/response-collection.js +43 -0
- package/server/services/builders/response.js +32 -0
- package/server/services/builders/type.js +370 -0
- package/server/services/builders/utils.js +131 -0
- package/server/services/constants.js +147 -0
- package/server/services/content-api/index.js +168 -0
- package/server/services/content-api/policy.js +59 -0
- package/server/services/content-api/register-functions/collection-type.js +72 -0
- package/server/services/content-api/register-functions/component.js +15 -0
- package/server/services/content-api/register-functions/content-type/dynamic-zones.js +36 -0
- package/server/services/content-api/register-functions/content-type/enums.js +33 -0
- package/server/services/content-api/register-functions/content-type/filters.js +15 -0
- package/server/services/content-api/register-functions/content-type/index.js +13 -0
- package/server/services/content-api/register-functions/content-type/inputs.js +21 -0
- package/server/services/content-api/register-functions/index.js +22 -0
- package/server/services/content-api/register-functions/internals.js +13 -0
- package/server/services/content-api/register-functions/polymorphic.js +69 -0
- package/server/services/content-api/register-functions/scalars.js +14 -0
- package/server/services/content-api/register-functions/single-type.js +72 -0
- package/server/services/content-api/wrap-resolvers.js +146 -0
- package/server/services/extension/extension.js +95 -0
- package/server/services/extension/index.js +5 -0
- package/server/services/extension/shadow-crud-manager.js +159 -0
- package/server/services/format/index.js +7 -0
- package/server/services/format/return-types.js +27 -0
- package/server/services/index.js +21 -0
- package/server/services/internals/args/index.js +11 -0
- package/server/services/internals/args/pagination.js +19 -0
- package/server/services/internals/args/publication-state.js +12 -0
- package/server/services/internals/args/sort.js +10 -0
- package/server/services/internals/helpers/get-enabled-scalars.js +15 -0
- package/server/services/internals/helpers/index.js +7 -0
- package/server/services/internals/index.js +13 -0
- package/server/services/internals/scalars/index.js +18 -0
- package/server/services/internals/scalars/time.js +35 -0
- package/server/services/internals/types/error.js +33 -0
- package/server/services/internals/types/filters.js +39 -0
- package/server/services/internals/types/index.js +29 -0
- package/server/services/internals/types/pagination.js +24 -0
- package/server/services/internals/types/publication-state.js +24 -0
- package/server/services/internals/types/response-collection-meta.js +38 -0
- package/server/services/type-registry.js +103 -0
- package/server/services/utils/attributes.js +84 -0
- package/server/services/utils/index.js +11 -0
- package/server/services/utils/mappers/entity-to-response-entity.js +12 -0
- package/server/services/utils/mappers/graphql-filters-to-strapi-query.js +107 -0
- package/server/services/utils/mappers/graphql-scalar-to-operators.js +17 -0
- package/server/services/utils/mappers/index.js +13 -0
- package/server/services/utils/mappers/strapi-scalar-to-graphql-scalar.js +24 -0
- package/server/services/utils/naming.js +282 -0
- package/strapi-admin.js +3 -0
- package/strapi-server.js +11 -0
- package/config/routes.json +0 -3
- package/config/schema.graphql +0 -1
- package/config/settings.json +0 -12
- package/controllers/GraphQL.js +0 -9
- package/hooks/graphql/defaults.json +0 -5
- package/hooks/graphql/index.js +0 -174
- package/hooks/graphql/load-config.js +0 -42
- package/services/build-aggregation.js +0 -565
- package/services/data-loaders.js +0 -55
- package/services/naming.js +0 -15
- package/services/resolvers-builder.js +0 -204
- package/services/schema-definitions.js +0 -131
- package/services/schema-generator.js +0 -178
- package/services/shadow-crud.js +0 -612
- package/services/type-builder.js +0 -311
- package/services/utils.js +0 -200
- package/types/dynamiczoneScalar.js +0 -40
- package/types/publication-state.js +0 -16
- package/types/time.js +0 -26
|
@@ -0,0 +1,370 @@
|
|
|
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);
|
|
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
|
+
builder.field(attributeName, { type, resolve, args });
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
const isNotPrivate = contentType => attributeName => {
|
|
237
|
+
return !contentTypes.isPrivateAttribute(contentType, attributeName);
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
const isNotDisabled = contentType => attributeName => {
|
|
241
|
+
return extension
|
|
242
|
+
.shadowCRUD(contentType.uid)
|
|
243
|
+
.field(attributeName)
|
|
244
|
+
.hasOutputEnabled();
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
return {
|
|
248
|
+
/**
|
|
249
|
+
* Create a type definition for a given content type
|
|
250
|
+
* @param contentType - The content type used to created the definition
|
|
251
|
+
* @return {NexusObjectTypeDef}
|
|
252
|
+
*/
|
|
253
|
+
buildTypeDefinition(contentType) {
|
|
254
|
+
const utils = getGraphQLService('utils');
|
|
255
|
+
|
|
256
|
+
const { getComponentName, getTypeName } = utils.naming;
|
|
257
|
+
const {
|
|
258
|
+
isStrapiScalar,
|
|
259
|
+
isComponent,
|
|
260
|
+
isDynamicZone,
|
|
261
|
+
isEnumeration,
|
|
262
|
+
isMedia,
|
|
263
|
+
isMorphRelation,
|
|
264
|
+
isRelation,
|
|
265
|
+
} = utils.attributes;
|
|
266
|
+
|
|
267
|
+
const { attributes, modelType, options = {} } = contentType;
|
|
268
|
+
|
|
269
|
+
const attributesKey = Object.keys(attributes);
|
|
270
|
+
const hasTimestamps = isArray(options.timestamps);
|
|
271
|
+
|
|
272
|
+
const name = (modelType === 'component' ? getComponentName : getTypeName).call(
|
|
273
|
+
null,
|
|
274
|
+
contentType
|
|
275
|
+
);
|
|
276
|
+
|
|
277
|
+
return objectType({
|
|
278
|
+
name,
|
|
279
|
+
|
|
280
|
+
definition(t) {
|
|
281
|
+
if (modelType === 'component' && isNotDisabled(contentType)('id')) {
|
|
282
|
+
t.nonNull.id('id');
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// 1. Timestamps
|
|
286
|
+
// If the content type has timestamps enabled
|
|
287
|
+
// then we should add the corresponding attributes in the definition
|
|
288
|
+
if (hasTimestamps) {
|
|
289
|
+
const [createdAtKey, updatedAtKey] = contentType.options.timestamps;
|
|
290
|
+
|
|
291
|
+
t.nonNull.dateTime(createdAtKey);
|
|
292
|
+
t.nonNull.dateTime(updatedAtKey);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/** 2. Attributes
|
|
296
|
+
*
|
|
297
|
+
* Attributes can be of 7 different kind:
|
|
298
|
+
* - Scalar
|
|
299
|
+
* - Component
|
|
300
|
+
* - Dynamic Zone
|
|
301
|
+
* - Enum
|
|
302
|
+
* - Media
|
|
303
|
+
* - Polymorphic Relations
|
|
304
|
+
* - Regular Relations
|
|
305
|
+
*
|
|
306
|
+
* Here, we iterate over each non-private attribute
|
|
307
|
+
* and add it to the type definition based on its type
|
|
308
|
+
*/
|
|
309
|
+
attributesKey
|
|
310
|
+
// Ignore private attributes
|
|
311
|
+
.filter(isNotPrivate(contentType))
|
|
312
|
+
// Ignore disabled fields (from extension service)
|
|
313
|
+
.filter(isNotDisabled(contentType))
|
|
314
|
+
// Add each attribute to the type definition
|
|
315
|
+
.forEach(attributeName => {
|
|
316
|
+
const attribute = attributes[attributeName];
|
|
317
|
+
|
|
318
|
+
// We create a copy of the builder (t) to apply custom
|
|
319
|
+
// rules only on the current attribute (eg: nonNull, list, ...)
|
|
320
|
+
let builder = t;
|
|
321
|
+
|
|
322
|
+
if (attribute.required) {
|
|
323
|
+
builder = builder.nonNull;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* @type {TypeBuildersOptions}
|
|
328
|
+
*/
|
|
329
|
+
const options = { builder, attributeName, attribute, contentType, context };
|
|
330
|
+
|
|
331
|
+
// Scalars
|
|
332
|
+
if (isStrapiScalar(attribute)) {
|
|
333
|
+
addScalarAttribute(options);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// Components
|
|
337
|
+
else if (isComponent(attribute)) {
|
|
338
|
+
addComponentAttribute(options);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// Dynamic Zones
|
|
342
|
+
else if (isDynamicZone(attribute)) {
|
|
343
|
+
addDynamicZoneAttribute(options);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// Enums
|
|
347
|
+
else if (isEnumeration(attribute)) {
|
|
348
|
+
addEnumAttribute(options);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// Media
|
|
352
|
+
else if (isMedia(attribute)) {
|
|
353
|
+
addMediaAttribute(options);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// Polymorphic Relations
|
|
357
|
+
else if (isMorphRelation(attribute)) {
|
|
358
|
+
addPolymorphicRelationalAttribute(options);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// Regular Relations
|
|
362
|
+
else if (isRelation(attribute) || isMedia(attribute)) {
|
|
363
|
+
addRegularRelationalAttribute(options);
|
|
364
|
+
}
|
|
365
|
+
});
|
|
366
|
+
},
|
|
367
|
+
});
|
|
368
|
+
},
|
|
369
|
+
};
|
|
370
|
+
};
|
|
@@ -0,0 +1,131 @@
|
|
|
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
|
+
return {
|
|
29
|
+
filters: naming.getFiltersInputTypeName(contentType),
|
|
30
|
+
pagination: args.PaginationArg,
|
|
31
|
+
sort: args.SortArg,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Collection Types
|
|
36
|
+
else if (kind === 'collectionType') {
|
|
37
|
+
if (!multiple) {
|
|
38
|
+
return { id: 'ID' };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const params = {
|
|
42
|
+
filters: naming.getFiltersInputTypeName(contentType),
|
|
43
|
+
pagination: args.PaginationArg,
|
|
44
|
+
sort: args.SortArg,
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
if (hasDraftAndPublish(contentType)) {
|
|
48
|
+
Object.assign(params, { publicationState: args.PublicationStateArg });
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return params;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Single Types
|
|
55
|
+
else if (kind === 'singleType') {
|
|
56
|
+
const params = {};
|
|
57
|
+
|
|
58
|
+
if (hasDraftAndPublish(contentType)) {
|
|
59
|
+
Object.assign(params, { publicationState: args.PublicationStateArg });
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return params;
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Filter an object entries and keep only those whose value is a unique scalar attribute
|
|
68
|
+
* @param {object} attributes
|
|
69
|
+
* @return {Object<string, object>}
|
|
70
|
+
*/
|
|
71
|
+
getUniqueScalarAttributes(attributes) {
|
|
72
|
+
const { isStrapiScalar } = getService('utils').attributes;
|
|
73
|
+
|
|
74
|
+
const uniqueAttributes = entries(attributes).filter(
|
|
75
|
+
([, attribute]) => isStrapiScalar(attribute) && attribute.unique
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
return Object.fromEntries(uniqueAttributes);
|
|
79
|
+
},
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Map each value from an attribute to a FiltersInput type name
|
|
83
|
+
* @param {object} attributes - The attributes object to transform
|
|
84
|
+
* @return {Object<string, string>}
|
|
85
|
+
*/
|
|
86
|
+
scalarAttributesToFiltersMap: mapValues(attribute => {
|
|
87
|
+
const { mappers, naming } = getService('utils');
|
|
88
|
+
|
|
89
|
+
const gqlScalar = mappers.strapiScalarToGraphQLScalar(attribute.type);
|
|
90
|
+
|
|
91
|
+
return naming.getScalarFilterInputTypeName(gqlScalar);
|
|
92
|
+
}),
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Apply basic transform to GQL args
|
|
96
|
+
*/
|
|
97
|
+
transformArgs(args, { contentType, usePagination = false } = {}) {
|
|
98
|
+
const { mappers } = getService('utils');
|
|
99
|
+
const { pagination = {}, filters = {} } = args;
|
|
100
|
+
|
|
101
|
+
// Init
|
|
102
|
+
const newArgs = omit(['pagination', 'filters'], args);
|
|
103
|
+
|
|
104
|
+
// Pagination
|
|
105
|
+
if (usePagination) {
|
|
106
|
+
const defaultLimit = strapi.plugin('graphql').config('defaultLimit');
|
|
107
|
+
const maxLimit = strapi.plugin('graphql').config('maxLimit', -1);
|
|
108
|
+
|
|
109
|
+
Object.assign(
|
|
110
|
+
newArgs,
|
|
111
|
+
withDefaultPagination(pagination, {
|
|
112
|
+
maxLimit,
|
|
113
|
+
defaults: {
|
|
114
|
+
offset: { limit: defaultLimit },
|
|
115
|
+
page: { pageSize: defaultLimit },
|
|
116
|
+
},
|
|
117
|
+
})
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Filters
|
|
122
|
+
if (args.filters) {
|
|
123
|
+
Object.assign(newArgs, {
|
|
124
|
+
filters: mappers.graphQLFiltersToStrapiQuery(filters, contentType),
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return newArgs;
|
|
129
|
+
},
|
|
130
|
+
};
|
|
131
|
+
};
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const PAGINATION_TYPE_NAME = 'Pagination';
|
|
4
|
+
const PUBLICATION_STATE_TYPE_NAME = 'PublicationState';
|
|
5
|
+
const ERROR_TYPE_NAME = 'Error';
|
|
6
|
+
|
|
7
|
+
const RESPONSE_COLLECTION_META_TYPE_NAME = 'ResponseCollectionMeta';
|
|
8
|
+
|
|
9
|
+
const GRAPHQL_SCALARS = [
|
|
10
|
+
'ID',
|
|
11
|
+
'Boolean',
|
|
12
|
+
'Int',
|
|
13
|
+
'String',
|
|
14
|
+
'Long',
|
|
15
|
+
'Float',
|
|
16
|
+
'JSON',
|
|
17
|
+
'Date',
|
|
18
|
+
'Time',
|
|
19
|
+
'DateTime',
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
const STRAPI_SCALARS = [
|
|
23
|
+
'boolean',
|
|
24
|
+
'integer',
|
|
25
|
+
'string',
|
|
26
|
+
'richtext',
|
|
27
|
+
'biginteger',
|
|
28
|
+
'float',
|
|
29
|
+
'decimal',
|
|
30
|
+
'json',
|
|
31
|
+
'date',
|
|
32
|
+
'time',
|
|
33
|
+
'datetime',
|
|
34
|
+
'timestamp',
|
|
35
|
+
'uid',
|
|
36
|
+
'email',
|
|
37
|
+
'password',
|
|
38
|
+
'text',
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
const SCALARS_ASSOCIATIONS = {
|
|
42
|
+
uid: 'String',
|
|
43
|
+
email: 'String',
|
|
44
|
+
password: 'String',
|
|
45
|
+
text: 'String',
|
|
46
|
+
boolean: 'Boolean',
|
|
47
|
+
integer: 'Int',
|
|
48
|
+
string: 'String',
|
|
49
|
+
richtext: 'String',
|
|
50
|
+
biginteger: 'Long',
|
|
51
|
+
float: 'Float',
|
|
52
|
+
decimal: 'Float',
|
|
53
|
+
json: 'JSON',
|
|
54
|
+
date: 'Date',
|
|
55
|
+
time: 'Time',
|
|
56
|
+
datetime: 'DateTime',
|
|
57
|
+
timestamp: 'DateTime',
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const GENERIC_MORPH_TYPENAME = 'GenericMorph';
|
|
61
|
+
|
|
62
|
+
const KINDS = {
|
|
63
|
+
type: 'type',
|
|
64
|
+
component: 'component',
|
|
65
|
+
dynamicZone: 'dynamic-zone',
|
|
66
|
+
enum: 'enum',
|
|
67
|
+
entity: 'entity',
|
|
68
|
+
entityResponse: 'entity-response',
|
|
69
|
+
entityResponseCollection: 'entity-response-collection',
|
|
70
|
+
relationResponseCollection: 'relation-response-collection',
|
|
71
|
+
query: 'query',
|
|
72
|
+
mutation: 'mutation',
|
|
73
|
+
input: 'input',
|
|
74
|
+
filtersInput: 'filters-input',
|
|
75
|
+
scalar: 'scalar',
|
|
76
|
+
morph: 'polymorphic',
|
|
77
|
+
internal: 'internal',
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const allOperators = [
|
|
81
|
+
'and',
|
|
82
|
+
'or',
|
|
83
|
+
'not',
|
|
84
|
+
|
|
85
|
+
'eq',
|
|
86
|
+
'ne',
|
|
87
|
+
|
|
88
|
+
'startsWith',
|
|
89
|
+
'endsWith',
|
|
90
|
+
|
|
91
|
+
'contains',
|
|
92
|
+
'notContains',
|
|
93
|
+
|
|
94
|
+
'containsi',
|
|
95
|
+
'notContainsi',
|
|
96
|
+
|
|
97
|
+
'gt',
|
|
98
|
+
'gte',
|
|
99
|
+
|
|
100
|
+
'lt',
|
|
101
|
+
'lte',
|
|
102
|
+
|
|
103
|
+
'null',
|
|
104
|
+
'notNull',
|
|
105
|
+
|
|
106
|
+
'in',
|
|
107
|
+
'notIn',
|
|
108
|
+
|
|
109
|
+
'between',
|
|
110
|
+
];
|
|
111
|
+
|
|
112
|
+
const GRAPHQL_SCALAR_OPERATORS = {
|
|
113
|
+
// ID
|
|
114
|
+
ID: allOperators,
|
|
115
|
+
// Booleans
|
|
116
|
+
Boolean: allOperators,
|
|
117
|
+
// Strings
|
|
118
|
+
String: allOperators,
|
|
119
|
+
// Numbers
|
|
120
|
+
Int: allOperators,
|
|
121
|
+
Long: allOperators,
|
|
122
|
+
Float: allOperators,
|
|
123
|
+
// Dates
|
|
124
|
+
Date: allOperators,
|
|
125
|
+
Time: allOperators,
|
|
126
|
+
DateTime: allOperators,
|
|
127
|
+
// Others
|
|
128
|
+
JSON: allOperators,
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
const ERROR_CODES = {
|
|
132
|
+
emptyDynamicZone: 'dynamiczone.empty',
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
module.exports = () => ({
|
|
136
|
+
PAGINATION_TYPE_NAME,
|
|
137
|
+
RESPONSE_COLLECTION_META_TYPE_NAME,
|
|
138
|
+
PUBLICATION_STATE_TYPE_NAME,
|
|
139
|
+
GRAPHQL_SCALARS,
|
|
140
|
+
STRAPI_SCALARS,
|
|
141
|
+
GENERIC_MORPH_TYPENAME,
|
|
142
|
+
KINDS,
|
|
143
|
+
GRAPHQL_SCALAR_OPERATORS,
|
|
144
|
+
SCALARS_ASSOCIATIONS,
|
|
145
|
+
ERROR_CODES,
|
|
146
|
+
ERROR_TYPE_NAME,
|
|
147
|
+
});
|