diff --git a/server/foxbank_server/apis/__init__.py b/server/foxbank_server/apis/__init__.py index ab0f6f8..cf10cd0 100644 --- a/server/foxbank_server/apis/__init__.py +++ b/server/foxbank_server/apis/__init__.py @@ -5,6 +5,7 @@ from .accounts import bp as acc_bp from .login import bp as login_bp from .transactions import bp as transactions_bp from .notifications import bp as notifications_bp +from .forex import bp as forex_bp class ApiWithErr(Api): def handle_http_exception(self, error): @@ -31,3 +32,4 @@ def init_apis(app: Flask): api.register_blueprint(acc_bp, url_prefix='/accounts') api.register_blueprint(transactions_bp, url_prefix='/transactions') api.register_blueprint(notifications_bp, url_prefix='/notifications') + api.register_blueprint(forex_bp, url_prefix='/forex') diff --git a/server/foxbank_server/apis/forex.py b/server/foxbank_server/apis/forex.py new file mode 100644 index 0000000..9dc6fbc --- /dev/null +++ b/server/foxbank_server/apis/forex.py @@ -0,0 +1,26 @@ +from flask.views import MethodView +from flask_smorest import Blueprint +from marshmallow import Schema, fields + +from ..db_utils import get_forex_rate +from .. import returns + +bp = Blueprint('forex', __name__, description='Foreign Exchange information') + +class GetExchangeResult(returns.SuccessSchema): + rate = fields.Float(optional=True) + +@bp.get('//') +@bp.response(422, returns.ErrorSchema, description='Invalid currency') +@bp.response(200, GetExchangeResult) +def get_exchange(from_currency: str, to_currency: str): + """Get exchange rate between two currencies""" + if from_currency == to_currency: + rate = 1 + else: + rate = get_forex_rate(from_currency, to_currency) + + if rate is None: + return returns.abort(returns.invalid_argument('currency')) + + return returns.success(rate=rate) diff --git a/server/foxbank_server/apis/notifications.py b/server/foxbank_server/apis/notifications.py index ab31612..7f39a76 100644 --- a/server/foxbank_server/apis/notifications.py +++ b/server/foxbank_server/apis/notifications.py @@ -1,4 +1,4 @@ -from datetime import datetime +from datetime import datetime, timezone from flask.views import MethodView from flask_smorest import Blueprint from marshmallow import Schema, fields @@ -41,7 +41,7 @@ class NotificationsList(MethodView): The usefulness of this endpoint is questionable besides debugging since it's a notification to self """ - now = datetime.now() + now = datetime.now(timezone.utc).astimezone() notification = Notification.new_notification(body, now, read) insert_notification(decorators.user_id, notification) return returns.success(notification=notification) diff --git a/server/foxbank_server/apis/transactions.py b/server/foxbank_server/apis/transactions.py index aac3dcf..3ff730f 100644 --- a/server/foxbank_server/apis/transactions.py +++ b/server/foxbank_server/apis/transactions.py @@ -1,4 +1,4 @@ -from datetime import date, datetime +from datetime import date, datetime, timezone from flask.views import MethodView from flask_smorest import Blueprint from marshmallow import Schema, fields @@ -6,7 +6,7 @@ from marshmallow import Schema, fields import re from ..decorators import ensure_logged_in -from ..db_utils import get_transactions, get_account, get_accounts, insert_transaction, whose_account, insert_notification +from ..db_utils import get_transactions, get_account, get_accounts, insert_transaction, whose_account, insert_notification, get_forex_rate from ..models import Account, Notification, Transaction from ..utils.iban import check_iban from .. import decorators, returns @@ -75,22 +75,25 @@ class TransactionsList(MethodView): if not check_iban(destination_iban): return returns.abort(returns.INVALID_IBAN) - date = datetime.now() + date = datetime.now(timezone.utc).astimezone() # Check if transaction is to another FoxBank account reverse_transaction = None if destination_iban[4:8] == 'FOXB': for acc in get_accounts(): if destination_iban == acc.iban: + rate = get_forex_rate(account.currency, acc.currency) reverse_transaction = Transaction.new_transaction( date_time=date, transaction_type='receive_transfer', status='processed', other_party={'iban': account.iban,}, extra={ - 'currency': account.currency, - 'amount': -amount, + 'currency': acc.currency, + 'amount': int(-amount * rate), 'description': description, + 'originalAmount': -amount, + 'originalCurrency': account.currency, }, ) insert_transaction(acc.id, reverse_transaction) diff --git a/server/foxbank_server/db_utils.py b/server/foxbank_server/db_utils.py index 7f02a4d..33740ce 100644 --- a/server/foxbank_server/db_utils.py +++ b/server/foxbank_server/db_utils.py @@ -306,5 +306,35 @@ class Module(ModuleType): ) self.db.commit() + @get_db + def get_forex_rate(self, from_currency: str, to_currency: str) -> float | None: + if from_currency == to_currency: + return 1.0 + + cur = self.db.cursor() + + if from_currency == 'RON' or to_currency == 'RON': + currency_pairs = [(from_currency, to_currency)] + else: + currency_pairs = [(from_currency, 'RON'), ('RON', to_currency)] + + amount = 1.0 + for currency_pair in currency_pairs: + to_select = 'to_ron' + if currency_pair[0] == 'RON': + to_select = 'from_ron' + cur.execute( + f'select {to_select} from exchange where currency = ?', + (currency_pair[1] if currency_pair[0] == 'RON' else currency_pair[0],), + ) + rate = cur.fetchone() + if rate is None: + amount = None + break + rate = rate[0] + amount *= rate + + return amount + sys.modules[__name__] = Module(__name__) diff --git a/server/init.sql b/server/init.sql index 7643ad3..f2d1aee 100644 --- a/server/init.sql +++ b/server/init.sql @@ -60,6 +60,13 @@ create table users_notifications ( foreign key (notification_id) references notifications (id) ); +create table exchange ( + id integer primary key autoincrement, + currency text not null, + to_ron real not null, + from_ron real not null +); + create view V_account_balance as select accounts_transactions.account_id as "account_id",