feat: v1 APIs done
Signed-off-by: Ameya Shenoy <shenoy.ameya@gmail.com>
This commit is contained in:
parent
d59993286c
commit
d7295d9f31
4 changed files with 271 additions and 7 deletions
|
|
@ -17,13 +17,16 @@ from django.conf import settings
|
||||||
from django.conf.urls.static import static
|
from django.conf.urls.static import static
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
from foldbank.api import AccountsAPI, TransactionsAPI, UpcomingRecurringPaymentAPI
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('admin/', admin.site.urls),
|
path("admin/", admin.site.urls),
|
||||||
|
path("api/v1/transactions/", TransactionsAPI.as_view()),
|
||||||
|
path("api/v1/accounts/", AccountsAPI.as_view()),
|
||||||
|
path("api/v1/recurringPayments/upcoming/", UpcomingRecurringPaymentAPI.as_view()),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
if settings.DEBUG:
|
if settings.DEBUG:
|
||||||
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
|
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
|
||||||
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||||
|
|
||||||
|
|
|
||||||
263
backend/foldbank/api.py
Normal file
263
backend/foldbank/api.py
Normal file
|
|
@ -0,0 +1,263 @@
|
||||||
|
import logging
|
||||||
|
from collections import OrderedDict
|
||||||
|
from random import randrange
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.contrib.auth import get_user_model
|
||||||
|
from drf_yasg import openapi
|
||||||
|
from drf_yasg.utils import swagger_auto_schema
|
||||||
|
from foldbank.models import Account, RecurringPayment, Transaction, User
|
||||||
|
from rest_framework import generics, serializers
|
||||||
|
from rest_framework.pagination import LimitOffsetPagination as _LimitOffsetPagination
|
||||||
|
from rest_framework.response import Response
|
||||||
|
|
||||||
|
UserModel = get_user_model()
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def create_serializer_class(name, fields):
|
||||||
|
return type(name, (serializers.Serializer,), fields)
|
||||||
|
|
||||||
|
|
||||||
|
def inline_serializer(*, fields, data=None, **kwargs):
|
||||||
|
serializer_class = create_serializer_class(name="", fields=fields)
|
||||||
|
|
||||||
|
if data is not None:
|
||||||
|
return serializer_class(data=data, **kwargs)
|
||||||
|
|
||||||
|
return serializer_class(**kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def get_paginated_response(
|
||||||
|
*,
|
||||||
|
pagination_class,
|
||||||
|
serializer_class,
|
||||||
|
queryset,
|
||||||
|
request,
|
||||||
|
view,
|
||||||
|
mod_func=None,
|
||||||
|
):
|
||||||
|
paginator = pagination_class()
|
||||||
|
|
||||||
|
page = paginator.paginate_queryset(queryset, request, view=view)
|
||||||
|
|
||||||
|
if page is not None:
|
||||||
|
if mod_func:
|
||||||
|
page = mod_func(page)
|
||||||
|
serializer = serializer_class(page, many=True)
|
||||||
|
return paginator.get_paginated_response(serializer.data)
|
||||||
|
|
||||||
|
serializer = serializer_class(queryset, many=True)
|
||||||
|
|
||||||
|
return Response(serializer.data)
|
||||||
|
|
||||||
|
|
||||||
|
class LimitOffsetPagination(_LimitOffsetPagination):
|
||||||
|
default_limit = 10
|
||||||
|
max_limit = 50
|
||||||
|
|
||||||
|
def get_paginated_data(self, data):
|
||||||
|
return OrderedDict(
|
||||||
|
[
|
||||||
|
("limit", self.limit),
|
||||||
|
("offset", self.offset),
|
||||||
|
("count", self.count),
|
||||||
|
("next", self.get_next_link()),
|
||||||
|
("previous", self.get_previous_link()),
|
||||||
|
("results", data),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_paginated_response(self, data):
|
||||||
|
"""
|
||||||
|
We redefine this method in order to return `limit` and `offset`.
|
||||||
|
This is used by the frontend to construct the pagination itself.
|
||||||
|
"""
|
||||||
|
return Response(
|
||||||
|
OrderedDict(
|
||||||
|
[
|
||||||
|
("limit", self.limit),
|
||||||
|
("offset", self.offset),
|
||||||
|
("count", self.count),
|
||||||
|
("next", self.get_next_link()),
|
||||||
|
("previous", self.get_previous_link()),
|
||||||
|
("results", data),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_transactions(user):
|
||||||
|
return Transaction.objects.filter(from_account__user=user).order_by("created_at")
|
||||||
|
|
||||||
|
|
||||||
|
class TransactionsAPI(generics.ListAPIView):
|
||||||
|
authentication_classes = []
|
||||||
|
permission_classes = []
|
||||||
|
|
||||||
|
class Pagination(LimitOffsetPagination):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class TransactionsInputSerializer(serializers.Serializer):
|
||||||
|
bank_id = serializers.UUIDField(default=None)
|
||||||
|
|
||||||
|
class TransactionsOutputSerializer(serializers.Serializer):
|
||||||
|
id = serializers.UUIDField()
|
||||||
|
created_at = serializers.DateTimeField()
|
||||||
|
amount = serializers.IntegerField()
|
||||||
|
tag = inline_serializer(
|
||||||
|
required=True,
|
||||||
|
fields={
|
||||||
|
"title": serializers.CharField(required=True),
|
||||||
|
"icon_type": serializers.CharField(required=True),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
to_account = inline_serializer(
|
||||||
|
required=True,
|
||||||
|
fields={
|
||||||
|
"holders_name": serializers.CharField(required=True),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
success_response = openapi.Response(
|
||||||
|
"Success Response", TransactionsOutputSerializer
|
||||||
|
)
|
||||||
|
|
||||||
|
@swagger_auto_schema(
|
||||||
|
query_serializer=TransactionsInputSerializer,
|
||||||
|
operation_summary="Transactions List",
|
||||||
|
responses={
|
||||||
|
200: success_response,
|
||||||
|
},
|
||||||
|
tags=["Transactions"],
|
||||||
|
)
|
||||||
|
def get(self, request):
|
||||||
|
qs = []
|
||||||
|
|
||||||
|
serializer = self.TransactionsInputSerializer(data=request.query_params)
|
||||||
|
serializer.is_valid(raise_exception=True)
|
||||||
|
|
||||||
|
# TODO: hardcoding nishant to mitigate user login
|
||||||
|
user = User.objects.get(username="nishant")
|
||||||
|
qs = get_transactions(user=user)
|
||||||
|
|
||||||
|
def modify_qs(qs):
|
||||||
|
return qs
|
||||||
|
|
||||||
|
return get_paginated_response(
|
||||||
|
pagination_class=self.Pagination,
|
||||||
|
serializer_class=self.TransactionsOutputSerializer,
|
||||||
|
queryset=qs,
|
||||||
|
request=request,
|
||||||
|
view=self,
|
||||||
|
mod_func=modify_qs,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_bank_accounts(user):
|
||||||
|
return Account.objects.filter(user=user)
|
||||||
|
|
||||||
|
|
||||||
|
class AccountsAPI(generics.ListAPIView):
|
||||||
|
authentication_classes = []
|
||||||
|
permission_classes = []
|
||||||
|
|
||||||
|
class Pagination(LimitOffsetPagination):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class AccountsOutputSerializer(serializers.Serializer):
|
||||||
|
id = serializers.UUIDField()
|
||||||
|
created_at = serializers.DateTimeField()
|
||||||
|
balance = serializers.IntegerField()
|
||||||
|
bank = inline_serializer(
|
||||||
|
required=True,
|
||||||
|
fields={
|
||||||
|
"name": serializers.CharField(required=True),
|
||||||
|
"logo": serializers.CharField(required=True),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
account_number = serializers.CharField()
|
||||||
|
ifsc_code = serializers.CharField()
|
||||||
|
swift_bic = serializers.CharField()
|
||||||
|
holders_name = serializers.CharField()
|
||||||
|
|
||||||
|
success_response = openapi.Response("Success Response", AccountsOutputSerializer)
|
||||||
|
|
||||||
|
@swagger_auto_schema(
|
||||||
|
operation_summary="Accounts List",
|
||||||
|
responses={
|
||||||
|
200: success_response,
|
||||||
|
},
|
||||||
|
tags=["Accounts"],
|
||||||
|
)
|
||||||
|
def get(self, request):
|
||||||
|
qs = []
|
||||||
|
|
||||||
|
# TODO: hardcoding nishant to mitigate user login
|
||||||
|
user = User.objects.get(username="nishant")
|
||||||
|
qs = get_bank_accounts(user=user)
|
||||||
|
|
||||||
|
def modify_qs(qs):
|
||||||
|
return qs
|
||||||
|
|
||||||
|
return get_paginated_response(
|
||||||
|
pagination_class=self.Pagination,
|
||||||
|
serializer_class=self.AccountsOutputSerializer,
|
||||||
|
queryset=qs,
|
||||||
|
request=request,
|
||||||
|
view=self,
|
||||||
|
mod_func=modify_qs,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_upcoming_recurring_payments(user):
|
||||||
|
return RecurringPayment.objects.filter(from_account__user=user)
|
||||||
|
|
||||||
|
|
||||||
|
class UpcomingRecurringPaymentAPI(generics.ListAPIView):
|
||||||
|
authentication_classes = []
|
||||||
|
permission_classes = []
|
||||||
|
|
||||||
|
class Pagination(LimitOffsetPagination):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class UpcomingRecurringPaymentOutputSerializer(serializers.Serializer):
|
||||||
|
id = serializers.UUIDField()
|
||||||
|
amount = serializers.IntegerField()
|
||||||
|
due_on = serializers.DateTimeField()
|
||||||
|
frequency = serializers.DurationField()
|
||||||
|
to_account = inline_serializer(
|
||||||
|
required=True,
|
||||||
|
fields={
|
||||||
|
"holders_name": serializers.CharField(required=True),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
success_response = openapi.Response("Success Response", UpcomingRecurringPaymentOutputSerializer)
|
||||||
|
|
||||||
|
@swagger_auto_schema(
|
||||||
|
operation_summary="Recurring Payments List",
|
||||||
|
responses={
|
||||||
|
200: success_response,
|
||||||
|
},
|
||||||
|
tags=["Recurring Payments"],
|
||||||
|
)
|
||||||
|
def get(self, request):
|
||||||
|
qs = []
|
||||||
|
|
||||||
|
# TODO: hardcoding nishant to mitigate user login
|
||||||
|
user = User.objects.get(username="nishant")
|
||||||
|
qs = get_upcoming_recurring_payments(user=user)
|
||||||
|
|
||||||
|
def modify_qs(qs):
|
||||||
|
return qs
|
||||||
|
|
||||||
|
return get_paginated_response(
|
||||||
|
pagination_class=self.Pagination,
|
||||||
|
serializer_class=self.UpcomingRecurringPaymentOutputSerializer,
|
||||||
|
queryset=qs,
|
||||||
|
request=request,
|
||||||
|
view=self,
|
||||||
|
mod_func=modify_qs,
|
||||||
|
)
|
||||||
|
|
@ -303,7 +303,7 @@ class Command(BaseCommand):
|
||||||
{
|
{
|
||||||
"from_account": Account.objects.get(user=nishant, bank=Bank.objects.get(name="Axis Bank")),
|
"from_account": Account.objects.get(user=nishant, bank=Bank.objects.get(name="Axis Bank")),
|
||||||
"to_account": Account.objects.get(user=admin, holders_name="Swiggy"),
|
"to_account": Account.objects.get(user=admin, holders_name="Swiggy"),
|
||||||
"amount": "35",
|
"amount": "249",
|
||||||
"tag": Tag.objects.get(title="Food & Drinks", sub_category="Swiggy"),
|
"tag": Tag.objects.get(title="Food & Drinks", sub_category="Swiggy"),
|
||||||
"defaults": {
|
"defaults": {
|
||||||
"created_at": make_aware(datetime.datetime(year=2023, month=1, day=1, hour=11, minute=17)),
|
"created_at": make_aware(datetime.datetime(year=2023, month=1, day=1, hour=11, minute=17)),
|
||||||
|
|
@ -312,13 +312,14 @@ class Command(BaseCommand):
|
||||||
{
|
{
|
||||||
"from_account": Account.objects.get(user=nishant, bank=Bank.objects.get(name="Axis Bank")),
|
"from_account": Account.objects.get(user=nishant, bank=Bank.objects.get(name="Axis Bank")),
|
||||||
"to_account": Account.objects.get(user=admin, holders_name="Netflix"),
|
"to_account": Account.objects.get(user=admin, holders_name="Netflix"),
|
||||||
"amount": "35",
|
"amount": "199",
|
||||||
"tag": Tag.objects.get(title="Subscription", sub_category="Netflix"),
|
"tag": Tag.objects.get(title="Subscription", sub_category="Netflix"),
|
||||||
"defaults": {
|
"defaults": {
|
||||||
"created_at": make_aware(datetime.datetime(year=2023, month=1, day=23, hour=23, minute=45)),
|
"created_at": make_aware(datetime.datetime(year=2023, month=1, day=23, hour=23, minute=45)),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
# Transaction.objects.all().delete()
|
||||||
for t in transactions:
|
for t in transactions:
|
||||||
Transaction.objects.get_or_create(**t)
|
Transaction.objects.get_or_create(**t)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,6 @@ class CustomAccountManager(BaseUserManager):
|
||||||
return user
|
return user
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Create your models here.
|
# Create your models here.
|
||||||
class User(AbstractBaseUser, PermissionsMixin):
|
class User(AbstractBaseUser, PermissionsMixin):
|
||||||
# TODO: make username case insensitive yet retain case sensitivity
|
# TODO: make username case insensitive yet retain case sensitivity
|
||||||
|
|
@ -151,7 +150,6 @@ class Transaction(models.Model):
|
||||||
return f"{self.from_account.holders_name} - {self.to_account.holders_name}"
|
return f"{self.from_account.holders_name} - {self.to_account.holders_name}"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class RecurringPayment(models.Model):
|
class RecurringPayment(models.Model):
|
||||||
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
||||||
from_account = models.ForeignKey(
|
from_account = models.ForeignKey(
|
||||||
|
|
@ -170,7 +168,6 @@ class RecurringPayment(models.Model):
|
||||||
return f"{self.from_account.holders_name} - {self.to_account.holders_name}"
|
return f"{self.from_account.holders_name} - {self.to_account.holders_name}"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class RecurringPaymentTransactionLink(models.Model):
|
class RecurringPaymentTransactionLink(models.Model):
|
||||||
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
||||||
recurring_payment = models.ForeignKey("RecurringPayment", on_delete=models.PROTECT)
|
recurring_payment = models.ForeignKey("RecurringPayment", on_delete=models.PROTECT)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue