Merge remote-tracking branch 'origin/main' into bot

This commit is contained in:
Linus E 2022-04-25 17:34:11 +02:00
commit 14963176cc
21 changed files with 578 additions and 593 deletions

View File

@ -17,7 +17,9 @@ def verify_token(token):
if token is None: if token is None:
return False return False
if ':' in token: # Bot token # We decided to append the user id to the bearer token using ":" as separator to select an specific user
# To validate the token we can remove the user id since we only validate the token and not the user id
if ':' in token:
token = token.split(":")[0] token = token.split(":")[0]
try: try:

View File

@ -31,9 +31,10 @@ def add_keyword(data):
key = data['keyword'] key = data['keyword']
# Check if keyword already exists
check_keyword = db.session.query(Keyword).filter_by(keyword=key, email=email).first() check_keyword = db.session.query(Keyword).filter_by(keyword=key, email=email).first()
if check_keyword is None: if check_keyword is None:
# Keyword doesn't exist yet for this user # Keyword doesn't exist yet for this user -> add it
new_keyword = Keyword( new_keyword = Keyword(
email=email, email=email,
keyword=key keyword=key
@ -54,17 +55,17 @@ def add_keyword(data):
def remove_keyword(data): def remove_keyword(data):
email = get_email_or_abort_401() email = get_email_or_abort_401()
# Check if request data is valid
if not check_if_keyword_data_exists(data): if not check_if_keyword_data_exists(data):
abort(400, message="Keyword missing") abort(400, message="Keyword missing")
key = data['keyword'] # Check if keyword exists
check_keyword = db.session.query(Keyword).filter_by(keyword=data['keyword'], email=email).first()
check_keyword = db.session.query(Keyword).filter_by(keyword=key, email=email).first()
if check_keyword is None: if check_keyword is None:
return abort(500, "Keyword doesn't exist for this user") return abort(500, "Keyword doesn't exist for this user")
else: else:
db.session.query(Keyword).filter_by(keyword=key, email=email).delete() # Keyword exists -> delete it
db.session.query(Keyword).filter_by(keyword=data['keyword'], email=email).delete()
db.session.commit() db.session.commit()
return make_response({}, 200, "Successfully removed keyword") return make_response({}, 200, "Successfully removed keyword")
@ -80,6 +81,8 @@ def get_keywords():
return_keywords = [] return_keywords = []
keywords = db.session.query(Keyword).filter_by(email=email).all() keywords = db.session.query(Keyword).filter_by(email=email).all()
# If no keywords exist for this user -> return empty list
# Otherwise iterate over all keywords, convert them to json and add them to the return list
if keywords is not None: if keywords is not None:
for row in keywords: for row in keywords:
return_keywords.append(row.as_dict()) return_keywords.append(row.as_dict())

View File

@ -2,7 +2,7 @@ __author__ = "Florian Kaiser"
__copyright__ = "Copyright 2022, Project Aktienbot" __copyright__ = "Copyright 2022, Project Aktienbot"
__credits__ = ["Florian Kaiser", "Florian Kellermann", "Linus Eickhof", "Kevin Pauer"] __credits__ = ["Florian Kaiser", "Florian Kellermann", "Linus Eickhof", "Kevin Pauer"]
__license__ = "GPL 3.0" __license__ = "GPL 3.0"
__version__ = "1.0.0" __version__ = "1.0.1"
import os import os
@ -25,19 +25,25 @@ def get_portfolio():
email = get_email_or_abort_401() email = get_email_or_abort_401()
return_portfolio = [] return_portfolio = []
transactions = db.session.execute("SELECT symbol, SUM(count), SUM(price), MAX(time) FROM `transactions` WHERE email = '" + email + "' GROUP BY symbol;").all()
# Get all transactions of current user
transactions = db.session.execute("SELECT isin, comment, SUM(count), SUM(price), MAX(time) FROM `transactions` WHERE email = '" + email + "' GROUP BY isin, comment;").all()
# If there are no transactions, return empty portfolio
# Otherwise calculate portfolio
if transactions is not None: if transactions is not None:
for row in transactions: for row in transactions:
data = { data = {
"symbol": row[0], "isin": row[0],
"count": row[1], "comment": row[1],
# "calculated_price": row[2], "count": row[2],
"last_transaction": row[3], # "calculated_price": row[3],
"last_transaction": row[4],
'current_price': 0 'current_price': 0
} }
query_share_price = db.session.query(SharePrice).filter_by(symbol=row[0]).order_by(SharePrice.date.desc()).first() # Add current share value to portfolio
query_share_price = db.session.query(SharePrice).filter_by(isin=row[0]).order_by(SharePrice.date.desc()).first()
if query_share_price is not None: if query_share_price is not None:
data['current_price'] = query_share_price.as_dict()['price'] data['current_price'] = query_share_price.as_dict()['price']

View File

@ -2,17 +2,16 @@ __author__ = "Florian Kaiser"
__copyright__ = "Copyright 2022, Project Aktienbot" __copyright__ = "Copyright 2022, Project Aktienbot"
__credits__ = ["Florian Kaiser", "Florian Kellermann", "Linus Eickhof", "Kevin Pauer"] __credits__ = ["Florian Kaiser", "Florian Kellermann", "Linus Eickhof", "Kevin Pauer"]
__license__ = "GPL 3.0" __license__ = "GPL 3.0"
__version__ = "1.0.0" __version__ = "1.0.1"
import datetime import datetime
import os import os
from apiflask import APIBlueprint, abort from apiflask import APIBlueprint, abort
from app.auth import auth
from app.models import SharePrice
from app.db import database as db from app.db import database as db
from app.helper_functions import make_response from app.helper_functions import make_response
from app.auth import auth from app.models import SharePrice
from app.schema import SymbolPriceSchema from app.schema import SymbolPriceSchema
share_price_blueprint = APIBlueprint('share_price', __name__, url_prefix='/api') share_price_blueprint = APIBlueprint('share_price', __name__, url_prefix='/api')
@ -24,7 +23,8 @@ __location__ = os.path.realpath(os.path.join(os.getcwd(), os.path.dirname(__file
@share_price_blueprint.auth_required(auth) @share_price_blueprint.auth_required(auth)
@share_price_blueprint.doc(summary="Returns all transaction symbols", description="Returns all transaction symbols for all users") @share_price_blueprint.doc(summary="Returns all transaction symbols", description="Returns all transaction symbols for all users")
def get_transaction_symbols(): def get_transaction_symbols():
symbols = db.session.execute("SELECT symbol FROM `transactions` GROUP BY symbol;").all() # Get all transaction symbols
symbols = db.session.execute("SELECT isin FROM `transactions` GROUP BY isin;").all()
return_symbols = [] return_symbols = []
for s in symbols: for s in symbols:
@ -37,10 +37,11 @@ def get_transaction_symbols():
@share_price_blueprint.output({}, 200) @share_price_blueprint.output({}, 200)
@share_price_blueprint.input(schema=SymbolPriceSchema) @share_price_blueprint.input(schema=SymbolPriceSchema)
@share_price_blueprint.auth_required(auth) @share_price_blueprint.auth_required(auth)
@share_price_blueprint.doc(summary="Returns all transaction symbols", description="Returns all transaction symbols for all users") @share_price_blueprint.doc(summary="Adds new price for isin", description="Adds new price to database")
def add_symbol_price(data): def add_symbol_price(data):
if not check_if_symbol_data_exists(data): # Check if required data is available
abort(400, message="Symbol missing") if not check_if_isin_data_exists(data):
abort(400, message="ISIN missing")
if not check_if_price_data_exists(data): if not check_if_price_data_exists(data):
abort(400, message="Price missing") abort(400, message="Price missing")
@ -48,14 +49,11 @@ def add_symbol_price(data):
if not check_if_time_data_exists(data): if not check_if_time_data_exists(data):
abort(400, message="Time missing") abort(400, message="Time missing")
symbol = data['symbol'] # Add share price
price = data['price']
time = data['time']
share_price = SharePrice( share_price = SharePrice(
symbol=symbol, isin=data['isin'],
price=price, price=data['price'],
date=datetime.datetime.strptime(time, '%Y-%m-%dT%H:%M:%S.%fZ'), date=datetime.datetime.strptime(data['time'], '%Y-%m-%dT%H:%M:%S.%fZ'),
) )
db.session.add(share_price) db.session.add(share_price)
@ -64,11 +62,11 @@ def add_symbol_price(data):
return make_response(share_price.as_dict(), 200, "Successfully added price") return make_response(share_price.as_dict(), 200, "Successfully added price")
def check_if_symbol_data_exists(data): def check_if_isin_data_exists(data):
if 'symbol' not in data: if 'isin' not in data:
return False return False
if data['symbol'] == "" or data['symbol'] is None: if data['isin'] == "" or data['isin'] is None:
return False return False
return True return True

View File

@ -2,17 +2,16 @@ __author__ = "Florian Kaiser"
__copyright__ = "Copyright 2022, Project Aktienbot" __copyright__ = "Copyright 2022, Project Aktienbot"
__credits__ = ["Florian Kaiser", "Florian Kellermann", "Linus Eickhof", "Kevin Pauer"] __credits__ = ["Florian Kaiser", "Florian Kellermann", "Linus Eickhof", "Kevin Pauer"]
__license__ = "GPL 3.0" __license__ = "GPL 3.0"
__version__ = "1.0.0" __version__ = "1.0.1"
import os import os
from apiflask import APIBlueprint, abort from apiflask import APIBlueprint, abort
from app.auth import auth from app.auth import auth
from app.db import database as db from app.db import database as db
from app.helper_functions import make_response, get_email_or_abort_401 from app.helper_functions import make_response, get_email_or_abort_401
from app.models import Share from app.models import Share
from app.schema import SymbolSchema, SymbolResponseSchema, DeleteSuccessfulSchema from app.schema import SymbolSchema, SymbolResponseSchema, DeleteSuccessfulSchema, SymbolRemoveSchema
shares_blueprint = APIBlueprint('share', __name__, url_prefix='/api') shares_blueprint = APIBlueprint('share', __name__, url_prefix='/api')
__location__ = os.path.realpath(os.path.join(os.getcwd(), os.path.dirname(__file__))) __location__ = os.path.realpath(os.path.join(os.getcwd(), os.path.dirname(__file__)))
@ -26,17 +25,21 @@ __location__ = os.path.realpath(os.path.join(os.getcwd(), os.path.dirname(__file
def add_symbol(data): def add_symbol(data):
email = get_email_or_abort_401() email = get_email_or_abort_401()
if not check_if_symbol_data_exists(data): # Check if required data is available
abort(400, message="Symbol missing") if not check_if_isin_data_exists(data):
abort(400, message="ISIN missing")
symbol = data['symbol'] if not check_if_comment_data_exists(data):
abort(400, message="Comment missing")
check_share = db.session.query(Share).filter_by(symbol=symbol, email=email).first() # Check if share already exists
check_share = db.session.query(Share).filter_by(isin=data['isin'], email=email).first()
if check_share is None: if check_share is None:
# Keyword doesn't exist yet for this user # Keyword doesn't exist yet for this user -> add it
new_symbol = Share( new_symbol = Share(
email=email, email=email,
symbol=symbol isin=data['isin'],
comment=data['comment']
) )
db.session.add(new_symbol) db.session.add(new_symbol)
db.session.commit() db.session.commit()
@ -48,23 +51,23 @@ def add_symbol(data):
@shares_blueprint.route('/share', methods=['DELETE']) @shares_blueprint.route('/share', methods=['DELETE'])
@shares_blueprint.output(DeleteSuccessfulSchema, 200) @shares_blueprint.output(DeleteSuccessfulSchema, 200)
@shares_blueprint.input(schema=SymbolSchema) @shares_blueprint.input(schema=SymbolRemoveSchema)
@shares_blueprint.auth_required(auth) @shares_blueprint.auth_required(auth)
@shares_blueprint.doc(summary="Removes existing symbol", description="Removes existing symbol for current user") @shares_blueprint.doc(summary="Removes existing symbol", description="Removes existing symbol for current user")
def remove_symbol(data): def remove_symbol(data):
email = get_email_or_abort_401() email = get_email_or_abort_401()
if not check_if_symbol_data_exists(data): # Check if required data is available
abort(400, message="Symbol missing") if not check_if_isin_data_exists(data):
abort(400, message="ISIN missing")
symbol = data['symbol']
check_share = db.session.query(Share).filter_by(symbol=symbol, email=email).first()
# Check if share exists
check_share = db.session.query(Share).filter_by(isin=data['isin'], email=email).first()
if check_share is None: if check_share is None:
abort(500, "Symbol doesn't exist for this user") abort(500, "Symbol doesn't exist for this user")
else: else:
db.session.query(Share).filter_by(symbol=symbol, email=email).delete() # Delete share
db.session.query(Share).filter_by(isin=data['isin'], email=email).delete()
db.session.commit() db.session.commit()
return make_response({}, 200, "Successfully removed symbol") return make_response({}, 200, "Successfully removed symbol")
@ -80,6 +83,8 @@ def get_symbol():
return_symbols = [] return_symbols = []
symbols = db.session.query(Share).filter_by(email=email).all() symbols = db.session.query(Share).filter_by(email=email).all()
# If no shares exist for this user -> return empty list
# Otherwise iterate over all shares, convert them to json and add them to the return list
if symbols is not None: if symbols is not None:
for row in symbols: for row in symbols:
return_symbols.append(row.as_dict()) return_symbols.append(row.as_dict())
@ -87,11 +92,21 @@ def get_symbol():
return make_response(return_symbols, 200, "Successfully loaded symbols") return make_response(return_symbols, 200, "Successfully loaded symbols")
def check_if_symbol_data_exists(data): def check_if_isin_data_exists(data):
if "symbol" not in data: if "isin" not in data:
return False return False
if data['symbol'] == "" or data['symbol'] is None: if data['isin'] == "" or data['isin'] is None:
return False
return True
def check_if_comment_data_exists(data):
if "comment" not in data:
return False
if data['comment'] == "" or data['comment'] is None:
return False return False
return True return True

View File

@ -24,11 +24,13 @@ __location__ = os.path.realpath(os.path.join(os.getcwd(), os.path.dirname(__file
def add_keyword(data): def add_keyword(data):
email = get_email_or_abort_401() email = get_email_or_abort_401()
# Check if request data is valid
if not check_if_telegram_user_id_data_exists(data): if not check_if_telegram_user_id_data_exists(data):
abort(400, message="User ID missing") abort(400, message="User ID missing")
query_user = get_user(email) query_user = get_user(email)
# Change user id
query_user.telegram_user_id = data['telegram_user_id'] query_user.telegram_user_id = data['telegram_user_id']
db.session.commit() db.session.commit()

View File

@ -2,13 +2,12 @@ __author__ = "Florian Kaiser"
__copyright__ = "Copyright 2022, Project Aktienbot" __copyright__ = "Copyright 2022, Project Aktienbot"
__credits__ = ["Florian Kaiser", "Florian Kellermann", "Linus Eickhof", "Kevin Pauer"] __credits__ = ["Florian Kaiser", "Florian Kellermann", "Linus Eickhof", "Kevin Pauer"]
__license__ = "GPL 3.0" __license__ = "GPL 3.0"
__version__ = "1.0.0" __version__ = "1.0.1"
import datetime import datetime
import os import os
from apiflask import abort, APIBlueprint from apiflask import abort, APIBlueprint
from app.auth import auth from app.auth import auth
from app.db import database as db from app.db import database as db
from app.helper_functions import make_response, get_email_or_abort_401 from app.helper_functions import make_response, get_email_or_abort_401
@ -27,21 +26,27 @@ __location__ = os.path.realpath(os.path.join(os.getcwd(), os.path.dirname(__file
def add_transaction(data): def add_transaction(data):
email = get_email_or_abort_401() email = get_email_or_abort_401()
if not check_if_symbol_data_exists(data): # Check if required data is available
abort(400, "Symbol missing") if not check_if_isin_data_exists(data):
abort(400, "ISIN missing")
if not check_if_time_data_exists(data): if not check_if_time_data_exists(data):
abort(400, "Time missing") abort(400, "Time missing")
if not check_if_comment_data_exists(data):
abort(400, "Comment missing")
if not check_if_count_data_exists(data): if not check_if_count_data_exists(data):
abort(400, "Count missing") abort(400, "Count missing")
if not check_if_price_data_exists(data): if not check_if_price_data_exists(data):
abort(400, "Price missing") abort(400, "Price missing")
# Add transaction
new_transaction = Transaction( new_transaction = Transaction(
email=email, email=email,
symbol=data['symbol'], isin=data['isin'],
comment=data['comment'],
time=datetime.datetime.strptime(data['time'], '%Y-%m-%dT%H:%M:%S.%fZ'), time=datetime.datetime.strptime(data['time'], '%Y-%m-%dT%H:%M:%S.%fZ'),
count=data['count'], count=data['count'],
price=data['price'] price=data['price']
@ -60,8 +65,11 @@ def get_transaction():
email = get_email_or_abort_401() email = get_email_or_abort_401()
return_transactions = [] return_transactions = []
# Get all transactions
transactions = db.session.query(Transaction).filter_by(email=email).all() transactions = db.session.query(Transaction).filter_by(email=email).all()
# Iterate over transactions and add them to return_transactions
if transactions is not None: if transactions is not None:
for row in transactions: for row in transactions:
return_transactions.append(row.as_dict()) return_transactions.append(row.as_dict())
@ -69,11 +77,11 @@ def get_transaction():
return make_response(return_transactions, 200, "Successfully loaded transactions") return make_response(return_transactions, 200, "Successfully loaded transactions")
def check_if_symbol_data_exists(data): def check_if_isin_data_exists(data):
if "symbol" not in data: if "isin" not in data:
return False return False
if data['symbol'] == "" or data['symbol'] is None: if data['isin'] == "" or data['isin'] is None:
return False return False
return True return True
@ -89,6 +97,16 @@ def check_if_time_data_exists(data):
return True return True
def check_if_comment_data_exists(data):
if "comment" not in data:
return False
if data['comment'] == "" or data['comment'] is None:
return False
return True
def check_if_count_data_exists(data): def check_if_count_data_exists(data):
if "count" not in data: if "count" not in data:
return False return False

View File

@ -28,6 +28,8 @@ def users():
abort_if_no_admin() abort_if_no_admin()
res = [] res = []
# Query all users and convert them to dicts
for i in User.query.all(): for i in User.query.all():
res.append(i.as_dict()) res.append(i.as_dict())
@ -41,6 +43,7 @@ def users():
def user(): def user():
email = get_email_or_abort_401() email = get_email_or_abort_401()
# Query current user
query_user = get_user(email) query_user = get_user(email)
return make_response(query_user.as_dict(), 200, "Successfully received current user data") return make_response(query_user.as_dict(), 200, "Successfully received current user data")
@ -51,23 +54,26 @@ def user():
@users_blueprint.input(schema=LoginDataSchema) @users_blueprint.input(schema=LoginDataSchema)
@users_blueprint.doc(summary="Login", description="Returns jwt token if username and password match, otherwise returns error") @users_blueprint.doc(summary="Login", description="Returns jwt token if username and password match, otherwise returns error")
def login(data): def login(data):
# Check if required data is available
if not check_if_password_data_exists(data): if not check_if_password_data_exists(data):
abort(400, "Password missing") abort(400, "Password missing")
if not check_if_email_data_exists(data): if not check_if_email_data_exists(data):
abort(400, "Email missing") abort(400, "Email missing")
email = data['email'] # Query current user
password = data['password'] query_user = get_user(data['email'])
query_user = get_user(email) # Check if password matches
if not check_password(query_user.password, data['password'].encode("utf-8")): # Password incorrect
if not check_password(query_user.password, password.encode("utf-8")): # Password incorrect
abort(500, message="Unable to login") abort(500, message="Unable to login")
# Check if user is bot
if query_user.email == current_app.config['BOT_EMAIL']: if query_user.email == current_app.config['BOT_EMAIL']:
# Set bot token valid for 1 year
token = jwt.encode({'email': query_user.email, 'exp': datetime.datetime.utcnow() + datetime.timedelta(days=365)}, current_app.config['SECRET_KEY'], "HS256") token = jwt.encode({'email': query_user.email, 'exp': datetime.datetime.utcnow() + datetime.timedelta(days=365)}, current_app.config['SECRET_KEY'], "HS256")
else: else:
# Set token valid for 1 day
token = jwt.encode({'email': query_user.email, 'exp': datetime.datetime.utcnow() + datetime.timedelta(days=1)}, current_app.config['SECRET_KEY'], "HS256") token = jwt.encode({'email': query_user.email, 'exp': datetime.datetime.utcnow() + datetime.timedelta(days=1)}, current_app.config['SECRET_KEY'], "HS256")
return make_response({"token": token}, 200, "Successfully logged in") return make_response({"token": token}, 200, "Successfully logged in")
@ -78,6 +84,7 @@ def login(data):
@users_blueprint.input(schema=RegisterDataSchema) @users_blueprint.input(schema=RegisterDataSchema)
@users_blueprint.doc(summary="Register", description="Registers user") @users_blueprint.doc(summary="Register", description="Registers user")
def register(data): def register(data):
# Check if required data is available
if not check_if_email_data_exists(data): if not check_if_email_data_exists(data):
abort(400, "Email missing") abort(400, "Email missing")
@ -87,19 +94,16 @@ def register(data):
if not check_if_password_data_exists(data): if not check_if_password_data_exists(data):
abort(400, "Password missing") abort(400, "Password missing")
email = data['email'] # Check if user already exists
username = data['username'] query_user = db.session.query(User).filter_by(email=data['email']).first()
password = data['password'] if query_user is not None:
query_user = db.session.query(User).filter_by(email=email).first()
if query_user is not None: # Username already exist
abort(500, message="Email already exist") abort(500, message="Email already exist")
# Add user to database
new_user = User( new_user = User(
email=email, email=data['email'],
username=username, username=data['username'],
password=hash_password(password), password=hash_password(data['password']),
admin=False, admin=False,
cron="0 8 * * *" cron="0 8 * * *"
) )
@ -117,11 +121,14 @@ def register(data):
def update_user(data): def update_user(data):
email = get_email_or_abort_401() email = get_email_or_abort_401()
# Query current user
query_user = get_user(email) query_user = get_user(email)
# Check if password data is available -> if, change password
if check_if_password_data_exists(data): if check_if_password_data_exists(data):
query_user.password = hash_password(data['password']) query_user.password = hash_password(data['password'])
# Check if username data is available -> if, change username
if check_if_username_data_exists(data): if check_if_username_data_exists(data):
query_user.username = data['username'] query_user.username = data['username']
@ -138,18 +145,18 @@ def update_user(data):
def set_admin(data): def set_admin(data):
abort_if_no_admin() # Only admin users can do this abort_if_no_admin() # Only admin users can do this
# Check if required data is available
if not check_if_email_data_exists(data): if not check_if_email_data_exists(data):
abort(400, "Email missing") abort(400, "Email missing")
if not check_if_admin_data_exists(data): if not check_if_admin_data_exists(data):
abort(400, "Admin data missing") abort(400, "Admin data missing")
email = data['email'] # Get user by email
admin = data['admin'] query_user = get_user(data['email'])
query_user = get_user(email) # Update user admin state
query_user.admin = data['admin']
query_user.admin = admin
db.session.commit() db.session.commit()
return make_response({}, 200, "Successfully updated users admin rights") return make_response({}, 200, "Successfully updated users admin rights")
@ -163,9 +170,11 @@ def set_admin(data):
def set_cron(data): def set_cron(data):
email = get_email_or_abort_401() email = get_email_or_abort_401()
# Check if required data is available
if not check_if_cron_data_exists(data): if not check_if_cron_data_exists(data):
abort(400, "Cron data missing") abort(400, "Cron data missing")
# Update user cron
get_user(email).cron = data['cron'] get_user(email).cron = data['cron']
db.session.commit() db.session.commit()
@ -178,18 +187,22 @@ def set_cron(data):
@users_blueprint.auth_required(auth) @users_blueprint.auth_required(auth)
@users_blueprint.doc(summary="Delete user", description="Deletes user by username") @users_blueprint.doc(summary="Delete user", description="Deletes user by username")
def delete_user(data): def delete_user(data):
# Check if required data is available
if not check_if_email_data_exists(data): if not check_if_email_data_exists(data):
abort(400, "Email missing") abort(400, "Email missing")
email = data['email'] # Check if email to delete is current user
# -> if, delete user
if email == get_email_or_abort_401(): # Username is same as current user # -> if not, check if user is admin
db.session.query(User).filter_by(email=email).delete() # -> if, delete user
# -> else, abort
if data['email'] == get_email_or_abort_401(): # Username is same as current user
db.session.query(User).filter_by(email=data['email']).delete()
db.session.commit() db.session.commit()
else: # Delete different user than my user -> only admin users else:
abort_if_no_admin() abort_if_no_admin()
db.session.query(User).filter_by(email=email).delete() db.session.query(User).filter_by(email=data['email']).delete()
db.session.commit() db.session.commit()
return make_response({}, 200, "Successfully removed user") return make_response({}, 200, "Successfully removed user")

View File

@ -14,28 +14,48 @@ from flask import request, jsonify
def hash_password(password): def hash_password(password):
"""
Hash plain password to save it in the database
"""
return bcrypt.hashpw(password.encode("utf-8"), bcrypt.gensalt()) return bcrypt.hashpw(password.encode("utf-8"), bcrypt.gensalt())
def check_password(hashed_password, user_password): def check_password(hashed_password, user_password):
"""
Check if the password is correct using the bcrypt checkpw function
"""
return bcrypt.checkpw(user_password, hashed_password) return bcrypt.checkpw(user_password, hashed_password)
def get_email_from_token_data(token): def get_email_from_token_data(token):
"""
Extract email from token data
"""
# If token is not provided-> return None
if token is None or len(token) < 2: if token is None or len(token) < 2:
return None return None
else: else:
# Token contains "Bearer " -> remove it
token = token[1] token = token[1]
# Again: Check if token is not None
# Don't know why, but sometimes the token is None
if token is not None: if token is not None:
if ':' in token: # Maybe bot token, check if token valid and return username after ":" then
# We decided to append the user id to the bearer token using ":" as separator to select an specific user
# If the token contains ":" -> It may be a bot token
# If token valid -> return user email, not bot email
if ':' in token:
telegram_user_id = token.split(":")[1] telegram_user_id = token.split(":")[1]
token = token.split(":")[0] token = token.split(":")[0]
try: try:
# Only allow selecting users with telegram_user_id if current user is the bot user
if jwt.decode(token, current_app.config['SECRET_KEY'], algorithms=["HS256"])['email'] == current_app.config['BOT_EMAIL']: if jwt.decode(token, current_app.config['SECRET_KEY'], algorithms=["HS256"])['email'] == current_app.config['BOT_EMAIL']:
res = db.session.query(User).filter_by(telegram_user_id=telegram_user_id).first() res = db.session.query(User).filter_by(telegram_user_id=telegram_user_id).first()
# Check if user id exists
if res is not None: if res is not None:
return res.as_dict()['email'] return res.as_dict()['email']
else: else:
@ -47,12 +67,18 @@ def get_email_from_token_data(token):
else: # "Normal" token, extract username from token else: # "Normal" token, extract username from token
try: try:
# Return email from token if token is valid
return jwt.decode(token, current_app.config['SECRET_KEY'], algorithms=["HS256"])['email'] return jwt.decode(token, current_app.config['SECRET_KEY'], algorithms=["HS256"])['email']
except jwt.PyJWTError: except jwt.PyJWTError:
return None return None
def get_token(): def get_token():
"""
Extract token from Authorization header
"""
# Check if Authorization header is provided
if 'Authorization' in request.headers: if 'Authorization' in request.headers:
return request.headers['Authorization'].split(" ") return request.headers['Authorization'].split(" ")
else: else:
@ -60,7 +86,10 @@ def get_token():
def get_email_or_abort_401(): def get_email_or_abort_401():
# get username from jwt token """
Try to receive email from token data
If email is not provided -> abort 401
"""
email = get_email_from_token_data(get_token()) email = get_email_from_token_data(get_token())
if email is None: # If token not provided or invalid -> return 401 code if email is None: # If token not provided or invalid -> return 401 code
@ -70,24 +99,38 @@ def get_email_or_abort_401():
def abort_if_no_admin(): def abort_if_no_admin():
"""
Check if user is admin
If not -> abort 401
"""
if not is_user_admin(): if not is_user_admin():
abort(401, message="Only admin users can access this") abort(401, message="Only admin users can access this")
def is_user_admin(): def is_user_admin():
"""
Return users admin status
"""
email = get_email_or_abort_401() email = get_email_or_abort_401()
return db.session.query(User).filter_by(email=email).first().admin return db.session.query(User).filter_by(email=email).first().admin
def make_response(data, status=200, text=""): def make_response(data, status=200, text=""):
"""
Generate response object
"""
return jsonify({"status": status, "text": text, "data": data}) return jsonify({"status": status, "text": text, "data": data})
def get_user(email): def get_user(email):
"""
Get user from database
"""
query_user = db.session.query(User).filter_by(email=email).first() query_user = db.session.query(User).filter_by(email=email).first()
if query_user is None: # Username doesn't exist # Check if user exists
if query_user is None:
abort(500, message="Can't find user") abort(500, message="Can't find user")
return query_user return query_user

View File

@ -2,7 +2,7 @@ __author__ = "Florian Kaiser"
__copyright__ = "Copyright 2022, Project Aktienbot" __copyright__ = "Copyright 2022, Project Aktienbot"
__credits__ = ["Florian Kaiser", "Florian Kellermann", "Linus Eickhof", "Kevin Pauer"] __credits__ = ["Florian Kaiser", "Florian Kellermann", "Linus Eickhof", "Kevin Pauer"]
__license__ = "GPL 3.0" __license__ = "GPL 3.0"
__version__ = "1.0.0" __version__ = "1.0.1"
from app.db import database as db from app.db import database as db
@ -30,7 +30,8 @@ class Transaction(db.Model):
__tablename__ = 'transactions' __tablename__ = 'transactions'
t_id = db.Column('t_id', db.Integer(), nullable=False, unique=True, primary_key=True) t_id = db.Column('t_id', db.Integer(), nullable=False, unique=True, primary_key=True)
email = db.Column('email', db.String(255), db.ForeignKey('users.email', ondelete='CASCADE')) email = db.Column('email', db.String(255), db.ForeignKey('users.email', ondelete='CASCADE'))
symbol = db.Column('symbol', db.String(255)) isin = db.Column('isin', db.String(255))
comment = db.Column('comment', db.String(255))
time = db.Column('time', db.DateTime()) time = db.Column('time', db.DateTime())
count = db.Column('count', db.Integer()) count = db.Column('count', db.Integer())
price = db.Column('price', db.Float()) price = db.Column('price', db.Float())
@ -53,7 +54,8 @@ class Share(db.Model):
__tablename__ = 'shares' __tablename__ = 'shares'
a_id = db.Column('a_id', db.Integer(), nullable=False, unique=True, primary_key=True) a_id = db.Column('a_id', db.Integer(), nullable=False, unique=True, primary_key=True)
email = db.Column('email', db.String(255), db.ForeignKey('users.email', ondelete='CASCADE')) email = db.Column('email', db.String(255), db.ForeignKey('users.email', ondelete='CASCADE'))
symbol = db.Column('symbol', db.String(255)) isin = db.Column('isin', db.String(255))
comment = db.Column('comment', db.String(255))
def as_dict(self): def as_dict(self):
return {c.name: getattr(self, c.name) for c in self.__table__.columns} return {c.name: getattr(self, c.name) for c in self.__table__.columns}
@ -62,7 +64,7 @@ class Share(db.Model):
class SharePrice(db.Model): class SharePrice(db.Model):
__tablename__ = 'share_price' __tablename__ = 'share_price'
id = db.Column('id', db.Integer(), nullable=False, unique=True, primary_key=True) id = db.Column('id', db.Integer(), nullable=False, unique=True, primary_key=True)
symbol = db.Column('symbol', db.String(255)) isin = db.Column('isin', db.String(255))
price = db.Column('price', db.Float()) price = db.Column('price', db.Float())
date = db.Column('date', db.DateTime()) date = db.Column('date', db.DateTime())

View File

@ -2,7 +2,7 @@ __author__ = "Florian Kaiser"
__copyright__ = "Copyright 2022, Project Aktienbot" __copyright__ = "Copyright 2022, Project Aktienbot"
__credits__ = ["Florian Kaiser", "Florian Kellermann", "Linus Eickhof", "Kevin Pauer"] __credits__ = ["Florian Kaiser", "Florian Kellermann", "Linus Eickhof", "Kevin Pauer"]
__license__ = "GPL 3.0" __license__ = "GPL 3.0"
__version__ = "1.0.0" __version__ = "1.0.1"
from apiflask import Schema from apiflask import Schema
from apiflask.fields import Integer, String, Boolean, Field, Float from apiflask.fields import Integer, String, Boolean, Field, Float
@ -22,6 +22,7 @@ class UsersSchema(Schema):
username = String() username = String()
telegram_user_id = String() telegram_user_id = String()
email = Email() email = Email()
cron = String()
class AdminDataSchema(Schema): class AdminDataSchema(Schema):
@ -71,18 +72,24 @@ class KeywordSchema(Schema):
class SymbolSchema(Schema): class SymbolSchema(Schema):
symbol = String() isin = String()
comment = String()
class SymbolRemoveSchema(Schema):
isin = String()
class TransactionSchema(Schema): class TransactionSchema(Schema):
symbol = String() isin = String()
comment = String()
time = String(validate=validate.Regexp(r"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z")) time = String(validate=validate.Regexp(r"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z"))
count = Integer() count = Integer()
price = Float() price = Float()
class SymbolPriceSchema(Schema): class SymbolPriceSchema(Schema):
symbol = String() isin = String()
time = String(validate=validate.Regexp(r"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z")) time = String(validate=validate.Regexp(r"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z"))
price = Float() price = Float()
@ -102,7 +109,8 @@ class KeywordResponseSchema(Schema):
class SymbolResponseSchema(Schema): class SymbolResponseSchema(Schema):
symbol = String() isin = String()
comment = String()
s_id = Integer() s_id = Integer()
email = Email() email = Email()
@ -114,14 +122,16 @@ class PortfolioShareResponseSchema(Schema):
class TransactionResponseSchema(Schema): class TransactionResponseSchema(Schema):
email = Email() email = Email()
symbol = String() isin = String()
comment = String()
time = String() time = String()
count = Integer() count = Integer()
price = Float() price = Float()
class PortfolioResponseSchema(Schema): class PortfolioResponseSchema(Schema):
symbol = String() isin = String()
comment = String()
last_transaction = String() last_transaction = String()
count = Integer() count = Integer()
# price = Float() # price = Float()

View File

@ -20,7 +20,7 @@ fake = faker.Faker()
token = requests.post(os.getenv("API_URL") + '/user/login', json={"email": username, "password": password}).json()['data']['token'] token = requests.post(os.getenv("API_URL") + '/user/login', json={"email": username, "password": password}).json()['data']['token']
for i in range(1, 1000): for i in range(1, 10):
payload = { payload = {
"count": random.randint(1, 100), "count": random.randint(1, 100),
"price": random.random() * 100, "price": random.random() * 100,

View File

@ -8,9 +8,9 @@ pyjwt==2.3.0
apiflask==0.12.0 apiflask==0.12.0
flask-cors==3.0.10 flask-cors==3.0.10
bcrypt==3.2.0 bcrypt==3.2.0
pytest~=7.1.1 pytest~=7.1.2
pytest-cov pytest-cov
marshmallow~=3.15.0 marshmallow~=3.15.0
faker~=13.3.4 faker~=13.4.0
yfinance~=0.1.70 yfinance~=0.1.70
requests~=2.27.1 requests~=2.27.1

3
deploy/base/.env Normal file
View File

@ -0,0 +1,3 @@
WATCHTOWER_SCHEDULE=0 5 3 * * *
WATCHTOWER_ROLLING_RESTART=true
WATCHTOWER_CLEANUP=true

View File

@ -64,6 +64,14 @@ services:
- portainer_data:/data - portainer_data:/data
- /var/run/docker.sock:/var/run/docker.sock - /var/run/docker.sock:/var/run/docker.sock
watchtower:
image: containrrr/watchtower
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- /etc/localtime:/etc/localtime:ro
env_file:
- ${PWD}/.env
networks: networks:
default: default:
external: external:

File diff suppressed because it is too large Load Diff

View File

@ -16,7 +16,7 @@
"@angular/compiler": "~13.2.0", "@angular/compiler": "~13.2.0",
"@angular/core": "~13.2.0", "@angular/core": "~13.2.0",
"@angular/forms": "~13.2.0", "@angular/forms": "~13.2.0",
"@angular/material": "^13.3.2", "@angular/material": "^13.3.3",
"@angular/platform-browser": "~13.2.0", "@angular/platform-browser": "~13.2.0",
"@angular/platform-browser-dynamic": "~13.2.0", "@angular/platform-browser-dynamic": "~13.2.0",
"@angular/router": "~13.2.0", "@angular/router": "~13.2.0",
@ -26,16 +26,16 @@
"zone.js": "~0.11.4" "zone.js": "~0.11.4"
}, },
"devDependencies": { "devDependencies": {
"@angular-devkit/build-angular": "~13.3.1", "@angular-devkit/build-angular": "~13.3.3",
"@angular/cli": "~13.3.1", "@angular/cli": "~13.3.3",
"@angular/compiler-cli": "~13.2.0", "@angular/compiler-cli": "~13.2.0",
"@types/jasmine": "~4.0.2", "@types/jasmine": "~4.0.3",
"@types/node": "^17.0.23", "@types/node": "^17.0.25",
"jasmine-core": "~4.0.0", "karma": "~6.3.18",
"karma": "~6.3.0", "jasmine-core": "~4.1.0",
"karma-chrome-launcher": "~3.1.0", "karma-chrome-launcher": "~3.1.0",
"karma-coverage": "~2.2.0", "karma-coverage": "~2.2.0",
"karma-jasmine": "~4.0.0", "karma-jasmine": "~5.0.0",
"karma-jasmine-html-reporter": "~1.7.0", "karma-jasmine-html-reporter": "~1.7.0",
"typescript": "~4.5.2" "typescript": "~4.5.2"
} }

View File

@ -95,17 +95,17 @@ export class BotService {
} }
/** /**
* @param {string} share * @param {string} symbol
* @returns Observable * @returns Observable
*/ */
public deleteShare(share: string): Observable<any> { public deleteShare(symbol: string): Observable<any> {
return this.http.delete(API_URL + 'share', { return this.http.delete(API_URL + 'share', {
headers: new HttpHeaders({ headers: new HttpHeaders({
'Content-Type': 'application/json', 'Content-Type': 'application/json',
Authorization: 'Bearer ' + this.tokenStorage.getToken(), Authorization: 'Bearer ' + this.tokenStorage.getToken(),
}), }),
body: { body: {
share, symbol,
}, },
}); });
} }

View File

@ -11,4 +11,4 @@ RUN pip install -r requirements.txt --src /usr/local/src --no-warn-script-locati
COPY telegram_bot /srv/flask_app COPY telegram_bot /srv/flask_app
# Run the application # Run the application
CMD ["python bot.py"] CMD ["/usr/local/bin/python", "bot.py"]

View File

@ -11,4 +11,4 @@ RUN pip install -r requirements.txt --src /usr/local/src --no-warn-script-locati
COPY telegram_bot /srv/flask_app COPY telegram_bot /srv/flask_app
# Run the application # Run the application
CMD ["python bot_updates.py"] CMD ["/usr/local/bin/python", "bot_updates.py"]

View File

@ -1,4 +1,4 @@
pyTelegramBotAPI~=4.4.0 pyTelegramBotAPI~=4.5.0
Markdown~=3.3.6 Markdown~=3.3.6
yfinance~=0.1.70 yfinance~=0.1.70
newsapi-python~=0.2.6 newsapi-python~=0.2.6