first commit
This commit is contained in:
commit
34ea15a54d
246 changed files with 18660 additions and 0 deletions
586
docs/API_SPEC.md
Normal file
586
docs/API_SPEC.md
Normal file
|
|
@ -0,0 +1,586 @@
|
|||
# API Specification — Desi Clans
|
||||
|
||||
**Version:** 1.0
|
||||
**Base URL:** `https://api.desiclans.com` (prod) / `http://localhost:8000` (dev)
|
||||
**Format:** JSON unless noted
|
||||
**Auth:** `Authorization: Bearer <access_token>` for protected routes
|
||||
|
||||
---
|
||||
|
||||
## 1. Conventions
|
||||
|
||||
| Item | Rule |
|
||||
|------|------|
|
||||
| Errors | `{ "detail": "message" }` or validation array from FastAPI |
|
||||
| Dates | ISO 8601 UTC in JSON |
|
||||
| Pagination | `?limit=20&offset=0` where added later; v1 lists unbounded with sane caps in service |
|
||||
| IDs | Integer primary keys in v1 |
|
||||
| Slugs | Lowercase, hyphenated, unique per edition |
|
||||
|
||||
---
|
||||
|
||||
## 2. Platform — `/api`
|
||||
|
||||
### `GET /health`
|
||||
|
||||
Liveness and PostgreSQL connectivity check.
|
||||
|
||||
**Response 200** — database reachable
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "ok",
|
||||
"service": "desi-clans-api",
|
||||
"database": "connected"
|
||||
}
|
||||
```
|
||||
|
||||
**Response 503** — database unreachable
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "degraded",
|
||||
"service": "desi-clans-api",
|
||||
"database": "unavailable"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `GET /events`
|
||||
|
||||
List catalog events (published by default).
|
||||
|
||||
**Query**
|
||||
|
||||
| Param | Type | Default | Description |
|
||||
|-------|------|---------|-------------|
|
||||
| `category` | `holi` \| `garba` \| `dandiya` \| `bhajan_clubbing` \| `special` | — | Filter by category |
|
||||
| `status` | `draft` \| `published` \| `cancelled` | `published` | Filter by status |
|
||||
| `upcoming` | boolean | `true` | If true, only `starts_at >= now` (UTC) |
|
||||
|
||||
**Response 200** — `EventSummary[]`
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"id": 1,
|
||||
"slug": "rang-barse-palace-grounds-2026",
|
||||
"title": "Rang Barse — Palace Grounds",
|
||||
"category": "holi",
|
||||
"status": "published",
|
||||
"starts_at": "2026-03-09T10:00:00Z",
|
||||
"ends_at": "2026-03-09T18:00:00Z",
|
||||
"is_featured": true,
|
||||
"cover_image_url": "https://…",
|
||||
"cover_image_alt": "…",
|
||||
"venue": {
|
||||
"id": 1,
|
||||
"slug": "palace-grounds",
|
||||
"name": "Palace Grounds",
|
||||
"area": "Vasanth Nagar",
|
||||
"city": "Bengaluru",
|
||||
"address": "…",
|
||||
"map_url": "https://…",
|
||||
"description": "…"
|
||||
}
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `GET /events/{slug}`
|
||||
|
||||
**Response 200** — `EventDetail` (includes `description`, `meta_description`, `media[]`, `ticket_types[]`)
|
||||
|
||||
**Response 404** — unknown slug
|
||||
|
||||
---
|
||||
|
||||
### `GET /events/{id}/tickets`
|
||||
|
||||
List purchasable ticket types for a **published** event (numeric event id).
|
||||
|
||||
**Response 200** — `EventTicketTypePublic[]`
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"id": 1,
|
||||
"event_id": 1,
|
||||
"name": "General admission",
|
||||
"description": "Full-day access",
|
||||
"price_paise": 149900,
|
||||
"status": "active",
|
||||
"sort_order": 0,
|
||||
"remaining_quantity": 42
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
**Response 404** — unknown or unpublished event
|
||||
|
||||
---
|
||||
|
||||
### `GET /venues`
|
||||
|
||||
**Response 200** — `VenuePublic[]`
|
||||
|
||||
---
|
||||
|
||||
### `GET /venues/{id}`
|
||||
|
||||
**Response 200** — `VenuePublic`
|
||||
**Response 404** — unknown id
|
||||
|
||||
---
|
||||
|
||||
## 3. Public — `/api/public` (legacy, planned)
|
||||
|
||||
Legacy public routes will mount under this prefix when business routers are enabled.
|
||||
|
||||
---
|
||||
|
||||
### `GET /config` (not yet mounted)
|
||||
|
||||
Site-wide config: platform links, feature flags (future).
|
||||
|
||||
**Response 200** — `SiteConfigPublic`
|
||||
|
||||
```json
|
||||
{
|
||||
"site_name": "Desi Clans",
|
||||
"city": "Bengaluru",
|
||||
"links": [
|
||||
{ "id": 1, "kind": "instagram", "label": "Instagram", "url": "https://...", "sort_order": 0 }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `GET /editions`
|
||||
|
||||
List published editions.
|
||||
|
||||
**Query**
|
||||
|
||||
| Param | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `event_type` | string | Optional enum filter |
|
||||
| `featured` | bool | Optional |
|
||||
|
||||
**Response 200** — `EventEditionPublic[]`
|
||||
|
||||
---
|
||||
|
||||
### `GET /editions/{slug}`
|
||||
|
||||
Single published edition.
|
||||
|
||||
**Response 200** — `EventEditionPublic`
|
||||
**Response 404** — not found or unpublished
|
||||
|
||||
**`EventEditionPublic` (shape)**
|
||||
|
||||
```json
|
||||
{
|
||||
"id": 1,
|
||||
"title": "Holi at Palace Grounds",
|
||||
"slug": "holi-palace-grounds-2026",
|
||||
"event_type": "holi",
|
||||
"city": "Bengaluru",
|
||||
"venue_name": "Palace Grounds",
|
||||
"event_date": "2026-03-10T10:00:00Z",
|
||||
"description": "...",
|
||||
"is_featured": true,
|
||||
"cover_image_url": "/media/...",
|
||||
"cover_thumb_url": "/media/.../thumb",
|
||||
"tickets": {
|
||||
"bookmyshow": "https://...",
|
||||
"district": null,
|
||||
"instagram": null
|
||||
},
|
||||
"meta_description": "...",
|
||||
"media": [
|
||||
{ "id": 1, "url": "...", "thumb_url": "...", "caption": null, "sort_order": 0 }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
*Future:* `ticketing_mode`, `tiers[]` when native ticketing is enabled.
|
||||
|
||||
---
|
||||
|
||||
### `GET /go`
|
||||
|
||||
Outbound redirect with click tracking.
|
||||
|
||||
**Query**
|
||||
|
||||
| Param | Required | Description |
|
||||
|-------|----------|-------------|
|
||||
| `kind` | yes | e.g. `bookmyshow`, `district`, `instagram` |
|
||||
| `edition_id` | no | Edition context |
|
||||
|
||||
**Response 302** → destination URL
|
||||
|
||||
---
|
||||
|
||||
### `POST /subscribe`
|
||||
|
||||
**Body** — `SubscribeRequest`
|
||||
|
||||
```json
|
||||
{ "email": "user@example.com", "neighborhood": "HSR Layout" }
|
||||
```
|
||||
|
||||
**Response 201** — created
|
||||
**Response 409** — email already subscribed
|
||||
|
||||
---
|
||||
|
||||
### `GET /editions/{slug}/calendar.ics`
|
||||
|
||||
**Response 200** — `text/calendar` body
|
||||
|
||||
---
|
||||
|
||||
## 3. Share (crawler HTML)
|
||||
|
||||
### `GET /share/events/{slug}`
|
||||
|
||||
Also available under public router duplicate path per deployment.
|
||||
|
||||
**Response 200** — HTML with Open Graph tags (no auth).
|
||||
|
||||
---
|
||||
|
||||
## 4. Auth — `/api/auth`
|
||||
|
||||
Attendee accounts (bcrypt-hashed passwords, JWT bearer tokens). Admin auth remains separate under `/api/admin` when legacy routers are mounted.
|
||||
|
||||
| Method | Path | Auth | Description |
|
||||
|--------|------|------|-------------|
|
||||
| POST | `/register` | — | Create account |
|
||||
| POST | `/login` | — | Issue access token |
|
||||
| GET | `/me` | Bearer | Current user profile |
|
||||
|
||||
### `POST /register`
|
||||
|
||||
**Body** — `RegisterRequest`
|
||||
|
||||
```json
|
||||
{
|
||||
"email": "guest@example.com",
|
||||
"password": "minimum-8-chars",
|
||||
"full_name": "Optional Name"
|
||||
}
|
||||
```
|
||||
|
||||
**Response 201** — `Token`
|
||||
|
||||
**Response 409** — email already registered
|
||||
**Response 422** — validation error (password < 8 characters, invalid email)
|
||||
|
||||
---
|
||||
|
||||
### `POST /login`
|
||||
|
||||
**Body** — `LoginRequest`
|
||||
|
||||
```json
|
||||
{ "email": "guest@example.com", "password": "********" }
|
||||
```
|
||||
|
||||
**Response 200** — `Token`
|
||||
|
||||
```json
|
||||
{ "access_token": "...", "token_type": "bearer" }
|
||||
```
|
||||
|
||||
**Response 401** — invalid credentials
|
||||
|
||||
JWT claims: `sub` (user id), `exp`, `type: "user"`. Lifetime from `ACCESS_TOKEN_EXPIRE_MINUTES` (server env).
|
||||
|
||||
---
|
||||
|
||||
### `GET /me`
|
||||
|
||||
**Headers** — `Authorization: Bearer <access_token>`
|
||||
|
||||
**Response 200** — `UserPublic`
|
||||
|
||||
```json
|
||||
{
|
||||
"id": 1,
|
||||
"email": "guest@example.com",
|
||||
"full_name": "Optional Name",
|
||||
"is_active": true,
|
||||
"created_at": "2026-04-04T12:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
**Response 401** — missing, invalid, or expired token
|
||||
|
||||
Protected routes use the `CurrentUser` dependency (`get_current_user` in `app/core/deps.py`).
|
||||
|
||||
---
|
||||
|
||||
## 5. Orders & tickets — `/api/orders`, `/api/tickets`
|
||||
|
||||
Requires attendee Bearer token. Prices are **always** computed server-side from `ticket_types.price_paise`; the client sends only `ticket_type_id` and `quantity`.
|
||||
|
||||
Checkout reserves inventory while `status: pending`. Tickets are issued only after **verified** payment (checkout signature or webhook). Never trust client-side “payment success” flags.
|
||||
|
||||
**Provider:** Razorpay in production (`PAYMENT_PROVIDER=razorpay`). Local dev uses `mock` (no secrets in React).
|
||||
|
||||
Optional header: `Idempotency-Key` on `POST /orders` (unique per checkout attempt).
|
||||
|
||||
### `POST /orders`
|
||||
|
||||
**Body** — `OrderCreate`
|
||||
|
||||
```json
|
||||
{
|
||||
"event_id": 1,
|
||||
"items": [{ "ticket_type_id": 2, "quantity": 2 }]
|
||||
}
|
||||
```
|
||||
|
||||
**Response 201** — `OrderCheckoutResponse` (`status: pending`, `payment` block with public `key_id`, `provider_order_id`, `amount_paise`, `expires_at`)
|
||||
|
||||
**Response 400** — invalid event, wrong event/tier pairing, empty cart
|
||||
**Response 401** — not authenticated
|
||||
**Response 409** — insufficient inventory
|
||||
|
||||
---
|
||||
|
||||
### `POST /orders/{id}/payment/verify`
|
||||
|
||||
Server-side verification of Razorpay (or mock) checkout signature. Marks order `paid` and issues tickets.
|
||||
|
||||
**Body** — `PaymentVerifyRequest`
|
||||
|
||||
```json
|
||||
{
|
||||
"provider_order_id": "order_…",
|
||||
"provider_payment_id": "pay_…",
|
||||
"signature": "…"
|
||||
}
|
||||
```
|
||||
|
||||
**Response 200** — `OrderPublic` (`status: paid`)
|
||||
**Response 400** — invalid signature or amount mismatch
|
||||
**Response 409** — order not pending
|
||||
|
||||
---
|
||||
|
||||
### `POST /orders/{id}/cancel`
|
||||
|
||||
User abandoned checkout. Releases reserved inventory. **Pending orders only.**
|
||||
|
||||
**Response 200** — `OrderPublic` (`status: cancelled`)
|
||||
|
||||
---
|
||||
|
||||
### `POST /orders/{id}/payment/mock-complete`
|
||||
|
||||
Development only (`ENVIRONMENT=development`, `PAYMENT_PROVIDER=mock`). Server completes mock payment without client-held secrets.
|
||||
|
||||
---
|
||||
|
||||
### Webhooks — `/api/webhooks`
|
||||
|
||||
| Method | Path | Auth | Description |
|
||||
|--------|------|------|-------------|
|
||||
| POST | `/razorpay` | `X-Razorpay-Signature` | Razorpay events (`payment.captured`, `payment.failed`) |
|
||||
| POST | `/mock` | `X-Mock-Signature` | HMAC body signature (tests / local) |
|
||||
|
||||
Webhook processing is **idempotent** (`payment_webhook_events.provider_event_id` unique). Duplicate deliveries return `{ "status": "duplicate_ignored" }`.
|
||||
|
||||
**Env (server only):** `RAZORPAY_KEY_SECRET`, `RAZORPAY_WEBHOOK_SECRET` — never expose to the frontend. `RAZORPAY_KEY_ID` is public and returned in checkout payloads for Razorpay.js.
|
||||
|
||||
Pending orders expire after `ORDER_PAYMENT_TIMEOUT_MINUTES` (inventory released, `status: failed`).
|
||||
|
||||
---
|
||||
|
||||
### `GET /orders/{id}`
|
||||
|
||||
**Response 200** — `OrderPublic` (owner only)
|
||||
**Response 404** — not found or not your order
|
||||
|
||||
---
|
||||
|
||||
### `GET /tickets/my`
|
||||
|
||||
**Response 200** — `TicketPublic[]` for the current user
|
||||
|
||||
---
|
||||
|
||||
### `GET /tickets/{id}`
|
||||
|
||||
Authenticated owner only. Returns full digital pass payload including server-generated QR PNG (base64).
|
||||
|
||||
**Response 200** — `TicketDetailPublic` (event, venue, attendee, ticket type, `code`, `qr_payload`, `qr_image_base64`, `qr_token`)
|
||||
**Response 404** — not found or not your ticket
|
||||
|
||||
---
|
||||
|
||||
### `POST /tickets/verify`
|
||||
|
||||
Gate / check-in verification (server-side). Marks ticket `used` atomically — **QR reuse rejected**.
|
||||
|
||||
**Headers:** `X-Check-In-Key` (required in production via `TICKET_VERIFY_API_KEY`)
|
||||
|
||||
**Body** — `TicketVerifyRequest`
|
||||
|
||||
```json
|
||||
{ "qr_token": "…" }
|
||||
```
|
||||
|
||||
**Response 200** — `TicketVerifyResponse` (`valid`, `status`, `message`, `ticket_code`, `event_title`)
|
||||
|
||||
---
|
||||
|
||||
**`TicketPublic.status` / `TicketDetailPublic.status`:** `valid` | `used` | `cancelled`
|
||||
|
||||
---
|
||||
|
||||
## 6. Admin — `/api/admin`
|
||||
|
||||
Requires admin JWT. `editor` vs `super_admin` enforced per endpoint.
|
||||
|
||||
### `GET /me`
|
||||
|
||||
Current admin profile.
|
||||
|
||||
**Response 200** — `AdminUserPublic`
|
||||
|
||||
---
|
||||
|
||||
### Platform links
|
||||
|
||||
| Method | Path | Role | Description |
|
||||
|--------|------|------|-------------|
|
||||
| GET | `/links` | editor+ | List active links |
|
||||
| POST | `/links` | super_admin | Create |
|
||||
| PATCH | `/links/{link_id}` | super_admin | Update |
|
||||
| DELETE | `/links/{link_id}` | super_admin | Delete |
|
||||
|
||||
---
|
||||
|
||||
### Editions
|
||||
|
||||
| Method | Path | Role | Description |
|
||||
|--------|------|------|-------------|
|
||||
| GET | `/editions` | editor+ | All editions (incl. draft) |
|
||||
| POST | `/editions` | editor+ | Create |
|
||||
| PATCH | `/editions/{edition_id}` | editor+ | Update fields, publish flags |
|
||||
| POST | `/editions/{edition_id}/cover` | editor+ | Multipart cover upload |
|
||||
| POST | `/editions/{edition_id}/media` | editor+ | Gallery upload |
|
||||
| PATCH | `/media/{media_id}` | editor+ | Caption, publish flag |
|
||||
| POST | `/editions/{edition_id}/media/reorder` | editor+ | Body: ordered media ids |
|
||||
| DELETE | `/media/{media_id}` | editor+ | Remove asset |
|
||||
|
||||
**`EventEditionAdmin`** extends public fields with `is_published`, `sort_order`, raw URLs, etc.
|
||||
|
||||
---
|
||||
|
||||
### Users & subscribers
|
||||
|
||||
| Method | Path | Role | Description |
|
||||
|--------|------|------|-------------|
|
||||
| GET | `/users` | super_admin | List admins |
|
||||
| POST | `/users` | super_admin | Create admin |
|
||||
| GET | `/subscribers` | editor+ | Newsletter list |
|
||||
|
||||
---
|
||||
|
||||
## 7. Analytics — `/api/admin/analytics`
|
||||
|
||||
### `GET /clicks/summary`
|
||||
|
||||
**Response 200** — `ClickSummaryItem[]`
|
||||
|
||||
```json
|
||||
[{ "link_kind": "bookmyshow", "count": 42 }]
|
||||
```
|
||||
|
||||
### `GET /clicks/recent`
|
||||
|
||||
**Response 200** — `OutboundClickPublic[]`
|
||||
|
||||
---
|
||||
|
||||
## 8. Future — Check-in & enhancements
|
||||
|
||||
### Catalog — `/api/public/editions/{slug}/tiers`
|
||||
|
||||
**Response** — active tiers with `remaining_quantity` (or sold out flag).
|
||||
|
||||
### Orders (extensions)
|
||||
|
||||
| Method | Path | Auth | Description |
|
||||
|--------|------|------|-------------|
|
||||
| POST | `/` | User | Create pending order |
|
||||
| GET | `/` | User | My orders |
|
||||
| GET | `/{order_id}` | User | Order detail + tickets |
|
||||
| POST | `/webhooks/payment` | HMAC | Provider callback (no JWT) |
|
||||
|
||||
### Check-in — `/api/check-in`
|
||||
|
||||
| Method | Path | Auth | Description |
|
||||
|--------|------|------|-------------|
|
||||
| POST | `/scan` | Staff | Body: `{ "qr": "..." }` → ticket status |
|
||||
|
||||
---
|
||||
|
||||
## 8. Enums
|
||||
|
||||
### `EventType`
|
||||
|
||||
`holi` | `garba` | `dandiya` | `bhajan_clubbing` | `special`
|
||||
|
||||
### `PlatformKind`
|
||||
|
||||
`bookmyshow` | `district` | `instagram` | `highape` | `youtube` | `whatsapp` | `website` | `other`
|
||||
|
||||
### `AdminRole`
|
||||
|
||||
`super_admin` | `editor`
|
||||
|
||||
### `OrderStatus`
|
||||
|
||||
`pending` | `paid` | `cancelled` | `failed`
|
||||
|
||||
### `TicketStatus` (issued ticket)
|
||||
|
||||
`valid` | `used` | `cancelled`
|
||||
|
||||
---
|
||||
|
||||
## 9. CORS
|
||||
|
||||
Allowed origins from `CORS_ORIGINS` env (comma-separated). Credentials enabled for admin SPA origin.
|
||||
|
||||
---
|
||||
|
||||
## 10. Static media
|
||||
|
||||
- **Local dev:** `GET /media/{path}`
|
||||
- **Prod:** CDN URL built from stored key in `file_path`
|
||||
|
||||
---
|
||||
|
||||
## 11. Versioning
|
||||
|
||||
Breaking changes require `/api/v2` prefix or negotiated migration. v1 remains unversioned path until scale demands it.
|
||||
|
||||
---
|
||||
|
||||
## 12. Related
|
||||
|
||||
- [DATABASE.md](./DATABASE.md)
|
||||
- [ARCHITECTURE.md](./ARCHITECTURE.md)
|
||||
275
docs/ARCHITECTURE.md
Normal file
275
docs/ARCHITECTURE.md
Normal file
|
|
@ -0,0 +1,275 @@
|
|||
# Architecture — Desi Clans
|
||||
|
||||
**Version:** 1.0
|
||||
**Last updated:** 2026-04-04
|
||||
|
||||
---
|
||||
|
||||
## 1. System context
|
||||
|
||||
```
|
||||
┌─────────────┐ HTTPS ┌──────────────┐ SQL ┌────────────┐
|
||||
│ Browser │ ──────────────► │ FastAPI API │ ◄──────────► │ PostgreSQL │
|
||||
│ (React) │ │ + static │ └────────────┘
|
||||
└─────────────┘ │ media │
|
||||
│ └──────┬───────┘
|
||||
│ │
|
||||
│ optional ▼
|
||||
└──────────────────────► S3 / CDN (prod media)
|
||||
```
|
||||
|
||||
- **Monorepo:** `frontend/`, `backend/`, `docs/`.
|
||||
- **Deployment unit:** API container + static frontend (CDN or same origin) + managed Postgres.
|
||||
- **No microservices in v1.** One API process, clear router boundaries for future split.
|
||||
|
||||
---
|
||||
|
||||
## 2. Architectural principles
|
||||
|
||||
1. **API-first content** — Public UI never hardcodes event copy or images.
|
||||
2. **Thin client** — Business rules (inventory, check-in) live in the backend.
|
||||
3. **Explicit phases** — Ship discovery + admin before native payments if needed.
|
||||
4. **Boring tech** — Stack locked to PRD; no extra frameworks without ADR.
|
||||
5. **Security defaults** — JWT for auth, parameterized SQL via SQLAlchemy, validated Pydantic I/O.
|
||||
|
||||
---
|
||||
|
||||
## 3. Repository layout (target)
|
||||
|
||||
```
|
||||
DESI-CLANS/
|
||||
├── frontend/
|
||||
│ ├── public/
|
||||
│ ├── src/
|
||||
│ │ ├── api/ # Axios clients, types mirroring API_SPEC
|
||||
│ │ ├── components/ # UI primitives + composites
|
||||
│ │ ├── hooks/
|
||||
│ │ ├── layouts/
|
||||
│ │ ├── pages/ # Route-level views
|
||||
│ │ ├── styles/ # Tailwind entry, tokens
|
||||
│ │ ├── utils/ # seo, dates, formatters
|
||||
│ │ ├── animations/ # GSAP contexts, ScrollTrigger setup
|
||||
│ │ ├── App.jsx
|
||||
│ │ └── main.jsx
|
||||
│ ├── index.html
|
||||
│ ├── vite.config.js
|
||||
│ ├── tailwind.config.js
|
||||
│ └── package.json
|
||||
├── backend/
|
||||
│ ├── alembic/
|
||||
│ ├── app/
|
||||
│ │ ├── routers/ # public, auth, admin, share, analytics, orders (future)
|
||||
│ │ ├── services/ # domain logic
|
||||
│ │ ├── models.py
|
||||
│ │ ├── schemas.py
|
||||
│ │ ├── auth.py
|
||||
│ │ ├── config.py
|
||||
│ │ ├── database.py
|
||||
│ │ └── main.py
|
||||
│ ├── scripts/ # seed, migrate helpers
|
||||
│ ├── tests/
|
||||
│ ├── Dockerfile
|
||||
│ └── requirements.txt
|
||||
├── docs/
|
||||
└── README.md
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Backend layers
|
||||
|
||||
| Layer | Responsibility |
|
||||
|-------|----------------|
|
||||
| **Routers** | HTTP, status codes, dependency injection, no business logic |
|
||||
| **Services** | Transactions, orchestration (editions, media, tickets, email) |
|
||||
| **Models** | SQLAlchemy ORM, enums |
|
||||
| **Schemas** | Pydantic request/response; never expose hashed passwords |
|
||||
|
||||
**Cross-cutting**
|
||||
|
||||
- `config.py` — settings from environment (see `.env.example` when added).
|
||||
- `auth.py` — JWT create/verify, `get_current_admin`, future `get_current_user`.
|
||||
- Migrations — Alembic only; avoid `create_all` in production (dev convenience may exist temporarily).
|
||||
|
||||
---
|
||||
|
||||
## 5. Frontend layers
|
||||
|
||||
| Layer | Responsibility |
|
||||
|-------|----------------|
|
||||
| **Pages** | Data loading per route, SEO hooks, layout composition |
|
||||
| **Components** | Presentational; accept props, no direct Axios in leaf components |
|
||||
| **api/** | Base Axios instance, interceptors for admin token |
|
||||
| **animations/** | GSAP timelines registered once; respect `prefers-reduced-motion` |
|
||||
|
||||
**Routing (React Router)**
|
||||
|
||||
- Public: `/`, `/events`, `/events/:slug`, `/subscribe` (optional dedicated page)
|
||||
- Admin: `/admin/login`, `/admin/*` (protected)
|
||||
- Account (phase): `/account`, `/account/tickets/:id`
|
||||
- Check-in (phase): `/admin/check-in` or staff-only route
|
||||
|
||||
**SEO**
|
||||
|
||||
- Vite SPA: use `react-helmet-async` **only if** needed; prefer minimal approach:
|
||||
- Default meta in `index.html`
|
||||
- Per-route `document.title` + meta updates in page `useEffect`
|
||||
- Backend `/share/events/{slug}` HTML for crawlers (already planned in API)
|
||||
|
||||
---
|
||||
|
||||
## 6. API boundaries
|
||||
|
||||
| Namespace | Auth | Purpose |
|
||||
|-----------|------|---------|
|
||||
| `/api/public/*` | None | Read editions, config, subscribe, outbound redirect, calendar |
|
||||
| `/api/auth/*` | Credentials | Admin login (attendee login added with ticketing) |
|
||||
| `/api/admin/*` | Admin JWT | CRUD editions, media, links, users, subscribers |
|
||||
| `/api/admin/analytics/*` | Admin JWT | Click summaries |
|
||||
| `/share/events/{slug}` | None | OG HTML for social crawlers |
|
||||
| `/api/orders/*` (future) | User JWT | Checkout, my tickets |
|
||||
| `/api/check-in/*` (future) | Staff JWT | Validate QR, mark checked in |
|
||||
|
||||
Public reads are cache-friendly (CDN `Cache-Control` on GET editions list with short TTL).
|
||||
|
||||
---
|
||||
|
||||
## 7. Media pipeline
|
||||
|
||||
1. Admin uploads via multipart → service stores file.
|
||||
2. **Local:** `upload_dir` served at `/media/...`
|
||||
3. **Production:** S3 bucket + public or signed URLs; store `file_path` key in DB.
|
||||
4. Optional thumbnails generated on upload (service layer); expose `thumb_url` in API.
|
||||
|
||||
---
|
||||
|
||||
## 8. Ticketing architecture (target)
|
||||
|
||||
```
|
||||
EventEdition ──< TicketTier
|
||||
│
|
||||
Order ──────────────┼──< OrderLine (tier, qty, unit_price)
|
||||
│
|
||||
└──< Ticket (code, qr_secret, status, checked_in_at)
|
||||
```
|
||||
|
||||
**Checkout flow (native)**
|
||||
|
||||
1. Client POST order intent with tier + quantity.
|
||||
2. Server validates window, inventory; creates `pending` order; returns payment session (phase B).
|
||||
3. Webhook confirms payment → status `paid` → issue tickets with unique codes.
|
||||
4. Email service sends QR (PNG or link to ticket page).
|
||||
|
||||
**QR validation**
|
||||
|
||||
- Payload: `ticket_id` + HMAC signature (server secret).
|
||||
- Scan endpoint: verify signature, edition match, status `issued` → `checked_in`.
|
||||
|
||||
**Outbound mode (per edition flag)**
|
||||
|
||||
- `ticketing_mode: outbound | native`
|
||||
- Outbound uses existing `bookmyshow_url`, `district_url`, etc., via `/api/public/go` click tracking.
|
||||
|
||||
---
|
||||
|
||||
## 9. Security
|
||||
|
||||
- Passwords: bcrypt via passlib.
|
||||
- JWT: HS256, `ACCESS_TOKEN_EXPIRE_MINUTES` configurable.
|
||||
- CORS: explicit origin list from env.
|
||||
- Rate limit: add at reverse proxy (nginx/Cloudflare) for public POST subscribe and auth login.
|
||||
- Admin actions audited via structured logs (user id, action, entity id).
|
||||
|
||||
---
|
||||
|
||||
## 10. Performance requirements
|
||||
|
||||
| Area | Target |
|
||||
|------|--------|
|
||||
| Public GET editions | p95 < 300ms @ warm DB |
|
||||
| Image delivery | WebP/AVIF where supported; width variants |
|
||||
| Frontend bundle | Route-based code splitting for admin |
|
||||
| DB | Indexes on `slug`, `event_type`, `is_published`, `event_date` |
|
||||
| Animation | GSAP only on hero/key sections; disable heavy ScrollTrigger on mobile low power |
|
||||
|
||||
---
|
||||
|
||||
## 11. Observability
|
||||
|
||||
- Health: `GET /api/public/health`
|
||||
- Structured JSON logs (request id, path, status, duration)
|
||||
- Production: disable OpenAPI UI (`openapi_url = None` when `environment=production`)
|
||||
|
||||
---
|
||||
|
||||
## 12. Coding conventions
|
||||
|
||||
### Python (backend)
|
||||
|
||||
- Python 3.11+
|
||||
- Type hints on public functions
|
||||
- `ruff` or `flake8` + `black` (pick one formatter in CI)
|
||||
- Router functions: max ~30 lines; delegate to services
|
||||
- Enums in `models.py` for DB enums; mirror in Pydantic where needed
|
||||
|
||||
### JavaScript (frontend)
|
||||
|
||||
- React 18 functional components only
|
||||
- Named exports for components; default export for pages optional
|
||||
- Tailwind for styling; no CSS-in-JS library
|
||||
- Axios instance with `baseURL` from `import.meta.env.VITE_API_URL`
|
||||
- Env vars prefixed `VITE_` only for client-safe values
|
||||
|
||||
### Git
|
||||
|
||||
- Conventional commits encouraged: `feat:`, `fix:`, `docs:`
|
||||
- One Alembic revision per schema change
|
||||
|
||||
---
|
||||
|
||||
## 13. Implementation phases
|
||||
|
||||
### Phase 0 — Constitution (current)
|
||||
|
||||
Documentation only; no new application features.
|
||||
|
||||
### Phase 1 — Public experience + admin content
|
||||
|
||||
- Vite + React + Tailwind + Router
|
||||
- Wire public API: home, list, detail, subscribe, outbound go links
|
||||
- Admin: login, edition CRUD, media upload
|
||||
- Share previews + basic SEO
|
||||
- **Aligns with partial backend already in repo**
|
||||
|
||||
### Phase 2 — Polish & performance
|
||||
|
||||
- GSAP hero + scroll sections with reduced-motion guard
|
||||
- Image lazy loading, skeleton states
|
||||
- Accessibility pass (focus, contrast, aria labels)
|
||||
|
||||
### Phase 3 — Native ticketing
|
||||
|
||||
- DB: tiers, orders, tickets
|
||||
- Attendee auth
|
||||
- Checkout + payment webhook
|
||||
- Ticket page + QR generation
|
||||
- Staff check-in UI
|
||||
|
||||
### Phase 4 — Hardening
|
||||
|
||||
- E2E smoke tests, load test on read paths
|
||||
- CDN, backup strategy, runbooks
|
||||
|
||||
---
|
||||
|
||||
## 14. ADR policy
|
||||
|
||||
Significant deviations (new dependency, new service, auth model change) require a short `docs/adr/NNN-title.md` before implementation.
|
||||
|
||||
---
|
||||
|
||||
## 15. Related documents
|
||||
|
||||
- [API_SPEC.md](./API_SPEC.md)
|
||||
- [DATABASE.md](./DATABASE.md)
|
||||
- [DESIGN_SYSTEM.md](./DESIGN_SYSTEM.md)
|
||||
210
docs/DATABASE.md
Normal file
210
docs/DATABASE.md
Normal file
|
|
@ -0,0 +1,210 @@
|
|||
# Database Schema — Desi Clans
|
||||
|
||||
**Version:** 1.2
|
||||
**Engine:** PostgreSQL 15+
|
||||
**ORM:** SQLAlchemy 2.x
|
||||
**Migrations:** Alembic
|
||||
|
||||
---
|
||||
|
||||
## 1. Overview
|
||||
|
||||
The database supports:
|
||||
|
||||
1. **Catalog (Phase 6)** — `venues`, `events`, `event_media`, `ticket_types`
|
||||
2. **Legacy content** — `event_editions`, `media_assets` (admin-era tables)
|
||||
3. **Marketing** — platform links, newsletter subscribers, outbound clicks
|
||||
4. **Admin** — admin users and roles
|
||||
5. **Accounts** — attendee `users` (Phase 8)
|
||||
6. **Ticketing (Phase 9)** — orders, order items, issued tickets
|
||||
|
||||
---
|
||||
|
||||
## 2. Catalog ERD (implemented)
|
||||
|
||||
```
|
||||
users ──< orders ──< order_items ──< tickets
|
||||
│ │
|
||||
│ └──> ticket_types
|
||||
└──> events ──< event_media
|
||||
│
|
||||
└──< ticket_types
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Tables — catalog
|
||||
|
||||
### `venues`
|
||||
|
||||
| Column | Type | Constraints |
|
||||
|--------|------|-------------|
|
||||
| id | SERIAL | PK |
|
||||
| slug | VARCHAR(120) | UNIQUE, NOT NULL |
|
||||
| name | VARCHAR(200) | NOT NULL |
|
||||
| area | VARCHAR(120) | NOT NULL |
|
||||
| city | VARCHAR(80) | NOT NULL, default Bengaluru |
|
||||
| address | VARCHAR(512) | NOT NULL |
|
||||
| map_url | VARCHAR(512) | NULL |
|
||||
| description | TEXT | NULL |
|
||||
| created_at | TIMESTAMPTZ | DEFAULT now() |
|
||||
| updated_at | TIMESTAMPTZ | DEFAULT now() |
|
||||
|
||||
---
|
||||
|
||||
### `events`
|
||||
|
||||
| Column | Type | Constraints |
|
||||
|--------|------|-------------|
|
||||
| id | SERIAL | PK |
|
||||
| slug | VARCHAR(200) | UNIQUE, NOT NULL |
|
||||
| title | VARCHAR(240) | NOT NULL |
|
||||
| category | eventtype ENUM | NOT NULL, INDEX |
|
||||
| status | eventstatus ENUM | NOT NULL, default draft, INDEX |
|
||||
| venue_id | INT | FK → venues.id RESTRICT, INDEX |
|
||||
| description | TEXT | NULL |
|
||||
| starts_at | TIMESTAMPTZ | NOT NULL, INDEX |
|
||||
| ends_at | TIMESTAMPTZ | NULL |
|
||||
| is_featured | BOOLEAN | default false |
|
||||
| cover_image_url | VARCHAR(1024) | NULL |
|
||||
| cover_image_alt | VARCHAR(300) | NULL |
|
||||
| meta_description | VARCHAR(320) | NULL |
|
||||
| created_at | TIMESTAMPTZ | DEFAULT now() |
|
||||
| updated_at | TIMESTAMPTZ | DEFAULT now() |
|
||||
|
||||
**Indexes:** `(status, starts_at)`, `(category, status)`
|
||||
|
||||
**`eventstatus`:** `draft`, `published`, `cancelled`
|
||||
|
||||
---
|
||||
|
||||
### `event_media`
|
||||
|
||||
| Column | Type | Constraints |
|
||||
|--------|------|-------------|
|
||||
| id | SERIAL | PK |
|
||||
| event_id | INT | FK → events.id CASCADE, INDEX |
|
||||
| image_url | VARCHAR(1024) | NOT NULL |
|
||||
| alt_text | VARCHAR(300) | NULL |
|
||||
| sort_order | INT | default 0 |
|
||||
| created_at | TIMESTAMPTZ | DEFAULT now() |
|
||||
|
||||
---
|
||||
|
||||
### `ticket_types`
|
||||
|
||||
| Column | Type | Constraints |
|
||||
|--------|------|-------------|
|
||||
| id | SERIAL | PK |
|
||||
| event_id | INT | FK → events.id CASCADE, INDEX |
|
||||
| name | VARCHAR(120) | NOT NULL |
|
||||
| description | VARCHAR(512) | NULL |
|
||||
| price_paise | INT | NOT NULL |
|
||||
| quantity_total | INT | NOT NULL |
|
||||
| quantity_sold | INT | default 0 |
|
||||
| status | tickettypestatus ENUM | default active |
|
||||
| sort_order | INT | default 0 |
|
||||
| created_at | TIMESTAMPTZ | DEFAULT now() |
|
||||
| updated_at | TIMESTAMPTZ | DEFAULT now() |
|
||||
|
||||
**`tickettypestatus`:** `active`, `sold_out`, `inactive`
|
||||
|
||||
**Check:** `quantity_sold <= quantity_total` (migration `20260404_0007`)
|
||||
|
||||
Inventory updates use row-level locks (`SELECT … FOR UPDATE`) during checkout.
|
||||
|
||||
---
|
||||
|
||||
### `users` (attendees)
|
||||
|
||||
| Column | Type | Constraints |
|
||||
|--------|------|-------------|
|
||||
| id | SERIAL | PK |
|
||||
| email | VARCHAR(255) | UNIQUE, NOT NULL |
|
||||
| hashed_password | VARCHAR(255) | NOT NULL (bcrypt) |
|
||||
| full_name | VARCHAR(120) | NULL |
|
||||
| is_active | BOOLEAN | default true |
|
||||
| created_at | TIMESTAMPTZ | DEFAULT now() |
|
||||
|
||||
---
|
||||
|
||||
### `orders`
|
||||
|
||||
| Column | Type | Constraints |
|
||||
|--------|------|-------------|
|
||||
| id | SERIAL | PK |
|
||||
| user_id | INT | FK → users.id RESTRICT, INDEX |
|
||||
| event_id | INT | FK → events.id RESTRICT, INDEX |
|
||||
| status | orderstatus ENUM | NOT NULL, INDEX |
|
||||
| total_paise | INT | NOT NULL (server-calculated) |
|
||||
| currency | VARCHAR(3) | default INR |
|
||||
| payment_reference | VARCHAR(64) | NULL (mock `mock_*` until gateway) |
|
||||
| paid_at | TIMESTAMPTZ | NULL |
|
||||
| created_at | TIMESTAMPTZ | DEFAULT now() |
|
||||
| updated_at | TIMESTAMPTZ | DEFAULT now() |
|
||||
|
||||
**`orderstatus`:** `pending`, `paid`, `cancelled`, `failed`
|
||||
Checkout currently completes in one transaction with mock payment → `paid`.
|
||||
|
||||
---
|
||||
|
||||
### `order_items`
|
||||
|
||||
| Column | Type | Constraints |
|
||||
|--------|------|-------------|
|
||||
| id | SERIAL | PK |
|
||||
| order_id | INT | FK → orders.id CASCADE, INDEX |
|
||||
| ticket_type_id | INT | FK → ticket_types.id RESTRICT, INDEX |
|
||||
| quantity | INT | NOT NULL, > 0 |
|
||||
| unit_price_paise | INT | NOT NULL (snapshot at purchase) |
|
||||
| line_total_paise | INT | NOT NULL |
|
||||
|
||||
---
|
||||
|
||||
### `tickets` (issued passes)
|
||||
|
||||
| Column | Type | Constraints |
|
||||
|--------|------|-------------|
|
||||
| id | SERIAL | PK |
|
||||
| code | VARCHAR(32) | UNIQUE, NOT NULL (e.g. `DC-…`) |
|
||||
| order_id | INT | FK → orders.id CASCADE, INDEX |
|
||||
| order_item_id | INT | FK → order_items.id CASCADE, INDEX |
|
||||
| ticket_type_id | INT | FK → ticket_types.id RESTRICT, INDEX |
|
||||
| event_id | INT | FK → events.id RESTRICT, INDEX |
|
||||
| user_id | INT | FK → users.id RESTRICT, INDEX |
|
||||
| status | ticketstatus ENUM | NOT NULL, INDEX |
|
||||
| created_at | TIMESTAMPTZ | DEFAULT now() |
|
||||
|
||||
**`ticketstatus`:** `valid`, `cancelled`, `redeemed`
|
||||
**Index:** `(user_id, status)`
|
||||
|
||||
---
|
||||
|
||||
## 4. Seed data
|
||||
|
||||
```bash
|
||||
python scripts/seed_catalog.py
|
||||
```
|
||||
|
||||
Creates Bengaluru venues (Palace Grounds, KTPO Whitefield, HSR Layout) and published events for **Holi**, **Garba**, and **Dandiya** with media URLs and ticket tiers.
|
||||
|
||||
---
|
||||
|
||||
## 5. Legacy tables
|
||||
|
||||
See prior revisions for `event_editions`, `media_assets`, `admin_users`, `platform_links`, `newsletter_subscribers`, `outbound_clicks`, `alert_dispatches`.
|
||||
|
||||
---
|
||||
|
||||
## 6. Migration policy
|
||||
|
||||
- Revision `20260404_0005` adds catalog tables.
|
||||
- Revision `20260404_0006` adds attendee `users`.
|
||||
- Revision `20260404_0007` adds `orders`, `order_items`, `tickets`.
|
||||
- If the DB was created via `create_all` before Alembic history existed, run `alembic stamp 20260404_0004` once, then `alembic upgrade head`.
|
||||
|
||||
---
|
||||
|
||||
## 7. Related
|
||||
|
||||
- [API_SPEC.md](./API_SPEC.md)
|
||||
188
docs/DESIGN_SYSTEM.md
Normal file
188
docs/DESIGN_SYSTEM.md
Normal file
|
|
@ -0,0 +1,188 @@
|
|||
# Design System — Desi Clans
|
||||
|
||||
**Version:** 1.0
|
||||
**Last updated:** 2026-04-04
|
||||
|
||||
---
|
||||
|
||||
## 1. Brand intent
|
||||
|
||||
Desi Clans should read as **premium editorial culture**, not a discount ticket site.
|
||||
|
||||
- **Cinematic:** Full-bleed imagery, deliberate negative space, restrained color.
|
||||
- **Warm, not loud:** Celebratory without carnival clutter.
|
||||
- **Bengaluru-rooted:** Urban sophistication + festival energy.
|
||||
- **Trust:** Clear dates, venues, and pricing; no dark patterns on checkout.
|
||||
|
||||
---
|
||||
|
||||
## 2. Design principles
|
||||
|
||||
1. **Content leads** — Photography and event title dominate; UI chrome stays quiet.
|
||||
2. **Mobile first** — Design at 390px width; scale up with fluid typography.
|
||||
3. **Motion with purpose** — GSAP for hero reveals and section entrances; never block interaction.
|
||||
4. **Accessible by default** — Color is not the only signal; focus states are always visible.
|
||||
5. **Performance is UX** — Prefer CSS transforms for animation; lazy-load below-fold media.
|
||||
|
||||
---
|
||||
|
||||
## 3. Color (Tailwind tokens)
|
||||
|
||||
Define in `tailwind.config.js` under `theme.extend.colors`:
|
||||
|
||||
| Token | Role | Suggested value | Usage |
|
||||
|-------|------|-----------------|--------|
|
||||
| `ink` | Primary text | `#0F0E0C` | Headlines, body |
|
||||
| `ink-muted` | Secondary text | `#5C574F` | Meta, captions |
|
||||
| `surface` | Page background | `#FAF8F5` | Default canvas |
|
||||
| `surface-elevated` | Cards | `#FFFFFF` | Cards, modals |
|
||||
| `accent` | Primary CTA | `#C45C26` | Buttons, links (terracotta) |
|
||||
| `accent-hover` | CTA hover | `#A34A1E` | Hover states |
|
||||
| `gold` | Highlight | `#C9A227` | Featured badges, dividers (sparingly) |
|
||||
| `festival` | Seasonal accent | `#8B2E5A` | Holi/Garba campaign strips (optional per edition) |
|
||||
| `border` | Dividers | `#E8E4DD` | Hairlines |
|
||||
| `success` | Valid ticket | `#2D6A4F` | Check-in success |
|
||||
| `danger` | Errors | `#B42318` | Form errors |
|
||||
|
||||
**Dark sections:** Hero and footer may use `ink` background with `surface` text for contrast blocks—use for one section per page max.
|
||||
|
||||
---
|
||||
|
||||
## 4. Typography
|
||||
|
||||
Use **two families** only (load via Google Fonts or self-host):
|
||||
|
||||
| Role | Family | Tailwind class | Notes |
|
||||
|------|--------|----------------|-------|
|
||||
| Display | *Fraunces* or *Playfair Display* | `font-display` | H1–H2, editorial pull quotes |
|
||||
| UI / body | *DM Sans* or *Inter* | `font-sans` | Body, buttons, labels |
|
||||
|
||||
**Scale (mobile-first)**
|
||||
|
||||
| Element | Size | Weight | Tracking |
|
||||
|---------|------|--------|----------|
|
||||
| H1 | `text-4xl` → `md:text-6xl` | 600 | tight |
|
||||
| H2 | `text-3xl` → `md:text-4xl` | 600 | tight |
|
||||
| H3 | `text-xl` | 600 | normal |
|
||||
| Body | `text-base` | 400 | normal |
|
||||
| Small / meta | `text-sm` | 400 | `text-ink-muted` |
|
||||
| Button | `text-sm` uppercase | 600 | `tracking-wide` |
|
||||
|
||||
**Line length:** Prose blocks `max-w-prose` (~65ch).
|
||||
|
||||
---
|
||||
|
||||
## 5. Spacing & layout
|
||||
|
||||
- **Grid:** 4px base; use Tailwind spacing scale consistently.
|
||||
- **Page gutters:** `px-4` mobile, `px-6` tablet, `px-8` desktop; `max-w-7xl mx-auto` for content.
|
||||
- **Section rhythm:** `py-16` md:`py-24` between major sections.
|
||||
- **Cards:** `rounded-2xl`, subtle shadow `shadow-sm`, border `border-border`.
|
||||
|
||||
---
|
||||
|
||||
## 6. Components (catalog)
|
||||
|
||||
Build in `frontend/src/components/` with consistent naming:
|
||||
|
||||
| Component | Description |
|
||||
|-----------|-------------|
|
||||
| `Button` | Primary, secondary (outline), ghost; min height 44px touch target |
|
||||
| `Badge` | Event type, featured, sold out |
|
||||
| `EventCard` | Cover, title, date, venue, CTA |
|
||||
| `EventHero` | Full-width cover + gradient scrim + title |
|
||||
| `MediaGallery` | Grid + lightbox (keyboard trap, focus return) |
|
||||
| `TicketCTA` | Sticky on mobile for detail page |
|
||||
| `Navbar` | Logo, Events, Subscribe; drawer on mobile |
|
||||
| `Footer` | Links, social, newsletter |
|
||||
| `Input`, `Label`, `FormError` | Accessible form primitives |
|
||||
| `Skeleton` | Loading placeholders matching card layout |
|
||||
| `AdminShell` | Sidebar + content (separate visual tone: denser, neutral) |
|
||||
|
||||
**Icons:** Lucide React only; size `20` inline, `24` in buttons.
|
||||
|
||||
---
|
||||
|
||||
## 7. Motion (GSAP + ScrollTrigger)
|
||||
|
||||
**Setup**
|
||||
|
||||
- Register `ScrollTrigger` once in `animations/gsap.js`.
|
||||
- Export `prefersReducedMotion()` helper; if true, skip timelines and set final state.
|
||||
|
||||
**Patterns**
|
||||
|
||||
| Pattern | Use | Duration |
|
||||
|---------|-----|----------|
|
||||
| Hero fade + y | Home hero | 0.8–1.2s |
|
||||
| Stagger cards | Event grid on scroll | 0.4s stagger |
|
||||
| Parallax (subtle) | Hero image only | scrub 0.5 |
|
||||
|
||||
**Rules**
|
||||
|
||||
- No infinite loops on main content.
|
||||
- `will-change` only during active animation.
|
||||
- Do not animate `width`/`height`; use `transform` and `opacity`.
|
||||
|
||||
---
|
||||
|
||||
## 8. Photography & imagery
|
||||
|
||||
- **Aspect ratios:** Hero 16:9 or 3:2; cards 4:5 or 3:2.
|
||||
- **Treatment:** Slight gradient overlay (`from-ink/60`) for text on images.
|
||||
- **Alt text:** Required; describe the scene, not "event image".
|
||||
- **Placeholders:** Neutral `surface` with shimmer skeleton, not gray boxes.
|
||||
|
||||
---
|
||||
|
||||
## 9. Accessibility
|
||||
|
||||
- Minimum touch target **44×44px**.
|
||||
- Focus ring: `ring-2 ring-accent ring-offset-2 ring-offset-surface`.
|
||||
- Color contrast: body text ≥ 4.5:1; large text ≥ 3:1.
|
||||
- `prefers-reduced-motion`: disable ScrollTrigger scrubbing.
|
||||
- Forms: associate labels, `aria-invalid`, `aria-describedby` for errors.
|
||||
- Skip link: "Skip to main content" as first focusable element.
|
||||
|
||||
---
|
||||
|
||||
## 10. Admin UI tone
|
||||
|
||||
Admin is **utilitarian**, not marketing:
|
||||
|
||||
- Neutral `surface` background, compact table density.
|
||||
- Same `font-sans`; skip display font in admin except page titles.
|
||||
- Primary actions still use `accent` for consistency.
|
||||
|
||||
---
|
||||
|
||||
## 11. Content voice
|
||||
|
||||
- **Headlines:** Specific ("Garba Under the Lights — KTPO Whitefield") not generic ("Amazing Night").
|
||||
- **CTAs:** "Get tickets", "View edition", "Join the list" — avoid "Click here".
|
||||
- **Venues:** Always city + venue name when known.
|
||||
|
||||
---
|
||||
|
||||
## 12. Tailwind configuration checklist
|
||||
|
||||
- `content: ['./index.html', './src/**/*.{js,jsx}']`
|
||||
- Extend `colors`, `fontFamily`, `borderRadius` as above
|
||||
- No `@apply` abuse; prefer utility classes in JSX for clarity
|
||||
- `plugins`: only `@tailwindcss/forms` if needed for input resets
|
||||
|
||||
---
|
||||
|
||||
## 13. Do not use
|
||||
|
||||
- Neon gradients, glassmorphism stacks, or concert-poster clutter.
|
||||
- More than two font families.
|
||||
- Auto-playing video with sound.
|
||||
- Icon-only buttons without `aria-label`.
|
||||
|
||||
---
|
||||
|
||||
## 14. References
|
||||
|
||||
- [PRD.md](./PRD.md) — product scope
|
||||
- [ARCHITECTURE.md](./ARCHITECTURE.md) — frontend structure
|
||||
73
docs/PERFORMANCE.md
Normal file
73
docs/PERFORMANCE.md
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
# Production performance — Desi Clans (public frontend)
|
||||
|
||||
**Last audit:** 2026-04-04
|
||||
**Build:** `cd frontend && npm run build`
|
||||
|
||||
## Summary
|
||||
|
||||
| Area | Finding | Action taken |
|
||||
|------|---------|--------------|
|
||||
| **Bundle** | Admin + GSAP + Three on critical path | Route-level `React.lazy`, dynamic `loadGsap()`, isolated `hero-atmosphere` chunk |
|
||||
| **Initial JS** | Single large entry | Split: `vendor-react`, `vendor-http`, `gsap`, `three`, per-route pages |
|
||||
| **Images** | Full-width Unsplash URLs everywhere | `withImageWidth` / `srcSet` on hero, cards, featured, galleries |
|
||||
| **Video** | None in app | N/A — no `<video>` on marketing site |
|
||||
| **Fonts** | Google Fonts (2 families, limited weights) | `preconnect`, `display=swap` in CSS URL; no JS font loader |
|
||||
| **API** | Home blocked on 2nd request for featured copy | List-only first paint; featured detail deferred via `requestIdleCallback` |
|
||||
| **Caching** | Refetch on every navigation | In-memory TTL cache (60s) for events list, slug detail, venues |
|
||||
| **GSAP** | Static import bloated entry | `loadGsap()` dynamic import on home + event detail only |
|
||||
| **WebGL** | Large Three chunk | Desktop-only, idle mount, separate chunk; mobile = no download |
|
||||
| **React** | Navbar scroll → many setState | `requestAnimationFrame` throttling |
|
||||
| **CLS** | Hero / cards | Explicit `width`/`height` on hero; reserved route fallback shell |
|
||||
|
||||
## Production chunks (2026-04-04 build)
|
||||
|
||||
| Chunk | gzip (approx.) | When loaded |
|
||||
|-------|----------------|-------------|
|
||||
| `index-*.js` | **6.9 KB** | First paint (shell + router) |
|
||||
| `vendor-react-*.js` | 79 KB | First paint |
|
||||
| `vendor-http-*.js` | 19 KB | First API call |
|
||||
| `HomePage-*.js` | 5.8 KB | Home route |
|
||||
| `gsap-*.js` | 45 KB | After `loadGsap()` on home / event detail |
|
||||
| `hero-atmosphere-*.js` | 124 KB | Idle + desktop only |
|
||||
| `AdminRoutes-*.js` | 4.4 KB | `/admin` only |
|
||||
|
||||
**Before route split:** main `index` JS was ~**44 KB** gzip in one bundle with admin + pages.
|
||||
**After:** entry shell ~**7 KB** gzip; admin/GSAP/Three not on marketing critical path.
|
||||
|
||||
Run `npm run build` and inspect `dist/assets/` for current gzip sizes.
|
||||
|
||||
## Lazy loading
|
||||
|
||||
| Asset | Strategy |
|
||||
|-------|----------|
|
||||
| Hero photo | `fetchpriority="high"`, preload 1600w, responsive `srcSet` |
|
||||
| Event cards, categories, gallery, featured | `loading="lazy"`, `decoding="async"`, `sizes` |
|
||||
| Event detail cover | `fetchPriority="high"` |
|
||||
| Routes | `React.lazy` + `Suspense` |
|
||||
| GSAP / WebGL | Dynamic import + idle callback |
|
||||
|
||||
## Mobile
|
||||
|
||||
- WebGL atmosphere **disabled** < 1024px
|
||||
- GSAP scroll reveals skipped when `prefers-reduced-motion`
|
||||
- Smaller hero `srcSet` widths selected by browser
|
||||
- Sticky ticket bar uses safe-area padding
|
||||
|
||||
## Lighthouse (preview)
|
||||
|
||||
Measure against production build:
|
||||
|
||||
```bash
|
||||
cd frontend
|
||||
npm run build && npm run preview -- --host 127.0.0.1 --port 4173
|
||||
npx lighthouse http://127.0.0.1:4173/ --only-categories=performance,accessibility --chrome-flags="--headless"
|
||||
```
|
||||
|
||||
Prior baseline (pre-split): ~86 performance / 96 a11y on home. Re-run after deploy; scores vary with network and Unsplash latency.
|
||||
|
||||
## Future (not in scope)
|
||||
|
||||
- CDN cache headers for `/api` and `/media` (backend)
|
||||
- `short_description` on `EventSummary` to avoid featured detail fetch
|
||||
- Self-hosted fonts subset for Fraunces + DM Sans
|
||||
- Service worker / offline (PWA)
|
||||
185
docs/PRD.md
Normal file
185
docs/PRD.md
Normal file
|
|
@ -0,0 +1,185 @@
|
|||
# Product Requirements Document — Desi Clans
|
||||
|
||||
**Version:** 1.0
|
||||
**Status:** Constitution (pre-build)
|
||||
**Last updated:** 2026-04-04
|
||||
**Owner:** Product / Engineering
|
||||
|
||||
---
|
||||
|
||||
## 1. Vision
|
||||
|
||||
Desi Clans is a **premium, Bengaluru-first** platform for discovering and attending **Indian cultural celebrations**—Holi, Garba, Dandiya, and related festivities. The experience should feel **cinematic and editorial**, not like a generic ticket aggregator or concert listing site.
|
||||
|
||||
**In scope:** Cultural events, curated editions, venues across Bengaluru (Palace Grounds, KTPO Whitefield, HSR Layout, and similar).
|
||||
**Out of scope:** Concerts, generic nightlife listings, multi-city expansion beyond Bengaluru in v1.
|
||||
|
||||
---
|
||||
|
||||
## 2. Goals
|
||||
|
||||
| Goal | Success signal |
|
||||
|------|----------------|
|
||||
| Premium brand perception | Editorial layouts, strong typography, intentional motion |
|
||||
| Mobile-first attendance | ≥60% traffic on mobile; core flows usable one-handed |
|
||||
| Fast | LCP < 2.5s on 4G; API p95 < 300ms for public reads |
|
||||
| Accessible | WCAG 2.1 AA for public UI; keyboard + screen reader paths |
|
||||
| SEO | Indexable event URLs, share previews, structured data |
|
||||
| Operable | Admin can publish editions, media, and ticket inventory without deploys |
|
||||
| Trustworthy ticketing | Clear pricing, issued tickets with QR, reliable check-in |
|
||||
|
||||
---
|
||||
|
||||
## 3. Personas
|
||||
|
||||
### 3.1 Attendee (primary)
|
||||
|
||||
- Discovers events via search, social, or word of mouth.
|
||||
- Needs date, venue, vibe, gallery, and a frictionless path to buy or hold tickets.
|
||||
- Expects mobile ticket (QR) at the gate.
|
||||
|
||||
### 3.2 Organizer / Desi Clans ops
|
||||
|
||||
- Publishes event editions, hero media, copy, and SEO fields.
|
||||
- Manages ticket tiers, capacity, and sales window.
|
||||
- Monitors outbound or native sales and gate check-in.
|
||||
|
||||
### 3.3 Gate staff
|
||||
|
||||
- Scans QR codes quickly; sees valid / already used / invalid states.
|
||||
- Works on mobile browsers in variable network conditions.
|
||||
|
||||
### 3.4 Platform admin
|
||||
|
||||
- Manages admin users (roles), site links, newsletter subscribers, and analytics.
|
||||
|
||||
---
|
||||
|
||||
## 4. User stories (v1)
|
||||
|
||||
### Discovery & content
|
||||
|
||||
- As an attendee, I can browse **published** event editions filtered by type and date.
|
||||
- As an attendee, I can open an **event detail page** with cover, gallery, venue, calendar add, and ticket CTA.
|
||||
- As an attendee, I can share an event; recipients see a **rich preview** (title, image, description).
|
||||
|
||||
### Accounts (ticketing phase)
|
||||
|
||||
- As an attendee, I can register / log in to purchase and view **my tickets**.
|
||||
- As an attendee, I receive a **confirmation** with ticket QR(s) after purchase.
|
||||
|
||||
### Ticketing
|
||||
|
||||
- As ops, I define **ticket tiers** (name, price, quantity, sale window) per edition.
|
||||
- As an attendee, I can **purchase** tickets (native checkout in target architecture; see phases).
|
||||
- As gate staff, I can **scan** a QR and mark entry once per ticket.
|
||||
|
||||
### Admin
|
||||
|
||||
- As an editor, I can CRUD editions, upload cover and gallery media, set publish/featured flags.
|
||||
- As super admin, I can manage admin users and view click / sales summaries.
|
||||
- As ops, I can export subscriber list and send alerts (email) for new editions (batch, not real-time chat).
|
||||
|
||||
### Newsletter
|
||||
|
||||
- As a visitor, I can subscribe with email and optional neighborhood.
|
||||
|
||||
---
|
||||
|
||||
## 5. Functional requirements
|
||||
|
||||
### 5.1 Public site
|
||||
|
||||
- Home: featured edition(s), upcoming list, brand story strip.
|
||||
- Event listing with filters: `event_type`, date range, featured.
|
||||
- Event detail: slug URL, `meta_description`, Open Graph via share route or SSR meta injection.
|
||||
- Outbound ticket links (BookMyShow, District, etc.) **or** native checkout when enabled per edition.
|
||||
- Calendar download (`.ics`) for edition start time.
|
||||
- Newsletter signup.
|
||||
|
||||
### 5.2 Authentication
|
||||
|
||||
- **Admin:** JWT (access token), bcrypt passwords, role-based access (`super_admin`, `editor`).
|
||||
- **Attendees (ticketing phase):** JWT for account + order history; separate from admin namespace.
|
||||
|
||||
### 5.3 Admin dashboard
|
||||
|
||||
- Secure login; session via Bearer token stored in memory (not localStorage for admin if avoidable; httpOnly cookie acceptable future enhancement).
|
||||
- Edition editor, media manager, platform links, subscribers, analytics (outbound clicks; order stats when native ticketing live).
|
||||
- Check-in view for authorized staff (role or edition-scoped permission).
|
||||
|
||||
### 5.4 Ticketing model (target)
|
||||
|
||||
- **Edition** → one or more **ticket tiers** → **orders** → **tickets** (unique code + QR payload).
|
||||
- Inventory enforced at order creation ( pessimistic row lock or atomic decrement ).
|
||||
- QR encodes signed token or opaque ticket id verified server-side (no PII in QR).
|
||||
- States: `issued`, `checked_in`, `cancelled`, `refunded` (refund workflow optional v1.1).
|
||||
|
||||
### 5.5 Payments (phased)
|
||||
|
||||
- **Phase A:** Outbound links only (no payment on Desi Clans).
|
||||
- **Phase B:** Native orders with payment provider webhook (e.g. Razorpay)—documented in architecture, implemented only when product enables it.
|
||||
|
||||
### 5.6 Non-functional
|
||||
|
||||
- API version prefix: `/api/...`
|
||||
- PostgreSQL as system of record; Alembic migrations required for schema changes.
|
||||
- Media: local disk in dev; S3-compatible object storage in production (optional boto3—already present in environment).
|
||||
- No concert content types in taxonomy.
|
||||
|
||||
---
|
||||
|
||||
## 6. Event taxonomy
|
||||
|
||||
| `event_type` | Description |
|
||||
|--------------|-------------|
|
||||
| `holi` | Holi celebrations |
|
||||
| `garba` | Garba nights |
|
||||
| `dandiya` | Dandiya events |
|
||||
| `bhajan_clubbing` | Curated devotional / club fusion (brand-specific) |
|
||||
| `special` | Other Indian cultural celebrations (not concerts) |
|
||||
|
||||
---
|
||||
|
||||
## 7. Out of scope (v1)
|
||||
|
||||
- Multi-language UI (English only v1).
|
||||
- Native mobile apps.
|
||||
- Seat maps / reserved seating.
|
||||
- Secondary resale marketplace.
|
||||
- Live streaming.
|
||||
- User-generated reviews or forums.
|
||||
- Concert and non-cultural listings.
|
||||
|
||||
---
|
||||
|
||||
## 8. Risks & mitigations
|
||||
|
||||
| Risk | Mitigation |
|
||||
|------|------------|
|
||||
| Double ticket sale | DB transactions, unique constraints, idempotent payment webhooks |
|
||||
| QR forgery | Signed payloads, server validation, rate limits on scan |
|
||||
| Admin token theft | Short JWT TTL, HTTPS only, minimal CORS origins |
|
||||
| Heavy media hurting LCP | Responsive images, lazy load, CDN for production assets |
|
||||
|
||||
---
|
||||
|
||||
## 9. Acceptance criteria (MVP public site)
|
||||
|
||||
- [ ] Published editions render from API on home, list, and detail routes.
|
||||
- [ ] Event pages include correct meta for SEO and social sharing.
|
||||
- [ ] Mobile layout passes manual QA on iOS Safari and Chrome Android.
|
||||
- [ ] Admin can publish an edition with cover and gallery without code changes.
|
||||
- [ ] Lighthouse accessibility score ≥ 90 on event detail (lab).
|
||||
|
||||
**MVP ticketing (later phase):** purchase flow, email with QR, successful check-in scan.
|
||||
|
||||
---
|
||||
|
||||
## 10. References
|
||||
|
||||
- [ARCHITECTURE.md](./ARCHITECTURE.md) — system design and phases
|
||||
- [API_SPEC.md](./API_SPEC.md) — endpoints
|
||||
- [DATABASE.md](./DATABASE.md) — entities
|
||||
- [DESIGN_SYSTEM.md](./DESIGN_SYSTEM.md) — UI principles
|
||||
- [PROGRESS.md](./PROGRESS.md) — implementation status
|
||||
414
docs/PROGRESS.md
Normal file
414
docs/PROGRESS.md
Normal file
|
|
@ -0,0 +1,414 @@
|
|||
# Implementation Progress — Desi Clans
|
||||
|
||||
**Last updated:** 2026-04-04 (Phase 13 visual refinement)
|
||||
|
||||
This file tracks delivery against [ARCHITECTURE.md](./ARCHITECTURE.md) phases.
|
||||
|
||||
---
|
||||
|
||||
## Legend
|
||||
|
||||
| Symbol | Meaning |
|
||||
|--------|---------|
|
||||
| ✅ | Done |
|
||||
| 🟡 | Partial |
|
||||
| ⬜ | Not started |
|
||||
|
||||
---
|
||||
|
||||
## Phase 0 — Constitution
|
||||
|
||||
| Item | Status | Notes |
|
||||
|------|--------|-------|
|
||||
| PRD.md | ✅ | v1.0 |
|
||||
| ARCHITECTURE.md | ✅ | v1.0 |
|
||||
| DESIGN_SYSTEM.md | ✅ | v1.0 |
|
||||
| API_SPEC.md | ✅ | v1.0 |
|
||||
| DATABASE.md | ✅ | v1.0 |
|
||||
| PROGRESS.md | ✅ | This file |
|
||||
| README.md | ✅ | Root readme |
|
||||
|
||||
---
|
||||
|
||||
## Phase 1a — Project foundation
|
||||
|
||||
### Monorepo & tooling
|
||||
|
||||
| Item | Status | Notes |
|
||||
|------|--------|-------|
|
||||
| Root `.gitignore` | ✅ | Node, Python, env, uploads |
|
||||
| `docker-compose.yml` | ✅ | PostgreSQL 16 for local dev |
|
||||
| Dev scripts (`scripts/*.ps1`, `*.sh`) | ✅ | Backend + frontend |
|
||||
| Root README setup | ✅ | Quick start |
|
||||
|
||||
### Backend foundation
|
||||
|
||||
| Item | Status | Notes |
|
||||
|------|--------|-------|
|
||||
| FastAPI app entry | ✅ | CORS, optional `/media` mount |
|
||||
| `app/config.py` (Pydantic Settings) | ✅ | `.env` driven |
|
||||
| `app/database.py` | ✅ | SQLAlchemy engine + session |
|
||||
| `app/storage.py` | ✅ | Local + S3 backends |
|
||||
| `requirements.txt` | ✅ | Pinned ranges |
|
||||
| `backend/.env.example` | ✅ | Documented variables |
|
||||
| `backend/README.md` | ✅ | Setup + scripts |
|
||||
| Alembic + migrations | ✅ | Existing revisions (models in repo) |
|
||||
| Health endpoint only in `main` | ✅ | `GET /api/public/health` |
|
||||
| `tests/test_health.py` | ✅ | FastAPI TestClient |
|
||||
| `create_all` removed from `main` | ✅ | Use Alembic for schema |
|
||||
|
||||
### Frontend foundation
|
||||
|
||||
| Item | Status | Notes |
|
||||
|------|--------|-------|
|
||||
| Vite + React (JSX) | ✅ | `frontend/` |
|
||||
| Tailwind CSS v3 | ✅ | Design tokens in config |
|
||||
| React Router | ✅ | Home + 404, `AppLayout` |
|
||||
| Axios client | ✅ | `src/api/client.js` |
|
||||
| GSAP setup | ✅ | `src/animations/gsap.js` + hero fade |
|
||||
| Lucide React | ✅ | Home page icon |
|
||||
| Folder structure | ✅ | api, components, hooks, layouts, pages, styles, utils, animations |
|
||||
| `frontend/.env.example` | ✅ | `VITE_API_URL` |
|
||||
| Vite dev proxy `/api` | ✅ | `vite.config.js` |
|
||||
| `frontend/README.md` | ✅ | |
|
||||
|
||||
### Business features (deferred — next step)
|
||||
|
||||
| Item | Status | Notes |
|
||||
|------|--------|-------|
|
||||
| Mount public/admin/auth routers | ⬜ | Code exists under `app/routers/` |
|
||||
| Public event pages (UI) | ⬜ | |
|
||||
| Admin dashboard (UI) | ⬜ | |
|
||||
| Wire Axios to editions API | ⬜ | |
|
||||
|
||||
---
|
||||
|
||||
## Phase 1b — Public site + admin content
|
||||
|
||||
### Backend (routers to enable)
|
||||
|
||||
| Item | Status | Notes |
|
||||
|------|--------|-------|
|
||||
| JWT admin auth | 🟡 | `app/auth.py` + `routers/auth.py` |
|
||||
| Public editions API | 🟡 | `routers/public.py` |
|
||||
| Admin CRUD + media | 🟡 | `routers/admin.py` |
|
||||
| Analytics, share, subscribers | 🟡 | Routers present, not mounted |
|
||||
|
||||
### Frontend
|
||||
|
||||
| Item | Status | Notes |
|
||||
|------|--------|-------|
|
||||
| Events list + detail pages | ⬜ | |
|
||||
| Admin dashboard UI | ⬜ | |
|
||||
| SEO meta per route | 🟡 | Default in `index.html` only |
|
||||
|
||||
### DevOps
|
||||
|
||||
| Item | Status | Notes |
|
||||
|------|--------|-------|
|
||||
| Docker backend image | 🟡 | `backend/Dockerfile` |
|
||||
| CI lint/test | ⬜ | |
|
||||
|
||||
---
|
||||
|
||||
## Phase 2 — Design system (UI primitives)
|
||||
|
||||
| Item | Status | Notes |
|
||||
|------|--------|-------|
|
||||
| Tailwind tokens (color, type, spacing, radius, shadow) | ✅ | `tailwind.config.js` |
|
||||
| Typography components (`Heading`, `Text`, `Eyebrow`) | ✅ | Fluid display scale |
|
||||
| `Button`, `Badge`, `Card`, `Container` | ✅ | `src/components/ui/` |
|
||||
| `Section`, `SectionHeader`, `SectionSplit` | ✅ | Editorial layouts |
|
||||
| `Navbar`, `NavLink`, `SiteFooter` | ✅ | Mobile menu + sticky header |
|
||||
| `EventCard` | ✅ | Image-led, minimal radius |
|
||||
| `Skeleton`, `Spinner`, `EmptyState` | ✅ | Loading & empty patterns |
|
||||
| Barrel exports | ✅ | `src/components/index.js` |
|
||||
| Showcase route `/system` | ✅ | Responsive verification + breakpoint hook |
|
||||
| `npm run build` | ✅ | Verified |
|
||||
|
||||
---
|
||||
|
||||
## Phase 3 — Public homepage (mock data)
|
||||
|
||||
| Item | Status | Notes |
|
||||
|------|--------|-------|
|
||||
| `MarketingLayout` + overlay nav | ✅ | Scroll-solid header, anchor links |
|
||||
| Cinematic `HomeHero` | ✅ | Full-viewport, parallax image, GSAP entrance |
|
||||
| Upcoming events + `EventCard` grid | ✅ | `src/data/homeMock.js` |
|
||||
| Featured edition section | ✅ | Editorial split layout |
|
||||
| Cultural categories | ✅ | Image tiles, ink section |
|
||||
| Bengaluru venues list | ✅ | `#venues` |
|
||||
| Immersive gallery | ✅ | Horizontal snap mobile, grid desktop |
|
||||
| Brand story | ✅ | `SectionSplit` + pull quote |
|
||||
| Final CTA + mock subscribe form | ✅ | No API |
|
||||
| Premium `MarketingFooter` | ✅ | |
|
||||
| GSAP ScrollTrigger reveals | ✅ | `useHomeScrollAnimations`, gallery stagger |
|
||||
| `prefers-reduced-motion` | ✅ | Animations skipped |
|
||||
| `overflow-x-hidden` / responsive gutters | ✅ | |
|
||||
| `npm run build` | ✅ | |
|
||||
|
||||
**Not in scope:** backend API, ticketing, auth.
|
||||
|
||||
---
|
||||
|
||||
## Phase 4 — Event details (mock data)
|
||||
|
||||
| Item | Status | Notes |
|
||||
|------|--------|-------|
|
||||
| Route `/events/:slug` | ✅ | `EventDetailsPage` |
|
||||
| Mock catalog `eventsMock.js` + `getEventBySlug` | ✅ | 5 editions incl. long title / no cover |
|
||||
| Reusable `components/event/*` sections (10 blocks) | ✅ | Hero → CTA |
|
||||
| Invalid slug empty state | ✅ | Tested `/events/does-not-exist` |
|
||||
| Homepage cards → event routes | ✅ | `EventCard` `to` prop |
|
||||
| Mobile sticky ticket bar | ✅ | Hidden `lg+` |
|
||||
| Document title per edition | ✅ | `useDocumentTitle` |
|
||||
| Payments / checkout | ⬜ | Deferred |
|
||||
| API-driven editions | ⬜ | Replace mock |
|
||||
|
||||
---
|
||||
|
||||
## Phase 5 — FastAPI backend foundation
|
||||
|
||||
| Item | Status | Notes |
|
||||
|------|--------|-------|
|
||||
| `app/core/` config, deps, errors | ✅ | Pydantic Settings, DI, handlers |
|
||||
| `app/db/` session + Base | ✅ | PostgreSQL via `DATABASE_URL` |
|
||||
| `app/models/` package | ✅ | Entities for Alembic |
|
||||
| `app/schemas/` package | ✅ | `HealthResponse` + domain schemas |
|
||||
| `app/api/routes/health.py` | ✅ | `GET /api/health` + DB ping |
|
||||
| `app/main.py` factory | ✅ | CORS, static media, router mount |
|
||||
| Legacy `app/routers/` | 🟡 | Not mounted yet |
|
||||
| `pytest` health test | ✅ | 200 or 503 with DB fields |
|
||||
| API_SPEC.md | ✅ | `/api/health` documented |
|
||||
|
||||
---
|
||||
|
||||
## Phase 6 — Event & venue data layer
|
||||
|
||||
| Item | Status | Notes |
|
||||
|------|--------|-------|
|
||||
| Models: `Venue`, `Event`, `EventMedia`, `TicketType` | ✅ | `app/models/catalog.py` |
|
||||
| Alembic `20260404_0005` | ✅ | FKs, indexes, enums |
|
||||
| `GET /api/events` + filters | ✅ | category, status, upcoming |
|
||||
| `GET /api/events/{slug}` | ✅ | Detail + media + tickets |
|
||||
| `GET /api/venues`, `/api/venues/{id}` | ✅ | |
|
||||
| Pydantic `schemas/catalog.py` | ✅ | |
|
||||
| `scripts/seed_catalog.py` | ✅ | Holi, Garba, Dandiya |
|
||||
| API tests `test_catalog_api.py` | ✅ | 8 pytest total passing |
|
||||
| DATABASE.md / API_SPEC.md | ✅ | |
|
||||
| Frontend wired to API | ✅ | Phase 7 — see below |
|
||||
|
||||
---
|
||||
|
||||
## Phase 7 — Frontend API integration
|
||||
|
||||
| Item | Status | Notes |
|
||||
|------|--------|-------|
|
||||
| `src/services/api.js` | ✅ | Axios client, `ApiError`, health |
|
||||
| `src/services/events.js` | ✅ | List, slug, view mappers |
|
||||
| `src/services/venues.js` | ✅ | List + row mapper |
|
||||
| `useAsyncQuery` + domain hooks | ✅ | `useEventsList`, `useEventDetail`, `useVenuesList` |
|
||||
| Homepage → events + venues API | ✅ | Loading / error / empty / retry |
|
||||
| Event details → slug API | ✅ | 404 empty, error retry |
|
||||
| Presentation vs data separation | ✅ | Sections receive props; no fetch in UI |
|
||||
| `RequestState` / skeletons | ✅ | Shared loading & error UI |
|
||||
| Vitest service tests | ✅ | Success, network, empty, 404 slug |
|
||||
| `npm run build` | ✅ | Production bundle |
|
||||
| Static hero/brand/categories | 🟡 | Still `homeMock.js` (non-catalog) |
|
||||
|
||||
---
|
||||
|
||||
## Phase 8 — Authentication
|
||||
|
||||
| Item | Status | Notes |
|
||||
|------|--------|-------|
|
||||
| `User` model + Alembic `20260404_0006` | ✅ | `users` table, bcrypt hashes only |
|
||||
| `POST /api/auth/register` | ✅ | Validation, 409 on duplicate |
|
||||
| `POST /api/auth/login` | ✅ | JWT `type: user` |
|
||||
| `GET /api/auth/me` | ✅ | `CurrentUser` dependency |
|
||||
| `app/core/security.py` | ✅ | Hash, verify, encode/decode JWT |
|
||||
| Config: `SECRET_KEY`, token TTL | ✅ | Production strength checks |
|
||||
| Frontend `services/auth.js` + `token.js` | ✅ | Bearer interceptor on API client |
|
||||
| `AuthProvider` + `useAuth` | ✅ | Bootstrap via `/me` |
|
||||
| Login / register / account pages | ✅ | Marketing layout |
|
||||
| `ProtectedRoute` (`/account`) | ✅ | Redirect to `/login` |
|
||||
| Navbar sign in / sign out | ✅ | |
|
||||
| Backend tests `test_auth`, `test_security` | ✅ | |
|
||||
| Frontend vitest `auth.test.js` | ✅ | |
|
||||
| Admin RBAC | ⬜ | Deferred — legacy routers not mounted |
|
||||
|
||||
---
|
||||
|
||||
## Phase 9 — Ticket inventory & orders
|
||||
|
||||
| Item | Status | Notes |
|
||||
|------|--------|-------|
|
||||
| `Order`, `OrderItem`, `Ticket` + migration `0007` | ✅ | Inventory reservation, unique codes |
|
||||
| Catalog `TicketType` inventory | ✅ | `FOR UPDATE`, oversell checks |
|
||||
| `GET /api/events/{id}/tickets` | ✅ | |
|
||||
| `POST /api/orders`, `GET` order/tickets | ✅ | Server-side pricing |
|
||||
| Concurrency tests | ✅ | `test_orders.py` |
|
||||
|
||||
---
|
||||
|
||||
## Phase 10 — Payment integration
|
||||
|
||||
| Item | Status | Notes |
|
||||
|------|--------|-------|
|
||||
| Razorpay server SDK + env secrets | ✅ | `razorpay` package |
|
||||
| `PAYMENT_PROVIDER` mock / razorpay | ✅ | Mock for local & CI |
|
||||
| Pending order → verified `paid` | ✅ | Tickets issued after confirm |
|
||||
| `POST …/payment/verify` | ✅ | Signature verification |
|
||||
| Webhooks + idempotency table | ✅ | `0008`, duplicate safe |
|
||||
| Cancel / timeout / failed payment | ✅ | Inventory release |
|
||||
| `Idempotency-Key` on checkout | ✅ | |
|
||||
| Frontend checkout (no secrets) | ✅ | `useCheckout`, Razorpay.js |
|
||||
| Tests `test_payments.py` | ✅ | verify, webhook, cancel |
|
||||
| Garba category image fix | ✅ | `homeMock` + seed alt media |
|
||||
|
||||
---
|
||||
|
||||
## Phase 12 — Admin dashboard
|
||||
|
||||
| Item | Status | Notes |
|
||||
|------|--------|-------|
|
||||
| Admin JWT (`type: admin`) separate from attendees | ✅ | `decode_admin_access_token` |
|
||||
| `POST /api/admin/auth/login`, `GET /api/admin/auth/me` | ✅ | |
|
||||
| RBAC `editor` / `super_admin` deps | ✅ | `EditorAdmin`, `SuperAdmin` |
|
||||
| Dashboard metrics API | ✅ | Revenue, tickets, orders, attendance |
|
||||
| Events / venues / ticket types CRUD | ✅ | Admin catalog services |
|
||||
| Orders & payments lists | ✅ | |
|
||||
| Admin ticket verify | ✅ | Reuses gate logic |
|
||||
| Event statistics | ✅ | Per-event breakdown |
|
||||
| Admin SPA `/admin/*` | ✅ | Tables, forms, filters |
|
||||
| `scripts/seed_admin.py` | ✅ | |
|
||||
| `test_admin_api.py` | ✅ | Attendee blocked |
|
||||
|
||||
---
|
||||
|
||||
## Phase 11 — Digital tickets
|
||||
|
||||
| Item | Status | Notes |
|
||||
|------|--------|-------|
|
||||
| `qr_token` + QR PNG generation | ✅ | Issued on payment; migration `0009` |
|
||||
| `GET /api/tickets/{id}` detail | ✅ | Owner-only digital pass |
|
||||
| `POST /api/tickets/verify` | ✅ | Check-in key, atomic `used` |
|
||||
| Ticket states `valid` / `used` / `cancelled` | ✅ | Renamed from `redeemed` |
|
||||
| React `DigitalTicket` + `/tickets/:id` | ✅ | Branding, QR, metadata |
|
||||
| Account ticket list | ✅ | Links to pass |
|
||||
| Tests `test_digital_tickets.py` | ✅ | QR detail + reuse prevention |
|
||||
|
||||
---
|
||||
|
||||
## Phase 15 — Security audit
|
||||
|
||||
| Item | Status | Notes |
|
||||
|------|--------|-------|
|
||||
| `docs/SECURITY.md` | ✅ | Assumptions, controls, prod checklist |
|
||||
| Production settings validation | ✅ | Gate key, Razorpay, CORS, admin password |
|
||||
| Mock webhook gated to development | ✅ | |
|
||||
| Admin verify requires gate key | ✅ | Constant-time key compare |
|
||||
| Auth rate limits (in-process) | ✅ | login/register/verify/payment |
|
||||
| Login password min length aligned | ✅ | 8 chars attendee + admin |
|
||||
|
||||
---
|
||||
|
||||
## Phase 14 — Production performance audit
|
||||
|
||||
| Item | Status | Notes |
|
||||
|------|--------|-------|
|
||||
| Route code splitting (marketing, admin, auth) | ✅ | `App.jsx` lazy routes |
|
||||
| Dynamic GSAP load | ✅ | `loadGsap.js` |
|
||||
| API cache + deferred featured detail | ✅ | `queryCache.js`, idle fetch |
|
||||
| Responsive Unsplash `srcSet` | ✅ | `utils/mediaUrl.js` |
|
||||
| Navbar scroll rAF throttle | ✅ | |
|
||||
| `docs/PERFORMANCE.md` | ✅ | Audit + targets |
|
||||
|
||||
---
|
||||
|
||||
## Phase 13b — Hero WebGL atmosphere (single 3D touchpoint)
|
||||
|
||||
| Item | Status | Notes |
|
||||
|------|--------|-------|
|
||||
| Home hero festival dust (Three.js) | ✅ | `src/webgl/hero-atmosphere/` only |
|
||||
| Lazy load + idle defer | ✅ | `React.lazy`, `requestIdleCallback`, separate chunk |
|
||||
| Desktop / WebGL / reduced-motion gates | ✅ | `eligibility.js` + vitest |
|
||||
| Mobile & no-WebGL fallback | ✅ | Layer not mounted; hero unchanged |
|
||||
| Offscreen pause (IntersectionObserver) | ✅ | Stops rAF when hero scrolls away |
|
||||
|
||||
---
|
||||
|
||||
## Phase 13 — Visual refinement (public site)
|
||||
|
||||
| Item | Status | Notes |
|
||||
|------|--------|-------|
|
||||
| Typography, spacing, cards (`rounded-2xl`, shadows) | ✅ | Buttons, `EventCard`, tiers, ticket pass |
|
||||
| Nav / hover / section rhythm | ✅ | Editorial nav underline, fade into content |
|
||||
| GSAP: home stagger groups + event detail reveals | ✅ | `useEventDetailAnimations`, parallax heroes |
|
||||
| `prefers-reduced-motion` CSS + GSAP guards | ✅ | `index.css`, hooks skip motion |
|
||||
| Image cropping (`object` focal) + `sizes` | ✅ | Hero, gallery, cards |
|
||||
| Vite chunk split (GSAP) | ✅ | `manualChunks` |
|
||||
| Lighthouse pass (local preview) | 🟡 | Home perf ~86, a11y 96 (preview build); LCP tuned via hero preload + no opacity-hiding H1 |
|
||||
|
||||
---
|
||||
|
||||
## Phase 2b — Polish & performance
|
||||
|
||||
| Item | Status |
|
||||
|------|--------|
|
||||
| GSAP ScrollTrigger sections | ✅ | Phase 13 |
|
||||
| `prefers-reduced-motion` (beyond GSAP init) | ✅ | Phase 13 |
|
||||
| Image lazy load on real event pages | ✅ | Lazy below fold; hero `fetchPriority` |
|
||||
| Accessibility audit (WCAG AA) | ⬜ |
|
||||
| Lighthouse performance pass | 🟡 | Chunking + LCP hints; audit ongoing |
|
||||
|
||||
---
|
||||
|
||||
## Phase 3 — Native ticketing
|
||||
|
||||
| Item | Status |
|
||||
|------|--------|
|
||||
| DB: ticket_tiers, orders, tickets | ✅ | Phase 9 |
|
||||
| Attendee users + auth | ✅ | Phase 8 |
|
||||
| Checkout + payment webhook | ✅ | Phase 10 |
|
||||
| QR generation + ticket page | ✅ | Phase 11 |
|
||||
| Staff check-in API + UI | ⬜ |
|
||||
| Email ticket delivery | ⬜ |
|
||||
|
||||
---
|
||||
|
||||
## Phase 4 — Hardening
|
||||
|
||||
| Item | Status |
|
||||
|------|--------|
|
||||
| E2E tests (critical paths) | ⬜ |
|
||||
| Rate limiting / WAF | ⬜ |
|
||||
| Production CDN for media | ⬜ |
|
||||
| Runbooks + monitoring | ⬜ |
|
||||
|
||||
---
|
||||
|
||||
## Current focus (recommended)
|
||||
|
||||
1. Mount existing API routers in `app/main.py` and verify against PostgreSQL.
|
||||
2. Add admin SPA routes and auth token handling.
|
||||
3. GSAP polish, a11y audit, Lighthouse pass (Phase 2b).
|
||||
|
||||
---
|
||||
|
||||
## Changelog
|
||||
|
||||
| Date | Change |
|
||||
|------|--------|
|
||||
| 2026-04-04 | Initial constitution docs |
|
||||
| 2026-04-04 | Phase 1a foundation: frontend scaffold, backend config/database/storage, health API, docker-compose, dev scripts |
|
||||
| 2026-04-04 | Phase 2 design system: Tailwind tokens, React primitives, `/system` showcase |
|
||||
| 2026-04-04 | Phase 3 public homepage: mock data, marketing sections, GSAP motion |
|
||||
| 2026-04-04 | Phase 4 event details: `/events/:slug`, reusable sections, mock catalog |
|
||||
| 2026-04-04 | Phase 5 backend: layered app layout, `/api/health`, PostgreSQL check |
|
||||
| 2026-04-04 | Phase 6 catalog: venues/events/media/tickets API + seed |
|
||||
| 2026-04-04 | Phase 7: services layer, hooks, homepage + event detail API wiring, vitest |
|
||||
| 2026-04-04 | Phase 8: attendee auth API, JWT, React auth flow, tests |
|
||||
| 2026-04-04 | Phase 9–10: orders, Razorpay/mock payments, webhooks, checkout UI |
|
||||
| 2026-04-04 | Phase 11: QR digital passes, verify API, ticket UI |
|
||||
| 2026-04-04 | Phase 13: design-system visual polish, motion, perf chunking |
|
||||
194
docs/SECURITY.md
Normal file
194
docs/SECURITY.md
Normal file
|
|
@ -0,0 +1,194 @@
|
|||
# Security — Desi Clans
|
||||
|
||||
**Last reviewed:** 2026-04-04
|
||||
|
||||
This document describes security assumptions, controls, and operational requirements for the Desi Clans monorepo (FastAPI API + React SPA).
|
||||
|
||||
---
|
||||
|
||||
## Threat model (summary)
|
||||
|
||||
| Asset | Primary risks |
|
||||
|-------|----------------|
|
||||
| User accounts | Credential stuffing, session theft |
|
||||
| Admin panel | Privilege abuse, catalog tampering |
|
||||
| Orders / payments | Price tampering, replay, webhook forgery |
|
||||
| Digital tickets | QR leakage, double entry, scanner abuse |
|
||||
| Media uploads | Malware, path traversal (legacy paths only) |
|
||||
|
||||
**Out of scope for v1:** DDoS at scale (use CDN/WAF in production), PCI DSS Level 1 (Razorpay.js handles card data).
|
||||
|
||||
---
|
||||
|
||||
## Authentication & JWT
|
||||
|
||||
### Attendees (`/api/auth/*`)
|
||||
|
||||
- Passwords hashed with **bcrypt** (passlib). Plain passwords never stored or returned.
|
||||
- JWT **HS256**, claim `type: "user"`, `sub` = numeric user id.
|
||||
- Register/login: Pydantic validation (email normalized, password **≥ 8** characters).
|
||||
- **Rate limit:** 20 login / 10 register requests per IP per minute (in-process).
|
||||
|
||||
### Admins (`/api/admin/auth/*`)
|
||||
|
||||
- Separate JWT claim `type: "admin"`; attendee tokens **cannot** call admin routes (tested).
|
||||
- Roles: `editor`, `super_admin` — all admin API routes currently require `editor` or above.
|
||||
- **Rate limit:** 20 login attempts per IP per minute.
|
||||
|
||||
### Frontend token storage
|
||||
|
||||
- Attendee token: `localStorage` (`desiclans_access_token`).
|
||||
- Admin token: separate key (`desiclans_admin_token`).
|
||||
- **Assumption:** XSS on the marketing or admin origin equals token compromise. Mitigate with CSP, dependency hygiene, and short TTL if needed.
|
||||
|
||||
### Production configuration
|
||||
|
||||
- `SECRET_KEY` ≥ 32 chars, not on blocklist (`config.py`).
|
||||
- `ACCESS_TOKEN_EXPIRE_MINUTES` bounded 5–20160.
|
||||
|
||||
---
|
||||
|
||||
## Authorization
|
||||
|
||||
| Endpoint class | Enforcement |
|
||||
|----------------|-------------|
|
||||
| Public catalog | No auth |
|
||||
| Orders, tickets (owner) | `CurrentUser` + `user_id` match on orders/tickets |
|
||||
| Gate verify | `X-Check-In-Key` (+ rate limit) |
|
||||
| Admin CRUD | `EditorAdmin` JWT |
|
||||
| Admin ticket verify | **Editor JWT + same gate key** as public verify |
|
||||
|
||||
**QR reuse:** `verify_ticket_at_gate` locks ticket row (`SELECT … FOR UPDATE`), sets `used` once; second scan returns `valid: false` (HTTP 200).
|
||||
|
||||
**Order manipulation:** Clients send `event_id` + line items only; **prices and totals** come from `TicketType` rows under row lock. Ticket types must belong to the event.
|
||||
|
||||
**Payment verify:** User must own order; `provider_order_id` must match order; signature verified via Razorpay/mock HMAC; amount/currency compared to `order.total_paise`.
|
||||
|
||||
---
|
||||
|
||||
## Payments & webhooks
|
||||
|
||||
| Provider | Use |
|
||||
|----------|-----|
|
||||
| `mock` | Local dev / CI only |
|
||||
| `razorpay` | Production |
|
||||
|
||||
- **`POST /api/webhooks/mock`** returns **404** unless `ENVIRONMENT=development`.
|
||||
- Razorpay webhooks: signature verified (`RAZORPAY_WEBHOOK_SECRET`).
|
||||
- Webhook events stored with **idempotency** on `provider_event_id`.
|
||||
- `POST …/payment/mock-complete` only when `development` + `mock` provider.
|
||||
|
||||
**Production startup validation** (`Settings.production_hardening`):
|
||||
|
||||
- `PAYMENT_PROVIDER=razorpay` with Razorpay secrets set.
|
||||
- `TICKET_VERIFY_API_KEY` ≥ 16 characters.
|
||||
- `ADMIN_PASSWORD` not default.
|
||||
- `CORS_ORIGINS` non-empty.
|
||||
|
||||
---
|
||||
|
||||
## CORS
|
||||
|
||||
- Explicit origin list from `CORS_ORIGINS` (no `*`).
|
||||
- `allow_credentials=True` — **do not** add untrusted origins in production.
|
||||
|
||||
---
|
||||
|
||||
## SQL injection
|
||||
|
||||
- Business logic uses **SQLAlchemy ORM** / parameterized queries.
|
||||
- Raw SQL limited to health check `SELECT 1` and Alembic migrations.
|
||||
|
||||
---
|
||||
|
||||
## Input validation
|
||||
|
||||
- Request bodies use **Pydantic** schemas (`schemas/*`).
|
||||
- Ticket verify: `qr_token` length bounds.
|
||||
- Order lines: quantity 1–20, max 20 lines.
|
||||
|
||||
---
|
||||
|
||||
## File uploads
|
||||
|
||||
- **Active `/api` router:** no upload endpoints.
|
||||
- Legacy `app/routers/admin.py` (not mounted): extension whitelist, 8MB cap, PIL re-encode to WebP.
|
||||
- **Assumption:** Keep legacy routers unmounted or remove before exposing uploads on production API.
|
||||
|
||||
---
|
||||
|
||||
## Secrets & environment
|
||||
|
||||
| Variable | Notes |
|
||||
|----------|--------|
|
||||
| `SECRET_KEY` | JWT signing; rotate with forced re-login |
|
||||
| `PAYMENT_MOCK_SECRET` | Dev only |
|
||||
| `RAZORPAY_*` | Production payments |
|
||||
| `TICKET_VERIFY_API_KEY` | Gate scanners; constant-time compare |
|
||||
| `DATABASE_URL` | Never commit; least-privilege DB user |
|
||||
|
||||
See `backend/.env.example` and `frontend/.env.example`.
|
||||
|
||||
---
|
||||
|
||||
## API error leakage
|
||||
|
||||
- Production: `debug=False`, OpenAPI disabled, generic 500 body.
|
||||
- `AppError` returns intentional `detail` strings only.
|
||||
- Development may expose exception text on 500.
|
||||
|
||||
---
|
||||
|
||||
## Rate limiting
|
||||
|
||||
In-process sliding window (`app/core/rate_limit.py`) on:
|
||||
|
||||
- Auth login/register
|
||||
- Admin login
|
||||
- Ticket verify
|
||||
- Payment verify
|
||||
|
||||
**Limitation:** Per-process only; use reverse-proxy or WAF limits for multi-instance deployments.
|
||||
|
||||
---
|
||||
|
||||
## QR codes
|
||||
|
||||
- Opaque `qr_token` (url-safe random); payload is JSON `{"v":1,"token":…}` without embedded HMAC.
|
||||
- Security relies on **HTTPS**, token secrecy, and server-side verify.
|
||||
- Ticket detail (`GET /api/tickets/{id}`) owner-only; includes QR image for valid paid orders.
|
||||
|
||||
---
|
||||
|
||||
## Frontend (checkout)
|
||||
|
||||
- Razorpay **key id** only in browser; secrets stay on server.
|
||||
- No payment card data touches Desi Clans backend.
|
||||
|
||||
---
|
||||
|
||||
## Scanner / admin verify UI
|
||||
|
||||
- Staff UI calls `POST /api/tickets/verify` with optional `VITE_TICKET_VERIFY_API_KEY` → `X-Check-In-Key` header.
|
||||
- In development, if `TICKET_VERIFY_API_KEY` is unset, verify is open (local convenience only).
|
||||
|
||||
---
|
||||
|
||||
## Checklist before production
|
||||
|
||||
1. Set strong `SECRET_KEY`, `TICKET_VERIFY_API_KEY`, Razorpay secrets, non-default `ADMIN_PASSWORD`.
|
||||
2. `ENVIRONMENT=production`, `PAYMENT_PROVIDER=razorpay`.
|
||||
3. Tight `CORS_ORIGINS` to real site origin(s).
|
||||
4. TLS everywhere; HSTS at edge.
|
||||
5. Do not mount legacy `app/routers/*` without security review.
|
||||
6. Enable edge rate limiting and monitoring on auth/webhook paths.
|
||||
|
||||
---
|
||||
|
||||
## Related tests
|
||||
|
||||
- `tests/test_security.py` — JWT types, bcrypt
|
||||
- `tests/test_security_hardening.py` — production settings, mock webhook
|
||||
- `tests/test_payments.py` — verify + webhook idempotency
|
||||
- `tests/test_digital_tickets.py` — QR reuse
|
||||
- `tests/test_admin_api.py` — attendee blocked from admin
|
||||
Loading…
Add table
Add a link
Reference in a new issue