'use strict'; //////////////////////////////////////////////////////////////////////////////// /// DISCLAIMER /// /// Copyright 2010-2013 triAGENS GmbH, Cologne, Germany /// Copyright 2016 ArangoDB GmbH, Cologne, Germany /// /// 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. /// /// Copyright holder is ArangoDB GmbH, Cologne, Germany /// /// @author Michael Hackstein /// @author Alan Plum //////////////////////////////////////////////////////////////////////////////// const _ = require('lodash'); const joi = require('joi'); const dd = require('dedent'); const statuses = require('statuses'); const httperr = require('http-errors'); const errors = require('@arangodb').errors; const cluster = require('@arangodb/cluster'); const Graph = require('@arangodb/general-graph'); const createRouter = require('@arangodb/foxx/router'); const actions = require('@arangodb/actions'); const NOT_MODIFIED = statuses('not modified'); const ACCEPTED = statuses('accepted'); const CREATED = statuses('created'); const OK = statuses('ok'); const router = createRouter(); module.context.use(router); router.use((req, res, next) => { try { next(); } catch (e) { if (e.isArangoError) { const status = actions.arangoErrorToHttpCode(e.errorNum); res.throw(status, e.errorMessage, {errorNum: e.errorNum, cause: e}); } if (e.statusCode === NOT_MODIFIED) { res.status(NOT_MODIFIED); return; } throw e; } }); function collectionRepresentation(collection, showProperties, showCount, showFigures) { const result = { id: collection._id, name: collection.name(), isSystem: (result.name.charAt(0) === '_'), status: collection.status(), type: collection.type() }; if (showProperties) { const properties = collection.properties(); result.doCompact = properties.doCompact; result.isVolatile = properties.isVolatile; result.journalSize = properties.journalSize; result.keyOptions = properties.keyOptions; result.waitForSync = properties.waitForSync; if (cluster.isCoordinator()) { result.shardKeys = properties.shardKeys; result.numberOfShards = properties.numberOfShards; } } if (showCount) { result.count = collection.count(); } if (showFigures) { const figures = collection.figures(); if (figures) { result.figures = figures; } } return result; } function checkCollection(g, collection) { if (!g[collection]) { throw Object.assign( new httperr.NotFound(errors.ERROR_ARANGO_COLLECTION_NOT_FOUND.message), {errorNum: errors.ERROR_ARANGO_COLLECTION_NOT_FOUND.code} ); } } function setResponse(res, name, body, code) { res.status(code); if (body._rev) { res.set('etag', body._rev); } res.json({ error: false, [name]: body, code }); } function matchVertexRevision(req, rev) { if (req.headers['if-none-match']) { if (rev === req.headers['if-none-match'].replace(/(^["']|["']$)/g, '')) { throw httperr(NOT_MODIFIED); } } if (req.headers['if-match']) { if (rev !== req.headers['if-match'].replace(/(^["']|["']$)/g, '')) { throw Object.assign( new httperr.PreconditionFailed('wrong revision'), {errorNum: errors.ERROR_GRAPH_INVALID_VERTEX.code} ); } } if (req.queryParams.rev) { if (rev !== req.queryParams.rev) { throw Object.assign( new httperr.PreconditionFailed('wrong revision'), {errorNum: errors.ERROR_GRAPH_INVALID_VERTEX.code} ); } } } function graphForClient(g) { return { name: g.__name, edgeDefinitions: g.__edgeDefinitions, orphanCollections: g._orphanCollections(), _id : g.__id, _rev : g.__rev }; } // PHP clients like to convert booleans to numbers const phpCompatFlag = joi.alternatives().try( joi.boolean(), joi.number().integer() ); const graphName = joi.string() .description("Name of the graph."); const vertexCollectionName = joi.string() .description('Name of the vertex collection.'); const edgeCollectionName = joi.string() .description('Name of the edge collection.'); const dropCollectionFlag = phpCompatFlag .description('Flag to drop collection as well.'); const definitionEdgeCollectionName = joi.string() .description('Name of the edge collection in the definition.'); const waitForSyncFlag = phpCompatFlag .description('define if the request should wait until synced to disk.'); const vertexKey = joi.string() .description('_key attribute of one specific vertex'); const edgeKey = joi.string() .description('_key attribute of one specific edge.'); const keepNullFlag = phpCompatFlag .description('define if null values should not be deleted.'); // Graph Creation router.get('/', function(req, res) { setResponse(res, 'graphs', Graph._listObjects(), OK); }) .summary('List graphs') .description('Creates a list of all available graphs.'); router.post('/', function(req, res) { const waitForSync = Boolean(req.queryParams.waitForSync); let g; try { g = Graph._create( req.body.name, req.body.edgeDefinitions, req.body.orphanCollections, {waitForSync} ); } catch (e) { if (e.isArangoError && e.errorNum === errors.ERROR_GRAPH_DUPLICATE.code) { throw Object.assign( new httperr.Conflict(e.errorMessage), {errorNum: e.errorNum, cause: e} ); } if (e.isArangoError && [ errors.ERROR_GRAPH_VERTEX_COL_DOES_NOT_EXIST.code, errors.ERROR_GRAPH_WRONG_COLLECTION_TYPE_VERTEX.code, errors.ERROR_GRAPH_COLLECTION_USED_IN_EDGE_DEF.code, errors.ERROR_GRAPH_COLLECTION_USED_IN_ORPHANS.code ].indexOf(e.errorNum) !== -1) { throw Object.assign( new httperr.BadRequest(e.errorMessage), {errorNum: e.errorNum, cause: e} ); } throw e; } setResponse(res, 'graph', graphForClient(g), waitForSync ? CREATED : ACCEPTED); }) .queryParam('waitForSync', waitForSyncFlag) .body(joi.object({ name: joi.string().required(), edgeDefinitions: joi.any().optional(), orphanCollections: joi.any().optional() }).required(), 'The required information for a graph') .error('bad request', 'Graph creation error.') .error('conflict', 'Graph creation error.') .summary('Creates a new graph') .description('Creates a new graph object'); router.get('/:graph', function(req, res) { const name = req.pathParams.graph; let g; try { g = Graph._graph(name); } catch (e) { if (e.isArangoError && e.errorNum === errors.ERROR_GRAPH_NOT_FOUND.code) { throw Object.assign( new httperr.NotFound(e.errorMessage), {errorNum: e.errorNum, cause: e} ); } throw e; } setResponse(res, 'graph', graphForClient(g), OK); }) .pathParam('graph', graphName) .error('not found', 'Graph could not be found.') .summary('Get information of a graph') .description(dd` Selects information for a given graph. Will return the edge definitions as well as the vertex collections. Or throws a 404 if the graph does not exist. `); router.delete('/:graph', function(req, res) { const dropCollections = Boolean(req.queryParams.dropCollections); const name = req.pathParams.graph; try { Graph._drop(name, dropCollections); } catch (e) { if (e.isArangoError && e.errorNum === errors.ERROR_GRAPH_NOT_FOUND.code) { throw Object.assign( new httperr.NotFound(e.errorMessage), {errorNum: e.errorNum, cause: e} ); } throw e; } setResponse(res, 'removed', true, ACCEPTED); }) .pathParam('graph', graphName) .queryParam('dropCollections', dropCollectionFlag) .error('not found', 'The graph does not exist.') .summary('Drops an existing graph') .description(dd` Drops an existing graph object by name. Optionally all collections not used by other graphs can be dropped as well. `); // Definitions router.get('/:graph/vertex', function(req, res) { const name = req.pathParams.graph; let g; try { g = Graph._graph(name); } catch (e) { if (e.isArangoError && e.errorNum === errors.ERROR_GRAPH_NOT_FOUND.code) { throw Object.assign( new httperr.NotFound(e.errorMessage), {errorNum: e.errorNum, cause: e} ); } throw e; } const mapFunc = ( req.pathParams.collectionObjects ? (c) => collectionRepresentation(c, false, false, false) : (c) => c.name() ); setResponse(res, 'collections', _.map(g._vertexCollections(), mapFunc).sort(), OK); }) .pathParam('graph', graphName) .error('not found', 'The graph could not be found.') .summary('List all vertex collections.') .description('Gets the list of all vertex collections.'); router.post('/:graph/vertex', function(req, res) { const name = req.pathParams.graph; let g; try { g = Graph._graph(name); } catch (e) { if (e.isArangoError && e.errorNum === errors.ERROR_GRAPH_NOT_FOUND.code) { throw Object.assign( new httperr.NotFound(e.errorMessage), {errorNum: e.errorNum, cause: e} ); } throw e; } try { g._addVertexCollection(req.body.collection); } catch (e) { if (e.isArangoError && e.errorNum === errors.ERROR_GRAPH_VERTEX_COL_DOES_NOT_EXIST.code) { throw Object.assign( new httperr.NotFound(e.errorMessage), {errorNum: e.errorNum, cause: e} ); } if (e.isArangoError && [ errors.ERROR_GRAPH_WRONG_COLLECTION_TYPE_VERTEX.code, errors.ERROR_GRAPH_COLLECTION_USED_IN_EDGE_DEF.code, errors.ERROR_GRAPH_COLLECTION_USED_IN_ORPHANS.code ].indexOf(e.errorNum) !== -1) { throw Object.assign( new httperr.BadRequest(e.errorMessage), {errorNum: e.errorNum, cause: e} ); } throw e; } setResponse(res, 'graph', graphForClient(g), ACCEPTED); }) .pathParam('graph', graphName) .body(joi.object({ collection: joi.any().required() }).required(), 'The vertex collection to be stored.') .error('bad request', 'The vertex collection is invalid.') .error('not found', 'The graph could not be found.') .summary('Create a new vertex collection.') .description('Stores a new vertex collection. This has to contain the vertex-collection name.'); router.delete('/:graph/vertex/:collection', function(req, res) { const dropCollection = Boolean(req.queryParams.dropCollection); const name = req.pathParams.graph; const defName = req.pathParams.collection; let g; try { g = Graph._graph(name); } catch (e) { if (e.isArangoError && e.errorNum === errors.ERROR_GRAPH_NOT_FOUND.code) { throw Object.assign( new httperr.NotFound(e.errorMessage), {errorNum: e.errorNum, cause: e} ); } throw e; } try { g._removeVertexCollection(defName, dropCollection); } catch (e) { if (e.isArangoError && e.errorNum === errors.ERROR_GRAPH_VERTEX_COL_DOES_NOT_EXIST.code) { throw Object.assign( new httperr.NotFound(e.errorMessage), {errorNum: e.errorNum, cause: e} ); } if (e.isArangoError && e.errorNum === errors.ERROR_GRAPH_NOT_IN_ORPHAN_COLLECTION.code) { throw Object.assign( new httperr.BadRequest(e.errorMessage), {errorNum: e.errorNum, cause: e} ); } throw e; } setResponse(res, 'graph', graphForClient(g), ACCEPTED); }) .pathParam('graph', graphName) .pathParam('collection', vertexCollectionName) .queryParam('dropCollection', dropCollectionFlag) .error('bad request', 'The collection is not found or part of an edge definition.') .error('not found', 'The graph could not be found.') .summary('Delete a vertex collection.') .description('Removes a vertex collection from this graph. If this collection is used in one or more edge definitions'); router.get('/:graph/edge', function(req, res) { const name = req.pathParams.graph; let g; try { g = Graph._graph(name); } catch (e) { if (e.isArangoError && e.errorNum === errors.ERROR_GRAPH_NOT_FOUND.code) { throw Object.assign( new httperr.NotFound(e.errorMessage), {errorNum: e.errorNum, cause: e} ); } throw e; } setResponse(res, 'collections', _.map(g._edgeCollections(), (c) => c.name()).sort(), OK); }) .pathParam('graph', graphName) .error('not found', 'The graph could not be found.') .summary('List all edge collections.') .description('Get the list of all edge collection.'); router.post('/:graph/edge', function(req, res) { const name = req.pathParams.graph; let g; try { g = Graph._graph(name); } catch (e) { if (e.isArangoError && e.errorNum === errors.ERROR_GRAPH_NOT_FOUND.code) { throw Object.assign( new httperr.NotFound(e.errorMessage), {errorNum: e.errorNum, cause: e} ); } throw e; } try { g._extendEdgeDefinitions(req.body); } catch (e) { if (e.isArangoError && [ errors.ERROR_GRAPH_COLLECTION_MULTI_USE.code, errors.ERROR_GRAPH_COLLECTION_USE_IN_MULTI_GRAPHS.code, errors.ERROR_GRAPH_CREATE_MALFORMED_EDGE_DEFINITION.code ].indexOf(e.errorNum) !== -1) { throw Object.assign( new httperr.BadRequest(e.errorMessage), {errorNum: e.errorNum, cause: e} ); } throw e; } setResponse(res, 'graph', graphForClient(g), ACCEPTED); }) .pathParam('graph', graphName) .body(joi.any().required(), 'The edge definition to be stored.') .error('bad request', 'The edge definition is invalid.') .error('not found', 'The graph could not be found.') .summary('Create a new edge definition.') .description(dd` Stores a new edge definition with the information contained within the body. This has to contain the edge-collection name, as well as set of from and to collections-names respectively. `); router.put('/:graph/edge/:definition', function(req, res) { const name = req.pathParams.graph; const defName = req.pathParams.definition; let g; try { g = Graph._graph(name); } catch (e) { if (e.isArangoError && e.errorNum === errors.ERROR_GRAPH_NOT_FOUND.code) { throw Object.assign( new httperr.NotFound(e.errorMessage), {errorNum: e.errorNum, cause: e} ); } throw e; } if (defName !== req.body.collection) { throw Object.assign( new httperr.NotFound(errors.ERROR_GRAPH_EDGE_COLLECTION_NOT_USED.message), {errorNum: errors.ERROR_GRAPH_EDGE_COLLECTION_NOT_USED.code} ); } try { g._editEdgeDefinitions(req.body); } catch (e) { if (e.isArangoError && [ errors.ERROR_GRAPH_EDGE_COLLECTION_NOT_USED.code, errors.ERROR_GRAPH_CREATE_MALFORMED_EDGE_DEFINITION.code ].indexOf(e.errorNum) !== -1) { throw Object.assign( new httperr.BadRequest(e.errorMessage), {errorNum: e.errorNum, cause: e} ); } throw e; } setResponse(res, 'graph', graphForClient(g), ACCEPTED); }) .pathParam('graph', graphName) .pathParam('definition', definitionEdgeCollectionName) .body(joi.object().required(), 'The edge definition to be stored.') .error('bad request', 'The edge definition is invalid.') .error('not found', 'The graph could not be found.') .summary('Replace an edge definition.') .description(dd` Replaces an existing edge definition with the information contained within the body. This has to contain the edge-collection name, as well as set of from and to collections-names respectively. This will also change the edge definitions of all other graphs using this definition as well. `); router.delete('/:graph/edge/:definition', function(req, res) { const dropCollection = Boolean(req.queryParams.dropCollection); const name = req.pathParams.graph; const defName = req.pathParams.definition; let g; try { g = Graph._graph(name); g._deleteEdgeDefinition(defName, dropCollection); } catch (e) { if (e.isArangoError && [ errors.ERROR_GRAPH_NOT_FOUND.code, errors.ERROR_GRAPH_EDGE_COLLECTION_NOT_USED.code, ].indexOf(e.errorNum) !== -1) { throw Object.assign( new httperr.NotFound(e.errorMessage), {errorNum: e.errorNum, cause: e} ); } throw e; } setResponse(res, 'graph', graphForClient(g), ACCEPTED); }) .pathParam('graph', graphName) .pathParam('definition', definitionEdgeCollectionName) .queryParam('dropCollection', dropCollectionFlag) .error('not found', 'The graph could not be found.') .summary('Delete an edge definition.') .description(dd` Removes an existing edge definition from this graph. All data stored in the edge collection are dropped as well as long as it is not used in other graphs. `); // Vertex Operations router.post('/:graph/vertex/:collection', function(req, res) { const waitForSync = Boolean(req.queryParams.waitForSync); const name = req.pathParams.graph; const collection = req.pathParams.collection; let g; try { g = Graph._graph(name); } catch (e) { if (e.isArangoError && e.errorNum === errors.ERROR_GRAPH_NOT_FOUND.code) { throw Object.assign( new httperr.NotFound(e.errorMessage), {errorNum: e.errorNum, cause: e} ); } throw e; } checkCollection(g, collection); let meta; try { meta = g[collection].save(req.body, {waitForSync}); } catch (e) { if (e.isArangoError && e.errorNum === errors.ERROR_GRAPH_INVALID_EDGE.code) { throw Object.assign( new httperr.BadRequest(e.errorMessage), {errorNum: e.errorNum, cause: e} ); } throw e; } setResponse(res, 'vertex', meta, waitForSync ? CREATED : ACCEPTED); }) .pathParam('graph', graphName) .pathParam('collection', vertexCollectionName) .queryParam('waitForSync', waitForSyncFlag) .body(joi.any().required(), 'The document to be stored') .error('bad request', 'The edge definition is invalid.') .error('not found', 'Graph or collection not found.') .summary('Create a new vertex.') .description('Stores a new vertex with the information contained within the body into the given collection.'); router.get('/:graph/vertex/:collection/:key', function(req, res) { const name = req.pathParams.graph; const collection = req.pathParams.collection; const key = req.pathParams.key; const id = `${collection}/${key}`; let g; try { g = Graph._graph(name); } catch (e) { if (e.isArangoError && e.errorNum === errors.ERROR_GRAPH_NOT_FOUND.code) { throw Object.assign( new httperr.NotFound(e.errorMessage), {errorNum: e.errorNum, cause: e} ); } throw e; } checkCollection(g, collection); let doc; try { doc = g[collection].document(id); } catch (e) { if (e.isArangoError && e.errorNum === errors.ERROR_ARANGO_DOCUMENT_NOT_FOUND.code) { throw Object.assign( new httperr.NotFound(e.errorMessage), {errorNum: e.errorNum, cause: e} ); } throw e; } matchVertexRevision(req, doc._rev); setResponse(res, 'vertex', doc, OK); }) .pathParam('graph', graphName) .pathParam('collection', vertexCollectionName) .pathParam('key', vertexKey) .error('not found', 'The vertex does not exist.') .summary('Get a vertex.') .description('Gets a vertex with the given key if it is contained within your graph.'); router.put('/:graph/vertex/:collection/:key', function(req, res) { const waitForSync = Boolean(req.queryParams.waitForSync); const name = req.pathParams.graph; const collection = req.pathParams.collection; const key = req.pathParams.key; const id = `${collection}/${key}`; let g; try { g = Graph._graph(name); } catch (e) { if (e.isArangoError && e.errorNum === errors.ERROR_GRAPH_NOT_FOUND.code) { throw Object.assign( new httperr.NotFound(e.errorMessage), {errorNum: e.errorNum, cause: e} ); } throw e; } checkCollection(g, collection); let doc; try { doc = g[collection].document(id); } catch (e) { if (e.isArangoError && e.errorNum === errors.ERROR_ARANGO_DOCUMENT_NOT_FOUND.code) { throw Object.assign( new httperr.NotFound(e.errorMessage), {errorNum: e.errorNum, cause: e} ); } throw e; } matchVertexRevision(req, doc._rev); const meta = g[collection].replace(id, req.body, {waitForSync}); setResponse(res, 'vertex', meta, waitForSync ? OK : ACCEPTED); }) .pathParam('graph', graphName) .pathParam('collection', vertexCollectionName) .pathParam('key', vertexKey) .queryParam('waitForSync', waitForSyncFlag) .body(joi.any().required(), 'The document to be stored') .error('bad request', 'The vertex is invalid.') .error('not found', 'The vertex does not exist.') .summary('Replace a vertex.') .description(dd` Replaces a vertex with the given id by the content in the body. This will only run successfully if the vertex is contained within the graph. `); router.patch('/:graph/vertex/:collection/:key', function(req, res) { const waitForSync = Boolean(req.queryParams.waitForSync); const keepNull = Boolean(req.queryParams.keepNull); const name = req.pathParams.graph; const collection = req.pathParams.collection; const key = req.pathParams.key; const id = `${collection}/${key}`; let g; try { g = Graph._graph(name); } catch (e) { if (e.isArangoError && e.errorNum === errors.ERROR_GRAPH_NOT_FOUND.code) { throw Object.assign( new httperr.NotFound(e.errorMessage), {errorNum: e.errorNum, cause: e} ); } throw e; } checkCollection(g, collection); let doc; try { doc = g[collection].document(id); } catch (e) { if (e.isArangoError && e.errorNum === errors.ERROR_ARANGO_DOCUMENT_NOT_FOUND.code) { throw Object.assign( new httperr.NotFound(e.errorMessage), {errorNum: e.errorNum, cause: e} ); } throw e; } matchVertexRevision(req, doc._rev); const meta = g[collection].update(id, req.body, {waitForSync, keepNull}); setResponse(res, 'vertex', meta, waitForSync ? OK : ACCEPTED); }) .pathParam('graph', graphName) .pathParam('collection', vertexCollectionName) .pathParam('key', vertexKey) .queryParam('waitForSync', waitForSyncFlag) .queryParam('keepNull', keepNullFlag) .body(joi.any().required(), 'The values that should be modified') .error('bad request', 'The vertex is invalid.') .error('not found', 'The vertex does not exist.') .summary('Update a vertex.') .description(dd` Updates a vertex with the given id by adding the content in the body. This will only run successfully if the vertex is contained within the graph. `); router.delete('/:graph/vertex/:collection/:key', function(req, res) { const waitForSync = Boolean(req.queryParams.waitForSync); const name = req.pathParams.graph; const collection = req.pathParams.collection; const key = req.pathParams.key; const id = `${collection}/${key}`; let g; try { g = Graph._graph(name); } catch (e) { if (e.isArangoError && e.errorNum === errors.ERROR_GRAPH_NOT_FOUND.code) { throw Object.assign( new httperr.NotFound(e.errorMessage), {errorNum: e.errorNum, cause: e} ); } throw e; } checkCollection(g, collection); let doc; try { doc = g[collection].document(id); } catch (e) { if (e.isArangoError && e.errorNum === errors.ERROR_ARANGO_DOCUMENT_NOT_FOUND.code) { throw Object.assign( new httperr.NotFound(e.errorMessage), {errorNum: e.errorNum, cause: e} ); } throw e; } matchVertexRevision(req, doc._rev); let didRemove; try { didRemove = g[collection].remove(id, {waitForSync}); } catch (e) { if (e.isArangoError && e.errorNum === errors.ERROR_ARANGO_DOCUMENT_NOT_FOUND.code) { throw Object.assign( new httperr.NotFound(e.errorMessage), {errorNum: e.errorNum, cause: e} ); } throw e; } setResponse(res, 'removed', didRemove, waitForSync ? OK : ACCEPTED); }) .pathParam('graph', graphName) .pathParam('collection', vertexCollectionName) .pathParam('key', vertexKey) .queryParam('waitForSync', waitForSyncFlag) .error('not found', 'The vertex does not exist.') .summary('Delete a vertex.') .description(dd` Deletes a vertex with the given id, if it is contained within the graph. Furthermore all edges connected to this vertex will be deleted. `); // Edge Operations router.post('/:graph/edge/:collection', function(req, res) { const name = req.pathParams.graph; const collection = req.pathParams.collection; if (!req.body._from || !req.body._to) { throw Object.assign( new httperr.Gone(errors.ERROR_GRAPH_INVALID_EDGE.message), {errorNum: errors.ERROR_GRAPH_INVALID_EDGE.code} ); } let g; try { g = Graph._graph(name); } catch (e) { if (e.isArangoError && e.errorNum === errors.ERROR_GRAPH_NOT_FOUND.code) { throw Object.assign( new httperr.NotFound(e.errorMessage), {errorNum: e.errorNum, cause: e} ); } throw e; } checkCollection(g, collection); let meta; try { meta = g[collection].save(req.body); } catch(e) { if (e.errorNum !== errors.ERROR_GRAPH_INVALID_EDGE.code) { throw Object.assign( new httperr.Gone(e.errorMessage), {errorNum: errors.ERROR_GRAPH_INVALID_EDGE.code, cause: e} ); } throw e; } setResponse(res, 'edge', meta, ACCEPTED); }) .pathParam('graph', graphName) .pathParam('collection', edgeCollectionName) .body(joi.object().required(), 'The edge to be stored. Has to contain _from and _to attributes.') .error('bad request', 'The edge is invalid.') .error('not found', 'Graph or collection not found.') .summary('Create a new edge.') .description('Stores a new edge with the information contained within the body into the given collection.'); router.get('/:graph/edge/:collection/:key', function(req, res) { const name = req.pathParams.graph; const collection = req.pathParams.collection; const key = req.pathParams.key; const id = `${collection}/${key}`; let g; try { g = Graph._graph(name); } catch (e) { if (e.isArangoError && e.errorNum === errors.ERROR_GRAPH_NOT_FOUND.code) { throw Object.assign( new httperr.NotFound(e.errorMessage), {errorNum: e.errorNum, cause: e} ); } throw e; } checkCollection(g, collection); let doc; try { doc = g[collection].document(id); } catch (e) { if (e.isArangoError && e.errorNum === errors.ERROR_ARANGO_DOCUMENT_NOT_FOUND.code) { throw Object.assign( new httperr.NotFound(e.errorMessage), {errorNum: e.errorNum, cause: e} ); } throw e; } matchVertexRevision(req, doc._rev); setResponse(res, 'edge', doc, OK); }) .pathParam('graph', graphName) .pathParam('collection', edgeCollectionName) .pathParam('key', edgeKey) .error('not found', 'The edge does not exist.') .summary('Load an edge.') .description('Loads an edge with the given id if it is contained within your graph.'); router.put('/:graph/edge/:collection/:key', function(req, res) { const waitForSync = Boolean(req.queryParams.waitForSync); const name = req.pathParams.graph; const collection = req.pathParams.collection; const key = req.pathParams.key; const id = `${collection}/${key}`; let g; try { g = Graph._graph(name); } catch (e) { if (e.isArangoError && e.errorNum === errors.ERROR_GRAPH_NOT_FOUND.code) { throw Object.assign( new httperr.NotFound(e.errorMessage), {errorNum: e.errorNum, cause: e} ); } throw e; } checkCollection(g, collection); let doc; try { doc = g[collection].document(id); } catch (e) { if (e.isArangoError && e.errorNum === errors.ERROR_ARANGO_DOCUMENT_NOT_FOUND.code) { throw Object.assign( new httperr.NotFound(e.errorMessage), {errorNum: e.errorNum, cause: e} ); } throw e; } matchVertexRevision(req, doc._rev); const meta = g[collection].replace(id, req.body, {waitForSync}); setResponse(res, 'edge', meta, waitForSync ? OK : ACCEPTED); }) .pathParam('graph', graphName) .pathParam('collection', edgeCollectionName) .pathParam('key', edgeKey) .queryParam('waitForSync', waitForSyncFlag) .body(joi.any().required(), 'The document to be stored. _from and _to attributes are ignored') .error('bad request', 'The edge is invalid.') .error('not found', 'The edge does not exist.') .summary('Replace an edge.') .description(dd` Replaces an edge with the given id by the content in the body. This will only run successfully if the edge is contained within the graph. `); router.patch('/:graph/edge/:collection/:key', function(req, res) { const waitForSync = Boolean(req.queryParams.waitForSync); const keepNull = Boolean(req.queryParams.keepNull); const name = req.pathParams.graph; const collection = req.pathParams.collection; const key = req.pathParams.key; const id = `${collection}/${key}`; let g; try { g = Graph._graph(name); } catch (e) { if (e.isArangoError && e.errorNum === errors.ERROR_GRAPH_NOT_FOUND.code) { throw Object.assign( new httperr.NotFound(e.errorMessage), {errorNum: e.errorNum, cause: e} ); } throw e; } checkCollection(g, collection); let doc; try { doc = g[collection].document(id); } catch (e) { if (e.isArangoError && e.errorNum === errors.ERROR_ARANGO_DOCUMENT_NOT_FOUND.code) { throw Object.assign( new httperr.NotFound(e.errorMessage), {errorNum: e.errorNum, cause: e} ); } throw e; } matchVertexRevision(req, doc._rev); const meta = g[collection].update(id, req.body, {waitForSync, keepNull}); setResponse(res, 'edge', meta, waitForSync ? OK : ACCEPTED); }) .pathParam('graph', graphName) .pathParam('collection', edgeCollectionName) .pathParam('key', edgeKey) .queryParam('waitForSync', waitForSyncFlag) .queryParam('keepNull', keepNullFlag) .body(joi.any().required(), 'The values that should be modified. _from and _to attributes are ignored') .error('bad request', 'The edge is invalid.') .error('not found', 'The edge does not exist.') .summary('Update an edge.') .description(dd` Updates an edge with the given id by adding the content in the body. This will only run successfully if the edge is contained within the graph. `); router.delete('/:graph/edge/:collection/:key', function(req, res) { const waitForSync = Boolean(req.queryParams.waitForSync); const name = req.pathParams.graph; const collection = req.pathParams.collection; const key = req.pathParams.key; const id = `${collection}/${key}`; let g; try { g = Graph._graph(name); } catch (e) { if (e.isArangoError && e.errorNum === errors.ERROR_GRAPH_NOT_FOUND.code) { throw Object.assign( new httperr.NotFound(e.errorMessage), {errorNum: e.errorNum, cause: e} ); } throw e; } checkCollection(g, collection); let doc; try { doc = g[collection].document(id); } catch (e) { if (e.isArangoError && e.errorNum === errors.ERROR_ARANGO_DOCUMENT_NOT_FOUND.code) { throw Object.assign( new httperr.NotFound(e.errorMessage), {errorNum: e.errorNum, cause: e} ); } throw e; } matchVertexRevision(req, doc._rev); let didRemove; try { didRemove = g[collection].remove(id, {waitForSync}); } catch (e) { if (e.isArangoError && e.errorNum === errors.ERROR_ARANGO_DOCUMENT_NOT_FOUND.code) { throw Object.assign( new httperr.NotFound(e.errorMessage), {errorNum: e.errorNum, cause: e} ); } throw e; } setResponse(res, 'removed', didRemove, waitForSync ? OK : ACCEPTED); }) .pathParam('graph', graphName) .pathParam('collection', edgeCollectionName) .pathParam('key', edgeKey) .queryParam('waitForSync', waitForSyncFlag) .error('not found', 'The edge does not exist.') .summary('Delete an edge.') .description('Deletes an edge with the given id, if it is contained within the graph.');