@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
package/README.md CHANGED
@@ -3,4 +3,4 @@
3
3
  This plugin will add GraphQL functionality to your app.
4
4
  By default it will provide you with most of the CRUD methods exposed in the Strapi REST API.
5
5
 
6
- To learn more about GraphQL in Strapi [visit documentation](https://strapi.io/documentation/developer-docs/latest/development/plugins/graphql.html)
6
+ To learn more about GraphQL in Strapi [visit documentation](https://docs.strapi.io/developer-docs/latest/plugins/graphql.html)
@@ -1,22 +1,14 @@
1
1
  import { prefixPluginTranslations } from '@strapi/helper-plugin';
2
2
  import pluginPkg from '../../package.json';
3
3
  import pluginId from './pluginId';
4
- import pluginLogo from './assets/images/logo.svg';
5
4
 
6
- const pluginDescription = pluginPkg.strapi.description || pluginPkg.description;
7
- const icon = pluginPkg.strapi.icon;
8
5
  const name = pluginPkg.strapi.name;
9
6
 
10
7
  export default {
11
8
  register(app) {
12
9
  app.registerPlugin({
13
- description: pluginDescription,
14
- icon,
15
10
  id: pluginId,
16
- isReady: true,
17
- isRequired: pluginPkg.strapi.required || false,
18
11
  name,
19
- pluginLogo,
20
12
  });
21
13
  },
22
14
  bootstrap() {},
package/package.json CHANGED
@@ -1,57 +1,65 @@
1
1
  {
2
2
  "name": "@strapi/plugin-graphql",
3
- "version": "4.0.0-next.8",
3
+ "version": "4.0.2",
4
4
  "description": "Adds GraphQL endpoint with default API methods.",
5
- "strapi": {
6
- "displayName": "GraphQL",
7
- "name": "graphql",
8
- "icon": "plug",
9
- "description": "graphql.plugin.description",
10
- "kind": "plugin"
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://github.com/strapi/strapi.git",
8
+ "directory": "packages/plugins/graphql"
9
+ },
10
+ "license": "SEE LICENSE IN LICENSE",
11
+ "author": {
12
+ "name": "Strapi Solutions SAS",
13
+ "email": "hi@strapi.io",
14
+ "url": "https://strapi.io"
11
15
  },
16
+ "maintainers": [
17
+ {
18
+ "name": "Strapi Solutions SAS",
19
+ "email": "hi@strapi.io",
20
+ "url": "https://strapi.io"
21
+ }
22
+ ],
12
23
  "scripts": {
13
- "test": "echo \"no tests yet\""
24
+ "test:front": "cross-env IS_EE=true jest --config ./jest.config.front.js",
25
+ "test:front:watch": "cross-env IS_EE=true jest --config ./jest.config.front.js --watchAll",
26
+ "test:front:ce": "cross-env IS_EE=false jest --config ./jest.config.front.js",
27
+ "test:front:watch:ce": "cross-env IS_EE=false jest --config ./jest.config.front.js --watchAll"
14
28
  },
15
29
  "dependencies": {
16
- "@apollo/federation": "^0.20.7",
17
- "@graphql-tools/utils": "7.2.4",
18
- "@strapi/utils": "4.0.0-next.8",
19
- "apollo-server-koa": "2.24.0",
20
- "dataloader": "^1.4.0",
21
- "glob": "^7.1.6",
22
- "graphql": "15.5.0",
30
+ "@apollo/federation": "^0.28.0",
31
+ "@graphql-tools/schema": "8.1.2",
32
+ "@graphql-tools/utils": "^8.0.2",
33
+ "@strapi/utils": "4.0.2",
34
+ "apollo-server-core": "3.1.2",
35
+ "apollo-server-koa": "3.1.2",
36
+ "glob": "^7.1.7",
37
+ "graphql": "15.5.1",
23
38
  "graphql-depth-limit": "^1.1.0",
24
39
  "graphql-iso-date": "^3.6.1",
25
40
  "graphql-playground-middleware-koa": "^1.6.21",
26
- "graphql-tools": "4.0.8",
27
- "graphql-type-json": "0.3.2",
41
+ "graphql-type-json": "^0.3.2",
28
42
  "graphql-type-long": "^0.1.1",
29
- "graphql-upload": "11.0.0",
43
+ "graphql-upload": "^13.0.0",
30
44
  "koa-compose": "^4.1.0",
31
45
  "lodash": "4.17.21",
32
- "pluralize": "^8.0.0"
46
+ "nexus": "1.1.0",
47
+ "pluralize": "^8.0.0",
48
+ "subscriptions-transport-ws": "0.9.19"
33
49
  },
34
50
  "devDependencies": {
35
51
  "cross-env": "^7.0.3",
36
- "koa": "^2.13.1",
37
- "rimraf": "3.0.2"
52
+ "koa": "^2.13.1"
38
53
  },
39
- "author": {
40
- "name": "A Strapi developer",
41
- "email": "",
42
- "url": ""
43
- },
44
- "maintainers": [
45
- {
46
- "name": "A Strapi developer",
47
- "email": "",
48
- "url": ""
49
- }
50
- ],
51
54
  "engines": {
52
55
  "node": ">=12.x.x <=16.x.x",
53
56
  "npm": ">=6.0.0"
54
57
  },
55
- "license": "SEE LICENSE IN LICENSE",
56
- "gitHead": "e3452f6662a45a4ba96e96861e076e313b297666"
58
+ "strapi": {
59
+ "displayName": "GraphQL",
60
+ "name": "graphql",
61
+ "description": "Adds GraphQL endpoint with default API methods.",
62
+ "kind": "plugin"
63
+ },
64
+ "gitHead": "fd656a47698e0a33aae42abd4330410c8cba1d08"
57
65
  }
@@ -0,0 +1,148 @@
1
+ 'use strict';
2
+
3
+ const { isEmpty, mergeWith, isArray } = require('lodash/fp');
4
+ const { execute, subscribe } = require('graphql');
5
+ const { SubscriptionServer } = require('subscriptions-transport-ws');
6
+ const { ApolloServer } = require('apollo-server-koa');
7
+ const {
8
+ ApolloServerPluginLandingPageDisabled,
9
+ ApolloServerPluginLandingPageGraphQLPlayground,
10
+ } = require('apollo-server-core');
11
+ const depthLimit = require('graphql-depth-limit');
12
+ const { graphqlUploadKoa } = require('graphql-upload');
13
+ const formatGraphqlError = require('./format-graphql-error');
14
+
15
+ const merge = mergeWith((a, b) => {
16
+ if (isArray(a) && isArray(b)) {
17
+ return a.concat(b);
18
+ }
19
+ });
20
+
21
+ module.exports = async ({ strapi }) => {
22
+ // Generate the GraphQL schema for the content API
23
+ const schema = strapi
24
+ .plugin('graphql')
25
+ .service('content-api')
26
+ .buildSchema();
27
+
28
+ if (isEmpty(schema)) {
29
+ strapi.log.warn('The GraphQL schema has not been generated because it is empty');
30
+
31
+ return;
32
+ }
33
+
34
+ const { config } = strapi.plugin('graphql');
35
+
36
+ const path = config('endpoint');
37
+
38
+ const defaultServerConfig = {
39
+ // Schema
40
+ schema,
41
+
42
+ // Initialize loaders for this request.
43
+ context: ({ ctx }) => ({
44
+ state: ctx.state,
45
+ koaContext: ctx,
46
+ }),
47
+
48
+ // Validation
49
+ validationRules: [depthLimit(config('depthLimit'))],
50
+
51
+ // Errors
52
+ formatError: formatGraphqlError,
53
+
54
+ // Misc
55
+ cors: false,
56
+ uploads: false,
57
+ bodyParserConfig: true,
58
+
59
+ plugins: [
60
+ process.env.NODE_ENV === 'production'
61
+ ? ApolloServerPluginLandingPageDisabled()
62
+ : ApolloServerPluginLandingPageGraphQLPlayground(),
63
+ ],
64
+ };
65
+
66
+ const serverConfig = merge(defaultServerConfig, config('apolloServer'));
67
+
68
+ // Handle subscriptions
69
+ if (config('subscriptions')) {
70
+ const subscriptionServer = SubscriptionServer.create(
71
+ { schema, execute, subscribe },
72
+ { server: strapi.server.httpServer, path }
73
+ );
74
+
75
+ serverConfig.plugins.push({
76
+ async serverWillStart() {
77
+ return {
78
+ async drainServer() {
79
+ subscriptionServer.close();
80
+ },
81
+ };
82
+ },
83
+ });
84
+ }
85
+
86
+ // Create a new Apollo server
87
+ const server = new ApolloServer(serverConfig);
88
+
89
+ // Register the upload middleware
90
+ useUploadMiddleware(strapi, path);
91
+
92
+ try {
93
+ // Since Apollo-Server v3, server.start() must be called before using server.applyMiddleware()
94
+ await server.start();
95
+ } catch (e) {
96
+ strapi.log.error('Failed to start the Apollo server', e.message);
97
+ }
98
+
99
+ // Link the Apollo server & the Strapi app
100
+ strapi.server.routes([
101
+ {
102
+ method: 'ALL',
103
+ path,
104
+ handler: [
105
+ (ctx, next) => {
106
+ ctx.state.route = {
107
+ info: {
108
+ // Indicate it's a content API route
109
+ type: 'content-api',
110
+ },
111
+ };
112
+
113
+ return strapi.auth.authenticate(ctx, next);
114
+ },
115
+
116
+ // Apollo Server
117
+ server.getMiddleware({ path }),
118
+ ],
119
+ config: {
120
+ auth: false,
121
+ },
122
+ },
123
+ ]);
124
+
125
+ // Register destroy behavior
126
+ // We're doing it here instead of exposing a destroy method to the strapi-server.js
127
+ // file since we need to have access to the ApolloServer instance
128
+ strapi.plugin('graphql').destroy = async () => {
129
+ await server.stop();
130
+ };
131
+ };
132
+
133
+ /**
134
+ * Register the upload middleware powered by graphql-upload in Strapi
135
+ * @param {object} strapi
136
+ * @param {string} path
137
+ */
138
+ const useUploadMiddleware = (strapi, path) => {
139
+ const uploadMiddleware = graphqlUploadKoa();
140
+
141
+ strapi.server.app.use((ctx, next) => {
142
+ if (ctx.path === path) {
143
+ return uploadMiddleware(ctx, next);
144
+ }
145
+
146
+ return next();
147
+ });
148
+ };
@@ -0,0 +1,13 @@
1
+ 'use strict';
2
+
3
+ module.exports = {
4
+ shadowCRUD: true,
5
+
6
+ endpoint: '/graphql',
7
+
8
+ subscriptions: false,
9
+
10
+ maxLimit: -1,
11
+
12
+ apolloServer: {},
13
+ };
@@ -0,0 +1,7 @@
1
+ 'use strict';
2
+
3
+ const defaultConfig = require('./default-config');
4
+
5
+ module.exports = {
6
+ default: defaultConfig,
7
+ };
@@ -0,0 +1,50 @@
1
+ 'use strict';
2
+
3
+ const { toUpper, snakeCase, pick, isEmpty } = require('lodash/fp');
4
+ const {
5
+ HttpError,
6
+ ForbiddenError,
7
+ UnauthorizedError,
8
+ ApplicationError,
9
+ ValidationError,
10
+ } = require('@strapi/utils').errors;
11
+ const {
12
+ ApolloError,
13
+ UserInputError: ApolloUserInputError,
14
+ ForbiddenError: ApolloForbiddenError,
15
+ } = require('apollo-server-koa');
16
+ const { GraphQLError } = require('graphql');
17
+
18
+ const formatToCode = name => `STRAPI_${toUpper(snakeCase(name))}`;
19
+ const formatErrorToExtension = error => ({ error: pick(['name', 'message', 'details'])(error) });
20
+
21
+ const formatGraphqlError = error => {
22
+ const { originalError } = error;
23
+
24
+ if (isEmpty(originalError)) {
25
+ return error;
26
+ }
27
+
28
+ if (originalError instanceof ForbiddenError || originalError instanceof UnauthorizedError) {
29
+ return new ApolloForbiddenError(originalError.message, formatErrorToExtension(originalError));
30
+ }
31
+
32
+ if (originalError instanceof ValidationError) {
33
+ return new ApolloUserInputError(originalError.message, formatErrorToExtension(originalError));
34
+ }
35
+
36
+ if (originalError instanceof ApplicationError || originalError instanceof HttpError) {
37
+ const name = formatToCode(originalError.name);
38
+ return new ApolloError(originalError.message, name, formatErrorToExtension(originalError));
39
+ }
40
+
41
+ if (originalError instanceof ApolloError || originalError instanceof GraphQLError) {
42
+ return error;
43
+ }
44
+
45
+ // Internal server error
46
+ strapi.log.error(originalError);
47
+ return ApolloError('Internal Server Error', 'INTERNAL_SERVER_ERROR');
48
+ };
49
+
50
+ module.exports = formatGraphqlError;
@@ -0,0 +1,97 @@
1
+ 'use strict';
2
+
3
+ const { Kind, valueFromASTUntyped } = require('graphql');
4
+ const { omit } = require('lodash/fp');
5
+ const { unionType, scalarType } = require('nexus');
6
+ const { ApplicationError } = require('@strapi/utils');
7
+
8
+ module.exports = ({ strapi }) => {
9
+ const buildTypeDefinition = (name, components) => {
10
+ const { ERROR_TYPE_NAME } = strapi.plugin('graphql').service('constants');
11
+ const isEmpty = components.length === 0;
12
+
13
+ const componentsTypeNames = components.map(componentUID => {
14
+ const component = strapi.components[componentUID];
15
+
16
+ if (!component) {
17
+ throw new ApplicationError(
18
+ `Trying to create a dynamic zone type with an unknown component: "${componentUID}"`
19
+ );
20
+ }
21
+
22
+ return component.globalId;
23
+ });
24
+
25
+ return unionType({
26
+ name,
27
+
28
+ resolveType(obj) {
29
+ if (isEmpty) {
30
+ return ERROR_TYPE_NAME;
31
+ }
32
+
33
+ return strapi.components[obj.__component].globalId;
34
+ },
35
+
36
+ definition(t) {
37
+ t.members(...componentsTypeNames, ERROR_TYPE_NAME);
38
+ },
39
+ });
40
+ };
41
+
42
+ const buildInputDefinition = (name, components) => {
43
+ const parseData = value => {
44
+ const component = Object.values(strapi.components).find(
45
+ component => component.globalId === value.__typename
46
+ );
47
+
48
+ if (!component) {
49
+ throw new ApplicationError(
50
+ `Component not found. expected one of: ${components
51
+ .map(uid => strapi.components[uid].globalId)
52
+ .join(', ')}`
53
+ );
54
+ }
55
+
56
+ return {
57
+ __component: component.uid,
58
+ ...omit(['__typename'], value),
59
+ };
60
+ };
61
+
62
+ return scalarType({
63
+ name,
64
+
65
+ serialize: value => value,
66
+
67
+ parseValue: value => parseData(value),
68
+
69
+ parseLiteral(ast, variables) {
70
+ if (ast.kind !== Kind.OBJECT) {
71
+ return undefined;
72
+ }
73
+
74
+ const value = valueFromASTUntyped(ast, variables);
75
+ return parseData(value);
76
+ },
77
+ });
78
+ };
79
+
80
+ return {
81
+ /**
82
+ * Build a Nexus dynamic zone type from a Strapi dz attribute
83
+ * @param {object} definition - The definition of the dynamic zone
84
+ * @param {string} name - the name of the dynamic zone
85
+ * @param {string} inputName - the name of the dynamic zone's input
86
+ * @return {[NexusUnionTypeDef, NexusScalarTypeDef]}
87
+ */
88
+ buildDynamicZoneDefinition(definition, name, inputName) {
89
+ const { components } = definition;
90
+
91
+ const typeDefinition = buildTypeDefinition(name, components);
92
+ const inputDefinition = buildInputDefinition(inputName, components);
93
+
94
+ return [typeDefinition, inputDefinition];
95
+ },
96
+ };
97
+ };
@@ -0,0 +1,7 @@
1
+ 'use strict';
2
+
3
+ function buildEntityMetaDefinition(/*contentType*/) {}
4
+
5
+ module.exports = () => ({
6
+ buildEntityMetaDefinition,
7
+ });
@@ -0,0 +1,43 @@
1
+ 'use strict';
2
+
3
+ const { objectType } = require('nexus');
4
+ const { prop, identity, isEmpty } = require('lodash/fp');
5
+
6
+ module.exports = ({ strapi }) => {
7
+ const { naming } = strapi.plugin('graphql').service('utils');
8
+
9
+ return {
10
+ /**
11
+ * Build a higher level type for a content type which contains the attributes, the ID and the metadata
12
+ * @param {object} contentType The content type which will be used to build its entity type
13
+ * @return {NexusObjectTypeDef}
14
+ */
15
+ buildEntityDefinition(contentType) {
16
+ const { attributes } = contentType;
17
+
18
+ const name = naming.getEntityName(contentType);
19
+ const typeName = naming.getTypeName(contentType);
20
+
21
+ return objectType({
22
+ name,
23
+
24
+ definition(t) {
25
+ // Keep the ID attribute at the top level
26
+ t.id('id', { resolve: prop('id') });
27
+
28
+ if (!isEmpty(attributes)) {
29
+ // Keep the fetched object into a dedicated `attributes` field
30
+ // TODO: [v4] precise why we keep the ID
31
+ t.field('attributes', {
32
+ type: typeName,
33
+ resolve: identity,
34
+ });
35
+ }
36
+
37
+ // todo[v4]: add the meta field to the entity when there will be data in it (can't add an empty type for now)
38
+ // t.field('meta', { type: utils.getEntityMetaName(contentType) });
39
+ },
40
+ });
41
+ },
42
+ };
43
+ };
@@ -0,0 +1,24 @@
1
+ 'use strict';
2
+
3
+ const { enumType } = require('nexus');
4
+ const { set } = require('lodash/fp');
5
+
6
+ /**
7
+ * Build a Nexus enum type from a Strapi enum attribute
8
+ * @param {object} definition - The definition of the enum
9
+ * @param {string[]} definition.enum - The params of the enum
10
+ * @param {string} name - The name of the enum
11
+ * @return {NexusEnumTypeDef}
12
+ */
13
+ const buildEnumTypeDefinition = (definition, name) => {
14
+ return enumType({
15
+ name,
16
+ // In Strapi V3, the key of an enum is also its value
17
+ // todo[V4]: allow passing an object of key/value instead of an array
18
+ members: definition.enum.reduce((acc, value) => set(value, value, acc), {}),
19
+ });
20
+ };
21
+
22
+ module.exports = () => ({
23
+ buildEnumTypeDefinition,
24
+ });
@@ -0,0 +1,93 @@
1
+ 'use strict';
2
+
3
+ const { inputObjectType } = require('nexus');
4
+
5
+ module.exports = ({ strapi }) => {
6
+ const rootLevelOperators = () => {
7
+ const { operators } = strapi.plugin('graphql').service('builders').filters;
8
+
9
+ return [operators.and, operators.or, operators.not];
10
+ };
11
+
12
+ const buildContentTypeFilters = contentType => {
13
+ const utils = strapi.plugin('graphql').service('utils');
14
+ const extension = strapi.plugin('graphql').service('extension');
15
+
16
+ const { getFiltersInputTypeName, getScalarFilterInputTypeName } = utils.naming;
17
+ const { isStrapiScalar, isRelation } = utils.attributes;
18
+
19
+ const { attributes } = contentType;
20
+
21
+ const filtersTypeName = getFiltersInputTypeName(contentType);
22
+
23
+ return inputObjectType({
24
+ name: filtersTypeName,
25
+
26
+ definition(t) {
27
+ const validAttributes = Object.entries(attributes).filter(([attributeName]) =>
28
+ extension
29
+ .shadowCRUD(contentType.uid)
30
+ .field(attributeName)
31
+ .hasFiltersEnabeld()
32
+ );
33
+
34
+ const isIDFilterEnabled = extension
35
+ .shadowCRUD(contentType.uid)
36
+ .field('id')
37
+ .hasFiltersEnabeld();
38
+ // Add an ID filter to the collection types
39
+ if (contentType.kind === 'collectionType' && isIDFilterEnabled) {
40
+ t.field('id', { type: getScalarFilterInputTypeName('ID') });
41
+ }
42
+
43
+ // Add every defined attribute
44
+ for (const [attributeName, attribute] of validAttributes) {
45
+ // Handle scalars
46
+ if (isStrapiScalar(attribute)) {
47
+ addScalarAttribute(t, attributeName, attribute);
48
+ }
49
+
50
+ // Handle relations
51
+ else if (isRelation(attribute)) {
52
+ addRelationalAttribute(t, attributeName, attribute);
53
+ }
54
+ }
55
+
56
+ // Conditional clauses
57
+ for (const operator of rootLevelOperators()) {
58
+ operator.add(t, filtersTypeName);
59
+ }
60
+ },
61
+ });
62
+ };
63
+
64
+ const addScalarAttribute = (builder, attributeName, attribute) => {
65
+ const { naming, mappers } = strapi.plugin('graphql').service('utils');
66
+
67
+ const gqlType = mappers.strapiScalarToGraphQLScalar(attribute.type);
68
+
69
+ builder.field(attributeName, { type: naming.getScalarFilterInputTypeName(gqlType) });
70
+ };
71
+
72
+ const addRelationalAttribute = (builder, attributeName, attribute) => {
73
+ const utils = strapi.plugin('graphql').service('utils');
74
+ const extension = strapi.plugin('graphql').service('extension');
75
+ const { getFiltersInputTypeName } = utils.naming;
76
+ const { isMorphRelation } = utils.attributes;
77
+
78
+ const model = strapi.getModel(attribute.target);
79
+
80
+ // If there is no model corresponding to the attribute configuration
81
+ // or if the attribute is a polymorphic relation, then ignore it
82
+ if (!model || isMorphRelation(attribute)) return;
83
+
84
+ // If the target model is disabled, then ignore it too
85
+ if (extension.shadowCRUD(model.uid).isDisabled()) return;
86
+
87
+ builder.field(attributeName, { type: getFiltersInputTypeName(model) });
88
+ };
89
+
90
+ return {
91
+ buildContentTypeFilters,
92
+ };
93
+ };
@@ -0,0 +1,7 @@
1
+ 'use strict';
2
+
3
+ const contentType = require('./content-type');
4
+
5
+ module.exports = context => ({
6
+ ...contentType(context),
7
+ });
@@ -0,0 +1,15 @@
1
+ 'use strict';
2
+
3
+ const { list } = require('nexus');
4
+
5
+ const AND_FIELD_NAME = 'and';
6
+
7
+ module.exports = () => ({
8
+ fieldName: AND_FIELD_NAME,
9
+
10
+ strapiOperator: '$and',
11
+
12
+ add(t, type) {
13
+ t.field(AND_FIELD_NAME, { type: list(type) });
14
+ },
15
+ });
@@ -0,0 +1,15 @@
1
+ 'use strict';
2
+
3
+ const { list } = require('nexus');
4
+
5
+ const BETWEEN_FIELD_NAME = 'between';
6
+
7
+ module.exports = () => ({
8
+ fieldName: BETWEEN_FIELD_NAME,
9
+
10
+ strapiOperator: '$between',
11
+
12
+ add(t, type) {
13
+ t.field(BETWEEN_FIELD_NAME, { type: list(type) });
14
+ },
15
+ });
@@ -0,0 +1,13 @@
1
+ 'use strict';
2
+
3
+ const CONTAINS_FIELD_NAME = 'contains';
4
+
5
+ module.exports = () => ({
6
+ fieldName: CONTAINS_FIELD_NAME,
7
+
8
+ strapiOperator: '$contains',
9
+
10
+ add(t, type) {
11
+ t.field(CONTAINS_FIELD_NAME, { type });
12
+ },
13
+ });