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:
@@ -1,75 +1,81 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset='utf-8'>
|
||||
<meta name='viewport' content='width=device-width, initial-scale=1'>
|
||||
<title>Eventify</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<!-- jQuery required for Summernote -->
|
||||
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
|
||||
<div class="container-fluid">
|
||||
<a class="navbar-brand" href="{% url 'accounts:dashboard' %}">Eventify</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navmenu">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navmenu">
|
||||
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
|
||||
<div class="container-fluid">
|
||||
<a class="navbar-brand" href="{% url 'accounts:dashboard' %}">Eventify</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navmenu">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navmenu">
|
||||
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
|
||||
|
||||
<!-- Accessible by Admin, Manager, Staff -->
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{% url 'accounts:dashboard' %}">Dashboard</a>
|
||||
</li>
|
||||
<!-- Accessible by Admin, Manager, Staff -->
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{% url 'accounts:dashboard' %}">Dashboard</a>
|
||||
</li>
|
||||
|
||||
{% if user.role == "admin" or user.role == "manager" %}
|
||||
{% if user.role == "admin" or user.role == "manager" %}
|
||||
<!-- Admin + Manager -->
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{% url 'master_data:event_type_list' %}">Categories</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{% if user.role in "admin manager staff" %}
|
||||
{% if user.role in "admin manager staff" %}
|
||||
<!-- Admin + Manager + Staff -->
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{% url 'events:event_list' %}">Events</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{% if user.role == "admin" %}
|
||||
{% if user.role == "admin" %}
|
||||
<!-- Admin only -->
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{% url 'accounts:user_list' %}">Users</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
</ul>
|
||||
<ul class="navbar-nav">
|
||||
{% if user.is_authenticated %}
|
||||
</ul>
|
||||
<ul class="navbar-nav">
|
||||
{% if user.is_authenticated %}
|
||||
<li class="nav-item"><a class="nav-link" href="#">
|
||||
{% if user.first_name and user.last_name %}
|
||||
{% if user.first_name and user.last_name %}
|
||||
{{ user.first_name }} {{ user.last_name }}
|
||||
{% elif user.username %}
|
||||
{% elif user.username %}
|
||||
{{ user.username }}
|
||||
{% else %}
|
||||
{% else %}
|
||||
{{ user.email }}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</a></li>
|
||||
<li class="nav-item"><a class="nav-link text-danger" href="{% url 'accounts:logout' %}">Logout</a></li>
|
||||
{% else %}
|
||||
<li class="nav-item"><a class="nav-link text-danger" href="{% url 'accounts:logout' %}">Logout</a>
|
||||
</li>
|
||||
{% else %}
|
||||
<li class="nav-item"><a class="nav-link" href="{% url 'accounts:login' %}">Login</a></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<div class="container mt-4">
|
||||
{% if messages %}
|
||||
</nav>
|
||||
<div class="container mt-4">
|
||||
{% if messages %}
|
||||
{% for message in messages %}
|
||||
<div class="alert alert-{{ message.tags }}">{{ message }}</div>
|
||||
<div class="alert alert-{{ message.tags }}">{{ message }}</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% block content %}{% endblock %}
|
||||
</div>
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||
{% endif %}
|
||||
{% block content %}{% endblock %}
|
||||
</div>
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
</html>
|
||||
@@ -1,49 +1,50 @@
|
||||
{% extends 'base.html' %}
|
||||
{% block content %}
|
||||
<div class="container mt-4">
|
||||
<h3>{% if object %}Edit{% else %}Add{% endif %} Event</h3>
|
||||
<div class="container mt-4">
|
||||
<h3>{% if object %}Edit{% else %}Add{% endif %} Event</h3>
|
||||
|
||||
<form method="post" novalidate>
|
||||
{% csrf_token %}
|
||||
<form method="post" novalidate>
|
||||
{% csrf_token %}
|
||||
{{ form.media }}
|
||||
|
||||
{% for field in form %}
|
||||
<div class="mb-3">
|
||||
{{ field.label_tag }}
|
||||
{{ field }}
|
||||
{% for error in field.errors %}
|
||||
<div class="text-danger">{{ error }}</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% for field in form %}
|
||||
<div class="mb-3">
|
||||
{{ field.label_tag }}
|
||||
{{ field }}
|
||||
{% for error in field.errors %}
|
||||
<div class="text-danger">{{ error }}</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
<button class="btn btn-primary">Save</button>
|
||||
<a class="btn btn-secondary" href="{% url 'events:event_list' %}">Cancel</a>
|
||||
</form>
|
||||
</div>
|
||||
<button class="btn btn-primary">Save</button>
|
||||
<a class="btn btn-secondary" href="{% url 'events:event_list' %}">Cancel</a>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const allYearEventCheckbox = document.getElementById('id_all_year_event');
|
||||
const startDateField = document.getElementById('id_start_date');
|
||||
const endDateField = document.getElementById('id_end_date');
|
||||
const startTimeField = document.getElementById('id_start_time');
|
||||
const endTimeField = document.getElementById('id_end_time');
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
const allYearEventCheckbox = document.getElementById('id_all_year_event');
|
||||
const startDateField = document.getElementById('id_start_date');
|
||||
const endDateField = document.getElementById('id_end_date');
|
||||
const startTimeField = document.getElementById('id_start_time');
|
||||
const endTimeField = document.getElementById('id_end_time');
|
||||
|
||||
function toggleDateTimeFields() {
|
||||
const isDisabled = allYearEventCheckbox.checked;
|
||||
startDateField.disabled = isDisabled;
|
||||
endDateField.disabled = isDisabled;
|
||||
startTimeField.disabled = isDisabled;
|
||||
endTimeField.disabled = isDisabled;
|
||||
}
|
||||
function toggleDateTimeFields() {
|
||||
const isDisabled = allYearEventCheckbox.checked;
|
||||
startDateField.disabled = isDisabled;
|
||||
endDateField.disabled = isDisabled;
|
||||
startTimeField.disabled = isDisabled;
|
||||
endTimeField.disabled = isDisabled;
|
||||
}
|
||||
|
||||
// Set initial state
|
||||
toggleDateTimeFields();
|
||||
// Set initial state
|
||||
toggleDateTimeFields();
|
||||
|
||||
// Listen for checkbox changes
|
||||
if (allYearEventCheckbox) {
|
||||
allYearEventCheckbox.addEventListener('change', toggleDateTimeFields);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
// Listen for checkbox changes
|
||||
if (allYearEventCheckbox) {
|
||||
allYearEventCheckbox.addEventListener('change', toggleDateTimeFields);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -1,9 +1,20 @@
|
||||
{% extends 'base.html' %}
|
||||
{% block content %}
|
||||
<div class="d-flex justify-content-between mb-3">
|
||||
<h3>Events</h3>
|
||||
<a class="btn btn-success" href="{% url 'events:event_add' %}">Add Event</a>
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-6">
|
||||
<h3>Events</h3>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<form method="get" action="." class="d-flex">
|
||||
<input class="form-control me-2" type="search" name="q" placeholder="Search events..." aria-label="Search" value="{{ request.GET.q }}">
|
||||
<button class="btn btn-outline-success" type="submit">Search</button>
|
||||
</form>
|
||||
</div>
|
||||
<div class="col-md-2 text-end">
|
||||
<a class="btn btn-success" href="{% url 'events:event_add' %}">Add Event</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
@@ -44,4 +55,35 @@
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<!-- Pagination -->
|
||||
{% if is_paginated %}
|
||||
<nav aria-label="Page navigation">
|
||||
<ul class="pagination">
|
||||
{% if page_obj.has_previous %}
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="?page=1{% if request.GET.q %}&q={{ request.GET.q }}{% endif %}">« First</a>
|
||||
</li>
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="?page={{ page_obj.previous_page_number }}{% if request.GET.q %}&q={{ request.GET.q }}{% endif %}">Previous</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
<li class="page-item disabled">
|
||||
<span class="page-link">
|
||||
Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}
|
||||
</span>
|
||||
</li>
|
||||
|
||||
{% if page_obj.has_next %}
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="?page={{ page_obj.next_page_number }}{% if request.GET.q %}&q={{ request.GET.q }}{% endif %}">Next</a>
|
||||
</li>
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="?page={{ page_obj.paginator.num_pages }}{% if request.GET.q %}&q={{ request.GET.q }}{% endif %}">Last »</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</nav>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
Reference in New Issue
Block a user