-- Kundencenter Grundschema (MariaDB). utf8mb4_unicode_ci. CREATE TABLE settings ( `key` VARCHAR(100) PRIMARY KEY, `value` VARCHAR(1000) NOT NULL, updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) ); INSERT INTO settings (`key`,`value`) VALUES ('registration_enabled','false'); CREATE TABLE users ( id CHAR(36) PRIMARY KEY, email VARCHAR(254) NOT NULL UNIQUE, name VARCHAR(150) NOT NULL, password_hash VARCHAR(255) NULL, kind ENUM('customer','staff') NOT NULL DEFAULT 'customer', staff_role ENUM('support','accounting','admin','superadmin') NULL, status ENUM('active','disabled','invited') NOT NULL DEFAULT 'invited', email_verified_at DATETIME(3) NULL, failed_logins INT NOT NULL DEFAULT 0, locked_until DATETIME(3) NULL, locale VARCHAR(10) NOT NULL DEFAULT 'de', created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3), CHECK ((kind = 'staff' AND staff_role IS NOT NULL) OR (kind = 'customer' AND staff_role IS NULL)) ); CREATE TABLE sessions ( id CHAR(36) PRIMARY KEY, user_id CHAR(36) NOT NULL, token_hash CHAR(64) NOT NULL UNIQUE, csrf_token CHAR(43) NOT NULL, mfa_verified TINYINT(1) NOT NULL DEFAULT 0, pending_mfa TINYINT(1) NOT NULL DEFAULT 0, ip VARCHAR(45) NULL, user_agent VARCHAR(255) NULL, created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), last_seen_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), expires_at DATETIME(3) NOT NULL, revoked_at DATETIME(3) NULL, FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, INDEX (user_id) ); CREATE TABLE mfa_totp ( user_id CHAR(36) PRIMARY KEY, secret_enc VARCHAR(512) NOT NULL, confirmed_at DATETIME(3) NULL, last_step BIGINT NULL, FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE ); CREATE TABLE recovery_codes ( id BIGINT AUTO_INCREMENT PRIMARY KEY, user_id CHAR(36) NOT NULL, code_hash CHAR(64) NOT NULL, used_at DATETIME(3) NULL, FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, INDEX (user_id) ); CREATE TABLE organizations ( id CHAR(36) PRIMARY KEY, customer_number VARCHAR(20) NOT NULL UNIQUE, name VARCHAR(200) NOT NULL, status ENUM('active','suspended','closed') NOT NULL DEFAULT 'active', created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) ); CREATE TABLE customer_sequences (id TINYINT PRIMARY KEY, next_value INT NOT NULL); INSERT INTO customer_sequences VALUES (1, 10000); CREATE TABLE billing_profiles ( org_id CHAR(36) PRIMARY KEY, company VARCHAR(200) NULL, contact_name VARCHAR(150) NULL, street VARCHAR(200) NULL, zip VARCHAR(20) NULL, city VARCHAR(100) NULL, country CHAR(2) NOT NULL DEFAULT 'DE', vat_id VARCHAR(30) NULL, billing_email VARCHAR(254) NULL, phone VARCHAR(50) NULL, FOREIGN KEY (org_id) REFERENCES organizations(id) ON DELETE CASCADE ); CREATE TABLE memberships ( org_id CHAR(36) NOT NULL, user_id CHAR(36) NOT NULL, role ENUM('owner','admin','member') NOT NULL DEFAULT 'member', created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), PRIMARY KEY (org_id, user_id), FOREIGN KEY (org_id) REFERENCES organizations(id) ON DELETE CASCADE, FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE ); -- Einmal-Token: Einladung, Passwort-Reset, E-Mail-Verifizierung CREATE TABLE user_tokens ( id CHAR(36) PRIMARY KEY, user_id CHAR(36) NOT NULL, purpose ENUM('invite','password_reset','verify_email') NOT NULL, token_hash CHAR(64) NOT NULL UNIQUE, expires_at DATETIME(3) NOT NULL, used_at DATETIME(3) NULL, created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE ); CREATE TABLE audit_events ( id BIGINT AUTO_INCREMENT PRIMARY KEY, ts DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), actor_type ENUM('user','system','anonymous') NOT NULL, actor_id CHAR(36) NULL, org_id CHAR(36) NULL, action VARCHAR(100) NOT NULL, resource_type VARCHAR(50) NULL, resource_id VARCHAR(64) NULL, result ENUM('success','denied','failure') NOT NULL, error_class VARCHAR(100) NULL, connector VARCHAR(50) NULL, correlation_id CHAR(36) NULL, ip VARCHAR(45) NULL, before_json JSON NULL, after_json JSON NULL, prev_hash CHAR(64) NOT NULL, hash CHAR(64) NOT NULL, INDEX (ts), INDEX (actor_id), INDEX (org_id), INDEX (action) ); -- Persistente Aufträge (Outbox), Basis für Worker/Discord; später Provider-Jobs CREATE TABLE jobs ( id CHAR(36) PRIMARY KEY, type VARCHAR(100) NOT NULL, payload JSON NOT NULL, idempotency_key VARCHAR(150) NULL UNIQUE, status ENUM('scheduled','running','waiting_external','succeeded','partial','failed','retrying','needs_review') NOT NULL DEFAULT 'scheduled', attempts INT NOT NULL DEFAULT 0, max_attempts INT NOT NULL DEFAULT 5, run_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), locked_at DATETIME(3) NULL, last_error VARCHAR(500) NULL, correlation_id CHAR(36) NULL, created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3), INDEX (status, run_at) ); CREATE TABLE mail_log ( id BIGINT AUTO_INCREMENT PRIMARY KEY, to_email VARCHAR(254) NOT NULL, template VARCHAR(60) NOT NULL, status ENUM('sent','failed','not_configured') NOT NULL, error VARCHAR(300) NULL, created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) );