Beispiel #1
0
    def __init__(self, clients, managers=None):
        super().__init__(clients, managers=managers)
        managers = managers or {}
        managers['chat'] = self
        self.block_manager = managers.get('block') or models.BlockManager(
            clients, managers=managers)
        self.chat_message_manager = managers.get(
            'chat_message') or models.ChatMessageManager(clients,
                                                         managers=managers)
        self.user_manager = managers.get('user') or models.UserManager(
            clients, managers=managers)

        self.clients = clients
        self.real_dating_client = RealDatingClient()
        if 'dynamo' in clients:
            self.dynamo = ChatDynamo(clients['dynamo'])
            self.member_dynamo = ChatMemberDynamo(clients['dynamo'])
Beispiel #2
0
    def __init__(self, clients, managers=None):
        super().__init__(clients, managers=managers)
        managers = managers or {}
        managers['comment'] = self
        self.block_manager = managers.get('block') or models.BlockManager(
            clients, managers=managers)
        self.follower_manager = managers.get(
            'follower') or models.FollowerManager(clients, managers=managers)
        self.post_manager = managers.get('post') or models.PostManager(
            clients, managers=managers)
        self.user_manager = managers.get('user') or models.UserManager(
            clients, managers=managers)

        self.real_dating_client = RealDatingClient()
        # self.bad_words_client = BadWordsClient()
        if 'dynamo' in clients:
            self.dynamo = CommentDynamo(clients['dynamo'])
Beispiel #3
0
 def __init__(
     self,
     user_item,
     clients,
     dynamo=None,
     album_manager=None,
     block_manager=None,
     chat_manager=None,
     comment_manager=None,
     follower_manager=None,
     like_manager=None,
     post_manager=None,
     user_manager=None,
     email_dynamo=None,
     phone_number_dynamo=None,
     placeholder_photos_directory=S3_PLACEHOLDER_PHOTOS_DIRECTORY,
     frontend_resources_domain=CLOUDFRONT_FRONTEND_RESOURCES_DOMAIN,
     **kwargs,
 ):
     super().__init__(**kwargs)
     self.clients = clients
     for client_name in self.client_names:
         if client_name in clients:
             setattr(self, f'{client_name}_client', clients[client_name])
     if dynamo:
         self.dynamo = dynamo
     if album_manager:
         self.album_manager = album_manager
     if block_manager:
         self.block_manager = block_manager
     if chat_manager:
         self.chat_manager = chat_manager
     if comment_manager:
         self.comment_manager = comment_manager
     if follower_manager:
         self.follower_manager = follower_manager
     if like_manager:
         self.like_manager = like_manager
     if post_manager:
         self.post_manager = post_manager
     if user_manager:
         self.user_manager = user_manager
     if email_dynamo:
         self.email_dynamo = email_dynamo
     if phone_number_dynamo:
         self.phone_number_dynamo = phone_number_dynamo
     self.item = user_item
     self.id = user_item['userId']
     self.placeholder_photos_directory = placeholder_photos_directory
     self.frontend_resources_domain = frontend_resources_domain
     self.real_dating_client = RealDatingClient()
     self.redeem_promotion_client = RedeemPromotionClient()
Beispiel #4
0
class ChatManager(FlagManagerMixin, ViewManagerMixin, ManagerBase):

    item_type = 'chat'

    def __init__(self, clients, managers=None):
        super().__init__(clients, managers=managers)
        managers = managers or {}
        managers['chat'] = self
        self.block_manager = managers.get('block') or models.BlockManager(
            clients, managers=managers)
        self.chat_message_manager = managers.get(
            'chat_message') or models.ChatMessageManager(clients,
                                                         managers=managers)
        self.user_manager = managers.get('user') or models.UserManager(
            clients, managers=managers)

        self.clients = clients
        self.real_dating_client = RealDatingClient()
        if 'dynamo' in clients:
            self.dynamo = ChatDynamo(clients['dynamo'])
            self.member_dynamo = ChatMemberDynamo(clients['dynamo'])

    def get_model(self, item_id, strongly_consistent=False):
        return self.get_chat(item_id, strongly_consistent=strongly_consistent)

    def get_chat(self, chat_id, strongly_consistent=False):
        item = self.dynamo.get(chat_id,
                               strongly_consistent=strongly_consistent)
        return self.init_chat(item) if item else None

    def get_direct_chat(self, user_id_1, user_id_2):
        item = self.dynamo.get_direct_chat(user_id_1, user_id_2)
        return self.init_chat(item) if item else None

    def init_chat(self, chat_item):
        kwargs = {
            'dynamo': getattr(self, 'dynamo', None),
            'flag_dynamo': getattr(self, 'flag_dynamo', None),
            'member_dynamo': getattr(self, 'member_dynamo', None),
            'view_dynamo': getattr(self, 'view_dynamo', None),
            'block_manager': self.block_manager,
            'chat_manager': self,
            'chat_message_manager': self.chat_message_manager,
            'user_manager': self.user_manager,
        }
        return Chat(chat_item, **kwargs) if chat_item else None

    def add_direct_chat(self,
                        chat_id,
                        created_by_user_id,
                        with_user_id,
                        now=None):
        now = now or pendulum.now('utc')

        # can't direct chat with ourselves
        if created_by_user_id == with_user_id:
            raise ChatException(
                f'User `{created_by_user_id}` cannot open direct chat with themselves'
            )

        # can't chat if there's a blocking relationship, either direction
        if self.block_manager.is_blocked(created_by_user_id, with_user_id):
            raise ChatException(
                f'User `{created_by_user_id}` has blocked user `{with_user_id}`'
            )
        if self.block_manager.is_blocked(with_user_id, created_by_user_id):
            raise ChatException(
                f'User `{created_by_user_id}` has been blocked by `{with_user_id}`'
            )

        # can't add a chat if one already exists between the two users
        if self.get_direct_chat(created_by_user_id, with_user_id):
            raise ChatException(
                f'Chat already exists between user `{created_by_user_id}` and user `{with_user_id}`',
            )

        # can't chat with non-ACTIVE users
        with_user = self.user_manager.get_user(with_user_id)
        if with_user.status != UserStatus.ACTIVE:
            raise ChatException(
                f'Cannot open direct chat with user with status `{with_user.status}`'
            )

        # can't add a chat if the match status is rejected/approved
        if not self.validate_dating_match_chat(created_by_user_id,
                                               with_user_id):
            raise ChatException(
                'Cannot chat user viewed on dating unless it is a match')

        transacts = [
            self.dynamo.transact_add(
                chat_id,
                ChatType.DIRECT,
                created_by_user_id,
                with_user_id=with_user_id,
                now=now,
            ),
            self.member_dynamo.transact_add(chat_id,
                                            created_by_user_id,
                                            now=now),
            self.member_dynamo.transact_add(chat_id, with_user_id, now=now),
        ]
        transact_exceptions = [
            ChatException(
                f'Unable to add chat with id `{chat_id}`... id already used?'),
            ChatException(
                f'Unable to add user `{created_by_user_id}` to chat `{chat_id}`'
            ),
            ChatException(
                f'Unable to add user `{with_user_id}` to chat `{chat_id}`'),
        ]
        self.dynamo.client.transact_write_items(transacts, transact_exceptions)

        return self.get_chat(chat_id, strongly_consistent=True)

    def add_group_chat(self, chat_id, created_by_user, name=None, now=None):
        now = now or pendulum.now('utc')

        # create the group chat with just caller in it
        transacts = [
            self.dynamo.transact_add(chat_id,
                                     ChatType.GROUP,
                                     created_by_user.id,
                                     name=name,
                                     now=now),
            self.member_dynamo.transact_add(chat_id,
                                            created_by_user.id,
                                            now=now),
        ]
        transact_exceptions = [
            ChatException(
                f'Unable to add chat with id `{chat_id}`... id already used?'),
            ChatException(
                f'Unable to add user `{created_by_user.id}` to chat `{chat_id}`'
            ),
        ]
        self.dynamo.client.transact_write_items(transacts, transact_exceptions)

        self.chat_message_manager.add_system_message_group_created(
            chat_id, created_by_user, name=name, now=now)
        return self.get_chat(chat_id, strongly_consistent=True)

    def on_user_delete_leave_all_chats(self, user_id, old_item):
        user = self.user_manager.init_user(old_item)
        for chat_id in self.member_dynamo.generate_chat_ids_by_user(user_id):
            chat = self.get_chat(chat_id)
            if not chat:
                logger.warning(
                    f'Unable to find chat `{chat_id}` that user `{user_id}` is member of, ignoring'
                )
                continue
            if chat.type == ChatType.DIRECT:
                chat.delete()
            else:
                chat.leave(user)

    def record_views(self, chat_ids, user_id, viewed_at=None):
        for chat_id, view_count in dict(collections.Counter(chat_ids)).items():
            chat = self.get_chat(chat_id)
            if not chat:
                logger.warning(
                    f'Cannot record view(s) by user `{user_id}` on DNE chat `{chat_id}`'
                )
            elif not chat.is_member(user_id):
                logger.warning(
                    f'Cannot record view(s) by non-member user `{user_id}` on chat `{chat_id}`'
                )
            else:
                chat.record_view_count(user_id,
                                       view_count,
                                       viewed_at=viewed_at)

    def on_chat_message_add(self, message_id, new_item):
        message = self.chat_message_manager.init_chat_message(new_item)
        self.dynamo.update_last_message_activity_at(message.chat_id,
                                                    message.created_at)
        self.dynamo.increment_messages_count(message.chat_id)

        # for each memeber of the chat
        #   - update the last message activity timestamp (controls chat ordering)
        #   - for everyone except the author, increment their 'messagesUnviewedCount'
        for user_id in self.member_dynamo.generate_user_ids_by_chat(
                message.chat_id):
            self.member_dynamo.update_last_message_activity_at(
                message.chat_id, user_id, message.created_at)
            if user_id != message.user_id:
                # Note that dynamo has no support for batch updates.
                self.member_dynamo.increment_messages_unviewed_count(
                    message.chat_id, user_id)
                # TODO
                # we can be in a state where the user manually dismissed a card, and this view does not
                # change the user's overall count of chats with unread messages, but should still create a card

    def on_chat_message_delete(self, message_id, old_item):
        message = self.chat_message_manager.init_chat_message(old_item)
        self.dynamo.decrement_messages_count(message.chat_id)

        # for each memeber of the chat other than the author
        #   - delete any view record that exists directly on the message
        #   - determine if the message had status 'unviewed', and if so, then decrement the unviewed message counter
        for user_id in self.member_dynamo.generate_user_ids_by_chat(
                message.chat_id):
            if user_id != message.user_id:
                chat_view_item = self.view_dynamo.get_view(
                    message.chat_id, user_id)
                chat_last_viewed_at = pendulum.parse(
                    chat_view_item['lastViewedAt']) if chat_view_item else None
                if not (chat_last_viewed_at
                        and chat_last_viewed_at > message.created_at):
                    # Note that dynamo has no support for batch updates.
                    self.member_dynamo.decrement_messages_unviewed_count(
                        message.chat_id, user_id)

    def sync_member_messages_unviewed_count(self,
                                            chat_id,
                                            new_item,
                                            old_item=None):
        if new_item.get('viewCount', 0) > (old_item or {}).get('viewCount', 0):
            user_id = new_item['sortKey'].split('/')[1]
            self.member_dynamo.clear_messages_unviewed_count(chat_id, user_id)

    def on_flag_add(self, chat_id, new_item):
        chat_item = self.dynamo.increment_flag_count(chat_id)
        chat = self.init_chat(chat_item)

        # force delete the chat_message?
        if chat.is_crowdsourced_forced_removal_criteria_met():
            logger.warning(f'Force deleting chat `{chat_id}` from flagging')
            chat.delete()

    def on_chat_delete_delete_memberships(self, chat_id, old_item):
        for user_id in self.member_dynamo.generate_user_ids_by_chat(chat_id):
            self.member_dynamo.delete(chat_id, user_id)

    def validate_dating_match_chat(self, user_id, match_user_id):
        response_1 = json.loads(
            self.real_dating_client.match_status(
                user_id, match_user_id)['Payload'].read().decode())
        response_2 = json.loads(
            self.real_dating_client.match_status(
                user_id=match_user_id,
                match_user_id=user_id)['Payload'].read().decode())
        match_status_1 = response_1['status']
        match_status_2 = response_2['status']
        blockChatExpiredAt = response_1['blockChatExpiredAt']

        if match_status_1 != 'CONFIRMED' or match_status_2 != 'CONFIRMED':
            if (blockChatExpiredAt is not None
                    and pendulum.parse(blockChatExpiredAt) >
                    pendulum.now()):  # 30 days blocking chat
                return False
        return True
Beispiel #5
0
class CommentManager(FlagManagerMixin, ManagerBase):

    item_type = 'comment'

    def __init__(self, clients, managers=None):
        super().__init__(clients, managers=managers)
        managers = managers or {}
        managers['comment'] = self
        self.block_manager = managers.get('block') or models.BlockManager(
            clients, managers=managers)
        self.follower_manager = managers.get(
            'follower') or models.FollowerManager(clients, managers=managers)
        self.post_manager = managers.get('post') or models.PostManager(
            clients, managers=managers)
        self.user_manager = managers.get('user') or models.UserManager(
            clients, managers=managers)

        self.real_dating_client = RealDatingClient()
        # self.bad_words_client = BadWordsClient()
        if 'dynamo' in clients:
            self.dynamo = CommentDynamo(clients['dynamo'])

    def get_model(self, item_id):
        return self.get_comment(item_id)

    def get_comment(self, comment_id):
        comment_item = self.dynamo.get_comment(comment_id)
        return self.init_comment(comment_item) if comment_item else None

    def init_comment(self, comment_item):
        kwargs = {
            'dynamo': getattr(self, 'dynamo', None),
            'flag_dynamo': getattr(self, 'flag_dynamo', None),
            'block_manager': self.block_manager,
            'follower_manager': self.follower_manager,
            'post_manager': self.post_manager,
            'user_manager': self.user_manager,
        }
        return Comment(comment_item, **kwargs)

    def add_comment(self, comment_id, post_id, user_id, text, now=None):
        now = now or pendulum.now('utc')

        post = self.post_manager.get_post(post_id)
        if not post:
            raise CommentException(f'Post `{post_id}` does not exist')

        if post.item.get('commentsDisabled', False):
            raise CommentException(
                f'Comments are disabled on post `{post_id}`')

        if user_id != post.user_id:

            # can't comment if there's a blocking relationship, either direction
            if self.block_manager.is_blocked(post.user_id, user_id):
                raise CommentException(
                    f'Post owner `{post.user_id}` has blocked user `{user_id}`'
                )
            if self.block_manager.is_blocked(user_id, post.user_id):
                raise CommentException(
                    f'User `{user_id}` has blocked post owner `{post.user_id}`'
                )

            # if post owner is private, must be a follower to comment
            poster = self.user_manager.get_user(post.user_id)
            if poster.item['privacyStatus'] == UserPrivacyStatus.PRIVATE:
                follow = self.follower_manager.get_follow(
                    user_id, post.user_id)
                if not follow or follow.status != FollowStatus.FOLLOWING:
                    msg = f'Post owner `{post.user_id}` is private and user `{user_id}` is not a follower'
                    raise CommentException(msg)

            # can't add a comment if the match status is not CONFIRMED, only post type is IMAGE
            if post.type == PostType.IMAGE:
                if not self.validate_dating_match_comment(
                        user_id, post.user_id):
                    raise CommentException(
                        'Cannot add comment unless it is a confirmed match on dating'
                    )

        text_tags = self.user_manager.get_text_tags(text)
        comment_item = self.dynamo.add_comment(comment_id,
                                               post_id,
                                               user_id,
                                               text,
                                               text_tags,
                                               commented_at=now)
        return self.init_comment(comment_item)

    def clear_comment_bad_words(self):
        # scan for all comment texts and detect bad words
        for comment in self.dynamo.generate_all_comments_by_scan():
            self.on_comment_added_detect_bad_words(comment['commentId'],
                                                   comment)

    def on_user_delete_delete_all_by_user(self, user_id, old_item):
        for comment_item in self.dynamo.generate_by_user(user_id):
            self.init_comment(comment_item).delete()

    def delete_all_on_post(self, post_id):
        for comment_item in self.dynamo.generate_by_post(post_id):
            self.init_comment(comment_item).delete()

    def on_flag_add(self, comment_id, new_item):
        comment_item = self.dynamo.increment_flag_count(comment_id)
        comment = self.init_comment(comment_item)

        user_id = new_item['sortKey'].split('/')[1]
        flagger = self.user_manager.get_user(user_id)

        # force delete the comment?
        if (flagger.id == comment.post.user_id
                or flagger.username in self.flag_admin_usernames
                or comment.is_crowdsourced_forced_removal_criteria_met()):
            logger.warning(
                f'Force deleting comment `{comment_id}` from flagging')
            comment.delete(forced=True)

    def on_comment_added_detect_bad_words(self, comment_id, new_item):
        text = new_item['text']
        comment = self.init_comment(new_item)

        # if they are 2 way follow, skip bad words detection
        post = self.post_manager.get_post(comment.post_id)
        if comment.user_id != post.user_id:
            follow = self.follower_manager.get_follow(comment.user_id,
                                                      post.user_id)
            follow_back = self.follower_manager.get_follow(
                post.user_id, comment.user_id)

            if (follow and follow_back
                    and follow.status == FollowStatus.FOLLOWING
                    and follow_back.status == FollowStatus.FOLLOWING):
                return

        # if detects bad words, force delete the comment
        bad_words_client = BadWordsClient()
        if bad_words_client.validate_bad_words_detection(text):
            logger.warning(
                f'Force deleting comment `{comment_id}` from detecting bad words'
            )
            comment.delete(forced=True)

    def validate_dating_match_comment(self, user_id, match_user_id):
        response_1 = json.loads(
            self.real_dating_client.match_status(
                user_id, match_user_id)['Payload'].read().decode())
        response_2 = json.loads(
            self.real_dating_client.match_status(
                user_id=match_user_id,
                match_user_id=user_id)['Payload'].read().decode())
        match_status_1 = response_1['status']
        match_status_2 = response_2['status']
        # we can reference blockChatExpiredAt to block adding comment
        blockChatExpiredAt = response_1['blockChatExpiredAt']

        if match_status_1 != 'CONFIRMED' or match_status_2 != 'CONFIRMED':
            if (blockChatExpiredAt is not None
                    and pendulum.parse(blockChatExpiredAt) >
                    pendulum.now()):  # 30 days blocking comment
                return False
        return True