You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
141 lines
5.1 KiB
141 lines
5.1 KiB
from http import HTTPStatus |
|
from flask.views import MethodView |
|
from flask_smorest import Blueprint, abort |
|
from marshmallow import Schema, fields |
|
|
|
import re |
|
|
|
from ..decorators import ensure_logged_in |
|
from ..models import Account |
|
from ..utils.iban import IBAN_BANKS, check_iban |
|
from .. import decorators |
|
from .. import db_utils |
|
from .. import returns |
|
|
|
bp = Blueprint('accounts', __name__, description='Bank Accounts operations') |
|
|
|
VALID_CURRENCIES = ['RON', 'EUR', 'USD'] |
|
ACCOUNT_TYPES = ['Checking', 'Savings'] |
|
|
|
class MetaCurrenciesSchema(returns.SuccessSchema): |
|
currencies = fields.List(fields.Str()) |
|
|
|
class MetaAccountTypesSchema(returns.SuccessSchema): |
|
account_types = fields.List(fields.Str(), data_key='accountTypes') |
|
|
|
class MetaValidateIbanParams(Schema): |
|
iban = fields.Str(example='RO15RZBR0000060021338765') |
|
|
|
class MetaValidateIbanSchema(returns.SuccessSchema): |
|
valid = fields.Bool() |
|
formatted_iban = fields.Str(data_key='formattedIban', optional=True) |
|
bank_name = fields.Str(data_key='bankName', optional=True, description='Known bank for IBAN') |
|
|
|
@bp.get('/meta/currencies') |
|
@bp.response(200, MetaCurrenciesSchema) |
|
def get_valid_currencies(): |
|
"""Get valid account currencies""" |
|
return returns.success(currencies=VALID_CURRENCIES) |
|
|
|
|
|
@bp.get('/meta/account_types') |
|
@bp.response(200, MetaAccountTypesSchema) |
|
def get_valid_account_types(): |
|
"""Get valid account types""" |
|
return returns.success(account_types=ACCOUNT_TYPES) |
|
|
|
|
|
@bp.get('/meta/validate_iban') |
|
@bp.arguments(MetaValidateIbanParams, location='query', as_kwargs=True) |
|
@bp.response(200, MetaValidateIbanSchema) |
|
def get_validate_iban(iban: str): |
|
"""Validate IBAN""" |
|
iban = re.sub(r'\s', '', iban) |
|
valid = len(iban) > 8 and re.match(r'^[A-Z]{2}[0-9]{2}', iban) is not None and check_iban(iban) |
|
bank_name = None |
|
if iban[0:2] in IBAN_BANKS: |
|
if iban[4:8] in IBAN_BANKS[iban[0:2]]: |
|
bank_name = IBAN_BANKS[iban[0:2]][iban[4:8]] |
|
|
|
return returns.success( |
|
valid=valid, |
|
formatted_iban=re.sub(r'(.{4})', r'\1 ', iban).strip() if valid else None, |
|
bank_name=bank_name if valid else None, |
|
) |
|
|
|
|
|
class AccountResponseSchema(returns.SuccessSchema): |
|
account = fields.Nested(Account.AccountSchema) |
|
|
|
|
|
@bp.get('/<int:account_id>') |
|
@ensure_logged_in |
|
@bp.response(401, returns.ErrorSchema, description='Login failure or not allowed') |
|
@bp.doc(security=[{'Token': []}]) |
|
@bp.response(200, AccountResponseSchema) |
|
def get_account_id(account_id: int): |
|
"""Get account by id""" |
|
account = db_utils.get_account(account_id=account_id) |
|
if account is None: |
|
return returns.abort(returns.NOT_FOUND) |
|
if decorators.user_id != db_utils.whose_account(account): |
|
return returns.abort(returns.UNAUTHORIZED) |
|
# account = account.to_json() |
|
return returns.success(account=account) |
|
|
|
|
|
@bp.get('/IBAN_<iban>') |
|
@ensure_logged_in |
|
@bp.response(401, returns.ErrorSchema, description='Login failure') |
|
@bp.doc(security=[{'Token': []}]) |
|
@bp.response(200, AccountResponseSchema) |
|
def get_account_iban(iban: str): |
|
"""Get account by IBAN""" |
|
account = db_utils.get_account(iban=iban) |
|
if account is None: |
|
return returns.abort(returns.NOT_FOUND) |
|
if decorators.user_id != db_utils.whose_account(account): |
|
return returns.abort(returns.UNAUTHORIZED) |
|
# account = account.to_json() |
|
return returns.success(account=account) |
|
|
|
|
|
@bp.route('/') |
|
class AccountsList(MethodView): |
|
class CreateAccountParams(Schema): |
|
currency = fields.String(example='RON') |
|
account_type = fields.String(data_key='accountType', example='Checking') |
|
custom_name = fields.String(data_key='customName', example='Daily Spending') |
|
|
|
class CreateAccountResponseSchema(returns.SuccessSchema): |
|
account = fields.Nested(Account.AccountSchema) |
|
|
|
@ensure_logged_in |
|
@bp.response(401, returns.ErrorSchema, description='Login failure') |
|
@bp.doc(security=[{'Token': []}]) |
|
@bp.arguments(CreateAccountParams, as_kwargs=True) |
|
@bp.response(200, CreateAccountResponseSchema) |
|
@bp.response(422, returns.ErrorSchema, description='Invalid currency or account type') |
|
def post(self, currency: str, account_type: str, custom_name: str): |
|
"""Create account""" |
|
if currency not in VALID_CURRENCIES: |
|
return returns.abort(returns.invalid_argument('currency')) |
|
if account_type not in ACCOUNT_TYPES: |
|
return returns.abort(returns.invalid_argument('account_type')) |
|
|
|
account = Account(-1, '', currency, account_type, custom_name or '') |
|
db_utils.insert_account(decorators.user_id, account) |
|
# return returns.success(account=account.to_json()) |
|
return returns.success(account=account) |
|
|
|
class AccountsResponseSchema(returns.SuccessSchema): |
|
accounts = fields.List(fields.Nested(Account.AccountSchema)) |
|
|
|
@ensure_logged_in |
|
@bp.response(401, returns.ErrorSchema, description='Login failure') |
|
@bp.doc(security=[{'Token': []}]) |
|
@bp.response(200, AccountsResponseSchema) |
|
def get(self): |
|
"""Get all accounts of user""" |
|
return returns.success(accounts=db_utils.get_accounts(decorators.user_id)) |
|
|
|
|