diff --git a/alembic/versions/1ab5aa1e3054_start_associating_comments_with_.py b/alembic/versions/1ab5aa1e3054_start_associating_comments_with_.py new file mode 100644 index 0000000..fde6462 --- /dev/null +++ b/alembic/versions/1ab5aa1e3054_start_associating_comments_with_.py @@ -0,0 +1,28 @@ +"""Start associating comments with registered users and use user information +as a display name instead of arbitrary author names. + +Revision ID: 1ab5aa1e3054 +Revises: 2c00861f3ff1 +Create Date: 2015-09-01 18:05:35.598253 + +""" + +# revision identifiers, used by Alembic. +revision = '1ab5aa1e3054' +down_revision = '2c00861f3ff1' +branch_labels = None +depends_on = None + +from alembic import op +import sqlalchemy as sa + +def upgrade(): + op.drop_column('user_comments', 'author_name') + op.add_column('user_comments', + sa.Column('user_id', + sa.BigInteger, + sa.ForeignKey('users.id'))) + +def downgrade(): + op.drop_column('user_comments', 'user_id') + op.add_column('user_comments', sa.Column('author_name', sa.String()),) diff --git a/cycledash/api/comments.py b/cycledash/api/comments.py index addef63..2a09922 100644 --- a/cycledash/api/comments.py +++ b/cycledash/api/comments.py @@ -2,13 +2,15 @@ from collections import defaultdict from flask import jsonify, request from flask.ext.restful import abort, fields +from flask.ext.login import current_user from sqlalchemy import select, func, desc from voluptuous import Any, Required, Coerce, Schema from common.helpers import tables, to_epoch from cycledash import db from cycledash.helpers import (prepare_request_data, success_response, - abort_if_none_for, camelcase_dict) + abort_if_none_for, camelcase_dict, + desentisize_user) from cycledash.validations import Doc, to_epoch from . import Resource, validate_with, marshal_with @@ -20,8 +22,7 @@ Required("position"): Coerce(int), Required("reference"): basestring, Required("alternates"): basestring, - Required("comment_text"): basestring, - "author_name": basestring, + Required("comment_text"): basestring }) DeleteComment = Schema({ @@ -30,8 +31,7 @@ UpdateComment = Schema({ Required('last_modified'): Coerce(float), - "comment_text": basestring, - "author_name": basestring, + "comment_text": basestring }) CommentFields = Schema({ @@ -52,8 +52,8 @@ basestring, Doc('comment_text', 'The text of the comment.'): Any(basestring, None), - Doc('author_name', 'The name of the author of this comment.'): - Any(basestring, None), + Doc('user_id', 'The ID of the User this comment is associated with.'): + Any(long, None), Doc('created', 'The time at which the comment was created (in epoch time).'): Coerce(to_epoch), @@ -77,9 +77,11 @@ def get(self, run_id): @marshal_with(CommentFields) def post(self, run_id): """Create a comment.""" + print current_user with tables(db.engine, 'user_comments') as (con, comments): q = comments.insert().values( vcf_id=run_id, + user_id=current_user['id'], **request.validated_body ).returning(*comments.c) return dict(q.execute().fetchone()), 201 @@ -163,6 +165,7 @@ def _row_key(comment, table): comment['last_modified'] = to_epoch(comment['last_modified']) comment['created'] = to_epoch(comment['created']) comment = camelcase_dict(comment) + comment = userify_comment(comment) results_map[row_key].append(comment) return {'comments': results_map} @@ -174,8 +177,30 @@ def get_last_comments(n=5): q = select(cols.values()).order_by( desc(cols.created)).limit(n) comments = [camelcase_dict(dict(c)) for c in con.execute(q).fetchall()] - return epochify_comments(comments) + return userify_comments(epochify_comments(comments)) +def userify_comments(comments): + """Given comments with userIds, attaches the relevant user + info to the comments + """ + for comment in comments: + userify_comment(comment) + return comments + +def userify_comment(comment): + """Given a comment with userId, attaches the relevant user + info to the comment + """ + if 'userId' in comment: + with tables(db.engine, 'users') as (con, users): + q = select(users.c).where(users.c.id == comment['userId']) + user = q.execute().fetchone() + if user is not None: + user = desentisize_user(dict(user)) + comment['user'] = user + else: + comment['user'] = None + return comment def epochify_comments(comments): """Sets `lastModified` and `created` to be epoch time instead of iso8061.""" @@ -205,4 +230,5 @@ def _get_comment(comment_table, id=None, **query_kwargs): q = comment_table.select().where(comment_table.c.id == id) for colname, val in query_kwargs.items(): q = q.where(comment_table.c[colname] == val) - return dict(abort_if_none_for('comment')(q.execute().fetchone(), id)) + comment = q.execute().fetchone() + return dict(abort_if_none_for('comment')(comment, id)) diff --git a/cycledash/auth.py b/cycledash/auth.py index 79fd568..6c0a478 100644 --- a/cycledash/auth.py +++ b/cycledash/auth.py @@ -65,6 +65,24 @@ def is_anonymous(self): def get_id(self): return unicode(self['id']) +class AnonymousUser(dict): + def __init__(self): + self['id'] = None + self['username'] = u'Anonymous' + + def is_authenticated(self): + return False + + def is_active(self): + return False + + def is_anonymous(self): + return True + + def get_id(self): + return self['id'] + +login_manager.anonymous_user = AnonymousUser def wrap_user(user): """Wraps user record returned from the database in a class providing methods diff --git a/cycledash/helpers.py b/cycledash/helpers.py index ad59027..46ad609 100644 --- a/cycledash/helpers.py +++ b/cycledash/helpers.py @@ -195,6 +195,11 @@ def abort_if_none(obj, obj_id): return obj return abort_if_none +def desentisize_user(user): + """Removes sensitive user information before passing it to the frontend.""" + del user['password'] + del user['email'] + return user class CollisionError(Exception): pass diff --git a/cycledash/static/js/comments/components/comment.js b/cycledash/static/js/comments/components/comment.js index c08981f..6144793 100644 --- a/cycledash/static/js/comments/components/comment.js +++ b/cycledash/static/js/comments/components/comment.js @@ -59,8 +59,7 @@ var Comment = React.createClass({ // moment uses the local timezone by default (converting the // value, which starts as a UNIX timestamp, to that timezone) var relativeDate = moment.unix(comment.created).fromNow(); - var authorName = comment.authorName ? - comment.authorName.slice(0, 15) : 'Anonymous'; + var authorName = comment.userId ? comment.user.username : "Anonymous"; return (