mirror of
https://github.com/jcreek/CosmicClash.git
synced 2026-09-11 08:23:45 +00:00
feat: add matchmaking durable schema migration
This commit is contained in:
@@ -0,0 +1,138 @@
|
||||
-- Cosmic Clash matchmaking control plane, migration 0001.
|
||||
-- PostgreSQL is the durable authority. Redis indexes are rebuildable and do
|
||||
-- not appear in this schema or in any ownership constraint.
|
||||
|
||||
CREATE TABLE identities (
|
||||
player_id TEXT PRIMARY KEY,
|
||||
steam_id TEXT NOT NULL UNIQUE,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
banned_until TIMESTAMPTZ,
|
||||
ban_reason TEXT
|
||||
);
|
||||
|
||||
CREATE TABLE sessions (
|
||||
session_id TEXT PRIMARY KEY,
|
||||
player_id TEXT NOT NULL REFERENCES identities(player_id),
|
||||
token_digest BYTEA NOT NULL UNIQUE,
|
||||
expires_at TIMESTAMPTZ NOT NULL,
|
||||
revoked_at TIMESTAMPTZ,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE TABLE queue_tickets (
|
||||
ticket_id TEXT PRIMARY KEY,
|
||||
player_id TEXT NOT NULL REFERENCES identities(player_id),
|
||||
playlist TEXT NOT NULL CHECK (playlist IN ('casual', 'ranked')),
|
||||
state TEXT NOT NULL CHECK (state IN ('QUEUED', 'PROPOSED', 'ACCEPTED', 'ALLOCATING', 'PROCESS_READY', 'ASSIGNMENT_READY', 'ASSIGNED', 'CONNECTING', 'LIVE', 'RESULT_PENDING', 'COMPLETED', 'CANCELLED', 'EXPIRED', 'FAILED')),
|
||||
client_build TEXT NOT NULL,
|
||||
protocol_version INTEGER NOT NULL CHECK (protocol_version > 0),
|
||||
enqueued_at TIMESTAMPTZ NOT NULL,
|
||||
expires_at TIMESTAMPTZ NOT NULL,
|
||||
revision BIGINT NOT NULL DEFAULT 0 CHECK (revision >= 0),
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX queue_tickets_one_active_per_player
|
||||
ON queue_tickets (player_id)
|
||||
WHERE state IN ('QUEUED', 'PROPOSED', 'ACCEPTED', 'ALLOCATING', 'PROCESS_READY', 'ASSIGNMENT_READY', 'ASSIGNED', 'CONNECTING', 'LIVE', 'RESULT_PENDING');
|
||||
|
||||
CREATE TABLE proposals (
|
||||
proposal_id TEXT PRIMARY KEY,
|
||||
playlist TEXT NOT NULL CHECK (playlist IN ('casual', 'ranked')),
|
||||
state TEXT NOT NULL CHECK (state IN ('OPEN', 'ACCEPTED', 'DECLINED', 'EXPIRED', 'CANCELLED')),
|
||||
expires_at TIMESTAMPTZ NOT NULL,
|
||||
revision BIGINT NOT NULL DEFAULT 0 CHECK (revision >= 0),
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE TABLE proposal_participants (
|
||||
proposal_id TEXT NOT NULL REFERENCES proposals(proposal_id),
|
||||
player_id TEXT NOT NULL REFERENCES identities(player_id),
|
||||
ticket_id TEXT NOT NULL REFERENCES queue_tickets(ticket_id),
|
||||
response TEXT NOT NULL CHECK (response IN ('PENDING', 'ACCEPTED', 'DECLINED', 'TIMED_OUT')),
|
||||
responded_at TIMESTAMPTZ,
|
||||
PRIMARY KEY (proposal_id, player_id),
|
||||
UNIQUE (proposal_id, ticket_id)
|
||||
);
|
||||
|
||||
CREATE TABLE matches (
|
||||
match_id TEXT PRIMARY KEY,
|
||||
playlist TEXT NOT NULL CHECK (playlist IN ('casual', 'ranked')),
|
||||
state TEXT NOT NULL CHECK (state IN ('ALLOCATING', 'PROCESS_READY', 'ASSIGNMENT_READY', 'ASSIGNED', 'CONNECTING', 'LIVE', 'RESULT_PENDING', 'COMPLETED', 'CANCELLED', 'FAILED')),
|
||||
region TEXT NOT NULL CHECK (region IN ('EU', 'NA')),
|
||||
protocol_version INTEGER NOT NULL CHECK (protocol_version > 0),
|
||||
server_id TEXT,
|
||||
revision BIGINT NOT NULL DEFAULT 0 CHECK (revision >= 0),
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
completed_at TIMESTAMPTZ
|
||||
);
|
||||
|
||||
CREATE TABLE match_participants (
|
||||
match_id TEXT NOT NULL REFERENCES matches(match_id),
|
||||
player_id TEXT NOT NULL REFERENCES identities(player_id),
|
||||
ticket_id TEXT NOT NULL REFERENCES queue_tickets(ticket_id),
|
||||
slot INTEGER NOT NULL CHECK (slot BETWEEN 0 AND 5),
|
||||
team INTEGER NOT NULL CHECK (team IN (0, 1)),
|
||||
connection_generation BIGINT NOT NULL DEFAULT 0 CHECK (connection_generation >= 0),
|
||||
connected_at TIMESTAMPTZ,
|
||||
abandoned_at TIMESTAMPTZ,
|
||||
participation_active BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
PRIMARY KEY (match_id, player_id),
|
||||
UNIQUE (match_id, slot)
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX match_participants_one_active_match
|
||||
ON match_participants (player_id)
|
||||
WHERE participation_active;
|
||||
|
||||
CREATE TABLE ratings (
|
||||
player_id TEXT PRIMARY KEY REFERENCES identities(player_id),
|
||||
rating DOUBLE PRECISION NOT NULL DEFAULT 1500,
|
||||
deviation DOUBLE PRECISION NOT NULL DEFAULT 350,
|
||||
volatility DOUBLE PRECISION NOT NULL DEFAULT 0.06,
|
||||
ranked_games INTEGER NOT NULL DEFAULT 0 CHECK (ranked_games >= 0),
|
||||
revision BIGINT NOT NULL DEFAULT 0 CHECK (revision >= 0),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE TABLE result_receipts (
|
||||
result_id TEXT PRIMARY KEY,
|
||||
match_id TEXT NOT NULL UNIQUE REFERENCES matches(match_id),
|
||||
result_nonce TEXT NOT NULL UNIQUE,
|
||||
payload_digest BYTEA NOT NULL,
|
||||
integrity_state TEXT NOT NULL CHECK (integrity_state IN ('CERTIFIED', 'SUPPRESSED', 'REVIEW')),
|
||||
received_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
committed_at TIMESTAMPTZ
|
||||
);
|
||||
|
||||
CREATE TABLE outbox (
|
||||
event_id TEXT PRIMARY KEY,
|
||||
aggregate_type TEXT NOT NULL,
|
||||
aggregate_id TEXT NOT NULL,
|
||||
revision BIGINT NOT NULL CHECK (revision >= 0),
|
||||
event_type TEXT NOT NULL,
|
||||
payload JSONB NOT NULL,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
published_at TIMESTAMPTZ,
|
||||
UNIQUE (aggregate_type, aggregate_id, revision)
|
||||
);
|
||||
|
||||
CREATE TABLE audit_events (
|
||||
audit_id BIGSERIAL PRIMARY KEY,
|
||||
actor_type TEXT NOT NULL CHECK (actor_type IN ('PLAYER', 'SERVER', 'SYSTEM', 'ADMIN')),
|
||||
actor_id TEXT,
|
||||
action TEXT NOT NULL,
|
||||
aggregate_type TEXT NOT NULL,
|
||||
aggregate_id TEXT NOT NULL,
|
||||
request_id TEXT,
|
||||
metadata JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE INDEX queue_tickets_candidate_order
|
||||
ON queue_tickets (playlist, enqueued_at, ticket_id)
|
||||
WHERE state = 'QUEUED';
|
||||
|
||||
CREATE INDEX outbox_unpublished_order
|
||||
ON outbox (created_at, event_id)
|
||||
WHERE published_at IS NULL;
|
||||
Reference in New Issue
Block a user