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
6,232 changes: 5,959 additions & 273 deletions generated/schema.graphql

Large diffs are not rendered by default.

38,765 changes: 22,414 additions & 16,351 deletions generated/schema.ts

Large diffs are not rendered by default.

59,104 changes: 33,549 additions & 25,555 deletions generated/types.ts

Large diffs are not rendered by default.

122 changes: 122 additions & 0 deletions hasura/functions/events/event_access.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
-- Visibility/upload access primitives for events. Single file so
-- is_event_member exists before its callers within one boot apply.

-- LANGUAGE sql is safe here: every referenced relation is created in the
-- migrations boot phase, which runs before hasura/functions.
CREATE OR REPLACE FUNCTION public.is_event_member(
event public.events,
_steam_id bigint
) RETURNS boolean
LANGUAGE sql
STABLE
AS $$
SELECT _steam_id IS NOT NULL AND (
event.organizer_steam_id = _steam_id
OR EXISTS (
SELECT 1 FROM public.event_organizers eo
WHERE eo.event_id = event.id AND eo.steam_id = _steam_id
)
OR EXISTS (
SELECT 1 FROM public.event_players ep
WHERE ep.event_id = event.id AND ep.steam_id = _steam_id
)
OR EXISTS (
SELECT 1
FROM public.event_teams et
JOIN public.team_roster tr ON tr.team_id = et.team_id
WHERE et.event_id = event.id AND tr.player_steam_id = _steam_id
)
OR EXISTS (
SELECT 1
FROM public.event_tournaments evt
JOIN public.tournament_team_roster ttr
ON ttr.tournament_id = evt.tournament_id
WHERE evt.event_id = event.id AND ttr.player_steam_id = _steam_id
)
OR EXISTS (
SELECT 1
FROM public.event_tournaments evt
JOIN public.tournament_organizers torg
ON torg.tournament_id = evt.tournament_id
WHERE evt.event_id = event.id AND torg.steam_id = _steam_id
)
OR EXISTS (
SELECT 1
FROM public.event_tournaments evt
JOIN public.tournament_teams tt
ON tt.tournament_id = evt.tournament_id
WHERE evt.event_id = event.id AND tt.owner_steam_id = _steam_id
)
);
$$;

-- LANGUAGE plpgsql: bodies are not parsed for object references at CREATE
-- time, so ordering against sibling function files never matters on a
-- fresh install (see get_event_leaderboard.sql for the same rationale).
CREATE OR REPLACE FUNCTION public.can_view_event(
event public.events,
hasura_session json
) RETURNS boolean
LANGUAGE plpgsql
STABLE
AS $$
DECLARE
_steam_id bigint := nullif(hasura_session ->> 'x-hasura-user-id', '')::bigint;
BEGIN
IF hasura_session ->> 'x-hasura-role'
IN ('admin', 'administrator', 'tournament_organizer') THEN
RETURN true;
END IF;

IF event.visibility = 'Public' THEN
RETURN true;
END IF;

IF _steam_id IS NULL THEN
RETURN false;
END IF;

IF public.is_event_member(event, _steam_id) THEN
RETURN true;
END IF;

IF event.visibility = 'Friends' THEN
-- friends holds one row per friendship regardless of direction, so
-- match the viewer against both columns (see v_my_friends.sql).
RETURN EXISTS (
SELECT 1
FROM public.friends f
WHERE f.status = 'Accepted'
AND (f.player_steam_id = _steam_id
OR f.other_player_steam_id = _steam_id)
AND public.is_event_member(
event,
CASE WHEN f.player_steam_id = _steam_id
THEN f.other_player_steam_id
ELSE f.player_steam_id END
)
);
END IF;

RETURN false;
END;
$$;

CREATE OR REPLACE FUNCTION public.can_upload_event_media(
event public.events,
hasura_session json
) RETURNS boolean
LANGUAGE plpgsql
STABLE
AS $$
BEGIN
RETURN public.is_event_organizer(event, hasura_session)
OR (
event.media_access = 'Involved'
AND public.is_event_member(
event,
nullif(hasura_session ->> 'x-hasura-user-id', '')::bigint
)
);
END;
$$;
124 changes: 124 additions & 0 deletions hasura/functions/events/get_event_leaderboard.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
-- Event-scoped leaderboard over the event's derived match set.
-- Stale-overload cleanup: CREATE OR REPLACE cannot remove an old overload once
-- a second signature exists (SQLSTATE 42725). Drop known signatures first so
-- re-applying this file always lands on exactly one get_event_leaderboard.
DROP FUNCTION IF EXISTS public.get_event_leaderboard(UUID, TEXT, TEXT, INT);
DROP FUNCTION IF EXISTS public.get_event_leaderboard(UUID, TEXT, TEXT, INT, JSON);

-- LANGUAGE plpgsql (not sql): a "sql"-language body is parsed for relation
-- references at CREATE time, so this function would fail to create on a
-- fresh install before v_player_match_map_hltv exists in a later boot phase.
-- plpgsql bodies are not parsed for relation references at creation time, so
-- this creates cleanly regardless of what else has been applied yet.
CREATE OR REPLACE FUNCTION public.get_event_leaderboard(
_event_id UUID,
_category TEXT,
_match_type TEXT DEFAULT NULL,
_min_rounds INT DEFAULT 10,
hasura_session JSON DEFAULT NULL
)
RETURNS SETOF public.leaderboard_entries
LANGUAGE plpgsql STABLE
AS $$
BEGIN
IF _category NOT IN ('rating', 'adr', 'kdr', 'kills', 'wins') THEN
RAISE EXCEPTION 'get_event_leaderboard: unknown category %', _category;
END IF;

-- An explicit NULL would make the HAVING comparison below silently filter
-- every row; treat it as "no minimum" instead of returning an empty board.
IF _min_rounds IS NULL THEN
_min_rounds := 0;
END IF;

-- This function is exposed to the guest role and takes an arbitrary event
-- id, so it must apply the same guard as the events table select
-- permissions: Private/Friends events are only visible per can_view_event.
-- Otherwise a caller could compute standings for an event they cannot see.
IF NOT EXISTS (
SELECT 1 FROM public.events e
WHERE e.id = _event_id
AND public.can_view_event(e, hasura_session)
) THEN
RETURN;
END IF;

RETURN QUERY
-- event_match_links is the trigger-maintained materialization of
-- v_event_matches (tournaments + windowed teams/players), so stats
-- aggregate over an indexed table instead of re-deriving the joins.
WITH e_matches AS (
SELECT eml.match_id
FROM public.event_match_links eml
WHERE eml.event_id = _event_id
),
f_matches AS (
SELECT em.match_id
FROM e_matches em
JOIN matches m ON m.id = em.match_id
LEFT JOIN match_options mo ON mo.id = m.match_options_id
WHERE _match_type IS NULL OR mo.type = _match_type
),
roster AS (
-- Explicit curation: when event_players has rows for this event,
-- only those players appear on the board.
SELECT ep.steam_id FROM event_players ep WHERE ep.event_id = _event_id
),
agg AS (
SELECT
pmms.steam_id,
SUM(pmms.kills)::float AS kills,
SUM(pmms.deaths)::float AS deaths,
SUM(pmms.damage)::float AS damage,
SUM(pmms.rounds_played)::int AS rounds_played,
COUNT(DISTINCT pmms.match_id)::int AS matches_played,
CASE WHEN SUM(h.rounds_played) > 0
THEN SUM(COALESCE(h.hltv_rating, 0) * h.rounds_played)
/ SUM(h.rounds_played)
ELSE 0
END AS rating
FROM f_matches fm
JOIN player_match_map_stats pmms ON pmms.match_id = fm.match_id
LEFT JOIN v_player_match_map_hltv h
ON h.match_map_id = pmms.match_map_id
AND h.steam_id = pmms.steam_id
WHERE NOT EXISTS (SELECT 1 FROM roster)
OR pmms.steam_id IN (SELECT steam_id FROM roster)
GROUP BY pmms.steam_id
HAVING SUM(pmms.rounds_played) >= _min_rounds
),
wins AS (
SELECT mlp.steam_id, COUNT(DISTINCT m.id)::float AS wins
FROM f_matches fm
JOIN matches m ON m.id = fm.match_id AND m.winning_lineup_id IS NOT NULL
JOIN match_lineup_players mlp ON mlp.match_lineup_id = m.winning_lineup_id
GROUP BY mlp.steam_id
)
SELECT
a.steam_id::text AS player_steam_id,
p.name AS player_name,
p.avatar_url AS player_avatar_url,
p.country AS player_country,
CASE _category
WHEN 'rating' THEN ROUND(a.rating::numeric, 2)::float
WHEN 'adr' THEN CASE WHEN a.rounds_played > 0
THEN ROUND((a.damage / a.rounds_played)::numeric, 1)::float
ELSE 0 END
WHEN 'kdr' THEN CASE WHEN a.deaths = 0 THEN a.kills
ELSE ROUND((a.kills / a.deaths)::numeric, 2)::float END
WHEN 'kills' THEN a.kills
WHEN 'wins' THEN COALESCE(w.wins, 0)
ELSE 0
END AS value,
a.kills AS secondary_value,
a.deaths AS tertiary_value,
a.matches_played
FROM agg a
JOIN players p ON p.steam_id = a.steam_id
LEFT JOIN wins w ON w.steam_id = a.steam_id
-- No LIMIT here: the web paginates via Hasura-level order_by/limit/offset
-- (like the global get_leaderboard), so an in-function cap would truncate
-- events with more than N participants and skew the aggregate count.
ORDER BY value DESC;
END;
$$;
22 changes: 22 additions & 0 deletions hasura/functions/events/is_event_organizer.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
CREATE OR REPLACE FUNCTION public.is_event_organizer(
event public.events,
hasura_session json
) RETURNS boolean
LANGUAGE sql
STABLE
AS $$
-- nullif guard matches event_access.sql: an empty x-hasura-user-id must
-- read as anonymous rather than fail the ::bigint cast. COALESCE because
-- a NULL user id makes the organizer comparison NULL, not false.
SELECT COALESCE(
hasura_session ->> 'x-hasura-role' IN ('admin', 'administrator', 'tournament_organizer')
OR event.organizer_steam_id = nullif(hasura_session ->> 'x-hasura-user-id', '')::bigint
OR EXISTS (
SELECT 1
FROM public.event_organizers
WHERE event_id = event.id
AND steam_id = nullif(hasura_session ->> 'x-hasura-user-id', '')::bigint
),
false
);
$$;
20 changes: 20 additions & 0 deletions hasura/functions/match/get_match_server_plugin_runtime.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
CREATE OR REPLACE FUNCTION public.get_match_server_plugin_runtime(match public.matches)
RETURNS text
LANGUAGE plpgsql
STABLE
AS $$
DECLARE
runtime text;
BEGIN
IF match.server_id IS NULL THEN
RETURN NULL;
END IF;

SELECT plugin_runtime
INTO runtime
FROM servers
WHERE id = match.server_id;

RETURN runtime;
END
$$;
8 changes: 6 additions & 2 deletions hasura/functions/servers/get_server_connection.sql
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ DECLARE
connection_string text;
min_role_to_connect text;
BEGIN
IF server.connected = false OR server.enabled = false OR server.type = 'Ranked' OR server.host IS NULL OR server.port IS NULL THEN
-- A disabled server can still be online (external servers keep running),
-- so gate on connected rather than enabled.
IF server.connected = false OR server.type = 'Ranked' OR server.host IS NULL OR server.port IS NULL THEN
RETURN NULL;
END IF;

Expand All @@ -32,7 +34,9 @@ DECLARE
server_host text;
min_role_to_connect text;
BEGIN
IF server.connected = false OR server.enabled = false OR server.type = 'Ranked' OR NULLIF(server.connect_password, '') IS NOT NULL THEN
-- A disabled server can still be online (external servers keep running),
-- so gate on connected rather than enabled.
IF server.connected = false OR server.type = 'Ranked' OR NULLIF(server.connect_password, '') IS NOT NULL THEN
RETURN NULL;
END IF;

Expand Down
1 change: 1 addition & 0 deletions hasura/metadata/databases/default/functions/functions.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
- "!include public_approve_league_season_movements.yaml"
- "!include public_clone_league_season.yaml"
- "!include public_get_event_leaderboard.yaml"
- "!include public_get_leaderboard.yaml"
- "!include public_get_league_season_leaderboard.yaml"
- "!include public_league_award_forfeit.yaml"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
function:
name: get_event_leaderboard
schema: public
configuration:
custom_root_fields: {}
exposed_as: query
session_argument: hasura_session
permissions:
- role: guest
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
table:
name: e_event_media_access
schema: public
is_enum: true
select_permissions:
- role: guest
permission:
columns:
- description
- value
filter: {}
comment: ""
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
table:
name: e_event_visibility
schema: public
is_enum: true
select_permissions:
- role: guest
permission:
columns:
- description
- value
filter: {}
comment: ""
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
table:
name: event_match_links
schema: public
object_relationships:
- name: event
using:
foreign_key_constraint_on: event_id
- name: match
using:
foreign_key_constraint_on: match_id
select_permissions:
- role: guest
permission:
columns:
- created_at
- event_id
- match_id
filter:
event:
visibility:
_eq: Public
allow_aggregations: true
comment: ""
- role: user
permission:
columns:
- created_at
- event_id
- match_id
filter:
event:
can_view:
_eq: true
allow_aggregations: true
comment: ""
Loading
Loading