# 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://: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.