Commit 873278e4 authored by Mahmoud Aglan's avatar Mahmoud Aglan

feat(db): the big-tournament hosting schema (P0)

One idempotent migration for every hosting package: stages carry their own
rounds, teams, team matches and stage entries, playable brackets, check-in,
waitlist, registration forms and answers, prize categories and a prize hold,
presentation (media, sponsors, ad slots, announcements), disputes,
certificates, notices, forbidden pairs, tournament columns on the backgammon,
domino and ludo tables, and club and lobby groups. Two storage buckets:
tournament-media (public) and tournament-uploads (private).

The 21 tournament tables keep RLS on with the service role only: anon and
authenticated hold no grant at all (they read nothing there today; RLS had
no policy for them). el3ab_tournaments and el3ab_tournament_rounds keep the
read policies Realtime needs.

Applied to production on 2026-10-26 after a backup in
/root/backups/2026-10-27-hosting-schema/, then run a second time: the schema
dumps before and after the second run are identical, and the 9 tournaments,
16 registrations and 39 rounds are unchanged (the 4 legacy string pairings
hold objects, so none was unwrapped). A commented rollback block is at the end
of the file, tested inside BEGIN/ROLLBACK on production.

Departures from the spec, each recorded in docs/tournament-hosting-contract.md:
'unpaired' rounds stay legal only for the old Swiss service, a team event must
have a team size, dispute_window_minutes gets a column, groups get kind and
tournament_id.
Co-Authored-By: 's avatarClaude Opus 5.5 <noreply@anthropic.com>
parent 9ce197dd
-- 2026-10-27 Big-tournament hosting: the shared schema (package P0).
--
-- Schema only: columns, tables, checks, indexes, two storage buckets and the
-- grants. No functions; each package ships its own in
-- 2026-10-27-hosting-{engine,bracket,registration,prizes}.sql, which run after
-- this file. The design is docs/tournament-hosting-spec.md section 3.1; the
-- reference every package codes against is docs/tournament-hosting-contract.md.
--
-- Backup taken before the first run (schema of public, data of every table
-- touched here, storage.buckets):
-- /root/backups/2026-10-27-hosting-schema/
--
-- Idempotent: every statement is IF NOT EXISTS, or a DROP ... IF EXISTS
-- followed by the same ADD, so a second run leaves the schema identical.
--
-- Where this file departs from the spec text (each is written up in the
-- contract, section "Decisions"):
-- * el3ab_rounds_status_chk also accepts 'unpaired', but only on a round of
-- the old external Swiss service (swiss_round_id set): the manager's
-- unpairRound still writes it for those rounds. An engine round never can.
-- * el3ab_t_entrant_chk: a team event must have a team_size. In the spec's
-- form a NULL team_size made the check pass.
-- * el3ab_tournaments.dispute_window_minutes: the spec names the setting
-- (section 5.2, default 15) but gave it no column.
-- * groups.kind and groups.tournament_id: club and tournament chat groups
-- (F2, F4). The spec names groups.kind but no package owned a migration.
-- * The two read policies Realtime needs (el3ab_tournaments,
-- el3ab_tournament_rounds) are re-asserted, never changed.
--
-- A rollback block is at the end of the file, commented out.
BEGIN;
SET LOCAL lock_timeout = '5s';
SET LOCAL client_min_messages = warning; -- the first run's "does not exist, skipping" notices
-- 1. Tournaments -------------------------------------------------------------
ALTER TABLE public.el3ab_tournaments
ADD COLUMN IF NOT EXISTS entrant_kind text NOT NULL DEFAULT 'player',
ADD COLUMN IF NOT EXISTS team_size int,
ADD COLUMN IF NOT EXISTS team_reserves int NOT NULL DEFAULT 0,
ADD COLUMN IF NOT EXISTS team_scoring jsonb NOT NULL DEFAULT '{}'::jsonb,
ADD COLUMN IF NOT EXISTS allowed_org_ids uuid[],
ADD COLUMN IF NOT EXISTS max_per_org int,
ADD COLUMN IF NOT EXISTS entry_conditions jsonb NOT NULL DEFAULT '{}'::jsonb,
ADD COLUMN IF NOT EXISTS access text NOT NULL DEFAULT 'public',
ADD COLUMN IF NOT EXISTS approval_required boolean NOT NULL DEFAULT false,
ADD COLUMN IF NOT EXISTS waitlist_enabled boolean NOT NULL DEFAULT true,
ADD COLUMN IF NOT EXISTS waitlist_max int,
ADD COLUMN IF NOT EXISTS check_in_required boolean NOT NULL DEFAULT false,
ADD COLUMN IF NOT EXISTS check_in_mode text NOT NULL DEFAULT 'app',
ADD COLUMN IF NOT EXISTS check_in_opens_minutes int NOT NULL DEFAULT 60,
ADD COLUMN IF NOT EXISTS check_in_closes_minutes int NOT NULL DEFAULT 5,
ADD COLUMN IF NOT EXISTS check_in_closed_at timestamptz,
ADD COLUMN IF NOT EXISTS form_required_to_play boolean NOT NULL DEFAULT true,
ADD COLUMN IF NOT EXISTS round_schedule jsonb NOT NULL DEFAULT '[]'::jsonb,
ADD COLUMN IF NOT EXISTS round_break_minutes int NOT NULL DEFAULT 0,
ADD COLUMN IF NOT EXISTS bye_value numeric(3,1) NOT NULL DEFAULT 1.0,
ADD COLUMN IF NOT EXISTS rules_ar text,
ADD COLUMN IF NOT EXISTS dispute_window_minutes int NOT NULL DEFAULT 15,
ADD COLUMN IF NOT EXISTS prize_review_required boolean NOT NULL DEFAULT false,
ADD COLUMN IF NOT EXISTS prizes_status text NOT NULL DEFAULT 'none',
ADD COLUMN IF NOT EXISTS prizes_released_at timestamptz,
ADD COLUMN IF NOT EXISTS prizes_released_by_admin uuid REFERENCES public.admin_users(id) ON DELETE SET NULL,
ADD COLUMN IF NOT EXISTS completed_at timestamptz;
ALTER TABLE public.el3ab_tournaments
DROP CONSTRAINT IF EXISTS el3ab_t_status_chk,
ADD CONSTRAINT el3ab_t_status_chk CHECK (status IN ('draft','registration','in_progress','paused','completed','cancelled')),
DROP CONSTRAINT IF EXISTS el3ab_t_entrant_chk,
ADD CONSTRAINT el3ab_t_entrant_chk CHECK (entrant_kind IN ('player','team')
AND (team_size IS NULL OR team_size BETWEEN 1 AND 12)
AND (entrant_kind = 'player' OR team_size IS NOT NULL)
AND team_reserves BETWEEN 0 AND 6),
DROP CONSTRAINT IF EXISTS el3ab_t_access_chk,
ADD CONSTRAINT el3ab_t_access_chk CHECK (access IN ('public','code_only','hidden')),
DROP CONSTRAINT IF EXISTS el3ab_t_checkin_chk,
ADD CONSTRAINT el3ab_t_checkin_chk CHECK (check_in_mode IN ('app','venue_code','staff_only')
AND check_in_opens_minutes BETWEEN 5 AND 1440 AND check_in_closes_minutes BETWEEN 0 AND 120
AND check_in_closes_minutes < check_in_opens_minutes),
DROP CONSTRAINT IF EXISTS el3ab_t_misc_chk,
ADD CONSTRAINT el3ab_t_misc_chk CHECK (round_break_minutes BETWEEN 0 AND 1440 AND bye_value IN (0, 0.5, 1)
AND prizes_status IN ('none','held','released')
AND (max_per_org IS NULL OR max_per_org > 0) AND (waitlist_max IS NULL OR waitlist_max >= 0)
AND dispute_window_minutes BETWEEN 0 AND 1440
AND jsonb_typeof(team_scoring) = 'object' AND jsonb_typeof(entry_conditions) = 'object'
AND jsonb_typeof(round_schedule) = 'array');
CREATE INDEX IF NOT EXISTS idx_el3ab_tournaments_org ON public.el3ab_tournaments (org_id) WHERE org_id IS NOT NULL;
CREATE INDEX IF NOT EXISTS idx_el3ab_tournaments_status_start ON public.el3ab_tournaments (status, starts_at);
-- el3ab_tournaments is world-readable (Realtime). Secrets live apart.
CREATE TABLE IF NOT EXISTS public.tournament_private_settings (
tournament_id uuid PRIMARY KEY REFERENCES public.el3ab_tournaments(id) ON DELETE CASCADE,
venue_check_in_code text,
updated_at timestamptz NOT NULL DEFAULT now()
);
-- 2. Stages ------------------------------------------------------------------
ALTER TABLE public.tournament_phases
ADD COLUMN IF NOT EXISTS entrant_kind text NOT NULL DEFAULT 'player',
ADD COLUMN IF NOT EXISTS time_control public.time_control,
ADD COLUMN IF NOT EXISTS custom_time_initial_ms int,
ADD COLUMN IF NOT EXISTS custom_time_increment_ms int,
ADD COLUMN IF NOT EXISTS rounds_planned int,
ADD COLUMN IF NOT EXISTS first_round int,
ADD COLUMN IF NOT EXISTS last_round int,
ADD COLUMN IF NOT EXISTS tiebreak_rules jsonb,
ADD COLUMN IF NOT EXISTS starts_at timestamptz,
ADD COLUMN IF NOT EXISTS cut_review_until timestamptz,
ADD COLUMN IF NOT EXISTS cut_confirmed_at timestamptz,
ADD COLUMN IF NOT EXISTS cut_confirmed_by text,
ADD COLUMN IF NOT EXISTS standings_snapshot jsonb;
ALTER TABLE public.tournament_phases
DROP CONSTRAINT IF EXISTS tournament_phases_type_check,
ADD CONSTRAINT tournament_phases_type_check CHECK (type IN
('swiss','round_robin','group_stage','single_elimination','double_elimination','heats','arena')),
DROP CONSTRAINT IF EXISTS tournament_phases_status_check,
ADD CONSTRAINT tournament_phases_status_check CHECK (status IN ('pending','in_progress','cut_review','completed','cancelled')),
DROP CONSTRAINT IF EXISTS tournament_phases_shape_chk,
ADD CONSTRAINT tournament_phases_shape_chk CHECK (entrant_kind IN ('player','team')
AND jsonb_typeof(coalesce(config, '{}'::jsonb)) = 'object'
AND jsonb_typeof(coalesce(advancement_rule, '{}'::jsonb)) = 'object'
AND (rounds_planned IS NULL OR rounds_planned BETWEEN 1 AND 40)
AND (first_round IS NULL OR last_round IS NULL OR last_round >= first_round));
CREATE UNIQUE INDEX IF NOT EXISTS tournament_phases_first_round_uq
ON public.tournament_phases (tournament_id, first_round) WHERE first_round IS NOT NULL;
-- 3. Teams -------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS public.tournament_teams (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
tournament_id uuid NOT NULL REFERENCES public.el3ab_tournaments(id) ON DELETE CASCADE,
org_id uuid REFERENCES public.el3ab_organizations(id) ON DELETE SET NULL,
name text NOT NULL,
name_ar text,
short_name text,
seed int,
status text NOT NULL DEFAULT 'registered',
captain_player_id uuid REFERENCES public.profiles(id) ON DELETE SET NULL,
roster_id uuid REFERENCES public.org_rosters(id) ON DELETE SET NULL,
lineup_override jsonb NOT NULL DEFAULT '{}'::jsonb, -- {"<round>": [player ids in board order]}
final_standing int,
created_by_admin uuid REFERENCES public.admin_users(id) ON DELETE SET NULL,
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now(),
CONSTRAINT tournament_teams_chk CHECK (status IN ('registered','withdrawn','disqualified')
AND char_length(name) BETWEEN 2 AND 60 AND jsonb_typeof(lineup_override) = 'object')
);
CREATE UNIQUE INDEX IF NOT EXISTS tournament_teams_name_uq ON public.tournament_teams (tournament_id, lower(name));
CREATE INDEX IF NOT EXISTS tournament_teams_org_idx ON public.tournament_teams (tournament_id, org_id);
-- 4. Rounds ------------------------------------------------------------------
ALTER TABLE public.el3ab_tournament_rounds
ADD COLUMN IF NOT EXISTS phase_id uuid REFERENCES public.tournament_phases(id) ON DELETE CASCADE,
ADD COLUMN IF NOT EXISTS stage_round int,
ADD COLUMN IF NOT EXISTS kind text NOT NULL DEFAULT 'swiss',
ADD COLUMN IF NOT EXISTS scheduled_start_at timestamptz,
ADD COLUMN IF NOT EXISTS published_at timestamptz,
ADD COLUMN IF NOT EXISTS version int NOT NULL DEFAULT 0;
ALTER TABLE public.el3ab_tournament_rounds
DROP CONSTRAINT IF EXISTS el3ab_rounds_kind_chk,
ADD CONSTRAINT el3ab_rounds_kind_chk CHECK (kind IN ('swiss','round_robin','group','knockout','heats','playoff','arena')),
DROP CONSTRAINT IF EXISTS el3ab_rounds_status_chk,
ADD CONSTRAINT el3ab_rounds_status_chk CHECK (status IN ('pending','in_progress','completed')
OR (status = 'unpaired' AND swiss_round_id IS NOT NULL));
CREATE INDEX IF NOT EXISTS el3ab_rounds_phase_idx ON public.el3ab_tournament_rounds (phase_id, stage_round);
CREATE INDEX IF NOT EXISTS el3ab_rounds_due_idx ON public.el3ab_tournament_rounds (scheduled_start_at) WHERE status = 'pending';
-- Some legacy rows hold pairings as a JSON string: unwrap the ones that are lists.
-- (On 2026-10-26 the 4 string rows all held an object, so none qualified.)
DO $$ DECLARE r record; v jsonb; BEGIN
FOR r IN SELECT id, pairings FROM public.el3ab_tournament_rounds WHERE jsonb_typeof(pairings) = 'string' LOOP
BEGIN v := (r.pairings #>> '{}')::jsonb; EXCEPTION WHEN others THEN v := NULL; END;
IF jsonb_typeof(v) = 'array' THEN UPDATE public.el3ab_tournament_rounds SET pairings = v WHERE id = r.id; END IF;
END LOOP; END $$;
-- 5. Brackets ----------------------------------------------------------------
ALTER TABLE public.tournament_brackets
ADD COLUMN IF NOT EXISTS size int,
ADD COLUMN IF NOT EXISTS third_place boolean NOT NULL DEFAULT false;
ALTER TABLE public.bracket_matches
ADD COLUMN IF NOT EXISTS round_ref int,
ADD COLUMN IF NOT EXISTS pairing_id uuid,
ADD COLUMN IF NOT EXISTS match_id uuid REFERENCES public.matches(id) ON DELETE SET NULL,
ADD COLUMN IF NOT EXISTS games jsonb NOT NULL DEFAULT '[]'::jsonb,
ADD COLUMN IF NOT EXISTS best_of int NOT NULL DEFAULT 1,
ADD COLUMN IF NOT EXISTS points_a numeric(4,1) NOT NULL DEFAULT 0,
ADD COLUMN IF NOT EXISTS points_b numeric(4,1) NOT NULL DEFAULT 0,
ADD COLUMN IF NOT EXISTS tiebreak_stage text,
ADD COLUMN IF NOT EXISTS decided_by text,
ADD COLUMN IF NOT EXISTS team_a_id uuid REFERENCES public.tournament_teams(id) ON DELETE SET NULL,
ADD COLUMN IF NOT EXISTS team_b_id uuid REFERENCES public.tournament_teams(id) ON DELETE SET NULL,
ADD COLUMN IF NOT EXISTS winner_team_id uuid REFERENCES public.tournament_teams(id) ON DELETE SET NULL,
ADD COLUMN IF NOT EXISTS disputed boolean NOT NULL DEFAULT false;
ALTER TABLE public.bracket_matches
DROP CONSTRAINT IF EXISTS bracket_matches_status_check,
ADD CONSTRAINT bracket_matches_status_check CHECK (status IN ('pending','ready','in_progress','tiebreak','completed','bye')),
DROP CONSTRAINT IF EXISTS bracket_matches_shape_chk,
ADD CONSTRAINT bracket_matches_shape_chk CHECK (best_of BETWEEN 1 AND 7 AND jsonb_typeof(games) = 'array'
AND (tiebreak_stage IS NULL OR tiebreak_stage IN ('rapid','blitz','armageddon','berlin','arbiter'))
AND (decided_by IS NULL OR decided_by IN
('games','tiebreak','armageddon','berlin','forfeit','double_forfeit','bye','arbiter','higher_seed')));
CREATE UNIQUE INDEX IF NOT EXISTS bracket_matches_slot_uq ON public.bracket_matches (bracket_id, round_number, match_number);
CREATE UNIQUE INDEX IF NOT EXISTS bracket_matches_pairing_uq ON public.bracket_matches (pairing_id) WHERE pairing_id IS NOT NULL;
CREATE INDEX IF NOT EXISTS bracket_matches_round_ref_idx ON public.bracket_matches (tournament_id, round_ref);
-- 6. Team matches and stage entries -----------------------------------------
CREATE TABLE IF NOT EXISTS public.tournament_team_matches (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
tournament_id uuid NOT NULL REFERENCES public.el3ab_tournaments(id) ON DELETE CASCADE,
phase_id uuid REFERENCES public.tournament_phases(id) ON DELETE CASCADE,
round_number int NOT NULL,
table_no int NOT NULL,
team_a_id uuid NOT NULL REFERENCES public.tournament_teams(id) ON DELETE CASCADE,
team_b_id uuid REFERENCES public.tournament_teams(id) ON DELETE CASCADE, -- null: team bye
team_a_white_odd boolean NOT NULL DEFAULT true,
lineup_a jsonb NOT NULL DEFAULT '[]'::jsonb,
lineup_b jsonb NOT NULL DEFAULT '[]'::jsonb,
boards_total int NOT NULL,
boards_decided int NOT NULL DEFAULT 0,
game_points_a numeric(5,1) NOT NULL DEFAULT 0,
game_points_b numeric(5,1) NOT NULL DEFAULT 0,
match_points_a numeric(4,1),
match_points_b numeric(4,1),
result text,
bracket_match_id uuid REFERENCES public.bracket_matches(id) ON DELETE SET NULL,
status text NOT NULL DEFAULT 'in_progress',
completed_at timestamptz,
created_at timestamptz NOT NULL DEFAULT now(),
CONSTRAINT ttm_uq UNIQUE (tournament_id, round_number, table_no),
CONSTRAINT ttm_chk CHECK ((result IS NULL OR result IN ('team_a_wins','team_b_wins','draw','bye','double_forfeit'))
AND status IN ('in_progress','completed') AND (team_b_id IS NULL OR team_b_id <> team_a_id))
);
CREATE INDEX IF NOT EXISTS ttm_team_a_idx ON public.tournament_team_matches (team_a_id);
CREATE INDEX IF NOT EXISTS ttm_team_b_idx ON public.tournament_team_matches (team_b_id);
CREATE TABLE IF NOT EXISTS public.tournament_stage_entries (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
tournament_id uuid NOT NULL REFERENCES public.el3ab_tournaments(id) ON DELETE CASCADE,
phase_id uuid NOT NULL REFERENCES public.tournament_phases(id) ON DELETE CASCADE,
player_id uuid, -- no FK, like registrations (bots)
team_id uuid REFERENCES public.tournament_teams(id) ON DELETE CASCADE,
entrant_id uuid GENERATED ALWAYS AS (coalesce(player_id, team_id)) STORED,
seed int,
group_label text,
source_rank int,
carried_points numeric(6,2) NOT NULL DEFAULT 0,
status text NOT NULL DEFAULT 'active',
final_rank int,
final_points numeric(6,2),
tiebreaks jsonb,
qualified_by text,
updated_by text,
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now(),
CONSTRAINT tse_uq UNIQUE (phase_id, entrant_id), -- PostgREST on_conflict target
CONSTRAINT tse_chk CHECK ((player_id IS NULL) <> (team_id IS NULL)
AND status IN ('active','qualified','eliminated','withdrawn','disqualified')
AND (qualified_by IS NULL OR qualified_by IN
('rank','group_rank','best_next','points','playoff','arbiter','wildcard','winner')))
);
CREATE INDEX IF NOT EXISTS tse_phase_seed_idx ON public.tournament_stage_entries (phase_id, seed);
-- 7. Registrations -----------------------------------------------------------
ALTER TABLE public.tournament_registrations
ADD COLUMN IF NOT EXISTS represent_org_id uuid REFERENCES public.el3ab_organizations(id) ON DELETE SET NULL,
ADD COLUMN IF NOT EXISTS team_id uuid REFERENCES public.tournament_teams(id) ON DELETE SET NULL,
ADD COLUMN IF NOT EXISTS board_order int,
ADD COLUMN IF NOT EXISTS team_role text,
ADD COLUMN IF NOT EXISTS checked_in_at timestamptz,
ADD COLUMN IF NOT EXISTS check_in_source text,
ADD COLUMN IF NOT EXISTS waitlisted_at timestamptz,
ADD COLUMN IF NOT EXISTS promoted_at timestamptz,
ADD COLUMN IF NOT EXISTS form_status text NOT NULL DEFAULT 'none',
ADD COLUMN IF NOT EXISTS form_submitted_at timestamptz,
ADD COLUMN IF NOT EXISTS reviewed_by_admin uuid REFERENCES public.admin_users(id) ON DELETE SET NULL,
ADD COLUMN IF NOT EXISTS reviewed_at timestamptz,
ADD COLUMN IF NOT EXISTS review_note text,
ADD COLUMN IF NOT EXISTS requested_byes int[] NOT NULL DEFAULT '{}',
ADD COLUMN IF NOT EXISTS late_entry_round int,
ADD COLUMN IF NOT EXISTS fair_play_status text NOT NULL DEFAULT 'clear',
ADD COLUMN IF NOT EXISTS source text NOT NULL DEFAULT 'app',
ADD COLUMN IF NOT EXISTS redemption_id uuid REFERENCES public.redeem_redemptions(id) ON DELETE SET NULL,
ADD COLUMN IF NOT EXISTS staff_note text,
ADD COLUMN IF NOT EXISTS updated_at timestamptz NOT NULL DEFAULT now();
ALTER TABLE public.tournament_registrations
ALTER COLUMN tournament_id SET NOT NULL, ALTER COLUMN player_id SET NOT NULL, ALTER COLUMN status SET NOT NULL,
DROP CONSTRAINT IF EXISTS treg_status_chk,
ADD CONSTRAINT treg_status_chk CHECK (status IN
('registered','pending_approval','waitlisted','cancelled','withdrawn','no_show','rejected','disqualified')),
DROP CONSTRAINT IF EXISTS treg_shape_chk,
ADD CONSTRAINT treg_shape_chk CHECK (form_status IN ('none','incomplete','complete')
AND fair_play_status IN ('clear','flagged','under_review','cleared','disqualified')
AND source IN ('app','link','code','staff','import','waitlist')
AND (team_role IS NULL OR team_role IN ('player','reserve','captain'))
AND (check_in_source IS NULL OR check_in_source IN ('app','venue_code','staff'))
AND (status <> 'waitlisted' OR waitlisted_at IS NOT NULL)
AND cardinality(requested_byes) <= 5);
CREATE INDEX IF NOT EXISTS treg_player_status_idx ON public.tournament_registrations (player_id, status);
CREATE INDEX IF NOT EXISTS treg_tournament_status_idx ON public.tournament_registrations (tournament_id, status);
CREATE INDEX IF NOT EXISTS treg_waitlist_idx ON public.tournament_registrations (tournament_id, waitlisted_at) WHERE status = 'waitlisted';
CREATE INDEX IF NOT EXISTS treg_team_idx ON public.tournament_registrations (team_id) WHERE team_id IS NOT NULL;
CREATE INDEX IF NOT EXISTS treg_org_idx ON public.tournament_registrations (tournament_id, represent_org_id) WHERE represent_org_id IS NOT NULL;
-- 8. Registration forms ------------------------------------------------------
CREATE TABLE IF NOT EXISTS public.tournament_form_fields (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
tournament_id uuid NOT NULL REFERENCES public.el3ab_tournaments(id) ON DELETE CASCADE,
field_key text NOT NULL,
type text NOT NULL,
label_ar text NOT NULL,
label_en text,
help_ar text,
help_en text,
placeholder_ar text,
required boolean NOT NULL DEFAULT false,
options jsonb NOT NULL DEFAULT '[]'::jsonb,
validation jsonb NOT NULL DEFAULT '{}'::jsonb,
show_if jsonb,
visibility text NOT NULL DEFAULT 'private',
editable_until text NOT NULL DEFAULT 'check_in_close',
display_order int NOT NULL DEFAULT 0,
is_active boolean NOT NULL DEFAULT true,
created_by_admin uuid REFERENCES public.admin_users(id) ON DELETE SET NULL,
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now(),
CONSTRAINT tff_key_uq UNIQUE (tournament_id, field_key),
CONSTRAINT tff_chk CHECK (field_key ~ '^[a-z][a-z0-9_]{1,39}$'
AND type IN ('text','textarea','number','phone','email','select','multiselect','date','boolean','consent','file','country','region')
AND char_length(label_ar) BETWEEN 1 AND 120 AND (help_ar IS NULL OR char_length(help_ar) <= 400)
AND jsonb_typeof(options) = 'array' AND jsonb_array_length(options) <= 300
AND (type NOT IN ('select','multiselect') OR jsonb_array_length(options) >= 1)
AND jsonb_typeof(validation) = 'object' AND (show_if IS NULL OR jsonb_typeof(show_if) = 'object')
AND visibility IN ('private','public')
AND editable_until IN ('registration_close','check_in_close','start','never'))
);
CREATE INDEX IF NOT EXISTS tff_order_idx ON public.tournament_form_fields (tournament_id, display_order) WHERE is_active;
CREATE TABLE IF NOT EXISTS public.tournament_registration_answers (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
tournament_id uuid NOT NULL REFERENCES public.el3ab_tournaments(id) ON DELETE CASCADE,
registration_id uuid NOT NULL REFERENCES public.tournament_registrations(id) ON DELETE CASCADE,
field_id uuid NOT NULL REFERENCES public.tournament_form_fields(id) ON DELETE RESTRICT, -- answered fields are archived, never deleted
value jsonb NOT NULL,
value_text text,
file_path text,
updated_by_player uuid REFERENCES public.profiles(id) ON DELETE SET NULL,
updated_by_admin uuid REFERENCES public.admin_users(id) ON DELETE SET NULL,
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now(),
CONSTRAINT tra_uq UNIQUE (registration_id, field_id),
CONSTRAINT tra_chk CHECK ((value_text IS NULL OR char_length(value_text) <= 1000)
AND (file_path IS NULL OR file_path LIKE 'tournament-uploads/%'))
);
CREATE INDEX IF NOT EXISTS tra_filter_idx ON public.tournament_registration_answers (field_id, value_text);
CREATE INDEX IF NOT EXISTS tra_tournament_idx ON public.tournament_registration_answers (tournament_id);
-- 9. Prize categories and payouts ---------------------------------------------
CREATE TABLE IF NOT EXISTS public.tournament_prize_categories (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
tournament_id uuid NOT NULL REFERENCES public.el3ab_tournaments(id) ON DELETE CASCADE,
key text NOT NULL,
name_ar text NOT NULL,
name_en text,
rule jsonb NOT NULL,
prizes jsonb NOT NULL DEFAULT '[]'::jsonb,
exclusive boolean NOT NULL DEFAULT true,
display_order int NOT NULL DEFAULT 0,
is_public boolean NOT NULL DEFAULT true,
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now(),
CONSTRAINT tpc_uq UNIQUE (tournament_id, key),
CONSTRAINT tpc_chk CHECK (key ~ '^[a-z][a-z0-9_]{1,39}$' AND jsonb_typeof(rule) = 'object' AND jsonb_typeof(prizes) = 'array')
);
ALTER TABLE public.tournament_prize_payouts
ADD COLUMN IF NOT EXISTS category_id uuid REFERENCES public.tournament_prize_categories(id) ON DELETE RESTRICT,
ADD COLUMN IF NOT EXISTS team_id uuid REFERENCES public.tournament_teams(id) ON DELETE SET NULL,
ADD COLUMN IF NOT EXISTS status text NOT NULL DEFAULT 'paid',
ADD COLUMN IF NOT EXISTS hold_reason text,
ADD COLUMN IF NOT EXISTS released_at timestamptz,
ADD COLUMN IF NOT EXISTS released_by_admin uuid REFERENCES public.admin_users(id) ON DELETE SET NULL;
ALTER TABLE public.tournament_prize_payouts
DROP CONSTRAINT IF EXISTS tpp_status_chk, ADD CONSTRAINT tpp_status_chk CHECK (status IN ('held','paid','void')),
DROP CONSTRAINT IF EXISTS tpp_uq, ADD CONSTRAINT tpp_uq UNIQUE NULLS NOT DISTINCT (tournament_id, player_id, category_id);
-- 10. Presentation -----------------------------------------------------------
ALTER TABLE public.tournament_media
ADD COLUMN IF NOT EXISTS kind text NOT NULL DEFAULT 'gallery',
ADD COLUMN IF NOT EXISTS storage_path text,
ADD COLUMN IF NOT EXISTS width int,
ADD COLUMN IF NOT EXISTS height int,
ADD COLUMN IF NOT EXISTS alt_ar text,
ADD COLUMN IF NOT EXISTS show_in_app boolean NOT NULL DEFAULT true,
ADD COLUMN IF NOT EXISTS uploaded_by_admin uuid REFERENCES public.admin_users(id) ON DELETE SET NULL;
ALTER TABLE public.tournament_media
DROP CONSTRAINT IF EXISTS tournament_media_kind_chk,
ADD CONSTRAINT tournament_media_kind_chk CHECK (kind IN ('banner','cover','logo','share_card','gallery')
AND (kind = 'gallery' OR media_type = 'image')
AND (storage_path IS NULL OR storage_path LIKE 'tournament-media/%'));
CREATE UNIQUE INDEX IF NOT EXISTS tournament_media_slot_uq ON public.tournament_media (tournament_id, kind) WHERE kind <> 'gallery';
ALTER TABLE public.sponsors
ADD COLUMN IF NOT EXISTS logo_path text,
ADD COLUMN IF NOT EXISTS description_ar text,
ADD COLUMN IF NOT EXISTS created_by_admin uuid REFERENCES public.admin_users(id) ON DELETE SET NULL,
ADD COLUMN IF NOT EXISTS updated_at timestamptz NOT NULL DEFAULT now();
ALTER TABLE public.sponsors DROP CONSTRAINT IF EXISTS sponsors_url_chk,
ADD CONSTRAINT sponsors_url_chk CHECK (website IS NULL OR website ~* '^https://');
ALTER TABLE public.tournament_sponsorships
ADD COLUMN IF NOT EXISTS tier text NOT NULL DEFAULT 'partner',
ADD COLUMN IF NOT EXISTS label_ar text,
ADD COLUMN IF NOT EXISTS link_url text,
ADD COLUMN IF NOT EXISTS logo_url text,
ADD COLUMN IF NOT EXISTS display_order int NOT NULL DEFAULT 0,
ADD COLUMN IF NOT EXISTS is_active boolean NOT NULL DEFAULT true,
ADD COLUMN IF NOT EXISTS show_in_app boolean NOT NULL DEFAULT true,
ADD COLUMN IF NOT EXISTS show_on_live boolean NOT NULL DEFAULT true,
ADD COLUMN IF NOT EXISTS impressions int NOT NULL DEFAULT 0,
ADD COLUMN IF NOT EXISTS clicks int NOT NULL DEFAULT 0,
ADD COLUMN IF NOT EXISTS updated_at timestamptz NOT NULL DEFAULT now();
ALTER TABLE public.tournament_sponsorships
ALTER COLUMN sponsor_id SET NOT NULL, ALTER COLUMN tournament_id SET NOT NULL, -- 0 rows on 2026-10-26
DROP CONSTRAINT IF EXISTS tournament_sponsorships_tournament_id_fkey,
ADD CONSTRAINT tournament_sponsorships_tournament_id_fkey FOREIGN KEY (tournament_id)
REFERENCES public.el3ab_tournaments(id) ON DELETE CASCADE,
DROP CONSTRAINT IF EXISTS tournament_sponsorships_chk,
ADD CONSTRAINT tournament_sponsorships_chk CHECK (tier IN ('title','presenting','gold','silver','bronze','partner','media','venue')
AND (link_url IS NULL OR link_url ~* '^https://'));
CREATE UNIQUE INDEX IF NOT EXISTS tournament_sponsorships_uq ON public.tournament_sponsorships (tournament_id, sponsor_id);
ALTER TABLE public.tournament_ad_slots
ADD COLUMN IF NOT EXISTS sponsor_id uuid REFERENCES public.sponsors(id) ON DELETE SET NULL,
ADD COLUMN IF NOT EXISTS image_path text,
ADD COLUMN IF NOT EXISTS alt_ar text;
ALTER TABLE public.tournament_ad_slots
DROP CONSTRAINT IF EXISTS tournament_ad_slots_position_check,
ADD CONSTRAINT tournament_ad_slots_position_check CHECK (position IN
('hero_top','hero_bottom','sidebar_top','sidebar_bottom','between_sections','footer','ticker',
'app_detail_top','app_detail_mid','app_round_break','app_results')),
DROP CONSTRAINT IF EXISTS tournament_ad_slots_slot_type_check,
ADD CONSTRAINT tournament_ad_slots_slot_type_check CHECK (slot_type IN ('image','html')), -- 'script' removed (0 rows)
DROP CONSTRAINT IF EXISTS tournament_ad_slots_app_chk,
ADD CONSTRAINT tournament_ad_slots_app_chk CHECK ((position NOT LIKE 'app\_%' OR slot_type = 'image')
AND (link_url IS NULL OR link_url ~* '^https://'));
ALTER TABLE public.tournament_announcements
ADD COLUMN IF NOT EXISTS title_ar text,
ADD COLUMN IF NOT EXISTS content_ar text,
ADD COLUMN IF NOT EXISTS audience text NOT NULL DEFAULT 'all',
ADD COLUMN IF NOT EXISTS audience_ref uuid,
ADD COLUMN IF NOT EXISTS show_in_app boolean NOT NULL DEFAULT true,
ADD COLUMN IF NOT EXISTS notify boolean NOT NULL DEFAULT false,
ADD COLUMN IF NOT EXISTS notified_at timestamptz,
ADD COLUMN IF NOT EXISTS notified_count int,
ADD COLUMN IF NOT EXISTS expires_at timestamptz,
ADD COLUMN IF NOT EXISTS created_by_admin uuid REFERENCES public.admin_users(id) ON DELETE SET NULL,
ADD COLUMN IF NOT EXISTS updated_at timestamptz NOT NULL DEFAULT now();
ALTER TABLE public.tournament_announcements
DROP CONSTRAINT IF EXISTS tournament_announcements_type_check,
ADD CONSTRAINT tournament_announcements_type_check CHECK (type IN ('info','result','alert','commentary','milestone','schedule','sponsor')),
DROP CONSTRAINT IF EXISTS tournament_announcements_audience_chk,
ADD CONSTRAINT tournament_announcements_audience_chk CHECK (audience IN
('all','registered','checked_in','not_checked_in','waitlisted','team','org')
AND ((audience IN ('team','org')) = (audience_ref IS NOT NULL)));
CREATE INDEX IF NOT EXISTS tann_notify_idx ON public.tournament_announcements (created_at) WHERE notify AND notified_at IS NULL;
-- 11. Disputes, certificates, notices, forbidden pairs ------------------------
CREATE TABLE IF NOT EXISTS public.tournament_disputes (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
tournament_id uuid NOT NULL REFERENCES public.el3ab_tournaments(id) ON DELETE CASCADE,
round_number int,
pairing_id uuid,
match_id uuid,
game_key text,
reporter_id uuid REFERENCES public.profiles(id) ON DELETE SET NULL,
against_player_id uuid REFERENCES public.profiles(id) ON DELETE SET NULL,
kind text NOT NULL,
message text,
evidence_path text,
status text NOT NULL DEFAULT 'open',
blocks_progress boolean NOT NULL DEFAULT false,
result_before text,
result_after text,
decision_note text,
decided_by_admin uuid REFERENCES public.admin_users(id) ON DELETE SET NULL,
decided_at timestamptz,
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now(),
CONSTRAINT tdisp_chk CHECK (kind IN ('wrong_result','result_mismatch','fair_play','connection','clock','conduct','other')
AND status IN ('open','reviewing','upheld','rejected','withdrawn')
AND (message IS NULL OR char_length(message) <= 1000)
AND (evidence_path IS NULL OR evidence_path LIKE 'tournament-uploads/%'))
);
CREATE UNIQUE INDEX IF NOT EXISTS tdisp_one_open_uq ON public.tournament_disputes (pairing_id, reporter_id)
WHERE status IN ('open','reviewing');
CREATE INDEX IF NOT EXISTS tdisp_queue_idx ON public.tournament_disputes (tournament_id, status, created_at);
CREATE TABLE IF NOT EXISTS public.tournament_certificates (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
code text NOT NULL UNIQUE, -- 10 symbols, redeem alphabet
tournament_id uuid NOT NULL REFERENCES public.el3ab_tournaments(id) ON DELETE CASCADE,
player_id uuid REFERENCES public.profiles(id) ON DELETE SET NULL,
team_id uuid REFERENCES public.tournament_teams(id) ON DELETE SET NULL,
kind text NOT NULL,
place int,
category_id uuid REFERENCES public.tournament_prize_categories(id) ON DELETE SET NULL,
name_snapshot text NOT NULL,
issued_at timestamptz NOT NULL DEFAULT now(),
revoked_at timestamptz,
revoke_reason text,
CONSTRAINT tcert_chk CHECK (kind IN ('participation','placement','category','team')),
CONSTRAINT tcert_uq UNIQUE NULLS NOT DISTINCT (tournament_id, player_id, team_id, kind, category_id)
);
CREATE TABLE IF NOT EXISTS public.tournament_notices_sent (
tournament_id uuid NOT NULL REFERENCES public.el3ab_tournaments(id) ON DELETE CASCADE,
player_id uuid NOT NULL REFERENCES public.profiles(id) ON DELETE CASCADE,
kind text NOT NULL, -- t24h, t1h, checkin_open, t5m, round:<n>, ko:<slot>:<game>, waitlist_promoted, prize, ann:<id>
sent_at timestamptz NOT NULL DEFAULT now(),
PRIMARY KEY (tournament_id, player_id, kind)
);
CREATE TABLE IF NOT EXISTS public.tournament_forbidden_pairs (
tournament_id uuid NOT NULL REFERENCES public.el3ab_tournaments(id) ON DELETE CASCADE,
player_a uuid NOT NULL,
player_b uuid NOT NULL,
until_round int,
reason text,
created_by_admin uuid REFERENCES public.admin_users(id) ON DELETE SET NULL,
created_at timestamptz NOT NULL DEFAULT now(),
PRIMARY KEY (tournament_id, player_a, player_b),
CONSTRAINT tfp_order_chk CHECK (player_a < player_b)
);
-- 12. Games, groups --------------------------------------------------------
CREATE INDEX IF NOT EXISTS idx_matches_tournament ON public.matches (tournament_id, tournament_round) WHERE tournament_id IS NOT NULL;
ALTER TABLE public.backgammon_matches ADD COLUMN IF NOT EXISTS tournament_id uuid REFERENCES public.el3ab_tournaments(id) ON DELETE SET NULL,
ADD COLUMN IF NOT EXISTS tournament_round int, ADD COLUMN IF NOT EXISTS pairing_id uuid;
ALTER TABLE public.domino_matches ADD COLUMN IF NOT EXISTS tournament_id uuid REFERENCES public.el3ab_tournaments(id) ON DELETE SET NULL,
ADD COLUMN IF NOT EXISTS tournament_round int, ADD COLUMN IF NOT EXISTS pairing_id uuid;
ALTER TABLE public.ludo_matches ADD COLUMN IF NOT EXISTS tournament_id uuid REFERENCES public.el3ab_tournaments(id) ON DELETE SET NULL,
ADD COLUMN IF NOT EXISTS tournament_round int, ADD COLUMN IF NOT EXISTS pairing_id uuid;
CREATE UNIQUE INDEX IF NOT EXISTS backgammon_matches_pairing_uq ON public.backgammon_matches (pairing_id) WHERE pairing_id IS NOT NULL;
CREATE UNIQUE INDEX IF NOT EXISTS domino_matches_pairing_uq ON public.domino_matches (pairing_id) WHERE pairing_id IS NOT NULL;
CREATE UNIQUE INDEX IF NOT EXISTS ludo_matches_pairing_uq ON public.ludo_matches (pairing_id) WHERE pairing_id IS NOT NULL;
ALTER TABLE public.groups
ADD COLUMN IF NOT EXISTS org_id uuid REFERENCES public.el3ab_organizations(id) ON DELETE SET NULL,
ADD COLUMN IF NOT EXISTS kind text NOT NULL DEFAULT 'player',
ADD COLUMN IF NOT EXISTS tournament_id uuid REFERENCES public.el3ab_tournaments(id) ON DELETE CASCADE;
ALTER TABLE public.groups
DROP CONSTRAINT IF EXISTS groups_kind_chk,
ADD CONSTRAINT groups_kind_chk CHECK (kind IN ('player','org','tournament')
AND (kind <> 'org' OR org_id IS NOT NULL)
AND (kind <> 'tournament' OR tournament_id IS NOT NULL));
CREATE INDEX IF NOT EXISTS groups_org_idx ON public.groups (org_id) WHERE org_id IS NOT NULL;
CREATE UNIQUE INDEX IF NOT EXISTS groups_tournament_uq ON public.groups (tournament_id) WHERE kind = 'tournament';
-- 13. Storage ----------------------------------------------------------------
INSERT INTO storage.buckets (id, name, public, file_size_limit, allowed_mime_types) VALUES
('tournament-media', 'tournament-media', true, 5242880, ARRAY['image/jpeg','image/png','image/webp']),
('tournament-uploads', 'tournament-uploads', false, 10485760, ARRAY['image/jpeg','image/png','image/webp','application/pdf'])
ON CONFLICT (id) DO NOTHING;
-- 14. RLS: service role only. Reads go through PHP. service_role bypasses RLS,
-- so no policy is needed for it; anon and authenticated keep no grant at all.
DO $$ DECLARE t text; BEGIN
FOREACH t IN ARRAY ARRAY[
'tournament_private_settings','tournament_teams','tournament_team_matches','tournament_stage_entries',
'tournament_form_fields','tournament_registration_answers','tournament_prize_categories','tournament_disputes',
'tournament_certificates','tournament_notices_sent','tournament_forbidden_pairs',
'tournament_phases','tournament_brackets','bracket_matches','tournament_registrations','tournament_media',
'tournament_ad_slots','tournament_announcements','sponsors','tournament_sponsorships','tournament_prize_payouts']
LOOP
EXECUTE format('ALTER TABLE public.%I ENABLE ROW LEVEL SECURITY', t);
EXECUTE format('REVOKE ALL ON TABLE public.%I FROM PUBLIC, anon, authenticated', t);
EXECUTE format('GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE public.%I TO service_role', t);
END LOOP; END $$;
-- Realtime stays on el3ab_tournaments and el3ab_tournament_rounds: their read
-- policies are kept exactly as they are (created only if somehow missing).
DO $$ BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_policy WHERE polrelid = 'public.el3ab_tournaments'::regclass
AND polname = 'Tournaments viewable by everyone') THEN
CREATE POLICY "Tournaments viewable by everyone" ON public.el3ab_tournaments FOR SELECT USING (true);
END IF;
IF NOT EXISTS (SELECT 1 FROM pg_policy WHERE polrelid = 'public.el3ab_tournament_rounds'::regclass
AND polname = 'tournament_rounds_viewable_by_everyone') THEN
CREATE POLICY tournament_rounds_viewable_by_everyone ON public.el3ab_tournament_rounds FOR SELECT USING (true);
END IF;
END $$;
COMMIT;
NOTIFY pgrst, 'reload schema';
-- ===========================================================================
-- ROLLBACK, by hand only. It undoes this file and nothing else.
--
-- * First drop or revert the functions of 2026-10-27-hosting-{engine,
-- bracket,registration,prizes}.sql: they read the columns removed here.
-- * Everything stored in the new tables and columns is lost. Take a backup.
-- * It restores the old checks, so it fails (and changes nothing) if a row
-- already holds a new value, for example a stage in 'cut_review'.
-- * It puts back the grants anon and authenticated held before. They gave
-- nothing away (RLS had no policy for them on these tables); drop those two
-- GRANT lines if you do not want them back.
--
-- Use: copy the lines between the markers and remove the leading "-- ".
-- ROLLBACK-BEGIN
-- BEGIN;
-- SET LOCAL lock_timeout = '5s';
-- DROP TABLE IF EXISTS public.tournament_registration_answers, public.tournament_form_fields,
-- public.tournament_certificates, public.tournament_disputes, public.tournament_notices_sent,
-- public.tournament_forbidden_pairs, public.tournament_stage_entries, public.tournament_team_matches,
-- public.tournament_private_settings;
-- ALTER TABLE public.tournament_prize_payouts DROP CONSTRAINT IF EXISTS tpp_uq, DROP CONSTRAINT IF EXISTS tpp_status_chk,
-- DROP COLUMN IF EXISTS category_id, DROP COLUMN IF EXISTS team_id, DROP COLUMN IF EXISTS status,
-- DROP COLUMN IF EXISTS hold_reason, DROP COLUMN IF EXISTS released_at, DROP COLUMN IF EXISTS released_by_admin;
-- DROP TABLE IF EXISTS public.tournament_prize_categories;
-- DROP INDEX IF EXISTS public.treg_player_status_idx, public.treg_tournament_status_idx;
-- ALTER TABLE public.tournament_registrations DROP CONSTRAINT IF EXISTS treg_status_chk, DROP CONSTRAINT IF EXISTS treg_shape_chk,
-- ALTER COLUMN tournament_id DROP NOT NULL, ALTER COLUMN player_id DROP NOT NULL, ALTER COLUMN status DROP NOT NULL,
-- DROP COLUMN IF EXISTS represent_org_id, DROP COLUMN IF EXISTS team_id, DROP COLUMN IF EXISTS board_order,
-- DROP COLUMN IF EXISTS team_role, DROP COLUMN IF EXISTS checked_in_at, DROP COLUMN IF EXISTS check_in_source,
-- DROP COLUMN IF EXISTS waitlisted_at, DROP COLUMN IF EXISTS promoted_at, DROP COLUMN IF EXISTS form_status,
-- DROP COLUMN IF EXISTS form_submitted_at, DROP COLUMN IF EXISTS reviewed_by_admin, DROP COLUMN IF EXISTS reviewed_at,
-- DROP COLUMN IF EXISTS review_note, DROP COLUMN IF EXISTS requested_byes, DROP COLUMN IF EXISTS late_entry_round,
-- DROP COLUMN IF EXISTS fair_play_status, DROP COLUMN IF EXISTS source, DROP COLUMN IF EXISTS redemption_id,
-- DROP COLUMN IF EXISTS staff_note, DROP COLUMN IF EXISTS updated_at;
-- DROP INDEX IF EXISTS public.bracket_matches_slot_uq;
-- ALTER TABLE public.bracket_matches DROP CONSTRAINT IF EXISTS bracket_matches_shape_chk,
-- DROP CONSTRAINT IF EXISTS bracket_matches_status_check,
-- ADD CONSTRAINT bracket_matches_status_check CHECK (status IN ('pending','ready','in_progress','completed','bye')),
-- DROP COLUMN IF EXISTS round_ref, DROP COLUMN IF EXISTS pairing_id, DROP COLUMN IF EXISTS match_id,
-- DROP COLUMN IF EXISTS games, DROP COLUMN IF EXISTS best_of, DROP COLUMN IF EXISTS points_a,
-- DROP COLUMN IF EXISTS points_b, DROP COLUMN IF EXISTS tiebreak_stage, DROP COLUMN IF EXISTS decided_by,
-- DROP COLUMN IF EXISTS team_a_id, DROP COLUMN IF EXISTS team_b_id, DROP COLUMN IF EXISTS winner_team_id,
-- DROP COLUMN IF EXISTS disputed;
-- DROP TABLE IF EXISTS public.tournament_teams;
-- ALTER TABLE public.tournament_brackets DROP COLUMN IF EXISTS size, DROP COLUMN IF EXISTS third_place;
-- ALTER TABLE public.el3ab_tournament_rounds DROP CONSTRAINT IF EXISTS el3ab_rounds_kind_chk,
-- DROP CONSTRAINT IF EXISTS el3ab_rounds_status_chk,
-- DROP COLUMN IF EXISTS phase_id, DROP COLUMN IF EXISTS stage_round, DROP COLUMN IF EXISTS kind,
-- DROP COLUMN IF EXISTS scheduled_start_at, DROP COLUMN IF EXISTS published_at, DROP COLUMN IF EXISTS version;
-- ALTER TABLE public.tournament_phases DROP CONSTRAINT IF EXISTS tournament_phases_shape_chk,
-- DROP CONSTRAINT IF EXISTS tournament_phases_type_check,
-- ADD CONSTRAINT tournament_phases_type_check CHECK (type IN
-- ('swiss','round_robin','single_elimination','double_elimination','arena','group_stage')),
-- DROP CONSTRAINT IF EXISTS tournament_phases_status_check,
-- ADD CONSTRAINT tournament_phases_status_check CHECK (status IN ('pending','in_progress','completed')),
-- DROP COLUMN IF EXISTS entrant_kind, DROP COLUMN IF EXISTS time_control, DROP COLUMN IF EXISTS custom_time_initial_ms,
-- DROP COLUMN IF EXISTS custom_time_increment_ms, DROP COLUMN IF EXISTS rounds_planned, DROP COLUMN IF EXISTS first_round,
-- DROP COLUMN IF EXISTS last_round, DROP COLUMN IF EXISTS tiebreak_rules, DROP COLUMN IF EXISTS starts_at,
-- DROP COLUMN IF EXISTS cut_review_until, DROP COLUMN IF EXISTS cut_confirmed_at, DROP COLUMN IF EXISTS cut_confirmed_by,
-- DROP COLUMN IF EXISTS standings_snapshot;
-- DROP INDEX IF EXISTS public.idx_el3ab_tournaments_org, public.idx_el3ab_tournaments_status_start;
-- ALTER TABLE public.el3ab_tournaments DROP CONSTRAINT IF EXISTS el3ab_t_status_chk, DROP CONSTRAINT IF EXISTS el3ab_t_entrant_chk,
-- DROP CONSTRAINT IF EXISTS el3ab_t_access_chk, DROP CONSTRAINT IF EXISTS el3ab_t_checkin_chk, DROP CONSTRAINT IF EXISTS el3ab_t_misc_chk,
-- DROP COLUMN IF EXISTS entrant_kind, DROP COLUMN IF EXISTS team_size, DROP COLUMN IF EXISTS team_reserves,
-- DROP COLUMN IF EXISTS team_scoring, DROP COLUMN IF EXISTS allowed_org_ids, DROP COLUMN IF EXISTS max_per_org,
-- DROP COLUMN IF EXISTS entry_conditions, DROP COLUMN IF EXISTS access, DROP COLUMN IF EXISTS approval_required,
-- DROP COLUMN IF EXISTS waitlist_enabled, DROP COLUMN IF EXISTS waitlist_max, DROP COLUMN IF EXISTS check_in_required,
-- DROP COLUMN IF EXISTS check_in_mode, DROP COLUMN IF EXISTS check_in_opens_minutes, DROP COLUMN IF EXISTS check_in_closes_minutes,
-- DROP COLUMN IF EXISTS check_in_closed_at, DROP COLUMN IF EXISTS form_required_to_play, DROP COLUMN IF EXISTS round_schedule,
-- DROP COLUMN IF EXISTS round_break_minutes, DROP COLUMN IF EXISTS bye_value, DROP COLUMN IF EXISTS rules_ar,
-- DROP COLUMN IF EXISTS dispute_window_minutes, DROP COLUMN IF EXISTS prize_review_required, DROP COLUMN IF EXISTS prizes_status,
-- DROP COLUMN IF EXISTS prizes_released_at, DROP COLUMN IF EXISTS prizes_released_by_admin, DROP COLUMN IF EXISTS completed_at;
-- DROP INDEX IF EXISTS public.tournament_media_slot_uq;
-- ALTER TABLE public.tournament_media DROP CONSTRAINT IF EXISTS tournament_media_kind_chk,
-- DROP COLUMN IF EXISTS kind, DROP COLUMN IF EXISTS storage_path, DROP COLUMN IF EXISTS width, DROP COLUMN IF EXISTS height,
-- DROP COLUMN IF EXISTS alt_ar, DROP COLUMN IF EXISTS show_in_app, DROP COLUMN IF EXISTS uploaded_by_admin;
-- ALTER TABLE public.tournament_ad_slots DROP CONSTRAINT IF EXISTS tournament_ad_slots_app_chk,
-- DROP CONSTRAINT IF EXISTS tournament_ad_slots_position_check,
-- ADD CONSTRAINT tournament_ad_slots_position_check CHECK (position IN
-- ('hero_top','hero_bottom','sidebar_top','sidebar_bottom','between_sections','footer','ticker')),
-- DROP CONSTRAINT IF EXISTS tournament_ad_slots_slot_type_check,
-- ADD CONSTRAINT tournament_ad_slots_slot_type_check CHECK (slot_type IN ('image','html','script')),
-- DROP COLUMN IF EXISTS sponsor_id, DROP COLUMN IF EXISTS image_path, DROP COLUMN IF EXISTS alt_ar;
-- DROP INDEX IF EXISTS public.tournament_sponsorships_uq;
-- ALTER TABLE public.tournament_sponsorships DROP CONSTRAINT IF EXISTS tournament_sponsorships_chk,
-- ALTER COLUMN sponsor_id DROP NOT NULL, ALTER COLUMN tournament_id DROP NOT NULL,
-- DROP CONSTRAINT IF EXISTS tournament_sponsorships_tournament_id_fkey,
-- ADD CONSTRAINT tournament_sponsorships_tournament_id_fkey FOREIGN KEY (tournament_id) REFERENCES public.el3ab_tournaments(id),
-- DROP COLUMN IF EXISTS tier, DROP COLUMN IF EXISTS label_ar, DROP COLUMN IF EXISTS link_url, DROP COLUMN IF EXISTS logo_url,
-- DROP COLUMN IF EXISTS display_order, DROP COLUMN IF EXISTS is_active, DROP COLUMN IF EXISTS show_in_app,
-- DROP COLUMN IF EXISTS show_on_live, DROP COLUMN IF EXISTS impressions, DROP COLUMN IF EXISTS clicks,
-- DROP COLUMN IF EXISTS updated_at;
-- ALTER TABLE public.sponsors DROP CONSTRAINT IF EXISTS sponsors_url_chk,
-- DROP COLUMN IF EXISTS logo_path, DROP COLUMN IF EXISTS description_ar, DROP COLUMN IF EXISTS created_by_admin,
-- DROP COLUMN IF EXISTS updated_at;
-- DROP INDEX IF EXISTS public.tann_notify_idx;
-- ALTER TABLE public.tournament_announcements DROP CONSTRAINT IF EXISTS tournament_announcements_audience_chk,
-- DROP CONSTRAINT IF EXISTS tournament_announcements_type_check,
-- ADD CONSTRAINT tournament_announcements_type_check CHECK (type IN ('info','result','alert','commentary','milestone')),
-- DROP COLUMN IF EXISTS title_ar, DROP COLUMN IF EXISTS content_ar, DROP COLUMN IF EXISTS audience,
-- DROP COLUMN IF EXISTS audience_ref, DROP COLUMN IF EXISTS show_in_app, DROP COLUMN IF EXISTS notify,
-- DROP COLUMN IF EXISTS notified_at, DROP COLUMN IF EXISTS notified_count, DROP COLUMN IF EXISTS expires_at,
-- DROP COLUMN IF EXISTS created_by_admin, DROP COLUMN IF EXISTS updated_at;
-- DROP INDEX IF EXISTS public.idx_matches_tournament;
-- ALTER TABLE public.backgammon_matches DROP COLUMN IF EXISTS tournament_id, DROP COLUMN IF EXISTS tournament_round, DROP COLUMN IF EXISTS pairing_id;
-- ALTER TABLE public.domino_matches DROP COLUMN IF EXISTS tournament_id, DROP COLUMN IF EXISTS tournament_round, DROP COLUMN IF EXISTS pairing_id;
-- ALTER TABLE public.ludo_matches DROP COLUMN IF EXISTS tournament_id, DROP COLUMN IF EXISTS tournament_round, DROP COLUMN IF EXISTS pairing_id;
-- ALTER TABLE public.groups DROP CONSTRAINT IF EXISTS groups_kind_chk,
-- DROP COLUMN IF EXISTS org_id, DROP COLUMN IF EXISTS kind, DROP COLUMN IF EXISTS tournament_id;
-- DELETE FROM storage.buckets b WHERE b.id IN ('tournament-media','tournament-uploads')
-- AND NOT EXISTS (SELECT 1 FROM storage.objects o WHERE o.bucket_id = b.id);
-- REVOKE ALL ON TABLE public.tournament_phases, public.tournament_brackets, public.bracket_matches,
-- public.tournament_registrations, public.tournament_media, public.tournament_ad_slots, public.tournament_announcements,
-- public.sponsors, public.tournament_sponsorships, public.tournament_prize_payouts FROM service_role;
-- GRANT ALL ON TABLE public.tournament_phases, public.tournament_brackets, public.bracket_matches,
-- public.tournament_registrations, public.tournament_media, public.tournament_ad_slots, public.tournament_announcements,
-- public.sponsors, public.tournament_sponsorships, public.tournament_prize_payouts TO anon;
-- GRANT ALL ON TABLE public.tournament_phases, public.tournament_brackets, public.bracket_matches,
-- public.tournament_registrations, public.tournament_media, public.tournament_ad_slots, public.tournament_announcements,
-- public.sponsors, public.tournament_sponsorships, public.tournament_prize_payouts TO authenticated;
-- GRANT ALL ON TABLE public.tournament_phases, public.tournament_brackets, public.bracket_matches,
-- public.tournament_registrations, public.tournament_media, public.tournament_ad_slots, public.tournament_announcements,
-- public.sponsors, public.tournament_sponsorships, public.tournament_prize_payouts TO service_role;
-- COMMIT;
-- NOTIFY pgrst, 'reload schema';
-- ROLLBACK-END
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment