DariusTFox24
3 years ago
committed by
GitHub
11 changed files with 529 additions and 26 deletions
@ -0,0 +1,60 @@
|
||||
from datetime import datetime |
||||
from flask.views import MethodView |
||||
from flask_smorest import Blueprint |
||||
from marshmallow import Schema, fields |
||||
|
||||
from ..db_utils import get_notifications, insert_notification, mark_notification_as_read, whose_notification |
||||
from ..decorators import ensure_logged_in |
||||
from ..models import Notification |
||||
from .. import decorators, returns |
||||
|
||||
bp = Blueprint('notifications', __name__, description='Notifications operations') |
||||
|
||||
@bp.post('/<int:notification_id>/mark_read') |
||||
@ensure_logged_in |
||||
@bp.response(401, returns.ErrorSchema, description='Login failure or not allowed') |
||||
@bp.doc(security=[{'Token': []}]) |
||||
@bp.response(201, description='Successfully marked as read') |
||||
def mark_as_read(notification_id: int): |
||||
"""Mark notification as read""" |
||||
if decorators.user_id != whose_notification(notification_id): |
||||
return returns.abort(returns.UNAUTHORIZED) |
||||
mark_notification_as_read(notification_id) |
||||
|
||||
|
||||
@bp.route('/') |
||||
class NotificationsList(MethodView): |
||||
class NotificationsListPostParams(Schema): |
||||
body = fields.Str(description='Text of the notification') |
||||
read = fields.Bool(default=False, description='Whether the notification was read or not') |
||||
|
||||
class NotificationsListPostSchema(returns.SuccessSchema): |
||||
notification = fields.Nested(Notification.NotificationSchema) |
||||
|
||||
@ensure_logged_in |
||||
@bp.response(401, returns.ErrorSchema, description='Login failure') |
||||
@bp.doc(security=[{'Token': []}]) |
||||
@bp.arguments(NotificationsListPostParams, as_kwargs=True) |
||||
@bp.response(200, NotificationsListPostSchema) |
||||
def post(self, body: str, read: bool = False): |
||||
"""Post a notification to the currently logged in user |
||||
|
||||
The usefulness of this endpoint is questionable besides debugging since it's a notification to self |
||||
""" |
||||
now = datetime.now() |
||||
notification = Notification.new_notification(body, now, read) |
||||
insert_notification(decorators.user_id, notification) |
||||
return returns.success(notification=notification) |
||||
|
||||
class NotificationsListGetSchema(returns.SuccessSchema): |
||||
notifications = fields.List(fields.Nested(Notification.NotificationSchema)) |
||||
|
||||
@ensure_logged_in |
||||
@bp.response(401, returns.ErrorSchema, description='Login failure') |
||||
@bp.doc(security=[{'Token': []}]) |
||||
@bp.response(200, NotificationsListGetSchema) |
||||
def get(self): |
||||
"""Get all notifications for current user""" |
||||
notifications = get_notifications(decorators.user_id) |
||||
|
||||
return returns.success(notifications=notifications) |
@ -0,0 +1,122 @@
|
||||
from datetime import date, datetime |
||||
from flask.views import MethodView |
||||
from flask_smorest import Blueprint |
||||
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 ..models import Account, Notification, Transaction |
||||
from ..utils.iban import check_iban |
||||
from .. import decorators, returns |
||||
|
||||
bp = Blueprint('transactions', __name__, description='Bank transfers and other transactions') |
||||
|
||||
@bp.route('/') |
||||
class TransactionsList(MethodView): |
||||
class TransactionsParams(Schema): |
||||
account_id = fields.Int(min=1) |
||||
|
||||
class TransactionsGetResponse(returns.SuccessSchema): |
||||
transactions = fields.List(fields.Nested(Transaction.TransactionSchema)) |
||||
|
||||
@ensure_logged_in |
||||
@bp.response(401, returns.ErrorSchema, description='Login failure or not allowed') |
||||
@bp.doc(security=[{'Token': []}]) |
||||
@bp.arguments(TransactionsParams, as_kwargs=True, location='query') |
||||
@bp.response(200, TransactionsGetResponse) |
||||
def get(self, account_id: int): |
||||
"""Get transactions for a certain account""" |
||||
if whose_account(account_id) != decorators.user_id: |
||||
return returns.abort(returns.UNAUTHORIZED) |
||||
|
||||
# return returns.success( |
||||
# transactions=[t.to_json() for t in get_transactions(account_id)] |
||||
# ) |
||||
return returns.success( |
||||
transactions=get_transactions(account_id) |
||||
) |
||||
|
||||
class TransactionsCreateParams(Schema): |
||||
account_id = fields.Int(min=1) |
||||
destination_iban = fields.Str() |
||||
amount = fields.Int(min=1) |
||||
description = fields.Str(default='') |
||||
|
||||
class TransactionsCreateResponse(returns.SuccessSchema): |
||||
transaction = fields.Nested(Transaction.TransactionSchema) |
||||
|
||||
@ensure_logged_in |
||||
@bp.response(401, returns.ErrorSchema, description='Login failure or not allowed') |
||||
@bp.response(404, returns.ErrorSchema, description='Destination account not found') |
||||
@bp.response(422, returns.ErrorSchema, description='Invalid account') |
||||
@bp.doc(security=[{'Token': []}]) |
||||
@bp.arguments(TransactionsCreateParams, as_kwargs=True) |
||||
@bp.response(200, TransactionsCreateResponse) |
||||
def post(self, account_id: int, destination_iban: str, amount: int, description: str = ''): |
||||
"""Create a send_transfer transaction""" |
||||
if whose_account(account_id) != decorators.user_id: |
||||
return returns.abort(returns.UNAUTHORIZED) |
||||
|
||||
account: Account = get_account(account_id=account_id) |
||||
|
||||
if account is None: |
||||
return returns.abort(returns.invalid_argument('account_id')) |
||||
|
||||
amount = -1 * abs(amount) |
||||
|
||||
if account.balance + amount < 0: |
||||
return returns.abort(returns.NO_BALANCE) |
||||
|
||||
# Check if IBAN is valid |
||||
destination_iban = re.sub(r'\s', '', destination_iban) |
||||
|
||||
if not check_iban(destination_iban): |
||||
return returns.abort(returns.INVALID_IBAN) |
||||
|
||||
date = datetime.now() |
||||
|
||||
# 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: |
||||
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, |
||||
'description': description, |
||||
}, |
||||
) |
||||
insert_transaction(acc.id, reverse_transaction) |
||||
formatted_iban = re.sub(r'(.{4})', r'\1 ', account.iban).strip() |
||||
notification = Notification.new_notification( |
||||
body=f'Transfer of {-amount // 100}.{-amount % 100:0>2} {account.currency} received from {formatted_iban} in your {acc.custom_name or acc.account_type} account.', |
||||
date_time=date, |
||||
read=False, |
||||
) |
||||
insert_notification(acc.id, notification) |
||||
break |
||||
else: |
||||
return returns.abort(returns.NOT_FOUND) |
||||
|
||||
transaction = Transaction.new_transaction( |
||||
date_time=date, |
||||
transaction_type='send_transfer', |
||||
status=('processed' if reverse_transaction is not None else 'pending'), |
||||
other_party={'iban': destination_iban,}, |
||||
extra={ |
||||
'currency': account.currency, |
||||
'amount': amount, |
||||
'description': description, |
||||
}, |
||||
) |
||||
|
||||
insert_transaction(account_id, transaction) |
||||
|
||||
return returns.success(transaction=transaction) |
Loading…
Reference in new issue