diff --git a/api/api_blueprint_keyword.py b/api/api_blueprint_keyword.py index 7474bcb..232a97b 100644 --- a/api/api_blueprint_keyword.py +++ b/api/api_blueprint_keyword.py @@ -1,12 +1,11 @@ import os from apiflask import APIBlueprint, abort -from flask import jsonify from db import db -from helper_functions import get_user_id_from_username, get_username_or_abort_401 +from helper_functions import get_user_id_from_username, get_username_or_abort_401, make_response from auth import auth -from scheme import KeywordResponseSchema, KeywordSchema, DeleteSuccessfulSchema +from schema import KeywordResponseSchema, KeywordSchema, DeleteSuccessfulSchema from models import Keyword keyword_blueprint = APIBlueprint('keyword', __name__, url_prefix='/api') @@ -35,7 +34,7 @@ def add_keyword(data): db.session.add(new_keyword) db.session.commit() - return jsonify({"status": 200, "text": "Successfully added keyword", "data": new_keyword.as_dict()}) + return make_response(new_keyword.as_dict(), 200, "Successfully added keyword") else: abort(500, message="Keyword already exist for this user") @@ -55,7 +54,7 @@ def remove_keyword(data): db.session.query(Keyword).filter_by(keyword=key, user_id=get_user_id_from_username(username)).delete() db.session.commit() - return jsonify({"status": 200, "text": "Successfully removed keyword", "data": {}}) + return make_response({}, 200, "Successfully removed keyword") @keyword_blueprint.route('/keywords', methods=['GET']) @@ -72,7 +71,7 @@ def get_keywords(): for row in keywords: return_keywords.append(row.as_dict()) - return jsonify({"status": 200, "text": "Successfully loaded keywords", "data": return_keywords}) + return make_response(return_keywords, 200, "Successfully loaded keywords") def check_if_keyword_data_exists(data): diff --git a/api/api_blueprint_portfolio.py b/api/api_blueprint_portfolio.py index 2a54275..5970ced 100644 --- a/api/api_blueprint_portfolio.py +++ b/api/api_blueprint_portfolio.py @@ -1,10 +1,9 @@ import os from apiflask import APIBlueprint -from flask import jsonify from db import db -from helper_functions import get_user_id_from_username, get_username_or_abort_401 +from helper_functions import get_user_id_from_username, get_username_or_abort_401, make_response from models import Transaction from auth import auth @@ -30,4 +29,4 @@ def get_portfolio(): else: return_portfolio[row.symbol] = {"count": row.count, "last_transaction": row.time} - return jsonify({"status": 200, "text": "Successfully loaded symbols", "data": return_portfolio}) + return make_response(return_portfolio, 200, "Successfully loaded symbols") diff --git a/api/api_blueprint_shares.py b/api/api_blueprint_shares.py index ef68d99..3e18e8c 100644 --- a/api/api_blueprint_shares.py +++ b/api/api_blueprint_shares.py @@ -1,13 +1,12 @@ import os from apiflask import APIBlueprint, abort -from flask import jsonify from auth import auth from db import db -from helper_functions import get_user_id_from_username, get_username_or_abort_401 +from helper_functions import get_user_id_from_username, get_username_or_abort_401, make_response from models import Share -from scheme import SymbolSchema, SymbolResponseSchema, DeleteSuccessfulSchema +from schema import SymbolSchema, SymbolResponseSchema, DeleteSuccessfulSchema shares_blueprint = APIBlueprint('share', __name__, url_prefix='/api') __location__ = os.path.realpath(os.path.join(os.getcwd(), os.path.dirname(__file__))) @@ -35,9 +34,9 @@ def add_symbol(data): db.session.add(new_symbol) db.session.commit() - return jsonify({"status": 200, "text": "Successfully added symbol", "data": new_symbol.as_dict()}) + return make_response(new_symbol.as_dict(), 200, "Successfully added symbol") else: - return jsonify({"status": 500, "text": "Symbol already exist for this user"}) + return make_response({}, 500, "Symbol already exist for this user") @shares_blueprint.route('/share', methods=['DELETE']) @@ -55,7 +54,7 @@ def remove_symbol(data): db.session.query(Share).filter_by(symbol=symbol, user_id=get_user_id_from_username(username)).delete() db.session.commit() - return jsonify({"status": 200, "text": "Successfully removed symbol", "data": {}}) + return make_response({}, 200, "Successfully removed symbol") @shares_blueprint.route('/shares', methods=['GET']) @@ -72,7 +71,7 @@ def get_symbol(): for row in symbols: return_symbols.append(row.as_dict()) - return jsonify({"status": 200, "text": "Successfully loaded symbols", "data": return_symbols}) + return make_response(return_symbols, 200, "Successfully loaded symbols") def check_if_symbol_data_exists(data): diff --git a/api/api_blueprint_transactions.py b/api/api_blueprint_transactions.py index 2a8fed2..b0e58fc 100644 --- a/api/api_blueprint_transactions.py +++ b/api/api_blueprint_transactions.py @@ -2,12 +2,11 @@ import os import datetime from apiflask import abort, APIBlueprint -from flask import jsonify from db import db -from helper_functions import get_user_id_from_username, get_username_or_abort_401 +from helper_functions import get_user_id_from_username, get_username_or_abort_401, make_response from models import Transaction -from scheme import TransactionSchema +from schema import TransactionSchema from auth import auth transaction_blueprint = APIBlueprint('transaction', __name__, url_prefix='/api') @@ -34,7 +33,7 @@ def add_transaction(data): db.session.add(new_transaction) db.session.commit() - return jsonify({"status": 200, "text": "Successfully added transaction", "data": new_transaction.as_dict()}) + return make_response(new_transaction.as_dict(), 200, "Successfully added transaction") @transaction_blueprint.route('/transactions', methods=['GET']) @@ -51,7 +50,7 @@ def get_transaction(): for row in transactions: return_transactions.append(row.as_dict()) - return jsonify({"status": 200, "text": "Successfully loaded transactions", "data": return_transactions}) + return make_response(return_transactions, 200, "Successfully loaded transactions") def check_if_transaction_data_exists(data): diff --git a/api/api_blueprint_user.py b/api/api_blueprint_user.py index c7e6940..747a82f 100644 --- a/api/api_blueprint_user.py +++ b/api/api_blueprint_user.py @@ -3,12 +3,11 @@ import os import jwt from apiflask import APIBlueprint, abort -from flask import jsonify from db import db -from helper_functions import check_password, hash_password, get_username_or_abort_401, abort_if_no_admin +from helper_functions import check_password, hash_password, get_username_or_abort_401, abort_if_no_admin, make_response from models import User -from scheme import UsersSchema, TokenSchema, LoginDataSchema, AdminDataSchema, DeleteUserSchema +from schema import UsersSchema, TokenSchema, LoginDataSchema, AdminDataSchema, DeleteUserSchema from auth import auth users_blueprint = APIBlueprint('users', __name__, url_prefix='/api') @@ -26,7 +25,7 @@ def users(): for i in User.query.all(): res.append(i.as_dict()) - return jsonify({"status": 200, "data": res}) + return make_response(res, 200, "Successfully received all users") @users_blueprint.route('/user', methods=['GET']) @@ -38,7 +37,7 @@ def user(): res = db.session.query(User).filter_by(username=username).first().as_dict() - return jsonify({"status": 200, "data": res}) + return make_response(res, 200, "Successfully received current user data") @users_blueprint.route('/user/login', methods=['POST']) @@ -60,7 +59,8 @@ def login(data): 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") - return jsonify({"status": 200, "text": "Successfully logged in", "data": {"token": token}}) + + return make_response({"token": token}, 200, "Successfully logged in") @users_blueprint.route('/user/register', methods=['POST']) @@ -86,7 +86,7 @@ def register(data): db.session.add(new_user) db.session.commit() - return jsonify({"status": 200, "text": "Successfully registered user", "data": new_user.as_dict()}) + return make_response(new_user.as_dict(), 200, "Successfully registered user") @users_blueprint.route('/user', methods=['PUT']) @@ -114,7 +114,7 @@ def update_user(data): db.session.commit() - return jsonify({"status": 200, "text": "Successfully updated user", "data": {}}) + return make_response({}, 200, "Successfully updated user") @users_blueprint.route('/user/setAdmin', methods=['PUT']) @@ -138,7 +138,7 @@ def set_admin(data): query_user.admin = admin db.session.commit() - return jsonify({"status": 200, "text": "Successfully updated users admin rights", "data": {}}) + return make_response({}, 200, "Successfully updated users admin rights") @users_blueprint.route('/user', methods=['DELETE']) @@ -160,7 +160,7 @@ def delete_user(data): db.session.query(User).filter_by(username=username).delete() db.session.commit() - return jsonify({"status": 200, "text": "Successfully removed user", "data": {}}) + return make_response({}, 200, "Successfully removed user") def check_if_user_data_exists(data): diff --git a/api/app.py b/api/app.py index 193bb4b..933b471 100644 --- a/api/app.py +++ b/api/app.py @@ -24,9 +24,6 @@ def create_app(): db.init_app(application) - # Create all tables - db.create_all() - # api blueprints application.register_blueprint(keyword_blueprint) application.register_blueprint(shares_blueprint) @@ -34,6 +31,10 @@ def create_app(): application.register_blueprint(portfolio_blueprint) application.register_blueprint(users_blueprint) + @application.before_first_request + def init_database(): + db.create_all() + return application diff --git a/api/auth.py b/api/auth.py index 7db89a7..8096012 100644 --- a/api/auth.py +++ b/api/auth.py @@ -11,6 +11,9 @@ def verify_token(token): if token is None: return False + if ':' in token: # Bot token + token = token.split(":")[0] + try: jwt.decode(token, os.getenv('SECRET_KEY'), algorithms=["HS256"]) return True diff --git a/api/config.py b/api/config.py index d3f69e6..a12c60a 100644 --- a/api/config.py +++ b/api/config.py @@ -2,7 +2,7 @@ import os from dotenv import load_dotenv -from scheme import BaseResponseSchema +from schema import BaseResponseSchema load_dotenv() diff --git a/api/helper_functions.py b/api/helper_functions.py index 2791494..59991c6 100644 --- a/api/helper_functions.py +++ b/api/helper_functions.py @@ -4,7 +4,7 @@ import uuid import jwt from apiflask import abort -from flask import request +from flask import request, jsonify from db import db from models import User @@ -38,11 +38,30 @@ def extract_token_data(token): return None -def get_username_from_token_data(token_data): - if token_data is not None: - return token_data['username'] - else: - return None +def get_username_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] + token = token.split(":")[0] + + try: + if jwt.decode(token, os.getenv('SECRET_KEY'), algorithms=["HS256"])['username'] == "bot": + return username + else: + return None + except jwt.exceptions.DecodeError: + 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 None + + return None def get_user_id_from_username(username): @@ -54,7 +73,7 @@ def get_user_id_from_username(username): def get_username_or_abort_401(): # get username from jwt token - username = get_username_from_token_data(extract_token_data(get_token())) + username = get_username_from_token_data() if username is None: # If token not provided or invalid -> return 401 code abort(401, message="Unable to login") @@ -71,3 +90,7 @@ def is_user_admin(): username = get_username_or_abort_401() return db.session.query(User).filter_by(username=username).first().admin + + +def make_response(data, status=200, text=""): + return jsonify({"status": status, "text": text, "data": {"token": data}}) diff --git a/api/requirements.txt b/api/requirements.txt index 91de9c7..70218cf 100644 --- a/api/requirements.txt +++ b/api/requirements.txt @@ -1,12 +1,9 @@ Flask~=2.0.3 python-dotenv==0.19.2 -requests==2.27.1 uwsgi==2.0.20 Flask_SQLAlchemy==2.5.1 python-dotenv==0.19.2 pymysql==1.0.2 pyjwt==2.3.0 apiflask==0.12.0 -flask-swagger-ui==3.36.0 -flask-cors==3.0.10 - +flask-cors==3.0.10 \ No newline at end of file diff --git a/api/scheme.py b/api/schema.py similarity index 100% rename from api/scheme.py rename to api/schema.py diff --git a/documentation/README.md b/documentation/README.md index 3bdec2f..b575c73 100644 --- a/documentation/README.md +++ b/documentation/README.md @@ -1,8 +1,5 @@ # Dokumentation -## Swagger Documentation -Visit https://aktienbot.flokaiser.com/api/docs - ## API - `api/openapi.json` - OpenAPI-Dokumentation @@ -28,4 +25,4 @@ Function|Admin|No Admin| |Delete user|yes|only current user| |Update users|yes|only current user| |Set admin state|yes|no| -|Show all users|yes|no| +|Show all users|yes|no| \ No newline at end of file diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 1eb150b..d149dc2 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -28,7 +28,7 @@ "@angular/cli": "~13.3.0", "@angular/compiler-cli": "~13.2.0", "@types/jasmine": "~4.0.0", - "@types/node": "^17.0.22", + "@types/node": "^17.0.21", "jasmine-core": "~4.0.0", "karma": "~6.3.0", "karma-chrome-launcher": "~3.1.0", @@ -2730,9 +2730,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "17.0.22", - "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.22.tgz", - "integrity": "sha512-8FwbVoG4fy+ykY86XCAclKZDORttqE5/s7dyWZKLXTdv3vRy5HozBEinG5IqhvPXXzIZEcTVbuHlQEI6iuwcmw==", + "version": "17.0.21", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.21.tgz", + "integrity": "sha512-DBZCJbhII3r90XbQxI8Y9IjjiiOGlZ0Hr32omXIZvwwZ7p4DMMXGrKXVyPfuoBOri9XNtL0UK69jYIBIsRX3QQ==", "dev": true }, "node_modules/@types/parse-json": { @@ -13361,9 +13361,9 @@ "dev": true }, "@types/node": { - "version": "17.0.22", - "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.22.tgz", - "integrity": "sha512-8FwbVoG4fy+ykY86XCAclKZDORttqE5/s7dyWZKLXTdv3vRy5HozBEinG5IqhvPXXzIZEcTVbuHlQEI6iuwcmw==", + "version": "17.0.21", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.21.tgz", + "integrity": "sha512-DBZCJbhII3r90XbQxI8Y9IjjiiOGlZ0Hr32omXIZvwwZ7p4DMMXGrKXVyPfuoBOri9XNtL0UK69jYIBIsRX3QQ==", "dev": true }, "@types/parse-json": { diff --git a/frontend/package.json b/frontend/package.json index 21e5411..8b58ec3 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -30,7 +30,7 @@ "@angular/cli": "~13.3.0", "@angular/compiler-cli": "~13.2.0", "@types/jasmine": "~4.0.0", - "@types/node": "^17.0.22", + "@types/node": "^17.0.21", "jasmine-core": "~4.0.0", "karma": "~6.3.0", "karma-chrome-launcher": "~3.1.0",