Bot into main #71

Merged
NormalParameter merged 55 commits from bot into main 2022-04-25 15:02:35 +00:00
75 changed files with 1909 additions and 210 deletions
Showing only changes of commit 99d39a0ed9 - Show all commits

View File

@ -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
@ -11,7 +12,7 @@ pipeline:
build_api:
image: woodpeckerci/plugin-docker-buildx
settings:
repo:
repo:
from_secret: repo_api
username:
from_secret: username
@ -30,7 +31,7 @@ pipeline:
build_bot:
image: woodpeckerci/plugin-docker-buildx
settings:
repo:
repo:
from_secret: repo_bot
username:
from_secret: username
@ -49,7 +50,7 @@ pipeline:
build_frontend:
image: woodpeckerci/plugin-docker-buildx
settings:
repo:
repo:
from_secret: repo_frontend
username:
from_secret: username
@ -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

View File

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

View File

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

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

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

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

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

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

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

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

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

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.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()

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

@ -10,4 +10,7 @@ flask-cors==3.0.10
bcrypt==3.2.0
pytest~=7.1.1
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
from app import create_app, db
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
@ -8,4 +14,4 @@ def get_token(test_client, email, password):
if "token" in json.loads(response.data)["data"]:
return json.loads(response.data)["data"]["token"]
return ""
return ""

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

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

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

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

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

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.
"""
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'

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

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

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.
"""
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):

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

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

View File

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

View File

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

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 { 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;
}
}

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 { 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',

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 { 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({

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 { 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', () => {

View File

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

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

View File

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

View File

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

View File

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

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>
<span>Aktienbot</span>
<a href=""><span>Aktienbot</span></a>
<span class="example-spacer"></span>
<button
mat-icon-button

View File

@ -1,3 +1,8 @@
.example-spacer {
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 { 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();
}
}
});
}
}

View File

@ -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,
},
];

View File

@ -1 +0,0 @@

View File

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