class Post(db.Model): # __tablename__ = 'posts' id = db.Column(db.Integer, primary_key=True, autoincrement=True) author_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) comments = db.relationship('Comment', backref='Post', lazy=True) likes = db.relationship('Like', backref='Post', lazy=True) tags = db.relationship( 'Tag', secondary=tags, lazy='subquery', backref=db.backref('posts', lazy=True), ) title = db.Column(db.String, unique=False) desc = db.Column(db.Text, unique=False) # TODO:Check limit and create character limit? s3_name = db.Column(db.String, unique=True, nullable=False) # TODO replace this with a method to return url with s3 name? # s3_url = db.Column(db.String, unique=True, nullable=False) def s3_url(self): return f"https://{s3.bucket_name}.s3.amazonaws.com/{self.s3_name}" def resp_dict(self): get_user = lambda id: User.query.get(id) post_author = User.query.get(self.author_id) return { 'id': self.id, 'title': self.title, 'author_id': self.author_id, # 's3_url': self.s3_url, 's3_url': self.s3_url(), 'description': self.desc, 'author_username': post_author.username, 'author_avi': post_author.avi_url(), 'comments': [ { 'body': comment.body, 'author_id': comment.author_id, 'avi': get_user(comment.author_id).avi_url(), 'username': get_user(comment.author_id).username, } for comment in self.comments ], 'likes': [ { 'value': like.value, 'user_id': like.user_id, 'avi': get_user(like.user_id).avi_url(), 'username': get_user(like.user_id).username } for like in self.likes ], 'tags': [tag.name for tag in self.tags] }
class PlotModel(db.Model): __tablename__ = 'plot' id = db.Column(db.Integer, primary_key=True) cpm = db.Column(db.String(30)) values = db.Column(db.JSON) data = db.Column(db.JSON) user_id = db.Column(db.Integer, db.ForeignKey('user.id')) user = db.relationship('UserModel') # type: src.UserModel def __init__(self, cpm: str, values, data, user_id): self.cpm = cpm self.values = values self.data = data self.user_id = user_id def json(self): return { 'plot': { 'user': self.user.username, 'cpm': self.cpm, 'values': self.values, 'data': self.data } } def save(self): db.session.add(self) db.session.commit() @classmethod def find_by_id(cls, _id) -> PlotModel: return cls.query.get(_id)
class Recipe(db.Model): id = db.Column(db.Integer, primary_key=True) user_id = db.Column(db.Integer, db.ForeignKey('user.id')) name = db.Column(db.String(80)) ingredients = db.relationship('Ingredients', backref='recipe', lazy='dynamic') imageUrl = db.Column(db.String(80)) created = db.Column(db.DateTime) def __init__(self, user_id, name, ingredients, imageUrl, created=None): self.user_id = user_id self.name = name self.ingredients = [] for ing in ingredients: if 'name' in name: name = ing.name if name is None: name = ing.get('name') self.ingredients.append(Ingredients(self.id, name)) if created is None: created = datetime.utcnow() self.created = created self.imageUrl = imageUrl def __repr__(self): return '<Recipe: user_id: %s, name: %s, imageUrl: %s>' % ( self.user_id, self.name, self.imageUrl)
class User(Model): id = Column(db.Integer, primary_key=True) username = Column(db.String(80), nullable=False) email = Column(db.String(128), unique=True, nullable=False) password_hash = Column(db.String(128), nullable=False) confirmed = Column(db.Boolean, default=False) todos = db.relationship(Todo, backref='user', lazy=True) created_at = Column(db.DateTime, nullable=False, default=datetime.utcnow) def set_password(self, password): print('hello') if len(password) < 8 or len(password) > 50: raise AssertionError( 'Password must be between 8 and 50 characters') self.password_hash = generate_password_hash(password) def check_password(self, password): return check_password_hash(self.password_hash, password) def save(self): db.session.add(self) db.session.commit() def __repr__(self): return f'<User {self.username}>'
class User(db.Model): id = db.Column(db.Integer, primary_key=True) username = db.Column(db.String(80)) email = db.Column(db.String(80)) password = db.Column(db.String(200)) created = db.Column(db.DateTime) recipes = db.relationship('Recipe', backref='user', lazy='dynamic') token = db.Column(db.String(250)) def hash_password(self, password): self.password = pwd_context.encrypt(password) def verify_password(self, password): return pwd_context.verify(password, self.password) def generate_auth_token( self, expiration=(60 * 10)): # 60 * x mins - will probably want to increase this # Check if valid token already exists, otherwise we can generate a new one if self.token is not None: token_state = User.verify_auth_token(self.token) if token_state is "Valid": log.info("Token is valid...returning") return self.token s = Serializer(secret_key, expires_in=expiration) self.token = s.dumps({'id': self.id}) return self.token def getID(self): return self.id @staticmethod def verify_auth_token(token): s = Serializer(secret_key) try: data = s.loads(token) except SignatureExpired: return "Expired" except BadSignature: return "Invalid" return "Valid" def __init__(self, username, email, password, recipes=[], created=None): self.username = username self.email = email self.password = pwd_context.encrypt(password) self.recipes = recipes if created is None: created = datetime.utcnow() self.created = created self.token = self.generate_auth_token() def __repr__(self): return '<User: id: %s, username: %s, email: %s, password: %s, token: %s>' % ( self.id, self.username, self.email, self.password, self.token)
class User(db.Model): __tablename__ = "users" id = db.Column(db.String(64), primary_key=True, nullable=False, unique=True) pseudo = db.Column(db.String(64), primary_key=True, nullable=False) creation_date = db.Column(db.DateTime, nullable=False) update_date = db.Column(db.DateTime, nullable=True) tweets = db.relationship('Tweet', back_populates="user")
class Tweet(db.Model): __tablename__ = "tweets" id = db.Column(db.String(64), primary_key=True, nullable=False, unique=True) message = db.Column(db.String(280), nullable=False) creation_date = db.Column(db.DateTime, nullable=False) update_date = db.Column(db.DateTime, nullable=True) user_id = db.Column(db.String(64), db.ForeignKey(user.User.id)) user = db.relationship('User', back_populates="tweets")
class Todos(db.Model): __table_name__ = "todos" id = db.Column(db.Integer, primary_key=True) title = db.Column(db.String(50)) contents = db.Column(db.String(200)) created_at = db.Column(db.DateTime, server_default=func.now()) updated_at = db.Column(db.DateTime, onupdate=func.now()) user_id = db.Column(db.Integer, db.ForeignKey("user.id"), nullable=False) user = db.relationship("User", backref="todos", lazy=True) # 객체 이름을 받는다.
class Chat(db.Model): id = db.Column(db.Integer, primary_key=True, autoincrement=True) room_name = db.Column(db.String, nullable=False, unique=True) messages = db.relationship( 'Message', secondary=messages, lazy='subquery', backref=db.backref('messages', lazy=True), ) users = db.relationship( 'User', secondary=chats, lazy='subquery', backref=db.backref('users', lazy=True), ) def resp_dict(self, exceptID=None): return { 'roomName': self.room_name, 'users': [user.resp_dict(follows=False) for user in self.users if user.id != exceptID], 'messages': [message.resp_dict() for message in self.messages] } def __repr__(self): return f"<Chat id:{self.id} name:{self.name}>"
class UserModel(db.Model): __tablename__ = 'user' id = db.Column(db.Integer, primary_key=True) username = db.Column(db.String(30), unique=True) password = db.Column(db.String(30)) plots = db.relationship('PlotModel', lazy='dynamic') # type: list def __init__(self, username, password): self.username = username self.password = password def json(self): return { 'user': self.username, 'is_admin': (self.is_admin() is not None), 'plots': [plot.id for plot in self.plots] } def save(self): db.session.add(self) db.session.commit() def is_admin(self): return AdminModel.find_by_id(self.id) @classmethod def find_by_username(cls, username) -> UserModel: return cls.query.filter_by(username=username).first() @classmethod def find_by_id(cls, _id) -> UserModel: return cls.query.get(_id) @classmethod def set_master_admin(cls, password, access_level) -> bool: _LOGGER.debug("Checking master admin..") master_admin = UserModel.find_by_username("admin") if not master_admin: _LOGGER.debug("No master admin set, creating new..") user = UserModel("admin", password) user.save() master_admin = AdminModel(user.id, access_level) master_admin.save() _LOGGER.debug(f"Admin 'admin' set with password '{password}'.") return True else: _LOGGER.debug("Already set.") return False
class Chat(db.Model): """ Telegram chat. One user can have multiple chats. """ __tablename__ = "chats" # Columns id = db.Column(db.Integer, primary_key=True) telegram_id = db.Column( db.BigInteger, unique=True, nullable=False, comment="Unique ID to identificate chat in Telegram") type = db.Column(db.Enum(ChatType), nullable=False, comment="Type of Telegram chat") user_id = db.Column( db.Integer, db.ForeignKey("users.id"), nullable=False, comment="Through this chat message can be sent to this user") # Relationships user = db.relationship("User", back_populates="chats", uselist=False) def __repr__(self): return f"<Chat {self.id}>" @staticmethod def create_fake(user): """ Creates fake chat. :param user: User to associate created chat with. """ from faker import Faker fake = Faker() random_number = fake.pyint(min_value=1, max_value=10, step=1) result = Chat(user=user) result.telegram_id = fake.pyint(min_value=10000000, max_value=10000000000, step=1) result.type = (fake.random_element(list(ChatType)) if (random_number % 10 == 0) else ChatType.PRIVATE) return result
class AdminModel(db.Model): __tablename__ = 'admin' user_id = db.Column(db.Integer, db.ForeignKey('user.id'), primary_key=True) access_level = db.Column(db.Integer, nullable=False) user = db.relationship('UserModel') def __init__(self, user_id, access_level): self.user_id = user_id self.access_level = access_level def json(self): return {'admin': self.user.username, 'access_level': self.access_level} def save(self): db.session.add(self) db.session.commit() @classmethod def find_by_id(cls, user_id) -> AdminModel: return cls.query.get(user_id)
class User(db.Model): """ Telegram user. """ __tablename__ = "users" # Columns id = db.Column(db.Integer, primary_key=True) create_date = db.Column(db.DateTime(timezone=False), server_default=func.now(), nullable=False) last_update_date = db.Column(db.DateTime(timezone=False), server_default=func.now(), onupdate=func.now(), nullable=False) telegram_id = db.Column( db.Integer, unique=True, nullable=False, comment="Unique ID to identificate user in Telegram") is_bot = db.Column(db.Boolean, nullable=False, comment="User is bot in Telegram") language = db.Column(db.Enum(SupportedLanguages), default=SupportedLanguages.EN, nullable=False, comment="Preferred language of user") group = db.Column(db.Enum(UserGroup), default=UserGroup.USER, nullable=False, comment="User rights group") # Relationships chats = db.relationship("Chat", back_populates="user", uselist=True) yandex_disk_token = db.relationship("YandexDiskToken", back_populates="user", uselist=False) def __repr__(self): return f"<User {self.id}>" @staticmethod def create_fake(): """ Creates fake user. """ from faker import Faker fake = Faker() random_number = fake.pyint(min_value=1, max_value=20, step=1) result = User() result.create_date = fake.date_time_between(start_date="-2y", end_date="now", tzinfo=None) result.last_update_date = fake.date_time_between_dates( datetime_start=result.create_date, tzinfo=None) result.telegram_id = fake.pyint(min_value=100000, max_value=10000000, step=1) result.is_bot = (fake.pyint() % 121 == 0) result.language = fake.random_element(list(SupportedLanguages)) result.group = (fake.random_element(list(UserGroup)) if (random_number % 20 == 0) else UserGroup.USER) return result
class User(db.Model): # __tablename__ = "users" id = db.Column(db.Integer, primary_key=True) email = db.Column(db.String(25), unique=True, nullable=False) username = db.Column(db.String(255), unique=True, nullable=False) password = db.Column(db.String(255), nullable=False) last_ip = db.Column(db.String(255), nullable=True) created = db.Column(db.DateTime, nullable=False) admin = db.Column(db.Boolean, nullable=False, default=False) posts = db.relationship('Post', backref='author', lazy=True) comments = db.relationship('Comment', backref='author', lazy=True) likes = db.relationship('Like', backref='user', lazy=True) bio = db.Column(db.Text, nullable=True) avi_s3_name = db.Column(db.String, unique=True, nullable=True) chats = db.relationship( 'Chat', secondary=chats, lazy='subquery', backref=db.backref('chats', lazy=True) ) def __init__(self, email, username, password, admin=False): self.email = email self.username = username self.password = bcrypt.generate_password_hash( password, current_app.config.get('BCRYPT_LOG_ROUNDS') ).decode() self.created = datetime.utcnow() self.admin = admin def __repr__(self): return f"<User id: {self.id} username: '******'>" def avi_url(self): url = f"https://{s3.bucket_name}.s3.amazonaws.com/{self.avi_s3_name}" if self.avi_s3_name else None return url def resp_dict(self, follows=True, include_private=False): user = { 'id': self.id, 'username': self.username, 'created': str(self.created), 'bio': self.bio, 'avi': self.avi_url(), } if follows: user['following'] = [ user.resp_dict(follows=False) for _, user in UserFollow.query.join( User, UserFollow.followed_id == User.id ).filter( UserFollow.follower_id == self.id ).add_entity( User ).all() ] if include_private: # TODO: extra information to supply to own user? pass return user def encode_auth_token(self, user_id): try: payload = { 'exp': datetime.utcnow() + timedelta(days=0, minutes=30, seconds=0), 'iat': datetime.utcnow(), 'sub': user_id } return jwt.encode( payload, current_app.config.get('SECRET_KEY'), algorithm='HS256' ) except Exception as e: raise e @staticmethod def decode_auth_token(auth_token): try: payload = jwt.decode(auth_token, current_app.config.get('SECRET_KEY')) is_blacklisted_token = BlacklistToken.check_blacklist(auth_token) if is_blacklisted_token: return "Token is blacklisted - please log in again" else: return payload['sub'] except jwt.ExpiredSignatureError: return 'Signiture expired. Please log in again.' except jwt.InvalidTokenError: return 'Invalid token. Please log in again.'
class UserFollow(db.Model): id = db.Column(db.Integer, primary_key=True, autoincrement=True) follower_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False, unique=False) followed_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False, unique=False) follower = db.relationship('User', foreign_keys=[followed_id]) followed = db.relationship('User', foreign_keys=[follower_id])
class YandexDiskToken(db.Model): """ Yandex.Disk token. One user can have only one token. """ __tablename__ = "yandex_disk_tokens" # Columns id = db.Column(db.Integer, primary_key=True) _access_token = db.Column("access_token", db.String, nullable=True, default=null(), comment="Encrypted Y.D. OAuth token") access_token_type = db.Column(db.String, nullable=True, default=null(), comment="Type of access token") access_token_expires_in = db.Column( db.BigInteger, nullable=True, default=null(), comment="Access token lifetime in seconds") _refresh_token = db.Column( "refresh_token", db.String, nullable=True, default=null(), comment="Encrypted Y.D. refresh token to use to update access token") _insert_token = db.Column( "insert_token", db.String, nullable=True, default=null(), comment=( "Encrypted token for DB update controlling. " "i.e., you shouldn't insert values if you don't know insert token" )) insert_token_expires_in = db.Column( db.BigInteger, nullable=True, default=null(), comment="Insert token lifetime in seconds") user_id = db.Column(db.Integer, db.ForeignKey("users.id"), unique=True, nullable=False, comment="Tokens belongs to this user") # Relationships user = db.relationship('User', back_populates="yandex_disk_token", uselist=False) def __init__(self, **kwargs): super(YandexDiskToken, self).__init__(**kwargs) if ("_access_token" in kwargs): raise AttributeError("`access_token` can't be accessed directly") elif ("_refresh_token" in kwargs): raise AttributeError("`refresh_token` can't be accessed directly") elif ("_insert_token" in kwargs): raise AttributeError("`insert_token` can't be accessed directly") def __repr__(self): return f"<YD Token {self.id}>" def __getitem__(self, key): return getattr(self, key) def __setitem__(self, key, value): return setattr(self, key, value) @staticmethod def create_fake(user) -> dict: """ Creates fake Yandex.Disk token. There is two types of token: "pending" or "received". Pending means the app wants to write in `access_token` column, but request to Yandex not maded at the moment. If app received invalid `insert_token` from user, then it shouldn't make request to Yandex. Received means the app successfully made a request to Yandex and `insert_token` not exists in a row, because `INSERT` operation was successfully completed. :param user: User to associate token with. :returns: "Pending" token (chance is 1/10) or "received" token (chance is 9/10). """ from faker import Faker fake = Faker() result = YandexDiskToken(user=user) result_type = None random_number = fake.pyint(min_value=1, max_value=10, step=1) if (random_number % 10): result_type = "pending" result.set_access_token(fake.pystr(min_chars=32, max_chars=32)) result.set_refresh_token(fake.pystr(min_chars=32, max_chars=32)) result.access_token_type = "bearer" result.access_token_expires_in = fake.pyint(min_value=31536000, max_value=63072000, step=1) else: result_type = "received" result.set_insert_token(fake.pystr(min_chars=32, max_chars=32)) result.insert_token_expires_in = fake.pyint(min_value=600, max_value=900, step=100) return {"value": result, "type": result_type} @hybrid_property def access_token(self): raise AttributeError("`access_token` can't be accessed directly") @access_token.setter def access_token(self, new_value): raise AttributeError("`access_token` can't be accessed directly") @hybrid_property def refresh_token(self): raise AttributeError("`refresh_token` can't be accessed directly") @refresh_token.setter def refresh_token(self, new_value): raise AttributeError("`refresh_token` can't be accessed directly") @hybrid_property def insert_token(self): raise AttributeError("`insert_token` can't be accessed directly") @insert_token.setter def insert_token(self, new_value): raise AttributeError("`insert_token` can't be accessed directly") def set_access_token(self, token: Union[str, None]) -> None: """ Sets encrypted access token. """ self._set_token(token_attribute_name="_access_token", value=token) def get_access_token(self) -> Union[str, None]: """ Returns decrypted access token. :raises DataCorruptedError: Data in DB is corrupted. :raises InvalidTokenError: Encrypted token is invalid or expired. """ return self._get_token( token_attribute_name="_access_token", expires_attribute_name="access_token_expires_in") def set_refresh_token(self, token: Union[str, None]) -> None: """ Sets encrypted refresh token. """ self._set_token(token_attribute_name="_refresh_token", value=token) def get_refresh_token(self) -> Union[str, None]: """ Returns decrypted refresh token. :raises DataCorruptedError: Data in DB is corrupted. :raises InvalidTokenError: Encrypted token is invalid. """ return self._get_token(token_attribute_name="_refresh_token") def set_insert_token(self, token: Union[str, None]) -> None: """ Sets encrypted insert token. """ self._set_token(token_attribute_name="_insert_token", value=token) def get_insert_token(self) -> Union[str, None]: """ Returns decrypted insert token. :raises DataCorruptedError: Data in DB is corrupted. :raises InvalidTokenError: Encrypted token is invalid or expired. """ return self._get_token( token_attribute_name="_insert_token", expires_attribute_name="insert_token_expires_in") def have_access_token(self) -> bool: """ :returns: `True` if `access_token` contains any value otherwise `False`. """ return self._have_token(token_attribute_name="_access_token") def have_refresh_token(self) -> bool: """ :returns: `True` if `refresh_token` contains any value otherwise `False`. """ return self._have_token(token_attribute_name="_refresh_token") def have_insert_token(self) -> bool: """ :returns: `True` if `insert_token` contains any value otherwise `False`. """ return self._have_token(token_attribute_name="_insert_token") def clear_access_token(self) -> None: """ Clears all data that belongs to access token. - perform a commit in order to save changes! """ self.access_token_type = null() return self._clear_token( token_attribute_name="_access_token", expires_attribute_name="access_token_expires_in") def clear_refresh_token(self) -> None: """ Clears all data that belongs to refresh token. - perform a commit in order to save changes! """ return self._clear_token(token_attribute_name="_refresh_token") def clear_insert_token(self) -> None: """ Clears all data that belongs to insert token. - perform a commit in order to save changes! """ return self._clear_token( token_attribute_name="_insert_token", expires_attribute_name="insert_token_expires_in") def clear_all_tokens(self) -> None: """ Clears all data that belongs to any kind of token. - perform a commit in order to save changes! """ self.clear_access_token() self.clear_refresh_token() self.clear_insert_token() def _set_token(self, **kwargs) -> None: """ Sets encrypted token. :param token_attribute_name: Name of token attribute in class. :param value: Value to set. """ fernet = Fernet(current_app.secret_key.encode()) token_attribute_name = kwargs["token_attribute_name"] value = kwargs["value"] if (value is None): self[token_attribute_name] = None else: encrypted_data = fernet.encrypt(value.encode()) self[token_attribute_name] = encrypted_data.decode() def _get_token(self, **kwargs) -> Union[str, None]: """ Returns decrypted token. :param token_attribute_name: Name of token attribute in class. :param expires_attribute_name: Optional. Token lifetime in seconds. If specified, expiration date will be checked. :returns: Decrypted token or `None` if value is NULL. :raises DataCorruptedError: Data in DB is corrupted. :raises InvalidTokenError: Encrypted token is invalid. """ fernet = Fernet(current_app.secret_key.encode()) token_attribute_name = kwargs["token_attribute_name"] encrypted_token = self[token_attribute_name] if (encrypted_token is None): return None token_lifetime = None expires_attribute_name = kwargs.get("expires_attribute_name") if (expires_attribute_name is not None): token_lifetime = self[expires_attribute_name] if (not isinstance(token_lifetime, int)): raise DataCorruptedError("Token lifetime is not an integer") encrypted_token = encrypted_token.encode() decrypted_token = None try: decrypted_token = fernet.decrypt(encrypted_token, token_lifetime) except InvalidTokenFernetError: raise InvalidTokenError("Token is invalid or expired") decrypted_token = decrypted_token.decode() return decrypted_token def _have_token(self, **kwargs) -> bool: """ :param token_attribute_name: Name of token attribute in class. :returns: `True` if token contains any value, `False` otherwise. """ token_attribute_name = kwargs["token_attribute_name"] value = self[token_attribute_name] return (isinstance(value, str) and len(value) > 0) def _clear_token(self, **kwargs) -> None: """ Clears token data. - perform a commit in order to save changes! :param token_attribute_name: Name of token attribute in class. :param expires_attribute_name: Optional. Token lifetime in seconds. If specified, expiration date will be cleared. """ token_attribute_name = kwargs["token_attribute_name"] expires_attribute_name = kwargs.get("expires_attribute_name") self[token_attribute_name] = null() if (expires_attribute_name): self[expires_attribute_name] = null()