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

This commit is contained in:
Linus E 2022-04-12 09:53:52 +02:00
commit 99d39a0ed9
75 changed files with 1909 additions and 210 deletions

View File

@ -4,6 +4,7 @@ pipeline:
commands: commands:
- echo -n "${CI_COMMIT_BRANCH//\//-}-${CI_COMMIT_SHA:0:8}, latest" > .tags - echo -n "${CI_COMMIT_BRANCH//\//-}-${CI_COMMIT_SHA:0:8}, latest" > .tags
when: when:
path: [ "frontend/**", "telegram_bot/**", "api/**" ]
event: push event: push
@ -80,6 +81,7 @@ pipeline:
- docker-compose pull - docker-compose pull
- docker-compose -p "aktienbot" up -d - docker-compose -p "aktienbot" up -d
when: when:
path: [ "frontend/**", "telegram_bot/**", "api/**" ]
event: push event: push
branches: main branches: main

View File

@ -16,3 +16,21 @@ WebEngineering2 Projekt: Aktien und News Bot für Telegram
## Dokumentation ## Dokumentation
-> README.md in /documentation -> README.md in /documentation
## Team
* Florian Kaiser
* Florian Kellermann
* Linus Eickhoff
* Kevin Pauer
## Nützliche Tools
- Portainer (https://gruppe1.testsites.info/portainer/) \
*Container Management System*
- phpMyAdmin (https://gruppe1.testsites.info/phpmyadmin/) \
*Administration von MySQL-Datenbanken*
- goaccess (https://gruppe1.testsites.info/goaccess/) \
*Webanalyseanwendung*
- Uptimekuma (https://uptimekuma.flokaiser.com/status/aktienbot) \
*Monitoring*
- Woodpecker (https://woodpecker.flokaiser.com/WebEngineering2/TelegramAktienBot) \
*Continuous Integration platform*

View File

@ -16,3 +16,6 @@ BOT_PASSWORD=
ADMIN_EMAIL= ADMIN_EMAIL=
ADMIN_USERNAME= ADMIN_USERNAME=
ADMIN_PASSWORD= ADMIN_PASSWORD=
# API URL (used for load_share_price.py and generate_sample_transactions.py)
API_URL=

View File

@ -1,16 +1,16 @@
FROM python:3.10-alpine FROM python:3.10-slim
# Change the working directory to the project root # Change the working directory to the root of the project
WORKDIR /srv/flask_app WORKDIR /srv/flask_app
# Install dependencies # Install dependencies
RUN apk add nginx build-base libffi-dev curl uwsgi RUN apt update && apt install -y python3 python3-pip curl nginx && rm -rf /var/lib/apt/lists/*
# Install python dependencies # Install the dependencies
COPY api/requirements.txt /srv/flask_app/ COPY api/requirements.txt /srv/flask_app/
RUN pip install -r requirements.txt --src /usr/local/src --no-warn-script-location RUN pip install -r requirements.txt --src /usr/local/src --no-warn-script-location
# Copy the app # Copy the source code to the working directory
COPY api /srv/flask_app COPY api /srv/flask_app
COPY api/deploy/nginx.conf /etc/nginx COPY api/deploy/nginx.conf /etc/nginx

View 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"
from app import create_app from app import create_app
# Create an application instance that web servers can use. # Create an application instance that web servers can use.

View 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"
from flask import current_app from flask import current_app
from apiflask import APIFlask from apiflask import APIFlask
@ -7,6 +13,7 @@ from flask_cors import CORS
from app.blueprints.keyword import keyword_blueprint from app.blueprints.keyword import keyword_blueprint
from app.blueprints.portfolio import portfolio_blueprint from app.blueprints.portfolio import portfolio_blueprint
from app.blueprints.shares import shares_blueprint from app.blueprints.shares import shares_blueprint
from app.blueprints.share_price import share_price_blueprint
from app.blueprints.transactions import transaction_blueprint from app.blueprints.transactions import transaction_blueprint
from app.blueprints.telegram import telegram_blueprint from app.blueprints.telegram import telegram_blueprint
from app.blueprints.user import users_blueprint from app.blueprints.user import users_blueprint
@ -23,13 +30,12 @@ def create_app(config_filename=None):
CORS(application, resources={r"*": {"origins": "*"}}) CORS(application, resources={r"*": {"origins": "*"}})
application.app_context().push()
db.init_app(application) db.init_app(application)
# api blueprints # api blueprints
application.register_blueprint(keyword_blueprint) application.register_blueprint(keyword_blueprint)
application.register_blueprint(shares_blueprint) application.register_blueprint(shares_blueprint)
application.register_blueprint(share_price_blueprint)
application.register_blueprint(transaction_blueprint) application.register_blueprint(transaction_blueprint)
application.register_blueprint(portfolio_blueprint) application.register_blueprint(portfolio_blueprint)
application.register_blueprint(users_blueprint) application.register_blueprint(users_blueprint)

View 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"
from flask import current_app from flask import current_app
import jwt import jwt

View File

@ -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"

View 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"
import os import os
from apiflask import APIBlueprint, abort from apiflask import APIBlueprint, abort

View File

@ -1,11 +1,17 @@
__author__ = "Florian Kaiser"
__copyright__ = "Copyright 2022, Project Aktienbot"
__credits__ = ["Florian Kaiser", "Florian Kellermann", "Linus Eickhof", "Kevin Pauer"]
__license__ = "GPL 3.0"
__version__ = "1.0.0"
import os import os
from apiflask import APIBlueprint from apiflask import APIBlueprint
from app.auth import auth
from app.schema import PortfolioResponseSchema
from app.db import database as db from app.db import database as db
from app.helper_functions import make_response, get_email_or_abort_401 from app.helper_functions import make_response, get_email_or_abort_401
from app.auth import auth from app.models import SharePrice
from app.schema import PortfolioResponseSchema
portfolio_blueprint = APIBlueprint('portfolio', __name__, url_prefix='/api') portfolio_blueprint = APIBlueprint('portfolio', __name__, url_prefix='/api')
__location__ = os.path.realpath(os.path.join(os.getcwd(), os.path.dirname(__file__))) __location__ = os.path.realpath(os.path.join(os.getcwd(), os.path.dirname(__file__)))
@ -23,11 +29,18 @@ def get_portfolio():
if transactions is not None: if transactions is not None:
for row in transactions: for row in transactions:
return_portfolio.append({ data = {
"symbol": row[0], "symbol": row[0],
"count": row[1], "count": row[1],
# "price": row[2], # "calculated_price": row[2],
"last_transaction": row[3] "last_transaction": row[3],
}) 'current_price': 0
}
query_share_price = db.session.query(SharePrice).filter_by(symbol=row[0]).order_by(SharePrice.date.desc()).first()
if query_share_price is not None:
data['current_price'] = query_share_price.as_dict()['price']
return_portfolio.append(data)
return make_response(return_portfolio, 200, "Successfully loaded symbols") return make_response(return_portfolio, 200, "Successfully loaded symbols")

View 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

View 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"
import os import os
from apiflask import APIBlueprint, abort from apiflask import APIBlueprint, abort

View File

@ -1,12 +1,16 @@
__author__ = "Florian Kaiser"
__copyright__ = "Copyright 2022, Project Aktienbot"
__credits__ = ["Florian Kaiser", "Florian Kellermann", "Linus Eickhof", "Kevin Pauer"]
__license__ = "GPL 3.0"
__version__ = "1.0.0"
import os import os
from apiflask import APIBlueprint, abort from apiflask import APIBlueprint, abort
from app.db import database as db
from app.helper_functions import make_response, get_email_or_abort_401
from app.auth import auth from app.auth import auth
from app.db import database as db
from app.helper_functions import make_response, get_email_or_abort_401, get_user
from app.schema import TelegramIdSchema, UsersSchema from app.schema import TelegramIdSchema, UsersSchema
from app.models import User
telegram_blueprint = APIBlueprint('telegram', __name__, url_prefix='/api') telegram_blueprint = APIBlueprint('telegram', __name__, url_prefix='/api')
__location__ = os.path.realpath(os.path.join(os.getcwd(), os.path.dirname(__file__))) __location__ = os.path.realpath(os.path.join(os.getcwd(), os.path.dirname(__file__)))
@ -23,7 +27,8 @@ def add_keyword(data):
if not check_if_telegram_user_id_data_exists(data): if not check_if_telegram_user_id_data_exists(data):
abort(400, message="User ID missing") abort(400, message="User ID missing")
query_user = db.session.query(User).filter_by(email=email).first() query_user = get_user(email)
query_user.telegram_user_id = data['telegram_user_id'] query_user.telegram_user_id = data['telegram_user_id']
db.session.commit() db.session.commit()

View File

@ -1,13 +1,19 @@
import os __author__ = "Florian Kaiser"
__copyright__ = "Copyright 2022, Project Aktienbot"
__credits__ = ["Florian Kaiser", "Florian Kellermann", "Linus Eickhof", "Kevin Pauer"]
__license__ = "GPL 3.0"
__version__ = "1.0.0"
import datetime import datetime
import os
from apiflask import abort, APIBlueprint from apiflask import abort, APIBlueprint
from app.auth import auth
from app.db import database as db from app.db import database as db
from app.helper_functions import make_response, get_email_or_abort_401 from app.helper_functions import make_response, get_email_or_abort_401
from app.models import Transaction from app.models import Transaction
from app.schema import TransactionSchema, TransactionResponseSchema from app.schema import TransactionSchema, TransactionResponseSchema
from app.auth import auth
transaction_blueprint = APIBlueprint('transaction', __name__, url_prefix='/api') transaction_blueprint = APIBlueprint('transaction', __name__, url_prefix='/api')
__location__ = os.path.realpath(os.path.join(os.getcwd(), os.path.dirname(__file__))) __location__ = os.path.realpath(os.path.join(os.getcwd(), os.path.dirname(__file__)))

View File

@ -1,15 +1,20 @@
__author__ = "Florian Kaiser"
__copyright__ = "Copyright 2022, Project Aktienbot"
__credits__ = ["Florian Kaiser", "Florian Kellermann", "Linus Eickhof", "Kevin Pauer"]
__license__ = "GPL 3.0"
__version__ = "1.0.0"
import datetime import datetime
import os import os
from flask import current_app
import jwt import jwt
from apiflask import APIBlueprint, abort from apiflask import APIBlueprint, abort
from app.db import database as db
from app.helper_functions import check_password, hash_password, abort_if_no_admin, make_response, get_email_or_abort_401
from app.models import User
from app.schema import UsersSchema, TokenSchema, LoginDataSchema, AdminDataSchema, DeleteUserSchema, RegisterDataSchema, UpdateUserDataSchema
from app.auth import auth from app.auth import auth
from app.db import database as db
from app.helper_functions import check_password, hash_password, abort_if_no_admin, make_response, get_email_or_abort_401, get_user
from app.models import User
from app.schema import UsersSchema, TokenSchema, LoginDataSchema, AdminDataSchema, DeleteUserSchema, RegisterDataSchema, UpdateUserDataSchema, CronDataSchema
from flask import current_app
users_blueprint = APIBlueprint('users', __name__, url_prefix='/api') users_blueprint = APIBlueprint('users', __name__, url_prefix='/api')
__location__ = os.path.realpath(os.path.join(os.getcwd(), os.path.dirname(__file__))) __location__ = os.path.realpath(os.path.join(os.getcwd(), os.path.dirname(__file__)))
@ -36,9 +41,9 @@ def users():
def user(): def user():
email = get_email_or_abort_401() email = get_email_or_abort_401()
res = db.session.query(User).filter_by(email=email).first().as_dict() query_user = get_user(email)
return make_response(res, 200, "Successfully received current user data") return make_response(query_user.as_dict(), 200, "Successfully received current user data")
@users_blueprint.route('/user/login', methods=['POST']) @users_blueprint.route('/user/login', methods=['POST'])
@ -55,10 +60,7 @@ def login(data):
email = data['email'] email = data['email']
password = data['password'] password = data['password']
query_user = db.session.query(User).filter_by(email=email).first() query_user = get_user(email)
if query_user is None: # email doesn't exist
abort(500, message="Unable to login")
if not check_password(query_user.password, password.encode("utf-8")): # Password incorrect if not check_password(query_user.password, password.encode("utf-8")): # Password incorrect
abort(500, message="Unable to login") abort(500, message="Unable to login")
@ -98,7 +100,8 @@ def register(data):
email=email, email=email,
username=username, username=username,
password=hash_password(password), password=hash_password(password),
admin=False admin=False,
cron="0 8 * * *"
) )
db.session.add(new_user) db.session.add(new_user)
db.session.commit() db.session.commit()
@ -114,7 +117,7 @@ def register(data):
def update_user(data): def update_user(data):
email = get_email_or_abort_401() email = get_email_or_abort_401()
query_user = db.session.query(User).filter_by(email=email).first() query_user = get_user(email)
if check_if_password_data_exists(data): if check_if_password_data_exists(data):
query_user.password = hash_password(data['password']) query_user.password = hash_password(data['password'])
@ -144,10 +147,7 @@ def set_admin(data):
email = data['email'] email = data['email']
admin = data['admin'] admin = data['admin']
query_user = db.session.query(User).filter_by(email=email).first() query_user = get_user(email)
if query_user is None: # Username doesn't exist
abort(500, message="Unable to update user")
query_user.admin = admin query_user.admin = admin
db.session.commit() db.session.commit()
@ -155,6 +155,23 @@ def set_admin(data):
return make_response({}, 200, "Successfully updated users admin rights") return make_response({}, 200, "Successfully updated users admin rights")
@users_blueprint.route('/user/setCron', methods=['PUT'])
@users_blueprint.output({}, 200)
@users_blueprint.input(schema=CronDataSchema)
@users_blueprint.auth_required(auth)
@users_blueprint.doc(summary="Set update cron", description="Set update cron of specified user")
def set_cron(data):
email = get_email_or_abort_401()
if not check_if_cron_data_exists(data):
abort(400, "Cron data missing")
get_user(email).cron = data['cron']
db.session.commit()
return make_response({}, 200, "Successfully updated users cron")
@users_blueprint.route('/user', methods=['DELETE']) @users_blueprint.route('/user', methods=['DELETE'])
@users_blueprint.output({}, 200) @users_blueprint.output({}, 200)
@users_blueprint.input(schema=DeleteUserSchema) @users_blueprint.input(schema=DeleteUserSchema)
@ -216,3 +233,13 @@ def check_if_admin_data_exists(data):
return False return False
return True return True
def check_if_cron_data_exists(data):
if "cron" not in data:
return False
if data['cron'] == "" or data['cron'] is None:
return False
return True

View 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"
import os import os
from app.schema import BaseResponseSchema from app.schema import BaseResponseSchema

View 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"
import os import os
from app.schema import BaseResponseSchema from app.schema import BaseResponseSchema

View 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"
from flask_sqlalchemy import SQLAlchemy from flask_sqlalchemy import SQLAlchemy
# database object # database object

View File

@ -1,12 +1,16 @@
from flask import current_app __author__ = "Florian Kaiser"
__copyright__ = "Copyright 2022, Project Aktienbot"
__credits__ = ["Florian Kaiser", "Florian Kellermann", "Linus Eickhof", "Kevin Pauer"]
__license__ = "GPL 3.0"
__version__ = "1.0.0"
import bcrypt import bcrypt
import jwt import jwt
from apiflask import abort from apiflask import abort
from flask import request, jsonify
from app.db import database as db from app.db import database as db
from app.models import User from app.models import User
from flask import current_app
from flask import request, jsonify
def hash_password(password): def hash_password(password):
@ -17,11 +21,8 @@ def check_password(hashed_password, user_password):
return bcrypt.checkpw(user_password, hashed_password) return bcrypt.checkpw(user_password, hashed_password)
def get_email_from_token_data(): def get_email_from_token_data(token):
if 'Authorization' in request.headers: if token is None or len(token) < 2:
token = request.headers['Authorization'].split(" ")
if len(token) < 2:
return None return None
else: else:
token = token[1] token = token[1]
@ -50,12 +51,17 @@ def get_email_from_token_data():
except jwt.PyJWTError: except jwt.PyJWTError:
return None return None
def get_token():
if 'Authorization' in request.headers:
return request.headers['Authorization'].split(" ")
else:
return None return None
def get_email_or_abort_401(): def get_email_or_abort_401():
# get username from jwt token # get username from jwt token
email = get_email_from_token_data() email = get_email_from_token_data(get_token())
if email is None: # If token not provided or invalid -> return 401 code if email is None: # If token not provided or invalid -> return 401 code
abort(401, message="Unable to login") abort(401, message="Unable to login")
@ -76,3 +82,12 @@ def is_user_admin():
def make_response(data, status=200, text=""): def make_response(data, status=200, text=""):
return jsonify({"status": status, "text": text, "data": data}) return jsonify({"status": status, "text": text, "data": data})
def get_user(email):
query_user = db.session.query(User).filter_by(email=email).first()
if query_user is None: # Username doesn't exist
abort(500, message="Can't find user")
return query_user

View 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"
from app.db import database as db from app.db import database as db
@ -7,14 +13,16 @@ class User(db.Model):
password = db.Column('password', db.BINARY(60), nullable=False) password = db.Column('password', db.BINARY(60), nullable=False)
username = db.Column('username', db.String(255), nullable=False, server_default='') username = db.Column('username', db.String(255), nullable=False, server_default='')
telegram_user_id = db.Column('telegram_user_id', db.String(255), nullable=True, server_default='') telegram_user_id = db.Column('telegram_user_id', db.String(255), nullable=True, server_default='')
admin = db.Column('admin', db.Boolean(), server_default='0') admin = db.Column('admin', db.Boolean(), server_default='0') # 0 = False, 1 = True
cron = db.Column('cron', db.String(20), server_default='0 8 * * *', nullable=False)
def as_dict(self): def as_dict(self):
return { return {
"email": self.email, "email": self.email,
"username": self.username, "username": self.username,
"telegram_user_id": self.telegram_user_id, "telegram_user_id": self.telegram_user_id,
"admin": self.admin "admin": self.admin,
"cron": self.cron
} }
@ -49,3 +57,14 @@ class Share(db.Model):
def as_dict(self): def as_dict(self):
return {c.name: getattr(self, c.name) for c in self.__table__.columns} return {c.name: getattr(self, c.name) for c in self.__table__.columns}
class SharePrice(db.Model):
__tablename__ = 'share_price'
id = db.Column('id', db.Integer(), nullable=False, unique=True, primary_key=True)
symbol = db.Column('symbol', db.String(255))
price = db.Column('price', db.Float())
date = db.Column('date', db.DateTime())
def as_dict(self):
return {c.name: getattr(self, c.name) for c in self.__table__.columns}

View 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"
from apiflask import Schema from apiflask import Schema
from apiflask.fields import Integer, String, Boolean, Field, Float from apiflask.fields import Integer, String, Boolean, Field, Float
from marshmallow import validate from marshmallow import validate
@ -23,6 +29,10 @@ class AdminDataSchema(Schema):
admin = Boolean() admin = Boolean()
class CronDataSchema(Schema):
cron = String()
class TokenSchema(Schema): class TokenSchema(Schema):
token = String() token = String()
@ -71,6 +81,12 @@ class TransactionSchema(Schema):
price = Float() price = Float()
class SymbolPriceSchema(Schema):
symbol = String()
time = String(validate=validate.Regexp(r"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z"))
price = Float()
class TelegramIdSchema(Schema): class TelegramIdSchema(Schema):
telegram_user_id = String() telegram_user_id = String()

View 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
View 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)

View File

@ -11,3 +11,6 @@ bcrypt==3.2.0
pytest~=7.1.1 pytest~=7.1.1
pytest-cov pytest-cov
marshmallow~=3.15.0 marshmallow~=3.15.0
faker~=13.3.4
yfinance~=0.1.70
requests~=2.27.1

View 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"
import pytest import pytest
from app import create_app, db from app import create_app, db
from app.models import User, Transaction, Keyword, Share from app.models import User, Transaction, Keyword, Share

View File

@ -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"

View 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"
import json import json

View 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_keyword.py) contains the functional tests for the `keyword` blueprint. This file (test_keyword.py) contains the functional tests for the `keyword` blueprint.
""" """

View 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_portfolio.py) contains the functional tests for the `portfolio` blueprint. This file (test_portfolio.py) contains the functional tests for the `portfolio` blueprint.
""" """

View 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_share.py) contains the functional tests for the `share` blueprint. This file (test_share.py) contains the functional tests for the `share` blueprint.
""" """

View 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_telegram.py) contains the functional tests for the `telegram` blueprint. This file (test_telegram.py) contains the functional tests for the `telegram` blueprint.
""" """

View 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_transaction.py) contains the functional tests for the `transaction` blueprint. This file (test_transaction.py) contains the functional tests for the `transaction` blueprint.
""" """

View File

@ -1,7 +1,14 @@
__author__ = "Florian Kaiser"
__copyright__ = "Copyright 2022, Project Aktienbot"
__credits__ = ["Florian Kaiser", "Florian Kellermann", "Linus Eickhof", "Kevin Pauer"]
__license__ = "GPL 3.0"
__version__ = "1.0.0"
""" """
This file (test_user.py) contains the functional tests for the `users` blueprint. This file (test_user.py) contains the functional tests for the `users` blueprint.
""" """
import json import json
from tests.functional.helper_functions import get_token from tests.functional.helper_functions import get_token
@ -35,7 +42,7 @@ def test_login_user_not_exist(test_client, init_database):
""" """
response = test_client.post('/api/user/login', data=json.dumps(dict(email="notexistinguser@example.com", password="password")), content_type='application/json') response = test_client.post('/api/user/login', data=json.dumps(dict(email="notexistinguser@example.com", password="password")), content_type='application/json')
assert response.status_code == 500 assert response.status_code == 500
assert b'Unable to login' in response.data assert b'Can\'t find user' in response.data
def test_login_email_missing(test_client, init_database): def test_login_email_missing(test_client, init_database):
@ -407,7 +414,7 @@ def test_set_admin_admin1_logged_in_user_not_exist(test_client, init_database):
headers={"Authorization": "Bearer {}".format(get_token(test_client, "admin1@example.com", "admin1"))}, headers={"Authorization": "Bearer {}".format(get_token(test_client, "admin1@example.com", "admin1"))},
content_type='application/json') content_type='application/json')
assert response.status_code == 500 assert response.status_code == 500
assert b'Unable to update user' in response.data assert b'Can\'t find user' in response.data
def test_set_admin_admin1_logged_in_email_missing(test_client, init_database): def test_set_admin_admin1_logged_in_email_missing(test_client, init_database):
@ -466,6 +473,94 @@ def test_set_admin_admin1_logged_in_admin_empty(test_client, init_database):
assert b'Field may not be null' in response.data assert b'Field may not be null' in response.data
def test_set_cron_user_not_logged_in(test_client, init_database):
"""
Test PUT '/api/user/setCron'
User is not logged in
"""
response = test_client.put('/api/user/setCron')
assert response.status_code == 401
assert b'Unauthorized' in response.data
def test_set_cron_user1_logged_in(test_client, init_database):
"""
Test PUT '/api/user/setCron'
User1 is logged in
"""
response = test_client.put('/api/user/setCron', data=json.dumps(dict(cron="* * * * *")),
headers={"Authorization": "Bearer {}".format(get_token(test_client, "user1@example.com", "password"))},
content_type='application/json')
assert response.status_code == 200
assert b'Successfully updated users cron' in response.data
def test_set_empty_cron_user1_logged_in(test_client, init_database):
"""
Test PUT '/api/user/setCron'
User1 is logged in
Interval is empty
"""
response = test_client.put('/api/user/setCron', data=json.dumps(dict(cron="")),
headers={"Authorization": "Bearer {}".format(get_token(test_client, "user1@example.com", "password"))},
content_type='application/json')
assert response.status_code == 400
def test_set_cron_bot_logged_in_user_exists(test_client, init_database):
"""
Test PUT '/api/user/setCron'
Bot1 is logged in and requests user 12345678
"""
response = test_client.put('/api/user/setCron', data=json.dumps(dict(cron="* * * * *")),
headers={"Authorization": "Bearer {}".format(get_token(test_client, "bot1@example.com", "bot1") + ":12345678")},
content_type='application/json')
assert response.status_code == 200
assert b'Successfully updated users cron' in response.data
def test_set_cron_bot_logged_in_user_not_exists(test_client, init_database):
"""
Test PUT '/api/user/setCron'
Bot1 is logged in and requests user 1234 (not existing)
"""
response = test_client.put('/api/user/setCron', data=json.dumps(dict(cron="* * * * *")),
headers={"Authorization": "Bearer {}".format(get_token(test_client, "bot1@example.com", "bot1") + ":1234")},
content_type='application/json')
assert response.status_code == 401
assert b'Unable to login' in response.data
def test_set_cron_user1_logged_in_but_no_bot(test_client, init_database):
"""
Test PUT '/api/user/setCron'
User1 is logged in and requests user 1234 (not existing)
Fails because user1 is not a bot
"""
response = test_client.put('/api/user/setCron', data=json.dumps(dict(cron="* * * * *")),
headers={"Authorization": "Bearer {}".format(get_token(test_client, "user1@example.com", "password") + ":12345678")},
content_type='application/json')
assert response.status_code == 401
assert b'Unable to login' in response.data
def test_set_cron_invalid_token(test_client, init_database):
"""
Test PUT '/api/user/setCron'
Invalid Bearer token
"""
response = test_client.put('/api/user/setCron', headers={"Authorization": "Bearer {}".format("invalidtoken:12345678")})
assert response.status_code == 401
assert b'Unauthorized' in response.data
def test_get_users_not_logged_in(test_client, init_database): def test_get_users_not_logged_in(test_client, init_database):
""" """
Test GET '/api/users' Test GET '/api/users'

View File

@ -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"

View File

@ -1,3 +1,9 @@
__author__ = "Florian Kaiser"
__copyright__ = "Copyright 2022, Project Aktienbot"
__credits__ = ["Florian Kaiser", "Florian Kellermann", "Linus Eickhof", "Kevin Pauer"]
__license__ = "GPL 3.0"
__version__ = "1.0.0"
""" """
This file (test_helper_functions.py) contains the unit tests for the helper_functions.py file. This file (test_helper_functions.py) contains the unit tests for the helper_functions.py file.
""" """

View File

@ -1,3 +1,9 @@
__author__ = "Florian Kaiser"
__copyright__ = "Copyright 2022, Project Aktienbot"
__credits__ = ["Florian Kaiser", "Florian Kellermann", "Linus Eickhof", "Kevin Pauer"]
__license__ = "GPL 3.0"
__version__ = "1.0.0"
""" """
This file (test_helper_functions.py) contains the unit tests for the helper_functions.py file. This file (test_helper_functions.py) contains the unit tests for the helper_functions.py file.
""" """
@ -18,3 +24,10 @@ def test_check_password():
hashed = hash_password("password") hashed = hash_password("password")
assert check_password(hashed, "password".encode("utf-8")) is True assert check_password(hashed, "password".encode("utf-8")) is True
assert check_password(hashed, "password1".encode("utf-8")) is False assert check_password(hashed, "password1".encode("utf-8")) is False
def test_get_email_from_token():
"""
Test get_email_from_token function
"""
assert get_email_from_token_data(None) is None

View File

@ -1,9 +1,14 @@
__author__ = "Florian Kaiser"
__copyright__ = "Copyright 2022, Project Aktienbot"
__credits__ = ["Florian Kaiser", "Florian Kellermann", "Linus Eickhof", "Kevin Pauer"]
__license__ = "GPL 3.0"
__version__ = "1.0.0"
""" """
This file (test_models.py) contains the unit tests for the models.py file. This file (test_models.py) contains the unit tests for the models.py file.
""" """
from app.models import User, Transaction, Keyword, Share
from app.helper_functions import hash_password from app.helper_functions import hash_password
from app.models import User, Transaction, Keyword, Share
def test_new_user(): def test_new_user():
@ -16,14 +21,15 @@ def test_new_user():
email="user@example.com", email="user@example.com",
username="user", username="user",
password=hash_password("password"), password=hash_password("password"),
admin=False admin=False,
cron="0 8 * * *",
) )
assert user.email == 'user@example.com' assert user.email == 'user@example.com'
assert user.password != 'password' assert user.password != 'password'
assert user.username == "user" assert user.username == "user"
assert user.telegram_user_id is None assert user.telegram_user_id is None
assert user.admin is False assert user.admin is False
assert user.as_dict() == {'email': 'user@example.com', 'username': 'user', 'telegram_user_id': None, 'admin': False} assert user.as_dict() == {'cron': '0 8 * * *', 'email': 'user@example.com', 'username': 'user', 'telegram_user_id': None, 'admin': False}
def test_new_user_with_fixture(new_user): def test_new_user_with_fixture(new_user):

View 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_transaction.py) contains the unit tests for the blueprints/transaction.py file. This file (test_transaction.py) contains the unit tests for the blueprints/transaction.py file.
""" """

View File

@ -1,3 +1,9 @@
__author__ = "Florian Kaiser"
__copyright__ = "Copyright 2022, Project Aktienbot"
__credits__ = ["Florian Kaiser", "Florian Kellermann", "Linus Eickhof", "Kevin Pauer"]
__license__ = "GPL 3.0"
__version__ = "1.0.0"
""" """
This file (test_helper_functions.py) contains the unit tests for the helper_functions.py file. This file (test_helper_functions.py) contains the unit tests for the helper_functions.py file.
""" """
@ -38,3 +44,12 @@ def test_check_if_admin_data_exists():
assert check_if_admin_data_exists(dict(admin=True)) is True assert check_if_admin_data_exists(dict(admin=True)) is True
assert check_if_admin_data_exists(dict(admin=None)) is False assert check_if_admin_data_exists(dict(admin=None)) is False
assert check_if_admin_data_exists(dict()) is False assert check_if_admin_data_exists(dict()) is False
def test_check_if_cron_data_exists():
"""
Test check_if_cron_data_exists function
"""
assert check_if_cron_data_exists(dict(cron="* * * * *")) is True
assert check_if_cron_data_exists(dict(cron="")) is False
assert check_if_cron_data_exists(dict()) is False

View File

@ -5,7 +5,7 @@ services:
image: registry.flokaiser.com/aktienbot/frontend image: registry.flokaiser.com/aktienbot/frontend
labels: labels:
traefik.enable: 'true' traefik.enable: 'true'
traefik.http.routers.aktienbot_fe.rule: Host(`gruppe1.testsites.info`) traefik.http.routers.aktienbot_fe.rule: Host(`gruppe1.testsites.info`) && !PathPrefix(`/api`) && !PathPrefix(`/phpmyadmin`) && !PathPrefix(`/portainer`)
traefik.http.routers.aktienbot_fe.middlewares: secHeaders@file traefik.http.routers.aktienbot_fe.middlewares: secHeaders@file
traefik.http.routers.aktienbot_fe.priority: 40 traefik.http.routers.aktienbot_fe.priority: 40
traefik.http.routers.aktienbot_fe.tls: true traefik.http.routers.aktienbot_fe.tls: true

View File

@ -12,6 +12,41 @@ services:
- ${PWD}/traefik-dynamic.toml:/etc/traefik/traefik-dynamic.toml - ${PWD}/traefik-dynamic.toml:/etc/traefik/traefik-dynamic.toml
- ${PWD}/acme.json:/etc/traefik/acme.json - ${PWD}/acme.json:/etc/traefik/acme.json
- ${PWD}/access.log:/etc/traefik/access.log - ${PWD}/access.log:/etc/traefik/access.log
- ${PWD}/users:/etc/traefik/users
goaccess:
image: allinurl/goaccess
command:
- --no-global-config
- --config-file=/srv/data/goaccess.conf
- --num-tests=0
volumes:
- ${PWD}/access.log:/srv/logs/access.log:ro
- ${PWD}/goaccess.conf:/srv/data/goaccess.conf
- goaccess_data:/srv/data
- goaccess_report:/srv/report
labels:
traefik.enable: true
traefik.http.routers.goaccess.rule: Host(`gruppe1.testsites.info`) && PathPrefix(`/goaccess/ws`)
traefik.http.routers.goaccess.priority: 55
traefik.http.routers.goaccess.middlewares: strip_goaccess,goaccess_auth,secHeaders@file
traefik.http.routers.goaccess.tls: true
traefik.http.routers.goaccess.tls.certresolver: myresolver
nginx:
image: nginx
volumes:
- goaccess_report:/usr/share/nginx/html
labels:
traefik.enable: true
traefik.http.routers.goaccess_web.rule: Host(`gruppe1.testsites.info`) && PathPrefix(`/goaccess`)
traefik.http.routers.goaccess_web.priority: 50
traefik.http.routers.goaccess_web.middlewares: strip_goaccess,goaccess_auth,secHeaders@file
traefik.http.routers.goaccess_web.tls: true
traefik.http.routers.goaccess_web.tls.certresolver: myresolver
traefik.http.middlewares.strip_goaccess.stripprefix.prefixes: /goaccess
traefik.http.middlewares.goaccess_auth.basicauth.usersfile: /etc/traefik/users
portainer: portainer:
image: portainer/portainer-ce image: portainer/portainer-ce
@ -36,3 +71,5 @@ networks:
volumes: volumes:
portainer_data: portainer_data:
goaccess_report:
goaccess_data:

View 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
View File

@ -0,0 +1 @@
# Create user by using ```htpasswd -nb user password``` and append output to this file.

View File

@ -1,10 +1,43 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Stock } from '../Models/stock.model'; import { BotService } from '../Services/bot.service';
import { Keyword, Share } from '../Views/bot-settings/bot-settings.component';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root',
}) })
export class HelperService { export class HelperService {
constructor(private botService: BotService) {}
constructor() { } /**
* @param {number} ms
*/
delay(ms: number) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
formatShareData(): Share[] {
var shares: Share[] = [];
this.botService.getSymbols().subscribe((result) => {
var data = JSON.parse(result);
for (let i = 0; i < data.data.length; i++) {
shares.push({
symbol: data.data[i].symbol,
});
}
});
return shares;
}
formatKeywordsData(): Keyword[] {
var keywords: Keyword[] = [];
this.botService.getKeywords().subscribe((result) => {
var data = JSON.parse(result);
for (let i = 0; i < data.data.length; i++) {
keywords.push({
name: data.data[i].keyword,
});
}
});
return keywords;
}
} }

View File

@ -1,6 +0,0 @@
export class Stock {
count = 0;
price = 0;
symbol = '';
time = '';
}

View File

@ -1,7 +1,8 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http'; import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
const AUTH_API = 'https://aktienbot.flokaiser.com/api/user/'; const AUTH_API = 'https://gruppe1.testsites.info/api/user';
const httpOptions = { const httpOptions = {
headers: new HttpHeaders({ 'Content-Type': 'application/json' }), headers: new HttpHeaders({ 'Content-Type': 'application/json' }),
}; };
@ -11,12 +12,25 @@ const httpOptions = {
}) })
export class AuthService { export class AuthService {
constructor(private http: HttpClient) {} constructor(private http: HttpClient) {}
/**
* @param {string} email
* @param {string} password
* @returns Observable
*/
login(email: string, password: string): Observable<any> { login(email: string, password: string): Observable<any> {
return this.http.post(AUTH_API + '/login', { return this.http.post(AUTH_API + '/login', {
email, email,
password, password,
}); });
} }
/**
* @param {string} email
* @param {string} username
* @param {string} password
* @returns Observable
*/
register(email: string, username: string, password: string): Observable<any> { register(email: string, username: string, password: string): Observable<any> {
return this.http.post( return this.http.post(
AUTH_API + '/register', AUTH_API + '/register',

View 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();
});
});

View 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,
},
});
}
}

View File

@ -2,16 +2,23 @@ import { Injectable, OnInit } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http'; import { HttpClient, HttpHeaders } from '@angular/common/http';
import { delay, Observable } from 'rxjs'; import { delay, Observable } from 'rxjs';
import { TokenStorageService } from './token.service'; import { TokenStorageService } from './token.service';
const API_URL = 'https://aktienbot.flokaiser.com/api/'; const API_URL = 'https://gruppe1.testsites.info/api/';
@Injectable({ @Injectable({
providedIn: 'root', providedIn: 'root',
}) })
export class DataService { export class DataService {
/**
* @param {HttpClient} privatehttp
* @param {TokenStorageService} privatetokenStorage
*/
constructor( constructor(
private http: HttpClient, private http: HttpClient,
private tokenStorage: TokenStorageService private tokenStorage: TokenStorageService
) {} ) {}
/**
* @returns Observable
*/
public getStockData(): Observable<any> { public getStockData(): Observable<any> {
return this.http.get(API_URL + 'portfolio', { return this.http.get(API_URL + 'portfolio', {
headers: new HttpHeaders({ headers: new HttpHeaders({
@ -22,6 +29,9 @@ export class DataService {
}); });
} }
/**
* @returns Observable
*/
public getTransactionData(): Observable<any> { public getTransactionData(): Observable<any> {
return this.http.get(API_URL + 'transactions', { return this.http.get(API_URL + 'transactions', {
headers: new HttpHeaders({ headers: new HttpHeaders({
@ -32,6 +42,41 @@ export class DataService {
}); });
} }
/**
* @param {string} symbol
* @param {Date} time
* @param {number} count
* @param {number} price
* @returns Observable
*/
public createTransaction(
symbol: string,
time: string,
count: number,
price: number
): Observable<any> {
time = time + 'T12:00:00.000Z';
price.toFixed(2);
return this.http.post(
API_URL + 'transaction',
{
count,
price,
symbol,
time,
},
{
headers: new HttpHeaders({
'Content-Type': 'application/json',
Authorization: 'Bearer ' + this.tokenStorage.getToken(),
}),
}
);
}
/**
* @returns Observable
*/
public getKeywords(): Observable<any> { public getKeywords(): Observable<any> {
return this.http.get(API_URL + 'keywords', { return this.http.get(API_URL + 'keywords', {
headers: new HttpHeaders({ headers: new HttpHeaders({

View 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();
});
});

View 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(),
}),
}
);
}
}

View File

@ -1,13 +1,13 @@
import { TestBed } from '@angular/core/testing'; import { TestBed } from '@angular/core/testing';
import { TokenService } from './token.service'; import { TokenStorageService } from './token.service';
describe('TokenService', () => { describe('TokenStorageService', () => {
let service: TokenService; let service: TokenStorageService;
beforeEach(() => { beforeEach(() => {
TestBed.configureTestingModule({}); TestBed.configureTestingModule({});
service = TestBed.inject(TokenService); service = TestBed.inject(TokenStorageService);
}); });
it('should be created', () => { it('should be created', () => {

View File

@ -6,20 +6,42 @@ const USER_KEY = 'auth-user';
}) })
export class TokenStorageService { export class TokenStorageService {
constructor() {} constructor() {}
/**
* @returns void
*/
signOut(): void { signOut(): void {
window.sessionStorage.clear(); window.sessionStorage.clear();
} }
/**
* @param {string} token
* @returns void
*/
public saveToken(token: string): void { public saveToken(token: string): void {
window.sessionStorage.removeItem(TOKEN_KEY); window.sessionStorage.removeItem(TOKEN_KEY);
window.sessionStorage.setItem(TOKEN_KEY, token); window.sessionStorage.setItem(TOKEN_KEY, token);
} }
/**
* @returns string
*/
public getToken(): string | null { public getToken(): string | null {
return window.sessionStorage.getItem(TOKEN_KEY); return window.sessionStorage.getItem(TOKEN_KEY);
} }
/**
* @param {any} user
* @returns void
*/
public saveUser(user: any): void { public saveUser(user: any): void {
window.sessionStorage.removeItem(USER_KEY); window.sessionStorage.removeItem(USER_KEY);
window.sessionStorage.setItem(USER_KEY, JSON.stringify(user)); window.sessionStorage.setItem(USER_KEY, JSON.stringify(user));
} }
/**
* @returns any
*/
public getUser(): any { public getUser(): any {
const user = window.sessionStorage.getItem(USER_KEY); const user = window.sessionStorage.getItem(USER_KEY);
if (user) { if (user) {

View File

@ -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>

View File

@ -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%;
}

View File

@ -1,15 +1,102 @@
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { C, COMMA, ENTER, F } from '@angular/cdk/keycodes';
import { MatChipInputEvent } from '@angular/material/chips';
import { BotService } from 'src/app/Services/bot.service';
import { HelperService } from 'src/app/Helpers/helper.service';
export interface Fruit {
name: string;
}
export interface Share {
symbol: string;
}
export interface Keyword {
name: string;
}
@Component({ @Component({
selector: 'app-bot-settings', selector: 'app-bot-settings',
templateUrl: './bot-settings.component.html', templateUrl: './bot-settings.component.html',
styleUrls: ['./bot-settings.component.scss'] styleUrls: ['./bot-settings.component.scss'],
}) })
export class BotSettingsComponent implements OnInit { export class BotSettingsComponent implements OnInit {
keywords: Keyword[] = [];
shares: Share[] = [];
constructor() { } constructor(private botService: BotService, private helper: HelperService) {}
ngOnInit(): void { ngOnInit(): void {
this.shares = this.helper.formatShareData();
this.keywords = this.helper.formatKeywordsData();
} }
addOnBlur = true;
readonly separatorKeysCodes = [ENTER, COMMA] as const;
async addKeyword(event: MatChipInputEvent): Promise<void> {
const value = (event.value || '').trim();
// Add keyword to database
if (value && !this.keywords.includes({ name: value })) {
console.log('Added: ' + value);
this.botService.createKeyword(value).subscribe((result) => {
console.log(result);
});
}
// Clear the input value
event.chipInput!.clear();
if (value) {
await this.helper.delay(1000);
this.keywords = [];
this.keywords = this.helper.formatKeywordsData();
}
}
async removeKeyword(keyword: Keyword): Promise<void> {
this.botService.deleteKeyword(keyword.name).subscribe((result) => {
console.log(result);
});
await this.helper.delay(1000);
this.keywords = [];
this.keywords = this.helper.formatKeywordsData();
}
async addShare(event: MatChipInputEvent): Promise<void> {
const value = (event.value || '').trim();
// Add share to database
if (value && !this.shares.includes({ symbol: value })) {
console.log('Added: ' + value);
this.botService.createShare(value).subscribe((result) => {
console.log(result);
});
}
// Clear the input value
event.chipInput!.clear();
if (value) {
await this.helper.delay(1000);
this.shares = [];
this.shares = this.helper.formatShareData();
}
}
async removeShare(share: Share): Promise<void> {
this.botService.deleteShare(share.symbol).subscribe((result) => {
console.log(result);
});
await this.helper.delay(1000);
this.shares = [];
this.shares = this.helper.formatShareData();
}
} }

View File

@ -3,100 +3,148 @@
<mat-grid-tile colspan="1" rowspan="2"> <mat-grid-tile colspan="1" rowspan="2">
<div class="stockOverview"> <div class="stockOverview">
<div class="heading"> <div class="heading">
<div class="vertical-center">Aktienübersicht</div> <div class="vertical-center">Stocks</div>
<span class="spacer"></span>
<button
mat-icon-button
class="add-icon"
aria-label="Example icon-button with heart icon"
[disableRipple]="true"
>
<mat-icon>add</mat-icon>
</button>
</div> </div>
<div class="stockTable"> <mat-card class="placeholder">
<table mat-table [dataSource]="dataSource" class="mat-elevation-z8"> <div class="stockTableLHS">
<table mat-table [dataSource]="dataSourceStocks">
<!--- Note that these columns can be defined in any order. <!--- Note that these columns can be defined in any order.
The actual rendered columns are set as a property on the row definition" --> The actual rendered columns are set as a property on the row definition" -->
<!-- Position Column --> <!-- Symbol Column -->
<ng-container matColumnDef="position"> <ng-container matColumnDef="position">
<th mat-header-cell *matHeaderCellDef>Symbol</th> <th mat-header-cell *matHeaderCellDef>Symbol</th>
<td mat-cell *matCellDef="let element">{{ element.position }}</td>
</ng-container>
<!-- Name Column -->
<ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef>Name</th>
<td mat-cell *matCellDef="let element">{{ element.name }}</td>
</ng-container>
<!-- Weight Column -->
<ng-container matColumnDef="weight">
<th mat-header-cell *matHeaderCellDef>Volume</th>
<td mat-cell *matCellDef="let element">{{ element.weight }}</td>
</ng-container>
<!-- Symbol Column -->
<ng-container matColumnDef="symbol">
<th mat-header-cell *matHeaderCellDef>Worth</th>
<td mat-cell *matCellDef="let element">{{ element.symbol }}</td> <td mat-cell *matCellDef="let element">{{ element.symbol }}</td>
</ng-container> </ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr> <!-- Count Column -->
<tr mat-row *matRowDef="let row; columns: displayedColumns"></tr> <ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef>Count</th>
<td mat-cell *matCellDef="let element">{{ element.count }}</td>
</ng-container>
<!-- Time Column -->
<ng-container matColumnDef="weight">
<th mat-header-cell *matHeaderCellDef>Time</th>
<td mat-cell *matCellDef="let element">{{ element.time }}</td>
</ng-container>
<!-- Time Column -->
<ng-container matColumnDef="current-price">
<th mat-header-cell *matHeaderCellDef>Current Price</th>
<td mat-cell *matCellDef="let element">
{{ element.currentPrice }}
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumnsStocks"></tr>
<tr
mat-row
*matRowDef="let row; columns: displayedColumnsStocks"
></tr>
</table> </table>
</div> </div>
</mat-card>
</div> </div>
</mat-grid-tile> </mat-grid-tile>
<!-- Depot Overview --> <!-- Depot Overview -->
<mat-grid-tile colspan="1" rowspan="1" class="right-side"> <mat-grid-tile colspan="1" rowspan="1" class="right-side">
<div class="depotOverview"> <div class="depotOverview">
<div class="heading fix-right-side"> <div class="heading fix-right-side">
<div class="vertical-center">Depotübersicht</div> <div class="vertical-center">Depot</div>
</div> </div>
<mat-card class="placeholder"></mat-card> <mat-card class="placeholderRHS content-container">
<div class="content">
<div class="row">
<div class="col-md-4">
<div class="value-set">
<h3>Portfolio Value</h3>
</div>
</div>
<div class="col-md-4">
<div class="value-set">
<h3>Portfolio Cost</h3>
</div>
</div>
<div class="col-md-4">
<div class="value-set">
<h3>Portfolio Profit</h3>
</div>
</div>
</div>
<br />
<div class="row">
<div class="col-6 col-sm-4">
<mat-icon>savings</mat-icon
><span class="money">{{ depotCurrentValue.toFixed(2) }}</span>
</div>
<div class="col-6 col-sm-4">
<mat-icon>paid</mat-icon
><span class="money">{{ depotCost.toFixed(2) }}</span>
</div>
<div class="col-6 col-sm-4">
<mat-icon>account_balance</mat-icon
><span
class="money"
[ngClass]="{ green: profit >= 0, red: profit < 0 }"
>{{ profit.toFixed(2) }}</span
>
</div>
</div>
</div>
</mat-card>
</div> </div>
</mat-grid-tile> </mat-grid-tile>
<!-- Transaktions --> <!-- Transaktions -->
<mat-grid-tile colspan="1" rowspan="1" class="right-side"> <mat-grid-tile colspan="1" rowspan="1" class="right-side">
<div class="depotOverview"> <div class="depotOverviewDown">
<div class="heading fix-right-side"> <div class="heading fix-right-side">
<div class="vertical-center">Transaktionen</div> <div class="vertical-center">Transactions</div>
<span class="spacer"></span>
<button
mat-icon-button
class="add-icon"
aria-label="Example icon-button with heart icon"
[disableRipple]="true"
(click)="openDialog()"
>
<mat-icon>add</mat-icon>
</button>
</div> </div>
<div class="stockTable"> <mat-card class="placeholderRHS"
<table mat-table [dataSource]="dataSource" class="mat-elevation-z8"> ><div class="stockTable">
<table mat-table [dataSource]="dataSourceTransactions">
<!--- Note that these columns can be defined in any order. <!--- Note that these columns can be defined in any order.
The actual rendered columns are set as a property on the row definition" --> The actual rendered columns are set as a property on the row definition" -->
<!-- Position Column --> <!-- Position Column -->
<ng-container matColumnDef="position"> <ng-container matColumnDef="position">
<th mat-header-cell *matHeaderCellDef>Count</th> <th mat-header-cell *matHeaderCellDef>Count</th>
<td mat-cell *matCellDef="let element">{{ element.position }}</td> <td mat-cell *matCellDef="let element">{{ element.count }}</td>
</ng-container> </ng-container>
<!-- Name Column --> <!-- Name Column -->
<ng-container matColumnDef="name"> <ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef>Pirce</th> <th mat-header-cell *matHeaderCellDef>Price</th>
<td mat-cell *matCellDef="let element">{{ element.name }}</td> <td mat-cell *matCellDef="let element">{{ element.price }}</td>
</ng-container> </ng-container>
<!-- Weight Column --> <!-- Weight Column -->
<ng-container matColumnDef="weight"> <ng-container matColumnDef="weight">
<th mat-header-cell *matHeaderCellDef>Symbol</th> <th mat-header-cell *matHeaderCellDef>Symbol</th>
<td mat-cell *matCellDef="let element">{{ element.weight }}</td> <td mat-cell *matCellDef="let element">{{ element.symbol }}</td>
</ng-container> </ng-container>
<!-- Symbol Column --> <!-- Symbol Column -->
<ng-container matColumnDef="symbol"> <ng-container matColumnDef="symbol">
<th mat-header-cell *matHeaderCellDef>Time</th> <th mat-header-cell *matHeaderCellDef>Time</th>
<td mat-cell *matCellDef="let element">{{ element.symbol }}</td> <td mat-cell *matCellDef="let element">{{ element.time }}</td>
</ng-container> </ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr> <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns"></tr> <tr mat-row *matRowDef="let row; columns: displayedColumns"></tr>
</table> </table></div
</div> ></mat-card>
</div> </div>
</mat-grid-tile> </mat-grid-tile>
</mat-grid-list> </mat-grid-list>

View File

@ -13,6 +13,14 @@
margin-top: 10%; margin-top: 10%;
margin-left: 5%; margin-left: 5%;
margin-right: 10%; margin-right: 10%;
text-align: center;
}
.depotOverviewDown {
height: 100%;
width: 100%;
margin-left: 5%;
margin-right: 10%;
} }
.stockTable { .stockTable {
@ -21,12 +29,20 @@
width: 100%; width: 100%;
} }
.stockTableLHS {
overflow: auto;
height: 83%;
width: 100%;
}
.heading { .heading {
font-size: xx-large; font-size: xx-large;
height: 10%; height: 10%;
width: 100%; width: 100%;
position: relative; position: relative;
display: flex; display: flex;
justify-content: center;
align-items: center;
} }
.fix-right-side { .fix-right-side {
@ -47,7 +63,6 @@
.add-icon { .add-icon {
transform: scale(2); transform: scale(2);
margin-top: 2%;
outline: none !important; outline: none !important;
} }
@ -62,3 +77,37 @@ table {
.placeholder { .placeholder {
height: 100%; height: 100%;
} }
.placeholderRHS {
height: 80%;
}
.mat-ripple-element {
display: none !important;
}
.money {
margin-left: 2vw;
}
.green {
color: green;
}
.red {
color: red;
}
.row {
height: 20%;
}
.content {
height: inherit;
}
.content-container {
width: 100%;
display: grid;
align-items: center;
}

View File

@ -1,7 +1,9 @@
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { throwToolbarMixedModesError } from '@angular/material/toolbar';
import { DataService } from 'src/app/Services/data.service'; import { DataService } from 'src/app/Services/data.service';
import { TokenStorageService } from 'src/app/Services/token.service'; import { MatDialog } from '@angular/material/dialog';
import { UserDialogComponent } from './user-dialog/user-dialog.component';
import { C } from '@angular/cdk/keycodes';
import { HelperService } from 'src/app/Helpers/helper.service';
export interface PeriodicElement { export interface PeriodicElement {
name: string; name: string;
@ -11,11 +13,10 @@ export interface PeriodicElement {
} }
export interface Stock { export interface Stock {
count: number;
currentPrice: number;
symbol: string; symbol: string;
count: Float32Array; time: string;
lastTransaction: Date;
boughtPrice: Float32Array;
currentPrice: Float32Array;
} }
//symbol count lastTransaction boughtPrice currentPrice(+?) //symbol count lastTransaction boughtPrice currentPrice(+?)
@ -24,43 +25,99 @@ const ELEMENT_DATA: PeriodicElement[] = [
{ position: 1, name: 'Hydrogen', weight: 1.0079, symbol: 'H' }, { position: 1, name: 'Hydrogen', weight: 1.0079, symbol: 'H' },
{ position: 2, name: 'Helium', weight: 4.0026, symbol: 'He' }, { position: 2, name: 'Helium', weight: 4.0026, symbol: 'He' },
{ position: 3, name: 'Lithium', weight: 6.941, symbol: 'Li' }, { position: 3, name: 'Lithium', weight: 6.941, symbol: 'Li' },
{ position: 4, name: 'Beryllium', weight: 9.0122, symbol: 'Be' },
{ position: 5, name: 'Boron', weight: 10.811, symbol: 'B' },
{ position: 6, name: 'Carbon', weight: 12.0107, symbol: 'C' },
{ position: 7, name: 'Nitrogen', weight: 14.0067, symbol: 'N' },
{ position: 8, name: 'Oxygen', weight: 15.9994, symbol: 'O' },
{ position: 9, name: 'Fluorine', weight: 18.9984, symbol: 'F' },
{ position: 10, name: 'Neon', weight: 20.1797, symbol: 'Ne' },
{ position: 11, name: 'Hydrogen', weight: 1.0079, symbol: 'H' },
{ position: 12, name: 'Helium', weight: 4.0026, symbol: 'He' },
{ position: 13, name: 'Lithium', weight: 6.941, symbol: 'Li' },
{ position: 14, name: 'Beryllium', weight: 9.0122, symbol: 'Be' },
{ position: 15, name: 'Boron', weight: 10.811, symbol: 'B' },
{ position: 16, name: 'Carbon', weight: 12.0107, symbol: 'C' },
{ position: 17, name: 'Nitrogen', weight: 14.0067, symbol: 'N' },
{ position: 18, name: 'Oxygen', weight: 15.9994, symbol: 'O' },
{ position: 19, name: 'Fluorine', weight: 18.9984, symbol: 'F' },
{ position: 20, name: 'Neon', weight: 20.1797, symbol: 'Ne' },
]; ];
var TRANSACTION_DATA: TransactionData[] = [];
var STOCK_DATA: Stock[] = [];
export interface TransactionData {
symbol: string;
time: string;
count: number;
price: number;
}
@Component({ @Component({
selector: 'app-dashboard', selector: 'app-dashboard',
templateUrl: './dashboard.component.html', templateUrl: './dashboard.component.html',
styleUrls: ['./dashboard.component.scss'], styleUrls: ['./dashboard.component.scss'],
}) })
export class DashboardComponent implements OnInit { export class DashboardComponent implements OnInit {
constructor(private dataService: DataService) {} constructor(
private helper: HelperService,
private dataService: DataService,
public dialog: MatDialog
) {}
dataSourceTransactions: TransactionData[] = [];
dataSourceStocks: Stock[] = [];
depotCurrentValue: number = 0;
depotCost: number = 0;
profit: number = 0;
ngOnInit() { ngOnInit() {
this.dataService.getStockData().subscribe((response: any) => { this.dataService.getStockData().subscribe((response: any) => {
console.log(response); var data = JSON.parse(response);
//TODO map data on array for display for (let i = 0; i < data.data.length; i++) {
this.depotCurrentValue += data.data[i].current_price;
STOCK_DATA.push({
count: data.data[i].count,
currentPrice: data.data[i].current_price,
symbol: data.data[i].symbol,
time: data.data[i].last_transaction,
}); });
}
this.dataSourceStocks = STOCK_DATA;
//TODO move to helper service
this.profit += this.depotCurrentValue;
});
this.dataService.getTransactionData().subscribe((response: any) => { this.dataService.getTransactionData().subscribe((response: any) => {
console.log(response); var data = JSON.parse(response);
//TODO map data on array for display for (let i = 0; i < data.data.length; i++) {
this.depotCost += data.data[i].price;
TRANSACTION_DATA.push({
symbol: data.data[i].symbol,
time: data.data[i].time,
count: data.data[i].count,
price: data.data[i].price,
});
}
this.dataSourceTransactions = TRANSACTION_DATA;
//TODO move to helper service
this.profit -= this.depotCost;
}); });
} }
displayedColumns: string[] = ['position', 'name', 'weight', 'symbol']; symbol: string = '';
time: Date = new Date();
count: number = 0.0;
price: number = 0.0;
openDialog(): void {
const dialogRef = this.dialog.open(UserDialogComponent, {
width: '50vw',
data: {
symbol: this.symbol,
time: this.time,
count: this.count,
price: this.price,
},
});
dialogRef.afterClosed().subscribe((result) => {
console.log('The dialog was closed');
});
}
displayedColumns: string[] = ['weight', 'position', 'name', 'symbol'];
displayedColumnsStocks: string[] = [
'position',
'name',
'weight',
'current-price',
];
dataSource = ELEMENT_DATA; dataSource = ELEMENT_DATA;
} }

View File

@ -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>

View File

@ -0,0 +1,9 @@
.spacer {
flex-grow: 1;
width: 5%;
}
.footer-buttons {
display: flex;
width: 100%;
}

View File

@ -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();
});
});

View File

@ -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();
}
}

View File

@ -1,5 +1,5 @@
<mat-toolbar> <mat-toolbar>
<span>Aktienbot</span> <a href=""><span>Aktienbot</span></a>
<span class="example-spacer"></span> <span class="example-spacer"></span>
<button <button
mat-icon-button mat-icon-button

View File

@ -1,3 +1,8 @@
.example-spacer { .example-spacer {
flex: 1 1 auto; flex: 1 1 auto;
} }
a {
color: white;
text-decoration: none; /* no underline */
}

View File

@ -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>

View File

@ -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%;
}

View File

@ -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();
});
});

View File

@ -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() {}
}

View File

@ -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>

View File

@ -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%;
}

View File

@ -1,4 +1,8 @@
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { FormControl, PatternValidator, Validators } from '@angular/forms';
import { MatDialog } from '@angular/material/dialog';
import { ProfileService } from 'src/app/Services/profile.service';
import { ConfirmationDialogComponent } from './confirmation-dialog/confirmation-dialog.component';
@Component({ @Component({
selector: 'app-profile', selector: 'app-profile',
@ -6,7 +10,69 @@ import { Component, OnInit } from '@angular/core';
styleUrls: ['./profile.component.scss'], styleUrls: ['./profile.component.scss'],
}) })
export class ProfileComponent implements OnInit { export class ProfileComponent implements OnInit {
constructor() {} userNameFormControl = new FormControl('', [Validators.required]);
passwordFormControl = new FormControl('', [
Validators.required,
Validators.minLength(6),
]);
telegramIdFormControl = new FormControl('', [Validators.required]);
ngOnInit(): void {} userId = '';
form: any = {
username: null,
email: 'example@web.com',
password: 'password',
};
constructor(
private profileService: ProfileService,
public dialog: MatDialog
) {}
ngOnInit(): void {
this.profileService.getUserData().subscribe((result) => {
console.log(result);
result = JSON.parse(result);
this.form.username = result.data.username;
this.form.password = result.data.password;
this.form.email = result.data.email;
this.userId = result.data.telegram_user_id;
});
}
onSubmit() {
if (this.userId != '') {
console.log(this.userId);
this.profileService.addTelegramId(this.userId).subscribe((result) => {
console.log(result);
});
}
}
updateUser() {
const { username, email, password } = this.form;
this.profileService
.updateProfile(this.form.username, this.form.password)
.subscribe((result) => {
console.log(result);
});
}
openDialog(action: string) {
const dialogRef = this.dialog.open(ConfirmationDialogComponent, {
width: '50vw',
height: '20vh',
});
dialogRef.afterClosed().subscribe((result) => {
if (result === true) {
if (action === 'addTelegram') {
this.onSubmit();
} else if (action === 'updateUser') {
this.updateUser();
}
}
});
}
} }

View File

@ -1,5 +1,7 @@
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router'; import { RouterModule, Routes } from '@angular/router';
import { BotService } from './Services/bot.service';
import { BotSettingsComponent } from './Views/bot-settings/bot-settings.component';
import { DashboardComponent } from './Views/dashboard/dashboard.component'; import { DashboardComponent } from './Views/dashboard/dashboard.component';
import { LoginComponent } from './Views/login/login.component'; import { LoginComponent } from './Views/login/login.component';
import { ProfileComponent } from './Views/profile/profile.component'; import { ProfileComponent } from './Views/profile/profile.component';
@ -24,7 +26,7 @@ const routes: Routes = [
}, },
{ {
path: 'settings', path: 'settings',
component: ProfileComponent, component: BotSettingsComponent,
}, },
]; ];

View File

@ -1 +0,0 @@

View File

@ -1,6 +1,6 @@
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser'; import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms'; import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { HttpClientModule } from '@angular/common/http'; import { HttpClientModule } from '@angular/common/http';
import { MatToolbarModule } from '@angular/material/toolbar'; import { MatToolbarModule } from '@angular/material/toolbar';
@ -10,6 +10,9 @@ import { MatGridListModule } from '@angular/material/grid-list';
import { MatCardModule } from '@angular/material/card'; import { MatCardModule } from '@angular/material/card';
import { MatTableModule } from '@angular/material/table'; import { MatTableModule } from '@angular/material/table';
import { MatMenuModule } from '@angular/material/menu'; import { MatMenuModule } from '@angular/material/menu';
import { MatDialogModule } from '@angular/material/dialog';
import { MatInputModule } from '@angular/material/input';
import { MatChipsModule } from '@angular/material/chips';
import { AppRoutingModule } from './app-routing.module'; import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component'; import { AppComponent } from './app.component';
@ -20,6 +23,8 @@ import { DashboardComponent } from './Views/dashboard/dashboard.component';
import { RegisterComponent } from './Views/register/register.component'; import { RegisterComponent } from './Views/register/register.component';
import { ProfileComponent } from './Views/profile/profile.component'; import { ProfileComponent } from './Views/profile/profile.component';
import { BotSettingsComponent } from './Views/bot-settings/bot-settings.component'; import { BotSettingsComponent } from './Views/bot-settings/bot-settings.component';
import { UserDialogComponent } from './Views/dashboard/user-dialog/user-dialog.component';
import { ConfirmationDialogComponent } from './Views/profile/confirmation-dialog/confirmation-dialog.component';
@NgModule({ @NgModule({
declarations: [ declarations: [
@ -30,6 +35,8 @@ import { BotSettingsComponent } from './Views/bot-settings/bot-settings.componen
RegisterComponent, RegisterComponent,
ProfileComponent, ProfileComponent,
BotSettingsComponent, BotSettingsComponent,
UserDialogComponent,
ConfirmationDialogComponent,
], ],
imports: [ imports: [
BrowserModule, BrowserModule,
@ -44,6 +51,10 @@ import { BotSettingsComponent } from './Views/bot-settings/bot-settings.componen
FormsModule, FormsModule,
HttpClientModule, HttpClientModule,
MatMenuModule, MatMenuModule,
MatDialogModule,
MatInputModule,
ReactiveFormsModule,
MatChipsModule,
], ],
providers: [], providers: [],
bootstrap: [AppComponent], bootstrap: [AppComponent],