Browse Source

Implemented initial /transactions endpoint support

pull/7/head
Kenneth Bruen 3 years ago
parent
commit
5da66e520b
Signed by: kbruen
GPG Key ID: C1980A470C3EE5B1
  1. 3
      server/.vscode/launch.json
  2. 2
      server/foxbank_server/apis/__init__.py
  3. 12
      server/foxbank_server/apis/accounts.py
  4. 46
      server/foxbank_server/apis/transactions.py
  5. 22
      server/foxbank_server/db_utils.py
  6. 47
      server/foxbank_server/models.py
  7. 6
      server/foxbank_server/returns.py

3
server/.vscode/launch.json vendored

@ -11,7 +11,8 @@
"module": "flask", "module": "flask",
"env": { "env": {
"FLASK_APP": "server.py", "FLASK_APP": "server.py",
"FLASK_ENV": "development" "FLASK_ENV": "development",
"FLASK_RUN_PORT": "5001"
}, },
"args": [ "args": [
"run", "run",

2
server/foxbank_server/apis/__init__.py

@ -3,6 +3,7 @@ from flask_smorest import Api
from .accounts import bp as acc_bp 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
class ApiWithErr(Api): class ApiWithErr(Api):
def handle_http_exception(self, error): def handle_http_exception(self, error):
@ -27,3 +28,4 @@ def init_apis(app: Flask):
}) })
api.register_blueprint(login_bp, url_prefix='/login') api.register_blueprint(login_bp, url_prefix='/login')
api.register_blueprint(acc_bp, url_prefix='/accounts') api.register_blueprint(acc_bp, url_prefix='/accounts')
api.register_blueprint(transactions_bp, url_prefix='/transactions')

12
server/foxbank_server/apis/accounts.py

@ -13,12 +13,10 @@ bp = Blueprint('accounts', __name__, description='Bank Accounts operations')
VALID_CURRENCIES = ['RON', 'EUR', 'USD'] VALID_CURRENCIES = ['RON', 'EUR', 'USD']
ACCOUNT_TYPES = ['Checking', 'Savings'] ACCOUNT_TYPES = ['Checking', 'Savings']
class MetaCurrenciesSchema(Schema): class MetaCurrenciesSchema(returns.SuccessSchema):
status = fields.Constant('success')
currencies = fields.List(fields.Str()) currencies = fields.List(fields.Str())
class MetaAccountTypesSchema(Schema): class MetaAccountTypesSchema(returns.SuccessSchema):
status = fields.Constant('success')
account_types = fields.List(fields.Str(), data_key='accountTypes') account_types = fields.List(fields.Str(), data_key='accountTypes')
@bp.get('/meta/currencies') @bp.get('/meta/currencies')
@ -36,7 +34,7 @@ def get_valid_account_types():
class AccountResponseSchema(returns.SuccessSchema): class AccountResponseSchema(returns.SuccessSchema):
account = fields.Nested(Account.Schema) account = fields.Nested(Account.AccountSchema)
@bp.get('/<int:account_id>') @bp.get('/<int:account_id>')
@ -79,7 +77,7 @@ class AccountsList(MethodView):
custom_name = fields.String(data_key='customName') custom_name = fields.String(data_key='customName')
class CreateAccountResponseSchema(returns.SuccessSchema): class CreateAccountResponseSchema(returns.SuccessSchema):
account = fields.Nested(Account.Schema) account = fields.Nested(Account.AccountSchema)
@ensure_logged_in @ensure_logged_in
@bp.response(401, returns.ErrorSchema, description='Login failure') @bp.response(401, returns.ErrorSchema, description='Login failure')
@ -99,7 +97,7 @@ class AccountsList(MethodView):
return returns.success(account=account.to_json()) return returns.success(account=account.to_json())
class AccountsResponseSchema(returns.SuccessSchema): class AccountsResponseSchema(returns.SuccessSchema):
accounts = fields.List(fields.Nested(Account.Schema)) accounts = fields.List(fields.Nested(Account.AccountSchema))
@ensure_logged_in @ensure_logged_in
@bp.response(401, returns.ErrorSchema, description='Login failure') @bp.response(401, returns.ErrorSchema, description='Login failure')

46
server/foxbank_server/apis/transactions.py

@ -0,0 +1,46 @@
from flask.views import MethodView
from flask_smorest import Blueprint
from marshmallow import Schema, fields
from ..decorators import ensure_logged_in
from ..models import Transaction
from ..db_utils import get_transactions
from .. import 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')
@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"""
return returns.success(
transactions=[t.to_json() for t in get_transactions(account_id)]
)
class TransactionsCreateParams(Schema):
account_id = fields.Int(min=1)
destination_iban = fields.Str()
amount = fields.Int(min=1)
class TransactionsCreateResponse(returns.SuccessSchema):
transaction = fields.Nested(Transaction.TransactionSchema)
@ensure_logged_in
@bp.response(401, returns.ErrorSchema, description='Login failure')
@bp.doc(security=[{'Token': []}])
@bp.arguments(TransactionsCreateParams)
@bp.response(200, TransactionsCreateResponse)
def post(self, account_id: int, destination_iban: str, amount: int):
"""Create a send_transfer transaction"""
raise NotImplementedError()

22
server/foxbank_server/db_utils.py

@ -158,4 +158,26 @@ class Module(ModuleType):
self.db.commit() self.db.commit()
@get_db
def get_transactions(self, account_id: int) -> list[models.Transaction]:
cur = self.db.cursor()
cur.execute(
'select transaction_id from accounts_transactions where account_id = ?',
(account_id,),
)
transactions = []
for tid in (row['transaction_id'] for row in cur.fetchall()):
cur.execute(
'select * from transactions where id = ?',
(tid,),
)
db_res = cur.fetchone()
if db_res is None:
continue
transactions.append(models.Transaction.from_query(db_res))
return transactions
sys.modules[__name__] = Module(__name__) sys.modules[__name__] = Module(__name__)

47
server/foxbank_server/models.py

@ -1,5 +1,6 @@
from dataclasses import dataclass from dataclasses import dataclass
from marshmallow import Schema, fields from marshmallow import Schema, fields
from datetime import datetime
@dataclass @dataclass
class User: class User:
@ -51,7 +52,7 @@ class Account:
account_type: str account_type: str
custom_name: str custom_name: str
class Schema(Schema): class AccountSchema(Schema):
id = fields.Int(required=False) id = fields.Int(required=False)
iban = fields.Str() iban = fields.Str()
currency = fields.Str() currency = fields.Str()
@ -82,3 +83,47 @@ class Account:
@classmethod @classmethod
def from_query(cls, query_result): def from_query(cls, query_result):
return cls(*query_result) return cls(*query_result)
@dataclass
class Transaction:
id: int
date_time: datetime
other_party: str
status: str
transaction_type: str
extra: str
class TransactionSchema(Schema):
id = fields.Int(required=False)
date_time = fields.DateTime(data_key='datetime')
other_party = fields.Str(data_key='otherParty')
transaction_type = fields.Str(data_key='transactionType')
extra = fields.Str()
@staticmethod
def new_transaction(date_time: datetime, other_party: str, status: str, transaction_type: str, extra: str = '') -> 'Account':
return Transaction(
id=-1,
date_time=date_time,
other_party=other_party,
status=status,
transaction_type=transaction_type,
extra=extra,
)
def to_json(self, include_id=True):
result = {
'datetime': self.date_time.isoformat(),
'otherParty': self.other_party,
'status': self.status,
'transactionType': self.transaction_type,
'extra': self.extra,
}
if include_id:
result['id'] = self.id
return result
@classmethod
def from_query(cls, query_result):
return cls(*query_result)

6
server/foxbank_server/returns.py

@ -74,15 +74,15 @@ def success(http_status: Any = _HTTPStatus.OK, /, **kargs):
# Schemas # Schemas
from marshmallow import Schema, fields from marshmallow import Schema, fields, validate
class ErrorSchema(Schema): class ErrorSchema(Schema):
status = fields.Constant('error') status = fields.Str(default='error', validate=validate.Equal('error'))
code = fields.Str() code = fields.Str()
message = fields.Str(required=False) message = fields.Str(required=False)
class SuccessSchema(Schema): class SuccessSchema(Schema):
status = fields.Constant('success') status = fields.Str(default='success', validate=validate.Equal('success'))
# smorest # smorest

Loading…
Cancel
Save