311 lines
10 KiB
Markdown
311 lines
10 KiB
Markdown
# 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.
|