221 lines
9.5 KiB
SQL
221 lines
9.5 KiB
SQL
-- EvoBGP initial schema (PostgreSQL). See plan §5.
|
|
-- UUIDs: application may use UUID v7/ULID as text-compatible UUID.
|
|
|
|
CREATE TABLE tenant (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
name TEXT NOT NULL,
|
|
slug TEXT NOT NULL,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
CONSTRAINT tenant_slug_chk CHECK (length(trim(slug)) > 0),
|
|
CONSTRAINT tenant_slug_uniq UNIQUE (slug)
|
|
);
|
|
|
|
CREATE TABLE doh_profile (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id UUID NOT NULL REFERENCES tenant (id) ON DELETE CASCADE,
|
|
name TEXT NOT NULL DEFAULT '',
|
|
url TEXT NOT NULL,
|
|
timeout_ms INTEGER,
|
|
secret_ref TEXT,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
CONSTRAINT doh_profile_url_chk CHECK (length(trim(url)) > 0)
|
|
);
|
|
|
|
CREATE INDEX idx_doh_profile_tenant ON doh_profile (tenant_id);
|
|
|
|
CREATE TABLE bgp_community (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id UUID NOT NULL REFERENCES tenant (id) ON DELETE CASCADE,
|
|
name TEXT NOT NULL DEFAULT '',
|
|
kind TEXT NOT NULL,
|
|
value_json JSONB NOT NULL DEFAULT '{}',
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
CONSTRAINT bgp_community_kind_chk CHECK (length(trim(kind)) > 0)
|
|
);
|
|
|
|
CREATE INDEX idx_bgp_community_tenant ON bgp_community (tenant_id);
|
|
|
|
CREATE TABLE module (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id UUID NOT NULL REFERENCES tenant (id) ON DELETE CASCADE,
|
|
type TEXT NOT NULL,
|
|
name TEXT NOT NULL,
|
|
enabled BOOLEAN NOT NULL DEFAULT true,
|
|
priority INTEGER NOT NULL DEFAULT 0,
|
|
doh_profile_id UUID REFERENCES doh_profile (id) ON DELETE SET NULL,
|
|
refresh_interval_sec INTEGER,
|
|
cron_expr TEXT,
|
|
default_community_id UUID REFERENCES bgp_community (id) ON DELETE SET NULL,
|
|
deleted_at TIMESTAMPTZ,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
CONSTRAINT module_type_chk CHECK (type IN (
|
|
'AS_PREFIXES', 'CDN_CIDRS', 'DOMAINS', 'IP_RANGES'
|
|
)),
|
|
CONSTRAINT module_name_chk CHECK (length(trim(name)) > 0)
|
|
);
|
|
|
|
CREATE INDEX idx_module_tenant ON module (tenant_id);
|
|
CREATE INDEX idx_module_tenant_type ON module (tenant_id, type);
|
|
CREATE INDEX idx_module_tenant_enabled ON module (tenant_id, enabled) WHERE deleted_at IS NULL;
|
|
|
|
CREATE TABLE module_cdn_source (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
module_id UUID NOT NULL REFERENCES module (id) ON DELETE CASCADE,
|
|
source_kind TEXT NOT NULL,
|
|
url TEXT NOT NULL,
|
|
etag TEXT,
|
|
refresh_interval_sec INTEGER,
|
|
community_id UUID REFERENCES bgp_community (id) ON DELETE SET NULL,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
CONSTRAINT module_cdn_source_url_chk CHECK (length(trim(url)) > 0)
|
|
);
|
|
|
|
CREATE INDEX idx_module_cdn_source_module ON module_cdn_source (module_id);
|
|
|
|
CREATE TABLE module_domain_entry (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
module_id UUID NOT NULL REFERENCES module (id) ON DELETE CASCADE,
|
|
fqdn TEXT NOT NULL,
|
|
community_id UUID REFERENCES bgp_community (id) ON DELETE SET NULL,
|
|
resolve_meta JSONB NOT NULL DEFAULT '{}',
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
CONSTRAINT module_domain_fqdn_chk CHECK (length(trim(fqdn)) > 0)
|
|
);
|
|
|
|
CREATE INDEX idx_module_domain_entry_module ON module_domain_entry (module_id);
|
|
|
|
CREATE TABLE module_as_entry (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
module_id UUID NOT NULL REFERENCES module (id) ON DELETE CASCADE,
|
|
asn BIGINT,
|
|
prefix CIDR,
|
|
community_id UUID REFERENCES bgp_community (id) ON DELETE SET NULL,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
CONSTRAINT module_as_entry_asn_or_prefix_chk CHECK (asn IS NOT NULL OR prefix IS NOT NULL)
|
|
);
|
|
|
|
CREATE INDEX idx_module_as_entry_module ON module_as_entry (module_id);
|
|
|
|
CREATE TABLE module_ip_range_entry (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
module_id UUID NOT NULL REFERENCES module (id) ON DELETE CASCADE,
|
|
prefix CIDR NOT NULL,
|
|
community_id UUID REFERENCES bgp_community (id) ON DELETE SET NULL,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
);
|
|
|
|
CREATE INDEX idx_module_ip_range_entry_module ON module_ip_range_entry (module_id);
|
|
|
|
-- Full config snapshot for a tenant (optionally tied to one module for filtering in API).
|
|
CREATE TABLE config_revision (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id UUID NOT NULL REFERENCES tenant (id) ON DELETE CASCADE,
|
|
module_id UUID REFERENCES module (id) ON DELETE SET NULL,
|
|
content_hash TEXT NOT NULL,
|
|
parent_revision_id UUID REFERENCES config_revision (id) ON DELETE SET NULL,
|
|
artifact_ref TEXT,
|
|
meta_json JSONB NOT NULL DEFAULT '{}',
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
CONSTRAINT config_revision_hash_chk CHECK (length(trim(content_hash)) > 0)
|
|
);
|
|
|
|
CREATE INDEX idx_config_revision_tenant ON config_revision (tenant_id, created_at DESC);
|
|
CREATE INDEX idx_config_revision_module ON config_revision (module_id) WHERE module_id IS NOT NULL;
|
|
|
|
CREATE TABLE bgp_speaker (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id UUID NOT NULL REFERENCES tenant (id) ON DELETE CASCADE,
|
|
role TEXT NOT NULL,
|
|
endpoint TEXT,
|
|
last_applied_revision_id UUID REFERENCES config_revision (id) ON DELETE SET NULL,
|
|
meta_json JSONB NOT NULL DEFAULT '{}',
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
CONSTRAINT bgp_speaker_role_chk CHECK (role IN ('master', 'replica'))
|
|
);
|
|
|
|
CREATE INDEX idx_bgp_speaker_tenant ON bgp_speaker (tenant_id);
|
|
|
|
CREATE TABLE bgp_peer (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id UUID NOT NULL REFERENCES tenant (id) ON DELETE CASCADE,
|
|
bgp_speaker_id UUID REFERENCES bgp_speaker (id) ON DELETE SET NULL,
|
|
neighbor INET NOT NULL,
|
|
remote_asn BIGINT NOT NULL,
|
|
enabled BOOLEAN NOT NULL DEFAULT true,
|
|
policies_json JSONB NOT NULL DEFAULT '{}',
|
|
meta_json JSONB NOT NULL DEFAULT '{}',
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
);
|
|
|
|
CREATE INDEX idx_bgp_peer_tenant ON bgp_peer (tenant_id);
|
|
CREATE INDEX idx_bgp_peer_speaker ON bgp_peer (bgp_speaker_id);
|
|
|
|
CREATE TABLE revision_materialized_prefix (
|
|
id BIGSERIAL PRIMARY KEY,
|
|
revision_id UUID NOT NULL REFERENCES config_revision (id) ON DELETE CASCADE,
|
|
prefix CIDR NOT NULL,
|
|
community_id UUID REFERENCES bgp_community (id) ON DELETE SET NULL,
|
|
source TEXT NOT NULL DEFAULT '',
|
|
meta_json JSONB NOT NULL DEFAULT '{}'
|
|
);
|
|
|
|
CREATE INDEX idx_rev_mat_prefix_revision ON revision_materialized_prefix (revision_id);
|
|
CREATE INDEX idx_rev_mat_prefix_value ON revision_materialized_prefix (prefix);
|
|
|
|
CREATE TABLE job_audit (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id UUID NOT NULL REFERENCES tenant (id) ON DELETE CASCADE,
|
|
kind TEXT NOT NULL,
|
|
status TEXT NOT NULL,
|
|
idempotency_key TEXT,
|
|
module_id UUID REFERENCES module (id) ON DELETE SET NULL,
|
|
error_message TEXT,
|
|
progress_pct SMALLINT,
|
|
meta_json JSONB NOT NULL DEFAULT '{}',
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
started_at TIMESTAMPTZ,
|
|
finished_at TIMESTAMPTZ,
|
|
CONSTRAINT job_audit_status_chk CHECK (status IN (
|
|
'queued', 'running', 'succeeded', 'failed', 'cancelled'
|
|
)),
|
|
CONSTRAINT job_audit_kind_chk CHECK (length(trim(kind)) > 0)
|
|
);
|
|
|
|
CREATE INDEX idx_job_audit_tenant_created ON job_audit (tenant_id, created_at DESC);
|
|
CREATE INDEX idx_job_audit_tenant_status ON job_audit (tenant_id, status);
|
|
CREATE UNIQUE INDEX idx_job_audit_idempotency
|
|
ON job_audit (tenant_id, idempotency_key)
|
|
WHERE idempotency_key IS NOT NULL;
|
|
|
|
CREATE TABLE global_settings (
|
|
tenant_id UUID NOT NULL REFERENCES tenant (id) ON DELETE CASCADE,
|
|
key TEXT NOT NULL,
|
|
value_json JSONB NOT NULL DEFAULT '{}',
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
PRIMARY KEY (tenant_id, key),
|
|
CONSTRAINT global_settings_key_chk CHECK (length(trim(key)) > 0)
|
|
);
|
|
|
|
-- Optional CDN fetch audit (plan §5.4).
|
|
CREATE TABLE module_cdn_fetch_log (
|
|
id BIGSERIAL PRIMARY KEY,
|
|
module_id UUID NOT NULL REFERENCES module (id) ON DELETE CASCADE,
|
|
source_id UUID REFERENCES module_cdn_source (id) ON DELETE SET NULL,
|
|
http_status INTEGER,
|
|
bytes BIGINT,
|
|
error TEXT,
|
|
fetched_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
);
|
|
|
|
CREATE INDEX idx_module_cdn_fetch_log_module ON module_cdn_fetch_log (module_id, fetched_at DESC);
|