def sort_results(k): """Allow sorting of the search results by closest matchng then by date the item was created.""" if k.get('hashtags'): for hashtag in k.get('hashtags'): # pragma: no branch if hashtag.get('hashtag', '').startswith(query): return (hashtag.get('hashtag'), timestamp() - k.get('created', 0)) else: return (k.get('username'), timestamp() - k.get('created', 0))
def test_timeify_filter(self): """Test the timeify filter """ self.assertEqual(timeify_filter(timestamp()), 'Less than a second ago') # Check one year ago time_yearago = timestamp() - 31536000 self.assertEqual(timeify_filter(time_yearago), '1 year ago') # Check two months ago time_yearago = timestamp() - 5184000 self.assertEqual(timeify_filter(time_yearago), '2 months ago') # Check 3 weeks ago time_yearago = timestamp() - 1814400 self.assertEqual(timeify_filter(time_yearago), '3 weeks ago')
def signin(user_id): """Logs the user with uid in by adding the uid to the session. """ session["user_id"] = user_id # update last login m.db.users.update({"_id": user_id}, {"$set": {"last_login": timestamp()}})
def timeify_filter(time): """Takes integer epoch time and returns a DateTime string for display. If this conversion fails this function will return "Err" """ try: # Please not that time is now a floating point value for extra # precision. We don't really need this when displaying it to the users # however. # Time can't be coverted directly to a int as it is a float point repr time = int(timestamp() - float(time)) multiples = [(31536000, 'year'), (2592000, 'month'), (604800, 'week'), (86400, 'day'), (3600, 'hour'), (60, 'minute'), (1, 'second')] # Find the closest time multiple since this post was posted # Work out the number of these multiples and return the string for multiple in multiples: if time < multiple[0]: continue number_of = math.floor(time / multiple[0]) if number_of > 1: time_frame = multiple[1] + 's' else: time_frame = multiple[1] return "{0} {1} ago".format(int(number_of), time_frame) # Default return means that this was checked less than a second ago return "Less than a second ago" except (TypeError, ValueError): return "Err"
def signin(user_id): """Logs the user with uid in by adding the uid to the session. """ session['user_id'] = user_id # update last login m.db.users.update({'_id': user_id}, {'$set': {'last_login': timestamp()}})
def login(uid): """Logs the user with uid in by adding the uid to the session. """ session['uid'] = uid # update last login r.hset(K.USER.format(uid), 'last_login', timestamp())
def display_time(response): """This is will write the time to the console in DEBUG mode""" if app.debug and not app.testing: # pragma: no cover if request.endpoint != 'static': print request.path, request.endpoint, \ str((timestamp() - g.start_time) * 100) + 'ms' return response
def create_account(username, email, password): """Creates a new user account. :param username: The new users user name :type username: str :param email: The new users e-mail address :type email: str :param password: The new users password un-hashed :type password: str :returns: The UID of the new user :rtype: str or None """ username = username.lower() email = email.lower() try: if check_username(username) and check_username_pattern(username) and \ check_email(email) and check_email_pattern(email): # Get a new UUID for the user uid = get_uuid() user = { '_id': uid, 'username': username.lower(), 'email': email.lower(), 'password': generate_password(password, method='pbkdf2:sha256:2000', salt_length=20), 'created': timestamp(), 'last_login': -1, 'active': False, 'banned': False, 'op': False, 'muted': False, 'about': "", 'score': 0, 'alerts_last_checked': -1, # Set the TTL for a newly created user, this has to be Datetime # object for MongoDB to recognise it. This is removed on # activation. 'ttl': datetime.utcnow() } # Set all the tips for new users for tip_name in k.VALID_TIP_NAMES: user['tip_{}'.format(tip_name)] = True # Insert the new user in to Mongo. If this fails a None will be # returned result = m.db.users.insert(user) return uid if result else None except DuplicateKeyError: # pragma: no cover # Oh no something went wrong. Pass over it. A None will be returned. pass return None
def create_account(username, email, password): """Creates a new user account. :param username: The new users user name :type username: str :param email: The new users e-mail address :type email: str :param password: The new users password un-hashed :type password: str :returns: The UID of the new user :rtype: str or None """ username = username.lower() email = email.lower() try: if ( check_username(username) and check_username_pattern(username) and check_email(email) and check_email_pattern(email) ): # Get a new UUID for the user uid = get_uuid() user = { "_id": uid, "username": username.lower(), "email": email.lower(), "password": generate_password(password, method="pbkdf2:sha256:2000", salt_length=20), "created": timestamp(), "last_login": -1, "active": False, "banned": False, "op": False, "muted": False, "about": "", "score": 0, "alerts_last_checked": -1, # Set the TTL for a newly created user, this has to be Datetime # object for MongoDB to recognise it. This is removed on # activation. "ttl": datetime.utcnow(), } # Insert the new user in to Mongo. If this fails a None will be # returned result = m.db.users.insert(user) return uid if result else None except DuplicateKeyError: # pragma: no cover # Oh no something went wrong. Pass over it. A None will be returned. pass return None
def get_alerts(user_id, page=1, per_page=None): """Return a list of alert objects as a pagination. """ if per_page is None: per_page = app.config.get('ALERT_ITEMS_PER_PAGE') # Get the last time the users checked the alerts # Try and cast the value to an int so we can boolean compare them try: alerts_last_checked = m.db.users.find_one({ '_id': user_id }).get('alerts_last_checked') except (AttributeError, TypeError, ValueError): alerts_last_checked = 0 # Get total number of elements in the sorted set total = r.zcard(k.USER_ALERTS.format(user_id)) aids = r.zrevrange(k.USER_ALERTS.format(user_id), (page - 1) * per_page, (page * per_page) - 1) # Create AlertManager to load the alerts am = AlertManager() alerts = [] for aid in aids: # Load the alert in to the alert manager alert = am.get(aid) if alert: # Check to see if the alert is newer than the time we last checked. # This allows us to highlight in the template # This will assign a new property to the object: `new` if int(alert.timestamp) > alerts_last_checked: alert.new = True # Add the entire alert from the manager on the list alerts.append(alert) else: # Self cleaning zset r.zrem(k.USER_ALERTS.format(user_id), aid) total = r.zcard(k.USER_ALERTS.format(user_id)) # May as well delete the alert if there is one r.delete(k.ALERT.format(aid)) # Update the last time the user checked there alerts # This will allow us to alert a user too new alerts with the /i-has-alerts # url m.db.users.update({'_id': user_id}, {'$set': { 'alerts_last_checked': timestamp() }}) return Pagination(alerts, total, page, per_page)
def approve_user(who_uid, whom_uid): """Allow a user to approve a follower""" # Check that the user is actually following. # Fail if not if r.zrank(k.USER_FOLLOWERS.format(who_uid), whom_uid) is None: return False # Add the user to the approved list # No alert is generated r.zadd(k.USER_APPROVED.format(who_uid), timestamp(), whom_uid) return True
def follow_user(who_uid, whom_uid): """Add whom to who's following zset and who to whom's followers zset. Generate an alert for this action. """ # Check that we are not already following the user if r.zrank(k.USER_FOLLOWING.format(who_uid), whom_uid) is not None: return False # Follow user # Score is based on UTC epoch time r.zadd(k.USER_FOLLOWING.format(who_uid), timestamp(), whom_uid) r.zadd(k.USER_FOLLOWERS.format(whom_uid), timestamp(), who_uid) # Create an alert and inform whom that who is now following them alert = FollowAlert(who_uid) AlertManager().alert(alert, [whom_uid]) # Back fill the who's feed with some posts from whom back_feed(who_uid, whom_uid) return True
def get_alerts(user_id, page=1, per_page=None): """Return a list of alert objects as a pagination. """ if per_page is None: per_page = app.config.get('ALERT_ITEMS_PER_PAGE') # Get the last time the users checked the alerts # Try and cast the value to an int so we can boolean compare them try: alerts_last_checked = m.db.users.find_one( {'_id': user_id} ).get('alerts_last_checked') except (AttributeError, TypeError, ValueError): alerts_last_checked = 0 # Get total number of elements in the sorted set total = r.zcard(k.USER_ALERTS.format(user_id)) aids = r.zrevrange(k.USER_ALERTS.format(user_id), (page - 1) * per_page, (page * per_page) - 1) # Create AlertManager to load the alerts am = AlertManager() alerts = [] for aid in aids: # Load the alert in to the alert manager alert = am.get(aid) if alert: # Check to see if the alert is newer than the time we last checked. # This allows us to highlight in the template # This will assign a new property to the object: `new` if int(alert.timestamp) > alerts_last_checked: alert.new = True # Add the entire alert from the manager on the list alerts.append(alert) else: # Self cleaning zset r.zrem(k.USER_ALERTS.format(user_id), aid) total = r.zcard(k.USER_ALERTS.format(user_id)) # May as well delete the alert if there is one r.delete(k.ALERT.format(aid)) # Update the last time the user checked there alerts # This will allow us to alert a user too new alerts with the /i-has-alerts # url m.db.users.update({'_id': user_id}, {'$set': {'alerts_last_checked': timestamp()}}) return Pagination(alerts, total, page, per_page)
def create_post(uid, body): """Creates a new post """ # Get a new UUID for the pid pid = get_uuid() # Hash form for posts # TODO this needs expanding to include some form of image upload hook post = { 'pid': pid, 'uid': uid, 'body': body, 'created': timestamp(), 'score': 0 } # Add post r.hmset(K.POST.format(pid), post) # Add post to users post list r.lpush(K.USER_POSTS.format(uid), pid) # Add post to authors feed r.lpush(K.USER_FEED.format(uid), pid) # Ensure the feed does not grow to large r.ltrim(K.USER_FEED.format(uid), 0, 999) # Append to all followers feeds populate_feeds(uid, pid) # Subscribe the poster to there post subscribe(uid, pid, SubscriptionReasons.POSTER) # TAGGING # Create alert manager and alert alert = TaggingAlert(uid, pid) # Alert tagees tagees = parse_tags(body) # Store a list of uids which need to alerted to the tagging tagees_to_alert = [] for tagee in tagees: # Don't allow tagging yourself if tagee[0] != uid: # Subscribe the tagee to the alert subscribe(tagee[0], pid, SubscriptionReasons.TAGEE) # Add the tagee's uid to the list to alert them tagees_to_alert.append(tagee[0]) # Alert the required tagees AlertManager().alert(alert, tagees_to_alert) return pid
def flag_post(user_id, post_id): """Flags a post for moderator review. :returns: True if flagged, false if removed. `CantFlagOwn` in case of error. """ # Get the comment so we can check who the author is post = get_post(post_id) if post.get('user_id') != user_id: if not has_flagged(user_id, post_id): # Increment the flag count by one and store the user name r.zadd(k.POST_FLAGS.format(post_id), timestamp(), user_id) m.db.posts.update({'_id': post_id}, {'$inc': {'flags': 1}}) else: raise AlreadyFlagged else: raise CantFlagOwn
def create_user(username, email, password): """Creates a user account """ username = username.lower() email = email.lower() if check_username(username) and check_email(email) and \ check_username_pattern(username) and check_email_pattern(email): # Create the user lookup keys. This LUA script ensures # that the name can not be taken at the same time causing a race # condition. This is also passed a UUID and will only return it if # successful uid = L.create_user(keys=[K.UID_USERNAME.format(username), K.UID_EMAIL.format(email)], args=[get_uuid()]) # Create user dictionary ready for HMSET only if uid is not None # This will only be None in the event of a race condition which we cant # really test for. if uid is not None: # pragma: no branch user = { 'uid': uid, 'username': username, 'email': email, 'password': generate_password(password), 'created': timestamp(), 'last_login': -1, 'active': 0, 'banned': 0, 'op': 0, 'muted': 0, 'about': "", 'score': 0, 'alerts_last_checked': 0 } r.hmset(K.USER.format(uid), user) # Set the TTL for the user account r.expire(K.USER.format(uid), K.EXPIRE_24HRS) return uid # If none of this worked return nothing return None
def timeify_filter(time): """Takes integer epoch time and returns a DateTime string for display. If this conversion fails this function will return "Err" """ try: # Please not that time is now a floating point value for extra # precision. We don't really need this when displaying it to the users # however. # Time can't be coverted directly to a int as it is a float point repr time = int(timestamp() - float(time)) multiples = [ (31536000, 'year'), (2592000, 'month'), (604800, 'week'), (86400, 'day'), (3600, 'hour'), (60, 'minute'), (1, 'second') ] # Find the closest time multiple since this post was posted # Work out the number of these multiples and return the string for multiple in multiples: if time < multiple[0]: continue number_of = math.floor(time / multiple[0]) if number_of > 1: time_frame = multiple[1] + 's' else: time_frame = multiple[1] return "{0} {1} ago".format(int(number_of), time_frame) # Default return means that this was checked less than a second ago return "Less than a second ago" except (TypeError, ValueError): return "Err"
def create_comment(uid, pid, body): """Create a new comment """ # Get a new UUID for the cid cid = get_uuid() # Form for comment hash comment = { 'cid': cid, 'uid': uid, 'pid': pid, 'body': body, 'created': timestamp(), 'score': 0 } # Add comment r.hmset(K.COMMENT.format(cid), comment) # Add comment to posts comment list r.lpush(K.POST_COMMENTS.format(pid), cid) # Add comment to users comment list # This may seem redundant but it allows for perfect account deletion # Please see Issue #3 on Github r.lpush(K.USER_COMMENTS.format(uid), cid) # COMMENT ALERTING # Alert all subscribers to the post that a new comment has been added. # We do this before subscribing anyone new # Create alert manager and alert alert = CommentingAlert(uid, pid) subscribers = [] # Iterate through subscribers and let them know about the comment for subscriber in get_subscribers(pid): # Ensure we don't get alerted for our own comments if subscriber != uid: subscribers.append(subscriber) # Push the comment alert out to all subscribers AlertManager().alert(alert, subscribers) # Subscribe the user to the post, will not change anything if they are # already subscribed subscribe(uid, pid, SubscriptionReasons.COMMENTER) # TAGGING # Create alert alert = TaggingAlert(uid, pid) # Subscribe tagees tagees = parse_tags(body) tagees_to_alert = [] for tagee in tagees: # Don't allow tagging yourself if tagee[0] != uid: subscribe(tagee[0], pid, SubscriptionReasons.TAGEE) tagees_to_alert.append(tagee[0]) # Get an alert manager to notify all tagees AlertManager().alert(alert, tagees_to_alert) return cid
def test_votes(self): """ Test that the voting mechanism will adjust the relevant score counters on users, posts and comments, etc... """ # Create three test users user1 = create_account('user1', '*****@*****.**', 'Password') user2 = create_account('user2', '*****@*****.**', 'Password') user3 = create_account('user3', '*****@*****.**', 'Password') # Create a post by user 1 post1 = create_post(user1, 'user1', 'Test post') # Get user 3 to downvote self.assertEqual(vote_post(user3, post1, amount=-1), -1) # Ensure post score has been adjusted self.assertEqual(get_post(post1).get('score'), -1) # Ensure user score has been adjusted self.assertEqual(get_user(user1).get('score'), -1) # Check that a user can reverse their vote within TIMEOUT self.assertEqual(vote_post(user3, post1, amount=-1), 0) self.assertEqual(get_post(post1).get('score'), 0) self.assertEqual(get_user(user1).get('score'), 0) # Get user 2 to upvote self.assertEqual(vote_post(user2, post1), 1) # Ensure post score has been adjusted self.assertEqual(get_post(post1).get('score'), 1) # Ensure user score has been adjusted self.assertEqual(get_user(user1).get('score'), 1) # Ensure user 1 can not vote on there own post self.assertRaises(CantVoteOnOwn, lambda: vote_post(user1, post1)) # Ensure the scores have not been adjusted self.assertEqual(get_post(post1).get('score'), 1) self.assertEqual(get_user(user1).get('score'), 1) # Ensure the user has voted self.assertTrue(has_voted(user2, post1)) # Check that a user can reverse their vote within TIMEOUT self.assertEqual(vote_post(user2, post1), 0) self.assertEqual(get_post(post1).get('score'), 0) self.assertEqual(get_user(user1).get('score'), 0) # Ensure the user has voted self.assertFalse(has_voted(user2, post1)) # Check that the score reflects an opposite vote within TIMEOUT self.assertEqual(vote_post(user2, post1, 1), 1) self.assertEqual(get_post(post1).get('score'), 1) self.assertEqual(get_user(user1).get('score'), 1) self.assertTrue(has_voted(user2, post1)) self.assertEqual(vote_post(user2, post1, -1), -1) self.assertEqual(get_post(post1).get('score'), -1) self.assertEqual(get_user(user1).get('score'), -1) self.assertTrue(has_voted(user2, post1)) # Check that a user can not reverse there vote after the TIMEOUT self.assertRaises(AlreadyVoted, lambda: vote_post(user2, post1, -1, timestamp() + K.VOTE_TIMEOUT + 1)) self.assertRaises(AlreadyVoted, lambda: vote_post(user2, post1, 1, timestamp() + K.VOTE_TIMEOUT + 1)) # Repeat the same tests on a comment # Create a comment by user 1 comment1 = create_post(user1, 'user1', 'Test comment', post1) # Let's cheat and set user1's score back to 0 m.db.users.update({'_id': user1}, {'$set': {'score': 0}}) # Get user 3 to downvote self.assertEqual(vote_post(user3, comment1, amount=-1), -1) # Ensure post score has been adjusted self.assertEqual(get_post(comment1).get('score'), -1) self.assertEqual(get_user(user1).get('score'), -1) # Reverse user3's vote just so it's not confusing self.assertEqual(vote_post(user3, comment1, amount=-1), 0) self.assertEqual(get_post(comment1).get('score'), 0) self.assertEqual(get_user(user1).get('score'), 0) # Ensure user 1 can not vote on there own comment self.assertRaises(CantVoteOnOwn, lambda: vote_post(user1, comment1)) # Ensure post score has been adjusted self.assertEqual(get_post(comment1).get('score'), 0) # Ensure user score has been adjusted self.assertEqual(get_user(user1).get('score'), 0) # Get user 2 to upvote self.assertEqual(vote_post(user2, comment1), 1) # Ensure post score has been adjusted self.assertEqual(get_post(comment1).get('score'), 1) # Ensure user score has been adjusted self.assertEqual(get_user(user1).get('score'), 1) self.assertTrue(has_voted(user2, comment1)) # Check that a user can reverse their vote within TIMEOUT self.assertEqual(vote_post(user2, comment1), 0) self.assertEqual(get_post(comment1).get('score'), 0) self.assertEqual(get_user(user1).get('score'), 0) self.assertFalse(has_voted(user2, comment1)) # Check that the score reflects an opposite vote within TIMEOUT self.assertEqual(vote_post(user2, comment1, -1), -1) self.assertEqual(get_post(comment1).get('score'), -1) self.assertEqual(get_user(user1).get('score'), -1) self.assertTrue(has_voted(user2, comment1)) self.assertEqual(vote_post(user2, comment1, 1), 1) self.assertEqual(get_post(comment1).get('score'), 1) self.assertEqual(get_user(user1).get('score'), 1) self.assertTrue(has_voted(user2, comment1)) # Check that a user can not reverse there vote after the TIMEOUT self.assertRaises(AlreadyVoted, lambda: vote_post(user2, comment1, -1, timestamp() + K.VOTE_TIMEOUT + 1)) self.assertRaises(AlreadyVoted, lambda: vote_post(user2, comment1, 1, timestamp() + K.VOTE_TIMEOUT + 1))
def __init__(self, user_id): self.alert_id = get_uuid() self.timestamp = timestamp() self.user_id = user_id
def gather_time(): """This is used to measure the request time for each page""" if app.debug and not app.testing: # pragma: no cover if request.endpoint != 'static': g.start_time = timestamp()
def test_votes(self): """ Test that the voting mechanism will adjust the relevant score counters on users, posts and comments, etc... """ # Create three test users user1 = create_account('user1', '*****@*****.**', 'Password') user2 = create_account('user2', '*****@*****.**', 'Password') user3 = create_account('user3', '*****@*****.**', 'Password') # Create a post by user 1 post1 = create_post(user1, 'user1', 'Test post') # Get user 3 to downvote self.assertEqual(vote_post(user3, post1, amount=-1), -1) # Ensure post score has been adjusted self.assertEqual(get_post(post1).get('score'), -1) # Ensure user score has been adjusted self.assertEqual(get_user(user1).get('score'), -1) # Check that a user can reverse their vote within TIMEOUT self.assertEqual(vote_post(user3, post1, amount=-1), 0) self.assertEqual(get_post(post1).get('score'), 0) self.assertEqual(get_user(user1).get('score'), 0) # Get user 2 to upvote self.assertEqual(vote_post(user2, post1), 1) # Ensure post score has been adjusted self.assertEqual(get_post(post1).get('score'), 1) # Ensure user score has been adjusted self.assertEqual(get_user(user1).get('score'), 1) # Ensure user 1 can not vote on there own post self.assertRaises(CantVoteOnOwn, lambda: vote_post(user1, post1)) # Ensure the scores have not been adjusted self.assertEqual(get_post(post1).get('score'), 1) self.assertEqual(get_user(user1).get('score'), 1) # Ensure the user has voted self.assertTrue(has_voted(user2, post1)) # Check that a user can reverse their vote within TIMEOUT self.assertEqual(vote_post(user2, post1), 0) self.assertEqual(get_post(post1).get('score'), 0) self.assertEqual(get_user(user1).get('score'), 0) # Ensure the user has voted self.assertFalse(has_voted(user2, post1)) # Check that the score reflects an opposite vote within TIMEOUT self.assertEqual(vote_post(user2, post1, 1), 1) self.assertEqual(get_post(post1).get('score'), 1) self.assertEqual(get_user(user1).get('score'), 1) self.assertTrue(has_voted(user2, post1)) self.assertEqual(vote_post(user2, post1, -1), -1) self.assertEqual(get_post(post1).get('score'), -1) self.assertEqual(get_user(user1).get('score'), -1) self.assertTrue(has_voted(user2, post1)) # Check that a user can not reverse there vote after the TIMEOUT self.assertRaises( AlreadyVoted, lambda: vote_post(user2, post1, -1, timestamp() + K.VOTE_TIMEOUT + 1)) self.assertRaises( AlreadyVoted, lambda: vote_post(user2, post1, 1, timestamp() + K.VOTE_TIMEOUT + 1)) # Repeat the same tests on a comment # Create a comment by user 1 comment1 = create_post(user1, 'user1', 'Test comment', post1) # Let's cheat and set user1's score back to 0 m.db.users.update({'_id': user1}, {'$set': {'score': 0}}) # Get user 3 to downvote self.assertEqual(vote_post(user3, comment1, amount=-1), -1) # Ensure post score has been adjusted self.assertEqual(get_post(comment1).get('score'), -1) self.assertEqual(get_user(user1).get('score'), -1) # Reverse user3's vote just so it's not confusing self.assertEqual(vote_post(user3, comment1, amount=-1), 0) self.assertEqual(get_post(comment1).get('score'), 0) self.assertEqual(get_user(user1).get('score'), 0) # Ensure user 1 can not vote on there own comment self.assertRaises(CantVoteOnOwn, lambda: vote_post(user1, comment1)) # Ensure post score has been adjusted self.assertEqual(get_post(comment1).get('score'), 0) # Ensure user score has been adjusted self.assertEqual(get_user(user1).get('score'), 0) # Get user 2 to upvote self.assertEqual(vote_post(user2, comment1), 1) # Ensure post score has been adjusted self.assertEqual(get_post(comment1).get('score'), 1) # Ensure user score has been adjusted self.assertEqual(get_user(user1).get('score'), 1) self.assertTrue(has_voted(user2, comment1)) # Check that a user can reverse their vote within TIMEOUT self.assertEqual(vote_post(user2, comment1), 0) self.assertEqual(get_post(comment1).get('score'), 0) self.assertEqual(get_user(user1).get('score'), 0) self.assertFalse(has_voted(user2, comment1)) # Check that the score reflects an opposite vote within TIMEOUT self.assertEqual(vote_post(user2, comment1, -1), -1) self.assertEqual(get_post(comment1).get('score'), -1) self.assertEqual(get_user(user1).get('score'), -1) self.assertTrue(has_voted(user2, comment1)) self.assertEqual(vote_post(user2, comment1, 1), 1) self.assertEqual(get_post(comment1).get('score'), 1) self.assertEqual(get_user(user1).get('score'), 1) self.assertTrue(has_voted(user2, comment1)) # Check that a user can not reverse there vote after the TIMEOUT self.assertRaises( AlreadyVoted, lambda: vote_post(user2, comment1, -1, timestamp() + K.VOTE_TIMEOUT + 1)) self.assertRaises( AlreadyVoted, lambda: vote_post(user2, comment1, 1, timestamp() + K.VOTE_TIMEOUT + 1))
def create_post(user_id, username, body, reply_to=None, upload=None, permission=k.PERM_PUBLIC): """Creates a new post This handled both posts and what used to be called comments. If the reply_to field is not None then the post will be treat as a comment. You will need to make sure the reply_to post exists. :param user_id: The user id of the user posting the post :type user_id: str :param username: The user name of the user posting (saves a lookup) :type username: str :param body: The content of the post :type body: str :param reply_to: The post id of the post this is a reply to if any :type reply_to: str :param upload: :returns: The post id of the new post :param permission: Who can see/interact with the post you are posting :type permission: int :rtype: str or None """ # Get a new UUID for the post_id ("_id" in MongoDB) post_id = get_uuid() # Get the timestamp, we will use this to populate users feeds post_time = timestamp() post = { '_id': post_id, # Newly created post id 'user_id': user_id, # User id of the poster 'username': username, # Username of the poster 'body': body, # Body of the post 'created': post_time, # Unix timestamp for this moment in time 'score': 0, # Atomic score counter } if reply_to is not None: # If the is a reply it must have this property post['reply_to'] = reply_to else: # Replies don't need a comment count post['comment_count'] = 0 # Set the permission a user needs to view post['permission'] = permission # TODO: Make the upload process better at dealing with issues if upload: # If there is an upload along with this post it needs to go for # processing. # process_upload() can throw an Exception of UploadError. We will let # it fall through as a 500 is okay I think. # TODO: Turn this in to a Queue task at some point filename = process_upload(upload) if filename is not None: # If the upload process was okay attach the filename to the doc post['upload'] = filename else: # Stop the image upload process here if something went wrong. return None # Process everything thats needed in a post links, mentions, hashtags = parse_post(body) # Only add the fields if we need too. if links: post['links'] = links if mentions: post['mentions'] = mentions if hashtags: post['hashtags'] = hashtags # Add the post to the database # If the post isn't stored, result will be None result = m.db.posts.insert(post) # Only carry out the rest of the actions if the insert was successful if result: if reply_to is None: # Add post to authors feed r.zadd(k.USER_FEED.format(user_id), post_time, post_id) # Ensure the feed does not grow to large r.zremrangebyrank(k.USER_FEED.format(user_id), 0, -1000) # Subscribe the poster to there post subscribe(user_id, post_id, SubscriptionReasons.POSTER) # Alert everyone tagged in the post alert_tagees(mentions, user_id, post_id) # Append to all followers feeds or approved followers based # on the posts permission if permission < k.PERM_APPROVED: populate_followers_feeds(user_id, post_id, post_time) else: populate_approved_followers_feeds(user_id, post_id, post_time) else: # To reduce database look ups on the read path we will increment # the reply_to's comment count. m.db.posts.update({'_id': reply_to}, {'$inc': {'comment_count': 1}}) # Alert all subscribers to the post that a new comment has been # added. We do this before subscribing anyone new alert = CommentingAlert(user_id, reply_to) subscribers = [] # Iterate through subscribers and let them know about the comment for subscriber_id in get_subscribers(reply_to): # Ensure we don't get alerted for our own comments if subscriber_id != user_id: subscribers.append(subscriber_id) # Push the comment alert out to all subscribers AlertManager().alert(alert, subscribers) # Subscribe the user to the post, will not change anything if they # are already subscribed subscribe(user_id, reply_to, SubscriptionReasons.COMMENTER) # Alert everyone tagged in the post alert_tagees(mentions, user_id, reply_to) return post_id # If there was a problem putting the post in to Mongo we will return None return None # pragma: no cover
def reversable_filter(vote): """The time voted `vote` has to be newer than VOTE_TIMEOUT """ vote = 0 if not vote else vote return abs(vote) + k.VOTE_TIMEOUT > timestamp()
def display_time(response): """This is will write the time to the console in DEBUG mode""" if app.debug and not app.testing: # pragma: no cover print timestamp() - g.start_time, 'secs' return response
def vote_post(user_id, post_id, amount=1, ts=None): """Handles voting on posts :param user_id: User who is voting :type user_id: str :param post_id: ID of the post the user is voting on :type post_id: int :param amount: The way to vote (-1 or 1) :type amount: int :param ts: Timestamp to use for vote (ONLY FOR TESTING) :type ts: int :returns: -1 if downvote, 0 if reverse vote and +1 if upvote """ if ts is None: ts = timestamp() # Get the comment so we can check who the author is author_uid = get_post(post_id).get('user_id') # Votes can ONLY ever be -1 or 1 and nothing else # we use the sign to store the time and score in one zset score amount = 1 if amount >= 0 else -1 voted = has_voted(user_id, post_id) if not voted: if author_uid != user_id: # Store the timestamp of the vote with the sign of the vote r.zadd(k.POST_VOTES.format(post_id), amount * timestamp(), user_id) # Update post score m.db.posts.update({'_id': post_id}, {'$inc': {'score': amount}}) # Update user score m.db.users.update({'_id': author_uid}, {'$inc': {'score': amount}}) return amount else: raise CantVoteOnOwn elif voted and abs(voted) + k.VOTE_TIMEOUT > ts: # No need to check if user is current user because it can't # happen in the first place # Remove the vote from Redis r.zrem(k.POST_VOTES.format(post_id), user_id) previous_vote = -1 if voted < 0 else 1 # Calculate how much to increment/decrement the scores by # Saves multiple trips to Mongo if amount == previous_vote: if previous_vote < 0: amount = 1 result = 0 else: amount = -1 result = 0 else: # We will only register the new vote if it is NOT a vote reversal. r.zadd(k.POST_VOTES.format(post_id), amount * timestamp(), user_id) if previous_vote < 0: amount = 2 result = 1 else: amount = -2 result = -1 # Update post score m.db.posts.update({'_id': post_id}, {'$inc': {'score': amount}}) # Update user score m.db.users.update({'_id': author_uid}, {'$inc': {'score': amount}}) return result else: raise AlreadyVoted