Changed database model

This commit is contained in:
Administrator 2022-03-27 17:23:33 +02:00
parent 2ba8a9bd13
commit 12d59d69ff
10 changed files with 160 additions and 130 deletions

View File

@ -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,13 +45,18 @@ 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()
check_keyword = db.session.query(Keyword).filter_by(keyword=key, email=email).first()
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")
@ -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:

View File

@ -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")

View File

@ -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,13 +45,18 @@ 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()
check_share = db.session.query(Share).filter_by(symbol=symbol, email=email).first()
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")
@ -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:

View File

@ -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:

View File

@ -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")

View File

@ -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
)

View File

@ -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

View File

@ -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})

View File

@ -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):

View File

@ -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