# EL3AB Supabase Master Audit

**Last Updated:** 2026-06-03
**Database:** PostgreSQL 15.8.1 (Supabase self-hosted)
**Host:** 3.68.63.185 (Docker container: safe-supabase-db)

---

## Discovery Progress Tracker

| Resource | Scanned | Total | Status |
|----------|---------|-------|--------|
| Schemas | 5/5 | 5 relevant | COMPLETE |
| Tables | 106/106 | 106 | COMPLETE |
| Views | 0/0 | 0 | COMPLETE (none exist) |
| Functions (business) | 11/11 | 11 | COMPLETE |
| Functions (pgvector) | 118/118 | 118 | COMPLETE (extension) |
| RLS Policies | 111/111 | 111 | COMPLETE |
| Triggers | 1/1 | 1 | COMPLETE |
| Storage Buckets | 9/9 | 9 | COMPLETE |
| Enum Types | 22/22 | 22 | COMPLETE |
| Foreign Keys | 165/165 | 165 | COMPLETE |
| Realtime Tables | 106/106 | ALL public tables | COMPLETE |

---

## Schemas

| Schema | Purpose |
|--------|---------|
| public | Main application schema — all 106 business tables |
| El3ab | Custom schema (currently empty — no tables) |
| auth | Supabase GoTrue authentication (users, sessions, tokens) |
| storage | File storage (buckets, objects) |
| realtime | Supabase Realtime message partitions |
| _analytics | Supabase Logflare analytics |
| _realtime | Realtime tenants/extensions |
| _supavisor | Connection pooler |
| extensions | PostgreSQL extensions |
| graphql / graphql_public | pg_graphql (auto-generated) |

---

## Custom Enum Types (22)

| Enum Name | Values |
|-----------|--------|
| audit_action | create, update, delete, pair_round, unpair_round, enter_result, modify_result, generate_standings, export_trf |
| color | white, black |
| cosmetic_rarity | common, uncommon, rare, epic, legendary |
| cosmetic_type | avatar_frame, board_theme, piece_set, profile_banner, chat_emoji, victory_animation, title_badge, trail_effect, sound_pack |
| game_result | white_wins, black_wins, draw, white_forfeit, black_forfeit, double_forfeit, bye_full, bye_half, bye_zero, not_played |
| match_result | white_wins, black_wins, draw, white_timeout, black_timeout, white_resign, black_resign, white_abandon, black_abandon, stalemate, insufficient_material, threefold_repetition, fifty_moves, mutual_draw, aborted |
| match_status | waiting, ready, in_progress, paused, completed, aborted, abandoned |
| oban_job_state | available, scheduled, executing, retryable, completed, discarded, cancelled |
| org_membership_status | active, suspended, invited |
| platform_role | superadmin, platform_admin, org_owner, org_admin, tournament_director, arbiter, content_manager, match_moderator, sponsor_admin, player, spectator |
| round_status | pending, paired, in_progress, completed |
| tiebreak_type | buchholz, buchholz_cut_1, buchholz_median, sonneborn_berger, direct_encounter, number_of_wins, number_of_blacks, koya, progressive_score, average_rating_opponents, performance_rating |
| time_control | bullet_1_0, bullet_1_1, bullet_2_1, blitz_3_0, blitz_3_2, blitz_5_0, blitz_5_3, rapid_10_0, rapid_10_5, rapid_15_10, rapid_30_0, classical_60_0, classical_90_30, custom |
| time_control_type | standard, rapid, blitz, bullet |
| tournament_format | swiss, round_robin, single_elimination, double_elimination, swiss_to_bracket, arena, team_battle |
| tournament_status | draft, registration, in_progress, completed, cancelled |
| tournament_type | swiss, round_robin, double_round_robin |
| user_role | super_admin, org_admin, arbiter, player, spectator |

---

## Triggers (1)

| Trigger | Schema | Table | Event | Function |
|---------|--------|-------|-------|----------|
| on_auth_user_created | auth | users | AFTER INSERT | public.handle_new_user() |

**Purpose:** When a new user signs up via GoTrue, automatically creates a profile row in `public.profiles` with username from metadata (or generated), display_name defaulting to "لاعب جديد", and preferred_language = 'ar'.

---

## Storage Buckets (9)

| Bucket | Public | Purpose |
|--------|--------|---------|
| avatars | YES | User avatar images |
| chat-attachments | NO | Chat file attachments |
| membership-proofs | NO | Org membership proof documents |
| org-banners | YES | Organization banner images |
| org-content-attachments | NO | Organization content attachments |
| org-logos | YES | Organization logo images |
| org-media | YES | Organization media files |
| profile-frames | YES | Profile frame assets |
| profile-images | YES | User profile images |

### Storage Policies (on storage.objects)

| Policy | Bucket | Operation | Condition |
|--------|--------|-----------|-----------|
| Public read profile images | profile-images | SELECT | bucket_id = 'profile-images' |
| Users upload own avatar | profile-images | INSERT | (any authenticated) |
| Users update own avatar | profile-images | UPDATE | user owns folder |
| Users delete own avatar | profile-images | DELETE | user owns folder |

---

## Business Functions (11)

### handle_new_user()
- **Returns:** trigger
- **Security:** DEFINER
- **Purpose:** Creates profile on user signup
- **Logic:** INSERT INTO profiles (id, username, display_name, preferred_language) using auth metadata

### cancel_tournament_registration(p_tournament_id integer)
- **Returns:** json
- **Security:** DEFINER
- **Purpose:** Player cancels tournament registration
- **Logic:** DELETE from tournament_players, decrement tournaments.current_players
- **Response:** `{status: 'success', message: 'تم إلغاء التسجيل'}`

### check_app_version(client_version text)
- **Returns:** json
- **Security:** DEFINER
- **Purpose:** Mobile app version check (references app_version table — MAY NOT EXIST)
- **Logic:** Compare client version with latest min_app_version

### check_opponent_timeout(p_bracket_id integer, p_user_numeric_id integer)
- **Returns:** json
- **Security:** DEFINER
- **Purpose:** Check if tournament bracket opponent has timed out
- **Logic:** Checks scheduled_time vs max_join_delay, verifies opponent check-in status
- **Responses:** already_finished / opponent_timeout / waiting

### get_all_tournaments()
- **Returns:** json
- **Security:** DEFINER
- **Purpose:** Get all tournaments with players and brackets (for mobile Unity app)
- **Logic:** Joins tournaments → tournament_players → profiles, tournaments → tournament_brackets
- **Note:** Uses `profiles.numeric_id`, `profiles.unity_auth_id`, `profiles.profile_image_url` columns

### get_friends_by_auth_ids(auth_ids text[])
- **Returns:** json
- **Security:** DEFINER
- **Purpose:** Get friend profiles by unity_auth_id array
- **Logic:** Returns username, club_name, profile_image_url for matching auth IDs

### get_tournament_by_id(p_id integer)
- **Returns:** json
- **Security:** DEFINER
- **Purpose:** Get single tournament with full player/bracket data
- **Logic:** Same structure as get_all_tournaments but filtered by ID

### get_tournament_room(p_tournament_id integer, p_bracket_id integer)
- **Returns:** json
- **Security:** DEFINER
- **Purpose:** Get lobby ID for tournament match
- **Logic:** Checks timing, returns lobby_id from tournament_brackets
- **Responses:** late / success (with lobby_id) / empty

### get_user_org_ids()
- **Returns:** uuid[]
- **Security:** DEFINER, STABLE
- **Purpose:** Helper for RLS — returns org IDs where user has active membership
- **Logic:** SELECT organization_id FROM org_memberships WHERE user_id = auth.uid() AND status = 'active'

### register_tournament_player(p_tournament_id integer, p_player_auth_id text)
- **Returns:** json
- **Security:** DEFINER
- **Purpose:** Register player for tournament
- **Logic:** Validates status = 'registration_open', checks capacity, inserts tournament_player
- **Responses:** error (closed/full) / success

### user_has_role_in_org(p_org_id uuid, p_roles user_role[])
- **Returns:** boolean
- **Security:** DEFINER, STABLE
- **Purpose:** Helper for RLS — checks if user has any of given roles in org
- **Logic:** EXISTS check on org_memberships

---

## Tables (106) — Grouped by Domain

### Domain: Core Player

#### profiles
**Purpose:** Central player profile — ratings, economy, status, identity

| Column | Type | Nullable | Default |
|--------|------|----------|---------|
| id | uuid | NO | — |
| username | text | NO | — |
| display_name | text | YES | — |
| display_name_ar | text | YES | — |
| avatar_url | text | YES | — |
| banner_url | text | YES | — |
| bio | text | YES | — |
| bio_ar | text | YES | — |
| country_code | char(2) | YES | — |
| city | text | YES | — |
| preferred_language | text | YES | 'ar' |
| elo_bullet | integer | YES | — |
| elo_blitz | integer | YES | — |
| elo_rapid | integer | YES | — |
| elo_classical | integer | YES | — |
| fide_id | text | YES | — |
| fide_rating_standard | integer | YES | — |
| fide_rating_rapid | integer | YES | — |
| fide_rating_blitz | integer | YES | — |
| fide_title | text | YES | — |
| xp | integer | YES | 0 |
| level | integer | YES | 1 |
| coins | integer | YES | 0 |
| gems | integer | YES | 0 |
| is_online | boolean | YES | false |
| last_seen_at | timestamptz | YES | — |
| is_banned | boolean | YES | false |
| ban_reason | text | YES | — |
| ban_expires_at | timestamptz | YES | — |
| total_games_played | integer | YES | 0 |
| total_wins | integer | YES | 0 |
| total_draws | integer | YES | 0 |
| total_losses | integer | YES | 0 |
| total_tournaments_played | integer | YES | 0 |
| total_tournaments_won | integer | YES | 0 |
| win_streak | integer | YES | 0 |
| best_win_streak | integer | YES | 0 |
| created_at | timestamptz | YES | now() |
| updated_at | timestamptz | YES | now() |
| premium_currency | integer | YES | — |
| last_daily_reward | timestamptz | YES | — |
| daily_streak | integer | YES | 0 |
| banned_by | uuid | YES | — |
| current_game | uuid | YES | — |
| games_played | integer | YES | 0 |
| avatar_frame_id | text | YES | — |
| avatar_border_color | text | YES | — |
| active_org_frame_id | uuid | YES | — |
| numeric_id | integer | YES | auto |
| unity_auth_id | text | YES | — |
| profile_image_url | text | YES | — |
| club_name | text | YES | — |

**RLS:** Public SELECT, own-user UPDATE

---

#### user_profiles
**Purpose:** Alternate/legacy profile table (used by Swiss system integration)

| Column | Type | Nullable | Default |
|--------|------|----------|---------|
| id | uuid | NO | — |
| full_name | text | YES | — |
| fide_id | text | YES | — |
| fide_rating_standard | integer | YES | — |
| fide_rating_rapid | integer | YES | — |
| fide_rating_blitz | integer | YES | — |
| fide_title | text | YES | — |
| nationality | text | YES | — |
| birth_date | date | YES | — |
| gender | text | YES | — |
| email | text | YES | — |
| phone | text | YES | — |
| avatar_url | text | YES | — |
| created_at | timestamptz | NO | now() |
| updated_at | timestamptz | NO | now() |

**RLS:** Own-user SELECT, own-user UPDATE

---

#### player_game_ratings
**Purpose:** Per-game rating tracking (multi-game support)

| Column | Type | Nullable | Default |
|--------|------|----------|---------|
| id | uuid | NO | gen_random_uuid() |
| player_id | uuid | NO | — |
| game_key | text | NO | — |
| rating | integer | NO | 1200 |
| games_played | integer | YES | 0 |
| wins | integer | YES | 0 |
| losses | integer | YES | 0 |
| draws | integer | YES | 0 |
| last_played_at | timestamptz | YES | — |
| updated_at | timestamptz | YES | now() |

**RLS:** Public SELECT, service_role ALL

---

### Domain: Games & Matches

#### matches
**Purpose:** Chess match records (core gameplay table)

| Column | Type | Nullable | Default |
|--------|------|----------|---------|
| id | uuid | NO | gen_random_uuid() |
| game_key | text | NO | 'chess' |
| white_player_id | uuid | YES | — |
| black_player_id | uuid | YES | — |
| match_type | text | NO | — |
| tournament_id | uuid | YES | — |
| tournament_round | integer | YES | — |
| pairing_id | uuid | YES | — |
| status | match_status | YES | 'waiting' |
| result | match_result | YES | — |
| time_control | time_control | NO | — |
| initial_time_ms | integer | NO | — |
| increment_ms | integer | YES | 0 |
| white_time_remaining_ms | integer | YES | — |
| black_time_remaining_ms | integer | YES | — |
| starting_fen | text | YES | standard starting FEN |
| current_fen | text | YES | — |
| pgn | text | YES | — |
| moves | jsonb | YES | '[]' |
| move_count | integer | YES | 0 |
| game_state | jsonb | YES | '{}' |
| white_rating_before | integer | YES | — |
| black_rating_before | integer | YES | — |
| white_rating_after | integer | YES | — |
| black_rating_after | integer | YES | — |
| rating_change_white | integer | YES | — |
| rating_change_black | integer | YES | — |
| bot_id | text | YES | — |
| bot_difficulty | text | YES | — |
| is_flagged | boolean | YES | false |
| cheat_score | numeric | YES | 0 |
| analysis_complete | boolean | YES | false |
| is_rated | boolean | YES | true |
| is_rematch | boolean | YES | false |
| rematch_of | uuid | YES | — |
| started_at | timestamptz | YES | — |
| completed_at | timestamptz | YES | — |
| created_at | timestamptz | YES | now() |
| updated_at | timestamptz | YES | now() |

**FKs:** white_player_id → profiles, black_player_id → profiles, rematch_of → matches
**RLS:** Public SELECT, authenticated INSERT (own player), authenticated UPDATE (own player)

---

#### domino_matches
**Purpose:** Domino game state (2-4 players)

| Column | Type | Nullable | Default |
|--------|------|----------|---------|
| id | uuid | NO | gen_random_uuid() |
| room_code | varchar | YES | — |
| status | varchar | YES | 'waiting' |
| mode | varchar | YES | '2p' |
| player_count | integer | YES | 2 |
| players | jsonb | YES | '[]' |
| current_turn | integer | YES | 0 |
| board | jsonb | YES | '[]' |
| hands | jsonb | YES | '{}' |
| boneyard | jsonb | YES | '[]' |
| spinner | jsonb | YES | — |
| scores | jsonb | YES | '{}' |
| round_number | integer | YES | 1 |
| round_history | jsonb | YES | '[]' |
| target_score | integer | YES | 100 |
| moves | jsonb | YES | '[]' |
| game_state | jsonb | YES | '{}' |
| chat | jsonb | YES | '[]' |
| host_id | uuid | YES | — |
| result | varchar | YES | — |
| created_at | timestamptz | YES | now() |
| completed_at | timestamptz | YES | — |
| winner_id | uuid | YES | — |

---

#### ludo_matches
**Purpose:** Ludo game state (2-4 players)

| Column | Type | Nullable | Default |
|--------|------|----------|---------|
| id | uuid | NO | gen_random_uuid() |
| room_code | text | YES | — |
| status | text | NO | 'waiting' |
| player_count | integer | NO | 4 |
| players | jsonb | NO | '[]' |
| current_turn | integer | NO | 0 |
| dice_value | integer | YES | — |
| positions | jsonb | NO | '{}' |
| moves | jsonb | NO | '[]' |
| winners | jsonb | NO | '[]' |
| game_state | jsonb | NO | '{}' |
| chat | jsonb | NO | '[]' |
| host_id | uuid | YES | — |
| created_at | timestamptz | YES | now() |
| completed_at | timestamptz | YES | — |
| result | text | YES | — |

**RLS:** Public SELECT, public ALL

---

#### matchmaking_queue
**Purpose:** Live matchmaking — players waiting for opponents

| Column | Type | Nullable | Default |
|--------|------|----------|---------|
| id | uuid | NO | gen_random_uuid() |
| player_id | uuid | YES | — |
| game_key | text | NO | 'chess' |
| time_control | time_control | NO | — |
| rating | integer | NO | — |
| rating_range_min | integer | YES | — |
| rating_range_max | integer | YES | — |
| is_rated | boolean | YES | true |
| queued_at | timestamptz | YES | now() |
| range_expansion_per_sec | integer | YES | 10 |
| max_wait_seconds | integer | YES | 60 |
| block_list | uuid[] | YES | '{}' |
| region_preference | text | YES | — |
| status | text | YES | 'searching' |
| matched_with | uuid | YES | — |
| match_id | uuid | YES | — |
| expires_at | timestamptz | YES | — |

**FKs:** player_id → profiles, matched_with → profiles, match_id → matches
**RLS:** Own-user INSERT/SELECT/DELETE/UPDATE

---

#### domino_queue / ludo_queue
**Purpose:** Game-specific matchmaking queues

---

#### game_plugins
**Purpose:** Registry of available games (chess, backgammon, domino, ludo, etc.)

| Column | Type | Nullable | Default |
|--------|------|----------|---------|
| game_key | text | NO (PK) | — |
| name | text | NO | — |
| name_ar | text | NO | — |
| description | text | YES | — |
| description_ar | text | YES | — |
| icon_url | text | YES | — |
| banner_url | text | YES | — |
| is_enabled | boolean | YES | true |
| is_beta | boolean | YES | false |
| min_players | integer | YES | 2 |
| max_players | integer | YES | 2 |
| supports_ranked | boolean | YES | true |
| supports_tournament | boolean | YES | true |
| supports_bot | boolean | YES | false |
| supports_spectator | boolean | YES | true |
| supports_arena | boolean | YES | false |
| rating_system | text | YES | 'elo' |
| default_time_controls | jsonb | YES | '[]' |
| config | jsonb | YES | '{}' |
| sort_order | integer | YES | 0 |

**RLS:** Public SELECT

---

#### puzzles
**Purpose:** Chess puzzles for training

| Column | Type | Nullable | Default |
|--------|------|----------|---------|
| id | integer | NO | serial |
| fen | text | NO | — |
| moves | text | NO | — |
| rating | integer | YES | 1200 |
| themes | text | YES | — |
| game_url | text | YES | — |
| created_at | timestamptz | YES | now() |

**RLS:** Public SELECT

---

### Domain: Ratings & Leaderboards

#### rating_history
**Purpose:** Rating changes over time per match

| Column | Type | Nullable | Default |
|--------|------|----------|---------|
| id | uuid | NO | gen_random_uuid() |
| player_id | uuid | YES | — |
| game_key | text | NO | 'chess' |
| time_control_type | text | NO | — |
| rating_before | integer | NO | — |
| rating_after | integer | NO | — |
| rating_change | integer | NO | — |
| match_id | uuid | YES | — |
| opponent_id | uuid | YES | — |
| result | text | YES | — |
| recorded_at | timestamptz | YES | now() |

**FKs:** player_id → profiles, opponent_id → profiles, match_id → matches
**RLS:** Public SELECT

---

#### leaderboards
**Purpose:** Cached rating leaderboards by game/time-control/period

| Column | Type | Nullable | Default |
|--------|------|----------|---------|
| id | uuid | NO | gen_random_uuid() |
| player_id | uuid | YES | — |
| game_key | text | NO | 'chess' |
| time_control_type | text | NO | — |
| period | text | NO | — |
| period_key | text | YES | — |
| rank | integer | YES | — |
| rating | integer | NO | — |
| games_played | integer | YES | 0 |
| win_rate | numeric | YES | — |
| country_code | char(2) | YES | — |
| updated_at | timestamptz | YES | now() |

**RLS:** Public SELECT

---

### Domain: Social

#### friendships
**Purpose:** Friend requests and connections

| Column | Type | Nullable | Default |
|--------|------|----------|---------|
| id | uuid | NO | gen_random_uuid() |
| requester_id | uuid | YES | — |
| addressee_id | uuid | YES | — |
| status | text | YES | 'pending' |
| created_at | timestamptz | YES | now() |
| updated_at | timestamptz | YES | now() |

**FKs:** requester_id → profiles, addressee_id → profiles
**RLS:** Authenticated SELECT (own), authenticated INSERT (own requester)

---

#### notifications
**Purpose:** Push-style notifications to users

| Column | Type | Nullable | Default |
|--------|------|----------|---------|
| id | uuid | NO | gen_random_uuid() |
| user_id | uuid | YES | — |
| type | text | NO | — |
| title | text | NO | — |
| title_ar | text | YES | — |
| body | text | YES | — |
| body_ar | text | YES | — |
| data | jsonb | YES | '{}' |
| is_read | boolean | YES | false |
| read_at | timestamptz | YES | — |
| created_at | timestamptz | YES | now() |

**FKs:** user_id → profiles
**RLS:** Own-user SELECT, own-user UPDATE

---

#### chat_messages
**Purpose:** General chat messages (match chat, etc.)

| Column | Type | Nullable | Default |
|--------|------|----------|---------|
| id | uuid | NO | gen_random_uuid() |
| channel_type | text | NO | — |
| channel_id | uuid | NO | — |
| sender_id | uuid | YES | — |
| content | text | NO | — |
| is_system | boolean | YES | false |
| is_deleted | boolean | YES | false |
| created_at | timestamptz | YES | now() |

**RLS:** Authenticated SELECT, authenticated INSERT (own sender)

---

#### activity_feed
**Purpose:** Social activity feed

| Column | Type | Nullable | Default |
|--------|------|----------|---------|
| id | uuid | NO | gen_random_uuid() |
| actor_id | uuid | YES | — |
| action | text | NO | — |
| target_type | text | YES | — |
| target_id | uuid | YES | — |
| metadata | jsonb | YES | '{}' |
| visibility | text | YES | 'friends' |
| created_at | timestamptz | YES | now() |

---

### Domain: Economy & Cosmetics

#### economy_transactions
**Purpose:** Player coin/gem transaction history

| Column | Type | Nullable | Default |
|--------|------|----------|---------|
| id | uuid | NO | uuid_generate_v4() |
| player_id | uuid | NO | — |
| type | text | NO | — |
| currency | text | NO | — |
| amount | integer | NO | — |
| balance_after | integer | NO | — |
| reason | text | NO | — |
| source_id | text | YES | — |
| created_at | timestamptz | YES | now() |

**RLS:** Own-player SELECT

---

#### cosmetics
**Purpose:** Purchasable cosmetic items

| Column | Type | Nullable | Default |
|--------|------|----------|---------|
| id | text | NO (PK) | — |
| name | text | NO | — |
| name_ar | text | YES | — |
| description | text | YES | — |
| type | cosmetic_type | NO | — |
| rarity | cosmetic_rarity | NO | — |
| preview_url | text | YES | — |
| asset_url | text | YES | — |
| price_coins | integer | YES | — |
| price_gems | integer | YES | — |
| unlock_condition | jsonb | YES | — |
| is_purchasable | boolean | YES | true |
| is_limited_edition | boolean | YES | false |
| available_until | timestamptz | YES | — |
| created_at | timestamptz | YES | now() |

**RLS:** Public SELECT

---

#### player_cosmetics
**Purpose:** Player-owned cosmetics

| Column | Type | Nullable | Default |
|--------|------|----------|---------|
| id | uuid | NO | gen_random_uuid() |
| player_id | uuid | YES | — |
| cosmetic_id | text | YES | — |
| is_equipped | boolean | YES | false |
| acquired_at | timestamptz | YES | now() |
| source | text | YES | — |

**RLS:** Own-player SELECT

---

#### achievements / player_achievements
**Purpose:** Achievement definitions and player progress

**achievements columns:** id (text PK), name, name_ar, description, description_ar, icon_url, category, tier, condition (jsonb), xp_reward, coins_reward, cosmetic_reward, is_hidden, created_at

**player_achievements columns:** id, player_id, achievement_id, progress (integer), is_completed, completed_at, created_at

---

#### xp_levels
**Purpose:** XP thresholds per level

| Column | Type | Nullable | Default |
|--------|------|----------|---------|
| level | integer | NO (PK) | — |
| xp_required | integer | NO | — |
| title | text | YES | — |
| title_ar | text | YES | — |
| reward_coins | integer | YES | 0 |
| reward_gems | integer | YES | 0 |

**RLS:** Public SELECT

---

### Domain: Tournaments (EL3AB System)

#### el3ab_tournaments
**Purpose:** Main tournament table (EL3AB platform tournaments)

**Key columns:** id, swiss_api_tournament_id, org_id, game_key, name, name_ar, format (tournament_format enum), time_control (time_control enum), rounds_total, swiss_rounds, bracket_size, entry_fee_coins/gems, min/max_rating, min/max_players, prize_pool_coins/gems, prize_distribution (jsonb), registration_opens/closes_at, starts_at, status, current_round, charity_id/percent, sponsor_id/branding, is_rated, is_fide_rated, tournament_mode, phase_config, tiebreak_rules, slug, live_enabled/theme/visibility/branding, view_count

**FKs:** org_id → el3ab_organizations, created_by → profiles

---

#### el3ab_tournament_rounds
**Purpose:** Round tracking for EL3AB tournaments

**Columns:** id, tournament_id, round_number, status, started_at, completed_at, pairings (jsonb), results (jsonb), swiss_round_id

---

#### tournament_brackets
**Purpose:** Bracket/elimination structure

**Key columns:** id, tournament_id, phase_id, bracket_type, size, seeding_method, status, config, results (jsonb)

---

#### bracket_matches
**Purpose:** Individual bracket match records

**Key columns:** id, bracket_id, tournament_id, phase_id, round_number, match_number, player_a/b_id/name/seed, result, score_a/b, winner_id, next_match_id/slot, loser_next_match_id/slot, source_match_a/b_id, scheduled/started/completed_at, status, metadata

---

#### tournament_phases
**Purpose:** Multi-phase tournament structure (Swiss → Bracket)

**Columns:** id, tournament_id, phase_number, phase_type, config, status, started_at, completed_at

---

#### tournament_registrations
**Purpose:** Player registration for EL3AB tournaments

**Columns:** id, tournament_id, player_id, status, registered_at, payment_status, seed

---

#### tournament_announcements / tournament_media / tournament_page_views / tournament_ad_slots / tournament_prize_payouts / tournament_sponsorships
**Purpose:** Supporting tournament content, analytics, prizes, and sponsorship tables

---

### Domain: Tournaments (Swiss System — OTB)

#### tournaments (separate table)
**Purpose:** Swiss-system OTB tournament management (linked to organizations)

**Key columns:** id, organization_id, event_id, name, time_control (time_control enum), time_control_type (time_control_type enum), rounds, current_round, pairing_system, tiebreaks (tiebreak_type[]), status (tournament_status enum), current_players, max_players, registration_start/end, tournament_start/end, prize_amount, price, game_type, sponsor_name/image_url, max_join_delay

**FKs:** organization_id → organizations, event_id → events

---

#### tournament_players
**Purpose:** Players in Swiss OTB tournaments

**Columns:** id, tournament_id, user_id, category_id, organization_id, fide_id, name, fide_rating, national_rating, title, seed, score, buchholz, performance_rating, status, numeric_user_id, player_auth_id

---

#### rounds / pairings / standings / categories
**Purpose:** Swiss-system round management, pairings, standings, player categories

---

### Domain: Organizations (EL3AB Platform)

#### el3ab_organizations
**Purpose:** Platform organizations/clubs (EL3AB native)

**Key columns:** id, swiss_api_org_id, name, name_ar, slug, logo/banner_url, description, website, contact_email, country_code, city, tier, max_members, max_concurrent_tournaments, features_enabled, custom_branding, revenue_share_percent, charity_percent, is_verified, is_active, primary/secondary_color, social_links, custom_css, tier_points, member/tournament/match_count

---

#### org_members
**Purpose:** Organization membership

**Columns:** id, org_id, player_id, role, joined_via, application_id, joined_at, display_name, custom_title, permissions (jsonb), is_muted, muted_until, contribution_points, last_active_at, invited_by, invite_link_id

---

#### org_memberships
**Purpose:** Organization membership (Swiss system variant)

**Columns:** id, user_id, organization_id, role (user_role enum), status (org_membership_status enum), invited_by, joined_at

---

#### org_* tables (34 total)
The following org_ prefixed tables handle the comprehensive organization ecosystem:

| Table | Purpose |
|-------|---------|
| org_achievements | Org-specific achievements |
| org_activity_log | Org audit trail |
| org_announcements | Org news/announcements |
| org_asset_overrides | Custom asset replacements |
| org_challenges | Inter-org challenges |
| org_chat_channels | Org chat channels |
| org_chat_messages | Org chat messages |
| org_chat_moderation | Chat moderation actions |
| org_content | Org articles/guides |
| org_event_participants | Org event attendees |
| org_events | Org calendar events |
| org_invite_codes | Single-use invite codes |
| org_invite_links | Shareable invite links |
| org_invite_uses | Invite usage tracking |
| org_leaderboards | Org internal rankings |
| org_loyalty_rewards | Day-streak rewards |
| org_media | Org media gallery |
| org_member_spotlights | MVP highlights |
| org_membership_applications | Join applications |
| org_partnerships | Inter-org alliances |
| org_player_transfers | Player transfers between orgs |
| org_recruitment_posts | Recruitment listings |
| org_referrals | Referral tracking |
| org_roster_players | Roster members |
| org_rosters | Team rosters |
| org_seasonal_rankings | Org season rankings |
| org_theme_overrides | Custom theme tokens |
| org_training_sessions | Coaching sessions |
| org_treasury | Org coin/gem balance |
| org_treasury_transactions | Treasury history |

---

### Domain: Organizations (Swiss System)

#### organizations
**Purpose:** OTB chess organizations (Swiss system)

**Columns:** id, name, slug, logo_url, website, country_code, fide_federation_id, contact_email, settings, subscription_tier, max_tournaments, max_players_per_tournament

---

### Domain: Advertising

#### ad_campaigns / ad_creatives / ad_impressions / ad_slots
**Purpose:** Complete ad serving system — campaigns by sponsors, creative assets, impression tracking, placement slots

---

### Domain: Sponsors & Charities

#### sponsors
**Purpose:** Sponsor companies/individuals

**Columns:** id, name, name_ar, logo_url, website, contact_email, contact_name, tier, is_verified, is_active, total_sponsored

---

#### charities / charity_donations
**Purpose:** Charity organizations and donation tracking from tournaments

---

### Domain: Clubs (Legacy)

#### clubs / club_members
**Purpose:** Simple club system (possibly legacy, separate from organizations)

---

### Domain: Platform Administration

#### admin_users
**Purpose:** Admin panel users (separate from auth.users)

**Key columns:** id, username, email, password_hash, role, org_id, is_active, permissions (jsonb)

---

#### platform_roles
**Purpose:** Platform role assignments

**Columns:** id, user_id, role (platform_role enum), scope_org_id, scope_tournament_id, is_active, granted_by, granted_at, expires_at

---

#### platform_theme / platform_assets / platform_branding
**Purpose:** Global platform theming, assets, and branding configuration

---

#### system_config
**Purpose:** System-wide configuration key-value pairs

**Columns:** id, key, value, description, is_secret, updated_by, updated_at

---

#### feature_flags
**Purpose:** Feature toggles with rollout control

**Columns:** id, name, description, is_enabled, rollout_percentage, conditions (jsonb)

---

#### audit_log / audit_logs
**Purpose:** Two audit systems — one for platform (audit_log) and one for org-level (audit_logs with audit_action enum)

---

#### cheat_reports
**Purpose:** Player cheat reports with engine correlation data

---

#### approval_requests
**Purpose:** Approval workflow for various entity changes

---

#### workflow_rules
**Purpose:** Configurable workflow automation rules per org

---

### Domain: Theming

#### theme_settings / game_theme_overrides / org_theme_overrides / cosmetic_type_registry / profile_frames / player_frames
**Purpose:** Multi-level theming system — platform → game → org → player

---

### Domain: Miscellaneous

#### _migrations
**Purpose:** Schema migration tracking

#### trivia_questions
**Purpose:** In-game trivia/quiz system

#### events
**Purpose:** OTB chess events (parent of tournaments)

---

## RLS Policy Summary (111 policies)

### Public Access (no auth required)
- achievements, ad_creatives (approved), ad_slots (enabled), clubs, cosmetics, el3ab_organizations (active), el3ab_tournaments, feature_flags, game_plugins, game_theme_overrides, leaderboards, ludo_matches, matches, platform_assets, platform_branding, platform_theme, player_game_ratings, profiles, puzzles, rating_history, theme_settings, tournament_format_registry, trivia_questions, xp_levels

### Authenticated User (own data)
- economy_transactions (own), friendships (own), matchmaking_queue (own CRUD), notifications (own), player_achievements (own), player_cosmetics (own), platform_roles (own read), user_profiles (own)

### Role-Based (org membership)
- audit_logs (org_admin/super_admin), events (org roles), org_memberships (org_admin), organizations (own orgs), pairings (arbiter/admin), rounds (arbiter/admin), standings (arbiter/admin), tournament_players (arbiter/admin), tournaments (arbiter/admin)

### Platform Admin
- audit_log (superadmin/platform_admin), el3ab_organizations UPDATE (org_owner+), el3ab_tournaments INSERT (tournament_director+), platform_branding (superadmin only)

### Service Role Only (28 tables)
- All org_* tables (except org_invite_codes, org_memberships), bracket_matches, player_achievements, player_frames, profile_frames, tournament_* (brackets/phases/media/etc.)

---

## Realtime Configuration

**ALL 106 public tables** are published to the `supabase_realtime` publication. Key realtime-critical tables:

- `matches` — Live game state updates
- `matchmaking_queue` — Queue status changes
- `domino_matches` / `ludo_matches` — Multi-player game state
- `notifications` — Real-time notifications
- `chat_messages` / `org_chat_messages` — Chat
- `tournament_brackets` — Tournament progress
- `friendships` — Friend request notifications

---

## Key Architectural Observations

1. **Dual Organization Systems:** `organizations` (Swiss OTB) and `el3ab_organizations` (platform native) exist separately with different membership tables (`org_memberships` vs `org_members`).

2. **Dual Tournament Systems:** `tournaments` (Swiss OTB, linked to `organizations`) and `el3ab_tournaments` (platform, linked to `el3ab_organizations`).

3. **Mobile Unity Integration:** Functions reference `numeric_id`, `unity_auth_id`, `profile_image_url` columns — indicating a Unity mobile app integration alongside the web platform.

4. **Multi-Game Architecture:** `game_key` field appears across matches, matchmaking, ratings, leaderboards — designed for chess, backgammon, domino, ludo with room for more.

5. **Comprehensive Org Ecosystem:** 34 org_* tables cover chat, events, rosters, transfers, treasury, challenges, recruitment, loyalty, achievements, theming — a fully-featured guild/club system.

6. **No Database Triggers:** Only 1 trigger exists (auth signup → profile creation). All business logic must be handled at application level or via RPC functions.

7. **Vector Extension (pgvector):** Installed but not actively used by any business table — possibly for future AI features.

8. **Service Role Dependencies:** 28 tables (mostly org system) only allow service_role access — the backend MUST use the service key for org management operations.

---

## MISSING INFORMATION

1. `app_version` table referenced by `check_app_version()` function — not found in public schema (may have been dropped)
2. `tournament_brackets` column details not fully captured for the "old" system variant (columns: lobby_id, can_join, player3_user_id, player4_user_id suggest 4-player bracket support)
3. Edge Functions — not inspected (would need to check the edge-runtime container)
4. Exact realtime channel/presence configuration — cannot be determined from DB alone
