finance-watcher / api / endpoints / account.py
account.py
Raw
from pathlib import Path
import sys

sys.path.append(str(Path(__file__).parent.parent.parent))

from flask import request
from flask_restx import Namespace, Resource, fields, reqparse
from finance_watcher_dataclasses.import_dataclasses import AccountRow
from finance_watcher_dataclasses.enums import AccountTypeEnum as AccountTypeEnum
from database.finance_database import FinanceDatabase

from helpers.helper_functions import (
    json_success_response, json_error_response, get_date_from_string, get_string_from_date
)
from datetime import datetime


namespace = Namespace(
    'account', 
    description='API endpoints related to Account resource'
)

user_id_parser = reqparse.RequestParser()
user_id_parser.add_argument('user_id', required=True, type=int, help='id of the user')


account_payload = namespace.model("Payload", {
    "name": fields.String(required=True, description='name of the account'),
    "account_type_id": fields.Integer(required=True, description='id of the account type'),
    "user_id": fields.Integer(required=True, description='id of the user'),
}, required=True, description='account details to insert')

totals_payload = namespace.model("Payload", {
    "user_id": fields.Integer(required=True, description='id of the user'),
    "account_totals": fields.List(fields.Nested(namespace.model("data", {
        "account_id":fields.Integer(required=True, description='id of account'),
        "total":fields.Integer(required=True, description='total amount on the account'),
        "date":fields.String(required=False, description='date of the transaction')
        })), required=True, description="list of account totals")
})

@namespace.route('')
class AccountAPI(Resource):
    """
    API for the Account resource.
    """
    def post(self):
        """
        Inserts Account into the database.
        """
        payload = request.json
        finance_db = FinanceDatabase()

        user = finance_db.get_dashboard_user(payload['user_id'])
        if user is None:
            return json_error_response(
                "ERROR: User does not exist."
            )

        account = finance_db.insert_account(
            name=payload['name'],
            account_type_id=payload['account_type_id'],
            dashboard_user_id=payload['user_id']
        )
        if account is None:
            return json_error_response(
                f"ERROR: Failed to insert the account '{payload['name']}'"
            )
        
        return json_success_response({
            'id': account.id,
            'user_id': account.dashboard_user_id,
            'name': account.name,
            'account_type_id': account.account_type_id,
            'created_date': get_string_from_date(account.created_date),
            'updated_date': get_string_from_date(account.updated_date) if account.updated_date else 'null'
        })


@namespace.route('/type/<string:name>')
class AccountTypeAPI(Resource):
    """
    API for Account Type
    """
    def get(self, name: str):
        """
        Gets Account Type resource.
        
        :param type: type of an account
        """
        finance_db = FinanceDatabase()
        account_type_enum = AccountTypeEnum.get_account_type_from_str(name)
        if account_type_enum is None:
            return json_error_response(
                f"ERROR: Account type '{name}' is not valid."
        )
        account_type = finance_db.get_account_type(account_type_enum)

        if account_type is None:
            return json_error_response(
                f"ERROR: Failed to get account type '{name}'."
            )
        return json_success_response({
            'id': account_type.id,
            'name': account_type.name,
            'is_income': 'True' if account_type.is_income else 'False',
            'is_expense': 'True' if account_type.is_expense else 'False'
        })

@namespace.route('/totals')
class AccountTotalsAPI(Resource):
    """
    API to get Account totals.
    """
    @namespace.expect(user_id_parser)
    def get(self):
        """
        Gets account totals from a User.
        """
        args = user_id_parser.parse_args()
        user_id = args['user_id']

        finance_db = FinanceDatabase()

        user = finance_db.get_dashboard_user(user_id)
        if user is None:
            return json_error_response(
                "ERROR: User does not exist."
            )

        account_totals = finance_db.get_all_accounts_and_latest_totals(user_id)
        return json_success_response([
            {
                'account_id': account_id,
                'account_name': account_totals[0],
                'total': account_totals[1]
            }
            for account_id, account_totals in account_totals.items()
        ])

    @namespace.expect(totals_payload)
    def post(self):
        """
        Inserts a Transaction into the database.
        """
        payload = request.json
        finance_db = FinanceDatabase()

        account_ids = [values['account_id'] for values in payload['account_totals']]
        connected = finance_db.check_account_connection(
            payload['user_id'],
            account_ids = account_ids
        )
        if not connected:
            return json_error_response(
                "ERROR: An account is not connected to this user."
            )

        totals_to_insert = []

        for values in payload['account_totals']:

            date = values.get('date')
            if date is None:
                date = datetime.now()
            else:
                date = get_date_from_string(date)

            account_row = AccountRow(
                account_id=values['account_id'],
                date_to_amount={
                    date: values['total'],
                }
            )
            
            totals_to_insert.append(account_row)

        if not finance_db.insert_account_totals(
            totals_to_insert
        ):
            return json_error_response(
                "ERROR: Failed to insert transactions."
            )

        return json_success_response()
            

@namespace.route('/latest_percentages')
class AccountPercentagesAPI(Resource):
    """
    API to get the latest Account Percentages.
    """
    @namespace.expect(user_id_parser)
    def get(self):
        """
        Gets latest account percentages from a User.
        """
        args = user_id_parser.parse_args()
        user_id = args['user_id']

        finance_db = FinanceDatabase()

        user = finance_db.get_dashboard_user(user_id)
        if user is None:
            return json_error_response(
                "ERROR: User does not exist."
            )

        account_percentages = finance_db.get_latest_account_percentages(user_id)
        return json_success_response([
            {
                'account_name': account,
                'percentage': percentage 
            }
            for account, percentage in account_percentages.items()
        ])