diff --git a/api/api_blueprint_keyword.py b/api/api_blueprint_keyword.py index 232a97b..a22abb3 100644 --- a/api/api_blueprint_keyword.py +++ b/api/api_blueprint_keyword.py @@ -3,7 +3,7 @@ import os from apiflask import APIBlueprint, abort from db import db -from helper_functions import get_user_id_from_username, get_username_or_abort_401, make_response +from helper_functions import make_response, get_email_or_abort_401 from auth import auth from schema import KeywordResponseSchema, KeywordSchema, DeleteSuccessfulSchema from models import Keyword @@ -18,17 +18,17 @@ __location__ = os.path.realpath(os.path.join(os.getcwd(), os.path.dirname(__file @keyword_blueprint.auth_required(auth) @keyword_blueprint.doc(summary="Add new keyword", description="Adds new keyword for current user") def add_keyword(data): - username = get_username_or_abort_401() + email = get_email_or_abort_401() check_if_keyword_data_exists(data) key = data['keyword'] - check_keyword = db.session.query(Keyword).filter_by(keyword=key, user_id=get_user_id_from_username(username)).first() + check_keyword = db.session.query(Keyword).filter_by(keyword=key, email=email).first() if check_keyword is None: # Keyword doesn't exist yet for this user new_keyword = Keyword( - user_id=get_user_id_from_username(username), + email=email, keyword=key ) db.session.add(new_keyword) @@ -45,16 +45,21 @@ def add_keyword(data): @keyword_blueprint.auth_required(auth) @keyword_blueprint.doc(summary="Removes existing keyword", description="Removes existing keyword for current user") def remove_keyword(data): - username = get_username_or_abort_401() + email = get_email_or_abort_401() check_if_keyword_data_exists(data) key = data['keyword'] - db.session.query(Keyword).filter_by(keyword=key, user_id=get_user_id_from_username(username)).delete() - db.session.commit() + check_keyword = db.session.query(Keyword).filter_by(keyword=key, email=email).first() - return make_response({}, 200, "Successfully removed keyword") + if check_keyword is None: + return make_response({}, 500, "Keyword doesn't exist for this user") + else: + db.session.query(Keyword).filter_by(keyword=key, email=email).delete() + db.session.commit() + + return make_response({}, 200, "Successfully removed keyword") @keyword_blueprint.route('/keywords', methods=['GET']) @@ -62,10 +67,10 @@ def remove_keyword(data): @keyword_blueprint.auth_required(auth) @keyword_blueprint.doc(summary="Returns all keywords", description="Returns all keywords for current user") def get_keywords(): - username = get_username_or_abort_401() + email = get_email_or_abort_401() return_keywords = [] - keywords = db.session.query(Keyword).filter_by(user_id=get_user_id_from_username(username)).all() + keywords = db.session.query(Keyword).filter_by(email=email).all() if keywords is not None: for row in keywords: diff --git a/api/api_blueprint_portfolio.py b/api/api_blueprint_portfolio.py index 5970ced..b6ecea3 100644 --- a/api/api_blueprint_portfolio.py +++ b/api/api_blueprint_portfolio.py @@ -2,9 +2,9 @@ import os from apiflask import APIBlueprint +from api.schema import PortfolioResponseSchema from db import db -from helper_functions import get_user_id_from_username, get_username_or_abort_401, make_response -from models import Transaction +from helper_functions import make_response, get_email_or_abort_401 from auth import auth portfolio_blueprint = APIBlueprint('portfolio', __name__, url_prefix='/api') @@ -12,21 +12,22 @@ __location__ = os.path.realpath(os.path.join(os.getcwd(), os.path.dirname(__file @portfolio_blueprint.route('/portfolio', methods=['GET']) -@portfolio_blueprint.output(200) +@portfolio_blueprint.output(PortfolioResponseSchema(many=True), 200) @portfolio_blueprint.auth_required(auth) @portfolio_blueprint.doc(summary="Returns portfolio", description="Returns all shares of current user") def get_portfolio(): - username = get_username_or_abort_401() + email = get_email_or_abort_401() - return_portfolio = {} - transactions = db.session.query(Transaction).filter_by(user_id=get_user_id_from_username(username)).all() + return_portfolio = [] + transactions = db.session.execute("SELECT symbol, SUM(count), SUM(price), MAX(time) FROM `transactions` WHERE email = '" + email + "' GROUP BY symbol;").all() if transactions is not None: for row in transactions: - if row.symbol in return_portfolio: - return_portfolio[row.symbol]['count'] += row.count - return_portfolio[row.symbol]['last_transaction'] = row.time - else: - return_portfolio[row.symbol] = {"count": row.count, "last_transaction": row.time} + return_portfolio.append({ + "symbol": row[0], + "count": row[1], + # "price": row[2], + "last_transaction": row[3] + }) return make_response(return_portfolio, 200, "Successfully loaded symbols") diff --git a/api/api_blueprint_shares.py b/api/api_blueprint_shares.py index 3e18e8c..1c7c785 100644 --- a/api/api_blueprint_shares.py +++ b/api/api_blueprint_shares.py @@ -4,7 +4,7 @@ from apiflask import APIBlueprint, abort from auth import auth from db import db -from helper_functions import get_user_id_from_username, get_username_or_abort_401, make_response +from helper_functions import make_response, get_email_or_abort_401 from models import Share from schema import SymbolSchema, SymbolResponseSchema, DeleteSuccessfulSchema @@ -18,17 +18,17 @@ __location__ = os.path.realpath(os.path.join(os.getcwd(), os.path.dirname(__file @shares_blueprint.auth_required(auth) @shares_blueprint.doc(summary="Add new symbol", description="Adds new symbol for current user") def add_symbol(data): - username = get_username_or_abort_401() + email = get_email_or_abort_401() check_if_symbol_data_exists(data) symbol = data['symbol'] - check_share = db.session.query(Share).filter_by(symbol=symbol, user_id=get_user_id_from_username(username)).first() + check_share = db.session.query(Share).filter_by(symbol=symbol, email=email).first() if check_share is None: # Keyword doesn't exist yet for this user new_symbol = Share( - user_id=get_user_id_from_username(username), + email=email, symbol=symbol ) db.session.add(new_symbol) @@ -45,16 +45,21 @@ def add_symbol(data): @shares_blueprint.auth_required(auth) @shares_blueprint.doc(summary="Removes existing symbol", description="Removes existing symbol for current user") def remove_symbol(data): - username = get_username_or_abort_401() + email = get_email_or_abort_401() check_if_symbol_data_exists(data) symbol = data['symbol'] - db.session.query(Share).filter_by(symbol=symbol, user_id=get_user_id_from_username(username)).delete() - db.session.commit() + check_share = db.session.query(Share).filter_by(symbol=symbol, email=email).first() - return make_response({}, 200, "Successfully removed symbol") + if check_share is None: + return make_response({}, 500, "Symbol doesn't exist for this user") + else: + db.session.query(Share).filter_by(symbol=symbol, email=email).delete() + db.session.commit() + + return make_response({}, 200, "Successfully removed symbol") @shares_blueprint.route('/shares', methods=['GET']) @@ -62,10 +67,10 @@ def remove_symbol(data): @shares_blueprint.auth_required(auth) @shares_blueprint.doc(summary="Returns all symbols", description="Returns all symbols for current user") def get_symbol(): - username = get_username_or_abort_401() + email = get_email_or_abort_401() return_symbols = [] - symbols = db.session.query(Share).filter_by(user_id=get_user_id_from_username(username)).all() + symbols = db.session.query(Share).filter_by(email=email).all() if symbols is not None: for row in symbols: diff --git a/api/api_blueprint_transactions.py b/api/api_blueprint_transactions.py index b0e58fc..054a032 100644 --- a/api/api_blueprint_transactions.py +++ b/api/api_blueprint_transactions.py @@ -4,9 +4,9 @@ import datetime from apiflask import abort, APIBlueprint from db import db -from helper_functions import get_user_id_from_username, get_username_or_abort_401, make_response +from helper_functions import make_response, get_email_or_abort_401 from models import Transaction -from schema import TransactionSchema +from schema import TransactionSchema, TransactionResponseSchema from auth import auth transaction_blueprint = APIBlueprint('transaction', __name__, url_prefix='/api') @@ -14,17 +14,17 @@ __location__ = os.path.realpath(os.path.join(os.getcwd(), os.path.dirname(__file @transaction_blueprint.route('/transaction', methods=['POST']) -@transaction_blueprint.output((), 200) +@transaction_blueprint.output(TransactionResponseSchema(), 200) @transaction_blueprint.input(schema=TransactionSchema) @transaction_blueprint.auth_required(auth) @transaction_blueprint.doc(summary="Adds new transaction", description="Adds new transaction for current user") def add_transaction(data): - username = get_username_or_abort_401() + email = get_email_or_abort_401() check_if_transaction_data_exists(data) new_transaction = Transaction( - user_id=get_user_id_from_username(username), + email=email, symbol=data['symbol'], time=datetime.datetime.strptime(data['time'], '%Y-%m-%dT%H:%M:%S.%fZ'), count=data['count'], @@ -41,10 +41,10 @@ def add_transaction(data): @transaction_blueprint.auth_required(auth) @transaction_blueprint.doc(summary="Returns all transactions", description="Returns all transactions for current user") def get_transaction(): - username = get_username_or_abort_401() + email = get_email_or_abort_401() return_transactions = [] - transactions = db.session.query(Transaction).filter_by(user_id=get_user_id_from_username(username)).all() + transactions = db.session.query(Transaction).filter_by(email=email).all() if transactions is not None: for row in transactions: diff --git a/api/api_blueprint_user.py b/api/api_blueprint_user.py index 747a82f..0afd873 100644 --- a/api/api_blueprint_user.py +++ b/api/api_blueprint_user.py @@ -5,9 +5,9 @@ import jwt from apiflask import APIBlueprint, abort from db import db -from helper_functions import check_password, hash_password, get_username_or_abort_401, abort_if_no_admin, make_response +from helper_functions import check_password, hash_password, abort_if_no_admin, make_response, get_email_or_abort_401 from models import User -from schema import UsersSchema, TokenSchema, LoginDataSchema, AdminDataSchema, DeleteUserSchema +from schema import UsersSchema, TokenSchema, LoginDataSchema, AdminDataSchema, DeleteUserSchema, RegisterDataSchema, UpdateUserDataSchema from auth import auth users_blueprint = APIBlueprint('users', __name__, url_prefix='/api') @@ -33,9 +33,9 @@ def users(): @users_blueprint.auth_required(auth) @users_blueprint.doc(summary="Get current user", description="Returns current user") def user(): - username = get_username_or_abort_401() + email = get_email_or_abort_401() - res = db.session.query(User).filter_by(username=username).first().as_dict() + res = db.session.query(User).filter_by(email=email).first().as_dict() return make_response(res, 200, "Successfully received current user data") @@ -45,40 +45,45 @@ def user(): @users_blueprint.input(schema=LoginDataSchema) @users_blueprint.doc(summary="Login", description="Returns jwt token if username and password match, otherwise returns error") def login(data): - check_if_user_data_exists(data) + check_if_email_data_exists(data) + check_if_password_data_exists(data) - username = data['username'] + email = data['email'] password = data['password'] - query_user = db.session.query(User).filter_by(username=username).first() + query_user = db.session.query(User).filter_by(email=email).first() - if query_user is None: # Username doesn't exist + if query_user is None: # email doesn't exist abort(500, message="Unable to login") if not check_password(query_user.password, password): # Password incorrect abort(500, message="Unable to login") - token = jwt.encode({'username': query_user.username, 'exp': datetime.datetime.utcnow() + datetime.timedelta(minutes=45)}, os.getenv('SECRET_KEY'), "HS256") + token = jwt.encode({'email': query_user.email, 'exp': datetime.datetime.utcnow() + datetime.timedelta(minutes=45)}, os.getenv('SECRET_KEY'), "HS256") return make_response({"token": token}, 200, "Successfully logged in") @users_blueprint.route('/user/register', methods=['POST']) @users_blueprint.output(UsersSchema(), 200) -@users_blueprint.input(schema=LoginDataSchema) +@users_blueprint.input(schema=RegisterDataSchema) @users_blueprint.doc(summary="Register", description="Registers user") def register(data): - check_if_user_data_exists(data) + check_if_email_data_exists(data) + check_if_username_data_exists(data) + check_if_password_data_exists(data) + email = data['email'] username = data['username'] password = data['password'] - query_user = db.session.query(User).filter_by(username=username).first() + query_user = db.session.query(User).filter_by(email=email).first() if query_user is not None: # Username already exist - abort(500, message="Username already exist") + abort(500, message="Email already exist") new_user = User( + email=email, username=username, password=hash_password(password), admin=False @@ -91,26 +96,21 @@ def register(data): @users_blueprint.route('/user', methods=['PUT']) @users_blueprint.output({}, 200) -@users_blueprint.input(schema=LoginDataSchema) +@users_blueprint.input(schema=UpdateUserDataSchema) @users_blueprint.auth_required(auth) @users_blueprint.doc(summary="Update user", description="Changes password and/or username of current user") def update_user(data): - username = get_username_or_abort_401() + email = get_email_or_abort_401() - check_if_user_data_exists(data) - - new_username = data['username'] - new_password = data['password'] - - query_user = db.session.query(User).filter_by(username=username).first() + query_user = db.session.query(User).filter_by(email=email).first() if query_user is None: # Username doesn't exist abort(500, message="Unable to login") - if new_password is not None: - query_user.password = hash_password(new_password) - if new_username is not None: - query_user.username = new_username + if "password" in data and data['password'] is not None: + query_user.password = hash_password(data['password']) + if "username" in data and data['username'] is not None: + query_user.username = data['username'] db.session.commit() @@ -125,12 +125,13 @@ def update_user(data): def set_admin(data): abort_if_no_admin() # Only admin users can do this + check_if_email_data_exists(data) check_if_admin_data_exists(data) - username = data['username'] + email = data['email'] admin = data['admin'] - query_user = db.session.query(User).filter_by(username=username).first() + query_user = db.session.query(User).filter_by(email=email).first() if query_user is None: # Username doesn't exist abort(500, message="Unable to login") @@ -147,29 +148,31 @@ def set_admin(data): @users_blueprint.auth_required(auth) @users_blueprint.doc(summary="Delete user", description="Deletes user by username") def delete_user(data): - check_if_delete_data_exists(data) + check_if_email_data_exists(data) - username = data['username'] + email = data['email'] - if username == get_username_or_abort_401(): # Username is same as current user - db.session.query(User).filter_by(username=username).delete() + if email == get_email_or_abort_401(): # Username is same as current user + db.session.query(User).filter_by(email=email).delete() db.session.commit() else: # Delete different user than my user -> only admin users abort_if_no_admin() - db.session.query(User).filter_by(username=username).delete() + db.session.query(User).filter_by(email=email).delete() db.session.commit() return make_response({}, 200, "Successfully removed user") -def check_if_user_data_exists(data): - if "username" not in data: - abort(400, message="Username missing") +def check_if_email_data_exists(data): + if "email" not in data: + abort(400, message="Email missing") - if data['username'] == "" or data['username'] is None: - abort(400, message="Username missing") + if data['email'] == "" or data['email'] is None: + abort(400, message="Email missing") + +def check_if_password_data_exists(data): if "password" not in data: abort(400, message="Password missing") @@ -177,23 +180,17 @@ def check_if_user_data_exists(data): abort(400, message="Password missing") -def check_if_admin_data_exists(data): +def check_if_username_data_exists(data): if "username" not in data: abort(400, message="Username missing") if data['username'] == "" or data['username'] is None: abort(400, message="Username missing") + +def check_if_admin_data_exists(data): if "admin" not in data: abort(400, message="Admin state missing") if data['admin'] == "" or data['admin'] is None: abort(400, message="Admin state missing") - - -def check_if_delete_data_exists(data): - if "username" not in data: - abort(400, message="Username missing") - - if data['username'] == "" or data['username'] is None: - abort(400, message="Username missing") diff --git a/api/app.py b/api/app.py index da31695..4e1dfa3 100644 --- a/api/app.py +++ b/api/app.py @@ -7,11 +7,7 @@ from flask_cors import CORS from api.helper_functions import hash_password from models import * -from api_blueprint_keyword import keyword_blueprint -from api_blueprint_shares import shares_blueprint from api_blueprint_user import users_blueprint -from api_blueprint_transactions import transaction_blueprint -from api_blueprint_portfolio import portfolio_blueprint def create_app(): @@ -38,20 +34,22 @@ def create_app(): def init_database(): db.create_all() - if os.getenv("BOT_USER") is not None and os.getenv("BOT_PASSWORD") is not None: - if db.session.query(User).filter_by(username=os.getenv("BOT_USER")).first() is None: # Check if user already exist + if os.getenv("BOT_EMAIL") is not None and os.getenv("BOT_USERNAME") is not None and os.getenv("BOT_PASSWORD") is not None: + if db.session.query(User).filter_by(email=os.getenv("BOT_EMAIL")).first() is None: # Check if user already exist bot = User( - username=os.getenv("BOT_USER"), + email=os.getenv("BOT_EMAIL"), + username=os.getenv("BOT_USERNAME"), password=hash_password(os.getenv("BOT_PASSWORD")), admin=False ) db.session.add(bot) db.session.commit() - if os.getenv("ADMIN_USER") is not None and os.getenv("ADMIN_PASSWORD") is not None: - if db.session.query(User).filter_by(username=os.getenv("ADMIN_USER")).first() is None: # Check if user already exist + if os.getenv("ADMIN_EMAIL") is not None and os.getenv("ADMIN_USERNAME") is not None and os.getenv("ADMIN_PASSWORD") is not None: + if db.session.query(User).filter_by(email=os.getenv("ADMIN_EMAIL")).first() is None: # Check if user already exist admin = User( - username=os.getenv("ADMIN_USER"), + email=os.getenv("ADMIN_EMAIL"), + username=os.getenv("ADMIN_USERNAME"), password=hash_password(os.getenv("ADMIN_PASSWORD")), admin=True ) diff --git a/api/auth.py b/api/auth.py index 8096012..f13eda3 100644 --- a/api/auth.py +++ b/api/auth.py @@ -17,5 +17,5 @@ def verify_token(token): try: jwt.decode(token, os.getenv('SECRET_KEY'), algorithms=["HS256"]) return True - except jwt.exceptions.DecodeError: + except: return False diff --git a/api/helper_functions.py b/api/helper_functions.py index 59991c6..6eca7a5 100644 --- a/api/helper_functions.py +++ b/api/helper_functions.py @@ -32,53 +32,46 @@ def extract_token_data(token): if token is not None: try: return jwt.decode(token, os.getenv('SECRET_KEY'), algorithms=["HS256"]) - except jwt.exceptions.DecodeError: + except: return None else: return None -def get_username_from_token_data(): +def get_email_from_token_data(): if 'Authorization' in request.headers: token = request.headers['Authorization'].split(" ")[1] if token is not None: if ':' in token: # Maybe bot token, check if token valid and return username after ":" then - username = token.split(":")[1] + email = token.split(":")[1] token = token.split(":")[0] try: - if jwt.decode(token, os.getenv('SECRET_KEY'), algorithms=["HS256"])['username'] == "bot": - return username + if jwt.decode(token, os.getenv('SECRET_KEY'), algorithms=["HS256"])['email'] == os.getenv("BOT_USER"): + return email else: return None - except jwt.exceptions.DecodeError: + except: return None else: # "Normal" token, extract username from token try: - return jwt.decode(token, os.getenv('SECRET_KEY'), algorithms=["HS256"])['username'] - except jwt.exceptions.DecodeError: + return jwt.decode(token, os.getenv('SECRET_KEY'), algorithms=["HS256"])['email'] + except: return None return None -def get_user_id_from_username(username): - if username is not None: - return db.session.query(User).filter_by(username=username).first().user_id - else: - return None - - -def get_username_or_abort_401(): +def get_email_or_abort_401(): # get username from jwt token - username = get_username_from_token_data() + email = get_email_from_token_data() - if username is None: # If token not provided or invalid -> return 401 code + if email is None: # If token not provided or invalid -> return 401 code abort(401, message="Unable to login") - return username + return email def abort_if_no_admin(): @@ -87,10 +80,10 @@ def abort_if_no_admin(): def is_user_admin(): - username = get_username_or_abort_401() + email = get_email_or_abort_401() - return db.session.query(User).filter_by(username=username).first().admin + return db.session.query(User).filter_by(email=email).first().admin def make_response(data, status=200, text=""): - return jsonify({"status": status, "text": text, "data": {"token": data}}) + return jsonify({"status": status, "text": text, "data": data}) diff --git a/api/models.py b/api/models.py index 59e4287..20c4830 100644 --- a/api/models.py +++ b/api/models.py @@ -3,10 +3,10 @@ from db import db class User(db.Model): __tablename__ = 'users' - username = db.Column('username', db.String(255), nullable=False, unique=True) + email = db.Column('email', db.String(255), primary_key=True, nullable=False, unique=True) password = db.Column('password', db.String(255), nullable=False, server_default='') - user_id = db.Column('user_id', db.Integer(), primary_key=True) - telegram_name = db.Column('telegram_name', db.String(255), nullable=True, server_default='') + username = db.Column('username', db.String(255), nullable=False, server_default='') + telegram_user_id = db.Column('telegram_user_id', db.String(255), nullable=True, server_default='') admin = db.Column('admin', db.Boolean(), server_default='0') def as_dict(self): @@ -16,7 +16,7 @@ class User(db.Model): class Transaction(db.Model): __tablename__ = 'transactions' t_id = db.Column('t_id', db.Integer(), nullable=False, unique=True, primary_key=True) - user_id = db.Column('user_id', db.Integer(), db.ForeignKey('users.user_id', ondelete='CASCADE')) + email = db.Column('email', db.String(255), db.ForeignKey('users.email', ondelete='CASCADE')) symbol = db.Column('symbol', db.String(255)) time = db.Column('time', db.DateTime()) count = db.Column('count', db.Integer()) @@ -29,7 +29,7 @@ class Transaction(db.Model): class Keyword(db.Model): __tablename__ = 'keywords' s_id = db.Column('s_id', db.Integer(), nullable=False, unique=True, primary_key=True) - user_id = db.Column('user_id', db.Integer(), db.ForeignKey('users.user_id', ondelete='CASCADE')) + email = db.Column('email', db.String(255), db.ForeignKey('users.email', ondelete='CASCADE')) keyword = db.Column('keyword', db.String(255)) def as_dict(self): @@ -39,7 +39,7 @@ class Keyword(db.Model): class Share(db.Model): __tablename__ = 'shares' a_id = db.Column('a_id', db.Integer(), nullable=False, unique=True, primary_key=True) - user_id = db.Column('user_id', db.Integer(), db.ForeignKey('users.user_id', ondelete='CASCADE')) + email = db.Column('email', db.String(255), db.ForeignKey('users.email', ondelete='CASCADE')) symbol = db.Column('symbol', db.String(255)) def as_dict(self): diff --git a/api/schema.py b/api/schema.py index bb1371a..67fdf65 100644 --- a/api/schema.py +++ b/api/schema.py @@ -1,5 +1,7 @@ from apiflask import Schema from apiflask.fields import Integer, String, Boolean, Field, Float +from marshmallow import validate +from marshmallow.fields import Email class BaseResponseSchema(Schema): @@ -11,13 +13,13 @@ class BaseResponseSchema(Schema): class UsersSchema(Schema): admin = Boolean() password = String() - telegram_name = String() - user_id = Integer() username = String() + telegram_user_id = String() + email = Email() class AdminDataSchema(Schema): - username = String() + email = Email() admin = Boolean() @@ -26,12 +28,23 @@ class TokenSchema(Schema): class LoginDataSchema(Schema): + email = Email() + password = String() + + +class RegisterDataSchema(Schema): + email = Email() username = String() password = String() +class UpdateUserDataSchema(Schema): + username = String(required=False) + password = String(required=False) + + class DeleteUserSchema(Schema): - username = String() + email = Email() class ChangePasswordSchema(Schema): @@ -50,7 +63,7 @@ class KeywordSchema(Schema): class KeywordResponseSchema(Schema): keyword = String() s_id = Integer() - user_id = Integer() + email = Email() class SymbolSchema(Schema): @@ -60,7 +73,7 @@ class SymbolSchema(Schema): class SymbolResponseSchema(Schema): symbol = String() s_id = Integer() - user_id = Integer() + email = Email() class PortfolioShareResponseSchema(Schema): @@ -69,12 +82,30 @@ class PortfolioShareResponseSchema(Schema): class TransactionSchema(Schema): - user_id = Integer() + symbol = String() + time = String(validate=validate.Regexp(r"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z")) + count = Integer() + price = Float() + + +class TransactionResponseSchema(Schema): + email = Email() symbol = String() time = String() count = Integer() price = Float() +class TelegramIdSchema(Schema): + telegram_user_id = String() + + +class PortfolioResponseSchema(Schema): + symbol = String() + last_transaction = String() + count = Integer() + # price = Float() + + class DeleteSuccessfulSchema(Schema): pass