Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions hasura/functions/leagues/league_season_lifecycle.sql
Original file line number Diff line number Diff line change
Expand Up @@ -342,10 +342,12 @@ BEGIN
DELETE FROM public.league_relegation_playoffs WHERE league_season_id = _league_season_id;
DELETE FROM public.league_team_movements WHERE league_season_id = _league_season_id;

-- Revive to Live; tau_league_seasons re-runs start_league_season, which
-- rebuilds the division tournaments from the same Approved team seasons.
-- Reset to Setup so the admin can reconfigure (dates, divisions, teams) and
-- run the season again from scratch; the torn-down tournaments are rebuilt
-- when it next reaches Live. The bypass lets the Canceled -> Setup transition
-- past the terminal-status and source-status guards.
PERFORM set_config('fivestack.league_restart', 'true', true);
UPDATE public.league_seasons SET status = 'Live' WHERE id = _league_season_id;
UPDATE public.league_seasons SET status = 'Setup' WHERE id = _league_season_id;
PERFORM set_config('fivestack.league_restart', 'false', true);

PERFORM set_config('fivestack.league_cascade', 'false', true);
Expand Down
25 changes: 15 additions & 10 deletions hasura/functions/players/get_player_elo.sql
Original file line number Diff line number Diff line change
Expand Up @@ -46,24 +46,29 @@ END;
$$;

-- Season ELO: latest ELO tagged to this season (season_id-indexed lookup; rows are
-- populated by the season backfill).
-- populated by the season backfill). Falls back to the player's lifetime ELO when
-- they have no rating in this season yet (e.g. the start of a new season) so the UI
-- shows their last known ELO instead of a blank.
CREATE OR REPLACE FUNCTION public.get_player_season_elo_by_type(player public.players, _type text, _season_id UUID) RETURNS numeric
LANGUAGE plpgsql STABLE
AS $$
DECLARE
elo_value numeric;
BEGIN
IF _season_id IS NULL THEN
RETURN NULL;
IF _season_id IS NOT NULL THEN
SELECT current INTO elo_value
FROM player_elo
WHERE steam_id = player.steam_id
AND "type" = _type
AND season_id = _season_id
ORDER BY created_at DESC
LIMIT 1;
END IF;

SELECT current INTO elo_value
FROM player_elo
WHERE steam_id = player.steam_id
AND "type" = _type
AND season_id = _season_id
ORDER BY created_at DESC
LIMIT 1;
-- No season rating yet: fall back to lifetime (global) ELO for this type.
IF elo_value IS NULL THEN
elo_value := get_player_elo_by_type(player, _type);
END IF;

RETURN elo_value;
END;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
ALTER TABLE public.events
ALTER COLUMN starts_at DROP NOT NULL;

ALTER TABLE public.events
ALTER COLUMN starts_at DROP DEFAULT;
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
-- Every event must have a start date. The start date is the lower bound of
-- the event window in public.v_event_matches: a match belongs to the event
-- only when it falls in [starts_at, ends_at + 1 day) AND involves a tracked
-- team/player (or sits in an attached tournament's bracket). Without a start
-- date the window opened to -infinity and pulled in an attached team's or
-- player's entire match history (lifetime stats/highlights).
--
-- Backfill existing dateless events to today, then enforce NOT NULL. Setting
-- starts_at fires tg_events_sync_match_links (AFTER UPDATE OF starts_at), so
-- event_match_links is re-derived for each backfilled event as part of this
-- migration.
UPDATE public.events
SET starts_at = date_trunc('day', now())
WHERE starts_at IS NULL;

-- New events without an explicit start default to today rather than being
-- rejected outright; the event form also requires the field.
ALTER TABLE public.events
ALTER COLUMN starts_at SET DEFAULT date_trunc('day', now());

ALTER TABLE public.events
ALTER COLUMN starts_at SET NOT NULL;
3 changes: 3 additions & 0 deletions hasura/triggers/event_match_links.sql
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,9 @@ CREATE TRIGGER tg_mlp_sync_event_match_links

-- Full backfill/prune. Runs only when this file's digest changes; keeps the
-- table exact after upgrades that alter the derivation.
-- Re-run trigger: v_event_matches now bounds team/player-derived matches to
-- the event window (missing start date -> no windowed matches), so this prune
-- must drop the stale lifetime links a previous unbounded window inserted.
DELETE FROM public.event_match_links l
WHERE NOT EXISTS (
SELECT 1 FROM public.v_event_matches v
Expand Down
10 changes: 8 additions & 2 deletions hasura/triggers/league_seasons.sql
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ BEGIN
END IF;

IF NEW.status IS DISTINCT FROM OLD.status THEN
-- restart_league_season revives a Canceled season back to Live; it sets
-- restart_league_season resets a Canceled season back to Setup; it sets
-- this bypass so the terminal-status and source-status guards stand aside.
IF OLD.status IN ('Finished', 'Canceled')
AND current_setting('fivestack.league_restart', true) IS DISTINCT FROM 'true' THEN
Expand Down Expand Up @@ -163,7 +163,10 @@ BEGIN
RAISE EXCEPTION USING ERRCODE = '22000', MESSAGE = 'Season can only finish from Live or Playoffs';
END IF;
WHEN 'Setup' THEN
IF OLD.status != 'RegistrationOpen' THEN
-- Normally only a re-opened registration reverts to Setup; a
-- restart also resets a Canceled season back to Setup.
IF OLD.status != 'RegistrationOpen'
AND current_setting('fivestack.league_restart', true) IS DISTINCT FROM 'true' THEN
RAISE EXCEPTION USING ERRCODE = '22000', MESSAGE = 'Season can only return to Setup from RegistrationOpen';
END IF;
WHEN 'Canceled' THEN
Expand All @@ -174,7 +177,10 @@ BEGIN
END IF;

-- Re-validate scheduling when the window moves (or a season is revived).
-- A restart to Setup skips this: the whole point is to reconfigure, and the
-- old (possibly stale/overlapping) dates get fixed before it runs again.
IF NEW.status NOT IN ('Canceled', 'Finished')
AND current_setting('fivestack.league_restart', true) IS DISTINCT FROM 'true'
AND (NEW.signup_opens_at IS DISTINCT FROM OLD.signup_opens_at
OR NEW.starts_at IS DISTINCT FROM OLD.starts_at
OR NEW.match_weeks_count IS DISTINCT FROM OLD.match_weeks_count
Expand Down
22 changes: 17 additions & 5 deletions hasura/views/v_event_matches.sql
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,28 @@
-- - one of its lineup players is an attached player and the match falls
-- inside the event window.
--
-- The window end gets a day of grace so matches that start on the final
-- evening still count when ends_at is set to midday; missing dates leave
-- that side of the window open.
-- The window is [starts_at, end + 1 day):
-- - window_start is the event's starts_at. An event with NO start date is
-- excluded from the windowed (team/player) branches entirely — otherwise
-- a missing start defaulted to -infinity and pulled in an attached team's
-- or player's ENTIRE match history (lifetime stats/highlights instead of
-- just this event's). Dateless events still get their attached
-- tournaments' bracket matches (that branch needs no window).
-- - window_end is ends_at + 1 day, or, for an ongoing event with no end
-- date, now() + 1 day so every match played up to today is captured. The
-- day of grace lets matches that start on the final evening still count
-- when ends_at is set to midday.
-- event_match_links materializes this view via triggers; a match completed
-- while an event is ongoing is re-derived on its started_at update (see
-- hasura/triggers/event_match_links.sql), so now() here stays accurate.
CREATE OR REPLACE VIEW public.v_event_matches AS
WITH windowed AS (
SELECT
e.id AS event_id,
COALESCE(e.starts_at, '-infinity'::timestamptz) AS window_start,
COALESCE(e.ends_at + interval '1 day', 'infinity'::timestamptz) AS window_end
e.starts_at AS window_start,
COALESCE(e.ends_at, now()) + interval '1 day' AS window_end
FROM events e
WHERE e.starts_at IS NOT NULL
)
SELECT DISTINCT s.event_id, s.match_id
FROM (
Expand Down
Loading