UNPKG

@directus/api

Version:

Directus is a real-time API and App dashboard for managing SQL database content

388 lines (386 loc) 14.8 kB
import database_default from "../../../database/index.js"; import { FilesService } from "../../files.js"; import { RelationsService } from "../../relations.js"; import { UsersService } from "../../users.js"; import { FieldsService } from "../../fields.js"; import { fetchAccountabilityCollectionAccess } from "../../../permissions/modules/fetch-accountability-collection-access/fetch-accountability-collection-access.js"; import { fetchAccountabilityPolicyGlobals } from "../../../permissions/modules/fetch-accountability-policy-globals/fetch-accountability-policy-globals.js"; import { RolesService } from "../../roles.js"; import { getQuery } from "../schema/parse-query.js"; import { ServerService } from "../../server.js"; import { SpecificationService } from "../../specifications.js"; import { dedupeResolver } from "../utils/dedupe-resolvers.js"; import { replaceFragmentsInSelections } from "../utils/replace-fragments.js"; import { getCollectionType } from "./get-collection-type.js"; import { getFieldType } from "./get-field-type.js"; import { getRelationType as getRelationType$1 } from "./get-relation-type.js"; import { resolveSystemAdmin } from "./system-admin.js"; import { globalResolvers } from "./system-global.js"; import { generateSchema } from "../schema/index.js"; import { GraphQLService } from "../index.js"; import { CollectionsService } from "../../collections.js"; import { useEnv } from "@directus/env"; import { toBoolean } from "@directus/utils"; import { GraphQLBoolean, GraphQLEnumType, GraphQLInt, GraphQLList, GraphQLNonNull, GraphQLObjectType, GraphQLString } from "graphql"; import { GraphQLJSON, SchemaComposer, toInputObjectType } from "graphql-compose"; //#region src/services/graphql/resolvers/system.ts const env = useEnv(); function injectSystemResolvers(gql, schemaComposer, { CreateCollectionTypes, ReadCollectionTypes, UpdateCollectionTypes }, schema) { globalResolvers(gql, schemaComposer); const ServerInfo = schemaComposer.createObjectTC({ name: "server_info", fields: { project: { type: new GraphQLObjectType({ name: "server_info_project", fields: { project_name: { type: GraphQLString }, project_descriptor: { type: GraphQLString }, project_logo: { type: GraphQLString }, project_color: { type: GraphQLString }, default_language: { type: GraphQLString }, public_foreground: { type: GraphQLString }, public_background: { type: GraphQLString }, public_note: { type: GraphQLString }, custom_css: { type: GraphQLString }, public_registration: { type: GraphQLBoolean }, public_registration_verify_email: { type: GraphQLBoolean } } }) }, license: { type: new GraphQLObjectType({ name: "server_info_license", fields: { source: { type: GraphQLString }, entitlements: { type: new GraphQLObjectType({ name: "server_info_license_entitlements", fields: { production_enabled: { type: new GraphQLNonNull(GraphQLBoolean) }, ai_translations_enabled: { type: new GraphQLNonNull(GraphQLBoolean) }, display_powered_by: { type: new GraphQLNonNull(GraphQLString) } } }) } } }) } } }); if (gql.accountability?.user) ServerInfo.addFields({ rateLimit: env["RATE_LIMITER_ENABLED"] ? { type: new GraphQLObjectType({ name: "server_info_rate_limit", fields: { points: { type: GraphQLInt }, duration: { type: GraphQLInt } } }) } : GraphQLBoolean, rateLimitGlobal: env["RATE_LIMITER_GLOBAL_ENABLED"] ? { type: new GraphQLObjectType({ name: "server_info_rate_limit_global", fields: { points: { type: GraphQLInt }, duration: { type: GraphQLInt } } }) } : GraphQLBoolean, websocket: toBoolean(env["WEBSOCKETS_ENABLED"]) ? { type: new GraphQLObjectType({ name: "server_info_websocket", fields: { rest: { type: toBoolean(env["WEBSOCKETS_REST_ENABLED"]) ? new GraphQLObjectType({ name: "server_info_websocket_rest", fields: { authentication: { type: new GraphQLEnumType({ name: "server_info_websocket_rest_authentication", values: { public: { value: "public" }, handshake: { value: "handshake" }, strict: { value: "strict" } } }) }, path: { type: GraphQLString } } }) : GraphQLBoolean }, graphql: { type: toBoolean(env["WEBSOCKETS_GRAPHQL_ENABLED"]) ? new GraphQLObjectType({ name: "server_info_websocket_graphql", fields: { authentication: { type: new GraphQLEnumType({ name: "server_info_websocket_graphql_authentication", values: { public: { value: "public" }, handshake: { value: "handshake" }, strict: { value: "strict" } } }) }, path: { type: GraphQLString } } }) : GraphQLBoolean }, heartbeat: { type: toBoolean(env["WEBSOCKETS_HEARTBEAT_ENABLED"]) ? GraphQLInt : GraphQLBoolean } } }) } : GraphQLBoolean, queryLimit: { type: new GraphQLObjectType({ name: "server_info_query_limit", fields: { default: { type: GraphQLInt }, max: { type: GraphQLInt } } }) } }); if (env["OPENAPI_ENABLED"] !== false) schemaComposer.Query.addFields({ server_specs_oas: { type: GraphQLJSON, resolve: dedupeResolver(async () => { return await new SpecificationService({ schema: gql.schema, accountability: gql.accountability }).oas.generate(); }, "server_specs_oas") } }); /** Globally available query */ if (env["GRAPHQL_INTROSPECTION"] !== false) schemaComposer.Query.addFields({ server_specs_graphql: { type: GraphQLString, args: { scope: new GraphQLEnumType({ name: "graphql_sdl_scope", values: { items: { value: "items" }, system: { value: "system" } } }) }, resolve: dedupeResolver(async (_, args) => { return await generateSchema(new GraphQLService({ schema: gql.schema, accountability: gql.accountability, scope: args["scope"] ?? "items" }), "sdl"); }) } }); schemaComposer.Query.addFields({ server_ping: { type: GraphQLString, resolve: () => "pong" }, server_info: { type: ServerInfo, resolve: dedupeResolver(async () => { return await new ServerService({ accountability: gql.accountability, schema: gql.schema }).serverInfo(); }, "server_info") } }); if (toBoolean(env["HEALTHCHECK_ENABLED"]) !== false) schemaComposer.Query.addFields({ server_health: { type: GraphQLJSON, resolve: dedupeResolver(async () => { return await new ServerService({ accountability: gql.accountability, schema: gql.schema }).health(); }, "server_health") } }); if ("directus_collections" in schema.read.collections) { const Collection = getCollectionType(schemaComposer, schema, "read"); schemaComposer.Query.addFields({ collections: { type: new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(Collection.getType()))), resolve: dedupeResolver(async () => { return await new CollectionsService({ accountability: gql.accountability, schema: gql.schema }).readByQuery(); }, "directus_collections") }, collections_by_name: { type: Collection, args: { name: new GraphQLNonNull(GraphQLString) }, resolve: dedupeResolver(async (_, args) => { return await new CollectionsService({ accountability: gql.accountability, schema: gql.schema }).readOne(args["name"]); }) } }); } if ("directus_fields" in schema.read.collections) { const Field = getFieldType(schemaComposer, schema, "read"); schemaComposer.Query.addFields({ fields: { type: new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(Field.getType()))), resolve: dedupeResolver(async () => { return await new FieldsService({ accountability: gql.accountability, schema: gql.schema }).readAll(); }, "directus_fields") }, fields_in_collection: { type: new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(Field.getType()))), args: { collection: new GraphQLNonNull(GraphQLString) }, resolve: dedupeResolver(async (_, args) => { return await new FieldsService({ accountability: gql.accountability, schema: gql.schema }).readAll(args["collection"]); }) }, fields_by_name: { type: Field, args: { collection: new GraphQLNonNull(GraphQLString), field: new GraphQLNonNull(GraphQLString) }, resolve: dedupeResolver(async (_, args) => { return await new FieldsService({ accountability: gql.accountability, schema: gql.schema }).readOne(args["collection"], args["field"]); }) } }); } if ("directus_relations" in schema.read.collections) { const Relation = getRelationType$1(schemaComposer, schema, "read"); schemaComposer.Query.addFields({ relations: { type: new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(Relation.getType()))), resolve: dedupeResolver(async () => { return await new RelationsService({ accountability: gql.accountability, schema: gql.schema }).readAll(); }, "directus_relations") }, relations_in_collection: { type: new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(Relation.getType()))), args: { collection: new GraphQLNonNull(GraphQLString) }, resolve: dedupeResolver(async (_, args) => { return await new RelationsService({ accountability: gql.accountability, schema: gql.schema }).readAll(args["collection"]); }) }, relations_by_name: { type: Relation, args: { collection: new GraphQLNonNull(GraphQLString), field: new GraphQLNonNull(GraphQLString) }, resolve: dedupeResolver(async (_, args) => { return await new RelationsService({ accountability: gql.accountability, schema: gql.schema }).readOne(args["collection"], args["field"]); }) } }); } resolveSystemAdmin(gql, schema, schemaComposer); if ("directus_users" in schema.read.collections) schemaComposer.Query.addFields({ users_me: { type: ReadCollectionTypes["directus_users"], resolve: dedupeResolver(async (_, args, __, info) => { if (!gql.accountability?.user) return null; const service = new UsersService({ schema: gql.schema, accountability: gql.accountability }); const selections = replaceFragmentsInSelections(info.fieldNodes[0]?.selectionSet?.selections, info.fragments); const query = await getQuery(args, gql.schema, selections || [], info.variableValues, gql.accountability, "directus_users"); return await service.readOne(gql.accountability.user, query); }) } }); if ("directus_permissions" in schema.read.collections) schemaComposer.Query.addFields({ permissions_me: { type: schemaComposer.createScalarTC({ name: "permissions_me_type", parseValue: (value) => value, serialize: (value) => value }), resolve: dedupeResolver(async (_, _args, __, _info) => { if (!gql.accountability?.user && !gql.accountability?.role) return null; return await fetchAccountabilityCollectionAccess(gql.accountability, { schema: gql.schema, knex: database_default() }); }) } }); if ("directus_roles" in schema.read.collections) schemaComposer.Query.addFields({ roles_me: { type: ReadCollectionTypes["directus_roles"].List, resolve: dedupeResolver(async (_, args, __, info) => { if (!gql.accountability?.user && !gql.accountability?.role) return null; const service = new RolesService({ accountability: gql.accountability, schema: gql.schema }); const selections = replaceFragmentsInSelections(info.fieldNodes[0]?.selectionSet?.selections, info.fragments); const query = await getQuery(args, gql.schema, selections || [], info.variableValues, gql.accountability, "directus_roles"); query.limit = -1; return await service.readMany(gql.accountability.roles, query); }) } }); if ("directus_policies" in schema.read.collections) schemaComposer.Query.addFields({ policies_me_globals: { type: schemaComposer.createObjectTC({ name: "policy_me_globals_type", fields: { enforce_tfa: "Boolean", app_access: "Boolean", admin_access: "Boolean" } }), resolve: dedupeResolver(async (_, _args, __, _info) => { if (!gql.accountability?.user && !gql.accountability?.role) return null; return await fetchAccountabilityPolicyGlobals(gql.accountability, { schema: gql.schema, knex: database_default() }); }) } }); if ("directus_users" in schema.update.collections && gql.accountability?.user) schemaComposer.Mutation.addFields({ update_users_me: { type: ReadCollectionTypes["directus_users"], args: { data: toInputObjectType(UpdateCollectionTypes["directus_users"]) }, resolve: async (_, args, __, info) => { if (!gql.accountability?.user) return null; const service = new UsersService({ schema: gql.schema, accountability: gql.accountability }); await service.updateOne(gql.accountability.user, args["data"]); if ("directus_users" in ReadCollectionTypes) { const selections = replaceFragmentsInSelections(info.fieldNodes[0]?.selectionSet?.selections, info.fragments); const query = await getQuery(args, gql.schema, selections || [], info.variableValues, gql.accountability, "directus_users"); return await service.readOne(gql.accountability.user, query); } return true; } } }); if ("directus_files" in schema.create.collections) schemaComposer.Mutation.addFields({ import_file: { type: ReadCollectionTypes["directus_files"] ?? GraphQLBoolean, args: { url: new GraphQLNonNull(GraphQLString), data: toInputObjectType(CreateCollectionTypes["directus_files"]).setTypeName("create_directus_files_input") }, resolve: async (_, args, __, info) => { const service = new FilesService({ accountability: gql.accountability, schema: gql.schema }); const primaryKey = await service.importOne(args["url"], args["data"]); if ("directus_files" in ReadCollectionTypes) { const selections = replaceFragmentsInSelections(info.fieldNodes[0]?.selectionSet?.selections, info.fragments); const query = await getQuery(args, gql.schema, selections || [], info.variableValues, gql.accountability, "directus_files"); return await service.readOne(primaryKey, query); } return true; } } }); if ("directus_users" in schema.create.collections) schemaComposer.Mutation.addFields({ users_invite: { type: GraphQLBoolean, args: { email: new GraphQLNonNull(GraphQLString), role: new GraphQLNonNull(GraphQLString), invite_url: GraphQLString }, resolve: async (_, args) => { await new UsersService({ accountability: gql.accountability, schema: gql.schema }).inviteUser(args["email"], args["role"], args["invite_url"] || null); return true; } } }); return schemaComposer; } //#endregion export { injectSystemResolvers };