feat: Phase 1+2 - JWT auth, dashboard metrics API, DB indexes

Phase 1 - JWT Auth Foundation:
- Replace token auth with djangorestframework-simplejwt
- POST /api/v1/admin/auth/login/ - returns access + refresh JWT
- POST /api/v1/auth/refresh/ - JWT refresh
- GET /api/v1/auth/me/ - current admin profile
- GET /api/v1/health/ - DB health check
- Add ledger app to INSTALLED_APPS

Phase 2 - Dashboard Metrics API:
- GET /api/v1/dashboard/metrics/ - revenue, partners, events, tickets
- GET /api/v1/dashboard/revenue/ - 7-day revenue vs payouts chart data
- GET /api/v1/dashboard/activity/ - last 10 platform events feed
- GET /api/v1/dashboard/actions/ - KYC queue, flagged events, pending payouts

DB Indexes (dashboard query optimisation):
- RazorpayTransaction: status, captured_at
- Partner: status, kyc_compliance_status
- Event: event_status, start_date, created_date
- Booking: created_date
- PaymentTransaction: payment_type, payment_transaction_status, payment_transaction_date

Infra:
- Add Dockerfile for eventify-backend container
- Add simplejwt to requirements.txt
- All 4 dashboard views use IsAuthenticated permission class
This commit is contained in:
Ubuntu
2026-03-24 17:46:41 +00:00
parent 37001f8e70
commit b60d03142c
14 changed files with 416 additions and 94 deletions

View File

@@ -50,3 +50,239 @@ class HealthView(APIView):
except Exception:
db_status = 'error'
return Response({'status': 'ok', 'db': db_status})
# ---------------------------------------------------------------------------
# Phase 2: Dashboard Views
# ---------------------------------------------------------------------------
class DashboardMetricsView(APIView):
permission_classes = [IsAuthenticated]
def get(self, request):
from ledger.models import RazorpayTransaction
from partner.models import Partner
from events.models import Event
from bookings.models import Ticket, Booking
from django.db.models import Sum
from django.utils import timezone
import datetime
today = timezone.now().date()
# --- Revenue ---
total_paise = (
RazorpayTransaction.objects
.filter(status='captured')
.aggregate(total=Sum('amount'))['total'] or 0
)
total_revenue = total_paise / 100
# This-month / last-month revenue for growth
first_of_this_month = today.replace(day=1)
first_of_last_month = (first_of_this_month - datetime.timedelta(days=1)).replace(day=1)
this_month_paise = (
RazorpayTransaction.objects
.filter(status='captured', captured_at__date__gte=first_of_this_month)
.aggregate(total=Sum('amount'))['total'] or 0
)
last_month_paise = (
RazorpayTransaction.objects
.filter(
status='captured',
captured_at__date__gte=first_of_last_month,
captured_at__date__lt=first_of_this_month,
)
.aggregate(total=Sum('amount'))['total'] or 0
)
this_month_rev = this_month_paise / 100
last_month_rev = last_month_paise / 100
revenue_growth = (
round((this_month_rev - last_month_rev) / last_month_rev * 100, 2)
if last_month_rev else 0
)
# --- Partners ---
active_partners = Partner.objects.filter(status='active').count()
pending_partners = Partner.objects.filter(kyc_compliance_status='pending').count()
# --- Events ---
live_events = Event.objects.filter(event_status='live').count()
events_today = Event.objects.filter(start_date=today).count()
# --- Tickets ---
ticket_sales = Ticket.objects.count()
# This-week / last-week ticket growth
week_start = today - datetime.timedelta(days=today.weekday()) # Monday
last_week_start = week_start - datetime.timedelta(days=7)
this_week_tickets = Ticket.objects.filter(
booking__created_date__gte=week_start
).count()
last_week_tickets = Ticket.objects.filter(
booking__created_date__gte=last_week_start,
booking__created_date__lt=week_start,
).count()
ticket_growth = (
round((this_week_tickets - last_week_tickets) / last_week_tickets * 100, 2)
if last_week_tickets else 0
)
return Response({
'totalRevenue': total_revenue,
'revenueGrowth': revenue_growth,
'activePartners': active_partners,
'pendingPartners': pending_partners,
'liveEvents': live_events,
'eventsToday': events_today,
'ticketSales': ticket_sales,
'ticketGrowth': ticket_growth,
})
class DashboardRevenueView(APIView):
permission_classes = [IsAuthenticated]
def get(self, request):
from ledger.models import RazorpayTransaction
from banking_operations.models import PaymentTransaction
from django.db.models import Sum
from django.utils import timezone
import datetime
today = timezone.now().date()
result = []
for i in range(6, -1, -1):
day = today - datetime.timedelta(days=i)
rev_paise = (
RazorpayTransaction.objects
.filter(status='captured', captured_at__date=day)
.aggregate(total=Sum('amount'))['total'] or 0
)
revenue = rev_paise / 100
payouts = (
PaymentTransaction.objects
.filter(
payment_type='debit',
payment_transaction_status='completed',
payment_transaction_date=day,
)
.aggregate(total=Sum('payment_transaction_amount'))['total'] or 0
)
result.append({
'day': day.strftime('%a'),
'revenue': float(revenue),
'payouts': float(payouts),
})
return Response(result)
class DashboardActivityView(APIView):
permission_classes = [IsAuthenticated]
def get(self, request):
from partner.models import Partner
from events.models import Event
from bookings.models import Booking
from django.utils import timezone
items = []
# --- Partners (last 5 by id desc; no date field — timestamp=None, filtered out later) ---
for p in Partner.objects.order_by('-id')[:5]:
items.append({
'id': f'partner-{p.id}',
'type': 'partner',
'title': f'{p.name} registered',
'description': f'New partner — {getattr(p, "partner_type", "individual")}',
'timestamp': None,
'status': p.kyc_compliance_status,
})
# --- Events (last 5 by created_date desc) ---
for e in Event.objects.order_by('-created_date')[:5]:
display_name = e.title if getattr(e, 'title', None) else getattr(e, 'name', '')
ts = e.created_date
items.append({
'id': f'event-{e.id}',
'type': 'event',
'title': f'{display_name} created',
'description': f'Event status: {e.event_status}',
'timestamp': ts.isoformat() if ts else None,
'status': e.event_status,
})
# --- Bookings (last 5 by id desc) ---
for b in Booking.objects.order_by('-id')[:5]:
ts = b.created_date
items.append({
'id': f'booking-{b.booking_id}',
'type': 'booking',
'title': f'Booking {b.booking_id} placed',
'description': f'{b.quantity} ticket(s) at ₹{b.price}',
'timestamp': ts.isoformat() if ts else None,
'status': 'confirmed',
})
# Filter out items with no timestamp, sort desc, return top 10
dated = [item for item in items if item['timestamp'] is not None]
dated.sort(key=lambda x: x['timestamp'], reverse=True)
return Response(dated[:10])
class DashboardActionsView(APIView):
permission_classes = [IsAuthenticated]
def get(self, request):
from partner.models import Partner
from events.models import Event
from banking_operations.models import PaymentTransaction
from django.db.models import Sum
kyc_count = Partner.objects.filter(kyc_compliance_status='pending').count()
flagged_count = Event.objects.filter(event_status='flagged').count()
pending_payouts = float(
PaymentTransaction.objects
.filter(payment_type='debit', payment_transaction_status='pending')
.aggregate(total=Sum('payment_transaction_amount'))['total'] or 0
)
actions = [
{
'id': 'kyc',
'type': 'kyc',
'count': kyc_count,
'title': 'Partner Approval Queue',
'description': f'{kyc_count} partners awaiting KYC review',
'href': '/partners?filter=pending',
'priority': 'high',
},
{
'id': 'flagged',
'type': 'flagged',
'count': flagged_count,
'title': 'Flagged Events',
'description': f'{flagged_count} events reported for review',
'href': '/events?filter=flagged',
'priority': 'high',
},
{
'id': 'payout',
'type': 'payout',
'count': pending_payouts,
'title': 'Pending Payouts',
'description': f'{pending_payouts:,.0f} ready for release',
'href': '/financials?tab=payouts',
'priority': 'medium',
},
]
return Response(actions)