def unapprove_user(who_uid, whom_uid): """Allow a user to un-approve a follower""" # Check the follower is actually approved if r.zrank(k.USER_APPROVED.format(who_uid), whom_uid) is None: return False # No alert for un-approved r.zrem(k.USER_APPROVED.format(who_uid), whom_uid) return True
def delete_account(user_id): """Will delete a users account. This **REMOVES ALL** details, posts, replies, etc. Not votes though. .. note: Ensure the user has authenticated this request. This is going to be the most *expensive* task in Pjuu, be warned. :param user_id: The `user_id` of the user to delete :type user_id: str """ # Delete the user from MongoDB m.db.users.remove({"_id": user_id}) # Remove all posts a user has ever made. This includes all votes # on the posts and all comments of the posts. # This calls the backend function from posts to do the deed posts_cursor = m.db.posts.find({"user_id": user_id}, {}) for post in posts_cursor: delete_post(post.get("_id")) # Remove all the following relationships from Redis # Delete all references to followers of the user. # This will remove the user from the other users following list # TODO Replace with ZSCAN follower_cursor = r.zrange(k.USER_FOLLOWERS.format(user_id), 0, -1) for follower_id in follower_cursor: # Clear the followers following list of the uid r.zrem(k.USER_FOLLOWING.format(follower_id), user_id) # Delete the followers list r.delete(k.USER_FOLLOWERS.format(user_id)) # Delete all references to the users the user is following # This will remove the user from the others users followers list # TODO Replace with ZSCAN followee_cursor = r.zrange(k.USER_FOLLOWING.format(user_id), 0, -1) for followee_id in followee_cursor: # Clear the followers list of people uid is following r.zrem(k.USER_FOLLOWERS.format(followee_id), user_id) # Delete the following list r.delete(k.USER_FOLLOWING.format(user_id)) # Delete the users feed, this may have been added too during this process. # Probably not but let's be on the safe side r.delete(k.USER_FEED.format(user_id)) # Delete the users alert list # DO NOT DELETE ANY ALERTS AS THESE ARE GENERIC r.delete(k.USER_ALERTS.format(user_id))
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 unfollow_user(who_uid, whom_uid): """Remove whom from who's following zset and who to whom's followers zset. """ # Check that we are actually following the users if r.zrank(k.USER_FOLLOWING.format(who_uid), whom_uid) is None: return False # Delete uid from who following and whom followers r.zrem(k.USER_FOLLOWING.format(who_uid), whom_uid) r.zrem(k.USER_FOLLOWERS.format(whom_uid), who_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 unfollow_user(who_uid, whom_uid): """Remove whom from who's following zset and who to whom's followers zset. """ # Check that we are actually following the users if r.zrank(k.USER_FOLLOWING.format(who_uid), whom_uid) is None: return False # Delete uid from who following and whom followers r.zrem(k.USER_FOLLOWING.format(who_uid), whom_uid) r.zrem(k.USER_FOLLOWERS.format(whom_uid), who_uid) # Delete the user from the approved list unapprove_user(whom_uid, who_uid) return True
def get_following(uid, page=1): """Returns a list of users uid is following as a pagination object.""" per_page = app.config.get('PROFILE_ITEMS_PER_PAGE') total = r.zcard(k.USER_FOLLOWING.format(uid)) fids = r.zrevrange(k.USER_FOLLOWING.format(uid), (page - 1) * per_page, (page * per_page) - 1) users = [] for fid in fids: user = get_user(fid) if user: users.append(user) else: # Self cleaning sorted sets r.zrem(k.USER_FOLLOWING.format(uid), fid) total = r.zcard(k.USER_FOLLOWING.format(id)) return Pagination(users, total, page, per_page)
def get_feed(user_id, page=1, per_page=None): """Returns all the posts in a users feed as a pagination object. .. note: The feed is stored inside Redis still as this requires fan-out to update all the users who are following you. """ if per_page is None: per_page = app.config.get('FEED_ITEMS_PER_PAGE') # Get the total number of item in the feed and the subset for the current # page. total = r.zcard(k.USER_FEED.format(user_id)) pids = r.zrevrange(k.USER_FEED.format(user_id), (page - 1) * per_page, (page * per_page) - 1) # Get all the posts in one call to MongoDB posts = [] cursor = m.db.posts.find({ '_id': { '$in': pids } }).sort('created', pymongo.DESCENDING) for post in cursor: posts.append(post) # Get a list of unique `user_id`s from all the post. user_ids = list(set([post.get('user_id') for post in posts])) cursor = m.db.users.find({'_id': {'$in': user_ids}}, {'avatar': True}) # Create a lookup dict `{username: email}` user_avatars = \ dict((user.get('_id'), user.get('avatar')) for user in cursor) # Add the e-mails to the posts processed_posts = [] for post in posts: post['user_avatar'] = user_avatars.get(post.get('user_id')) processed_posts.append(post) # Clean up the list in Redis if the if len(processed_posts) < len(pids): diff_pids = list( set(pids) - set([post.get('_id') for post in processed_posts])) r.zrem(k.USER_FEED.format(user_id), *diff_pids) return Pagination(processed_posts, total, page, per_page)
def get_feed(user_id, page=1, per_page=None): """Returns all the posts in a users feed as a pagination object. .. note: The feed is stored inside Redis still as this requires fan-out to update all the users who are following you. """ if per_page is None: per_page = app.config.get('FEED_ITEMS_PER_PAGE') # Get the total number of item in the feed and the subset for the current # page. total = r.zcard(k.USER_FEED.format(user_id)) pids = r.zrevrange(k.USER_FEED.format(user_id), (page - 1) * per_page, (page * per_page) - 1) # Get all the posts in one call to MongoDB posts = [] cursor = m.db.posts.find({'_id': {'$in': pids}}).sort( 'created', pymongo.DESCENDING) for post in cursor: posts.append(post) # Get a list of unique `user_id`s from all the post. user_ids = list(set([post.get('user_id') for post in posts])) cursor = m.db.users.find({'_id': {'$in': user_ids}}, {'avatar': True}) # Create a lookup dict `{username: email}` user_avatars = \ dict((user.get('_id'), user.get('avatar')) for user in cursor) # Add the e-mails to the posts processed_posts = [] for post in posts: post['user_avatar'] = user_avatars.get(post.get('user_id')) processed_posts.append(post) # Clean up the list in Redis if the if len(processed_posts) < len(pids): diff_pids = list( set(pids) - set([post.get('_id') for post in processed_posts])) r.zrem(k.USER_FEED.format(user_id), *diff_pids) return Pagination(processed_posts, total, page, per_page)
def get_following(uid, page=1, per_page=None): """Returns a list of users uid is following as a pagination object.""" if per_page is None: per_page = app.config.get('FEED_ITEMS_PER_PAGE') total = r.zcard(k.USER_FOLLOWING.format(uid)) fids = r.zrevrange(k.USER_FOLLOWING.format(uid), (page - 1) * per_page, (page * per_page) - 1) users = [] for fid in fids: user = get_user(fid) if user: users.append(user) else: # Self cleaning sorted sets r.zrem(k.USER_FOLLOWING.format(uid), fid) total = r.zcard(k.USER_FOLLOWING.format(id)) return Pagination(users, total, page, per_page)
def get_followers(uid, page=1, per_page=None): """Returns a list of users who follow user with uid as a pagination object. """ if per_page is None: per_page = app.config.get('FEED_ITEMS_PER_PAGE') total = r.zcard(k.USER_FOLLOWERS.format(uid)) fids = r.zrevrange(k.USER_FOLLOWERS.format(uid), (page - 1) * per_page, (page * per_page) - 1) users = [] for fid in fids: user = get_user(fid) if user: users.append(user) else: # Self cleaning sorted sets r.zrem(k.USER_FOLLOWERS.format(uid), fid) total = r.zcard(k.USER_FOLLOWERS.format(uid)) return Pagination(users, total, page, per_page)
def get_feed(user_id, page=1): """Returns a users feed as a pagination object. .. note: The feed is stored inside Redis still as this requires fan-out to update all the users who are following you. """ per_page = app.config.get('FEED_ITEMS_PER_PAGE') total = r.zcard(k.USER_FEED.format(user_id)) pids = r.zrevrange(k.USER_FEED.format(user_id), (page - 1) * per_page, (page * per_page) - 1) posts = [] for pid in pids: # Get the post post = get_post(pid) if post: posts.append(post) else: # Self cleaning lists r.zrem(k.USER_FEED.format(user_id), pid) total = r.zcard(k.USER_FEED.format(user_id)) return Pagination(posts, total, page, per_page)
def unsubscribe(user_id, post_id): """Unsubscribe a user from a post. """ # Actually remove the uid from the subscribers list return bool(r.zrem(k.POST_SUBSCRIBERS.format(post_id), user_id))
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
def delete_alert(user_id, alert_id): """Removes an alert with aid from user with uid's alert feed. This does not delete the alert object, it may be on other users feeds. """ return bool(r.zrem(k.USER_ALERTS.format(user_id), alert_id))
def delete_account(uid): """Will delete a users account. This _MUST_ _REMOVE_ _ALL_ details, comments, posts, etc. Note: Ensure the user has authenticated this request. This is going to be the most _expensive_ task in Pjuu, be warned. """ # Get some information from the hashes to delete lookup keys username = r.hget(K.USER.format(uid), 'username') email = r.hget(K.USER.format(uid), 'email') # Clear the users lookup keys and user account. These are not needed pipe = r.pipeline() # Delete lookup keys. This will stop the user being found or logging in pipe.set(K.UID_USERNAME.format(username), K.NIL_VALUE) pipe.expire(K.UID_USERNAME.format(username), K.EXPIRE_SECONDS) pipe.set(K.UID_EMAIL.format(email), K.NIL_VALUE) pipe.expire(K.UID_EMAIL.format(email), K.EXPIRE_SECONDS) # Delete user account pipe.delete(K.USER.format(uid)) pipe.execute() # Remove all posts a user has ever made. This includes all votes # on that post and all comments. pids = r.lrange(K.USER_POSTS.format(uid), 0, -1) for pid in pids: # Delete post r.delete(K.POST.format(pid)) # Delete all the votes made on the post r.delete(K.POST_VOTES.format(pid)) # Delete posts subscribers list r.delete(K.POST_SUBSCRIBERS.format(pid)) cids = r.lrange(K.POST_COMMENTS.format(pid), 0, -1) for cid in cids: # Get author, ensure uid is an int cid_author = r.hget(K.COMMENT.format(cid), 'uid') # Delete comment r.delete(K.COMMENT.format(cid)) # Delete comment votes r.delete(K.COMMENT_VOTES.format(cid)) # Remove the cid from users comment list # This may remove some of ours. This will just make deleting # a bit quicker r.lrem(K.USER_COMMENTS.format(cid_author), 0, cid) # Delete the comments list r.delete(K.POST_COMMENTS.format(pid)) # Delete the users post list r.delete(K.USER_POSTS.format(uid)) # Delete all comments the user has every made. Including all votes on # those comments # This is a stripped down version of above for post comments. # We are not going to clean the lists related to the posts, they will # self clean. We also do not need to clear the comments from the users # comments list as it will be getting deleted straight after cids = r.lrange(K.USER_COMMENTS.format(uid), 0, -1) for cid in cids: # Get author, ensure uid is an int cid_author = r.hget(K.COMMENT.format(cid), 'uid') # Delete comment r.delete(K.COMMENT.format(cid)) # Delete comment votes r.delete(K.COMMENT_VOTES.format(cid)) # Delete the comments list r.delete(K.USER_COMMENTS.format(uid)) # Delete all references to followers of the the user. # This will remove the user from the other users following list fids = r.zrange(K.USER_FOLLOWERS.format(uid), 0, -1) for fid in fids: # Clear the followers following list of the uid r.zrem(K.USER_FOLLOWING.format(fid), uid) # Delete the followers list r.delete(K.USER_FOLLOWERS.format(uid)) # Delete all references to the users the user is following # This will remove the user from the others users followers list fids = r.zrange(K.USER_FOLLOWING.format(uid), 0, -1) for fid in fids: # Clear the followers list of people uid is following r.zrem(K.USER_FOLLOWERS.format(fid), uid) # Delete the following list r.delete(K.USER_FOLLOWING.format(uid)) # Finally delete the users feed, this may have been added too during this # process. Probably not but let's be on the safe side r.delete(K.USER_FEED.format(uid)) # Delete the users alert list # DO NOT DELETE ANY ALERTS AS THESE ARE GENERIC r.delete(K.USER_ALERTS.format(uid))
def delete_account(user_id): """Will delete a users account. This **REMOVES ALL** details, posts, replies, etc. Not votes though. .. note: Ensure the user has authenticated this request. This is going to be the most *expensive* task in Pjuu, be warned. :param user_id: The `user_id` of the user to delete :type user_id: str """ # Get the user object we will need this to remove the avatar user = get_user(user_id) # Delete the user from MongoDB m.db.users.remove({'_id': user_id}) # If the user has an avatar remove it if user.get('avatar'): delete_upload(user.get('avatar')) # Remove all posts a user has ever made. This includes all votes # on the posts and all comments of the posts. # This calls the backend function from posts to do the deed posts_cursor = m.db.posts.find({'user_id': user_id}, {}) for post in posts_cursor: delete_post(post.get('_id')) # Remove all the following relationships from Redis # Delete all references to followers of the user. # This will remove the user from the other users following list # TODO Replace with ZSCAN follower_cursor = r.zrange(k.USER_FOLLOWERS.format(user_id), 0, -1) for follower_id in follower_cursor: # Clear the followers following list of the uid r.zrem(k.USER_FOLLOWING.format(follower_id), user_id) # Delete the followers list r.delete(k.USER_FOLLOWERS.format(user_id)) # Delete all references to the users the user is following # This will remove the user from the others users followers list # TODO Replace with ZSCAN followee_cursor = r.zrange(k.USER_FOLLOWING.format(user_id), 0, -1) for followee_id in followee_cursor: # Clear the followers list of people uid is following r.zrem(k.USER_FOLLOWERS.format(followee_id), user_id) # Delete the following list r.delete(k.USER_FOLLOWING.format(user_id)) # Delete the users feed, this may have been added too during this process. # Probably not but let's be on the safe side r.delete(k.USER_FEED.format(user_id)) # Delete the users alert list # DO NOT DELETE ANY ALERTS AS THESE ARE GENERIC r.delete(k.USER_ALERTS.format(user_id))
def remove_from_feed(post_id, user_id): """Remove ``post_id`` from ``user_id``s feed.""" return bool(r.zrem(k.USER_FEED.format(user_id), post_id))