feat: transactions api done

Signed-off-by: Ameya Shenoy <shenoy.ameya@gmail.com>
This commit is contained in:
Ameya Shenoy 2023-03-22 16:48:37 +05:30
parent d7295d9f31
commit 1ae3e0ce2b
Signed by: codingcoffee
GPG key ID: EEC8EA855D61CEEC
9 changed files with 202 additions and 74 deletions

View file

@ -16,17 +16,49 @@ Including another URLconf
from django.conf import settings 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 drf_yasg.views import get_schema_view
from drf_yasg import openapi
from rest_framework import permissions
from django.urls import include, path, re_path
from foldbank.api import AccountsAPI, TransactionsAPI, UpcomingRecurringPaymentAPI from foldbank.api import AccountsAPI, TransactionsAPI, UpcomingRecurringPaymentAPI
schema_view = get_schema_view(
openapi.Info(
title="Grapevine API",
default_version="v1",
description="",
terms_of_service="https://gvine.app/terms/",
contact=openapi.Contact(email="support@gvine.app"),
license=openapi.License(name="BSD License"),
),
public=True,
permission_classes=[permissions.AllowAny],
)
urlpatterns = [ urlpatterns = [
path("admin/", admin.site.urls), path("admin/", admin.site.urls),
path("api/v1/transactions/", TransactionsAPI.as_view()), path("api/v1/transactions/", TransactionsAPI.as_view()),
path("api/v1/accounts/", AccountsAPI.as_view()), path("api/v1/accounts/", AccountsAPI.as_view()),
path("api/v1/recurringPayments/upcoming/", UpcomingRecurringPaymentAPI.as_view()), path("api/v1/recurringPayments/upcoming/", UpcomingRecurringPaymentAPI.as_view()),
re_path(
r"^swagger(?P<format>\.json|\.yaml)$",
schema_view.without_ui(cache_timeout=0),
name="schema-json",
),
re_path(
r"^swagger/$",
schema_view.with_ui("swagger", cache_timeout=0),
name="schema-swagger-ui",
),
re_path(
r"^redoc/$",
schema_view.with_ui("redoc", cache_timeout=0),
name="schema-redoc",
),
] ]
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)

View file

@ -84,6 +84,12 @@ class UserAdminConfig(UserAdmin):
) )
class TransactionConfig(admin.ModelAdmin):
list_display = (
"from_account",
"created_at",
)
# Register your models here. # Register your models here.
admin.site.register(User, UserAdminConfig) admin.site.register(User, UserAdminConfig)
@ -91,6 +97,6 @@ admin.site.register(Tag)
admin.site.register(Bank) admin.site.register(Bank)
admin.site.register(Account) admin.site.register(Account)
admin.site.register(RecurringPayment) admin.site.register(RecurringPayment)
admin.site.register(Transaction) admin.site.register(Transaction, TransactionConfig)
admin.site.register(RecurringPaymentTransactionLink) admin.site.register(RecurringPaymentTransactionLink)

View file

@ -88,7 +88,7 @@ class LimitOffsetPagination(_LimitOffsetPagination):
def get_transactions(user): def get_transactions(user):
return Transaction.objects.filter(from_account__user=user).order_by("created_at") return Transaction.objects.filter(from_account__user=user).order_by("-created_at")
class TransactionsAPI(generics.ListAPIView): class TransactionsAPI(generics.ListAPIView):

View file

@ -1,5 +1,6 @@
# standard imports # standard importspopulatedb
import datetime import datetime
from zoneinfo import ZoneInfo
# third-party imports # third-party imports
from django.utils.timezone import make_aware from django.utils.timezone import make_aware
@ -226,6 +227,9 @@ class Command(BaseCommand):
Account.objects.get_or_create(**account) Account.objects.get_or_create(**account)
# recurring payment # recurring payment
ist = ZoneInfo("Asia/Kolkata")
today = datetime.datetime.now(tz=ist)
yesterday = datetime.datetime.now(tz=ist) - datetime.timedelta(days=1)
recurring_payments = [ recurring_payments = [
{ {
"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")),
@ -233,9 +237,9 @@ class Command(BaseCommand):
"amount": "36031", "amount": "36031",
"tag": Tag.objects.get(title="Rent"), "tag": Tag.objects.get(title="Rent"),
"defaults": { "defaults": {
"created_at": make_aware(datetime.datetime(year=2023, month=1, day=23)), "created_at": datetime.datetime(year=2023, month=1, day=23, tzinfo=ist),
"frequency": datetime.timedelta(days=30), "frequency": datetime.timedelta(days=30),
"due_on": make_aware(datetime.datetime(year=2023, month=1, day=23)), "due_on": datetime.datetime(year=2023, month=1, day=23, tzinfo=ist),
}, },
}, },
{ {
@ -244,9 +248,9 @@ class Command(BaseCommand):
"amount": "36031", "amount": "36031",
"tag": Tag.objects.get(title="Rent"), "tag": Tag.objects.get(title="Rent"),
"defaults": { "defaults": {
"created_at": make_aware(datetime.datetime(year=2023, month=1, day=20)), "created_at": datetime.datetime(year=2023, month=1, day=20, tzinfo=ist),
"frequency": datetime.timedelta(days=30), "frequency": datetime.timedelta(days=30),
"due_on": make_aware(datetime.datetime(year=2023, month=2, day=19)), "due_on": datetime.datetime(year=2023, month=2, day=19, tzinfo=ist),
} }
}, },
] ]
@ -261,7 +265,7 @@ class Command(BaseCommand):
"amount": "3940", "amount": "3940",
"tag": Tag.objects.get(title="Tag"), "tag": Tag.objects.get(title="Tag"),
"defaults": { "defaults": {
"created_at": make_aware(datetime.datetime(year=2023, month=1, day=23, hour=11, minute=17)), "created_at": datetime.datetime(year=today.year, month=today.month, day=today.day, hour=11, minute=17, tzinfo=ist),
} }
}, },
{ {
@ -270,7 +274,7 @@ class Command(BaseCommand):
"amount": "35", "amount": "35",
"tag": Tag.objects.get(title="Food & Drinks", sub_category="Drink"), "tag": Tag.objects.get(title="Food & Drinks", sub_category="Drink"),
"defaults": { "defaults": {
"created_at": make_aware(datetime.datetime(year=2023, month=1, day=23, hour=23, minute=45)), "created_at": datetime.datetime(year=today.year, month=today.month, day=today.day, hour=23, minute=45, tzinfo=ist),
} }
}, },
{ {
@ -279,7 +283,7 @@ class Command(BaseCommand):
"amount": "2399", "amount": "2399",
"tag": Tag.objects.get(title="Groceries"), "tag": Tag.objects.get(title="Groceries"),
"defaults": { "defaults": {
"created_at": make_aware(datetime.datetime(year=2023, month=1, day=23, hour=5, minute=37)), "created_at": datetime.datetime(year=yesterday.year, month=yesterday.month, day=yesterday.day, hour=5, minute=37, tzinfo=ist),
} }
}, },
{ {
@ -288,7 +292,7 @@ class Command(BaseCommand):
"amount": "312", "amount": "312",
"tag": Tag.objects.get(title="Food & Drinks", sub_category="Food"), "tag": Tag.objects.get(title="Food & Drinks", sub_category="Food"),
"defaults": { "defaults": {
"created_at": make_aware(datetime.datetime(year=2023, month=1, day=23, hour=12, minute=17)), "created_at": datetime.datetime(year=yesterday.year, month=yesterday.month, day=yesterday.day, hour=12, minute=17, tzinfo=ist),
} }
}, },
{ {
@ -297,7 +301,7 @@ class Command(BaseCommand):
"amount": "75", "amount": "75",
"tag": Tag.objects.get(title="Transport"), "tag": Tag.objects.get(title="Transport"),
"defaults": { "defaults": {
"created_at": make_aware(datetime.datetime(year=2023, month=1, day=2, hour=10, minute=32)), "created_at": datetime.datetime(year=2023, month=1, day=2, hour=10, minute=32, tzinfo=ist),
} }
}, },
{ {
@ -306,7 +310,7 @@ class Command(BaseCommand):
"amount": "249", "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": datetime.datetime(year=2023, month=1, day=1, hour=11, minute=17, tzinfo=ist),
} }
}, },
{ {
@ -315,11 +319,13 @@ class Command(BaseCommand):
"amount": "199", "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": datetime.datetime(year=2022, month=12, day=31, hour=23, minute=45, tzinfo=ist),
} }
}, },
] ]
# Transaction.objects.all().delete() # Transaction.objects.all().delete()
for t in transactions: for t in transactions:
Transaction.objects.get_or_create(**t) tnx, _ = Transaction.objects.get_or_create(**t)
tnx.created_at = t["defaults"]["created_at"]
tnx.save()

View file

@ -1,8 +1,9 @@
# Django # Django
SECRET_KEY=sample SECRET_KEY=sample
DEBUG=true DEBUG=true
ALLOWED_HOSTS=0.0.0.0,localhost,127.0.0.1,* ALLOWED_HOSTS=0.0.0.0,localhost,127.0.0.1,localhost:3000
CSRF_TRUSTED_ORIGINS=https://foldbank.codingcoffee.me CSRF_TRUSTED_ORIGINS=http://localhost:3000,https://foldbank.codingcoffee.me
CORS_ALLOWED_ORIGINS=http://localhost:3000,https://foldbank.codingcoffee.me
DJANGO_LOG_LEVEL=INFO DJANGO_LOG_LEVEL=INFO
# Django - Postgres # Django - Postgres
POSTGRES_SERVER=postgres-db POSTGRES_SERVER=postgres-db

View file

@ -3,15 +3,15 @@
version: '3.7' version: '3.7'
services: services:
# web: web:
# image: node:16.19.1-alpine3.17 image: node:16.19.1-alpine3.17
# restart: unless-stopped restart: unless-stopped
# volumes: volumes:
# - ./web:/app - ./web:/app
# working_dir: /app working_dir: /app
# entrypoint: npm run dev entrypoint: npm run dev
# ports: ports:
# - 3000:3000 - 3000:3000
backend: backend:
image: foldbank-backend-dev image: foldbank-backend-dev

View file

@ -1,64 +1,109 @@
"use client"
import React from 'react' import React from 'react'
import styles from './recentTnx.module.css' import styles from './recentTnx.module.css'
import { BsTriangleFill } from 'react-icons/bs'; import { BsTriangleFill } from 'react-icons/bs';
import SingleTransaction from './SingleTransaction' import SingleTransaction from './SingleTransaction'
import useSWR from 'swr'
export default function RecentTransactions() { export default function RecentTransactions() {
const transactions = [ const fetcher = (...args) => fetch(...args).then((res) => res.json())
{ const { data, error } = useSWR(`http://localhost:8000/api/v1/transactions/`, fetcher)
title: "Fenny's Banglore",
datetime: "Today, 11:17 am", if (error) return <div>Error: Failed to load</div>
amount: "3,940", if (!data) return <div>Loading...</div>
tag: "Tag",
}, { const transactions = data.results
title: "Sendoor",
datetime: "Today, 11:45 pm", // const transactions = [
amount: "35", // {
tag: "Food & Drinks", // title: "Fenny's Banglore",
icon_type: "drink", // datetime: "Today, 11:17 am",
}, { // amount: "3,940",
title: "Reliance Fresh", // tag: "Tag",
datetime: "Yesterday, 5:37 pm", // }, {
amount: "2,399", // title: "Sendoor",
tag: "Groceries", // datetime: "Today, 11:45 pm",
icon_type: "groceries", // amount: "35",
}, { // tag: "Food & Drinks",
title: "Chai Point", // icon_type: "drink",
datetime: "Yesterday, 12:17 pm", // }, {
amount: "312", // title: "Reliance Fresh",
tag: "Food & Drinks", // datetime: "Yesterday, 5:37 pm",
icon_type: "food", // amount: "2,399",
}, { // tag: "Groceries",
title: "Uber", // icon_type: "groceries",
datetime: "Jan 2, 10:32 am", // }, {
amount: "75", // title: "Chai Point",
tag: "Transport", // datetime: "Yesterday, 12:17 pm",
icon_type: "transport", // amount: "312",
}, { // tag: "Food & Drinks",
title: "Swiggy", // icon_type: "food",
datetime: "Jan 1, 11:17 pm", // }, {
amount: "249", // title: "Uber",
tag: "Food & Drinks", // datetime: "Jan 2, 10:32 am",
icon_type: "swiggy", // amount: "75",
}, { // tag: "Transport",
title: "Netflix", // icon_type: "transport",
datetime: "Dec 31, 11:59 pm", // }, {
amount: "199", // title: "Swiggy",
tag: "Subsciption", // datetime: "Jan 1, 11:17 pm",
icon_type: "netflix", // amount: "249",
// tag: "Food & Drinks",
// icon_type: "swiggy",
// }, {
// title: "Netflix",
// datetime: "Dec 31, 11:59 pm",
// amount: "199",
// tag: "Subsciption",
// icon_type: "netflix",
// }
// ]
const relateive_date = (date) => {
const today = new Date();
let fuzzy;
if (today.getYear() == date.getYear()) {
if (today.getMonth() == date.getMonth()) {
if (today.getDate() == date.getDate()) {
fuzzy = 'Today';
} else if (today.getDate() == date.getDate() + 1) {
fuzzy = 'Yesterday';
}
}
} }
]
if (fuzzy == undefined){
const monthNames = [
"Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
];
fuzzy = `${monthNames[date.getMonth()]} ${date.getDate()}`
}
let hours = date.getHours()
let ampm = 'AM';
if (hours >= 12) {
ampm = 'PM';
hours -= 12;
}
fuzzy = fuzzy + `, ${hours}:${date.getMinutes()} ${ampm}`
return fuzzy
}
const recent_transactions = []; const recent_transactions = [];
transactions.forEach((transaction) => { transactions.forEach((transaction) => {
recent_transactions.push( recent_transactions.push(
<SingleTransaction <SingleTransaction
title={transaction.title} title={transaction.to_account.holders_name}
tag={transaction.tag} tag={transaction.tag.title}
datetime={transaction.datetime} datetime={relateive_date(new Date(transaction.created_at))}
amount={transaction.amount} amount={transaction.amount}
icon_type={transaction.icon_type} icon_type={transaction.tag.icon_type}
/> />
) )
}) })

37
web/package-lock.json generated
View file

@ -23,6 +23,7 @@
"react": "18.2.0", "react": "18.2.0",
"react-dom": "18.2.0", "react-dom": "18.2.0",
"react-icons": "^4.7.1", "react-icons": "^4.7.1",
"swr": "^2.1.0",
"typescript": "4.9.5" "typescript": "4.9.5"
} }
}, },
@ -3901,6 +3902,20 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/swr": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/swr/-/swr-2.1.0.tgz",
"integrity": "sha512-4hYl5p3/KalQ1MORealM79g/DtLohmud6yyfXw5l4wjtFksYUnocRFudvyaoUtgj3FrVNK9lS25Av9dsZYvz0g==",
"dependencies": {
"use-sync-external-store": "^1.2.0"
},
"engines": {
"pnpm": "7"
},
"peerDependencies": {
"react": "^16.11.0 || ^17.0.0 || ^18.0.0"
}
},
"node_modules/synckit": { "node_modules/synckit": {
"version": "0.8.5", "version": "0.8.5",
"resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.5.tgz", "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.5.tgz",
@ -4061,6 +4076,14 @@
"punycode": "^2.1.0" "punycode": "^2.1.0"
} }
}, },
"node_modules/use-sync-external-store": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz",
"integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==",
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
}
},
"node_modules/which": { "node_modules/which": {
"version": "2.0.2", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
@ -6776,6 +6799,14 @@
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==" "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="
}, },
"swr": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/swr/-/swr-2.1.0.tgz",
"integrity": "sha512-4hYl5p3/KalQ1MORealM79g/DtLohmud6yyfXw5l4wjtFksYUnocRFudvyaoUtgj3FrVNK9lS25Av9dsZYvz0g==",
"requires": {
"use-sync-external-store": "^1.2.0"
}
},
"synckit": { "synckit": {
"version": "0.8.5", "version": "0.8.5",
"resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.5.tgz", "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.5.tgz",
@ -6895,6 +6926,12 @@
"punycode": "^2.1.0" "punycode": "^2.1.0"
} }
}, },
"use-sync-external-store": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz",
"integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==",
"requires": {}
},
"which": { "which": {
"version": "2.0.2", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",

View file

@ -24,6 +24,7 @@
"react": "18.2.0", "react": "18.2.0",
"react-dom": "18.2.0", "react-dom": "18.2.0",
"react-icons": "^4.7.1", "react-icons": "^4.7.1",
"swr": "^2.1.0",
"typescript": "4.9.5" "typescript": "4.9.5"
} }
} }