1
0
Files
breadcrumb-the-shire/db/init/init.sql
fs 149d4515de refactor(settings): remove global appearance settings — tenant is sole source
Removes the global app_theme, app_theme_user and app_primary_color settings
from the admin/settings area and the underlying service/cache/API/i18n/docs
layers. The tenant columns (tenants.primary_color, default_theme,
allow_user_theme) become the single source of truth for appearance.
Branding helpers are tenant-only and fall back to a hardcoded 'light' theme
with no brand color when no tenant context is available (login, error,
pre-session pages). The APP_THEME env var is removed.

Scope:
- UI: Appearance tab gone from /admin/settings; tenant edit form drops the
  global-fallback color preview.
- Service: SettingsAppGateway no longer exposes setting-backed accessors
  for theme/user-theme/primary-color. Theme catalog utilities (listThemes,
  isAllowedTheme, normalizeTheme, resolveDefaultTheme) stay and are used
  across Tenant / Auth / User flows; resolveDefaultTheme now hardcodes
  'light' with a catalog fallback (no global/env lookup).
- AdminSettingsService: buildPageData, sanitization, audit snapshot, apply
  step and cache payload are all stripped of the three keys. Dead helper
  applyAppPrimaryColor + normalizePrimaryColor removed.
- Cache: SettingCacheService HOT_PATH_KEYS drops the three keys.
- API: /settings (GET/PUT) and /settings/public (GET) no longer expose
  appearance fields; OpenAPI schemas pruned accordingly.
- Audit redaction list shortened.
- DB: db/init/init.sql seed rows removed. No migration for existing
  installs — legacy rows stay inert.
- i18n + docs: setting.app_theme, setting.app_theme_user,
  setting.app_primary_color removed from de+en locales; reference and
  explanation docs updated.
- Tests: covering tests for the removed methods pruned; ThemeResolutionTest
  rewritten for tenant-only semantics. One unrelated PHP-CS-Fixer issue in
  UserRegistrar.php cleaned up to keep QG-006 green.

Workflow: .agents/runs/SETTINGS-APPEARANCE-TENANT-ONLY/ (gitignored).
Gates: QG-001..003, QG-006, QG-008 all pass (1985 tests, 28764 assertions).
Reviews: code, security, acceptance all pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 22:40:15 +02:00

1750 lines
69 KiB
SQL

-- Consolidated schema (fresh install)
-- Generated: 2026-02-19
-- Includes all current tables, columns, indexes and seed data.
CREATE TABLE IF NOT EXISTS `users` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`uuid` CHAR(36) NOT NULL,
`first_name` VARCHAR(100) NOT NULL,
`last_name` VARCHAR(100) NOT NULL,
`display_name` VARCHAR(210) NOT NULL,
`email` VARCHAR(255) NOT NULL,
`profile_description` TEXT NULL,
`job_title` VARCHAR(160) NULL,
`phone` VARCHAR(50) NULL,
`mobile` VARCHAR(50) NULL,
`short_dial` VARCHAR(20) NULL,
`address` VARCHAR(255) NULL,
`postal_code` VARCHAR(20) NULL,
`city` VARCHAR(100) NULL,
`country` VARCHAR(100) NULL,
`region` VARCHAR(100) NULL,
`hire_date` DATE NULL,
`email_verified_at` DATETIME NULL DEFAULT NULL,
`last_login_at` DATETIME NULL DEFAULT NULL,
`last_login_provider` VARCHAR(20) NULL DEFAULT NULL,
`password` VARCHAR(255) NOT NULL,
`locale` VARCHAR(10) DEFAULT NULL,
`totp_secret` VARCHAR(255) DEFAULT NULL,
`theme` VARCHAR(10) NOT NULL DEFAULT 'light',
`primary_tenant_id` INT NULL,
`current_tenant_id` INT UNSIGNED NULL,
`created_by` INT UNSIGNED DEFAULT NULL,
`modified_by` INT UNSIGNED DEFAULT NULL,
`created` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
`modified` DATETIME NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
`active` TINYINT(1) NOT NULL DEFAULT 1,
`authz_version` INT UNSIGNED NOT NULL DEFAULT 1,
`active_changed_at` DATETIME NULL DEFAULT NULL,
`active_changed_by` INT UNSIGNED DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `uniq_users_uuid` (`uuid`),
UNIQUE KEY `uniq_users_email` (`email`),
KEY `idx_users_primary_tenant_id` (`primary_tenant_id`),
KEY `idx_users_current_tenant_id` (`current_tenant_id`),
KEY `idx_users_created_by` (`created_by`),
KEY `idx_users_modified_by` (`modified_by`),
KEY `idx_users_active_changed_by` (`active_changed_by`),
CONSTRAINT `fk_users_created_by` FOREIGN KEY (`created_by`) REFERENCES `users` (`id`) ON DELETE SET NULL,
CONSTRAINT `fk_users_modified_by` FOREIGN KEY (`modified_by`) REFERENCES `users` (`id`) ON DELETE SET NULL,
CONSTRAINT `fk_users_active_changed_by` FOREIGN KEY (`active_changed_by`) REFERENCES `users` (`id`) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `tenants` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`uuid` CHAR(36) NOT NULL,
`description` VARCHAR(255) NOT NULL,
`address` VARCHAR(255) NULL,
`postal_code` VARCHAR(20) NULL,
`city` VARCHAR(100) NULL,
`country` VARCHAR(100) NULL,
`region` VARCHAR(100) NULL,
`vat_id` VARCHAR(50) NULL,
`tax_number` VARCHAR(50) NULL,
`phone` VARCHAR(50) NULL,
`fax` VARCHAR(100) NULL,
`email` VARCHAR(190) NULL,
`support_email` VARCHAR(190) NULL,
`support_phone` VARCHAR(50) NULL,
`billing_email` VARCHAR(190) NULL,
`website` VARCHAR(255) NULL,
`privacy_url` VARCHAR(255) NULL,
`imprint_url` VARCHAR(255) NULL,
`primary_color` VARCHAR(7) NULL,
`default_theme` VARCHAR(32) NULL,
`allow_user_theme` TINYINT(1) NULL,
`status` VARCHAR(20) NOT NULL DEFAULT 'active',
`status_changed_at` DATETIME NULL DEFAULT NULL,
`status_changed_by` INT UNSIGNED DEFAULT NULL,
`created_by` INT UNSIGNED DEFAULT NULL,
`modified_by` INT UNSIGNED DEFAULT NULL,
`created` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
`modified` DATETIME NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uniq_tenants_uuid` (`uuid`),
KEY `idx_tenants_created_by` (`created_by`),
KEY `idx_tenants_modified_by` (`modified_by`),
KEY `idx_tenants_status_changed_by` (`status_changed_by`),
CONSTRAINT `chk_tenants_status` CHECK (`status` IN ('active', 'inactive')),
CONSTRAINT `fk_tenants_created_by` FOREIGN KEY (`created_by`) REFERENCES `users` (`id`) ON DELETE SET NULL,
CONSTRAINT `fk_tenants_modified_by` FOREIGN KEY (`modified_by`) REFERENCES `users` (`id`) ON DELETE SET NULL,
CONSTRAINT `fk_tenants_status_changed_by` FOREIGN KEY (`status_changed_by`) REFERENCES `users` (`id`) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
SET @fk_users_current_tenant_exists = (
SELECT COUNT(*)
FROM information_schema.TABLE_CONSTRAINTS
WHERE CONSTRAINT_SCHEMA = DATABASE()
AND TABLE_NAME = 'users'
AND CONSTRAINT_NAME = 'fk_users_current_tenant'
AND CONSTRAINT_TYPE = 'FOREIGN KEY'
);
SET @sql_add_fk_users_current_tenant = IF(
@fk_users_current_tenant_exists = 0,
'ALTER TABLE `users` ADD CONSTRAINT `fk_users_current_tenant` FOREIGN KEY (`current_tenant_id`) REFERENCES `tenants` (`id`) ON DELETE SET NULL',
'SELECT 1'
);
PREPARE stmt_add_fk_users_current_tenant FROM @sql_add_fk_users_current_tenant;
EXECUTE stmt_add_fk_users_current_tenant;
DEALLOCATE PREPARE stmt_add_fk_users_current_tenant;
CREATE TABLE IF NOT EXISTS `roles` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`uuid` CHAR(36) NOT NULL,
`description` VARCHAR(255) NOT NULL,
`code` VARCHAR(50) DEFAULT NULL,
`active` TINYINT(1) NOT NULL DEFAULT 1,
`created_by` INT UNSIGNED DEFAULT NULL,
`modified_by` INT UNSIGNED DEFAULT NULL,
`created` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
`modified` DATETIME NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uniq_roles_uuid` (`uuid`),
UNIQUE KEY `uniq_roles_code` (`code`),
KEY `idx_roles_active` (`active`),
KEY `idx_roles_created_by` (`created_by`),
KEY `idx_roles_modified_by` (`modified_by`),
CONSTRAINT `fk_roles_created_by` FOREIGN KEY (`created_by`) REFERENCES `users` (`id`) ON DELETE SET NULL,
CONSTRAINT `fk_roles_modified_by` FOREIGN KEY (`modified_by`) REFERENCES `users` (`id`) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `permissions` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`key` VARCHAR(191) NOT NULL,
`description` VARCHAR(255) DEFAULT NULL,
`active` TINYINT(1) NOT NULL DEFAULT 1,
`is_system` TINYINT(1) NOT NULL DEFAULT 0,
`created` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uniq_permissions_key` (`key`),
KEY `idx_permissions_active` (`active`),
KEY `idx_permissions_system` (`is_system`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `departments` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`uuid` CHAR(36) NOT NULL,
`description` VARCHAR(255) NOT NULL,
`tenant_id` INT UNSIGNED NOT NULL,
`code` VARCHAR(50) DEFAULT NULL,
`cost_center` VARCHAR(50) DEFAULT NULL,
`active` TINYINT(1) NOT NULL DEFAULT 1,
`created_by` INT UNSIGNED DEFAULT NULL,
`modified_by` INT UNSIGNED DEFAULT NULL,
`created` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
`modified` DATETIME NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uniq_departments_uuid` (`uuid`),
KEY `idx_departments_tenant` (`tenant_id`),
KEY `idx_departments_active` (`active`),
KEY `idx_departments_created_by` (`created_by`),
KEY `idx_departments_modified_by` (`modified_by`),
CONSTRAINT `fk_departments_tenant` FOREIGN KEY (`tenant_id`) REFERENCES `tenants` (`id`) ON DELETE RESTRICT,
CONSTRAINT `fk_departments_created_by` FOREIGN KEY (`created_by`) REFERENCES `users` (`id`) ON DELETE SET NULL,
CONSTRAINT `fk_departments_modified_by` FOREIGN KEY (`modified_by`) REFERENCES `users` (`id`) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `pages` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`uuid` CHAR(36) NOT NULL,
`slug` VARCHAR(190) NOT NULL,
`created_by` INT UNSIGNED NULL,
`modified_by` INT UNSIGNED NULL,
`created` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
`modified` DATETIME NULL DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `uniq_pages_uuid` (`uuid`),
UNIQUE KEY `uniq_pages_slug` (`slug`),
KEY `idx_pages_created_by` (`created_by`),
KEY `idx_pages_modified_by` (`modified_by`),
CONSTRAINT `fk_pages_created_by` FOREIGN KEY (`created_by`) REFERENCES `users` (`id`) ON DELETE SET NULL,
CONSTRAINT `fk_pages_modified_by` FOREIGN KEY (`modified_by`) REFERENCES `users` (`id`) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `page_contents` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`page_id` INT UNSIGNED NOT NULL,
`locale` VARCHAR(10) NOT NULL,
`content` LONGTEXT NULL,
`created_by` INT UNSIGNED NULL,
`modified_by` INT UNSIGNED NULL,
`created` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
`modified` DATETIME NULL DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `uniq_page_locale` (`page_id`, `locale`),
KEY `idx_page_id` (`page_id`),
KEY `idx_page_contents_created_by` (`created_by`),
KEY `idx_page_contents_modified_by` (`modified_by`),
CONSTRAINT `fk_page_contents_page` FOREIGN KEY (`page_id`) REFERENCES `pages` (`id`) ON DELETE CASCADE,
CONSTRAINT `fk_page_contents_created_by` FOREIGN KEY (`created_by`) REFERENCES `users` (`id`) ON DELETE SET NULL,
CONSTRAINT `fk_page_contents_modified_by` FOREIGN KEY (`modified_by`) REFERENCES `users` (`id`) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `settings` (
`key` VARCHAR(64) NOT NULL,
`value` TEXT NULL,
`description` VARCHAR(255) NULL,
`updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`key`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
CREATE TABLE IF NOT EXISTS `tenant_auth_microsoft` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`tenant_id` INT UNSIGNED NOT NULL,
`enabled` TINYINT(1) NOT NULL DEFAULT 0,
`enforce_microsoft_login` TINYINT(1) NOT NULL DEFAULT 0,
`sync_profile_on_login` TINYINT(1) NOT NULL DEFAULT 0,
`sync_profile_fields` TEXT NULL,
`entra_tenant_id` VARCHAR(64) NULL,
`allowed_domains` TEXT NULL,
`use_shared_app` TINYINT(1) NOT NULL DEFAULT 1,
`client_id_override` VARCHAR(191) NULL,
`client_secret_override_enc` TEXT NULL,
`auto_remember_mode` TINYINT(1) NULL DEFAULT NULL,
`created` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
`modified` DATETIME NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uniq_tenant_auth_microsoft_tenant` (`tenant_id`),
CONSTRAINT `fk_tenant_auth_microsoft_tenant` FOREIGN KEY (`tenant_id`) REFERENCES `tenants` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `tenant_auth_ldap` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`tenant_id` INT UNSIGNED NOT NULL,
`enabled` TINYINT(1) NOT NULL DEFAULT 0,
`enforce_ldap_login` TINYINT(1) NOT NULL DEFAULT 0,
`host` VARCHAR(255) NOT NULL DEFAULT '',
`port` INT UNSIGNED NOT NULL DEFAULT 389,
`encryption_mode` ENUM('none','starttls','ldaps') NOT NULL DEFAULT 'starttls',
`verify_tls_certificate` TINYINT(1) NOT NULL DEFAULT 1,
`base_dn` VARCHAR(500) NOT NULL DEFAULT '',
`bind_dn_enc` TEXT NULL,
`bind_password_enc` TEXT NULL,
`user_search_filter` VARCHAR(500) NOT NULL DEFAULT '(&(objectClass=user)(sAMAccountName=%s))',
`user_search_scope` ENUM('sub','one') NOT NULL DEFAULT 'sub',
`bind_method` ENUM('search_then_bind','direct_bind') NOT NULL DEFAULT 'search_then_bind',
`bind_username_format` VARCHAR(255) NULL,
`unique_id_attribute` VARCHAR(64) NOT NULL DEFAULT 'objectGUID',
`attribute_map` JSON NOT NULL DEFAULT '{"first_name":"givenName","last_name":"sn","email":"mail","phone":"telephoneNumber","mobile":"mobile"}',
`sync_profile_on_login` TINYINT(1) NOT NULL DEFAULT 0,
`sync_profile_fields` TEXT NULL,
`allowed_domains` TEXT NULL,
`auto_remember_mode` TINYINT(1) NULL DEFAULT NULL,
`network_timeout` INT UNSIGNED NOT NULL DEFAULT 5,
`created` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
`modified` DATETIME NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uniq_tenant_auth_ldap_tenant` (`tenant_id`),
CONSTRAINT `fk_tenant_auth_ldap_tenant` FOREIGN KEY (`tenant_id`) REFERENCES `tenants` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `user_external_identities` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`user_id` INT UNSIGNED NOT NULL,
`provider` VARCHAR(32) NOT NULL,
`oid` VARCHAR(128) NOT NULL,
`tid` VARCHAR(64) NOT NULL,
`issuer` VARCHAR(255) NOT NULL,
`subject` VARCHAR(255) NOT NULL,
`email_at_link_time` VARCHAR(255) NULL,
`created` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uniq_user_external_provider_tid_oid` (`provider`, `tid`, `oid`),
UNIQUE KEY `uniq_user_external_provider_iss_sub` (`provider`, `issuer`, `subject`),
KEY `idx_user_external_user` (`user_id`),
CONSTRAINT `fk_user_external_identity_user` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `user_tenants` (
`user_id` INT UNSIGNED NOT NULL,
`tenant_id` INT UNSIGNED NOT NULL,
`created` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`user_id`, `tenant_id`),
KEY `idx_user_tenants_user` (`user_id`),
KEY `idx_user_tenants_tenant` (`tenant_id`),
CONSTRAINT `fk_user_tenants_user` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE,
CONSTRAINT `fk_user_tenants_tenant` FOREIGN KEY (`tenant_id`) REFERENCES `tenants` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `user_roles` (
`user_id` INT UNSIGNED NOT NULL,
`role_id` INT UNSIGNED NOT NULL,
`created` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`user_id`, `role_id`),
KEY `idx_user_roles_user` (`user_id`),
KEY `idx_user_roles_role` (`role_id`),
CONSTRAINT `fk_user_roles_user` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE,
CONSTRAINT `fk_user_roles_role` FOREIGN KEY (`role_id`) REFERENCES `roles` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `role_permissions` (
`role_id` INT UNSIGNED NOT NULL,
`permission_id` INT UNSIGNED NOT NULL,
`created` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`role_id`, `permission_id`),
KEY `idx_role_permissions_permission` (`permission_id`),
CONSTRAINT `fk_role_permissions_role` FOREIGN KEY (`role_id`) REFERENCES `roles` (`id`) ON DELETE CASCADE,
CONSTRAINT `fk_role_permissions_permission` FOREIGN KEY (`permission_id`) REFERENCES `permissions` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `role_assignable_roles` (
`role_id` INT UNSIGNED NOT NULL,
`assignable_role_id` INT UNSIGNED NOT NULL,
`created` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`role_id`, `assignable_role_id`),
KEY `idx_rar_assignable` (`assignable_role_id`),
CONSTRAINT `fk_rar_role` FOREIGN KEY (`role_id`) REFERENCES `roles` (`id`) ON DELETE CASCADE,
CONSTRAINT `fk_rar_assignable` FOREIGN KEY (`assignable_role_id`) REFERENCES `roles` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `user_departments` (
`user_id` INT UNSIGNED NOT NULL,
`department_id` INT UNSIGNED NOT NULL,
`created` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`user_id`, `department_id`),
KEY `idx_user_departments_user` (`user_id`),
KEY `idx_user_departments_department` (`department_id`),
CONSTRAINT `fk_user_departments_user` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE,
CONSTRAINT `fk_user_departments_department` FOREIGN KEY (`department_id`) REFERENCES `departments` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `user_bookmark_groups` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`user_id` INT UNSIGNED NOT NULL,
`name` VARCHAR(100) NOT NULL,
`icon` VARCHAR(40) NOT NULL DEFAULT 'bi-folder',
`sort_order` SMALLINT UNSIGNED NOT NULL DEFAULT 0,
`created` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
`modified` DATETIME NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `idx_ubg_user_sort` (`user_id`, `sort_order`),
CONSTRAINT `fk_ubg_user` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `user_bookmarks` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`user_id` INT UNSIGNED NOT NULL,
`group_id` INT UNSIGNED NULL,
`name` VARCHAR(120) NOT NULL,
`url` VARCHAR(500) NOT NULL,
`sort_order` SMALLINT UNSIGNED NOT NULL DEFAULT 0,
`created` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
`modified` DATETIME NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uniq_ub_user_url` (`user_id`, `url`),
KEY `idx_ub_user_sort` (`user_id`, `sort_order`),
KEY `idx_ub_user_group_sort` (`user_id`, `group_id`, `sort_order`),
KEY `idx_ub_group` (`group_id`),
CONSTRAINT `fk_ub_user` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE,
CONSTRAINT `fk_ub_group` FOREIGN KEY (`group_id`) REFERENCES `user_bookmark_groups` (`id`) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `user_notifications` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
`recipient_user_id` INT UNSIGNED NOT NULL,
`tenant_id` INT UNSIGNED NULL,
`type` VARCHAR(60) NOT NULL,
`title` VARCHAR(255) NOT NULL,
`body` VARCHAR(500) NULL,
`link` VARCHAR(500) NULL,
`data` JSON NULL,
`dedupe_fingerprint` CHAR(64) NULL,
`dedupe_bucket` INT UNSIGNED NULL,
`dedupe_until` DATETIME NULL DEFAULT NULL,
`is_read` TINYINT(1) NOT NULL DEFAULT 0,
`read_at` DATETIME NULL DEFAULT NULL,
`created` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `idx_un_recipient_read_created` (`recipient_user_id`, `is_read`, `created` DESC),
KEY `idx_un_recipient_created` (`recipient_user_id`, `created` DESC),
KEY `idx_un_tenant_created` (`tenant_id`, `created` DESC),
KEY `idx_un_cleanup` (`is_read`, `read_at`),
UNIQUE KEY `uniq_un_dedupe_bucket` (`recipient_user_id`, `dedupe_fingerprint`, `dedupe_bucket`),
CONSTRAINT `fk_un_recipient` FOREIGN KEY (`recipient_user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE,
CONSTRAINT `fk_un_tenant` FOREIGN KEY (`tenant_id`) REFERENCES `tenants` (`id`) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `tenant_custom_field_definitions` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`uuid` CHAR(36) NOT NULL,
`tenant_id` INT UNSIGNED NOT NULL,
`field_key` VARCHAR(120) NOT NULL,
`label` VARCHAR(160) NOT NULL,
`type` VARCHAR(32) NOT NULL,
`is_required` TINYINT(1) NOT NULL DEFAULT 0,
`is_filterable` TINYINT(1) NOT NULL DEFAULT 0,
`active` TINYINT(1) NOT NULL DEFAULT 1,
`sort_order` INT NOT NULL DEFAULT 100,
`created_by` INT UNSIGNED DEFAULT NULL,
`modified_by` INT UNSIGNED DEFAULT NULL,
`created` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
`modified` DATETIME NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uniq_tenant_custom_field_uuid` (`uuid`),
UNIQUE KEY `uniq_tenant_custom_field_key` (`tenant_id`, `field_key`),
KEY `idx_tenant_custom_field_tenant_active` (`tenant_id`, `active`, `sort_order`),
KEY `idx_tenant_custom_field_created_by` (`created_by`),
KEY `idx_tenant_custom_field_modified_by` (`modified_by`),
CONSTRAINT `fk_tenant_custom_field_tenant` FOREIGN KEY (`tenant_id`) REFERENCES `tenants` (`id`) ON DELETE CASCADE,
CONSTRAINT `fk_tenant_custom_field_created_by` FOREIGN KEY (`created_by`) REFERENCES `users` (`id`) ON DELETE SET NULL,
CONSTRAINT `fk_tenant_custom_field_modified_by` FOREIGN KEY (`modified_by`) REFERENCES `users` (`id`) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `tenant_custom_field_options` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`definition_id` INT UNSIGNED NOT NULL,
`option_key` VARCHAR(120) NOT NULL,
`label` VARCHAR(160) NOT NULL,
`active` TINYINT(1) NOT NULL DEFAULT 1,
`sort_order` INT NOT NULL DEFAULT 100,
`created` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
`modified` DATETIME NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uniq_custom_field_option_key` (`definition_id`, `option_key`),
KEY `idx_custom_field_option_definition_active` (`definition_id`, `active`, `sort_order`),
CONSTRAINT `fk_custom_field_option_definition` FOREIGN KEY (`definition_id`) REFERENCES `tenant_custom_field_definitions` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `user_custom_field_values` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`user_id` INT UNSIGNED NOT NULL,
`definition_id` INT UNSIGNED NOT NULL,
`value_text` TEXT NULL,
`value_bool` TINYINT(1) NULL,
`value_date` DATE NULL,
`option_id` INT UNSIGNED NULL,
`created` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
`modified` DATETIME NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uniq_user_custom_field_definition` (`user_id`, `definition_id`),
KEY `idx_ucfv_definition` (`definition_id`),
KEY `idx_ucfv_option` (`option_id`),
KEY `idx_ucfv_bool` (`definition_id`, `value_bool`),
KEY `idx_ucfv_date` (`definition_id`, `value_date`),
CONSTRAINT `fk_ucfv_user` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE,
CONSTRAINT `fk_ucfv_definition` FOREIGN KEY (`definition_id`) REFERENCES `tenant_custom_field_definitions` (`id`) ON DELETE CASCADE,
CONSTRAINT `fk_ucfv_option` FOREIGN KEY (`option_id`) REFERENCES `tenant_custom_field_options` (`id`) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `user_custom_field_value_options` (
`value_id` INT UNSIGNED NOT NULL,
`option_id` INT UNSIGNED NOT NULL,
`created` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`value_id`, `option_id`),
KEY `idx_ucfvo_option` (`option_id`),
CONSTRAINT `fk_ucfvo_value` FOREIGN KEY (`value_id`) REFERENCES `user_custom_field_values` (`id`) ON DELETE CASCADE,
CONSTRAINT `fk_ucfvo_option` FOREIGN KEY (`option_id`) REFERENCES `tenant_custom_field_options` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `module_migrations` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`module_id` VARCHAR(64) NOT NULL,
`filename` VARCHAR(255) NOT NULL,
`applied_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uq_module_migration` (`module_id`, `filename`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `user_api_tokens` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`uuid` CHAR(36) NOT NULL,
`user_id` INT UNSIGNED NOT NULL,
`name` VARCHAR(120) NOT NULL DEFAULT '',
`selector` CHAR(24) NOT NULL,
`token_hash` CHAR(64) NOT NULL,
`tenant_id` INT UNSIGNED NULL COMMENT 'Optional: scope token to specific tenant',
`last_used_at` DATETIME NULL DEFAULT NULL,
`last_ip` VARCHAR(45) NULL DEFAULT NULL,
`expires_at` DATETIME NULL DEFAULT NULL,
`revoked_at` DATETIME NULL DEFAULT NULL,
`created_by` INT UNSIGNED NULL,
`created` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uniq_user_api_token_uuid` (`uuid`),
UNIQUE KEY `uniq_user_api_token_selector` (`selector`),
KEY `idx_user_api_token_user` (`user_id`),
KEY `idx_user_api_token_tenant` (`tenant_id`),
KEY `idx_user_api_token_expires` (`expires_at`),
CONSTRAINT `fk_user_api_token_user` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE,
CONSTRAINT `fk_user_api_token_tenant` FOREIGN KEY (`tenant_id`) REFERENCES `tenants` (`id`) ON DELETE SET NULL,
CONSTRAINT `fk_user_api_token_created_by` FOREIGN KEY (`created_by`) REFERENCES `users` (`id`) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `mail_log` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`to_email` VARCHAR(255) NOT NULL,
`subject` VARCHAR(255) NOT NULL,
`template` VARCHAR(100) NULL,
`status` VARCHAR(20) NOT NULL DEFAULT 'queued',
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
`sent_at` DATETIME NULL DEFAULT NULL,
`error_message` TEXT NULL,
`provider_message_id` VARCHAR(255) NULL,
PRIMARY KEY (`id`),
KEY `idx_mail_log_status` (`status`),
KEY `idx_mail_log_to_email` (`to_email`),
CONSTRAINT `chk_mail_log_status` CHECK (`status` IN ('queued', 'sent', 'failed'))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `api_audit_log` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
`request_id` CHAR(36) NOT NULL,
`method` VARCHAR(8) NOT NULL,
`path` VARCHAR(255) NOT NULL,
`query_json` JSON NULL,
`status_code` SMALLINT UNSIGNED NOT NULL,
`duration_ms` INT UNSIGNED NULL,
`error_code` VARCHAR(100) NULL,
`user_id` INT UNSIGNED NULL,
`tenant_id` INT UNSIGNED NULL,
`api_token_id` INT UNSIGNED NULL,
`token_tenant_id` INT UNSIGNED NULL,
`ip` CHAR(64) NULL,
`user_agent` CHAR(64) NULL,
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uniq_api_audit_request_id_created` (`request_id`, `created_at`),
KEY `idx_api_audit_created` (`created_at`),
KEY `idx_api_audit_status_created` (`status_code`, `created_at`),
KEY `idx_api_audit_user_created` (`user_id`, `created_at`),
KEY `idx_api_audit_tenant_created` (`tenant_id`, `created_at`),
KEY `idx_api_audit_path_created` (`path`, `created_at`),
KEY `idx_api_audit_error_created` (`error_code`, `created_at`),
KEY `idx_api_audit_token_created` (`api_token_id`, `created_at`),
CONSTRAINT `fk_api_audit_user` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE SET NULL,
CONSTRAINT `fk_api_audit_tenant` FOREIGN KEY (`tenant_id`) REFERENCES `tenants` (`id`) ON DELETE SET NULL,
CONSTRAINT `fk_api_audit_token` FOREIGN KEY (`api_token_id`) REFERENCES `user_api_tokens` (`id`) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `system_audit_log` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
`event_uuid` CHAR(36) NOT NULL,
`request_id` CHAR(36) NULL,
`channel` VARCHAR(16) NOT NULL,
`event_type` VARCHAR(64) NOT NULL,
`outcome` VARCHAR(16) NOT NULL,
`error_code` VARCHAR(100) NULL,
`actor_user_id` INT UNSIGNED NULL,
`actor_tenant_id` INT UNSIGNED NULL,
`target_type` VARCHAR(64) NULL,
`target_id` INT UNSIGNED NULL,
`target_uuid` CHAR(36) NULL,
`method` VARCHAR(8) NULL,
`path` VARCHAR(255) NULL,
`ip_hash` CHAR(64) NULL,
`user_agent_hash` CHAR(64) NULL,
`metadata_json` TEXT NULL,
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uniq_system_audit_event_uuid` (`event_uuid`),
KEY `idx_system_audit_created` (`created_at`),
KEY `idx_system_audit_event_created` (`event_type`, `created_at`),
KEY `idx_system_audit_actor_created` (`actor_user_id`, `created_at`),
KEY `idx_system_audit_target_created` (`target_type`, `target_uuid`, `created_at`),
KEY `idx_system_audit_request_created` (`request_id`, `created_at`),
KEY `idx_system_audit_outcome_created` (`outcome`, `created_at`),
CONSTRAINT `chk_system_audit_log_channel` CHECK (`channel` IN ('web', 'api', 'scheduler', 'cli')),
CONSTRAINT `chk_system_audit_log_outcome` CHECK (`outcome` IN ('success', 'failed', 'denied')),
CONSTRAINT `fk_system_audit_actor_user` FOREIGN KEY (`actor_user_id`) REFERENCES `users` (`id`) ON DELETE SET NULL,
CONSTRAINT `fk_system_audit_actor_tenant` FOREIGN KEY (`actor_tenant_id`) REFERENCES `tenants` (`id`) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `import_audit_runs` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
`run_uuid` CHAR(36) NOT NULL,
`profile_key` VARCHAR(32) NOT NULL,
`status` VARCHAR(16) NOT NULL,
`source_filename` VARCHAR(255) NULL,
`mapped_targets_csv` TEXT NULL,
`rows_total` INT UNSIGNED NOT NULL DEFAULT 0,
`created_count` INT UNSIGNED NOT NULL DEFAULT 0,
`skipped_count` INT UNSIGNED NOT NULL DEFAULT 0,
`failed_count` INT UNSIGNED NOT NULL DEFAULT 0,
`error_codes_json` TEXT NULL,
`started_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
`finished_at` DATETIME NULL DEFAULT NULL,
`duration_ms` INT UNSIGNED NULL,
`user_id` INT UNSIGNED NULL,
`current_tenant_id` INT UNSIGNED NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `uniq_import_audit_run_uuid` (`run_uuid`),
KEY `idx_import_audit_started` (`started_at`),
KEY `idx_import_audit_status_started` (`status`, `started_at`),
KEY `idx_import_audit_profile_started` (`profile_key`, `started_at`),
KEY `idx_import_audit_user_started` (`user_id`, `started_at`),
CONSTRAINT `chk_import_audit_runs_status` CHECK (`status` IN ('running', 'success', 'partial', 'failed')),
CONSTRAINT `fk_import_audit_user` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE SET NULL,
CONSTRAINT `fk_import_audit_tenant` FOREIGN KEY (`current_tenant_id`) REFERENCES `tenants` (`id`) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `user_lifecycle_audit_log` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
`run_uuid` CHAR(36) NOT NULL,
`action` VARCHAR(16) NOT NULL,
`trigger_type` VARCHAR(16) NOT NULL,
`status` VARCHAR(16) NOT NULL,
`reason_code` VARCHAR(64) NULL,
`policy_deactivate_days` INT UNSIGNED NOT NULL DEFAULT 0,
`policy_delete_days` INT UNSIGNED NOT NULL DEFAULT 0,
`actor_user_id` INT UNSIGNED NULL,
`target_user_id` INT UNSIGNED NULL,
`target_user_uuid` CHAR(36) NULL,
`target_user_email` VARCHAR(255) NULL,
`snapshot_enc` LONGTEXT NULL,
`snapshot_version` SMALLINT UNSIGNED NOT NULL DEFAULT 1,
`restored_at` DATETIME NULL DEFAULT NULL,
`restored_by_user_id` INT UNSIGNED NULL,
`restored_user_id` INT UNSIGNED NULL,
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `idx_user_lifecycle_created` (`created_at`),
KEY `idx_user_lifecycle_action_created` (`action`, `created_at`),
KEY `idx_user_lifecycle_status_created` (`status`, `created_at`),
KEY `idx_user_lifecycle_target_uuid_created` (`target_user_uuid`, `created_at`),
KEY `idx_user_lifecycle_run_uuid` (`run_uuid`),
KEY `idx_user_lifecycle_restored` (`restored_at`),
CONSTRAINT `chk_user_lifecycle_action` CHECK (`action` IN ('deactivate', 'delete', 'restore')),
CONSTRAINT `chk_user_lifecycle_trigger_type` CHECK (`trigger_type` IN ('manual', 'cron', 'system')),
CONSTRAINT `chk_user_lifecycle_status` CHECK (`status` IN ('success', 'skipped', 'failed')),
CONSTRAINT `fk_user_lifecycle_actor` FOREIGN KEY (`actor_user_id`) REFERENCES `users` (`id`) ON DELETE SET NULL,
CONSTRAINT `fk_user_lifecycle_target` FOREIGN KEY (`target_user_id`) REFERENCES `users` (`id`) ON DELETE SET NULL,
CONSTRAINT `fk_user_lifecycle_restored_by` FOREIGN KEY (`restored_by_user_id`) REFERENCES `users` (`id`) ON DELETE SET NULL,
CONSTRAINT `fk_user_lifecycle_restored_user` FOREIGN KEY (`restored_user_id`) REFERENCES `users` (`id`) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `scheduled_jobs` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
`job_key` VARCHAR(64) NOT NULL,
`label` VARCHAR(120) NOT NULL,
`description` VARCHAR(255) NULL,
`enabled` TINYINT(1) NOT NULL DEFAULT 1,
`timezone` VARCHAR(64) NOT NULL DEFAULT 'Europe/Berlin',
`schedule_type` VARCHAR(16) NOT NULL,
`schedule_interval` SMALLINT UNSIGNED NOT NULL DEFAULT 1,
`schedule_time` CHAR(5) NULL,
`schedule_weekdays_csv` VARCHAR(32) NULL,
`catchup_once` TINYINT(1) NOT NULL DEFAULT 1,
`next_run_at` DATETIME NULL DEFAULT NULL,
`last_run_started_at` DATETIME NULL DEFAULT NULL,
`last_run_finished_at` DATETIME NULL DEFAULT NULL,
`last_run_status` VARCHAR(16) NULL,
`last_error_code` VARCHAR(64) NULL,
`last_error_message` VARCHAR(255) NULL,
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` DATETIME NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uniq_scheduled_jobs_job_key` (`job_key`),
KEY `idx_scheduled_jobs_enabled_next` (`enabled`, `next_run_at`),
KEY `idx_scheduled_jobs_status` (`last_run_status`),
CONSTRAINT `chk_scheduled_jobs_last_run_status` CHECK (`last_run_status` IS NULL OR `last_run_status` IN ('running', 'success', 'failed', 'skipped'))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `scheduled_job_runs` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
`run_uuid` CHAR(36) NOT NULL,
`job_id` BIGINT UNSIGNED NOT NULL,
`job_key` VARCHAR(64) NOT NULL,
`trigger_type` VARCHAR(16) NOT NULL,
`status` VARCHAR(16) NOT NULL,
`actor_user_id` INT UNSIGNED NULL,
`started_at` DATETIME NOT NULL,
`finished_at` DATETIME NULL DEFAULT NULL,
`duration_ms` INT UNSIGNED NULL,
`error_code` VARCHAR(64) NULL,
`error_message` VARCHAR(255) NULL,
`result_json` TEXT NULL,
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uniq_sched_runs_run_uuid` (`run_uuid`),
KEY `idx_sched_runs_job_created` (`job_id`, `created_at`),
KEY `idx_sched_runs_status_created` (`status`, `created_at`),
KEY `idx_sched_runs_trigger_created` (`trigger_type`, `created_at`),
CONSTRAINT `chk_scheduled_job_runs_status` CHECK (`status` IN ('success', 'failed', 'skipped')),
CONSTRAINT `chk_scheduled_job_runs_trigger_type` CHECK (`trigger_type` IN ('scheduler', 'manual')),
CONSTRAINT `fk_sched_runs_job` FOREIGN KEY (`job_id`) REFERENCES `scheduled_jobs` (`id`) ON DELETE CASCADE,
CONSTRAINT `fk_sched_runs_actor_user` FOREIGN KEY (`actor_user_id`) REFERENCES `users` (`id`) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `scheduler_runtime_status` (
`id` TINYINT UNSIGNED NOT NULL,
`last_heartbeat_at` DATETIME NOT NULL,
`last_result` VARCHAR(32) NULL,
`last_error_code` VARCHAR(64) NULL,
`updated_at` DATETIME NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
CONSTRAINT `chk_scheduler_runtime_status_last_result` CHECK (`last_result` IS NULL OR `last_result` IN ('ok', 'lock_not_acquired', 'unexpected_error'))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `request_rate_limits` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
`scope` VARCHAR(64) NOT NULL,
`subject_hash` CHAR(64) NOT NULL,
`hits` INT UNSIGNED NOT NULL DEFAULT 0,
`window_started_at` DATETIME NOT NULL,
`blocked_until` DATETIME NULL DEFAULT NULL,
`created` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
`modified` DATETIME NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uniq_request_rate_scope_subject` (`scope`, `subject_hash`),
KEY `idx_request_rate_scope_blocked` (`scope`, `blocked_until`),
KEY `idx_request_rate_modified` (`modified`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `password_resets` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`user_id` INT UNSIGNED NOT NULL,
`code_hash` VARCHAR(255) NOT NULL,
`expires_at` DATETIME NOT NULL,
`attempts` INT UNSIGNED NOT NULL DEFAULT 0 COMMENT 'Failed verification attempts',
`used_at` DATETIME NULL DEFAULT NULL,
`created` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `idx_password_resets_user` (`user_id`),
KEY `idx_password_resets_expires` (`expires_at`),
CONSTRAINT `fk_password_resets_user` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `email_verifications` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`user_id` INT UNSIGNED NOT NULL,
`code_hash` VARCHAR(255) NOT NULL,
`expires_at` DATETIME NOT NULL,
`attempts` INT UNSIGNED NOT NULL DEFAULT 0 COMMENT 'Failed verification attempts',
`used_at` DATETIME NULL DEFAULT NULL,
`created` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `idx_email_verifications_user` (`user_id`),
KEY `idx_email_verifications_expires` (`expires_at`),
CONSTRAINT `fk_email_verifications_user` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `user_remember_tokens` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`user_id` INT UNSIGNED NOT NULL,
`selector` CHAR(24) NOT NULL,
`token_hash` CHAR(64) NOT NULL,
`expires_at` DATETIME NOT NULL,
`expired_by_admin_at` DATETIME NULL DEFAULT NULL COMMENT 'Expired by admin action',
`created` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
`last_used` DATETIME NULL DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `uniq_user_remember_selector` (`selector`),
KEY `idx_user_remember_user` (`user_id`),
KEY `idx_user_remember_expires` (`expires_at`),
CONSTRAINT `fk_user_remember_user` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
INSERT INTO `tenants` (
`uuid`,
`description`,
`address`,
`postal_code`,
`city`,
`country`,
`region`,
`vat_id`,
`tax_number`,
`phone`,
`fax`,
`email`,
`support_email`,
`support_phone`,
`billing_email`,
`website`,
`privacy_url`,
`imprint_url`,
`primary_color`,
`default_theme`,
`allow_user_theme`,
`status`,
`created`
)
SELECT
UUID(),
'MusterMandant',
'Musterstrasse 1',
'10115',
'Berlin',
'Deutschland',
'Berlin',
'DE123456789',
'12/345/67890',
'+49 30 12345670',
'+49 30 12345679',
'info@mustermandant.de',
'support@mustermandant.de',
'+49 30 12345671',
'billing@mustermandant.de',
'https://www.mustermandant.de',
'https://www.mustermandant.de/datenschutz',
'https://www.mustermandant.de/impressum',
'#1A73E8',
'light',
1,
'active',
NOW()
WHERE NOT EXISTS (SELECT 1 FROM tenants WHERE description = 'MusterMandant');
UPDATE `tenants`
SET
`address` = COALESCE(`address`, 'Musterstrasse 1'),
`postal_code` = COALESCE(`postal_code`, '10115'),
`city` = COALESCE(`city`, 'Berlin'),
`country` = COALESCE(`country`, 'Deutschland'),
`region` = COALESCE(`region`, 'Berlin'),
`vat_id` = COALESCE(`vat_id`, 'DE123456789'),
`tax_number` = COALESCE(`tax_number`, '12/345/67890'),
`phone` = COALESCE(`phone`, '+49 30 12345670'),
`fax` = COALESCE(`fax`, '+49 30 12345679'),
`email` = COALESCE(`email`, 'info@mustermandant.de'),
`support_email` = COALESCE(`support_email`, 'support@mustermandant.de'),
`support_phone` = COALESCE(`support_phone`, '+49 30 12345671'),
`billing_email` = COALESCE(`billing_email`, 'billing@mustermandant.de'),
`website` = COALESCE(`website`, 'https://www.mustermandant.de'),
`privacy_url` = COALESCE(`privacy_url`, 'https://www.mustermandant.de/datenschutz'),
`imprint_url` = COALESCE(`imprint_url`, 'https://www.mustermandant.de/impressum'),
`primary_color` = COALESCE(`primary_color`, '#1A73E8'),
`default_theme` = COALESCE(`default_theme`, 'light'),
`allow_user_theme` = COALESCE(`allow_user_theme`, 1),
`status` = COALESCE(`status`, 'active')
WHERE `description` = 'MusterMandant';
INSERT INTO `departments` (`uuid`, `description`, `tenant_id`, `code`, `active`, `created`)
SELECT UUID(), 'Musterabteilung', t.id, 'MUSTER', 1, NOW()
FROM tenants t
WHERE t.description = 'MusterMandant'
AND NOT EXISTS (
SELECT 1 FROM departments d
WHERE d.tenant_id = t.id
AND d.description = 'Musterabteilung'
);
INSERT INTO `departments` (`uuid`, `description`, `tenant_id`, `code`, `active`, `created`)
SELECT UUID(), 'Musterabteilung 2', t.id, 'MUSTER2', 1, NOW()
FROM tenants t
WHERE t.description = 'MusterMandant'
AND NOT EXISTS (
SELECT 1 FROM departments d
WHERE d.tenant_id = t.id
AND d.description = 'Musterabteilung 2'
);
INSERT INTO `roles` (`uuid`, `description`, `code`, `active`, `created`)
SELECT UUID(), 'Admin', 'ADMIN', 1, NOW()
WHERE NOT EXISTS (SELECT 1 FROM roles WHERE description = 'Admin');
INSERT INTO `roles` (`uuid`, `description`, `code`, `active`, `created`)
SELECT UUID(), 'User', 'USER', 1, NOW()
WHERE NOT EXISTS (SELECT 1 FROM roles WHERE code = 'USER');
INSERT INTO `roles` (`uuid`, `description`, `code`, `active`, `created`)
SELECT UUID(), 'Manager', 'MANAGER', 1, NOW()
WHERE NOT EXISTS (SELECT 1 FROM roles WHERE code = 'MANAGER');
INSERT INTO `roles` (`uuid`, `description`, `code`, `active`, `created`)
SELECT UUID(), 'Auditor', 'AUDITOR', 1, NOW()
WHERE NOT EXISTS (SELECT 1 FROM roles WHERE code = 'AUDITOR');
INSERT INTO `roles` (`uuid`, `description`, `code`, `active`, `created`)
SELECT UUID(), 'Support', 'SUPPORT', 1, NOW()
WHERE NOT EXISTS (SELECT 1 FROM roles WHERE code = 'SUPPORT');
INSERT INTO `users` (
`uuid`,
`first_name`,
`last_name`,
`display_name`,
`email`,
`profile_description`,
`job_title`,
`phone`,
`mobile`,
`short_dial`,
`address`,
`postal_code`,
`city`,
`country`,
`region`,
`hire_date`,
`email_verified_at`,
`password`,
`locale`,
`totp_secret`,
`theme`,
`primary_tenant_id`,
`created_by`,
`created`,
`active`,
`active_changed_at`,
`active_changed_by`
)
SELECT
UUID(),
'Detlef',
'Demo',
'Detlef Demo',
'demo@user.com',
'Demo-Nutzer fuer Vorschau und Test der Adressbuchdarstellung.',
'Sachbearbeiter',
'+49 30 1234567',
'+49 171 2345678',
'123',
'Musterstrasse 1',
'10115',
'Berlin',
'Deutschland',
'Berlin',
'2024-01-15',
NOW(),
'$2y$12$KVCQvuy4Pl1aySBuzSpc7ehpZhAzYZkndDI9OaMi05E2P/Mhob5HO',
'de',
NULL,
'light',
(SELECT id FROM tenants WHERE description = 'MusterMandant' LIMIT 1),
NULL,
NOW(),
1,
NOW(),
NULL
WHERE NOT EXISTS (SELECT 1 FROM users WHERE email = 'demo@user.com');
UPDATE `users`
SET `email_verified_at` = COALESCE(`email_verified_at`, NOW())
WHERE `email` = 'demo@user.com';
UPDATE `users`
SET
`profile_description` = COALESCE(`profile_description`, 'Demo-Nutzer fuer Vorschau und Test der Adressbuchdarstellung.'),
`job_title` = COALESCE(`job_title`, 'Sachbearbeiter'),
`phone` = COALESCE(`phone`, '+49 30 1234567'),
`mobile` = COALESCE(`mobile`, '+49 171 2345678'),
`short_dial` = COALESCE(`short_dial`, '123'),
`address` = COALESCE(`address`, 'Musterstrasse 1'),
`postal_code` = COALESCE(`postal_code`, '10115'),
`city` = COALESCE(`city`, 'Berlin'),
`country` = COALESCE(`country`, 'Deutschland'),
`region` = COALESCE(`region`, 'Berlin'),
`hire_date` = COALESCE(`hire_date`, '2024-01-15')
WHERE `email` = 'demo@user.com';
INSERT INTO `user_tenants` (`user_id`, `tenant_id`, `created`)
SELECT u.id, t.id, NOW()
FROM users u
JOIN tenants t ON t.description = 'MusterMandant'
WHERE u.email = 'demo@user.com'
AND NOT EXISTS (
SELECT 1 FROM user_tenants ut WHERE ut.user_id = u.id AND ut.tenant_id = t.id
);
INSERT INTO `user_departments` (`user_id`, `department_id`, `created`)
SELECT u.id, d.id, NOW()
FROM users u
JOIN departments d ON d.code = 'MUSTER'
JOIN tenants t ON t.id = d.tenant_id AND t.description = 'MusterMandant'
WHERE u.email = 'demo@user.com'
AND NOT EXISTS (
SELECT 1 FROM user_departments ud WHERE ud.user_id = u.id AND ud.department_id = d.id
);
INSERT INTO `user_roles` (`user_id`, `role_id`, `created`)
SELECT u.id, r.id, NOW()
FROM users u
JOIN roles r ON r.description = 'Admin'
WHERE u.email = 'demo@user.com'
AND NOT EXISTS (
SELECT 1 FROM user_roles ur WHERE ur.user_id = u.id AND ur.role_id = r.id
);
-- ─── Demo user: Max Machtvoll (Manager) ───
INSERT INTO `users` (
`uuid`,
`first_name`,
`last_name`,
`display_name`,
`email`,
`profile_description`,
`job_title`,
`phone`,
`mobile`,
`short_dial`,
`address`,
`postal_code`,
`city`,
`country`,
`region`,
`hire_date`,
`email_verified_at`,
`password`,
`locale`,
`totp_secret`,
`theme`,
`primary_tenant_id`,
`created_by`,
`created`,
`active`,
`active_changed_at`,
`active_changed_by`
)
SELECT
UUID(),
'Max',
'Machtvoll',
'Max Machtvoll',
'max@user.com',
'Manager mit Weitblick und festem Griff am Steuer.',
'Abteilungsleiter',
'+49 30 9876543',
'+49 172 1111111',
'201',
'Chefetage 7',
'80331',
'Muenchen',
'Deutschland',
'Bayern',
'2023-06-01',
NOW(),
'$2y$12$KVCQvuy4Pl1aySBuzSpc7ehpZhAzYZkndDI9OaMi05E2P/Mhob5HO',
'de',
NULL,
'light',
(SELECT id FROM tenants WHERE description = 'MusterMandant' LIMIT 1),
NULL,
NOW(),
1,
NOW(),
NULL
WHERE NOT EXISTS (SELECT 1 FROM users WHERE email = 'max@user.com');
INSERT INTO `user_tenants` (`user_id`, `tenant_id`, `created`)
SELECT u.id, t.id, NOW()
FROM users u
JOIN tenants t ON t.description = 'MusterMandant'
WHERE u.email = 'max@user.com'
AND NOT EXISTS (
SELECT 1 FROM user_tenants ut WHERE ut.user_id = u.id AND ut.tenant_id = t.id
);
INSERT INTO `user_departments` (`user_id`, `department_id`, `created`)
SELECT u.id, d.id, NOW()
FROM users u
JOIN departments d ON d.code = 'MUSTER'
JOIN tenants t ON t.id = d.tenant_id AND t.description = 'MusterMandant'
WHERE u.email = 'max@user.com'
AND NOT EXISTS (
SELECT 1 FROM user_departments ud WHERE ud.user_id = u.id AND ud.department_id = d.id
);
INSERT INTO `user_roles` (`user_id`, `role_id`, `created`)
SELECT u.id, r.id, NOW()
FROM users u
JOIN roles r ON r.description = 'Manager'
WHERE u.email = 'max@user.com'
AND NOT EXISTS (
SELECT 1 FROM user_roles ur WHERE ur.user_id = u.id AND ur.role_id = r.id
);
-- ─── Demo user: Bernd Benutzer (User) ───
INSERT INTO `users` (
`uuid`,
`first_name`,
`last_name`,
`display_name`,
`email`,
`profile_description`,
`job_title`,
`phone`,
`mobile`,
`short_dial`,
`address`,
`postal_code`,
`city`,
`country`,
`region`,
`hire_date`,
`email_verified_at`,
`password`,
`locale`,
`totp_secret`,
`theme`,
`primary_tenant_id`,
`created_by`,
`created`,
`active`,
`active_changed_at`,
`active_changed_by`
)
SELECT
UUID(),
'Bernd',
'Benutzer',
'Bernd Benutzer',
'bernd@user.com',
'Stinknormaler Benutzer. Macht seine Arbeit, trinkt seinen Kaffee.',
'Sachbearbeiter',
'+49 40 5551234',
'+49 173 2222222',
'302',
'Normalweg 42',
'20095',
'Hamburg',
'Deutschland',
'Hamburg',
'2024-03-10',
NOW(),
'$2y$12$KVCQvuy4Pl1aySBuzSpc7ehpZhAzYZkndDI9OaMi05E2P/Mhob5HO',
'de',
NULL,
'light',
(SELECT id FROM tenants WHERE description = 'MusterMandant' LIMIT 1),
NULL,
NOW(),
1,
NOW(),
NULL
WHERE NOT EXISTS (SELECT 1 FROM users WHERE email = 'bernd@user.com');
INSERT INTO `user_tenants` (`user_id`, `tenant_id`, `created`)
SELECT u.id, t.id, NOW()
FROM users u
JOIN tenants t ON t.description = 'MusterMandant'
WHERE u.email = 'bernd@user.com'
AND NOT EXISTS (
SELECT 1 FROM user_tenants ut WHERE ut.user_id = u.id AND ut.tenant_id = t.id
);
INSERT INTO `user_departments` (`user_id`, `department_id`, `created`)
SELECT u.id, d.id, NOW()
FROM users u
JOIN departments d ON d.code = 'MUSTER2'
JOIN tenants t ON t.id = d.tenant_id AND t.description = 'MusterMandant'
WHERE u.email = 'bernd@user.com'
AND NOT EXISTS (
SELECT 1 FROM user_departments ud WHERE ud.user_id = u.id AND ud.department_id = d.id
);
INSERT INTO `user_roles` (`user_id`, `role_id`, `created`)
SELECT u.id, r.id, NOW()
FROM users u
JOIN roles r ON r.description = 'User'
WHERE u.email = 'bernd@user.com'
AND NOT EXISTS (
SELECT 1 FROM user_roles ur WHERE ur.user_id = u.id AND ur.role_id = r.id
);
-- ─── Demo user: Petra Pruefblick (Auditor) ───
INSERT INTO `users` (
`uuid`,
`first_name`,
`last_name`,
`display_name`,
`email`,
`profile_description`,
`job_title`,
`phone`,
`mobile`,
`short_dial`,
`address`,
`postal_code`,
`city`,
`country`,
`region`,
`hire_date`,
`email_verified_at`,
`password`,
`locale`,
`totp_secret`,
`theme`,
`primary_tenant_id`,
`created_by`,
`created`,
`active`,
`active_changed_at`,
`active_changed_by`
)
SELECT
UUID(),
'Petra',
'Pruefblick',
'Petra Pruefblick',
'petra@user.com',
'Sieht alles. Vergisst nichts. Ihr Rotstift ist legendaer.',
'Prueferin',
'+49 69 4449876',
'+49 174 3333333',
'403',
'Kontrollgasse 8',
'60311',
'Frankfurt am Main',
'Deutschland',
'Hessen',
'2023-11-20',
NOW(),
'$2y$12$KVCQvuy4Pl1aySBuzSpc7ehpZhAzYZkndDI9OaMi05E2P/Mhob5HO',
'de',
NULL,
'light',
(SELECT id FROM tenants WHERE description = 'MusterMandant' LIMIT 1),
NULL,
NOW(),
1,
NOW(),
NULL
WHERE NOT EXISTS (SELECT 1 FROM users WHERE email = 'petra@user.com');
INSERT INTO `user_tenants` (`user_id`, `tenant_id`, `created`)
SELECT u.id, t.id, NOW()
FROM users u
JOIN tenants t ON t.description = 'MusterMandant'
WHERE u.email = 'petra@user.com'
AND NOT EXISTS (
SELECT 1 FROM user_tenants ut WHERE ut.user_id = u.id AND ut.tenant_id = t.id
);
INSERT INTO `user_departments` (`user_id`, `department_id`, `created`)
SELECT u.id, d.id, NOW()
FROM users u
JOIN departments d ON d.code = 'MUSTER'
JOIN tenants t ON t.id = d.tenant_id AND t.description = 'MusterMandant'
WHERE u.email = 'petra@user.com'
AND NOT EXISTS (
SELECT 1 FROM user_departments ud WHERE ud.user_id = u.id AND ud.department_id = d.id
);
INSERT INTO `user_roles` (`user_id`, `role_id`, `created`)
SELECT u.id, r.id, NOW()
FROM users u
JOIN roles r ON r.description = 'Auditor'
WHERE u.email = 'petra@user.com'
AND NOT EXISTS (
SELECT 1 FROM user_roles ur WHERE ur.user_id = u.id AND ur.role_id = r.id
);
-- ─── Demo user: Hilfe Hilde (Support) ───
INSERT INTO `users` (
`uuid`,
`first_name`,
`last_name`,
`display_name`,
`email`,
`profile_description`,
`job_title`,
`phone`,
`mobile`,
`short_dial`,
`address`,
`postal_code`,
`city`,
`country`,
`region`,
`hire_date`,
`email_verified_at`,
`password`,
`locale`,
`totp_secret`,
`theme`,
`primary_tenant_id`,
`created_by`,
`created`,
`active`,
`active_changed_at`,
`active_changed_by`
)
SELECT
UUID(),
'Hilde',
'Hilfe',
'Hilfe Hilde',
'hilde@user.com',
'Erste Hilfe fuer alle Notfaelle. Kein Ticket bleibt unbeantwortet.',
'Supportmitarbeiterin',
'+49 221 7773210',
'+49 175 4444444',
'504',
'Rettungsring 3',
'50667',
'Koeln',
'Deutschland',
'Nordrhein-Westfalen',
'2024-07-01',
NOW(),
'$2y$12$KVCQvuy4Pl1aySBuzSpc7ehpZhAzYZkndDI9OaMi05E2P/Mhob5HO',
'de',
NULL,
'light',
(SELECT id FROM tenants WHERE description = 'MusterMandant' LIMIT 1),
NULL,
NOW(),
1,
NOW(),
NULL
WHERE NOT EXISTS (SELECT 1 FROM users WHERE email = 'hilde@user.com');
INSERT INTO `user_tenants` (`user_id`, `tenant_id`, `created`)
SELECT u.id, t.id, NOW()
FROM users u
JOIN tenants t ON t.description = 'MusterMandant'
WHERE u.email = 'hilde@user.com'
AND NOT EXISTS (
SELECT 1 FROM user_tenants ut WHERE ut.user_id = u.id AND ut.tenant_id = t.id
);
INSERT INTO `user_departments` (`user_id`, `department_id`, `created`)
SELECT u.id, d.id, NOW()
FROM users u
JOIN departments d ON d.code = 'MUSTER2'
JOIN tenants t ON t.id = d.tenant_id AND t.description = 'MusterMandant'
WHERE u.email = 'hilde@user.com'
AND NOT EXISTS (
SELECT 1 FROM user_departments ud WHERE ud.user_id = u.id AND ud.department_id = d.id
);
INSERT INTO `user_roles` (`user_id`, `role_id`, `created`)
SELECT u.id, r.id, NOW()
FROM users u
JOIN roles r ON r.description = 'Support'
WHERE u.email = 'hilde@user.com'
AND NOT EXISTS (
SELECT 1 FROM user_roles ur WHERE ur.user_id = u.id AND ur.role_id = r.id
);
INSERT INTO `pages` (`uuid`, `slug`, `created`)
SELECT UUID(), v.slug, NOW()
FROM (
SELECT 'imprint' AS slug
UNION ALL SELECT 'privacy'
UNION ALL SELECT 'terms'
) AS v
WHERE NOT EXISTS (SELECT 1 FROM pages p WHERE p.slug = v.slug);
INSERT INTO `page_contents` (`page_id`, `locale`, `content`, `created`)
SELECT p.id, 'de', '{"blocks":[{"type":"header","data":{"text":"Impressum","level":2}},{"type":"paragraph","data":{"text":"Diese Seite ist eine Vorlage. Bitte tragen Sie hier die Pflichtangaben Ihres Unternehmens ein."}},{"type":"paragraph","data":{"text":"Angaben gemaess Paragraph 5 TMG: Firmenname, Rechtsform, vertretungsberechtigte Person, Anschrift, Kontakt und Registerangaben."}},{"type":"paragraph","data":{"text":"Inhaltlich verantwortlich: Name und Anschrift."}}]}', p.created
FROM pages p
WHERE p.slug = 'imprint'
AND NOT EXISTS (
SELECT 1 FROM page_contents pc WHERE pc.page_id = p.id AND pc.locale = 'de'
);
INSERT INTO `page_contents` (`page_id`, `locale`, `content`, `created`)
SELECT p.id, 'en', '{"blocks":[{"type":"header","data":{"text":"Imprint","level":2}},{"type":"paragraph","data":{"text":"This page is a template. Please add your mandatory company details here."}},{"type":"paragraph","data":{"text":"Provide legal publisher information, address, contact channels, company register data and authorized representatives."}},{"type":"paragraph","data":{"text":"Responsible for content: name and address."}}]}', p.created
FROM pages p
WHERE p.slug = 'imprint'
AND NOT EXISTS (
SELECT 1 FROM page_contents pc WHERE pc.page_id = p.id AND pc.locale = 'en'
);
INSERT INTO `page_contents` (`page_id`, `locale`, `content`, `created`)
SELECT p.id, 'de', '{"blocks":[{"type":"header","data":{"text":"Datenschutz","level":2}},{"type":"paragraph","data":{"text":"Diese Seite ist eine Vorlage. Beschreiben Sie hier transparent, welche personenbezogenen Daten verarbeitet werden."}},{"type":"paragraph","data":{"text":"Nennen Sie Rechtsgrundlagen, Speicherdauer, Empfaenger, Betroffenenrechte und Kontakt der Datenschutzstelle."}},{"type":"paragraph","data":{"text":"Pruefen Sie die Inhalte regelmaessig mit Ihrem Datenschutzbeauftragten."}}]}', p.created
FROM pages p
WHERE p.slug = 'privacy'
AND NOT EXISTS (
SELECT 1 FROM page_contents pc WHERE pc.page_id = p.id AND pc.locale = 'de'
);
INSERT INTO `page_contents` (`page_id`, `locale`, `content`, `created`)
SELECT p.id, 'en', '{"blocks":[{"type":"header","data":{"text":"Privacy","level":2}},{"type":"paragraph","data":{"text":"This page is a template. Explain which personal data is processed and for what purpose."}},{"type":"paragraph","data":{"text":"Document legal bases, retention periods, recipients, data subject rights and your privacy contact point."}},{"type":"paragraph","data":{"text":"Review this content regularly with your legal or privacy team."}}]}', p.created
FROM pages p
WHERE p.slug = 'privacy'
AND NOT EXISTS (
SELECT 1 FROM page_contents pc WHERE pc.page_id = p.id AND pc.locale = 'en'
);
INSERT INTO `page_contents` (`page_id`, `locale`, `content`, `created`)
SELECT p.id, 'de', '{"blocks":[{"type":"header","data":{"text":"Nutzungsbedingungen","level":2}},{"type":"paragraph","data":{"text":"Diese Seite ist eine Vorlage. Beschreiben Sie hier Zweck, Leistungsumfang und zulaessige Nutzung Ihres Angebots."}},{"type":"paragraph","data":{"text":"Ergaenzen Sie Regelungen zu Haftung, Laufzeit, Kuendigung, Gerichtsstand und anwendbarem Recht."}},{"type":"paragraph","data":{"text":"Lassen Sie die Inhalte vor Veroeffentlichung rechtlich pruefen."}}]}', p.created
FROM pages p
WHERE p.slug = 'terms'
AND NOT EXISTS (
SELECT 1 FROM page_contents pc WHERE pc.page_id = p.id AND pc.locale = 'de'
);
INSERT INTO `page_contents` (`page_id`, `locale`, `content`, `created`)
SELECT p.id, 'en', '{"blocks":[{"type":"header","data":{"text":"Terms of Service","level":2}},{"type":"paragraph","data":{"text":"This page is a template. Describe the service scope, intended use and user obligations."}},{"type":"paragraph","data":{"text":"Add clauses for liability, term, termination, governing law and jurisdiction."}},{"type":"paragraph","data":{"text":"Have this content reviewed by legal counsel before publishing."}}]}', p.created
FROM pages p
WHERE p.slug = 'terms'
AND NOT EXISTS (
SELECT 1 FROM page_contents pc WHERE pc.page_id = p.id AND pc.locale = 'en'
);
INSERT INTO `page_contents` (`page_id`, `locale`, `content`, `created`)
SELECT p.id, 'de', NULL, p.created
FROM pages p
WHERE NOT EXISTS (
SELECT 1 FROM page_contents pc WHERE pc.page_id = p.id AND pc.locale = 'de'
);
INSERT INTO `permissions` (`key`, `description`, `active`, `is_system`)
VALUES
('users.view', 'Can view users', 1, 1),
('users.view_meta', 'Can view user status and meta', 1, 1),
('users.view_audit', 'Can view user audit log', 1, 1),
('users.create', 'Can create users', 1, 1),
('users.update', 'Can update users', 1, 1),
('users.access_pdf', 'Can generate onboarding access PDFs', 1, 1),
('users.delete', 'Can delete users', 1, 1),
('users.self_update', 'Can update own user', 1, 1),
('users.update_assignments', 'Can update user assignments', 1, 1),
('address_book.view', 'Can view address book', 1, 1),
('tenants.view', 'Can view tenants', 1, 1),
('tenants.create', 'Can create tenants', 1, 1),
('tenants.update', 'Can update tenants', 1, 1),
('tenants.sso_manage', 'Can manage tenant Microsoft SSO settings', 1, 1),
('tenants.delete', 'Can delete tenants', 1, 1),
('departments.view', 'Can view departments', 1, 1),
('departments.create', 'Can create departments', 1, 1),
('departments.update', 'Can update departments', 1, 1),
('departments.delete', 'Can delete departments', 1, 1),
('departments.import', 'Can import departments', 1, 1),
('roles.view', 'Can view roles', 1, 1),
('roles.create', 'Can create roles', 1, 1),
('roles.update', 'Can update roles', 1, 1),
('roles.delete', 'Can delete roles', 1, 1),
('permissions.view', 'Can view permissions', 1, 1),
('permissions.create', 'Can create permissions', 1, 1),
('permissions.update', 'Can update permissions', 1, 1),
('permissions.delete', 'Can delete permissions', 1, 1),
('settings.view', 'Can view settings', 1, 1),
('settings.update', 'Can update settings', 1, 1),
('tenant.scope.global', 'Can bypass tenant scope globally', 1, 1),
('imports.view', 'Can view imports', 1, 1),
('imports.audit.view', 'Can view import audit logs', 1, 1),
('jobs.view', 'Can view scheduled jobs', 1, 1),
('jobs.manage', 'Can manage scheduled jobs', 1, 1),
('jobs.run_now', 'Can run scheduled jobs manually', 1, 1),
('user_lifecycle_audit.view', 'Can view user lifecycle audit logs', 1, 1),
('users.import', 'Can import users', 1, 1),
('users.import_assignments', 'Can assign tenant, role and department during user import', 1, 1),
('users.lifecycle_restore', 'Can restore users from lifecycle audit', 1, 1),
('custom_fields.manage', 'Can manage tenant custom field definitions', 1, 1),
('custom_fields.edit_values', 'Can edit user custom field values', 1, 1),
('api_docs.view', 'Can view API documentation', 1, 1),
('docs.view', 'Can view developer documentation', 1, 1),
('mail_log.view', 'Can view mail logs', 1, 1),
('api_audit.view', 'Can view API audit logs', 1, 1),
('system_audit.view', 'Can view system audit logs', 1, 1),
('system_audit.purge', 'Can purge system audit logs', 1, 1),
('api_tokens.manage', 'Can manage user API tokens', 1, 1),
('stats.view', 'Can view statistics', 1, 1),
('system_info.view', 'Can view system info and health status', 1, 1),
('roles.assign_all', 'Can assign all roles (bypass assignable-roles check)', 1, 1),
('notifications.view', 'Can view and manage own notifications', 1, 1)
ON DUPLICATE KEY UPDATE
`description` = VALUES(`description`),
`active` = VALUES(`active`),
`is_system` = VALUES(`is_system`);
INSERT INTO `scheduled_jobs` (
`job_key`,
`label`,
`description`,
`enabled`,
`timezone`,
`schedule_type`,
`schedule_interval`,
`schedule_time`,
`schedule_weekdays_csv`,
`catchup_once`,
`next_run_at`
)
VALUES (
'user_lifecycle_run',
'User lifecycle run',
'Runs automatic user deactivate/delete policy',
1,
'Europe/Berlin',
'daily',
1,
'02:15',
NULL,
1,
NULL
)
ON DUPLICATE KEY UPDATE
`label` = VALUES(`label`),
`description` = VALUES(`description`);
INSERT INTO `scheduled_jobs` (
`job_key`,
`label`,
`description`,
`enabled`,
`timezone`,
`schedule_type`,
`schedule_interval`,
`schedule_time`,
`schedule_weekdays_csv`,
`catchup_once`,
`next_run_at`
)
VALUES (
'system_audit_purge',
'System audit purge',
'Purges system audit entries by retention policy',
1,
'Europe/Berlin',
'daily',
1,
'03:00',
NULL,
1,
NULL
)
ON DUPLICATE KEY UPDATE
`label` = VALUES(`label`),
`description` = VALUES(`description`);
INSERT INTO `scheduler_runtime_status` (
`id`,
`last_heartbeat_at`,
`last_result`,
`last_error_code`
)
VALUES (
1,
NOW(),
'ok',
NULL
)
ON DUPLICATE KEY UPDATE
`id` = `id`;
INSERT INTO `role_permissions` (`role_id`, `permission_id`, `created`)
SELECT r.id, p.id, NOW()
FROM roles r
JOIN permissions p ON p.`key` IN (
'users.view', 'users.view_meta', 'users.view_audit', 'users.create', 'users.update', 'users.access_pdf', 'users.delete',
'users.self_update', 'users.update_assignments',
'address_book.view',
'tenants.view', 'tenants.create', 'tenants.update', 'tenants.sso_manage', 'tenants.delete',
'departments.view', 'departments.create', 'departments.update', 'departments.delete', 'departments.import',
'roles.view', 'roles.create', 'roles.update', 'roles.delete',
'permissions.view', 'permissions.create', 'permissions.update', 'permissions.delete',
'settings.view', 'settings.update', 'tenant.scope.global',
'imports.view', 'imports.audit.view', 'jobs.view', 'jobs.manage', 'jobs.run_now',
'user_lifecycle_audit.view', 'users.import', 'users.import_assignments',
'users.lifecycle_restore',
'custom_fields.manage', 'custom_fields.edit_values',
'api_docs.view', 'docs.view',
'api_tokens.manage',
'mail_log.view', 'api_audit.view', 'system_audit.view', 'system_audit.purge',
'stats.view', 'system_info.view',
'roles.assign_all',
'helpdesk.access', 'helpdesk.settings.manage', 'helpdesk.team-workload.view', 'helpdesk.risk-radar.view', 'helpdesk.software-products.manage',
'helpdesk.handovers.view', 'helpdesk.handovers.create', 'helpdesk.handovers.manage',
'helpdesk.updates.view', 'helpdesk.updates.manage'
)
WHERE r.description IN ('Admin', 'Administrator') OR r.id = 1
ON DUPLICATE KEY UPDATE role_id = role_id;
-- Notifications are user-scoped and should be visible to all active roles by default.
INSERT INTO `role_permissions` (`role_id`, `permission_id`, `created`)
SELECT r.id, p.id, NOW()
FROM roles r
JOIN permissions p ON p.`key` = 'notifications.view'
WHERE r.active = 1
ON DUPLICATE KEY UPDATE role_id = role_id;
INSERT INTO `role_permissions` (`role_id`, `permission_id`, `created`)
SELECT r.id, p.id, NOW()
FROM roles r
JOIN permissions p ON p.`key` IN ('users.self_update', 'address_book.view')
WHERE r.code = 'USER' OR r.description = 'User'
ON DUPLICATE KEY UPDATE role_id = role_id;
-- Manager: user & department management
INSERT INTO `role_permissions` (`role_id`, `permission_id`, `created`)
SELECT r.id, p.id, NOW()
FROM roles r
JOIN permissions p ON p.`key` IN (
'users.view', 'users.view_meta', 'users.create', 'users.update',
'users.update_assignments', 'users.access_pdf', 'users.self_update',
'users.import', 'users.import_assignments',
'address_book.view',
'departments.view', 'departments.create', 'departments.update',
'roles.view',
'custom_fields.edit_values'
)
WHERE r.code = 'MANAGER'
ON DUPLICATE KEY UPDATE role_id = role_id;
-- Auditor: read-only + audit logs
INSERT INTO `role_permissions` (`role_id`, `permission_id`, `created`)
SELECT r.id, p.id, NOW()
FROM roles r
JOIN permissions p ON p.`key` IN (
'users.view', 'users.view_meta', 'users.view_audit', 'users.self_update',
'address_book.view',
'tenants.view', 'departments.view', 'roles.view', 'permissions.view',
'settings.view',
'imports.view', 'imports.audit.view',
'user_lifecycle_audit.view',
'mail_log.view', 'api_audit.view', 'system_audit.view',
'stats.view', 'jobs.view'
)
WHERE r.code = 'AUDITOR'
ON DUPLICATE KEY UPDATE role_id = role_id;
-- Support: helpdesk / user support
INSERT INTO `role_permissions` (`role_id`, `permission_id`, `created`)
SELECT r.id, p.id, NOW()
FROM roles r
JOIN permissions p ON p.`key` IN (
'users.view', 'users.view_meta', 'users.view_audit',
'users.update', 'users.self_update',
'address_book.view',
'departments.view', 'roles.view',
'api_tokens.manage',
'custom_fields.edit_values',
'helpdesk.access'
)
WHERE r.code = 'SUPPORT'
ON DUPLICATE KEY UPDATE role_id = role_id;
-- Admin role can assign every role (also has roles.assign_all permission as fallback)
INSERT IGNORE INTO `role_assignable_roles` (`role_id`, `assignable_role_id`)
SELECT admin_role.id, all_roles.id
FROM roles admin_role
CROSS JOIN roles all_roles
WHERE admin_role.description IN ('Admin', 'Administrator') OR admin_role.id = 1;
-- Manager can assign USER and SUPPORT roles
INSERT IGNORE INTO `role_assignable_roles` (`role_id`, `assignable_role_id`)
SELECT mgr.id, target.id
FROM roles mgr
JOIN roles target ON target.code IN ('USER', 'SUPPORT')
WHERE mgr.code = 'MANAGER';
INSERT INTO `settings` (`key`, `value`, `description`)
SELECT 'default_tenant_id', CAST(t.id AS CHAR), 'setting.default_tenant'
FROM tenants t
WHERE t.description = 'MusterMandant'
LIMIT 1
ON DUPLICATE KEY UPDATE
`key` = `key`;
INSERT INTO `settings` (`key`, `value`, `description`)
SELECT 'default_department_id', CAST(d.id AS CHAR), 'setting.default_department'
FROM departments d
JOIN tenants t ON t.id = d.tenant_id
WHERE d.code = 'MUSTER'
AND t.description = 'MusterMandant'
LIMIT 1
ON DUPLICATE KEY UPDATE
`key` = `key`;
INSERT INTO `settings` (`key`, `value`, `description`)
SELECT 'default_role_id', CAST(r.id AS CHAR), 'setting.default_role'
FROM roles r
WHERE r.code = 'USER'
LIMIT 1
ON DUPLICATE KEY UPDATE
`key` = `key`;
INSERT INTO `settings` (`key`, `value`, `description`)
SELECT 'api_cors_allowed_origins', CONCAT('http://localhost:8080', CHAR(10), 'http://127.0.0.1:8080'), 'setting.api_cors_allowed_origins'
ON DUPLICATE KEY UPDATE
`key` = `key`;
INSERT INTO `settings` (`key`, `value`, `description`)
VALUES
('app_title', 'CoreCore', 'setting.app_title'),
('app_locale', 'de', 'setting.app_locale'),
('app_registration', '1', 'setting.app_registration'),
('api_token_default_ttl_days', '90', 'setting.api_token_default_ttl_days'),
('api_token_max_ttl_days', '365', 'setting.api_token_max_ttl_days'),
('session_idle_timeout_minutes', '30', 'setting.session_idle_timeout_minutes'),
('session_absolute_timeout_hours', '8', 'setting.session_absolute_timeout_hours'),
('user_inactivity_deactivate_days', '180', 'setting.user_inactivity_deactivate_days'),
('user_inactivity_delete_days', '365', 'setting.user_inactivity_delete_days'),
('system_audit_enabled', '1', 'setting.system_audit_enabled'),
('system_audit_retention_days', '365', 'setting.system_audit_retention_days'),
('frontend_telemetry_enabled', '1', 'setting.frontend_telemetry_enabled'),
('frontend_telemetry_sample_rate', '0.2', 'setting.frontend_telemetry_sample_rate'),
('frontend_telemetry_allowed_events', 'ajax_error,warn_once', 'setting.frontend_telemetry_allowed_events'),
('microsoft_auto_remember_default', '0', 'setting.microsoft_auto_remember_default'),
('remember_token_lifetime_days', '30', 'setting.remember_token_lifetime_days')
ON DUPLICATE KEY UPDATE
`key` = `key`;
-- =============================================
-- Seed: sample notifications for demo user
-- =============================================
INSERT INTO `user_notifications` (
`recipient_user_id`, `tenant_id`, `type`, `title`, `body`, `link`, `data`, `is_read`, `read_at`, `created`
)
SELECT
u.id,
(SELECT t.id FROM tenants t ORDER BY t.id LIMIT 1),
n.type, n.title, n.body, n.link, n.data, n.is_read, n.read_at, n.created
FROM users u
CROSS JOIN (
SELECT 'user.created' AS type,
'Neuer Benutzer: Max Mustermann' AS title,
NULL AS body,
'admin/users/edit/00000000-0000-0000-0000-000000000001' AS link,
'{"user_id":99}' AS data,
0 AS is_read,
NULL AS read_at,
DATE_SUB(NOW(), INTERVAL 5 MINUTE) AS created
UNION ALL
SELECT 'user.created',
'Neuer Benutzer: Erika Musterfrau',
NULL,
'admin/users/edit/00000000-0000-0000-0000-000000000002',
'{"user_id":98}',
0,
NULL,
DATE_SUB(NOW(), INTERVAL 30 MINUTE)
UNION ALL
SELECT 'user.deleted',
'Benutzer geloescht: Karl Schmidt',
NULL,
NULL,
'{"user_id":97}',
0,
NULL,
DATE_SUB(NOW(), INTERVAL 2 HOUR)
UNION ALL
SELECT 'system',
'Systemwartung abgeschlossen',
'Alle Dienste laufen wieder normal.',
NULL,
NULL,
1,
DATE_SUB(NOW(), INTERVAL 3 HOUR),
DATE_SUB(NOW(), INTERVAL 4 HOUR)
UNION ALL
SELECT 'user.created',
'Neuer Benutzer: Anna Weber',
NULL,
'admin/users/edit/00000000-0000-0000-0000-000000000003',
'{"user_id":96}',
1,
DATE_SUB(NOW(), INTERVAL 1 DAY),
DATE_SUB(NOW(), INTERVAL 2 DAY)
) n
WHERE u.email = 'demo@user.com'
AND NOT EXISTS (SELECT 1 FROM user_notifications un WHERE un.recipient_user_id = u.id);