- 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)
75 lines
2.6 KiB
PL/PgSQL
75 lines
2.6 KiB
PL/PgSQL
-- ============================================================
|
|
-- Audit Trigger Migration — Run on production after 001
|
|
-- Adds per-field audit triggers to configuration_items
|
|
-- ============================================================
|
|
BEGIN;
|
|
|
|
-- Fine-grained audit: capture individual field changes
|
|
CREATE OR REPLACE FUNCTION audit_ci_fields_func()
|
|
RETURNS TRIGGER AS $$
|
|
DECLARE
|
|
col TEXT;
|
|
old_val TEXT;
|
|
new_val TEXT;
|
|
BEGIN
|
|
IF TG_OP = 'UPDATE' THEN
|
|
FOR col IN SELECT unnest(ARRAY[
|
|
'name', 'description', 'status', 'location_id',
|
|
'serial_number', 'asset_tag', 'purchase_date', 'warranty_expiry',
|
|
'tags'
|
|
])
|
|
LOOP
|
|
old_val := row_to_json(OLD) ->> col;
|
|
new_val := row_to_json(NEW) ->> col;
|
|
IF old_val IS DISTINCT FROM new_val THEN
|
|
INSERT INTO changelog (ci_id, action, field_name, old_value, new_value, version, snapshot)
|
|
VALUES (NEW.id, 'update', col, old_val, NEW.version, NEW.version, to_jsonb(NEW));
|
|
END IF;
|
|
END LOOP;
|
|
|
|
-- JSONB attributes changes
|
|
IF OLD.attributes IS DISTINCT FROM NEW.attributes THEN
|
|
INSERT INTO changelog (ci_id, action, field_name, old_value, new_value, version, snapshot)
|
|
VALUES (NEW.id, 'update', 'attributes', OLD.attributes::text, NEW.attributes::text, NEW.version, to_jsonb(NEW));
|
|
END IF;
|
|
|
|
NEW.updated_at = now();
|
|
NEW.version = OLD.version + 1;
|
|
RETURN NEW;
|
|
END IF;
|
|
RETURN NULL;
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
|
|
DROP TRIGGER IF EXISTS trg_ci_audit ON configuration_items;
|
|
CREATE TRIGGER trg_ci_field_audit
|
|
AFTER UPDATE ON configuration_items
|
|
FOR EACH ROW
|
|
EXECUTE FUNCTION audit_ci_fields_func();
|
|
|
|
-- Relationship audit
|
|
CREATE OR REPLACE FUNCTION audit_relationship_func()
|
|
RETURNS TRIGGER AS $$
|
|
BEGIN
|
|
IF TG_OP = 'INSERT' THEN
|
|
INSERT INTO changelog (ci_id, action, field_name, new_value, version, snapshot)
|
|
VALUES (NEW.source_ci_id, 'relationship_add', NEW.relationship::text,
|
|
NEW.target_ci_id::text, 0, to_jsonb(NEW));
|
|
RETURN NEW;
|
|
ELSIF TG_OP = 'DELETE' THEN
|
|
INSERT INTO changelog (ci_id, action, field_name, old_value, version, snapshot)
|
|
VALUES (OLD.source_ci_id, 'relationship_remove', OLD.relationship::text,
|
|
OLD.target_ci_id::text, 0, to_jsonb(OLD));
|
|
RETURN OLD;
|
|
END IF;
|
|
RETURN NULL;
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
|
|
CREATE TRIGGER trg_relationship_audit
|
|
AFTER INSERT OR DELETE ON ci_relationships
|
|
FOR EACH ROW
|
|
EXECUTE FUNCTION audit_relationship_func();
|
|
|
|
COMMIT;
|