This commit is contained in:
ZOMBIIIIIII
2026-05-13 12:07:48 +03:00
parent 3a890b79ee
commit 762a46871b
10 changed files with 375 additions and 155 deletions

View File

@@ -4,6 +4,11 @@
-- ║ Безопасно прогонять повторно на existing БД. ║
-- ╚══════════════════════════════════════════════════════════════════╝
-- NOTE: idempotency_keys + audit_log таблицы УДАЛЕНЫ из БД.
-- - idempotency_keys → KeyDB (Redis cache), см. apps/api/src/config/redis.ts
-- - audit_log → stdout-only (Docker logs / log-aggregator подбирает JSON lines)
-- Migration ниже drop'ает их если они существуют от прошлой версии.
CREATE TABLE IF NOT EXISTS users (
id VARCHAR(26) NOT NULL PRIMARY KEY,
email VARCHAR(255) NOT NULL UNIQUE,
@@ -43,9 +48,6 @@ BEGIN
END $$;
-- Sanity check на blob size. Floor 100 (worst-case 12-word всё-3char mnemonic):
-- plaintext 47 bytes + IV(12) + tag(16) = 75 raw → 100 base64
-- typical 12-word: 113 raw → 152 base64; 24-word: 240 raw → 320 base64
-- (Раньше floor 140 отвергал ~4% валидных 12-word mnemonics — fixed.)
DO $$
BEGIN
IF EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'users_encrypted_mnemonic_size') THEN
@@ -89,8 +91,6 @@ END $$;
-- ── WALLETS ─────────────────────────────────────────────────────────
-- ON DELETE RESTRICT: hard-delete user → запрос отвергнут пока есть wallets.
-- Это защита от unrecoverable fund loss при GDPR-wipe или admin удалении.
-- Use is_deleted=true для soft-delete.
CREATE TABLE IF NOT EXISTS wallets (
id VARCHAR(26) NOT NULL PRIMARY KEY,
user_id VARCHAR(26) NOT NULL REFERENCES users(id) ON DELETE RESTRICT,
@@ -118,38 +118,8 @@ BEGIN
END IF;
END $$;
-- ── AUDIT_LOG (durable sink для критических custodial операций) ─────
-- Pre-mutation INSERT 'pending', post-mutation UPDATE 'completed' с txid.
-- Если INSERT fails — операция НЕ происходит (fail-secure).
CREATE TABLE IF NOT EXISTS audit_log (
id VARCHAR(26) NOT NULL PRIMARY KEY,
user_id VARCHAR(26) NOT NULL,
event VARCHAR(64) NOT NULL,
status VARCHAR(16) NOT NULL DEFAULT 'pending'
CHECK (status IN ('pending', 'success', 'failure')),
error_code VARCHAR(64),
ip VARCHAR(64),
trace_id VARCHAR(64),
meta JSONB,
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS idx_audit_log_user_id ON audit_log(user_id);
CREATE INDEX IF NOT EXISTS idx_audit_log_event_created ON audit_log(event, created_at DESC);
-- ── IDEMPOTENCY_KEYS (защита от double-spend на retry) ──────────────
-- Client шлёт Idempotency-Key header. Pre-mutation INSERT row, post-mutation UPDATE с response.
-- На retry — возвращаем cached response без второго broadcast.
CREATE TABLE IF NOT EXISTS idempotency_keys (
user_id VARCHAR(26) NOT NULL,
key VARCHAR(128) NOT NULL,
request_hash VARCHAR(64) NOT NULL,
response_status SMALLINT,
response_body TEXT,
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (user_id, key)
);
CREATE INDEX IF NOT EXISTS idx_idempotency_keys_created ON idempotency_keys(created_at);
-- Retention cleanup (run via cron): DELETE FROM idempotency_keys WHERE created_at < NOW() - INTERVAL '24 hours';
-- ── DROP legacy tables (если existing БД от прошлой версии) ────────
-- idempotency_keys → KeyDB cache (apps/api/src/lib/idempotency.ts → Redis)
-- audit_log → stdout-only (apps/api/src/lib/audit-log.ts)
DROP TABLE IF EXISTS audit_log CASCADE;
DROP TABLE IF EXISTS idempotency_keys CASCADE;