156 lines
5.1 KiB
PL/PgSQL
156 lines
5.1 KiB
PL/PgSQL
-- ============================================
|
|
-- TEMPLATE-SAAS: Audit Logs
|
|
-- Schema: audit
|
|
-- Version: 1.0.0
|
|
-- ============================================
|
|
|
|
CREATE TABLE audit.audit_logs (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id UUID NOT NULL REFERENCES tenants.tenants(id) ON DELETE CASCADE,
|
|
|
|
-- Actor
|
|
user_id UUID REFERENCES users.users(id),
|
|
user_email VARCHAR(255),
|
|
actor_type VARCHAR(50) DEFAULT 'user', -- 'user', 'system', 'api_key', 'webhook'
|
|
|
|
-- Action
|
|
action audit.action_type NOT NULL,
|
|
resource_type VARCHAR(100) NOT NULL, -- 'user', 'product', 'invoice', etc.
|
|
resource_id VARCHAR(255),
|
|
resource_name VARCHAR(255),
|
|
|
|
-- Details
|
|
description TEXT,
|
|
metadata JSONB DEFAULT '{}'::jsonb,
|
|
|
|
-- Change tracking (expanded structure for better query flexibility)
|
|
old_values JSONB, -- Previous values: { field1: oldVal1, ... }
|
|
new_values JSONB, -- New values: { field1: newVal1, ... }
|
|
changed_fields JSONB, -- List of changed fields: ['field1', 'field2', ...]
|
|
|
|
-- Severity
|
|
severity audit.severity DEFAULT 'info',
|
|
|
|
-- Context
|
|
ip_address INET,
|
|
user_agent TEXT,
|
|
request_id VARCHAR(100),
|
|
session_id UUID,
|
|
|
|
-- HTTP context (for API audit)
|
|
endpoint VARCHAR(255),
|
|
http_method VARCHAR(10),
|
|
response_status SMALLINT,
|
|
duration_ms INTEGER,
|
|
|
|
-- Timestamp
|
|
created_at TIMESTAMPTZ DEFAULT NOW() NOT NULL,
|
|
|
|
-- Partitioning support (optional)
|
|
partition_key DATE DEFAULT CURRENT_DATE
|
|
);
|
|
|
|
-- Activity logs (lighter weight, for analytics)
|
|
CREATE TABLE audit.activity_logs (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id UUID NOT NULL REFERENCES tenants.tenants(id) ON DELETE CASCADE,
|
|
user_id UUID REFERENCES users.users(id),
|
|
|
|
-- Activity
|
|
activity_type VARCHAR(100) NOT NULL, -- 'page_view', 'button_click', 'search', etc.
|
|
page_url VARCHAR(500),
|
|
referrer VARCHAR(500),
|
|
|
|
-- Context
|
|
session_id UUID,
|
|
device_type VARCHAR(50),
|
|
browser VARCHAR(100),
|
|
os VARCHAR(100),
|
|
|
|
-- Data
|
|
data JSONB DEFAULT '{}'::jsonb,
|
|
|
|
-- Timestamp
|
|
created_at TIMESTAMPTZ DEFAULT NOW() NOT NULL
|
|
);
|
|
|
|
-- Indexes
|
|
CREATE INDEX idx_audit_logs_tenant ON audit.audit_logs(tenant_id, created_at DESC);
|
|
CREATE INDEX idx_audit_logs_user ON audit.audit_logs(tenant_id, user_id, created_at DESC);
|
|
CREATE INDEX idx_audit_logs_resource ON audit.audit_logs(tenant_id, resource_type, resource_id);
|
|
CREATE INDEX idx_audit_logs_action ON audit.audit_logs(tenant_id, action, created_at DESC);
|
|
CREATE INDEX idx_audit_logs_severity ON audit.audit_logs(tenant_id, severity) WHERE severity IN ('warning', 'error', 'critical');
|
|
|
|
CREATE INDEX idx_activity_logs_tenant ON audit.activity_logs(tenant_id, created_at DESC);
|
|
CREATE INDEX idx_activity_logs_user ON audit.activity_logs(tenant_id, user_id, created_at DESC);
|
|
CREATE INDEX idx_activity_logs_type ON audit.activity_logs(tenant_id, activity_type);
|
|
|
|
-- RLS
|
|
ALTER TABLE audit.audit_logs ENABLE ROW LEVEL SECURITY;
|
|
ALTER TABLE audit.activity_logs ENABLE ROW LEVEL SECURITY;
|
|
|
|
CREATE POLICY audit_logs_tenant_isolation ON audit.audit_logs
|
|
USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
|
|
|
|
CREATE POLICY activity_logs_tenant_isolation ON audit.activity_logs
|
|
USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
|
|
|
|
-- Function to log audit event
|
|
CREATE OR REPLACE FUNCTION audit.log_event(
|
|
p_tenant_id UUID,
|
|
p_user_id UUID,
|
|
p_action audit.action_type,
|
|
p_resource_type VARCHAR,
|
|
p_resource_id VARCHAR DEFAULT NULL,
|
|
p_resource_name VARCHAR DEFAULT NULL,
|
|
p_description TEXT DEFAULT NULL,
|
|
p_old_values JSONB DEFAULT NULL,
|
|
p_new_values JSONB DEFAULT NULL,
|
|
p_changed_fields JSONB DEFAULT NULL,
|
|
p_metadata JSONB DEFAULT '{}'::jsonb,
|
|
p_severity audit.severity DEFAULT 'info'
|
|
)
|
|
RETURNS UUID AS $$
|
|
DECLARE
|
|
v_id UUID;
|
|
BEGIN
|
|
INSERT INTO audit.audit_logs (
|
|
tenant_id, user_id, action, resource_type, resource_id,
|
|
resource_name, description, old_values, new_values, changed_fields,
|
|
metadata, severity
|
|
) VALUES (
|
|
p_tenant_id, p_user_id, p_action, p_resource_type, p_resource_id,
|
|
p_resource_name, p_description, p_old_values, p_new_values, p_changed_fields,
|
|
p_metadata, p_severity
|
|
) RETURNING id INTO v_id;
|
|
|
|
RETURN v_id;
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
|
|
-- Retention policy function
|
|
CREATE OR REPLACE FUNCTION audit.cleanup_old_logs(retention_days INT DEFAULT 90)
|
|
RETURNS INTEGER AS $$
|
|
DECLARE
|
|
deleted_count INTEGER;
|
|
BEGIN
|
|
WITH deleted AS (
|
|
DELETE FROM audit.audit_logs
|
|
WHERE created_at < NOW() - (retention_days || ' days')::INTERVAL
|
|
AND severity NOT IN ('error', 'critical')
|
|
RETURNING *
|
|
)
|
|
SELECT COUNT(*) INTO deleted_count FROM deleted;
|
|
|
|
DELETE FROM audit.activity_logs
|
|
WHERE created_at < NOW() - (retention_days || ' days')::INTERVAL;
|
|
|
|
RETURN deleted_count;
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
|
|
-- Comments
|
|
COMMENT ON TABLE audit.audit_logs IS 'Comprehensive audit trail for compliance';
|
|
COMMENT ON TABLE audit.activity_logs IS 'User activity tracking for analytics';
|
|
COMMENT ON COLUMN audit.audit_logs.changes IS 'JSON diff of field changes';
|