215 lines
11 KiB
SQL
Executable File
215 lines
11 KiB
SQL
Executable File
-- ITSM-Plattform: PostgreSQL-Schema
|
|
-- Mandantenfaehig (mehrere Organisationen auf einer Instanz, Daten per tenant_id getrennt).
|
|
-- Enthaelt audit_log fuer Nachvollziehbarkeit gemaess ISO 27001 A.12.4 / DSGVO Art. 30.
|
|
|
|
CREATE TABLE IF NOT EXISTS tenants (
|
|
id SERIAL PRIMARY KEY,
|
|
name TEXT NOT NULL,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
dsb_name TEXT, -- Datenschutzbeauftragter/Kontakt
|
|
dsb_email TEXT,
|
|
retention_tickets_days INTEGER NOT NULL DEFAULT 1095, -- DSGVO Speicherbegrenzung (Default ~3 Jahre)
|
|
retention_audit_days INTEGER NOT NULL DEFAULT 1825, -- Audit-Log-Aufbewahrung (Default ~5 Jahre)
|
|
sla_antwort_minuten INTEGER NOT NULL DEFAULT 480,
|
|
sla_loesung_minuten INTEGER NOT NULL DEFAULT 2880
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS users (
|
|
id SERIAL PRIMARY KEY,
|
|
tenant_id INTEGER NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
|
|
email TEXT NOT NULL UNIQUE,
|
|
password_hash TEXT NOT NULL,
|
|
role TEXT NOT NULL DEFAULT 'agent', -- 'admin' | 'agent'
|
|
active BOOLEAN NOT NULL DEFAULT TRUE,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
last_login_at TIMESTAMPTZ
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS services (
|
|
id SERIAL PRIMARY KEY,
|
|
tenant_id INTEGER NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
|
|
name TEXT NOT NULL,
|
|
beschreibung TEXT,
|
|
kategorie TEXT,
|
|
gebucht BOOLEAN NOT NULL DEFAULT TRUE,
|
|
endpoint TEXT,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS tickets (
|
|
id SERIAL PRIMARY KEY,
|
|
tenant_id INTEGER NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
|
|
ticket_nr TEXT NOT NULL,
|
|
titel TEXT NOT NULL,
|
|
beschreibung TEXT,
|
|
service_id INTEGER REFERENCES services(id) ON DELETE SET NULL,
|
|
status TEXT NOT NULL DEFAULT 'Offen',
|
|
prioritaet TEXT NOT NULL DEFAULT 'Mittel',
|
|
kategorie TEXT NOT NULL DEFAULT 'Task',
|
|
zugewiesen_an TEXT,
|
|
ersteller_id INTEGER REFERENCES users(id) ON DELETE SET NULL,
|
|
fortschritt INTEGER NOT NULL DEFAULT 0,
|
|
sla_antwort_minuten INTEGER,
|
|
sla_loesung_minuten INTEGER,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
UNIQUE (tenant_id, ticket_nr)
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS ticket_timeline (
|
|
id SERIAL PRIMARY KEY,
|
|
ticket_id INTEGER NOT NULL REFERENCES tickets(id) ON DELETE CASCADE,
|
|
tenant_id INTEGER NOT NULL,
|
|
zeit TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
akteur TEXT,
|
|
text TEXT
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS audit_log (
|
|
id SERIAL PRIMARY KEY,
|
|
tenant_id INTEGER REFERENCES tenants(id) ON DELETE CASCADE,
|
|
user_id INTEGER REFERENCES users(id) ON DELETE SET NULL,
|
|
zeit TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
aktion TEXT NOT NULL,
|
|
entity_typ TEXT,
|
|
entity_id TEXT,
|
|
details JSONB,
|
|
ip TEXT
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_tickets_tenant_status ON tickets (tenant_id, status);
|
|
CREATE INDEX IF NOT EXISTS idx_tickets_tenant_kategorie ON tickets (tenant_id, kategorie);
|
|
CREATE INDEX IF NOT EXISTS idx_timeline_ticket ON ticket_timeline (ticket_id);
|
|
CREATE INDEX IF NOT EXISTS idx_audit_tenant_zeit ON audit_log (tenant_id, zeit DESC);
|
|
CREATE INDEX IF NOT EXISTS idx_services_tenant ON services (tenant_id);
|
|
CREATE INDEX IF NOT EXISTS idx_users_tenant ON users (tenant_id);
|
|
|
|
-- Vorbereitung fuer spaetere AD/LDAP-Anbindung (Nutzer-Vorgabe 2026-07-13):
|
|
-- 'local' = im ITSM selbst verwaltetes Konto (heutiger Stand), 'ldap' reserviert
|
|
-- fuer die kommende Ausbaustufe. Kein Verhaltensunterschied heute, nur Vorbereitung.
|
|
ALTER TABLE users ADD COLUMN IF NOT EXISTS auth_source TEXT NOT NULL DEFAULT 'local';
|
|
|
|
-- Zusaetzliche Nutzer-Details (Nutzer-Vorgabe 2026-07-13: "Mehr Details fuer
|
|
-- die User (Telefon, Adresse, etc.)"). Alle optional, da nicht jeder Mandant
|
|
-- diese Angaben pflegen moechte.
|
|
ALTER TABLE users ADD COLUMN IF NOT EXISTS vorname TEXT;
|
|
ALTER TABLE users ADD COLUMN IF NOT EXISTS nachname TEXT;
|
|
ALTER TABLE users ADD COLUMN IF NOT EXISTS telefon TEXT;
|
|
ALTER TABLE users ADD COLUMN IF NOT EXISTS abteilung TEXT;
|
|
ALTER TABLE users ADD COLUMN IF NOT EXISTS adresse TEXT;
|
|
|
|
-- ── Wissensdatenbank ──────────────────────────────────────────────────────────
|
|
CREATE TABLE IF NOT EXISTS kb_articles (
|
|
id SERIAL PRIMARY KEY,
|
|
tenant_id INTEGER NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
|
|
titel TEXT NOT NULL,
|
|
kategorie TEXT NOT NULL DEFAULT 'Allgemein',
|
|
inhalt TEXT NOT NULL,
|
|
tags TEXT,
|
|
autor_user_id INTEGER REFERENCES users(id) ON DELETE SET NULL,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_kb_tenant_kategorie ON kb_articles (tenant_id, kategorie);
|
|
|
|
-- ── CMDB (Configuration Management Database) ──────────────────────────────────
|
|
CREATE TABLE IF NOT EXISTS configuration_items (
|
|
id SERIAL PRIMARY KEY,
|
|
tenant_id INTEGER NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
|
|
name TEXT NOT NULL,
|
|
ci_typ TEXT NOT NULL DEFAULT 'Sonstiges',
|
|
status TEXT NOT NULL DEFAULT 'Aktiv',
|
|
beschreibung TEXT,
|
|
attribute JSONB NOT NULL DEFAULT '{}'::jsonb,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_ci_tenant_typ ON configuration_items (tenant_id, ci_typ);
|
|
CREATE INDEX IF NOT EXISTS idx_ci_tenant_status ON configuration_items (tenant_id, status);
|
|
|
|
CREATE TABLE IF NOT EXISTS ci_relationships (
|
|
id SERIAL PRIMARY KEY,
|
|
tenant_id INTEGER NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
|
|
from_ci_id INTEGER NOT NULL REFERENCES configuration_items(id) ON DELETE CASCADE,
|
|
to_ci_id INTEGER NOT NULL REFERENCES configuration_items(id) ON DELETE CASCADE,
|
|
beziehungs_typ TEXT NOT NULL DEFAULT 'haengt ab von',
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_cirel_tenant ON ci_relationships (tenant_id);
|
|
CREATE INDEX IF NOT EXISTS idx_cirel_from ON ci_relationships (from_ci_id);
|
|
CREATE INDEX IF NOT EXISTS idx_cirel_to ON ci_relationships (to_ci_id);
|
|
|
|
|
|
-- ═══════════════════════════════════════════════════════════════════════════════
|
|
-- Migration 2026-07-15: ITIL-v3/v4-Ausrichtung + Sicherheits-Haertung (Rust-Rewrite)
|
|
-- Alle Aenderungen idempotent, damit init beim App-Start gefahrlos wiederholt laeuft.
|
|
-- ═══════════════════════════════════════════════════════════════════════════════
|
|
|
|
-- ── Rollen (ITIL-orientiert) ──────────────────────────────────────────────────
|
|
-- 'admin' (Service Owner), 'change_manager' (Change Enablement/CAB),
|
|
-- 'agent' (Service Desk), 'user' (Requester/Self-Service).
|
|
-- users.role existiert bereits als TEXT; erlaubte Werte prueft die App.
|
|
|
|
-- ── Tickets: Impact/Urgency-Prioritaetsmatrix (ITIL v3, SO 4.2.5.4) ──────────
|
|
ALTER TABLE tickets ADD COLUMN IF NOT EXISTS impact TEXT NOT NULL DEFAULT 'Mittel';
|
|
ALTER TABLE tickets ADD COLUMN IF NOT EXISTS urgency TEXT NOT NULL DEFAULT 'Mittel';
|
|
|
|
-- ── Tickets: Change Enablement (v4) / Change Management (v3) ──────────────────
|
|
ALTER TABLE tickets ADD COLUMN IF NOT EXISTS change_typ TEXT;
|
|
ALTER TABLE tickets ADD COLUMN IF NOT EXISTS approval_status TEXT;
|
|
ALTER TABLE tickets ADD COLUMN IF NOT EXISTS approved_by INTEGER REFERENCES users(id) ON DELETE SET NULL;
|
|
ALTER TABLE tickets ADD COLUMN IF NOT EXISTS approved_at TIMESTAMPTZ;
|
|
|
|
-- ── Tickets: Problem Management (v3 SO 4.4 / v4 Practice) ─────────────────────
|
|
ALTER TABLE tickets ADD COLUMN IF NOT EXISTS problem_id INTEGER REFERENCES tickets(id) ON DELETE SET NULL;
|
|
ALTER TABLE tickets ADD COLUMN IF NOT EXISTS known_error BOOLEAN NOT NULL DEFAULT FALSE;
|
|
CREATE INDEX IF NOT EXISTS idx_tickets_problem ON tickets (problem_id);
|
|
|
|
-- ── Tickets: SLA-Zeitstempel (Service Level Management) ───────────────────────
|
|
ALTER TABLE tickets ADD COLUMN IF NOT EXISTS first_response_at TIMESTAMPTZ;
|
|
ALTER TABLE tickets ADD COLUMN IF NOT EXISTS resolved_at TIMESTAMPTZ;
|
|
ALTER TABLE tickets ADD COLUMN IF NOT EXISTS closed_at TIMESTAMPTZ;
|
|
|
|
-- ── Ticket-CI-Verknuepfung (SACM v3 / Service Configuration Management v4) ────
|
|
CREATE TABLE IF NOT EXISTS ticket_ci_links (
|
|
id SERIAL PRIMARY KEY,
|
|
tenant_id INTEGER NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
|
|
ticket_id INTEGER NOT NULL REFERENCES tickets(id) ON DELETE CASCADE,
|
|
ci_id INTEGER NOT NULL REFERENCES configuration_items(id) ON DELETE CASCADE,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
UNIQUE (ticket_id, ci_id)
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_tcl_ticket ON ticket_ci_links (ticket_id);
|
|
CREATE INDEX IF NOT EXISTS idx_tcl_ci ON ticket_ci_links (ci_id);
|
|
|
|
-- ── Wissensdatenbank: Freigabe-Workflow (Knowledge Management) ────────────────
|
|
-- Bestehende Artikel werden einmalig 'Freigegeben'; neue starten als 'Entwurf'.
|
|
ALTER TABLE kb_articles ADD COLUMN IF NOT EXISTS status TEXT;
|
|
UPDATE kb_articles SET status='Freigegeben' WHERE status IS NULL;
|
|
ALTER TABLE kb_articles ALTER COLUMN status SET DEFAULT 'Entwurf';
|
|
ALTER TABLE kb_articles ALTER COLUMN status SET NOT NULL;
|
|
|
|
-- ── Login-Rate-Limiting (Brute-Force-Schutz, DB-basiert => multi-worker-fest) ──
|
|
CREATE TABLE IF NOT EXISTS login_attempts (
|
|
id SERIAL PRIMARY KEY,
|
|
email TEXT NOT NULL,
|
|
ip TEXT,
|
|
zeit TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
success BOOLEAN NOT NULL DEFAULT FALSE
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_login_attempts_email_zeit ON login_attempts (email, zeit DESC);
|
|
CREATE INDEX IF NOT EXISTS idx_login_attempts_ip_zeit ON login_attempts (ip, zeit DESC);
|
|
|
|
-- ── Server-seitige Sessions (Rust-Rewrite 2026-07-15) ─────────────────────────
|
|
-- In der DB liegt nur der SHA-256-Hash des Cookie-Tokens: ein DB-Leak
|
|
-- kompromittiert keine laufenden Sessions; Sessions sind widerrufbar.
|
|
CREATE TABLE IF NOT EXISTS sessions (
|
|
token_hash TEXT PRIMARY KEY,
|
|
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
csrf_token TEXT NOT NULL,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
expires_at TIMESTAMPTZ NOT NULL
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_sessions_user ON sessions (user_id);
|
|
CREATE INDEX IF NOT EXISTS idx_sessions_expires ON sessions (expires_at);
|