games: automatic CS2 / PUBG / R6 match history — games.matches, match_players, seasons, snapshots, accounts; in-app poller (Leetify, FACEIT, Steam share codes, PUBG API, Ubisoft) every 20 min plus scripts/games-ingest.ts for backfills; match history, stats, maps and seasons on the per-game pages
All checks were successful
Deploy production / deploy (push) Successful in 1m6s
All checks were successful
Deploy production / deploy (push) Successful in 1m6s
This commit is contained in:
133
drizzle/20261025155800_games_matches/migration.sql
Normal file
133
drizzle/20261025155800_games_matches/migration.sql
Normal file
@@ -0,0 +1,133 @@
|
||||
CREATE TABLE "games"."cs2_share_codes" (
|
||||
"code" text PRIMARY KEY,
|
||||
"steam_id" text NOT NULL,
|
||||
"match_id" bigint NOT NULL,
|
||||
"outcome_id" bigint NOT NULL,
|
||||
"token" integer NOT NULL,
|
||||
"found_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"demo_parsed_at" timestamp with time zone
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "games"."accounts" (
|
||||
"id" serial PRIMARY KEY,
|
||||
"game" text NOT NULL,
|
||||
"source" text NOT NULL,
|
||||
"external_id" text NOT NULL,
|
||||
"name" text,
|
||||
"enabled" boolean DEFAULT true NOT NULL,
|
||||
"state" jsonb DEFAULT '{}' NOT NULL,
|
||||
"last_polled_at" timestamp with time zone,
|
||||
"last_success_at" timestamp with time zone,
|
||||
"last_error" text,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "games"."match_players" (
|
||||
"id" serial PRIMARY KEY,
|
||||
"match_id" integer NOT NULL,
|
||||
"player_id" text NOT NULL,
|
||||
"name" text,
|
||||
"team" text,
|
||||
"is_me" boolean DEFAULT false NOT NULL,
|
||||
"kills" integer,
|
||||
"deaths" integer,
|
||||
"assists" integer,
|
||||
"damage" integer,
|
||||
"headshots" integer,
|
||||
"placement" integer,
|
||||
"rating" numeric,
|
||||
"stats" jsonb NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "games"."matches" (
|
||||
"id" serial PRIMARY KEY,
|
||||
"game" text NOT NULL,
|
||||
"source" text NOT NULL,
|
||||
"external_id" text NOT NULL,
|
||||
"played_at" timestamp with time zone NOT NULL,
|
||||
"duration_s" integer,
|
||||
"mode" text,
|
||||
"map" text,
|
||||
"result" text,
|
||||
"score_own" integer,
|
||||
"score_opp" integer,
|
||||
"placement" integer,
|
||||
"team_count" integer,
|
||||
"kills" integer,
|
||||
"deaths" integer,
|
||||
"assists" integer,
|
||||
"damage" integer,
|
||||
"headshots" integer,
|
||||
"rating" numeric,
|
||||
"rating_label" text,
|
||||
"match_count" integer DEFAULT 1 NOT NULL,
|
||||
"share_code" text,
|
||||
"extra" jsonb DEFAULT '{}' NOT NULL,
|
||||
"raw" jsonb NOT NULL,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "games"."seasons" (
|
||||
"id" serial PRIMARY KEY,
|
||||
"game" text NOT NULL,
|
||||
"source" text NOT NULL,
|
||||
"season" text NOT NULL,
|
||||
"board" text DEFAULT 'all' NOT NULL,
|
||||
"rank" text,
|
||||
"max_rank" text,
|
||||
"rank_points" integer,
|
||||
"max_rank_points" integer,
|
||||
"matches" integer,
|
||||
"wins" integer,
|
||||
"losses" integer,
|
||||
"kills" integer,
|
||||
"deaths" integer,
|
||||
"raw" jsonb NOT NULL,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "games"."snapshots" (
|
||||
"id" serial PRIMARY KEY,
|
||||
"game" text NOT NULL,
|
||||
"source" text NOT NULL,
|
||||
"kind" text NOT NULL,
|
||||
"taken_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"data" jsonb NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX "accounts_game_source_ext_idx" ON "games"."accounts" ("game","source","external_id");--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX "match_players_match_player_idx" ON "games"."match_players" ("match_id","player_id");--> statement-breakpoint
|
||||
CREATE INDEX "match_players_player_idx" ON "games"."match_players" ("player_id");--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX "matches_source_ext_idx" ON "games"."matches" ("game","source","external_id");--> statement-breakpoint
|
||||
CREATE INDEX "matches_game_played_idx" ON "games"."matches" ("game","played_at");--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX "seasons_key_idx" ON "games"."seasons" ("game","source","season","board");--> statement-breakpoint
|
||||
CREATE INDEX "snapshots_game_kind_idx" ON "games"."snapshots" ("game","source","kind","taken_at");--> statement-breakpoint
|
||||
ALTER TABLE "games"."match_players" ADD CONSTRAINT "match_players_match_id_matches_id_fkey" FOREIGN KEY ("match_id") REFERENCES "games"."matches"("id") ON DELETE CASCADE;--> statement-breakpoint
|
||||
-- Hand-written: grants (same pattern as the other app tables) and the accounts to poll.
|
||||
DO $$
|
||||
BEGIN
|
||||
IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'personal_app') THEN
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON games.accounts, games.matches, games.match_players, games.seasons, games.snapshots, games.cs2_share_codes TO personal_app;
|
||||
GRANT USAGE, SELECT ON SEQUENCE games.accounts_id_seq, games.matches_id_seq, games.match_players_id_seq, games.seasons_id_seq, games.snapshots_id_seq TO personal_app;
|
||||
END IF;
|
||||
IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'personal_ro') THEN
|
||||
GRANT SELECT ON games.accounts, games.matches, games.match_players, games.seasons, games.snapshots, games.cs2_share_codes TO personal_ro;
|
||||
END IF;
|
||||
END $$;
|
||||
--> statement-breakpoint
|
||||
-- Identifiers only (Steam64 of steamcommunity.com/id/stanrunge, FACEIT VashNerf, PUBG account
|
||||
-- linked on FACEIT, Ubisoft name linked on FACEIT); keys and logins live in prod.env.
|
||||
INSERT INTO games.accounts (game, source, external_id, name, state) VALUES
|
||||
('cs2', 'leetify', '76561198299226338', 'stan', '{}'),
|
||||
('cs2', 'faceit', '155cd4e3-e419-46b2-8a67-2e278b667445', 'VashNerf', '{"games":["cs2","csgo"]}'),
|
||||
('cs2', 'steam', '76561198299226338', 'stan', '{}'),
|
||||
('pubg', 'pubg', 'account.93db433b69c442f79502c0cabfaed8b0', 'Stannie2K', '{}'),
|
||||
('pubg', 'faceit', '155cd4e3-e419-46b2-8a67-2e278b667445', 'VashNerf', '{"games":["pubg"]}'),
|
||||
('r6', 'ubisoft', 'stan_osu', 'stan_osu', '{}')
|
||||
ON CONFLICT (game, source, external_id) DO NOTHING;
|
||||
--> statement-breakpoint
|
||||
-- PUBG and R6 are tracked on PC accounts.
|
||||
UPDATE games.library SET platform = 'PC' WHERE slug IN ('pubg', 'r6') AND platform IS NULL;
|
||||
28436
drizzle/20261025155800_games_matches/snapshot.json
Normal file
28436
drizzle/20261025155800_games_matches/snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user