Full app rebuild: FastAPI backend + React Native mobile with auth, championships, admin

Backend (FastAPI + SQLAlchemy + SQLite):
- JWT auth with access/refresh tokens, bcrypt password hashing
- User model with member/organizer/admin roles, auto-approve members
- Championship, Registration, ParticipantList, Notification models
- Alembic async migrations, seed data with test users
- Registration endpoint returns tokens for members, pending for organizers
- /registrations/my returns championship title/date/location via eager loading
- Admin endpoints: list users, approve/reject organizers

Mobile (React Native + Expo + TypeScript):
- Zustand auth store, Axios client with token refresh interceptor
- Role-based registration (Member vs Organizer) with contextual form labels
- Tab navigation with Ionicons, safe area headers, admin tab for admin role
- Championships list with status badges, detail screen with registration progress
- My Registrations with championship title, progress bar, and tap-to-navigate
- Admin panel with pending/all filter, approve/reject with confirmation
- Profile screen with role badge, Ionicons info rows, sign out
- Password visibility toggle (Ionicons), keyboard flow hints (returnKeyType)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Dianaka123
2026-02-25 22:46:50 +03:00
parent 9eb68695e9
commit 789d2bf0a6
81 changed files with 16283 additions and 310 deletions

View File

@@ -0,0 +1,357 @@
# DanceChamp — Database Schema
## Overview
Backend: **Supabase** (PostgreSQL + Auth + Storage + Realtime)
All tables use `uuid` primary keys generated by `gen_random_uuid()`.
All tables have `created_at` and `updated_at` timestamps.
---
## Tables
### users
Extended from Supabase Auth. This is a `public.users` table that mirrors `auth.users` via trigger.
```sql
create table public.users (
id uuid primary key references auth.users(id) on delete cascade,
email text not null,
name text not null,
role text not null check (role in ('admin', 'organization', 'member')),
city text,
instagram_handle text,
experience_years integer,
disciplines text[] default '{}', -- ['Pole Exotic', 'Pole Art']
auth_provider text default 'email', -- 'email' | 'google' | 'instagram'
avatar_url text,
status text not null default 'active' check (status in ('active', 'warned', 'blocked')),
warn_reason text,
block_reason text,
created_at timestamptz default now(),
updated_at timestamptz default now()
);
```
### organizations
One-to-one with a user (role = 'organization').
```sql
create table public.organizations (
id uuid primary key default gen_random_uuid(),
user_id uuid not null references public.users(id) on delete cascade,
name text not null,
instagram_handle text,
email text,
city text,
logo_url text,
verified boolean not null default false,
status text not null default 'pending' check (status in ('active', 'pending', 'blocked')),
block_reason text,
created_at timestamptz default now(),
updated_at timestamptz default now()
);
```
### championships
Belongs to an organization. Core entity.
```sql
create table public.championships (
id uuid primary key default gen_random_uuid(),
org_id uuid not null references public.organizations(id) on delete cascade,
name text not null,
subtitle text,
event_date text, -- "May 30, 2026" or ISO date
reg_start text, -- registration opens
reg_end text, -- registration closes (must be before event_date)
location text, -- "Minsk, Belarus"
venue text, -- "Prime Hall"
accent_color text default '#D4145A',
image_emoji text default '💃',
status text not null default 'draft' check (status in ('draft', 'pending_approval', 'live', 'completed', 'blocked')),
-- configurable sections progress
config_info boolean not null default false,
config_categories boolean not null default false,
config_fees boolean not null default false,
config_rules boolean not null default false,
config_judges boolean not null default false,
-- links
form_url text, -- Google Forms URL
rules_doc_url text, -- Rules document URL
created_at timestamptz default now(),
updated_at timestamptz default now()
);
```
### disciplines
Championship has many disciplines. Each discipline has levels.
```sql
create table public.disciplines (
id uuid primary key default gen_random_uuid(),
championship_id uuid not null references public.championships(id) on delete cascade,
name text not null, -- "Exotic Pole Dance"
levels text[] default '{}', -- ['Beginners', 'Amateur', 'Semi-Pro', 'Profi', 'Elite']
sort_order integer default 0,
created_at timestamptz default now()
);
```
### styles
Championship-level styles (not per-discipline).
```sql
create table public.styles (
id uuid primary key default gen_random_uuid(),
championship_id uuid not null references public.championships(id) on delete cascade,
name text not null, -- "Classic", "Flow", "Theater"
sort_order integer default 0,
created_at timestamptz default now()
);
```
### fees
One-to-one with championship.
```sql
create table public.fees (
id uuid primary key default gen_random_uuid(),
championship_id uuid not null unique references public.championships(id) on delete cascade,
video_selection text, -- "50 BYN / 1,500 RUB"
solo text, -- "280 BYN / 7,500 RUB"
duet text, -- "210 BYN / 5,800 RUB pp"
"group" text, -- "190 BYN / 4,500 RUB pp"
refund_note text,
created_at timestamptz default now(),
updated_at timestamptz default now()
);
```
### rules
Championship has many rules across sections.
```sql
create table public.rules (
id uuid primary key default gen_random_uuid(),
championship_id uuid not null references public.championships(id) on delete cascade,
section text not null check (section in ('general', 'costume', 'scoring', 'penalty')),
name text not null, -- rule text or criterion name
value text, -- for scoring: "10" (max), for penalty: "-2" or "DQ"
sort_order integer default 0,
created_at timestamptz default now()
);
```
### judges
Championship has many judges.
```sql
create table public.judges (
id uuid primary key default gen_random_uuid(),
championship_id uuid not null references public.championships(id) on delete cascade,
name text not null,
instagram text,
bio text,
photo_url text,
sort_order integer default 0,
created_at timestamptz default now()
);
```
### registrations
Links a member to a championship. Tracks the 10-step progress.
```sql
create table public.registrations (
id uuid primary key default gen_random_uuid(),
user_id uuid not null references public.users(id) on delete cascade,
championship_id uuid not null references public.championships(id) on delete cascade,
discipline_id uuid references public.disciplines(id),
level text, -- "Semi-Pro"
style text, -- "Classic"
participation_type text default 'solo' check (participation_type in ('solo', 'duet', 'group')),
-- Progress steps (step 110)
step_rules_reviewed boolean default false,
step_category_selected boolean default false,
step_video_recorded boolean default false,
step_form_submitted boolean default false,
step_video_fee_paid boolean default false, -- confirmed by org
step_video_fee_receipt_url text, -- uploaded receipt
step_results text check (step_results in ('pending', 'passed', 'failed')),
step_champ_fee_paid boolean default false,
step_champ_fee_receipt_url text,
step_about_me_submitted boolean default false,
step_insurance_confirmed boolean default false,
step_insurance_doc_url text,
-- Video
video_url text,
-- Meta
current_step integer default 1,
created_at timestamptz default now(),
updated_at timestamptz default now(),
unique(user_id, championship_id)
);
```
### notifications
Push to member's in-app feed.
```sql
create table public.notifications (
id uuid primary key default gen_random_uuid(),
user_id uuid not null references public.users(id) on delete cascade,
championship_id uuid references public.championships(id) on delete set null,
type text not null check (type in (
'category_changed', 'payment_confirmed', 'results',
'deadline_reminder', 'registration_confirmed', 'announcement',
'champ_approved', 'champ_rejected', 'org_approved', 'org_rejected'
)),
title text not null,
message text not null,
read boolean not null default false,
created_at timestamptz default now()
);
```
### activity_logs
Admin audit trail.
```sql
create table public.activity_logs (
id uuid primary key default gen_random_uuid(),
actor_id uuid references public.users(id) on delete set null,
action text not null, -- "org_approved", "user_blocked", "champ_auto_approved"
target_type text not null, -- "organization", "championship", "user"
target_id uuid,
target_name text, -- denormalized for display
details jsonb, -- extra context
created_at timestamptz default now()
);
```
---
## Relationships Diagram
```
users (1) ──── (1) organizations
│ has many
championships
┌────┼────┬────┬────┐
│ │ │ │ │
disciplines styles fees rules judges
registrations ─┘
(user + championship)
notifications
```
---
## Row Level Security (RLS)
Enable RLS on all tables.
### users
```sql
-- Members can read/update their own row
create policy "Users can read own" on users for select using (auth.uid() = id);
create policy "Users can update own" on users for update using (auth.uid() = id);
-- Org admins can read members registered to their championships
create policy "Orgs can read their members" on users for select using (
id in (
select r.user_id from registrations r
join championships c on r.championship_id = c.id
join organizations o on c.org_id = o.id
where o.user_id = auth.uid()
)
);
-- Admin can read/update all
create policy "Admin full access" on users for all using (
exists (select 1 from users where id = auth.uid() and role = 'admin')
);
```
### championships
```sql
-- Anyone can read live championships
create policy "Public read live" on championships for select using (status = 'live');
-- Org can CRUD their own
create policy "Org manages own" on championships for all using (
org_id in (select id from organizations where user_id = auth.uid())
);
-- Admin full access
create policy "Admin full access" on championships for all using (
exists (select 1 from users where id = auth.uid() and role = 'admin')
);
```
### registrations
```sql
-- Members can read/create their own
create policy "Member own registrations" on registrations for select using (user_id = auth.uid());
create policy "Member can register" on registrations for insert with check (user_id = auth.uid());
-- Org can read/update registrations for their championships
create policy "Org manages registrations" on registrations for all using (
championship_id in (
select c.id from championships c
join organizations o on c.org_id = o.id
where o.user_id = auth.uid()
)
);
-- Admin full access
create policy "Admin full access" on registrations for all using (
exists (select 1 from users where id = auth.uid() and role = 'admin')
);
```
### notifications
```sql
-- Users can read their own notifications
create policy "Read own" on notifications for select using (user_id = auth.uid());
-- Users can mark their own as read
create policy "Update own" on notifications for update using (user_id = auth.uid());
```
---
## Storage Buckets
```
receipts/ -- Payment receipt screenshots
{user_id}/{registration_id}/receipt.jpg
insurance/ -- Insurance documents
{user_id}/{registration_id}/insurance.pdf
judge-photos/ -- Judge profile photos
{championship_id}/{judge_id}.jpg
org-logos/ -- Organization logos
{org_id}/logo.jpg
```
---
## Seed Data
For development, seed with:
- 1 admin user
- 2 organizations (1 verified, 1 unverified/pending)
- 2 championships for verified org (1 live, 1 draft)
- 1 championship for unverified org (pending_approval)
- 7 member users with registrations at various progress stages
- Sample notifications, activity logs
This matches the prototype demo data.