From bcfa8f61e1421b88afdbb11a22381e7feb39b02b Mon Sep 17 00:00:00 2001 From: Mark Veidemanis Date: Thu, 9 Mar 2023 16:44:16 +0000 Subject: [PATCH] Fetch account details and display --- app/urls.py | 14 ++- core/clients/aggregator.py | 46 +++++++++ core/clients/aggregators/nordigen.py | 36 ++++--- core/lib/schemas/nordigen_s.py | 7 +- core/management/commands/scheduling.py | 13 +++ ...ator_account_info_aggregator_currencies.py | 23 +++++ .../0006_aggregator_fetch_accounts.py | 18 ++++ ..._alter_aggregator_account_info_and_more.py | 23 +++++ core/models.py | 5 + core/templates/base.html | 34 +++++++ .../partials/banks-currencies-list.html | 50 ++++++++++ core/views/aggregators.py | 96 ++++++++++--------- core/views/banks.py | 42 ++++++++ core/views/helpers.py | 18 ++++ 14 files changed, 362 insertions(+), 63 deletions(-) create mode 100644 core/clients/aggregator.py create mode 100644 core/migrations/0005_aggregator_account_info_aggregator_currencies.py create mode 100644 core/migrations/0006_aggregator_fetch_accounts.py create mode 100644 core/migrations/0007_alter_aggregator_account_info_and_more.py create mode 100644 core/templates/partials/banks-currencies-list.html create mode 100644 core/views/banks.py create mode 100644 core/views/helpers.py diff --git a/app/urls.py b/app/urls.py index 5f003ab..b9c452c 100644 --- a/app/urls.py +++ b/app/urls.py @@ -20,7 +20,7 @@ from django.contrib.auth.views import LogoutView from django.urls import include, path from two_factor.urls import urlpatterns as tf_urls -from core.views import aggregators, base, notifications +from core.views import aggregators, banks, base, notifications # from core.views.stripe_callbacks import Callback @@ -93,4 +93,16 @@ urlpatterns = [ aggregators.ReqInfo.as_view(), name="req_info", ), + # Request bank fetch + path( + "ops/bank_fetch//", + aggregators.RequestBankFetch.as_view(), + name="bank_fetch", + ), + # Bank details by currency + path( + "banks//details/", + banks.BanksCurrencies.as_view(), + name="currencies", + ), ] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) diff --git a/core/clients/aggregator.py b/core/clients/aggregator.py new file mode 100644 index 0000000..d12a308 --- /dev/null +++ b/core/clients/aggregator.py @@ -0,0 +1,46 @@ +from abc import ABC + + +class AggregatorClient(ABC): + def store_account_info(self, account_infos): + print("STORE ACCOUNT INFO CALLED") + # account_infos = { + # bank: accounts + # for bank, accounts in account_info.items() + # for account in accounts + # #if account["account_id"] in self.banks + # } + # For each bank + for bank, accounts in account_infos.items(): + # Iterate the accounts + for index, account in enumerate(list(accounts)): + if "account_number" not in account: + account_infos[bank][index]["account_number"] = {} + fields = ["sort_code", "number", "iban"] + for field in fields: + if field in account: + account_infos[bank][index]["account_number"][ + field + ] = account[field] + del account_infos[bank][index][field] + # if len(account["account_number"]) == 1: + # account_infos[bank].remove(account) + currencies = [ + account["currency"] + for bank, accounts in account_infos.items() + for account in accounts + ] + for bank, accounts in account_infos.items(): + if not self.instance.account_info: + self.instance.account_info = {} + self.instance.account_info[bank] = [] + for account in accounts: + self.instance.account_info[bank].append(account) + # self.account_info = account_infos + self.currencies = currencies + + self.instance.currencies = currencies + self.instance.save() + + print("CURRENCIES", self.instance.currencies) + print("ACCOUNT INFO", self.instance.account_info) diff --git a/core/clients/aggregators/nordigen.py b/core/clients/aggregators/nordigen.py index c8c44a6..87f3e21 100644 --- a/core/clients/aggregators/nordigen.py +++ b/core/clients/aggregators/nordigen.py @@ -3,13 +3,14 @@ from datetime import timedelta from django.conf import settings from django.utils import timezone +from core.clients.aggregator import AggregatorClient from core.clients.base import BaseClient from core.util import logs log = logs.get_logger("nordigen") -class NordigenClient(BaseClient): +class NordigenClient(BaseClient, AggregatorClient): url = "https://ob.nordigen.com/api/v2" async def connect(self): @@ -60,7 +61,7 @@ class NordigenClient(BaseClient): """ # This function is a stub. - return ["GB", "SE"] + return ["GB", "SE", "BG", "UA"] async def get_banks(self, country): """ @@ -128,28 +129,31 @@ class NordigenClient(BaseClient): path = f"accounts/{account_id}/details" response = await self.call(path, schema="AccountDetails") + print("RESPONSE", response) if "account" not in response: return False parsed = response["account"] - if "bban" in parsed and parsed["currency"] == "GBP": - sort_code = parsed["bban"][0:6] - account_number = parsed["bban"][6:] - del parsed["bban"] - if "iban" in parsed: + if "iban" in parsed and parsed["currency"] == "GBP": + if parsed["iban"]: + sort_code = parsed["iban"][-14:-8] + account_number = parsed["iban"][-8:] del parsed["iban"] - sort_code = "-".join(list(map("".join, zip(*[iter(sort_code)] * 2)))) - parsed["sort_code"] = sort_code - parsed["number"] = account_number - parsed["recipient"] = "TODO" + # if "iban" in parsed: + # del parsed["iban"] + sort_code = "-".join(list(map("".join, zip(*[iter(sort_code)] * 2)))) + parsed["sort_code"] = sort_code + parsed["number"] = account_number + parsed["recipient"] = "TODO" # Let's add the account ID so we can reference it later parsed["account_id"] = account_id return parsed - async def get_all_account_info(self, requisition=None): + async def get_all_account_info(self, requisition=None, store=False): to_return = {} if not requisition: - raise NotImplementedError - # requisitions = await self.get_requisitions() + print("NOT REQUISITION") + requisitions = await self.get_requisitions() + print("GOT REQS", requisitions) else: requisitions = [await self.get_requisition(requisition)] @@ -163,4 +167,8 @@ class NordigenClient(BaseClient): to_return[req["institution_id"]].append(account_info) else: to_return[req["institution_id"]] = [account_info] + + if store: + print("TO STORE IS TRUE") + self.store_account_info(to_return) return to_return diff --git a/core/lib/schemas/nordigen_s.py b/core/lib/schemas/nordigen_s.py index c50986c..9a4da5a 100644 --- a/core/lib/schemas/nordigen_s.py +++ b/core/lib/schemas/nordigen_s.py @@ -113,9 +113,12 @@ class AccountDetailsNested(BaseModel): currency: str ownerName: str cashAccountType: str - status: str + status: str | None maskedPan: str | None - details: str + details: str | None + iban: str | None + name: str | None + product: str | None class AccountDetails(BaseModel): diff --git a/core/management/commands/scheduling.py b/core/management/commands/scheduling.py index ab54c7e..87ebbc5 100644 --- a/core/management/commands/scheduling.py +++ b/core/management/commands/scheduling.py @@ -3,6 +3,8 @@ import asyncio from apscheduler.schedulers.asyncio import AsyncIOScheduler from django.core.management.base import BaseCommand +from core.clients.aggregators.nordigen import NordigenClient +from core.models import Aggregator from core.util import logs log = logs.get_logger("scheduling") @@ -12,6 +14,17 @@ INTERVAL = 5 async def job(): print("Running schedule.") + aggregators = Aggregator.objects.filter(enabled=True, fetch_accounts=True) + for aggregator in aggregators: + if aggregator.service == "nordigen": + instance = await NordigenClient(aggregator) + print("RUNNING GET ALL ACCOUNT INFO") + await instance.get_all_account_info(store=True) + print("FINISHED RUNNING") + else: + raise NotImplementedError(f"No such client library: {aggregator.service}") + aggregator.fetch_accounts = False + aggregator.save() class Command(BaseCommand): diff --git a/core/migrations/0005_aggregator_account_info_aggregator_currencies.py b/core/migrations/0005_aggregator_account_info_aggregator_currencies.py new file mode 100644 index 0000000..f3e3165 --- /dev/null +++ b/core/migrations/0005_aggregator_account_info_aggregator_currencies.py @@ -0,0 +1,23 @@ +# Generated by Django 4.1.7 on 2023-03-09 11:54 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0004_aggregator_access_token_expires'), + ] + + operations = [ + migrations.AddField( + model_name='aggregator', + name='account_info', + field=models.JSONField(default=list), + ), + migrations.AddField( + model_name='aggregator', + name='currencies', + field=models.JSONField(default=list), + ), + ] diff --git a/core/migrations/0006_aggregator_fetch_accounts.py b/core/migrations/0006_aggregator_fetch_accounts.py new file mode 100644 index 0000000..b48c580 --- /dev/null +++ b/core/migrations/0006_aggregator_fetch_accounts.py @@ -0,0 +1,18 @@ +# Generated by Django 4.1.7 on 2023-03-09 11:54 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0005_aggregator_account_info_aggregator_currencies'), + ] + + operations = [ + migrations.AddField( + model_name='aggregator', + name='fetch_accounts', + field=models.BooleanField(default=False), + ), + ] diff --git a/core/migrations/0007_alter_aggregator_account_info_and_more.py b/core/migrations/0007_alter_aggregator_account_info_and_more.py new file mode 100644 index 0000000..0937b11 --- /dev/null +++ b/core/migrations/0007_alter_aggregator_account_info_and_more.py @@ -0,0 +1,23 @@ +# Generated by Django 4.1.7 on 2023-03-09 14:14 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0006_aggregator_fetch_accounts'), + ] + + operations = [ + migrations.AlterField( + model_name='aggregator', + name='account_info', + field=models.JSONField(default=dict), + ), + migrations.AlterField( + model_name='aggregator', + name='fetch_accounts', + field=models.BooleanField(default=True), + ), + ] diff --git a/core/models.py b/core/models.py index a3db6de..cf2c4ed 100644 --- a/core/models.py +++ b/core/models.py @@ -45,6 +45,11 @@ class Aggregator(models.Model): access_token_expires = models.DateTimeField(null=True, blank=True) poll_interval = models.IntegerField(default=10) + account_info = models.JSONField(default=dict) + currencies = models.JSONField(default=list) + + fetch_accounts = models.BooleanField(default=True) + enabled = models.BooleanField(default=True) def __str__(self): diff --git a/core/templates/base.html b/core/templates/base.html index f9132a5..afd19dd 100644 --- a/core/templates/base.html +++ b/core/templates/base.html @@ -219,6 +219,40 @@ Home {% if user.is_authenticated %} + + Profit + + +