Clear project — starting fresh from spec
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
310
dance-champ-app.md
Normal file
310
dance-champ-app.md
Normal file
@@ -0,0 +1,310 @@
|
||||
# Pole Dance Championships App — Technical Document
|
||||
|
||||
## 1. Project Overview
|
||||
|
||||
### Problem
|
||||
|
||||
Pole dance championships lack dedicated tools for organizers and participants. Organizers rely on Instagram posts to announce events, and registrations are managed via Google Forms — leading to:
|
||||
|
||||
- **No visibility** — Participants can't track their registration status in one place.
|
||||
- **Scattered info** — Dates, rules, and results are buried across Instagram posts.
|
||||
- **No access control** — Anyone can register; organizers need a way to accept or reject participants.
|
||||
|
||||
### Solution
|
||||
|
||||
A **members-only mobile app** for pole dance championship participants and organizers.
|
||||
|
||||
- New users register and wait for admin approval before accessing the app.
|
||||
- Championship announcements are auto-imported from Instagram (Graph API).
|
||||
- Members browse championships, submit registrations, and track their status.
|
||||
- Organizers accept, reject, or waitlist participants and publish the final participant list.
|
||||
- Push notifications alert members when results are published.
|
||||
|
||||
---
|
||||
|
||||
## 2. User Roles
|
||||
|
||||
| Role | Access |
|
||||
|---|---|
|
||||
| **Member** | Browse championships, register, track own registration status |
|
||||
| **Organizer** | All of the above + manage registrations, publish participant lists |
|
||||
| **Admin** | All of the above + approve/reject new member accounts |
|
||||
|
||||
New accounts start as `pending` and must be approved by an admin before they can use the app.
|
||||
|
||||
---
|
||||
|
||||
## 3. Tech Stack
|
||||
|
||||
| Layer | Technology |
|
||||
|---|---|
|
||||
| **Mobile** | React Native + TypeScript (Expo managed workflow) |
|
||||
| **Navigation** | React Navigation v6 |
|
||||
| **Server state** | TanStack React Query v5 |
|
||||
| **Client state** | Zustand |
|
||||
| **Backend** | FastAPI (Python, async) |
|
||||
| **ORM** | SQLAlchemy 2.0 async |
|
||||
| **Database** | SQLite (local dev) / PostgreSQL (production) |
|
||||
| **Migrations** | Alembic |
|
||||
| **Auth** | JWT — access tokens (15 min) + refresh tokens (7 days, rotation-based) |
|
||||
| **Instagram sync** | Instagram Graph API, polled every 30 min via APScheduler |
|
||||
| **Push notifications** | Expo Push API (routes to FCM + APNs) |
|
||||
| **Token storage** | expo-secure-store (with in-memory cache for reliability) |
|
||||
|
||||
---
|
||||
|
||||
## 4. Architecture
|
||||
|
||||
```
|
||||
┌─────────────────────┐ ┌──────────────────────────────┐
|
||||
│ React Native App │◄──────►│ FastAPI Backend │
|
||||
│ (Expo Go / APK) │ HTTPS │ /api/v1/... │
|
||||
└─────────────────────┘ │ │
|
||||
│ ┌──────────────────────┐ │
|
||||
│ │ APScheduler │ │
|
||||
│ │ - Instagram poll/30m│ │
|
||||
│ │ - Token refresh/7d │ │
|
||||
│ └──────────────────────┘ │
|
||||
│ │
|
||||
│ ┌──────────────────────┐ │
|
||||
│ │ SQLite / PostgreSQL │ │
|
||||
│ └──────────────────────┘ │
|
||||
└──────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Data Model
|
||||
|
||||
### User
|
||||
```
|
||||
id UUID (PK)
|
||||
email String (unique)
|
||||
full_name String
|
||||
phone String (nullable)
|
||||
hashed_password String
|
||||
role Enum: member | organizer | admin
|
||||
status Enum: pending | approved | rejected
|
||||
expo_push_token String (nullable)
|
||||
created_at DateTime
|
||||
updated_at DateTime
|
||||
```
|
||||
|
||||
### Championship
|
||||
```
|
||||
id UUID (PK)
|
||||
title String
|
||||
description String (nullable)
|
||||
location String (nullable)
|
||||
event_date DateTime (nullable)
|
||||
registration_open_at DateTime (nullable)
|
||||
registration_close_at DateTime (nullable)
|
||||
status Enum: draft | open | closed | completed
|
||||
source Enum: manual | instagram
|
||||
instagram_media_id String (nullable, unique)
|
||||
image_url String (nullable)
|
||||
raw_caption_text Text (nullable)
|
||||
created_at DateTime
|
||||
updated_at DateTime
|
||||
```
|
||||
|
||||
### Registration
|
||||
```
|
||||
id UUID (PK)
|
||||
championship_id UUID (FK → Championship)
|
||||
user_id UUID (FK → User)
|
||||
category String (nullable)
|
||||
level String (nullable)
|
||||
notes Text (nullable)
|
||||
status Enum: submitted | accepted | rejected | waitlisted
|
||||
submitted_at DateTime
|
||||
decided_at DateTime (nullable)
|
||||
```
|
||||
|
||||
### ParticipantList
|
||||
```
|
||||
id UUID (PK)
|
||||
championship_id UUID (FK → Championship, unique)
|
||||
published_by UUID (FK → User)
|
||||
is_published Boolean
|
||||
published_at DateTime (nullable)
|
||||
notes Text (nullable)
|
||||
```
|
||||
|
||||
### RefreshToken
|
||||
```
|
||||
id UUID (PK)
|
||||
user_id UUID (FK → User)
|
||||
token_hash String (SHA-256, unique)
|
||||
expires_at DateTime
|
||||
revoked Boolean
|
||||
created_at DateTime
|
||||
```
|
||||
|
||||
### NotificationLog
|
||||
```
|
||||
id UUID (PK)
|
||||
user_id UUID (FK → User)
|
||||
championship_id UUID (FK → Championship, nullable)
|
||||
title String
|
||||
body String
|
||||
sent_at DateTime
|
||||
status String
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. API Endpoints
|
||||
|
||||
### Auth
|
||||
| Method | Path | Description |
|
||||
|---|---|---|
|
||||
| POST | `/api/v1/auth/register` | Register new account (status=pending) |
|
||||
| POST | `/api/v1/auth/login` | Login, get access + refresh tokens |
|
||||
| POST | `/api/v1/auth/refresh` | Refresh access token |
|
||||
| POST | `/api/v1/auth/logout` | Revoke refresh token |
|
||||
| GET | `/api/v1/auth/me` | Get current user |
|
||||
|
||||
### Championships
|
||||
| Method | Path | Access |
|
||||
|---|---|---|
|
||||
| GET | `/api/v1/championships` | Approved members |
|
||||
| GET | `/api/v1/championships/{id}` | Approved members |
|
||||
| POST | `/api/v1/championships` | Organizer+ |
|
||||
| PATCH | `/api/v1/championships/{id}` | Organizer+ |
|
||||
| DELETE | `/api/v1/championships/{id}` | Admin |
|
||||
|
||||
### Registrations
|
||||
| Method | Path | Access |
|
||||
|---|---|---|
|
||||
| POST | `/api/v1/championships/{id}/register` | Approved member |
|
||||
| GET | `/api/v1/championships/{id}/registrations` | Organizer+ |
|
||||
| PATCH | `/api/v1/registrations/{id}/status` | Organizer+ |
|
||||
| GET | `/api/v1/registrations/mine` | Member (own only) |
|
||||
| DELETE | `/api/v1/registrations/{id}` | Member (own, before decision) |
|
||||
|
||||
### Participant Lists
|
||||
| Method | Path | Access |
|
||||
|---|---|---|
|
||||
| GET | `/api/v1/championships/{id}/participant-list` | Approved member |
|
||||
| PUT | `/api/v1/championships/{id}/participant-list` | Organizer+ |
|
||||
| POST | `/api/v1/championships/{id}/participant-list/publish` | Organizer+ |
|
||||
| POST | `/api/v1/championships/{id}/participant-list/unpublish` | Organizer+ |
|
||||
|
||||
### Users (Admin)
|
||||
| Method | Path | Description |
|
||||
|---|---|---|
|
||||
| GET | `/api/v1/users` | List all users |
|
||||
| PATCH | `/api/v1/users/{id}/status` | Approve/reject member |
|
||||
| PATCH | `/api/v1/users/me/push-token` | Register push token |
|
||||
|
||||
---
|
||||
|
||||
## 7. Mobile Screens
|
||||
|
||||
### Auth Flow
|
||||
- **LoginScreen** — Email + password login
|
||||
- **RegisterScreen** — Full name, phone, email, password
|
||||
- **PendingApprovalScreen** — Shown after register until admin approves
|
||||
|
||||
### App (approved members)
|
||||
- **ChampionshipListScreen** — All championships with status badges
|
||||
- **ChampionshipDetailScreen** — Full info + registration button
|
||||
- **RegistrationFormScreen** — Category, level, notes
|
||||
- **ProfileScreen** — User info, logout
|
||||
|
||||
### Navigation
|
||||
```
|
||||
No user ──► AuthStack (Login / Register)
|
||||
Pending ──► PendingApprovalScreen
|
||||
Approved ──► AppStack (Championships / Profile)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. Instagram Integration
|
||||
|
||||
### How It Works
|
||||
1. Admin configures `INSTAGRAM_USER_ID` and `INSTAGRAM_ACCESS_TOKEN` in `.env`.
|
||||
2. APScheduler polls the Instagram Graph API every 30 minutes.
|
||||
3. New media posts are parsed: title (first line of caption), location (`Место:` / `Location:` prefix), dates (regex for RU + EN date formats).
|
||||
4. New championships are created with `status=draft` for admin review.
|
||||
5. Long-lived tokens are refreshed weekly automatically.
|
||||
|
||||
### Parsing Logic
|
||||
- **Title** — First non-empty line of the caption.
|
||||
- **Location** — Line starting with `Место:` or `Location:`.
|
||||
- **Date** — Regex matches formats like `15 апреля 2026`, `April 15, 2026`, `15.04.2026`.
|
||||
- **Deduplication** — Championships are matched by `instagram_media_id`.
|
||||
|
||||
---
|
||||
|
||||
## 9. Local Development Setup
|
||||
|
||||
### Prerequisites
|
||||
- Python 3.11+
|
||||
- Node.js 18+
|
||||
- Expo Go app on phone
|
||||
|
||||
### Backend
|
||||
```bat
|
||||
cd backend
|
||||
python -m venv .venv
|
||||
.venv\Scripts\pip install -r requirements.txt
|
||||
alembic upgrade head
|
||||
python scripts/create_admin.py # creates admin@poledance.app / Admin1234
|
||||
uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload
|
||||
```
|
||||
|
||||
Or use: `start-backend.bat`
|
||||
|
||||
### Mobile
|
||||
```bat
|
||||
cd mobile
|
||||
npm install
|
||||
npx expo start --lan --clear
|
||||
```
|
||||
|
||||
Or use: `start-mobile.bat`
|
||||
|
||||
### Environment (backend/.env)
|
||||
```
|
||||
DATABASE_URL=sqlite+aiosqlite:///./poledance.db
|
||||
SECRET_KEY=dev-secret-key-change-before-production-32x
|
||||
INSTAGRAM_USER_ID=
|
||||
INSTAGRAM_ACCESS_TOKEN=
|
||||
INSTAGRAM_POLL_INTERVAL=1800
|
||||
```
|
||||
|
||||
### Environment (mobile/.env)
|
||||
```
|
||||
EXPO_PUBLIC_API_URL=http://<your-local-ip>:8000/api/v1
|
||||
```
|
||||
|
||||
### Test Accounts
|
||||
| Email | Password | Role | Status |
|
||||
|---|---|---|---|
|
||||
| admin@poledance.app | Admin1234 | admin | approved |
|
||||
| anna@example.com | Member1234 | member | approved |
|
||||
| maria@example.com | Member1234 | member | approved |
|
||||
| elena@example.com | Member1234 | member | pending |
|
||||
|
||||
---
|
||||
|
||||
## 10. Known Limitations (Local Dev)
|
||||
|
||||
- **SQLite** is used instead of PostgreSQL — change `DATABASE_URL` for production.
|
||||
- **Push notifications** don't work in Expo Go SDK 53 — requires a development build.
|
||||
- **Instagram polling** requires a valid Business/Creator account token.
|
||||
- Windows Firewall must allow inbound TCP on port 8000 for phone to reach the backend.
|
||||
|
||||
---
|
||||
|
||||
## 11. Future Features
|
||||
|
||||
- **Web admin panel** — Browser-based dashboard for organizers to manage registrations and championships.
|
||||
- **In-app notifications feed** — History of received push notifications.
|
||||
- **Calendar sync** — Export championship dates to phone calendar.
|
||||
- **Production deployment** — Docker + PostgreSQL + nginx + SSL.
|
||||
- **OCR / LLM parsing** — Better extraction of championship details from Instagram images and captions.
|
||||
- **Multi-organizer** — Support multiple Instagram accounts for different championships.
|
||||
Reference in New Issue
Block a user