summaryrefslogtreecommitdiff
path: root/inventory/api.py
diff options
context:
space:
mode:
Diffstat (limited to 'inventory/api.py')
-rw-r--r--inventory/api.py147
1 files changed, 147 insertions, 0 deletions
diff --git a/inventory/api.py b/inventory/api.py
new file mode 100644
index 0000000..0fde577
--- /dev/null
+++ b/inventory/api.py
@@ -0,0 +1,147 @@
+import datetime
+
+from flask import Blueprint, jsonify, current_app, request, abort, url_for
+from flask_pymongo import PyMongo
+from marshmallow import ValidationError
+import pymongo
+import pytz
+
+from .schema import NodeSchema
+
+# Setup PyMongo
+mongo = PyMongo(current_app, tz_aware=True)
+mongo.db.nodes.create_index([('fields.value', pymongo.TEXT), ('name', pymongo.TEXT)], name='fields.value_text_name_text')
+mongo.db.nodes.create_index([('parent_id', pymongo.ASCENDING)], name='parent_id')
+
+app = Blueprint('api', __name__)
+
+
+# Error handling
+@app.errorhandler(400)
+def on_invalid_request(e):
+ return jsonify({'message': e.description or 'Invalid request'}), 400
+
+
+@app.errorhandler(404)
+def on_not_found(e):
+ return jsonify({'message': e.description or 'Not found'}), 404
+
+
+@app.errorhandler(500)
+def on_internal_error(e):
+ return jsonify({'message': e.description or 'Invalid request'}), 500
+
+
+@app.errorhandler(ValidationError)
+def on_validation_error(e):
+ return jsonify({'message': 'Validation error', 'fields': e.messages}), 400
+
+
+# Routes
+@app.route('/nodes')
+def root_nodes():
+ schema = NodeSchema(many=True)
+ data = schema.dump(mongo.db.nodes.find({'parent_id': None}))
+ return jsonify(data)
+
+
+@app.route('/nodes', methods=['POST'])
+def add_node():
+ data = request.json
+ if data is None or not isinstance(data, dict):
+ abort(400, 'Payload must be a JSON object')
+ schema = NodeSchema()
+ node = schema.load(data)
+ node['created_at'] = pytz.utc.localize(datetime.datetime.utcnow())
+ result = mongo.db.nodes.insert_one(node)
+ if not result.acknowledged:
+ abort(500, 'Write operation not acknowledged')
+ node_id = result.inserted_id
+ return jsonify({'id': str(node_id), 'url': url_for('.node', node_id=node_id)}), 201
+
+
+@app.route('/nodes/<ObjectId:node_id>')
+def node(node_id):
+ result = mongo.db.nodes.aggregate([
+ {'$match': {'_id': node_id}},
+ {
+ '$graphLookup': {
+ 'from': 'nodes',
+ 'startWith': '$parent_id',
+ 'connectFromField': 'parent_id',
+ 'connectToField': '_id',
+ 'as': 'parents',
+ },
+ },
+ {
+ '$graphLookup': {
+ 'from': 'nodes',
+ 'startWith': '$_id',
+ 'connectFromField': '_id',
+ 'connectToField': 'parent_id',
+ 'as': 'children',
+ 'maxDepth': 1,
+ },
+ },
+ ])
+ try:
+ node = result.next()
+ except StopIteration:
+ abort(404, 'No node found')
+
+ parents = dict((parent['_id'], parent) for parent in node.pop('parents'))
+ current = node
+ # Recursively assign each node their respective parent
+ while current:
+ # NOTE: Python assigns from left to right
+ current['parent'] = current = parents.get(current.get('parent_id'))
+
+ children = dict((child['_id'], child) for child in node.pop('children'))
+ children_list = node['children'] = []
+ for child in children.values():
+ # Direct children should be assigned to the top-most list
+ if child['parent_id'] == node['_id']:
+ children_list.append(child)
+ continue
+ # Otherwise assign to their respective parent
+ parent = children[child['parent_id']]
+ parent.setdefault('children', []).append(child)
+
+ schema = NodeSchema()
+ return jsonify(schema.dump(node))
+
+
+@app.route('/nodes/<ObjectId:node_id>', methods=['PUT'])
+def update_node(node_id):
+ data = request.json
+ if data is None or not isinstance(data, dict):
+ abort(400, 'Payload must be a JSON object')
+ schema = NodeSchema()
+ node = schema.load(data)
+ node['updated_at'] = pytz.utc.localize(datetime.datetime.utcnow())
+ result = mongo.db.nodes.update_one({'_id': node_id}, {'$set': node})
+ if not result.acknowledged:
+ abort(500, 'Write operation not acknowledged')
+ return '', 204
+
+
+@app.route('/nodes/<ObjectId:node_id>', methods=['DELETE'])
+def delete_node(node_id):
+ result = mongo.db.nodes.delete_one({'_id': node_id})
+ if result.deleted_count == 0:
+ abort(404, 'No node found')
+ return jsonify({}), 204
+
+
+@app.route('/search', methods=['POST'])
+def find_nodes():
+ if 'q' not in request.form:
+ abort(400, 'Missing q argument')
+ schema = NodeSchema(many=True)
+ data = schema.dump(mongo.db.nodes.find({'$text': {'$search': request.form['q']}}))
+ return jsonify(data)
+
+
+@app.route('/')
+def index():
+ return 'api'