diff --git a/api/.env.example b/api/.env.example index 25dc0bf..2d183f9 100644 --- a/api/.env.example +++ b/api/.env.example @@ -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= \ No newline at end of file diff --git a/api/app/__init__.py b/api/app/__init__.py index 89e8bb5..8040125 100644 --- a/api/app/__init__.py +++ b/api/app/__init__.py @@ -7,6 +7,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 @@ -28,6 +29,7 @@ def create_app(config_filename=None): # 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) diff --git a/api/app/blueprints/share_price.py b/api/app/blueprints/share_price.py new file mode 100644 index 0000000..c313cda --- /dev/null +++ b/api/app/blueprints/share_price.py @@ -0,0 +1,88 @@ +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 diff --git a/api/app/blueprints/transactions.py b/api/app/blueprints/transactions.py index 33eae41..71c856a 100644 --- a/api/app/blueprints/transactions.py +++ b/api/app/blueprints/transactions.py @@ -1,13 +1,13 @@ -import os 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__))) diff --git a/api/app/models.py b/api/app/models.py index 14b42ac..7889271 100644 --- a/api/app/models.py +++ b/api/app/models.py @@ -51,3 +51,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} diff --git a/api/app/schema.py b/api/app/schema.py index 834ada3..7caad63 100644 --- a/api/app/schema.py +++ b/api/app/schema.py @@ -75,6 +75,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() diff --git a/api/generate_sample_transactions.py b/api/generate_sample_transactions.py index adfbec4..8ef4542 100644 --- a/api/generate_sample_transactions.py +++ b/api/generate_sample_transactions.py @@ -1,9 +1,9 @@ +import os import random import faker import requests -url = 'http://127.0.0.1:5000/api' username = '' password = '' @@ -12,7 +12,7 @@ shares = ["TWTR", "GOOG", "AAPL", "MSFT", "AMZN", "FB", "NFLX", "TSLA", "BABA", fake = faker.Faker() -token = requests.post(url + '/user/login', json={"email": username, "password": password}).json()['data']['token'] +token = requests.post(os.getenv("API_URL") + '/user/login', json={"email": username, "password": password}).json()['data']['token'] for i in range(1, 1000): payload = { @@ -22,4 +22,4 @@ for i in range(1, 1000): "time": fake.date_time().isoformat() + ".000Z" } - response = requests.post(url + '/transaction', json=payload, headers={'Authorization': 'Bearer ' + token}) + response = requests.post(os.getenv("API_URL") + '/transaction', json=payload, headers={'Authorization': 'Bearer ' + token}) diff --git a/api/load_share_price.py b/api/load_share_price.py new file mode 100644 index 0000000..c58f7a9 --- /dev/null +++ b/api/load_share_price.py @@ -0,0 +1,33 @@ +import datetime +import os +import threading + +import requests +import yfinance + + +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}) + + +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'] + +for symbol in response: + x = threading.Thread(target=thread_function, args=(symbol,)) + x.start() + diff --git a/api/requirements.txt b/api/requirements.txt index 6edbe0f..db4e4a4 100644 --- a/api/requirements.txt +++ b/api/requirements.txt @@ -11,4 +11,6 @@ bcrypt==3.2.0 pytest~=7.1.1 pytest-cov marshmallow~=3.15.0 -faker~=4.0.0 \ No newline at end of file +faker~=4.0.0 +yfinance~=0.1.6 +requests~=2.22.0 \ No newline at end of file