-- ═══════════════════════════════════════════════════════════════════════════ -- NIS2 Agile - Schema Database Iniziale -- Database: nis2_agile_db -- Versione: 1.0.0 -- Data: 2026-02-17 -- ═══════════════════════════════════════════════════════════════════════════ CREATE DATABASE IF NOT EXISTS nis2_agile_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE nis2_agile_db; -- ═══════════════════════════════════════════════════════════════════════════ -- CORE: Organizzazioni e Utenti -- ═══════════════════════════════════════════════════════════════════════════ CREATE TABLE organizations ( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(255) NOT NULL, vat_number VARCHAR(20), fiscal_code VARCHAR(16), sector ENUM( 'energy','transport','banking','health','water','digital_infra', 'public_admin','manufacturing','postal','chemical','food', 'waste','ict_services','digital_providers','space','research','other' ) NOT NULL DEFAULT 'other', entity_type ENUM('essential','important','not_applicable') DEFAULT 'not_applicable', employee_count INT, annual_turnover_eur DECIMAL(15,2), country VARCHAR(2) DEFAULT 'IT', city VARCHAR(100), address VARCHAR(255), website VARCHAR(255), contact_email VARCHAR(255), contact_phone VARCHAR(30), subscription_plan ENUM('free','professional','enterprise') DEFAULT 'free', is_active TINYINT(1) DEFAULT 1, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, INDEX idx_sector (sector), INDEX idx_entity_type (entity_type), INDEX idx_subscription (subscription_plan) ) ENGINE=InnoDB; CREATE TABLE users ( id INT AUTO_INCREMENT PRIMARY KEY, email VARCHAR(255) UNIQUE NOT NULL, password_hash VARCHAR(255) NOT NULL, full_name VARCHAR(255) NOT NULL, phone VARCHAR(30), role ENUM( 'super_admin','org_admin','compliance_manager', 'board_member','auditor','employee','consultant' ) NOT NULL DEFAULT 'employee', preferred_language VARCHAR(5) DEFAULT 'it', is_active TINYINT(1) DEFAULT 1, email_verified_at DATETIME, last_login_at DATETIME, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, INDEX idx_email (email), INDEX idx_role (role), INDEX idx_active (is_active) ) ENGINE=InnoDB; CREATE TABLE user_organizations ( id INT AUTO_INCREMENT PRIMARY KEY, user_id INT NOT NULL, organization_id INT NOT NULL, role ENUM('org_admin','compliance_manager','board_member','auditor','employee') NOT NULL DEFAULT 'employee', is_primary TINYINT(1) DEFAULT 0, joined_at DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE CASCADE, UNIQUE KEY uk_user_org (user_id, organization_id), INDEX idx_user (user_id), INDEX idx_org (organization_id) ) ENGINE=InnoDB; CREATE TABLE refresh_tokens ( id INT AUTO_INCREMENT PRIMARY KEY, user_id INT NOT NULL, token VARCHAR(255) NOT NULL, expires_at DATETIME NOT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, INDEX idx_token (token), INDEX idx_expires (expires_at) ) ENGINE=InnoDB; -- ═══════════════════════════════════════════════════════════════════════════ -- GAP ANALYSIS & ASSESSMENT -- ═══════════════════════════════════════════════════════════════════════════ CREATE TABLE assessments ( id INT AUTO_INCREMENT PRIMARY KEY, organization_id INT NOT NULL, title VARCHAR(255) NOT NULL, assessment_type ENUM('initial','periodic','post_incident') DEFAULT 'initial', status ENUM('draft','in_progress','completed') DEFAULT 'draft', overall_score DECIMAL(5,2), category_scores JSON, completed_by INT, completed_at DATETIME, ai_summary TEXT, ai_recommendations JSON, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE CASCADE, FOREIGN KEY (completed_by) REFERENCES users(id) ON DELETE SET NULL, INDEX idx_org (organization_id), INDEX idx_status (status) ) ENGINE=InnoDB; CREATE TABLE assessment_responses ( id INT AUTO_INCREMENT PRIMARY KEY, assessment_id INT NOT NULL, question_code VARCHAR(50) NOT NULL, nis2_article VARCHAR(20), iso27001_control VARCHAR(20), category VARCHAR(100), question_text TEXT NOT NULL, response_value ENUM('not_implemented','partial','implemented','not_applicable'), maturity_level TINYINT, evidence_description TEXT, notes TEXT, answered_by INT, answered_at DATETIME, FOREIGN KEY (assessment_id) REFERENCES assessments(id) ON DELETE CASCADE, FOREIGN KEY (answered_by) REFERENCES users(id) ON DELETE SET NULL, INDEX idx_assessment (assessment_id), INDEX idx_category (category), UNIQUE KEY uk_assessment_question (assessment_id, question_code) ) ENGINE=InnoDB; -- ═══════════════════════════════════════════════════════════════════════════ -- RISK MANAGEMENT -- ═══════════════════════════════════════════════════════════════════════════ CREATE TABLE risks ( id INT AUTO_INCREMENT PRIMARY KEY, organization_id INT NOT NULL, risk_code VARCHAR(20), title VARCHAR(255) NOT NULL, description TEXT, category ENUM('cyber','operational','compliance','supply_chain','physical','human') NOT NULL, threat_source VARCHAR(255), vulnerability VARCHAR(255), affected_assets JSON, likelihood TINYINT, impact TINYINT, inherent_risk_score TINYINT, treatment ENUM('mitigate','accept','transfer','avoid') DEFAULT 'mitigate', residual_likelihood TINYINT, residual_impact TINYINT, residual_risk_score TINYINT, status ENUM('identified','analyzing','treating','monitored','closed') DEFAULT 'identified', owner_user_id INT, review_date DATE, nis2_article VARCHAR(20), created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE CASCADE, FOREIGN KEY (owner_user_id) REFERENCES users(id) ON DELETE SET NULL, INDEX idx_org (organization_id), INDEX idx_status (status), INDEX idx_category (category), UNIQUE KEY uk_org_risk_code (organization_id, risk_code) ) ENGINE=InnoDB; CREATE TABLE risk_treatments ( id INT AUTO_INCREMENT PRIMARY KEY, risk_id INT NOT NULL, action_description TEXT NOT NULL, responsible_user_id INT, due_date DATE, status ENUM('planned','in_progress','completed','overdue') DEFAULT 'planned', completion_date DATE, notes TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, FOREIGN KEY (risk_id) REFERENCES risks(id) ON DELETE CASCADE, FOREIGN KEY (responsible_user_id) REFERENCES users(id) ON DELETE SET NULL, INDEX idx_risk (risk_id), INDEX idx_status (status) ) ENGINE=InnoDB; -- ═══════════════════════════════════════════════════════════════════════════ -- INCIDENT MANAGEMENT (Art. 23) -- ═══════════════════════════════════════════════════════════════════════════ CREATE TABLE incidents ( id INT AUTO_INCREMENT PRIMARY KEY, organization_id INT NOT NULL, incident_code VARCHAR(20), title VARCHAR(255) NOT NULL, description TEXT, classification ENUM( 'cyber_attack','data_breach','system_failure', 'human_error','natural_disaster','supply_chain','other' ) NOT NULL, severity ENUM('low','medium','high','critical') NOT NULL, is_significant TINYINT(1) DEFAULT 0, status ENUM( 'detected','analyzing','containing','eradicating', 'recovering','closed','post_mortem' ) DEFAULT 'detected', detected_at DATETIME NOT NULL, -- NIS2 Art.23 milestones early_warning_due DATETIME, early_warning_sent_at DATETIME, notification_due DATETIME, notification_sent_at DATETIME, final_report_due DATETIME, final_report_sent_at DATETIME, -- Impact affected_services TEXT, affected_users_count INT, cross_border_impact TINYINT(1) DEFAULT 0, malicious_action TINYINT(1) DEFAULT 0, -- Resolution root_cause TEXT, remediation_actions TEXT, lessons_learned TEXT, reported_by INT, assigned_to INT, closed_at DATETIME, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE CASCADE, FOREIGN KEY (reported_by) REFERENCES users(id) ON DELETE SET NULL, FOREIGN KEY (assigned_to) REFERENCES users(id) ON DELETE SET NULL, INDEX idx_org (organization_id), INDEX idx_status (status), INDEX idx_severity (severity), UNIQUE KEY uk_org_incident_code (organization_id, incident_code) ) ENGINE=InnoDB; CREATE TABLE incident_timeline ( id INT AUTO_INCREMENT PRIMARY KEY, incident_id INT NOT NULL, event_type ENUM('detection','escalation','notification','action','update','resolution') NOT NULL, description TEXT NOT NULL, created_by INT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (incident_id) REFERENCES incidents(id) ON DELETE CASCADE, FOREIGN KEY (created_by) REFERENCES users(id) ON DELETE SET NULL, INDEX idx_incident (incident_id) ) ENGINE=InnoDB; -- ═══════════════════════════════════════════════════════════════════════════ -- POLICY & PROCEDURE MANAGEMENT -- ═══════════════════════════════════════════════════════════════════════════ CREATE TABLE policies ( id INT AUTO_INCREMENT PRIMARY KEY, organization_id INT NOT NULL, title VARCHAR(255) NOT NULL, category ENUM( 'information_security','access_control','incident_response', 'business_continuity','supply_chain','encryption','hr_security', 'asset_management','network_security','vulnerability_management' ) NOT NULL, nis2_article VARCHAR(20), version VARCHAR(10) DEFAULT '1.0', status ENUM('draft','review','approved','published','archived') DEFAULT 'draft', content LONGTEXT, approved_by INT, approved_at DATETIME, next_review_date DATE, ai_generated TINYINT(1) DEFAULT 0, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE CASCADE, FOREIGN KEY (approved_by) REFERENCES users(id) ON DELETE SET NULL, INDEX idx_org (organization_id), INDEX idx_status (status), INDEX idx_category (category) ) ENGINE=InnoDB; -- ═══════════════════════════════════════════════════════════════════════════ -- SUPPLY CHAIN SECURITY (Art. 21.2.d) -- ═══════════════════════════════════════════════════════════════════════════ CREATE TABLE suppliers ( id INT AUTO_INCREMENT PRIMARY KEY, organization_id INT NOT NULL, name VARCHAR(255) NOT NULL, vat_number VARCHAR(20), contact_email VARCHAR(255), contact_name VARCHAR(255), service_type VARCHAR(255), service_description TEXT, criticality ENUM('low','medium','high','critical') DEFAULT 'medium', risk_score TINYINT, last_assessment_date DATE, next_assessment_date DATE, contract_start_date DATE, contract_expiry_date DATE, security_requirements_met TINYINT(1) DEFAULT 0, assessment_responses JSON, notes TEXT, status ENUM('active','under_review','suspended','terminated') DEFAULT 'active', created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE CASCADE, INDEX idx_org (organization_id), INDEX idx_criticality (criticality), INDEX idx_status (status) ) ENGINE=InnoDB; -- ═══════════════════════════════════════════════════════════════════════════ -- TRAINING & AWARENESS (Art. 20) -- ═══════════════════════════════════════════════════════════════════════════ CREATE TABLE training_courses ( id INT AUTO_INCREMENT PRIMARY KEY, organization_id INT, title VARCHAR(255) NOT NULL, description TEXT, target_role ENUM('all','board_member','compliance_manager','employee','technical') DEFAULT 'all', nis2_article VARCHAR(20), is_mandatory TINYINT(1) DEFAULT 0, duration_minutes INT, content JSON, quiz JSON, passing_score TINYINT DEFAULT 70, is_active TINYINT(1) DEFAULT 1, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE CASCADE, INDEX idx_org (organization_id), INDEX idx_role (target_role) ) ENGINE=InnoDB; CREATE TABLE training_assignments ( id INT AUTO_INCREMENT PRIMARY KEY, course_id INT NOT NULL, user_id INT NOT NULL, organization_id INT NOT NULL, status ENUM('assigned','in_progress','completed','overdue') DEFAULT 'assigned', due_date DATE, started_at DATETIME, completed_at DATETIME, quiz_score TINYINT, certificate_url VARCHAR(255), created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, FOREIGN KEY (course_id) REFERENCES training_courses(id) ON DELETE CASCADE, FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE CASCADE, INDEX idx_user (user_id), INDEX idx_org (organization_id), INDEX idx_status (status), UNIQUE KEY uk_course_user (course_id, user_id) ) ENGINE=InnoDB; -- ═══════════════════════════════════════════════════════════════════════════ -- ASSET MANAGEMENT (Art. 21.2.i) -- ═══════════════════════════════════════════════════════════════════════════ CREATE TABLE assets ( id INT AUTO_INCREMENT PRIMARY KEY, organization_id INT NOT NULL, name VARCHAR(255) NOT NULL, asset_type ENUM('hardware','software','network','data','service','personnel','facility') NOT NULL, category VARCHAR(100), description TEXT, criticality ENUM('low','medium','high','critical') DEFAULT 'medium', owner_user_id INT, location VARCHAR(255), ip_address VARCHAR(45), vendor VARCHAR(255), version VARCHAR(50), serial_number VARCHAR(100), purchase_date DATE, warranty_expiry DATE, status ENUM('active','maintenance','decommissioned') DEFAULT 'active', dependencies JSON, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE CASCADE, FOREIGN KEY (owner_user_id) REFERENCES users(id) ON DELETE SET NULL, INDEX idx_org (organization_id), INDEX idx_type (asset_type), INDEX idx_criticality (criticality), INDEX idx_status (status) ) ENGINE=InnoDB; -- ═══════════════════════════════════════════════════════════════════════════ -- AUDIT & COMPLIANCE TRACKING -- ═══════════════════════════════════════════════════════════════════════════ CREATE TABLE compliance_controls ( id INT AUTO_INCREMENT PRIMARY KEY, organization_id INT NOT NULL, control_code VARCHAR(50) NOT NULL, framework ENUM('nis2','iso27001','both') DEFAULT 'nis2', title VARCHAR(255) NOT NULL, description TEXT, status ENUM('not_started','in_progress','implemented','verified') DEFAULT 'not_started', implementation_percentage TINYINT DEFAULT 0, evidence_description TEXT, responsible_user_id INT, last_verified_at DATETIME, next_review_date DATE, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE CASCADE, FOREIGN KEY (responsible_user_id) REFERENCES users(id) ON DELETE SET NULL, INDEX idx_org (organization_id), INDEX idx_framework (framework), INDEX idx_status (status), UNIQUE KEY uk_org_control (organization_id, control_code) ) ENGINE=InnoDB; CREATE TABLE evidence_files ( id INT AUTO_INCREMENT PRIMARY KEY, organization_id INT NOT NULL, control_id INT, entity_type VARCHAR(50), entity_id INT, file_name VARCHAR(255) NOT NULL, file_path VARCHAR(500) NOT NULL, file_size INT, mime_type VARCHAR(100), uploaded_by INT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE CASCADE, FOREIGN KEY (control_id) REFERENCES compliance_controls(id) ON DELETE SET NULL, FOREIGN KEY (uploaded_by) REFERENCES users(id) ON DELETE SET NULL, INDEX idx_org (organization_id), INDEX idx_entity (entity_type, entity_id) ) ENGINE=InnoDB; CREATE TABLE audit_logs ( id INT AUTO_INCREMENT PRIMARY KEY, user_id INT, organization_id INT, action VARCHAR(255) NOT NULL, entity_type VARCHAR(100), entity_id INT, details JSON, ip_address VARCHAR(45), user_agent VARCHAR(500), created_at DATETIME DEFAULT CURRENT_TIMESTAMP, INDEX idx_user (user_id), INDEX idx_org (organization_id), INDEX idx_action (action), INDEX idx_entity (entity_type, entity_id), INDEX idx_created (created_at) ) ENGINE=InnoDB; -- ═══════════════════════════════════════════════════════════════════════════ -- AI INTERACTIONS LOG -- ═══════════════════════════════════════════════════════════════════════════ CREATE TABLE ai_interactions ( id INT AUTO_INCREMENT PRIMARY KEY, organization_id INT NOT NULL, user_id INT NOT NULL, interaction_type ENUM( 'gap_analysis','risk_suggestion','policy_draft', 'incident_classification','qa','report_generation' ) NOT NULL, prompt_summary VARCHAR(500), response_summary TEXT, tokens_used INT, model_used VARCHAR(50), created_at DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE CASCADE, FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, INDEX idx_org (organization_id), INDEX idx_type (interaction_type) ) ENGINE=InnoDB; -- ═══════════════════════════════════════════════════════════════════════════ -- RATE LIMITING -- ═══════════════════════════════════════════════════════════════════════════ CREATE TABLE rate_limits ( id INT AUTO_INCREMENT PRIMARY KEY, rate_key VARCHAR(255) NOT NULL, ip_address VARCHAR(45) NOT NULL, attempts INT DEFAULT 1, window_start DATETIME NOT NULL, INDEX idx_key_ip (rate_key, ip_address), INDEX idx_window (window_start) ) ENGINE=InnoDB;