Files
----/backend/migrations/001_initial_schema.sql
smolkik-code 9b15f8b09c feat: CMDB full-stack app - FastAPI + PostgreSQL + React
- PostgreSQL schema: 14 tables, JSONB attributes, audit triggers, soft delete
- FastAPI backend: CRUD, search/filter, relationship graph, bulk import, JWT RBAC
- React frontend: CI table, detail card, force-graph, dashboard
- Seed data: homelab scenario (Proxmox, Mikrotik, VMs, services)
- Docker Compose + Kubernetes manifests
- 20 backend tests (pytest + httpx)
2026-06-25 13:01:40 +07:00

388 lines
15 KiB
PL/PgSQL

-- ============================================================
-- CMDB Schema v1.0 — PostgreSQL
-- ============================================================
-- Strategy: soft delete everywhere, audit trail via triggers,
-- JSONB for extensible attributes, full indexing.
-- ============================================================
BEGIN;
-- -----------------------------------------------------------
-- 0. Extensions
-- -----------------------------------------------------------
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
CREATE EXTENSION IF NOT EXISTS "pgcrypto";
-- -----------------------------------------------------------
-- 1. Custom ENUM types
-- -----------------------------------------------------------
CREATE TYPE ci_status AS ENUM (
'active', 'inactive', 'maintenance', 'deprecated', 'planned'
);
CREATE TYPE relationship_type AS ENUM (
'depends_on', 'connected_to', 'hosted_on', 'runs_on',
'manages', 'contains', 'part_of', 'related_to'
);
CREATE TYPE change_action AS ENUM (
'create', 'update', 'delete', 'restore', 'relationship_add', 'relationship_remove'
);
CREATE TYPE user_role AS ENUM ('admin', 'editor', 'viewer');
-- -----------------------------------------------------------
-- 2. Locations
-- -----------------------------------------------------------
CREATE TABLE locations (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
name TEXT NOT NULL UNIQUE,
description TEXT,
parent_id UUID REFERENCES locations(id) ON DELETE SET NULL,
metadata JSONB DEFAULT '{}',
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
deleted_at TIMESTAMPTZ
);
CREATE INDEX idx_locations_parent ON locations(parent_id) WHERE deleted_at IS NULL;
CREATE INDEX idx_locations_metadata ON locations USING gin(metadata);
-- -----------------------------------------------------------
-- 3. Users / Teams (owners)
-- -----------------------------------------------------------
CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
username TEXT NOT NULL UNIQUE,
email TEXT NOT NULL UNIQUE,
full_name TEXT,
role user_role NOT NULL DEFAULT 'viewer',
team TEXT,
is_active BOOLEAN NOT NULL DEFAULT true,
password_hash TEXT NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
deleted_at TIMESTAMPTZ
);
CREATE INDEX idx_users_role ON users(role) WHERE deleted_at IS NULL;
CREATE INDEX idx_users_team ON users(team) WHERE deleted_at IS NULL AND team IS NOT NULL;
-- -----------------------------------------------------------
-- 4. CI Classes (taxonomy)
-- -----------------------------------------------------------
CREATE TABLE ci_classes (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
name TEXT NOT NULL UNIQUE, -- e.g. 'Server', 'NetworkDevice', 'Service'
description TEXT,
parent_id UUID REFERENCES ci_classes(id) ON DELETE SET NULL,
icon TEXT, -- optional icon name for UI
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
deleted_at TIMESTAMPTZ
);
CREATE INDEX idx_ci_classes_parent ON ci_classes(parent_id) WHERE deleted_at IS NULL;
-- -----------------------------------------------------------
-- 5. CI Types (subtypes within a class)
-- -----------------------------------------------------------
CREATE TABLE ci_types (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
class_id UUID NOT NULL REFERENCES ci_classes(id) ON DELETE CASCADE,
name TEXT NOT NULL, -- e.g. 'PhysicalServer', 'VirtualMachine', 'Switch'
description TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
deleted_at TIMESTAMPTZ,
UNIQUE(class_id, name)
);
CREATE INDEX idx_ci_types_class ON ci_types(class_id) WHERE deleted_at IS NULL;
-- -----------------------------------------------------------
-- 6. Attribute definitions (template per class)
-- -----------------------------------------------------------
CREATE TABLE attributes (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
ci_class_id UUID NOT NULL REFERENCES ci_classes(id) ON DELETE CASCADE,
name TEXT NOT NULL, -- e.g. 'cpu_cores', 'ram_gb'
label TEXT NOT NULL, -- display label
data_type TEXT NOT NULL DEFAULT 'text', -- text, integer, float, boolean, date, ip, json
is_required BOOLEAN NOT NULL DEFAULT false,
is_unique BOOLEAN NOT NULL DEFAULT false,
default_value TEXT,
validation_regex TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
deleted_at TIMESTAMPTZ,
UNIQUE(ci_class_id, name)
);
CREATE INDEX idx_attributes_class ON attributes(ci_class_id) WHERE deleted_at IS NULL;
-- -----------------------------------------------------------
-- 7. Owners (many-to-many: CI ↔ User)
-- -----------------------------------------------------------
CREATE TABLE owners (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
ci_id UUID NOT NULL,
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
role TEXT NOT NULL DEFAULT 'owner', -- owner, admin, responsible
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
UNIQUE(ci_id, user_id, role)
);
-- -----------------------------------------------------------
-- 8. Configuration Items (the core entity)
-- -----------------------------------------------------------
CREATE TABLE configuration_items (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
ci_type_id UUID NOT NULL REFERENCES ci_types(id) ON DELETE RESTRICT,
name TEXT NOT NULL,
description TEXT,
status ci_status NOT NULL DEFAULT 'active',
location_id UUID REFERENCES locations(id) ON DELETE SET NULL,
serial_number TEXT,
asset_tag TEXT,
purchase_date DATE,
warranty_expiry DATE,
attributes JSONB DEFAULT '{}', -- extensible key-value store
tags TEXT[] DEFAULT '{}',
created_by UUID REFERENCES users(id),
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
deleted_at TIMESTAMPTZ,
version INTEGER NOT NULL DEFAULT 1
);
CREATE INDEX idx_ci_type ON configuration_items(ci_type_id) WHERE deleted_at IS NULL;
CREATE INDEX idx_ci_status ON configuration_items(status) WHERE deleted_at IS NULL;
CREATE INDEX idx_ci_location ON configuration_items(location_id) WHERE deleted_at IS NULL;
CREATE INDEX idx_ci_name ON configuration_items(name) WHERE deleted_at IS NULL;
CREATE INDEX idx_ci_serial ON configuration_items(serial_number) WHERE deleted_at IS NULL AND serial_number IS NOT NULL;
CREATE INDEX idx_ci_asset_tag ON configuration_items(asset_tag) WHERE deleted_at IS NULL AND asset_tag IS NOT NULL;
CREATE INDEX idx_ci_tags ON configuration_items USING gin(tags) WHERE deleted_at IS NULL;
CREATE INDEX idx_ci_attributes ON configuration_items USING gin(attributes) WHERE deleted_at IS NULL;
-- Unique constraint on name + type for active items
CREATE UNIQUE INDEX idx_ci_name_type_active
ON configuration_items(name, ci_type_id)
WHERE deleted_at IS NULL;
-- -----------------------------------------------------------
-- 9. IP Addresses
-- -----------------------------------------------------------
CREATE TABLE ip_addresses (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
ci_id UUID NOT NULL REFERENCES configuration_items(id) ON DELETE CASCADE,
ip_address INET NOT NULL,
subnet_mask INET,
gateway INET,
dns_servers INET[],
is_primary BOOLEAN NOT NULL DEFAULT false,
vlan_id INTEGER,
dhcp_enabled BOOLEAN DEFAULT false,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
deleted_at TIMESTAMPTZ
);
CREATE INDEX idx_ip_address ON ip_addresses(ip_address) WHERE deleted_at IS NULL;
CREATE INDEX idx_ip_ci ON ip_addresses(ci_id) WHERE deleted_at IS NULL;
-- -----------------------------------------------------------
-- 10. Network Interfaces
-- -----------------------------------------------------------
CREATE TABLE network_interfaces (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
ci_id UUID NOT NULL REFERENCES configuration_items(id) ON DELETE CASCADE,
name TEXT NOT NULL, -- eth0, ens192, bond0
mac_address MACADDR,
speed_mbps INTEGER,
interface_type TEXT DEFAULT 'ethernet', -- ethernet, bond, bridge, vlan
is_up BOOLEAN DEFAULT true,
ip_address_id UUID REFERENCES ip_addresses(id) ON DELETE SET NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
deleted_at TIMESTAMPTZ
);
CREATE INDEX idx_nic_ci ON network_interfaces(ci_id) WHERE deleted_at IS NULL;
CREATE INDEX idx_nic_mac ON network_interfaces(mac_address) WHERE deleted_at IS NULL AND mac_address IS NOT NULL;
-- -----------------------------------------------------------
-- 11. Hardware Details
-- -----------------------------------------------------------
CREATE TABLE hardware_details (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
ci_id UUID NOT NULL REFERENCES configuration_items(id) ON DELETE CASCADE,
manufacturer TEXT,
model TEXT,
cpu_model TEXT,
cpu_cores INTEGER,
ram_gb NUMERIC(10,2),
storage_gb NUMERIC(10,2),
storage_type TEXT, -- SSD, HDD, NVMe
form_factor TEXT, -- 1U, 2U,塔式, 刀片
power_supply TEXT,
bios_version TEXT,
serial_number TEXT,
specs JSONB DEFAULT '{}', -- extra hardware specs
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
deleted_at TIMESTAMPTZ
);
CREATE UNIQUE INDEX idx_hw_ci ON hardware_details(ci_id) WHERE deleted_at IS NULL;
-- -----------------------------------------------------------
-- 12. Software Instances
-- -----------------------------------------------------------
CREATE TABLE software_instances (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
ci_id UUID NOT NULL REFERENCES configuration_items(id) ON DELETE CASCADE,
name TEXT NOT NULL,
vendor TEXT,
version TEXT,
license_key TEXT,
license_type TEXT, -- open_source, commercial, trial
install_path TEXT,
config_path TEXT,
port INTEGER,
protocol TEXT, -- tcp, udp
start_command TEXT,
service_user TEXT,
config JSONB DEFAULT '{}',
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
deleted_at TIMESTAMPTZ
);
CREATE INDEX idx_sw_ci ON software_instances(ci_id) WHERE deleted_at IS NULL;
CREATE INDEX idx_sw_name ON software_instances(name) WHERE deleted_at IS NULL;
-- -----------------------------------------------------------
-- 13. CI Relationships (graph edges)
-- -----------------------------------------------------------
CREATE TABLE ci_relationships (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
source_ci_id UUID NOT NULL REFERENCES configuration_items(id) ON DELETE CASCADE,
target_ci_id UUID NOT NULL REFERENCES configuration_items(id) ON DELETE CASCADE,
relationship relationship_type NOT NULL,
description TEXT,
metadata JSONB DEFAULT '{}',
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
deleted_at TIMESTAMPTZ,
UNIQUE(source_ci_id, target_ci_id, relationship)
);
CREATE INDEX idx_rel_source ON ci_relationships(source_ci_id) WHERE deleted_at IS NULL;
CREATE INDEX idx_rel_target ON ci_relationships(target_ci_id) WHERE deleted_at IS NULL;
CREATE INDEX idx_rel_type ON ci_relationships(relationship) WHERE deleted_at IS NULL;
-- -----------------------------------------------------------
-- 14. Change Log (audit trail / versioning)
-- -----------------------------------------------------------
CREATE TABLE changelog (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
ci_id UUID NOT NULL REFERENCES configuration_items(id) ON DELETE CASCADE,
action change_action NOT NULL,
changed_by UUID REFERENCES users(id),
field_name TEXT,
old_value TEXT,
new_value TEXT,
snapshot JSONB, -- full CI snapshot at time of change
version INTEGER NOT NULL,
comment TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX idx_changelog_ci ON changelog(ci_id);
CREATE INDEX idx_changelog_action ON changelog(action);
CREATE INDEX idx_changelog_created ON changelog(created_at);
CREATE INDEX idx_changelog_version ON changelog(ci_id, version);
-- -----------------------------------------------------------
-- 15. Audit Trigger Function
-- -----------------------------------------------------------
CREATE OR REPLACE FUNCTION audit_trigger_func()
RETURNS TRIGGER AS $$
BEGIN
IF TG_OP = 'UPDATE' THEN
INSERT INTO changelog (ci_id, action, field_name, old_value, new_value, version, snapshot)
VALUES (
NEW.id,
'update',
'general',
row_to_json(OLD)::text,
row_to_json(NEW)::text,
NEW.version,
to_jsonb(NEW)
);
NEW.updated_at = now();
NEW.version = OLD.version + 1;
RETURN NEW;
ELSIF TG_OP = 'DELETE' THEN
INSERT INTO changelog (ci_id, action, snapshot, version)
VALUES (
OLD.id,
'delete',
to_jsonb(OLD),
OLD.version
);
RETURN OLD;
END IF;
RETURN NULL;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER trg_ci_audit
AFTER UPDATE OR DELETE ON configuration_items
FOR EACH ROW
EXECUTE FUNCTION audit_trigger_func();
-- -----------------------------------------------------------
-- 16. Updated_at auto-trigger
-- -----------------------------------------------------------
CREATE OR REPLACE FUNCTION update_updated_at()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = now();
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER trg_locations_updated
BEFORE UPDATE ON locations
FOR EACH ROW EXECUTE FUNCTION update_updated_at();
CREATE TRIGGER trg_users_updated
BEFORE UPDATE ON users
FOR EACH ROW EXECUTE FUNCTION update_updated_at();
CREATE TRIGGER trg_ci_classes_updated
BEFORE UPDATE ON ci_classes
FOR EACH ROW EXECUTE FUNCTION update_updated_at();
CREATE TRIGGER trg_ci_types_updated
BEFORE UPDATE ON ci_types
FOR EACH ROW EXECUTE FUNCTION update_updated_at();
CREATE TRIGGER trg_ip_updated
BEFORE UPDATE ON ip_addresses
FOR EACH ROW EXECUTE FUNCTION update_updated_at();
CREATE TRIGGER trg_nic_updated
BEFORE UPDATE ON network_interfaces
FOR EACH ROW EXECUTE FUNCTION update_updated_at();
CREATE TRIGGER trg_hw_updated
BEFORE UPDATE ON hardware_details
FOR EACH ROW EXECUTE FUNCTION update_updated_at();
CREATE TRIGGER trg_sw_updated
BEFORE UPDATE ON software_instances
FOR EACH ROW EXECUTE FUNCTION update_updated_at();
COMMIT;