-- CryptoWallet API — DB schema (idempotent, custodial v3.0) CREATE TABLE IF NOT EXISTS users ( id VARCHAR(26) PRIMARY KEY, email VARCHAR(255) NOT NULL UNIQUE, password_hash VARCHAR(255) NOT NULL, last_name VARCHAR(255), first_name VARCHAR(255), middle_name VARCHAR(255), birth_date DATE, crypto_wallet VARCHAR(255), phone VARCHAR(64), bik VARCHAR(64), account_number VARCHAR(64), card_number VARCHAR(64), inn VARCHAR(64), kyc_verified BOOLEAN NOT NULL DEFAULT FALSE, kyc_verified_at TIMESTAMPTZ, is_deleted BOOLEAN NOT NULL DEFAULT FALSE, encrypted_vault TEXT, vault_salt VARCHAR(128), encrypted_mnemonic TEXT, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); DO $$ BEGIN IF NOT EXISTS ( SELECT 1 FROM information_schema.columns WHERE table_name = 'users' AND column_name = 'encrypted_mnemonic' ) THEN ALTER TABLE users ADD COLUMN encrypted_mnemonic TEXT; END IF; END $$; DO $$ BEGIN IF NOT EXISTS ( SELECT 1 FROM pg_constraint WHERE conname = 'users_encrypted_mnemonic_size' ) THEN ALTER TABLE users ADD CONSTRAINT users_encrypted_mnemonic_size CHECK (encrypted_mnemonic IS NULL OR (char_length(encrypted_mnemonic) BETWEEN 140 AND 512)); END IF; END $$; CREATE TABLE IF NOT EXISTS wallets ( id VARCHAR(26) PRIMARY KEY, user_id VARCHAR(26) NOT NULL REFERENCES users(id) ON DELETE CASCADE, chain VARCHAR(16) NOT NULL, address VARCHAR(256) NOT NULL, derivation_path VARCHAR(64) NOT NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), UNIQUE (user_id, chain) ); CREATE INDEX IF NOT EXISTS idx_wallets_user_id ON wallets(user_id); CREATE TABLE IF NOT EXISTS sessions ( id VARCHAR(26) PRIMARY KEY, user_id VARCHAR(26) NOT NULL REFERENCES users(id) ON DELETE CASCADE, expires_at TIMESTAMPTZ NOT NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); CREATE INDEX IF NOT EXISTS idx_sessions_user_id ON sessions(user_id); CREATE INDEX IF NOT EXISTS idx_sessions_expires_at ON sessions(expires_at);