Схема операций (Аудит, Уведомления, Надежность)
Журнал аудита
sql
CREATE TABLE core.audit_log (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
event_type VARCHAR(100) NOT NULL,
event_category VARCHAR(50) NOT NULL,
actor_id UUID,
actor_type VARCHAR(20) NOT NULL, -- USER, ADMIN, SYSTEM
target_type VARCHAR(50),
target_id UUID,
action VARCHAR(20) NOT NULL, -- CREATE, UPDATE, DELETE, READ
changes JSONB, -- {field: {old, new}}
ip_address INET,
user_agent TEXT,
session_id UUID,
request_id UUID
);
-- Партиционирование по месяцам для производительности
-- CREATE TABLE core.audit_log_2024_01 PARTITION OF core.audit_log
-- FOR VALUES FROM ('2024-01-01') TO ('2024-02-01');
CREATE INDEX idx_audit_actor ON core.audit_log(actor_id, created_at DESC);
CREATE INDEX idx_audit_target ON core.audit_log(target_type, target_id);
CREATE INDEX idx_audit_event ON core.audit_log(event_type, created_at DESC);Финансовый журнал аудита (Неизменяемый)
sql
CREATE TABLE mlm.financial_audit_log (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
event_type VARCHAR(50) NOT NULL,
partner_id UUID NOT NULL,
amount DECIMAL(20,2),
currency VARCHAR(3),
balance_before DECIMAL(20,2),
balance_after DECIMAL(20,2),
source_type VARCHAR(30),
source_id UUID,
metadata JSONB,
checksum VARCHAR(64) NOT NULL, -- SHA256 для целостности
previous_checksum VARCHAR(64) -- Проверка цепочки
);
-- Запрет модификаций
-- REVOKE UPDATE, DELETE ON mlm.financial_audit_log FROM app_user;
CREATE INDEX idx_fin_audit_partner ON mlm.financial_audit_log(partner_id, created_at DESC);
CREATE INDEX idx_fin_audit_type ON mlm.financial_audit_log(event_type);Уведомления
sql
CREATE TABLE core.notifications (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
user_id UUID NOT NULL REFERENCES core.users(id) ON DELETE CASCADE,
type VARCHAR(50) NOT NULL,
title VARCHAR(200) NOT NULL,
message TEXT,
action_url VARCHAR(500),
priority VARCHAR(20) DEFAULT 'NORMAL',
-- LOW, NORMAL, HIGH, URGENT
is_read BOOLEAN DEFAULT FALSE,
read_at TIMESTAMP,
expires_at TIMESTAMP,
created_at TIMESTAMP NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_notifications_user ON core.notifications(user_id, created_at DESC);
CREATE INDEX idx_notifications_unread ON core.notifications(user_id, is_read) WHERE is_read = FALSE;Настройки уведомлений
sql
CREATE TABLE core.notification_preferences (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
user_id UUID NOT NULL REFERENCES core.users(id) ON DELETE CASCADE,
event_type VARCHAR(50) NOT NULL,
email_enabled BOOLEAN DEFAULT TRUE,
sms_enabled BOOLEAN DEFAULT FALSE,
push_enabled BOOLEAN DEFAULT TRUE,
in_app_enabled BOOLEAN DEFAULT TRUE,
UNIQUE(user_id, event_type)
);
CREATE INDEX idx_notif_prefs_user ON core.notification_preferences(user_id);Ключи идемпотентности (Предотвращение дублирования операций)
sql
CREATE TABLE core.idempotency_keys (
key VARCHAR(255) PRIMARY KEY,
request_hash VARCHAR(64) NOT NULL,
response JSONB,
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
expires_at TIMESTAMP NOT NULL
);
-- Периодическая очистка старых ключей (задача pg-boss)
CREATE INDEX idx_idempotency_expires ON core.idempotency_keys(expires_at);Промокоды
sql
CREATE TABLE product.promo_codes (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
code VARCHAR(50) NOT NULL UNIQUE,
discount_type VARCHAR(20) NOT NULL, -- PERCENTAGE, FIXED_AMOUNT
discount_value DECIMAL(20,2) NOT NULL,
min_order_amount DECIMAL(20,2),
max_discount_amount DECIMAL(20,2), -- Лимит для процентных скидок
usage_limit INT, -- NULL = без ограничений
usage_count INT DEFAULT 0,
per_user_limit INT, -- NULL = без ограничений на пользователя
valid_from TIMESTAMP NOT NULL,
valid_to TIMESTAMP,
applicable_to VARCHAR(20), -- ALL, FIRST_ORDER, SPECIFIC_PRODUCTS
product_ids UUID[], -- Если applicable_to = SPECIFIC_PRODUCTS
is_active BOOLEAN DEFAULT TRUE,
created_by UUID REFERENCES core.users(id),
created_at TIMESTAMP NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_promo_codes_code ON product.promo_codes(code) WHERE is_active = TRUE;
CREATE INDEX idx_promo_codes_valid ON product.promo_codes(valid_from, valid_to) WHERE is_active = TRUE;Использование промокодов
sql
CREATE TABLE product.promo_code_usage (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
promo_code_id UUID NOT NULL REFERENCES product.promo_codes(id),
user_id UUID NOT NULL REFERENCES core.users(id),
order_id UUID REFERENCES product.orders(id),
discount_applied DECIMAL(20,2) NOT NULL,
used_at TIMESTAMP NOT NULL DEFAULT NOW(),
UNIQUE(promo_code_id, order_id)
);
CREATE INDEX idx_promo_usage_user ON product.promo_code_usage(user_id, promo_code_id);Резервирование запасов
sql
CREATE TABLE product.inventory_reservations (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
product_id UUID NOT NULL REFERENCES product.products(id),
cart_id UUID REFERENCES product.carts(id),
order_id UUID REFERENCES product.orders(id),
quantity INT NOT NULL,
status VARCHAR(20) NOT NULL DEFAULT 'RESERVED',
-- RESERVED, COMMITTED, RELEASED, EXPIRED
reserved_at TIMESTAMP NOT NULL DEFAULT NOW(),
expires_at TIMESTAMP NOT NULL,
committed_at TIMESTAMP,
CONSTRAINT positive_quantity CHECK (quantity > 0)
);
CREATE INDEX idx_reservations_product ON product.inventory_reservations(product_id, status);
CREATE INDEX idx_reservations_expires ON product.inventory_reservations(expires_at) WHERE status = 'RESERVED';
CREATE INDEX idx_reservations_cart ON product.inventory_reservations(cart_id) WHERE status = 'RESERVED';Ограничение частоты запросов (Опционально - если не используется Redis)
sql
CREATE TABLE core.rate_limits (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
identifier VARCHAR(255) NOT NULL, -- IP, user_id или API-ключ
endpoint VARCHAR(100) NOT NULL,
request_count INT NOT NULL DEFAULT 1,
window_start TIMESTAMP NOT NULL DEFAULT NOW(),
UNIQUE(identifier, endpoint, window_start)
);
-- Автоочистка старых записей
CREATE INDEX idx_rate_limits_cleanup ON core.rate_limits(window_start);