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
1,052 changes: 1,051 additions & 1 deletion generated/schema.graphql

Large diffs are not rendered by default.

1,326 changes: 1,300 additions & 26 deletions generated/schema.ts

Large diffs are not rendered by default.

61,907 changes: 31,657 additions & 30,250 deletions generated/types.ts

Large diffs are not rendered by default.

14 changes: 13 additions & 1 deletion hasura/enums/notification-types.sql
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,18 @@ INSERT INTO e_notification_types ("value", "description") VALUES
('LeagueProposalDeclined', 'Your league match time proposal was declined'),
('LeagueMatchUnscheduled', 'A league matchup is unscheduled and will default soon'),
('LeagueRegistrationDecision', 'Your league registration was reviewed'),
('LeagueRosterUndersized', 'Your league team no longer meets the minimum roster size')
('LeagueRosterUndersized', 'Your league team no longer meets the minimum roster size'),
('ChatMessage', 'A new message in a chat you are part of'),
('TournamentCreated', 'Registration opened for a tournament'),
('TournamentReminder', 'A tournament you are registered for starts soon'),
('NewsPublished', 'A news article was published'),
('TeamInvite', 'You were invited to a team'),
('TournamentTeamInvite', 'You were invited to play in a tournament'),
('DraftInvite', 'You were invited to a draft lobby'),
('MatchStatsReady', 'Stats for a match you played are ready'),
('ClipReady', 'A clip you requested finished rendering'),
('AwardGranted', 'You received an award'),
('EventReminder', 'An event you are attending starts soon'),
('SeasonEnded', 'A season has ended')
ON CONFLICT("value") DO UPDATE
SET "description" = EXCLUDED."description";
32 changes: 24 additions & 8 deletions hasura/functions/demos/persist_imported_demo.sql
Original file line number Diff line number Diff line change
Expand Up @@ -279,8 +279,15 @@ BEGIN
v_match_id, v_match_map_id,
rt_match.round,
v_start_time + ((elem->>'tick')::int::numeric / v_tick_rate::numeric) * interval '1 second',
NULLIF(elem->>'killer', '')::bigint,
NULLIF(elem->>'killer_team', ''),
-- A bomb or world death has no killer. The live path records those as
-- self-inflicted (see KillEvent.ts, "self damage") so the death still
-- lands; mirror it here rather than dropping the row, or the same death
-- counts in a live match and vanishes from an imported demo. Attributing
-- it to the victim does not invent a kill: recompute_player_match_map_stats
-- counts kills FILTER (attacker_team <> attacked_team), and a self-kill has
-- the same team on both sides.
COALESCE(NULLIF(elem->>'killer', '')::bigint, NULLIF(elem->>'victim', '')::bigint),
COALESCE(NULLIF(elem->>'killer_team', ''), NULLIF(elem->>'victim_team', '')),
'',
NULLIF(concat_ws(' ', elem->>'attacker_x', elem->>'attacker_y', elem->>'attacker_z'), ''),
NULLIF(elem->>'victim', '')::bigint,
Expand All @@ -296,16 +303,22 @@ BEGIN
false, false,
NULLIF(elem->>'assist', '') IS NOT NULL
FROM jsonb_array_elements(COALESCE(p_parsed->'kills', '[]'::jsonb)) elem
CROSS JOIN LATERAL (
LEFT JOIN LATERAL (
SELECT COALESCE((rt->>'round')::int, 0) AS round
FROM jsonb_array_elements(COALESCE(p_parsed->'round_ticks', '[]'::jsonb)) rt
WHERE (elem->>'tick')::int >= COALESCE((rt->>'start_tick')::int, 0)
AND (elem->>'tick')::int <= COALESCE((rt->>'end_tick')::int, 2147483647)
ORDER BY (rt->>'round')::int
LIMIT 1
) rt_match
) rt_match ON true
WHERE NULLIF(elem->>'victim', '') IS NOT NULL
AND NULLIF(elem->>'killer', '') IS NOT NULL;
-- A death outside every round window did not happen during play: the demo
-- keeps recording through the post-match walkaround, where players shoot
-- each other for fun. That is not a scoreboard death, so it is dropped.
-- Written as a LEFT JOIN plus an explicit test rather than leaning on a
-- CROSS JOIN quietly matching nothing — the exclusion is deliberate, and
-- the next person to touch this join should have to mean it.
AND rt_match.round IS NOT NULL;

INSERT INTO public.player_assists (
match_id, match_map_id, round, time,
Expand All @@ -323,16 +336,19 @@ BEGIN
COALESCE(NULLIF(elem->>'victim_team', ''), ''),
COALESCE((elem->>'assist_flash')::boolean, false)
FROM jsonb_array_elements(COALESCE(p_parsed->'kills', '[]'::jsonb)) elem
CROSS JOIN LATERAL (
LEFT JOIN LATERAL (
SELECT COALESCE((rt->>'round')::int, 0) AS round
FROM jsonb_array_elements(COALESCE(p_parsed->'round_ticks', '[]'::jsonb)) rt
WHERE (elem->>'tick')::int >= COALESCE((rt->>'start_tick')::int, 0)
AND (elem->>'tick')::int <= COALESCE((rt->>'end_tick')::int, 2147483647)
ORDER BY (rt->>'round')::int
LIMIT 1
) rt_match
) rt_match ON true
WHERE NULLIF(elem->>'assist', '') IS NOT NULL
AND NULLIF(elem->>'victim', '') IS NOT NULL;
AND NULLIF(elem->>'victim', '') IS NOT NULL
-- Same round gate as the kills insert above, so a post-match kill and its
-- assist are dropped together rather than leaving an orphaned assist.
AND rt_match.round IS NOT NULL;

INSERT INTO public.player_damages (
match_id, match_map_id, round, time,
Expand Down
40 changes: 40 additions & 0 deletions hasura/functions/notifications/is_quiet_hours.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
-- True when the player's local time falls inside their quiet window.
--
-- Gates PUSH only. The bell keeps collecting, so nothing is lost -- quiet hours
-- silence the buzz, they don't drop the notification.
CREATE OR REPLACE FUNCTION public.is_quiet_hours(
_start time,
_end time,
_timezone text
) RETURNS boolean
LANGUAGE plpgsql
-- STABLE, not IMMUTABLE: the answer depends on now(). Marked IMMUTABLE the
-- planner may fold the call at plan time and reuse it across executions of a
-- cached plan, which keeps returning the stale answer across a window boundary.
STABLE
AS $$
DECLARE
_local time;
BEGIN
-- Unset, or a zero-width window, means quiet hours are off.
IF _start IS NULL OR _end IS NULL OR _start = _end THEN
RETURN false;
END IF;

BEGIN
_local := (now() AT TIME ZONE COALESCE(_timezone, 'UTC'))::time;
EXCEPTION WHEN OTHERS THEN
-- An unrecognised zone would otherwise raise and take the whole
-- recipient query with it, silencing everyone rather than nobody.
_local := (now() AT TIME ZONE 'UTC')::time;
END;

-- A window that wraps midnight (22:00 -> 07:00) is the common case, so it
-- gets its own branch rather than being handled by accident.
IF _start < _end THEN
RETURN _local >= _start AND _local < _end;
END IF;

RETURN _local >= _start OR _local < _end;
END;
$$;
35 changes: 16 additions & 19 deletions hasura/functions/tournaments/clone_match_options.sql
Original file line number Diff line number Diff line change
Expand Up @@ -3,31 +3,28 @@ CREATE OR REPLACE FUNCTION clone_match_options(
)
RETURNS uuid AS $$
DECLARE
cloned_id uuid;
_options match_options%ROWTYPE;
BEGIN
IF _match_options_id IS NULL THEN
RETURN NULL;
END IF;

INSERT INTO match_options (
overtime, knife_round, mr, best_of, coaches, number_of_substitutes,
map_veto, timeout_setting, tech_timeout_setting, map_pool_id, type,
regions, prefer_dedicated_server, invite_code,
region_veto, ready_setting, check_in_setting, default_models, tv_delay,
auto_cancellation, match_mode, auto_cancel_duration, live_match_timeout,
veto_pick_timeout
)
SELECT
overtime, knife_round, mr, best_of, coaches, number_of_substitutes,
map_veto, timeout_setting, tech_timeout_setting, map_pool_id, type,
regions, prefer_dedicated_server, invite_code,
region_veto, ready_setting, check_in_setting, default_models, tv_delay,
auto_cancellation, match_mode, auto_cancel_duration, live_match_timeout,
veto_pick_timeout
SELECT * INTO _options
FROM match_options
WHERE id = _match_options_id
RETURNING id INTO cloned_id;
WHERE id = _match_options_id;

RETURN cloned_id;
IF NOT FOUND THEN
RETURN NULL;
END IF;

-- Whole-row copy instead of an explicit column list. Every hand-maintained
-- list of match_options columns has silently dropped settings as new ones
-- were added (round_restart_delay, halftime_pausematch, auto_cancellation),
-- producing tournaments whose matches quietly ignore their own settings.
_options.id := gen_random_uuid();

INSERT INTO match_options VALUES (_options.*);

RETURN _options.id;
END;
$$ LANGUAGE plpgsql;
59 changes: 13 additions & 46 deletions hasura/functions/tournaments/update_match_options_best_of.sql
Original file line number Diff line number Diff line change
Expand Up @@ -4,70 +4,37 @@ CREATE OR REPLACE FUNCTION update_match_options_best_of(
RETURNS uuid AS $$
DECLARE
original_match_options_id uuid;
match_options_record match_options%ROWTYPE;
final_match_options_id uuid;
_decider_best_of int;
_effective_best_of int;
_current_best_of int;
BEGIN
-- Get match_options_id from stage first, then tournament if stage doesn't have one
SELECT COALESCE(
ts.match_options_id,
t.match_options_id
) INTO original_match_options_id
SELECT COALESCE(ts.match_options_id, t.match_options_id), ts.decider_best_of
INTO original_match_options_id, _decider_best_of
FROM tournament_stages ts
INNER JOIN tournaments t ON t.id = ts.tournament_id
WHERE ts.id = _stage_id;

-- If no match_options_id found, return NULL
IF original_match_options_id IS NULL THEN
RETURN NULL;
END IF;

-- Get match_options record
SELECT * INTO match_options_record
FROM match_options
WHERE id = original_match_options_id;

-- Read decider_best_of from the stage
SELECT ts.decider_best_of INTO _decider_best_of
FROM tournament_stages ts WHERE ts.id = _stage_id;

-- Determine effective best_of for decider matches
-- Only apply a different BO if the stage explicitly has decider_best_of set
IF _decider_best_of IS NOT NULL THEN
_effective_best_of := _decider_best_of;
ELSE
IF _decider_best_of IS NULL THEN
RETURN original_match_options_id;
END IF;

SELECT best_of INTO _current_best_of
FROM match_options
WHERE id = original_match_options_id;

-- If target BO equals current BO, no clone needed
IF _effective_best_of = match_options_record.best_of THEN
IF _decider_best_of = _current_best_of THEN
RETURN original_match_options_id;
END IF;

-- Clone match_options with the new best_of
match_options_record.best_of := _effective_best_of;
INSERT INTO match_options (
overtime, knife_round, mr, best_of, coaches, number_of_substitutes,
map_veto, timeout_setting, tech_timeout_setting, map_pool_id, type,
regions, prefer_dedicated_server, invite_code,
region_veto, ready_setting, check_in_setting, default_models, tv_delay,
veto_pick_timeout
) VALUES (
match_options_record.overtime, match_options_record.knife_round,
match_options_record.mr, match_options_record.best_of,
match_options_record.coaches, match_options_record.number_of_substitutes,
match_options_record.map_veto, match_options_record.timeout_setting,
match_options_record.tech_timeout_setting, match_options_record.map_pool_id,
match_options_record.type, match_options_record.regions,
match_options_record.prefer_dedicated_server, match_options_record.invite_code,
match_options_record.region_veto,
match_options_record.ready_setting, match_options_record.check_in_setting,
match_options_record.default_models, match_options_record.tv_delay,
match_options_record.veto_pick_timeout
)
RETURNING id INTO final_match_options_id;

RETURN final_match_options_id;
RETURN clone_match_options_with_best_of(
original_match_options_id,
_decider_best_of
);
END;
$$ LANGUAGE plpgsql;
19 changes: 19 additions & 0 deletions hasura/metadata/actions.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,14 @@ type Mutation {
attachDemo: WatchDemoOutput
}

type Query {
webPushStatus: WebPushStatusOutput
}

type Mutation {
generateWebPushKeys: WebPushKeysOutput
}

type Mutation {
bakeShaders(
game_server_node_id: uuid!
Expand Down Expand Up @@ -979,6 +987,16 @@ type SuccessOutput {
success: Boolean!
}

type WebPushStatusOutput {
configured: Boolean!
managed_by_environment: Boolean!
subscriptions: Int!
}

type WebPushKeysOutput {
success: Boolean!
}

type DraftGamePreviewPlayer {
steam_id: String!
name: String
Expand Down Expand Up @@ -1573,6 +1591,7 @@ type Mutation {
addDraftPlayer(
draftGameId: uuid!
steamId: String!
lineup: Int
): SuccessOutput
}

Expand Down
18 changes: 18 additions & 0 deletions hasura/metadata/actions.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,22 @@ actions:
permissions:
- role: streamer
comment: Dev-only — attach the demo player to a standing dev game-streamer pod (no Job boot)
- name: webPushStatus
definition:
kind: synchronous
handler: '{{HASURA_GRAPHQL_ACTIONS_HOOK}}'
forward_client_headers: true
permissions:
- role: administrator
comment: Web push setup status for the application settings page; never returns the private key
- name: generateWebPushKeys
definition:
kind: synchronous
handler: '{{HASURA_GRAPHQL_ACTIONS_HOOK}}'
forward_client_headers: true
permissions:
- role: administrator
comment: Generates a fresh self-signed VAPID keypair; invalidates every existing subscription
- name: bakeShaders
definition:
kind: synchronous
Expand Down Expand Up @@ -1318,6 +1334,8 @@ custom_types:
source: default
type: object
- name: SuccessOutput
- name: WebPushStatusOutput
- name: WebPushKeysOutput
- name: ScanStartedOutput
- name: OrphanObject
- name: OrphanScanResultOutput
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ insert_permissions:
check: {}
columns:
- best_of
- camera_required
- camera_allow_teammates
- check_in_setting
- coaches
- default_models
Expand Down Expand Up @@ -104,6 +106,8 @@ select_permissions:
permission:
columns:
- best_of
- camera_required
- camera_allow_teammates
- check_in_setting
- coaches
- default_models
Expand Down Expand Up @@ -136,6 +140,8 @@ update_permissions:
permission:
columns:
- best_of
- camera_required
- camera_allow_teammates
- check_in_setting
- coaches
- default_models
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
table:
name: notification_preferences
schema: public
Loading
Loading