Merge remote-tracking branch 'origin/main' into bot
This commit is contained in:
commit
99d39a0ed9
@ -4,6 +4,7 @@ pipeline:
|
||||
commands:
|
||||
- echo -n "${CI_COMMIT_BRANCH//\//-}-${CI_COMMIT_SHA:0:8}, latest" > .tags
|
||||
when:
|
||||
path: [ "frontend/**", "telegram_bot/**", "api/**" ]
|
||||
event: push
|
||||
|
||||
|
||||
@ -79,7 +80,8 @@ pipeline:
|
||||
- cd /root/docker/aktienbot
|
||||
- docker-compose pull
|
||||
- docker-compose -p "aktienbot" up -d
|
||||
when:
|
||||
event: push
|
||||
when:
|
||||
path: [ "frontend/**", "telegram_bot/**", "api/**" ]
|
||||
event: push
|
||||
|
||||
branches: main
|
||||
|
18
README.md
18
README.md
@ -16,3 +16,21 @@ WebEngineering2 Projekt: Aktien und News Bot für Telegram
|
||||
|
||||
## Dokumentation
|
||||
-> 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_USERNAME=
|
||||
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
|
||||
|
||||
# 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/
|
||||
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/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
|
||||
|
||||
# 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 apiflask import APIFlask
|
||||
|
||||
@ -7,6 +13,7 @@ from flask_cors import CORS
|
||||
from app.blueprints.keyword import keyword_blueprint
|
||||
from app.blueprints.portfolio import portfolio_blueprint
|
||||
from app.blueprints.shares import shares_blueprint
|
||||
from app.blueprints.share_price import share_price_blueprint
|
||||
from app.blueprints.transactions import transaction_blueprint
|
||||
from app.blueprints.telegram import telegram_blueprint
|
||||
from app.blueprints.user import users_blueprint
|
||||
@ -23,13 +30,12 @@ def create_app(config_filename=None):
|
||||
|
||||
CORS(application, resources={r"*": {"origins": "*"}})
|
||||
|
||||
application.app_context().push()
|
||||
|
||||
db.init_app(application)
|
||||
|
||||
# api blueprints
|
||||
application.register_blueprint(keyword_blueprint)
|
||||
application.register_blueprint(shares_blueprint)
|
||||
application.register_blueprint(share_price_blueprint)
|
||||
application.register_blueprint(transaction_blueprint)
|
||||
application.register_blueprint(portfolio_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
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
from apiflask import APIBlueprint
|
||||
|
||||
from app.schema import PortfolioResponseSchema
|
||||
from app.auth import auth
|
||||
from app.db import database as db
|
||||
from app.helper_functions import make_response, get_email_or_abort_401
|
||||
from app.auth import auth
|
||||
from app.models import SharePrice
|
||||
from app.schema import PortfolioResponseSchema
|
||||
|
||||
portfolio_blueprint = APIBlueprint('portfolio', __name__, url_prefix='/api')
|
||||
__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:
|
||||
for row in transactions:
|
||||
return_portfolio.append({
|
||||
data = {
|
||||
"symbol": row[0],
|
||||
"count": row[1],
|
||||
# "price": row[2],
|
||||
"last_transaction": row[3]
|
||||
})
|
||||
# "calculated_price": row[2],
|
||||
"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")
|
||||
|
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
|
||||
|
||||
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
|
||||
|
||||
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.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.models import User
|
||||
|
||||
telegram_blueprint = APIBlueprint('telegram', __name__, url_prefix='/api')
|
||||
__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):
|
||||
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']
|
||||
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 os
|
||||
|
||||
from apiflask import abort, APIBlueprint
|
||||
|
||||
from app.auth import auth
|
||||
from app.db import database as db
|
||||
from app.helper_functions import make_response, get_email_or_abort_401
|
||||
from app.models import Transaction
|
||||
from app.schema import TransactionSchema, TransactionResponseSchema
|
||||
from app.auth import auth
|
||||
|
||||
transaction_blueprint = APIBlueprint('transaction', __name__, url_prefix='/api')
|
||||
__location__ = os.path.realpath(os.path.join(os.getcwd(), os.path.dirname(__file__)))
|
||||
|
@ -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 os
|
||||
from flask import current_app
|
||||
|
||||
import jwt
|
||||
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.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')
|
||||
__location__ = os.path.realpath(os.path.join(os.getcwd(), os.path.dirname(__file__)))
|
||||
@ -36,9 +41,9 @@ def users():
|
||||
def user():
|
||||
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'])
|
||||
@ -55,10 +60,7 @@ def login(data):
|
||||
email = data['email']
|
||||
password = data['password']
|
||||
|
||||
query_user = db.session.query(User).filter_by(email=email).first()
|
||||
|
||||
if query_user is None: # email doesn't exist
|
||||
abort(500, message="Unable to login")
|
||||
query_user = get_user(email)
|
||||
|
||||
if not check_password(query_user.password, password.encode("utf-8")): # Password incorrect
|
||||
abort(500, message="Unable to login")
|
||||
@ -98,7 +100,8 @@ def register(data):
|
||||
email=email,
|
||||
username=username,
|
||||
password=hash_password(password),
|
||||
admin=False
|
||||
admin=False,
|
||||
cron="0 8 * * *"
|
||||
)
|
||||
db.session.add(new_user)
|
||||
db.session.commit()
|
||||
@ -114,7 +117,7 @@ def register(data):
|
||||
def update_user(data):
|
||||
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):
|
||||
query_user.password = hash_password(data['password'])
|
||||
@ -144,10 +147,7 @@ def set_admin(data):
|
||||
email = data['email']
|
||||
admin = data['admin']
|
||||
|
||||
query_user = db.session.query(User).filter_by(email=email).first()
|
||||
|
||||
if query_user is None: # Username doesn't exist
|
||||
abort(500, message="Unable to update user")
|
||||
query_user = get_user(email)
|
||||
|
||||
query_user.admin = admin
|
||||
db.session.commit()
|
||||
@ -155,6 +155,23 @@ def set_admin(data):
|
||||
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.output({}, 200)
|
||||
@users_blueprint.input(schema=DeleteUserSchema)
|
||||
@ -216,3 +233,13 @@ def check_if_admin_data_exists(data):
|
||||
return False
|
||||
|
||||
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
|
||||
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
|
||||
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
|
||||
|
||||
# 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 jwt
|
||||
from apiflask import abort
|
||||
from flask import request, jsonify
|
||||
|
||||
from app.db import database as db
|
||||
from app.models import User
|
||||
from flask import current_app
|
||||
from flask import request, jsonify
|
||||
|
||||
|
||||
def hash_password(password):
|
||||
@ -17,45 +21,47 @@ def check_password(hashed_password, user_password):
|
||||
return bcrypt.checkpw(user_password, hashed_password)
|
||||
|
||||
|
||||
def get_email_from_token_data():
|
||||
if 'Authorization' in request.headers:
|
||||
token = request.headers['Authorization'].split(" ")
|
||||
def get_email_from_token_data(token):
|
||||
if token is None or len(token) < 2:
|
||||
return None
|
||||
else:
|
||||
token = token[1]
|
||||
|
||||
if len(token) < 2:
|
||||
return None
|
||||
else:
|
||||
token = token[1]
|
||||
if token is not None:
|
||||
if ':' in token: # Maybe bot token, check if token valid and return username after ":" then
|
||||
telegram_user_id = token.split(":")[1]
|
||||
token = token.split(":")[0]
|
||||
|
||||
if token is not None:
|
||||
if ':' in token: # Maybe bot token, check if token valid and return username after ":" then
|
||||
telegram_user_id = token.split(":")[1]
|
||||
token = token.split(":")[0]
|
||||
try:
|
||||
if jwt.decode(token, current_app.config['SECRET_KEY'], algorithms=["HS256"])['email'] == current_app.config['BOT_EMAIL']:
|
||||
res = db.session.query(User).filter_by(telegram_user_id=telegram_user_id).first()
|
||||
|
||||
try:
|
||||
if jwt.decode(token, current_app.config['SECRET_KEY'], algorithms=["HS256"])['email'] == current_app.config['BOT_EMAIL']:
|
||||
res = db.session.query(User).filter_by(telegram_user_id=telegram_user_id).first()
|
||||
|
||||
if res is not None:
|
||||
return res.as_dict()['email']
|
||||
else:
|
||||
return None
|
||||
if res is not None:
|
||||
return res.as_dict()['email']
|
||||
else:
|
||||
return None
|
||||
except jwt.PyJWTError:
|
||||
else:
|
||||
return None
|
||||
except jwt.PyJWTError:
|
||||
return None
|
||||
|
||||
else: # "Normal" token, extract username from token
|
||||
try:
|
||||
return jwt.decode(token, current_app.config['SECRET_KEY'], algorithms=["HS256"])['email']
|
||||
except jwt.PyJWTError:
|
||||
return None
|
||||
else: # "Normal" token, extract username from token
|
||||
try:
|
||||
return jwt.decode(token, current_app.config['SECRET_KEY'], algorithms=["HS256"])['email']
|
||||
except jwt.PyJWTError:
|
||||
return None
|
||||
|
||||
return None
|
||||
|
||||
def get_token():
|
||||
if 'Authorization' in request.headers:
|
||||
return request.headers['Authorization'].split(" ")
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def get_email_or_abort_401():
|
||||
# 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
|
||||
abort(401, message="Unable to login")
|
||||
@ -76,3 +82,12 @@ def is_user_admin():
|
||||
|
||||
def make_response(data, status=200, text=""):
|
||||
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
|
||||
|
||||
|
||||
@ -7,14 +13,16 @@ class User(db.Model):
|
||||
password = db.Column('password', db.BINARY(60), nullable=False)
|
||||
username = db.Column('username', db.String(255), nullable=False, server_default='')
|
||||
telegram_user_id = db.Column('telegram_user_id', db.String(255), nullable=True, server_default='')
|
||||
admin = db.Column('admin', db.Boolean(), server_default='0')
|
||||
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):
|
||||
return {
|
||||
"email": self.email,
|
||||
"username": self.username,
|
||||
"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):
|
||||
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.fields import Integer, String, Boolean, Field, Float
|
||||
from marshmallow import validate
|
||||
@ -23,6 +29,10 @@ class AdminDataSchema(Schema):
|
||||
admin = Boolean()
|
||||
|
||||
|
||||
class CronDataSchema(Schema):
|
||||
cron = String()
|
||||
|
||||
|
||||
class TokenSchema(Schema):
|
||||
token = String()
|
||||
|
||||
@ -71,6 +81,12 @@ class TransactionSchema(Schema):
|
||||
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):
|
||||
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-cov
|
||||
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
|
||||
from app import create_app, db
|
||||
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
|
||||
|
||||
|
||||
|
@ -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.
|
||||
"""
|
||||
|
@ -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.
|
||||
"""
|
||||
|
@ -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.
|
||||
"""
|
||||
|
@ -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.
|
||||
"""
|
||||
|
@ -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.
|
||||
"""
|
||||
|
@ -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.
|
||||
"""
|
||||
import json
|
||||
|
||||
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')
|
||||
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):
|
||||
@ -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"))},
|
||||
content_type='application/json')
|
||||
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):
|
||||
@ -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
|
||||
|
||||
|
||||
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):
|
||||
"""
|
||||
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.
|
||||
"""
|
||||
|
@ -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.
|
||||
"""
|
||||
@ -18,3 +24,10 @@ def test_check_password():
|
||||
hashed = hash_password("password")
|
||||
assert check_password(hashed, "password".encode("utf-8")) is True
|
||||
assert check_password(hashed, "password1".encode("utf-8")) is False
|
||||
|
||||
|
||||
def test_get_email_from_token():
|
||||
"""
|
||||
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.
|
||||
"""
|
||||
from app.models import User, Transaction, Keyword, Share
|
||||
|
||||
from app.helper_functions import hash_password
|
||||
from app.models import User, Transaction, Keyword, Share
|
||||
|
||||
|
||||
def test_new_user():
|
||||
@ -16,14 +21,15 @@ def test_new_user():
|
||||
email="user@example.com",
|
||||
username="user",
|
||||
password=hash_password("password"),
|
||||
admin=False
|
||||
admin=False,
|
||||
cron="0 8 * * *",
|
||||
)
|
||||
assert user.email == 'user@example.com'
|
||||
assert user.password != 'password'
|
||||
assert user.username == "user"
|
||||
assert user.telegram_user_id is None
|
||||
assert user.admin is False
|
||||
assert user.as_dict() == {'email': 'user@example.com', 'username': 'user', 'telegram_user_id': None, 'admin': False}
|
||||
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):
|
||||
|
@ -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.
|
||||
"""
|
||||
|
@ -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.
|
||||
"""
|
||||
@ -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=None)) 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
|
||||
labels:
|
||||
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.priority: 40
|
||||
traefik.http.routers.aktienbot_fe.tls: true
|
||||
|
@ -12,6 +12,41 @@ services:
|
||||
- ${PWD}/traefik-dynamic.toml:/etc/traefik/traefik-dynamic.toml
|
||||
- ${PWD}/acme.json:/etc/traefik/acme.json
|
||||
- ${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:
|
||||
image: portainer/portainer-ce
|
||||
@ -36,3 +71,5 @@ networks:
|
||||
|
||||
volumes:
|
||||
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 { Stock } from '../Models/stock.model';
|
||||
import { BotService } from '../Services/bot.service';
|
||||
import { Keyword, Share } from '../Views/bot-settings/bot-settings.component';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
providedIn: 'root',
|
||||
})
|
||||
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 { HttpClient, HttpHeaders } from '@angular/common/http';
|
||||
import { Observable } from 'rxjs';
|
||||
const AUTH_API = 'https://aktienbot.flokaiser.com/api/user/';
|
||||
const AUTH_API = 'https://gruppe1.testsites.info/api/user';
|
||||
|
||||
const httpOptions = {
|
||||
headers: new HttpHeaders({ 'Content-Type': 'application/json' }),
|
||||
};
|
||||
@ -11,12 +12,25 @@ const httpOptions = {
|
||||
})
|
||||
export class AuthService {
|
||||
constructor(private http: HttpClient) {}
|
||||
|
||||
/**
|
||||
* @param {string} email
|
||||
* @param {string} password
|
||||
* @returns Observable
|
||||
*/
|
||||
login(email: string, password: string): Observable<any> {
|
||||
return this.http.post(AUTH_API + '/login', {
|
||||
email,
|
||||
password,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} email
|
||||
* @param {string} username
|
||||
* @param {string} password
|
||||
* @returns Observable
|
||||
*/
|
||||
register(email: string, username: string, password: string): Observable<any> {
|
||||
return this.http.post(
|
||||
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 { delay, Observable } from 'rxjs';
|
||||
import { TokenStorageService } from './token.service';
|
||||
const API_URL = 'https://aktienbot.flokaiser.com/api/';
|
||||
const API_URL = 'https://gruppe1.testsites.info/api/';
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class DataService {
|
||||
/**
|
||||
* @param {HttpClient} privatehttp
|
||||
* @param {TokenStorageService} privatetokenStorage
|
||||
*/
|
||||
constructor(
|
||||
private http: HttpClient,
|
||||
private tokenStorage: TokenStorageService
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @returns Observable
|
||||
*/
|
||||
public getStockData(): Observable<any> {
|
||||
return this.http.get(API_URL + 'portfolio', {
|
||||
headers: new HttpHeaders({
|
||||
@ -22,6 +29,9 @@ export class DataService {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns Observable
|
||||
*/
|
||||
public getTransactionData(): Observable<any> {
|
||||
return this.http.get(API_URL + 'transactions', {
|
||||
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> {
|
||||
return this.http.get(API_URL + 'keywords', {
|
||||
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 { TokenService } from './token.service';
|
||||
import { TokenStorageService } from './token.service';
|
||||
|
||||
describe('TokenService', () => {
|
||||
let service: TokenService;
|
||||
describe('TokenStorageService', () => {
|
||||
let service: TokenStorageService;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({});
|
||||
service = TestBed.inject(TokenService);
|
||||
service = TestBed.inject(TokenStorageService);
|
||||
});
|
||||
|
||||
it('should be created', () => {
|
||||
|
@ -6,20 +6,42 @@ const USER_KEY = 'auth-user';
|
||||
})
|
||||
export class TokenStorageService {
|
||||
constructor() {}
|
||||
|
||||
/**
|
||||
* @returns void
|
||||
*/
|
||||
signOut(): void {
|
||||
window.sessionStorage.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} token
|
||||
* @returns void
|
||||
*/
|
||||
public saveToken(token: string): void {
|
||||
window.sessionStorage.removeItem(TOKEN_KEY);
|
||||
window.sessionStorage.setItem(TOKEN_KEY, token);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns string
|
||||
*/
|
||||
public getToken(): string | null {
|
||||
return window.sessionStorage.getItem(TOKEN_KEY);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {any} user
|
||||
* @returns void
|
||||
*/
|
||||
public saveUser(user: any): void {
|
||||
window.sessionStorage.removeItem(USER_KEY);
|
||||
window.sessionStorage.setItem(USER_KEY, JSON.stringify(user));
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns any
|
||||
*/
|
||||
public getUser(): any {
|
||||
const user = window.sessionStorage.getItem(USER_KEY);
|
||||
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 { 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({
|
||||
selector: 'app-bot-settings',
|
||||
templateUrl: './bot-settings.component.html',
|
||||
styleUrls: ['./bot-settings.component.scss']
|
||||
styleUrls: ['./bot-settings.component.scss'],
|
||||
})
|
||||
export class BotSettingsComponent implements OnInit {
|
||||
keywords: Keyword[] = [];
|
||||
shares: Share[] = [];
|
||||
|
||||
constructor() { }
|
||||
constructor(private botService: BotService, private helper: HelperService) {}
|
||||
|
||||
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">
|
||||
<div class="stockOverview">
|
||||
<div class="heading">
|
||||
<div class="vertical-center">Aktienübersicht</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 class="vertical-center">Stocks</div>
|
||||
</div>
|
||||
<div class="stockTable">
|
||||
<table mat-table [dataSource]="dataSource" class="mat-elevation-z8">
|
||||
<!--- Note that these columns can be defined in any order.
|
||||
The actual rendered columns are set as a property on the row definition" -->
|
||||
<mat-card class="placeholder">
|
||||
<div class="stockTableLHS">
|
||||
<table mat-table [dataSource]="dataSourceStocks">
|
||||
<!--- Note that these columns can be defined in any order.
|
||||
The actual rendered columns are set as a property on the row definition" -->
|
||||
|
||||
<!-- Position Column -->
|
||||
<ng-container matColumnDef="position">
|
||||
<th mat-header-cell *matHeaderCellDef>Symbol</th>
|
||||
<td mat-cell *matCellDef="let element">{{ element.position }}</td>
|
||||
</ng-container>
|
||||
<!-- Symbol Column -->
|
||||
<ng-container matColumnDef="position">
|
||||
<th mat-header-cell *matHeaderCellDef>Symbol</th>
|
||||
<td mat-cell *matCellDef="let element">{{ element.symbol }}</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>
|
||||
<!-- Count Column -->
|
||||
<ng-container matColumnDef="name">
|
||||
<th mat-header-cell *matHeaderCellDef>Count</th>
|
||||
<td mat-cell *matCellDef="let element">{{ element.count }}</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>
|
||||
<!-- Time Column -->
|
||||
<ng-container matColumnDef="weight">
|
||||
<th mat-header-cell *matHeaderCellDef>Time</th>
|
||||
<td mat-cell *matCellDef="let element">{{ element.time }}</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>
|
||||
</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="displayedColumns"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: displayedColumns"></tr>
|
||||
</table>
|
||||
</div>
|
||||
<tr mat-header-row *matHeaderRowDef="displayedColumnsStocks"></tr>
|
||||
<tr
|
||||
mat-row
|
||||
*matRowDef="let row; columns: displayedColumnsStocks"
|
||||
></tr>
|
||||
</table>
|
||||
</div>
|
||||
</mat-card>
|
||||
</div>
|
||||
</mat-grid-tile>
|
||||
<!-- Depot Overview -->
|
||||
<mat-grid-tile colspan="1" rowspan="1" class="right-side">
|
||||
<div class="depotOverview">
|
||||
<div class="heading fix-right-side">
|
||||
<div class="vertical-center">Depotübersicht</div>
|
||||
<div class="vertical-center">Depot</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>
|
||||
</mat-grid-tile>
|
||||
<!-- Transaktions -->
|
||||
<mat-grid-tile colspan="1" rowspan="1" class="right-side">
|
||||
<div class="depotOverview">
|
||||
<div class="depotOverviewDown">
|
||||
<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 class="stockTable">
|
||||
<table mat-table [dataSource]="dataSource" class="mat-elevation-z8">
|
||||
<!--- Note that these columns can be defined in any order.
|
||||
<mat-card class="placeholderRHS"
|
||||
><div class="stockTable">
|
||||
<table mat-table [dataSource]="dataSourceTransactions">
|
||||
<!--- Note that these columns can be defined in any order.
|
||||
The actual rendered columns are set as a property on the row definition" -->
|
||||
|
||||
<!-- Position Column -->
|
||||
<ng-container matColumnDef="position">
|
||||
<th mat-header-cell *matHeaderCellDef>Count</th>
|
||||
<td mat-cell *matCellDef="let element">{{ element.position }}</td>
|
||||
</ng-container>
|
||||
<!-- Position Column -->
|
||||
<ng-container matColumnDef="position">
|
||||
<th mat-header-cell *matHeaderCellDef>Count</th>
|
||||
<td mat-cell *matCellDef="let element">{{ element.count }}</td>
|
||||
</ng-container>
|
||||
|
||||
<!-- Name Column -->
|
||||
<ng-container matColumnDef="name">
|
||||
<th mat-header-cell *matHeaderCellDef>Pirce</th>
|
||||
<td mat-cell *matCellDef="let element">{{ element.name }}</td>
|
||||
</ng-container>
|
||||
<!-- Name Column -->
|
||||
<ng-container matColumnDef="name">
|
||||
<th mat-header-cell *matHeaderCellDef>Price</th>
|
||||
<td mat-cell *matCellDef="let element">{{ element.price }}</td>
|
||||
</ng-container>
|
||||
|
||||
<!-- Weight Column -->
|
||||
<ng-container matColumnDef="weight">
|
||||
<th mat-header-cell *matHeaderCellDef>Symbol</th>
|
||||
<td mat-cell *matCellDef="let element">{{ element.weight }}</td>
|
||||
</ng-container>
|
||||
<!-- Weight Column -->
|
||||
<ng-container matColumnDef="weight">
|
||||
<th mat-header-cell *matHeaderCellDef>Symbol</th>
|
||||
<td mat-cell *matCellDef="let element">{{ element.symbol }}</td>
|
||||
</ng-container>
|
||||
|
||||
<!-- Symbol Column -->
|
||||
<ng-container matColumnDef="symbol">
|
||||
<th mat-header-cell *matHeaderCellDef>Time</th>
|
||||
<td mat-cell *matCellDef="let element">{{ element.symbol }}</td>
|
||||
</ng-container>
|
||||
<!-- Symbol Column -->
|
||||
<ng-container matColumnDef="symbol">
|
||||
<th mat-header-cell *matHeaderCellDef>Time</th>
|
||||
<td mat-cell *matCellDef="let element">{{ element.time }}</td>
|
||||
</ng-container>
|
||||
|
||||
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: displayedColumns"></tr>
|
||||
</table>
|
||||
</div>
|
||||
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: displayedColumns"></tr>
|
||||
</table></div
|
||||
></mat-card>
|
||||
</div>
|
||||
</mat-grid-tile>
|
||||
</mat-grid-list>
|
||||
|
@ -13,6 +13,14 @@
|
||||
margin-top: 10%;
|
||||
margin-left: 5%;
|
||||
margin-right: 10%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.depotOverviewDown {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
margin-left: 5%;
|
||||
margin-right: 10%;
|
||||
}
|
||||
|
||||
.stockTable {
|
||||
@ -21,12 +29,20 @@
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.stockTableLHS {
|
||||
overflow: auto;
|
||||
height: 83%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.heading {
|
||||
font-size: xx-large;
|
||||
height: 10%;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.fix-right-side {
|
||||
@ -47,7 +63,6 @@
|
||||
|
||||
.add-icon {
|
||||
transform: scale(2);
|
||||
margin-top: 2%;
|
||||
outline: none !important;
|
||||
}
|
||||
|
||||
@ -62,3 +77,37 @@ table {
|
||||
.placeholder {
|
||||
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 { throwToolbarMixedModesError } from '@angular/material/toolbar';
|
||||
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 {
|
||||
name: string;
|
||||
@ -11,11 +13,10 @@ export interface PeriodicElement {
|
||||
}
|
||||
|
||||
export interface Stock {
|
||||
count: number;
|
||||
currentPrice: number;
|
||||
symbol: string;
|
||||
count: Float32Array;
|
||||
lastTransaction: Date;
|
||||
boughtPrice: Float32Array;
|
||||
currentPrice: Float32Array;
|
||||
time: string;
|
||||
}
|
||||
|
||||
//symbol count lastTransaction boughtPrice currentPrice(+?)
|
||||
@ -24,43 +25,99 @@ const ELEMENT_DATA: PeriodicElement[] = [
|
||||
{ position: 1, name: 'Hydrogen', weight: 1.0079, symbol: 'H' },
|
||||
{ position: 2, name: 'Helium', weight: 4.0026, symbol: 'He' },
|
||||
{ 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({
|
||||
selector: 'app-dashboard',
|
||||
templateUrl: './dashboard.component.html',
|
||||
styleUrls: ['./dashboard.component.scss'],
|
||||
})
|
||||
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() {
|
||||
this.dataService.getStockData().subscribe((response: any) => {
|
||||
console.log(response);
|
||||
//TODO map data on array for display
|
||||
var data = JSON.parse(response);
|
||||
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) => {
|
||||
console.log(response);
|
||||
//TODO map data on array for display
|
||||
var data = JSON.parse(response);
|
||||
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;
|
||||
}
|
||||
|
@ -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>
|
||||
<span>Aktienbot</span>
|
||||
<a href=""><span>Aktienbot</span></a>
|
||||
<span class="example-spacer"></span>
|
||||
<button
|
||||
mat-icon-button
|
||||
|
@ -1,3 +1,8 @@
|
||||
.example-spacer {
|
||||
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 { 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({
|
||||
selector: 'app-profile',
|
||||
@ -6,7 +10,69 @@ import { Component, OnInit } from '@angular/core';
|
||||
styleUrls: ['./profile.component.scss'],
|
||||
})
|
||||
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 { 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 { LoginComponent } from './Views/login/login.component';
|
||||
import { ProfileComponent } from './Views/profile/profile.component';
|
||||
@ -24,7 +26,7 @@ const routes: Routes = [
|
||||
},
|
||||
{
|
||||
path: 'settings',
|
||||
component: ProfileComponent,
|
||||
component: BotSettingsComponent,
|
||||
},
|
||||
];
|
||||
|
||||
|
@ -1 +0,0 @@
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||
import { HttpClientModule } from '@angular/common/http';
|
||||
|
||||
import { MatToolbarModule } from '@angular/material/toolbar';
|
||||
@ -10,6 +10,9 @@ import { MatGridListModule } from '@angular/material/grid-list';
|
||||
import { MatCardModule } from '@angular/material/card';
|
||||
import { MatTableModule } from '@angular/material/table';
|
||||
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 { AppComponent } from './app.component';
|
||||
@ -20,6 +23,8 @@ import { DashboardComponent } from './Views/dashboard/dashboard.component';
|
||||
import { RegisterComponent } from './Views/register/register.component';
|
||||
import { ProfileComponent } from './Views/profile/profile.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({
|
||||
declarations: [
|
||||
@ -30,6 +35,8 @@ import { BotSettingsComponent } from './Views/bot-settings/bot-settings.componen
|
||||
RegisterComponent,
|
||||
ProfileComponent,
|
||||
BotSettingsComponent,
|
||||
UserDialogComponent,
|
||||
ConfirmationDialogComponent,
|
||||
],
|
||||
imports: [
|
||||
BrowserModule,
|
||||
@ -44,6 +51,10 @@ import { BotSettingsComponent } from './Views/bot-settings/bot-settings.componen
|
||||
FormsModule,
|
||||
HttpClientModule,
|
||||
MatMenuModule,
|
||||
MatDialogModule,
|
||||
MatInputModule,
|
||||
ReactiveFormsModule,
|
||||
MatChipsModule,
|
||||
],
|
||||
providers: [],
|
||||
bootstrap: [AppComponent],
|
||||
|
Loading…
x
Reference in New Issue
Block a user