docs: reorder & testing

This commit is contained in:
Ilia Denisov
2026-05-07 00:58:53 +03:00
committed by GitHub
parent f446c6a2ac
commit 604fe40bcf
148 changed files with 9150 additions and 2757 deletions
@@ -31,13 +31,14 @@ CREATE INDEX device_sessions_user_idx ON device_sessions (user_id);
CREATE INDEX device_sessions_status_idx ON device_sessions (status);
CREATE TABLE auth_challenges (
challenge_id uuid PRIMARY KEY,
email text NOT NULL,
code_hash bytea NOT NULL,
attempts integer NOT NULL DEFAULT 0,
created_at timestamptz NOT NULL DEFAULT now(),
expires_at timestamptz NOT NULL,
consumed_at timestamptz
challenge_id uuid PRIMARY KEY,
email text NOT NULL,
code_hash bytea NOT NULL,
attempts integer NOT NULL DEFAULT 0,
created_at timestamptz NOT NULL DEFAULT now(),
expires_at timestamptz NOT NULL,
consumed_at timestamptz,
preferred_language text NOT NULL DEFAULT ''
);
CREATE INDEX auth_challenges_email_idx ON auth_challenges (email);
@@ -48,6 +49,30 @@ CREATE TABLE blocked_emails (
blocked_at timestamptz NOT NULL DEFAULT now()
);
-- session_revocations is the durable audit trail of every device-session
-- revocation. Each revoke writes one row carrying the actor kind, actor
-- id, and free-form reason. The table is append-only; reading it is the
-- only way to answer "who and why revoked this session". The
-- device_session_id column is not a foreign key because device_sessions
-- rows survive after revoke (status='revoked'), and dropping a session
-- through a future cleanup must not implicitly drop its audit history.
CREATE TABLE session_revocations (
revocation_id uuid PRIMARY KEY,
device_session_id uuid NOT NULL,
user_id uuid NOT NULL,
actor_kind text NOT NULL,
actor_user_id uuid,
actor_username text,
reason text NOT NULL DEFAULT '',
revoked_at timestamptz NOT NULL DEFAULT now(),
CONSTRAINT session_revocations_actor_chk
CHECK (actor_user_id IS NULL OR actor_username IS NULL)
);
CREATE INDEX session_revocations_user_idx ON session_revocations (user_id, revoked_at DESC);
CREATE INDEX session_revocations_device_idx ON session_revocations (device_session_id, revoked_at DESC);
CREATE INDEX session_revocations_actor_kind_idx ON session_revocations (actor_kind, revoked_at DESC);
-- =====================================================================
-- User domain
-- =====================================================================
@@ -64,14 +89,17 @@ CREATE TABLE accounts (
preferred_language text NOT NULL,
time_zone text NOT NULL,
declared_country text,
permanent_block boolean NOT NULL DEFAULT false,
deleted_actor_type text,
deleted_actor_id text,
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now(),
deleted_at timestamptz,
permanent_block boolean NOT NULL DEFAULT false,
deleted_actor_type text,
deleted_actor_user_id uuid,
deleted_actor_username text,
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now(),
deleted_at timestamptz,
CONSTRAINT accounts_email_unique UNIQUE (email),
CONSTRAINT accounts_user_name_unique UNIQUE (user_name)
CONSTRAINT accounts_user_name_unique UNIQUE (user_name),
CONSTRAINT accounts_deleted_actor_chk
CHECK (deleted_actor_user_id IS NULL OR deleted_actor_username IS NULL)
);
CREATE INDEX accounts_listing_idx
@@ -88,19 +116,22 @@ CREATE INDEX accounts_declared_country_idx
-- shape used by sanction_records/limit_records: the *_active rollup carries
-- only the binding, the records table is the durable audit log.
CREATE TABLE entitlement_records (
record_id uuid PRIMARY KEY,
user_id uuid NOT NULL REFERENCES accounts (user_id),
tier text NOT NULL,
is_paid boolean NOT NULL,
source text NOT NULL,
actor_type text NOT NULL,
actor_id text,
reason_code text NOT NULL DEFAULT '',
starts_at timestamptz NOT NULL DEFAULT now(),
ends_at timestamptz,
created_at timestamptz NOT NULL DEFAULT now(),
record_id uuid PRIMARY KEY,
user_id uuid NOT NULL REFERENCES accounts (user_id),
tier text NOT NULL,
is_paid boolean NOT NULL,
source text NOT NULL,
actor_type text NOT NULL,
actor_user_id uuid,
actor_username text,
reason_code text NOT NULL DEFAULT '',
starts_at timestamptz NOT NULL DEFAULT now(),
ends_at timestamptz,
created_at timestamptz NOT NULL DEFAULT now(),
CONSTRAINT entitlement_records_tier_chk
CHECK (tier IN ('free', 'monthly', 'yearly', 'permanent'))
CHECK (tier IN ('free', 'monthly', 'yearly', 'permanent')),
CONSTRAINT entitlement_records_actor_chk
CHECK (actor_user_id IS NULL OR actor_username IS NULL)
);
CREATE INDEX entitlement_records_user_idx
@@ -117,32 +148,41 @@ CREATE TABLE entitlement_snapshots (
is_paid boolean NOT NULL,
source text NOT NULL,
actor_type text NOT NULL,
actor_id text,
actor_user_id uuid,
actor_username text,
reason_code text NOT NULL DEFAULT '',
starts_at timestamptz NOT NULL,
ends_at timestamptz,
max_registered_race_names integer NOT NULL,
updated_at timestamptz NOT NULL DEFAULT now(),
CONSTRAINT entitlement_snapshots_tier_chk
CHECK (tier IN ('free', 'monthly', 'yearly', 'permanent'))
CHECK (tier IN ('free', 'monthly', 'yearly', 'permanent')),
CONSTRAINT entitlement_snapshots_actor_chk
CHECK (actor_user_id IS NULL OR actor_username IS NULL)
);
CREATE TABLE sanction_records (
record_id uuid PRIMARY KEY,
user_id uuid NOT NULL REFERENCES accounts (user_id),
sanction_code text NOT NULL,
scope text NOT NULL,
reason_code text NOT NULL,
actor_type text NOT NULL,
actor_id text,
applied_at timestamptz NOT NULL DEFAULT now(),
expires_at timestamptz,
removed_at timestamptz,
removed_by_type text,
removed_by_id text,
removed_reason_code text,
record_id uuid PRIMARY KEY,
user_id uuid NOT NULL REFERENCES accounts (user_id),
sanction_code text NOT NULL,
scope text NOT NULL,
reason_code text NOT NULL,
actor_type text NOT NULL,
actor_user_id uuid,
actor_username text,
applied_at timestamptz NOT NULL DEFAULT now(),
expires_at timestamptz,
removed_at timestamptz,
removed_by_type text,
removed_by_user_id uuid,
removed_by_username text,
removed_reason_code text,
CONSTRAINT sanction_records_code_chk
CHECK (sanction_code IN ('permanent_block'))
CHECK (sanction_code IN ('permanent_block')),
CONSTRAINT sanction_records_actor_chk
CHECK (actor_user_id IS NULL OR actor_username IS NULL),
CONSTRAINT sanction_records_removed_by_chk
CHECK (removed_by_user_id IS NULL OR removed_by_username IS NULL)
);
CREATE INDEX sanction_records_user_idx
@@ -161,19 +201,25 @@ CREATE TABLE sanction_active (
CREATE INDEX sanction_active_code_idx ON sanction_active (sanction_code);
CREATE TABLE limit_records (
record_id uuid PRIMARY KEY,
user_id uuid NOT NULL REFERENCES accounts (user_id),
limit_code text NOT NULL,
value integer NOT NULL,
reason_code text NOT NULL,
actor_type text NOT NULL,
actor_id text,
applied_at timestamptz NOT NULL DEFAULT now(),
expires_at timestamptz,
removed_at timestamptz,
removed_by_type text,
removed_by_id text,
removed_reason_code text
record_id uuid PRIMARY KEY,
user_id uuid NOT NULL REFERENCES accounts (user_id),
limit_code text NOT NULL,
value integer NOT NULL,
reason_code text NOT NULL,
actor_type text NOT NULL,
actor_user_id uuid,
actor_username text,
applied_at timestamptz NOT NULL DEFAULT now(),
expires_at timestamptz,
removed_at timestamptz,
removed_by_type text,
removed_by_user_id uuid,
removed_by_username text,
removed_reason_code text,
CONSTRAINT limit_records_actor_chk
CHECK (actor_user_id IS NULL OR actor_username IS NULL),
CONSTRAINT limit_records_removed_by_chk
CHECK (removed_by_user_id IS NULL OR removed_by_username IS NULL)
);
CREATE INDEX limit_records_user_idx
@@ -1,13 +0,0 @@
-- +goose Up
-- Persist the locale captured at send-email-code so it can be replayed at
-- confirm-email-code when the auth flow needs `preferred_language` to seed
-- a freshly-created `accounts` row. Existing rows default to '' and are
-- treated by the auth service as "no captured locale", in which case the
-- service falls back to the geoip-derived language and finally to "en".
ALTER TABLE backend.auth_challenges
ADD COLUMN preferred_language text NOT NULL DEFAULT '';
-- +goose Down
ALTER TABLE backend.auth_challenges
DROP COLUMN preferred_language;
@@ -0,0 +1,26 @@
# Backend migrations
Goose migrations embedded into the backend binary by `embed.go`. Applied
at startup before any listener opens (see `internal/postgres`).
## Pre-production single-file rule
**While the platform is not yet in production, every schema change goes
into the existing `00001_init.sql` file** rather than a new
`00002_*`-prefixed file. The intent is to keep the schema in one
canonical place so reviewers and developers do not have to reconstruct
the latest shape from a chain of incremental migrations.
Operationally this means that pulling a branch with schema changes
requires a fresh database — the only consumer today is local development
and integration tests, both of which spin up disposable Postgres
instances.
> **Remove this rule before the first production deployment.** From
> that point on every schema change must be a new migration file with a
> monotonically increasing prefix, and `00001_init.sql` becomes
> immutable history.
If you need to make a change, edit `00001_init.sql` directly. Down
migrations should still be kept in sync (they live at the bottom of the
file — currently a single `DROP SCHEMA backend CASCADE`).