diff options
author | sadbeast <sadbeast@sadbeast.com> | 2024-07-16 18:16:29 -0700 |
---|---|---|
committer | sadbeast <sadbeast@sadbeast.com> | 2024-10-05 16:40:55 -0700 |
commit | 6bd24af2ffbea91db1b10a5d5258980ce2068c7f (patch) | |
tree | 66634833f2d45260be5fcaf9111400eda12f03cc /src/db/drafts.sql | |
download | teamdraft-main.tar.gz teamdraft-main.tar.bz2 |
Diffstat (limited to 'src/db/drafts.sql')
-rw-r--r-- | src/db/drafts.sql | 385 |
1 files changed, 385 insertions, 0 deletions
diff --git a/src/db/drafts.sql b/src/db/drafts.sql new file mode 100644 index 0000000..4fff19f --- /dev/null +++ b/src/db/drafts.sql @@ -0,0 +1,385 @@ +CREATE TABLE draft_statuses( + draft_status_id smallint NOT NULL, + name text NOT NULL, + UNIQUE (name), + CONSTRAINT draft_statuses_pk PRIMARY KEY (draft_status_id) +); + +INSERT INTO draft_statuses(draft_status_id, name) + VALUES (1, 'Scheduled'); + +INSERT INTO draft_statuses(draft_status_id, name) + VALUES (2, 'Running'); + +INSERT INTO draft_statuses(draft_status_id, name) + VALUES (3, 'Complete'); + +INSERT INTO draft_statuses(draft_status_id, name) + VALUES (4, 'Manual'); + +CREATE TABLE drafts ( + draft_id bigint NOT NULL GENERATED ALWAYS AS IDENTITY, + season_id bigint NOT NULL, + draft_status_id smallint NOT NULL DEFAULT 1, + name text, + account_id bigint NOT NULL, + started_at timestamptz, + created_at timestamptz NOT NULL DEFAULT now(), + modified_at timestamptz NOT NULL DEFAULT now(), + + CONSTRAINT drafts_pk PRIMARY KEY (draft_id), + CONSTRAINT drafts_seasons_fk FOREIGN KEY (season_id) REFERENCES seasons(season_id) MATCH SIMPLE ON DELETE NO ACTION ON UPDATE NO ACTION, + CONSTRAINT drafts_name_ck CHECK (char_length(name) <= 255), + CONSTRAINT drafts_accounts_fk FOREIGN KEY (account_id) REFERENCES accounts(account_id) MATCH SIMPLE ON DELETE NO ACTION ON UPDATE NO ACTION +); + +CREATE TABLE draft_users ( + draft_user_id bigint NOT NULL GENERATED ALWAYS AS IDENTITY, + draft_id bigint NOT NULL, + name text NOT NULL, + account_id bigint, + created_at timestamptz NOT NULL DEFAULT now(), + modified_at timestamptz NOT NULL DEFAULT now(), + + CONSTRAINT draft_users_pk PRIMARY KEY (draft_user_id), + CONSTRAINT draft_users_drafts_fk FOREIGN KEY (draft_id) REFERENCES drafts(draft_id) MATCH SIMPLE ON DELETE NO ACTION ON UPDATE NO ACTION, + CONSTRAINT draft_users_accounts_fk FOREIGN KEY (account_id) REFERENCES accounts(account_id) MATCH SIMPLE ON DELETE NO ACTION ON UPDATE NO ACTION +); + +CREATE OR REPLACE FUNCTION gen_invite_code() + RETURNS varchar + LANGUAGE plpgsql +AS $$ +DECLARE + base10 bigint; + base36 varchar := ''; + intval bigint; + char0z char[] := regexp_split_to_array('0123456789abcdefghijklmnopqrstuvwxyz', ''); +BEGIN + SELECT floor(random() * 78364164095 + 2176782336) INTO base10; + intval := abs(base10); + WHILE intval != 0 LOOP + base36 := char0z[(intval % 36)+1] || base36; + intval := intval / 36; + END LOOP; + + IF base10 = 0 THEN base36 := '0'; END IF; + RETURN base36; +END; +$$; + +CREATE TABLE draft_user_invites ( + draft_user_invite_id bigint NOT NULL GENERATED ALWAYS AS IDENTITY, + draft_user_id bigint NOT NULL, + name text NOT NULL, + code text NOT NULL DEFAULT gen_invite_code(), + created_at timestamptz NOT NULL DEFAULT now(), + + CONSTRAINT draft_user_invites_pk PRIMARY KEY (draft_user_id), + CONSTRAINT draft_user_invites_draft_users_fk FOREIGN KEY (draft_user_id) REFERENCES draft_users(draft_user_id) MATCH SIMPLE ON DELETE NO ACTION ON UPDATE NO ACTION, + CONSTRAINT draft_user_invites_name_ck CHECK (char_length(name) <= 255) +); + +CREATE TABLE picks ( + pick_id integer NOT NULL GENERATED ALWAYS AS IDENTITY, + draft_id integer NOT NULL, + draft_user_id integer NOT NULL, + team_id int NOT NULL, + auto boolean NOT NULL DEFAULT FALSE, + picked_at timestamptz NOT NULL DEFAULT now(), + created_at timestamptz NOT NULL DEFAULT now(), + modified_at timestamptz NOT NULL DEFAULT now(), + + UNIQUE (draft_id, team_id), + CONSTRAINT picks_pk PRIMARY KEY (pick_id), + CONSTRAINT picks_drafts_fk FOREIGN KEY (draft_id) REFERENCES drafts(draft_id) MATCH SIMPLE ON DELETE NO ACTION ON UPDATE NO ACTION, + CONSTRAINT picks_draft_users_fk FOREIGN KEY (draft_user_id) REFERENCES draft_users(draft_user_id) MATCH SIMPLE ON DELETE NO ACTION ON UPDATE NO ACTION, + CONSTRAINT picks_teams_fk FOREIGN KEY (team_id) REFERENCES teams(team_id) MATCH SIMPLE ON DELETE NO ACTION ON UPDATE NO ACTION +); + +-- TODO: support more than 2 players by persisting draft order and current_picker +CREATE OR REPLACE FUNCTION current_picker_name(_draft_id bigint) + RETURNS text + LANGUAGE plpgsql + SECURITY INVOKER + AS $$ +BEGIN + RETURN (SELECT name FROM draft_users WHERE draft_user_id = current_picker_id(_draft_id)); +END; +$$; + +CREATE OR REPLACE FUNCTION current_picker_account_id(_draft_id bigint) + RETURNS bigint + LANGUAGE plpgsql + SECURITY INVOKER + AS $$ +DECLARE + pick_count smallint; + player_1 integer; + player_2 integer; +BEGIN + SELECT count(1) FROM picks WHERE draft_id = _draft_id INTO pick_count; + + SELECT account_id + FROM draft_users + WHERE draft_id = _draft_id + ORDER BY draft_user_id + LIMIT 1 INTO player_1; + + SELECT account_id + FROM draft_users + WHERE draft_id = _draft_id + ORDER BY draft_user_id DESC + LIMIT 1 INTO player_2; + + IF pick_count % 2 = 0 THEN + RETURN player_1; + ELSE + RETURN player_2; + END IF; +END; +$$; + +CREATE OR REPLACE FUNCTION current_picker_id(_draft_id bigint) + RETURNS integer + LANGUAGE plpgsql + SECURITY INVOKER + AS $$ +DECLARE + pick_count smallint; + player_1 integer; + player_2 integer; +BEGIN + SELECT count(1) FROM picks WHERE draft_id = _draft_id INTO pick_count; + + SELECT draft_user_id + FROM draft_users + WHERE draft_id = _draft_id + ORDER BY draft_user_id + LIMIT 1 INTO player_1; + + SELECT draft_user_id + FROM draft_users + WHERE draft_id = _draft_id + ORDER BY draft_user_id DESC + LIMIT 1 INTO player_2; + + IF pick_count % 2 = 0 THEN + RETURN player_1; + ELSE + RETURN player_2; + END IF; +END; +$$; + +CREATE OR REPLACE FUNCTION pick(_draft_id integer, _team_id integer) + RETURNS boolean + LANGUAGE plpgsql + SECURITY INVOKER + AS $$ +BEGIN + INSERT INTO picks (draft_id, league_user_id, team_id) VALUES (_draft_id, current_picker_id(_draft_id), _team_id); +END; +$$; + +-- create missing draft picks if any +CREATE OR REPLACE PROCEDURE auto_draft(_draft_id integer) +LANGUAGE plpgsql +SECURITY INVOKER +AS $$ +DECLARE + latest_pick_time timestamptz; + latest_pick picks%ROWTYPE; + pick_count integer; + next_auto_draft_team integer; + draft drafts%ROWTYPE; + draft_league_id integer; +BEGIN + SELECT * FROM drafts WHERE draft_id = _draft_id INTO draft; + SELECT league_id FROM seasons WHERE season_id = draft.season_id INTO draft_league_id; + + latest_pick_time = draft.started_at; + + IF now() >= latest_pick_time AND draft.draft_status_id = 1 THEN + UPDATE drafts SET draft_status_id = 2, modified_at = now() WHERE draft_id = _draft_id; + END IF; + + LOOP + SELECT count(1) FROM picks WHERE draft_id = _draft_id INTO pick_count; + + IF pick_count = (SELECT count(1) FROM teams JOIN league_divisions USING(league_division_id) WHERE league_id = draft_league_id) THEN + UPDATE drafts SET draft_status_id = 3, modified_at = now() WHERE draft_id = _draft_id; + RETURN; + END IF; + + SELECT * FROM picks WHERE draft_id = _draft_id ORDER BY picked_at DESC LIMIT 1 INTO latest_pick; + + IF latest_pick IS NOT NULL THEN + latest_pick_time = latest_pick.picked_at; + END IF; + + IF (date_part('epoch', now())::integer - date_part('epoch', latest_pick_time)::integer) / 30 >= 1 THEN + SELECT + current_rankings.team_id + FROM + current_rankings + LEFT JOIN picks ON picks.team_id = current_rankings.team_id + WHERE + pick_id IS NULL + AND league_id = draft_league_id + ORDER BY + rank ASC + LIMIT 1 INTO next_auto_draft_team; + + INSERT INTO picks(draft_id, draft_user_id, team_id, auto, picked_at) + VALUES (_draft_id, current_picker_id(_draft_id), + (SELECT + current_rankings.team_id + FROM + current_rankings + LEFT JOIN picks ON picks.team_id = current_rankings.team_id + AND picks.draft_id = _draft_id + WHERE + pick_id IS NULL + ORDER BY + rank ASC + LIMIT 1), + TRUE, + latest_pick_time + interval '30 seconds'); + ELSE + EXIT; + END IF; + END LOOP; +END; +$$; + +CREATE OR REPLACE VIEW draft_picks AS +SELECT + picks.draft_id, + teams.team_id, + teams.name AS team, + accounts.name AS player +FROM + picks + JOIN teams USING (team_id) + JOIN draft_users USING (draft_user_id) + JOIN accounts USING (account_id) +ORDER BY + picks.created_at; + +CREATE OR REPLACE VIEW draft_info AS +WITH latest_draft_pick AS ( + SELECT + draft_id, + MAX(picked_at) AS picked_at + FROM + picks + GROUP BY + draft_id +) +SELECT + draft_id, + current_picker_account_id(draft_id) as current_player_id, + current_picker_name(draft_id) as current_player_name, + draft_status_id, + CASE WHEN draft_status_id IN (1, 3) THEN + 0 + ELSE + date_part('epoch',(coalesce(latest_draft_pick.picked_at, started_at) + interval '30 seconds') - now())::integer + END AS round_time_remaining, + CASE WHEN draft_status_id = 1 THEN + 'The draft will start in ' || to_char(started_at - now(), 'MI:SS') + WHEN draft_status_id = 2 THEN + 'It''s ' || current_picker_name(draft_id) || '''s turn!' + WHEN draft_status_id = 3 THEN + 'The draft is over, good luck!' + END AS message, + draft_status_id = 2 AS can_pick +FROM + drafts + LEFT JOIN latest_draft_pick USING (draft_id); + +CREATE OR REPLACE FUNCTION accept_invite(_code text) + RETURNS table (session_id text, invite_draft_id bigint) + LANGUAGE plpgsql + SECURITY INVOKER + AS $$ +DECLARE + new_account_id bigint; + invite_draft_user_id bigint; + invite_draft_id bigint; + new_session_id text; +BEGIN + SELECT draft_user_id FROM draft_user_invites WHERE code = _code INTO invite_draft_user_id; + SELECT draft_id FROM draft_users WHERE draft_user_id = invite_draft_user_id INTO invite_draft_id; + + INSERT INTO accounts DEFAULT VALUES RETURNING account_id INTO new_account_id; + + UPDATE draft_users SET account_id = new_account_id WHERE draft_user_id = invite_draft_user_id; + SELECT create_session(new_account_id) INTO new_session_id; + + RETURN QUERY SELECT new_session_id, invite_draft_id; +END; +$$; + +CREATE OR REPLACE FUNCTION create_draft(league_id int, player_1 text, player_2 text, started_at timestamptz) + RETURNS table (session_id text, new_draft_id bigint) + LANGUAGE plpgsql + SECURITY INVOKER + AS $$ +DECLARE + new_account_id bigint; + new_draft_id bigint; + current_season_id int := current_season_id(league_id); + new_session_id text; + player_2_id bigint; +BEGIN + INSERT INTO accounts DEFAULT VALUES RETURNING account_id INTO new_account_id; + SELECT create_session(new_account_id) INTO new_session_id; + + IF started_at IS NULL THEN + INSERT INTO drafts (season_id, account_id, draft_status_id) VALUES (current_season_id, new_account_id, 4) RETURNING draft_id INTO new_draft_id; + ELSE + INSERT INTO drafts (season_id, account_id, started_at) VALUES (current_season_id, new_account_id, started_at) RETURNING draft_id INTO new_draft_id; + END IF; + + INSERT INTO draft_users (draft_id, name, account_id) VALUES (new_draft_id, player_1, new_account_id); + INSERT INTO draft_users (draft_id, name) VALUES (new_draft_id, CASE WHEN player_2 = '' THEN 'Player 2' ELSE player_2 END) RETURNING draft_user_id INTO player_2_id; + INSERT INTO draft_user_invites (draft_user_id, name) VALUES (player_2_id, player_2); + + RETURN QUERY SELECT new_session_id, new_draft_id; +END; +$$; + +CREATE OR REPLACE FUNCTION current_draft_picks(_draft_id int) + RETURNS TABLE (team_id int, + rank smallint, + name text, + picked boolean, + pick_user text) + LANGUAGE plpgsql + SECURITY INVOKER + AS $$ +DECLARE + draft_season_id integer; +BEGIN + SELECT season_id FROM drafts WHERE draft_id = _draft_id INTO draft_season_id; +RETURN QUERY + WITH season_rankings AS ( + SELECT + teams.team_id, + rankings.rank, + teams.name + FROM + teams + LEFT JOIN rankings ON rankings.team_id = teams.team_id + AND rankings.season_id = draft_season_id + ORDER BY + rank + ) + SELECT r.*, draft_users.name IS NOT NULL AS picked, draft_users.name AS pick_user + FROM season_rankings r + LEFT JOIN picks ON r.team_id = picks.team_id AND picks.draft_id = _draft_id + LEFT JOIN draft_users ON draft_users.draft_user_id = picks.draft_user_id; +END; +$$; |