Пример #1
0
class ChatParticipant(CRUDMixin, db.Model):
    __tablename__ = 'chat_participant'

    chat_id = db.Column(
        db.ForeignKey(
            column='chat.id',
            name='fk_chat_participant_chat_id',
            onupdate='CASCADE',
            ondelete='CASCADE'
        ),
        primary_key=True
    )
    participant_id = db.Column(
        db.ForeignKey(
            column='user.id',
            name='fk_chat_participant_participant',
            onupdate='CASCADE',
            ondelete='CASCADE'
        ),
        primary_key=True
    )
    nickname = db.Column(db.String(255), nullable=True, unique=False)

    __table_args__ = (
        db.PrimaryKeyConstraint('chat_id', 'participant_id',
                                name='pk_chat_participant'),
    )

    @property
    def name(self):
        return self.nickname or self.participant.profile.name

    def to_public_json(self):
        return self.to_json(operations=[('difference', {'chat_id'})])
Пример #2
0
class GroupChat(CRUDMixin, db.Model):
    __tablename__ = 'group_chat'

    id = db.Column(
        db.ForeignKey(
            column='chat.id',
            name='fk_group_chat_chat_id',
            onupdate='CASCADE',
            ondelete='CASCADE'
        ),
        primary_key=True
    )
    name = db.Column(db.String(255), nullable=True, unique=False)
Пример #3
0
class MessageText(CRUDMixin, db.Model):
    __tablename__ = 'message_text'

    id = db.Column(db.Integer,
                   db.ForeignKey(column='message.id',
                                 name='fk_message_text_id',
                                 onupdate='CASCADE',
                                 ondelete='CASCADE'),
                   primary_key=True)
    content = db.Column(db.String(512), nullable=False, unique=False)

    __table_args__ = (db.CheckConstraint('char_length(content) > 0',
                                         name='cc_message_text_content'), )
Пример #4
0
class User(CRUDMixin, db.Model):
    __tablename__ = 'user'

    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    password = db.Column(db.String(255), nullable=False, unique=False)
    date_registered = db.Column(db.DateTime,
                                nullable=False,
                                unique=False,
                                server_default=func.now())
    date_activated = db.Column(db.DateTime, nullable=True, unique=False)

    PASSWORD_MINLEN = 8

    profile = db.relationship('UserProfile',
                              uselist=False,
                              backref='user',
                              cascade='all, delete',
                              passive_updates=True,
                              passive_deletes=True)
    conversations = db.relationship('ChatParticipant',
                                    uselist=True,
                                    backref='participant',
                                    cascade='all, delete',
                                    passive_updates=True,
                                    passive_deletes=True)
    messages = db.relationship('Message',
                               uselist=True,
                               backref='author',
                               cascade='all, delete',
                               passive_updates=True,
                               passive_deletes=True)

    @property
    def is_active(self):
        return self.date_activated is not None

    @classmethod
    def get_first_active(cls, **kwargs):
        first = cls.get_first(**kwargs)
        return first if (first and first.is_active) else None

    def check_password(self, password):
        return check_password_hash(self.password, password)

    def to_public_json(self):
        return self.to_json(operations=[('intersection',
                                         {'id', 'date_activated'})])
Пример #5
0
class Message(CRUDMixin, db.Model):
    __tablename__ = 'message'

    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    chat_id = db.Column(db.ForeignKey(column='chat.id',
                                      name='fk_message_chat_id',
                                      onupdate='CASCADE',
                                      ondelete='CASCADE'),
                        nullable=False,
                        unique=False)
    author_id = db.Column(db.ForeignKey(column='user.id',
                                        name='fk_message_author_id',
                                        onupdate='CASCADE',
                                        ondelete='CASCADE'),
                          nullable=False,
                          unique=False)
    timestamp = db.Column(db.DateTime,
                          nullable=False,
                          unique=False,
                          server_default=func.now())
    parent_id = db.Column(db.ForeignKey(column='message.id',
                                        name='fk_message_parent_id',
                                        onupdate='CASCADE',
                                        ondelete='CASCADE'),
                          nullable=True,
                          unique=False)

    children = db.relationship('Message',
                               uselist=True,
                               backref=db.backref('parent', remote_side=[id]),
                               cascade='all, delete',
                               passive_updates=True,
                               passive_deletes=True)
    text = db.relationship('MessageText',
                           uselist=False,
                           backref='message',
                           cascade='all, delete',
                           passive_updates=True,
                           passive_deletes=True)

    def to_json(self, operations=None):
        return super().to_json(operations) | {'timestamp': str(self.timestamp)}

    def to_public_json(self):
        return self.to_json(operations=[(
            'difference',
            {'chat_id'})]) | self.text.to_json() if self.text else {}
Пример #6
0
class DirectChat(CRUDMixin, db.Model):
    __tablename__ = 'direct_chat'

    id = db.Column(
        db.ForeignKey(
            column='chat.id',
            name='fk_direct_chat_chat_id',
            onupdate='CASCADE',
            ondelete='CASCADE'
        ),
        primary_key=True
    )
Пример #7
0
class JWTBlacklist(CRUDMixin, db.Model):
    __tablename__ = 'jwt_blacklist'

    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    audience = db.Column(db.Integer,
                         db.ForeignKey(column='user.id',
                                       name='fk_jwt_blacklist_audience',
                                       onupdate='CASCADE',
                                       ondelete='SET NULL'),
                         nullable=True,
                         unique=False)
    jti = db.Column(db.String(255), nullable=False, unique=True)
    token_type = db.Column(db.String(255), nullable=False, unique=False)
    issue_date = db.Column(db.DateTime, nullable=False, unique=False)
    expiration_date = db.Column(db.DateTime, nullable=False, unique=False)

    @classmethod
    def insert_if_not_exists(cls, decoded_token):
        return super().insert_if_not_exists(
            audience=decoded_token['identity'],
            jti=decoded_token['jti'],
            token_type=decoded_token['type'],
            issue_date=dt.fromtimestamp(decoded_token['iat']),
            expiration_date=dt.fromtimestamp(decoded_token['exp']))
Пример #8
0
class UserRelationship(CRUDMixin, db.Model):
    __tablename__ = 'user_relationship'

    user_a = db.Column(db.Integer,
                       db.ForeignKey(column='user.id',
                                     name='fk_user_relationship_user_a',
                                     onupdate='CASCADE',
                                     ondelete='CASCADE'),
                       primary_key=True)
    user_b = db.Column(db.Integer,
                       db.ForeignKey(column='user.id',
                                     name='fk_user_relationship_user_b',
                                     onupdate='CASCADE',
                                     ondelete='CASCADE'),
                       primary_key=True)
    relation = db.Column(db.String(2), nullable=True, unique=False)
    since = db.Column(db.DateTime, nullable=True, unique=False)

    __table_args__ = (db.PrimaryKeyConstraint('user_a',
                                              'user_b',
                                              name='pk_user_relation'),
                      db.CheckConstraint('user_a < user_b',
                                         name='cc_user_relation_pk'),
                      db.CheckConstraint(
                          f'relation IN ({UserRelation.to_quoted_csv_str()})',
                          name='cc_user_relation_relation'))

    @validates('user_a', 'user_b')
    def validates_user_id_pair(self, key, value):
        lhs = value if key == 'user_a' else self.user_a
        rhs = value if key == 'user_b' else self.user_b

        if lhs is not None and rhs is not None and not lhs < rhs:
            raise ValueError('Value of user_a must be less than user_b')

        return value

    @validates('relation')
    def validates(self, key, value):
        if value not in UserRelation.to_list():
            raise ValueError('Relation must be one of the following values: ' +
                             UserRelation.to_csv_str())

        return value

    @classmethod
    def sort_user_id_pair(cls, user_id_pair):
        return {
            'user_a': min(user_id_pair[0], user_id_pair[1]),
            'user_b': max(user_id_pair[0], user_id_pair[1])
        }

    @classmethod
    def resolve_friend_requester(cls, sorted_user_id_pair, current_user_id):
        if sorted_user_id_pair['user_a'] == current_user_id:
            return UserRelation.FRIEND_REQUEST_FROM_A_TO_B.value
        else:
            return UserRelation.FRIEND_REQUEST_FROM_B_TO_A.value

    @classmethod
    def resolve_friend_requestee(cls, sorted_user_id_pair, current_user_id):
        if sorted_user_id_pair['user_a'] == current_user_id:
            return UserRelation.FRIEND_REQUEST_FROM_B_TO_A.value
        else:
            return UserRelation.FRIEND_REQUEST_FROM_A_TO_B.value

    @classmethod
    def get_filtered(cls, **kwargs):
        user_id = kwargs.pop('user_id', None)
        user_id_pair = kwargs.pop('user_id_pair', None)
        status = kwargs.pop('status', None)
        values = deque()

        if user_id:
            values.appendleft(
                cls.query.filter_by(**kwargs).filter(
                    or_(cls.user_a == user_id, cls.user_b == user_id)))
            return values
        elif user_id_pair:
            sorted_user_id_pair = cls.sort_user_id_pair(user_id_pair)
            kwargs |= sorted_user_id_pair

            if status:
                current_user_id = status.get('current_user_id')
                assert isinstance(current_user_id, int)

                if status.get('requester'):
                    relation = cls.resolve_friend_requester(
                        sorted_user_id_pair, current_user_id)
                else:
                    relation = cls.resolve_friend_requestee(
                        sorted_user_id_pair, current_user_id)

                if status.get('insert'):
                    kwargs['relation'] = relation

                values.appendleft(relation)

            values.appendleft(sorted_user_id_pair)

        values.appendleft(cls.query.filter_by(**kwargs))
        return values

    @classmethod
    def get_first(cls, **kwargs):
        filtered, *args = cls.get_filtered(**kwargs)
        return (filtered.first(), *args)

    @classmethod
    def has_row(cls, **kwargs):
        filtered, *args = cls.get_filtered(**kwargs)
        return (filtered.scalar() is not None, *args)

    def other_user(self, user_id):
        id = self.user_a if self.user_a != user_id else self.user_b
        return User.get_first(id=id)

    def user_is_requester(self, user_id):
        p = 0b10 if self.user_a == user_id else 0b01
        q = UserRelation.to_binary(self.relation)
        return p & q

    def user_is_requestee(self, user_id):
        return not self.user_is_requester(user_id)

    def to_json(self, operations=None):
        return super().to_json(operations) | {'since': str(self.since)}
Пример #9
0
class Chat(CRUDMixin, db.Model):
    __tablename__ = 'chat'

    id = db.Column(db.Integer, primary_key=True, autoincrement=True)

    participants = db.relationship(
        'ChatParticipant',
        uselist=True,
        backref='chat',
        cascade='all, delete',
        passive_updates=True,
        passive_deletes=True
    )
    messages = db.relationship(
        'Message',
        uselist=True,
        order_by='asc(Message.timestamp)',
        backref='chat',
        cascade='all, delete',
        passive_updates=True,
        passive_deletes=True,
    )

    direct_chat = db.relationship(
        'DirectChat',
        uselist=False,
        backref='chat',
        cascade='all, delete',
        passive_updates=True,
        passive_deletes=True
    )
    group_chat = db.relationship(
        'GroupChat',
        uselist=False,
        backref='chat',
        cascade='all, delete',
        passive_updates=True,
        passive_deletes=True
    )

    @property
    def latest_message(self):
        try:
            return self.messages[-1]
        except IndexError:
            return None

    @classmethod
    def get_filtered(cls, **kwargs):
        user_id = kwargs.pop('user_id', None)
        participant_id_set = kwargs.pop('participant_id_set', None)

        if user_id is not None:
            return (
                cls.query.filter_by(**kwargs)
                .filter(cls.participants.any(
                    ChatParticipant.participant_id == user_id
                ))
            )
        elif participant_id_set:
            return (
                cls.query.filter_by(**kwargs)
                .join(ChatParticipant)
                .group_by(cls.id)
                .having(and_(
                    (
                        func.count(ChatParticipant.participant_id)
                        == len(participant_id_set)
                    ),
                    func.every(
                        ChatParticipant.participant_id.in_(participant_id_set)
                    )
                ))
            )
        else:
            return cls.query.filter_by(**kwargs)

    def has_participant(self, participant):
        for p in self.participants:
            if p.participant_id == participant.id:
                return True

        return False

    def resolve_name(self, **kwargs):
        if self.direct_chat or self.group_chat:
            if self.group_chat and self.group_chat.name:
                return self.group_chat.name

            current_user_id = kwargs.get('current_user_id')
            current_user_name = (
                'CityChat User' if self.direct_chat
                else 'CityChat Group'
            )

            names = []

            for p in self.participants:
                if p.participant_id != current_user_id:
                    names.append(p.name)
                else:
                    current_user_name = p.name

            return ', '.join(names) or current_user_name
        else:
            return 'Chat'

    def to_public_json(self, get_latest_message=False, **kwargs):
        json = {
            'chat': self.to_json() | {'name': self.resolve_name(**kwargs)},
            'participants': {
                p.participant_id: (
                    p.to_public_json()
                    | p.participant.profile.to_public_json()
                )
                for p in self.participants
            }
        }

        if get_latest_message:
            json |= {'latest_message': (
                self.latest_message.to_public_json()
                if self.latest_message else None
            )}

        return json