trading-platform-database-v2/schemas/07_audit_schema.sql
rckrdmrd 45e77e9a9c feat: Initial commit - Database schemas and scripts
DDL schemas for Trading Platform:
- User management
- Authentication
- Payments
- Education
- ML predictions
- Trading data

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-18 04:30:23 -06:00

403 lines
13 KiB
PL/PgSQL

-- ============================================================================
-- OrbiQuant IA - Esquema AUDIT
-- ============================================================================
-- Archivo: 07_audit_schema.sql
-- Descripción: Logs de auditoría, eventos del sistema y seguridad
-- Fecha: 2025-12-05
-- ============================================================================
SET search_path TO audit;
-- ============================================================================
-- TABLA: audit_logs
-- Descripción: Log general de acciones en el sistema
-- ============================================================================
CREATE TABLE IF NOT EXISTS audit_logs (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
-- Tabla afectada
table_name VARCHAR(100) NOT NULL,
record_id TEXT,
-- Acción
action VARCHAR(20) NOT NULL, -- 'INSERT', 'UPDATE', 'DELETE'
-- Datos
old_data JSONB,
new_data JSONB,
-- Usuario
user_id UUID,
user_email VARCHAR(255),
-- Contexto
ip_address INET,
user_agent TEXT,
session_id UUID,
-- Timestamp
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
);
-- Índices para búsqueda eficiente
CREATE INDEX idx_audit_logs_table ON audit_logs(table_name);
CREATE INDEX idx_audit_logs_record ON audit_logs(table_name, record_id);
CREATE INDEX idx_audit_logs_user ON audit_logs(user_id);
CREATE INDEX idx_audit_logs_action ON audit_logs(action);
CREATE INDEX idx_audit_logs_created ON audit_logs(created_at DESC);
-- Particionamiento por mes (para producción)
-- CREATE TABLE audit_logs_2025_01 PARTITION OF audit_logs
-- FOR VALUES FROM ('2025-01-01') TO ('2025-02-01');
-- ============================================================================
-- TABLA: security_events
-- Descripción: Eventos de seguridad
-- ============================================================================
CREATE TYPE security_event_type AS ENUM (
'login_success',
'login_failed',
'logout',
'password_changed',
'password_reset_requested',
'password_reset_completed',
'2fa_enabled',
'2fa_disabled',
'2fa_failed',
'account_locked',
'account_unlocked',
'suspicious_activity',
'api_key_created',
'api_key_revoked',
'permission_denied',
'rate_limit_exceeded'
);
CREATE TYPE security_severity AS ENUM ('info', 'warning', 'error', 'critical');
CREATE TABLE IF NOT EXISTS security_events (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
-- Usuario
user_id UUID REFERENCES auth.users(id),
user_email VARCHAR(255),
-- Evento
event_type security_event_type NOT NULL,
severity security_severity DEFAULT 'info',
description TEXT,
-- Contexto
ip_address INET,
user_agent TEXT,
location JSONB, -- {country, city, lat, lon}
-- Datos adicionales
metadata JSONB,
-- Estado
acknowledged BOOLEAN DEFAULT FALSE,
acknowledged_by UUID REFERENCES auth.users(id),
acknowledged_at TIMESTAMPTZ,
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_security_events_user ON security_events(user_id);
CREATE INDEX idx_security_events_type ON security_events(event_type);
CREATE INDEX idx_security_events_severity ON security_events(severity);
CREATE INDEX idx_security_events_created ON security_events(created_at DESC);
CREATE INDEX idx_security_events_unack ON security_events(acknowledged) WHERE acknowledged = FALSE;
-- ============================================================================
-- TABLA: system_events
-- Descripción: Eventos del sistema (no de usuario)
-- ============================================================================
CREATE TYPE system_event_type AS ENUM (
'service_started',
'service_stopped',
'service_error',
'database_backup',
'database_restore',
'deployment',
'config_changed',
'scheduled_job_started',
'scheduled_job_completed',
'scheduled_job_failed',
'integration_connected',
'integration_disconnected',
'integration_error',
'alert_triggered',
'maintenance_started',
'maintenance_completed'
);
CREATE TABLE IF NOT EXISTS system_events (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
-- Evento
event_type system_event_type NOT NULL,
service_name VARCHAR(100),
description TEXT,
-- Severidad
severity security_severity DEFAULT 'info',
-- Detalles
details JSONB,
error_message TEXT,
stack_trace TEXT,
-- Metadata
hostname VARCHAR(255),
environment VARCHAR(50), -- 'development', 'staging', 'production'
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_system_events_type ON system_events(event_type);
CREATE INDEX idx_system_events_service ON system_events(service_name);
CREATE INDEX idx_system_events_severity ON system_events(severity);
CREATE INDEX idx_system_events_created ON system_events(created_at DESC);
-- ============================================================================
-- TABLA: trading_audit
-- Descripción: Auditoría específica de operaciones de trading
-- ============================================================================
CREATE TABLE IF NOT EXISTS trading_audit (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
-- Referencias
bot_id UUID,
signal_id UUID,
position_id UUID,
account_id UUID,
user_id UUID,
-- Evento
event_type VARCHAR(50) NOT NULL, -- 'signal_generated', 'order_placed', 'order_filled', etc.
description TEXT,
-- Datos de la operación
symbol VARCHAR(20),
direction VARCHAR(10),
lot_size DECIMAL(10,4),
price DECIMAL(20,8),
-- Precios
entry_price DECIMAL(20,8),
stop_loss DECIMAL(20,8),
take_profit DECIMAL(20,8),
-- Resultado
pnl DECIMAL(15,2),
outcome VARCHAR(20),
-- Contexto del modelo
model_id UUID,
confidence DECIMAL(5,4),
amd_phase VARCHAR(20),
-- Metadata
metadata JSONB,
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_trading_audit_bot ON trading_audit(bot_id);
CREATE INDEX idx_trading_audit_signal ON trading_audit(signal_id);
CREATE INDEX idx_trading_audit_position ON trading_audit(position_id);
CREATE INDEX idx_trading_audit_account ON trading_audit(account_id);
CREATE INDEX idx_trading_audit_event ON trading_audit(event_type);
CREATE INDEX idx_trading_audit_symbol ON trading_audit(symbol);
CREATE INDEX idx_trading_audit_created ON trading_audit(created_at DESC);
-- ============================================================================
-- TABLA: api_request_logs
-- Descripción: Logs de requests a la API
-- ============================================================================
CREATE TABLE IF NOT EXISTS api_request_logs (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
-- Usuario
user_id UUID,
api_key_id UUID,
-- Request
method VARCHAR(10) NOT NULL,
path VARCHAR(500) NOT NULL,
query_params JSONB,
headers JSONB,
body_size INT,
-- Response
status_code INT,
response_time_ms INT,
response_size INT,
-- Contexto
ip_address INET,
user_agent TEXT,
-- Error (si aplica)
error_message TEXT,
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
);
-- Índices para análisis
CREATE INDEX idx_api_logs_user ON api_request_logs(user_id);
CREATE INDEX idx_api_logs_path ON api_request_logs(path);
CREATE INDEX idx_api_logs_status ON api_request_logs(status_code);
CREATE INDEX idx_api_logs_created ON api_request_logs(created_at DESC);
-- ============================================================================
-- TABLA: data_access_logs
-- Descripción: Log de acceso a datos sensibles
-- ============================================================================
CREATE TABLE IF NOT EXISTS data_access_logs (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
-- Usuario que accedió
user_id UUID NOT NULL REFERENCES auth.users(id),
-- Dato accedido
resource_type VARCHAR(100) NOT NULL, -- 'user_profile', 'kyc_document', 'wallet_balance', etc.
resource_id UUID,
resource_owner_id UUID, -- Usuario dueño del dato
-- Acción
action VARCHAR(50) NOT NULL, -- 'view', 'export', 'modify'
-- Contexto
reason TEXT, -- Justificación del acceso
ip_address INET,
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_data_access_user ON data_access_logs(user_id);
CREATE INDEX idx_data_access_resource ON data_access_logs(resource_type, resource_id);
CREATE INDEX idx_data_access_owner ON data_access_logs(resource_owner_id);
CREATE INDEX idx_data_access_created ON data_access_logs(created_at DESC);
-- ============================================================================
-- TABLA: compliance_logs
-- Descripción: Logs de cumplimiento regulatorio
-- ============================================================================
CREATE TABLE IF NOT EXISTS compliance_logs (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
-- Usuario
user_id UUID REFERENCES auth.users(id),
-- Evento
event_type VARCHAR(100) NOT NULL,
-- 'terms_accepted', 'risk_disclosure_accepted', 'kyc_submitted', 'kyc_approved',
-- 'aml_check_passed', 'suspicious_activity_flagged', etc.
-- Detalles
description TEXT,
document_version VARCHAR(50),
document_url TEXT,
-- Metadata
metadata JSONB,
ip_address INET,
-- Estado de revisión
requires_review BOOLEAN DEFAULT FALSE,
reviewed_by UUID REFERENCES auth.users(id),
reviewed_at TIMESTAMPTZ,
review_notes TEXT,
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_compliance_user ON compliance_logs(user_id);
CREATE INDEX idx_compliance_event ON compliance_logs(event_type);
CREATE INDEX idx_compliance_review ON compliance_logs(requires_review) WHERE requires_review = TRUE;
CREATE INDEX idx_compliance_created ON compliance_logs(created_at DESC);
-- ============================================================================
-- VISTAS DE ADMINISTRACIÓN
-- ============================================================================
-- Vista: Actividad reciente de usuarios
CREATE OR REPLACE VIEW admin_user_activity AS
SELECT
u.id AS user_id,
u.email,
p.first_name,
p.last_name,
u.role,
u.status,
u.last_login_at,
u.last_login_ip,
(SELECT COUNT(*) FROM audit.security_events se WHERE se.user_id = u.id AND se.created_at > NOW() - INTERVAL '24 hours') AS security_events_24h,
(SELECT COUNT(*) FROM audit.api_request_logs ar WHERE ar.user_id = u.id AND ar.created_at > NOW() - INTERVAL '24 hours') AS api_requests_24h
FROM auth.users u
LEFT JOIN public.profiles p ON p.user_id = u.id;
-- Vista: Alertas de seguridad pendientes
CREATE OR REPLACE VIEW admin_security_alerts AS
SELECT
se.*,
u.email AS user_email,
p.first_name,
p.last_name
FROM audit.security_events se
LEFT JOIN auth.users u ON u.id = se.user_id
LEFT JOIN public.profiles p ON p.user_id = se.user_id
WHERE se.acknowledged = FALSE
AND se.severity IN ('warning', 'error', 'critical')
ORDER BY se.created_at DESC;
-- Vista: Resumen de trading por bot
CREATE OR REPLACE VIEW admin_bot_trading_summary AS
SELECT
ta.bot_id,
b.name AS bot_name,
b.risk_profile,
DATE(ta.created_at) AS date,
COUNT(*) FILTER (WHERE ta.event_type = 'signal_generated') AS signals_generated,
COUNT(*) FILTER (WHERE ta.event_type = 'order_placed') AS orders_placed,
COUNT(*) FILTER (WHERE ta.event_type = 'order_filled') AS orders_filled,
SUM(ta.pnl) AS total_pnl,
COUNT(*) FILTER (WHERE ta.pnl > 0) AS winning_trades,
COUNT(*) FILTER (WHERE ta.pnl < 0) AS losing_trades
FROM audit.trading_audit ta
LEFT JOIN trading.bots b ON b.id = ta.bot_id
WHERE ta.bot_id IS NOT NULL
GROUP BY ta.bot_id, b.name, b.risk_profile, DATE(ta.created_at);
-- ============================================================================
-- FUNCIONES DE LIMPIEZA
-- ============================================================================
-- Función para limpiar logs antiguos
CREATE OR REPLACE FUNCTION cleanup_old_logs(retention_days INT DEFAULT 90)
RETURNS TABLE(
table_name TEXT,
rows_deleted BIGINT
) AS $$
DECLARE
cutoff_date TIMESTAMPTZ;
BEGIN
cutoff_date := NOW() - (retention_days || ' days')::INTERVAL;
-- API request logs (30 días por defecto)
DELETE FROM audit.api_request_logs WHERE created_at < cutoff_date;
RETURN QUERY SELECT 'api_request_logs'::TEXT, COUNT(*)::BIGINT FROM audit.api_request_logs WHERE created_at < cutoff_date;
-- Audit logs (90 días por defecto, excepto críticos)
DELETE FROM audit.audit_logs WHERE created_at < cutoff_date;
-- Security events (mantener indefinidamente los críticos)
DELETE FROM audit.security_events
WHERE created_at < cutoff_date
AND severity NOT IN ('error', 'critical');
END;
$$ LANGUAGE plpgsql;