SlotsOne — PostgreSQL 16 Database Layer | v1.0 | Last updated: 2026-02-28
Complete reference of every SQL statement executed by the SlotsOne backend.
All queries use parameterised placeholders ($1, $2, …) via the
pg driver to prevent SQL injection. No raw string concatenation is ever used.
Six tables across two migrations (001_init.sql, 002_game_history.sql), executed on every server startup via initDb().
All DDL uses IF NOT EXISTS for idempotency.
| id | UUID | PK | gen_random_uuid() |
| TEXT | UNIQUE NOT NULL | ||
| password_hash | TEXT | NOT NULL | |
| created_at | TIMESTAMPTZ | NOT NULL | NOW() |
| token | TEXT | PK | |
| user_id | UUID | FK NOT NULL | → users(id) |
| expires_at | TIMESTAMPTZ | NOT NULL | |
| created_at | TIMESTAMPTZ | NOT NULL | NOW() |
idx_refresh_tokens_user_id B-tree on user_ididx_refresh_tokens_expires_at B-tree on expires_at
| id | UUID | PK | gen_random_uuid() |
| user_id | UUID | FK UNIQUE NOT NULL | → users(id) |
| balance_cents | BIGINT | NOT NULL | 100000 |
| version | INTEGER | NOT NULL | 1 |
| created_at | TIMESTAMPTZ | NOT NULL | NOW() |
| updated_at | TIMESTAMPTZ | NOT NULL | NOW() |
| id | UUID | PK | gen_random_uuid() |
| user_id | UUID | FK NOT NULL | → users(id) |
| server_seed | TEXT | NOT NULL | |
| server_seed_hash | TEXT | NOT NULL | |
| client_seed | TEXT | NOT NULL | 'default' |
| nonce | INTEGER | NOT NULL | 0 |
| active | BOOLEAN | NOT NULL | TRUE |
| revealed_at | TIMESTAMPTZ | ||
| created_at | TIMESTAMPTZ | NOT NULL | NOW() |
idx_seed_pairs_user_active B-tree on (user_id, active)
| id | UUID | PK | gen_random_uuid() |
| user_id | UUID | FK NOT NULL | → users(id) |
| session_id | TEXT | NOT NULL | |
| game_id | TEXT | NOT NULL | |
| seed_pair_id | UUID | FK | → seed_pairs(id) |
| nonce | INTEGER | ||
| bet_cents | BIGINT | NOT NULL | |
| win_cents | BIGINT | NOT NULL | 0 |
| currency | TEXT | NOT NULL | 'USD' |
| lines | INTEGER | NOT NULL | |
| balance_before_cents | BIGINT | NOT NULL | |
| balance_after_cents | BIGINT | NOT NULL | |
| reel_matrix | JSONB | NOT NULL | |
| win_breakdown | JSONB | NOT NULL | '[]'::jsonb |
| bonus_triggered | JSONB | ||
| outcome_hash | TEXT | ||
| created_at | TIMESTAMPTZ | NOT NULL | NOW() |
idx_game_rounds_user_created B-tree on (user_id, created_at DESC)
| id | UUID | PK | gen_random_uuid() |
| round_id | UUID | FK | → game_rounds(id) nullable |
| user_id | UUID | FK NOT NULL | → users(id) |
| type | TEXT | NOT NULL | CHECK ('bet','win','topup') |
| amount_cents | BIGINT | NOT NULL | |
| balance_after_cents | BIGINT | NOT NULL | |
| created_at | TIMESTAMPTZ | NOT NULL | NOW() |
idx_transactions_user_created B-tree on (user_id, created_at DESC)
pg.Pool (node-postgres) — singleton, lazily initialisedClick any row to jump to the full query detail.
$N placeholder argument — never concatenated into SQL strings. This eliminates SQL injection vectors.DELETE … RETURNING consumes a refresh token and retrieves the owner in a single atomic statement, preventing race conditions and replay attacks.refresh_tokens.user_id FK uses ON DELETE CASCADE — deleting a user automatically purges all their tokens.users.email column has a UNIQUE constraint. The app catches PostgreSQL error code 23505 and returns a 409 Conflict.NOW() + interval '7 days', avoiding clock-skew between Node.js and PostgreSQL.CREATE TABLE IF NOT EXISTS and CREATE INDEX IF NOT EXISTS — safe to run repeatedly on every server start.refresh_tokens(user_id) and refresh_tokens(expires_at) ensure O(log n) lookups for validation and cleanup.closePool() drains active connections and resets the singleton reference, preventing connection leaks on restart.version column check in the WHERE clause. If a concurrent request modifies the wallet between read and update, the version mismatch causes 0 rows affected — preventing lost updates without SELECT FOR UPDATE locks.BIGINT in cents (1 USD = 100 cents). This eliminates IEEE 754 floating-point rounding errors that occur with DECIMAL or FLOAT in JavaScript.BEGIN/COMMIT using pool.connect(). On failure, ROLLBACK ensures no orphaned records persist.HMAC-SHA256(server_seed, client_seed:nonce) for spin derivation and SHA-256 for server seed hashing. The server seed is never exposed while the pair is active.(user_id, created_at DESC) and (user_id, active) eliminate sort steps and heap filters for the most frequent query patterns.backend/src/migrations/001_init.sqlbackend/src/migrations/002_game_history.sqlbackend/src/db.tsbackend/src/userStore.tsbackend/src/auth/refreshTokenStore.tsbackend/src/walletStore.tsbackend/src/seedStore.tsbackend/src/roundStore.tsbackend/src/provablyFair.ts