@directus/api
Version:
Directus is a real-time API and App dashboard for managing SQL database content
388 lines (386 loc) • 14.8 kB
JavaScript
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 };