mirror of
https://github.com/dancojocaru2000/foxbank.git
synced 2025-02-23 08:09:35 +02:00
Merge pull request #10 from dancojocaru2000/Backend
Currency exchange in Backend
This commit is contained in:
commit
649e2c729f
6 changed files with 75 additions and 7 deletions
|
@ -5,6 +5,7 @@ from .accounts import bp as acc_bp
|
||||||
from .login import bp as login_bp
|
from .login import bp as login_bp
|
||||||
from .transactions import bp as transactions_bp
|
from .transactions import bp as transactions_bp
|
||||||
from .notifications import bp as notifications_bp
|
from .notifications import bp as notifications_bp
|
||||||
|
from .forex import bp as forex_bp
|
||||||
|
|
||||||
class ApiWithErr(Api):
|
class ApiWithErr(Api):
|
||||||
def handle_http_exception(self, error):
|
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(acc_bp, url_prefix='/accounts')
|
||||||
api.register_blueprint(transactions_bp, url_prefix='/transactions')
|
api.register_blueprint(transactions_bp, url_prefix='/transactions')
|
||||||
api.register_blueprint(notifications_bp, url_prefix='/notifications')
|
api.register_blueprint(notifications_bp, url_prefix='/notifications')
|
||||||
|
api.register_blueprint(forex_bp, url_prefix='/forex')
|
||||||
|
|
26
server/foxbank_server/apis/forex.py
Normal file
26
server/foxbank_server/apis/forex.py
Normal file
|
@ -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('/<from_currency>/<to_currency>')
|
||||||
|
@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)
|
|
@ -1,4 +1,4 @@
|
||||||
from datetime import datetime
|
from datetime import datetime, timezone
|
||||||
from flask.views import MethodView
|
from flask.views import MethodView
|
||||||
from flask_smorest import Blueprint
|
from flask_smorest import Blueprint
|
||||||
from marshmallow import Schema, fields
|
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
|
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)
|
notification = Notification.new_notification(body, now, read)
|
||||||
insert_notification(decorators.user_id, notification)
|
insert_notification(decorators.user_id, notification)
|
||||||
return returns.success(notification=notification)
|
return returns.success(notification=notification)
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
from datetime import date, datetime
|
from datetime import date, datetime, timezone
|
||||||
from flask.views import MethodView
|
from flask.views import MethodView
|
||||||
from flask_smorest import Blueprint
|
from flask_smorest import Blueprint
|
||||||
from marshmallow import Schema, fields
|
from marshmallow import Schema, fields
|
||||||
|
@ -6,7 +6,7 @@ from marshmallow import Schema, fields
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from ..decorators import ensure_logged_in
|
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 ..models import Account, Notification, Transaction
|
||||||
from ..utils.iban import check_iban
|
from ..utils.iban import check_iban
|
||||||
from .. import decorators, returns
|
from .. import decorators, returns
|
||||||
|
@ -75,22 +75,25 @@ class TransactionsList(MethodView):
|
||||||
if not check_iban(destination_iban):
|
if not check_iban(destination_iban):
|
||||||
return returns.abort(returns.INVALID_IBAN)
|
return returns.abort(returns.INVALID_IBAN)
|
||||||
|
|
||||||
date = datetime.now()
|
date = datetime.now(timezone.utc).astimezone()
|
||||||
|
|
||||||
# Check if transaction is to another FoxBank account
|
# Check if transaction is to another FoxBank account
|
||||||
reverse_transaction = None
|
reverse_transaction = None
|
||||||
if destination_iban[4:8] == 'FOXB':
|
if destination_iban[4:8] == 'FOXB':
|
||||||
for acc in get_accounts():
|
for acc in get_accounts():
|
||||||
if destination_iban == acc.iban:
|
if destination_iban == acc.iban:
|
||||||
|
rate = get_forex_rate(account.currency, acc.currency)
|
||||||
reverse_transaction = Transaction.new_transaction(
|
reverse_transaction = Transaction.new_transaction(
|
||||||
date_time=date,
|
date_time=date,
|
||||||
transaction_type='receive_transfer',
|
transaction_type='receive_transfer',
|
||||||
status='processed',
|
status='processed',
|
||||||
other_party={'iban': account.iban,},
|
other_party={'iban': account.iban,},
|
||||||
extra={
|
extra={
|
||||||
'currency': account.currency,
|
'currency': acc.currency,
|
||||||
'amount': -amount,
|
'amount': int(-amount * rate),
|
||||||
'description': description,
|
'description': description,
|
||||||
|
'originalAmount': -amount,
|
||||||
|
'originalCurrency': account.currency,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
insert_transaction(acc.id, reverse_transaction)
|
insert_transaction(acc.id, reverse_transaction)
|
||||||
|
|
|
@ -306,5 +306,35 @@ class Module(ModuleType):
|
||||||
)
|
)
|
||||||
self.db.commit()
|
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__)
|
sys.modules[__name__] = Module(__name__)
|
||||||
|
|
|
@ -60,6 +60,13 @@ create table users_notifications (
|
||||||
foreign key (notification_id) references notifications (id)
|
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
|
create view V_account_balance as
|
||||||
select
|
select
|
||||||
accounts_transactions.account_id as "account_id",
|
accounts_transactions.account_id as "account_id",
|
||||||
|
|
Loading…
Add table
Reference in a new issue