UNPKG

@neo4j/graphql

Version:

A GraphQL to Cypher query execution layer for Neo4j and JavaScript GraphQL implementations

206 lines 9.15 kB
"use strict"; /* * Copyright (c) "Neo4j" * Neo4j Sweden AB [http://neo4j.com] * * This file is part of Neo4j. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.ConnectOperation = void 0; const cypher_builder_1 = __importDefault(require("@neo4j/cypher-builder")); const utils_1 = require("../../../../utils/utils"); const check_authentication_1 = require("../../../authorization/check-authentication"); const create_node_from_entity_1 = require("../../utils/create-node-from-entity"); const is_concrete_entity_1 = require("../../utils/is-concrete-entity"); const wrap_subquery_in_calls_1 = require("../../utils/wrap-subquery-in-calls"); const ParamInputField_1 = require("../input-fields/ParamInputField"); const operations_1 = require("./operations"); class ConnectOperation extends operations_1.MutationOperation { constructor({ target, relationship, selectionPattern, }) { super(); this.authFilters = []; this.sourceAuthFilters = []; this.inputFields = new Map(); this.filters = []; this.target = target; this.relationship = relationship; this.selectionPattern = selectionPattern; } getChildren() { return (0, utils_1.filterTruthy)([ this.selectionPattern, ...this.filters, ...this.authFilters, ...this.inputFields.values(), ]); } print() { return `${super.print()} <${this.target.name}>`; } addAuthFilters(...filter) { this.authFilters.push(...filter); } addSourceAuthFilters(...filter) { this.sourceAuthFilters.push(...filter); } /** * Get and set field methods are utilities to remove duplicate fields between separate inputs * TODO: This logic should be handled in the factory. */ getField(key, attachedTo) { return this.inputFields.get(`${attachedTo}_${key}`); } addField(field, attachedTo) { if (!this.inputFields.has(field.name)) { this.inputFields.set(`${attachedTo}_${field.name}`, field); } } addFilters(...filters) { this.filters.push(...filters); } getAuthorizationSubqueries(_context) { const nestedContext = this.nestedContext; if (!nestedContext) { throw new Error("Error parsing query, nested context not available, need to call transpile first. Please contact support"); } return [...this.inputFields.values()].flatMap((inputField) => { return inputField.getAuthorizationSubqueries(nestedContext); }); } transpile(context) { if (!context.hasTarget()) { throw new Error("No parent node found!"); } const { nestedContext } = this.selectionPattern.apply(context); this.nestedContext = nestedContext; (0, check_authentication_1.checkEntityAuthentication)({ context: nestedContext.neo4jGraphQLContext, entity: this.target.entity, targetOperations: ["CREATE_RELATIONSHIP"], }); if ((0, is_concrete_entity_1.isConcreteEntity)(this.relationship.source)) { (0, check_authentication_1.checkEntityAuthentication)({ context: nestedContext.neo4jGraphQLContext, entity: this.relationship.source.entity, targetOperations: ["CREATE_RELATIONSHIP"], }); } this.inputFields.forEach((field) => { if (field.attachedTo === "node" && field instanceof ParamInputField_1.ParamInputField) { (0, check_authentication_1.checkEntityAuthentication)({ context: nestedContext.neo4jGraphQLContext, entity: this.target.entity, targetOperations: ["CREATE_RELATIONSHIP"], field: field.name, }); } }); const matchPattern = new cypher_builder_1.default.Pattern(nestedContext.target, { labels: (0, create_node_from_entity_1.getEntityLabels)(this.target, context.neo4jGraphQLContext), }); const allFilters = [...this.authFilters, ...this.filters]; const filterSubqueries = (0, wrap_subquery_in_calls_1.wrapSubqueriesInCypherCalls)(nestedContext, allFilters, [nestedContext.target]); const predicate = cypher_builder_1.default.and(...allFilters.map((f) => f.getPredicate(nestedContext))); let matchClause; if (filterSubqueries.length > 0) { matchClause = cypher_builder_1.default.utils.concat(new cypher_builder_1.default.Match(matchPattern), ...filterSubqueries, new cypher_builder_1.default.With("*").where(predicate)); } else { matchClause = new cypher_builder_1.default.Match(matchPattern).where(predicate); } const relVar = new cypher_builder_1.default.Relationship(); const relDirection = this.relationship.cypherDirectionFromRelDirection(); const connectPattern = new cypher_builder_1.default.Pattern(context.target) .related(relVar, { direction: relDirection, type: this.relationship.type }) .to(nestedContext.target); const connectContext = context.push({ target: nestedContext.target, relationship: relVar }); const connectClause = new cypher_builder_1.default.Create(connectPattern); const setParams = Array.from(this.inputFields.values()).flatMap((input) => { return input.getSetParams(connectContext); }); connectClause.set(...setParams); const mutationSubqueries = Array.from(this.inputFields.values()).flatMap((input) => { return input.getSubqueries(connectContext); }); const clauses = cypher_builder_1.default.utils.concat(matchClause, ...this.getAuthorizationClauses(nestedContext), // THESE ARE "BEFORE" AUTH ...mutationSubqueries, connectClause, ...this.getAuthorizationClausesAfter(nestedContext), // THESE ARE "AFTER" AUTH ...this.getSourceAuthorizationClausesAfter(context) // ONLY RUN "AFTER" AUTH ON THE SOURCE NODE ); const callClause = new cypher_builder_1.default.Call(clauses, [context.target]); return { projectionExpr: context.returnVariable, clauses: [callClause], }; } getAuthorizationClauses(context) { const { subqueries, validations } = this.transpileAuthClauses(context); if (!validations.length) { return []; } return [...subqueries, ...validations]; } getAuthorizationClausesAfter(context) { const validationsAfter = []; for (const authFilter of this.authFilters) { const validationAfter = authFilter.getValidation(context, "AFTER"); if (validationAfter) { validationsAfter.push(validationAfter); } } if (validationsAfter.length > 0) { return [new cypher_builder_1.default.With("*"), ...validationsAfter]; } return []; } getSourceAuthorizationClausesAfter(context) { const validationsAfter = []; for (const authFilter of this.sourceAuthFilters) { const validationAfter = authFilter.getValidation(context, "AFTER"); if (validationAfter) { validationsAfter.push(validationAfter); } } if (validationsAfter.length > 0) { return [new cypher_builder_1.default.With("*"), ...validationsAfter]; } return []; } transpileAuthClauses(context) { const selections = []; const subqueries = []; const predicates = []; const validations = []; for (const authFilter of this.authFilters) { const extraSelections = authFilter.getSelection(context); const authSubqueries = authFilter.getSubqueries(context); const validation = authFilter.getValidation(context, "BEFORE"); if (extraSelections) { selections.push(...extraSelections); } if (authSubqueries) { subqueries.push(...authSubqueries); } if (validation) { validations.push(validation); } } return { selections, subqueries, predicates, validations }; } } exports.ConnectOperation = ConnectOperation; //# sourceMappingURL=ConnectOperation.js.map