@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.
Files changed (117) hide show
  1. package/admin/src/translations/zh-Hans.json +4 -0
  2. package/package.json +17 -15
  3. package/server/bootstrap.js +124 -0
  4. package/server/services/builders/dynamic-zones.js +96 -0
  5. package/server/services/builders/entity-meta.js +7 -0
  6. package/server/services/builders/entity.js +43 -0
  7. package/server/services/builders/enums.js +24 -0
  8. package/server/services/builders/filters/content-type.js +84 -0
  9. package/server/services/builders/filters/index.js +7 -0
  10. package/server/services/builders/filters/operators/and.js +15 -0
  11. package/server/services/builders/filters/operators/between.js +15 -0
  12. package/server/services/builders/filters/operators/contains.js +13 -0
  13. package/server/services/builders/filters/operators/containsi.js +13 -0
  14. package/server/services/builders/filters/operators/ends-with.js +13 -0
  15. package/server/services/builders/filters/operators/eq.js +19 -0
  16. package/server/services/builders/filters/operators/gt.js +13 -0
  17. package/server/services/builders/filters/operators/gte.js +13 -0
  18. package/server/services/builders/filters/operators/in.js +15 -0
  19. package/server/services/builders/filters/operators/index.js +38 -0
  20. package/server/services/builders/filters/operators/lt.js +13 -0
  21. package/server/services/builders/filters/operators/lte.js +13 -0
  22. package/server/services/builders/filters/operators/ne.js +13 -0
  23. package/server/services/builders/filters/operators/not-contains.js +13 -0
  24. package/server/services/builders/filters/operators/not-containsi.js +13 -0
  25. package/server/services/builders/filters/operators/not-in.js +15 -0
  26. package/server/services/builders/filters/operators/not-null.js +13 -0
  27. package/server/services/builders/filters/operators/not.js +19 -0
  28. package/server/services/builders/filters/operators/null.js +13 -0
  29. package/server/services/builders/filters/operators/or.js +15 -0
  30. package/server/services/builders/filters/operators/starts-with.js +13 -0
  31. package/server/services/builders/generic-morph.js +41 -0
  32. package/server/services/builders/index.js +92 -0
  33. package/server/services/builders/input.js +118 -0
  34. package/server/services/builders/mutations/collection-type.js +170 -0
  35. package/server/services/builders/mutations/index.js +9 -0
  36. package/server/services/builders/mutations/single-type.js +135 -0
  37. package/server/services/builders/queries/collection-type.js +120 -0
  38. package/server/services/builders/queries/index.js +9 -0
  39. package/server/services/builders/queries/single-type.js +70 -0
  40. package/server/services/builders/relation-response-collection.js +35 -0
  41. package/server/services/builders/resolvers/association.js +64 -0
  42. package/server/services/builders/resolvers/component.js +14 -0
  43. package/server/services/builders/resolvers/dynamic-zone.js +9 -0
  44. package/server/services/builders/resolvers/index.js +18 -0
  45. package/server/services/builders/resolvers/mutation.js +33 -0
  46. package/server/services/builders/resolvers/query.js +19 -0
  47. package/server/services/builders/response-collection.js +43 -0
  48. package/server/services/builders/response.js +32 -0
  49. package/server/services/builders/type.js +370 -0
  50. package/server/services/builders/utils.js +131 -0
  51. package/server/services/constants.js +147 -0
  52. package/server/services/content-api/index.js +168 -0
  53. package/server/services/content-api/policy.js +59 -0
  54. package/server/services/content-api/register-functions/collection-type.js +72 -0
  55. package/server/services/content-api/register-functions/component.js +15 -0
  56. package/server/services/content-api/register-functions/content-type/dynamic-zones.js +36 -0
  57. package/server/services/content-api/register-functions/content-type/enums.js +33 -0
  58. package/server/services/content-api/register-functions/content-type/filters.js +15 -0
  59. package/server/services/content-api/register-functions/content-type/index.js +13 -0
  60. package/server/services/content-api/register-functions/content-type/inputs.js +21 -0
  61. package/server/services/content-api/register-functions/index.js +22 -0
  62. package/server/services/content-api/register-functions/internals.js +13 -0
  63. package/server/services/content-api/register-functions/polymorphic.js +69 -0
  64. package/server/services/content-api/register-functions/scalars.js +14 -0
  65. package/server/services/content-api/register-functions/single-type.js +72 -0
  66. package/server/services/content-api/wrap-resolvers.js +146 -0
  67. package/server/services/extension/extension.js +95 -0
  68. package/server/services/extension/index.js +5 -0
  69. package/server/services/extension/shadow-crud-manager.js +159 -0
  70. package/server/services/format/index.js +7 -0
  71. package/server/services/format/return-types.js +27 -0
  72. package/server/services/index.js +21 -0
  73. package/server/services/internals/args/index.js +11 -0
  74. package/server/services/internals/args/pagination.js +19 -0
  75. package/server/services/internals/args/publication-state.js +12 -0
  76. package/server/services/internals/args/sort.js +10 -0
  77. package/server/services/internals/helpers/get-enabled-scalars.js +15 -0
  78. package/server/services/internals/helpers/index.js +7 -0
  79. package/server/services/internals/index.js +13 -0
  80. package/server/services/internals/scalars/index.js +18 -0
  81. package/server/services/internals/scalars/time.js +35 -0
  82. package/server/services/internals/types/error.js +33 -0
  83. package/server/services/internals/types/filters.js +39 -0
  84. package/server/services/internals/types/index.js +29 -0
  85. package/server/services/internals/types/pagination.js +24 -0
  86. package/server/services/internals/types/publication-state.js +24 -0
  87. package/server/services/internals/types/response-collection-meta.js +38 -0
  88. package/server/services/type-registry.js +103 -0
  89. package/server/services/utils/attributes.js +84 -0
  90. package/server/services/utils/index.js +11 -0
  91. package/server/services/utils/mappers/entity-to-response-entity.js +12 -0
  92. package/server/services/utils/mappers/graphql-filters-to-strapi-query.js +107 -0
  93. package/server/services/utils/mappers/graphql-scalar-to-operators.js +17 -0
  94. package/server/services/utils/mappers/index.js +13 -0
  95. package/server/services/utils/mappers/strapi-scalar-to-graphql-scalar.js +24 -0
  96. package/server/services/utils/naming.js +282 -0
  97. package/strapi-admin.js +3 -0
  98. package/strapi-server.js +11 -0
  99. package/config/routes.json +0 -3
  100. package/config/schema.graphql +0 -1
  101. package/config/settings.json +0 -12
  102. package/controllers/GraphQL.js +0 -9
  103. package/hooks/graphql/defaults.json +0 -5
  104. package/hooks/graphql/index.js +0 -174
  105. package/hooks/graphql/load-config.js +0 -42
  106. package/services/build-aggregation.js +0 -565
  107. package/services/data-loaders.js +0 -55
  108. package/services/naming.js +0 -15
  109. package/services/resolvers-builder.js +0 -204
  110. package/services/schema-definitions.js +0 -131
  111. package/services/schema-generator.js +0 -178
  112. package/services/shadow-crud.js +0 -612
  113. package/services/type-builder.js +0 -311
  114. package/services/utils.js +0 -200
  115. package/types/dynamiczoneScalar.js +0 -40
  116. package/types/publication-state.js +0 -16
  117. package/types/time.js +0 -26
@@ -0,0 +1,13 @@
1
+ 'use strict';
2
+
3
+ const NOT_CONTAINS_FIELD_NAME = 'notContains';
4
+
5
+ module.exports = () => ({
6
+ fieldName: NOT_CONTAINS_FIELD_NAME,
7
+
8
+ strapiOperator: '$notContains',
9
+
10
+ add(t, type) {
11
+ t.field(NOT_CONTAINS_FIELD_NAME, { type });
12
+ },
13
+ });
@@ -0,0 +1,13 @@
1
+ 'use strict';
2
+
3
+ const NOT_CONTAINSI_FIELD_NAME = 'notContainsi';
4
+
5
+ module.exports = () => ({
6
+ fieldName: NOT_CONTAINSI_FIELD_NAME,
7
+
8
+ strapiOperator: '$notContainsi',
9
+
10
+ add(t, type) {
11
+ t.field(NOT_CONTAINSI_FIELD_NAME, { type });
12
+ },
13
+ });
@@ -0,0 +1,15 @@
1
+ 'use strict';
2
+
3
+ const { list } = require('nexus');
4
+
5
+ const NOT_IN_FIELD_NAME = 'notIn';
6
+
7
+ module.exports = () => ({
8
+ fieldName: NOT_IN_FIELD_NAME,
9
+
10
+ strapiOperator: '$notIn',
11
+
12
+ add(t, type) {
13
+ t.field(NOT_IN_FIELD_NAME, { type: list(type) });
14
+ },
15
+ });
@@ -0,0 +1,13 @@
1
+ 'use strict';
2
+
3
+ const NOT_NULL_FIELD_NAME = 'notNull';
4
+
5
+ module.exports = () => ({
6
+ fieldName: NOT_NULL_FIELD_NAME,
7
+
8
+ strapiOperator: '$notNull',
9
+
10
+ add(t) {
11
+ t.boolean(NOT_NULL_FIELD_NAME);
12
+ },
13
+ });
@@ -0,0 +1,19 @@
1
+ 'use strict';
2
+
3
+ const NOT_FIELD_NAME = 'not';
4
+
5
+ module.exports = ({ strapi }) => ({
6
+ fieldName: NOT_FIELD_NAME,
7
+
8
+ strapiOperator: '$not',
9
+
10
+ add(t, type) {
11
+ const { naming, attributes } = strapi.plugin('graphql').service('utils');
12
+
13
+ if (attributes.isGraphQLScalar({ type })) {
14
+ t.field(NOT_FIELD_NAME, { type: naming.getScalarFilterInputTypeName(type) });
15
+ } else {
16
+ t.field(NOT_FIELD_NAME, { type });
17
+ }
18
+ },
19
+ });
@@ -0,0 +1,13 @@
1
+ 'use strict';
2
+
3
+ const NULL_FIELD_NAME = 'null';
4
+
5
+ module.exports = () => ({
6
+ fieldName: NULL_FIELD_NAME,
7
+
8
+ strapiOperator: '$null',
9
+
10
+ add(t) {
11
+ t.boolean(NULL_FIELD_NAME);
12
+ },
13
+ });
@@ -0,0 +1,15 @@
1
+ 'use strict';
2
+
3
+ const { list } = require('nexus');
4
+
5
+ const OR_FIELD_NAME = 'or';
6
+
7
+ module.exports = () => ({
8
+ fieldName: OR_FIELD_NAME,
9
+
10
+ strapiOperator: '$or',
11
+
12
+ add(t, type) {
13
+ t.field(OR_FIELD_NAME, { type: list(type) });
14
+ },
15
+ });
@@ -0,0 +1,13 @@
1
+ 'use strict';
2
+
3
+ const STARTS_WITH_FIELD_NAME = 'startsWith';
4
+
5
+ module.exports = () => ({
6
+ fieldName: STARTS_WITH_FIELD_NAME,
7
+
8
+ strapiOperator: '$startsWith',
9
+
10
+ add(t, type) {
11
+ t.field(STARTS_WITH_FIELD_NAME, { type });
12
+ },
13
+ });
@@ -0,0 +1,41 @@
1
+ 'use strict';
2
+
3
+ const { unionType } = require('nexus');
4
+ const { prop } = require('lodash/fp');
5
+
6
+ module.exports = ({ strapi, registry }) => {
7
+ const { naming } = strapi.plugin('graphql').service('utils');
8
+ const { KINDS, GENERIC_MORPH_TYPENAME } = strapi.plugin('graphql').service('constants');
9
+
10
+ return {
11
+ buildGenericMorphDefinition() {
12
+ return unionType({
13
+ name: GENERIC_MORPH_TYPENAME,
14
+
15
+ resolveType(obj) {
16
+ const contentType = strapi.getModel(obj.__type);
17
+
18
+ if (!contentType) {
19
+ return null;
20
+ }
21
+
22
+ if (contentType.modelType === 'component') {
23
+ return naming.getComponentName(contentType);
24
+ }
25
+
26
+ return naming.getTypeName(contentType);
27
+ },
28
+
29
+ definition(t) {
30
+ const members = registry
31
+ // Resolve every content-type or component
32
+ .where(({ config }) => [KINDS.type, KINDS.component].includes(config.kind))
33
+ // Only keep their name (the type's id)
34
+ .map(prop('name'));
35
+
36
+ t.members(...members);
37
+ },
38
+ });
39
+ },
40
+ };
41
+ };
@@ -0,0 +1,92 @@
1
+ 'use strict';
2
+
3
+ const { merge, map, pipe, reduce } = require('lodash/fp');
4
+
5
+ // Builders Factories
6
+
7
+ const enums = require('./enums');
8
+ const dynamicZone = require('./dynamic-zones');
9
+ const entity = require('./entity');
10
+ const entityMeta = require('./entity-meta');
11
+ const type = require('./type');
12
+ const response = require('./response');
13
+ const responseCollection = require('./response-collection');
14
+ const relationResponseCollection = require('./relation-response-collection');
15
+ const queries = require('./queries');
16
+ const mutations = require('./mutations');
17
+ const filters = require('./filters');
18
+ const inputs = require('./input');
19
+ const genericMorph = require('./generic-morph');
20
+ const resolvers = require('./resolvers');
21
+
22
+ // Misc
23
+
24
+ const operators = require('./filters/operators');
25
+ const utils = require('./utils');
26
+
27
+ const buildersFactories = [
28
+ enums,
29
+ dynamicZone,
30
+ entity,
31
+ entityMeta,
32
+ type,
33
+ response,
34
+ responseCollection,
35
+ relationResponseCollection,
36
+ queries,
37
+ mutations,
38
+ filters,
39
+ inputs,
40
+ genericMorph,
41
+ resolvers,
42
+ ];
43
+
44
+ module.exports = ({ strapi }) => {
45
+ const buildersMap = new Map();
46
+
47
+ return {
48
+ /**
49
+ * Instantiate every builder with a strapi instance & a type registry
50
+ * @param {string} name
51
+ * @param {object} registry
52
+ */
53
+ new(name, registry) {
54
+ const context = { strapi, registry };
55
+
56
+ const builders = pipe(
57
+ // Create a new instance of every builders
58
+ map(factory => factory(context)),
59
+ // Merge every builder into the same object
60
+ reduce(merge, {})
61
+ ).call(null, buildersFactories);
62
+
63
+ buildersMap.set(name, builders);
64
+
65
+ return builders;
66
+ },
67
+
68
+ /**
69
+ * Delete a set of builders instances from
70
+ * the builders map for a given name
71
+ * @param {string} name
72
+ */
73
+ delete(name) {
74
+ buildersMap.delete(name);
75
+ },
76
+
77
+ /**
78
+ * Retrieve a set of builders instances from
79
+ * the builders map for a given name
80
+ * @param {string} name
81
+ */
82
+ get(name) {
83
+ return buildersMap.get(name);
84
+ },
85
+
86
+ filters: {
87
+ operators: operators({ strapi }),
88
+ },
89
+
90
+ utils: utils({ strapi }),
91
+ };
92
+ };
@@ -0,0 +1,118 @@
1
+ 'use strict';
2
+
3
+ const { inputObjectType, nonNull } = require('nexus');
4
+
5
+ module.exports = context => {
6
+ const { strapi } = context;
7
+
8
+ const { naming, mappers, attributes } = strapi.plugin('graphql').service('utils');
9
+ const extension = strapi.plugin('graphql').service('extension');
10
+
11
+ const {
12
+ getComponentInputName,
13
+ getContentTypeInputName,
14
+ getEnumName,
15
+ getDynamicZoneInputName,
16
+ } = naming;
17
+
18
+ const {
19
+ isStrapiScalar,
20
+ isRelation,
21
+ isMorphRelation,
22
+ isMedia,
23
+ isEnumeration,
24
+ isComponent,
25
+ isDynamicZone,
26
+ } = attributes;
27
+
28
+ return {
29
+ buildInputType(contentType) {
30
+ const { attributes, modelType } = contentType;
31
+
32
+ const name = (modelType === 'component'
33
+ ? getComponentInputName
34
+ : getContentTypeInputName
35
+ ).call(null, contentType);
36
+
37
+ return inputObjectType({
38
+ name,
39
+
40
+ definition(t) {
41
+ const isFieldEnabled = fieldName => {
42
+ return extension
43
+ .shadowCRUD(contentType.uid)
44
+ .field(fieldName)
45
+ .hasInputEnabled();
46
+ };
47
+
48
+ const validAttributes = Object.entries(attributes).filter(([attributeName]) =>
49
+ isFieldEnabled(attributeName)
50
+ );
51
+
52
+ // Add the ID for the component to enable inplace updates
53
+ if (modelType === 'component' && isFieldEnabled('id')) {
54
+ t.id('id');
55
+ }
56
+
57
+ validAttributes.forEach(([attributeName, attribute]) => {
58
+ // Scalars
59
+ if (isStrapiScalar(attribute)) {
60
+ const gqlScalar = mappers.strapiScalarToGraphQLScalar(attribute.type);
61
+
62
+ t.field(attributeName, { type: gqlScalar });
63
+ }
64
+
65
+ // Media
66
+ else if (isMedia(attribute)) {
67
+ const isMultiple = attribute.multiple === true;
68
+
69
+ if (extension.shadowCRUD('plugin::upload.file').isDisabled()) {
70
+ return;
71
+ }
72
+
73
+ isMultiple ? t.list.id(attributeName) : t.id(attributeName);
74
+ }
75
+
76
+ // Regular Relations (ignore polymorphic relations)
77
+ else if (isRelation(attribute) && !isMorphRelation(attribute)) {
78
+ if (extension.shadowCRUD(attribute.target).isDisabled()) {
79
+ return;
80
+ }
81
+
82
+ const isToManyRelation = attribute.relation.endsWith('Many');
83
+
84
+ isToManyRelation ? t.list.id(attributeName) : t.id(attributeName);
85
+ }
86
+
87
+ // Enums
88
+ else if (isEnumeration(attribute)) {
89
+ const enumTypeName = getEnumName(contentType, attributeName);
90
+
91
+ t.field(attributeName, { type: enumTypeName });
92
+ }
93
+
94
+ // Components
95
+ else if (isComponent(attribute)) {
96
+ const isRepeatable = attribute.repeatable === true;
97
+ const component = strapi.components[attribute.component];
98
+ const componentInputType = getComponentInputName(component);
99
+
100
+ if (isRepeatable) {
101
+ t.list.field(attributeName, { type: componentInputType });
102
+ } else {
103
+ t.field(attributeName, { type: componentInputType });
104
+ }
105
+ }
106
+
107
+ // Dynamic Zones
108
+ else if (isDynamicZone(attribute)) {
109
+ const dzInputName = getDynamicZoneInputName(contentType, attributeName);
110
+
111
+ t.list.field(attributeName, { type: nonNull(dzInputName) });
112
+ }
113
+ });
114
+ },
115
+ });
116
+ },
117
+ };
118
+ };
@@ -0,0 +1,170 @@
1
+ 'use strict';
2
+
3
+ const { extendType, nonNull } = require('nexus');
4
+
5
+ module.exports = ({ strapi }) => {
6
+ const { service: getService } = strapi.plugin('graphql');
7
+
8
+ const { naming } = getService('utils');
9
+ const { transformArgs } = getService('builders').utils;
10
+ const { toEntityResponse } = getService('format').returnTypes;
11
+
12
+ const {
13
+ getCreateMutationTypeName,
14
+ getUpdateMutationTypeName,
15
+ getDeleteMutationTypeName,
16
+ getEntityResponseName,
17
+ getContentTypeInputName,
18
+ } = naming;
19
+
20
+ const addCreateMutation = (t, contentType) => {
21
+ const { uid } = contentType;
22
+
23
+ const createMutationName = getCreateMutationTypeName(contentType);
24
+ const responseTypeName = getEntityResponseName(contentType);
25
+
26
+ t.field(createMutationName, {
27
+ type: responseTypeName,
28
+
29
+ args: {
30
+ // Create payload
31
+ data: nonNull(getContentTypeInputName(contentType)),
32
+ },
33
+
34
+ async resolve(parent, args) {
35
+ const transformedArgs = transformArgs(args, { contentType });
36
+
37
+ const { create } = getService('builders')
38
+ .get('content-api')
39
+ .buildMutationsResolvers({ contentType });
40
+
41
+ const value = await create(parent, transformedArgs);
42
+
43
+ return toEntityResponse(value, { args: transformedArgs, resourceUID: uid });
44
+ },
45
+ });
46
+ };
47
+
48
+ const addUpdateMutation = (t, contentType) => {
49
+ const { uid } = contentType;
50
+
51
+ const updateMutationName = getUpdateMutationTypeName(contentType);
52
+ const responseTypeName = getEntityResponseName(contentType);
53
+
54
+ // todo[v4]: Don't allow to filter using every unique attributes for now
55
+ // Only authorize filtering using unique scalar fields for updateOne queries
56
+ // const uniqueAttributes = getUniqueAttributesFiltersMap(attributes);
57
+
58
+ t.field(updateMutationName, {
59
+ type: responseTypeName,
60
+
61
+ args: {
62
+ // Query args
63
+ id: nonNull('ID'),
64
+ // todo[v4]: Don't allow to filter using every unique attributes for now
65
+ // ...uniqueAttributes,
66
+
67
+ // Update payload
68
+ data: nonNull(getContentTypeInputName(contentType)),
69
+ },
70
+
71
+ async resolve(parent, args) {
72
+ const transformedArgs = transformArgs(args, { contentType });
73
+
74
+ const { update } = getService('builders')
75
+ .get('content-api')
76
+ .buildMutationsResolvers({ contentType });
77
+
78
+ const value = await update(parent, transformedArgs);
79
+
80
+ return toEntityResponse(value, { args: transformedArgs, resourceUID: uid });
81
+ },
82
+ });
83
+ };
84
+
85
+ const addDeleteMutation = (t, contentType) => {
86
+ const { uid } = contentType;
87
+
88
+ const deleteMutationName = getDeleteMutationTypeName(contentType);
89
+ const responseTypeName = getEntityResponseName(contentType);
90
+
91
+ // todo[v4]: Don't allow to filter using every unique attributes for now
92
+ // Only authorize filtering using unique scalar fields for updateOne queries
93
+ // const uniqueAttributes = getUniqueAttributesFiltersMap(attributes);
94
+
95
+ t.field(deleteMutationName, {
96
+ type: responseTypeName,
97
+
98
+ args: {
99
+ // Query args
100
+ id: nonNull('ID'),
101
+ // todo[v4]: Don't allow to filter using every unique attributes for now
102
+ // ...uniqueAttributes,
103
+ },
104
+
105
+ async resolve(parent, args) {
106
+ const transformedArgs = transformArgs(args, { contentType });
107
+
108
+ const { delete: deleteResolver } = getService('builders')
109
+ .get('content-api')
110
+ .buildMutationsResolvers({ contentType });
111
+
112
+ const value = await deleteResolver(parent, args);
113
+
114
+ return toEntityResponse(value, { args: transformedArgs, resourceUID: uid });
115
+ },
116
+ });
117
+ };
118
+
119
+ return {
120
+ buildCollectionTypeMutations(contentType) {
121
+ const createMutationName = `Mutation.${getCreateMutationTypeName(contentType)}`;
122
+ const updateMutationName = `Mutation.${getUpdateMutationTypeName(contentType)}`;
123
+ const deleteMutationName = `Mutation.${getDeleteMutationTypeName(contentType)}`;
124
+
125
+ const extension = getService('extension');
126
+
127
+ const registerAuthConfig = (action, auth) => {
128
+ return extension.use({ resolversConfig: { [action]: { auth } } });
129
+ };
130
+
131
+ const isActionEnabled = action => {
132
+ return extension.shadowCRUD(contentType.uid).isActionEnabled(action);
133
+ };
134
+
135
+ const isCreateEnabled = isActionEnabled('create');
136
+ const isUpdateEnabled = isActionEnabled('update');
137
+ const isDeleteEnabled = isActionEnabled('delete');
138
+
139
+ if (isCreateEnabled) {
140
+ registerAuthConfig(createMutationName, { scope: [`${contentType.uid}.create`] });
141
+ }
142
+
143
+ if (isUpdateEnabled) {
144
+ registerAuthConfig(updateMutationName, { scope: [`${contentType.uid}.update`] });
145
+ }
146
+
147
+ if (isDeleteEnabled) {
148
+ registerAuthConfig(deleteMutationName, { scope: [`${contentType.uid}.delete`] });
149
+ }
150
+
151
+ return extendType({
152
+ type: 'Mutation',
153
+
154
+ definition(t) {
155
+ if (isCreateEnabled) {
156
+ addCreateMutation(t, contentType);
157
+ }
158
+
159
+ if (isUpdateEnabled) {
160
+ addUpdateMutation(t, contentType);
161
+ }
162
+
163
+ if (isDeleteEnabled) {
164
+ addDeleteMutation(t, contentType);
165
+ }
166
+ },
167
+ });
168
+ },
169
+ };
170
+ };
@@ -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,135 @@
1
+ 'use strict';
2
+
3
+ const { extendType, nonNull } = require('nexus');
4
+ const { omit, isNil } = require('lodash/fp');
5
+ const { getNonWritableAttributes } = require('@strapi/utils').contentTypes;
6
+
7
+ const sanitizeInput = (contentType, data) => omit(getNonWritableAttributes(contentType), data);
8
+
9
+ module.exports = ({ strapi }) => {
10
+ const { service: getService } = strapi.plugin('graphql');
11
+
12
+ const { naming } = getService('utils');
13
+ const { transformArgs } = getService('builders').utils;
14
+ const { toEntityResponse } = getService('format').returnTypes;
15
+
16
+ const {
17
+ getUpdateMutationTypeName,
18
+ getEntityResponseName,
19
+ getContentTypeInputName,
20
+ getDeleteMutationTypeName,
21
+ } = naming;
22
+
23
+ const addUpdateMutation = (t, contentType) => {
24
+ const { uid } = contentType;
25
+
26
+ const updateMutationName = getUpdateMutationTypeName(contentType);
27
+ const responseTypeName = getEntityResponseName(contentType);
28
+
29
+ t.field(updateMutationName, {
30
+ type: responseTypeName,
31
+
32
+ args: {
33
+ // Update payload
34
+ data: nonNull(getContentTypeInputName(contentType)),
35
+ },
36
+
37
+ async resolve(parent, args) {
38
+ const transformedArgs = transformArgs(args, { contentType });
39
+
40
+ // Sanitize input data
41
+ Object.assign(transformedArgs, { data: sanitizeInput(contentType, transformedArgs.data) });
42
+
43
+ const { create, update } = getService('builders')
44
+ .get('content-api')
45
+ .buildMutationsResolvers({ contentType });
46
+
47
+ const findParams = omit(['data', 'files'], transformedArgs);
48
+ const entity = await strapi.entityService.findMany(uid, { params: findParams });
49
+
50
+ // Create or update
51
+ const value = isNil(entity)
52
+ ? create(parent, transformedArgs)
53
+ : update(uid, { id: entity.id, data: transformedArgs.data });
54
+
55
+ return toEntityResponse(value, { args: transformedArgs, resourceUID: uid });
56
+ },
57
+ });
58
+ };
59
+
60
+ const addDeleteMutation = (t, contentType) => {
61
+ const { uid } = contentType;
62
+
63
+ const deleteMutationName = getDeleteMutationTypeName(contentType);
64
+ const responseTypeName = getEntityResponseName(contentType);
65
+
66
+ t.field(deleteMutationName, {
67
+ type: responseTypeName,
68
+
69
+ args: {},
70
+
71
+ async resolve(parent, args) {
72
+ const transformedArgs = transformArgs(args, { contentType });
73
+
74
+ Object.assign(transformedArgs, { data: sanitizeInput(contentType, transformedArgs.data) });
75
+
76
+ const { delete: deleteResolver } = getService('builders')
77
+ .get('content-api')
78
+ .buildMutationsResolvers({ contentType });
79
+
80
+ const params = omit(['data', 'files'], transformedArgs);
81
+ const entity = await strapi.entityService.findMany(uid, { params });
82
+
83
+ if (!entity) {
84
+ throw new Error('Entity not found');
85
+ }
86
+
87
+ const value = await deleteResolver(parent, { id: entity.id, params });
88
+
89
+ return toEntityResponse(value, { args: transformedArgs, resourceUID: uid });
90
+ },
91
+ });
92
+ };
93
+
94
+ return {
95
+ buildSingleTypeMutations(contentType) {
96
+ const updateMutationName = `Mutation.${getUpdateMutationTypeName(contentType)}`;
97
+ const deleteMutationName = `Mutation.${getDeleteMutationTypeName(contentType)}`;
98
+
99
+ const extension = getService('extension');
100
+
101
+ const registerAuthConfig = (action, auth) => {
102
+ return extension.use({ resolversConfig: { [action]: { auth } } });
103
+ };
104
+
105
+ const isActionEnabled = action => {
106
+ return extension.shadowCRUD(contentType.uid).isActionEnabled(action);
107
+ };
108
+
109
+ const isUpdateEnabled = isActionEnabled('update');
110
+ const isDeleteEnabled = isActionEnabled('delete');
111
+
112
+ if (isUpdateEnabled) {
113
+ registerAuthConfig(updateMutationName, { scope: [`${contentType.uid}.update`] });
114
+ }
115
+
116
+ if (isDeleteEnabled) {
117
+ registerAuthConfig(deleteMutationName, { scope: [`${contentType.uid}.delete`] });
118
+ }
119
+
120
+ return extendType({
121
+ type: 'Mutation',
122
+
123
+ definition(t) {
124
+ if (isUpdateEnabled) {
125
+ addUpdateMutation(t, contentType);
126
+ }
127
+
128
+ if (isDeleteEnabled) {
129
+ addDeleteMutation(t, contentType);
130
+ }
131
+ },
132
+ });
133
+ },
134
+ };
135
+ };