Ejemplo n.º 1
0
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)
Ejemplo n.º 2
0
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)
Ejemplo n.º 3
0
def alert_tagees(tagees, user_id, post_id):
    """Creates a new tagging alert from `user_id` and `post_id` and alerts all
    in the `tagees` list.

    This will take the tagees processed as `mentions`, it will ensure no
    duplication and that the poster is not alerted if they tag themselves.

    :type tagees: list
    :type user_id: str
    :type post_id: str

    """
    alert = TaggingAlert(user_id, post_id)

    seen_user_ids = []
    for tagee in tagees:
        tagged_user_id = tagee.get('user_id')

        # Don't alert users more than once
        if tagged_user_id in seen_user_ids:
            continue

        # Don't alert posting user to tag
        if tagged_user_id == user_id:
            continue

        # Subscribe the tagee to the post won't change anything if they are
        # already subscribed
        subscribe(tagged_user_id, post_id, SubscriptionReasons.TAGEE)

        seen_user_ids.append(tagged_user_id)

    # Get an alert manager to notify all tagees
    AlertManager().alert(alert, seen_user_ids)
Ejemplo n.º 4
0
    def test_alertmanager(self):
        """
        Test the alert manager.

        Similar to the above a very simple test. Will check that it can alert
        users and one can be created.
        """
        # Create our alert manager
        am = AlertManager()

        # Try and load a non-existant alert
        self.assertIsNone(am.get(k.NIL_VALUE))

        # Try and alert on something which is not an alert
        self.assertRaises(ValueError, lambda: am.alert('ALERT', k.NIL_VALUE))

        # Test that alerting a single users does not work, they need to be
        # iterable
        # Create an alert
        alert = BaseAlert(k.NIL_VALUE)
        self.assertRaises(TypeError, lambda: am.alert(alert, 1))

        # Create a couple of users
        user1 = create_account('user1', '*****@*****.**', 'Password')
        user2 = create_account('user2', '*****@*****.**', 'Password')

        # Ensure the length of user1's alert feed is 0
        self.assertEqual(r.zcard(k.USER_ALERTS.format(user1)), 0)

        # Create an alert from user2
        alert = BaseAlert(user2)
        # Alert user1
        am.alert(alert, [user1])

        # Ensure the length of user1's alert feed is 1
        self.assertEqual(r.zcard(k.USER_ALERTS.format(user1)), 1)

        # Get alerts for user1, user Redis directly
        alerts = r.zrange(k.USER_ALERTS.format(user1), 0, 0)
        # Get the alert from alerts
        alert = am.get(alerts[0])
        self.assertIsNotNone(alert)
        self.assertEqual(alert.user.get('username'), 'user2')
        self.assertEqual(alert.user.get('email'), '*****@*****.**')

        # Delete test2 and ensure getting the alert returns None
        delete_account(user2)
        alert = am.get(alerts[0])
        self.assertIsNone(alert)
Ejemplo n.º 5
0
    def test_alertmanager(self):
        """
        Test the alert manager.

        Similar to the above a very simple test. Will check that it can alert
        users and one can be created.
        """
        # Create our alert manager
        am = AlertManager()

        # Try and load a non-existant alert
        self.assertIsNone(am.get(k.NIL_VALUE))

        # Try and alert on something which is not an alert
        self.assertRaises(ValueError, lambda: am.alert('ALERT', k.NIL_VALUE))

        # Test that alerting a single users does not work, they need to be
        # iterable
        # Create an alert
        alert = BaseAlert(k.NIL_VALUE)
        self.assertRaises(TypeError, lambda: am.alert(alert, 1))

        # Create a couple of users
        user1 = create_account('user1', '*****@*****.**', 'Password')
        user2 = create_account('user2', '*****@*****.**', 'Password')

        # Ensure the length of user1's alert feed is 0
        self.assertEqual(r.zcard(k.USER_ALERTS.format(user1)), 0)

        # Create an alert from user2
        alert = BaseAlert(user2)
        # Alert user1
        am.alert(alert, [user1])

        # Ensure the length of user1's alert feed is 1
        self.assertEqual(r.zcard(k.USER_ALERTS.format(user1)), 1)

        # Get alerts for user1, user Redis directly
        alerts = r.zrange(k.USER_ALERTS.format(user1), 0, 0)
        # Get the alert from alerts
        alert = am.get(alerts[0])
        self.assertIsNotNone(alert)
        self.assertEqual(alert.user.get('username'), 'user2')
        self.assertEqual(alert.user.get('email'), '*****@*****.**')

        # Delete test2 and ensure getting the alert returns None
        delete_account(user2)
        alert = am.get(alerts[0])
        self.assertIsNone(alert)
Ejemplo n.º 6
0
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
Ejemplo n.º 7
0
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