From 80b2ebdaf99bd47d87436feb287586f205a87f7d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 29 Mar 2022 09:42:19 +0000 Subject: [PATCH 01/33] Update flask requirement from ~=2.0.3 to ~=2.1.0 in /api Updates the requirements on [flask](https://github.com/pallets/flask) to permit the latest version. - [Release notes](https://github.com/pallets/flask/releases) - [Changelog](https://github.com/pallets/flask/blob/main/CHANGES.rst) - [Commits](https://github.com/pallets/flask/compare/2.0.3...2.1.0) --- updated-dependencies: - dependency-name: flask dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- api/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/requirements.txt b/api/requirements.txt index 0ab141c..57da142 100644 --- a/api/requirements.txt +++ b/api/requirements.txt @@ -1,4 +1,4 @@ -Flask~=2.0.3 +Flask~=2.1.0 python-dotenv==0.20.0 uwsgi==2.0.20 Flask_SQLAlchemy==2.5.1 From 21d2bc334cb189e789373362290c2e9a467469c5 Mon Sep 17 00:00:00 2001 From: H4CK3R-01 Date: Wed, 30 Mar 2022 10:46:54 +0200 Subject: [PATCH 02/33] Tests - Improved directory structure - Added functional and unit tests --- api/app.py | 76 +-- api/app/__init__.py | 67 +++ api/{ => app}/auth.py | 4 +- api/app/blueprints/__init__.py | 0 .../blueprints/keyword.py} | 24 +- .../blueprints/portfolio.py} | 8 +- .../blueprints/shares.py} | 26 +- .../blueprints/telegram.py} | 24 +- .../blueprints/transactions.py} | 54 +- .../blueprints/user.py} | 79 ++- api/app/config/flask.cfg | 26 + api/app/config/flask_test.cfg | 26 + api/{ => app}/db.py | 2 +- api/{ => app}/helper_functions.py | 35 +- api/{ => app}/models.py | 2 +- api/{ => app}/schema.py | 0 api/config.py | 55 -- api/requirements.txt | 4 +- api/tests/conftest.py | 108 ++++ api/tests/functional/__init__.py | 0 api/tests/functional/test_keyword.py | 191 +++++++ api/tests/functional/test_portfolio.py | 59 ++ api/tests/functional/test_share.py | 191 +++++++ api/tests/functional/test_telegram.py | 66 +++ api/tests/functional/test_transaction.py | 103 ++++ api/tests/functional/test_user.py | 513 ++++++++++++++++++ api/tests/pytest.ini | 0 api/tests/unit/__init__.py | 0 api/tests/unit/test_auth.py | 14 + api/tests/unit/test_helper_functions.py | 67 +++ api/tests/unit/test_models.py | 116 ++++ api/tests/unit/test_transaction.py | 40 ++ api/tests/unit/test_user.py | 40 ++ 33 files changed, 1782 insertions(+), 238 deletions(-) create mode 100644 api/app/__init__.py rename api/{ => app}/auth.py (73%) create mode 100644 api/app/blueprints/__init__.py rename api/{api_blueprint_keyword.py => app/blueprints/keyword.py} (85%) rename api/{api_blueprint_portfolio.py => app/blueprints/portfolio.py} (85%) rename api/{api_blueprint_shares.py => app/blueprints/shares.py} (83%) rename api/{api_blueprint_telegram.py => app/blueprints/telegram.py} (73%) rename api/{api_blueprint_transactions.py => app/blueprints/transactions.py} (67%) rename api/{api_blueprint_user.py => app/blueprints/user.py} (75%) create mode 100644 api/app/config/flask.cfg create mode 100644 api/app/config/flask_test.cfg rename api/{ => app}/db.py (63%) rename api/{ => app}/helper_functions.py (72%) rename api/{ => app}/models.py (98%) rename api/{ => app}/schema.py (100%) delete mode 100644 api/config.py create mode 100644 api/tests/conftest.py create mode 100644 api/tests/functional/__init__.py create mode 100644 api/tests/functional/test_keyword.py create mode 100644 api/tests/functional/test_portfolio.py create mode 100644 api/tests/functional/test_share.py create mode 100644 api/tests/functional/test_telegram.py create mode 100644 api/tests/functional/test_transaction.py create mode 100644 api/tests/functional/test_user.py create mode 100644 api/tests/pytest.ini create mode 100644 api/tests/unit/__init__.py create mode 100644 api/tests/unit/test_auth.py create mode 100644 api/tests/unit/test_helper_functions.py create mode 100644 api/tests/unit/test_models.py create mode 100644 api/tests/unit/test_transaction.py create mode 100644 api/tests/unit/test_user.py diff --git a/api/app.py b/api/app.py index 7225166..0424fca 100644 --- a/api/app.py +++ b/api/app.py @@ -1,72 +1,6 @@ -import os +from app import create_app -from apiflask import APIFlask - -from dotenv import load_dotenv -from flask_cors import CORS - -from api_blueprint_keyword import keyword_blueprint -from api_blueprint_portfolio import portfolio_blueprint -from api_blueprint_shares import shares_blueprint -from api_blueprint_transactions import transaction_blueprint -from api_blueprint_telegram import telegram_blueprint -from helper_functions import hash_password -from models import * -from api_blueprint_user import users_blueprint - - -def create_app(): - load_dotenv() - - # Create Flask app load app.config - application = APIFlask(__name__, openapi_blueprint_url_prefix='/api') - application.config.from_object("config.ConfigClass") - - CORS(application, resources={r"*": {"origins": "*"}}) - - application.app_context().push() - - db.init_app(application) - - # api blueprints - application.register_blueprint(keyword_blueprint) - application.register_blueprint(shares_blueprint) - application.register_blueprint(transaction_blueprint) - application.register_blueprint(portfolio_blueprint) - application.register_blueprint(users_blueprint) - application.register_blueprint(telegram_blueprint) - - @application.before_first_request - def init_database(): - db.create_all() - - 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( - 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_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( - email=os.getenv("ADMIN_EMAIL"), - username=os.getenv("ADMIN_USERNAME"), - password=hash_password(os.getenv("ADMIN_PASSWORD")), - admin=True - ) - db.session.add(admin) - db.session.commit() - - return application - - -app = create_app() - -# Start development web server -if __name__ == '__main__': - app.run() +# Call the application factory function to construct a Flask application +# instance using the development configuration +application = create_app('config/flask.cfg') +application.run() diff --git a/api/app/__init__.py b/api/app/__init__.py new file mode 100644 index 0000000..c62e70c --- /dev/null +++ b/api/app/__init__.py @@ -0,0 +1,67 @@ +from flask import current_app +from apiflask import APIFlask + +from dotenv import load_dotenv +from flask_cors import CORS + +from app.blueprints.keyword import keyword_blueprint +from app.blueprints.portfolio import portfolio_blueprint +from app.blueprints.shares import shares_blueprint +from app.blueprints.transactions import transaction_blueprint +from app.blueprints.telegram import telegram_blueprint +from app.blueprints.user import users_blueprint +from app.helper_functions import hash_password +from app.models import * + + +def create_app(config_filename=None): + load_dotenv() + + # Create Flask app load app.config + application = APIFlask(__name__, openapi_blueprint_url_prefix='/api') + application.config.from_pyfile(config_filename) + + CORS(application, resources={r"*": {"origins": "*"}}) + + application.app_context().push() + + db.init_app(application) + + # api blueprints + application.register_blueprint(keyword_blueprint) + application.register_blueprint(shares_blueprint) + application.register_blueprint(transaction_blueprint) + application.register_blueprint(portfolio_blueprint) + application.register_blueprint(users_blueprint) + application.register_blueprint(telegram_blueprint) + + @application.before_first_request + def init_database(): + db.create_all() + + if current_app.config['BOT_EMAIL'] is not None and current_app.config['BOT_USERNAME'] is not None and current_app.config['BOT_PASSWORD'] is not None: + if db.session.query(User).filter_by(email=current_app.config['BOT_EMAIL']).first() is None: # Check if user already exist + bot = User( + email=current_app.config['BOT_EMAIL'], + username=current_app.config['BOT_USERNAME'], + password=hash_password(current_app.config['BOT_PASSWORD']), + admin=False + ) + db.session.add(bot) + db.session.commit() + + if current_app.config['ADMIN_EMAIL'] is not None and current_app.config['ADMIN_USERNAME'] is not None and current_app.config['ADMIN_PASSWORD'] is not None: + if db.session.query(User).filter_by(email=current_app.config['ADMIN_EMAIL']).first() is None: # Check if user already exist + admin = User( + email=current_app.config['ADMIN_EMAIL'], + username=current_app.config['ADMIN_USERNAME'], + password=hash_password(current_app.config['ADMIN_PASSWORD']), + admin=True + ) + db.session.add(admin) + db.session.commit() + + return application + + +app = create_app("config/flask.cfg") diff --git a/api/auth.py b/api/app/auth.py similarity index 73% rename from api/auth.py rename to api/app/auth.py index d5db292..513a889 100644 --- a/api/auth.py +++ b/api/app/auth.py @@ -1,4 +1,4 @@ -import os +from flask import current_app import jwt from apiflask import HTTPTokenAuth @@ -15,7 +15,7 @@ def verify_token(token): token = token.split(":")[0] try: - jwt.decode(token, os.getenv('SECRET_KEY'), algorithms=["HS256"]) + jwt.decode(token, current_app.config['SECRET_KEY'], algorithms=["HS256"]) return True except jwt.PyJWTError: return False diff --git a/api/app/blueprints/__init__.py b/api/app/blueprints/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/api/api_blueprint_keyword.py b/api/app/blueprints/keyword.py similarity index 85% rename from api/api_blueprint_keyword.py rename to api/app/blueprints/keyword.py index a22abb3..cd00c53 100644 --- a/api/api_blueprint_keyword.py +++ b/api/app/blueprints/keyword.py @@ -2,11 +2,11 @@ import os from apiflask import APIBlueprint, abort -from db import db -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 +from app.db import database as db +from app.helper_functions import make_response, get_email_or_abort_401 +from app.auth import auth +from app.schema import KeywordResponseSchema, KeywordSchema, DeleteSuccessfulSchema +from app.models import Keyword keyword_blueprint = APIBlueprint('keyword', __name__, url_prefix='/api') __location__ = os.path.realpath(os.path.join(os.getcwd(), os.path.dirname(__file__))) @@ -20,7 +20,8 @@ __location__ = os.path.realpath(os.path.join(os.getcwd(), os.path.dirname(__file def add_keyword(data): email = get_email_or_abort_401() - check_if_keyword_data_exists(data) + if not check_if_keyword_data_exists(data): + abort(400, message="Keyword missing") key = data['keyword'] @@ -47,14 +48,15 @@ def add_keyword(data): def remove_keyword(data): email = get_email_or_abort_401() - check_if_keyword_data_exists(data) + if not check_if_keyword_data_exists(data): + abort(400, message="Keyword missing") key = data['keyword'] 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") + return abort(500, "Keyword doesn't exist for this user") else: db.session.query(Keyword).filter_by(keyword=key, email=email).delete() db.session.commit() @@ -81,7 +83,9 @@ def get_keywords(): def check_if_keyword_data_exists(data): if "keyword" not in data: - abort(400, message="Keyword missing") + return False if data['keyword'] == "" or data['keyword'] is None: - abort(400, message="Keyword missing") + return False + + return True diff --git a/api/api_blueprint_portfolio.py b/api/app/blueprints/portfolio.py similarity index 85% rename from api/api_blueprint_portfolio.py rename to api/app/blueprints/portfolio.py index cf3c9b1..465ce47 100644 --- a/api/api_blueprint_portfolio.py +++ b/api/app/blueprints/portfolio.py @@ -2,10 +2,10 @@ import os from apiflask import APIBlueprint -from schema import PortfolioResponseSchema -from db import db -from helper_functions import make_response, get_email_or_abort_401 -from auth import auth +from app.schema import PortfolioResponseSchema +from app.db import database as db +from app.helper_functions import make_response, get_email_or_abort_401 +from app.auth import auth portfolio_blueprint = APIBlueprint('portfolio', __name__, url_prefix='/api') __location__ = os.path.realpath(os.path.join(os.getcwd(), os.path.dirname(__file__))) diff --git a/api/api_blueprint_shares.py b/api/app/blueprints/shares.py similarity index 83% rename from api/api_blueprint_shares.py rename to api/app/blueprints/shares.py index 1c7c785..24656a1 100644 --- a/api/api_blueprint_shares.py +++ b/api/app/blueprints/shares.py @@ -2,11 +2,11 @@ import os from apiflask import APIBlueprint, abort -from auth import auth -from db import db -from helper_functions import make_response, get_email_or_abort_401 -from models import Share -from schema import SymbolSchema, SymbolResponseSchema, DeleteSuccessfulSchema +from app.auth import auth +from app.db import database as db +from app.helper_functions import make_response, get_email_or_abort_401 +from app.models import Share +from app.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__))) @@ -20,7 +20,8 @@ __location__ = os.path.realpath(os.path.join(os.getcwd(), os.path.dirname(__file def add_symbol(data): email = get_email_or_abort_401() - check_if_symbol_data_exists(data) + if not check_if_symbol_data_exists(data): + abort(400, message="Symbol missing") symbol = data['symbol'] @@ -36,7 +37,7 @@ def add_symbol(data): return make_response(new_symbol.as_dict(), 200, "Successfully added symbol") else: - return make_response({}, 500, "Symbol already exist for this user") + abort(500, "Symbol already exist for this user") @shares_blueprint.route('/share', methods=['DELETE']) @@ -47,14 +48,15 @@ def add_symbol(data): def remove_symbol(data): email = get_email_or_abort_401() - check_if_symbol_data_exists(data) + if not check_if_symbol_data_exists(data): + abort(400, message="Symbol missing") symbol = data['symbol'] 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") + abort(500, "Symbol doesn't exist for this user") else: db.session.query(Share).filter_by(symbol=symbol, email=email).delete() db.session.commit() @@ -81,7 +83,9 @@ def get_symbol(): def check_if_symbol_data_exists(data): if "symbol" not in data: - abort(400, message="Symbol missing") + return False if data['symbol'] == "" or data['symbol'] is None: - abort(400, message="Symbol missing") + return False + + return True diff --git a/api/api_blueprint_telegram.py b/api/app/blueprints/telegram.py similarity index 73% rename from api/api_blueprint_telegram.py rename to api/app/blueprints/telegram.py index 1aa6f6e..5208d71 100644 --- a/api/api_blueprint_telegram.py +++ b/api/app/blueprints/telegram.py @@ -2,11 +2,11 @@ import os from apiflask import APIBlueprint, abort -from db import db -from helper_functions import make_response, get_email_or_abort_401 -from auth import auth -from schema import TelegramIdSchema, UsersSchema -from models import User +from app.db import database as db +from app.helper_functions import make_response, get_email_or_abort_401 +from app.auth import auth +from app.schema import TelegramIdSchema, UsersSchema +from app.models import User telegram_blueprint = APIBlueprint('telegram', __name__, url_prefix='/api') __location__ = os.path.realpath(os.path.join(os.getcwd(), os.path.dirname(__file__))) @@ -20,15 +20,11 @@ __location__ = os.path.realpath(os.path.join(os.getcwd(), os.path.dirname(__file def add_keyword(data): email = get_email_or_abort_401() - check_if_telegram_user_id_data_exists(data) + if not check_if_telegram_user_id_data_exists(data): + abort(400, message="User ID missing") 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") - query_user.telegram_user_id = data['telegram_user_id'] - db.session.commit() return make_response(query_user.as_dict(), 200, "Successfully connected telegram user") @@ -36,7 +32,9 @@ def add_keyword(data): def check_if_telegram_user_id_data_exists(data): if "telegram_user_id" not in data: - abort(400, message="User ID missing") + return False if data['telegram_user_id'] == "" or data['telegram_user_id'] is None: - abort(400, message="User ID missing") + return False + + return True diff --git a/api/api_blueprint_transactions.py b/api/app/blueprints/transactions.py similarity index 67% rename from api/api_blueprint_transactions.py rename to api/app/blueprints/transactions.py index 054a032..33eae41 100644 --- a/api/api_blueprint_transactions.py +++ b/api/app/blueprints/transactions.py @@ -3,11 +3,11 @@ import datetime from apiflask import abort, APIBlueprint -from db import db -from helper_functions import make_response, get_email_or_abort_401 -from models import Transaction -from schema import TransactionSchema, TransactionResponseSchema -from auth import auth +from app.db import database as db +from app.helper_functions import make_response, get_email_or_abort_401 +from app.models import Transaction +from app.schema import TransactionSchema, TransactionResponseSchema +from app.auth import auth transaction_blueprint = APIBlueprint('transaction', __name__, url_prefix='/api') __location__ = os.path.realpath(os.path.join(os.getcwd(), os.path.dirname(__file__))) @@ -21,7 +21,17 @@ __location__ = os.path.realpath(os.path.join(os.getcwd(), os.path.dirname(__file def add_transaction(data): email = get_email_or_abort_401() - check_if_transaction_data_exists(data) + if not check_if_symbol_data_exists(data): + abort(400, "Symbol missing") + + if not check_if_time_data_exists(data): + abort(400, "Time missing") + + if not check_if_count_data_exists(data): + abort(400, "Count missing") + + if not check_if_price_data_exists(data): + abort(400, "Price missing") new_transaction = Transaction( email=email, @@ -53,27 +63,41 @@ def get_transaction(): return make_response(return_transactions, 200, "Successfully loaded transactions") -def check_if_transaction_data_exists(data): +def check_if_symbol_data_exists(data): if "symbol" not in data: - abort(400, message="Symbol missing") + return False if data['symbol'] == "" or data['symbol'] is None: - abort(400, message="Symbol missing") + return False + return True + + +def check_if_time_data_exists(data): if "time" not in data: - abort(400, message="Time missing") + return False if data['time'] == "" or data['time'] is None: - abort(400, message="Time missing") + return False + return True + + +def check_if_count_data_exists(data): if "count" not in data: - abort(400, message="Count missing") + return False if data['count'] == "" or data['count'] is None: - abort(400, message="Count missing") + return False + return True + + +def check_if_price_data_exists(data): if "price" not in data: - abort(400, message="Price missing") + return False if data['price'] == "" or data['price'] is None: - abort(400, message="Price missing") + return False + + return True diff --git a/api/api_blueprint_user.py b/api/app/blueprints/user.py similarity index 75% rename from api/api_blueprint_user.py rename to api/app/blueprints/user.py index ad2ec2b..771bd60 100644 --- a/api/api_blueprint_user.py +++ b/api/app/blueprints/user.py @@ -1,14 +1,15 @@ import datetime import os +from flask import current_app import jwt from apiflask import APIBlueprint, abort -from db import db -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, RegisterDataSchema, UpdateUserDataSchema -from auth import auth +from app.db import database as db +from app.helper_functions import check_password, hash_password, abort_if_no_admin, make_response, get_email_or_abort_401 +from app.models import User +from app.schema import UsersSchema, TokenSchema, LoginDataSchema, AdminDataSchema, DeleteUserSchema, RegisterDataSchema, UpdateUserDataSchema +from app.auth import auth users_blueprint = APIBlueprint('users', __name__, url_prefix='/api') __location__ = os.path.realpath(os.path.join(os.getcwd(), os.path.dirname(__file__))) @@ -45,8 +46,11 @@ 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_email_data_exists(data) - check_if_password_data_exists(data) + if not check_if_password_data_exists(data): + abort(400, "Password missing") + + if not check_if_email_data_exists(data): + abort(400, "Email missing") email = data['email'] password = data['password'] @@ -59,10 +63,10 @@ def login(data): if not check_password(query_user.password, password.encode("utf-8")): # Password incorrect abort(500, message="Unable to login") - if query_user.email == os.getenv("BOT_EMAIL"): - token = jwt.encode({'email': query_user.email, 'exp': datetime.datetime.utcnow() + datetime.timedelta(days=365)}, os.getenv('SECRET_KEY'), "HS256") + if query_user.email == current_app.config['BOT_EMAIL']: + token = jwt.encode({'email': query_user.email, 'exp': datetime.datetime.utcnow() + datetime.timedelta(days=365)}, current_app.config['SECRET_KEY'], "HS256") else: - token = jwt.encode({'email': query_user.email, 'exp': datetime.datetime.utcnow() + datetime.timedelta(days=1)}, os.getenv('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") @@ -72,9 +76,14 @@ def login(data): @users_blueprint.input(schema=RegisterDataSchema) @users_blueprint.doc(summary="Register", description="Registers user") def register(data): - check_if_email_data_exists(data) - check_if_username_data_exists(data) - check_if_password_data_exists(data) + if not check_if_email_data_exists(data): + abort(400, "Email missing") + + if not check_if_username_data_exists(data): + abort(400, "Username missing") + + if not check_if_password_data_exists(data): + abort(400, "Password missing") email = data['email'] username = data['username'] @@ -107,12 +116,10 @@ def update_user(data): 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 "password" in data and data['password'] is not None: + if check_if_password_data_exists(data): query_user.password = hash_password(data['password']) - if "username" in data and data['username'] is not None: + + if check_if_username_data_exists(data): query_user.username = data['username'] db.session.commit() @@ -128,8 +135,11 @@ 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) + if not check_if_email_data_exists(data): + abort(400, "Email missing") + + if not check_if_admin_data_exists(data): + abort(400, "Admin data missing") email = data['email'] admin = data['admin'] @@ -137,7 +147,7 @@ def set_admin(data): 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") + abort(500, message="Unable to update user") query_user.admin = admin db.session.commit() @@ -151,7 +161,8 @@ 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_email_data_exists(data) + if not check_if_email_data_exists(data): + abort(400, "Email missing") email = data['email'] @@ -169,31 +180,39 @@ def delete_user(data): def check_if_email_data_exists(data): if "email" not in data: - abort(400, message="Email missing") + return False if data['email'] == "" or data['email'] is None: - abort(400, message="Email missing") + return False + + return True def check_if_password_data_exists(data): if "password" not in data: - abort(400, message="Password missing") + return False if data['password'] == "" or data['password'] is None: - abort(400, message="Password missing") + return False + + return True def check_if_username_data_exists(data): if "username" not in data: - abort(400, message="Username missing") + return False if data['username'] == "" or data['username'] is None: - abort(400, message="Username missing") + return False + + return True def check_if_admin_data_exists(data): if "admin" not in data: - abort(400, message="Admin state missing") + return False if data['admin'] == "" or data['admin'] is None: - abort(400, message="Admin state missing") + return False + + return True diff --git a/api/app/config/flask.cfg b/api/app/config/flask.cfg new file mode 100644 index 0000000..1a6347c --- /dev/null +++ b/api/app/config/flask.cfg @@ -0,0 +1,26 @@ +import os +from app.schema import BaseResponseSchema + +# Flask settings +SECRET_KEY = os.getenv('SECRET_KEY', 'secret string') + + +# Flask-SQLAlchemy settings +SQLALCHEMY_DATABASE_URI = "mysql+pymysql://" + os.getenv('MYSQL_USER') + ":" + os.getenv('MYSQL_PASSWORD') + "@" + os.getenv('MYSQL_HOST') + ":" + os.getenv('MYSQL_PORT', '3306') + "/" + os.getenv('MYSQL_NAME', 'aktienbot') +SQLALCHEMY_TRACK_MODIFICATIONS = False # Avoids SQLAlchemy warning +SQLALCHEMY_ENGINE_OPTIONS = { + 'pool_size': 100, + 'pool_recycle': 240 # 4 minutes +} + +# openapi/Swagger config +BASE_RESPONSE_DATA_KEY = "data" +BASE_RESPONSE_SCHEMA = BaseResponseSchema + +BOT_EMAIL = os.getenv('BOT_EMAIL') +BOT_USERNAME = os.getenv('BOT_USERNAME') +BOT_PASSWORD = os.getenv('BOT_PASSWORD') + +ADMIN_EMAIL = os.getenv('ADMIN_EMAIL') +ADMIN_USERNAME = os.getenv('ADMIN_USERNAME') +ADMIN_PASSWORD = os.getenv('ADMIN_PASSWORD') diff --git a/api/app/config/flask_test.cfg b/api/app/config/flask_test.cfg new file mode 100644 index 0000000..01ebea6 --- /dev/null +++ b/api/app/config/flask_test.cfg @@ -0,0 +1,26 @@ +import os +from app.schema import BaseResponseSchema + +# Flask settings +SECRET_KEY = os.getenv('SECRET_KEY', 'secret string') + + +# Flask-SQLAlchemy settings +SQLALCHEMY_DATABASE_URI = "mysql+pymysql://" + os.getenv('MYSQL_USER') + ":" + os.getenv('MYSQL_PASSWORD') + "@" + os.getenv('MYSQL_HOST') + ":" + os.getenv('MYSQL_PORT', '3306') + "/" + os.getenv('MYSQL_NAME', 'aktienbot_test') +SQLALCHEMY_TRACK_MODIFICATIONS = False # Avoids SQLAlchemy warning +SQLALCHEMY_ENGINE_OPTIONS = { + 'pool_size': 100, + 'pool_recycle': 240 # 4 minutes +} + +# openapi/Swagger config +BASE_RESPONSE_DATA_KEY = "data" +BASE_RESPONSE_SCHEMA = BaseResponseSchema + +BOT_EMAIL = os.getenv('BOT_EMAIL') +BOT_USERNAME = os.getenv('BOT_USERNAME') +BOT_PASSWORD = os.getenv('BOT_PASSWORD') + +ADMIN_EMAIL = os.getenv('ADMIN_EMAIL') +ADMIN_USERNAME = os.getenv('ADMIN_USERNAME') +ADMIN_PASSWORD = os.getenv('ADMIN_PASSWORD') diff --git a/api/db.py b/api/app/db.py similarity index 63% rename from api/db.py rename to api/app/db.py index f0b13d6..890af1d 100644 --- a/api/db.py +++ b/api/app/db.py @@ -1,3 +1,3 @@ from flask_sqlalchemy import SQLAlchemy -db = SQLAlchemy() +database = SQLAlchemy() diff --git a/api/helper_functions.py b/api/app/helper_functions.py similarity index 72% rename from api/helper_functions.py rename to api/app/helper_functions.py index 4ebf10e..516d0e7 100644 --- a/api/helper_functions.py +++ b/api/app/helper_functions.py @@ -1,12 +1,12 @@ -import os +from flask import current_app import bcrypt import jwt from apiflask import abort from flask import request, jsonify -from db import db -from models import User +from app.db import database as db +from app.models import User def hash_password(password): @@ -17,27 +17,14 @@ def check_password(hashed_password, user_password): return bcrypt.checkpw(user_password, hashed_password) -def get_token(): - token = None - if 'Authorization' in request.headers: - token = request.headers['Authorization'].split(" ")[1] - - return token - - -def extract_token_data(token): - if token is not None: - try: - return jwt.decode(token, os.getenv('SECRET_KEY'), algorithms=["HS256"]) - except jwt.PyJWTError: - return None - else: - return None - - def get_email_from_token_data(): if 'Authorization' in request.headers: - token = request.headers['Authorization'].split(" ")[1] + token = request.headers['Authorization'].split(" ") + + if len(token) < 2: + return None + else: + token = token[1] if token is not None: if ':' in token: # Maybe bot token, check if token valid and return username after ":" then @@ -45,7 +32,7 @@ def get_email_from_token_data(): token = token.split(":")[0] try: - if jwt.decode(token, os.getenv('SECRET_KEY'), algorithms=["HS256"])['email'] == os.getenv("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() if res is not None: @@ -59,7 +46,7 @@ def get_email_from_token_data(): else: # "Normal" token, extract username from token try: - return jwt.decode(token, os.getenv('SECRET_KEY'), algorithms=["HS256"])['email'] + return jwt.decode(token, current_app.config['SECRET_KEY'], algorithms=["HS256"])['email'] except jwt.PyJWTError: return None diff --git a/api/models.py b/api/app/models.py similarity index 98% rename from api/models.py rename to api/app/models.py index d85e49c..6c06c7b 100644 --- a/api/models.py +++ b/api/app/models.py @@ -1,4 +1,4 @@ -from db import db +from app.db import database as db class User(db.Model): diff --git a/api/schema.py b/api/app/schema.py similarity index 100% rename from api/schema.py rename to api/app/schema.py diff --git a/api/config.py b/api/config.py deleted file mode 100644 index a12c60a..0000000 --- a/api/config.py +++ /dev/null @@ -1,55 +0,0 @@ -import os - -from dotenv import load_dotenv - -from schema import BaseResponseSchema - -load_dotenv() - - -class ConfigClass(object): - """ Flask application config """ - - # Flask settings - SECRET_KEY = os.getenv('SECRET_KEY') - - # Flask-SQLAlchemy settings - SQLALCHEMY_DATABASE_URI = "mysql+pymysql://" + \ - os.getenv('MYSQL_USER') + ":" + \ - os.getenv('MYSQL_PASSWORD') + "@" + \ - os.getenv('MYSQL_HOST') + ":" + \ - (os.getenv("MYSQL_PORT") or str(3306)) + "/" + \ - os.getenv('MYSQL_DATABASE') - SQLALCHEMY_TRACK_MODIFICATIONS = False # Avoids SQLAlchemy warning - SQLALCHEMY_ENGINE_OPTIONS = { - 'pool_size': 100, - 'pool_recycle': 240 # 4 minutes - } - - # openapi/Swagger config - SERVERS = [ - { - "name": "Production", - "url": "https://aktienbot.flokaiser.com" - }, - { - "name": "Local", - "url": "http://127.0.0.1:5000" - } - ] - INFO = { - 'description': 'Webengineering 2 | Telegram Aktienbot', - 'version': '0.0.1' - # 'termsOfService': 'http://example.com', - # 'contact': { - # 'name': 'API Support', - # 'url': 'http://www.example.com/support', - # 'email': 'support@example.com' - # }, - # 'license': { - # 'name': 'Apache 2.0', - # 'url': 'http://www.apache.org/licenses/LICENSE-2.0.html' - # } - } - BASE_RESPONSE_DATA_KEY = "data" - BASE_RESPONSE_SCHEMA = BaseResponseSchema diff --git a/api/requirements.txt b/api/requirements.txt index 0ab141c..16da00c 100644 --- a/api/requirements.txt +++ b/api/requirements.txt @@ -7,4 +7,6 @@ pymysql==1.0.2 pyjwt==2.3.0 apiflask==0.12.0 flask-cors==3.0.10 -bcrypt==3.2.0 \ No newline at end of file +bcrypt==3.2.0 +pytest +pytest-cov \ No newline at end of file diff --git a/api/tests/conftest.py b/api/tests/conftest.py new file mode 100644 index 0000000..c772474 --- /dev/null +++ b/api/tests/conftest.py @@ -0,0 +1,108 @@ +import json +import os + +import pytest +from app import create_app, db +from app.models import User, Transaction, Keyword, Share + +from app.helper_functions import hash_password + + +@pytest.fixture(scope='module') +def new_user(): + user = User( + email="user@example.com", + username="user", + password=hash_password("password"), + admin=False + ) + return user + + +@pytest.fixture(scope='module') +def new_transaction(): + transaction = Transaction( + email="user@example.com", + symbol="DTEGY", + time="2022-03-29T10:00:00.000Z", + count=10, + price=9.99 + ) + return transaction + + +@pytest.fixture(scope='module') +def new_keyword(): + keyword = Keyword( + email="user@example.com", + keyword="Elon Musk", + ) + return keyword + + +@pytest.fixture(scope='module') +def new_share(): + share = Share( + email="user@example.com", + symbol="DTEGY", + ) + return share + + +@pytest.fixture(scope='module') +def test_client(): + flask_app = create_app('config/flask_test.cfg') + + # Create a test client using the Flask application configured for testing + with flask_app.test_client() as testing_client: + # Establish an application context + with flask_app.app_context(): + yield testing_client # this is where the testing happens! + + +@pytest.fixture(scope='function') +def init_database(test_client): + # Create the database and the database table + db.create_all() + + # Insert user data + user1 = User( + email="user1@example.com", + username="user1", + password=hash_password("password"), + telegram_user_id="12345678", + admin=False + ) + user2 = User( + email="user2@example.com", + username="user2", + password=hash_password("password"), + telegram_user_id="87654321", + admin=False + ) + admin = User( + email="admin1@example.com", + username="admin1", + password=hash_password("admin1"), + telegram_user_id="00000000", + admin=True + ) + bot = User( + email="bot1@example.com", + username="bot1", + password=hash_password("bot1"), + telegram_user_id="00000000", + admin=False + ) + db.session.add(user1) + db.session.add(user2) + db.session.add(admin) + db.session.add(bot) + + # Commit the changes for the users + db.session.commit() + + yield # this is where the testing happens! + + db.session.commit() + db.drop_all() diff --git a/api/tests/functional/__init__.py b/api/tests/functional/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/api/tests/functional/test_keyword.py b/api/tests/functional/test_keyword.py new file mode 100644 index 0000000..72763ca --- /dev/null +++ b/api/tests/functional/test_keyword.py @@ -0,0 +1,191 @@ +""" +This file (test_keyword.py) contains the functional tests for the `keyword` blueprint. +""" +import json + + +def test_add_keyword_not_logged_in(test_client, init_database): + """ + Test POST '/api/keyword' + + User is not logged in + """ + response = test_client.post('/api/keyword') + assert response.status_code == 401 + assert b'Unauthorized' in response.data + + +def test_add_keyword_user1_logged_in(test_client, init_database): + """ + Test POST '/api/keyword' + + User1 is logged in + """ + response = test_client.post('/api/keyword', data=json.dumps(dict(keyword="DTEGY")), + headers={"Authorization": "Bearer {}".format(get_token(test_client, "user1@example.com", "password"))}, + content_type='application/json') + assert response.status_code == 200 + assert b'Successfully added keyword' in response.data + + +def test_add_keyword_user1_logged_in_but_keyword_exist(test_client, init_database): + """ + Test POST '/api/keyword' + + User1 is logged in + Add keyword two times + """ + test_client.post('/api/keyword', data=json.dumps(dict(keyword="DTEGY")), + headers={"Authorization": "Bearer {}".format(get_token(test_client, "user1@example.com", "password"))}, + content_type='application/json') + response = test_client.post('/api/keyword', data=json.dumps(dict(keyword="DTEGY")), + headers={"Authorization": "Bearer {}".format(get_token(test_client, "user1@example.com", "password"))}, + content_type='application/json') + assert response.status_code == 500 + assert b'Keyword already exist for this user' in response.data + + +def test_add_keyword_user1_logged_in_but_keyword_missing(test_client, init_database): + """ + Test POST '/api/keyword' + + User1 is logged in + Keyword is missing in post data + """ + response = test_client.post('/api/keyword', data=json.dumps(dict()), + headers={"Authorization": "Bearer {}".format(get_token(test_client, "user1@example.com", "password"))}, + content_type='application/json') + assert response.status_code == 400 + assert b'missing' in response.data + + +def test_add_keyword_user1_logged_in_but_keyword_empty(test_client, init_database): + """ + Test POST '/api/keyword' + + User1 is logged in + Keyword is empty in post data + """ + response = test_client.post('/api/keyword', data=json.dumps(dict(keyword="")), + headers={"Authorization": "Bearer {}".format(get_token(test_client, "user1@example.com", "password"))}, + content_type='application/json') + assert response.status_code == 400 + assert b'missing' in response.data + + +def test_get_keyword_not_logged_in(test_client, init_database): + """ + Test GET '/api/keyword' + + User is not logged in + """ + response = test_client.get('/api/keywords') + assert response.status_code == 401 + assert b'Unauthorized' in response.data + + +def test_get_keyword_user1_logged_in_empty_response(test_client, init_database): + """ + Test GET '/api/keyword' + + User1 is logged in + Empty response + """ + response = test_client.get('/api/keywords', headers={"Authorization": "Bearer {}".format(get_token(test_client, "user1@example.com", "password"))}) + assert response.status_code == 200 + assert b'"data":[' in response.data + + +def test_get_keyword_user1_logged_in_response_data(test_client, init_database): + """ + Test GET '/api/keyword' + + User1 is logged in + Create some keywords for user1 + """ + test_client.post('/api/keyword', data=json.dumps(dict(keyword="DTEGY")), + headers={"Authorization": "Bearer {}".format(get_token(test_client, "user1@example.com", "password"))}, + content_type='application/json') + response = test_client.get('/api/keywords', headers={"Authorization": "Bearer {}".format(get_token(test_client, "user1@example.com", "password"))}) + assert response.status_code == 200 + assert b'"data":[' in response.data + + +def test_delete_keyword_not_logged_in(test_client, init_database): + """ + Test DELETE '/api/keyword' + + User is not logged in + """ + response = test_client.delete('/api/keyword') + assert response.status_code == 401 + assert b'Unauthorized' in response.data + + +def test_delete_keyword_user1_logged_in_but_empty_keyword(test_client, init_database): + """ + Test DELETE '/api/keyword' + + User1 is logged in + Keyword empty in in delete data + """ + response = test_client.delete('/api/keyword', data=json.dumps(dict(keyword="")), + headers={"Authorization": "Bearer {}".format(get_token(test_client, "user1@example.com", "password"))}, + content_type='application/json') + assert response.status_code == 400 + assert b'missing' in response.data + + +def test_delete_keyword_user1_logged_in_but_missing_keyword(test_client, init_database): + """ + Test DELETE '/api/keyword' + + User1 is logged in + Keyword missing in in delete data + """ + response = test_client.delete('/api/keyword', data=json.dumps(dict()), + headers={"Authorization": "Bearer {}".format(get_token(test_client, "user1@example.com", "password"))}, + content_type='application/json') + assert response.status_code == 400 + assert b'missing' in response.data + + +def test_delete_keyword_user1_logged_in_keyword_exists(test_client, init_database): + """ + Test DELETE '/api/keyword' + + User1 is logged in + Keyword exists + """ + test_client.post('/api/keyword', data=json.dumps(dict(keyword="DTEGY")), + headers={"Authorization": "Bearer {}".format(get_token(test_client, "user1@example.com", "password"))}, + content_type='application/json') + response = test_client.delete('/api/keyword', data=json.dumps(dict(keyword="DTEGY")), + headers={"Authorization": "Bearer {}".format(get_token(test_client, "user1@example.com", "password"))}, + content_type='application/json') + assert response.status_code == 200 + assert b'Successfully removed keyword' in response.data + + +def test_delete_keyword_user1_logged_in_keyword_not_exists(test_client, init_database): + """ + Test DELETE '/api/keyword' + + User1 is logged in + Keyword doesn't exists + """ + response = test_client.delete('/api/keyword', data=json.dumps(dict(keyword="DTEGY")), + headers={"Authorization": "Bearer {}".format(get_token(test_client, "user1@example.com", "password"))}, + content_type='application/json') + assert response.status_code == 500 + assert b'Keyword doesn\'t exist for this user' in response.data + + +def get_token(test_client, email, password): + response = test_client.post('/api/user/login', data=json.dumps(dict(email=email, password=password)), content_type='application/json') + + if "data" in json.loads(response.data): + if "token" in json.loads(response.data)["data"]: + return json.loads(response.data)["data"]["token"] + + return "" diff --git a/api/tests/functional/test_portfolio.py b/api/tests/functional/test_portfolio.py new file mode 100644 index 0000000..10fd8cc --- /dev/null +++ b/api/tests/functional/test_portfolio.py @@ -0,0 +1,59 @@ +""" +This file (test_portfolio.py) contains the functional tests for the `portfolio` blueprint. +""" +import json + + +def test_get_portfolio_not_logged_in_empty_response(test_client, init_database): + """ + Test GET '/api/portfolio' + + User is not logged in + """ + response = test_client.get('/api/portfolio') + assert response.status_code == 401 + assert b'Unauthorized' in response.data + + +def test_get_portfolio_user1_logged_in_empty_response(test_client, init_database): + """ + Test GET '/api/portfolio' + + User1 is logged in + Empty response + """ + response = test_client.get('/api/portfolio', + headers={"Authorization": "Bearer {}".format(get_token(test_client, "user1@example.com", "password"))}, + content_type='application/json') + assert response.status_code == 200 + assert b'"data":[]' in response.data + assert b'Successfully loaded symbols' in response.data + + +def test_get_portfolio_user1_logged_in_response_data(test_client, init_database): + """ + Test GET '/api/portfolio' + + User1 is logged in + Create transaction data + """ + test_client.post('/api/transaction', data=json.dumps(dict(count=5, price=9.99, symbol="DTEGY", time="2022-03-29T10:00:00.000Z")), + headers={"Authorization": "Bearer {}".format(get_token(test_client, "user1@example.com", "password"))}, + content_type='application/json') + + response = test_client.get('/api/portfolio', + headers={"Authorization": "Bearer {}".format(get_token(test_client, "user1@example.com", "password"))}, + content_type='application/json') + assert response.status_code == 200 + assert b'"data":[]' not in response.data + assert b'"data":[' in response.data + + +def get_token(test_client, email, password): + response = test_client.post('/api/user/login', data=json.dumps(dict(email=email, password=password)), content_type='application/json') + + if "data" in json.loads(response.data): + if "token" in json.loads(response.data)["data"]: + return json.loads(response.data)["data"]["token"] + + return "" diff --git a/api/tests/functional/test_share.py b/api/tests/functional/test_share.py new file mode 100644 index 0000000..61772fa --- /dev/null +++ b/api/tests/functional/test_share.py @@ -0,0 +1,191 @@ +""" +This file (test_share.py) contains the functional tests for the `share` blueprint. +""" +import json + + +def test_add_share_not_logged_in(test_client, init_database): + """ + Test POST '/api/share' + + User is not logged in + """ + response = test_client.post('/api/share') + assert response.status_code == 401 + assert b'Unauthorized' in response.data + + +def test_add_share_user1_logged_in(test_client, init_database): + """ + Test POST '/api/share' + + User1 is logged in + """ + response = test_client.post('/api/share', data=json.dumps(dict(symbol="DTEGY")), + headers={"Authorization": "Bearer {}".format(get_token(test_client, "user1@example.com", "password"))}, + content_type='application/json') + assert response.status_code == 200 + assert b'Successfully added symbol' in response.data + + +def test_add_share_user1_logged_in_but_symbol_exist(test_client, init_database): + """ + Test POST '/api/share' + + User1 is logged in + Add symbol two times + """ + test_client.post('/api/share', data=json.dumps(dict(symbol="DTEGY")), + headers={"Authorization": "Bearer {}".format(get_token(test_client, "user1@example.com", "password"))}, + content_type='application/json') + response = test_client.post('/api/share', data=json.dumps(dict(symbol="DTEGY")), + headers={"Authorization": "Bearer {}".format(get_token(test_client, "user1@example.com", "password"))}, + content_type='application/json') + assert response.status_code == 500 + assert b'Symbol already exist for this user' in response.data + + +def test_add_share_user1_logged_in_but_symbol_missing(test_client, init_database): + """ + Test POST '/api/share' + + User1 is logged in + Symbol is missing in post data + """ + response = test_client.post('/api/share', data=json.dumps(dict()), + headers={"Authorization": "Bearer {}".format(get_token(test_client, "user1@example.com", "password"))}, + content_type='application/json') + assert response.status_code == 400 + assert b'missing' in response.data + + +def test_add_share_user1_logged_in_but_symbol_empty(test_client, init_database): + """ + Test POST '/api/share' + + User1 is logged in + Symbol is empty in post data + """ + response = test_client.post('/api/share', data=json.dumps(dict(symbol="")), + headers={"Authorization": "Bearer {}".format(get_token(test_client, "user1@example.com", "password"))}, + content_type='application/json') + assert response.status_code == 400 + assert b'missing' in response.data + + +def test_get_share_not_logged_in(test_client, init_database): + """ + Test GET '/api/share' + + User is not logged in + """ + response = test_client.get('/api/shares') + assert response.status_code == 401 + assert b'Unauthorized' in response.data + + +def test_get_share_user1_logged_in_empty_response(test_client, init_database): + """ + Test GET '/api/share' + + User1 is logged in + Empty response + """ + response = test_client.get('/api/shares', headers={"Authorization": "Bearer {}".format(get_token(test_client, "user1@example.com", "password"))}) + assert response.status_code == 200 + assert b'"data":[' in response.data + + +def test_get_share_user1_logged_in_response_data(test_client, init_database): + """ + Test GET '/api/share' + + User1 is logged in + Create some symbols for user1 + """ + test_client.post('/api/share', data=json.dumps(dict(symbol="DTEGY")), + headers={"Authorization": "Bearer {}".format(get_token(test_client, "user1@example.com", "password"))}, + content_type='application/json') + response = test_client.get('/api/shares', headers={"Authorization": "Bearer {}".format(get_token(test_client, "user1@example.com", "password"))}) + assert response.status_code == 200 + assert b'"data":[' in response.data + + +def test_delete_share_not_logged_in(test_client, init_database): + """ + Test DELETE '/api/share' + + User is not logged in + """ + response = test_client.delete('/api/share') + assert response.status_code == 401 + assert b'Unauthorized' in response.data + + +def test_delete_share_user1_logged_in_but_empty_symbol(test_client, init_database): + """ + Test DELETE '/api/share' + + User1 is logged in + Symbol empty in in delete data + """ + response = test_client.delete('/api/share', data=json.dumps(dict(symbol="")), + headers={"Authorization": "Bearer {}".format(get_token(test_client, "user1@example.com", "password"))}, + content_type='application/json') + assert response.status_code == 400 + assert b'missing' in response.data + + +def test_delete_share_user1_logged_in_but_missing_symbol(test_client, init_database): + """ + Test DELETE '/api/share' + + User1 is logged in + Symbol missing in in delete data + """ + response = test_client.delete('/api/share', data=json.dumps(dict()), + headers={"Authorization": "Bearer {}".format(get_token(test_client, "user1@example.com", "password"))}, + content_type='application/json') + assert response.status_code == 400 + assert b'missing' in response.data + + +def test_delete_share_user1_logged_in_symbol_exists(test_client, init_database): + """ + Test DELETE '/api/share' + + User1 is logged in + Symbol exists + """ + test_client.post('/api/share', data=json.dumps(dict(symbol="DTEGY")), + headers={"Authorization": "Bearer {}".format(get_token(test_client, "user1@example.com", "password"))}, + content_type='application/json') + response = test_client.delete('/api/share', data=json.dumps(dict(symbol="DTEGY")), + headers={"Authorization": "Bearer {}".format(get_token(test_client, "user1@example.com", "password"))}, + content_type='application/json') + assert response.status_code == 200 + assert b'Successfully removed symbol' in response.data + + +def test_delete_share_user1_logged_in_symbol_not_exists(test_client, init_database): + """ + Test DELETE '/api/share' + + User1 is logged in + Symbol doesn't exists + """ + response = test_client.delete('/api/share', data=json.dumps(dict(symbol="DTEGY")), + headers={"Authorization": "Bearer {}".format(get_token(test_client, "user1@example.com", "password"))}, + content_type='application/json') + assert response.status_code == 500 + assert b'Symbol doesn\'t exist for this user' in response.data + + +def get_token(test_client, email, password): + response = test_client.post('/api/user/login', data=json.dumps(dict(email=email, password=password)), content_type='application/json') + + if "data" in json.loads(response.data): + if "token" in json.loads(response.data)["data"]: + return json.loads(response.data)["data"]["token"] + + return "" diff --git a/api/tests/functional/test_telegram.py b/api/tests/functional/test_telegram.py new file mode 100644 index 0000000..756f6cd --- /dev/null +++ b/api/tests/functional/test_telegram.py @@ -0,0 +1,66 @@ +""" +This file (test_telegram.py) contains the functional tests for the `telegram` blueprint. +""" +import json + + +def test_add_telegram_not_logged_in(test_client, init_database): + """ + Test POST '/api/telegram' + + User is not logged in + """ + response = test_client.post('/api/telegram') + assert response.status_code == 401 + assert b'Unauthorized' in response.data + + +def test_add_telegram_user1_logged_in(test_client, init_database): + """ + Test POST '/api/telegram' + + User1 is logged in + """ + response = test_client.post('/api/telegram', data=json.dumps(dict(telegram_user_id="12345678")), + headers={"Authorization": "Bearer {}".format(get_token(test_client, "user1@example.com", "password"))}, + content_type='application/json') + assert response.status_code == 200 + assert b'Successfully connected telegram user' in response.data + + +def test_add_telegram_user1_logged_in_user_data_missing(test_client, init_database): + """ + Test POST '/api/telegram' + + User1 is logged in + telegram_user_id is missing + """ + response = test_client.post('/api/telegram', data=json.dumps(dict()), + headers={"Authorization": "Bearer {}".format(get_token(test_client, "user1@example.com", "password"))}, + content_type='application/json') + assert response.status_code == 400 + assert b'missing' in response.data + + +def test_add_telegram_user1_logged_in_user_data_empty(test_client, init_database): + """ + Test POST '/api/telegram' + + User1 is logged in + telegram_user_id is empty + """ + response = test_client.post('/api/telegram', data=json.dumps(dict(telegram_user_id="")), + headers={"Authorization": "Bearer {}".format(get_token(test_client, "user1@example.com", "password"))}, + content_type='application/json') + assert response.status_code == 400 + assert b'missing' in response.data + + +def get_token(test_client, email, password): + response = test_client.post('/api/user/login', data=json.dumps(dict(email=email, password=password)), content_type='application/json') + + if "data" in json.loads(response.data): + if "token" in json.loads(response.data)["data"]: + return json.loads(response.data)["data"]["token"] + + return "" diff --git a/api/tests/functional/test_transaction.py b/api/tests/functional/test_transaction.py new file mode 100644 index 0000000..d50578a --- /dev/null +++ b/api/tests/functional/test_transaction.py @@ -0,0 +1,103 @@ +""" +This file (test_transaction.py) contains the functional tests for the `transaction` blueprint. +""" +import json + + +def test_add_transaction_not_logged_in(test_client, init_database): + """ + Test POST '/api/transaction' + + User is not logged in + """ + response = test_client.get('/api/portfolio') + assert response.status_code == 401 + assert b'Unauthorized' in response.data + + +def test_add_transaction_user1_logged_in_missing_data(test_client, init_database): + """ + Test POST '/api/transaction' + + User1 is logged in + Data missing + """ + # symbol missing + response = test_client.post('/api/transaction', data=json.dumps(dict(time="2022-03-29T10:00:00.000Z", count=10, price=9.99)), + headers={"Authorization": "Bearer {}".format(get_token(test_client, "user1@example.com", "password"))}, + content_type='application/json') + assert response.status_code == 400 + assert b'missing' in response.data + + # time missing + response = test_client.post('/api/transaction', data=json.dumps(dict(symbol="DTEGY", count=10, price=9.99)), + headers={"Authorization": "Bearer {}".format(get_token(test_client, "user1@example.com", "password"))}, + content_type='application/json') + assert response.status_code == 400 + assert b'missing' in response.data + + # count missing + response = test_client.post('/api/transaction', data=json.dumps(dict(symbol="DTEGY", time="2022-03-29T10:00:00.000Z", price=9.99)), + headers={"Authorization": "Bearer {}".format(get_token(test_client, "user1@example.com", "password"))}, + content_type='application/json') + assert response.status_code == 400 + assert b'missing' in response.data + + # price missing + response = test_client.post('/api/transaction', data=json.dumps(dict(symbol="DTEGY", time="2022-03-29T10:00:00.000Z", count=10)), + headers={"Authorization": "Bearer {}".format(get_token(test_client, "user1@example.com", "password"))}, + content_type='application/json') + assert response.status_code == 400 + assert b'missing' in response.data + + +def test_get_transaction_not_logged_in(test_client, init_database): + """ + Test GET '/api/transaction' + + User is not logged in + """ + response = test_client.get('/api/transactions') + assert response.status_code == 401 + assert b'Unauthorized' in response.data + + +def test_get_transaction_user1_logged_in_empty_response(test_client, init_database): + """ + Test GET '/api/transaction' + + User1 is logged in + """ + response = test_client.get('/api/transactions', + headers={"Authorization": "Bearer {}".format(get_token(test_client, "user1@example.com", "password"))}, + content_type='application/json') + assert response.status_code == 200 + assert b'Successfully loaded transactions' in response.data + + +def test_get_transaction_user1_logged_in_response_data(test_client, init_database): + """ + Test GET '/api/transaction' + + User1 is logged in + Create transaction + """ + test_client.post('/api/transaction', data=json.dumps(dict(count=5, price=9.99, symbol="DTEGY", time="2022-03-29T10:00:00.000Z")), + headers={"Authorization": "Bearer {}".format(get_token(test_client, "user1@example.com", "password"))}, + content_type='application/json') + + response = test_client.get('/api/transactions', + headers={"Authorization": "Bearer {}".format(get_token(test_client, "user1@example.com", "password"))}, + content_type='application/json') + assert response.status_code == 200 + assert b'Successfully loaded transactions' in response.data + + +def get_token(test_client, email, password): + response = test_client.post('/api/user/login', data=json.dumps(dict(email=email, password=password)), content_type='application/json') + + if "data" in json.loads(response.data): + if "token" in json.loads(response.data)["data"]: + return json.loads(response.data)["data"]["token"] + + return "" diff --git a/api/tests/functional/test_user.py b/api/tests/functional/test_user.py new file mode 100644 index 0000000..6a88089 --- /dev/null +++ b/api/tests/functional/test_user.py @@ -0,0 +1,513 @@ +""" +This file (test_user.py) contains the functional tests for the `users` blueprint. +""" +import json + + +def test_login_with_valid_data(test_client, init_database): + """ + Test POST '/api/user/login' + + Valid data + """ + response = test_client.post('/api/user/login', data=json.dumps(dict(email="user1@example.com", password="password")), content_type='application/json') + assert response.status_code == 200 + assert b'Successfully logged in' in response.data + + +def test_login_with_wrong_password(test_client, init_database): + """ + Test POST '/api/user/login' + + Wrong password + """ + response = test_client.post('/api/user/login', data=json.dumps(dict(email="user2@example.com", password="password2")), content_type='application/json') + assert response.status_code == 500 + assert b'Unable to login' in response.data + + +def test_login_user_not_exist(test_client, init_database): + """ + Test POST '/api/user/login' + + User doesn't exist + """ + response = test_client.post('/api/user/login', data=json.dumps(dict(email="notexistinguser@example.com", password="password")), content_type='application/json') + assert response.status_code == 500 + assert b'Unable to login' in response.data + + +def test_login_email_missing(test_client, init_database): + """ + Test POST '/api/user/login' + + Email missing + """ + response = test_client.post('/api/user/login', data=json.dumps(dict(password="password")), content_type='application/json') + assert response.status_code == 400 + assert b'missing' in response.data + + +def test_login_password_missing(test_client, init_database): + """ + Test POST '/api/user/login' + + Password missing + """ + response = test_client.post('/api/user/login', data=json.dumps(dict(email="user1@example.com")), content_type='application/json') + assert response.status_code == 400 + assert b'missing' in response.data + + +def test_register_valid_data(test_client, init_database): + """ + Test POST '/api/user/register' + + Valid data + """ + response = test_client.post('/api/user/register', data=json.dumps(dict(email="user3@example.com", password="password", username="user3")), content_type='application/json') + assert response.status_code == 200 + assert b'Successfully registered user' in response.data + + +def test_register_user_exists_already(test_client, init_database): + """ + Test POST '/api/user/register' + + User exists already + """ + test_client.post('/api/user/register', data=json.dumps(dict(email="user3@example.com", password="password", username="user3")), content_type='application/json') + response = test_client.post('/api/user/register', data=json.dumps(dict(email="user3@example.com", password="password", username="user3")), content_type='application/json') + assert response.status_code == 500 + assert b'Email already exist' in response.data + + +def test_register_email_missing(test_client, init_database): + """ + Test POST '/api/user/register' + + Email missing + """ + response = test_client.post('/api/user/register', data=json.dumps(dict(password="password", username="user3")), content_type='application/json') + assert response.status_code == 400 + assert b'missing' in response.data + + +def test_register_email_empty(test_client, init_database): + """ + Test POST '/api/user/register' + + Email empty + """ + response = test_client.post('/api/user/register', data=json.dumps(dict(password="password", username="user3", email="")), content_type='application/json') + assert response.status_code == 400 + assert b'Not a valid email address' in response.data + + +def test_register_password_missing(test_client, init_database): + """ + Test POST '/api/user/register' + + Password missing + """ + response = test_client.post('/api/user/register', data=json.dumps(dict(email="user3@example.com", username="user3")), content_type='application/json') + assert response.status_code == 400 + assert b'missing' in response.data + + +def test_register_password_empty(test_client, init_database): + """ + Test POST '/api/user/register' + + password empty + """ + response = test_client.post('/api/user/register', data=json.dumps(dict(email="user3@example.com", username="user3", password="")), content_type='application/json') + assert response.status_code == 400 + assert b'missing' in response.data + + +def test_register_username_missing(test_client, init_database): + """ + Test POST '/api/user/register' + + Username missing + """ + response = test_client.post('/api/user/register', data=json.dumps(dict(password="password", email="user3@example.com")), content_type='application/json') + assert response.status_code == 400 + assert b'missing' in response.data + + +def test_register_username_empty(test_client, init_database): + """ + Test POST '/api/user/register' + + Username empty + """ + response = test_client.post('/api/user/register', data=json.dumps(dict(password="password", username="", email="user3@example.com")), content_type='application/json') + assert response.status_code == 400 + assert b'missing' in response.data + + +def test_delete_user_not_logged_in(test_client, init_database): + """ + Test DELETE '/api/user' + + User is not logged in + """ + test_client.post('/api/user/register', data=json.dumps(dict(email="user3@example.com", password="password", username="user3")), content_type='application/json') + response = test_client.delete('/api/user', data=json.dumps(dict(email="user3@example.com")), content_type='application/json') + assert response.status_code == 401 + assert b'Unauthorized' in response.data + + +def test_delete_user_same_user_logged_in(test_client, init_database): + """ + Test DELETE '/api/user' + + User3 is logged in + """ + test_client.post('/api/user/register', data=json.dumps(dict(email="user3@example.com", password="password", username="user3")), content_type='application/json') + response = test_client.delete('/api/user', + data=json.dumps(dict(email="user3@example.com")), + content_type='application/json', + headers={"Authorization": "Bearer {}".format(get_token(test_client, "user3@example.com", "password"))}) + assert response.status_code == 200 + assert b'Successfully removed user' in response.data + + +def test_delete_user_different_user_logged_in_no_admin(test_client, init_database): + """ + Test DELETE '/api/user' + + Different user is logged in -> no admin + """ + test_client.post('/api/user/register', data=json.dumps(dict(email="user3@example.com", password="password", username="user3")), content_type='application/json') + response = test_client.delete('/api/user', + data=json.dumps(dict(email="user3@example.com")), + content_type='application/json', + headers={"Authorization": "Bearer {}".format(get_token(test_client, "user1@example.com", "password"))}) + assert response.status_code == 401 + assert b'Only admin users can access this' in response.data + + +def test_delete_user_different_user_logged_in_admin(test_client, init_database): + """ + Test DELETE '/api/user' + + Different user is logged in -> admin + """ + test_client.post('/api/user/register', data=json.dumps(dict(email="user3@example.com", password="password", username="user3")), content_type='application/json') + response = test_client.delete('/api/user', + data=json.dumps(dict(email="user3@example.com")), + content_type='application/json', + headers={"Authorization": "Bearer {}".format(get_token(test_client, "admin1@example.com", "admin1"))}) + assert response.status_code == 200 + assert b'Successfully removed user' in response.data + + +def test_delete_user_email_missing(test_client, init_database): + """ + Test DELETE '/api/user' + + Email missing + """ + test_client.post('/api/user/register', data=json.dumps(dict(email="user3@example.com", password="password", username="user3")), content_type='application/json') + response = test_client.delete('/api/user', + data=json.dumps(dict()), + content_type='application/json', + headers={"Authorization": "Bearer {}".format(get_token(test_client, "user3@example.com", "password"))}) + assert response.status_code == 400 + assert b'missing' in response.data + + +def test_delete_user_email_empty(test_client, init_database): + """ + Test DELETE '/api/user' + + Email empty + """ + test_client.post('/api/user/register', data=json.dumps(dict(email="user3@example.com", password="password", username="user3")), content_type='application/json') + response = test_client.delete('/api/user', + data=json.dumps(dict(email="")), + content_type='application/json', + headers={"Authorization": "Bearer {}".format(get_token(test_client, "user3@example.com", "password"))}) + assert response.status_code == 400 + assert b'Not a valid email address' in response.data + + +def test_get_current_user_user_not_logged_in(test_client, init_database): + """ + Test GET '/api/user' + + User is not logged in + """ + response = test_client.get('/api/user') + assert response.status_code == 401 + assert b'Unauthorized' in response.data + + +def test_get_current_user_user1_logged_in(test_client, init_database): + """ + Test GET '/api/user' + + User1 is logged in + """ + response = test_client.get('/api/user', headers={"Authorization": "Bearer {}".format(get_token(test_client, "user1@example.com", "password"))}) + assert response.status_code == 200 + assert b'user1' in response.data + assert b'user2' not in response.data + + +def test_get_current_user_bot_logged_in_user_exists(test_client, init_database): + """ + Test GET '/api/user' + + Bot1 is logged in and requests user 12345678 + """ + response = test_client.get('/api/user', headers={"Authorization": "Bearer {}".format(get_token(test_client, "bot1@example.com", "bot1") + ":12345678")}) + assert response.status_code == 200 + assert b'user1' in response.data + assert b'bot' not in response.data + + +def test_get_current_user_bot_logged_in_user_not_exists(test_client, init_database): + """ + Test GET '/api/user' + + Bot1 is logged in and requests user 1234 (not existing) + """ + response = test_client.get('/api/user', headers={"Authorization": "Bearer {}".format(get_token(test_client, "bot1@example.com", "bot1") + ":1234")}) + assert response.status_code == 401 + assert b'Unable to login' in response.data + + +def test_get_current_user_user1_logged_in_but_no_bot(test_client, init_database): + """ + Test GET '/api/user' + + User1 is logged in and requests user 1234 (not existing) + Fails because user1 is not a bot + """ + response = test_client.get('/api/user', headers={"Authorization": "Bearer {}".format(get_token(test_client, "user1@example.com", "password") + ":12345678")}) + assert response.status_code == 401 + assert b'Unable to login' in response.data + + +def test_get_current_user_invalid_token(test_client, init_database): + """ + Test GET '/api/user' + + Invalid Bearer token + """ + response = test_client.get('/api/user', headers={"Authorization": "Bearer {}".format("invalidtoken:12345678")}) + assert response.status_code == 401 + assert b'Unauthorized' in response.data + + +def test_update_user_not_logged_in(test_client, init_database): + """ + Test PUT '/api/user' + + User is not logged in + """ + response = test_client.put('/api/user') + assert response.status_code == 401 + assert b'Unauthorized' in response.data + + +def test_update_user1_logged_in_password_username(test_client, init_database): + """ + Test PUT '/api/user' + + User1 is logged in + Change Username and Password + """ + test_client.put('/api/user', data=json.dumps(dict(username="user4", password="password")), + headers={"Authorization": "Bearer {}".format(get_token(test_client, "user1@example.com", "password"))}, + content_type='application/json') + response = test_client.get('/api/user', + headers={"Authorization": "Bearer {}".format(get_token(test_client, "user1@example.com", "password"))}, + content_type='application/json') + assert response.status_code == 200 + assert b'user4' in response.data + + +def test_update_user1_logged_in_password(test_client, init_database): + """ + Test PUT '/api/user' + + User1 is logged in + Change Password + """ + response = test_client.put('/api/user', data=json.dumps(dict(password="password123")), + headers={"Authorization": "Bearer {}".format(get_token(test_client, "user1@example.com", "password"))}) + assert response.status_code == 200 + assert b'Successfully updated user' in response.data + + +def test_update_user1_logged_in_username(test_client, init_database): + """ + Test PUT '/api/user' + + User1 is logged in + Change Username + """ + response = test_client.put('/api/user', data=json.dumps(dict(username="user1")), + headers={"Authorization": "Bearer {}".format(get_token(test_client, "user1@example.com", "password"))}) + assert response.status_code == 200 + assert b'Successfully updated user' in response.data + + +def test_set_admin_user_not_logged_in(test_client, init_database): + """ + Test PUT '/api/user/setAdmin' + + User is not logged in + """ + response = test_client.put('/api/user/setAdmin', data=json.dumps(dict(email="user1@example.com", admin=True)), content_type='application/json') + assert response.status_code == 401 + assert b'Unauthorized' in response.data + + +def test_set_admin_user1_logged_in(test_client, init_database): + """ + Test PUT '/api/user/setAdmin' + + User1 is logged in (no admin) + """ + response = test_client.put('/api/user/setAdmin', data=json.dumps(dict(email="user1@example.com", admin=True)), + headers={"Authorization": "Bearer {}".format(get_token(test_client, "user1@example.com", "password"))}, + content_type='application/json') + assert response.status_code == 401 + assert b'Only admin users can access this' in response.data + + +def test_set_admin_admin1_logged_in(test_client, init_database): + """ + Test PUT '/api/user/setAdmin' + + Admin1 is logged in (admin) + """ + response = test_client.put('/api/user/setAdmin', data=json.dumps(dict(email="user1@example.com", admin=True)), + headers={"Authorization": "Bearer {}".format(get_token(test_client, "admin1@example.com", "admin1"))}, + content_type='application/json') + assert response.status_code == 200 + assert b'Successfully updated users admin rights' in response.data + + +def test_set_admin_admin1_logged_in_user_not_exist(test_client, init_database): + """ + Test PUT '/api/user/setAdmin' + + Admin1 is logged in (admin) + notexistinguser@example.com does not exist + """ + response = test_client.put('/api/user/setAdmin', data=json.dumps(dict(email="notexistinguser@example.com", admin=True)), + headers={"Authorization": "Bearer {}".format(get_token(test_client, "admin1@example.com", "admin1"))}, + content_type='application/json') + assert response.status_code == 500 + assert b'Unable to update user' in response.data + + +def test_set_admin_admin1_logged_in_email_missing(test_client, init_database): + """ + Test PUT '/api/user/setAdmin' + + Admin1 is logged in (admin) + email missing + """ + response = test_client.put('/api/user/setAdmin', data=json.dumps(dict(admin=True)), + headers={"Authorization": "Bearer {}".format(get_token(test_client, "admin1@example.com", "admin1"))}, + content_type='application/json') + assert response.status_code == 400 + assert b'missing' in response.data + + +def test_set_admin_admin1_logged_in_email_empty(test_client, init_database): + """ + Test PUT '/api/user/setAdmin' + + Admin1 is logged in (admin) + email missing + """ + response = test_client.put('/api/user/setAdmin', data=json.dumps(dict(email="", admin=True)), + headers={"Authorization": "Bearer {}".format(get_token(test_client, "admin1@example.com", "admin1"))}, + content_type='application/json') + assert response.status_code == 400 + assert b'Not a valid email address.' in response.data + + +def test_set_admin_admin1_logged_in_admin_missing(test_client, init_database): + """ + Test PUT '/api/user/setAdmin' + + Admin1 is logged in (admin) + admin data missing + """ + response = test_client.put('/api/user/setAdmin', data=json.dumps(dict(email="user1@example.com")), + headers={"Authorization": "Bearer {}".format(get_token(test_client, "admin1@example.com", "admin1"))}, + content_type='application/json') + assert response.status_code == 400 + assert b'missing' in response.data + + +def test_set_admin_admin1_logged_in_admin_empty(test_client, init_database): + """ + Test PUT '/api/user/setAdmin' + + Admin1 is logged in (admin) + admin data missing + """ + response = test_client.put('/api/user/setAdmin', data=json.dumps(dict(email="user1@example.com", admin=None)), + headers={"Authorization": "Bearer {}".format(get_token(test_client, "admin1@example.com", "admin1"))}, + content_type='application/json') + assert response.status_code == 400 + assert b'Field may not be null' in response.data + + +def test_get_users_not_logged_in(test_client, init_database): + """ + Test GET '/api/users' + + User is not logged in + """ + response = test_client.get('/api/users') + assert response.status_code == 401 + assert b'Unauthorized' in response.data + + +def test_get_users_user1_logged_in(test_client, init_database): + """ + Test GET '/api/users' + + User1 is logged in (not admin) + """ + response = test_client.get('/api/users', + headers={"Authorization": "Bearer {}".format(get_token(test_client, "user1@example.com", "password"))}, + content_type='application/json') + + assert response.status_code == 401 + assert b'Only admin users can access this' in response.data + + +def test_get_users_admin1_logged_in(test_client, init_database): + """ + Test GET '/api/users' + + Admin1 is logged in (admin) + """ + response = test_client.get('/api/users', + headers={"Authorization": "Bearer {}".format(get_token(test_client, "admin1@example.com", "admin1"))}, + content_type='application/json') + assert response.status_code == 200 + assert b'Successfully received all users' in response.data + + +def get_token(test_client, email, password): + response = test_client.post('/api/user/login', data=json.dumps(dict(email=email, password=password)), content_type='application/json') + + if "data" in json.loads(response.data): + if "token" in json.loads(response.data)["data"]: + return json.loads(response.data)["data"]["token"] + + return "" diff --git a/api/tests/pytest.ini b/api/tests/pytest.ini new file mode 100644 index 0000000..e69de29 diff --git a/api/tests/unit/__init__.py b/api/tests/unit/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/api/tests/unit/test_auth.py b/api/tests/unit/test_auth.py new file mode 100644 index 0000000..cc996cd --- /dev/null +++ b/api/tests/unit/test_auth.py @@ -0,0 +1,14 @@ +""" +This file (test_helper_functions.py) contains the unit tests for the helper_functions.py file. +""" +import datetime + +import jwt +from app.auth import * + + +def test_verify_token(): + """ + Test verify_token function + """ + assert verify_token(None) is False diff --git a/api/tests/unit/test_helper_functions.py b/api/tests/unit/test_helper_functions.py new file mode 100644 index 0000000..e5b4b2c --- /dev/null +++ b/api/tests/unit/test_helper_functions.py @@ -0,0 +1,67 @@ +""" +This file (test_helper_functions.py) contains the unit tests for the helper_functions.py file. +""" +import datetime + +import jwt +from app.helper_functions import * + + +def test_hash_password(): + """ + Test hash_password function + """ + assert hash_password("password") != "password" + + +def test_check_password(): + """ + Test check_password function + """ + hashed = hash_password("password") + assert check_password(hashed, "password".encode("utf-8")) is True + assert check_password(hashed, "password1".encode("utf-8")) is False + + +def test_get_email_from_token_data(test_client, init_database): + """ + Test get_email_from_token_data function + """ + + # Invalid token + # request_ctx = test_client.test_request_context(headers={'Authorization': 'Bearer XXXXX'}) + # request_ctx.push() + # assert get_email_from_token_data("SECRET_KEY") is None + # + # # empty token + # request_ctx = test_client.test_request_context(headers={'Authorization': 'Bearer'}) + # request_ctx.push() + # assert get_email_from_token_data("SECRET_KEY") is None + # + # # valid token + # exp = datetime.datetime.utcnow() + datetime.timedelta(days=365) + # token = jwt.encode({'email': "user@example.com", 'exp': exp}, "SECRET_KEY", "HS256") + # request_ctx = test_client.test_request_context(headers={'Authorization': 'Bearer ' + token}) + # request_ctx.push() + # assert get_email_from_token_data("SECRET_KEY") == "user@example.com" + # + # # expired token + # exp = datetime.datetime.utcnow() + datetime.timedelta(days=-1) + # token = jwt.encode({'email': "user@example.com", 'exp': exp}, "SECRET_KEY", "HS256") + # request_ctx = test_client.test_request_context(headers={'Authorization': 'Bearer ' + token}) + # request_ctx.push() + # assert get_email_from_token_data("SECRET_KEY") is None + # + # # bot token (includes ":") but user doesn't exist + # exp = datetime.datetime.utcnow() + datetime.timedelta(days=365) + # token = jwt.encode({'email': "bot@example.com", 'exp': exp}, "SECRET_KEY", "HS256") + # request_ctx = test_client.test_request_context(headers={'Authorization': 'Bearer ' + token + ':12345'}) + # request_ctx.push() + # assert get_email_from_token_data("SECRET_KEY") is None + + # # bot token (includes ":") user exists + # exp = datetime.datetime.utcnow() + datetime.timedelta(days=365) + # token = jwt.encode({'email': "bot@example.com", 'exp': exp}, "SECRET_KEY", "HS256") + # request_ctx = test_client.test_request_context(headers={'Authorization': 'Bearer ' + token + ':12345'}) + # request_ctx.push() + # assert get_email_from_token_data("SECRET_KEY") is None \ No newline at end of file diff --git a/api/tests/unit/test_models.py b/api/tests/unit/test_models.py new file mode 100644 index 0000000..e8a336a --- /dev/null +++ b/api/tests/unit/test_models.py @@ -0,0 +1,116 @@ +""" +This file (test_models.py) contains the unit tests for the models.py file. +""" +from app.models import User, Transaction, Keyword, Share + +from app.helper_functions import hash_password + + +def test_new_user(): + """ + GIVEN a User model + WHEN a new User is created + THEN check the email, password, username, telegram_user_id and admin fields are defined correctly + """ + user = User( + email="user@example.com", + username="user", + password=hash_password("password"), + admin=False + ) + assert user.email == 'user@example.com' + assert user.password != 'password' + assert user.username == "user" + assert user.telegram_user_id is None + assert user.admin is False + assert user.as_dict() == {'email': 'user@example.com', 'username': 'user', 'telegram_user_id': None, 'admin': False} + + +def test_new_user_with_fixture(new_user): + """ + GIVEN a User model + WHEN a new User is created + THEN check the email and password_hashed fields are defined correctly + """ + assert new_user.email == 'user@example.com' + assert new_user.password != 'password' + + +def test_new_transaction(): + """ + GIVEN a Transaction model + WHEN a new Transaction is created + THEN check the email, symbol, time count and price fields are defined correctly + """ + transaction = Transaction( + email="user@example.com", + symbol="DTEGY", + time="2022-03-29T10:00:00.000Z", + count=10, + price=9.99 + ) + assert transaction.email == 'user@example.com' + assert transaction.symbol == "DTEGY" + assert transaction.count == 10 + assert transaction.price == 9.99 + assert transaction.as_dict() == {'t_id': None, 'email': 'user@example.com', 'symbol': 'DTEGY', 'time': '2022-03-29T10:00:00.000Z', 'count': 10, 'price': 9.99} + + +def test_new_transaction_with_fixture(new_transaction): + """ + GIVEN a User model + WHEN a new User is created + THEN check the email and password_hashed fields are defined correctly + """ + assert new_transaction.email == 'user@example.com' + assert new_transaction.symbol == 'DTEGY' + + +def test_new_keyword(): + """ + GIVEN a Transaction model + WHEN a new Transaction is created + THEN check the email, symbol, time count and price fields are defined correctly + """ + keyword = Keyword( + email="user@example.com", + keyword="Elon Musk" + ) + assert keyword.email == 'user@example.com' + assert keyword.keyword == "Elon Musk" + assert keyword.as_dict() == {'s_id': None, 'email': 'user@example.com', 'keyword': 'Elon Musk'} + + +def test_new_keyword_with_fixture(new_keyword): + """ + GIVEN a User model + WHEN a new User is created + THEN check the email and password_hashed fields are defined correctly + """ + assert new_keyword.email == 'user@example.com' + assert new_keyword.keyword == 'Elon Musk' + + +def test_new_share(): + """ + GIVEN a Transaction model + WHEN a new Transaction is created + THEN check the email, symbol, time count and price fields are defined correctly + """ + share = Share( + email="user@example.com", + symbol="DTEGY" + ) + assert share.email == 'user@example.com' + assert share.symbol == "DTEGY" + assert share.as_dict() == {'a_id': None, 'email': 'user@example.com', 'symbol': 'DTEGY'} + + +def test_new_share_with_fixture(new_share): + """ + GIVEN a User model + WHEN a new User is created + THEN check the email and password_hashed fields are defined correctly + """ + assert new_share.email == 'user@example.com' + assert new_share.symbol == 'DTEGY' diff --git a/api/tests/unit/test_transaction.py b/api/tests/unit/test_transaction.py new file mode 100644 index 0000000..66f51ae --- /dev/null +++ b/api/tests/unit/test_transaction.py @@ -0,0 +1,40 @@ +""" +This file (test_transaction.py) contains the unit tests for the blueprints/transaction.py file. +""" +from app.blueprints.transactions import * + + +def test_check_if_symbol_data_exists(): + """ + Test check_if_symbol_data_exists function + """ + assert check_if_symbol_data_exists(dict(symbol='DTEGY')) is True + assert check_if_symbol_data_exists(dict(symbol='')) is False + assert check_if_symbol_data_exists(dict()) is False + + +def test_check_if_time_data_exists(): + """ + Test check_if_time_data_exists function + """ + assert check_if_time_data_exists(dict(time='2022-03-29T10:00:00.000Z')) is True + assert check_if_time_data_exists(dict(time='')) is False + assert check_if_time_data_exists(dict()) is False + + +def test_check_if_count_data_exists(): + """ + Test check_if_count_data_exists function + """ + assert check_if_count_data_exists(dict(count=10)) is True + assert check_if_count_data_exists(dict(count=None)) is False + assert check_if_count_data_exists(dict()) is False + + +def test_check_if_price_data_exists(): + """ + Test check_if_price_data_exists function + """ + assert check_if_price_data_exists(dict(price=9.99)) is True + assert check_if_price_data_exists(dict(price=None)) is False + assert check_if_price_data_exists(dict()) is False diff --git a/api/tests/unit/test_user.py b/api/tests/unit/test_user.py new file mode 100644 index 0000000..91ad580 --- /dev/null +++ b/api/tests/unit/test_user.py @@ -0,0 +1,40 @@ +""" +This file (test_helper_functions.py) contains the unit tests for the helper_functions.py file. +""" +from app.blueprints.user import * + + +def test_check_if_email_data_exists(): + """ + Test check_if_email_data_exists function + """ + assert check_if_email_data_exists(dict(email='user@example.com')) is True + assert check_if_email_data_exists(dict(email='')) is False + assert check_if_email_data_exists(dict()) is False + + +def test_check_if_password_data_exists(): + """ + Test check_if_password_data_exists function + """ + assert check_if_password_data_exists(dict(password='password')) is True + assert check_if_password_data_exists(dict(password='')) is False + assert check_if_password_data_exists(dict()) is False + + +def test_check_if_username_data_exists(): + """ + Test check_if_username_data_exists function + """ + assert check_if_username_data_exists(dict(username='user')) is True + assert check_if_username_data_exists(dict(username='')) is False + assert check_if_username_data_exists(dict()) is False + + +def test_check_if_admin_data_exists(): + """ + Test check_if_admin_data_exists function + """ + assert check_if_admin_data_exists(dict(admin=True)) is True + assert check_if_admin_data_exists(dict(admin=None)) is False + assert check_if_admin_data_exists(dict()) is False From 4b7dfa8afd679ad1218f01b58711965b22a8073a Mon Sep 17 00:00:00 2001 From: H4CK3R-01 Date: Wed, 30 Mar 2022 10:51:38 +0200 Subject: [PATCH 03/33] Update README.md --- api/README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/api/README.md b/api/README.md index e771473..e9daaa2 100644 --- a/api/README.md +++ b/api/README.md @@ -10,6 +10,16 @@ Aktienbot API 2. Or set variables using `export` or `set` commands. (Windows `set`, Linux `export`) 4. Run api `python api/app.py` + +## Testing +1. Create virtual environment `python -m venv venv env/Scripts/activate` +2. Install requirements `pip install -r api/requirements.txt` +3. Set environment variables (see list below) + 1. Use `.env`-file in `api` directory like `.env.example` + 2. Or set variables using `export` or `set` commands. (Windows `set`, Linux `export`) +4. Change directory: `cd api/` +5. Run tests: `python -m pytest -v --cov-report term-missing --cov=app` + ## Environment variables ``` # Flask secret key From 1f2487f1c122bb57148b3e562ccbfb7b0d1bb5f1 Mon Sep 17 00:00:00 2001 From: H4CK3R-01 Date: Wed, 30 Mar 2022 11:10:48 +0200 Subject: [PATCH 04/33] Refactoring --- api/tests/conftest.py | 3 -- api/tests/functional/helper_functions.py | 11 ++++++ api/tests/functional/test_keyword.py | 11 +----- api/tests/functional/test_portfolio.py | 11 +----- api/tests/functional/test_share.py | 11 +----- api/tests/functional/test_telegram.py | 11 +----- api/tests/functional/test_transaction.py | 11 +----- api/tests/functional/test_user.py | 11 +----- api/tests/unit/test_auth.py | 3 -- api/tests/unit/test_helper_functions.py | 47 ------------------------ 10 files changed, 17 insertions(+), 113 deletions(-) create mode 100644 api/tests/functional/helper_functions.py diff --git a/api/tests/conftest.py b/api/tests/conftest.py index c772474..e1f32fc 100644 --- a/api/tests/conftest.py +++ b/api/tests/conftest.py @@ -1,6 +1,3 @@ -import json -import os - import pytest from app import create_app, db from app.models import User, Transaction, Keyword, Share diff --git a/api/tests/functional/helper_functions.py b/api/tests/functional/helper_functions.py new file mode 100644 index 0000000..9e68226 --- /dev/null +++ b/api/tests/functional/helper_functions.py @@ -0,0 +1,11 @@ +import json + + +def get_token(test_client, email, password): + response = test_client.post('/api/user/login', data=json.dumps(dict(email=email, password=password)), content_type='application/json') + + if "data" in json.loads(response.data): + if "token" in json.loads(response.data)["data"]: + return json.loads(response.data)["data"]["token"] + + return "" \ No newline at end of file diff --git a/api/tests/functional/test_keyword.py b/api/tests/functional/test_keyword.py index 72763ca..3517c18 100644 --- a/api/tests/functional/test_keyword.py +++ b/api/tests/functional/test_keyword.py @@ -2,6 +2,7 @@ This file (test_keyword.py) contains the functional tests for the `keyword` blueprint. """ import json +from tests.functional.helper_functions import get_token def test_add_keyword_not_logged_in(test_client, init_database): @@ -179,13 +180,3 @@ def test_delete_keyword_user1_logged_in_keyword_not_exists(test_client, init_dat content_type='application/json') assert response.status_code == 500 assert b'Keyword doesn\'t exist for this user' in response.data - - -def get_token(test_client, email, password): - response = test_client.post('/api/user/login', data=json.dumps(dict(email=email, password=password)), content_type='application/json') - - if "data" in json.loads(response.data): - if "token" in json.loads(response.data)["data"]: - return json.loads(response.data)["data"]["token"] - - return "" diff --git a/api/tests/functional/test_portfolio.py b/api/tests/functional/test_portfolio.py index 10fd8cc..f240c93 100644 --- a/api/tests/functional/test_portfolio.py +++ b/api/tests/functional/test_portfolio.py @@ -2,6 +2,7 @@ This file (test_portfolio.py) contains the functional tests for the `portfolio` blueprint. """ import json +from tests.functional.helper_functions import get_token def test_get_portfolio_not_logged_in_empty_response(test_client, init_database): @@ -47,13 +48,3 @@ def test_get_portfolio_user1_logged_in_response_data(test_client, init_database) assert response.status_code == 200 assert b'"data":[]' not in response.data assert b'"data":[' in response.data - - -def get_token(test_client, email, password): - response = test_client.post('/api/user/login', data=json.dumps(dict(email=email, password=password)), content_type='application/json') - - if "data" in json.loads(response.data): - if "token" in json.loads(response.data)["data"]: - return json.loads(response.data)["data"]["token"] - - return "" diff --git a/api/tests/functional/test_share.py b/api/tests/functional/test_share.py index 61772fa..8df2ced 100644 --- a/api/tests/functional/test_share.py +++ b/api/tests/functional/test_share.py @@ -2,6 +2,7 @@ This file (test_share.py) contains the functional tests for the `share` blueprint. """ import json +from tests.functional.helper_functions import get_token def test_add_share_not_logged_in(test_client, init_database): @@ -179,13 +180,3 @@ def test_delete_share_user1_logged_in_symbol_not_exists(test_client, init_databa content_type='application/json') assert response.status_code == 500 assert b'Symbol doesn\'t exist for this user' in response.data - - -def get_token(test_client, email, password): - response = test_client.post('/api/user/login', data=json.dumps(dict(email=email, password=password)), content_type='application/json') - - if "data" in json.loads(response.data): - if "token" in json.loads(response.data)["data"]: - return json.loads(response.data)["data"]["token"] - - return "" diff --git a/api/tests/functional/test_telegram.py b/api/tests/functional/test_telegram.py index 756f6cd..34acee7 100644 --- a/api/tests/functional/test_telegram.py +++ b/api/tests/functional/test_telegram.py @@ -2,6 +2,7 @@ This file (test_telegram.py) contains the functional tests for the `telegram` blueprint. """ import json +from tests.functional.helper_functions import get_token def test_add_telegram_not_logged_in(test_client, init_database): @@ -54,13 +55,3 @@ def test_add_telegram_user1_logged_in_user_data_empty(test_client, init_database content_type='application/json') assert response.status_code == 400 assert b'missing' in response.data - - -def get_token(test_client, email, password): - response = test_client.post('/api/user/login', data=json.dumps(dict(email=email, password=password)), content_type='application/json') - - if "data" in json.loads(response.data): - if "token" in json.loads(response.data)["data"]: - return json.loads(response.data)["data"]["token"] - - return "" diff --git a/api/tests/functional/test_transaction.py b/api/tests/functional/test_transaction.py index d50578a..cb7b9ad 100644 --- a/api/tests/functional/test_transaction.py +++ b/api/tests/functional/test_transaction.py @@ -2,6 +2,7 @@ This file (test_transaction.py) contains the functional tests for the `transaction` blueprint. """ import json +from tests.functional.helper_functions import get_token def test_add_transaction_not_logged_in(test_client, init_database): @@ -91,13 +92,3 @@ def test_get_transaction_user1_logged_in_response_data(test_client, init_databas content_type='application/json') assert response.status_code == 200 assert b'Successfully loaded transactions' in response.data - - -def get_token(test_client, email, password): - response = test_client.post('/api/user/login', data=json.dumps(dict(email=email, password=password)), content_type='application/json') - - if "data" in json.loads(response.data): - if "token" in json.loads(response.data)["data"]: - return json.loads(response.data)["data"]["token"] - - return "" diff --git a/api/tests/functional/test_user.py b/api/tests/functional/test_user.py index 6a88089..8b57338 100644 --- a/api/tests/functional/test_user.py +++ b/api/tests/functional/test_user.py @@ -2,6 +2,7 @@ This file (test_user.py) contains the functional tests for the `users` blueprint. """ import json +from tests.functional.helper_functions import get_token def test_login_with_valid_data(test_client, init_database): @@ -501,13 +502,3 @@ def test_get_users_admin1_logged_in(test_client, init_database): content_type='application/json') assert response.status_code == 200 assert b'Successfully received all users' in response.data - - -def get_token(test_client, email, password): - response = test_client.post('/api/user/login', data=json.dumps(dict(email=email, password=password)), content_type='application/json') - - if "data" in json.loads(response.data): - if "token" in json.loads(response.data)["data"]: - return json.loads(response.data)["data"]["token"] - - return "" diff --git a/api/tests/unit/test_auth.py b/api/tests/unit/test_auth.py index cc996cd..cf35577 100644 --- a/api/tests/unit/test_auth.py +++ b/api/tests/unit/test_auth.py @@ -1,9 +1,6 @@ """ This file (test_helper_functions.py) contains the unit tests for the helper_functions.py file. """ -import datetime - -import jwt from app.auth import * diff --git a/api/tests/unit/test_helper_functions.py b/api/tests/unit/test_helper_functions.py index e5b4b2c..5cf054b 100644 --- a/api/tests/unit/test_helper_functions.py +++ b/api/tests/unit/test_helper_functions.py @@ -1,9 +1,6 @@ """ This file (test_helper_functions.py) contains the unit tests for the helper_functions.py file. """ -import datetime - -import jwt from app.helper_functions import * @@ -21,47 +18,3 @@ def test_check_password(): hashed = hash_password("password") assert check_password(hashed, "password".encode("utf-8")) is True assert check_password(hashed, "password1".encode("utf-8")) is False - - -def test_get_email_from_token_data(test_client, init_database): - """ - Test get_email_from_token_data function - """ - - # Invalid token - # request_ctx = test_client.test_request_context(headers={'Authorization': 'Bearer XXXXX'}) - # request_ctx.push() - # assert get_email_from_token_data("SECRET_KEY") is None - # - # # empty token - # request_ctx = test_client.test_request_context(headers={'Authorization': 'Bearer'}) - # request_ctx.push() - # assert get_email_from_token_data("SECRET_KEY") is None - # - # # valid token - # exp = datetime.datetime.utcnow() + datetime.timedelta(days=365) - # token = jwt.encode({'email': "user@example.com", 'exp': exp}, "SECRET_KEY", "HS256") - # request_ctx = test_client.test_request_context(headers={'Authorization': 'Bearer ' + token}) - # request_ctx.push() - # assert get_email_from_token_data("SECRET_KEY") == "user@example.com" - # - # # expired token - # exp = datetime.datetime.utcnow() + datetime.timedelta(days=-1) - # token = jwt.encode({'email': "user@example.com", 'exp': exp}, "SECRET_KEY", "HS256") - # request_ctx = test_client.test_request_context(headers={'Authorization': 'Bearer ' + token}) - # request_ctx.push() - # assert get_email_from_token_data("SECRET_KEY") is None - # - # # bot token (includes ":") but user doesn't exist - # exp = datetime.datetime.utcnow() + datetime.timedelta(days=365) - # token = jwt.encode({'email': "bot@example.com", 'exp': exp}, "SECRET_KEY", "HS256") - # request_ctx = test_client.test_request_context(headers={'Authorization': 'Bearer ' + token + ':12345'}) - # request_ctx.push() - # assert get_email_from_token_data("SECRET_KEY") is None - - # # bot token (includes ":") user exists - # exp = datetime.datetime.utcnow() + datetime.timedelta(days=365) - # token = jwt.encode({'email': "bot@example.com", 'exp': exp}, "SECRET_KEY", "HS256") - # request_ctx = test_client.test_request_context(headers={'Authorization': 'Bearer ' + token + ':12345'}) - # request_ctx.push() - # assert get_email_from_token_data("SECRET_KEY") is None \ No newline at end of file From 041cd16450cebef0460c4fa842bbaa2b8ed1c940 Mon Sep 17 00:00:00 2001 From: H4CK3R-01 Date: Wed, 30 Mar 2022 11:11:02 +0200 Subject: [PATCH 05/33] Fixed test config --- api/app/config/flask_test.cfg | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/api/app/config/flask_test.cfg b/api/app/config/flask_test.cfg index 01ebea6..2cc085b 100644 --- a/api/app/config/flask_test.cfg +++ b/api/app/config/flask_test.cfg @@ -17,10 +17,10 @@ SQLALCHEMY_ENGINE_OPTIONS = { BASE_RESPONSE_DATA_KEY = "data" BASE_RESPONSE_SCHEMA = BaseResponseSchema -BOT_EMAIL = os.getenv('BOT_EMAIL') -BOT_USERNAME = os.getenv('BOT_USERNAME') -BOT_PASSWORD = os.getenv('BOT_PASSWORD') +BOT_EMAIL = "bot1@example.com" +BOT_USERNAME = "bot1" +BOT_PASSWORD = "bot1" -ADMIN_EMAIL = os.getenv('ADMIN_EMAIL') -ADMIN_USERNAME = os.getenv('ADMIN_USERNAME') -ADMIN_PASSWORD = os.getenv('ADMIN_PASSWORD') +ADMIN_EMAIL = "admin1@example.com" +ADMIN_USERNAME = "admin1" +ADMIN_PASSWORD = "admin1" From 985f5158aedb5e8efe06d39b61bead93a7e168c4 Mon Sep 17 00:00:00 2001 From: H4CK3R-01 Date: Wed, 30 Mar 2022 11:11:14 +0200 Subject: [PATCH 06/33] Updated requirements.txt --- api/requirements.txt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/api/requirements.txt b/api/requirements.txt index 16da00c..e25210c 100644 --- a/api/requirements.txt +++ b/api/requirements.txt @@ -8,5 +8,6 @@ pyjwt==2.3.0 apiflask==0.12.0 flask-cors==3.0.10 bcrypt==3.2.0 -pytest -pytest-cov \ No newline at end of file +pytest~=7.1.1 +pytest-cov +marshmallow~=3.15.0 \ No newline at end of file From 7dc903d583d832646fbe2189892f350895a5c700 Mon Sep 17 00:00:00 2001 From: H4CK3R-01 Date: Wed, 30 Mar 2022 11:18:03 +0200 Subject: [PATCH 07/33] Add tests to pipeline --- .woodpecker/pipeline.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.woodpecker/pipeline.yml b/.woodpecker/pipeline.yml index 56f8cf8..34a73c9 100644 --- a/.woodpecker/pipeline.yml +++ b/.woodpecker/pipeline.yml @@ -14,6 +14,16 @@ pipeline: event: pull_request # -------------------------------------- API -------------------------------------- + test_api: + image: python + commands: + - cd api/ + - pip install -r requirements.txt + - python -m pytest + when: + path: "api/*" + event: push + build_api: image: woodpeckerci/plugin-docker-buildx settings: From 7f0addfecb4be3e53391b47cc1188731f63d5d7e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 31 Mar 2022 10:31:50 +0000 Subject: [PATCH 08/33] Bump @angular-devkit/build-angular from 13.3.0 to 13.3.1 in /frontend Bumps [@angular-devkit/build-angular](https://github.com/angular/angular-cli) from 13.3.0 to 13.3.1. - [Release notes](https://github.com/angular/angular-cli/releases) - [Changelog](https://github.com/angular/angular-cli/blob/master/CHANGELOG.md) - [Commits](https://github.com/angular/angular-cli/compare/13.3.0...13.3.1) --- updated-dependencies: - dependency-name: "@angular-devkit/build-angular" dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- frontend/package-lock.json | 264 +++++++++++++++++++++++++++---------- frontend/package.json | 2 +- 2 files changed, 199 insertions(+), 67 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 0a65e59..2f4858e 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -24,7 +24,7 @@ "zone.js": "~0.11.4" }, "devDependencies": { - "@angular-devkit/build-angular": "~13.3.0", + "@angular-devkit/build-angular": "~13.3.1", "@angular/cli": "~13.3.0", "@angular/compiler-cli": "~13.2.0", "@types/jasmine": "~4.0.0", @@ -85,15 +85,15 @@ "dev": true }, "node_modules/@angular-devkit/build-angular": { - "version": "13.3.0", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-13.3.0.tgz", - "integrity": "sha512-3Ji7EeqGHj7i1Jgmeo3aIEXsnfKyFeQPpl65gcYmHwj5dP4lZzLSU4rMaWWUKksccgqCUXgPI2vKePTPazmikg==", + "version": "13.3.1", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-13.3.1.tgz", + "integrity": "sha512-xxBW4zZZM+lewW0nEpk9SXw6BMYhxe8WI/FjyEroOV8G2IuOrjZ4112QOpk6jCgmPHSOEldbltEdwoVLAnu09Q==", "dev": true, "dependencies": { "@ampproject/remapping": "1.1.1", - "@angular-devkit/architect": "0.1303.0", - "@angular-devkit/build-webpack": "0.1303.0", - "@angular-devkit/core": "13.3.0", + "@angular-devkit/architect": "0.1303.1", + "@angular-devkit/build-webpack": "0.1303.1", + "@angular-devkit/core": "13.3.1", "@babel/core": "7.16.12", "@babel/generator": "7.16.8", "@babel/helper-annotate-as-pure": "7.16.7", @@ -104,7 +104,7 @@ "@babel/runtime": "7.16.7", "@babel/template": "7.16.7", "@discoveryjs/json-ext": "0.5.6", - "@ngtools/webpack": "13.3.0", + "@ngtools/webpack": "13.3.1", "ansi-colors": "4.1.1", "babel-loader": "8.2.3", "babel-plugin-istanbul": "6.1.1", @@ -138,7 +138,7 @@ "regenerator-runtime": "0.13.9", "resolve-url-loader": "5.0.0", "rxjs": "6.6.7", - "sass": "1.49.0", + "sass": "1.49.9", "sass-loader": "12.4.0", "semver": "7.3.5", "source-map-loader": "3.0.1", @@ -194,6 +194,48 @@ } } }, + "node_modules/@angular-devkit/build-angular/node_modules/@angular-devkit/architect": { + "version": "0.1303.1", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1303.1.tgz", + "integrity": "sha512-ppaLzNZPrqrI96ddgm1RuEALVpWZsmHbIPLDd0GBwhF6aOkwF0LpZHd5XyS4ktGFZPReiFIjWSVtqV5vaBdRsw==", + "dev": true, + "dependencies": { + "@angular-devkit/core": "13.3.1", + "rxjs": "6.6.7" + }, + "engines": { + "node": "^12.20.0 || ^14.15.0 || >=16.10.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/@angular-devkit/core": { + "version": "13.3.1", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-13.3.1.tgz", + "integrity": "sha512-eXAcQaP1mn6rnQb+5bv5NsamY6b34UYM7G+S154Hnma6CTTSGBtcmoNAJs8cekuFqWlw7YgpB/e15jR5OLPkDA==", + "dev": true, + "dependencies": { + "ajv": "8.9.0", + "ajv-formats": "2.1.1", + "fast-json-stable-stringify": "2.1.0", + "magic-string": "0.25.7", + "rxjs": "6.6.7", + "source-map": "0.7.3" + }, + "engines": { + "node": "^12.20.0 || ^14.15.0 || >=16.10.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "peerDependencies": { + "chokidar": "^3.5.2" + }, + "peerDependenciesMeta": { + "chokidar": { + "optional": true + } + } + }, "node_modules/@angular-devkit/build-angular/node_modules/rxjs": { "version": "6.6.7", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", @@ -213,12 +255,12 @@ "dev": true }, "node_modules/@angular-devkit/build-webpack": { - "version": "0.1303.0", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1303.0.tgz", - "integrity": "sha512-a+Veg2oYn3RM2Kl148BReuONmD1kjbbYBnMUVi8nD6rvJPStFZkqN5s5ZkYybKeWnzMGaB3VasKR88z5XeH22A==", + "version": "0.1303.1", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1303.1.tgz", + "integrity": "sha512-KSnR3y2q5hxh7t7ZSi0Emv/Kh9+D105JaEeyEqjqRjLdZSd2m6eAxbSUMNOAsbqnJTMCfzU5AG7jhbujuge0dQ==", "dev": true, "dependencies": { - "@angular-devkit/architect": "0.1303.0", + "@angular-devkit/architect": "0.1303.1", "rxjs": "6.6.7" }, "engines": { @@ -231,6 +273,48 @@ "webpack-dev-server": "^4.0.0" } }, + "node_modules/@angular-devkit/build-webpack/node_modules/@angular-devkit/architect": { + "version": "0.1303.1", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1303.1.tgz", + "integrity": "sha512-ppaLzNZPrqrI96ddgm1RuEALVpWZsmHbIPLDd0GBwhF6aOkwF0LpZHd5XyS4ktGFZPReiFIjWSVtqV5vaBdRsw==", + "dev": true, + "dependencies": { + "@angular-devkit/core": "13.3.1", + "rxjs": "6.6.7" + }, + "engines": { + "node": "^12.20.0 || ^14.15.0 || >=16.10.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + } + }, + "node_modules/@angular-devkit/build-webpack/node_modules/@angular-devkit/core": { + "version": "13.3.1", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-13.3.1.tgz", + "integrity": "sha512-eXAcQaP1mn6rnQb+5bv5NsamY6b34UYM7G+S154Hnma6CTTSGBtcmoNAJs8cekuFqWlw7YgpB/e15jR5OLPkDA==", + "dev": true, + "dependencies": { + "ajv": "8.9.0", + "ajv-formats": "2.1.1", + "fast-json-stable-stringify": "2.1.0", + "magic-string": "0.25.7", + "rxjs": "6.6.7", + "source-map": "0.7.3" + }, + "engines": { + "node": "^12.20.0 || ^14.15.0 || >=16.10.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "peerDependencies": { + "chokidar": "^3.5.2" + }, + "peerDependenciesMeta": { + "chokidar": { + "optional": true + } + } + }, "node_modules/@angular-devkit/build-webpack/node_modules/rxjs": { "version": "6.6.7", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", @@ -2406,9 +2490,9 @@ } }, "node_modules/@ngtools/webpack": { - "version": "13.3.0", - "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-13.3.0.tgz", - "integrity": "sha512-QbTQWXK2WzYU+aKKVDG0ya7WYT+6rNAUXVt5ov9Nz1SGgDeozpiOx8ZqPWUvnToTY8EoodwWFGCVtkLHXUR+wA==", + "version": "13.3.1", + "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-13.3.1.tgz", + "integrity": "sha512-40iEqAA/l882MPbGuG5EYxzsPWJ37fT4fF22SkPLX2eBgNhJ4K8XMt0gqcFhkHUsSe63frg1qjQ1Pd31msu0bQ==", "dev": true, "engines": { "node": "^12.20.0 || ^14.15.0 || >=16.10.0", @@ -6055,9 +6139,9 @@ } }, "node_modules/html-entities": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.3.2.tgz", - "integrity": "sha512-c3Ab/url5ksaT0WyleslpBEthOzWhrjQbg75y7XUsfSzi3Dgzt0l8w5e7DylRn15MTlMMD58dTfzddNS2kcAjQ==", + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.3.3.tgz", + "integrity": "sha512-DV5Ln36z34NNTDgnz0EWGBLZENelNAtkiFA4kyNOG2tDI6Mz1uSWiq1wAKdyjnJwyDiDO7Fa2SO1CTxPXL8VxA==", "dev": true }, "node_modules/html-escaper": { @@ -7868,9 +7952,9 @@ "optional": true }, "node_modules/node-forge": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.0.tgz", - "integrity": "sha512-08ARB91bUi6zNKzVmaj3QO7cr397uiDT2nJ63cHjyNtCTWIgvS47j3eT0WfzUwS9+6Z5YshRaoasFkXCKrIYbA==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", + "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", "dev": true, "engines": { "node": ">= 6.13.0" @@ -8739,12 +8823,12 @@ } }, "node_modules/portfinder/node_modules/mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", "dev": true, "dependencies": { - "minimist": "^1.2.5" + "minimist": "^1.2.6" }, "bin": { "mkdirp": "bin/cmd.js" @@ -9776,9 +9860,9 @@ "dev": true }, "node_modules/sass": { - "version": "1.49.0", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.49.0.tgz", - "integrity": "sha512-TVwVdNDj6p6b4QymJtNtRS2YtLJ/CqZriGg0eIAbAKMlN8Xy6kbv33FsEZSF7FufFFM705SQviHjjThfaQ4VNw==", + "version": "1.49.9", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.49.9.tgz", + "integrity": "sha512-YlYWkkHP9fbwaFRZQRXgDi3mXZShslVmmo+FVK3kHLUELHHEYrCmL1x6IUjC7wLS6VuJSAFXRQS/DxdsC4xL1A==", "dev": true, "dependencies": { "chokidar": ">=3.0.0 <4.0.0", @@ -9789,7 +9873,7 @@ "sass": "sass.js" }, "engines": { - "node": ">=8.9.0" + "node": ">=12.0.0" } }, "node_modules/sass-loader": { @@ -9888,12 +9972,12 @@ "dev": true }, "node_modules/selfsigned": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.0.0.tgz", - "integrity": "sha512-cUdFiCbKoa1mZ6osuJs2uDHrs0k0oprsKveFiiaBKCNq3SYyb5gs2HxhQyDNLCmL51ZZThqi4YNDpCK6GOP1iQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.0.1.tgz", + "integrity": "sha512-LmME957M1zOsUhG+67rAjKfiWFox3SBxE/yymatMZsAx+oMrJ0YQ8AToOnyCm7xbeg2ep37IHLxdu0o2MavQOQ==", "dev": true, "dependencies": { - "node-forge": "^1.2.0" + "node-forge": "^1" }, "engines": { "node": ">=10" @@ -11459,15 +11543,15 @@ } }, "@angular-devkit/build-angular": { - "version": "13.3.0", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-13.3.0.tgz", - "integrity": "sha512-3Ji7EeqGHj7i1Jgmeo3aIEXsnfKyFeQPpl65gcYmHwj5dP4lZzLSU4rMaWWUKksccgqCUXgPI2vKePTPazmikg==", + "version": "13.3.1", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-13.3.1.tgz", + "integrity": "sha512-xxBW4zZZM+lewW0nEpk9SXw6BMYhxe8WI/FjyEroOV8G2IuOrjZ4112QOpk6jCgmPHSOEldbltEdwoVLAnu09Q==", "dev": true, "requires": { "@ampproject/remapping": "1.1.1", - "@angular-devkit/architect": "0.1303.0", - "@angular-devkit/build-webpack": "0.1303.0", - "@angular-devkit/core": "13.3.0", + "@angular-devkit/architect": "0.1303.1", + "@angular-devkit/build-webpack": "0.1303.1", + "@angular-devkit/core": "13.3.1", "@babel/core": "7.16.12", "@babel/generator": "7.16.8", "@babel/helper-annotate-as-pure": "7.16.7", @@ -11478,7 +11562,7 @@ "@babel/runtime": "7.16.7", "@babel/template": "7.16.7", "@discoveryjs/json-ext": "0.5.6", - "@ngtools/webpack": "13.3.0", + "@ngtools/webpack": "13.3.1", "ansi-colors": "4.1.1", "babel-loader": "8.2.3", "babel-plugin-istanbul": "6.1.1", @@ -11513,7 +11597,7 @@ "regenerator-runtime": "0.13.9", "resolve-url-loader": "5.0.0", "rxjs": "6.6.7", - "sass": "1.49.0", + "sass": "1.49.9", "sass-loader": "12.4.0", "semver": "7.3.5", "source-map-loader": "3.0.1", @@ -11531,6 +11615,30 @@ "webpack-subresource-integrity": "5.1.0" }, "dependencies": { + "@angular-devkit/architect": { + "version": "0.1303.1", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1303.1.tgz", + "integrity": "sha512-ppaLzNZPrqrI96ddgm1RuEALVpWZsmHbIPLDd0GBwhF6aOkwF0LpZHd5XyS4ktGFZPReiFIjWSVtqV5vaBdRsw==", + "dev": true, + "requires": { + "@angular-devkit/core": "13.3.1", + "rxjs": "6.6.7" + } + }, + "@angular-devkit/core": { + "version": "13.3.1", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-13.3.1.tgz", + "integrity": "sha512-eXAcQaP1mn6rnQb+5bv5NsamY6b34UYM7G+S154Hnma6CTTSGBtcmoNAJs8cekuFqWlw7YgpB/e15jR5OLPkDA==", + "dev": true, + "requires": { + "ajv": "8.9.0", + "ajv-formats": "2.1.1", + "fast-json-stable-stringify": "2.1.0", + "magic-string": "0.25.7", + "rxjs": "6.6.7", + "source-map": "0.7.3" + } + }, "rxjs": { "version": "6.6.7", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", @@ -11551,15 +11659,39 @@ } }, "@angular-devkit/build-webpack": { - "version": "0.1303.0", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1303.0.tgz", - "integrity": "sha512-a+Veg2oYn3RM2Kl148BReuONmD1kjbbYBnMUVi8nD6rvJPStFZkqN5s5ZkYybKeWnzMGaB3VasKR88z5XeH22A==", + "version": "0.1303.1", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1303.1.tgz", + "integrity": "sha512-KSnR3y2q5hxh7t7ZSi0Emv/Kh9+D105JaEeyEqjqRjLdZSd2m6eAxbSUMNOAsbqnJTMCfzU5AG7jhbujuge0dQ==", "dev": true, "requires": { - "@angular-devkit/architect": "0.1303.0", + "@angular-devkit/architect": "0.1303.1", "rxjs": "6.6.7" }, "dependencies": { + "@angular-devkit/architect": { + "version": "0.1303.1", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1303.1.tgz", + "integrity": "sha512-ppaLzNZPrqrI96ddgm1RuEALVpWZsmHbIPLDd0GBwhF6aOkwF0LpZHd5XyS4ktGFZPReiFIjWSVtqV5vaBdRsw==", + "dev": true, + "requires": { + "@angular-devkit/core": "13.3.1", + "rxjs": "6.6.7" + } + }, + "@angular-devkit/core": { + "version": "13.3.1", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-13.3.1.tgz", + "integrity": "sha512-eXAcQaP1mn6rnQb+5bv5NsamY6b34UYM7G+S154Hnma6CTTSGBtcmoNAJs8cekuFqWlw7YgpB/e15jR5OLPkDA==", + "dev": true, + "requires": { + "ajv": "8.9.0", + "ajv-formats": "2.1.1", + "fast-json-stable-stringify": "2.1.0", + "magic-string": "0.25.7", + "rxjs": "6.6.7", + "source-map": "0.7.3" + } + }, "rxjs": { "version": "6.6.7", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", @@ -13083,9 +13215,9 @@ } }, "@ngtools/webpack": { - "version": "13.3.0", - "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-13.3.0.tgz", - "integrity": "sha512-QbTQWXK2WzYU+aKKVDG0ya7WYT+6rNAUXVt5ov9Nz1SGgDeozpiOx8ZqPWUvnToTY8EoodwWFGCVtkLHXUR+wA==", + "version": "13.3.1", + "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-13.3.1.tgz", + "integrity": "sha512-40iEqAA/l882MPbGuG5EYxzsPWJ37fT4fF22SkPLX2eBgNhJ4K8XMt0gqcFhkHUsSe63frg1qjQ1Pd31msu0bQ==", "dev": true, "requires": {} }, @@ -15842,9 +15974,9 @@ } }, "html-entities": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.3.2.tgz", - "integrity": "sha512-c3Ab/url5ksaT0WyleslpBEthOzWhrjQbg75y7XUsfSzi3Dgzt0l8w5e7DylRn15MTlMMD58dTfzddNS2kcAjQ==", + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.3.3.tgz", + "integrity": "sha512-DV5Ln36z34NNTDgnz0EWGBLZENelNAtkiFA4kyNOG2tDI6Mz1uSWiq1wAKdyjnJwyDiDO7Fa2SO1CTxPXL8VxA==", "dev": true }, "html-escaper": { @@ -17190,9 +17322,9 @@ "optional": true }, "node-forge": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.0.tgz", - "integrity": "sha512-08ARB91bUi6zNKzVmaj3QO7cr397uiDT2nJ63cHjyNtCTWIgvS47j3eT0WfzUwS9+6Z5YshRaoasFkXCKrIYbA==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", + "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", "dev": true }, "node-gyp": { @@ -17855,12 +17987,12 @@ } }, "mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", "dev": true, "requires": { - "minimist": "^1.2.5" + "minimist": "^1.2.6" } } } @@ -18581,9 +18713,9 @@ "dev": true }, "sass": { - "version": "1.49.0", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.49.0.tgz", - "integrity": "sha512-TVwVdNDj6p6b4QymJtNtRS2YtLJ/CqZriGg0eIAbAKMlN8Xy6kbv33FsEZSF7FufFFM705SQviHjjThfaQ4VNw==", + "version": "1.49.9", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.49.9.tgz", + "integrity": "sha512-YlYWkkHP9fbwaFRZQRXgDi3mXZShslVmmo+FVK3kHLUELHHEYrCmL1x6IUjC7wLS6VuJSAFXRQS/DxdsC4xL1A==", "dev": true, "requires": { "chokidar": ">=3.0.0 <4.0.0", @@ -18652,12 +18784,12 @@ "dev": true }, "selfsigned": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.0.0.tgz", - "integrity": "sha512-cUdFiCbKoa1mZ6osuJs2uDHrs0k0oprsKveFiiaBKCNq3SYyb5gs2HxhQyDNLCmL51ZZThqi4YNDpCK6GOP1iQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.0.1.tgz", + "integrity": "sha512-LmME957M1zOsUhG+67rAjKfiWFox3SBxE/yymatMZsAx+oMrJ0YQ8AToOnyCm7xbeg2ep37IHLxdu0o2MavQOQ==", "dev": true, "requires": { - "node-forge": "^1.2.0" + "node-forge": "^1" } }, "semver": { diff --git a/frontend/package.json b/frontend/package.json index 5e381a2..d29ca7e 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -26,7 +26,7 @@ "zone.js": "~0.11.4" }, "devDependencies": { - "@angular-devkit/build-angular": "~13.3.0", + "@angular-devkit/build-angular": "~13.3.1", "@angular/cli": "~13.3.0", "@angular/compiler-cli": "~13.2.0", "@types/jasmine": "~4.0.0", From 2900d953e5a83a1a382949ce039594e457c4fd93 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 31 Mar 2022 10:32:06 +0000 Subject: [PATCH 09/33] Update flask requirement from ~=2.1.0 to ~=2.1.1 in /api Updates the requirements on [flask](https://github.com/pallets/flask) to permit the latest version. - [Release notes](https://github.com/pallets/flask/releases) - [Changelog](https://github.com/pallets/flask/blob/main/CHANGES.rst) - [Commits](https://github.com/pallets/flask/compare/2.1.0...2.1.1) --- updated-dependencies: - dependency-name: flask dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- api/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/requirements.txt b/api/requirements.txt index a73b603..2803098 100644 --- a/api/requirements.txt +++ b/api/requirements.txt @@ -1,4 +1,4 @@ -Flask~=2.1.0 +Flask~=2.1.1 python-dotenv==0.20.0 uwsgi==2.0.20 Flask_SQLAlchemy==2.5.1 From 708518ee5c6501986e8d50c4d79da1feab0ea733 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 31 Mar 2022 10:32:07 +0000 Subject: [PATCH 10/33] Bump @angular/cli from 13.3.0 to 13.3.1 in /frontend Bumps [@angular/cli](https://github.com/angular/angular-cli) from 13.3.0 to 13.3.1. - [Release notes](https://github.com/angular/angular-cli/releases) - [Changelog](https://github.com/angular/angular-cli/blob/master/CHANGELOG.md) - [Commits](https://github.com/angular/angular-cli/compare/13.3.0...13.3.1) --- updated-dependencies: - dependency-name: "@angular/cli" dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- frontend/package-lock.json | 284 ++++++++++++++++++++++++++++++++----- frontend/package.json | 2 +- 2 files changed, 252 insertions(+), 34 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 0a65e59..b8b1c74 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -25,7 +25,7 @@ }, "devDependencies": { "@angular-devkit/build-angular": "~13.3.0", - "@angular/cli": "~13.3.0", + "@angular/cli": "~13.3.1", "@angular/compiler-cli": "~13.2.0", "@types/jasmine": "~4.0.0", "@types/node": "^17.0.23", @@ -295,12 +295,12 @@ "dev": true }, "node_modules/@angular-devkit/schematics": { - "version": "13.3.0", - "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-13.3.0.tgz", - "integrity": "sha512-hq7tqnB3uVT/iDgqWWZ4kvnijeAcgd4cfLzZiCPaYn1nuhZf0tWsho6exhJ/odMZHvVp7w8OibqWiUKxNY9zHA==", + "version": "13.3.1", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-13.3.1.tgz", + "integrity": "sha512-DxXMjlq/sALcHuONZRMTBX5k30XPfN4b6Ue4k7Xl8JKZqyHhEzfXaZzgD9u2cwb7wybKEeF/BZ5eJd8JG525og==", "dev": true, "dependencies": { - "@angular-devkit/core": "13.3.0", + "@angular-devkit/core": "13.3.1", "jsonc-parser": "3.0.0", "magic-string": "0.25.7", "ora": "5.4.1", @@ -312,6 +312,33 @@ "yarn": ">= 1.13.0" } }, + "node_modules/@angular-devkit/schematics/node_modules/@angular-devkit/core": { + "version": "13.3.1", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-13.3.1.tgz", + "integrity": "sha512-eXAcQaP1mn6rnQb+5bv5NsamY6b34UYM7G+S154Hnma6CTTSGBtcmoNAJs8cekuFqWlw7YgpB/e15jR5OLPkDA==", + "dev": true, + "dependencies": { + "ajv": "8.9.0", + "ajv-formats": "2.1.1", + "fast-json-stable-stringify": "2.1.0", + "magic-string": "0.25.7", + "rxjs": "6.6.7", + "source-map": "0.7.3" + }, + "engines": { + "node": "^12.20.0 || ^14.15.0 || >=16.10.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "peerDependencies": { + "chokidar": "^3.5.2" + }, + "peerDependenciesMeta": { + "chokidar": { + "optional": true + } + } + }, "node_modules/@angular-devkit/schematics/node_modules/rxjs": { "version": "6.6.7", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", @@ -367,16 +394,16 @@ "optional": true }, "node_modules/@angular/cli": { - "version": "13.3.0", - "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-13.3.0.tgz", - "integrity": "sha512-2qCKP/QsyxrJnpd3g4P/iTQ4TjI04N8r+bG5YLLfudoMDsQ/Ti4ogdI7PBeG2IMbRylZW9XLjHraWG42+Y9tWw==", + "version": "13.3.1", + "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-13.3.1.tgz", + "integrity": "sha512-0uwU8v3V/2s95X4cZT582J6upReT/ZNw/VAf4p4q51JN+BBvdCEb251xTF+TcOojyToFyJYvg8T28XSrsNsmTQ==", "dev": true, "hasInstallScript": true, "dependencies": { - "@angular-devkit/architect": "0.1303.0", - "@angular-devkit/core": "13.3.0", - "@angular-devkit/schematics": "13.3.0", - "@schematics/angular": "13.3.0", + "@angular-devkit/architect": "0.1303.1", + "@angular-devkit/core": "13.3.1", + "@angular-devkit/schematics": "13.3.1", + "@schematics/angular": "13.3.1", "@yarnpkg/lockfile": "1.1.0", "ansi-colors": "4.1.1", "debug": "4.3.3", @@ -402,6 +429,66 @@ "yarn": ">= 1.13.0" } }, + "node_modules/@angular/cli/node_modules/@angular-devkit/architect": { + "version": "0.1303.1", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1303.1.tgz", + "integrity": "sha512-ppaLzNZPrqrI96ddgm1RuEALVpWZsmHbIPLDd0GBwhF6aOkwF0LpZHd5XyS4ktGFZPReiFIjWSVtqV5vaBdRsw==", + "dev": true, + "dependencies": { + "@angular-devkit/core": "13.3.1", + "rxjs": "6.6.7" + }, + "engines": { + "node": "^12.20.0 || ^14.15.0 || >=16.10.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + } + }, + "node_modules/@angular/cli/node_modules/@angular-devkit/core": { + "version": "13.3.1", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-13.3.1.tgz", + "integrity": "sha512-eXAcQaP1mn6rnQb+5bv5NsamY6b34UYM7G+S154Hnma6CTTSGBtcmoNAJs8cekuFqWlw7YgpB/e15jR5OLPkDA==", + "dev": true, + "dependencies": { + "ajv": "8.9.0", + "ajv-formats": "2.1.1", + "fast-json-stable-stringify": "2.1.0", + "magic-string": "0.25.7", + "rxjs": "6.6.7", + "source-map": "0.7.3" + }, + "engines": { + "node": "^12.20.0 || ^14.15.0 || >=16.10.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "peerDependencies": { + "chokidar": "^3.5.2" + }, + "peerDependenciesMeta": { + "chokidar": { + "optional": true + } + } + }, + "node_modules/@angular/cli/node_modules/rxjs": { + "version": "6.6.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", + "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", + "dev": true, + "dependencies": { + "tslib": "^1.9.0" + }, + "engines": { + "npm": ">=2.0.0" + } + }, + "node_modules/@angular/cli/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, "node_modules/@angular/common": { "version": "13.2.6", "resolved": "https://registry.npmjs.org/@angular/common/-/common-13.2.6.tgz", @@ -2564,13 +2651,13 @@ } }, "node_modules/@schematics/angular": { - "version": "13.3.0", - "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-13.3.0.tgz", - "integrity": "sha512-WND6DXWf0ZFefqlC2hUm1FzHDonRfGpDEPWVhVulhYkB7IUUaXuCz8K41HAScyJ3bxUngs2Lx9+4omikc05fxA==", + "version": "13.3.1", + "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-13.3.1.tgz", + "integrity": "sha512-+lrK/d1eJsAK6d6E9TDeg3Vc71bDy1KsE8M+lEINdX9Wax22mAz4pw20X9RSCw5RHgn+XcNUuMsgRJAwVhDNpg==", "dev": true, "dependencies": { - "@angular-devkit/core": "13.3.0", - "@angular-devkit/schematics": "13.3.0", + "@angular-devkit/core": "13.3.1", + "@angular-devkit/schematics": "13.3.1", "jsonc-parser": "3.0.0" }, "engines": { @@ -2579,6 +2666,51 @@ "yarn": ">= 1.13.0" } }, + "node_modules/@schematics/angular/node_modules/@angular-devkit/core": { + "version": "13.3.1", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-13.3.1.tgz", + "integrity": "sha512-eXAcQaP1mn6rnQb+5bv5NsamY6b34UYM7G+S154Hnma6CTTSGBtcmoNAJs8cekuFqWlw7YgpB/e15jR5OLPkDA==", + "dev": true, + "dependencies": { + "ajv": "8.9.0", + "ajv-formats": "2.1.1", + "fast-json-stable-stringify": "2.1.0", + "magic-string": "0.25.7", + "rxjs": "6.6.7", + "source-map": "0.7.3" + }, + "engines": { + "node": "^12.20.0 || ^14.15.0 || >=16.10.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "peerDependencies": { + "chokidar": "^3.5.2" + }, + "peerDependenciesMeta": { + "chokidar": { + "optional": true + } + } + }, + "node_modules/@schematics/angular/node_modules/rxjs": { + "version": "6.6.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", + "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", + "dev": true, + "dependencies": { + "tslib": "^1.9.0" + }, + "engines": { + "npm": ">=2.0.0" + } + }, + "node_modules/@schematics/angular/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, "node_modules/@socket.io/base64-arraybuffer": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/@socket.io/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz", @@ -11609,18 +11741,32 @@ } }, "@angular-devkit/schematics": { - "version": "13.3.0", - "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-13.3.0.tgz", - "integrity": "sha512-hq7tqnB3uVT/iDgqWWZ4kvnijeAcgd4cfLzZiCPaYn1nuhZf0tWsho6exhJ/odMZHvVp7w8OibqWiUKxNY9zHA==", + "version": "13.3.1", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-13.3.1.tgz", + "integrity": "sha512-DxXMjlq/sALcHuONZRMTBX5k30XPfN4b6Ue4k7Xl8JKZqyHhEzfXaZzgD9u2cwb7wybKEeF/BZ5eJd8JG525og==", "dev": true, "requires": { - "@angular-devkit/core": "13.3.0", + "@angular-devkit/core": "13.3.1", "jsonc-parser": "3.0.0", "magic-string": "0.25.7", "ora": "5.4.1", "rxjs": "6.6.7" }, "dependencies": { + "@angular-devkit/core": { + "version": "13.3.1", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-13.3.1.tgz", + "integrity": "sha512-eXAcQaP1mn6rnQb+5bv5NsamY6b34UYM7G+S154Hnma6CTTSGBtcmoNAJs8cekuFqWlw7YgpB/e15jR5OLPkDA==", + "dev": true, + "requires": { + "ajv": "8.9.0", + "ajv-formats": "2.1.1", + "fast-json-stable-stringify": "2.1.0", + "magic-string": "0.25.7", + "rxjs": "6.6.7", + "source-map": "0.7.3" + } + }, "rxjs": { "version": "6.6.7", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", @@ -11664,15 +11810,15 @@ } }, "@angular/cli": { - "version": "13.3.0", - "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-13.3.0.tgz", - "integrity": "sha512-2qCKP/QsyxrJnpd3g4P/iTQ4TjI04N8r+bG5YLLfudoMDsQ/Ti4ogdI7PBeG2IMbRylZW9XLjHraWG42+Y9tWw==", + "version": "13.3.1", + "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-13.3.1.tgz", + "integrity": "sha512-0uwU8v3V/2s95X4cZT582J6upReT/ZNw/VAf4p4q51JN+BBvdCEb251xTF+TcOojyToFyJYvg8T28XSrsNsmTQ==", "dev": true, "requires": { - "@angular-devkit/architect": "0.1303.0", - "@angular-devkit/core": "13.3.0", - "@angular-devkit/schematics": "13.3.0", - "@schematics/angular": "13.3.0", + "@angular-devkit/architect": "0.1303.1", + "@angular-devkit/core": "13.3.1", + "@angular-devkit/schematics": "13.3.1", + "@schematics/angular": "13.3.1", "@yarnpkg/lockfile": "1.1.0", "ansi-colors": "4.1.1", "debug": "4.3.3", @@ -11688,6 +11834,47 @@ "semver": "7.3.5", "symbol-observable": "4.0.0", "uuid": "8.3.2" + }, + "dependencies": { + "@angular-devkit/architect": { + "version": "0.1303.1", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1303.1.tgz", + "integrity": "sha512-ppaLzNZPrqrI96ddgm1RuEALVpWZsmHbIPLDd0GBwhF6aOkwF0LpZHd5XyS4ktGFZPReiFIjWSVtqV5vaBdRsw==", + "dev": true, + "requires": { + "@angular-devkit/core": "13.3.1", + "rxjs": "6.6.7" + } + }, + "@angular-devkit/core": { + "version": "13.3.1", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-13.3.1.tgz", + "integrity": "sha512-eXAcQaP1mn6rnQb+5bv5NsamY6b34UYM7G+S154Hnma6CTTSGBtcmoNAJs8cekuFqWlw7YgpB/e15jR5OLPkDA==", + "dev": true, + "requires": { + "ajv": "8.9.0", + "ajv-formats": "2.1.1", + "fast-json-stable-stringify": "2.1.0", + "magic-string": "0.25.7", + "rxjs": "6.6.7", + "source-map": "0.7.3" + } + }, + "rxjs": { + "version": "6.6.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", + "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + }, + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + } } }, "@angular/common": { @@ -13206,14 +13393,45 @@ "peer": true }, "@schematics/angular": { - "version": "13.3.0", - "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-13.3.0.tgz", - "integrity": "sha512-WND6DXWf0ZFefqlC2hUm1FzHDonRfGpDEPWVhVulhYkB7IUUaXuCz8K41HAScyJ3bxUngs2Lx9+4omikc05fxA==", + "version": "13.3.1", + "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-13.3.1.tgz", + "integrity": "sha512-+lrK/d1eJsAK6d6E9TDeg3Vc71bDy1KsE8M+lEINdX9Wax22mAz4pw20X9RSCw5RHgn+XcNUuMsgRJAwVhDNpg==", "dev": true, "requires": { - "@angular-devkit/core": "13.3.0", - "@angular-devkit/schematics": "13.3.0", + "@angular-devkit/core": "13.3.1", + "@angular-devkit/schematics": "13.3.1", "jsonc-parser": "3.0.0" + }, + "dependencies": { + "@angular-devkit/core": { + "version": "13.3.1", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-13.3.1.tgz", + "integrity": "sha512-eXAcQaP1mn6rnQb+5bv5NsamY6b34UYM7G+S154Hnma6CTTSGBtcmoNAJs8cekuFqWlw7YgpB/e15jR5OLPkDA==", + "dev": true, + "requires": { + "ajv": "8.9.0", + "ajv-formats": "2.1.1", + "fast-json-stable-stringify": "2.1.0", + "magic-string": "0.25.7", + "rxjs": "6.6.7", + "source-map": "0.7.3" + } + }, + "rxjs": { + "version": "6.6.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", + "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + }, + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + } } }, "@socket.io/base64-arraybuffer": { diff --git a/frontend/package.json b/frontend/package.json index 5e381a2..568c54a 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -27,7 +27,7 @@ }, "devDependencies": { "@angular-devkit/build-angular": "~13.3.0", - "@angular/cli": "~13.3.0", + "@angular/cli": "~13.3.1", "@angular/compiler-cli": "~13.2.0", "@types/jasmine": "~4.0.0", "@types/node": "^17.0.23", From 3b8e970ad98666fd4a084578a150eb729416d61c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 31 Mar 2022 10:32:53 +0000 Subject: [PATCH 11/33] Bump @angular/material from 13.3.1 to 13.3.2 in /frontend Bumps [@angular/material](https://github.com/angular/components) from 13.3.1 to 13.3.2. - [Release notes](https://github.com/angular/components/releases) - [Changelog](https://github.com/angular/components/blob/master/CHANGELOG.md) - [Commits](https://github.com/angular/components/compare/13.3.1...13.3.2) --- updated-dependencies: - dependency-name: "@angular/material" dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- frontend/package-lock.json | 28 ++++++++++++++-------------- frontend/package.json | 2 +- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 0a65e59..1555d29 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -14,7 +14,7 @@ "@angular/compiler": "~13.2.0", "@angular/core": "~13.2.0", "@angular/forms": "~13.2.0", - "@angular/material": "^13.3.1", + "@angular/material": "^13.3.2", "@angular/platform-browser": "~13.2.0", "@angular/platform-browser-dynamic": "~13.2.0", "@angular/router": "~13.2.0", @@ -345,9 +345,9 @@ } }, "node_modules/@angular/cdk": { - "version": "13.3.1", - "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-13.3.1.tgz", - "integrity": "sha512-JUCvhN6XomY5JOMH7Qyj/JodAuAtKgLlJYKs0tPmjCkGcBdR/T3/hOJ9gSRhz8PCOoiEORj+3xCh547oJRyesA==", + "version": "13.3.2", + "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-13.3.2.tgz", + "integrity": "sha512-Rb+SvQpjXqa0kedk/6nG57f8dC4uG1q35SR6mt6jCz84ikyS2zhikVbzaxLdG15uDvq1+N5Vx3NTSgH19XUSug==", "dependencies": { "tslib": "^2.3.0" }, @@ -577,15 +577,15 @@ } }, "node_modules/@angular/material": { - "version": "13.3.1", - "resolved": "https://registry.npmjs.org/@angular/material/-/material-13.3.1.tgz", - "integrity": "sha512-c9BYNxvZvxuCA5QticqfEhn3nuspUnwxIVejjO5ZTqKlkK+vQGKN3QhshHLrMHeFYyjao/E8jVnJX7ahitPM9w==", + "version": "13.3.2", + "resolved": "https://registry.npmjs.org/@angular/material/-/material-13.3.2.tgz", + "integrity": "sha512-agIuNcN1wO05ODEXc0wwrMK2ydnhPGO8tcBBCJXrDgiA/ktwm+WtfOsiZwPoev7aJ8pactVhxT5R0bJ6vW2SmA==", "dependencies": { "tslib": "^2.3.0" }, "peerDependencies": { "@angular/animations": "^13.0.0 || ^14.0.0-0", - "@angular/cdk": "13.3.1", + "@angular/cdk": "13.3.2", "@angular/common": "^13.0.0 || ^14.0.0-0", "@angular/core": "^13.0.0 || ^14.0.0-0", "@angular/forms": "^13.0.0 || ^14.0.0-0", @@ -11647,9 +11647,9 @@ } }, "@angular/cdk": { - "version": "13.3.1", - "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-13.3.1.tgz", - "integrity": "sha512-JUCvhN6XomY5JOMH7Qyj/JodAuAtKgLlJYKs0tPmjCkGcBdR/T3/hOJ9gSRhz8PCOoiEORj+3xCh547oJRyesA==", + "version": "13.3.2", + "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-13.3.2.tgz", + "integrity": "sha512-Rb+SvQpjXqa0kedk/6nG57f8dC4uG1q35SR6mt6jCz84ikyS2zhikVbzaxLdG15uDvq1+N5Vx3NTSgH19XUSug==", "requires": { "parse5": "^5.0.0", "tslib": "^2.3.0" @@ -11809,9 +11809,9 @@ } }, "@angular/material": { - "version": "13.3.1", - "resolved": "https://registry.npmjs.org/@angular/material/-/material-13.3.1.tgz", - "integrity": "sha512-c9BYNxvZvxuCA5QticqfEhn3nuspUnwxIVejjO5ZTqKlkK+vQGKN3QhshHLrMHeFYyjao/E8jVnJX7ahitPM9w==", + "version": "13.3.2", + "resolved": "https://registry.npmjs.org/@angular/material/-/material-13.3.2.tgz", + "integrity": "sha512-agIuNcN1wO05ODEXc0wwrMK2ydnhPGO8tcBBCJXrDgiA/ktwm+WtfOsiZwPoev7aJ8pactVhxT5R0bJ6vW2SmA==", "requires": { "tslib": "^2.3.0" } diff --git a/frontend/package.json b/frontend/package.json index 5e381a2..d97c6f2 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -16,7 +16,7 @@ "@angular/compiler": "~13.2.0", "@angular/core": "~13.2.0", "@angular/forms": "~13.2.0", - "@angular/material": "^13.3.1", + "@angular/material": "^13.3.2", "@angular/platform-browser": "~13.2.0", "@angular/platform-browser-dynamic": "~13.2.0", "@angular/router": "~13.2.0", From 0190b8d6332a26ba4ba112221a88aeaafda3f33d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 31 Mar 2022 16:11:01 +0000 Subject: [PATCH 12/33] Bump @types/jasmine from 4.0.0 to 4.0.1 in /frontend Bumps [@types/jasmine](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/jasmine) from 4.0.0 to 4.0.1. - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/jasmine) --- updated-dependencies: - dependency-name: "@types/jasmine" dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- frontend/package-lock.json | 14 +++++++------- frontend/package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 7d15060..f0ea7a0 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -27,7 +27,7 @@ "@angular-devkit/build-angular": "~13.3.0", "@angular/cli": "~13.3.1", "@angular/compiler-cli": "~13.2.0", - "@types/jasmine": "~4.0.0", + "@types/jasmine": "~4.0.1", "@types/node": "^17.0.23", "jasmine-core": "~4.0.0", "karma": "~6.3.0", @@ -2844,9 +2844,9 @@ } }, "node_modules/@types/jasmine": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-4.0.0.tgz", - "integrity": "sha512-KvhqNz4NaONk7cfp4E9x+uXOUp7x4H2Zeyb4yXnw2vIuxD5YfSi1767x+aF7z54elhZcC0OH9/78/WL6+5jcDg==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-4.0.1.tgz", + "integrity": "sha512-6KtN9P42PfhUSxybWY0iG6wpMJEoMMXyd9qi06EiOf5p6fOwAj9t/BwBEkx0Ys47oxAbMKv9sKqTV9igHYeUsQ==", "dev": true }, "node_modules/@types/json-schema": { @@ -13561,9 +13561,9 @@ } }, "@types/jasmine": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-4.0.0.tgz", - "integrity": "sha512-KvhqNz4NaONk7cfp4E9x+uXOUp7x4H2Zeyb4yXnw2vIuxD5YfSi1767x+aF7z54elhZcC0OH9/78/WL6+5jcDg==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-4.0.1.tgz", + "integrity": "sha512-6KtN9P42PfhUSxybWY0iG6wpMJEoMMXyd9qi06EiOf5p6fOwAj9t/BwBEkx0Ys47oxAbMKv9sKqTV9igHYeUsQ==", "dev": true }, "@types/json-schema": { diff --git a/frontend/package.json b/frontend/package.json index f0648ce..a393af2 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -29,7 +29,7 @@ "@angular-devkit/build-angular": "~13.3.0", "@angular/cli": "~13.3.1", "@angular/compiler-cli": "~13.2.0", - "@types/jasmine": "~4.0.0", + "@types/jasmine": "~4.0.1", "@types/node": "^17.0.23", "jasmine-core": "~4.0.0", "karma": "~6.3.0", From 61d88c101bbc3b1872939a340204e2276879fa3f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 1 Apr 2022 09:30:00 +0000 Subject: [PATCH 13/33] Bump @types/jasmine from 4.0.1 to 4.0.2 in /frontend Bumps [@types/jasmine](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/jasmine) from 4.0.1 to 4.0.2. - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/jasmine) --- updated-dependencies: - dependency-name: "@types/jasmine" dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- frontend/package-lock.json | 150 ++----------------------------------- frontend/package.json | 2 +- 2 files changed, 8 insertions(+), 144 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index db6af1c..2cc45ca 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -27,7 +27,7 @@ "@angular-devkit/build-angular": "~13.3.1", "@angular/cli": "~13.3.1", "@angular/compiler-cli": "~13.2.0", - "@types/jasmine": "~4.0.1", + "@types/jasmine": "~4.0.2", "@types/node": "^17.0.23", "jasmine-core": "~4.0.0", "karma": "~6.3.0", @@ -51,39 +51,6 @@ "node": ">=6.0.0" } }, - "node_modules/@angular-devkit/architect": { - "version": "0.1303.0", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1303.0.tgz", - "integrity": "sha512-kTcKB917ICA8j53SGo4gn+qAlzx8si+iHnOTbp5QlMr7qt/Iz07SVVI8mRlMD6c6lr7eE/fVlCLzEZ1+WCQpTA==", - "dev": true, - "dependencies": { - "@angular-devkit/core": "13.3.0", - "rxjs": "6.6.7" - }, - "engines": { - "node": "^12.20.0 || ^14.15.0 || >=16.10.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" - } - }, - "node_modules/@angular-devkit/architect/node_modules/rxjs": { - "version": "6.6.7", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", - "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", - "dev": true, - "dependencies": { - "tslib": "^1.9.0" - }, - "engines": { - "npm": ">=2.0.0" - } - }, - "node_modules/@angular-devkit/architect/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - }, "node_modules/@angular-devkit/build-angular": { "version": "13.3.1", "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-13.3.1.tgz", @@ -333,51 +300,6 @@ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", "dev": true }, - "node_modules/@angular-devkit/core": { - "version": "13.3.0", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-13.3.0.tgz", - "integrity": "sha512-8YrreVbWlJVZnk5zs4vfkRItrPEtWhUcxWOBfYT/Kwu4FwJVAnNuhJAxxXOAQ2Ckd7cv30Idh/RFVLbTZ5Gs9w==", - "dev": true, - "dependencies": { - "ajv": "8.9.0", - "ajv-formats": "2.1.1", - "fast-json-stable-stringify": "2.1.0", - "magic-string": "0.25.7", - "rxjs": "6.6.7", - "source-map": "0.7.3" - }, - "engines": { - "node": "^12.20.0 || ^14.15.0 || >=16.10.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" - }, - "peerDependencies": { - "chokidar": "^3.5.2" - }, - "peerDependenciesMeta": { - "chokidar": { - "optional": true - } - } - }, - "node_modules/@angular-devkit/core/node_modules/rxjs": { - "version": "6.6.7", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", - "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", - "dev": true, - "dependencies": { - "tslib": "^1.9.0" - }, - "engines": { - "npm": ">=2.0.0" - } - }, - "node_modules/@angular-devkit/core/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - }, "node_modules/@angular-devkit/schematics": { "version": "13.3.1", "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-13.3.1.tgz", @@ -2928,9 +2850,9 @@ } }, "node_modules/@types/jasmine": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-4.0.1.tgz", - "integrity": "sha512-6KtN9P42PfhUSxybWY0iG6wpMJEoMMXyd9qi06EiOf5p6fOwAj9t/BwBEkx0Ys47oxAbMKv9sKqTV9igHYeUsQ==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-4.0.2.tgz", + "integrity": "sha512-mSPIWhDyQ4nzYdR6Ixy15VhVKMVw93mSUlQxxpVb4S9Hj90lBvg+7kkBw23uYcv8CESPPXit+u3cARYcPeC8Jg==", "dev": true }, "node_modules/@types/json-schema": { @@ -11647,33 +11569,6 @@ "sourcemap-codec": "1.4.8" } }, - "@angular-devkit/architect": { - "version": "0.1303.0", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1303.0.tgz", - "integrity": "sha512-kTcKB917ICA8j53SGo4gn+qAlzx8si+iHnOTbp5QlMr7qt/Iz07SVVI8mRlMD6c6lr7eE/fVlCLzEZ1+WCQpTA==", - "dev": true, - "requires": { - "@angular-devkit/core": "13.3.0", - "rxjs": "6.6.7" - }, - "dependencies": { - "rxjs": { - "version": "6.6.7", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", - "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", - "dev": true, - "requires": { - "tslib": "^1.9.0" - } - }, - "tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - } - } - }, "@angular-devkit/build-angular": { "version": "13.3.1", "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-13.3.1.tgz", @@ -11841,37 +11736,6 @@ } } }, - "@angular-devkit/core": { - "version": "13.3.0", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-13.3.0.tgz", - "integrity": "sha512-8YrreVbWlJVZnk5zs4vfkRItrPEtWhUcxWOBfYT/Kwu4FwJVAnNuhJAxxXOAQ2Ckd7cv30Idh/RFVLbTZ5Gs9w==", - "dev": true, - "requires": { - "ajv": "8.9.0", - "ajv-formats": "2.1.1", - "fast-json-stable-stringify": "2.1.0", - "magic-string": "0.25.7", - "rxjs": "6.6.7", - "source-map": "0.7.3" - }, - "dependencies": { - "rxjs": { - "version": "6.6.7", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", - "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", - "dev": true, - "requires": { - "tslib": "^1.9.0" - } - }, - "tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - } - } - }, "@angular-devkit/schematics": { "version": "13.3.1", "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-13.3.1.tgz", @@ -13693,9 +13557,9 @@ } }, "@types/jasmine": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-4.0.1.tgz", - "integrity": "sha512-6KtN9P42PfhUSxybWY0iG6wpMJEoMMXyd9qi06EiOf5p6fOwAj9t/BwBEkx0Ys47oxAbMKv9sKqTV9igHYeUsQ==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-4.0.2.tgz", + "integrity": "sha512-mSPIWhDyQ4nzYdR6Ixy15VhVKMVw93mSUlQxxpVb4S9Hj90lBvg+7kkBw23uYcv8CESPPXit+u3cARYcPeC8Jg==", "dev": true }, "@types/json-schema": { diff --git a/frontend/package.json b/frontend/package.json index 22187fd..d31fa67 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -29,7 +29,7 @@ "@angular-devkit/build-angular": "~13.3.1", "@angular/cli": "~13.3.1", "@angular/compiler-cli": "~13.2.0", - "@types/jasmine": "~4.0.1", + "@types/jasmine": "~4.0.2", "@types/node": "^17.0.23", "jasmine-core": "~4.0.0", "karma": "~6.3.0", From 4379fac8d2b5c8dfe899daadd9f42662c9c6c18c Mon Sep 17 00:00:00 2001 From: Florian Kaiser <44125287+H4CK3R-01@users.noreply.github.com> Date: Mon, 4 Apr 2022 13:30:04 +0200 Subject: [PATCH 14/33] Update .env.example --- api/.env.example | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/api/.env.example b/api/.env.example index af67b58..25dc0bf 100644 --- a/api/.env.example +++ b/api/.env.example @@ -7,3 +7,12 @@ MYSQL_PASSWORD= # Flask secret key SECRET_KEY= + +# Users +BOT_EMAIL= +BOT_USERNAME= +BOT_PASSWORD= + +ADMIN_EMAIL= +ADMIN_USERNAME= +ADMIN_PASSWORD= From 2aea0fe9799e0e33c5f3fbd37bea2d32d5dcb313 Mon Sep 17 00:00:00 2001 From: H4CK3R-01 Date: Mon, 4 Apr 2022 14:10:25 +0200 Subject: [PATCH 15/33] Try to fix database problems --- api/app/config/flask.cfg | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/api/app/config/flask.cfg b/api/app/config/flask.cfg index 1a6347c..25babd4 100644 --- a/api/app/config/flask.cfg +++ b/api/app/config/flask.cfg @@ -9,8 +9,9 @@ SECRET_KEY = os.getenv('SECRET_KEY', 'secret string') SQLALCHEMY_DATABASE_URI = "mysql+pymysql://" + os.getenv('MYSQL_USER') + ":" + os.getenv('MYSQL_PASSWORD') + "@" + os.getenv('MYSQL_HOST') + ":" + os.getenv('MYSQL_PORT', '3306') + "/" + os.getenv('MYSQL_NAME', 'aktienbot') SQLALCHEMY_TRACK_MODIFICATIONS = False # Avoids SQLAlchemy warning SQLALCHEMY_ENGINE_OPTIONS = { - 'pool_size': 100, - 'pool_recycle': 240 # 4 minutes + 'pool_size': 10, + 'pool_recycle': 60, + 'pool_pre_ping': True } # openapi/Swagger config From 1624989f77d7c7e80c6328b755438d327654bfab Mon Sep 17 00:00:00 2001 From: H4CK3R-01 Date: Mon, 4 Apr 2022 14:16:56 +0200 Subject: [PATCH 16/33] Try to fix pipeline --- .woodpecker/pipeline.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.woodpecker/pipeline.yml b/.woodpecker/pipeline.yml index 34a73c9..5825f6e 100644 --- a/.woodpecker/pipeline.yml +++ b/.woodpecker/pipeline.yml @@ -17,6 +17,10 @@ pipeline: test_api: image: python commands: + - export MYSQL_USER=aktienbot + - export MYSQL_PASSWORD=12345678 + - export MYSQL_HOST=mariadb + - export MYSQL_PORT=3306 - cd api/ - pip install -r requirements.txt - python -m pytest From 14b5929c8d3cbd1123fb435ca9d863b725b28692 Mon Sep 17 00:00:00 2001 From: H4CK3R-01 Date: Mon, 4 Apr 2022 14:17:51 +0200 Subject: [PATCH 17/33] Test pipeline --- api/app/db.py | 1 + 1 file changed, 1 insertion(+) diff --git a/api/app/db.py b/api/app/db.py index 890af1d..d09ee30 100644 --- a/api/app/db.py +++ b/api/app/db.py @@ -1,3 +1,4 @@ from flask_sqlalchemy import SQLAlchemy +# database object database = SQLAlchemy() From f7ece8e439ba65f3490ad3206f760381f20f7597 Mon Sep 17 00:00:00 2001 From: H4CK3R-01 Date: Mon, 4 Apr 2022 14:18:57 +0200 Subject: [PATCH 18/33] Test pipeline --- api/app.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/api/app.py b/api/app.py index 0424fca..0261ad1 100644 --- a/api/app.py +++ b/api/app.py @@ -1,6 +1,4 @@ from app import create_app -# Call the application factory function to construct a Flask application -# instance using the development configuration application = create_app('config/flask.cfg') application.run() From 884c3f98ccb73bd43b173903da3aac8066fd02b0 Mon Sep 17 00:00:00 2001 From: H4CK3R-01 Date: Mon, 4 Apr 2022 15:07:29 +0200 Subject: [PATCH 19/33] Detach api tests --- .woodpecker/pipeline.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.woodpecker/pipeline.yml b/.woodpecker/pipeline.yml index 5825f6e..c8ebd9a 100644 --- a/.woodpecker/pipeline.yml +++ b/.woodpecker/pipeline.yml @@ -16,6 +16,7 @@ pipeline: # -------------------------------------- API -------------------------------------- test_api: image: python + detach: true commands: - export MYSQL_USER=aktienbot - export MYSQL_PASSWORD=12345678 From 2eb5fe133ec8267af7d048122efbcac497ad2653 Mon Sep 17 00:00:00 2001 From: H4CK3R-01 Date: Mon, 4 Apr 2022 15:11:07 +0200 Subject: [PATCH 20/33] Test pipeline --- api/app.py | 1 + 1 file changed, 1 insertion(+) diff --git a/api/app.py b/api/app.py index 0261ad1..3314930 100644 --- a/api/app.py +++ b/api/app.py @@ -1,4 +1,5 @@ from app import create_app +# Create an application instance that web servers can use. application = create_app('config/flask.cfg') application.run() From 6fd3f05ba2e826878b0c2170ff9962a40419f594 Mon Sep 17 00:00:00 2001 From: H4CK3R-01 Date: Mon, 4 Apr 2022 15:13:27 +0200 Subject: [PATCH 21/33] Disable tests --- .woodpecker/pipeline.yml | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/.woodpecker/pipeline.yml b/.woodpecker/pipeline.yml index c8ebd9a..4e5db94 100644 --- a/.woodpecker/pipeline.yml +++ b/.woodpecker/pipeline.yml @@ -14,20 +14,20 @@ pipeline: event: pull_request # -------------------------------------- API -------------------------------------- - test_api: - image: python - detach: true - commands: - - export MYSQL_USER=aktienbot - - export MYSQL_PASSWORD=12345678 - - export MYSQL_HOST=mariadb - - export MYSQL_PORT=3306 - - cd api/ - - pip install -r requirements.txt - - python -m pytest - when: - path: "api/*" - event: push +# test_api: +# image: python +# detach: true +# commands: +# - export MYSQL_USER=aktienbot +# - export MYSQL_PASSWORD=12345678 +# - export MYSQL_HOST=mariadb +# - export MYSQL_PORT=3306 +# - cd api/ +# - pip install -r requirements.txt +# - python -m pytest +# when: +# path: "api/*" +# event: push build_api: image: woodpeckerci/plugin-docker-buildx From 51c598d506db2b522f86a78108043f06f2c8f327 Mon Sep 17 00:00:00 2001 From: H4CK3R-01 Date: Mon, 4 Apr 2022 15:35:16 +0200 Subject: [PATCH 22/33] Update pipeline --- .woodpecker/pipeline.yml | 68 +++++++--------------------------------- 1 file changed, 11 insertions(+), 57 deletions(-) diff --git a/.woodpecker/pipeline.yml b/.woodpecker/pipeline.yml index 4e5db94..8cea7ba 100644 --- a/.woodpecker/pipeline.yml +++ b/.woodpecker/pipeline.yml @@ -6,29 +6,8 @@ pipeline: when: event: push - generate_docker_tag_pr: - image: golang - commands: - - echo -n "pr-${CI_COMMIT_SHA:0:8}, pr" > .tags - when: - event: pull_request # -------------------------------------- API -------------------------------------- -# test_api: -# image: python -# detach: true -# commands: -# - export MYSQL_USER=aktienbot -# - export MYSQL_PASSWORD=12345678 -# - export MYSQL_HOST=mariadb -# - export MYSQL_PORT=3306 -# - cd api/ -# - pip install -r requirements.txt -# - python -m pytest -# when: -# path: "api/*" -# event: push - build_api: image: woodpeckerci/plugin-docker-buildx settings: @@ -46,22 +25,6 @@ pipeline: path: "api/*" event: push - deploy_api: - image: appleboy/drone-ssh - network_mode: host - settings: - host: - from_secret: ssh_host - username: - from_secret: ssh_user - password: - from_secret: ssh_password - script: - - /opt/docker/TelegramAktienBot/deploy_api.sh - when: - path: "api/*" - event: push - # -------------------------------------- Bot -------------------------------------- build_bot: @@ -79,21 +42,7 @@ pipeline: platforms: linux/amd64 when: path: "telegram_bot/*" - - deploy_bot: - image: appleboy/drone-ssh - network_mode: host - settings: - host: - from_secret: ssh_host - username: - from_secret: ssh_user - password: - from_secret: ssh_password - script: - - /opt/docker/TelegramAktienBot/deploy_bot.sh - when: - path: "telegram_bot/*" + event: push # -------------------------------------- Frontend -------------------------------------- @@ -112,8 +61,11 @@ pipeline: platforms: linux/amd64 when: path: "frontend/*" - - deploy_frontend: + event: push + + + # -------------------------------------- Deploy -------------------------------------- + deploy: image: appleboy/drone-ssh network_mode: host settings: @@ -124,8 +76,10 @@ pipeline: password: from_secret: ssh_password script: - - /opt/docker/TelegramAktienBot/deploy_frontend.sh - when: - path: "frontend/*" + - cd /root/docker/aktienbot + - docker-compose up -p "aktienbot" --force-recreate --build -d + - docker image prune -f + when: + event: push branches: main From c9fcf736ffabce2641701efec6268bceaacd189d Mon Sep 17 00:00:00 2001 From: H4CK3R-01 Date: Mon, 4 Apr 2022 15:38:50 +0200 Subject: [PATCH 23/33] Fixed pipeline --- .woodpecker/pipeline.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.woodpecker/pipeline.yml b/.woodpecker/pipeline.yml index 8cea7ba..d54a74f 100644 --- a/.woodpecker/pipeline.yml +++ b/.woodpecker/pipeline.yml @@ -77,7 +77,7 @@ pipeline: from_secret: ssh_password script: - cd /root/docker/aktienbot - - docker-compose up -p "aktienbot" --force-recreate --build -d + - docker-compose up --force-recreate --build -d - docker image prune -f when: event: push From d6b663e8c8abb0b76681ca974ae4297b6f57419e Mon Sep 17 00:00:00 2001 From: H4CK3R-01 Date: Mon, 4 Apr 2022 15:51:43 +0200 Subject: [PATCH 24/33] Added comments to dockerfiles --- api/Dockerfile | 11 ++++++++++- frontend/Dockerfile | 10 ++++++++-- telegram_bot/Dockerfile | 10 +++++++--- 3 files changed, 25 insertions(+), 6 deletions(-) diff --git a/api/Dockerfile b/api/Dockerfile index 2736a90..cfc57aa 100644 --- a/api/Dockerfile +++ b/api/Dockerfile @@ -1,19 +1,28 @@ FROM python:3.10-alpine +# Change the working directory to the project root WORKDIR /srv/flask_app + +# Install dependencies RUN apk add nginx build-base libffi-dev curl uwsgi +# Install python dependencies COPY api/requirements.txt /srv/flask_app/ - RUN pip install -r requirements.txt --src /usr/local/src --no-warn-script-location +# Copy the app COPY api /srv/flask_app COPY api/deploy/nginx.conf /etc/nginx + +# Change file permissions RUN chmod +x ./deploy/start.sh RUN chmod +x ./deploy/healthcheck.sh +# Set healthcheck HEALTHCHECK --interval=15s --timeout=2s CMD ["./deploy/healthcheck.sh"] +# Expose webserver port EXPOSE 80 +# Run the app CMD ["./deploy/start.sh"] diff --git a/frontend/Dockerfile b/frontend/Dockerfile index 637f6aa..dc4d24f 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -1,24 +1,30 @@ FROM node:latest as build +# Change to the project directory WORKDIR /usr/local/app +# Copy the project files to the container COPY frontend /usr/local/app/ +# Install dependencies RUN npm install RUN npm run build -RUN ls /usr/local/app/dist - FROM nginx:latest +# Copy the project files to the container COPY --from=build /usr/local/app/dist/aktienbot /usr/share/nginx/html +# Copy configuration files COPY frontend/deploy/nginx.conf /etc/nginx COPY frontend/deploy deploy/ +# Change file permissions RUN chmod +x ./deploy/healthcheck.sh +# set healthcheck HEALTHCHECK --interval=15s --timeout=2s CMD ["./deploy/healthcheck.sh"] +# Expose webserver port EXPOSE 80 diff --git a/telegram_bot/Dockerfile b/telegram_bot/Dockerfile index dc51065..bb48e9a 100644 --- a/telegram_bot/Dockerfile +++ b/telegram_bot/Dockerfile @@ -1,17 +1,21 @@ FROM python:3.10-slim +# Change the working directory to the root of the project WORKDIR /srv/flask_app +# Install the dependencies COPY telegram_bot/requirements.txt /srv/flask_app/ - RUN pip install -r requirements.txt --src /usr/local/src --no-warn-script-location +# Copy the source code to the working directory COPY telegram_bot /srv/flask_app + +# Change file permissions RUN chmod +x ./deploy/start.sh RUN chmod +x ./deploy/healthcheck.sh +# TODO: Set healthcheck # HEALTHCHECK --interval=15s --timeout=2s CMD ["./deploy/healthcheck.sh"] -EXPOSE 80 - +# Run the application CMD ["./deploy/start.sh"] From 01f9a52ebd87ba1a8dea1b1540ae72874107cc9d Mon Sep 17 00:00:00 2001 From: H4CK3R-01 Date: Mon, 4 Apr 2022 16:00:55 +0200 Subject: [PATCH 25/33] Update pipeline --- .woodpecker/pipeline.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.woodpecker/pipeline.yml b/.woodpecker/pipeline.yml index d54a74f..1d64667 100644 --- a/.woodpecker/pipeline.yml +++ b/.woodpecker/pipeline.yml @@ -76,9 +76,9 @@ pipeline: password: from_secret: ssh_password script: - - cd /root/docker/aktienbot - - docker-compose up --force-recreate --build -d - - docker image prune -f + - docker-clean + - docker-compose pull web + - docker-compose up -d web when: event: push From f234f8c91e573164b213d1e78ee73ea183dc87d4 Mon Sep 17 00:00:00 2001 From: H4CK3R-01 Date: Mon, 4 Apr 2022 16:02:44 +0200 Subject: [PATCH 26/33] Update pipeline --- .woodpecker/pipeline.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.woodpecker/pipeline.yml b/.woodpecker/pipeline.yml index 1d64667..05f75d6 100644 --- a/.woodpecker/pipeline.yml +++ b/.woodpecker/pipeline.yml @@ -76,9 +76,9 @@ pipeline: password: from_secret: ssh_password script: - - docker-clean - - docker-compose pull web - - docker-compose up -d web + - cd /root/docker/aktienbot + - docker-compose pull + - docker-compose up -p "aktienbot" -d when: event: push From 1f0bff5ecc134ea8335359c49a8a5faa2f45f0d7 Mon Sep 17 00:00:00 2001 From: H4CK3R-01 Date: Mon, 4 Apr 2022 16:03:48 +0200 Subject: [PATCH 27/33] Update pipeline --- .woodpecker/pipeline.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.woodpecker/pipeline.yml b/.woodpecker/pipeline.yml index 05f75d6..3ae905f 100644 --- a/.woodpecker/pipeline.yml +++ b/.woodpecker/pipeline.yml @@ -78,7 +78,7 @@ pipeline: script: - cd /root/docker/aktienbot - docker-compose pull - - docker-compose up -p "aktienbot" -d + - docker-compose -p "aktienbot" up -d when: event: push From 75ea2671d42c7e72417e222f5336e62edd7bbf63 Mon Sep 17 00:00:00 2001 From: H4CK3R-01 Date: Mon, 4 Apr 2022 16:05:55 +0200 Subject: [PATCH 28/33] Update healthcheck.sh --- api/deploy/healthcheck.sh | 1 + frontend/deploy/healthcheck.sh | 1 + telegram_bot/deploy/healthcheck.sh | 2 -- 3 files changed, 2 insertions(+), 2 deletions(-) delete mode 100644 telegram_bot/deploy/healthcheck.sh diff --git a/api/deploy/healthcheck.sh b/api/deploy/healthcheck.sh index 390f507..fffa122 100644 --- a/api/deploy/healthcheck.sh +++ b/api/deploy/healthcheck.sh @@ -1,2 +1,3 @@ #!/usr/bin/env sh + curl -s http://localhost:80/ -o /dev/null || exit 1 diff --git a/frontend/deploy/healthcheck.sh b/frontend/deploy/healthcheck.sh index 390f507..fffa122 100644 --- a/frontend/deploy/healthcheck.sh +++ b/frontend/deploy/healthcheck.sh @@ -1,2 +1,3 @@ #!/usr/bin/env sh + curl -s http://localhost:80/ -o /dev/null || exit 1 diff --git a/telegram_bot/deploy/healthcheck.sh b/telegram_bot/deploy/healthcheck.sh deleted file mode 100644 index 390f507..0000000 --- a/telegram_bot/deploy/healthcheck.sh +++ /dev/null @@ -1,2 +0,0 @@ -#!/usr/bin/env sh -curl -s http://localhost:80/ -o /dev/null || exit 1 From 7b3624e41f2604067764d2db1e15eb3e75aec2f3 Mon Sep 17 00:00:00 2001 From: H4CK3R-01 Date: Mon, 4 Apr 2022 16:07:22 +0200 Subject: [PATCH 29/33] Update pipeline --- .woodpecker/pipeline.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.woodpecker/pipeline.yml b/.woodpecker/pipeline.yml index 3ae905f..d202d96 100644 --- a/.woodpecker/pipeline.yml +++ b/.woodpecker/pipeline.yml @@ -22,7 +22,7 @@ pipeline: dockerfile: api/Dockerfile platforms: linux/amd64 when: - path: "api/*" + path: "api/**" event: push @@ -41,7 +41,7 @@ pipeline: dockerfile: telegram_bot/Dockerfile platforms: linux/amd64 when: - path: "telegram_bot/*" + path: "telegram_bot/**" event: push @@ -60,7 +60,7 @@ pipeline: dockerfile: frontend/Dockerfile platforms: linux/amd64 when: - path: "frontend/*" + path: "frontend/**" event: push From 11140aac971f6b60b7aa9a87afa208621bae60c6 Mon Sep 17 00:00:00 2001 From: H4CK3R-01 Date: Mon, 4 Apr 2022 16:08:08 +0200 Subject: [PATCH 30/33] Changed some file to test pipeline execution --- api/deploy/start.sh | 1 + frontend/deploy/nginx.conf | 1 - telegram_bot/deploy/start.sh | 1 + 3 files changed, 2 insertions(+), 1 deletion(-) diff --git a/api/deploy/start.sh b/api/deploy/start.sh index 77fb780..db51705 100644 --- a/api/deploy/start.sh +++ b/api/deploy/start.sh @@ -1,3 +1,4 @@ #!/usr/bin/env sh + nginx -g "daemon off;" & uwsgi --ini deploy/uwsgi.ini \ No newline at end of file diff --git a/frontend/deploy/nginx.conf b/frontend/deploy/nginx.conf index d8a200f..f1595bf 100644 --- a/frontend/deploy/nginx.conf +++ b/frontend/deploy/nginx.conf @@ -23,7 +23,6 @@ http { gzip_proxied any; gzip_types - ## text/html is always compressed : https://nginx.org/en/docs/http/ngx_http_gzip_module.html text/plain text/css text/javascript diff --git a/telegram_bot/deploy/start.sh b/telegram_bot/deploy/start.sh index 4ee7078..232326f 100644 --- a/telegram_bot/deploy/start.sh +++ b/telegram_bot/deploy/start.sh @@ -1,2 +1,3 @@ #!/usr/bin/env sh + python bot.py \ No newline at end of file From 00e27ba776c850edc000de37f1bb53ed21bf76eb Mon Sep 17 00:00:00 2001 From: H4CK3R-01 Date: Mon, 4 Apr 2022 16:10:37 +0200 Subject: [PATCH 31/33] Fixed Dockerfile --- frontend/Dockerfile | 1 - telegram_bot/Dockerfile | 1 - 2 files changed, 2 deletions(-) diff --git a/frontend/Dockerfile b/frontend/Dockerfile index dc4d24f..d487766 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -10,7 +10,6 @@ COPY frontend /usr/local/app/ RUN npm install RUN npm run build - FROM nginx:latest # Copy the project files to the container diff --git a/telegram_bot/Dockerfile b/telegram_bot/Dockerfile index bb48e9a..49dd01c 100644 --- a/telegram_bot/Dockerfile +++ b/telegram_bot/Dockerfile @@ -12,7 +12,6 @@ COPY telegram_bot /srv/flask_app # Change file permissions RUN chmod +x ./deploy/start.sh -RUN chmod +x ./deploy/healthcheck.sh # TODO: Set healthcheck # HEALTHCHECK --interval=15s --timeout=2s CMD ["./deploy/healthcheck.sh"] From ab28346c6f984a8a5381ac59efe811109e045dce Mon Sep 17 00:00:00 2001 From: H4CK3R-01 Date: Mon, 4 Apr 2022 16:38:08 +0200 Subject: [PATCH 32/33] Added docker-compose and config files --- deploy/README.md | 3 ++ deploy/aktienbot/.env.api | 16 ++++++++ deploy/aktienbot/.env.bot | 3 ++ deploy/aktienbot/docker-compose.yml | 62 +++++++++++++++++++++++++++++ deploy/base/acme.json | 0 deploy/base/docker-compose.yml | 38 ++++++++++++++++++ deploy/base/traefik-dynamic.toml | 22 ++++++++++ deploy/base/traefik.toml | 44 ++++++++++++++++++++ 8 files changed, 188 insertions(+) create mode 100644 deploy/README.md create mode 100644 deploy/aktienbot/.env.api create mode 100644 deploy/aktienbot/.env.bot create mode 100644 deploy/aktienbot/docker-compose.yml create mode 100644 deploy/base/acme.json create mode 100644 deploy/base/docker-compose.yml create mode 100644 deploy/base/traefik-dynamic.toml create mode 100644 deploy/base/traefik.toml diff --git a/deploy/README.md b/deploy/README.md new file mode 100644 index 0000000..e7cf9f2 --- /dev/null +++ b/deploy/README.md @@ -0,0 +1,3 @@ +# Deploy + +Files that are used for deployment. diff --git a/deploy/aktienbot/.env.api b/deploy/aktienbot/.env.api new file mode 100644 index 0000000..81a2bf6 --- /dev/null +++ b/deploy/aktienbot/.env.api @@ -0,0 +1,16 @@ +BOT_API_KEY= +SECRET_KEY= + +MYSQL_USER= +MYSQL_PASSWORD= +MYSQL_HOST= +MYSQL_PORT= +MYSQL_DATABASE= + +BOT_EMAIL= +BOT_USERNAME= +BOT_PASSWORD= + +ADMIN_EMAIL= +ADMIN_USERNAME= +ADMIN_PASSWORD= diff --git a/deploy/aktienbot/.env.bot b/deploy/aktienbot/.env.bot new file mode 100644 index 0000000..c64cd3c --- /dev/null +++ b/deploy/aktienbot/.env.bot @@ -0,0 +1,3 @@ +BOT_API_KEY= +NEWS_API_KEY= +SECRET_KEY= diff --git a/deploy/aktienbot/docker-compose.yml b/deploy/aktienbot/docker-compose.yml new file mode 100644 index 0000000..f72ab3b --- /dev/null +++ b/deploy/aktienbot/docker-compose.yml @@ -0,0 +1,62 @@ +version: '3.7' + +services: + aktienbot_fe: + image: registry.flokaiser.com/aktienbot/frontend + labels: + traefik.enable: 'true' + traefik.http.routers.aktienbot_fe.rule: Host(`gruppe1.testsites.info`) + traefik.http.routers.aktienbot_fe.middlewares: secHeaders@file + traefik.http.routers.aktienbot_fe.priority: 40 + traefik.http.routers.aktienbot_fe.tls: true + traefik.http.routers.aktienbot_fe.tls.certresolver: myresolver + + aktienbot_api: + image: registry.flokaiser.com/aktienbot/api + labels: + traefik.enable: 'true' + traefik.http.routers.aktienbot_api.rule: Host(`gruppe1.testsites.info`) && PathPrefix(`/api`) + traefik.http.routers.aktienbot_api.middlewares: secHeaders@file + traefik.http.routers.aktienbot_api.priority: 50 + traefik.http.routers.aktienbot_api.tls: true + traefik.http.routers.aktienbot_api.tls.certresolver: myresolver + depends_on: + - mariadb + env_file: + - ${PWD}/.env.api + + aktienbot_bot: + image: registry.flokaiser.com/aktienbot/bot + env_file: + - ${PWD}/.env.bot + + mariadb: + image: mariadb + volumes: + - mariadb_data:/var/lib/mysql + environment: + - MYSQL_ROOT_PASSWORD=sBvKtMY7ej9*dETatTtk#uRd5f*5wJYovfdDJDa& + + phpmyadmin: + image: phpmyadmin + environment: + - PMA_HOST=mariadb + - PMA_ABSOLUTE_URI=http://gruppe1.testsites.info/phpmyadmin/ + labels: + traefik.enable: true + traefik.http.routers.phpmyadmin.rule: Host(`gruppe1.testsites.info`) && PathPrefix(`/phpmyadmin`) + traefik.http.routers.phpmyadmin.middlewares: secHeaders@file + traefik.http.routers.phpmyadmin.priority: 50 + traefik.http.routers.phpmyadmin.middlewares: strip_phpmyadmin + traefik.http.routers.phpmyadmin.tls: true + traefik.http.routers.phpmyadmin.tls.certresolver: myresolver + + traefik.http.middlewares.strip_phpmyadmin.stripprefix.prefixes: /phpmyadmin + +networks: + default: + external: + name: net +volumes: + portainer_data: + mariadb_data: diff --git a/deploy/base/acme.json b/deploy/base/acme.json new file mode 100644 index 0000000..e69de29 diff --git a/deploy/base/docker-compose.yml b/deploy/base/docker-compose.yml new file mode 100644 index 0000000..6cd9531 --- /dev/null +++ b/deploy/base/docker-compose.yml @@ -0,0 +1,38 @@ +version: '3' + +services: + traefik: + image: traefik + ports: + - "80:80" + - "443:443" + volumes: + - /var/run/docker.sock:/var/run/docker.sock + - ${PWD}/traefik.toml:/etc/traefik/traefik.toml + - ${PWD}/traefik-dynamic.toml:/etc/traefik/traefik-dynamic.toml + - ${PWD}/acme.json:/etc/traefik/acme.json + - ${PWD}/access.log:/etc/traefik/access.log + + portainer: + image: portainer/portainer-ce + labels: + traefik.enable: true + traefik.http.routers.portainer.rule: Host(`gruppe1.testsites.info`) && PathPrefix(`/portainer`) + traefik.http.routers.portainer.priority: 50 + traefik.http.services.portainer.loadbalancer.server.port: 9000 + traefik.http.routers.portainer.middlewares: strip_portainer,secHeaders@file + traefik.http.routers.portainer.tls: true + traefik.http.routers.portainer.tls.certresolver: myresolver + + traefik.http.middlewares.strip_portainer.stripprefix.prefixes: /portainer + volumes: + - portainer_data:/data + - /var/run/docker.sock:/var/run/docker.sock + +networks: + default: + external: + name: net + +volumes: + portainer_data: diff --git a/deploy/base/traefik-dynamic.toml b/deploy/base/traefik-dynamic.toml new file mode 100644 index 0000000..731644f --- /dev/null +++ b/deploy/base/traefik-dynamic.toml @@ -0,0 +1,22 @@ +[tls.options] + [tls.options.default] + minVersion = "VersionTLS12" + cipherSuites = [ + "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", + "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", + "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305", + "TLS_AES_128_GCM_SHA256", + "TLS_AES_256_GCM_SHA384", + "TLS_CHACHA20_POLY1305_SHA256" + ] + curvePreferences = [ "CurveP521", "CurveP384" ] + sniStrict = true + +[http.middlewares.secHeaders.headers] + browserXssFilter = true + contentTypeNosniff = true + frameDeny = true + stsIncludeSubdomains = true + stsPreload = true + stsSeconds = 31_536_000 + customFrameOptionsValue = "SAMEORIGIN" diff --git a/deploy/base/traefik.toml b/deploy/base/traefik.toml new file mode 100644 index 0000000..d9c0177 --- /dev/null +++ b/deploy/base/traefik.toml @@ -0,0 +1,44 @@ +[log] + level = "INFO" + +[accessLog] + filePath = "/etc/traefik/access.log" + +[entryPoints] + [entryPoints.web] + address = ":80" + + [entryPoints.web.forwardedHeaders] + insecure = true + + [entryPoints.web.http] + [entryPoints.web.http.redirections] + [entryPoints.web.http.redirections.entryPoint] + to = "web-secure" + scheme = "https" + + [entryPoints.web-secure] + address = ":443" + + [entryPoints.web-secure.forwardedHeaders] + insecure = true + + [entryPoints.websecure.http] + middlewares = ["secHeaders@file"] + +[api] + dashboard = true + insecure = true + +[providers.docker] + watch = true + exposedByDefault = false + +[providers.file] + filename = "/etc/traefik/traefik-dynamic.toml" + +[certificatesResolvers.myresolver.acme] + email = "inf20155@lehre.dhbw-stuttgart.de" + storage = "/etc/traefik/acme.json" + [certificatesResolvers.myresolver.acme.httpChallenge] + entryPoint = "web" \ No newline at end of file From cb850aa6bc89385e3d68de3c0f3de6babcd60786 Mon Sep 17 00:00:00 2001 From: H4CK3R-01 Date: Mon, 4 Apr 2022 16:39:35 +0200 Subject: [PATCH 33/33] Fixed docker-compose.yml --- deploy/aktienbot/docker-compose.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/deploy/aktienbot/docker-compose.yml b/deploy/aktienbot/docker-compose.yml index f72ab3b..a485892 100644 --- a/deploy/aktienbot/docker-compose.yml +++ b/deploy/aktienbot/docker-compose.yml @@ -41,13 +41,12 @@ services: image: phpmyadmin environment: - PMA_HOST=mariadb - - PMA_ABSOLUTE_URI=http://gruppe1.testsites.info/phpmyadmin/ + - PMA_ABSOLUTE_URI=https://gruppe1.testsites.info/phpmyadmin/ labels: traefik.enable: true traefik.http.routers.phpmyadmin.rule: Host(`gruppe1.testsites.info`) && PathPrefix(`/phpmyadmin`) - traefik.http.routers.phpmyadmin.middlewares: secHeaders@file + traefik.http.routers.phpmyadmin.middlewares: secHeaders@file,strip_phpmyadmin traefik.http.routers.phpmyadmin.priority: 50 - traefik.http.routers.phpmyadmin.middlewares: strip_phpmyadmin traefik.http.routers.phpmyadmin.tls: true traefik.http.routers.phpmyadmin.tls.certresolver: myresolver @@ -57,6 +56,7 @@ networks: default: external: name: net + volumes: portainer_data: mariadb_data: