369 lines
18 KiB
SQL
369 lines
18 KiB
SQL
-- ============================================================================
|
|
-- MijnBesluit - databaseschema voor MariaDB 10.6 of nieuwer
|
|
--
|
|
-- Aanmaken (in cmd, NIET in PowerShell - die kent "<" niet):
|
|
-- mysql -u root -p < 01_schema.sql
|
|
-- In PowerShell of HeidiSQL: zie docs/Website-testen.md.
|
|
--
|
|
-- Het script mag meerdere keren gedraaid worden; bestaande tabellen blijven ongemoeid.
|
|
--
|
|
-- De schemanaam staat hieronder één keer. Wil je een bestaande database
|
|
-- gebruiken, vervang dan `mijnbesluit` overal (of laat CREATE DATABASE weg
|
|
-- en zet er een USE <jouw naam>; voor in de plaats).
|
|
--
|
|
-- Opzet:
|
|
-- * Alles utf8mb4 en InnoDB, zodat namen met accenten en emoji goed gaan.
|
|
-- * Geheimen staan als VARBINARY in de tabel: versleuteld met AES-256-GCM,
|
|
-- nooit leesbaar. Zie docs/Beveiliging.md voor hoe de sleutels lopen.
|
|
-- * Datums worden in UTC bewaard (DATETIME, geen TIMESTAMP), zodat zomertijd
|
|
-- nooit een bezorgmoment kan verschuiven.
|
|
-- ============================================================================
|
|
|
|
CREATE DATABASE IF NOT EXISTS `mijnbesluit`
|
|
DEFAULT CHARACTER SET utf8mb4
|
|
DEFAULT COLLATE utf8mb4_unicode_ci;
|
|
|
|
USE `mijnbesluit`;
|
|
|
|
SET FOREIGN_KEY_CHECKS = 0;
|
|
|
|
-- ---------------------------------------------------------------- personen ---
|
|
-- Een profiel. Kan bestaan zonder account: dan is het aangemaakt door iemand
|
|
-- anders en wacht het op een uitnodiging.
|
|
CREATE TABLE IF NOT EXISTS `persons` (
|
|
`id` BIGINT NOT NULL AUTO_INCREMENT,
|
|
`first_name` VARCHAR(80) NOT NULL DEFAULT '',
|
|
`last_name` VARCHAR(80) NOT NULL DEFAULT '',
|
|
`nickname` VARCHAR(80) NULL,
|
|
`email` VARCHAR(255) NULL,
|
|
`mobile_number` VARCHAR(50) NULL,
|
|
`birth_date` DATE NULL,
|
|
`date_of_death` DATE NULL,
|
|
|
|
-- Wie het profiel aanmaakte (historie) en wie het nu beheert.
|
|
`created_by_user_id` BIGINT NULL,
|
|
`managed_by_user_id` BIGINT NULL,
|
|
|
|
-- Zodra dit gevuld is, is het profiel van de persoon zelf.
|
|
`claimed_by_user_id` BIGINT NULL,
|
|
`claimed_at` DATETIME(3) NULL,
|
|
|
|
-- Aantal meldingen dat nodig is om overlijden te bevestigen, en hoeveel dagen
|
|
-- de betrokkene daarna nog heeft om bezwaar te maken.
|
|
`required_death_declarations` INT NOT NULL DEFAULT 3,
|
|
`death_objection_days` INT NOT NULL DEFAULT 14,
|
|
`death_confirmed_at` DATETIME(3) NULL,
|
|
`death_objected_at` DATETIME(3) NULL,
|
|
|
|
-- Wie heeft gezegd dat deze aantekening niet over hem gaat. Zie db/17_aantekening_afgewezen.sql:
|
|
-- dan komt die vraag niet bij elke volgende keer opnieuw.
|
|
`link_declined_by_user_id` BIGINT NULL,
|
|
`link_declined_at` DATETIME(3) NULL,
|
|
|
|
`created_at` DATETIME(3) NOT NULL,
|
|
`updated_at` DATETIME(3) NOT NULL,
|
|
|
|
PRIMARY KEY (`id`),
|
|
-- Met opzet NIET uniek. Twee mensen mogen allebei hun eigen aantekening over dezelfde
|
|
-- persoon hebben; ze worden pas één zodra die persoon zelf beide uitnodigingen aanneemt.
|
|
-- Zie docs/Identiteit.md en db/15_adres_niet_uniek.sql. Bij `users.email` is uniek juist
|
|
-- wél goed: dat is een account.
|
|
KEY `ix_persons_email` (`email`),
|
|
KEY `ix_persons_claimed_by` (`claimed_by_user_id`),
|
|
KEY `ix_persons_managed_by` (`managed_by_user_id`),
|
|
KEY `ix_persons_death` (`death_confirmed_at`)
|
|
) ENGINE=InnoDB;
|
|
|
|
-- ---------------------------------------------------------------- accounts ---
|
|
CREATE TABLE IF NOT EXISTS `users` (
|
|
`id` BIGINT NOT NULL AUTO_INCREMENT,
|
|
`person_id` BIGINT NOT NULL,
|
|
`email` VARCHAR(255) NOT NULL,
|
|
|
|
`security_stamp` CHAR(36) NOT NULL,
|
|
|
|
-- De datasleutel van deze gebruiker, versleuteld met de mastersleutel.
|
|
`wrapped_data_key` VARBINARY(255) NOT NULL,
|
|
|
|
`status` TINYINT NOT NULL DEFAULT 0, -- 0 actief, 1 geblokkeerd, 2 overleden
|
|
`email_confirmed_at` DATETIME(3) NULL,
|
|
`two_factor_enabled` TINYINT(1) NOT NULL DEFAULT 0,
|
|
`failed_login_count` INT NOT NULL DEFAULT 0,
|
|
`locked_until` DATETIME(3) NULL,
|
|
`last_login_at` DATETIME(3) NULL,
|
|
`created_at` DATETIME(3) NOT NULL,
|
|
`updated_at` DATETIME(3) NOT NULL,
|
|
|
|
PRIMARY KEY (`id`),
|
|
UNIQUE KEY `ux_users_email` (`email`),
|
|
UNIQUE KEY `ux_users_person` (`person_id`),
|
|
CONSTRAINT `fk_users_person` FOREIGN KEY (`person_id`) REFERENCES `persons` (`id`)
|
|
) ENGINE=InnoDB;
|
|
|
|
-- `persons` en `users` verwijzen naar elkaar, dus deze twee sleutels kunnen pas gelegd worden
|
|
-- als beide tabellen bestaan. ALTER TABLE kent geen "IF NOT EXISTS" voor een constraint, dus
|
|
-- wordt er eerst gekeken of hij er al ligt: zo kun je dit script gerust nog een keer draaien.
|
|
SET @fk_claimed := (SELECT COUNT(*) FROM information_schema.TABLE_CONSTRAINTS
|
|
WHERE CONSTRAINT_SCHEMA = DATABASE()
|
|
AND TABLE_NAME = 'persons'
|
|
AND CONSTRAINT_NAME = 'fk_persons_claimed_by');
|
|
|
|
SET @sql := IF(@fk_claimed = 0,
|
|
'ALTER TABLE `persons` ADD CONSTRAINT `fk_persons_claimed_by` FOREIGN KEY (`claimed_by_user_id`) REFERENCES `users` (`id`) ON DELETE SET NULL',
|
|
'DO 0');
|
|
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
|
|
|
SET @fk_managed := (SELECT COUNT(*) FROM information_schema.TABLE_CONSTRAINTS
|
|
WHERE CONSTRAINT_SCHEMA = DATABASE()
|
|
AND TABLE_NAME = 'persons'
|
|
AND CONSTRAINT_NAME = 'fk_persons_managed_by');
|
|
|
|
SET @sql := IF(@fk_managed = 0,
|
|
'ALTER TABLE `persons` ADD CONSTRAINT `fk_persons_managed_by` FOREIGN KEY (`managed_by_user_id`) REFERENCES `users` (`id`) ON DELETE SET NULL',
|
|
'DO 0');
|
|
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
|
|
|
-- ---------------------------------------------------------------- families ---
|
|
CREATE TABLE IF NOT EXISTS `families` (
|
|
`id` BIGINT NOT NULL AUTO_INCREMENT,
|
|
`name` VARCHAR(120) NOT NULL,
|
|
`owner_person_id` BIGINT NOT NULL,
|
|
`is_active` TINYINT(1) NOT NULL DEFAULT 1,
|
|
`created_at` DATETIME(3) NOT NULL,
|
|
`updated_at` DATETIME(3) NOT NULL,
|
|
PRIMARY KEY (`id`),
|
|
KEY `ix_families_owner` (`owner_person_id`),
|
|
CONSTRAINT `fk_families_owner` FOREIGN KEY (`owner_person_id`) REFERENCES `persons` (`id`)
|
|
) ENGINE=InnoDB;
|
|
|
|
CREATE TABLE IF NOT EXISTS `family_members` (
|
|
`id` BIGINT NOT NULL AUTO_INCREMENT,
|
|
`family_id` BIGINT NOT NULL,
|
|
`person_id` BIGINT NOT NULL,
|
|
`relation` INT NOT NULL DEFAULT 0,
|
|
`relation_label` VARCHAR(50) NULL,
|
|
`added_by_user_id` BIGINT NULL,
|
|
`created_at` DATETIME(3) NOT NULL,
|
|
PRIMARY KEY (`id`),
|
|
UNIQUE KEY `ux_family_member` (`family_id`, `person_id`),
|
|
KEY `ix_family_members_person` (`person_id`),
|
|
CONSTRAINT `fk_fm_family` FOREIGN KEY (`family_id`) REFERENCES `families` (`id`) ON DELETE CASCADE,
|
|
CONSTRAINT `fk_fm_person` FOREIGN KEY (`person_id`) REFERENCES `persons` (`id`) ON DELETE CASCADE
|
|
) ENGINE=InnoDB;
|
|
|
|
-- ------------------------------------------------------------ uitnodigingen ---
|
|
-- Alleen de hash van het token staat hier; het token zelf bestaat enkel in de e-mail.
|
|
CREATE TABLE IF NOT EXISTS `invitations` (
|
|
`id` BIGINT NOT NULL AUTO_INCREMENT,
|
|
`person_id` BIGINT NOT NULL,
|
|
`family_id` BIGINT NOT NULL,
|
|
`email` VARCHAR(255) NOT NULL,
|
|
`token_hash` VARBINARY(32) NOT NULL,
|
|
`status` INT NOT NULL DEFAULT 0,
|
|
`created_by_user_id` BIGINT NOT NULL,
|
|
`created_at` DATETIME(3) NOT NULL,
|
|
`sent_at` DATETIME(3) NULL,
|
|
`expires_at` DATETIME(3) NOT NULL,
|
|
`accepted_at` DATETIME(3) NULL,
|
|
`resend_count` INT NOT NULL DEFAULT 0,
|
|
PRIMARY KEY (`id`),
|
|
UNIQUE KEY `ux_invitations_token` (`token_hash`),
|
|
KEY `ix_invitations_person` (`person_id`, `status`),
|
|
CONSTRAINT `fk_inv_person` FOREIGN KEY (`person_id`) REFERENCES `persons` (`id`) ON DELETE CASCADE,
|
|
CONSTRAINT `fk_inv_family` FOREIGN KEY (`family_id`) REFERENCES `families` (`id`) ON DELETE CASCADE
|
|
) ENGINE=InnoDB;
|
|
|
|
-- ------------------------------------------------------------------- kluis ---
|
|
-- Bijlagen: de bytes staan versleuteld buiten de database, hier alleen de verwijzing.
|
|
CREATE TABLE IF NOT EXISTS `vault_files` (
|
|
`id` BIGINT NOT NULL AUTO_INCREMENT,
|
|
`owner_person_id` BIGINT NOT NULL,
|
|
`file_name` VARCHAR(255) NOT NULL,
|
|
`content_type` VARCHAR(120) NOT NULL DEFAULT 'application/octet-stream',
|
|
`size_bytes` BIGINT NOT NULL DEFAULT 0,
|
|
`storage_key` VARCHAR(255) NOT NULL,
|
|
`nonce` VARBINARY(12) NOT NULL,
|
|
`tag` VARBINARY(16) NOT NULL,
|
|
`wrapped_content_key` VARBINARY(96) NOT NULL,
|
|
`sha256` VARBINARY(32) NOT NULL,
|
|
`created_at` DATETIME(3) NOT NULL,
|
|
PRIMARY KEY (`id`),
|
|
KEY `ix_vault_files_owner` (`owner_person_id`),
|
|
CONSTRAINT `fk_vf_owner` FOREIGN KEY (`owner_person_id`) REFERENCES `persons` (`id`) ON DELETE CASCADE
|
|
) ENGINE=InnoDB;
|
|
|
|
-- Kluisitems. `title` en `category` zijn leesbaar zodat er gezocht kan worden;
|
|
-- de inhoud zit versleuteld in `ciphertext`.
|
|
CREATE TABLE IF NOT EXISTS `vault_items` (
|
|
`id` BIGINT NOT NULL AUTO_INCREMENT,
|
|
`owner_person_id` BIGINT NOT NULL,
|
|
`category` INT NOT NULL,
|
|
`title` VARCHAR(150) NOT NULL,
|
|
`ciphertext` LONGBLOB NOT NULL,
|
|
`nonce` VARBINARY(12) NOT NULL,
|
|
`tag` VARBINARY(16) NOT NULL,
|
|
`wrapped_content_key` VARBINARY(96) NOT NULL,
|
|
`release_policy` INT NOT NULL DEFAULT 0, -- 0 alleen ikzelf, 1 na overlijden, 2 op datum
|
|
`release_at` DATETIME(3) NULL,
|
|
`vault_file_id` BIGINT NULL,
|
|
`created_at` DATETIME(3) NOT NULL,
|
|
`updated_at` DATETIME(3) NOT NULL,
|
|
`deleted_at` DATETIME(3) NULL,
|
|
PRIMARY KEY (`id`),
|
|
KEY `ix_vault_items_owner` (`owner_person_id`, `category`),
|
|
KEY `ix_vault_items_release` (`release_policy`, `release_at`),
|
|
CONSTRAINT `fk_vi_owner` FOREIGN KEY (`owner_person_id`) REFERENCES `persons` (`id`) ON DELETE CASCADE,
|
|
CONSTRAINT `fk_vi_file` FOREIGN KEY (`vault_file_id`) REFERENCES `vault_files` (`id`) ON DELETE SET NULL
|
|
) ENGINE=InnoDB;
|
|
|
|
-- Toegang van een ander tot een item: dezelfde contentsleutel, ingepakt met
|
|
-- de datasleutel van die ander. Zonder rij hier is er geen toegang, punt.
|
|
CREATE TABLE IF NOT EXISTS `content_grants` (
|
|
`id` BIGINT NOT NULL AUTO_INCREMENT,
|
|
`resource_type` VARCHAR(30) NOT NULL, -- VaultItem | VaultFile | Message
|
|
`resource_id` BIGINT NOT NULL,
|
|
`user_id` BIGINT NOT NULL,
|
|
`wrapped_content_key` VARBINARY(96) NOT NULL,
|
|
`reason` VARCHAR(50) NOT NULL, -- ontvanger | nalatenschap | gedeeld
|
|
`granted_at` DATETIME(3) NOT NULL,
|
|
`revoked_at` DATETIME(3) NULL,
|
|
PRIMARY KEY (`id`),
|
|
UNIQUE KEY `ux_grant` (`resource_type`, `resource_id`, `user_id`),
|
|
KEY `ix_grants_user` (`user_id`),
|
|
CONSTRAINT `fk_grant_user` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE
|
|
) ENGINE=InnoDB;
|
|
|
|
-- ---------------------------------------------------------------- berichten ---
|
|
CREATE TABLE IF NOT EXISTS `messages` (
|
|
`id` BIGINT NOT NULL AUTO_INCREMENT,
|
|
`from_person_id` BIGINT NOT NULL,
|
|
`to_person_id` BIGINT NOT NULL,
|
|
`kind` INT NOT NULL DEFAULT 0, -- 0 tekst, 1 video, 2 audio, 3 document
|
|
`subject` VARCHAR(200) NOT NULL,
|
|
`ciphertext` LONGBLOB NOT NULL,
|
|
`nonce` VARBINARY(12) NOT NULL,
|
|
`tag` VARBINARY(16) NOT NULL,
|
|
`wrapped_content_key` VARBINARY(96) NOT NULL,
|
|
`vault_file_id` BIGINT NULL,
|
|
`deliver_at` DATETIME(3) NULL,
|
|
`deliver_after_death` TINYINT(1) NOT NULL DEFAULT 0,
|
|
`status` INT NOT NULL DEFAULT 0, -- 0 concept, 1 gepland, 2 bezorgd, 3 gelezen, 4 ingetrokken
|
|
`delivered_at` DATETIME(3) NULL,
|
|
`read_at` DATETIME(3) NULL,
|
|
`created_at` DATETIME(3) NOT NULL,
|
|
`updated_at` DATETIME(3) NOT NULL,
|
|
PRIMARY KEY (`id`),
|
|
KEY `ix_messages_to` (`to_person_id`, `status`),
|
|
KEY `ix_messages_from` (`from_person_id`),
|
|
-- De index waarop de bezorgtaak draait.
|
|
KEY `ix_messages_due` (`status`, `deliver_at`, `deliver_after_death`),
|
|
CONSTRAINT `fk_msg_from` FOREIGN KEY (`from_person_id`) REFERENCES `persons` (`id`) ON DELETE CASCADE,
|
|
CONSTRAINT `fk_msg_to` FOREIGN KEY (`to_person_id`) REFERENCES `persons` (`id`) ON DELETE CASCADE,
|
|
CONSTRAINT `fk_msg_file` FOREIGN KEY (`vault_file_id`) REFERENCES `vault_files` (`id`) ON DELETE SET NULL
|
|
) ENGINE=InnoDB;
|
|
|
|
-- ------------------------------------------------------- overlijden & nalatenschap ---
|
|
-- Eén melding per gebruiker per profiel; dat dwingt de unieke sleutel af.
|
|
CREATE TABLE IF NOT EXISTS `death_declarations` (
|
|
`id` BIGINT NOT NULL AUTO_INCREMENT,
|
|
`person_id` BIGINT NOT NULL,
|
|
`declared_by_user_id` BIGINT NOT NULL,
|
|
`date_of_death` DATE NULL,
|
|
`note` VARCHAR(500) NULL,
|
|
`declared_at` DATETIME(3) NOT NULL,
|
|
`revoked_at` DATETIME(3) NULL,
|
|
PRIMARY KEY (`id`),
|
|
UNIQUE KEY `ux_declaration` (`person_id`, `declared_by_user_id`),
|
|
CONSTRAINT `fk_dd_person` FOREIGN KEY (`person_id`) REFERENCES `persons` (`id`) ON DELETE CASCADE,
|
|
CONSTRAINT `fk_dd_user` FOREIGN KEY (`declared_by_user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE
|
|
) ENGINE=InnoDB;
|
|
|
|
CREATE TABLE IF NOT EXISTS `legacy_managers` (
|
|
`id` BIGINT NOT NULL AUTO_INCREMENT,
|
|
`person_id` BIGINT NOT NULL,
|
|
`manager_person_id` BIGINT NOT NULL,
|
|
`priority` INT NOT NULL DEFAULT 1,
|
|
`accepted_at` DATETIME(3) NULL,
|
|
`access_granted_at` DATETIME(3) NULL,
|
|
`created_at` DATETIME(3) NOT NULL,
|
|
PRIMARY KEY (`id`),
|
|
UNIQUE KEY `ux_legacy_manager` (`person_id`, `manager_person_id`),
|
|
CONSTRAINT `fk_lm_person` FOREIGN KEY (`person_id`) REFERENCES `persons` (`id`) ON DELETE CASCADE,
|
|
CONSTRAINT `fk_lm_manager` FOREIGN KEY (`manager_person_id`) REFERENCES `persons` (`id`) ON DELETE CASCADE
|
|
) ENGINE=InnoDB;
|
|
|
|
-- ------------------------------------------------------------------ sessies ---
|
|
CREATE TABLE IF NOT EXISTS `refresh_tokens` (
|
|
`id` BIGINT NOT NULL AUTO_INCREMENT,
|
|
`user_id` BIGINT NOT NULL,
|
|
`token_hash` VARBINARY(32) NOT NULL,
|
|
`device` VARCHAR(120) NULL,
|
|
`created_by_ip` VARCHAR(45) NULL,
|
|
`created_at` DATETIME(3) NOT NULL,
|
|
`expires_at` DATETIME(3) NOT NULL,
|
|
`revoked_at` DATETIME(3) NULL,
|
|
PRIMARY KEY (`id`),
|
|
UNIQUE KEY `ux_refresh_token` (`token_hash`),
|
|
KEY `ix_refresh_user` (`user_id`, `expires_at`),
|
|
CONSTRAINT `fk_rt_user` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE
|
|
) ENGINE=InnoDB;
|
|
|
|
-- ---------------------------------------------------------------- sessies ---
|
|
-- Een aanmelding van de app. Staat in de database en niet in het geheugen van de SocketServer,
|
|
-- want anders logt elke herstart van de service iedereen uit - en dan is de vingerafdruk waarmee
|
|
-- je de app opent niets waard. Het token zelf staat er niet in, alleen de SHA-256 ervan.
|
|
CREATE TABLE IF NOT EXISTS `sessies` (
|
|
`id` BIGINT NOT NULL AUTO_INCREMENT,
|
|
`token_hash` VARBINARY(32) NOT NULL,
|
|
`user_id` BIGINT NOT NULL,
|
|
`person_id` BIGINT NOT NULL,
|
|
`toestel` VARCHAR(120) NULL,
|
|
`aangemaakt_op` DATETIME(3) NOT NULL,
|
|
`verloopt_op` DATETIME(3) NOT NULL,
|
|
`laatst_gezien` DATETIME(3) NOT NULL,
|
|
PRIMARY KEY (`id`),
|
|
UNIQUE KEY `ux_sessies_token` (`token_hash`),
|
|
KEY `ix_sessies_verloopt` (`verloopt_op`),
|
|
CONSTRAINT `fk_sessies_user` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE
|
|
) ENGINE=InnoDB;
|
|
|
|
-- ------------------------------------------------------------- inlogcodes ---
|
|
-- Er zijn geen wachtwoorden: je logt in met een code die per e-mail komt.
|
|
-- De code staat hier niet leesbaar in, alleen de hash ervan.
|
|
CREATE TABLE IF NOT EXISTS `login_codes` (
|
|
`id` BIGINT NOT NULL AUTO_INCREMENT,
|
|
`email` VARCHAR(255) NOT NULL,
|
|
`purpose` INT NOT NULL DEFAULT 0, -- 0 inloggen, 1 registreren
|
|
`code_hash` VARCHAR(255) NOT NULL,
|
|
`first_name` VARCHAR(80) NULL, -- alleen bij registreren
|
|
`last_name` VARCHAR(80) NULL,
|
|
`created_at` DATETIME(3) NOT NULL,
|
|
`expires_at` DATETIME(3) NOT NULL,
|
|
`consumed_at` DATETIME(3) NULL,
|
|
`attempts` INT NOT NULL DEFAULT 0,
|
|
`requested_from_ip` VARCHAR(45) NULL,
|
|
PRIMARY KEY (`id`),
|
|
-- De index waarop het opzoeken van een openstaande code draait.
|
|
KEY `ix_login_codes_open` (`email`, `purpose`, `expires_at`),
|
|
KEY `ix_login_codes_created` (`email`, `created_at`)
|
|
) ENGINE=InnoDB;
|
|
|
|
-- ----------------------------------------------------------------- logboek ---
|
|
-- Alleen toevoegen, nooit wijzigen. Bij een kluis wil je kunnen nagaan wie wat wanneer deed.
|
|
CREATE TABLE IF NOT EXISTS `audit_log` (
|
|
`id` BIGINT NOT NULL AUTO_INCREMENT,
|
|
`user_id` BIGINT NULL,
|
|
`action` INT NOT NULL,
|
|
`entity_type` VARCHAR(50) NULL,
|
|
`entity_id` BIGINT NULL,
|
|
`ip_address` VARCHAR(45) NULL,
|
|
`user_agent` VARCHAR(255) NULL,
|
|
`details` VARCHAR(1000) NULL,
|
|
`created_at` DATETIME(3) NOT NULL,
|
|
PRIMARY KEY (`id`),
|
|
KEY `ix_audit_user_time` (`user_id`, `created_at`),
|
|
KEY `ix_audit_entity` (`entity_type`, `entity_id`)
|
|
) ENGINE=InnoDB;
|
|
|
|
SET FOREIGN_KEY_CHECKS = 1;
|