Eerste commit
This commit is contained in:
@@ -0,0 +1,368 @@
|
||||
-- ============================================================================
|
||||
-- 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;
|
||||
@@ -0,0 +1,63 @@
|
||||
-- ============================================================================
|
||||
-- Een aparte databasegebruiker voor de applicatie, met alleen de rechten die
|
||||
-- hij nodig heeft: geen CREATE, geen DROP, geen ALTER. Schemawijzigingen doe
|
||||
-- je met een migratie onder je eigen account, niet vanuit de draaiende
|
||||
-- applicatie.
|
||||
--
|
||||
-- Vervang het wachtwoord hieronder en bewaar het niet in de broncode --
|
||||
-- zie docs/Beveiliging.md, "Waar de geheimen staan".
|
||||
--
|
||||
-- Draaien (in cmd, NIET in PowerShell):
|
||||
-- mysql -u root -p < 02_applicatiegebruiker.sql
|
||||
--
|
||||
-- Opnieuw draaien mag en is veilig: CREATE USER IF NOT EXISTS slaat de hele regel over als de
|
||||
-- gebruiker al bestaat, dus het wachtwoord verandert niet. Dat is precies wat je doet als er
|
||||
-- tabellen bij zijn gekomen.
|
||||
--
|
||||
-- LET OP: komt er een tabel bij, dan hoort hij hier ook bij te staan. Zonder recht krijgt de
|
||||
-- applicatie "SELECT command denied" - en dat merk je pas als iemand dat scherm opent. Er is
|
||||
-- een zelftest die elke CREATE TABLE in deze map naast deze lijst legt.
|
||||
--
|
||||
-- En hier, in DIT bestand, en nergens anders. Sommige migratiescripts deelden vroeger zelf
|
||||
-- rechten uit, vlak onder hun CREATE TABLE. Dat leest prettig, maar de scripts vanaf 03 worden
|
||||
-- ook door de uitrolagent gedraaid met een beperkt databaseaccount, en dat mag geen rechten
|
||||
-- uitdelen: de uitrol struikelde er dan op. Rechten uitdelen is inrichten, en dat doet een mens
|
||||
-- één keer als root. De zelftest bewaakt ook dat.
|
||||
-- ============================================================================
|
||||
|
||||
CREATE USER IF NOT EXISTS 'mijnbesluit_app'@'localhost'
|
||||
IDENTIFIED BY 'VERVANG_DIT_WACHTWOORD';
|
||||
|
||||
-- De rechten worden per tabel gegeven en niet in één keer op de hele database.
|
||||
-- Dat is met opzet: rechten in MariaDB zijn optellend, dus een recht dat op
|
||||
-- databaseniveau is gegeven kun je op tabelniveau niet meer afnemen. Wil je
|
||||
-- het logboek onwijzigbaar houden, dan mag UPDATE/DELETE op de database dus
|
||||
-- nooit gegeven worden.
|
||||
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON `mijnbesluit`.`persons` TO 'mijnbesluit_app'@'localhost';
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON `mijnbesluit`.`users` TO 'mijnbesluit_app'@'localhost';
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON `mijnbesluit`.`families` TO 'mijnbesluit_app'@'localhost';
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON `mijnbesluit`.`family_members` TO 'mijnbesluit_app'@'localhost';
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON `mijnbesluit`.`invitations` TO 'mijnbesluit_app'@'localhost';
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON `mijnbesluit`.`vault_items` TO 'mijnbesluit_app'@'localhost';
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON `mijnbesluit`.`vault_files` TO 'mijnbesluit_app'@'localhost';
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON `mijnbesluit`.`content_grants` TO 'mijnbesluit_app'@'localhost';
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON `mijnbesluit`.`messages` TO 'mijnbesluit_app'@'localhost';
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON `mijnbesluit`.`death_declarations` TO 'mijnbesluit_app'@'localhost';
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON `mijnbesluit`.`legacy_managers` TO 'mijnbesluit_app'@'localhost';
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON `mijnbesluit`.`refresh_tokens` TO 'mijnbesluit_app'@'localhost';
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON `mijnbesluit`.`login_codes` TO 'mijnbesluit_app'@'localhost';
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON `mijnbesluit`.`toestellen` TO 'mijnbesluit_app'@'localhost';
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON `mijnbesluit`.`samenvoegingen` TO 'mijnbesluit_app'@'localhost';
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON `mijnbesluit`.`adverteerders` TO 'mijnbesluit_app'@'localhost';
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON `mijnbesluit`.`campagnes` TO 'mijnbesluit_app'@'localhost';
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON `mijnbesluit`.`campagne_uren` TO 'mijnbesluit_app'@'localhost';
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON `mijnbesluit`.`sessies` TO 'mijnbesluit_app'@'localhost';
|
||||
|
||||
-- Het logboek mag er alleen bij komen en uit gelezen worden, niet uit verdwijnen.
|
||||
GRANT SELECT, INSERT ON `mijnbesluit`.`audit_log` TO 'mijnbesluit_app'@'localhost';
|
||||
|
||||
FLUSH PRIVILEGES;
|
||||
|
||||
-- Controle: audit_log hoort alleen SELECT en INSERT te hebben, de rest alles behalve DDL.
|
||||
SHOW GRANTS FOR 'mijnbesluit_app'@'localhost';
|
||||
@@ -0,0 +1,58 @@
|
||||
-- ============================================================================
|
||||
-- Overstap van wachtwoorden naar inloggen met een code per e-mail.
|
||||
--
|
||||
-- Draai dit op een database die met een eerdere versie van 01_schema.sql is
|
||||
-- aangemaakt. Op een verse database is dit niet nodig: 01_schema.sql bevat
|
||||
-- alles al.
|
||||
--
|
||||
-- Draaien (in cmd, NIET in PowerShell), of in HeidiSQL via
|
||||
-- Bestand -> SQL-bestand uitvoeren:
|
||||
-- mysql -u root -p < 03_inloggen_met_code.sql
|
||||
--
|
||||
-- Het script mag meerdere keren gedraaid worden.
|
||||
-- ============================================================================
|
||||
|
||||
USE `mijnbesluit`;
|
||||
|
||||
-- 1. De tabel met eenmalige codes.
|
||||
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,
|
||||
`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`),
|
||||
KEY `ix_login_codes_open` (`email`, `purpose`, `expires_at`),
|
||||
KEY `ix_login_codes_created` (`email`, `created_at`)
|
||||
) ENGINE=InnoDB;
|
||||
|
||||
-- 2. De wachtwoordkolom weg. ALTER TABLE kent geen "DROP COLUMN IF EXISTS" in
|
||||
-- elke MariaDB-versie, dus eerst kijken of hij er staat.
|
||||
SET @kolom := (SELECT COUNT(*) FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE()
|
||||
AND TABLE_NAME = 'users'
|
||||
AND COLUMN_NAME = 'password_hash');
|
||||
|
||||
SET @sql := IF(@kolom > 0, 'ALTER TABLE `users` DROP COLUMN `password_hash`', 'DO 0');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- 3. De rechten voor de applicatiegebruiker staan NIET hier maar in 02_applicatiegebruiker.sql.
|
||||
--
|
||||
-- Waarom: de scripts vanaf 03 worden ook door de uitrolagent gedraaid, met een beperkt
|
||||
-- databaseaccount dat geen rechten mág uitdelen. Een GRANT hier laat die hele uitrol struikelen,
|
||||
-- en dat account meer macht geven is de verkeerde kant op. Rechten uitdelen is inrichten; dat
|
||||
-- doet een mens één keer als root. Een zelfcontrole legt elke CREATE TABLE in deze map naast de
|
||||
-- lijst in 02, dus vergeten kan niet ongemerkt.
|
||||
|
||||
-- Controle.
|
||||
SELECT 'login_codes' AS tabel,
|
||||
(SELECT COUNT(*) FROM information_schema.TABLES
|
||||
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'login_codes') AS bestaat,
|
||||
(SELECT COUNT(*) FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'users' AND COLUMN_NAME = 'password_hash') AS wachtwoordkolom_nog_aanwezig;
|
||||
@@ -0,0 +1,55 @@
|
||||
-- MijnBesluit - bijlagen bij kluisitems
|
||||
--
|
||||
-- Voegt twee kolommen toe aan `vault_files`:
|
||||
-- vault_item_id : bij welk kluisitem dit bestand hoort. Leeg bij een bestand dat op zichzelf
|
||||
-- staat, zoals de opname bij een videobericht.
|
||||
-- deleted_at : gevuld zodra het bestand verwijderd is.
|
||||
--
|
||||
-- Draaien met:
|
||||
-- mysql -u root -p mijnbesluit < db\04_bijlagen.sql
|
||||
--
|
||||
-- Opnieuw draaien mag: er gebeurt dan niets.
|
||||
|
||||
USE `mijnbesluit`;
|
||||
|
||||
-- ---------------------------------------------------------------- vault_item_id
|
||||
SET @bestaat := (
|
||||
SELECT COUNT(*) FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'vault_files' AND COLUMN_NAME = 'vault_item_id');
|
||||
|
||||
SET @sql := IF(@bestaat = 0,
|
||||
'ALTER TABLE `vault_files` ADD COLUMN `vault_item_id` BIGINT NULL AFTER `owner_person_id`',
|
||||
'SELECT "vault_item_id bestaat al" AS melding');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- ---------------------------------------------------------------- deleted_at
|
||||
SET @bestaat := (
|
||||
SELECT COUNT(*) FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'vault_files' AND COLUMN_NAME = 'deleted_at');
|
||||
|
||||
SET @sql := IF(@bestaat = 0,
|
||||
'ALTER TABLE `vault_files` ADD COLUMN `deleted_at` DATETIME(3) NULL',
|
||||
'SELECT "deleted_at bestaat al" AS melding');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- ---------------------------------------------------------------- index + relatie
|
||||
SET @bestaat := (
|
||||
SELECT COUNT(*) FROM information_schema.STATISTICS
|
||||
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'vault_files' AND INDEX_NAME = 'ix_vault_files_item');
|
||||
|
||||
SET @sql := IF(@bestaat = 0,
|
||||
'CREATE INDEX `ix_vault_files_item` ON `vault_files` (`vault_item_id`)',
|
||||
'SELECT "index bestaat al" AS melding');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- Verdwijnt het kluisitem, dan verdwijnt de koppeling; het bestand zelf ruimt de applicatie op.
|
||||
SET @bestaat := (
|
||||
SELECT COUNT(*) FROM information_schema.TABLE_CONSTRAINTS
|
||||
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'vault_files' AND CONSTRAINT_NAME = 'fk_vf_item');
|
||||
|
||||
SET @sql := IF(@bestaat = 0,
|
||||
'ALTER TABLE `vault_files` ADD CONSTRAINT `fk_vf_item` FOREIGN KEY (`vault_item_id`) REFERENCES `vault_items` (`id`) ON DELETE SET NULL',
|
||||
'SELECT "fk_vf_item bestaat al" AS melding');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
SELECT 'Klaar: bijlagen toegevoegd aan vault_files.' AS melding;
|
||||
@@ -0,0 +1,38 @@
|
||||
-- MijnBesluit - toestellen voor push-meldingen
|
||||
--
|
||||
-- Elke app meldt zich met een kenmerk van Firebase (Android) of APNs (iOS). Daarmee kan de
|
||||
-- SocketServer een melding sturen als er een bericht wordt bezorgd, ook als de app dicht is.
|
||||
--
|
||||
-- Draaien met:
|
||||
-- mysql -u root -p mijnbesluit < db\05_meldingen.sql
|
||||
--
|
||||
-- Opnieuw draaien mag: er gebeurt dan niets.
|
||||
|
||||
USE `mijnbesluit`;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `toestellen` (
|
||||
`id` BIGINT NOT NULL AUTO_INCREMENT,
|
||||
`user_id` BIGINT NOT NULL,
|
||||
-- Het kenmerk van Google is lang; 512 is ruim genoeg en past nog in een unieke index.
|
||||
`kenmerk` VARCHAR(512) NOT NULL,
|
||||
`platform` VARCHAR(20) NOT NULL DEFAULT 'android',
|
||||
`naam` VARCHAR(120) NULL,
|
||||
`aangemeld_op` DATETIME(3) NOT NULL,
|
||||
`laatst_gezien` DATETIME(3) NOT NULL,
|
||||
`afgemeld_op` DATETIME(3) NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
-- Eén kenmerk hoort bij één toestel. Wisselt de gebruiker, dan verhuist de regel mee.
|
||||
UNIQUE KEY `ux_toestellen_kenmerk` (`kenmerk`),
|
||||
KEY `ix_toestellen_user` (`user_id`),
|
||||
CONSTRAINT `fk_toestel_user` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB;
|
||||
|
||||
-- De rechten voor de applicatiegebruiker staan NIET hier maar in 02_applicatiegebruiker.sql.
|
||||
--
|
||||
-- Waarom: de scripts vanaf 03 worden ook door de uitrolagent gedraaid, met een beperkt
|
||||
-- databaseaccount dat geen rechten mág uitdelen. Een GRANT hier laat die hele uitrol struikelen,
|
||||
-- en dat account meer macht geven is de verkeerde kant op. Rechten uitdelen is inrichten; dat
|
||||
-- doet een mens één keer als root. Een zelfcontrole legt elke CREATE TABLE in deze map naast de
|
||||
-- lijst in 02, dus vergeten kan niet ongemerkt.
|
||||
|
||||
SELECT 'Klaar: tabel toestellen aangemaakt.' AS melding;
|
||||
@@ -0,0 +1,22 @@
|
||||
-- MijnBesluit - instelling voor het overzicht
|
||||
--
|
||||
-- Voegt aan `users` toe hoeveel dagen vooruit iemand op zijn overzicht wil zien wat eraan komt.
|
||||
-- 0 betekent: niets tonen. Standaard 30, maximaal 60.
|
||||
--
|
||||
-- Draaien met:
|
||||
-- mysql -u root -p mijnbesluit < db\06_vooruitblik.sql
|
||||
--
|
||||
-- Opnieuw draaien mag: er gebeurt dan niets.
|
||||
|
||||
USE `mijnbesluit`;
|
||||
|
||||
SET @bestaat := (
|
||||
SELECT COUNT(*) FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'users' AND COLUMN_NAME = 'vooruitblik_dagen');
|
||||
|
||||
SET @sql := IF(@bestaat = 0,
|
||||
'ALTER TABLE `users` ADD COLUMN `vooruitblik_dagen` INT NOT NULL DEFAULT 30',
|
||||
'SELECT "vooruitblik_dagen bestaat al" AS melding');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
SELECT 'Klaar: vooruitblik-instelling toegevoegd aan users.' AS melding;
|
||||
@@ -0,0 +1,89 @@
|
||||
-- MijnBesluit - foto's bij personen en berichten
|
||||
--
|
||||
-- Tot nu toe kon een bestand alleen bij een kluisitem horen. Er komen twee koppelingen bij,
|
||||
-- plus een vinkje voor de profielfoto:
|
||||
--
|
||||
-- subject_person_id : bij welke persoon deze foto hoort. Let op het verschil met
|
||||
-- owner_person_id: dat is wie de foto bewaart, dit is wie erop staat.
|
||||
-- message_id : bij welk bericht deze foto hoort.
|
||||
-- is_profile_photo : dit is de foto die bij de persoon op de kaart komt te staan.
|
||||
-- Er is er hoogstens een per persoon; de applicatie bewaakt dat.
|
||||
--
|
||||
-- Draaien met:
|
||||
-- mysql -u root -p mijnbesluit < db\07_fotos.sql
|
||||
--
|
||||
-- Opnieuw draaien mag: er gebeurt dan niets.
|
||||
|
||||
USE `mijnbesluit`;
|
||||
|
||||
-- ---------------------------------------------------------------- subject_person_id
|
||||
SET @bestaat := (
|
||||
SELECT COUNT(*) FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'vault_files' AND COLUMN_NAME = 'subject_person_id');
|
||||
|
||||
SET @sql := IF(@bestaat = 0,
|
||||
'ALTER TABLE `vault_files` ADD COLUMN `subject_person_id` BIGINT NULL AFTER `vault_item_id`',
|
||||
'SELECT "subject_person_id bestaat al" AS melding');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- ---------------------------------------------------------------- message_id
|
||||
SET @bestaat := (
|
||||
SELECT COUNT(*) FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'vault_files' AND COLUMN_NAME = 'message_id');
|
||||
|
||||
SET @sql := IF(@bestaat = 0,
|
||||
'ALTER TABLE `vault_files` ADD COLUMN `message_id` BIGINT NULL AFTER `subject_person_id`',
|
||||
'SELECT "message_id bestaat al" AS melding');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- ---------------------------------------------------------------- is_profile_photo
|
||||
SET @bestaat := (
|
||||
SELECT COUNT(*) FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'vault_files' AND COLUMN_NAME = 'is_profile_photo');
|
||||
|
||||
SET @sql := IF(@bestaat = 0,
|
||||
'ALTER TABLE `vault_files` ADD COLUMN `is_profile_photo` TINYINT(1) NOT NULL DEFAULT 0',
|
||||
'SELECT "is_profile_photo bestaat al" AS melding');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- ---------------------------------------------------------------- indexen
|
||||
SET @bestaat := (
|
||||
SELECT COUNT(*) FROM information_schema.STATISTICS
|
||||
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'vault_files' AND INDEX_NAME = 'ix_vault_files_subject');
|
||||
|
||||
SET @sql := IF(@bestaat = 0,
|
||||
'CREATE INDEX `ix_vault_files_subject` ON `vault_files` (`subject_person_id`)',
|
||||
'SELECT "ix_vault_files_subject bestaat al" AS melding');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
SET @bestaat := (
|
||||
SELECT COUNT(*) FROM information_schema.STATISTICS
|
||||
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'vault_files' AND INDEX_NAME = 'ix_vault_files_message');
|
||||
|
||||
SET @sql := IF(@bestaat = 0,
|
||||
'CREATE INDEX `ix_vault_files_message` ON `vault_files` (`message_id`)',
|
||||
'SELECT "ix_vault_files_message bestaat al" AS melding');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- ---------------------------------------------------------------- relaties
|
||||
-- Verdwijnt de persoon of het bericht, dan verdwijnt de koppeling; het bestand zelf ruimt de
|
||||
-- applicatie op, want de versleutelde inhoud staat buiten de database.
|
||||
SET @bestaat := (
|
||||
SELECT COUNT(*) FROM information_schema.TABLE_CONSTRAINTS
|
||||
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'vault_files' AND CONSTRAINT_NAME = 'fk_vf_subject');
|
||||
|
||||
SET @sql := IF(@bestaat = 0,
|
||||
'ALTER TABLE `vault_files` ADD CONSTRAINT `fk_vf_subject` FOREIGN KEY (`subject_person_id`) REFERENCES `persons` (`id`) ON DELETE SET NULL',
|
||||
'SELECT "fk_vf_subject bestaat al" AS melding');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
SET @bestaat := (
|
||||
SELECT COUNT(*) FROM information_schema.TABLE_CONSTRAINTS
|
||||
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'vault_files' AND CONSTRAINT_NAME = 'fk_vf_message');
|
||||
|
||||
SET @sql := IF(@bestaat = 0,
|
||||
'ALTER TABLE `vault_files` ADD CONSTRAINT `fk_vf_message` FOREIGN KEY (`message_id`) REFERENCES `messages` (`id`) ON DELETE SET NULL',
|
||||
'SELECT "fk_vf_message bestaat al" AS melding');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
SELECT 'Klaar: foto''s bij personen en berichten toegevoegd aan vault_files.' AS melding;
|
||||
@@ -0,0 +1,55 @@
|
||||
-- MijnBesluit - kleine versie van een foto, om in lijstjes te tonen
|
||||
--
|
||||
-- Een profielfoto op de kaart van een persoon is een plaatje van hooguit veertig pixels. De
|
||||
-- foto zelf is al gauw een paar honderd kilobyte, en tien personen in een lijst zijn dan een
|
||||
-- paar megabyte - over een mobiele verbinding merk je dat meteen.
|
||||
--
|
||||
-- Daarom slaan we bij een foto meteen een kleine versie op. Die is versleuteld met dezelfde
|
||||
-- sleutel als de foto zelf, dus er staat niets leesbaars in de database:
|
||||
--
|
||||
-- thumbnail : de kleine versie, versleuteld
|
||||
-- thumbnail_nonce : het eenmalige getal van die versleuteling
|
||||
-- thumbnail_tag : het echtheidskenmerk van die versleuteling
|
||||
--
|
||||
-- Alle drie leeg betekent: er is geen kleine versie. Dat is geen fout - bij een document of een
|
||||
-- video is er niets te verkleinen, en bij foto's van voor deze wijziging is hij er nog niet.
|
||||
-- Het scherm valt dan terug op de beginletters, precies zoals het zonder foto ook doet.
|
||||
--
|
||||
-- Draaien met:
|
||||
-- mysql -u root -p mijnbesluit < db\08_thumbnails.sql
|
||||
--
|
||||
-- Opnieuw draaien mag: er gebeurt dan niets.
|
||||
|
||||
USE `mijnbesluit`;
|
||||
|
||||
-- ---------------------------------------------------------------- thumbnail
|
||||
SET @bestaat := (
|
||||
SELECT COUNT(*) FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'vault_files' AND COLUMN_NAME = 'thumbnail');
|
||||
|
||||
SET @sql := IF(@bestaat = 0,
|
||||
'ALTER TABLE `vault_files` ADD COLUMN `thumbnail` BLOB NULL',
|
||||
'SELECT "thumbnail bestaat al" AS melding');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- ---------------------------------------------------------------- thumbnail_nonce
|
||||
SET @bestaat := (
|
||||
SELECT COUNT(*) FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'vault_files' AND COLUMN_NAME = 'thumbnail_nonce');
|
||||
|
||||
SET @sql := IF(@bestaat = 0,
|
||||
'ALTER TABLE `vault_files` ADD COLUMN `thumbnail_nonce` VARBINARY(12) NULL',
|
||||
'SELECT "thumbnail_nonce bestaat al" AS melding');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- ---------------------------------------------------------------- thumbnail_tag
|
||||
SET @bestaat := (
|
||||
SELECT COUNT(*) FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'vault_files' AND COLUMN_NAME = 'thumbnail_tag');
|
||||
|
||||
SET @sql := IF(@bestaat = 0,
|
||||
'ALTER TABLE `vault_files` ADD COLUMN `thumbnail_tag` VARBINARY(16) NULL',
|
||||
'SELECT "thumbnail_tag bestaat al" AS melding');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
SELECT 'Klaar. De kleine versies worden vanaf nu bij het opslaan van een foto meegemaakt.' AS melding;
|
||||
@@ -0,0 +1,54 @@
|
||||
-- MijnBesluit - profielen samenvoegen
|
||||
--
|
||||
-- Twee mensen kunnen allebei een profiel aanmaken voor dezelfde persoon. Jij voegt je dochter
|
||||
-- toe, je ex-partner doet dat ook: twee rijen in `persons`, allebei een eerlijke aantekening
|
||||
-- van degene die hem maakte. Dat is geen fout, zolang niemand het profiel heeft geclaimd.
|
||||
--
|
||||
-- Zodra zij beide uitnodigingen aanneemt, zegt zij zelf dat het om haar gaat. Dan gaat het ene
|
||||
-- profiel op in het andere. Het duplicaat blijft staan - er hangt geschiedenis aan, en een
|
||||
-- verwijderde rij is niet terug te halen - maar wijst voortaan naar waar hij in is opgegaan:
|
||||
--
|
||||
-- merged_into_person_id : het profiel waarin deze is opgegaan. Leeg = gewoon in gebruik.
|
||||
--
|
||||
-- Alles wat naar het duplicaat verwees is op dat moment verhuisd: lidmaatschappen, berichten,
|
||||
-- foto's, uitnodigingen en rollen in een nalatenschap.
|
||||
--
|
||||
-- Draaien met:
|
||||
-- mysql -u root -p mijnbesluit < db\09_samenvoegen.sql
|
||||
--
|
||||
-- Opnieuw draaien mag: er gebeurt dan niets.
|
||||
|
||||
USE `mijnbesluit`;
|
||||
|
||||
-- ---------------------------------------------------------------- merged_into_person_id
|
||||
SET @bestaat := (
|
||||
SELECT COUNT(*) FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'persons' AND COLUMN_NAME = 'merged_into_person_id');
|
||||
|
||||
SET @sql := IF(@bestaat = 0,
|
||||
'ALTER TABLE `persons` ADD COLUMN `merged_into_person_id` BIGINT NULL',
|
||||
'SELECT "merged_into_person_id bestaat al" AS melding');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- ---------------------------------------------------------------- index
|
||||
-- Elke lijst met mensen filtert hierop, dus hij wordt vaak gelezen.
|
||||
SET @bestaat := (
|
||||
SELECT COUNT(*) FROM information_schema.STATISTICS
|
||||
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'persons' AND INDEX_NAME = 'ix_persons_merged');
|
||||
|
||||
SET @sql := IF(@bestaat = 0,
|
||||
'CREATE INDEX `ix_persons_merged` ON `persons` (`merged_into_person_id`)',
|
||||
'SELECT "ix_persons_merged bestaat al" AS melding');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- ---------------------------------------------------------------- verwijzing naar zichzelf
|
||||
SET @bestaat := (
|
||||
SELECT COUNT(*) FROM information_schema.TABLE_CONSTRAINTS
|
||||
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'persons' AND CONSTRAINT_NAME = 'fk_persons_merged');
|
||||
|
||||
SET @sql := IF(@bestaat = 0,
|
||||
'ALTER TABLE `persons` ADD CONSTRAINT `fk_persons_merged` FOREIGN KEY (`merged_into_person_id`) REFERENCES `persons` (`id`) ON DELETE SET NULL',
|
||||
'SELECT "fk_persons_merged bestaat al" AS melding');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
SELECT 'Klaar. Profielen kunnen vanaf nu worden samengevoegd.' AS melding;
|
||||
@@ -0,0 +1,53 @@
|
||||
-- MijnBesluit - bezwaar maken zonder in te loggen
|
||||
--
|
||||
-- Als er gemeld wordt dat je bent overleden, krijg je daar bericht van met de vraag of dat
|
||||
-- klopt. Tot nu toe moest je inloggen om te zeggen dat je er nog bent. Dat gaat mis op precies
|
||||
-- het moment dat het ertoe doet: zodra het overlijden bevestigd is, staat je account op
|
||||
-- "overleden" en kun je niet meer inloggen. Dan is er geen weg terug.
|
||||
--
|
||||
-- Daarom gaat er voortaan een link mee in die e-mail, die werkt zonder inloggen - net als een
|
||||
-- uitnodiging. Alleen de versleutelde vorm van dat kenmerk staat hier; de link zelf staat
|
||||
-- uitsluitend in de e-mail.
|
||||
--
|
||||
-- objection_token_hash : SHA-256 van het kenmerk uit de link
|
||||
-- objection_token_expires : tot wanneer die link werkt
|
||||
--
|
||||
-- Draaien met:
|
||||
-- mysql -u root -p mijnbesluit < db\10_bezwaarlink.sql
|
||||
--
|
||||
-- Opnieuw draaien mag: er gebeurt dan niets.
|
||||
|
||||
USE `mijnbesluit`;
|
||||
|
||||
-- ---------------------------------------------------------------- objection_token_hash
|
||||
SET @bestaat := (
|
||||
SELECT COUNT(*) FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'persons' AND COLUMN_NAME = 'objection_token_hash');
|
||||
|
||||
SET @sql := IF(@bestaat = 0,
|
||||
'ALTER TABLE `persons` ADD COLUMN `objection_token_hash` VARBINARY(32) NULL',
|
||||
'SELECT "objection_token_hash bestaat al" AS melding');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- ---------------------------------------------------------------- objection_token_expires
|
||||
SET @bestaat := (
|
||||
SELECT COUNT(*) FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'persons' AND COLUMN_NAME = 'objection_token_expires');
|
||||
|
||||
SET @sql := IF(@bestaat = 0,
|
||||
'ALTER TABLE `persons` ADD COLUMN `objection_token_expires` DATETIME(3) NULL',
|
||||
'SELECT "objection_token_expires bestaat al" AS melding');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- ---------------------------------------------------------------- index
|
||||
-- De link wordt opgezocht op dit kenmerk, dus zonder index is dat een tabelscan.
|
||||
SET @bestaat := (
|
||||
SELECT COUNT(*) FROM information_schema.STATISTICS
|
||||
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'persons' AND INDEX_NAME = 'ix_persons_objection');
|
||||
|
||||
SET @sql := IF(@bestaat = 0,
|
||||
'CREATE INDEX `ix_persons_objection` ON `persons` (`objection_token_hash`)',
|
||||
'SELECT "ix_persons_objection bestaat al" AS melding');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
SELECT 'Klaar. De waarschuwingsmail krijgt vanaf nu een bezwaarlink.' AS melding;
|
||||
@@ -0,0 +1,37 @@
|
||||
-- MijnBesluit - een beheerder
|
||||
--
|
||||
-- Er is één situatie waar niemand meer uit komt: een overlijden dat ten onrechte is bevestigd,
|
||||
-- terwijl de bezwaarlink uit de waarschuwingsmail is verlopen. Het account staat dan op
|
||||
-- "overleden", inloggen kan niet meer, en de enige weg terug is weg.
|
||||
--
|
||||
-- Daarvoor is een beheerder. Bewust zo klein mogelijk gehouden:
|
||||
--
|
||||
-- * een beheerder kan een profiel opzoeken op het VOLLEDIGE e-mailadres, niet op naam. Zoeken
|
||||
-- op naam zou een zoekmachine op mensen zijn - zie docs/Identiteit.md.
|
||||
-- * een beheerder kan een bevestigd overlijden terugdraaien, met een reden die wordt vastgelegd.
|
||||
-- * een beheerder kan GEEN kluis openen. Die handeling geeft nooit toegang, hij neemt alleen
|
||||
-- toegang weg.
|
||||
--
|
||||
-- Beheerder word je alleen hier, met de hand:
|
||||
-- UPDATE `users` SET `is_beheerder` = 1 WHERE `email` = 'jij@voorbeeld.nl';
|
||||
--
|
||||
-- Er is met opzet geen scherm om iemand beheerder te maken. Wat niet via de applicatie kan,
|
||||
-- kan ook niet via een lek in de applicatie.
|
||||
--
|
||||
-- Draaien met:
|
||||
-- mysql -u root -p mijnbesluit < db\11_beheerder.sql
|
||||
--
|
||||
-- Opnieuw draaien mag: er gebeurt dan niets.
|
||||
|
||||
USE `mijnbesluit`;
|
||||
|
||||
SET @bestaat := (
|
||||
SELECT COUNT(*) FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'users' AND COLUMN_NAME = 'is_beheerder');
|
||||
|
||||
SET @sql := IF(@bestaat = 0,
|
||||
'ALTER TABLE `users` ADD COLUMN `is_beheerder` TINYINT(1) NOT NULL DEFAULT 0',
|
||||
'SELECT "is_beheerder bestaat al" AS melding');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
SELECT 'Klaar. Maak iemand beheerder met: UPDATE `users` SET `is_beheerder` = 1 WHERE `email` = ...' AS melding;
|
||||
@@ -0,0 +1,51 @@
|
||||
-- MijnBesluit - het bonnetje van een samenvoeging
|
||||
--
|
||||
-- Twee profielen van dezelfde persoon worden er één zodra zij zelf beide uitnodigingen aanneemt.
|
||||
-- Tot nu toe was dat onomkeerbaar, en dat stond ook zo in docs/Identiteit.md bij "wat hier nog
|
||||
-- niet is opgelost".
|
||||
--
|
||||
-- Waarom terugdraaien niet zomaar kan: na afloop is niet meer te zien wélke lidmaatschappen,
|
||||
-- berichten en bestanden bij het duplicaat hoorden. Ze staan gewoon bij het doel, tussen alles
|
||||
-- wat daar al stond. Gokken zou andermans gegevens meesleuren.
|
||||
--
|
||||
-- Daarom deze tabel. Bij elke samenvoeging komt hier één regel in te staan met precies wat er is
|
||||
-- verhuisd, als JSON. Terugdraaien is dan die lijst in omgekeerde volgorde aflopen. Regels die
|
||||
-- bij het samenvoegen zijn weggegooid - een dubbel lidmaatschap, een dubbele beheerder - staan
|
||||
-- er als volledige kopie in, want die moeten opnieuw worden aangemaakt.
|
||||
--
|
||||
-- De regel blijft ook na het terugdraaien staan, met undone_at gevuld. Dat er ooit is
|
||||
-- samengevoegd is zelf ook geschiedenis.
|
||||
--
|
||||
-- Let op: samenvoegingen van VOOR deze tabel hebben geen bonnetje en zijn dus niet terug te
|
||||
-- draaien. Dat is geen fout die te repareren valt; de gegevens om het mee te doen bestaan niet.
|
||||
--
|
||||
-- Draaien met:
|
||||
-- mysql -u root -p mijnbesluit < db\12_samenvoegingen.sql
|
||||
--
|
||||
-- Opnieuw draaien mag: er gebeurt dan niets.
|
||||
|
||||
USE `mijnbesluit`;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `samenvoegingen` (
|
||||
`id` BIGINT NOT NULL AUTO_INCREMENT,
|
||||
`duplicate_person_id` BIGINT NOT NULL,
|
||||
`target_person_id` BIGINT NOT NULL,
|
||||
`by_user_id` BIGINT NOT NULL,
|
||||
`merged_at` DATETIME(6) NOT NULL,
|
||||
`steps` LONGTEXT NOT NULL,
|
||||
`undone_at` DATETIME(6) NULL,
|
||||
`undone_by_user_id` BIGINT NULL,
|
||||
`reason` VARCHAR(500) NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `ix_samenvoegingen_duplicaat` (`duplicate_person_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
-- De rechten voor de applicatiegebruiker staan NIET hier maar in 02_applicatiegebruiker.sql.
|
||||
--
|
||||
-- Waarom: de scripts vanaf 03 worden ook door de uitrolagent gedraaid, met een beperkt
|
||||
-- databaseaccount dat geen rechten mág uitdelen. Een GRANT hier laat die hele uitrol struikelen,
|
||||
-- en dat account meer macht geven is de verkeerde kant op. Rechten uitdelen is inrichten; dat
|
||||
-- doet een mens één keer als root. Een zelfcontrole legt elke CREATE TABLE in deze map naast de
|
||||
-- lijst in 02, dus vergeten kan niet ongemerkt.
|
||||
|
||||
SELECT 'Klaar. Samenvoegingen worden vanaf nu vastgelegd en kunnen worden teruggedraaid.' AS melding;
|
||||
@@ -0,0 +1,123 @@
|
||||
-- ============================================================================
|
||||
-- Adverteren op MijnBesluit: adverteerders, campagnes en tellers.
|
||||
--
|
||||
-- Lees docs/Adverteren.md voordat je hier iets aan verandert. Daar staat waarom de balk laag
|
||||
-- is, hoe er wordt gekozen wie er getoond wordt, en waarom goedkeuring vooraf gaat.
|
||||
--
|
||||
-- Draaien met:
|
||||
-- mysql -u root -p mijnbesluit < db\13_adverteren.sql
|
||||
--
|
||||
-- Opnieuw draaien mag: er gebeurt dan niets.
|
||||
-- ============================================================================
|
||||
|
||||
USE `mijnbesluit`;
|
||||
|
||||
-- ------------------------------------------------------------ adverteerders ---
|
||||
-- Een bedrijf dat adverteert. Bewust een eigen tabel en géén regel in `users`:
|
||||
--
|
||||
-- * Een adverteerder is een bedrijf, geen persoon met een kluis, familie of nalatenschap.
|
||||
-- Hem in `users` zetten zou betekenen dat elke query over gebruikers hem moet uitzonderen,
|
||||
-- en één vergeten uitzondering is er één te veel in een applicatie die over nalatenschappen
|
||||
-- gaat.
|
||||
-- * Het scheidt de twee werelden ook fysiek: wie in het adverteerdersdeel inbreekt, staat nog
|
||||
-- steeds nergens bij de kluizen in de buurt.
|
||||
--
|
||||
-- Inloggen gaat net als bij gebruikers met een code per e-mail; die codes staan in dezelfde
|
||||
-- tabel `login_codes`, met een eigen doel-nummer.
|
||||
CREATE TABLE IF NOT EXISTS `adverteerders` (
|
||||
`id` BIGINT NOT NULL AUTO_INCREMENT,
|
||||
`company_name` VARCHAR(160) NOT NULL,
|
||||
`contact_name` VARCHAR(160) NOT NULL DEFAULT '',
|
||||
`email` VARCHAR(255) NOT NULL,
|
||||
`phone` VARCHAR(50) NULL,
|
||||
`address` VARCHAR(160) NULL,
|
||||
`postal_code` VARCHAR(20) NULL,
|
||||
`city` VARCHAR(120) NULL,
|
||||
`country` VARCHAR(80) NULL,
|
||||
`coc_number` VARCHAR(40) NULL, -- KvK
|
||||
`vat_number` VARCHAR(40) NULL, -- btw
|
||||
`created_at` DATETIME(6) NOT NULL,
|
||||
`last_seen_at` DATETIME(6) NULL,
|
||||
`blocked_at` DATETIME(6) NULL, -- geen nieuwe campagnes meer; bestaande blijven staan
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `ux_adverteerders_email` (`email`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
-- ---------------------------------------------------------------- campagnes ---
|
||||
-- Eén geboekte advertentie: wat er staat, wanneer hij mag draaien, hoe vaak per uur, wat hij
|
||||
-- kost en hoe ver hij is.
|
||||
--
|
||||
-- De afbeelding staat hier als blob en niet in `vault_files`. Die tabel is voor spullen van
|
||||
-- gebruikers en is versleuteld met sleutels per gebruiker; een advertentielogo is juist
|
||||
-- openbaar en hoort daar niet tussen te staan. Hij wordt bij het opslaan verkleind, dus groot
|
||||
-- kan hij niet worden.
|
||||
--
|
||||
-- `amount_cents` wordt bij het boeken berekend en dan vastgelegd. Verandert het tarief later,
|
||||
-- dan verandert een al geboekte campagne niet mee - je hebt afgesproken wat je afsprak.
|
||||
CREATE TABLE IF NOT EXISTS `campagnes` (
|
||||
`id` BIGINT NOT NULL AUTO_INCREMENT,
|
||||
`advertiser_id` BIGINT NOT NULL,
|
||||
|
||||
`text` VARCHAR(300) NOT NULL,
|
||||
`link` VARCHAR(500) NOT NULL,
|
||||
`image` LONGBLOB NULL,
|
||||
`image_type` VARCHAR(100) NULL,
|
||||
|
||||
`starts_on` DATE NOT NULL,
|
||||
`ends_on` DATE NOT NULL,
|
||||
`per_hour` INT NOT NULL,
|
||||
|
||||
`amount_cents` INT NOT NULL,
|
||||
`rate_cents` INT NOT NULL, -- het tarief waarmee gerekend is, voor de verantwoording
|
||||
`payment_reference` VARCHAR(40) NULL, -- wat de adverteerder bij zijn bunq-betaling zet
|
||||
`paid_at` DATETIME(6) NULL,
|
||||
`paid_by_user_id` BIGINT NULL, -- welke beheerder het afvinkte
|
||||
|
||||
`state` TINYINT NOT NULL DEFAULT 0,
|
||||
`approved_at` DATETIME(6) NULL,
|
||||
`approved_by_user_id` BIGINT NULL,
|
||||
`rejection_reason` VARCHAR(500) NULL,
|
||||
|
||||
`created_at` DATETIME(6) NOT NULL,
|
||||
`updated_at` DATETIME(6) NULL,
|
||||
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `ix_campagnes_adverteerder` (`advertiser_id`),
|
||||
|
||||
-- De vraag "welke campagnes mogen nu draaien" komt bij élke keer dat iemand het overzicht
|
||||
-- opent. Die moet op een index kunnen landen en niet op de hele tabel.
|
||||
KEY `ix_campagnes_lopend` (`state`, `starts_on`, `ends_on`),
|
||||
|
||||
CONSTRAINT `fk_campagnes_adverteerder` FOREIGN KEY (`advertiser_id`)
|
||||
REFERENCES `adverteerders` (`id`) ON DELETE RESTRICT
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
-- --------------------------------------------------------------- vertoningen ---
|
||||
-- Eén regel per campagne per klokuur, met tellers erin. Níét één regel per vertoning: bij een
|
||||
-- paar honderd bezoekers per uur loopt zo'n tabel eindeloos vol, en voor het kiezen én voor de
|
||||
-- verantwoording is een teller per uur genoeg.
|
||||
--
|
||||
-- Het uur staat in UTC, op het hele uur afgerond. De unieke sleutel is wat het optellen veilig
|
||||
-- maakt: twee bezoekers tegelijk leveren een INSERT ... ON DUPLICATE KEY UPDATE op en geen
|
||||
-- tweede regel.
|
||||
CREATE TABLE IF NOT EXISTS `campagne_uren` (
|
||||
`id` BIGINT NOT NULL AUTO_INCREMENT,
|
||||
`campaign_id` BIGINT NOT NULL,
|
||||
`hour_utc` DATETIME(6) NOT NULL,
|
||||
`shown` INT NOT NULL DEFAULT 0,
|
||||
`clicks` INT NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `ux_campagne_uren` (`campaign_id`, `hour_utc`),
|
||||
CONSTRAINT `fk_campagne_uren_campagne` FOREIGN KEY (`campaign_id`)
|
||||
REFERENCES `campagnes` (`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
-- De rechten voor de applicatiegebruiker staan NIET hier maar in 02_applicatiegebruiker.sql.
|
||||
--
|
||||
-- Waarom: de scripts vanaf 03 worden ook door de uitrolagent gedraaid, met een beperkt
|
||||
-- databaseaccount dat geen rechten mág uitdelen. Een GRANT hier laat die hele uitrol struikelen,
|
||||
-- en dat account meer macht geven is de verkeerde kant op. Rechten uitdelen is inrichten; dat
|
||||
-- doet een mens één keer als root. Een zelfcontrole legt elke CREATE TABLE in deze map naast de
|
||||
-- lijst in 02, dus vergeten kan niet ongemerkt.
|
||||
|
||||
SELECT 'Klaar. Adverteerders, campagnes en tellers staan klaar.' AS melding;
|
||||
@@ -0,0 +1,38 @@
|
||||
-- MijnBesluit - onthouden welke inlogcode ECHT is gebruikt
|
||||
--
|
||||
-- Er zit een rem op het aanvragen van codes: niet meer dan vijf per uur voor hetzelfde adres.
|
||||
-- Die rem is er tegen één ding: iemand die het postvak van een ander volgooit door steeds
|
||||
-- opnieuw een code voor dat adres aan te vragen.
|
||||
--
|
||||
-- Die telling keek naar alle codes van het afgelopen uur, ook de codes die gewoon zijn gebruikt
|
||||
-- om in te loggen. Wie vijf keer in een uur inlogde, sloot zichzelf dus een uur buiten - en dat
|
||||
-- gebeurt precies wanneer er íéts anders aan de hand is en je het een paar keer opnieuw
|
||||
-- probeert. Dat is de verkeerde kant om te falen: de rem hoort een vreemde te stoppen, niet de
|
||||
-- eigenaar.
|
||||
--
|
||||
-- Waarom daar een nieuwe kolom voor nodig is: `consumed_at` betekent "deze code werkt niet
|
||||
-- meer", en dat wordt ook gezet als een nieuwe aanvraag de vorige ongeldig maakt, en als een
|
||||
-- code te vaak fout is ingevuld. Precies die twee gevallen MOETEN meetellen - anders kan een
|
||||
-- vreemde de rem leegtrekken door codes aan te vragen of expres fout in te vullen.
|
||||
--
|
||||
-- `signed_in_at` staat alleen gevuld als de code goed is ingevuld en er iemand mee is
|
||||
-- binnengekomen. Alleen dát is het bewijs dat de aanvrager zelf in dat postvak zit, en alleen
|
||||
-- die codes tellen niet mee voor de rem.
|
||||
--
|
||||
-- Draaien met:
|
||||
-- mysql -u root -p mijnbesluit < db\14_inlogcode_gebruikt.sql
|
||||
--
|
||||
-- Opnieuw draaien mag: er gebeurt dan niets.
|
||||
|
||||
USE `mijnbesluit`;
|
||||
|
||||
SET @bestaat := (
|
||||
SELECT COUNT(*) FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'login_codes' AND COLUMN_NAME = 'signed_in_at');
|
||||
|
||||
SET @sql := IF(@bestaat = 0,
|
||||
'ALTER TABLE `login_codes` ADD COLUMN `signed_in_at` DATETIME(3) NULL',
|
||||
'SELECT "signed_in_at bestaat al" AS melding');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
SELECT 'Klaar.' AS melding;
|
||||
@@ -0,0 +1,46 @@
|
||||
-- MijnBesluit - twee mensen mogen dezelfde persoon kennen
|
||||
--
|
||||
-- Op `persons.email` stond een UNIQUE-index. Dat botste met het uitgangspunt van
|
||||
-- docs/Identiteit.md: iedereen heeft zijn EIGEN aantekening over een ander, en die aantekeningen
|
||||
-- worden pas één zodra de persoon zelf beide uitnodigingen aanneemt.
|
||||
--
|
||||
-- Wat er misging: voegde je iemand toe met een e-mailadres dat al ergens in de database stond -
|
||||
-- bij wie dan ook, ook bij een wildvreemde - dan liep het opslaan stuk op die index. Voor de
|
||||
-- gebruiker was dat een knop die niets deed. En het gevolg was erger dan een foutmelding: wie
|
||||
-- het adres van iemand het eerst invulde, bepaalde daarmee dat niemand anders die persoon nog
|
||||
-- kon toevoegen. In een applicatie waarin twee ouders allebei hun dochter toevoegen is dat
|
||||
-- precies de verkeerde kant om.
|
||||
--
|
||||
-- Uniek blijft het wél bij `users.email`: één account per e-mailadres. Dat is iets anders dan
|
||||
-- een aantekening over iemand, en dat moet uniek blijven, anders weet niemand wie er inlogt.
|
||||
--
|
||||
-- De index zelf blijft staan, alleen niet meer uniek: er wordt op adres gezocht.
|
||||
--
|
||||
-- Draaien met:
|
||||
-- mysql -u root -p mijnbesluit < db\15_adres_niet_uniek.sql
|
||||
--
|
||||
-- Opnieuw draaien mag: er gebeurt dan niets.
|
||||
|
||||
USE `mijnbesluit`;
|
||||
|
||||
SET @uniek := (
|
||||
SELECT COUNT(*) FROM information_schema.STATISTICS
|
||||
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'persons'
|
||||
AND INDEX_NAME = 'ux_persons_email');
|
||||
|
||||
SET @sql := IF(@uniek > 0,
|
||||
'ALTER TABLE `persons` DROP INDEX `ux_persons_email`',
|
||||
'SELECT "ux_persons_email bestaat niet (meer)" AS melding');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
SET @gewoon := (
|
||||
SELECT COUNT(*) FROM information_schema.STATISTICS
|
||||
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'persons'
|
||||
AND INDEX_NAME = 'ix_persons_email');
|
||||
|
||||
SET @sql := IF(@gewoon = 0,
|
||||
'ALTER TABLE `persons` ADD INDEX `ix_persons_email` (`email`)',
|
||||
'SELECT "ix_persons_email bestaat al" AS melding');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
SELECT 'Klaar.' AS melding;
|
||||
@@ -0,0 +1,54 @@
|
||||
-- MijnBesluit - aanmeldingen van de app bewaren, zodat ze een herstart overleven
|
||||
--
|
||||
-- Waarom deze tabel er komt: de app blijft aangemeld en komt er daarna in met de vingerafdruk
|
||||
-- of de code van de telefoon. Dat werkt alleen zolang de server het sessietoken nog kent. De
|
||||
-- sessies stonden in het geheugen van de SocketServer, dus elke herstart van de service - een
|
||||
-- uitrol, een Windows-update 's nachts, een reboot van de server - gooide ze allemaal weg. Het
|
||||
-- toestel wist dan nog wie je was, de server niet meer, en je moest weer een code uit je mail
|
||||
-- halen. Precies wat de vingerafdruk zou moeten voorkomen.
|
||||
--
|
||||
-- Het token zelf staat er NIET in, alleen de SHA-256 ervan - dezelfde afspraak als bij de
|
||||
-- inlogcodes. Wie de database in handen krijgt, krijgt daarmee dus geen werkende sessies mee:
|
||||
-- uit een hash valt geen token terug te rekenen.
|
||||
--
|
||||
-- `verloopt_op` schuift op bij gebruik, maar niet bij elk verzoek. Dat zou een schrijfactie per
|
||||
-- aanroep betekenen voor iets waar een uur speling volstaat bij een termijn van veertien dagen.
|
||||
--
|
||||
-- Draaien met:
|
||||
-- mysql -u root -p mijnbesluit < db\16_sessies.sql
|
||||
--
|
||||
-- Opnieuw draaien mag: er gebeurt dan niets.
|
||||
|
||||
USE `mijnbesluit`;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `sessies` (
|
||||
`id` BIGINT NOT NULL AUTO_INCREMENT,
|
||||
`token_hash` VARBINARY(32) NOT NULL, -- SHA-256 van het token; nooit het token zelf
|
||||
`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;
|
||||
|
||||
-- DE RECHTEN STAAN HIER MET OPZET NIET.
|
||||
--
|
||||
-- De verleiding was groot: een nieuwe tabel zonder GRANT is onzichtbaar tot iemand hem gebruikt,
|
||||
-- en dan zegt de server "INSERT command denied" terwijl de gebruiker alleen "Er ging iets mis"
|
||||
-- ziet. Precies wat er op 17 september gebeurde - inloggen in de app liep erop stuk.
|
||||
--
|
||||
-- Maar de scripts vanaf 03 worden ook door de uitrolagent gedraaid, met een beperkt
|
||||
-- databaseaccount dat helemaal geen rechten mág uitdelen. Een GRANT hier zou die hele uitrol
|
||||
-- laten struikelen, en de oplossing "geef dat account dan GRANT-rechten" maakt het uitrolkanaal
|
||||
-- machtiger dan het hoeft te zijn. Rechten uitdelen is inrichten, en dat doet een mens één keer.
|
||||
--
|
||||
-- De rechten staan dus in 02_applicatiegebruiker.sql, en een zelfcontrole
|
||||
-- ("Elke tabel geeft de applicatiegebruiker rechten") legt elke CREATE TABLE in deze map naast
|
||||
-- die lijst. Die vangt het al vóór de uitrol, en dat is de goede plek.
|
||||
|
||||
SELECT 'Klaar. Vergeet 02_applicatiegebruiker.sql niet als dit een nieuwe server is.' AS melding;
|
||||
@@ -0,0 +1,34 @@
|
||||
-- MijnBesluit - onthouden dat iemand "nee, dat ben ik niet" heeft gezegd
|
||||
--
|
||||
-- Waar dit voor is: maakt iemand een account aan op een adres waarover een ander al een
|
||||
-- aantekening heeft staan, dan vragen we hem of hij dat is. Hij beslist; koppelen op adres doen
|
||||
-- we niet, want een adres is geen identiteit (twee mensen kunnen er een delen, en één mens heeft
|
||||
-- er vaak meerdere). Zie docs/Identiteit.md.
|
||||
--
|
||||
-- Zegt hij nee, dan hoort die vraag niet bij elke volgende keer opnieuw te komen. Dat is wat
|
||||
-- hier wordt bewaard. Alleen "nee": een "ja" is geen vlaggetje maar een samenvoeging, en dan
|
||||
-- bestaat die aantekening als aparte rij niet meer (merged_into_person_id).
|
||||
--
|
||||
-- Degene die de aantekening maakte merkt hier niets van. Hij krijgt geen melding dat er iets
|
||||
-- gevraagd is en geen melding dat het antwoord nee was - anders zou het weigeren zelf een
|
||||
-- mededeling worden over wie er wel of niet een account heeft.
|
||||
--
|
||||
-- Draaien met:
|
||||
-- mysql -u root -p mijnbesluit < db\17_aantekening_afgewezen.sql
|
||||
--
|
||||
-- Opnieuw draaien mag: er gebeurt dan niets.
|
||||
|
||||
USE `mijnbesluit`;
|
||||
|
||||
SET @bestaat := (
|
||||
SELECT COUNT(*) FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'persons' AND COLUMN_NAME = 'link_declined_by_user_id');
|
||||
|
||||
SET @sql := IF(@bestaat = 0,
|
||||
'ALTER TABLE `persons`
|
||||
ADD COLUMN `link_declined_by_user_id` BIGINT NULL,
|
||||
ADD COLUMN `link_declined_at` DATETIME(3) NULL',
|
||||
'SELECT "link_declined_by_user_id bestaat al" AS melding');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
SELECT 'Klaar.' AS melding;
|
||||
@@ -0,0 +1,105 @@
|
||||
-- MijnBesluit - kijklink voor een moment aan iemand zonder account
|
||||
--
|
||||
-- Zet je een moment klaar voor iemand die (nog) geen MijnBesluit-account heeft, dan krijgt hij
|
||||
-- voortaan een e-mail met een link naar een landingspagina: daar is het bericht - tekst en een
|
||||
-- eventuele foto, video of document - te zien zonder in te loggen. Zelfde opzet als de
|
||||
-- uitnodigings- en bezwaarlink: alleen de hash van het token staat in de database, het token
|
||||
-- zelf staat uitsluitend in de e-mail.
|
||||
--
|
||||
-- view_token_hash : SHA-256 van het token uit de link
|
||||
-- view_token_expires : tot wanneer die link werkt (30 dagen na het versturen)
|
||||
-- view_token_sent_at : wanneer de linkmail voor het eerst is verstuurd
|
||||
-- viewed_at : wanneer de landingspagina daadwerkelijk is geopend
|
||||
-- view_reminder_sent_at : wanneer de herinnering is verstuurd (nooit meer dan één keer)
|
||||
-- wrapped_content_key_for_token: de contentsleutel, ingepakt met een sleutel uit het token zelf
|
||||
-- wrapped_vault_key_for_token : dezelfde inpakslag, voor de bijlage bij dit bericht
|
||||
--
|
||||
-- Draaien met:
|
||||
-- mysql -u root -p mijnbesluit < db\18_kijklink.sql
|
||||
--
|
||||
-- Opnieuw draaien mag: er gebeurt dan niets.
|
||||
|
||||
USE `mijnbesluit`;
|
||||
|
||||
-- ---------------------------------------------------------------- view_token_hash
|
||||
SET @bestaat := (
|
||||
SELECT COUNT(*) FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'messages' AND COLUMN_NAME = 'view_token_hash');
|
||||
|
||||
SET @sql := IF(@bestaat = 0,
|
||||
'ALTER TABLE `messages` ADD COLUMN `view_token_hash` VARBINARY(32) NULL',
|
||||
'SELECT "view_token_hash bestaat al" AS melding');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- ---------------------------------------------------------------- view_token_expires
|
||||
SET @bestaat := (
|
||||
SELECT COUNT(*) FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'messages' AND COLUMN_NAME = 'view_token_expires');
|
||||
|
||||
SET @sql := IF(@bestaat = 0,
|
||||
'ALTER TABLE `messages` ADD COLUMN `view_token_expires` DATETIME(3) NULL',
|
||||
'SELECT "view_token_expires bestaat al" AS melding');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- ---------------------------------------------------------------- view_token_sent_at
|
||||
SET @bestaat := (
|
||||
SELECT COUNT(*) FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'messages' AND COLUMN_NAME = 'view_token_sent_at');
|
||||
|
||||
SET @sql := IF(@bestaat = 0,
|
||||
'ALTER TABLE `messages` ADD COLUMN `view_token_sent_at` DATETIME(3) NULL',
|
||||
'SELECT "view_token_sent_at bestaat al" AS melding');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- ---------------------------------------------------------------- viewed_at
|
||||
SET @bestaat := (
|
||||
SELECT COUNT(*) FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'messages' AND COLUMN_NAME = 'viewed_at');
|
||||
|
||||
SET @sql := IF(@bestaat = 0,
|
||||
'ALTER TABLE `messages` ADD COLUMN `viewed_at` DATETIME(3) NULL',
|
||||
'SELECT "viewed_at bestaat al" AS melding');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- ---------------------------------------------------------------- view_reminder_sent_at
|
||||
SET @bestaat := (
|
||||
SELECT COUNT(*) FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'messages' AND COLUMN_NAME = 'view_reminder_sent_at');
|
||||
|
||||
SET @sql := IF(@bestaat = 0,
|
||||
'ALTER TABLE `messages` ADD COLUMN `view_reminder_sent_at` DATETIME(3) NULL',
|
||||
'SELECT "view_reminder_sent_at bestaat al" AS melding');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- ---------------------------------------------------------------- wrapped_content_key_for_token
|
||||
SET @bestaat := (
|
||||
SELECT COUNT(*) FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'messages' AND COLUMN_NAME = 'wrapped_content_key_for_token');
|
||||
|
||||
SET @sql := IF(@bestaat = 0,
|
||||
'ALTER TABLE `messages` ADD COLUMN `wrapped_content_key_for_token` VARBINARY(96) NULL',
|
||||
'SELECT "wrapped_content_key_for_token bestaat al" AS melding');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- ---------------------------------------------------------------- wrapped_vault_key_for_token
|
||||
SET @bestaat := (
|
||||
SELECT COUNT(*) FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'messages' AND COLUMN_NAME = 'wrapped_vault_key_for_token');
|
||||
|
||||
SET @sql := IF(@bestaat = 0,
|
||||
'ALTER TABLE `messages` ADD COLUMN `wrapped_vault_key_for_token` VARBINARY(96) NULL',
|
||||
'SELECT "wrapped_vault_key_for_token bestaat al" AS melding');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- ---------------------------------------------------------------- index
|
||||
-- De landingspagina zoekt het bericht op dit kenmerk op, dus zonder index is dat een tabelscan.
|
||||
SET @bestaat := (
|
||||
SELECT COUNT(*) FROM information_schema.STATISTICS
|
||||
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'messages' AND INDEX_NAME = 'ix_messages_view_token');
|
||||
|
||||
SET @sql := IF(@bestaat = 0,
|
||||
'CREATE INDEX `ix_messages_view_token` ON `messages` (`view_token_hash`)',
|
||||
'SELECT "ix_messages_view_token bestaat al" AS melding');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
SELECT 'Klaar. Momenten voor iemand zonder account krijgen vanaf nu een kijklink per e-mail.' AS melding;
|
||||
@@ -0,0 +1,28 @@
|
||||
-- MijnBesluit - beheerder kiezen om een uitvaartwens vooraf te bespreken
|
||||
--
|
||||
-- Bij een kluisitem van het soort Uitvaartwens kan de eigenaar voortaan één van zijn
|
||||
-- nalatenschapsbeheerders kiezen. Die beheerder krijgt dan meteen leestoegang tot dit ene item
|
||||
-- (via content_grants, reden "gedeeld"), nog vóór er sprake is van een overlijden - zodat het te
|
||||
-- bespreken is en vooraf al duidelijk is hoe en wat. Kiest de eigenaar niemand, dan blijft het
|
||||
-- item zoals altijd alleen voor hemzelf.
|
||||
--
|
||||
-- shared_manager_person_id : de gekozen beheerder (persons.id); NULL = niemand gekozen
|
||||
--
|
||||
-- Draaien met:
|
||||
-- mysql -u root -p mijnbesluit < db\19_uitvaartwens_beheerder.sql
|
||||
--
|
||||
-- Opnieuw draaien mag: er gebeurt dan niets.
|
||||
|
||||
USE `mijnbesluit`;
|
||||
|
||||
-- ---------------------------------------------------------------- shared_manager_person_id
|
||||
SET @bestaat := (
|
||||
SELECT COUNT(*) FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'vault_items' AND COLUMN_NAME = 'shared_manager_person_id');
|
||||
|
||||
SET @sql := IF(@bestaat = 0,
|
||||
'ALTER TABLE `vault_items` ADD COLUMN `shared_manager_person_id` BIGINT NULL',
|
||||
'SELECT "shared_manager_person_id bestaat al" AS melding');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
SELECT 'Klaar. Bij een uitvaartwens is voortaan een beheerder te kiezen om vooraf mee te bespreken.' AS melding;
|
||||
@@ -0,0 +1,8 @@
|
||||
-- MijnBesluit - een bezorgd bericht uit je eigen lijst verwijderen
|
||||
--
|
||||
-- Wie een bezorgd bericht "verwijdert" haalt het alleen bij zichzelf weg; de ander heeft er nog
|
||||
-- steeds recht op. Vandaar twee vlaggen in plaats van één verwijderdatum: de afzender en de
|
||||
-- ontvanger ruimen onafhankelijk van elkaar op. De rij zelf blijft altijd bestaan.
|
||||
ALTER TABLE `messages`
|
||||
ADD COLUMN `hidden_by_sender` TINYINT(1) NOT NULL DEFAULT 0 AFTER `read_at`,
|
||||
ADD COLUMN `hidden_by_recipient` TINYINT(1) NOT NULL DEFAULT 0 AFTER `hidden_by_sender`;
|
||||
Reference in New Issue
Block a user