def back_feed(who_id, whom_id): """Takes 5 lastest posts from user with ``who_id`` places them in user with ``whom_id`` feed. The reason behind this is that new users may follow someone but still have and empty feed, which makes them sad :( so we'll give them some. If the posts are to old for a non user they will be removed when the feed is trimmed, but they may make it in to the feed but not at the top. :param who_id: user who just followed ``who_id`` :type who_id: str :param whom_id: user who was just followed by ``whom_id`` :type whom_id: str :returns: None """ # Get followee's last 5 un-approved posts (doesn't matter if isn't any) # We only need the IDs and the created time posts = m.db.posts.find( {'user_id': whom_id, 'reply_to': None, 'permission': {'$lte': k.PERM_PJUU}}, {'_id': True, 'created': True}, ).sort('created', -1).limit(5) # Iterate the cursor and append the posts to the users feed for post in posts: timestamp = post.get('created') post_id = post.get('_id') # Place on the feed r.zadd(k.USER_FEED.format(who_id), timestamp, post_id) # Trim the feed to the 1000 max r.zremrangebyrank(k.USER_FEED.format(who_id), 0, -1000)
def populate_approved_followers_feeds(user_id, post_id, timestamp): """Fan out a post_id to all the users approved followers.""" # Get a list of ALL users who are following a user followers = r.zrange(k.USER_APPROVED.format(user_id), 0, -1) # This is not transactional as to not hold Redis up. for follower_id in followers: # Add the pid to the list r.zadd(k.USER_FEED.format(follower_id), timestamp, post_id) # Stop followers feeds from growing to large, doesn't matter if it # doesn't exist r.zremrangebyrank(k.USER_FEED.format(follower_id), 0, -1000)
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