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'})])
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 {}
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 )
class UserProfile(CRUDMixin, db.Model): __tablename__ = 'user_profile' id = db.Column(db.Integer, db.ForeignKey(column='user.id', name='fk_user_profile_id', onupdate='CASCADE', ondelete='CASCADE'), primary_key=True) email = db.Column(db.String(254), nullable=False, unique=True) name = db.Column(db.String(255), nullable=False, unique=False) email_regex = r'[^@]+@[^@]+\.[^@]+' __table_args__ = (db.CheckConstraint(f"email ~ '{email_regex}'", name='cc_user_profile_email'), db.CheckConstraint('char_length(name) > 0', name='cc_user_profile_name')) @validates('email') def validate_email(self, key, value): if not value or not re.fullmatch(self.email_regex, value): raise ValueError('Please enter a valid email address') return value @classmethod def get_filtered(cls, **kwargs): email = kwargs.pop('email', None) if email: return (cls.query.filter_by(**kwargs).filter( cls.email.ilike(email))) else: return cls.query.filter_by(**kwargs) @classmethod def get_first_active(cls, **kwargs): first = cls.get_first(**kwargs) return first if (first and first.user.is_active) else None @classmethod def get_first_inactive(cls, **kwargs): first = cls.get_first(**kwargs) return first if (first and not first.user.is_active) else None @classmethod def get_active(cls): return cls.query.join(User).filter(User.date_activated.isnot(None)) def to_public_json(self): return self.to_json(operations=[('intersection', {'id', 'name'})])
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)
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'), )
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']))
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)}