# UGE / L2 / Intro to relational databases / Python project prototype # Author: Pacien TRAN-GIRARD # Licence: EUPL-1.2 from decimal import Decimal from fastapi import APIRouter, Form, Depends, status from fastapi.responses import RedirectResponse, HTMLResponse from embrace.exceptions import IntegrityError from psycopg2.errors import CheckViolation from psycopg2.extensions import ISOLATION_LEVEL_SERIALIZABLE from app_sessions import UserSession, FlashMessageQueue from app_database import db_transaction from app_templating import TemplateRenderer to_wallet = RedirectResponse('/wallet', status_code=status.HTTP_303_SEE_OTHER) router = APIRouter() # TODO: add paging for the transaction history @router.get('/wallet', response_class=HTMLResponse) def wallet( session: UserSession=Depends(UserSession.authenticated), messages: FlashMessageQueue=Depends(FlashMessageQueue), render: TemplateRenderer=Depends(TemplateRenderer), ): with db_transaction() as tx: history = tx.fetch_transactions(user_id=session.get_user_id()) # rendering done in the transaction to make use the iteration cursor return render('wallet.html.jinja', transactions=history) @router.post('/wallet/transfer', response_class=HTMLResponse) def wallet_transfer( session: UserSession=Depends(UserSession.authenticated), messages: FlashMessageQueue=Depends(FlashMessageQueue), recipient: str=Form(...), amount: Decimal=Form(...), ): if amount <= 0: messages.add('error', 'Invalid transaction amount.') return to_wallet try: with db_transaction(ISOLATION_LEVEL_SERIALIZABLE) as tx: recipient_user = tx.fetch_account_username(username=recipient) if recipient_user is None: raise LookupError('Could not find recipient') if recipient_user.id == session.get_user_id(): raise LookupError('Cannot transfer money to oneself.') tx.transfer( from_user_id=session.get_user_id(), to_user_id=recipient_user.id, amount=amount, fee=amount * Decimal(0.10)) messages.add('success', 'Your business is appreciated.') return to_wallet except LookupError as exception: messages.add('error', str(exception)) return to_wallet except IntegrityError as exception: if isinstance(exception.__cause__, CheckViolation): messages.add('error', 'Insufficient funds.') return to_wallet else: raise exception @router.post('/wallet/deposit') def wallet_deposit( session: UserSession=Depends(UserSession.authenticated), messages: FlashMessageQueue=Depends(FlashMessageQueue), amount: Decimal=Form(...), ): if amount <= 0: messages.add('error', 'Invalid transaction amount.') return to_wallet with db_transaction(ISOLATION_LEVEL_SERIALIZABLE) as tx: tx.deposit(user_id=session.get_user_id(), amount=amount) messages.add('success', 'Your business is appreciated.') return to_wallet @router.post('/wallet/withdraw') def wallet_withdraw( session: UserSession=Depends(UserSession.authenticated), messages: FlashMessageQueue=Depends(FlashMessageQueue), render: TemplateRenderer=Depends(TemplateRenderer), amount: Decimal=Form(...), ): if amount <= 0: messages.add('error', 'Invalid transaction amount.') return to_wallet try: with db_transaction(ISOLATION_LEVEL_SERIALIZABLE) as tx: tx.withdraw(user_id=session.get_user_id(), amount=amount) messages.add('success', 'Annnnnd... It\'s gone.') return render('launder.html.jinja') except IntegrityError as exception: if isinstance(exception.__cause__, CheckViolation): messages.add('error', 'Insufficient funds.') return to_wallet else: raise exception