@strapi/plugin-graphql 4.0.0-next.8 → 4.0.2

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 +43 -35
  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 +149 -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 +39 -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 +7 -9
  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,191 @@
1
+ 'use strict';
2
+
3
+ const { extendType, nonNull } = require('nexus');
4
+ const { sanitize } = require('@strapi/utils');
5
+
6
+ module.exports = ({ strapi }) => {
7
+ const { service: getService } = strapi.plugin('graphql');
8
+
9
+ const { naming } = getService('utils');
10
+ const { transformArgs } = getService('builders').utils;
11
+ const { toEntityResponse } = getService('format').returnTypes;
12
+
13
+ const {
14
+ getCreateMutationTypeName,
15
+ getUpdateMutationTypeName,
16
+ getDeleteMutationTypeName,
17
+ getEntityResponseName,
18
+ getContentTypeInputName,
19
+ } = naming;
20
+
21
+ const addCreateMutation = (t, contentType) => {
22
+ const { uid } = contentType;
23
+
24
+ const createMutationName = getCreateMutationTypeName(contentType);
25
+ const responseTypeName = getEntityResponseName(contentType);
26
+
27
+ t.field(createMutationName, {
28
+ type: responseTypeName,
29
+
30
+ args: {
31
+ // Create payload
32
+ data: nonNull(getContentTypeInputName(contentType)),
33
+ },
34
+
35
+ async resolve(parent, args, context) {
36
+ const { auth } = context.state;
37
+ const transformedArgs = transformArgs(args, { contentType });
38
+
39
+ // Sanitize input data
40
+ const sanitizedInputData = await sanitize.contentAPI.input(
41
+ transformedArgs.data,
42
+ contentType,
43
+ { auth }
44
+ );
45
+
46
+ Object.assign(transformedArgs, { data: sanitizedInputData });
47
+
48
+ const { create } = getService('builders')
49
+ .get('content-api')
50
+ .buildMutationsResolvers({ contentType });
51
+
52
+ const value = await create(parent, transformedArgs);
53
+
54
+ return toEntityResponse(value, { args: transformedArgs, resourceUID: uid });
55
+ },
56
+ });
57
+ };
58
+
59
+ const addUpdateMutation = (t, contentType) => {
60
+ const { uid } = contentType;
61
+
62
+ const updateMutationName = getUpdateMutationTypeName(contentType);
63
+ const responseTypeName = getEntityResponseName(contentType);
64
+
65
+ // todo[v4]: Don't allow to filter using every unique attributes for now
66
+ // Only authorize filtering using unique scalar fields for updateOne queries
67
+ // const uniqueAttributes = getUniqueAttributesFiltersMap(attributes);
68
+
69
+ t.field(updateMutationName, {
70
+ type: responseTypeName,
71
+
72
+ args: {
73
+ // Query args
74
+ id: nonNull('ID'),
75
+ // todo[v4]: Don't allow to filter using every unique attributes for now
76
+ // ...uniqueAttributes,
77
+
78
+ // Update payload
79
+ data: nonNull(getContentTypeInputName(contentType)),
80
+ },
81
+
82
+ async resolve(parent, args, context) {
83
+ const { auth } = context.state;
84
+ const transformedArgs = transformArgs(args, { contentType });
85
+
86
+ // Sanitize input data
87
+ const sanitizedInputData = await sanitize.contentAPI.input(
88
+ transformedArgs.data,
89
+ contentType,
90
+ { auth }
91
+ );
92
+
93
+ Object.assign(transformedArgs, { data: sanitizedInputData });
94
+
95
+ const { update } = getService('builders')
96
+ .get('content-api')
97
+ .buildMutationsResolvers({ contentType });
98
+
99
+ const value = await update(parent, transformedArgs);
100
+
101
+ return toEntityResponse(value, { args: transformedArgs, resourceUID: uid });
102
+ },
103
+ });
104
+ };
105
+
106
+ const addDeleteMutation = (t, contentType) => {
107
+ const { uid } = contentType;
108
+
109
+ const deleteMutationName = getDeleteMutationTypeName(contentType);
110
+ const responseTypeName = getEntityResponseName(contentType);
111
+
112
+ // todo[v4]: Don't allow to filter using every unique attributes for now
113
+ // Only authorize filtering using unique scalar fields for updateOne queries
114
+ // const uniqueAttributes = getUniqueAttributesFiltersMap(attributes);
115
+
116
+ t.field(deleteMutationName, {
117
+ type: responseTypeName,
118
+
119
+ args: {
120
+ // Query args
121
+ id: nonNull('ID'),
122
+ // todo[v4]: Don't allow to filter using every unique attributes for now
123
+ // ...uniqueAttributes,
124
+ },
125
+
126
+ async resolve(parent, args) {
127
+ const transformedArgs = transformArgs(args, { contentType });
128
+
129
+ const { delete: deleteResolver } = getService('builders')
130
+ .get('content-api')
131
+ .buildMutationsResolvers({ contentType });
132
+
133
+ const value = await deleteResolver(parent, args);
134
+
135
+ return toEntityResponse(value, { args: transformedArgs, resourceUID: uid });
136
+ },
137
+ });
138
+ };
139
+
140
+ return {
141
+ buildCollectionTypeMutations(contentType) {
142
+ const createMutationName = `Mutation.${getCreateMutationTypeName(contentType)}`;
143
+ const updateMutationName = `Mutation.${getUpdateMutationTypeName(contentType)}`;
144
+ const deleteMutationName = `Mutation.${getDeleteMutationTypeName(contentType)}`;
145
+
146
+ const extension = getService('extension');
147
+
148
+ const registerAuthConfig = (action, auth) => {
149
+ return extension.use({ resolversConfig: { [action]: { auth } } });
150
+ };
151
+
152
+ const isActionEnabled = action => {
153
+ return extension.shadowCRUD(contentType.uid).isActionEnabled(action);
154
+ };
155
+
156
+ const isCreateEnabled = isActionEnabled('create');
157
+ const isUpdateEnabled = isActionEnabled('update');
158
+ const isDeleteEnabled = isActionEnabled('delete');
159
+
160
+ if (isCreateEnabled) {
161
+ registerAuthConfig(createMutationName, { scope: [`${contentType.uid}.create`] });
162
+ }
163
+
164
+ if (isUpdateEnabled) {
165
+ registerAuthConfig(updateMutationName, { scope: [`${contentType.uid}.update`] });
166
+ }
167
+
168
+ if (isDeleteEnabled) {
169
+ registerAuthConfig(deleteMutationName, { scope: [`${contentType.uid}.delete`] });
170
+ }
171
+
172
+ return extendType({
173
+ type: 'Mutation',
174
+
175
+ definition(t) {
176
+ if (isCreateEnabled) {
177
+ addCreateMutation(t, contentType);
178
+ }
179
+
180
+ if (isUpdateEnabled) {
181
+ addUpdateMutation(t, contentType);
182
+ }
183
+
184
+ if (isDeleteEnabled) {
185
+ addDeleteMutation(t, contentType);
186
+ }
187
+ },
188
+ });
189
+ },
190
+ };
191
+ };
@@ -0,0 +1,9 @@
1
+ 'use strict';
2
+
3
+ const createCollectionTypeMutationsBuilder = require('./collection-type');
4
+ const createSingleTypeMutationsBuilder = require('./single-type');
5
+
6
+ module.exports = context => ({
7
+ ...createCollectionTypeMutationsBuilder(context),
8
+ ...createSingleTypeMutationsBuilder(context),
9
+ });
@@ -0,0 +1,141 @@
1
+ 'use strict';
2
+
3
+ const { extendType, nonNull } = require('nexus');
4
+ const { omit, isNil } = require('lodash/fp');
5
+
6
+ const utils = require('@strapi/utils');
7
+
8
+ const { sanitize } = utils;
9
+ const { NotFoundError } = utils.errors;
10
+
11
+ module.exports = ({ strapi }) => {
12
+ const { service: getService } = strapi.plugin('graphql');
13
+
14
+ const { naming } = getService('utils');
15
+ const { transformArgs } = getService('builders').utils;
16
+ const { toEntityResponse } = getService('format').returnTypes;
17
+
18
+ const {
19
+ getUpdateMutationTypeName,
20
+ getEntityResponseName,
21
+ getContentTypeInputName,
22
+ getDeleteMutationTypeName,
23
+ } = naming;
24
+
25
+ const addUpdateMutation = (t, contentType) => {
26
+ const { uid } = contentType;
27
+
28
+ const updateMutationName = getUpdateMutationTypeName(contentType);
29
+ const responseTypeName = getEntityResponseName(contentType);
30
+
31
+ t.field(updateMutationName, {
32
+ type: responseTypeName,
33
+
34
+ args: {
35
+ // Update payload
36
+ data: nonNull(getContentTypeInputName(contentType)),
37
+ },
38
+
39
+ async resolve(parent, args, context) {
40
+ const { auth } = context.state;
41
+ const transformedArgs = transformArgs(args, { contentType });
42
+
43
+ // Sanitize input data
44
+ const sanitizedInputData = await sanitize.contentAPI.input(
45
+ transformedArgs.data,
46
+ contentType,
47
+ { auth }
48
+ );
49
+
50
+ Object.assign(transformedArgs, { data: sanitizedInputData });
51
+
52
+ const { create, update } = getService('builders')
53
+ .get('content-api')
54
+ .buildMutationsResolvers({ contentType });
55
+
56
+ const findParams = omit(['data', 'files'], transformedArgs);
57
+ const entity = await strapi.entityService.findMany(uid, { params: findParams });
58
+
59
+ // Create or update
60
+ const value = isNil(entity)
61
+ ? create(parent, transformedArgs)
62
+ : update(uid, { id: entity.id, data: transformedArgs.data });
63
+
64
+ return toEntityResponse(value, { args: transformedArgs, resourceUID: uid });
65
+ },
66
+ });
67
+ };
68
+
69
+ const addDeleteMutation = (t, contentType) => {
70
+ const { uid } = contentType;
71
+
72
+ const deleteMutationName = getDeleteMutationTypeName(contentType);
73
+ const responseTypeName = getEntityResponseName(contentType);
74
+
75
+ t.field(deleteMutationName, {
76
+ type: responseTypeName,
77
+
78
+ args: {},
79
+
80
+ async resolve(parent, args) {
81
+ const transformedArgs = transformArgs(args, { contentType });
82
+
83
+ const { delete: deleteResolver } = getService('builders')
84
+ .get('content-api')
85
+ .buildMutationsResolvers({ contentType });
86
+
87
+ const entity = await strapi.entityService.findMany(uid, { params: transformedArgs });
88
+
89
+ if (!entity) {
90
+ throw new NotFoundError('Entity not found');
91
+ }
92
+
93
+ const value = await deleteResolver(parent, { id: entity.id, params: transformedArgs });
94
+
95
+ return toEntityResponse(value, { args: transformedArgs, resourceUID: uid });
96
+ },
97
+ });
98
+ };
99
+
100
+ return {
101
+ buildSingleTypeMutations(contentType) {
102
+ const updateMutationName = `Mutation.${getUpdateMutationTypeName(contentType)}`;
103
+ const deleteMutationName = `Mutation.${getDeleteMutationTypeName(contentType)}`;
104
+
105
+ const extension = getService('extension');
106
+
107
+ const registerAuthConfig = (action, auth) => {
108
+ return extension.use({ resolversConfig: { [action]: { auth } } });
109
+ };
110
+
111
+ const isActionEnabled = action => {
112
+ return extension.shadowCRUD(contentType.uid).isActionEnabled(action);
113
+ };
114
+
115
+ const isUpdateEnabled = isActionEnabled('update');
116
+ const isDeleteEnabled = isActionEnabled('delete');
117
+
118
+ if (isUpdateEnabled) {
119
+ registerAuthConfig(updateMutationName, { scope: [`${contentType.uid}.update`] });
120
+ }
121
+
122
+ if (isDeleteEnabled) {
123
+ registerAuthConfig(deleteMutationName, { scope: [`${contentType.uid}.delete`] });
124
+ }
125
+
126
+ return extendType({
127
+ type: 'Mutation',
128
+
129
+ definition(t) {
130
+ if (isUpdateEnabled) {
131
+ addUpdateMutation(t, contentType);
132
+ }
133
+
134
+ if (isDeleteEnabled) {
135
+ addDeleteMutation(t, contentType);
136
+ }
137
+ },
138
+ });
139
+ },
140
+ };
141
+ };
@@ -0,0 +1,120 @@
1
+ 'use strict';
2
+
3
+ const { extendType } = require('nexus');
4
+
5
+ module.exports = ({ strapi }) => {
6
+ const { service: getService } = strapi.plugin('graphql');
7
+
8
+ const { naming } = getService('utils');
9
+ const { transformArgs, getContentTypeArgs } = getService('builders').utils;
10
+ const { toEntityResponse, toEntityResponseCollection } = getService('format').returnTypes;
11
+
12
+ const {
13
+ getFindOneQueryName,
14
+ getEntityResponseName,
15
+ getFindQueryName,
16
+ getEntityResponseCollectionName,
17
+ } = naming;
18
+
19
+ const buildCollectionTypeQueries = contentType => {
20
+ const findOneQueryName = `Query.${getFindOneQueryName(contentType)}`;
21
+ const findQueryName = `Query.${getFindQueryName(contentType)}`;
22
+
23
+ const extension = getService('extension');
24
+
25
+ const registerAuthConfig = (action, auth) => {
26
+ return extension.use({ resolversConfig: { [action]: { auth } } });
27
+ };
28
+
29
+ const isActionEnabled = action => {
30
+ return extension.shadowCRUD(contentType.uid).isActionEnabled(action);
31
+ };
32
+
33
+ const isFindOneEnabled = isActionEnabled('findOne');
34
+ const isFindEnabled = isActionEnabled('find');
35
+
36
+ if (isFindOneEnabled) {
37
+ registerAuthConfig(findOneQueryName, { scope: [`${contentType.uid}.findOne`] });
38
+ }
39
+
40
+ if (isFindEnabled) {
41
+ registerAuthConfig(findQueryName, { scope: [`${contentType.uid}.find`] });
42
+ }
43
+
44
+ return extendType({
45
+ type: 'Query',
46
+
47
+ definition(t) {
48
+ if (isFindOneEnabled) {
49
+ addFindOneQuery(t, contentType);
50
+ }
51
+
52
+ if (isFindEnabled) {
53
+ addFindQuery(t, contentType);
54
+ }
55
+ },
56
+ });
57
+ };
58
+
59
+ /**
60
+ * Register a "find one" query field to the nexus type definition
61
+ * @param {OutputDefinitionBlock<Query>} t
62
+ * @param contentType
63
+ */
64
+ const addFindOneQuery = (t, contentType) => {
65
+ const { uid } = contentType;
66
+
67
+ const findOneQueryName = getFindOneQueryName(contentType);
68
+ const responseTypeName = getEntityResponseName(contentType);
69
+
70
+ t.field(findOneQueryName, {
71
+ type: responseTypeName,
72
+
73
+ args: getContentTypeArgs(contentType, { multiple: false }),
74
+
75
+ async resolve(parent, args) {
76
+ const transformedArgs = transformArgs(args, { contentType });
77
+
78
+ const { findOne } = getService('builders')
79
+ .get('content-api')
80
+ .buildQueriesResolvers({ contentType });
81
+
82
+ const value = findOne(parent, transformedArgs);
83
+
84
+ return toEntityResponse(value, { args: transformedArgs, resourceUID: uid });
85
+ },
86
+ });
87
+ };
88
+
89
+ /**
90
+ * Register a "find" query field to the nexus type definition
91
+ * @param {OutputDefinitionBlock<Query>} t
92
+ * @param contentType
93
+ */
94
+ const addFindQuery = (t, contentType) => {
95
+ const { uid } = contentType;
96
+
97
+ const findQueryName = getFindQueryName(contentType);
98
+ const responseCollectionTypeName = getEntityResponseCollectionName(contentType);
99
+
100
+ t.field(findQueryName, {
101
+ type: responseCollectionTypeName,
102
+
103
+ args: getContentTypeArgs(contentType),
104
+
105
+ async resolve(parent, args) {
106
+ const transformedArgs = transformArgs(args, { contentType, usePagination: true });
107
+
108
+ const { find } = getService('builders')
109
+ .get('content-api')
110
+ .buildQueriesResolvers({ contentType });
111
+
112
+ const nodes = await find(parent, transformedArgs);
113
+
114
+ return toEntityResponseCollection(nodes, { args: transformedArgs, resourceUID: uid });
115
+ },
116
+ });
117
+ };
118
+
119
+ return { buildCollectionTypeQueries };
120
+ };
@@ -0,0 +1,9 @@
1
+ 'use strict';
2
+
3
+ const createCollectionTypeQueriesBuilder = require('./collection-type');
4
+ const createSingleTypeQueriesBuilder = require('./single-type');
5
+
6
+ module.exports = context => ({
7
+ ...createCollectionTypeQueriesBuilder(context),
8
+ ...createSingleTypeQueriesBuilder(context),
9
+ });
@@ -0,0 +1,70 @@
1
+ 'use strict';
2
+
3
+ const { extendType } = require('nexus');
4
+
5
+ module.exports = ({ strapi }) => {
6
+ const { service: getService } = strapi.plugin('graphql');
7
+
8
+ const { naming } = getService('utils');
9
+ const { transformArgs, getContentTypeArgs } = getService('builders').utils;
10
+ const { toEntityResponse } = getService('format').returnTypes;
11
+
12
+ const { getFindOneQueryName, getEntityResponseName } = naming;
13
+
14
+ const buildSingleTypeQueries = contentType => {
15
+ const findQueryName = `Query.${getFindOneQueryName(contentType)}`;
16
+
17
+ const extension = getService('extension');
18
+
19
+ const registerAuthConfig = (action, auth) => {
20
+ return extension.use({ resolversConfig: { [action]: { auth } } });
21
+ };
22
+
23
+ const isActionEnabled = action => {
24
+ return extension.shadowCRUD(contentType.uid).isActionEnabled(action);
25
+ };
26
+
27
+ const isFindEnabled = isActionEnabled('find');
28
+
29
+ if (isFindEnabled) {
30
+ registerAuthConfig(findQueryName, { scope: [`${contentType.uid}.find`] });
31
+ }
32
+
33
+ return extendType({
34
+ type: 'Query',
35
+
36
+ definition(t) {
37
+ if (isFindEnabled) {
38
+ addFindQuery(t, contentType);
39
+ }
40
+ },
41
+ });
42
+ };
43
+
44
+ const addFindQuery = (t, contentType) => {
45
+ const { uid } = contentType;
46
+
47
+ const findQueryName = getFindOneQueryName(contentType);
48
+ const responseTypeName = getEntityResponseName(contentType);
49
+
50
+ t.field(findQueryName, {
51
+ type: responseTypeName,
52
+
53
+ args: getContentTypeArgs(contentType),
54
+
55
+ async resolve(parent, args) {
56
+ const transformedArgs = transformArgs(args, { contentType });
57
+
58
+ const queriesResolvers = getService('builders')
59
+ .get('content-api')
60
+ .buildQueriesResolvers({ contentType });
61
+
62
+ const value = queriesResolvers.find(parent, transformedArgs);
63
+
64
+ return toEntityResponse(value, { args: transformedArgs, resourceUID: uid });
65
+ },
66
+ });
67
+ };
68
+
69
+ return { buildSingleTypeQueries };
70
+ };
@@ -0,0 +1,35 @@
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
+
9
+ return {
10
+ /**
11
+ * Build a type definition for a content API relation's collection 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
+ buildRelationResponseCollectionDefinition(contentType) {
16
+ const name = naming.getRelationResponseCollectionName(contentType);
17
+ const entityName = naming.getEntityName(contentType);
18
+
19
+ return objectType({
20
+ name,
21
+
22
+ definition(t) {
23
+ t.nonNull.list.field('data', {
24
+ type: nonNull(entityName),
25
+
26
+ resolve: pipe(
27
+ prop('nodes'),
28
+ defaultTo([])
29
+ ),
30
+ });
31
+ },
32
+ });
33
+ },
34
+ };
35
+ };
@@ -0,0 +1,85 @@
1
+ 'use strict';
2
+
3
+ const { get } = require('lodash/fp');
4
+
5
+ const utils = require('@strapi/utils');
6
+
7
+ const { sanitize, pipeAsync } = utils;
8
+ const { ApplicationError } = utils.errors;
9
+
10
+ module.exports = ({ strapi }) => {
11
+ const { service: getGraphQLService } = strapi.plugin('graphql');
12
+
13
+ const { isMorphRelation, isMedia } = getGraphQLService('utils').attributes;
14
+ const { transformArgs } = getGraphQLService('builders').utils;
15
+ const { toEntityResponse, toEntityResponseCollection } = getGraphQLService('format').returnTypes;
16
+
17
+ return {
18
+ buildAssociationResolver({ contentTypeUID, attributeName }) {
19
+ const contentType = strapi.getModel(contentTypeUID);
20
+ const attribute = contentType.attributes[attributeName];
21
+
22
+ if (!attribute) {
23
+ throw new ApplicationError(
24
+ `Failed to build an association resolver for ${contentTypeUID}::${attributeName}`
25
+ );
26
+ }
27
+
28
+ const isMediaAttribute = isMedia(attribute);
29
+ const isMorphAttribute = isMorphRelation(attribute);
30
+
31
+ const targetUID = isMediaAttribute ? 'plugins::upload.file' : attribute.target;
32
+ const isToMany = isMediaAttribute ? attribute.multiple : attribute.relation.endsWith('Many');
33
+
34
+ const targetContentType = strapi.getModel(targetUID);
35
+
36
+ return async (parent, args = {}, context) => {
37
+ const { auth } = context.state;
38
+
39
+ const transformedArgs = transformArgs(args, {
40
+ contentType: targetContentType,
41
+ usePagination: true,
42
+ });
43
+
44
+ const data = await strapi.entityService.load(
45
+ contentTypeUID,
46
+ parent,
47
+ attributeName,
48
+ transformedArgs
49
+ );
50
+
51
+ const info = {
52
+ args: transformedArgs,
53
+ resourceUID: targetUID,
54
+ };
55
+
56
+ // If this a polymorphic association, it sanitizes & returns the raw data
57
+ // Note: The value needs to be wrapped in a fake object that represents its parent
58
+ // so that the sanitize util can work properly.
59
+ if (isMorphAttribute) {
60
+ // Helpers used for the data cleanup
61
+ const wrapData = dataToWrap => ({ [attributeName]: dataToWrap });
62
+ const sanitizeData = dataToSanitize => {
63
+ return sanitize.contentAPI.output(dataToSanitize, contentType, { auth });
64
+ };
65
+ const unwrapData = get(attributeName);
66
+
67
+ // Sanitizer definition
68
+ const sanitizeMorphAttribute = pipeAsync(wrapData, sanitizeData, unwrapData);
69
+
70
+ return sanitizeMorphAttribute(data);
71
+ }
72
+
73
+ // If this is a to-many relation, it returns an object that
74
+ // matches what the entity-response-collection's resolvers expect
75
+ else if (isToMany) {
76
+ return toEntityResponseCollection(data, info);
77
+ }
78
+
79
+ // Else, it returns an object that matches
80
+ // what the entity-response's resolvers expect
81
+ return toEntityResponse(data, info);
82
+ };
83
+ },
84
+ };
85
+ };
@@ -0,0 +1,18 @@
1
+ 'use strict';
2
+
3
+ module.exports = ({ strapi }) => ({
4
+ buildComponentResolver({ contentTypeUID, attributeName }) {
5
+ const { transformArgs } = strapi.plugin('graphql').service('builders').utils;
6
+
7
+ return async (parent, args = {}) => {
8
+ const contentType = strapi.getModel(contentTypeUID);
9
+
10
+ const { component: componentName } = contentType.attributes[attributeName];
11
+ const component = strapi.getModel(componentName);
12
+
13
+ const transformedArgs = transformArgs(args, { contentType: component, usePagination: true });
14
+
15
+ return strapi.entityService.load(contentTypeUID, parent, attributeName, transformedArgs);
16
+ };
17
+ },
18
+ });
@@ -0,0 +1,9 @@
1
+ 'use strict';
2
+
3
+ module.exports = ({ strapi }) => ({
4
+ buildDynamicZoneResolver({ contentTypeUID, attributeName }) {
5
+ return async parent => {
6
+ return strapi.entityService.load(contentTypeUID, parent, attributeName);
7
+ };
8
+ },
9
+ });