Merge remote-tracking branch 'origin/main' into bot
This commit is contained in:
commit
99d39a0ed9
@ -4,6 +4,7 @@ pipeline:
|
|||||||
commands:
|
commands:
|
||||||
- echo -n "${CI_COMMIT_BRANCH//\//-}-${CI_COMMIT_SHA:0:8}, latest" > .tags
|
- echo -n "${CI_COMMIT_BRANCH//\//-}-${CI_COMMIT_SHA:0:8}, latest" > .tags
|
||||||
when:
|
when:
|
||||||
|
path: [ "frontend/**", "telegram_bot/**", "api/**" ]
|
||||||
event: push
|
event: push
|
||||||
|
|
||||||
|
|
||||||
@ -80,6 +81,7 @@ pipeline:
|
|||||||
- docker-compose pull
|
- docker-compose pull
|
||||||
- docker-compose -p "aktienbot" up -d
|
- docker-compose -p "aktienbot" up -d
|
||||||
when:
|
when:
|
||||||
|
path: [ "frontend/**", "telegram_bot/**", "api/**" ]
|
||||||
event: push
|
event: push
|
||||||
|
|
||||||
branches: main
|
branches: main
|
||||||
|
18
README.md
18
README.md
@ -16,3 +16,21 @@ WebEngineering2 Projekt: Aktien und News Bot für Telegram
|
|||||||
|
|
||||||
## Dokumentation
|
## Dokumentation
|
||||||
-> README.md in /documentation
|
-> README.md in /documentation
|
||||||
|
|
||||||
|
## Team
|
||||||
|
* Florian Kaiser
|
||||||
|
* Florian Kellermann
|
||||||
|
* Linus Eickhoff
|
||||||
|
* Kevin Pauer
|
||||||
|
|
||||||
|
## Nützliche Tools
|
||||||
|
- Portainer (https://gruppe1.testsites.info/portainer/) \
|
||||||
|
*Container Management System*
|
||||||
|
- phpMyAdmin (https://gruppe1.testsites.info/phpmyadmin/) \
|
||||||
|
*Administration von MySQL-Datenbanken*
|
||||||
|
- goaccess (https://gruppe1.testsites.info/goaccess/) \
|
||||||
|
*Webanalyseanwendung*
|
||||||
|
- Uptimekuma (https://uptimekuma.flokaiser.com/status/aktienbot) \
|
||||||
|
*Monitoring*
|
||||||
|
- Woodpecker (https://woodpecker.flokaiser.com/WebEngineering2/TelegramAktienBot) \
|
||||||
|
*Continuous Integration platform*
|
||||||
|
@ -16,3 +16,6 @@ BOT_PASSWORD=
|
|||||||
ADMIN_EMAIL=
|
ADMIN_EMAIL=
|
||||||
ADMIN_USERNAME=
|
ADMIN_USERNAME=
|
||||||
ADMIN_PASSWORD=
|
ADMIN_PASSWORD=
|
||||||
|
|
||||||
|
# API URL (used for load_share_price.py and generate_sample_transactions.py)
|
||||||
|
API_URL=
|
@ -1,16 +1,16 @@
|
|||||||
FROM python:3.10-alpine
|
FROM python:3.10-slim
|
||||||
|
|
||||||
# Change the working directory to the project root
|
# Change the working directory to the root of the project
|
||||||
WORKDIR /srv/flask_app
|
WORKDIR /srv/flask_app
|
||||||
|
|
||||||
# Install dependencies
|
# Install dependencies
|
||||||
RUN apk add nginx build-base libffi-dev curl uwsgi
|
RUN apt update && apt install -y python3 python3-pip curl nginx && rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
# Install python dependencies
|
# Install the dependencies
|
||||||
COPY api/requirements.txt /srv/flask_app/
|
COPY api/requirements.txt /srv/flask_app/
|
||||||
RUN pip install -r requirements.txt --src /usr/local/src --no-warn-script-location
|
RUN pip install -r requirements.txt --src /usr/local/src --no-warn-script-location
|
||||||
|
|
||||||
# Copy the app
|
# Copy the source code to the working directory
|
||||||
COPY api /srv/flask_app
|
COPY api /srv/flask_app
|
||||||
COPY api/deploy/nginx.conf /etc/nginx
|
COPY api/deploy/nginx.conf /etc/nginx
|
||||||
|
|
||||||
|
@ -1,3 +1,9 @@
|
|||||||
|
__author__ = "Florian Kaiser"
|
||||||
|
__copyright__ = "Copyright 2022, Project Aktienbot"
|
||||||
|
__credits__ = ["Florian Kaiser", "Florian Kellermann", "Linus Eickhof", "Kevin Pauer"]
|
||||||
|
__license__ = "GPL 3.0"
|
||||||
|
__version__ = "1.0.0"
|
||||||
|
|
||||||
from app import create_app
|
from app import create_app
|
||||||
|
|
||||||
# Create an application instance that web servers can use.
|
# Create an application instance that web servers can use.
|
||||||
|
@ -1,3 +1,9 @@
|
|||||||
|
__author__ = "Florian Kaiser"
|
||||||
|
__copyright__ = "Copyright 2022, Project Aktienbot"
|
||||||
|
__credits__ = ["Florian Kaiser", "Florian Kellermann", "Linus Eickhof", "Kevin Pauer"]
|
||||||
|
__license__ = "GPL 3.0"
|
||||||
|
__version__ = "1.0.0"
|
||||||
|
|
||||||
from flask import current_app
|
from flask import current_app
|
||||||
from apiflask import APIFlask
|
from apiflask import APIFlask
|
||||||
|
|
||||||
@ -7,6 +13,7 @@ from flask_cors import CORS
|
|||||||
from app.blueprints.keyword import keyword_blueprint
|
from app.blueprints.keyword import keyword_blueprint
|
||||||
from app.blueprints.portfolio import portfolio_blueprint
|
from app.blueprints.portfolio import portfolio_blueprint
|
||||||
from app.blueprints.shares import shares_blueprint
|
from app.blueprints.shares import shares_blueprint
|
||||||
|
from app.blueprints.share_price import share_price_blueprint
|
||||||
from app.blueprints.transactions import transaction_blueprint
|
from app.blueprints.transactions import transaction_blueprint
|
||||||
from app.blueprints.telegram import telegram_blueprint
|
from app.blueprints.telegram import telegram_blueprint
|
||||||
from app.blueprints.user import users_blueprint
|
from app.blueprints.user import users_blueprint
|
||||||
@ -23,13 +30,12 @@ def create_app(config_filename=None):
|
|||||||
|
|
||||||
CORS(application, resources={r"*": {"origins": "*"}})
|
CORS(application, resources={r"*": {"origins": "*"}})
|
||||||
|
|
||||||
application.app_context().push()
|
|
||||||
|
|
||||||
db.init_app(application)
|
db.init_app(application)
|
||||||
|
|
||||||
# api blueprints
|
# api blueprints
|
||||||
application.register_blueprint(keyword_blueprint)
|
application.register_blueprint(keyword_blueprint)
|
||||||
application.register_blueprint(shares_blueprint)
|
application.register_blueprint(shares_blueprint)
|
||||||
|
application.register_blueprint(share_price_blueprint)
|
||||||
application.register_blueprint(transaction_blueprint)
|
application.register_blueprint(transaction_blueprint)
|
||||||
application.register_blueprint(portfolio_blueprint)
|
application.register_blueprint(portfolio_blueprint)
|
||||||
application.register_blueprint(users_blueprint)
|
application.register_blueprint(users_blueprint)
|
||||||
|
@ -1,3 +1,9 @@
|
|||||||
|
__author__ = "Florian Kaiser"
|
||||||
|
__copyright__ = "Copyright 2022, Project Aktienbot"
|
||||||
|
__credits__ = ["Florian Kaiser", "Florian Kellermann", "Linus Eickhof", "Kevin Pauer"]
|
||||||
|
__license__ = "GPL 3.0"
|
||||||
|
__version__ = "1.0.0"
|
||||||
|
|
||||||
from flask import current_app
|
from flask import current_app
|
||||||
|
|
||||||
import jwt
|
import jwt
|
||||||
|
@ -0,0 +1,5 @@
|
|||||||
|
__author__ = "Florian Kaiser"
|
||||||
|
__copyright__ = "Copyright 2022, Project Aktienbot"
|
||||||
|
__credits__ = ["Florian Kaiser", "Florian Kellermann", "Linus Eickhof", "Kevin Pauer"]
|
||||||
|
__license__ = "GPL 3.0"
|
||||||
|
__version__ = "1.0.0"
|
@ -1,3 +1,9 @@
|
|||||||
|
__author__ = "Florian Kaiser"
|
||||||
|
__copyright__ = "Copyright 2022, Project Aktienbot"
|
||||||
|
__credits__ = ["Florian Kaiser", "Florian Kellermann", "Linus Eickhof", "Kevin Pauer"]
|
||||||
|
__license__ = "GPL 3.0"
|
||||||
|
__version__ = "1.0.0"
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from apiflask import APIBlueprint, abort
|
from apiflask import APIBlueprint, abort
|
||||||
|
@ -1,11 +1,17 @@
|
|||||||
|
__author__ = "Florian Kaiser"
|
||||||
|
__copyright__ = "Copyright 2022, Project Aktienbot"
|
||||||
|
__credits__ = ["Florian Kaiser", "Florian Kellermann", "Linus Eickhof", "Kevin Pauer"]
|
||||||
|
__license__ = "GPL 3.0"
|
||||||
|
__version__ = "1.0.0"
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from apiflask import APIBlueprint
|
from apiflask import APIBlueprint
|
||||||
|
from app.auth import auth
|
||||||
from app.schema import PortfolioResponseSchema
|
|
||||||
from app.db import database as db
|
from app.db import database as db
|
||||||
from app.helper_functions import make_response, get_email_or_abort_401
|
from app.helper_functions import make_response, get_email_or_abort_401
|
||||||
from app.auth import auth
|
from app.models import SharePrice
|
||||||
|
from app.schema import PortfolioResponseSchema
|
||||||
|
|
||||||
portfolio_blueprint = APIBlueprint('portfolio', __name__, url_prefix='/api')
|
portfolio_blueprint = APIBlueprint('portfolio', __name__, url_prefix='/api')
|
||||||
__location__ = os.path.realpath(os.path.join(os.getcwd(), os.path.dirname(__file__)))
|
__location__ = os.path.realpath(os.path.join(os.getcwd(), os.path.dirname(__file__)))
|
||||||
@ -23,11 +29,18 @@ def get_portfolio():
|
|||||||
|
|
||||||
if transactions is not None:
|
if transactions is not None:
|
||||||
for row in transactions:
|
for row in transactions:
|
||||||
return_portfolio.append({
|
data = {
|
||||||
"symbol": row[0],
|
"symbol": row[0],
|
||||||
"count": row[1],
|
"count": row[1],
|
||||||
# "price": row[2],
|
# "calculated_price": row[2],
|
||||||
"last_transaction": row[3]
|
"last_transaction": row[3],
|
||||||
})
|
'current_price': 0
|
||||||
|
}
|
||||||
|
|
||||||
|
query_share_price = db.session.query(SharePrice).filter_by(symbol=row[0]).order_by(SharePrice.date.desc()).first()
|
||||||
|
if query_share_price is not None:
|
||||||
|
data['current_price'] = query_share_price.as_dict()['price']
|
||||||
|
|
||||||
|
return_portfolio.append(data)
|
||||||
|
|
||||||
return make_response(return_portfolio, 200, "Successfully loaded symbols")
|
return make_response(return_portfolio, 200, "Successfully loaded symbols")
|
||||||
|
94
api/app/blueprints/share_price.py
Normal file
94
api/app/blueprints/share_price.py
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
__author__ = "Florian Kaiser"
|
||||||
|
__copyright__ = "Copyright 2022, Project Aktienbot"
|
||||||
|
__credits__ = ["Florian Kaiser", "Florian Kellermann", "Linus Eickhof", "Kevin Pauer"]
|
||||||
|
__license__ = "GPL 3.0"
|
||||||
|
__version__ = "1.0.0"
|
||||||
|
|
||||||
|
import datetime
|
||||||
|
import os
|
||||||
|
|
||||||
|
from apiflask import APIBlueprint, abort
|
||||||
|
|
||||||
|
from app.models import SharePrice
|
||||||
|
from app.db import database as db
|
||||||
|
from app.helper_functions import make_response
|
||||||
|
from app.auth import auth
|
||||||
|
from app.schema import SymbolPriceSchema
|
||||||
|
|
||||||
|
share_price_blueprint = APIBlueprint('share_price', __name__, url_prefix='/api')
|
||||||
|
__location__ = os.path.realpath(os.path.join(os.getcwd(), os.path.dirname(__file__)))
|
||||||
|
|
||||||
|
|
||||||
|
@share_price_blueprint.route('/symbols', methods=['GET'])
|
||||||
|
@share_price_blueprint.output({}, 200)
|
||||||
|
@share_price_blueprint.auth_required(auth)
|
||||||
|
@share_price_blueprint.doc(summary="Returns all transaction symbols", description="Returns all transaction symbols for all users")
|
||||||
|
def get_transaction_symbols():
|
||||||
|
symbols = db.session.execute("SELECT symbol FROM `transactions` GROUP BY symbol;").all()
|
||||||
|
|
||||||
|
return_symbols = []
|
||||||
|
for s in symbols:
|
||||||
|
return_symbols.append(s[0])
|
||||||
|
|
||||||
|
return make_response(return_symbols, 200, "Successfully loaded symbols")
|
||||||
|
|
||||||
|
|
||||||
|
@share_price_blueprint.route('/symbol', methods=['POST'])
|
||||||
|
@share_price_blueprint.output({}, 200)
|
||||||
|
@share_price_blueprint.input(schema=SymbolPriceSchema)
|
||||||
|
@share_price_blueprint.auth_required(auth)
|
||||||
|
@share_price_blueprint.doc(summary="Returns all transaction symbols", description="Returns all transaction symbols for all users")
|
||||||
|
def add_symbol_price(data):
|
||||||
|
if not check_if_symbol_data_exists(data):
|
||||||
|
abort(400, message="Symbol missing")
|
||||||
|
|
||||||
|
if not check_if_price_data_exists(data):
|
||||||
|
abort(400, message="Price missing")
|
||||||
|
|
||||||
|
if not check_if_time_data_exists(data):
|
||||||
|
abort(400, message="Time missing")
|
||||||
|
|
||||||
|
symbol = data['symbol']
|
||||||
|
price = data['price']
|
||||||
|
time = data['time']
|
||||||
|
|
||||||
|
share_price = SharePrice(
|
||||||
|
symbol=symbol,
|
||||||
|
price=price,
|
||||||
|
date=datetime.datetime.strptime(time, '%Y-%m-%dT%H:%M:%S.%fZ'),
|
||||||
|
)
|
||||||
|
|
||||||
|
db.session.add(share_price)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
return make_response(share_price.as_dict(), 200, "Successfully added price")
|
||||||
|
|
||||||
|
|
||||||
|
def check_if_symbol_data_exists(data):
|
||||||
|
if 'symbol' not in data:
|
||||||
|
return False
|
||||||
|
|
||||||
|
if data['symbol'] == "" or data['symbol'] is None:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def check_if_price_data_exists(data):
|
||||||
|
if 'price' not in data:
|
||||||
|
return False
|
||||||
|
|
||||||
|
if data['price'] == "" or data['price'] is None:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def check_if_time_data_exists(data):
|
||||||
|
if 'time' not in data:
|
||||||
|
return False
|
||||||
|
|
||||||
|
if data['time'] == "" or data['time'] is None:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
@ -1,3 +1,9 @@
|
|||||||
|
__author__ = "Florian Kaiser"
|
||||||
|
__copyright__ = "Copyright 2022, Project Aktienbot"
|
||||||
|
__credits__ = ["Florian Kaiser", "Florian Kellermann", "Linus Eickhof", "Kevin Pauer"]
|
||||||
|
__license__ = "GPL 3.0"
|
||||||
|
__version__ = "1.0.0"
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from apiflask import APIBlueprint, abort
|
from apiflask import APIBlueprint, abort
|
||||||
|
@ -1,12 +1,16 @@
|
|||||||
|
__author__ = "Florian Kaiser"
|
||||||
|
__copyright__ = "Copyright 2022, Project Aktienbot"
|
||||||
|
__credits__ = ["Florian Kaiser", "Florian Kellermann", "Linus Eickhof", "Kevin Pauer"]
|
||||||
|
__license__ = "GPL 3.0"
|
||||||
|
__version__ = "1.0.0"
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from apiflask import APIBlueprint, abort
|
from apiflask import APIBlueprint, abort
|
||||||
|
|
||||||
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.auth import auth
|
||||||
|
from app.db import database as db
|
||||||
|
from app.helper_functions import make_response, get_email_or_abort_401, get_user
|
||||||
from app.schema import TelegramIdSchema, UsersSchema
|
from app.schema import TelegramIdSchema, UsersSchema
|
||||||
from app.models import User
|
|
||||||
|
|
||||||
telegram_blueprint = APIBlueprint('telegram', __name__, url_prefix='/api')
|
telegram_blueprint = APIBlueprint('telegram', __name__, url_prefix='/api')
|
||||||
__location__ = os.path.realpath(os.path.join(os.getcwd(), os.path.dirname(__file__)))
|
__location__ = os.path.realpath(os.path.join(os.getcwd(), os.path.dirname(__file__)))
|
||||||
@ -23,7 +27,8 @@ def add_keyword(data):
|
|||||||
if not check_if_telegram_user_id_data_exists(data):
|
if not check_if_telegram_user_id_data_exists(data):
|
||||||
abort(400, message="User ID missing")
|
abort(400, message="User ID missing")
|
||||||
|
|
||||||
query_user = db.session.query(User).filter_by(email=email).first()
|
query_user = get_user(email)
|
||||||
|
|
||||||
query_user.telegram_user_id = data['telegram_user_id']
|
query_user.telegram_user_id = data['telegram_user_id']
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
|
@ -1,13 +1,19 @@
|
|||||||
import os
|
__author__ = "Florian Kaiser"
|
||||||
|
__copyright__ = "Copyright 2022, Project Aktienbot"
|
||||||
|
__credits__ = ["Florian Kaiser", "Florian Kellermann", "Linus Eickhof", "Kevin Pauer"]
|
||||||
|
__license__ = "GPL 3.0"
|
||||||
|
__version__ = "1.0.0"
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
|
import os
|
||||||
|
|
||||||
from apiflask import abort, APIBlueprint
|
from apiflask import abort, APIBlueprint
|
||||||
|
|
||||||
|
from app.auth import auth
|
||||||
from app.db import database as db
|
from app.db import database as db
|
||||||
from app.helper_functions import make_response, get_email_or_abort_401
|
from app.helper_functions import make_response, get_email_or_abort_401
|
||||||
from app.models import Transaction
|
from app.models import Transaction
|
||||||
from app.schema import TransactionSchema, TransactionResponseSchema
|
from app.schema import TransactionSchema, TransactionResponseSchema
|
||||||
from app.auth import auth
|
|
||||||
|
|
||||||
transaction_blueprint = APIBlueprint('transaction', __name__, url_prefix='/api')
|
transaction_blueprint = APIBlueprint('transaction', __name__, url_prefix='/api')
|
||||||
__location__ = os.path.realpath(os.path.join(os.getcwd(), os.path.dirname(__file__)))
|
__location__ = os.path.realpath(os.path.join(os.getcwd(), os.path.dirname(__file__)))
|
||||||
|
@ -1,15 +1,20 @@
|
|||||||
|
__author__ = "Florian Kaiser"
|
||||||
|
__copyright__ = "Copyright 2022, Project Aktienbot"
|
||||||
|
__credits__ = ["Florian Kaiser", "Florian Kellermann", "Linus Eickhof", "Kevin Pauer"]
|
||||||
|
__license__ = "GPL 3.0"
|
||||||
|
__version__ = "1.0.0"
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
import os
|
import os
|
||||||
from flask import current_app
|
|
||||||
|
|
||||||
import jwt
|
import jwt
|
||||||
from apiflask import APIBlueprint, abort
|
from apiflask import APIBlueprint, abort
|
||||||
|
|
||||||
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
|
from app.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, get_user
|
||||||
|
from app.models import User
|
||||||
|
from app.schema import UsersSchema, TokenSchema, LoginDataSchema, AdminDataSchema, DeleteUserSchema, RegisterDataSchema, UpdateUserDataSchema, CronDataSchema
|
||||||
|
from flask import current_app
|
||||||
|
|
||||||
users_blueprint = APIBlueprint('users', __name__, url_prefix='/api')
|
users_blueprint = APIBlueprint('users', __name__, url_prefix='/api')
|
||||||
__location__ = os.path.realpath(os.path.join(os.getcwd(), os.path.dirname(__file__)))
|
__location__ = os.path.realpath(os.path.join(os.getcwd(), os.path.dirname(__file__)))
|
||||||
@ -36,9 +41,9 @@ def users():
|
|||||||
def user():
|
def user():
|
||||||
email = get_email_or_abort_401()
|
email = get_email_or_abort_401()
|
||||||
|
|
||||||
res = db.session.query(User).filter_by(email=email).first().as_dict()
|
query_user = get_user(email)
|
||||||
|
|
||||||
return make_response(res, 200, "Successfully received current user data")
|
return make_response(query_user.as_dict(), 200, "Successfully received current user data")
|
||||||
|
|
||||||
|
|
||||||
@users_blueprint.route('/user/login', methods=['POST'])
|
@users_blueprint.route('/user/login', methods=['POST'])
|
||||||
@ -55,10 +60,7 @@ def login(data):
|
|||||||
email = data['email']
|
email = data['email']
|
||||||
password = data['password']
|
password = data['password']
|
||||||
|
|
||||||
query_user = db.session.query(User).filter_by(email=email).first()
|
query_user = get_user(email)
|
||||||
|
|
||||||
if query_user is None: # email doesn't exist
|
|
||||||
abort(500, message="Unable to login")
|
|
||||||
|
|
||||||
if not check_password(query_user.password, password.encode("utf-8")): # Password incorrect
|
if not check_password(query_user.password, password.encode("utf-8")): # Password incorrect
|
||||||
abort(500, message="Unable to login")
|
abort(500, message="Unable to login")
|
||||||
@ -98,7 +100,8 @@ def register(data):
|
|||||||
email=email,
|
email=email,
|
||||||
username=username,
|
username=username,
|
||||||
password=hash_password(password),
|
password=hash_password(password),
|
||||||
admin=False
|
admin=False,
|
||||||
|
cron="0 8 * * *"
|
||||||
)
|
)
|
||||||
db.session.add(new_user)
|
db.session.add(new_user)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
@ -114,7 +117,7 @@ def register(data):
|
|||||||
def update_user(data):
|
def update_user(data):
|
||||||
email = get_email_or_abort_401()
|
email = get_email_or_abort_401()
|
||||||
|
|
||||||
query_user = db.session.query(User).filter_by(email=email).first()
|
query_user = get_user(email)
|
||||||
|
|
||||||
if check_if_password_data_exists(data):
|
if check_if_password_data_exists(data):
|
||||||
query_user.password = hash_password(data['password'])
|
query_user.password = hash_password(data['password'])
|
||||||
@ -144,10 +147,7 @@ def set_admin(data):
|
|||||||
email = data['email']
|
email = data['email']
|
||||||
admin = data['admin']
|
admin = data['admin']
|
||||||
|
|
||||||
query_user = db.session.query(User).filter_by(email=email).first()
|
query_user = get_user(email)
|
||||||
|
|
||||||
if query_user is None: # Username doesn't exist
|
|
||||||
abort(500, message="Unable to update user")
|
|
||||||
|
|
||||||
query_user.admin = admin
|
query_user.admin = admin
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
@ -155,6 +155,23 @@ def set_admin(data):
|
|||||||
return make_response({}, 200, "Successfully updated users admin rights")
|
return make_response({}, 200, "Successfully updated users admin rights")
|
||||||
|
|
||||||
|
|
||||||
|
@users_blueprint.route('/user/setCron', methods=['PUT'])
|
||||||
|
@users_blueprint.output({}, 200)
|
||||||
|
@users_blueprint.input(schema=CronDataSchema)
|
||||||
|
@users_blueprint.auth_required(auth)
|
||||||
|
@users_blueprint.doc(summary="Set update cron", description="Set update cron of specified user")
|
||||||
|
def set_cron(data):
|
||||||
|
email = get_email_or_abort_401()
|
||||||
|
|
||||||
|
if not check_if_cron_data_exists(data):
|
||||||
|
abort(400, "Cron data missing")
|
||||||
|
|
||||||
|
get_user(email).cron = data['cron']
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
return make_response({}, 200, "Successfully updated users cron")
|
||||||
|
|
||||||
|
|
||||||
@users_blueprint.route('/user', methods=['DELETE'])
|
@users_blueprint.route('/user', methods=['DELETE'])
|
||||||
@users_blueprint.output({}, 200)
|
@users_blueprint.output({}, 200)
|
||||||
@users_blueprint.input(schema=DeleteUserSchema)
|
@users_blueprint.input(schema=DeleteUserSchema)
|
||||||
@ -216,3 +233,13 @@ def check_if_admin_data_exists(data):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def check_if_cron_data_exists(data):
|
||||||
|
if "cron" not in data:
|
||||||
|
return False
|
||||||
|
|
||||||
|
if data['cron'] == "" or data['cron'] is None:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
@ -1,3 +1,9 @@
|
|||||||
|
__author__ = "Florian Kaiser"
|
||||||
|
__copyright__ = "Copyright 2022, Project Aktienbot"
|
||||||
|
__credits__ = ["Florian Kaiser", "Florian Kellermann", "Linus Eickhof", "Kevin Pauer"]
|
||||||
|
__license__ = "GPL 3.0"
|
||||||
|
__version__ = "1.0.0"
|
||||||
|
|
||||||
import os
|
import os
|
||||||
from app.schema import BaseResponseSchema
|
from app.schema import BaseResponseSchema
|
||||||
|
|
||||||
|
@ -1,3 +1,9 @@
|
|||||||
|
__author__ = "Florian Kaiser"
|
||||||
|
__copyright__ = "Copyright 2022, Project Aktienbot"
|
||||||
|
__credits__ = ["Florian Kaiser", "Florian Kellermann", "Linus Eickhof", "Kevin Pauer"]
|
||||||
|
__license__ = "GPL 3.0"
|
||||||
|
__version__ = "1.0.0"
|
||||||
|
|
||||||
import os
|
import os
|
||||||
from app.schema import BaseResponseSchema
|
from app.schema import BaseResponseSchema
|
||||||
|
|
||||||
|
@ -1,3 +1,9 @@
|
|||||||
|
__author__ = "Florian Kaiser"
|
||||||
|
__copyright__ = "Copyright 2022, Project Aktienbot"
|
||||||
|
__credits__ = ["Florian Kaiser", "Florian Kellermann", "Linus Eickhof", "Kevin Pauer"]
|
||||||
|
__license__ = "GPL 3.0"
|
||||||
|
__version__ = "1.0.0"
|
||||||
|
|
||||||
from flask_sqlalchemy import SQLAlchemy
|
from flask_sqlalchemy import SQLAlchemy
|
||||||
|
|
||||||
# database object
|
# database object
|
||||||
|
@ -1,12 +1,16 @@
|
|||||||
from flask import current_app
|
__author__ = "Florian Kaiser"
|
||||||
|
__copyright__ = "Copyright 2022, Project Aktienbot"
|
||||||
|
__credits__ = ["Florian Kaiser", "Florian Kellermann", "Linus Eickhof", "Kevin Pauer"]
|
||||||
|
__license__ = "GPL 3.0"
|
||||||
|
__version__ = "1.0.0"
|
||||||
|
|
||||||
import bcrypt
|
import bcrypt
|
||||||
import jwt
|
import jwt
|
||||||
from apiflask import abort
|
from apiflask import abort
|
||||||
from flask import request, jsonify
|
|
||||||
|
|
||||||
from app.db import database as db
|
from app.db import database as db
|
||||||
from app.models import User
|
from app.models import User
|
||||||
|
from flask import current_app
|
||||||
|
from flask import request, jsonify
|
||||||
|
|
||||||
|
|
||||||
def hash_password(password):
|
def hash_password(password):
|
||||||
@ -17,11 +21,8 @@ def check_password(hashed_password, user_password):
|
|||||||
return bcrypt.checkpw(user_password, hashed_password)
|
return bcrypt.checkpw(user_password, hashed_password)
|
||||||
|
|
||||||
|
|
||||||
def get_email_from_token_data():
|
def get_email_from_token_data(token):
|
||||||
if 'Authorization' in request.headers:
|
if token is None or len(token) < 2:
|
||||||
token = request.headers['Authorization'].split(" ")
|
|
||||||
|
|
||||||
if len(token) < 2:
|
|
||||||
return None
|
return None
|
||||||
else:
|
else:
|
||||||
token = token[1]
|
token = token[1]
|
||||||
@ -50,12 +51,17 @@ def get_email_from_token_data():
|
|||||||
except jwt.PyJWTError:
|
except jwt.PyJWTError:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def get_token():
|
||||||
|
if 'Authorization' in request.headers:
|
||||||
|
return request.headers['Authorization'].split(" ")
|
||||||
|
else:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def get_email_or_abort_401():
|
def get_email_or_abort_401():
|
||||||
# get username from jwt token
|
# get username from jwt token
|
||||||
email = get_email_from_token_data()
|
email = get_email_from_token_data(get_token())
|
||||||
|
|
||||||
if email is None: # If token not provided or invalid -> return 401 code
|
if email is None: # If token not provided or invalid -> return 401 code
|
||||||
abort(401, message="Unable to login")
|
abort(401, message="Unable to login")
|
||||||
@ -76,3 +82,12 @@ def is_user_admin():
|
|||||||
|
|
||||||
def make_response(data, status=200, text=""):
|
def make_response(data, status=200, text=""):
|
||||||
return jsonify({"status": status, "text": text, "data": data})
|
return jsonify({"status": status, "text": text, "data": data})
|
||||||
|
|
||||||
|
|
||||||
|
def get_user(email):
|
||||||
|
query_user = db.session.query(User).filter_by(email=email).first()
|
||||||
|
|
||||||
|
if query_user is None: # Username doesn't exist
|
||||||
|
abort(500, message="Can't find user")
|
||||||
|
|
||||||
|
return query_user
|
||||||
|
@ -1,3 +1,9 @@
|
|||||||
|
__author__ = "Florian Kaiser"
|
||||||
|
__copyright__ = "Copyright 2022, Project Aktienbot"
|
||||||
|
__credits__ = ["Florian Kaiser", "Florian Kellermann", "Linus Eickhof", "Kevin Pauer"]
|
||||||
|
__license__ = "GPL 3.0"
|
||||||
|
__version__ = "1.0.0"
|
||||||
|
|
||||||
from app.db import database as db
|
from app.db import database as db
|
||||||
|
|
||||||
|
|
||||||
@ -7,14 +13,16 @@ class User(db.Model):
|
|||||||
password = db.Column('password', db.BINARY(60), nullable=False)
|
password = db.Column('password', db.BINARY(60), nullable=False)
|
||||||
username = db.Column('username', db.String(255), nullable=False, server_default='')
|
username = db.Column('username', db.String(255), nullable=False, server_default='')
|
||||||
telegram_user_id = db.Column('telegram_user_id', db.String(255), nullable=True, server_default='')
|
telegram_user_id = db.Column('telegram_user_id', db.String(255), nullable=True, server_default='')
|
||||||
admin = db.Column('admin', db.Boolean(), server_default='0')
|
admin = db.Column('admin', db.Boolean(), server_default='0') # 0 = False, 1 = True
|
||||||
|
cron = db.Column('cron', db.String(20), server_default='0 8 * * *', nullable=False)
|
||||||
|
|
||||||
def as_dict(self):
|
def as_dict(self):
|
||||||
return {
|
return {
|
||||||
"email": self.email,
|
"email": self.email,
|
||||||
"username": self.username,
|
"username": self.username,
|
||||||
"telegram_user_id": self.telegram_user_id,
|
"telegram_user_id": self.telegram_user_id,
|
||||||
"admin": self.admin
|
"admin": self.admin,
|
||||||
|
"cron": self.cron
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -49,3 +57,14 @@ class Share(db.Model):
|
|||||||
|
|
||||||
def as_dict(self):
|
def as_dict(self):
|
||||||
return {c.name: getattr(self, c.name) for c in self.__table__.columns}
|
return {c.name: getattr(self, c.name) for c in self.__table__.columns}
|
||||||
|
|
||||||
|
|
||||||
|
class SharePrice(db.Model):
|
||||||
|
__tablename__ = 'share_price'
|
||||||
|
id = db.Column('id', db.Integer(), nullable=False, unique=True, primary_key=True)
|
||||||
|
symbol = db.Column('symbol', db.String(255))
|
||||||
|
price = db.Column('price', db.Float())
|
||||||
|
date = db.Column('date', db.DateTime())
|
||||||
|
|
||||||
|
def as_dict(self):
|
||||||
|
return {c.name: getattr(self, c.name) for c in self.__table__.columns}
|
||||||
|
@ -1,3 +1,9 @@
|
|||||||
|
__author__ = "Florian Kaiser"
|
||||||
|
__copyright__ = "Copyright 2022, Project Aktienbot"
|
||||||
|
__credits__ = ["Florian Kaiser", "Florian Kellermann", "Linus Eickhof", "Kevin Pauer"]
|
||||||
|
__license__ = "GPL 3.0"
|
||||||
|
__version__ = "1.0.0"
|
||||||
|
|
||||||
from apiflask import Schema
|
from apiflask import Schema
|
||||||
from apiflask.fields import Integer, String, Boolean, Field, Float
|
from apiflask.fields import Integer, String, Boolean, Field, Float
|
||||||
from marshmallow import validate
|
from marshmallow import validate
|
||||||
@ -23,6 +29,10 @@ class AdminDataSchema(Schema):
|
|||||||
admin = Boolean()
|
admin = Boolean()
|
||||||
|
|
||||||
|
|
||||||
|
class CronDataSchema(Schema):
|
||||||
|
cron = String()
|
||||||
|
|
||||||
|
|
||||||
class TokenSchema(Schema):
|
class TokenSchema(Schema):
|
||||||
token = String()
|
token = String()
|
||||||
|
|
||||||
@ -71,6 +81,12 @@ class TransactionSchema(Schema):
|
|||||||
price = Float()
|
price = Float()
|
||||||
|
|
||||||
|
|
||||||
|
class SymbolPriceSchema(Schema):
|
||||||
|
symbol = String()
|
||||||
|
time = String(validate=validate.Regexp(r"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z"))
|
||||||
|
price = Float()
|
||||||
|
|
||||||
|
|
||||||
class TelegramIdSchema(Schema):
|
class TelegramIdSchema(Schema):
|
||||||
telegram_user_id = String()
|
telegram_user_id = String()
|
||||||
|
|
||||||
|
31
api/generate_sample_transactions.py
Normal file
31
api/generate_sample_transactions.py
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
__author__ = "Florian Kaiser"
|
||||||
|
__copyright__ = "Copyright 2022, Project Aktienbot"
|
||||||
|
__credits__ = ["Florian Kaiser", "Florian Kellermann", "Linus Eickhof", "Kevin Pauer"]
|
||||||
|
__license__ = "GPL 3.0"
|
||||||
|
__version__ = "1.0.0"
|
||||||
|
|
||||||
|
import os
|
||||||
|
import random
|
||||||
|
|
||||||
|
import faker
|
||||||
|
import requests
|
||||||
|
|
||||||
|
username = ''
|
||||||
|
password = ''
|
||||||
|
|
||||||
|
shares = ["TWTR", "GOOG", "AAPL", "MSFT", "AMZN", "FB", "NFLX", "TSLA", "BABA", "BA", "BAC", "C", "CAT", "CSCO", "CVX", "DIS", "DOW", "DUK", "GE", "HD", "IBM" "INTC", "JNJ", "JPM", "KO",
|
||||||
|
"MCD", "MMM", "MRK", "NKE", "PFE", "PG", "T", "UNH", "UTX", "V", "VZ", "WMT", "XOM", "YHOO", "ZTS"]
|
||||||
|
|
||||||
|
fake = faker.Faker()
|
||||||
|
|
||||||
|
token = requests.post(os.getenv("API_URL") + '/user/login', json={"email": username, "password": password}).json()['data']['token']
|
||||||
|
|
||||||
|
for i in range(1, 1000):
|
||||||
|
payload = {
|
||||||
|
"count": random.randint(1, 100),
|
||||||
|
"price": random.random() * 100,
|
||||||
|
"symbol": shares[random.randint(0, len(shares) - 1)],
|
||||||
|
"time": fake.date_time().isoformat() + ".000Z"
|
||||||
|
}
|
||||||
|
|
||||||
|
response = requests.post(os.getenv("API_URL") + '/transaction', json=payload, headers={'Authorization': 'Bearer ' + token})
|
50
api/load_share_price.py
Normal file
50
api/load_share_price.py
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
__author__ = "Florian Kaiser"
|
||||||
|
__copyright__ = "Copyright 2022, Project Aktienbot"
|
||||||
|
__credits__ = ["Florian Kaiser", "Florian Kellermann", "Linus Eickhof", "Kevin Pauer"]
|
||||||
|
__license__ = "GPL 3.0"
|
||||||
|
__version__ = "1.0.0"
|
||||||
|
|
||||||
|
import datetime
|
||||||
|
import os
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
|
|
||||||
|
import requests
|
||||||
|
import yfinance
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
|
|
||||||
|
def thread_function(s):
|
||||||
|
my_share_info = yfinance.Ticker(s)
|
||||||
|
my_share_data = my_share_info.info
|
||||||
|
|
||||||
|
if my_share_data['regularMarketPrice'] is not None:
|
||||||
|
payload = {
|
||||||
|
"symbol": s,
|
||||||
|
"price": float(my_share_data['regularMarketPrice']),
|
||||||
|
"time": datetime.datetime.now().strftime("%Y-%m-%dT%H:%M:%S.000Z")
|
||||||
|
}
|
||||||
|
|
||||||
|
requests.post(os.getenv("API_URL") + '/symbol', json=payload, headers={'Authorization': 'Bearer ' + token})
|
||||||
|
|
||||||
|
|
||||||
|
def split(a, n):
|
||||||
|
k, m = divmod(len(a), n)
|
||||||
|
return (a[i * k + min(i, m):(i + 1) * k + min(i + 1, m)] for i in range(n))
|
||||||
|
|
||||||
|
|
||||||
|
load_dotenv()
|
||||||
|
username = os.getenv('ADMIN_EMAIL')
|
||||||
|
password = os.getenv('ADMIN_PASSWORD')
|
||||||
|
|
||||||
|
token = requests.post(os.getenv("API_URL") + '/user/login', json={"email": username, "password": password}).json()['data']['token']
|
||||||
|
|
||||||
|
response = requests.get(os.getenv("API_URL") + '/symbols', headers={'Authorization': 'Bearer ' + token}).json()['data']
|
||||||
|
|
||||||
|
symbols = split(response, int(len(response) / 5))
|
||||||
|
for symbol_list in symbols:
|
||||||
|
for symbol in symbol_list:
|
||||||
|
x = threading.Thread(target=thread_function, args=(symbol,))
|
||||||
|
x.start()
|
||||||
|
|
||||||
|
time.sleep(10)
|
@ -11,3 +11,6 @@ bcrypt==3.2.0
|
|||||||
pytest~=7.1.1
|
pytest~=7.1.1
|
||||||
pytest-cov
|
pytest-cov
|
||||||
marshmallow~=3.15.0
|
marshmallow~=3.15.0
|
||||||
|
faker~=13.3.4
|
||||||
|
yfinance~=0.1.70
|
||||||
|
requests~=2.27.1
|
@ -1,3 +1,9 @@
|
|||||||
|
__author__ = "Florian Kaiser"
|
||||||
|
__copyright__ = "Copyright 2022, Project Aktienbot"
|
||||||
|
__credits__ = ["Florian Kaiser", "Florian Kellermann", "Linus Eickhof", "Kevin Pauer"]
|
||||||
|
__license__ = "GPL 3.0"
|
||||||
|
__version__ = "1.0.0"
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from app import create_app, db
|
from app import create_app, db
|
||||||
from app.models import User, Transaction, Keyword, Share
|
from app.models import User, Transaction, Keyword, Share
|
||||||
|
@ -0,0 +1,5 @@
|
|||||||
|
__author__ = "Florian Kaiser"
|
||||||
|
__copyright__ = "Copyright 2022, Project Aktienbot"
|
||||||
|
__credits__ = ["Florian Kaiser", "Florian Kellermann", "Linus Eickhof", "Kevin Pauer"]
|
||||||
|
__license__ = "GPL 3.0"
|
||||||
|
__version__ = "1.0.0"
|
@ -1,3 +1,9 @@
|
|||||||
|
__author__ = "Florian Kaiser"
|
||||||
|
__copyright__ = "Copyright 2022, Project Aktienbot"
|
||||||
|
__credits__ = ["Florian Kaiser", "Florian Kellermann", "Linus Eickhof", "Kevin Pauer"]
|
||||||
|
__license__ = "GPL 3.0"
|
||||||
|
__version__ = "1.0.0"
|
||||||
|
|
||||||
import json
|
import json
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,3 +1,9 @@
|
|||||||
|
__author__ = "Florian Kaiser"
|
||||||
|
__copyright__ = "Copyright 2022, Project Aktienbot"
|
||||||
|
__credits__ = ["Florian Kaiser", "Florian Kellermann", "Linus Eickhof", "Kevin Pauer"]
|
||||||
|
__license__ = "GPL 3.0"
|
||||||
|
__version__ = "1.0.0"
|
||||||
|
|
||||||
"""
|
"""
|
||||||
This file (test_keyword.py) contains the functional tests for the `keyword` blueprint.
|
This file (test_keyword.py) contains the functional tests for the `keyword` blueprint.
|
||||||
"""
|
"""
|
||||||
|
@ -1,3 +1,9 @@
|
|||||||
|
__author__ = "Florian Kaiser"
|
||||||
|
__copyright__ = "Copyright 2022, Project Aktienbot"
|
||||||
|
__credits__ = ["Florian Kaiser", "Florian Kellermann", "Linus Eickhof", "Kevin Pauer"]
|
||||||
|
__license__ = "GPL 3.0"
|
||||||
|
__version__ = "1.0.0"
|
||||||
|
|
||||||
"""
|
"""
|
||||||
This file (test_portfolio.py) contains the functional tests for the `portfolio` blueprint.
|
This file (test_portfolio.py) contains the functional tests for the `portfolio` blueprint.
|
||||||
"""
|
"""
|
||||||
|
@ -1,3 +1,9 @@
|
|||||||
|
__author__ = "Florian Kaiser"
|
||||||
|
__copyright__ = "Copyright 2022, Project Aktienbot"
|
||||||
|
__credits__ = ["Florian Kaiser", "Florian Kellermann", "Linus Eickhof", "Kevin Pauer"]
|
||||||
|
__license__ = "GPL 3.0"
|
||||||
|
__version__ = "1.0.0"
|
||||||
|
|
||||||
"""
|
"""
|
||||||
This file (test_share.py) contains the functional tests for the `share` blueprint.
|
This file (test_share.py) contains the functional tests for the `share` blueprint.
|
||||||
"""
|
"""
|
||||||
|
@ -1,3 +1,9 @@
|
|||||||
|
__author__ = "Florian Kaiser"
|
||||||
|
__copyright__ = "Copyright 2022, Project Aktienbot"
|
||||||
|
__credits__ = ["Florian Kaiser", "Florian Kellermann", "Linus Eickhof", "Kevin Pauer"]
|
||||||
|
__license__ = "GPL 3.0"
|
||||||
|
__version__ = "1.0.0"
|
||||||
|
|
||||||
"""
|
"""
|
||||||
This file (test_telegram.py) contains the functional tests for the `telegram` blueprint.
|
This file (test_telegram.py) contains the functional tests for the `telegram` blueprint.
|
||||||
"""
|
"""
|
||||||
|
@ -1,3 +1,9 @@
|
|||||||
|
__author__ = "Florian Kaiser"
|
||||||
|
__copyright__ = "Copyright 2022, Project Aktienbot"
|
||||||
|
__credits__ = ["Florian Kaiser", "Florian Kellermann", "Linus Eickhof", "Kevin Pauer"]
|
||||||
|
__license__ = "GPL 3.0"
|
||||||
|
__version__ = "1.0.0"
|
||||||
|
|
||||||
"""
|
"""
|
||||||
This file (test_transaction.py) contains the functional tests for the `transaction` blueprint.
|
This file (test_transaction.py) contains the functional tests for the `transaction` blueprint.
|
||||||
"""
|
"""
|
||||||
|
@ -1,7 +1,14 @@
|
|||||||
|
__author__ = "Florian Kaiser"
|
||||||
|
__copyright__ = "Copyright 2022, Project Aktienbot"
|
||||||
|
__credits__ = ["Florian Kaiser", "Florian Kellermann", "Linus Eickhof", "Kevin Pauer"]
|
||||||
|
__license__ = "GPL 3.0"
|
||||||
|
__version__ = "1.0.0"
|
||||||
|
|
||||||
"""
|
"""
|
||||||
This file (test_user.py) contains the functional tests for the `users` blueprint.
|
This file (test_user.py) contains the functional tests for the `users` blueprint.
|
||||||
"""
|
"""
|
||||||
import json
|
import json
|
||||||
|
|
||||||
from tests.functional.helper_functions import get_token
|
from tests.functional.helper_functions import get_token
|
||||||
|
|
||||||
|
|
||||||
@ -35,7 +42,7 @@ def test_login_user_not_exist(test_client, init_database):
|
|||||||
"""
|
"""
|
||||||
response = test_client.post('/api/user/login', data=json.dumps(dict(email="notexistinguser@example.com", password="password")), content_type='application/json')
|
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 response.status_code == 500
|
||||||
assert b'Unable to login' in response.data
|
assert b'Can\'t find user' in response.data
|
||||||
|
|
||||||
|
|
||||||
def test_login_email_missing(test_client, init_database):
|
def test_login_email_missing(test_client, init_database):
|
||||||
@ -407,7 +414,7 @@ def test_set_admin_admin1_logged_in_user_not_exist(test_client, init_database):
|
|||||||
headers={"Authorization": "Bearer {}".format(get_token(test_client, "admin1@example.com", "admin1"))},
|
headers={"Authorization": "Bearer {}".format(get_token(test_client, "admin1@example.com", "admin1"))},
|
||||||
content_type='application/json')
|
content_type='application/json')
|
||||||
assert response.status_code == 500
|
assert response.status_code == 500
|
||||||
assert b'Unable to update user' in response.data
|
assert b'Can\'t find user' in response.data
|
||||||
|
|
||||||
|
|
||||||
def test_set_admin_admin1_logged_in_email_missing(test_client, init_database):
|
def test_set_admin_admin1_logged_in_email_missing(test_client, init_database):
|
||||||
@ -466,6 +473,94 @@ def test_set_admin_admin1_logged_in_admin_empty(test_client, init_database):
|
|||||||
assert b'Field may not be null' in response.data
|
assert b'Field may not be null' in response.data
|
||||||
|
|
||||||
|
|
||||||
|
def test_set_cron_user_not_logged_in(test_client, init_database):
|
||||||
|
"""
|
||||||
|
Test PUT '/api/user/setCron'
|
||||||
|
|
||||||
|
User is not logged in
|
||||||
|
"""
|
||||||
|
response = test_client.put('/api/user/setCron')
|
||||||
|
assert response.status_code == 401
|
||||||
|
assert b'Unauthorized' in response.data
|
||||||
|
|
||||||
|
|
||||||
|
def test_set_cron_user1_logged_in(test_client, init_database):
|
||||||
|
"""
|
||||||
|
Test PUT '/api/user/setCron'
|
||||||
|
|
||||||
|
User1 is logged in
|
||||||
|
"""
|
||||||
|
response = test_client.put('/api/user/setCron', data=json.dumps(dict(cron="* * * * *")),
|
||||||
|
headers={"Authorization": "Bearer {}".format(get_token(test_client, "user1@example.com", "password"))},
|
||||||
|
content_type='application/json')
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert b'Successfully updated users cron' in response.data
|
||||||
|
|
||||||
|
|
||||||
|
def test_set_empty_cron_user1_logged_in(test_client, init_database):
|
||||||
|
"""
|
||||||
|
Test PUT '/api/user/setCron'
|
||||||
|
|
||||||
|
User1 is logged in
|
||||||
|
Interval is empty
|
||||||
|
"""
|
||||||
|
response = test_client.put('/api/user/setCron', data=json.dumps(dict(cron="")),
|
||||||
|
headers={"Authorization": "Bearer {}".format(get_token(test_client, "user1@example.com", "password"))},
|
||||||
|
content_type='application/json')
|
||||||
|
assert response.status_code == 400
|
||||||
|
|
||||||
|
|
||||||
|
def test_set_cron_bot_logged_in_user_exists(test_client, init_database):
|
||||||
|
"""
|
||||||
|
Test PUT '/api/user/setCron'
|
||||||
|
|
||||||
|
Bot1 is logged in and requests user 12345678
|
||||||
|
"""
|
||||||
|
response = test_client.put('/api/user/setCron', data=json.dumps(dict(cron="* * * * *")),
|
||||||
|
headers={"Authorization": "Bearer {}".format(get_token(test_client, "bot1@example.com", "bot1") + ":12345678")},
|
||||||
|
content_type='application/json')
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert b'Successfully updated users cron' in response.data
|
||||||
|
|
||||||
|
|
||||||
|
def test_set_cron_bot_logged_in_user_not_exists(test_client, init_database):
|
||||||
|
"""
|
||||||
|
Test PUT '/api/user/setCron'
|
||||||
|
|
||||||
|
Bot1 is logged in and requests user 1234 (not existing)
|
||||||
|
"""
|
||||||
|
response = test_client.put('/api/user/setCron', data=json.dumps(dict(cron="* * * * *")),
|
||||||
|
headers={"Authorization": "Bearer {}".format(get_token(test_client, "bot1@example.com", "bot1") + ":1234")},
|
||||||
|
content_type='application/json')
|
||||||
|
assert response.status_code == 401
|
||||||
|
assert b'Unable to login' in response.data
|
||||||
|
|
||||||
|
|
||||||
|
def test_set_cron_user1_logged_in_but_no_bot(test_client, init_database):
|
||||||
|
"""
|
||||||
|
Test PUT '/api/user/setCron'
|
||||||
|
|
||||||
|
User1 is logged in and requests user 1234 (not existing)
|
||||||
|
Fails because user1 is not a bot
|
||||||
|
"""
|
||||||
|
response = test_client.put('/api/user/setCron', data=json.dumps(dict(cron="* * * * *")),
|
||||||
|
headers={"Authorization": "Bearer {}".format(get_token(test_client, "user1@example.com", "password") + ":12345678")},
|
||||||
|
content_type='application/json')
|
||||||
|
assert response.status_code == 401
|
||||||
|
assert b'Unable to login' in response.data
|
||||||
|
|
||||||
|
|
||||||
|
def test_set_cron_invalid_token(test_client, init_database):
|
||||||
|
"""
|
||||||
|
Test PUT '/api/user/setCron'
|
||||||
|
|
||||||
|
Invalid Bearer token
|
||||||
|
"""
|
||||||
|
response = test_client.put('/api/user/setCron', headers={"Authorization": "Bearer {}".format("invalidtoken:12345678")})
|
||||||
|
assert response.status_code == 401
|
||||||
|
assert b'Unauthorized' in response.data
|
||||||
|
|
||||||
|
|
||||||
def test_get_users_not_logged_in(test_client, init_database):
|
def test_get_users_not_logged_in(test_client, init_database):
|
||||||
"""
|
"""
|
||||||
Test GET '/api/users'
|
Test GET '/api/users'
|
||||||
|
@ -0,0 +1,5 @@
|
|||||||
|
__author__ = "Florian Kaiser"
|
||||||
|
__copyright__ = "Copyright 2022, Project Aktienbot"
|
||||||
|
__credits__ = ["Florian Kaiser", "Florian Kellermann", "Linus Eickhof", "Kevin Pauer"]
|
||||||
|
__license__ = "GPL 3.0"
|
||||||
|
__version__ = "1.0.0"
|
@ -1,3 +1,9 @@
|
|||||||
|
__author__ = "Florian Kaiser"
|
||||||
|
__copyright__ = "Copyright 2022, Project Aktienbot"
|
||||||
|
__credits__ = ["Florian Kaiser", "Florian Kellermann", "Linus Eickhof", "Kevin Pauer"]
|
||||||
|
__license__ = "GPL 3.0"
|
||||||
|
__version__ = "1.0.0"
|
||||||
|
|
||||||
"""
|
"""
|
||||||
This file (test_helper_functions.py) contains the unit tests for the helper_functions.py file.
|
This file (test_helper_functions.py) contains the unit tests for the helper_functions.py file.
|
||||||
"""
|
"""
|
||||||
|
@ -1,3 +1,9 @@
|
|||||||
|
__author__ = "Florian Kaiser"
|
||||||
|
__copyright__ = "Copyright 2022, Project Aktienbot"
|
||||||
|
__credits__ = ["Florian Kaiser", "Florian Kellermann", "Linus Eickhof", "Kevin Pauer"]
|
||||||
|
__license__ = "GPL 3.0"
|
||||||
|
__version__ = "1.0.0"
|
||||||
|
|
||||||
"""
|
"""
|
||||||
This file (test_helper_functions.py) contains the unit tests for the helper_functions.py file.
|
This file (test_helper_functions.py) contains the unit tests for the helper_functions.py file.
|
||||||
"""
|
"""
|
||||||
@ -18,3 +24,10 @@ def test_check_password():
|
|||||||
hashed = hash_password("password")
|
hashed = hash_password("password")
|
||||||
assert check_password(hashed, "password".encode("utf-8")) is True
|
assert check_password(hashed, "password".encode("utf-8")) is True
|
||||||
assert check_password(hashed, "password1".encode("utf-8")) is False
|
assert check_password(hashed, "password1".encode("utf-8")) is False
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_email_from_token():
|
||||||
|
"""
|
||||||
|
Test get_email_from_token function
|
||||||
|
"""
|
||||||
|
assert get_email_from_token_data(None) is None
|
||||||
|
@ -1,9 +1,14 @@
|
|||||||
|
__author__ = "Florian Kaiser"
|
||||||
|
__copyright__ = "Copyright 2022, Project Aktienbot"
|
||||||
|
__credits__ = ["Florian Kaiser", "Florian Kellermann", "Linus Eickhof", "Kevin Pauer"]
|
||||||
|
__license__ = "GPL 3.0"
|
||||||
|
__version__ = "1.0.0"
|
||||||
|
|
||||||
"""
|
"""
|
||||||
This file (test_models.py) contains the unit tests for the models.py file.
|
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
|
from app.helper_functions import hash_password
|
||||||
|
from app.models import User, Transaction, Keyword, Share
|
||||||
|
|
||||||
|
|
||||||
def test_new_user():
|
def test_new_user():
|
||||||
@ -16,14 +21,15 @@ def test_new_user():
|
|||||||
email="user@example.com",
|
email="user@example.com",
|
||||||
username="user",
|
username="user",
|
||||||
password=hash_password("password"),
|
password=hash_password("password"),
|
||||||
admin=False
|
admin=False,
|
||||||
|
cron="0 8 * * *",
|
||||||
)
|
)
|
||||||
assert user.email == 'user@example.com'
|
assert user.email == 'user@example.com'
|
||||||
assert user.password != 'password'
|
assert user.password != 'password'
|
||||||
assert user.username == "user"
|
assert user.username == "user"
|
||||||
assert user.telegram_user_id is None
|
assert user.telegram_user_id is None
|
||||||
assert user.admin is False
|
assert user.admin is False
|
||||||
assert user.as_dict() == {'email': 'user@example.com', 'username': 'user', 'telegram_user_id': None, 'admin': False}
|
assert user.as_dict() == {'cron': '0 8 * * *', 'email': 'user@example.com', 'username': 'user', 'telegram_user_id': None, 'admin': False}
|
||||||
|
|
||||||
|
|
||||||
def test_new_user_with_fixture(new_user):
|
def test_new_user_with_fixture(new_user):
|
||||||
|
@ -1,3 +1,9 @@
|
|||||||
|
__author__ = "Florian Kaiser"
|
||||||
|
__copyright__ = "Copyright 2022, Project Aktienbot"
|
||||||
|
__credits__ = ["Florian Kaiser", "Florian Kellermann", "Linus Eickhof", "Kevin Pauer"]
|
||||||
|
__license__ = "GPL 3.0"
|
||||||
|
__version__ = "1.0.0"
|
||||||
|
|
||||||
"""
|
"""
|
||||||
This file (test_transaction.py) contains the unit tests for the blueprints/transaction.py file.
|
This file (test_transaction.py) contains the unit tests for the blueprints/transaction.py file.
|
||||||
"""
|
"""
|
||||||
|
@ -1,3 +1,9 @@
|
|||||||
|
__author__ = "Florian Kaiser"
|
||||||
|
__copyright__ = "Copyright 2022, Project Aktienbot"
|
||||||
|
__credits__ = ["Florian Kaiser", "Florian Kellermann", "Linus Eickhof", "Kevin Pauer"]
|
||||||
|
__license__ = "GPL 3.0"
|
||||||
|
__version__ = "1.0.0"
|
||||||
|
|
||||||
"""
|
"""
|
||||||
This file (test_helper_functions.py) contains the unit tests for the helper_functions.py file.
|
This file (test_helper_functions.py) contains the unit tests for the helper_functions.py file.
|
||||||
"""
|
"""
|
||||||
@ -38,3 +44,12 @@ def test_check_if_admin_data_exists():
|
|||||||
assert check_if_admin_data_exists(dict(admin=True)) is True
|
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(admin=None)) is False
|
||||||
assert check_if_admin_data_exists(dict()) is False
|
assert check_if_admin_data_exists(dict()) is False
|
||||||
|
|
||||||
|
|
||||||
|
def test_check_if_cron_data_exists():
|
||||||
|
"""
|
||||||
|
Test check_if_cron_data_exists function
|
||||||
|
"""
|
||||||
|
assert check_if_cron_data_exists(dict(cron="* * * * *")) is True
|
||||||
|
assert check_if_cron_data_exists(dict(cron="")) is False
|
||||||
|
assert check_if_cron_data_exists(dict()) is False
|
||||||
|
@ -5,7 +5,7 @@ services:
|
|||||||
image: registry.flokaiser.com/aktienbot/frontend
|
image: registry.flokaiser.com/aktienbot/frontend
|
||||||
labels:
|
labels:
|
||||||
traefik.enable: 'true'
|
traefik.enable: 'true'
|
||||||
traefik.http.routers.aktienbot_fe.rule: Host(`gruppe1.testsites.info`)
|
traefik.http.routers.aktienbot_fe.rule: Host(`gruppe1.testsites.info`) && !PathPrefix(`/api`) && !PathPrefix(`/phpmyadmin`) && !PathPrefix(`/portainer`)
|
||||||
traefik.http.routers.aktienbot_fe.middlewares: secHeaders@file
|
traefik.http.routers.aktienbot_fe.middlewares: secHeaders@file
|
||||||
traefik.http.routers.aktienbot_fe.priority: 40
|
traefik.http.routers.aktienbot_fe.priority: 40
|
||||||
traefik.http.routers.aktienbot_fe.tls: true
|
traefik.http.routers.aktienbot_fe.tls: true
|
||||||
|
@ -12,6 +12,41 @@ services:
|
|||||||
- ${PWD}/traefik-dynamic.toml:/etc/traefik/traefik-dynamic.toml
|
- ${PWD}/traefik-dynamic.toml:/etc/traefik/traefik-dynamic.toml
|
||||||
- ${PWD}/acme.json:/etc/traefik/acme.json
|
- ${PWD}/acme.json:/etc/traefik/acme.json
|
||||||
- ${PWD}/access.log:/etc/traefik/access.log
|
- ${PWD}/access.log:/etc/traefik/access.log
|
||||||
|
- ${PWD}/users:/etc/traefik/users
|
||||||
|
|
||||||
|
goaccess:
|
||||||
|
image: allinurl/goaccess
|
||||||
|
command:
|
||||||
|
- --no-global-config
|
||||||
|
- --config-file=/srv/data/goaccess.conf
|
||||||
|
- --num-tests=0
|
||||||
|
volumes:
|
||||||
|
- ${PWD}/access.log:/srv/logs/access.log:ro
|
||||||
|
- ${PWD}/goaccess.conf:/srv/data/goaccess.conf
|
||||||
|
- goaccess_data:/srv/data
|
||||||
|
- goaccess_report:/srv/report
|
||||||
|
labels:
|
||||||
|
traefik.enable: true
|
||||||
|
traefik.http.routers.goaccess.rule: Host(`gruppe1.testsites.info`) && PathPrefix(`/goaccess/ws`)
|
||||||
|
traefik.http.routers.goaccess.priority: 55
|
||||||
|
traefik.http.routers.goaccess.middlewares: strip_goaccess,goaccess_auth,secHeaders@file
|
||||||
|
traefik.http.routers.goaccess.tls: true
|
||||||
|
traefik.http.routers.goaccess.tls.certresolver: myresolver
|
||||||
|
|
||||||
|
nginx:
|
||||||
|
image: nginx
|
||||||
|
volumes:
|
||||||
|
- goaccess_report:/usr/share/nginx/html
|
||||||
|
labels:
|
||||||
|
traefik.enable: true
|
||||||
|
traefik.http.routers.goaccess_web.rule: Host(`gruppe1.testsites.info`) && PathPrefix(`/goaccess`)
|
||||||
|
traefik.http.routers.goaccess_web.priority: 50
|
||||||
|
traefik.http.routers.goaccess_web.middlewares: strip_goaccess,goaccess_auth,secHeaders@file
|
||||||
|
traefik.http.routers.goaccess_web.tls: true
|
||||||
|
traefik.http.routers.goaccess_web.tls.certresolver: myresolver
|
||||||
|
|
||||||
|
traefik.http.middlewares.strip_goaccess.stripprefix.prefixes: /goaccess
|
||||||
|
traefik.http.middlewares.goaccess_auth.basicauth.usersfile: /etc/traefik/users
|
||||||
|
|
||||||
portainer:
|
portainer:
|
||||||
image: portainer/portainer-ce
|
image: portainer/portainer-ce
|
||||||
@ -36,3 +71,5 @@ networks:
|
|||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
portainer_data:
|
portainer_data:
|
||||||
|
goaccess_report:
|
||||||
|
goaccess_data:
|
5
deploy/base/goaccess.conf
Normal file
5
deploy/base/goaccess.conf
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
log-format COMMON
|
||||||
|
log-file /srv/logs/access.log
|
||||||
|
output /srv/report/index.html
|
||||||
|
real-time-html true
|
||||||
|
ws-url wss://gruppe1.testsites.info:443/goaccess/ws
|
1
deploy/base/users
Normal file
1
deploy/base/users
Normal file
@ -0,0 +1 @@
|
|||||||
|
# Create user by using ```htpasswd -nb user password``` and append output to this file.
|
@ -1,10 +1,43 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { Stock } from '../Models/stock.model';
|
import { BotService } from '../Services/bot.service';
|
||||||
|
import { Keyword, Share } from '../Views/bot-settings/bot-settings.component';
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root',
|
||||||
})
|
})
|
||||||
export class HelperService {
|
export class HelperService {
|
||||||
|
constructor(private botService: BotService) {}
|
||||||
|
|
||||||
constructor() { }
|
/**
|
||||||
|
* @param {number} ms
|
||||||
|
*/
|
||||||
|
delay(ms: number) {
|
||||||
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||||
|
}
|
||||||
|
|
||||||
|
formatShareData(): Share[] {
|
||||||
|
var shares: Share[] = [];
|
||||||
|
this.botService.getSymbols().subscribe((result) => {
|
||||||
|
var data = JSON.parse(result);
|
||||||
|
for (let i = 0; i < data.data.length; i++) {
|
||||||
|
shares.push({
|
||||||
|
symbol: data.data[i].symbol,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return shares;
|
||||||
|
}
|
||||||
|
|
||||||
|
formatKeywordsData(): Keyword[] {
|
||||||
|
var keywords: Keyword[] = [];
|
||||||
|
this.botService.getKeywords().subscribe((result) => {
|
||||||
|
var data = JSON.parse(result);
|
||||||
|
for (let i = 0; i < data.data.length; i++) {
|
||||||
|
keywords.push({
|
||||||
|
name: data.data[i].keyword,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return keywords;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +0,0 @@
|
|||||||
export class Stock {
|
|
||||||
count = 0;
|
|
||||||
price = 0;
|
|
||||||
symbol = '';
|
|
||||||
time = '';
|
|
||||||
}
|
|
@ -1,7 +1,8 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { HttpClient, HttpHeaders } from '@angular/common/http';
|
import { HttpClient, HttpHeaders } from '@angular/common/http';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
const AUTH_API = 'https://aktienbot.flokaiser.com/api/user/';
|
const AUTH_API = 'https://gruppe1.testsites.info/api/user';
|
||||||
|
|
||||||
const httpOptions = {
|
const httpOptions = {
|
||||||
headers: new HttpHeaders({ 'Content-Type': 'application/json' }),
|
headers: new HttpHeaders({ 'Content-Type': 'application/json' }),
|
||||||
};
|
};
|
||||||
@ -11,12 +12,25 @@ const httpOptions = {
|
|||||||
})
|
})
|
||||||
export class AuthService {
|
export class AuthService {
|
||||||
constructor(private http: HttpClient) {}
|
constructor(private http: HttpClient) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} email
|
||||||
|
* @param {string} password
|
||||||
|
* @returns Observable
|
||||||
|
*/
|
||||||
login(email: string, password: string): Observable<any> {
|
login(email: string, password: string): Observable<any> {
|
||||||
return this.http.post(AUTH_API + '/login', {
|
return this.http.post(AUTH_API + '/login', {
|
||||||
email,
|
email,
|
||||||
password,
|
password,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} email
|
||||||
|
* @param {string} username
|
||||||
|
* @param {string} password
|
||||||
|
* @returns Observable
|
||||||
|
*/
|
||||||
register(email: string, username: string, password: string): Observable<any> {
|
register(email: string, username: string, password: string): Observable<any> {
|
||||||
return this.http.post(
|
return this.http.post(
|
||||||
AUTH_API + '/register',
|
AUTH_API + '/register',
|
||||||
|
16
frontend/src/app/Services/bot.service.spec.ts
Normal file
16
frontend/src/app/Services/bot.service.spec.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import { TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { BotService } from './bot.service';
|
||||||
|
|
||||||
|
describe('BotService', () => {
|
||||||
|
let service: BotService;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.configureTestingModule({});
|
||||||
|
service = TestBed.inject(BotService);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be created', () => {
|
||||||
|
expect(service).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
112
frontend/src/app/Services/bot.service.ts
Normal file
112
frontend/src/app/Services/bot.service.ts
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
import { HttpClient, HttpHeaders } from '@angular/common/http';
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
import { TokenStorageService } from './token.service';
|
||||||
|
|
||||||
|
const API_URL = 'https://gruppe1.testsites.info/api/';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root',
|
||||||
|
})
|
||||||
|
export class BotService {
|
||||||
|
constructor(
|
||||||
|
private http: HttpClient,
|
||||||
|
private tokenStorage: TokenStorageService
|
||||||
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns Observable
|
||||||
|
*/
|
||||||
|
public getKeywords(): Observable<any> {
|
||||||
|
return this.http.get(API_URL + 'keywords', {
|
||||||
|
headers: new HttpHeaders({
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
Authorization: 'Bearer ' + this.tokenStorage.getToken(),
|
||||||
|
}),
|
||||||
|
responseType: 'text',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} keyword
|
||||||
|
* @returns Observable
|
||||||
|
*/
|
||||||
|
public createKeyword(keyword: string): Observable<any> {
|
||||||
|
return this.http.post(
|
||||||
|
API_URL + 'keyword',
|
||||||
|
{
|
||||||
|
keyword,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headers: new HttpHeaders({
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
Authorization: 'Bearer ' + this.tokenStorage.getToken(),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} keyword
|
||||||
|
* @returns Observable
|
||||||
|
*/
|
||||||
|
public deleteKeyword(keyword: string): Observable<any> {
|
||||||
|
return this.http.delete(API_URL + 'keyword', {
|
||||||
|
headers: new HttpHeaders({
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
Authorization: 'Bearer ' + this.tokenStorage.getToken(),
|
||||||
|
}),
|
||||||
|
body: {
|
||||||
|
keyword,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns Observable
|
||||||
|
*/
|
||||||
|
public getSymbols(): Observable<any> {
|
||||||
|
return this.http.get(API_URL + 'shares', {
|
||||||
|
headers: new HttpHeaders({
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
Authorization: 'Bearer ' + this.tokenStorage.getToken(),
|
||||||
|
}),
|
||||||
|
responseType: 'text',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} keyword
|
||||||
|
* @returns Observable
|
||||||
|
*/
|
||||||
|
public createShare(symbol: string): Observable<any> {
|
||||||
|
return this.http.post(
|
||||||
|
API_URL + 'share',
|
||||||
|
{
|
||||||
|
symbol,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headers: new HttpHeaders({
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
Authorization: 'Bearer ' + this.tokenStorage.getToken(),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} share
|
||||||
|
* @returns Observable
|
||||||
|
*/
|
||||||
|
public deleteShare(share: string): Observable<any> {
|
||||||
|
return this.http.delete(API_URL + 'share', {
|
||||||
|
headers: new HttpHeaders({
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
Authorization: 'Bearer ' + this.tokenStorage.getToken(),
|
||||||
|
}),
|
||||||
|
body: {
|
||||||
|
share,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -2,16 +2,23 @@ import { Injectable, OnInit } from '@angular/core';
|
|||||||
import { HttpClient, HttpHeaders } from '@angular/common/http';
|
import { HttpClient, HttpHeaders } from '@angular/common/http';
|
||||||
import { delay, Observable } from 'rxjs';
|
import { delay, Observable } from 'rxjs';
|
||||||
import { TokenStorageService } from './token.service';
|
import { TokenStorageService } from './token.service';
|
||||||
const API_URL = 'https://aktienbot.flokaiser.com/api/';
|
const API_URL = 'https://gruppe1.testsites.info/api/';
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root',
|
providedIn: 'root',
|
||||||
})
|
})
|
||||||
export class DataService {
|
export class DataService {
|
||||||
|
/**
|
||||||
|
* @param {HttpClient} privatehttp
|
||||||
|
* @param {TokenStorageService} privatetokenStorage
|
||||||
|
*/
|
||||||
constructor(
|
constructor(
|
||||||
private http: HttpClient,
|
private http: HttpClient,
|
||||||
private tokenStorage: TokenStorageService
|
private tokenStorage: TokenStorageService
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns Observable
|
||||||
|
*/
|
||||||
public getStockData(): Observable<any> {
|
public getStockData(): Observable<any> {
|
||||||
return this.http.get(API_URL + 'portfolio', {
|
return this.http.get(API_URL + 'portfolio', {
|
||||||
headers: new HttpHeaders({
|
headers: new HttpHeaders({
|
||||||
@ -22,6 +29,9 @@ export class DataService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns Observable
|
||||||
|
*/
|
||||||
public getTransactionData(): Observable<any> {
|
public getTransactionData(): Observable<any> {
|
||||||
return this.http.get(API_URL + 'transactions', {
|
return this.http.get(API_URL + 'transactions', {
|
||||||
headers: new HttpHeaders({
|
headers: new HttpHeaders({
|
||||||
@ -32,6 +42,41 @@ export class DataService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} symbol
|
||||||
|
* @param {Date} time
|
||||||
|
* @param {number} count
|
||||||
|
* @param {number} price
|
||||||
|
* @returns Observable
|
||||||
|
*/
|
||||||
|
public createTransaction(
|
||||||
|
symbol: string,
|
||||||
|
time: string,
|
||||||
|
count: number,
|
||||||
|
price: number
|
||||||
|
): Observable<any> {
|
||||||
|
time = time + 'T12:00:00.000Z';
|
||||||
|
price.toFixed(2);
|
||||||
|
return this.http.post(
|
||||||
|
API_URL + 'transaction',
|
||||||
|
{
|
||||||
|
count,
|
||||||
|
price,
|
||||||
|
symbol,
|
||||||
|
time,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headers: new HttpHeaders({
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
Authorization: 'Bearer ' + this.tokenStorage.getToken(),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns Observable
|
||||||
|
*/
|
||||||
public getKeywords(): Observable<any> {
|
public getKeywords(): Observable<any> {
|
||||||
return this.http.get(API_URL + 'keywords', {
|
return this.http.get(API_URL + 'keywords', {
|
||||||
headers: new HttpHeaders({
|
headers: new HttpHeaders({
|
||||||
|
16
frontend/src/app/Services/profile.service.spec.ts
Normal file
16
frontend/src/app/Services/profile.service.spec.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import { TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { ProfileService } from './profile.service';
|
||||||
|
|
||||||
|
describe('ProfileService', () => {
|
||||||
|
let service: ProfileService;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.configureTestingModule({});
|
||||||
|
service = TestBed.inject(ProfileService);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be created', () => {
|
||||||
|
expect(service).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
69
frontend/src/app/Services/profile.service.ts
Normal file
69
frontend/src/app/Services/profile.service.ts
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
import { HttpClient, HttpHeaders } from '@angular/common/http';
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
import { TokenStorageService } from './token.service';
|
||||||
|
|
||||||
|
const API_URL = 'https://gruppe1.testsites.info/api/';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root',
|
||||||
|
})
|
||||||
|
export class ProfileService {
|
||||||
|
constructor(
|
||||||
|
private tokenStorage: TokenStorageService,
|
||||||
|
private http: HttpClient
|
||||||
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns Observable
|
||||||
|
*/
|
||||||
|
public getUserData(): Observable<any> {
|
||||||
|
return this.http.get(API_URL + 'user', {
|
||||||
|
headers: new HttpHeaders({
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
Authorization: 'Bearer ' + this.tokenStorage.getToken(),
|
||||||
|
}),
|
||||||
|
responseType: 'text',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} username
|
||||||
|
* @param {number} password
|
||||||
|
* @returns Observable
|
||||||
|
*/
|
||||||
|
public updateProfile(username: string, password: number): Observable<any> {
|
||||||
|
return this.http.put(
|
||||||
|
API_URL + 'user',
|
||||||
|
{
|
||||||
|
username,
|
||||||
|
password,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headers: new HttpHeaders({
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
Authorization: 'Bearer ' + this.tokenStorage.getToken(),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} telegramUserID
|
||||||
|
* @returns Observable
|
||||||
|
*/
|
||||||
|
public addTelegramId(telegram_user_id: string): Observable<any> {
|
||||||
|
return this.http.post(
|
||||||
|
API_URL + 'telegram',
|
||||||
|
{
|
||||||
|
telegram_user_id,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headers: new HttpHeaders({
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
Authorization: 'Bearer ' + this.tokenStorage.getToken(),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -1,13 +1,13 @@
|
|||||||
import { TestBed } from '@angular/core/testing';
|
import { TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
import { TokenService } from './token.service';
|
import { TokenStorageService } from './token.service';
|
||||||
|
|
||||||
describe('TokenService', () => {
|
describe('TokenStorageService', () => {
|
||||||
let service: TokenService;
|
let service: TokenStorageService;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
TestBed.configureTestingModule({});
|
TestBed.configureTestingModule({});
|
||||||
service = TestBed.inject(TokenService);
|
service = TestBed.inject(TokenStorageService);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should be created', () => {
|
it('should be created', () => {
|
||||||
|
@ -6,20 +6,42 @@ const USER_KEY = 'auth-user';
|
|||||||
})
|
})
|
||||||
export class TokenStorageService {
|
export class TokenStorageService {
|
||||||
constructor() {}
|
constructor() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns void
|
||||||
|
*/
|
||||||
signOut(): void {
|
signOut(): void {
|
||||||
window.sessionStorage.clear();
|
window.sessionStorage.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} token
|
||||||
|
* @returns void
|
||||||
|
*/
|
||||||
public saveToken(token: string): void {
|
public saveToken(token: string): void {
|
||||||
window.sessionStorage.removeItem(TOKEN_KEY);
|
window.sessionStorage.removeItem(TOKEN_KEY);
|
||||||
window.sessionStorage.setItem(TOKEN_KEY, token);
|
window.sessionStorage.setItem(TOKEN_KEY, token);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns string
|
||||||
|
*/
|
||||||
public getToken(): string | null {
|
public getToken(): string | null {
|
||||||
return window.sessionStorage.getItem(TOKEN_KEY);
|
return window.sessionStorage.getItem(TOKEN_KEY);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {any} user
|
||||||
|
* @returns void
|
||||||
|
*/
|
||||||
public saveUser(user: any): void {
|
public saveUser(user: any): void {
|
||||||
window.sessionStorage.removeItem(USER_KEY);
|
window.sessionStorage.removeItem(USER_KEY);
|
||||||
window.sessionStorage.setItem(USER_KEY, JSON.stringify(user));
|
window.sessionStorage.setItem(USER_KEY, JSON.stringify(user));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns any
|
||||||
|
*/
|
||||||
public getUser(): any {
|
public getUser(): any {
|
||||||
const user = window.sessionStorage.getItem(USER_KEY);
|
const user = window.sessionStorage.getItem(USER_KEY);
|
||||||
if (user) {
|
if (user) {
|
||||||
|
@ -1 +1,58 @@
|
|||||||
<p>bot-settings works!</p>
|
<mat-grid-list cols="2">
|
||||||
|
<mat-grid-tile>
|
||||||
|
<mat-card class="card">
|
||||||
|
<mat-card-title class="card-title">Keywords</mat-card-title>
|
||||||
|
<mat-card-content>
|
||||||
|
<mat-form-field class="example-chip-list" appearance="fill">
|
||||||
|
<mat-label>Keywords</mat-label>
|
||||||
|
<mat-chip-list #chipList aria-label="Keyword selection">
|
||||||
|
<mat-chip
|
||||||
|
*ngFor="let keyword of keywords"
|
||||||
|
(removed)="removeKeyword(keyword)"
|
||||||
|
>
|
||||||
|
{{ keyword.name }}
|
||||||
|
<button matChipRemove>
|
||||||
|
<mat-icon>cancel</mat-icon>
|
||||||
|
</button>
|
||||||
|
</mat-chip>
|
||||||
|
<input
|
||||||
|
placeholder="New keyword..."
|
||||||
|
[matChipInputFor]="chipList"
|
||||||
|
[matChipInputSeparatorKeyCodes]="separatorKeysCodes"
|
||||||
|
[matChipInputAddOnBlur]="addOnBlur"
|
||||||
|
(matChipInputTokenEnd)="addKeyword($event)"
|
||||||
|
/>
|
||||||
|
</mat-chip-list>
|
||||||
|
</mat-form-field>
|
||||||
|
</mat-card-content>
|
||||||
|
</mat-card>
|
||||||
|
</mat-grid-tile>
|
||||||
|
<mat-grid-tile>
|
||||||
|
<mat-card class="card">
|
||||||
|
<mat-card-title class="card-title">Shares</mat-card-title>
|
||||||
|
<mat-card-content>
|
||||||
|
<mat-form-field class="example-chip-list" appearance="fill">
|
||||||
|
<mat-label>Shares</mat-label>
|
||||||
|
<mat-chip-list #sharesList aria-label="Share selection">
|
||||||
|
<mat-chip
|
||||||
|
*ngFor="let share of shares"
|
||||||
|
(removed)="removeShare(share)"
|
||||||
|
>
|
||||||
|
{{ share.symbol }}
|
||||||
|
<button matChipRemove>
|
||||||
|
<mat-icon>cancel</mat-icon>
|
||||||
|
</button>
|
||||||
|
</mat-chip>
|
||||||
|
<input
|
||||||
|
placeholder="New share..."
|
||||||
|
[matChipInputFor]="sharesList"
|
||||||
|
[matChipInputSeparatorKeyCodes]="separatorKeysCodes"
|
||||||
|
[matChipInputAddOnBlur]="addOnBlur"
|
||||||
|
(matChipInputTokenEnd)="addShare($event)"
|
||||||
|
/>
|
||||||
|
</mat-chip-list>
|
||||||
|
</mat-form-field>
|
||||||
|
</mat-card-content>
|
||||||
|
</mat-card>
|
||||||
|
</mat-grid-tile>
|
||||||
|
</mat-grid-list>
|
||||||
|
@ -0,0 +1,26 @@
|
|||||||
|
.form {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
width: 90%;
|
||||||
|
height: 80%;
|
||||||
|
margin: 5%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.example-full-width {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-title {
|
||||||
|
padding-bottom: 2.5vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
mat-grid {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.example-chip-list {
|
||||||
|
width: 100%;
|
||||||
|
}
|
@ -1,15 +1,102 @@
|
|||||||
import { Component, OnInit } from '@angular/core';
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { C, COMMA, ENTER, F } from '@angular/cdk/keycodes';
|
||||||
|
import { MatChipInputEvent } from '@angular/material/chips';
|
||||||
|
import { BotService } from 'src/app/Services/bot.service';
|
||||||
|
import { HelperService } from 'src/app/Helpers/helper.service';
|
||||||
|
|
||||||
|
export interface Fruit {
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Share {
|
||||||
|
symbol: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Keyword {
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-bot-settings',
|
selector: 'app-bot-settings',
|
||||||
templateUrl: './bot-settings.component.html',
|
templateUrl: './bot-settings.component.html',
|
||||||
styleUrls: ['./bot-settings.component.scss']
|
styleUrls: ['./bot-settings.component.scss'],
|
||||||
})
|
})
|
||||||
export class BotSettingsComponent implements OnInit {
|
export class BotSettingsComponent implements OnInit {
|
||||||
|
keywords: Keyword[] = [];
|
||||||
|
shares: Share[] = [];
|
||||||
|
|
||||||
constructor() { }
|
constructor(private botService: BotService, private helper: HelperService) {}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
|
this.shares = this.helper.formatShareData();
|
||||||
|
this.keywords = this.helper.formatKeywordsData();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addOnBlur = true;
|
||||||
|
readonly separatorKeysCodes = [ENTER, COMMA] as const;
|
||||||
|
|
||||||
|
async addKeyword(event: MatChipInputEvent): Promise<void> {
|
||||||
|
const value = (event.value || '').trim();
|
||||||
|
|
||||||
|
// Add keyword to database
|
||||||
|
if (value && !this.keywords.includes({ name: value })) {
|
||||||
|
console.log('Added: ' + value);
|
||||||
|
this.botService.createKeyword(value).subscribe((result) => {
|
||||||
|
console.log(result);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear the input value
|
||||||
|
event.chipInput!.clear();
|
||||||
|
|
||||||
|
if (value) {
|
||||||
|
await this.helper.delay(1000);
|
||||||
|
this.keywords = [];
|
||||||
|
this.keywords = this.helper.formatKeywordsData();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async removeKeyword(keyword: Keyword): Promise<void> {
|
||||||
|
this.botService.deleteKeyword(keyword.name).subscribe((result) => {
|
||||||
|
console.log(result);
|
||||||
|
});
|
||||||
|
|
||||||
|
await this.helper.delay(1000);
|
||||||
|
|
||||||
|
this.keywords = [];
|
||||||
|
this.keywords = this.helper.formatKeywordsData();
|
||||||
|
}
|
||||||
|
|
||||||
|
async addShare(event: MatChipInputEvent): Promise<void> {
|
||||||
|
const value = (event.value || '').trim();
|
||||||
|
|
||||||
|
// Add share to database
|
||||||
|
if (value && !this.shares.includes({ symbol: value })) {
|
||||||
|
console.log('Added: ' + value);
|
||||||
|
this.botService.createShare(value).subscribe((result) => {
|
||||||
|
console.log(result);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear the input value
|
||||||
|
event.chipInput!.clear();
|
||||||
|
|
||||||
|
if (value) {
|
||||||
|
await this.helper.delay(1000);
|
||||||
|
|
||||||
|
this.shares = [];
|
||||||
|
this.shares = this.helper.formatShareData();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async removeShare(share: Share): Promise<void> {
|
||||||
|
this.botService.deleteShare(share.symbol).subscribe((result) => {
|
||||||
|
console.log(result);
|
||||||
|
});
|
||||||
|
|
||||||
|
await this.helper.delay(1000);
|
||||||
|
|
||||||
|
this.shares = [];
|
||||||
|
this.shares = this.helper.formatShareData();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,100 +3,148 @@
|
|||||||
<mat-grid-tile colspan="1" rowspan="2">
|
<mat-grid-tile colspan="1" rowspan="2">
|
||||||
<div class="stockOverview">
|
<div class="stockOverview">
|
||||||
<div class="heading">
|
<div class="heading">
|
||||||
<div class="vertical-center">Aktienübersicht</div>
|
<div class="vertical-center">Stocks</div>
|
||||||
<span class="spacer"></span>
|
|
||||||
<button
|
|
||||||
mat-icon-button
|
|
||||||
class="add-icon"
|
|
||||||
aria-label="Example icon-button with heart icon"
|
|
||||||
[disableRipple]="true"
|
|
||||||
>
|
|
||||||
<mat-icon>add</mat-icon>
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="stockTable">
|
<mat-card class="placeholder">
|
||||||
<table mat-table [dataSource]="dataSource" class="mat-elevation-z8">
|
<div class="stockTableLHS">
|
||||||
|
<table mat-table [dataSource]="dataSourceStocks">
|
||||||
<!--- Note that these columns can be defined in any order.
|
<!--- Note that these columns can be defined in any order.
|
||||||
The actual rendered columns are set as a property on the row definition" -->
|
The actual rendered columns are set as a property on the row definition" -->
|
||||||
|
|
||||||
<!-- Position Column -->
|
<!-- Symbol Column -->
|
||||||
<ng-container matColumnDef="position">
|
<ng-container matColumnDef="position">
|
||||||
<th mat-header-cell *matHeaderCellDef>Symbol</th>
|
<th mat-header-cell *matHeaderCellDef>Symbol</th>
|
||||||
<td mat-cell *matCellDef="let element">{{ element.position }}</td>
|
|
||||||
</ng-container>
|
|
||||||
|
|
||||||
<!-- Name Column -->
|
|
||||||
<ng-container matColumnDef="name">
|
|
||||||
<th mat-header-cell *matHeaderCellDef>Name</th>
|
|
||||||
<td mat-cell *matCellDef="let element">{{ element.name }}</td>
|
|
||||||
</ng-container>
|
|
||||||
|
|
||||||
<!-- Weight Column -->
|
|
||||||
<ng-container matColumnDef="weight">
|
|
||||||
<th mat-header-cell *matHeaderCellDef>Volume</th>
|
|
||||||
<td mat-cell *matCellDef="let element">{{ element.weight }}</td>
|
|
||||||
</ng-container>
|
|
||||||
|
|
||||||
<!-- Symbol Column -->
|
|
||||||
<ng-container matColumnDef="symbol">
|
|
||||||
<th mat-header-cell *matHeaderCellDef>Worth</th>
|
|
||||||
<td mat-cell *matCellDef="let element">{{ element.symbol }}</td>
|
<td mat-cell *matCellDef="let element">{{ element.symbol }}</td>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
<!-- Count Column -->
|
||||||
<tr mat-row *matRowDef="let row; columns: displayedColumns"></tr>
|
<ng-container matColumnDef="name">
|
||||||
|
<th mat-header-cell *matHeaderCellDef>Count</th>
|
||||||
|
<td mat-cell *matCellDef="let element">{{ element.count }}</td>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<!-- Time Column -->
|
||||||
|
<ng-container matColumnDef="weight">
|
||||||
|
<th mat-header-cell *matHeaderCellDef>Time</th>
|
||||||
|
<td mat-cell *matCellDef="let element">{{ element.time }}</td>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<!-- Time Column -->
|
||||||
|
<ng-container matColumnDef="current-price">
|
||||||
|
<th mat-header-cell *matHeaderCellDef>Current Price</th>
|
||||||
|
<td mat-cell *matCellDef="let element">
|
||||||
|
{{ element.currentPrice }}
|
||||||
|
</td>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<tr mat-header-row *matHeaderRowDef="displayedColumnsStocks"></tr>
|
||||||
|
<tr
|
||||||
|
mat-row
|
||||||
|
*matRowDef="let row; columns: displayedColumnsStocks"
|
||||||
|
></tr>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
</mat-card>
|
||||||
</div>
|
</div>
|
||||||
</mat-grid-tile>
|
</mat-grid-tile>
|
||||||
<!-- Depot Overview -->
|
<!-- Depot Overview -->
|
||||||
<mat-grid-tile colspan="1" rowspan="1" class="right-side">
|
<mat-grid-tile colspan="1" rowspan="1" class="right-side">
|
||||||
<div class="depotOverview">
|
<div class="depotOverview">
|
||||||
<div class="heading fix-right-side">
|
<div class="heading fix-right-side">
|
||||||
<div class="vertical-center">Depotübersicht</div>
|
<div class="vertical-center">Depot</div>
|
||||||
</div>
|
</div>
|
||||||
<mat-card class="placeholder"></mat-card>
|
<mat-card class="placeholderRHS content-container">
|
||||||
|
<div class="content">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="value-set">
|
||||||
|
<h3>Portfolio Value</h3>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="value-set">
|
||||||
|
<h3>Portfolio Cost</h3>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="value-set">
|
||||||
|
<h3>Portfolio Profit</h3>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<br />
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-6 col-sm-4">
|
||||||
|
<mat-icon>savings</mat-icon
|
||||||
|
><span class="money">{{ depotCurrentValue.toFixed(2) }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="col-6 col-sm-4">
|
||||||
|
<mat-icon>paid</mat-icon
|
||||||
|
><span class="money">{{ depotCost.toFixed(2) }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="col-6 col-sm-4">
|
||||||
|
<mat-icon>account_balance</mat-icon
|
||||||
|
><span
|
||||||
|
class="money"
|
||||||
|
[ngClass]="{ green: profit >= 0, red: profit < 0 }"
|
||||||
|
>{{ profit.toFixed(2) }}</span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</mat-card>
|
||||||
</div>
|
</div>
|
||||||
</mat-grid-tile>
|
</mat-grid-tile>
|
||||||
<!-- Transaktions -->
|
<!-- Transaktions -->
|
||||||
<mat-grid-tile colspan="1" rowspan="1" class="right-side">
|
<mat-grid-tile colspan="1" rowspan="1" class="right-side">
|
||||||
<div class="depotOverview">
|
<div class="depotOverviewDown">
|
||||||
<div class="heading fix-right-side">
|
<div class="heading fix-right-side">
|
||||||
<div class="vertical-center">Transaktionen</div>
|
<div class="vertical-center">Transactions</div>
|
||||||
|
<span class="spacer"></span>
|
||||||
|
<button
|
||||||
|
mat-icon-button
|
||||||
|
class="add-icon"
|
||||||
|
aria-label="Example icon-button with heart icon"
|
||||||
|
[disableRipple]="true"
|
||||||
|
(click)="openDialog()"
|
||||||
|
>
|
||||||
|
<mat-icon>add</mat-icon>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="stockTable">
|
<mat-card class="placeholderRHS"
|
||||||
<table mat-table [dataSource]="dataSource" class="mat-elevation-z8">
|
><div class="stockTable">
|
||||||
|
<table mat-table [dataSource]="dataSourceTransactions">
|
||||||
<!--- Note that these columns can be defined in any order.
|
<!--- Note that these columns can be defined in any order.
|
||||||
The actual rendered columns are set as a property on the row definition" -->
|
The actual rendered columns are set as a property on the row definition" -->
|
||||||
|
|
||||||
<!-- Position Column -->
|
<!-- Position Column -->
|
||||||
<ng-container matColumnDef="position">
|
<ng-container matColumnDef="position">
|
||||||
<th mat-header-cell *matHeaderCellDef>Count</th>
|
<th mat-header-cell *matHeaderCellDef>Count</th>
|
||||||
<td mat-cell *matCellDef="let element">{{ element.position }}</td>
|
<td mat-cell *matCellDef="let element">{{ element.count }}</td>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<!-- Name Column -->
|
<!-- Name Column -->
|
||||||
<ng-container matColumnDef="name">
|
<ng-container matColumnDef="name">
|
||||||
<th mat-header-cell *matHeaderCellDef>Pirce</th>
|
<th mat-header-cell *matHeaderCellDef>Price</th>
|
||||||
<td mat-cell *matCellDef="let element">{{ element.name }}</td>
|
<td mat-cell *matCellDef="let element">{{ element.price }}</td>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<!-- Weight Column -->
|
<!-- Weight Column -->
|
||||||
<ng-container matColumnDef="weight">
|
<ng-container matColumnDef="weight">
|
||||||
<th mat-header-cell *matHeaderCellDef>Symbol</th>
|
<th mat-header-cell *matHeaderCellDef>Symbol</th>
|
||||||
<td mat-cell *matCellDef="let element">{{ element.weight }}</td>
|
<td mat-cell *matCellDef="let element">{{ element.symbol }}</td>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<!-- Symbol Column -->
|
<!-- Symbol Column -->
|
||||||
<ng-container matColumnDef="symbol">
|
<ng-container matColumnDef="symbol">
|
||||||
<th mat-header-cell *matHeaderCellDef>Time</th>
|
<th mat-header-cell *matHeaderCellDef>Time</th>
|
||||||
<td mat-cell *matCellDef="let element">{{ element.symbol }}</td>
|
<td mat-cell *matCellDef="let element">{{ element.time }}</td>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
||||||
<tr mat-row *matRowDef="let row; columns: displayedColumns"></tr>
|
<tr mat-row *matRowDef="let row; columns: displayedColumns"></tr>
|
||||||
</table>
|
</table></div
|
||||||
</div>
|
></mat-card>
|
||||||
</div>
|
</div>
|
||||||
</mat-grid-tile>
|
</mat-grid-tile>
|
||||||
</mat-grid-list>
|
</mat-grid-list>
|
||||||
|
@ -13,6 +13,14 @@
|
|||||||
margin-top: 10%;
|
margin-top: 10%;
|
||||||
margin-left: 5%;
|
margin-left: 5%;
|
||||||
margin-right: 10%;
|
margin-right: 10%;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.depotOverviewDown {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
margin-left: 5%;
|
||||||
|
margin-right: 10%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.stockTable {
|
.stockTable {
|
||||||
@ -21,12 +29,20 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.stockTableLHS {
|
||||||
|
overflow: auto;
|
||||||
|
height: 83%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
.heading {
|
.heading {
|
||||||
font-size: xx-large;
|
font-size: xx-large;
|
||||||
height: 10%;
|
height: 10%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
position: relative;
|
position: relative;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fix-right-side {
|
.fix-right-side {
|
||||||
@ -47,7 +63,6 @@
|
|||||||
|
|
||||||
.add-icon {
|
.add-icon {
|
||||||
transform: scale(2);
|
transform: scale(2);
|
||||||
margin-top: 2%;
|
|
||||||
outline: none !important;
|
outline: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -62,3 +77,37 @@ table {
|
|||||||
.placeholder {
|
.placeholder {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.placeholderRHS {
|
||||||
|
height: 80%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mat-ripple-element {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.money {
|
||||||
|
margin-left: 2vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
.green {
|
||||||
|
color: green;
|
||||||
|
}
|
||||||
|
|
||||||
|
.red {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
|
||||||
|
.row {
|
||||||
|
height: 20%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
height: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-container {
|
||||||
|
width: 100%;
|
||||||
|
display: grid;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
import { Component, OnInit } from '@angular/core';
|
import { Component, OnInit } from '@angular/core';
|
||||||
import { throwToolbarMixedModesError } from '@angular/material/toolbar';
|
|
||||||
import { DataService } from 'src/app/Services/data.service';
|
import { DataService } from 'src/app/Services/data.service';
|
||||||
import { TokenStorageService } from 'src/app/Services/token.service';
|
import { MatDialog } from '@angular/material/dialog';
|
||||||
|
import { UserDialogComponent } from './user-dialog/user-dialog.component';
|
||||||
|
import { C } from '@angular/cdk/keycodes';
|
||||||
|
import { HelperService } from 'src/app/Helpers/helper.service';
|
||||||
|
|
||||||
export interface PeriodicElement {
|
export interface PeriodicElement {
|
||||||
name: string;
|
name: string;
|
||||||
@ -11,11 +13,10 @@ export interface PeriodicElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface Stock {
|
export interface Stock {
|
||||||
|
count: number;
|
||||||
|
currentPrice: number;
|
||||||
symbol: string;
|
symbol: string;
|
||||||
count: Float32Array;
|
time: string;
|
||||||
lastTransaction: Date;
|
|
||||||
boughtPrice: Float32Array;
|
|
||||||
currentPrice: Float32Array;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//symbol count lastTransaction boughtPrice currentPrice(+?)
|
//symbol count lastTransaction boughtPrice currentPrice(+?)
|
||||||
@ -24,43 +25,99 @@ const ELEMENT_DATA: PeriodicElement[] = [
|
|||||||
{ position: 1, name: 'Hydrogen', weight: 1.0079, symbol: 'H' },
|
{ position: 1, name: 'Hydrogen', weight: 1.0079, symbol: 'H' },
|
||||||
{ position: 2, name: 'Helium', weight: 4.0026, symbol: 'He' },
|
{ position: 2, name: 'Helium', weight: 4.0026, symbol: 'He' },
|
||||||
{ position: 3, name: 'Lithium', weight: 6.941, symbol: 'Li' },
|
{ position: 3, name: 'Lithium', weight: 6.941, symbol: 'Li' },
|
||||||
{ position: 4, name: 'Beryllium', weight: 9.0122, symbol: 'Be' },
|
|
||||||
{ position: 5, name: 'Boron', weight: 10.811, symbol: 'B' },
|
|
||||||
{ position: 6, name: 'Carbon', weight: 12.0107, symbol: 'C' },
|
|
||||||
{ position: 7, name: 'Nitrogen', weight: 14.0067, symbol: 'N' },
|
|
||||||
{ position: 8, name: 'Oxygen', weight: 15.9994, symbol: 'O' },
|
|
||||||
{ position: 9, name: 'Fluorine', weight: 18.9984, symbol: 'F' },
|
|
||||||
{ position: 10, name: 'Neon', weight: 20.1797, symbol: 'Ne' },
|
|
||||||
{ position: 11, name: 'Hydrogen', weight: 1.0079, symbol: 'H' },
|
|
||||||
{ position: 12, name: 'Helium', weight: 4.0026, symbol: 'He' },
|
|
||||||
{ position: 13, name: 'Lithium', weight: 6.941, symbol: 'Li' },
|
|
||||||
{ position: 14, name: 'Beryllium', weight: 9.0122, symbol: 'Be' },
|
|
||||||
{ position: 15, name: 'Boron', weight: 10.811, symbol: 'B' },
|
|
||||||
{ position: 16, name: 'Carbon', weight: 12.0107, symbol: 'C' },
|
|
||||||
{ position: 17, name: 'Nitrogen', weight: 14.0067, symbol: 'N' },
|
|
||||||
{ position: 18, name: 'Oxygen', weight: 15.9994, symbol: 'O' },
|
|
||||||
{ position: 19, name: 'Fluorine', weight: 18.9984, symbol: 'F' },
|
|
||||||
{ position: 20, name: 'Neon', weight: 20.1797, symbol: 'Ne' },
|
|
||||||
];
|
];
|
||||||
|
|
||||||
|
var TRANSACTION_DATA: TransactionData[] = [];
|
||||||
|
var STOCK_DATA: Stock[] = [];
|
||||||
|
|
||||||
|
export interface TransactionData {
|
||||||
|
symbol: string;
|
||||||
|
time: string;
|
||||||
|
count: number;
|
||||||
|
price: number;
|
||||||
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-dashboard',
|
selector: 'app-dashboard',
|
||||||
templateUrl: './dashboard.component.html',
|
templateUrl: './dashboard.component.html',
|
||||||
styleUrls: ['./dashboard.component.scss'],
|
styleUrls: ['./dashboard.component.scss'],
|
||||||
})
|
})
|
||||||
export class DashboardComponent implements OnInit {
|
export class DashboardComponent implements OnInit {
|
||||||
constructor(private dataService: DataService) {}
|
constructor(
|
||||||
|
private helper: HelperService,
|
||||||
|
private dataService: DataService,
|
||||||
|
public dialog: MatDialog
|
||||||
|
) {}
|
||||||
|
|
||||||
|
dataSourceTransactions: TransactionData[] = [];
|
||||||
|
dataSourceStocks: Stock[] = [];
|
||||||
|
depotCurrentValue: number = 0;
|
||||||
|
depotCost: number = 0;
|
||||||
|
profit: number = 0;
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.dataService.getStockData().subscribe((response: any) => {
|
this.dataService.getStockData().subscribe((response: any) => {
|
||||||
console.log(response);
|
var data = JSON.parse(response);
|
||||||
//TODO map data on array for display
|
for (let i = 0; i < data.data.length; i++) {
|
||||||
|
this.depotCurrentValue += data.data[i].current_price;
|
||||||
|
STOCK_DATA.push({
|
||||||
|
count: data.data[i].count,
|
||||||
|
currentPrice: data.data[i].current_price,
|
||||||
|
symbol: data.data[i].symbol,
|
||||||
|
time: data.data[i].last_transaction,
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
this.dataSourceStocks = STOCK_DATA;
|
||||||
|
//TODO move to helper service
|
||||||
|
|
||||||
|
this.profit += this.depotCurrentValue;
|
||||||
|
});
|
||||||
|
|
||||||
this.dataService.getTransactionData().subscribe((response: any) => {
|
this.dataService.getTransactionData().subscribe((response: any) => {
|
||||||
console.log(response);
|
var data = JSON.parse(response);
|
||||||
//TODO map data on array for display
|
for (let i = 0; i < data.data.length; i++) {
|
||||||
|
this.depotCost += data.data[i].price;
|
||||||
|
TRANSACTION_DATA.push({
|
||||||
|
symbol: data.data[i].symbol,
|
||||||
|
time: data.data[i].time,
|
||||||
|
count: data.data[i].count,
|
||||||
|
price: data.data[i].price,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this.dataSourceTransactions = TRANSACTION_DATA;
|
||||||
|
//TODO move to helper service
|
||||||
|
|
||||||
|
this.profit -= this.depotCost;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
displayedColumns: string[] = ['position', 'name', 'weight', 'symbol'];
|
symbol: string = '';
|
||||||
|
time: Date = new Date();
|
||||||
|
count: number = 0.0;
|
||||||
|
price: number = 0.0;
|
||||||
|
|
||||||
|
openDialog(): void {
|
||||||
|
const dialogRef = this.dialog.open(UserDialogComponent, {
|
||||||
|
width: '50vw',
|
||||||
|
data: {
|
||||||
|
symbol: this.symbol,
|
||||||
|
time: this.time,
|
||||||
|
count: this.count,
|
||||||
|
price: this.price,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
dialogRef.afterClosed().subscribe((result) => {
|
||||||
|
console.log('The dialog was closed');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
displayedColumns: string[] = ['weight', 'position', 'name', 'symbol'];
|
||||||
|
displayedColumnsStocks: string[] = [
|
||||||
|
'position',
|
||||||
|
'name',
|
||||||
|
'weight',
|
||||||
|
'current-price',
|
||||||
|
];
|
||||||
dataSource = ELEMENT_DATA;
|
dataSource = ELEMENT_DATA;
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,61 @@
|
|||||||
|
<h1 mat-dialog-title>Neue Transaktion hinzufügen</h1>
|
||||||
|
<form
|
||||||
|
name="form"
|
||||||
|
(ngSubmit)="f.form.valid && onSubmit()"
|
||||||
|
#f="ngForm"
|
||||||
|
novalidate
|
||||||
|
class="backgorund"
|
||||||
|
>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="symbol">Symbol</label>
|
||||||
|
<input
|
||||||
|
type="symbol"
|
||||||
|
class="form-control"
|
||||||
|
name="symbol"
|
||||||
|
[(ngModel)]="data.symbol"
|
||||||
|
required
|
||||||
|
symbol
|
||||||
|
#symbol="ngModel"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="time">Time</label>
|
||||||
|
<input
|
||||||
|
type="date"
|
||||||
|
class="form-control"
|
||||||
|
name="time"
|
||||||
|
[(ngModel)]="data.time"
|
||||||
|
required
|
||||||
|
time
|
||||||
|
#time="ngModel"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="count">Count</label>
|
||||||
|
<input
|
||||||
|
type="count"
|
||||||
|
class="form-control"
|
||||||
|
name="count"
|
||||||
|
[(ngModel)]="data.count"
|
||||||
|
required
|
||||||
|
count
|
||||||
|
#count="ngModel"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="price">Price</label>
|
||||||
|
<input
|
||||||
|
class="form-control"
|
||||||
|
name="price"
|
||||||
|
[(ngModel)]="data.price"
|
||||||
|
required
|
||||||
|
#price="ngModel"
|
||||||
|
type="number"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="form-group footer-buttons">
|
||||||
|
<button class="btn btn-danger btn-block" mat-dialog-close>Cancel</button>
|
||||||
|
<span class="spacer"></span>
|
||||||
|
<button class="btn btn-primary btn-block">Confirm</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
@ -0,0 +1,9 @@
|
|||||||
|
.spacer {
|
||||||
|
flex-grow: 1;
|
||||||
|
width: 5%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-buttons {
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { UserDialogComponent } from './user-dialog.component';
|
||||||
|
|
||||||
|
describe('UserDialogComponent', () => {
|
||||||
|
let component: UserDialogComponent;
|
||||||
|
let fixture: ComponentFixture<UserDialogComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
declarations: [ UserDialogComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(UserDialogComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,42 @@
|
|||||||
|
import { Component, Inject, OnInit } from '@angular/core';
|
||||||
|
import {
|
||||||
|
MatDialog,
|
||||||
|
MatDialogRef,
|
||||||
|
MAT_DIALOG_DATA,
|
||||||
|
} from '@angular/material/dialog';
|
||||||
|
import { DataService } from 'src/app/Services/data.service';
|
||||||
|
|
||||||
|
import { TransactionData } from '../dashboard.component';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-user-dialog',
|
||||||
|
templateUrl: './user-dialog.component.html',
|
||||||
|
styleUrls: ['./user-dialog.component.scss'],
|
||||||
|
})
|
||||||
|
export class UserDialogComponent implements OnInit {
|
||||||
|
constructor(
|
||||||
|
private dataService: DataService,
|
||||||
|
public dialog: MatDialog,
|
||||||
|
public dialogRef: MatDialogRef<UserDialogComponent>,
|
||||||
|
@Inject(MAT_DIALOG_DATA) public data: TransactionData
|
||||||
|
) {}
|
||||||
|
|
||||||
|
ngOnInit(): void {}
|
||||||
|
|
||||||
|
onSubmit() {
|
||||||
|
//TODO check tat price is decimal
|
||||||
|
console.log(
|
||||||
|
this.dataService
|
||||||
|
.createTransaction(
|
||||||
|
this.data.symbol,
|
||||||
|
this.data.time,
|
||||||
|
+this.data.count,
|
||||||
|
+this.data.price.toFixed(2)
|
||||||
|
)
|
||||||
|
.subscribe((data) => {
|
||||||
|
console.log(data);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
this.dialog.closeAll();
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
<mat-toolbar>
|
<mat-toolbar>
|
||||||
<span>Aktienbot</span>
|
<a href=""><span>Aktienbot</span></a>
|
||||||
<span class="example-spacer"></span>
|
<span class="example-spacer"></span>
|
||||||
<button
|
<button
|
||||||
mat-icon-button
|
mat-icon-button
|
||||||
|
@ -1,3 +1,8 @@
|
|||||||
.example-spacer {
|
.example-spacer {
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: white;
|
||||||
|
text-decoration: none; /* no underline */
|
||||||
|
}
|
||||||
|
@ -0,0 +1,28 @@
|
|||||||
|
<div class="containeer">
|
||||||
|
<h1 mat-dialog-title>Aktion bestätigen</h1>
|
||||||
|
<div mat-dialog-content class="content">
|
||||||
|
<span>Sind sie sicher, dass sie diese Handlung abschließen wollen?</span>
|
||||||
|
</div>
|
||||||
|
<div mat-dialog-actions class="form-group footer-buttons">
|
||||||
|
<div class="inner">
|
||||||
|
<button
|
||||||
|
id="cancelButton"
|
||||||
|
class="btn btn-primary btn-block"
|
||||||
|
(click)="returnBack()"
|
||||||
|
[mat-dialog-close]="false"
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="inner">
|
||||||
|
<button
|
||||||
|
id="okButton"
|
||||||
|
class="btn btn-danger btn-block"
|
||||||
|
(click)="confirm()"
|
||||||
|
[mat-dialog-close]="true"
|
||||||
|
>
|
||||||
|
Ok
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
@ -0,0 +1,18 @@
|
|||||||
|
.footer-buttons {
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spacer {
|
||||||
|
flex-grow: 1;
|
||||||
|
width: 5%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inner {
|
||||||
|
display: inline-block;
|
||||||
|
width: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
height: 80%;
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { ConfirmationDialogComponent } from './confirmation-dialog.component';
|
||||||
|
|
||||||
|
describe('ConfirmationDialogComponent', () => {
|
||||||
|
let component: ConfirmationDialogComponent;
|
||||||
|
let fixture: ComponentFixture<ConfirmationDialogComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
declarations: [ ConfirmationDialogComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(ConfirmationDialogComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,16 @@
|
|||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-confirmation-dialog',
|
||||||
|
templateUrl: './confirmation-dialog.component.html',
|
||||||
|
styleUrls: ['./confirmation-dialog.component.scss'],
|
||||||
|
})
|
||||||
|
export class ConfirmationDialogComponent implements OnInit {
|
||||||
|
constructor() {}
|
||||||
|
|
||||||
|
ngOnInit(): void {}
|
||||||
|
|
||||||
|
confirm() {}
|
||||||
|
|
||||||
|
returnBack() {}
|
||||||
|
}
|
@ -1 +1,135 @@
|
|||||||
<p>profile works!</p>
|
<mat-grid-list cols="2">
|
||||||
|
<mat-grid-tile>
|
||||||
|
<mat-card class="card">
|
||||||
|
<mat-card-title class="card-title">Profile Information</mat-card-title>
|
||||||
|
<mat-card-content>
|
||||||
|
<form
|
||||||
|
class="example-form form"
|
||||||
|
name="form"
|
||||||
|
(ngSubmit)="f.form.valid && openDialog('updateUser')"
|
||||||
|
#f="ngForm"
|
||||||
|
novalidate
|
||||||
|
>
|
||||||
|
<mat-form-field class="example-full-width" appearance="fill">
|
||||||
|
<mat-label>Username</mat-label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
name="username"
|
||||||
|
matInput
|
||||||
|
[formControl]="userNameFormControl"
|
||||||
|
placeholder="Ex. patrick-bateman"
|
||||||
|
[(ngModel)]="form.username"
|
||||||
|
#username
|
||||||
|
/>
|
||||||
|
<mat-error *ngIf="userNameFormControl.hasError('required')">
|
||||||
|
Username is <strong>required</strong>
|
||||||
|
</mat-error>
|
||||||
|
</mat-form-field>
|
||||||
|
<mat-form-field class="example-full-width" appearance="fill">
|
||||||
|
<mat-label>{{ form.email }}</mat-label>
|
||||||
|
<input
|
||||||
|
type="email"
|
||||||
|
matInput
|
||||||
|
name="email"
|
||||||
|
disabled
|
||||||
|
placeholder="Ex. patrickbateman@example.com"
|
||||||
|
/>
|
||||||
|
</mat-form-field>
|
||||||
|
<mat-form-field class="example-full-width" appearance="fill">
|
||||||
|
<mat-label>Password</mat-label>
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
matInput
|
||||||
|
name="password"
|
||||||
|
[formControl]="passwordFormControl"
|
||||||
|
placeholder="Password"
|
||||||
|
minlength="6"
|
||||||
|
[(ngModel)]="form.password"
|
||||||
|
#password
|
||||||
|
/>
|
||||||
|
<mat-error
|
||||||
|
*ngIf="
|
||||||
|
passwordFormControl.hasError('minlength') &&
|
||||||
|
!passwordFormControl.hasError('required')
|
||||||
|
"
|
||||||
|
>
|
||||||
|
Please enter a valid password
|
||||||
|
</mat-error>
|
||||||
|
<mat-error *ngIf="passwordFormControl.hasError('required')">
|
||||||
|
Password is <strong>required</strong>
|
||||||
|
</mat-error>
|
||||||
|
</mat-form-field>
|
||||||
|
<mat-form-field class="example-full-width" appearance="fill">
|
||||||
|
<mat-label>Repeat Password</mat-label>
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
matInput
|
||||||
|
[formControl]="passwordFormControl"
|
||||||
|
placeholder="Ex. pat@example.com"
|
||||||
|
/>
|
||||||
|
<mat-error
|
||||||
|
*ngIf="
|
||||||
|
passwordFormControl.hasError('minLength') &&
|
||||||
|
!passwordFormControl.hasError('required')
|
||||||
|
"
|
||||||
|
>
|
||||||
|
Please enter a valid password
|
||||||
|
</mat-error>
|
||||||
|
<mat-error *ngIf="passwordFormControl.hasError('required')">
|
||||||
|
Password is <strong>required</strong>
|
||||||
|
</mat-error>
|
||||||
|
</mat-form-field>
|
||||||
|
<div class="form-group footer-buttons">
|
||||||
|
<button
|
||||||
|
class="btn btn-primary btn-block"
|
||||||
|
[disabled]="
|
||||||
|
passwordFormControl.hasError('required') ||
|
||||||
|
passwordFormControl.hasError('minLength') ||
|
||||||
|
userNameFormControl.hasError('required')
|
||||||
|
"
|
||||||
|
>
|
||||||
|
Update
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</mat-card-content>
|
||||||
|
</mat-card>
|
||||||
|
</mat-grid-tile>
|
||||||
|
<mat-grid-tile>
|
||||||
|
<mat-card class="card">
|
||||||
|
<mat-card-title class="card-title">Add Telegram Id</mat-card-title>
|
||||||
|
<mat-card-content>
|
||||||
|
<form
|
||||||
|
name="form"
|
||||||
|
(ngSubmit)="f.form.valid && openDialog('addTelegram')"
|
||||||
|
#f="ngForm"
|
||||||
|
novalidate
|
||||||
|
class="backgorund form"
|
||||||
|
>
|
||||||
|
<mat-form-field class="example-full-width" appearance="fill">
|
||||||
|
<mat-label>Telegram UserId</mat-label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
matInput
|
||||||
|
[formControl]="telegramIdFormControl"
|
||||||
|
[(ngModel)]="userId"
|
||||||
|
required
|
||||||
|
#telegramId
|
||||||
|
/>
|
||||||
|
<mat-error *ngIf="telegramIdFormControl.hasError('required')">
|
||||||
|
Id is <strong>required</strong>
|
||||||
|
</mat-error>
|
||||||
|
</mat-form-field>
|
||||||
|
<div class="form-group footer-buttons">
|
||||||
|
<button
|
||||||
|
class="btn btn-primary btn-block"
|
||||||
|
[disabled]="telegramIdFormControl.hasError('required')"
|
||||||
|
>
|
||||||
|
Add
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</mat-card-content>
|
||||||
|
</mat-card>
|
||||||
|
</mat-grid-tile>
|
||||||
|
</mat-grid-list>
|
||||||
|
@ -0,0 +1,22 @@
|
|||||||
|
.form {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
width: 90%;
|
||||||
|
height: 80%;
|
||||||
|
margin: 5%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.example-full-width {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-title {
|
||||||
|
padding-bottom: 2.5vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
mat-grid {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
@ -1,4 +1,8 @@
|
|||||||
import { Component, OnInit } from '@angular/core';
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { FormControl, PatternValidator, Validators } from '@angular/forms';
|
||||||
|
import { MatDialog } from '@angular/material/dialog';
|
||||||
|
import { ProfileService } from 'src/app/Services/profile.service';
|
||||||
|
import { ConfirmationDialogComponent } from './confirmation-dialog/confirmation-dialog.component';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-profile',
|
selector: 'app-profile',
|
||||||
@ -6,7 +10,69 @@ import { Component, OnInit } from '@angular/core';
|
|||||||
styleUrls: ['./profile.component.scss'],
|
styleUrls: ['./profile.component.scss'],
|
||||||
})
|
})
|
||||||
export class ProfileComponent implements OnInit {
|
export class ProfileComponent implements OnInit {
|
||||||
constructor() {}
|
userNameFormControl = new FormControl('', [Validators.required]);
|
||||||
|
passwordFormControl = new FormControl('', [
|
||||||
|
Validators.required,
|
||||||
|
Validators.minLength(6),
|
||||||
|
]);
|
||||||
|
telegramIdFormControl = new FormControl('', [Validators.required]);
|
||||||
|
|
||||||
ngOnInit(): void {}
|
userId = '';
|
||||||
|
|
||||||
|
form: any = {
|
||||||
|
username: null,
|
||||||
|
email: 'example@web.com',
|
||||||
|
password: 'password',
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private profileService: ProfileService,
|
||||||
|
public dialog: MatDialog
|
||||||
|
) {}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.profileService.getUserData().subscribe((result) => {
|
||||||
|
console.log(result);
|
||||||
|
result = JSON.parse(result);
|
||||||
|
this.form.username = result.data.username;
|
||||||
|
this.form.password = result.data.password;
|
||||||
|
this.form.email = result.data.email;
|
||||||
|
this.userId = result.data.telegram_user_id;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onSubmit() {
|
||||||
|
if (this.userId != '') {
|
||||||
|
console.log(this.userId);
|
||||||
|
this.profileService.addTelegramId(this.userId).subscribe((result) => {
|
||||||
|
console.log(result);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateUser() {
|
||||||
|
const { username, email, password } = this.form;
|
||||||
|
this.profileService
|
||||||
|
.updateProfile(this.form.username, this.form.password)
|
||||||
|
.subscribe((result) => {
|
||||||
|
console.log(result);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
openDialog(action: string) {
|
||||||
|
const dialogRef = this.dialog.open(ConfirmationDialogComponent, {
|
||||||
|
width: '50vw',
|
||||||
|
height: '20vh',
|
||||||
|
});
|
||||||
|
|
||||||
|
dialogRef.afterClosed().subscribe((result) => {
|
||||||
|
if (result === true) {
|
||||||
|
if (action === 'addTelegram') {
|
||||||
|
this.onSubmit();
|
||||||
|
} else if (action === 'updateUser') {
|
||||||
|
this.updateUser();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { RouterModule, Routes } from '@angular/router';
|
import { RouterModule, Routes } from '@angular/router';
|
||||||
|
import { BotService } from './Services/bot.service';
|
||||||
|
import { BotSettingsComponent } from './Views/bot-settings/bot-settings.component';
|
||||||
import { DashboardComponent } from './Views/dashboard/dashboard.component';
|
import { DashboardComponent } from './Views/dashboard/dashboard.component';
|
||||||
import { LoginComponent } from './Views/login/login.component';
|
import { LoginComponent } from './Views/login/login.component';
|
||||||
import { ProfileComponent } from './Views/profile/profile.component';
|
import { ProfileComponent } from './Views/profile/profile.component';
|
||||||
@ -24,7 +26,7 @@ const routes: Routes = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'settings',
|
path: 'settings',
|
||||||
component: ProfileComponent,
|
component: BotSettingsComponent,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -1 +0,0 @@
|
|||||||
|
|
@ -1,6 +1,6 @@
|
|||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { BrowserModule } from '@angular/platform-browser';
|
import { BrowserModule } from '@angular/platform-browser';
|
||||||
import { FormsModule } from '@angular/forms';
|
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||||
import { HttpClientModule } from '@angular/common/http';
|
import { HttpClientModule } from '@angular/common/http';
|
||||||
|
|
||||||
import { MatToolbarModule } from '@angular/material/toolbar';
|
import { MatToolbarModule } from '@angular/material/toolbar';
|
||||||
@ -10,6 +10,9 @@ import { MatGridListModule } from '@angular/material/grid-list';
|
|||||||
import { MatCardModule } from '@angular/material/card';
|
import { MatCardModule } from '@angular/material/card';
|
||||||
import { MatTableModule } from '@angular/material/table';
|
import { MatTableModule } from '@angular/material/table';
|
||||||
import { MatMenuModule } from '@angular/material/menu';
|
import { MatMenuModule } from '@angular/material/menu';
|
||||||
|
import { MatDialogModule } from '@angular/material/dialog';
|
||||||
|
import { MatInputModule } from '@angular/material/input';
|
||||||
|
import { MatChipsModule } from '@angular/material/chips';
|
||||||
|
|
||||||
import { AppRoutingModule } from './app-routing.module';
|
import { AppRoutingModule } from './app-routing.module';
|
||||||
import { AppComponent } from './app.component';
|
import { AppComponent } from './app.component';
|
||||||
@ -20,6 +23,8 @@ import { DashboardComponent } from './Views/dashboard/dashboard.component';
|
|||||||
import { RegisterComponent } from './Views/register/register.component';
|
import { RegisterComponent } from './Views/register/register.component';
|
||||||
import { ProfileComponent } from './Views/profile/profile.component';
|
import { ProfileComponent } from './Views/profile/profile.component';
|
||||||
import { BotSettingsComponent } from './Views/bot-settings/bot-settings.component';
|
import { BotSettingsComponent } from './Views/bot-settings/bot-settings.component';
|
||||||
|
import { UserDialogComponent } from './Views/dashboard/user-dialog/user-dialog.component';
|
||||||
|
import { ConfirmationDialogComponent } from './Views/profile/confirmation-dialog/confirmation-dialog.component';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [
|
declarations: [
|
||||||
@ -30,6 +35,8 @@ import { BotSettingsComponent } from './Views/bot-settings/bot-settings.componen
|
|||||||
RegisterComponent,
|
RegisterComponent,
|
||||||
ProfileComponent,
|
ProfileComponent,
|
||||||
BotSettingsComponent,
|
BotSettingsComponent,
|
||||||
|
UserDialogComponent,
|
||||||
|
ConfirmationDialogComponent,
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
BrowserModule,
|
BrowserModule,
|
||||||
@ -44,6 +51,10 @@ import { BotSettingsComponent } from './Views/bot-settings/bot-settings.componen
|
|||||||
FormsModule,
|
FormsModule,
|
||||||
HttpClientModule,
|
HttpClientModule,
|
||||||
MatMenuModule,
|
MatMenuModule,
|
||||||
|
MatDialogModule,
|
||||||
|
MatInputModule,
|
||||||
|
ReactiveFormsModule,
|
||||||
|
MatChipsModule,
|
||||||
],
|
],
|
||||||
providers: [],
|
providers: [],
|
||||||
bootstrap: [AppComponent],
|
bootstrap: [AppComponent],
|
||||||
|
Loading…
x
Reference in New Issue
Block a user