diff --git a/pillar/application/modules/mongo_utils.py b/pillar/application/modules/mongo_utils.py new file mode 100644 index 00000000..cfeb1046 --- /dev/null +++ b/pillar/application/modules/mongo_utils.py @@ -0,0 +1,27 @@ +"""Utility functions for MongoDB stuff.""" + +from bson import ObjectId +from flask import current_app +from werkzeug.exceptions import NotFound + + +def find_one_or_404(collection_name, object_id): + """Returns the found object from the collection, or raises a NotFound exception. + + :param collection_name: name of the collection, such as 'users' or 'files' + :type collection_name: str + :param object_id: ID of the object to find. + :type object_id: str or bson.ObjectId + :returns: the found object + :rtype: dict + + :raises: werkzeug.exceptions.NotFound + """ + + collection = current_app.data.driver.db[collection_name] + found = collection.find_one(ObjectId(object_id)) + + if found is None: + raise NotFound() + + return found diff --git a/pillar/application/modules/projects.py b/pillar/application/modules/projects.py index 516e2c0d..50148668 100644 --- a/pillar/application/modules/projects.py +++ b/pillar/application/modules/projects.py @@ -7,9 +7,10 @@ from eve.methods.post import post_internal from eve.methods.patch import patch_internal from flask import g, Blueprint, request, abort, current_app +from application.modules import mongo_utils from application.utils import remove_private_keys, authorization, jsonify from application.utils.gcs import GoogleCloudStorageBucket -from application.utils.authorization import user_has_role, check_permissions +from application.utils.authorization import user_has_role, check_permissions, require_login from manage_extra.node_types.asset import node_type_asset from manage_extra.node_types.comment import node_type_comment from manage_extra.node_types.group import node_type_group @@ -49,7 +50,6 @@ def before_inserting_override_is_private_field(projects): def before_edit_check_permissions(document, original): - # Allow admin users to do whatever they want. # TODO: possibly move this into the check_permissions function. if user_has_role(u'admin'): @@ -316,6 +316,40 @@ def abort_with_error(status): abort(status if status // 100 >= 4 else 500) +@blueprint.route('//quotas') +@require_login() +def project_quotas(project_id): + """Returns information about the project's limits.""" + + # Check that the user has GET permissions on the project itself. + project = mongo_utils.find_one_or_404('projects', project_id) + check_permissions(project, 'GET') + + file_size_used = _project_total_file_size(project_id) + + info = { + 'file_size_quota': None, # TODO: implement this later. + 'file_size_used': file_size_used, + } + + return jsonify(info) + + +def _project_total_file_size(project_id): + """Returns the total number of bytes used by files of this project.""" + + files = current_app.data.driver.db['files'] + file_size_used = files.aggregate([ + {'$match': {'project': ObjectId(project_id)}}, + {'$project': {'length_aggregate_in_bytes': 1}}, + {'$group': {'_id': None, + 'all_files': {'$sum': '$length_aggregate_in_bytes'}}} + ]) + + # The aggregate function returns a cursor, not a document. + return next(file_size_used)['all_files'] + + def setup_app(app, url_prefix): app.on_replace_projects += override_is_private_field app.on_replace_projects += before_edit_check_permissions