diff --git a/pillar/application/__init__.py b/pillar/application/__init__.py index 55d91bf5..76366fee 100644 --- a/pillar/application/__init__.py +++ b/pillar/application/__init__.py @@ -101,6 +101,8 @@ if from_envvar: # configfile doesn't exist, it should error out (i.e. silent=False). app.config.from_pyfile(from_envvar, silent=False) +# Set the TMP + # Configure logging logging.basicConfig( level=logging.WARNING, @@ -225,11 +227,13 @@ from modules import local_auth from modules import file_storage from modules import users from modules import nodes +from modules import latest app.register_blueprint(encoding, url_prefix='/encoding') app.register_blueprint(blender_id, url_prefix='/blender_id') projects.setup_app(app, url_prefix='/p') local_auth.setup_app(app, url_prefix='/auth') file_storage.setup_app(app, url_prefix='/storage') +latest.setup_app(app, url_prefix='/latest') users.setup_app(app) nodes.setup_app(app) diff --git a/pillar/application/modules/latest/__init__.py b/pillar/application/modules/latest/__init__.py new file mode 100644 index 00000000..b84eaeec --- /dev/null +++ b/pillar/application/modules/latest/__init__.py @@ -0,0 +1,73 @@ +import itertools + +import pymongo +from flask import Blueprint, current_app + +from application.utils import jsonify + +blueprint = Blueprint('latest', __name__) + + +def keep_fetching(collection, db_filter, projection, sort, py_filter, batch_size=12): + """Yields results for which py_filter returns True""" + + curs = collection.find(db_filter, projection).sort(sort) + curs.batch_size(batch_size) + + for doc in curs: + if py_filter(doc): + yield doc + + +def latest_nodes(db_filter, projection, py_filter, limit): + nodes = current_app.data.driver.db['nodes'] + + latest = keep_fetching(nodes, db_filter, projection, + [('_updated', pymongo.DESCENDING)], + py_filter, limit) + + result = list(itertools.islice(latest, limit)) + return jsonify({'_items': result}) + + +def has_public_project(node_doc): + """Returns True iff the project the node belongs to is public.""" + + project_id = node_doc.get('project') + return is_project_public(project_id) + + +# TODO: cache result, at least for a limited amt. of time, or for this HTTP request. +def is_project_public(project_id): + """Returns True iff the project is public.""" + + project = current_app.data.driver.db['projects'].find_one(project_id) + if not project: + return False + + return not project.get('is_private') + + +@blueprint.route('/assets') +def latest_assets(): + latest = latest_nodes({'node_type': 'asset', 'properties.status': 'published'}, + {'name': 1, 'project': 1, 'user': 1, 'node_type': 1, + 'picture': 1, 'properties.status': 1, + 'properties.content_type': 1, + 'permissions.world': 1}, + has_public_project, 12) + return latest + + +@blueprint.route('/comments') +def latest_comments(): + latest = latest_nodes({'node_type': 'comment', 'properties.status': 'published'}, + {'project': 1, 'parent': 1, 'user': 1, + 'properties.content': 1, 'node_type': 1, 'properties.status': 1, + 'properties.is_reply': 1}, + has_public_project, 6) + return latest + + +def setup_app(app, url_prefix): + app.register_blueprint(blueprint, url_prefix=url_prefix) diff --git a/pillar/application/modules/projects.py b/pillar/application/modules/projects.py index e747feb9..d00e5771 100644 --- a/pillar/application/modules/projects.py +++ b/pillar/application/modules/projects.py @@ -6,7 +6,7 @@ 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.utils import remove_private_keys, authorization, PillarJSONEncoder +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 manage_extra.node_types.asset import node_type_asset @@ -213,11 +213,7 @@ def create_project(overrides=None): project = _create_new_project(project_name, user_id, overrides) # Return the project in the response. - resp = current_app.response_class(json.dumps(project, cls=PillarJSONEncoder), - mimetype='application/json', - status=201, - headers={'Location': '/projects/%s' % project['_id']}) - return resp + return jsonify(project, status=201, headers={'Location': '/projects/%s' % project['_id']}) def abort_with_error(status): diff --git a/pillar/application/utils/__init__.py b/pillar/application/utils/__init__.py index 6dee8945..2bd67608 100644 --- a/pillar/application/utils/__init__.py +++ b/pillar/application/utils/__init__.py @@ -4,6 +4,7 @@ import datetime import bson from eve import RFC1123_DATE_FORMAT +from flask import current_app __all__ = ('remove_private_keys', 'PillarJSONEncoder') @@ -37,3 +38,12 @@ class PillarJSONEncoder(json.JSONEncoder): # Let the base class default method raise the TypeError return json.JSONEncoder.default(self, obj) + + +def jsonify(mongo_doc, status=200, headers=None): + """JSonifies a Mongo document into a Flask response object.""" + + return current_app.response_class(json.dumps(mongo_doc, cls=PillarJSONEncoder), + mimetype='application/json', + status=status, + headers=headers)