@directus/api 24.0.1 → 25.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app.js +10 -4
- package/dist/auth/drivers/oauth2.js +2 -3
- package/dist/auth/drivers/openid.js +2 -3
- package/dist/cache.d.ts +2 -2
- package/dist/cache.js +20 -7
- package/dist/controllers/assets.js +2 -2
- package/dist/controllers/metrics.d.ts +2 -0
- package/dist/controllers/metrics.js +33 -0
- package/dist/controllers/server.js +1 -1
- package/dist/database/helpers/number/dialects/mssql.d.ts +2 -2
- package/dist/database/helpers/number/dialects/mssql.js +3 -3
- package/dist/database/helpers/number/dialects/oracle.d.ts +2 -2
- package/dist/database/helpers/number/dialects/oracle.js +2 -2
- package/dist/database/helpers/number/dialects/sqlite.d.ts +2 -2
- package/dist/database/helpers/number/dialects/sqlite.js +2 -2
- package/dist/database/helpers/number/types.d.ts +2 -2
- package/dist/database/helpers/number/types.js +2 -2
- package/dist/database/index.js +3 -0
- package/dist/metrics/index.d.ts +1 -0
- package/dist/metrics/index.js +1 -0
- package/dist/metrics/lib/create-metrics.d.ts +15 -0
- package/dist/metrics/lib/create-metrics.js +239 -0
- package/dist/metrics/lib/use-metrics.d.ts +17 -0
- package/dist/metrics/lib/use-metrics.js +15 -0
- package/dist/metrics/types/metric.d.ts +1 -0
- package/dist/metrics/types/metric.js +1 -0
- package/dist/middleware/respond.js +7 -1
- package/dist/operations/condition/index.js +7 -2
- package/dist/schedules/metrics.d.ts +7 -0
- package/dist/schedules/metrics.js +44 -0
- package/dist/services/assets.d.ts +6 -1
- package/dist/services/assets.js +8 -6
- package/dist/services/fields.js +1 -1
- package/dist/services/graphql/errors/format.d.ts +6 -0
- package/dist/services/graphql/errors/format.js +14 -0
- package/dist/services/graphql/index.d.ts +5 -53
- package/dist/services/graphql/index.js +5 -2720
- package/dist/services/graphql/resolvers/mutation.d.ts +4 -0
- package/dist/services/graphql/resolvers/mutation.js +74 -0
- package/dist/services/graphql/resolvers/query.d.ts +8 -0
- package/dist/services/graphql/resolvers/query.js +87 -0
- package/dist/services/graphql/resolvers/system-admin.d.ts +5 -0
- package/dist/services/graphql/resolvers/system-admin.js +236 -0
- package/dist/services/graphql/resolvers/system-global.d.ts +7 -0
- package/dist/services/graphql/resolvers/system-global.js +435 -0
- package/dist/services/graphql/resolvers/system.d.ts +11 -0
- package/dist/services/graphql/resolvers/system.js +554 -0
- package/dist/services/graphql/schema/get-types.d.ts +12 -0
- package/dist/services/graphql/schema/get-types.js +223 -0
- package/dist/services/graphql/schema/index.d.ts +32 -0
- package/dist/services/graphql/schema/index.js +190 -0
- package/dist/services/graphql/schema/parse-args.d.ts +9 -0
- package/dist/services/graphql/schema/parse-args.js +35 -0
- package/dist/services/graphql/schema/parse-query.d.ts +7 -0
- package/dist/services/graphql/schema/parse-query.js +98 -0
- package/dist/services/graphql/schema/read.d.ts +12 -0
- package/dist/services/graphql/schema/read.js +653 -0
- package/dist/services/graphql/schema/write.d.ts +9 -0
- package/dist/services/graphql/schema/write.js +142 -0
- package/dist/services/graphql/subscription.d.ts +1 -1
- package/dist/services/graphql/subscription.js +7 -6
- package/dist/services/graphql/utils/aggrgate-query.d.ts +6 -0
- package/dist/services/graphql/utils/aggrgate-query.js +32 -0
- package/dist/services/graphql/utils/replace-fragments.d.ts +6 -0
- package/dist/services/graphql/utils/replace-fragments.js +21 -0
- package/dist/services/graphql/utils/replace-funcs.d.ts +5 -0
- package/dist/services/graphql/utils/replace-funcs.js +21 -0
- package/dist/services/graphql/utils/sanitize-gql-schema.d.ts +1 -1
- package/dist/services/graphql/utils/sanitize-gql-schema.js +5 -5
- package/dist/services/items.js +0 -2
- package/dist/services/meta.js +25 -84
- package/dist/services/users.d.ts +4 -0
- package/dist/services/users.js +23 -1
- package/dist/utils/apply-query.d.ts +1 -1
- package/dist/utils/apply-query.js +58 -21
- package/dist/utils/freeze-schema.d.ts +3 -0
- package/dist/utils/freeze-schema.js +31 -0
- package/dist/utils/get-accountability-for-token.js +1 -0
- package/dist/utils/get-milliseconds.js +1 -1
- package/dist/utils/get-schema.js +10 -5
- package/dist/utils/permissions-cachable.d.ts +8 -0
- package/dist/utils/permissions-cachable.js +38 -0
- package/dist/utils/sanitize-schema.d.ts +1 -1
- package/dist/websocket/messages.d.ts +6 -6
- package/package.json +22 -19
|
@@ -1,75 +1,17 @@
|
|
|
1
|
-
import { FUNCTIONS } from '@directus/constants';
|
|
2
1
|
import { useEnv } from '@directus/env';
|
|
3
|
-
import {
|
|
4
|
-
import { isSystemCollection } from '@directus/system-data';
|
|
5
|
-
import { parseFilterFunctionPath, toBoolean } from '@directus/utils';
|
|
6
|
-
import argon2 from 'argon2';
|
|
7
|
-
import { GraphQLBoolean, GraphQLEnumType, GraphQLError, GraphQLFloat, GraphQLID, GraphQLInt, GraphQLList, GraphQLNonNull, GraphQLObjectType, GraphQLScalarType, GraphQLSchema, GraphQLString, GraphQLUnionType, NoSchemaIntrospectionCustomRule, execute, specifiedRules, validate, } from 'graphql';
|
|
8
|
-
import { GraphQLJSON, InputTypeComposer, ObjectTypeComposer, SchemaComposer, toInputObjectType } from 'graphql-compose';
|
|
9
|
-
import { flatten, get, mapKeys, merge, omit, pick, set, transform, uniq } from 'lodash-es';
|
|
10
|
-
import { clearSystemCache, getCache } from '../../cache.js';
|
|
11
|
-
import { DEFAULT_AUTH_PROVIDER, GENERATE_SPECIAL, REFRESH_COOKIE_OPTIONS, SESSION_COOKIE_OPTIONS, } from '../../constants.js';
|
|
2
|
+
import { NoSchemaIntrospectionCustomRule, execute, specifiedRules, validate } from 'graphql';
|
|
12
3
|
import getDatabase from '../../database/index.js';
|
|
13
|
-
import { rateLimiter } from '../../middleware/rate-limiter-registration.js';
|
|
14
|
-
import { fetchAccountabilityCollectionAccess } from '../../permissions/modules/fetch-accountability-collection-access/fetch-accountability-collection-access.js';
|
|
15
|
-
import { fetchAccountabilityPolicyGlobals } from '../../permissions/modules/fetch-accountability-policy-globals/fetch-accountability-policy-globals.js';
|
|
16
|
-
import { fetchAllowedFieldMap } from '../../permissions/modules/fetch-allowed-field-map/fetch-allowed-field-map.js';
|
|
17
|
-
import { fetchInconsistentFieldMap } from '../../permissions/modules/fetch-inconsistent-field-map/fetch-inconsistent-field-map.js';
|
|
18
|
-
import { createDefaultAccountability } from '../../permissions/utils/create-default-accountability.js';
|
|
19
|
-
import { generateHash } from '../../utils/generate-hash.js';
|
|
20
|
-
import { getGraphQLType } from '../../utils/get-graphql-type.js';
|
|
21
|
-
import { getIPFromReq } from '../../utils/get-ip-from-req.js';
|
|
22
|
-
import { getSecret } from '../../utils/get-secret.js';
|
|
23
4
|
import { getService } from '../../utils/get-service.js';
|
|
24
|
-
import
|
|
25
|
-
import { verifyAccessJWT } from '../../utils/jwt.js';
|
|
26
|
-
import { mergeVersionsRaw, mergeVersionsRecursive } from '../../utils/merge-version-data.js';
|
|
27
|
-
import { reduceSchema } from '../../utils/reduce-schema.js';
|
|
28
|
-
import { sanitizeQuery } from '../../utils/sanitize-query.js';
|
|
29
|
-
import { validateQuery } from '../../utils/validate-query.js';
|
|
30
|
-
import { AuthenticationService } from '../authentication.js';
|
|
31
|
-
import { CollectionsService } from '../collections.js';
|
|
32
|
-
import { ExtensionsService } from '../extensions.js';
|
|
33
|
-
import { FieldsService } from '../fields.js';
|
|
34
|
-
import { FilesService } from '../files.js';
|
|
35
|
-
import { RelationsService } from '../relations.js';
|
|
36
|
-
import { RevisionsService } from '../revisions.js';
|
|
37
|
-
import { RolesService } from '../roles.js';
|
|
38
|
-
import { ServerService } from '../server.js';
|
|
39
|
-
import { SpecificationService } from '../specifications.js';
|
|
40
|
-
import { TFAService } from '../tfa.js';
|
|
41
|
-
import { UsersService } from '../users.js';
|
|
42
|
-
import { UtilsService } from '../utils.js';
|
|
43
|
-
import { VersionsService } from '../versions.js';
|
|
5
|
+
import { formatError } from './errors/format.js';
|
|
44
6
|
import { GraphQLExecutionError, GraphQLValidationError } from './errors/index.js';
|
|
45
|
-
import {
|
|
46
|
-
import { createSubscriptionGenerator } from './subscription.js';
|
|
47
|
-
import { GraphQLBigInt } from './types/bigint.js';
|
|
48
|
-
import { GraphQLDate } from './types/date.js';
|
|
49
|
-
import { GraphQLGeoJSON } from './types/geojson.js';
|
|
50
|
-
import { GraphQLHash } from './types/hash.js';
|
|
51
|
-
import { GraphQLStringOrFloat } from './types/string-or-float.js';
|
|
52
|
-
import { GraphQLVoid } from './types/void.js';
|
|
7
|
+
import { generateSchema } from './schema/index.js';
|
|
53
8
|
import { addPathToValidationError } from './utils/add-path-to-validation-error.js';
|
|
54
9
|
import processError from './utils/process-error.js';
|
|
55
|
-
import { sanitizeGraphqlSchema } from './utils/sanitize-gql-schema.js';
|
|
56
10
|
const env = useEnv();
|
|
57
11
|
const validationRules = Array.from(specifiedRules);
|
|
58
12
|
if (env['GRAPHQL_INTROSPECTION'] === false) {
|
|
59
13
|
validationRules.push(NoSchemaIntrospectionCustomRule);
|
|
60
14
|
}
|
|
61
|
-
/**
|
|
62
|
-
* These should be ignored in the context of GraphQL, and/or are replaced by a custom resolver (for non-standard structures)
|
|
63
|
-
*/
|
|
64
|
-
const SYSTEM_DENY_LIST = [
|
|
65
|
-
'directus_collections',
|
|
66
|
-
'directus_fields',
|
|
67
|
-
'directus_relations',
|
|
68
|
-
'directus_migrations',
|
|
69
|
-
'directus_sessions',
|
|
70
|
-
'directus_extensions',
|
|
71
|
-
];
|
|
72
|
-
const READ_ONLY = ['directus_activity', 'directus_revisions'];
|
|
73
15
|
export class GraphQLService {
|
|
74
16
|
accountability;
|
|
75
17
|
knex;
|
|
@@ -114,1297 +56,7 @@ export class GraphQLService {
|
|
|
114
56
|
return formattedResult;
|
|
115
57
|
}
|
|
116
58
|
async getSchema(type = 'schema') {
|
|
117
|
-
|
|
118
|
-
const cachedSchema = cache.get(key);
|
|
119
|
-
if (cachedSchema)
|
|
120
|
-
return cachedSchema;
|
|
121
|
-
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
122
|
-
const self = this;
|
|
123
|
-
const schemaComposer = new SchemaComposer();
|
|
124
|
-
let schema;
|
|
125
|
-
const sanitizedSchema = sanitizeGraphqlSchema(this.schema);
|
|
126
|
-
if (!this.accountability || this.accountability.admin) {
|
|
127
|
-
schema = {
|
|
128
|
-
read: sanitizedSchema,
|
|
129
|
-
create: sanitizedSchema,
|
|
130
|
-
update: sanitizedSchema,
|
|
131
|
-
delete: sanitizedSchema,
|
|
132
|
-
};
|
|
133
|
-
}
|
|
134
|
-
else {
|
|
135
|
-
schema = {
|
|
136
|
-
read: reduceSchema(sanitizedSchema, await fetchAllowedFieldMap({
|
|
137
|
-
accountability: this.accountability,
|
|
138
|
-
action: 'read',
|
|
139
|
-
}, { schema: this.schema, knex: this.knex })),
|
|
140
|
-
create: reduceSchema(sanitizedSchema, await fetchAllowedFieldMap({
|
|
141
|
-
accountability: this.accountability,
|
|
142
|
-
action: 'create',
|
|
143
|
-
}, { schema: this.schema, knex: this.knex })),
|
|
144
|
-
update: reduceSchema(sanitizedSchema, await fetchAllowedFieldMap({
|
|
145
|
-
accountability: this.accountability,
|
|
146
|
-
action: 'update',
|
|
147
|
-
}, { schema: this.schema, knex: this.knex })),
|
|
148
|
-
delete: reduceSchema(sanitizedSchema, await fetchAllowedFieldMap({
|
|
149
|
-
accountability: this.accountability,
|
|
150
|
-
action: 'delete',
|
|
151
|
-
}, { schema: this.schema, knex: this.knex })),
|
|
152
|
-
};
|
|
153
|
-
}
|
|
154
|
-
const inconsistentFields = {
|
|
155
|
-
read: await fetchInconsistentFieldMap({
|
|
156
|
-
accountability: this.accountability,
|
|
157
|
-
action: 'read',
|
|
158
|
-
}, { schema: this.schema, knex: this.knex }),
|
|
159
|
-
create: await fetchInconsistentFieldMap({
|
|
160
|
-
accountability: this.accountability,
|
|
161
|
-
action: 'create',
|
|
162
|
-
}, { schema: this.schema, knex: this.knex }),
|
|
163
|
-
update: await fetchInconsistentFieldMap({
|
|
164
|
-
accountability: this.accountability,
|
|
165
|
-
action: 'update',
|
|
166
|
-
}, { schema: this.schema, knex: this.knex }),
|
|
167
|
-
delete: await fetchInconsistentFieldMap({
|
|
168
|
-
accountability: this.accountability,
|
|
169
|
-
action: 'delete',
|
|
170
|
-
}, { schema: this.schema, knex: this.knex }),
|
|
171
|
-
};
|
|
172
|
-
const subscriptionEventType = schemaComposer.createEnumTC({
|
|
173
|
-
name: 'EventEnum',
|
|
174
|
-
values: {
|
|
175
|
-
create: { value: 'create' },
|
|
176
|
-
update: { value: 'update' },
|
|
177
|
-
delete: { value: 'delete' },
|
|
178
|
-
},
|
|
179
|
-
});
|
|
180
|
-
const { ReadCollectionTypes, VersionCollectionTypes } = getReadableTypes();
|
|
181
|
-
const { CreateCollectionTypes, UpdateCollectionTypes, DeleteCollectionTypes } = getWritableTypes();
|
|
182
|
-
const scopeFilter = (collection) => {
|
|
183
|
-
if (this.scope === 'items' && isSystemCollection(collection.collection))
|
|
184
|
-
return false;
|
|
185
|
-
if (this.scope === 'system') {
|
|
186
|
-
if (isSystemCollection(collection.collection) === false)
|
|
187
|
-
return false;
|
|
188
|
-
if (SYSTEM_DENY_LIST.includes(collection.collection))
|
|
189
|
-
return false;
|
|
190
|
-
}
|
|
191
|
-
return true;
|
|
192
|
-
};
|
|
193
|
-
if (this.scope === 'system') {
|
|
194
|
-
this.injectSystemResolvers(schemaComposer, {
|
|
195
|
-
CreateCollectionTypes,
|
|
196
|
-
ReadCollectionTypes,
|
|
197
|
-
UpdateCollectionTypes,
|
|
198
|
-
}, schema);
|
|
199
|
-
}
|
|
200
|
-
const readableCollections = Object.values(schema.read.collections)
|
|
201
|
-
.filter((collection) => collection.collection in ReadCollectionTypes)
|
|
202
|
-
.filter(scopeFilter);
|
|
203
|
-
if (readableCollections.length > 0) {
|
|
204
|
-
schemaComposer.Query.addFields(readableCollections.reduce((acc, collection) => {
|
|
205
|
-
const collectionName = this.scope === 'items' ? collection.collection : collection.collection.substring(9);
|
|
206
|
-
acc[collectionName] = ReadCollectionTypes[collection.collection].getResolver(collection.collection);
|
|
207
|
-
if (this.schema.collections[collection.collection].singleton === false) {
|
|
208
|
-
acc[`${collectionName}_by_id`] = ReadCollectionTypes[collection.collection].getResolver(`${collection.collection}_by_id`);
|
|
209
|
-
acc[`${collectionName}_aggregated`] = ReadCollectionTypes[collection.collection].getResolver(`${collection.collection}_aggregated`);
|
|
210
|
-
}
|
|
211
|
-
if (this.scope === 'items') {
|
|
212
|
-
acc[`${collectionName}_by_version`] = VersionCollectionTypes[collection.collection].getResolver(`${collection.collection}_by_version`);
|
|
213
|
-
}
|
|
214
|
-
return acc;
|
|
215
|
-
}, {}));
|
|
216
|
-
}
|
|
217
|
-
else {
|
|
218
|
-
schemaComposer.Query.addFields({
|
|
219
|
-
_empty: {
|
|
220
|
-
type: GraphQLVoid,
|
|
221
|
-
description: "There's no data to query.",
|
|
222
|
-
},
|
|
223
|
-
});
|
|
224
|
-
}
|
|
225
|
-
if (Object.keys(schema.create.collections).length > 0) {
|
|
226
|
-
schemaComposer.Mutation.addFields(Object.values(schema.create.collections)
|
|
227
|
-
.filter((collection) => collection.collection in CreateCollectionTypes && collection.singleton === false)
|
|
228
|
-
.filter(scopeFilter)
|
|
229
|
-
.filter((collection) => READ_ONLY.includes(collection.collection) === false)
|
|
230
|
-
.reduce((acc, collection) => {
|
|
231
|
-
const collectionName = this.scope === 'items' ? collection.collection : collection.collection.substring(9);
|
|
232
|
-
acc[`create_${collectionName}_items`] = CreateCollectionTypes[collection.collection].getResolver(`create_${collection.collection}_items`);
|
|
233
|
-
acc[`create_${collectionName}_item`] = CreateCollectionTypes[collection.collection].getResolver(`create_${collection.collection}_item`);
|
|
234
|
-
return acc;
|
|
235
|
-
}, {}));
|
|
236
|
-
}
|
|
237
|
-
if (Object.keys(schema.update.collections).length > 0) {
|
|
238
|
-
schemaComposer.Mutation.addFields(Object.values(schema.update.collections)
|
|
239
|
-
.filter((collection) => collection.collection in UpdateCollectionTypes)
|
|
240
|
-
.filter(scopeFilter)
|
|
241
|
-
.filter((collection) => READ_ONLY.includes(collection.collection) === false)
|
|
242
|
-
.reduce((acc, collection) => {
|
|
243
|
-
const collectionName = this.scope === 'items' ? collection.collection : collection.collection.substring(9);
|
|
244
|
-
if (collection.singleton) {
|
|
245
|
-
acc[`update_${collectionName}`] = UpdateCollectionTypes[collection.collection].getResolver(`update_${collection.collection}`);
|
|
246
|
-
}
|
|
247
|
-
else {
|
|
248
|
-
acc[`update_${collectionName}_items`] = UpdateCollectionTypes[collection.collection].getResolver(`update_${collection.collection}_items`);
|
|
249
|
-
acc[`update_${collectionName}_batch`] = UpdateCollectionTypes[collection.collection].getResolver(`update_${collection.collection}_batch`);
|
|
250
|
-
acc[`update_${collectionName}_item`] = UpdateCollectionTypes[collection.collection].getResolver(`update_${collection.collection}_item`);
|
|
251
|
-
}
|
|
252
|
-
return acc;
|
|
253
|
-
}, {}));
|
|
254
|
-
}
|
|
255
|
-
if (Object.keys(schema.delete.collections).length > 0) {
|
|
256
|
-
schemaComposer.Mutation.addFields(Object.values(schema.delete.collections)
|
|
257
|
-
.filter((collection) => collection.singleton === false)
|
|
258
|
-
.filter(scopeFilter)
|
|
259
|
-
.filter((collection) => READ_ONLY.includes(collection.collection) === false)
|
|
260
|
-
.reduce((acc, collection) => {
|
|
261
|
-
const collectionName = this.scope === 'items' ? collection.collection : collection.collection.substring(9);
|
|
262
|
-
acc[`delete_${collectionName}_items`] = DeleteCollectionTypes['many'].getResolver(`delete_${collection.collection}_items`);
|
|
263
|
-
acc[`delete_${collectionName}_item`] = DeleteCollectionTypes['one'].getResolver(`delete_${collection.collection}_item`);
|
|
264
|
-
return acc;
|
|
265
|
-
}, {}));
|
|
266
|
-
}
|
|
267
|
-
if (type === 'sdl') {
|
|
268
|
-
const sdl = schemaComposer.toSDL();
|
|
269
|
-
cache.set(key, sdl);
|
|
270
|
-
return sdl;
|
|
271
|
-
}
|
|
272
|
-
const gqlSchema = schemaComposer.buildSchema();
|
|
273
|
-
cache.set(key, gqlSchema);
|
|
274
|
-
return gqlSchema;
|
|
275
|
-
/**
|
|
276
|
-
* Construct an object of types for every collection, using the permitted fields per action type
|
|
277
|
-
* as it's fields.
|
|
278
|
-
*/
|
|
279
|
-
function getTypes(action) {
|
|
280
|
-
const CollectionTypes = {};
|
|
281
|
-
const VersionTypes = {};
|
|
282
|
-
const CountFunctions = schemaComposer.createObjectTC({
|
|
283
|
-
name: 'count_functions',
|
|
284
|
-
fields: {
|
|
285
|
-
count: {
|
|
286
|
-
type: GraphQLInt,
|
|
287
|
-
},
|
|
288
|
-
},
|
|
289
|
-
});
|
|
290
|
-
const DateFunctions = schemaComposer.createObjectTC({
|
|
291
|
-
name: 'date_functions',
|
|
292
|
-
fields: {
|
|
293
|
-
year: {
|
|
294
|
-
type: GraphQLInt,
|
|
295
|
-
},
|
|
296
|
-
month: {
|
|
297
|
-
type: GraphQLInt,
|
|
298
|
-
},
|
|
299
|
-
week: {
|
|
300
|
-
type: GraphQLInt,
|
|
301
|
-
},
|
|
302
|
-
day: {
|
|
303
|
-
type: GraphQLInt,
|
|
304
|
-
},
|
|
305
|
-
weekday: {
|
|
306
|
-
type: GraphQLInt,
|
|
307
|
-
},
|
|
308
|
-
},
|
|
309
|
-
});
|
|
310
|
-
const TimeFunctions = schemaComposer.createObjectTC({
|
|
311
|
-
name: 'time_functions',
|
|
312
|
-
fields: {
|
|
313
|
-
hour: {
|
|
314
|
-
type: GraphQLInt,
|
|
315
|
-
},
|
|
316
|
-
minute: {
|
|
317
|
-
type: GraphQLInt,
|
|
318
|
-
},
|
|
319
|
-
second: {
|
|
320
|
-
type: GraphQLInt,
|
|
321
|
-
},
|
|
322
|
-
},
|
|
323
|
-
});
|
|
324
|
-
const DateTimeFunctions = schemaComposer.createObjectTC({
|
|
325
|
-
name: 'datetime_functions',
|
|
326
|
-
fields: {
|
|
327
|
-
...DateFunctions.getFields(),
|
|
328
|
-
...TimeFunctions.getFields(),
|
|
329
|
-
},
|
|
330
|
-
});
|
|
331
|
-
for (const collection of Object.values(schema[action].collections)) {
|
|
332
|
-
if (Object.keys(collection.fields).length === 0)
|
|
333
|
-
continue;
|
|
334
|
-
if (SYSTEM_DENY_LIST.includes(collection.collection))
|
|
335
|
-
continue;
|
|
336
|
-
CollectionTypes[collection.collection] = schemaComposer.createObjectTC({
|
|
337
|
-
name: action === 'read' ? collection.collection : `${action}_${collection.collection}`,
|
|
338
|
-
fields: Object.values(collection.fields).reduce((acc, field) => {
|
|
339
|
-
let type = getGraphQLType(field.type, field.special);
|
|
340
|
-
const fieldIsInconsistent = inconsistentFields[action][collection.collection]?.includes(field.field);
|
|
341
|
-
// GraphQL doesn't differentiate between not-null and has-to-be-submitted. We
|
|
342
|
-
// can't non-null in update, as that would require every not-nullable field to be
|
|
343
|
-
// submitted on updates
|
|
344
|
-
if (field.nullable === false &&
|
|
345
|
-
!field.defaultValue &&
|
|
346
|
-
!GENERATE_SPECIAL.some((flag) => field.special.includes(flag)) &&
|
|
347
|
-
fieldIsInconsistent === false &&
|
|
348
|
-
action !== 'update') {
|
|
349
|
-
type = new GraphQLNonNull(type);
|
|
350
|
-
}
|
|
351
|
-
if (collection.primary === field.field && fieldIsInconsistent === false) {
|
|
352
|
-
// permissions IDs need to be nullable https://github.com/directus/directus/issues/20509
|
|
353
|
-
if (collection.collection === 'directus_permissions') {
|
|
354
|
-
type = GraphQLID;
|
|
355
|
-
}
|
|
356
|
-
else if (!field.defaultValue && !field.special.includes('uuid') && action === 'create') {
|
|
357
|
-
type = new GraphQLNonNull(GraphQLID);
|
|
358
|
-
}
|
|
359
|
-
else if (['create', 'update'].includes(action)) {
|
|
360
|
-
type = GraphQLID;
|
|
361
|
-
}
|
|
362
|
-
else {
|
|
363
|
-
type = new GraphQLNonNull(GraphQLID);
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
acc[field.field] = {
|
|
367
|
-
type,
|
|
368
|
-
description: field.note,
|
|
369
|
-
resolve: (obj) => {
|
|
370
|
-
return obj[field.field];
|
|
371
|
-
},
|
|
372
|
-
};
|
|
373
|
-
if (action === 'read') {
|
|
374
|
-
if (field.type === 'date') {
|
|
375
|
-
acc[`${field.field}_func`] = {
|
|
376
|
-
type: DateFunctions,
|
|
377
|
-
resolve: (obj) => {
|
|
378
|
-
const funcFields = Object.keys(DateFunctions.getFields()).map((key) => `${field.field}_${key}`);
|
|
379
|
-
return mapKeys(pick(obj, funcFields), (_value, key) => key.substring(field.field.length + 1));
|
|
380
|
-
},
|
|
381
|
-
};
|
|
382
|
-
}
|
|
383
|
-
if (field.type === 'time') {
|
|
384
|
-
acc[`${field.field}_func`] = {
|
|
385
|
-
type: TimeFunctions,
|
|
386
|
-
resolve: (obj) => {
|
|
387
|
-
const funcFields = Object.keys(TimeFunctions.getFields()).map((key) => `${field.field}_${key}`);
|
|
388
|
-
return mapKeys(pick(obj, funcFields), (_value, key) => key.substring(field.field.length + 1));
|
|
389
|
-
},
|
|
390
|
-
};
|
|
391
|
-
}
|
|
392
|
-
if (field.type === 'dateTime' || field.type === 'timestamp') {
|
|
393
|
-
acc[`${field.field}_func`] = {
|
|
394
|
-
type: DateTimeFunctions,
|
|
395
|
-
resolve: (obj) => {
|
|
396
|
-
const funcFields = Object.keys(DateTimeFunctions.getFields()).map((key) => `${field.field}_${key}`);
|
|
397
|
-
return mapKeys(pick(obj, funcFields), (_value, key) => key.substring(field.field.length + 1));
|
|
398
|
-
},
|
|
399
|
-
};
|
|
400
|
-
}
|
|
401
|
-
if (field.type === 'json' || field.type === 'alias') {
|
|
402
|
-
acc[`${field.field}_func`] = {
|
|
403
|
-
type: CountFunctions,
|
|
404
|
-
resolve: (obj) => {
|
|
405
|
-
const funcFields = Object.keys(CountFunctions.getFields()).map((key) => `${field.field}_${key}`);
|
|
406
|
-
return mapKeys(pick(obj, funcFields), (_value, key) => key.substring(field.field.length + 1));
|
|
407
|
-
},
|
|
408
|
-
};
|
|
409
|
-
}
|
|
410
|
-
}
|
|
411
|
-
return acc;
|
|
412
|
-
}, {}),
|
|
413
|
-
});
|
|
414
|
-
if (self.scope === 'items') {
|
|
415
|
-
VersionTypes[collection.collection] = CollectionTypes[collection.collection].clone(`version_${collection.collection}`);
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
for (const relation of schema[action].relations) {
|
|
419
|
-
if (relation.related_collection) {
|
|
420
|
-
if (SYSTEM_DENY_LIST.includes(relation.related_collection))
|
|
421
|
-
continue;
|
|
422
|
-
CollectionTypes[relation.collection]?.addFields({
|
|
423
|
-
[relation.field]: {
|
|
424
|
-
type: CollectionTypes[relation.related_collection],
|
|
425
|
-
resolve: (obj, _, __, info) => {
|
|
426
|
-
return obj[info?.path?.key ?? relation.field];
|
|
427
|
-
},
|
|
428
|
-
},
|
|
429
|
-
});
|
|
430
|
-
VersionTypes[relation.collection]?.addFields({
|
|
431
|
-
[relation.field]: {
|
|
432
|
-
type: GraphQLJSON,
|
|
433
|
-
resolve: (obj, _, __, info) => {
|
|
434
|
-
return obj[info?.path?.key ?? relation.field];
|
|
435
|
-
},
|
|
436
|
-
},
|
|
437
|
-
});
|
|
438
|
-
if (relation.meta?.one_field) {
|
|
439
|
-
CollectionTypes[relation.related_collection]?.addFields({
|
|
440
|
-
[relation.meta.one_field]: {
|
|
441
|
-
type: [CollectionTypes[relation.collection]],
|
|
442
|
-
resolve: (obj, _, __, info) => {
|
|
443
|
-
return obj[info?.path?.key ?? relation.meta.one_field];
|
|
444
|
-
},
|
|
445
|
-
},
|
|
446
|
-
});
|
|
447
|
-
if (self.scope === 'items') {
|
|
448
|
-
VersionTypes[relation.related_collection]?.addFields({
|
|
449
|
-
[relation.meta.one_field]: {
|
|
450
|
-
type: GraphQLJSON,
|
|
451
|
-
resolve: (obj, _, __, info) => {
|
|
452
|
-
return obj[info?.path?.key ?? relation.meta.one_field];
|
|
453
|
-
},
|
|
454
|
-
},
|
|
455
|
-
});
|
|
456
|
-
}
|
|
457
|
-
}
|
|
458
|
-
}
|
|
459
|
-
else if (relation.meta?.one_allowed_collections && action === 'read') {
|
|
460
|
-
// NOTE: There are no union input types in GraphQL, so this only applies to Read actions
|
|
461
|
-
CollectionTypes[relation.collection]?.addFields({
|
|
462
|
-
[relation.field]: {
|
|
463
|
-
type: new GraphQLUnionType({
|
|
464
|
-
name: `${relation.collection}_${relation.field}_union`,
|
|
465
|
-
types: relation.meta.one_allowed_collections.map((collection) => CollectionTypes[collection].getType()),
|
|
466
|
-
resolveType(_value, context, info) {
|
|
467
|
-
let path = [];
|
|
468
|
-
let currentPath = info.path;
|
|
469
|
-
while (currentPath.prev) {
|
|
470
|
-
path.push(currentPath.key);
|
|
471
|
-
currentPath = currentPath.prev;
|
|
472
|
-
}
|
|
473
|
-
path = path.reverse().slice(0, -1);
|
|
474
|
-
let parent = context['data'];
|
|
475
|
-
for (const pathPart of path) {
|
|
476
|
-
parent = parent[pathPart];
|
|
477
|
-
}
|
|
478
|
-
const collection = parent[relation.meta.one_collection_field];
|
|
479
|
-
return CollectionTypes[collection].getType().name;
|
|
480
|
-
},
|
|
481
|
-
}),
|
|
482
|
-
resolve: (obj, _, __, info) => {
|
|
483
|
-
return obj[info?.path?.key ?? relation.field];
|
|
484
|
-
},
|
|
485
|
-
},
|
|
486
|
-
});
|
|
487
|
-
}
|
|
488
|
-
}
|
|
489
|
-
return { CollectionTypes, VersionTypes };
|
|
490
|
-
}
|
|
491
|
-
/**
|
|
492
|
-
* Create readable types and attach resolvers for each. Also prepares full filter argument structures
|
|
493
|
-
*/
|
|
494
|
-
function getReadableTypes() {
|
|
495
|
-
const { CollectionTypes: ReadCollectionTypes, VersionTypes: VersionCollectionTypes } = getTypes('read');
|
|
496
|
-
const ReadableCollectionFilterTypes = {};
|
|
497
|
-
const AggregatedFunctions = {};
|
|
498
|
-
const AggregatedFields = {};
|
|
499
|
-
const AggregateMethods = {};
|
|
500
|
-
const StringFilterOperators = schemaComposer.createInputTC({
|
|
501
|
-
name: 'string_filter_operators',
|
|
502
|
-
fields: {
|
|
503
|
-
_eq: {
|
|
504
|
-
type: GraphQLString,
|
|
505
|
-
},
|
|
506
|
-
_neq: {
|
|
507
|
-
type: GraphQLString,
|
|
508
|
-
},
|
|
509
|
-
_contains: {
|
|
510
|
-
type: GraphQLString,
|
|
511
|
-
},
|
|
512
|
-
_icontains: {
|
|
513
|
-
type: GraphQLString,
|
|
514
|
-
},
|
|
515
|
-
_ncontains: {
|
|
516
|
-
type: GraphQLString,
|
|
517
|
-
},
|
|
518
|
-
_starts_with: {
|
|
519
|
-
type: GraphQLString,
|
|
520
|
-
},
|
|
521
|
-
_nstarts_with: {
|
|
522
|
-
type: GraphQLString,
|
|
523
|
-
},
|
|
524
|
-
_istarts_with: {
|
|
525
|
-
type: GraphQLString,
|
|
526
|
-
},
|
|
527
|
-
_nistarts_with: {
|
|
528
|
-
type: GraphQLString,
|
|
529
|
-
},
|
|
530
|
-
_ends_with: {
|
|
531
|
-
type: GraphQLString,
|
|
532
|
-
},
|
|
533
|
-
_nends_with: {
|
|
534
|
-
type: GraphQLString,
|
|
535
|
-
},
|
|
536
|
-
_iends_with: {
|
|
537
|
-
type: GraphQLString,
|
|
538
|
-
},
|
|
539
|
-
_niends_with: {
|
|
540
|
-
type: GraphQLString,
|
|
541
|
-
},
|
|
542
|
-
_in: {
|
|
543
|
-
type: new GraphQLList(GraphQLString),
|
|
544
|
-
},
|
|
545
|
-
_nin: {
|
|
546
|
-
type: new GraphQLList(GraphQLString),
|
|
547
|
-
},
|
|
548
|
-
_null: {
|
|
549
|
-
type: GraphQLBoolean,
|
|
550
|
-
},
|
|
551
|
-
_nnull: {
|
|
552
|
-
type: GraphQLBoolean,
|
|
553
|
-
},
|
|
554
|
-
_empty: {
|
|
555
|
-
type: GraphQLBoolean,
|
|
556
|
-
},
|
|
557
|
-
_nempty: {
|
|
558
|
-
type: GraphQLBoolean,
|
|
559
|
-
},
|
|
560
|
-
},
|
|
561
|
-
});
|
|
562
|
-
const BooleanFilterOperators = schemaComposer.createInputTC({
|
|
563
|
-
name: 'boolean_filter_operators',
|
|
564
|
-
fields: {
|
|
565
|
-
_eq: {
|
|
566
|
-
type: GraphQLBoolean,
|
|
567
|
-
},
|
|
568
|
-
_neq: {
|
|
569
|
-
type: GraphQLBoolean,
|
|
570
|
-
},
|
|
571
|
-
_null: {
|
|
572
|
-
type: GraphQLBoolean,
|
|
573
|
-
},
|
|
574
|
-
_nnull: {
|
|
575
|
-
type: GraphQLBoolean,
|
|
576
|
-
},
|
|
577
|
-
},
|
|
578
|
-
});
|
|
579
|
-
const DateFilterOperators = schemaComposer.createInputTC({
|
|
580
|
-
name: 'date_filter_operators',
|
|
581
|
-
fields: {
|
|
582
|
-
_eq: {
|
|
583
|
-
type: GraphQLString,
|
|
584
|
-
},
|
|
585
|
-
_neq: {
|
|
586
|
-
type: GraphQLString,
|
|
587
|
-
},
|
|
588
|
-
_gt: {
|
|
589
|
-
type: GraphQLString,
|
|
590
|
-
},
|
|
591
|
-
_gte: {
|
|
592
|
-
type: GraphQLString,
|
|
593
|
-
},
|
|
594
|
-
_lt: {
|
|
595
|
-
type: GraphQLString,
|
|
596
|
-
},
|
|
597
|
-
_lte: {
|
|
598
|
-
type: GraphQLString,
|
|
599
|
-
},
|
|
600
|
-
_null: {
|
|
601
|
-
type: GraphQLBoolean,
|
|
602
|
-
},
|
|
603
|
-
_nnull: {
|
|
604
|
-
type: GraphQLBoolean,
|
|
605
|
-
},
|
|
606
|
-
_in: {
|
|
607
|
-
type: new GraphQLList(GraphQLString),
|
|
608
|
-
},
|
|
609
|
-
_nin: {
|
|
610
|
-
type: new GraphQLList(GraphQLString),
|
|
611
|
-
},
|
|
612
|
-
_between: {
|
|
613
|
-
type: new GraphQLList(GraphQLStringOrFloat),
|
|
614
|
-
},
|
|
615
|
-
_nbetween: {
|
|
616
|
-
type: new GraphQLList(GraphQLStringOrFloat),
|
|
617
|
-
},
|
|
618
|
-
},
|
|
619
|
-
});
|
|
620
|
-
// Uses StringOrFloat rather than Float to support api dynamic variables (like `$NOW`)
|
|
621
|
-
const NumberFilterOperators = schemaComposer.createInputTC({
|
|
622
|
-
name: 'number_filter_operators',
|
|
623
|
-
fields: {
|
|
624
|
-
_eq: {
|
|
625
|
-
type: GraphQLStringOrFloat,
|
|
626
|
-
},
|
|
627
|
-
_neq: {
|
|
628
|
-
type: GraphQLStringOrFloat,
|
|
629
|
-
},
|
|
630
|
-
_in: {
|
|
631
|
-
type: new GraphQLList(GraphQLStringOrFloat),
|
|
632
|
-
},
|
|
633
|
-
_nin: {
|
|
634
|
-
type: new GraphQLList(GraphQLStringOrFloat),
|
|
635
|
-
},
|
|
636
|
-
_gt: {
|
|
637
|
-
type: GraphQLStringOrFloat,
|
|
638
|
-
},
|
|
639
|
-
_gte: {
|
|
640
|
-
type: GraphQLStringOrFloat,
|
|
641
|
-
},
|
|
642
|
-
_lt: {
|
|
643
|
-
type: GraphQLStringOrFloat,
|
|
644
|
-
},
|
|
645
|
-
_lte: {
|
|
646
|
-
type: GraphQLStringOrFloat,
|
|
647
|
-
},
|
|
648
|
-
_null: {
|
|
649
|
-
type: GraphQLBoolean,
|
|
650
|
-
},
|
|
651
|
-
_nnull: {
|
|
652
|
-
type: GraphQLBoolean,
|
|
653
|
-
},
|
|
654
|
-
_between: {
|
|
655
|
-
type: new GraphQLList(GraphQLStringOrFloat),
|
|
656
|
-
},
|
|
657
|
-
_nbetween: {
|
|
658
|
-
type: new GraphQLList(GraphQLStringOrFloat),
|
|
659
|
-
},
|
|
660
|
-
},
|
|
661
|
-
});
|
|
662
|
-
const BigIntFilterOperators = schemaComposer.createInputTC({
|
|
663
|
-
name: 'big_int_filter_operators',
|
|
664
|
-
fields: {
|
|
665
|
-
_eq: {
|
|
666
|
-
type: GraphQLBigInt,
|
|
667
|
-
},
|
|
668
|
-
_neq: {
|
|
669
|
-
type: GraphQLBigInt,
|
|
670
|
-
},
|
|
671
|
-
_in: {
|
|
672
|
-
type: new GraphQLList(GraphQLBigInt),
|
|
673
|
-
},
|
|
674
|
-
_nin: {
|
|
675
|
-
type: new GraphQLList(GraphQLBigInt),
|
|
676
|
-
},
|
|
677
|
-
_gt: {
|
|
678
|
-
type: GraphQLBigInt,
|
|
679
|
-
},
|
|
680
|
-
_gte: {
|
|
681
|
-
type: GraphQLBigInt,
|
|
682
|
-
},
|
|
683
|
-
_lt: {
|
|
684
|
-
type: GraphQLBigInt,
|
|
685
|
-
},
|
|
686
|
-
_lte: {
|
|
687
|
-
type: GraphQLBigInt,
|
|
688
|
-
},
|
|
689
|
-
_null: {
|
|
690
|
-
type: GraphQLBoolean,
|
|
691
|
-
},
|
|
692
|
-
_nnull: {
|
|
693
|
-
type: GraphQLBoolean,
|
|
694
|
-
},
|
|
695
|
-
_between: {
|
|
696
|
-
type: new GraphQLList(GraphQLBigInt),
|
|
697
|
-
},
|
|
698
|
-
_nbetween: {
|
|
699
|
-
type: new GraphQLList(GraphQLBigInt),
|
|
700
|
-
},
|
|
701
|
-
},
|
|
702
|
-
});
|
|
703
|
-
const GeometryFilterOperators = schemaComposer.createInputTC({
|
|
704
|
-
name: 'geometry_filter_operators',
|
|
705
|
-
fields: {
|
|
706
|
-
_eq: {
|
|
707
|
-
type: GraphQLGeoJSON,
|
|
708
|
-
},
|
|
709
|
-
_neq: {
|
|
710
|
-
type: GraphQLGeoJSON,
|
|
711
|
-
},
|
|
712
|
-
_intersects: {
|
|
713
|
-
type: GraphQLGeoJSON,
|
|
714
|
-
},
|
|
715
|
-
_nintersects: {
|
|
716
|
-
type: GraphQLGeoJSON,
|
|
717
|
-
},
|
|
718
|
-
_intersects_bbox: {
|
|
719
|
-
type: GraphQLGeoJSON,
|
|
720
|
-
},
|
|
721
|
-
_nintersects_bbox: {
|
|
722
|
-
type: GraphQLGeoJSON,
|
|
723
|
-
},
|
|
724
|
-
_null: {
|
|
725
|
-
type: GraphQLBoolean,
|
|
726
|
-
},
|
|
727
|
-
_nnull: {
|
|
728
|
-
type: GraphQLBoolean,
|
|
729
|
-
},
|
|
730
|
-
},
|
|
731
|
-
});
|
|
732
|
-
const HashFilterOperators = schemaComposer.createInputTC({
|
|
733
|
-
name: 'hash_filter_operators',
|
|
734
|
-
fields: {
|
|
735
|
-
_null: {
|
|
736
|
-
type: GraphQLBoolean,
|
|
737
|
-
},
|
|
738
|
-
_nnull: {
|
|
739
|
-
type: GraphQLBoolean,
|
|
740
|
-
},
|
|
741
|
-
_empty: {
|
|
742
|
-
type: GraphQLBoolean,
|
|
743
|
-
},
|
|
744
|
-
_nempty: {
|
|
745
|
-
type: GraphQLBoolean,
|
|
746
|
-
},
|
|
747
|
-
},
|
|
748
|
-
});
|
|
749
|
-
const CountFunctionFilterOperators = schemaComposer.createInputTC({
|
|
750
|
-
name: 'count_function_filter_operators',
|
|
751
|
-
fields: {
|
|
752
|
-
count: {
|
|
753
|
-
type: NumberFilterOperators,
|
|
754
|
-
},
|
|
755
|
-
},
|
|
756
|
-
});
|
|
757
|
-
const DateFunctionFilterOperators = schemaComposer.createInputTC({
|
|
758
|
-
name: 'date_function_filter_operators',
|
|
759
|
-
fields: {
|
|
760
|
-
year: {
|
|
761
|
-
type: NumberFilterOperators,
|
|
762
|
-
},
|
|
763
|
-
month: {
|
|
764
|
-
type: NumberFilterOperators,
|
|
765
|
-
},
|
|
766
|
-
week: {
|
|
767
|
-
type: NumberFilterOperators,
|
|
768
|
-
},
|
|
769
|
-
day: {
|
|
770
|
-
type: NumberFilterOperators,
|
|
771
|
-
},
|
|
772
|
-
weekday: {
|
|
773
|
-
type: NumberFilterOperators,
|
|
774
|
-
},
|
|
775
|
-
},
|
|
776
|
-
});
|
|
777
|
-
const TimeFunctionFilterOperators = schemaComposer.createInputTC({
|
|
778
|
-
name: 'time_function_filter_operators',
|
|
779
|
-
fields: {
|
|
780
|
-
hour: {
|
|
781
|
-
type: NumberFilterOperators,
|
|
782
|
-
},
|
|
783
|
-
minute: {
|
|
784
|
-
type: NumberFilterOperators,
|
|
785
|
-
},
|
|
786
|
-
second: {
|
|
787
|
-
type: NumberFilterOperators,
|
|
788
|
-
},
|
|
789
|
-
},
|
|
790
|
-
});
|
|
791
|
-
const DateTimeFunctionFilterOperators = schemaComposer.createInputTC({
|
|
792
|
-
name: 'datetime_function_filter_operators',
|
|
793
|
-
fields: {
|
|
794
|
-
...DateFunctionFilterOperators.getFields(),
|
|
795
|
-
...TimeFunctionFilterOperators.getFields(),
|
|
796
|
-
},
|
|
797
|
-
});
|
|
798
|
-
for (const collection of Object.values(schema.read.collections)) {
|
|
799
|
-
if (Object.keys(collection.fields).length === 0)
|
|
800
|
-
continue;
|
|
801
|
-
if (SYSTEM_DENY_LIST.includes(collection.collection))
|
|
802
|
-
continue;
|
|
803
|
-
ReadableCollectionFilterTypes[collection.collection] = schemaComposer.createInputTC({
|
|
804
|
-
name: `${collection.collection}_filter`,
|
|
805
|
-
fields: Object.values(collection.fields).reduce((acc, field) => {
|
|
806
|
-
const graphqlType = getGraphQLType(field.type, field.special);
|
|
807
|
-
let filterOperatorType;
|
|
808
|
-
switch (graphqlType) {
|
|
809
|
-
case GraphQLBoolean:
|
|
810
|
-
filterOperatorType = BooleanFilterOperators;
|
|
811
|
-
break;
|
|
812
|
-
case GraphQLBigInt:
|
|
813
|
-
filterOperatorType = BigIntFilterOperators;
|
|
814
|
-
break;
|
|
815
|
-
case GraphQLInt:
|
|
816
|
-
case GraphQLFloat:
|
|
817
|
-
filterOperatorType = NumberFilterOperators;
|
|
818
|
-
break;
|
|
819
|
-
case GraphQLDate:
|
|
820
|
-
filterOperatorType = DateFilterOperators;
|
|
821
|
-
break;
|
|
822
|
-
case GraphQLGeoJSON:
|
|
823
|
-
filterOperatorType = GeometryFilterOperators;
|
|
824
|
-
break;
|
|
825
|
-
case GraphQLHash:
|
|
826
|
-
filterOperatorType = HashFilterOperators;
|
|
827
|
-
break;
|
|
828
|
-
default:
|
|
829
|
-
filterOperatorType = StringFilterOperators;
|
|
830
|
-
}
|
|
831
|
-
acc[field.field] = filterOperatorType;
|
|
832
|
-
if (field.type === 'date') {
|
|
833
|
-
acc[`${field.field}_func`] = {
|
|
834
|
-
type: DateFunctionFilterOperators,
|
|
835
|
-
};
|
|
836
|
-
}
|
|
837
|
-
if (field.type === 'time') {
|
|
838
|
-
acc[`${field.field}_func`] = {
|
|
839
|
-
type: TimeFunctionFilterOperators,
|
|
840
|
-
};
|
|
841
|
-
}
|
|
842
|
-
if (field.type === 'dateTime' || field.type === 'timestamp') {
|
|
843
|
-
acc[`${field.field}_func`] = {
|
|
844
|
-
type: DateTimeFunctionFilterOperators,
|
|
845
|
-
};
|
|
846
|
-
}
|
|
847
|
-
if (field.type === 'json' || field.type === 'alias') {
|
|
848
|
-
acc[`${field.field}_func`] = {
|
|
849
|
-
type: CountFunctionFilterOperators,
|
|
850
|
-
};
|
|
851
|
-
}
|
|
852
|
-
return acc;
|
|
853
|
-
}, {}),
|
|
854
|
-
});
|
|
855
|
-
ReadableCollectionFilterTypes[collection.collection].addFields({
|
|
856
|
-
_and: [ReadableCollectionFilterTypes[collection.collection]],
|
|
857
|
-
_or: [ReadableCollectionFilterTypes[collection.collection]],
|
|
858
|
-
});
|
|
859
|
-
AggregatedFields[collection.collection] = schemaComposer.createObjectTC({
|
|
860
|
-
name: `${collection.collection}_aggregated_fields`,
|
|
861
|
-
fields: Object.values(collection.fields).reduce((acc, field) => {
|
|
862
|
-
const graphqlType = getGraphQLType(field.type, field.special);
|
|
863
|
-
switch (graphqlType) {
|
|
864
|
-
case GraphQLBigInt:
|
|
865
|
-
case GraphQLInt:
|
|
866
|
-
case GraphQLFloat:
|
|
867
|
-
acc[field.field] = {
|
|
868
|
-
type: GraphQLFloat,
|
|
869
|
-
description: field.note,
|
|
870
|
-
};
|
|
871
|
-
break;
|
|
872
|
-
default:
|
|
873
|
-
break;
|
|
874
|
-
}
|
|
875
|
-
return acc;
|
|
876
|
-
}, {}),
|
|
877
|
-
});
|
|
878
|
-
const countType = schemaComposer.createObjectTC({
|
|
879
|
-
name: `${collection.collection}_aggregated_count`,
|
|
880
|
-
fields: Object.values(collection.fields).reduce((acc, field) => {
|
|
881
|
-
acc[field.field] = {
|
|
882
|
-
type: GraphQLInt,
|
|
883
|
-
description: field.note,
|
|
884
|
-
};
|
|
885
|
-
return acc;
|
|
886
|
-
}, {}),
|
|
887
|
-
});
|
|
888
|
-
AggregateMethods[collection.collection] = {
|
|
889
|
-
group: {
|
|
890
|
-
name: 'group',
|
|
891
|
-
type: GraphQLJSON,
|
|
892
|
-
},
|
|
893
|
-
countAll: {
|
|
894
|
-
name: 'countAll',
|
|
895
|
-
type: GraphQLInt,
|
|
896
|
-
},
|
|
897
|
-
count: {
|
|
898
|
-
name: 'count',
|
|
899
|
-
type: countType,
|
|
900
|
-
},
|
|
901
|
-
countDistinct: {
|
|
902
|
-
name: 'countDistinct',
|
|
903
|
-
type: countType,
|
|
904
|
-
},
|
|
905
|
-
};
|
|
906
|
-
const hasNumericAggregates = Object.values(collection.fields).some((field) => {
|
|
907
|
-
const graphqlType = getGraphQLType(field.type, field.special);
|
|
908
|
-
if (graphqlType === GraphQLInt || graphqlType === GraphQLFloat) {
|
|
909
|
-
return true;
|
|
910
|
-
}
|
|
911
|
-
return false;
|
|
912
|
-
});
|
|
913
|
-
if (hasNumericAggregates) {
|
|
914
|
-
Object.assign(AggregateMethods[collection.collection], {
|
|
915
|
-
avg: {
|
|
916
|
-
name: 'avg',
|
|
917
|
-
type: AggregatedFields[collection.collection],
|
|
918
|
-
},
|
|
919
|
-
sum: {
|
|
920
|
-
name: 'sum',
|
|
921
|
-
type: AggregatedFields[collection.collection],
|
|
922
|
-
},
|
|
923
|
-
avgDistinct: {
|
|
924
|
-
name: 'avgDistinct',
|
|
925
|
-
type: AggregatedFields[collection.collection],
|
|
926
|
-
},
|
|
927
|
-
sumDistinct: {
|
|
928
|
-
name: 'sumDistinct',
|
|
929
|
-
type: AggregatedFields[collection.collection],
|
|
930
|
-
},
|
|
931
|
-
min: {
|
|
932
|
-
name: 'min',
|
|
933
|
-
type: AggregatedFields[collection.collection],
|
|
934
|
-
},
|
|
935
|
-
max: {
|
|
936
|
-
name: 'max',
|
|
937
|
-
type: AggregatedFields[collection.collection],
|
|
938
|
-
},
|
|
939
|
-
});
|
|
940
|
-
}
|
|
941
|
-
AggregatedFunctions[collection.collection] = schemaComposer.createObjectTC({
|
|
942
|
-
name: `${collection.collection}_aggregated`,
|
|
943
|
-
fields: AggregateMethods[collection.collection],
|
|
944
|
-
});
|
|
945
|
-
const resolver = {
|
|
946
|
-
name: collection.collection,
|
|
947
|
-
type: collection.singleton
|
|
948
|
-
? ReadCollectionTypes[collection.collection]
|
|
949
|
-
: new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(ReadCollectionTypes[collection.collection].getType()))),
|
|
950
|
-
resolve: async ({ info, context }) => {
|
|
951
|
-
const result = await self.resolveQuery(info);
|
|
952
|
-
context['data'] = result;
|
|
953
|
-
return result;
|
|
954
|
-
},
|
|
955
|
-
};
|
|
956
|
-
if (collection.singleton === false) {
|
|
957
|
-
resolver.args = {
|
|
958
|
-
filter: ReadableCollectionFilterTypes[collection.collection],
|
|
959
|
-
sort: {
|
|
960
|
-
type: new GraphQLList(GraphQLString),
|
|
961
|
-
},
|
|
962
|
-
limit: {
|
|
963
|
-
type: GraphQLInt,
|
|
964
|
-
},
|
|
965
|
-
offset: {
|
|
966
|
-
type: GraphQLInt,
|
|
967
|
-
},
|
|
968
|
-
page: {
|
|
969
|
-
type: GraphQLInt,
|
|
970
|
-
},
|
|
971
|
-
search: {
|
|
972
|
-
type: GraphQLString,
|
|
973
|
-
},
|
|
974
|
-
};
|
|
975
|
-
}
|
|
976
|
-
else {
|
|
977
|
-
resolver.args = {
|
|
978
|
-
version: GraphQLString,
|
|
979
|
-
};
|
|
980
|
-
}
|
|
981
|
-
ReadCollectionTypes[collection.collection].addResolver(resolver);
|
|
982
|
-
ReadCollectionTypes[collection.collection].addResolver({
|
|
983
|
-
name: `${collection.collection}_aggregated`,
|
|
984
|
-
type: new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(AggregatedFunctions[collection.collection].getType()))),
|
|
985
|
-
args: {
|
|
986
|
-
groupBy: new GraphQLList(GraphQLString),
|
|
987
|
-
filter: ReadableCollectionFilterTypes[collection.collection],
|
|
988
|
-
limit: {
|
|
989
|
-
type: GraphQLInt,
|
|
990
|
-
},
|
|
991
|
-
offset: {
|
|
992
|
-
type: GraphQLInt,
|
|
993
|
-
},
|
|
994
|
-
page: {
|
|
995
|
-
type: GraphQLInt,
|
|
996
|
-
},
|
|
997
|
-
search: {
|
|
998
|
-
type: GraphQLString,
|
|
999
|
-
},
|
|
1000
|
-
sort: {
|
|
1001
|
-
type: new GraphQLList(GraphQLString),
|
|
1002
|
-
},
|
|
1003
|
-
},
|
|
1004
|
-
resolve: async ({ info, context }) => {
|
|
1005
|
-
const result = await self.resolveQuery(info);
|
|
1006
|
-
context['data'] = result;
|
|
1007
|
-
return result;
|
|
1008
|
-
},
|
|
1009
|
-
});
|
|
1010
|
-
if (collection.singleton === false) {
|
|
1011
|
-
ReadCollectionTypes[collection.collection].addResolver({
|
|
1012
|
-
name: `${collection.collection}_by_id`,
|
|
1013
|
-
type: ReadCollectionTypes[collection.collection],
|
|
1014
|
-
args: {
|
|
1015
|
-
id: new GraphQLNonNull(GraphQLID),
|
|
1016
|
-
version: GraphQLString,
|
|
1017
|
-
},
|
|
1018
|
-
resolve: async ({ info, context }) => {
|
|
1019
|
-
const result = await self.resolveQuery(info);
|
|
1020
|
-
context['data'] = result;
|
|
1021
|
-
return result;
|
|
1022
|
-
},
|
|
1023
|
-
});
|
|
1024
|
-
}
|
|
1025
|
-
if (self.scope === 'items') {
|
|
1026
|
-
VersionCollectionTypes[collection.collection].addResolver({
|
|
1027
|
-
name: `${collection.collection}_by_version`,
|
|
1028
|
-
type: VersionCollectionTypes[collection.collection],
|
|
1029
|
-
args: collection.singleton
|
|
1030
|
-
? { version: new GraphQLNonNull(GraphQLString) }
|
|
1031
|
-
: {
|
|
1032
|
-
version: new GraphQLNonNull(GraphQLString),
|
|
1033
|
-
id: new GraphQLNonNull(GraphQLID),
|
|
1034
|
-
},
|
|
1035
|
-
resolve: async ({ info, context }) => {
|
|
1036
|
-
const result = await self.resolveQuery(info);
|
|
1037
|
-
context['data'] = result;
|
|
1038
|
-
return result;
|
|
1039
|
-
},
|
|
1040
|
-
});
|
|
1041
|
-
}
|
|
1042
|
-
const eventName = `${collection.collection}_mutated`;
|
|
1043
|
-
if (collection.collection in ReadCollectionTypes) {
|
|
1044
|
-
const subscriptionType = schemaComposer.createObjectTC({
|
|
1045
|
-
name: eventName,
|
|
1046
|
-
fields: {
|
|
1047
|
-
key: new GraphQLNonNull(GraphQLID),
|
|
1048
|
-
event: subscriptionEventType,
|
|
1049
|
-
data: ReadCollectionTypes[collection.collection],
|
|
1050
|
-
},
|
|
1051
|
-
});
|
|
1052
|
-
schemaComposer.Subscription.addFields({
|
|
1053
|
-
[eventName]: {
|
|
1054
|
-
type: subscriptionType,
|
|
1055
|
-
args: {
|
|
1056
|
-
event: subscriptionEventType,
|
|
1057
|
-
},
|
|
1058
|
-
subscribe: createSubscriptionGenerator(self, eventName),
|
|
1059
|
-
},
|
|
1060
|
-
});
|
|
1061
|
-
}
|
|
1062
|
-
}
|
|
1063
|
-
for (const relation of schema.read.relations) {
|
|
1064
|
-
if (relation.related_collection) {
|
|
1065
|
-
if (SYSTEM_DENY_LIST.includes(relation.related_collection))
|
|
1066
|
-
continue;
|
|
1067
|
-
ReadableCollectionFilterTypes[relation.collection]?.addFields({
|
|
1068
|
-
[relation.field]: ReadableCollectionFilterTypes[relation.related_collection],
|
|
1069
|
-
});
|
|
1070
|
-
ReadCollectionTypes[relation.collection]?.addFieldArgs(relation.field, {
|
|
1071
|
-
filter: ReadableCollectionFilterTypes[relation.related_collection],
|
|
1072
|
-
sort: {
|
|
1073
|
-
type: new GraphQLList(GraphQLString),
|
|
1074
|
-
},
|
|
1075
|
-
limit: {
|
|
1076
|
-
type: GraphQLInt,
|
|
1077
|
-
},
|
|
1078
|
-
offset: {
|
|
1079
|
-
type: GraphQLInt,
|
|
1080
|
-
},
|
|
1081
|
-
page: {
|
|
1082
|
-
type: GraphQLInt,
|
|
1083
|
-
},
|
|
1084
|
-
search: {
|
|
1085
|
-
type: GraphQLString,
|
|
1086
|
-
},
|
|
1087
|
-
});
|
|
1088
|
-
if (relation.meta?.one_field) {
|
|
1089
|
-
ReadableCollectionFilterTypes[relation.related_collection]?.addFields({
|
|
1090
|
-
[relation.meta.one_field]: ReadableCollectionFilterTypes[relation.collection],
|
|
1091
|
-
});
|
|
1092
|
-
ReadCollectionTypes[relation.related_collection]?.addFieldArgs(relation.meta.one_field, {
|
|
1093
|
-
filter: ReadableCollectionFilterTypes[relation.collection],
|
|
1094
|
-
sort: {
|
|
1095
|
-
type: new GraphQLList(GraphQLString),
|
|
1096
|
-
},
|
|
1097
|
-
limit: {
|
|
1098
|
-
type: GraphQLInt,
|
|
1099
|
-
},
|
|
1100
|
-
offset: {
|
|
1101
|
-
type: GraphQLInt,
|
|
1102
|
-
},
|
|
1103
|
-
page: {
|
|
1104
|
-
type: GraphQLInt,
|
|
1105
|
-
},
|
|
1106
|
-
search: {
|
|
1107
|
-
type: GraphQLString,
|
|
1108
|
-
},
|
|
1109
|
-
});
|
|
1110
|
-
}
|
|
1111
|
-
}
|
|
1112
|
-
else if (relation.meta?.one_allowed_collections) {
|
|
1113
|
-
ReadableCollectionFilterTypes[relation.collection]?.removeField('item');
|
|
1114
|
-
for (const collection of relation.meta.one_allowed_collections) {
|
|
1115
|
-
ReadableCollectionFilterTypes[relation.collection]?.addFields({
|
|
1116
|
-
[`item__${collection}`]: ReadableCollectionFilterTypes[collection],
|
|
1117
|
-
});
|
|
1118
|
-
}
|
|
1119
|
-
}
|
|
1120
|
-
}
|
|
1121
|
-
return { ReadCollectionTypes, VersionCollectionTypes, ReadableCollectionFilterTypes };
|
|
1122
|
-
}
|
|
1123
|
-
function getWritableTypes() {
|
|
1124
|
-
const { CollectionTypes: CreateCollectionTypes } = getTypes('create');
|
|
1125
|
-
const { CollectionTypes: UpdateCollectionTypes } = getTypes('update');
|
|
1126
|
-
const DeleteCollectionTypes = {};
|
|
1127
|
-
for (const collection of Object.values(schema.create.collections)) {
|
|
1128
|
-
if (Object.keys(collection.fields).length === 0)
|
|
1129
|
-
continue;
|
|
1130
|
-
if (SYSTEM_DENY_LIST.includes(collection.collection))
|
|
1131
|
-
continue;
|
|
1132
|
-
if (collection.collection in CreateCollectionTypes === false)
|
|
1133
|
-
continue;
|
|
1134
|
-
const collectionIsReadable = collection.collection in ReadCollectionTypes;
|
|
1135
|
-
const creatableFields = CreateCollectionTypes[collection.collection]?.getFields() || {};
|
|
1136
|
-
if (Object.keys(creatableFields).length > 0) {
|
|
1137
|
-
const resolverDefinition = {
|
|
1138
|
-
name: `create_${collection.collection}_items`,
|
|
1139
|
-
type: collectionIsReadable
|
|
1140
|
-
? new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(ReadCollectionTypes[collection.collection].getType())))
|
|
1141
|
-
: GraphQLBoolean,
|
|
1142
|
-
resolve: async ({ args, info }) => await self.resolveMutation(args, info),
|
|
1143
|
-
};
|
|
1144
|
-
if (collectionIsReadable) {
|
|
1145
|
-
resolverDefinition.args = ReadCollectionTypes[collection.collection].getResolver(collection.collection).getArgs();
|
|
1146
|
-
}
|
|
1147
|
-
CreateCollectionTypes[collection.collection].addResolver(resolverDefinition);
|
|
1148
|
-
CreateCollectionTypes[collection.collection].addResolver({
|
|
1149
|
-
name: `create_${collection.collection}_item`,
|
|
1150
|
-
type: collectionIsReadable ? ReadCollectionTypes[collection.collection] : GraphQLBoolean,
|
|
1151
|
-
resolve: async ({ args, info }) => await self.resolveMutation(args, info),
|
|
1152
|
-
});
|
|
1153
|
-
CreateCollectionTypes[collection.collection].getResolver(`create_${collection.collection}_items`).addArgs({
|
|
1154
|
-
...CreateCollectionTypes[collection.collection].getResolver(`create_${collection.collection}_items`).getArgs(),
|
|
1155
|
-
data: [
|
|
1156
|
-
toInputObjectType(CreateCollectionTypes[collection.collection]).setTypeName(`create_${collection.collection}_input`).NonNull,
|
|
1157
|
-
],
|
|
1158
|
-
});
|
|
1159
|
-
CreateCollectionTypes[collection.collection].getResolver(`create_${collection.collection}_item`).addArgs({
|
|
1160
|
-
...CreateCollectionTypes[collection.collection].getResolver(`create_${collection.collection}_item`).getArgs(),
|
|
1161
|
-
data: toInputObjectType(CreateCollectionTypes[collection.collection]).setTypeName(`create_${collection.collection}_input`).NonNull,
|
|
1162
|
-
});
|
|
1163
|
-
}
|
|
1164
|
-
}
|
|
1165
|
-
for (const collection of Object.values(schema.update.collections)) {
|
|
1166
|
-
if (Object.keys(collection.fields).length === 0)
|
|
1167
|
-
continue;
|
|
1168
|
-
if (SYSTEM_DENY_LIST.includes(collection.collection))
|
|
1169
|
-
continue;
|
|
1170
|
-
if (collection.collection in UpdateCollectionTypes === false)
|
|
1171
|
-
continue;
|
|
1172
|
-
const collectionIsReadable = collection.collection in ReadCollectionTypes;
|
|
1173
|
-
const updatableFields = UpdateCollectionTypes[collection.collection]?.getFields() || {};
|
|
1174
|
-
if (Object.keys(updatableFields).length > 0) {
|
|
1175
|
-
if (collection.singleton) {
|
|
1176
|
-
UpdateCollectionTypes[collection.collection].addResolver({
|
|
1177
|
-
name: `update_${collection.collection}`,
|
|
1178
|
-
type: collectionIsReadable ? ReadCollectionTypes[collection.collection] : GraphQLBoolean,
|
|
1179
|
-
args: {
|
|
1180
|
-
data: toInputObjectType(UpdateCollectionTypes[collection.collection]).setTypeName(`update_${collection.collection}_input`).NonNull,
|
|
1181
|
-
},
|
|
1182
|
-
resolve: async ({ args, info }) => await self.resolveMutation(args, info),
|
|
1183
|
-
});
|
|
1184
|
-
}
|
|
1185
|
-
else {
|
|
1186
|
-
UpdateCollectionTypes[collection.collection].addResolver({
|
|
1187
|
-
name: `update_${collection.collection}_batch`,
|
|
1188
|
-
type: collectionIsReadable
|
|
1189
|
-
? new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(ReadCollectionTypes[collection.collection].getType())))
|
|
1190
|
-
: GraphQLBoolean,
|
|
1191
|
-
args: {
|
|
1192
|
-
...(collectionIsReadable
|
|
1193
|
-
? ReadCollectionTypes[collection.collection].getResolver(collection.collection).getArgs()
|
|
1194
|
-
: {}),
|
|
1195
|
-
data: [
|
|
1196
|
-
toInputObjectType(UpdateCollectionTypes[collection.collection]).setTypeName(`update_${collection.collection}_input`).NonNull,
|
|
1197
|
-
],
|
|
1198
|
-
},
|
|
1199
|
-
resolve: async ({ args, info }) => await self.resolveMutation(args, info),
|
|
1200
|
-
});
|
|
1201
|
-
UpdateCollectionTypes[collection.collection].addResolver({
|
|
1202
|
-
name: `update_${collection.collection}_items`,
|
|
1203
|
-
type: collectionIsReadable
|
|
1204
|
-
? new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(ReadCollectionTypes[collection.collection].getType())))
|
|
1205
|
-
: GraphQLBoolean,
|
|
1206
|
-
args: {
|
|
1207
|
-
...(collectionIsReadable
|
|
1208
|
-
? ReadCollectionTypes[collection.collection].getResolver(collection.collection).getArgs()
|
|
1209
|
-
: {}),
|
|
1210
|
-
ids: new GraphQLNonNull(new GraphQLList(GraphQLID)),
|
|
1211
|
-
data: toInputObjectType(UpdateCollectionTypes[collection.collection]).setTypeName(`update_${collection.collection}_input`).NonNull,
|
|
1212
|
-
},
|
|
1213
|
-
resolve: async ({ args, info }) => await self.resolveMutation(args, info),
|
|
1214
|
-
});
|
|
1215
|
-
UpdateCollectionTypes[collection.collection].addResolver({
|
|
1216
|
-
name: `update_${collection.collection}_item`,
|
|
1217
|
-
type: collectionIsReadable ? ReadCollectionTypes[collection.collection] : GraphQLBoolean,
|
|
1218
|
-
args: {
|
|
1219
|
-
id: new GraphQLNonNull(GraphQLID),
|
|
1220
|
-
data: toInputObjectType(UpdateCollectionTypes[collection.collection]).setTypeName(`update_${collection.collection}_input`).NonNull,
|
|
1221
|
-
},
|
|
1222
|
-
resolve: async ({ args, info }) => await self.resolveMutation(args, info),
|
|
1223
|
-
});
|
|
1224
|
-
}
|
|
1225
|
-
}
|
|
1226
|
-
}
|
|
1227
|
-
DeleteCollectionTypes['many'] = schemaComposer.createObjectTC({
|
|
1228
|
-
name: `delete_many`,
|
|
1229
|
-
fields: {
|
|
1230
|
-
ids: new GraphQLNonNull(new GraphQLList(GraphQLID)),
|
|
1231
|
-
},
|
|
1232
|
-
});
|
|
1233
|
-
DeleteCollectionTypes['one'] = schemaComposer.createObjectTC({
|
|
1234
|
-
name: `delete_one`,
|
|
1235
|
-
fields: {
|
|
1236
|
-
id: new GraphQLNonNull(GraphQLID),
|
|
1237
|
-
},
|
|
1238
|
-
});
|
|
1239
|
-
for (const collection of Object.values(schema.delete.collections)) {
|
|
1240
|
-
DeleteCollectionTypes['many'].addResolver({
|
|
1241
|
-
name: `delete_${collection.collection}_items`,
|
|
1242
|
-
type: DeleteCollectionTypes['many'],
|
|
1243
|
-
args: {
|
|
1244
|
-
ids: new GraphQLNonNull(new GraphQLList(GraphQLID)),
|
|
1245
|
-
},
|
|
1246
|
-
resolve: async ({ args, info }) => await self.resolveMutation(args, info),
|
|
1247
|
-
});
|
|
1248
|
-
DeleteCollectionTypes['one'].addResolver({
|
|
1249
|
-
name: `delete_${collection.collection}_item`,
|
|
1250
|
-
type: DeleteCollectionTypes['one'],
|
|
1251
|
-
args: {
|
|
1252
|
-
id: new GraphQLNonNull(GraphQLID),
|
|
1253
|
-
},
|
|
1254
|
-
resolve: async ({ args, info }) => await self.resolveMutation(args, info),
|
|
1255
|
-
});
|
|
1256
|
-
}
|
|
1257
|
-
return { CreateCollectionTypes, UpdateCollectionTypes, DeleteCollectionTypes };
|
|
1258
|
-
}
|
|
1259
|
-
}
|
|
1260
|
-
/**
|
|
1261
|
-
* Generic resolver that's used for every "regular" items/system query. Converts the incoming GraphQL AST / fragments into
|
|
1262
|
-
* Directus' query structure which is then executed by the services.
|
|
1263
|
-
*/
|
|
1264
|
-
async resolveQuery(info) {
|
|
1265
|
-
let collection = info.fieldName;
|
|
1266
|
-
if (this.scope === 'system')
|
|
1267
|
-
collection = `directus_${collection}`;
|
|
1268
|
-
const selections = this.replaceFragmentsInSelections(info.fieldNodes[0]?.selectionSet?.selections, info.fragments);
|
|
1269
|
-
if (!selections)
|
|
1270
|
-
return null;
|
|
1271
|
-
const args = this.parseArgs(info.fieldNodes[0].arguments || [], info.variableValues);
|
|
1272
|
-
let query;
|
|
1273
|
-
let versionRaw = false;
|
|
1274
|
-
const isAggregate = collection.endsWith('_aggregated') && collection in this.schema.collections === false;
|
|
1275
|
-
if (isAggregate) {
|
|
1276
|
-
query = this.getAggregateQuery(args, selections);
|
|
1277
|
-
collection = collection.slice(0, -11);
|
|
1278
|
-
}
|
|
1279
|
-
else {
|
|
1280
|
-
query = this.getQuery(args, selections, info.variableValues);
|
|
1281
|
-
if (collection.endsWith('_by_id') && collection in this.schema.collections === false) {
|
|
1282
|
-
collection = collection.slice(0, -6);
|
|
1283
|
-
}
|
|
1284
|
-
if (collection.endsWith('_by_version') && collection in this.schema.collections === false) {
|
|
1285
|
-
collection = collection.slice(0, -11);
|
|
1286
|
-
versionRaw = true;
|
|
1287
|
-
}
|
|
1288
|
-
}
|
|
1289
|
-
if (args['id']) {
|
|
1290
|
-
query.filter = {
|
|
1291
|
-
_and: [
|
|
1292
|
-
query.filter || {},
|
|
1293
|
-
{
|
|
1294
|
-
[this.schema.collections[collection].primary]: {
|
|
1295
|
-
_eq: args['id'],
|
|
1296
|
-
},
|
|
1297
|
-
},
|
|
1298
|
-
],
|
|
1299
|
-
};
|
|
1300
|
-
query.limit = 1;
|
|
1301
|
-
}
|
|
1302
|
-
// Transform count(a.b.c) into a.b.count(c)
|
|
1303
|
-
if (query.fields?.length) {
|
|
1304
|
-
for (let fieldIndex = 0; fieldIndex < query.fields.length; fieldIndex++) {
|
|
1305
|
-
query.fields[fieldIndex] = parseFilterFunctionPath(query.fields[fieldIndex]);
|
|
1306
|
-
}
|
|
1307
|
-
}
|
|
1308
|
-
const result = await this.read(collection, query);
|
|
1309
|
-
if (args['version']) {
|
|
1310
|
-
const versionsService = new VersionsService({ accountability: this.accountability, schema: this.schema });
|
|
1311
|
-
const saves = await versionsService.getVersionSaves(args['version'], collection, args['id']);
|
|
1312
|
-
if (saves) {
|
|
1313
|
-
if (this.schema.collections[collection].singleton) {
|
|
1314
|
-
return versionRaw
|
|
1315
|
-
? mergeVersionsRaw(result, saves)
|
|
1316
|
-
: mergeVersionsRecursive(result, saves, collection, this.schema);
|
|
1317
|
-
}
|
|
1318
|
-
else {
|
|
1319
|
-
if (result?.[0] === undefined)
|
|
1320
|
-
return null;
|
|
1321
|
-
return versionRaw
|
|
1322
|
-
? mergeVersionsRaw(result[0], saves)
|
|
1323
|
-
: mergeVersionsRecursive(result[0], saves, collection, this.schema);
|
|
1324
|
-
}
|
|
1325
|
-
}
|
|
1326
|
-
}
|
|
1327
|
-
if (args['id']) {
|
|
1328
|
-
return result?.[0] || null;
|
|
1329
|
-
}
|
|
1330
|
-
if (query.group) {
|
|
1331
|
-
// for every entry in result add a group field based on query.group;
|
|
1332
|
-
const aggregateKeys = Object.keys(query.aggregate ?? {});
|
|
1333
|
-
result['map']((field) => {
|
|
1334
|
-
field['group'] = omit(field, aggregateKeys);
|
|
1335
|
-
});
|
|
1336
|
-
}
|
|
1337
|
-
return result;
|
|
1338
|
-
}
|
|
1339
|
-
async resolveMutation(args, info) {
|
|
1340
|
-
const action = info.fieldName.split('_')[0];
|
|
1341
|
-
let collection = info.fieldName.substring(action.length + 1);
|
|
1342
|
-
if (this.scope === 'system')
|
|
1343
|
-
collection = `directus_${collection}`;
|
|
1344
|
-
const selections = this.replaceFragmentsInSelections(info.fieldNodes[0]?.selectionSet?.selections, info.fragments);
|
|
1345
|
-
const query = this.getQuery(args, selections || [], info.variableValues);
|
|
1346
|
-
const singleton = collection.endsWith('_batch') === false &&
|
|
1347
|
-
collection.endsWith('_items') === false &&
|
|
1348
|
-
collection.endsWith('_item') === false &&
|
|
1349
|
-
collection in this.schema.collections;
|
|
1350
|
-
const single = collection.endsWith('_items') === false && collection.endsWith('_batch') === false;
|
|
1351
|
-
const batchUpdate = action === 'update' && collection.endsWith('_batch');
|
|
1352
|
-
if (collection.endsWith('_batch'))
|
|
1353
|
-
collection = collection.slice(0, -6);
|
|
1354
|
-
if (collection.endsWith('_items'))
|
|
1355
|
-
collection = collection.slice(0, -6);
|
|
1356
|
-
if (collection.endsWith('_item'))
|
|
1357
|
-
collection = collection.slice(0, -5);
|
|
1358
|
-
if (singleton && action === 'update') {
|
|
1359
|
-
return await this.upsertSingleton(collection, args['data'], query);
|
|
1360
|
-
}
|
|
1361
|
-
const service = getService(collection, {
|
|
1362
|
-
knex: this.knex,
|
|
1363
|
-
accountability: this.accountability,
|
|
1364
|
-
schema: this.schema,
|
|
1365
|
-
});
|
|
1366
|
-
const hasQuery = (query.fields || []).length > 0;
|
|
1367
|
-
try {
|
|
1368
|
-
if (single) {
|
|
1369
|
-
if (action === 'create') {
|
|
1370
|
-
const key = await service.createOne(args['data']);
|
|
1371
|
-
return hasQuery ? await service.readOne(key, query) : true;
|
|
1372
|
-
}
|
|
1373
|
-
if (action === 'update') {
|
|
1374
|
-
const key = await service.updateOne(args['id'], args['data']);
|
|
1375
|
-
return hasQuery ? await service.readOne(key, query) : true;
|
|
1376
|
-
}
|
|
1377
|
-
if (action === 'delete') {
|
|
1378
|
-
await service.deleteOne(args['id']);
|
|
1379
|
-
return { id: args['id'] };
|
|
1380
|
-
}
|
|
1381
|
-
return undefined;
|
|
1382
|
-
}
|
|
1383
|
-
else {
|
|
1384
|
-
if (action === 'create') {
|
|
1385
|
-
const keys = await service.createMany(args['data']);
|
|
1386
|
-
return hasQuery ? await service.readMany(keys, query) : true;
|
|
1387
|
-
}
|
|
1388
|
-
if (action === 'update') {
|
|
1389
|
-
const keys = [];
|
|
1390
|
-
if (batchUpdate) {
|
|
1391
|
-
keys.push(...(await service.updateBatch(args['data'])));
|
|
1392
|
-
}
|
|
1393
|
-
else {
|
|
1394
|
-
keys.push(...(await service.updateMany(args['ids'], args['data'])));
|
|
1395
|
-
}
|
|
1396
|
-
return hasQuery ? await service.readMany(keys, query) : true;
|
|
1397
|
-
}
|
|
1398
|
-
if (action === 'delete') {
|
|
1399
|
-
const keys = await service.deleteMany(args['ids']);
|
|
1400
|
-
return { ids: keys };
|
|
1401
|
-
}
|
|
1402
|
-
return undefined;
|
|
1403
|
-
}
|
|
1404
|
-
}
|
|
1405
|
-
catch (err) {
|
|
1406
|
-
return this.formatError(err);
|
|
1407
|
-
}
|
|
59
|
+
return generateSchema(this, type);
|
|
1408
60
|
}
|
|
1409
61
|
/**
|
|
1410
62
|
* Execute the read action on the correct service. Checks for singleton as well.
|
|
@@ -1438,1374 +90,7 @@ export class GraphQLService {
|
|
|
1438
90
|
return true;
|
|
1439
91
|
}
|
|
1440
92
|
catch (err) {
|
|
1441
|
-
throw
|
|
1442
|
-
}
|
|
1443
|
-
}
|
|
1444
|
-
/**
|
|
1445
|
-
* GraphQL's regular resolver `args` variable only contains the "top-level" arguments. Seeing that we convert the
|
|
1446
|
-
* whole nested tree into one big query using Directus' own query resolver, we want to have a nested structure of
|
|
1447
|
-
* arguments for the whole resolving tree, which can later be transformed into Directus' AST using `deep`.
|
|
1448
|
-
* In order to do that, we'll parse over all ArgumentNodes and ObjectFieldNodes to manually recreate an object structure
|
|
1449
|
-
* of arguments
|
|
1450
|
-
*/
|
|
1451
|
-
parseArgs(args, variableValues) {
|
|
1452
|
-
if (!args || args['length'] === 0)
|
|
1453
|
-
return {};
|
|
1454
|
-
const parse = (node) => {
|
|
1455
|
-
switch (node.kind) {
|
|
1456
|
-
case 'Variable':
|
|
1457
|
-
return variableValues[node.name.value];
|
|
1458
|
-
case 'ListValue':
|
|
1459
|
-
return node.values.map(parse);
|
|
1460
|
-
case 'ObjectValue':
|
|
1461
|
-
return Object.fromEntries(node.fields.map((node) => [node.name.value, parse(node.value)]));
|
|
1462
|
-
case 'NullValue':
|
|
1463
|
-
return null;
|
|
1464
|
-
case 'StringValue':
|
|
1465
|
-
return String(node.value);
|
|
1466
|
-
case 'IntValue':
|
|
1467
|
-
case 'FloatValue':
|
|
1468
|
-
return Number(node.value);
|
|
1469
|
-
case 'BooleanValue':
|
|
1470
|
-
return Boolean(node.value);
|
|
1471
|
-
case 'EnumValue':
|
|
1472
|
-
default:
|
|
1473
|
-
return 'value' in node ? node.value : null;
|
|
1474
|
-
}
|
|
1475
|
-
};
|
|
1476
|
-
const argsObject = Object.fromEntries(args['map']((arg) => [arg.name.value, parse(arg.value)]));
|
|
1477
|
-
return argsObject;
|
|
1478
|
-
}
|
|
1479
|
-
/**
|
|
1480
|
-
* Get a Directus Query object from the parsed arguments (rawQuery) and GraphQL AST selectionSet. Converts SelectionSet into
|
|
1481
|
-
* Directus' `fields` query for use in the resolver. Also applies variables where appropriate.
|
|
1482
|
-
*/
|
|
1483
|
-
getQuery(rawQuery, selections, variableValues) {
|
|
1484
|
-
const query = sanitizeQuery(rawQuery, this.accountability);
|
|
1485
|
-
const parseAliases = (selections) => {
|
|
1486
|
-
const aliases = {};
|
|
1487
|
-
for (const selection of selections) {
|
|
1488
|
-
if (selection.kind !== 'Field')
|
|
1489
|
-
continue;
|
|
1490
|
-
if (selection.alias?.value) {
|
|
1491
|
-
aliases[selection.alias.value] = selection.name.value;
|
|
1492
|
-
}
|
|
1493
|
-
}
|
|
1494
|
-
return aliases;
|
|
1495
|
-
};
|
|
1496
|
-
const parseFields = (selections, parent) => {
|
|
1497
|
-
const fields = [];
|
|
1498
|
-
for (let selection of selections) {
|
|
1499
|
-
if ((selection.kind === 'Field' || selection.kind === 'InlineFragment') !== true)
|
|
1500
|
-
continue;
|
|
1501
|
-
selection = selection;
|
|
1502
|
-
let current;
|
|
1503
|
-
let currentAlias = null;
|
|
1504
|
-
// Union type (Many-to-Any)
|
|
1505
|
-
if (selection.kind === 'InlineFragment') {
|
|
1506
|
-
if (selection.typeCondition.name.value.startsWith('__'))
|
|
1507
|
-
continue;
|
|
1508
|
-
current = `${parent}:${selection.typeCondition.name.value}`;
|
|
1509
|
-
}
|
|
1510
|
-
// Any other field type
|
|
1511
|
-
else {
|
|
1512
|
-
// filter out graphql pointers, like __typename
|
|
1513
|
-
if (selection.name.value.startsWith('__'))
|
|
1514
|
-
continue;
|
|
1515
|
-
current = selection.name.value;
|
|
1516
|
-
if (selection.alias) {
|
|
1517
|
-
currentAlias = selection.alias.value;
|
|
1518
|
-
}
|
|
1519
|
-
if (parent) {
|
|
1520
|
-
current = `${parent}.${current}`;
|
|
1521
|
-
if (currentAlias) {
|
|
1522
|
-
currentAlias = `${parent}.${currentAlias}`;
|
|
1523
|
-
// add nested aliases into deep query
|
|
1524
|
-
if (selection.selectionSet) {
|
|
1525
|
-
if (!query.deep)
|
|
1526
|
-
query.deep = {};
|
|
1527
|
-
set(query.deep, parent, merge({}, get(query.deep, parent), { _alias: { [selection.alias.value]: selection.name.value } }));
|
|
1528
|
-
}
|
|
1529
|
-
}
|
|
1530
|
-
}
|
|
1531
|
-
}
|
|
1532
|
-
if (selection.selectionSet) {
|
|
1533
|
-
let children;
|
|
1534
|
-
if (current.endsWith('_func')) {
|
|
1535
|
-
children = [];
|
|
1536
|
-
const rootField = current.slice(0, -5);
|
|
1537
|
-
for (const subSelection of selection.selectionSet.selections) {
|
|
1538
|
-
if (subSelection.kind !== 'Field')
|
|
1539
|
-
continue;
|
|
1540
|
-
if (subSelection.name.value.startsWith('__'))
|
|
1541
|
-
continue;
|
|
1542
|
-
children.push(`${subSelection.name.value}(${rootField})`);
|
|
1543
|
-
}
|
|
1544
|
-
}
|
|
1545
|
-
else {
|
|
1546
|
-
children = parseFields(selection.selectionSet.selections, currentAlias ?? current);
|
|
1547
|
-
}
|
|
1548
|
-
fields.push(...children);
|
|
1549
|
-
}
|
|
1550
|
-
else {
|
|
1551
|
-
fields.push(current);
|
|
1552
|
-
}
|
|
1553
|
-
if (selection.kind === 'Field' && selection.arguments && selection.arguments.length > 0) {
|
|
1554
|
-
if (selection.arguments && selection.arguments.length > 0) {
|
|
1555
|
-
if (!query.deep)
|
|
1556
|
-
query.deep = {};
|
|
1557
|
-
const args = this.parseArgs(selection.arguments, variableValues);
|
|
1558
|
-
set(query.deep, currentAlias ?? current, merge({}, get(query.deep, currentAlias ?? current), mapKeys(sanitizeQuery(args, this.accountability), (_value, key) => `_${key}`)));
|
|
1559
|
-
}
|
|
1560
|
-
}
|
|
1561
|
-
}
|
|
1562
|
-
return uniq(fields);
|
|
1563
|
-
};
|
|
1564
|
-
query.alias = parseAliases(selections);
|
|
1565
|
-
query.fields = parseFields(selections);
|
|
1566
|
-
if (query.filter)
|
|
1567
|
-
query.filter = this.replaceFuncs(query.filter);
|
|
1568
|
-
query.deep = this.replaceFuncs(query.deep);
|
|
1569
|
-
validateQuery(query);
|
|
1570
|
-
return query;
|
|
1571
|
-
}
|
|
1572
|
-
/**
|
|
1573
|
-
* Resolve the aggregation query based on the requested aggregated fields
|
|
1574
|
-
*/
|
|
1575
|
-
getAggregateQuery(rawQuery, selections) {
|
|
1576
|
-
const query = sanitizeQuery(rawQuery, this.accountability);
|
|
1577
|
-
query.aggregate = {};
|
|
1578
|
-
for (let aggregationGroup of selections) {
|
|
1579
|
-
if ((aggregationGroup.kind === 'Field') !== true)
|
|
1580
|
-
continue;
|
|
1581
|
-
aggregationGroup = aggregationGroup;
|
|
1582
|
-
// filter out graphql pointers, like __typename
|
|
1583
|
-
if (aggregationGroup.name.value.startsWith('__'))
|
|
1584
|
-
continue;
|
|
1585
|
-
const aggregateProperty = aggregationGroup.name.value;
|
|
1586
|
-
query.aggregate[aggregateProperty] =
|
|
1587
|
-
aggregationGroup.selectionSet?.selections
|
|
1588
|
-
// filter out graphql pointers, like __typename
|
|
1589
|
-
.filter((selectionNode) => !selectionNode?.name.value.startsWith('__'))
|
|
1590
|
-
.map((selectionNode) => {
|
|
1591
|
-
selectionNode = selectionNode;
|
|
1592
|
-
return selectionNode.name.value;
|
|
1593
|
-
}) ?? [];
|
|
1594
|
-
}
|
|
1595
|
-
if (query.filter) {
|
|
1596
|
-
query.filter = this.replaceFuncs(query.filter);
|
|
1597
|
-
}
|
|
1598
|
-
validateQuery(query);
|
|
1599
|
-
return query;
|
|
1600
|
-
}
|
|
1601
|
-
/**
|
|
1602
|
-
* Replace functions from GraphQL format to Directus-Filter format
|
|
1603
|
-
*/
|
|
1604
|
-
replaceFuncs(filter) {
|
|
1605
|
-
return replaceFuncDeep(filter);
|
|
1606
|
-
function replaceFuncDeep(filter) {
|
|
1607
|
-
return transform(filter, (result, value, key) => {
|
|
1608
|
-
const isFunctionKey = typeof key === 'string' && key.endsWith('_func') && FUNCTIONS.includes(Object.keys(value)[0]);
|
|
1609
|
-
if (isFunctionKey) {
|
|
1610
|
-
const functionName = Object.keys(value)[0];
|
|
1611
|
-
const fieldName = key.slice(0, -5);
|
|
1612
|
-
result[`${functionName}(${fieldName})`] = Object.values(value)[0];
|
|
1613
|
-
}
|
|
1614
|
-
else {
|
|
1615
|
-
result[key] = value?.constructor === Object || value?.constructor === Array ? replaceFuncDeep(value) : value;
|
|
1616
|
-
}
|
|
1617
|
-
});
|
|
1618
|
-
}
|
|
1619
|
-
}
|
|
1620
|
-
/**
|
|
1621
|
-
* Convert Directus-Exception into a GraphQL format, so it can be returned by GraphQL properly.
|
|
1622
|
-
*/
|
|
1623
|
-
formatError(error) {
|
|
1624
|
-
if (Array.isArray(error)) {
|
|
1625
|
-
set(error[0], 'extensions.code', error[0].code);
|
|
1626
|
-
return new GraphQLError(error[0].message, undefined, undefined, undefined, undefined, error[0]);
|
|
1627
|
-
}
|
|
1628
|
-
set(error, 'extensions.code', error.code);
|
|
1629
|
-
return new GraphQLError(error.message, undefined, undefined, undefined, undefined, error);
|
|
1630
|
-
}
|
|
1631
|
-
/**
|
|
1632
|
-
* Replace all fragments in a selectionset for the actual selection set as defined in the fragment
|
|
1633
|
-
* Effectively merges the selections with the fragments used in those selections
|
|
1634
|
-
*/
|
|
1635
|
-
replaceFragmentsInSelections(selections, fragments) {
|
|
1636
|
-
if (!selections)
|
|
1637
|
-
return null;
|
|
1638
|
-
const result = flatten(selections.map((selection) => {
|
|
1639
|
-
// Fragments can contains fragments themselves. This allows for nested fragments
|
|
1640
|
-
if (selection.kind === 'FragmentSpread') {
|
|
1641
|
-
return this.replaceFragmentsInSelections(fragments[selection.name.value].selectionSet.selections, fragments);
|
|
1642
|
-
}
|
|
1643
|
-
// Nested relational fields can also contain fragments
|
|
1644
|
-
if ((selection.kind === 'Field' || selection.kind === 'InlineFragment') && selection.selectionSet) {
|
|
1645
|
-
selection.selectionSet.selections = this.replaceFragmentsInSelections(selection.selectionSet.selections, fragments);
|
|
1646
|
-
}
|
|
1647
|
-
return selection;
|
|
1648
|
-
})).filter((s) => s);
|
|
1649
|
-
return result;
|
|
1650
|
-
}
|
|
1651
|
-
injectSystemResolvers(schemaComposer, { CreateCollectionTypes, ReadCollectionTypes, UpdateCollectionTypes, }, schema) {
|
|
1652
|
-
const AuthTokens = schemaComposer.createObjectTC({
|
|
1653
|
-
name: 'auth_tokens',
|
|
1654
|
-
fields: {
|
|
1655
|
-
access_token: GraphQLString,
|
|
1656
|
-
expires: GraphQLBigInt,
|
|
1657
|
-
refresh_token: GraphQLString,
|
|
1658
|
-
},
|
|
1659
|
-
});
|
|
1660
|
-
const AuthMode = new GraphQLEnumType({
|
|
1661
|
-
name: 'auth_mode',
|
|
1662
|
-
values: {
|
|
1663
|
-
json: { value: 'json' },
|
|
1664
|
-
cookie: { value: 'cookie' },
|
|
1665
|
-
session: { value: 'session' },
|
|
1666
|
-
},
|
|
1667
|
-
});
|
|
1668
|
-
const ServerInfo = schemaComposer.createObjectTC({
|
|
1669
|
-
name: 'server_info',
|
|
1670
|
-
fields: {
|
|
1671
|
-
project: {
|
|
1672
|
-
type: new GraphQLObjectType({
|
|
1673
|
-
name: 'server_info_project',
|
|
1674
|
-
fields: {
|
|
1675
|
-
project_name: { type: GraphQLString },
|
|
1676
|
-
project_descriptor: { type: GraphQLString },
|
|
1677
|
-
project_logo: { type: GraphQLString },
|
|
1678
|
-
project_color: { type: GraphQLString },
|
|
1679
|
-
default_language: { type: GraphQLString },
|
|
1680
|
-
public_foreground: { type: GraphQLString },
|
|
1681
|
-
public_background: { type: GraphQLString },
|
|
1682
|
-
public_note: { type: GraphQLString },
|
|
1683
|
-
custom_css: { type: GraphQLString },
|
|
1684
|
-
public_registration: { type: GraphQLBoolean },
|
|
1685
|
-
public_registration_verify_email: { type: GraphQLBoolean },
|
|
1686
|
-
},
|
|
1687
|
-
}),
|
|
1688
|
-
},
|
|
1689
|
-
},
|
|
1690
|
-
});
|
|
1691
|
-
if (this.accountability?.user) {
|
|
1692
|
-
ServerInfo.addFields({
|
|
1693
|
-
rateLimit: env['RATE_LIMITER_ENABLED']
|
|
1694
|
-
? {
|
|
1695
|
-
type: new GraphQLObjectType({
|
|
1696
|
-
name: 'server_info_rate_limit',
|
|
1697
|
-
fields: {
|
|
1698
|
-
points: { type: GraphQLInt },
|
|
1699
|
-
duration: { type: GraphQLInt },
|
|
1700
|
-
},
|
|
1701
|
-
}),
|
|
1702
|
-
}
|
|
1703
|
-
: GraphQLBoolean,
|
|
1704
|
-
rateLimitGlobal: env['RATE_LIMITER_GLOBAL_ENABLED']
|
|
1705
|
-
? {
|
|
1706
|
-
type: new GraphQLObjectType({
|
|
1707
|
-
name: 'server_info_rate_limit_global',
|
|
1708
|
-
fields: {
|
|
1709
|
-
points: { type: GraphQLInt },
|
|
1710
|
-
duration: { type: GraphQLInt },
|
|
1711
|
-
},
|
|
1712
|
-
}),
|
|
1713
|
-
}
|
|
1714
|
-
: GraphQLBoolean,
|
|
1715
|
-
websocket: toBoolean(env['WEBSOCKETS_ENABLED'])
|
|
1716
|
-
? {
|
|
1717
|
-
type: new GraphQLObjectType({
|
|
1718
|
-
name: 'server_info_websocket',
|
|
1719
|
-
fields: {
|
|
1720
|
-
rest: {
|
|
1721
|
-
type: toBoolean(env['WEBSOCKETS_REST_ENABLED'])
|
|
1722
|
-
? new GraphQLObjectType({
|
|
1723
|
-
name: 'server_info_websocket_rest',
|
|
1724
|
-
fields: {
|
|
1725
|
-
authentication: {
|
|
1726
|
-
type: new GraphQLEnumType({
|
|
1727
|
-
name: 'server_info_websocket_rest_authentication',
|
|
1728
|
-
values: {
|
|
1729
|
-
public: { value: 'public' },
|
|
1730
|
-
handshake: { value: 'handshake' },
|
|
1731
|
-
strict: { value: 'strict' },
|
|
1732
|
-
},
|
|
1733
|
-
}),
|
|
1734
|
-
},
|
|
1735
|
-
path: { type: GraphQLString },
|
|
1736
|
-
},
|
|
1737
|
-
})
|
|
1738
|
-
: GraphQLBoolean,
|
|
1739
|
-
},
|
|
1740
|
-
graphql: {
|
|
1741
|
-
type: toBoolean(env['WEBSOCKETS_GRAPHQL_ENABLED'])
|
|
1742
|
-
? new GraphQLObjectType({
|
|
1743
|
-
name: 'server_info_websocket_graphql',
|
|
1744
|
-
fields: {
|
|
1745
|
-
authentication: {
|
|
1746
|
-
type: new GraphQLEnumType({
|
|
1747
|
-
name: 'server_info_websocket_graphql_authentication',
|
|
1748
|
-
values: {
|
|
1749
|
-
public: { value: 'public' },
|
|
1750
|
-
handshake: { value: 'handshake' },
|
|
1751
|
-
strict: { value: 'strict' },
|
|
1752
|
-
},
|
|
1753
|
-
}),
|
|
1754
|
-
},
|
|
1755
|
-
path: { type: GraphQLString },
|
|
1756
|
-
},
|
|
1757
|
-
})
|
|
1758
|
-
: GraphQLBoolean,
|
|
1759
|
-
},
|
|
1760
|
-
heartbeat: {
|
|
1761
|
-
type: toBoolean(env['WEBSOCKETS_HEARTBEAT_ENABLED']) ? GraphQLInt : GraphQLBoolean,
|
|
1762
|
-
},
|
|
1763
|
-
},
|
|
1764
|
-
}),
|
|
1765
|
-
}
|
|
1766
|
-
: GraphQLBoolean,
|
|
1767
|
-
queryLimit: {
|
|
1768
|
-
type: new GraphQLObjectType({
|
|
1769
|
-
name: 'server_info_query_limit',
|
|
1770
|
-
fields: {
|
|
1771
|
-
default: { type: GraphQLInt },
|
|
1772
|
-
max: { type: GraphQLInt },
|
|
1773
|
-
},
|
|
1774
|
-
}),
|
|
1775
|
-
},
|
|
1776
|
-
});
|
|
1777
|
-
}
|
|
1778
|
-
/** Globally available query */
|
|
1779
|
-
schemaComposer.Query.addFields({
|
|
1780
|
-
server_specs_oas: {
|
|
1781
|
-
type: GraphQLJSON,
|
|
1782
|
-
resolve: async () => {
|
|
1783
|
-
const service = new SpecificationService({ schema: this.schema, accountability: this.accountability });
|
|
1784
|
-
return await service.oas.generate();
|
|
1785
|
-
},
|
|
1786
|
-
},
|
|
1787
|
-
server_specs_graphql: {
|
|
1788
|
-
type: GraphQLString,
|
|
1789
|
-
args: {
|
|
1790
|
-
scope: new GraphQLEnumType({
|
|
1791
|
-
name: 'graphql_sdl_scope',
|
|
1792
|
-
values: {
|
|
1793
|
-
items: { value: 'items' },
|
|
1794
|
-
system: { value: 'system' },
|
|
1795
|
-
},
|
|
1796
|
-
}),
|
|
1797
|
-
},
|
|
1798
|
-
resolve: async (_, args) => {
|
|
1799
|
-
const service = new GraphQLService({
|
|
1800
|
-
schema: this.schema,
|
|
1801
|
-
accountability: this.accountability,
|
|
1802
|
-
scope: args['scope'] ?? 'items',
|
|
1803
|
-
});
|
|
1804
|
-
return await service.getSchema('sdl');
|
|
1805
|
-
},
|
|
1806
|
-
},
|
|
1807
|
-
server_ping: {
|
|
1808
|
-
type: GraphQLString,
|
|
1809
|
-
resolve: () => 'pong',
|
|
1810
|
-
},
|
|
1811
|
-
server_info: {
|
|
1812
|
-
type: ServerInfo,
|
|
1813
|
-
resolve: async () => {
|
|
1814
|
-
const service = new ServerService({
|
|
1815
|
-
accountability: this.accountability,
|
|
1816
|
-
schema: this.schema,
|
|
1817
|
-
});
|
|
1818
|
-
return await service.serverInfo();
|
|
1819
|
-
},
|
|
1820
|
-
},
|
|
1821
|
-
server_health: {
|
|
1822
|
-
type: GraphQLJSON,
|
|
1823
|
-
resolve: async () => {
|
|
1824
|
-
const service = new ServerService({
|
|
1825
|
-
accountability: this.accountability,
|
|
1826
|
-
schema: this.schema,
|
|
1827
|
-
});
|
|
1828
|
-
return await service.health();
|
|
1829
|
-
},
|
|
1830
|
-
},
|
|
1831
|
-
});
|
|
1832
|
-
const Collection = schemaComposer.createObjectTC({
|
|
1833
|
-
name: 'directus_collections',
|
|
1834
|
-
});
|
|
1835
|
-
const Field = schemaComposer.createObjectTC({
|
|
1836
|
-
name: 'directus_fields',
|
|
1837
|
-
});
|
|
1838
|
-
const Relation = schemaComposer.createObjectTC({
|
|
1839
|
-
name: 'directus_relations',
|
|
1840
|
-
});
|
|
1841
|
-
const Extension = schemaComposer.createObjectTC({
|
|
1842
|
-
name: 'directus_extensions',
|
|
1843
|
-
});
|
|
1844
|
-
/**
|
|
1845
|
-
* Globally available mutations
|
|
1846
|
-
*/
|
|
1847
|
-
schemaComposer.Mutation.addFields({
|
|
1848
|
-
auth_login: {
|
|
1849
|
-
type: AuthTokens,
|
|
1850
|
-
args: {
|
|
1851
|
-
email: new GraphQLNonNull(GraphQLString),
|
|
1852
|
-
password: new GraphQLNonNull(GraphQLString),
|
|
1853
|
-
mode: AuthMode,
|
|
1854
|
-
otp: GraphQLString,
|
|
1855
|
-
},
|
|
1856
|
-
resolve: async (_, args, { req, res }) => {
|
|
1857
|
-
const accountability = createDefaultAccountability();
|
|
1858
|
-
if (req?.ip)
|
|
1859
|
-
accountability.ip = req.ip;
|
|
1860
|
-
const userAgent = req?.get('user-agent');
|
|
1861
|
-
if (userAgent)
|
|
1862
|
-
accountability.userAgent = userAgent;
|
|
1863
|
-
const origin = req?.get('origin');
|
|
1864
|
-
if (origin)
|
|
1865
|
-
accountability.origin = origin;
|
|
1866
|
-
const authenticationService = new AuthenticationService({
|
|
1867
|
-
accountability: accountability,
|
|
1868
|
-
schema: this.schema,
|
|
1869
|
-
});
|
|
1870
|
-
const mode = args['mode'] ?? 'json';
|
|
1871
|
-
const { accessToken, refreshToken, expires } = await authenticationService.login(DEFAULT_AUTH_PROVIDER, args, {
|
|
1872
|
-
session: mode === 'session',
|
|
1873
|
-
otp: args?.otp,
|
|
1874
|
-
});
|
|
1875
|
-
const payload = { expires };
|
|
1876
|
-
if (mode === 'json') {
|
|
1877
|
-
payload.refresh_token = refreshToken;
|
|
1878
|
-
payload.access_token = accessToken;
|
|
1879
|
-
}
|
|
1880
|
-
if (mode === 'cookie') {
|
|
1881
|
-
res?.cookie(env['REFRESH_TOKEN_COOKIE_NAME'], refreshToken, REFRESH_COOKIE_OPTIONS);
|
|
1882
|
-
payload.access_token = accessToken;
|
|
1883
|
-
}
|
|
1884
|
-
if (mode === 'session') {
|
|
1885
|
-
res?.cookie(env['SESSION_COOKIE_NAME'], accessToken, SESSION_COOKIE_OPTIONS);
|
|
1886
|
-
}
|
|
1887
|
-
return payload;
|
|
1888
|
-
},
|
|
1889
|
-
},
|
|
1890
|
-
auth_refresh: {
|
|
1891
|
-
type: AuthTokens,
|
|
1892
|
-
args: {
|
|
1893
|
-
refresh_token: GraphQLString,
|
|
1894
|
-
mode: AuthMode,
|
|
1895
|
-
},
|
|
1896
|
-
resolve: async (_, args, { req, res }) => {
|
|
1897
|
-
const accountability = createDefaultAccountability();
|
|
1898
|
-
if (req?.ip)
|
|
1899
|
-
accountability.ip = req.ip;
|
|
1900
|
-
const userAgent = req?.get('user-agent');
|
|
1901
|
-
if (userAgent)
|
|
1902
|
-
accountability.userAgent = userAgent;
|
|
1903
|
-
const origin = req?.get('origin');
|
|
1904
|
-
if (origin)
|
|
1905
|
-
accountability.origin = origin;
|
|
1906
|
-
const authenticationService = new AuthenticationService({
|
|
1907
|
-
accountability: accountability,
|
|
1908
|
-
schema: this.schema,
|
|
1909
|
-
});
|
|
1910
|
-
const mode = args['mode'] ?? 'json';
|
|
1911
|
-
let currentRefreshToken;
|
|
1912
|
-
if (mode === 'json') {
|
|
1913
|
-
currentRefreshToken = args['refresh_token'];
|
|
1914
|
-
}
|
|
1915
|
-
else if (mode === 'cookie') {
|
|
1916
|
-
currentRefreshToken = req?.cookies[env['REFRESH_TOKEN_COOKIE_NAME']];
|
|
1917
|
-
}
|
|
1918
|
-
else if (mode === 'session') {
|
|
1919
|
-
const token = req?.cookies[env['SESSION_COOKIE_NAME']];
|
|
1920
|
-
if (isDirectusJWT(token)) {
|
|
1921
|
-
const payload = verifyAccessJWT(token, getSecret());
|
|
1922
|
-
currentRefreshToken = payload.session;
|
|
1923
|
-
}
|
|
1924
|
-
}
|
|
1925
|
-
if (!currentRefreshToken) {
|
|
1926
|
-
throw new InvalidPayloadError({
|
|
1927
|
-
reason: `The refresh token is required in either the payload or cookie`,
|
|
1928
|
-
});
|
|
1929
|
-
}
|
|
1930
|
-
const { accessToken, refreshToken, expires } = await authenticationService.refresh(currentRefreshToken, {
|
|
1931
|
-
session: mode === 'session',
|
|
1932
|
-
});
|
|
1933
|
-
const payload = { expires };
|
|
1934
|
-
if (mode === 'json') {
|
|
1935
|
-
payload.refresh_token = refreshToken;
|
|
1936
|
-
payload.access_token = accessToken;
|
|
1937
|
-
}
|
|
1938
|
-
if (mode === 'cookie') {
|
|
1939
|
-
res?.cookie(env['REFRESH_TOKEN_COOKIE_NAME'], refreshToken, REFRESH_COOKIE_OPTIONS);
|
|
1940
|
-
payload.access_token = accessToken;
|
|
1941
|
-
}
|
|
1942
|
-
if (mode === 'session') {
|
|
1943
|
-
res?.cookie(env['SESSION_COOKIE_NAME'], accessToken, SESSION_COOKIE_OPTIONS);
|
|
1944
|
-
}
|
|
1945
|
-
return payload;
|
|
1946
|
-
},
|
|
1947
|
-
},
|
|
1948
|
-
auth_logout: {
|
|
1949
|
-
type: GraphQLBoolean,
|
|
1950
|
-
args: {
|
|
1951
|
-
refresh_token: GraphQLString,
|
|
1952
|
-
mode: AuthMode,
|
|
1953
|
-
},
|
|
1954
|
-
resolve: async (_, args, { req, res }) => {
|
|
1955
|
-
const accountability = createDefaultAccountability();
|
|
1956
|
-
if (req?.ip)
|
|
1957
|
-
accountability.ip = req.ip;
|
|
1958
|
-
const userAgent = req?.get('user-agent');
|
|
1959
|
-
if (userAgent)
|
|
1960
|
-
accountability.userAgent = userAgent;
|
|
1961
|
-
const origin = req?.get('origin');
|
|
1962
|
-
if (origin)
|
|
1963
|
-
accountability.origin = origin;
|
|
1964
|
-
const authenticationService = new AuthenticationService({
|
|
1965
|
-
accountability: accountability,
|
|
1966
|
-
schema: this.schema,
|
|
1967
|
-
});
|
|
1968
|
-
const mode = args['mode'] ?? 'json';
|
|
1969
|
-
let currentRefreshToken;
|
|
1970
|
-
if (mode === 'json') {
|
|
1971
|
-
currentRefreshToken = args['refresh_token'];
|
|
1972
|
-
}
|
|
1973
|
-
else if (mode === 'cookie') {
|
|
1974
|
-
currentRefreshToken = req?.cookies[env['REFRESH_TOKEN_COOKIE_NAME']];
|
|
1975
|
-
}
|
|
1976
|
-
else if (mode === 'session') {
|
|
1977
|
-
const token = req?.cookies[env['SESSION_COOKIE_NAME']];
|
|
1978
|
-
if (isDirectusJWT(token)) {
|
|
1979
|
-
const payload = verifyAccessJWT(token, getSecret());
|
|
1980
|
-
currentRefreshToken = payload.session;
|
|
1981
|
-
}
|
|
1982
|
-
}
|
|
1983
|
-
if (!currentRefreshToken) {
|
|
1984
|
-
throw new InvalidPayloadError({
|
|
1985
|
-
reason: `The refresh token is required in either the payload or cookie`,
|
|
1986
|
-
});
|
|
1987
|
-
}
|
|
1988
|
-
await authenticationService.logout(currentRefreshToken);
|
|
1989
|
-
if (req?.cookies[env['REFRESH_TOKEN_COOKIE_NAME']]) {
|
|
1990
|
-
res?.clearCookie(env['REFRESH_TOKEN_COOKIE_NAME'], REFRESH_COOKIE_OPTIONS);
|
|
1991
|
-
}
|
|
1992
|
-
if (req?.cookies[env['SESSION_COOKIE_NAME']]) {
|
|
1993
|
-
res?.clearCookie(env['SESSION_COOKIE_NAME'], SESSION_COOKIE_OPTIONS);
|
|
1994
|
-
}
|
|
1995
|
-
return true;
|
|
1996
|
-
},
|
|
1997
|
-
},
|
|
1998
|
-
auth_password_request: {
|
|
1999
|
-
type: GraphQLBoolean,
|
|
2000
|
-
args: {
|
|
2001
|
-
email: new GraphQLNonNull(GraphQLString),
|
|
2002
|
-
reset_url: GraphQLString,
|
|
2003
|
-
},
|
|
2004
|
-
resolve: async (_, args, { req }) => {
|
|
2005
|
-
const accountability = createDefaultAccountability();
|
|
2006
|
-
if (req?.ip)
|
|
2007
|
-
accountability.ip = req.ip;
|
|
2008
|
-
const userAgent = req?.get('user-agent');
|
|
2009
|
-
if (userAgent)
|
|
2010
|
-
accountability.userAgent = userAgent;
|
|
2011
|
-
const origin = req?.get('origin');
|
|
2012
|
-
if (origin)
|
|
2013
|
-
accountability.origin = origin;
|
|
2014
|
-
const service = new UsersService({ accountability, schema: this.schema });
|
|
2015
|
-
try {
|
|
2016
|
-
await service.requestPasswordReset(args['email'], args['reset_url'] || null);
|
|
2017
|
-
}
|
|
2018
|
-
catch (err) {
|
|
2019
|
-
if (isDirectusError(err, ErrorCode.InvalidPayload)) {
|
|
2020
|
-
throw err;
|
|
2021
|
-
}
|
|
2022
|
-
}
|
|
2023
|
-
return true;
|
|
2024
|
-
},
|
|
2025
|
-
},
|
|
2026
|
-
auth_password_reset: {
|
|
2027
|
-
type: GraphQLBoolean,
|
|
2028
|
-
args: {
|
|
2029
|
-
token: new GraphQLNonNull(GraphQLString),
|
|
2030
|
-
password: new GraphQLNonNull(GraphQLString),
|
|
2031
|
-
},
|
|
2032
|
-
resolve: async (_, args, { req }) => {
|
|
2033
|
-
const accountability = createDefaultAccountability();
|
|
2034
|
-
if (req?.ip)
|
|
2035
|
-
accountability.ip = req.ip;
|
|
2036
|
-
const userAgent = req?.get('user-agent');
|
|
2037
|
-
if (userAgent)
|
|
2038
|
-
accountability.userAgent = userAgent;
|
|
2039
|
-
const origin = req?.get('origin');
|
|
2040
|
-
if (origin)
|
|
2041
|
-
accountability.origin = origin;
|
|
2042
|
-
const service = new UsersService({ accountability, schema: this.schema });
|
|
2043
|
-
await service.resetPassword(args['token'], args['password']);
|
|
2044
|
-
return true;
|
|
2045
|
-
},
|
|
2046
|
-
},
|
|
2047
|
-
users_me_tfa_generate: {
|
|
2048
|
-
type: new GraphQLObjectType({
|
|
2049
|
-
name: 'users_me_tfa_generate_data',
|
|
2050
|
-
fields: {
|
|
2051
|
-
secret: { type: GraphQLString },
|
|
2052
|
-
otpauth_url: { type: GraphQLString },
|
|
2053
|
-
},
|
|
2054
|
-
}),
|
|
2055
|
-
args: {
|
|
2056
|
-
password: new GraphQLNonNull(GraphQLString),
|
|
2057
|
-
},
|
|
2058
|
-
resolve: async (_, args) => {
|
|
2059
|
-
if (!this.accountability?.user)
|
|
2060
|
-
return null;
|
|
2061
|
-
const service = new TFAService({
|
|
2062
|
-
accountability: this.accountability,
|
|
2063
|
-
schema: this.schema,
|
|
2064
|
-
});
|
|
2065
|
-
const authService = new AuthenticationService({
|
|
2066
|
-
accountability: this.accountability,
|
|
2067
|
-
schema: this.schema,
|
|
2068
|
-
});
|
|
2069
|
-
await authService.verifyPassword(this.accountability.user, args['password']);
|
|
2070
|
-
const { url, secret } = await service.generateTFA(this.accountability.user);
|
|
2071
|
-
return { secret, otpauth_url: url };
|
|
2072
|
-
},
|
|
2073
|
-
},
|
|
2074
|
-
users_me_tfa_enable: {
|
|
2075
|
-
type: GraphQLBoolean,
|
|
2076
|
-
args: {
|
|
2077
|
-
otp: new GraphQLNonNull(GraphQLString),
|
|
2078
|
-
secret: new GraphQLNonNull(GraphQLString),
|
|
2079
|
-
},
|
|
2080
|
-
resolve: async (_, args) => {
|
|
2081
|
-
if (!this.accountability?.user)
|
|
2082
|
-
return null;
|
|
2083
|
-
const service = new TFAService({
|
|
2084
|
-
accountability: this.accountability,
|
|
2085
|
-
schema: this.schema,
|
|
2086
|
-
});
|
|
2087
|
-
await service.enableTFA(this.accountability.user, args['otp'], args['secret']);
|
|
2088
|
-
return true;
|
|
2089
|
-
},
|
|
2090
|
-
},
|
|
2091
|
-
users_me_tfa_disable: {
|
|
2092
|
-
type: GraphQLBoolean,
|
|
2093
|
-
args: {
|
|
2094
|
-
otp: new GraphQLNonNull(GraphQLString),
|
|
2095
|
-
},
|
|
2096
|
-
resolve: async (_, args) => {
|
|
2097
|
-
if (!this.accountability?.user)
|
|
2098
|
-
return null;
|
|
2099
|
-
const service = new TFAService({
|
|
2100
|
-
accountability: this.accountability,
|
|
2101
|
-
schema: this.schema,
|
|
2102
|
-
});
|
|
2103
|
-
const otpValid = await service.verifyOTP(this.accountability.user, args['otp']);
|
|
2104
|
-
if (otpValid === false) {
|
|
2105
|
-
throw new InvalidPayloadError({ reason: `"otp" is invalid` });
|
|
2106
|
-
}
|
|
2107
|
-
await service.disableTFA(this.accountability.user);
|
|
2108
|
-
return true;
|
|
2109
|
-
},
|
|
2110
|
-
},
|
|
2111
|
-
utils_random_string: {
|
|
2112
|
-
type: GraphQLString,
|
|
2113
|
-
args: {
|
|
2114
|
-
length: GraphQLInt,
|
|
2115
|
-
},
|
|
2116
|
-
resolve: async (_, args) => {
|
|
2117
|
-
const { nanoid } = await import('nanoid');
|
|
2118
|
-
if (args['length'] !== undefined && (args['length'] < 1 || args['length'] > 500)) {
|
|
2119
|
-
throw new InvalidPayloadError({ reason: `"length" must be between 1 and 500` });
|
|
2120
|
-
}
|
|
2121
|
-
return nanoid(args['length'] ? args['length'] : 32);
|
|
2122
|
-
},
|
|
2123
|
-
},
|
|
2124
|
-
utils_hash_generate: {
|
|
2125
|
-
type: GraphQLString,
|
|
2126
|
-
args: {
|
|
2127
|
-
string: new GraphQLNonNull(GraphQLString),
|
|
2128
|
-
},
|
|
2129
|
-
resolve: async (_, args) => {
|
|
2130
|
-
return await generateHash(args['string']);
|
|
2131
|
-
},
|
|
2132
|
-
},
|
|
2133
|
-
utils_hash_verify: {
|
|
2134
|
-
type: GraphQLBoolean,
|
|
2135
|
-
args: {
|
|
2136
|
-
string: new GraphQLNonNull(GraphQLString),
|
|
2137
|
-
hash: new GraphQLNonNull(GraphQLString),
|
|
2138
|
-
},
|
|
2139
|
-
resolve: async (_, args) => {
|
|
2140
|
-
return await argon2.verify(args['hash'], args['string']);
|
|
2141
|
-
},
|
|
2142
|
-
},
|
|
2143
|
-
utils_sort: {
|
|
2144
|
-
type: GraphQLBoolean,
|
|
2145
|
-
args: {
|
|
2146
|
-
collection: new GraphQLNonNull(GraphQLString),
|
|
2147
|
-
item: new GraphQLNonNull(GraphQLID),
|
|
2148
|
-
to: new GraphQLNonNull(GraphQLID),
|
|
2149
|
-
},
|
|
2150
|
-
resolve: async (_, args) => {
|
|
2151
|
-
const service = new UtilsService({
|
|
2152
|
-
accountability: this.accountability,
|
|
2153
|
-
schema: this.schema,
|
|
2154
|
-
});
|
|
2155
|
-
const { item, to } = args;
|
|
2156
|
-
await service.sort(args['collection'], { item, to });
|
|
2157
|
-
return true;
|
|
2158
|
-
},
|
|
2159
|
-
},
|
|
2160
|
-
utils_revert: {
|
|
2161
|
-
type: GraphQLBoolean,
|
|
2162
|
-
args: {
|
|
2163
|
-
revision: new GraphQLNonNull(GraphQLID),
|
|
2164
|
-
},
|
|
2165
|
-
resolve: async (_, args) => {
|
|
2166
|
-
const service = new RevisionsService({
|
|
2167
|
-
accountability: this.accountability,
|
|
2168
|
-
schema: this.schema,
|
|
2169
|
-
});
|
|
2170
|
-
await service.revert(args['revision']);
|
|
2171
|
-
return true;
|
|
2172
|
-
},
|
|
2173
|
-
},
|
|
2174
|
-
utils_cache_clear: {
|
|
2175
|
-
type: GraphQLVoid,
|
|
2176
|
-
resolve: async () => {
|
|
2177
|
-
if (this.accountability?.admin !== true) {
|
|
2178
|
-
throw new ForbiddenError();
|
|
2179
|
-
}
|
|
2180
|
-
const { cache } = getCache();
|
|
2181
|
-
await cache?.clear();
|
|
2182
|
-
await clearSystemCache();
|
|
2183
|
-
return;
|
|
2184
|
-
},
|
|
2185
|
-
},
|
|
2186
|
-
users_invite_accept: {
|
|
2187
|
-
type: GraphQLBoolean,
|
|
2188
|
-
args: {
|
|
2189
|
-
token: new GraphQLNonNull(GraphQLString),
|
|
2190
|
-
password: new GraphQLNonNull(GraphQLString),
|
|
2191
|
-
},
|
|
2192
|
-
resolve: async (_, args) => {
|
|
2193
|
-
const service = new UsersService({
|
|
2194
|
-
accountability: this.accountability,
|
|
2195
|
-
schema: this.schema,
|
|
2196
|
-
});
|
|
2197
|
-
await service.acceptInvite(args['token'], args['password']);
|
|
2198
|
-
return true;
|
|
2199
|
-
},
|
|
2200
|
-
},
|
|
2201
|
-
users_register: {
|
|
2202
|
-
type: GraphQLBoolean,
|
|
2203
|
-
args: {
|
|
2204
|
-
email: new GraphQLNonNull(GraphQLString),
|
|
2205
|
-
password: new GraphQLNonNull(GraphQLString),
|
|
2206
|
-
verification_url: GraphQLString,
|
|
2207
|
-
first_name: GraphQLString,
|
|
2208
|
-
last_name: GraphQLString,
|
|
2209
|
-
},
|
|
2210
|
-
resolve: async (_, args, { req }) => {
|
|
2211
|
-
const service = new UsersService({ accountability: null, schema: this.schema });
|
|
2212
|
-
const ip = req ? getIPFromReq(req) : null;
|
|
2213
|
-
if (ip) {
|
|
2214
|
-
await rateLimiter.consume(ip);
|
|
2215
|
-
}
|
|
2216
|
-
await service.registerUser({
|
|
2217
|
-
email: args.email,
|
|
2218
|
-
password: args.password,
|
|
2219
|
-
verification_url: args.verification_url,
|
|
2220
|
-
first_name: args.first_name,
|
|
2221
|
-
last_name: args.last_name,
|
|
2222
|
-
});
|
|
2223
|
-
return true;
|
|
2224
|
-
},
|
|
2225
|
-
},
|
|
2226
|
-
users_register_verify: {
|
|
2227
|
-
type: GraphQLBoolean,
|
|
2228
|
-
args: {
|
|
2229
|
-
token: new GraphQLNonNull(GraphQLString),
|
|
2230
|
-
},
|
|
2231
|
-
resolve: async (_, args) => {
|
|
2232
|
-
const service = new UsersService({ accountability: null, schema: this.schema });
|
|
2233
|
-
await service.verifyRegistration(args.token);
|
|
2234
|
-
return true;
|
|
2235
|
-
},
|
|
2236
|
-
},
|
|
2237
|
-
});
|
|
2238
|
-
if ('directus_collections' in schema.read.collections) {
|
|
2239
|
-
Collection.addFields({
|
|
2240
|
-
collection: GraphQLString,
|
|
2241
|
-
meta: schemaComposer.createObjectTC({
|
|
2242
|
-
name: 'directus_collections_meta',
|
|
2243
|
-
fields: Object.values(schema.read.collections['directus_collections'].fields).reduce((acc, field) => {
|
|
2244
|
-
acc[field.field] = {
|
|
2245
|
-
type: field.nullable
|
|
2246
|
-
? getGraphQLType(field.type, field.special)
|
|
2247
|
-
: new GraphQLNonNull(getGraphQLType(field.type, field.special)),
|
|
2248
|
-
description: field.note,
|
|
2249
|
-
};
|
|
2250
|
-
return acc;
|
|
2251
|
-
}, {}),
|
|
2252
|
-
}),
|
|
2253
|
-
schema: schemaComposer.createObjectTC({
|
|
2254
|
-
name: 'directus_collections_schema',
|
|
2255
|
-
fields: {
|
|
2256
|
-
name: GraphQLString,
|
|
2257
|
-
comment: GraphQLString,
|
|
2258
|
-
},
|
|
2259
|
-
}),
|
|
2260
|
-
});
|
|
2261
|
-
schemaComposer.Query.addFields({
|
|
2262
|
-
collections: {
|
|
2263
|
-
type: new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(Collection.getType()))),
|
|
2264
|
-
resolve: async () => {
|
|
2265
|
-
const collectionsService = new CollectionsService({
|
|
2266
|
-
accountability: this.accountability,
|
|
2267
|
-
schema: this.schema,
|
|
2268
|
-
});
|
|
2269
|
-
return await collectionsService.readByQuery();
|
|
2270
|
-
},
|
|
2271
|
-
},
|
|
2272
|
-
collections_by_name: {
|
|
2273
|
-
type: Collection,
|
|
2274
|
-
args: {
|
|
2275
|
-
name: new GraphQLNonNull(GraphQLString),
|
|
2276
|
-
},
|
|
2277
|
-
resolve: async (_, args) => {
|
|
2278
|
-
const collectionsService = new CollectionsService({
|
|
2279
|
-
accountability: this.accountability,
|
|
2280
|
-
schema: this.schema,
|
|
2281
|
-
});
|
|
2282
|
-
return await collectionsService.readOne(args['name']);
|
|
2283
|
-
},
|
|
2284
|
-
},
|
|
2285
|
-
});
|
|
2286
|
-
}
|
|
2287
|
-
if ('directus_fields' in schema.read.collections) {
|
|
2288
|
-
Field.addFields({
|
|
2289
|
-
collection: GraphQLString,
|
|
2290
|
-
field: GraphQLString,
|
|
2291
|
-
type: GraphQLString,
|
|
2292
|
-
meta: schemaComposer.createObjectTC({
|
|
2293
|
-
name: 'directus_fields_meta',
|
|
2294
|
-
fields: Object.values(schema.read.collections['directus_fields'].fields).reduce((acc, field) => {
|
|
2295
|
-
acc[field.field] = {
|
|
2296
|
-
type: field.nullable
|
|
2297
|
-
? getGraphQLType(field.type, field.special)
|
|
2298
|
-
: new GraphQLNonNull(getGraphQLType(field.type, field.special)),
|
|
2299
|
-
description: field.note,
|
|
2300
|
-
};
|
|
2301
|
-
return acc;
|
|
2302
|
-
}, {}),
|
|
2303
|
-
}),
|
|
2304
|
-
schema: schemaComposer.createObjectTC({
|
|
2305
|
-
name: 'directus_fields_schema',
|
|
2306
|
-
fields: {
|
|
2307
|
-
name: GraphQLString,
|
|
2308
|
-
table: GraphQLString,
|
|
2309
|
-
data_type: GraphQLString,
|
|
2310
|
-
default_value: GraphQLString,
|
|
2311
|
-
max_length: GraphQLInt,
|
|
2312
|
-
numeric_precision: GraphQLInt,
|
|
2313
|
-
numeric_scale: GraphQLInt,
|
|
2314
|
-
is_generated: GraphQLBoolean,
|
|
2315
|
-
generation_expression: GraphQLString,
|
|
2316
|
-
is_indexed: GraphQLBoolean,
|
|
2317
|
-
is_nullable: GraphQLBoolean,
|
|
2318
|
-
is_unique: GraphQLBoolean,
|
|
2319
|
-
is_primary_key: GraphQLBoolean,
|
|
2320
|
-
has_auto_increment: GraphQLBoolean,
|
|
2321
|
-
foreign_key_column: GraphQLString,
|
|
2322
|
-
foreign_key_table: GraphQLString,
|
|
2323
|
-
comment: GraphQLString,
|
|
2324
|
-
},
|
|
2325
|
-
}),
|
|
2326
|
-
});
|
|
2327
|
-
schemaComposer.Query.addFields({
|
|
2328
|
-
fields: {
|
|
2329
|
-
type: new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(Field.getType()))),
|
|
2330
|
-
resolve: async () => {
|
|
2331
|
-
const service = new FieldsService({
|
|
2332
|
-
accountability: this.accountability,
|
|
2333
|
-
schema: this.schema,
|
|
2334
|
-
});
|
|
2335
|
-
return await service.readAll();
|
|
2336
|
-
},
|
|
2337
|
-
},
|
|
2338
|
-
fields_in_collection: {
|
|
2339
|
-
type: new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(Field.getType()))),
|
|
2340
|
-
args: {
|
|
2341
|
-
collection: new GraphQLNonNull(GraphQLString),
|
|
2342
|
-
},
|
|
2343
|
-
resolve: async (_, args) => {
|
|
2344
|
-
const service = new FieldsService({
|
|
2345
|
-
accountability: this.accountability,
|
|
2346
|
-
schema: this.schema,
|
|
2347
|
-
});
|
|
2348
|
-
return await service.readAll(args['collection']);
|
|
2349
|
-
},
|
|
2350
|
-
},
|
|
2351
|
-
fields_by_name: {
|
|
2352
|
-
type: Field,
|
|
2353
|
-
args: {
|
|
2354
|
-
collection: new GraphQLNonNull(GraphQLString),
|
|
2355
|
-
field: new GraphQLNonNull(GraphQLString),
|
|
2356
|
-
},
|
|
2357
|
-
resolve: async (_, args) => {
|
|
2358
|
-
const service = new FieldsService({
|
|
2359
|
-
accountability: this.accountability,
|
|
2360
|
-
schema: this.schema,
|
|
2361
|
-
});
|
|
2362
|
-
return await service.readOne(args['collection'], args['field']);
|
|
2363
|
-
},
|
|
2364
|
-
},
|
|
2365
|
-
});
|
|
2366
|
-
}
|
|
2367
|
-
if ('directus_relations' in schema.read.collections) {
|
|
2368
|
-
Relation.addFields({
|
|
2369
|
-
collection: GraphQLString,
|
|
2370
|
-
field: GraphQLString,
|
|
2371
|
-
related_collection: GraphQLString,
|
|
2372
|
-
schema: schemaComposer.createObjectTC({
|
|
2373
|
-
name: 'directus_relations_schema',
|
|
2374
|
-
fields: {
|
|
2375
|
-
table: new GraphQLNonNull(GraphQLString),
|
|
2376
|
-
column: new GraphQLNonNull(GraphQLString),
|
|
2377
|
-
foreign_key_table: new GraphQLNonNull(GraphQLString),
|
|
2378
|
-
foreign_key_column: new GraphQLNonNull(GraphQLString),
|
|
2379
|
-
constraint_name: GraphQLString,
|
|
2380
|
-
on_update: new GraphQLNonNull(GraphQLString),
|
|
2381
|
-
on_delete: new GraphQLNonNull(GraphQLString),
|
|
2382
|
-
},
|
|
2383
|
-
}),
|
|
2384
|
-
meta: schemaComposer.createObjectTC({
|
|
2385
|
-
name: 'directus_relations_meta',
|
|
2386
|
-
fields: Object.values(schema.read.collections['directus_relations'].fields).reduce((acc, field) => {
|
|
2387
|
-
acc[field.field] = {
|
|
2388
|
-
type: getGraphQLType(field.type, field.special),
|
|
2389
|
-
description: field.note,
|
|
2390
|
-
};
|
|
2391
|
-
return acc;
|
|
2392
|
-
}, {}),
|
|
2393
|
-
}),
|
|
2394
|
-
});
|
|
2395
|
-
schemaComposer.Query.addFields({
|
|
2396
|
-
relations: {
|
|
2397
|
-
type: new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(Relation.getType()))),
|
|
2398
|
-
resolve: async () => {
|
|
2399
|
-
const service = new RelationsService({
|
|
2400
|
-
accountability: this.accountability,
|
|
2401
|
-
schema: this.schema,
|
|
2402
|
-
});
|
|
2403
|
-
return await service.readAll();
|
|
2404
|
-
},
|
|
2405
|
-
},
|
|
2406
|
-
relations_in_collection: {
|
|
2407
|
-
type: new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(Relation.getType()))),
|
|
2408
|
-
args: {
|
|
2409
|
-
collection: new GraphQLNonNull(GraphQLString),
|
|
2410
|
-
},
|
|
2411
|
-
resolve: async (_, args) => {
|
|
2412
|
-
const service = new RelationsService({
|
|
2413
|
-
accountability: this.accountability,
|
|
2414
|
-
schema: this.schema,
|
|
2415
|
-
});
|
|
2416
|
-
return await service.readAll(args['collection']);
|
|
2417
|
-
},
|
|
2418
|
-
},
|
|
2419
|
-
relations_by_name: {
|
|
2420
|
-
type: Relation,
|
|
2421
|
-
args: {
|
|
2422
|
-
collection: new GraphQLNonNull(GraphQLString),
|
|
2423
|
-
field: new GraphQLNonNull(GraphQLString),
|
|
2424
|
-
},
|
|
2425
|
-
resolve: async (_, args) => {
|
|
2426
|
-
const service = new RelationsService({
|
|
2427
|
-
accountability: this.accountability,
|
|
2428
|
-
schema: this.schema,
|
|
2429
|
-
});
|
|
2430
|
-
return await service.readOne(args['collection'], args['field']);
|
|
2431
|
-
},
|
|
2432
|
-
},
|
|
2433
|
-
});
|
|
2434
|
-
}
|
|
2435
|
-
if (this.accountability?.admin === true) {
|
|
2436
|
-
schemaComposer.Mutation.addFields({
|
|
2437
|
-
create_collections_item: {
|
|
2438
|
-
type: Collection,
|
|
2439
|
-
args: {
|
|
2440
|
-
data: toInputObjectType(Collection.clone('create_directus_collections'), {
|
|
2441
|
-
postfix: '_input',
|
|
2442
|
-
}).addFields({
|
|
2443
|
-
fields: [
|
|
2444
|
-
toInputObjectType(Field.clone('create_directus_collections_fields'), { postfix: '_input' }).NonNull,
|
|
2445
|
-
],
|
|
2446
|
-
}).NonNull,
|
|
2447
|
-
},
|
|
2448
|
-
resolve: async (_, args) => {
|
|
2449
|
-
const collectionsService = new CollectionsService({
|
|
2450
|
-
accountability: this.accountability,
|
|
2451
|
-
schema: this.schema,
|
|
2452
|
-
});
|
|
2453
|
-
const collectionKey = await collectionsService.createOne(args['data']);
|
|
2454
|
-
return await collectionsService.readOne(collectionKey);
|
|
2455
|
-
},
|
|
2456
|
-
},
|
|
2457
|
-
update_collections_item: {
|
|
2458
|
-
type: Collection,
|
|
2459
|
-
args: {
|
|
2460
|
-
collection: new GraphQLNonNull(GraphQLString),
|
|
2461
|
-
data: toInputObjectType(Collection.clone('update_directus_collections'), {
|
|
2462
|
-
postfix: '_input',
|
|
2463
|
-
}).removeField(['collection', 'schema']).NonNull,
|
|
2464
|
-
},
|
|
2465
|
-
resolve: async (_, args) => {
|
|
2466
|
-
const collectionsService = new CollectionsService({
|
|
2467
|
-
accountability: this.accountability,
|
|
2468
|
-
schema: this.schema,
|
|
2469
|
-
});
|
|
2470
|
-
const collectionKey = await collectionsService.updateOne(args['collection'], args['data']);
|
|
2471
|
-
return await collectionsService.readOne(collectionKey);
|
|
2472
|
-
},
|
|
2473
|
-
},
|
|
2474
|
-
delete_collections_item: {
|
|
2475
|
-
type: schemaComposer.createObjectTC({
|
|
2476
|
-
name: 'delete_collection',
|
|
2477
|
-
fields: {
|
|
2478
|
-
collection: GraphQLString,
|
|
2479
|
-
},
|
|
2480
|
-
}),
|
|
2481
|
-
args: {
|
|
2482
|
-
collection: new GraphQLNonNull(GraphQLString),
|
|
2483
|
-
},
|
|
2484
|
-
resolve: async (_, args) => {
|
|
2485
|
-
const collectionsService = new CollectionsService({
|
|
2486
|
-
accountability: this.accountability,
|
|
2487
|
-
schema: this.schema,
|
|
2488
|
-
});
|
|
2489
|
-
await collectionsService.deleteOne(args['collection']);
|
|
2490
|
-
return { collection: args['collection'] };
|
|
2491
|
-
},
|
|
2492
|
-
},
|
|
2493
|
-
});
|
|
2494
|
-
schemaComposer.Mutation.addFields({
|
|
2495
|
-
create_fields_item: {
|
|
2496
|
-
type: Field,
|
|
2497
|
-
args: {
|
|
2498
|
-
collection: new GraphQLNonNull(GraphQLString),
|
|
2499
|
-
data: toInputObjectType(Field.clone('create_directus_fields'), { postfix: '_input' }).NonNull,
|
|
2500
|
-
},
|
|
2501
|
-
resolve: async (_, args) => {
|
|
2502
|
-
const service = new FieldsService({
|
|
2503
|
-
accountability: this.accountability,
|
|
2504
|
-
schema: this.schema,
|
|
2505
|
-
});
|
|
2506
|
-
await service.createField(args['collection'], args['data']);
|
|
2507
|
-
return await service.readOne(args['collection'], args['data'].field);
|
|
2508
|
-
},
|
|
2509
|
-
},
|
|
2510
|
-
update_fields_item: {
|
|
2511
|
-
type: Field,
|
|
2512
|
-
args: {
|
|
2513
|
-
collection: new GraphQLNonNull(GraphQLString),
|
|
2514
|
-
field: new GraphQLNonNull(GraphQLString),
|
|
2515
|
-
data: toInputObjectType(Field.clone('update_directus_fields'), { postfix: '_input' }).NonNull,
|
|
2516
|
-
},
|
|
2517
|
-
resolve: async (_, args) => {
|
|
2518
|
-
const service = new FieldsService({
|
|
2519
|
-
accountability: this.accountability,
|
|
2520
|
-
schema: this.schema,
|
|
2521
|
-
});
|
|
2522
|
-
await service.updateField(args['collection'], {
|
|
2523
|
-
...args['data'],
|
|
2524
|
-
field: args['field'],
|
|
2525
|
-
});
|
|
2526
|
-
return await service.readOne(args['collection'], args['data'].field);
|
|
2527
|
-
},
|
|
2528
|
-
},
|
|
2529
|
-
delete_fields_item: {
|
|
2530
|
-
type: schemaComposer.createObjectTC({
|
|
2531
|
-
name: 'delete_field',
|
|
2532
|
-
fields: {
|
|
2533
|
-
collection: GraphQLString,
|
|
2534
|
-
field: GraphQLString,
|
|
2535
|
-
},
|
|
2536
|
-
}),
|
|
2537
|
-
args: {
|
|
2538
|
-
collection: new GraphQLNonNull(GraphQLString),
|
|
2539
|
-
field: new GraphQLNonNull(GraphQLString),
|
|
2540
|
-
},
|
|
2541
|
-
resolve: async (_, args) => {
|
|
2542
|
-
const service = new FieldsService({
|
|
2543
|
-
accountability: this.accountability,
|
|
2544
|
-
schema: this.schema,
|
|
2545
|
-
});
|
|
2546
|
-
await service.deleteField(args['collection'], args['field']);
|
|
2547
|
-
const { collection, field } = args;
|
|
2548
|
-
return { collection, field };
|
|
2549
|
-
},
|
|
2550
|
-
},
|
|
2551
|
-
});
|
|
2552
|
-
schemaComposer.Mutation.addFields({
|
|
2553
|
-
create_relations_item: {
|
|
2554
|
-
type: Relation,
|
|
2555
|
-
args: {
|
|
2556
|
-
data: toInputObjectType(Relation.clone('create_directus_relations'), { postfix: '_input' }).NonNull,
|
|
2557
|
-
},
|
|
2558
|
-
resolve: async (_, args) => {
|
|
2559
|
-
const relationsService = new RelationsService({
|
|
2560
|
-
accountability: this.accountability,
|
|
2561
|
-
schema: this.schema,
|
|
2562
|
-
});
|
|
2563
|
-
await relationsService.createOne(args['data']);
|
|
2564
|
-
return await relationsService.readOne(args['data'].collection, args['data'].field);
|
|
2565
|
-
},
|
|
2566
|
-
},
|
|
2567
|
-
update_relations_item: {
|
|
2568
|
-
type: Relation,
|
|
2569
|
-
args: {
|
|
2570
|
-
collection: new GraphQLNonNull(GraphQLString),
|
|
2571
|
-
field: new GraphQLNonNull(GraphQLString),
|
|
2572
|
-
data: toInputObjectType(Relation.clone('update_directus_relations'), { postfix: '_input' }).NonNull,
|
|
2573
|
-
},
|
|
2574
|
-
resolve: async (_, args) => {
|
|
2575
|
-
const relationsService = new RelationsService({
|
|
2576
|
-
accountability: this.accountability,
|
|
2577
|
-
schema: this.schema,
|
|
2578
|
-
});
|
|
2579
|
-
await relationsService.updateOne(args['collection'], args['field'], args['data']);
|
|
2580
|
-
return await relationsService.readOne(args['data'].collection, args['data'].field);
|
|
2581
|
-
},
|
|
2582
|
-
},
|
|
2583
|
-
delete_relations_item: {
|
|
2584
|
-
type: schemaComposer.createObjectTC({
|
|
2585
|
-
name: 'delete_relation',
|
|
2586
|
-
fields: {
|
|
2587
|
-
collection: GraphQLString,
|
|
2588
|
-
field: GraphQLString,
|
|
2589
|
-
},
|
|
2590
|
-
}),
|
|
2591
|
-
args: {
|
|
2592
|
-
collection: new GraphQLNonNull(GraphQLString),
|
|
2593
|
-
field: new GraphQLNonNull(GraphQLString),
|
|
2594
|
-
},
|
|
2595
|
-
resolve: async (_, args) => {
|
|
2596
|
-
const relationsService = new RelationsService({
|
|
2597
|
-
accountability: this.accountability,
|
|
2598
|
-
schema: this.schema,
|
|
2599
|
-
});
|
|
2600
|
-
await relationsService.deleteOne(args['collection'], args['field']);
|
|
2601
|
-
return { collection: args['collection'], field: args['field'] };
|
|
2602
|
-
},
|
|
2603
|
-
},
|
|
2604
|
-
});
|
|
2605
|
-
Extension.addFields({
|
|
2606
|
-
bundle: GraphQLString,
|
|
2607
|
-
name: new GraphQLNonNull(GraphQLString),
|
|
2608
|
-
schema: schemaComposer.createObjectTC({
|
|
2609
|
-
name: 'directus_extensions_schema',
|
|
2610
|
-
fields: {
|
|
2611
|
-
type: GraphQLString,
|
|
2612
|
-
local: GraphQLBoolean,
|
|
2613
|
-
},
|
|
2614
|
-
}),
|
|
2615
|
-
meta: schemaComposer.createObjectTC({
|
|
2616
|
-
name: 'directus_extensions_meta',
|
|
2617
|
-
fields: {
|
|
2618
|
-
enabled: GraphQLBoolean,
|
|
2619
|
-
},
|
|
2620
|
-
}),
|
|
2621
|
-
});
|
|
2622
|
-
schemaComposer.Query.addFields({
|
|
2623
|
-
extensions: {
|
|
2624
|
-
type: new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(Extension.getType()))),
|
|
2625
|
-
resolve: async () => {
|
|
2626
|
-
const service = new ExtensionsService({
|
|
2627
|
-
accountability: this.accountability,
|
|
2628
|
-
schema: this.schema,
|
|
2629
|
-
});
|
|
2630
|
-
return await service.readAll();
|
|
2631
|
-
},
|
|
2632
|
-
},
|
|
2633
|
-
});
|
|
2634
|
-
schemaComposer.Mutation.addFields({
|
|
2635
|
-
update_extensions_item: {
|
|
2636
|
-
type: Extension,
|
|
2637
|
-
args: {
|
|
2638
|
-
id: GraphQLID,
|
|
2639
|
-
data: toInputObjectType(schemaComposer.createObjectTC({
|
|
2640
|
-
name: 'update_directus_extensions_input',
|
|
2641
|
-
fields: {
|
|
2642
|
-
meta: schemaComposer.createObjectTC({
|
|
2643
|
-
name: 'update_directus_extensions_input_meta',
|
|
2644
|
-
fields: {
|
|
2645
|
-
enabled: GraphQLBoolean,
|
|
2646
|
-
},
|
|
2647
|
-
}),
|
|
2648
|
-
},
|
|
2649
|
-
})),
|
|
2650
|
-
},
|
|
2651
|
-
resolve: async (_, args) => {
|
|
2652
|
-
const extensionsService = new ExtensionsService({
|
|
2653
|
-
accountability: this.accountability,
|
|
2654
|
-
schema: this.schema,
|
|
2655
|
-
});
|
|
2656
|
-
await extensionsService.updateOne(args['id'], args['data']);
|
|
2657
|
-
return await extensionsService.readOne(args['id']);
|
|
2658
|
-
},
|
|
2659
|
-
},
|
|
2660
|
-
});
|
|
2661
|
-
}
|
|
2662
|
-
if ('directus_users' in schema.read.collections) {
|
|
2663
|
-
schemaComposer.Query.addFields({
|
|
2664
|
-
users_me: {
|
|
2665
|
-
type: ReadCollectionTypes['directus_users'],
|
|
2666
|
-
resolve: async (_, args, __, info) => {
|
|
2667
|
-
if (!this.accountability?.user)
|
|
2668
|
-
return null;
|
|
2669
|
-
const service = new UsersService({ schema: this.schema, accountability: this.accountability });
|
|
2670
|
-
const selections = this.replaceFragmentsInSelections(info.fieldNodes[0]?.selectionSet?.selections, info.fragments);
|
|
2671
|
-
const query = this.getQuery(args, selections || [], info.variableValues);
|
|
2672
|
-
return await service.readOne(this.accountability.user, query);
|
|
2673
|
-
},
|
|
2674
|
-
},
|
|
2675
|
-
});
|
|
2676
|
-
}
|
|
2677
|
-
if ('directus_permissions' in schema.read.collections) {
|
|
2678
|
-
schemaComposer.Query.addFields({
|
|
2679
|
-
permissions_me: {
|
|
2680
|
-
type: schemaComposer.createScalarTC({
|
|
2681
|
-
name: 'permissions_me_type',
|
|
2682
|
-
parseValue: (value) => value,
|
|
2683
|
-
serialize: (value) => value,
|
|
2684
|
-
}),
|
|
2685
|
-
resolve: async (_, _args, __, _info) => {
|
|
2686
|
-
if (!this.accountability?.user && !this.accountability?.role)
|
|
2687
|
-
return null;
|
|
2688
|
-
const result = await fetchAccountabilityCollectionAccess(this.accountability, {
|
|
2689
|
-
schema: this.schema,
|
|
2690
|
-
knex: getDatabase(),
|
|
2691
|
-
});
|
|
2692
|
-
return result;
|
|
2693
|
-
},
|
|
2694
|
-
},
|
|
2695
|
-
});
|
|
2696
|
-
}
|
|
2697
|
-
if ('directus_roles' in schema.read.collections) {
|
|
2698
|
-
schemaComposer.Query.addFields({
|
|
2699
|
-
roles_me: {
|
|
2700
|
-
type: ReadCollectionTypes['directus_roles'].List,
|
|
2701
|
-
resolve: async (_, args, __, info) => {
|
|
2702
|
-
if (!this.accountability?.user && !this.accountability?.role)
|
|
2703
|
-
return null;
|
|
2704
|
-
const service = new RolesService({
|
|
2705
|
-
accountability: this.accountability,
|
|
2706
|
-
schema: this.schema,
|
|
2707
|
-
});
|
|
2708
|
-
const selections = this.replaceFragmentsInSelections(info.fieldNodes[0]?.selectionSet?.selections, info.fragments);
|
|
2709
|
-
const query = this.getQuery(args, selections || [], info.variableValues);
|
|
2710
|
-
query.limit = -1;
|
|
2711
|
-
const roles = await service.readMany(this.accountability.roles, query);
|
|
2712
|
-
return roles;
|
|
2713
|
-
},
|
|
2714
|
-
},
|
|
2715
|
-
});
|
|
2716
|
-
}
|
|
2717
|
-
if ('directus_policies' in schema.read.collections) {
|
|
2718
|
-
schemaComposer.Query.addFields({
|
|
2719
|
-
policies_me_globals: {
|
|
2720
|
-
type: schemaComposer.createObjectTC({
|
|
2721
|
-
name: 'policy_me_globals_type',
|
|
2722
|
-
fields: {
|
|
2723
|
-
enforce_tfa: 'Boolean',
|
|
2724
|
-
app_access: 'Boolean',
|
|
2725
|
-
admin_access: 'Boolean',
|
|
2726
|
-
},
|
|
2727
|
-
}),
|
|
2728
|
-
resolve: async (_, _args, __, _info) => {
|
|
2729
|
-
if (!this.accountability?.user && !this.accountability?.role)
|
|
2730
|
-
return null;
|
|
2731
|
-
const result = await fetchAccountabilityPolicyGlobals(this.accountability, {
|
|
2732
|
-
schema: this.schema,
|
|
2733
|
-
knex: getDatabase(),
|
|
2734
|
-
});
|
|
2735
|
-
return result;
|
|
2736
|
-
},
|
|
2737
|
-
},
|
|
2738
|
-
});
|
|
2739
|
-
}
|
|
2740
|
-
if ('directus_users' in schema.update.collections && this.accountability?.user) {
|
|
2741
|
-
schemaComposer.Mutation.addFields({
|
|
2742
|
-
update_users_me: {
|
|
2743
|
-
type: ReadCollectionTypes['directus_users'],
|
|
2744
|
-
args: {
|
|
2745
|
-
data: toInputObjectType(UpdateCollectionTypes['directus_users']),
|
|
2746
|
-
},
|
|
2747
|
-
resolve: async (_, args, __, info) => {
|
|
2748
|
-
if (!this.accountability?.user)
|
|
2749
|
-
return null;
|
|
2750
|
-
const service = new UsersService({
|
|
2751
|
-
schema: this.schema,
|
|
2752
|
-
accountability: this.accountability,
|
|
2753
|
-
});
|
|
2754
|
-
await service.updateOne(this.accountability.user, args['data']);
|
|
2755
|
-
if ('directus_users' in ReadCollectionTypes) {
|
|
2756
|
-
const selections = this.replaceFragmentsInSelections(info.fieldNodes[0]?.selectionSet?.selections, info.fragments);
|
|
2757
|
-
const query = this.getQuery(args, selections || [], info.variableValues);
|
|
2758
|
-
return await service.readOne(this.accountability.user, query);
|
|
2759
|
-
}
|
|
2760
|
-
return true;
|
|
2761
|
-
},
|
|
2762
|
-
},
|
|
2763
|
-
});
|
|
2764
|
-
}
|
|
2765
|
-
if ('directus_files' in schema.create.collections) {
|
|
2766
|
-
schemaComposer.Mutation.addFields({
|
|
2767
|
-
import_file: {
|
|
2768
|
-
type: ReadCollectionTypes['directus_files'] ?? GraphQLBoolean,
|
|
2769
|
-
args: {
|
|
2770
|
-
url: new GraphQLNonNull(GraphQLString),
|
|
2771
|
-
data: toInputObjectType(CreateCollectionTypes['directus_files']).setTypeName('create_directus_files_input'),
|
|
2772
|
-
},
|
|
2773
|
-
resolve: async (_, args, __, info) => {
|
|
2774
|
-
const service = new FilesService({
|
|
2775
|
-
accountability: this.accountability,
|
|
2776
|
-
schema: this.schema,
|
|
2777
|
-
});
|
|
2778
|
-
const primaryKey = await service.importOne(args['url'], args['data']);
|
|
2779
|
-
if ('directus_files' in ReadCollectionTypes) {
|
|
2780
|
-
const selections = this.replaceFragmentsInSelections(info.fieldNodes[0]?.selectionSet?.selections, info.fragments);
|
|
2781
|
-
const query = this.getQuery(args, selections || [], info.variableValues);
|
|
2782
|
-
return await service.readOne(primaryKey, query);
|
|
2783
|
-
}
|
|
2784
|
-
return true;
|
|
2785
|
-
},
|
|
2786
|
-
},
|
|
2787
|
-
});
|
|
2788
|
-
}
|
|
2789
|
-
if ('directus_users' in schema.create.collections) {
|
|
2790
|
-
schemaComposer.Mutation.addFields({
|
|
2791
|
-
users_invite: {
|
|
2792
|
-
type: GraphQLBoolean,
|
|
2793
|
-
args: {
|
|
2794
|
-
email: new GraphQLNonNull(GraphQLString),
|
|
2795
|
-
role: new GraphQLNonNull(GraphQLString),
|
|
2796
|
-
invite_url: GraphQLString,
|
|
2797
|
-
},
|
|
2798
|
-
resolve: async (_, args) => {
|
|
2799
|
-
const service = new UsersService({
|
|
2800
|
-
accountability: this.accountability,
|
|
2801
|
-
schema: this.schema,
|
|
2802
|
-
});
|
|
2803
|
-
await service.inviteUser(args['email'], args['role'], args['invite_url'] || null);
|
|
2804
|
-
return true;
|
|
2805
|
-
},
|
|
2806
|
-
},
|
|
2807
|
-
});
|
|
93
|
+
throw formatError(err);
|
|
2808
94
|
}
|
|
2809
|
-
return schemaComposer;
|
|
2810
95
|
}
|
|
2811
96
|
}
|