All checks were successful
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-ai-compliance (push) Successful in 36s
CI / test-python-backend-compliance (push) Successful in 31s
CI / test-python-document-crawler (push) Successful in 23s
CI / test-python-dsms-gateway (push) Successful in 21s
Phase A: 8 new IT-Security training modules (SEC-PWD, SEC-DESK, SEC-KIAI, SEC-BYOD, SEC-VIDEO, SEC-USB, SEC-INC, SEC-HOME) with CTM entries. Bulk content and quiz generation endpoints for all 28 modules. Phase B: Piper TTS service (Python/FastAPI) for local German speech synthesis. training_media table, TTSClient in Go backend, audio generation endpoints, AudioPlayer component in frontend. MinIO storage integration. Phase C: FFmpeg presentation video pipeline — LLM generates slide scripts, ImageMagick renders 1920x1080 slides, FFmpeg combines with audio to MP4. VideoPlayer and ScriptPreview components in frontend. New files: 15 created, 9 modified - compliance-tts-service/ (Dockerfile, main.py, tts_engine.py, storage.py, slide_renderer.py, video_generator.py) - migrations 014-016 (training engine, IT-security modules, media table) - training package (models, store, content_generator, media, handlers) - frontend (AudioPlayer, VideoPlayer, ScriptPreview, api, types, page) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
269 lines
16 KiB
SQL
269 lines
16 KiB
SQL
-- =========================================================
|
|
-- Migration 014: Compliance Training Engine
|
|
-- =========================================================
|
|
-- Training Module Catalog, Compliance Training Matrix (CTM),
|
|
-- Assignments, Quiz Engine, Content Pipeline, Audit Trail
|
|
-- =========================================================
|
|
|
|
-- Training-Module-Katalog (erweiterte Kurs-Metadaten)
|
|
CREATE TABLE IF NOT EXISTS training_modules (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id UUID NOT NULL REFERENCES compliance_tenants(id) ON DELETE CASCADE,
|
|
academy_course_id UUID REFERENCES academy_courses(id),
|
|
module_code VARCHAR(20) NOT NULL,
|
|
title VARCHAR(255) NOT NULL,
|
|
description TEXT,
|
|
regulation_area VARCHAR(20) NOT NULL,
|
|
nis2_relevant BOOLEAN DEFAULT FALSE,
|
|
iso_controls JSONB DEFAULT '[]',
|
|
frequency_type VARCHAR(20) NOT NULL DEFAULT 'annual',
|
|
validity_days INT DEFAULT 365,
|
|
risk_weight FLOAT DEFAULT 2.0,
|
|
content_type VARCHAR(20) DEFAULT 'text',
|
|
duration_minutes INT DEFAULT 30,
|
|
pass_threshold INT DEFAULT 70,
|
|
is_active BOOLEAN DEFAULT TRUE,
|
|
sort_order INT DEFAULT 0,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
UNIQUE(tenant_id, module_code)
|
|
);
|
|
|
|
-- Compliance Training Matrix: welche Rollen brauchen welche Module
|
|
CREATE TABLE IF NOT EXISTS training_matrix (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id UUID NOT NULL REFERENCES compliance_tenants(id) ON DELETE CASCADE,
|
|
role_code VARCHAR(10) NOT NULL,
|
|
module_id UUID NOT NULL REFERENCES training_modules(id) ON DELETE CASCADE,
|
|
is_mandatory BOOLEAN DEFAULT TRUE,
|
|
priority INT DEFAULT 5,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
UNIQUE(tenant_id, role_code, module_id)
|
|
);
|
|
|
|
-- Training-Zuweisungen (automatisch oder manuell)
|
|
CREATE TABLE IF NOT EXISTS training_assignments (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id UUID NOT NULL REFERENCES compliance_tenants(id) ON DELETE CASCADE,
|
|
module_id UUID NOT NULL REFERENCES training_modules(id) ON DELETE CASCADE,
|
|
user_id UUID NOT NULL,
|
|
user_name VARCHAR(255) NOT NULL,
|
|
user_email VARCHAR(255) NOT NULL,
|
|
role_code VARCHAR(10),
|
|
trigger_type VARCHAR(20) NOT NULL,
|
|
trigger_event VARCHAR(100),
|
|
status VARCHAR(20) DEFAULT 'pending',
|
|
progress_percent INT DEFAULT 0,
|
|
quiz_score FLOAT,
|
|
quiz_passed BOOLEAN,
|
|
quiz_attempts INT DEFAULT 0,
|
|
started_at TIMESTAMPTZ,
|
|
completed_at TIMESTAMPTZ,
|
|
deadline TIMESTAMPTZ NOT NULL,
|
|
certificate_id UUID,
|
|
escalation_level INT DEFAULT 0,
|
|
last_escalation_at TIMESTAMPTZ,
|
|
enrollment_id UUID REFERENCES academy_enrollments(id),
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|
|
|
|
-- Quiz-Fragenbank (persistent, nicht nur JSONB)
|
|
CREATE TABLE IF NOT EXISTS training_quiz_questions (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
module_id UUID NOT NULL REFERENCES training_modules(id) ON DELETE CASCADE,
|
|
question TEXT NOT NULL,
|
|
options JSONB NOT NULL,
|
|
correct_index INT NOT NULL,
|
|
explanation TEXT,
|
|
difficulty VARCHAR(10) DEFAULT 'medium',
|
|
is_active BOOLEAN DEFAULT TRUE,
|
|
sort_order INT DEFAULT 0,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|
|
|
|
-- Quiz-Versuche (jeder Versuch einzeln getracked)
|
|
CREATE TABLE IF NOT EXISTS training_quiz_attempts (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
assignment_id UUID NOT NULL REFERENCES training_assignments(id) ON DELETE CASCADE,
|
|
user_id UUID NOT NULL,
|
|
answers JSONB NOT NULL,
|
|
score FLOAT NOT NULL,
|
|
passed BOOLEAN NOT NULL,
|
|
correct_count INT NOT NULL,
|
|
total_count INT NOT NULL,
|
|
duration_seconds INT,
|
|
attempted_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|
|
|
|
-- Audit Trail fuer Training-Aktionen
|
|
CREATE TABLE IF NOT EXISTS training_audit_log (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id UUID NOT NULL REFERENCES compliance_tenants(id) ON DELETE CASCADE,
|
|
user_id UUID,
|
|
action VARCHAR(50) NOT NULL,
|
|
entity_type VARCHAR(30) NOT NULL,
|
|
entity_id UUID,
|
|
details JSONB DEFAULT '{}',
|
|
ip_address VARCHAR(45),
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|
|
|
|
-- Module-Inhalte (LLM-generiert oder manuell)
|
|
CREATE TABLE IF NOT EXISTS training_module_content (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
module_id UUID NOT NULL REFERENCES training_modules(id) ON DELETE CASCADE,
|
|
version INT DEFAULT 1,
|
|
content_format VARCHAR(20) DEFAULT 'markdown',
|
|
content_body TEXT NOT NULL,
|
|
summary TEXT,
|
|
generated_by VARCHAR(50),
|
|
llm_model VARCHAR(100),
|
|
is_published BOOLEAN DEFAULT FALSE,
|
|
reviewed_by UUID,
|
|
reviewed_at TIMESTAMPTZ,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|
|
|
|
-- =========================================================
|
|
-- INDEXES
|
|
-- =========================================================
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_training_modules_tenant ON training_modules(tenant_id);
|
|
CREATE INDEX IF NOT EXISTS idx_training_modules_regulation ON training_modules(tenant_id, regulation_area);
|
|
CREATE INDEX IF NOT EXISTS idx_training_matrix_tenant_role ON training_matrix(tenant_id, role_code);
|
|
CREATE INDEX IF NOT EXISTS idx_training_matrix_module ON training_matrix(module_id);
|
|
CREATE INDEX IF NOT EXISTS idx_training_assignments_tenant ON training_assignments(tenant_id);
|
|
CREATE INDEX IF NOT EXISTS idx_training_assignments_user ON training_assignments(tenant_id, user_id);
|
|
CREATE INDEX IF NOT EXISTS idx_training_assignments_status ON training_assignments(tenant_id, status);
|
|
CREATE INDEX IF NOT EXISTS idx_training_assignments_deadline ON training_assignments(deadline)
|
|
WHERE status NOT IN ('completed', 'expired');
|
|
CREATE INDEX IF NOT EXISTS idx_training_assignments_overdue ON training_assignments(tenant_id, deadline, status)
|
|
WHERE status IN ('pending', 'in_progress') AND deadline < NOW();
|
|
CREATE INDEX IF NOT EXISTS idx_training_quiz_questions_module ON training_quiz_questions(module_id);
|
|
CREATE INDEX IF NOT EXISTS idx_training_quiz_attempts_assignment ON training_quiz_attempts(assignment_id);
|
|
CREATE INDEX IF NOT EXISTS idx_training_audit_log_tenant ON training_audit_log(tenant_id, created_at DESC);
|
|
CREATE INDEX IF NOT EXISTS idx_training_module_content_module ON training_module_content(module_id, is_published);
|
|
|
|
-- =========================================================
|
|
-- SEED DATA: Template Modules (tenant_id = 00000000-...)
|
|
-- =========================================================
|
|
|
|
DO $$
|
|
DECLARE
|
|
t_id UUID := '00000000-0000-0000-0000-000000000000';
|
|
m_gdpr_bas UUID; m_gdpr_adv UUID; m_gdpr_art UUID; m_gdpr_dpia UUID;
|
|
m_nis2_m1 UUID; m_nis2_m2 UUID; m_nis2_m3 UUID; m_nis2_m4 UUID; m_nis2_m5 UUID;
|
|
m_isms_bas UUID; m_isms_int UUID; m_isms_aud UUID;
|
|
m_ai_bas UUID; m_ai_adv UUID; m_ai_risk UUID;
|
|
m_gesch_bas UUID; m_hin_bas UUID;
|
|
m_mail_sec UUID; m_soc_eng UUID; m_phish UUID;
|
|
BEGIN
|
|
-- Skip if seed data already exists
|
|
IF EXISTS (SELECT 1 FROM training_modules WHERE tenant_id = t_id LIMIT 1) THEN
|
|
RAISE NOTICE 'Seed data already exists, skipping';
|
|
RETURN;
|
|
END IF;
|
|
|
|
-- Insert modules and capture IDs
|
|
INSERT INTO training_modules (id, tenant_id, module_code, title, description, regulation_area, nis2_relevant, frequency_type, validity_days, risk_weight, duration_minutes, pass_threshold, sort_order)
|
|
VALUES
|
|
(gen_random_uuid(), t_id, 'GDPR-BAS', 'DSGVO Grundlagen', 'Grundlegende Datenschutzprinzipien, Rechtsgrundlagen, Verarbeitungsgrundsaetze', 'dsgvo', false, 'annual', 365, 2.0, 30, 70, 1),
|
|
(gen_random_uuid(), t_id, 'GDPR-ADV', 'DSGVO Vertiefung', 'Auftragsverarbeitung, Drittlandtransfer, Datenschutz-Management', 'dsgvo', false, 'annual', 365, 2.5, 45, 70, 2),
|
|
(gen_random_uuid(), t_id, 'GDPR-ART', 'Betroffenenrechte', 'Auskunft, Berichtigung, Loeschung, Einschraenkung, Datenportabilitaet', 'dsgvo', false, 'annual', 365, 2.0, 30, 70, 3),
|
|
(gen_random_uuid(), t_id, 'GDPR-DPIA', 'Datenschutz-Folgenabschaetzung', 'DSFA-Durchfuehrung, Schwellwertanalyse, Risikobewertung', 'dsgvo', false, 'event_trigger', 365, 3.0, 60, 80, 4),
|
|
(gen_random_uuid(), t_id, 'NIS2-M1', 'NIS2 Geschaeftsleitungspflicht', 'Haftung, Schulungspflicht, Risikomanagement-Governance', 'nis2', true, 'annual', 365, 3.0, 45, 80, 5),
|
|
(gen_random_uuid(), t_id, 'NIS2-M2', 'NIS2 Incident Response', 'Meldepflichten, 24h/72h-Fristen, Incident-Response-Plan', 'nis2', true, 'annual', 365, 3.0, 60, 80, 6),
|
|
(gen_random_uuid(), t_id, 'NIS2-M3', 'NIS2 Lieferkettensicherheit', 'Supply-Chain-Risiken, Dienstleisterbewertung, Vertragsklauseln', 'nis2', true, 'annual', 365, 2.5, 45, 70, 7),
|
|
(gen_random_uuid(), t_id, 'NIS2-M4', 'NIS2 BCM', 'Business Continuity, Notfallplanung, Wiederherstellung', 'nis2', true, 'annual', 365, 2.5, 45, 70, 8),
|
|
(gen_random_uuid(), t_id, 'NIS2-M5', 'NIS2 Risikomanagement', 'Risikoanalyse, Massnahmenplanung, Wirksamkeitspruefung', 'nis2', true, 'annual', 365, 3.0, 60, 80, 9),
|
|
(gen_random_uuid(), t_id, 'ISMS-BAS', 'ISMS Grundlagen', 'ISO 27001 Anforderungen, PDCA-Zyklus, Informationssicherheitspolitik', 'iso27001', false, 'annual', 365, 2.0, 30, 70, 10),
|
|
(gen_random_uuid(), t_id, 'ISMS-INT', 'ISMS Interne Audits', 'Audit-Planung, Durchfuehrung, Berichterstattung, Massnahmenverfolgung', 'iso27001', false, 'annual', 365, 2.5, 45, 70, 11),
|
|
(gen_random_uuid(), t_id, 'ISMS-AUD', 'ISMS Audit-Vorbereitung', 'Zertifizierungsaudit, Dokumentation, Nachweisfuehrung', 'iso27001', false, 'event_trigger', 365, 2.5, 60, 70, 12),
|
|
(gen_random_uuid(), t_id, 'AI-BAS', 'KI-Kompetenz Grundlagen', 'EU AI Act Ueberblick, KI-Risikokategorien, Transparenzpflichten', 'ai_act', false, 'annual', 365, 2.0, 30, 70, 13),
|
|
(gen_random_uuid(), t_id, 'AI-ADV', 'KI-Risikomanagement', 'Hochrisiko-KI, Konformitaetsbewertung, Dokumentationspflichten', 'ai_act', false, 'annual', 365, 2.5, 45, 70, 14),
|
|
(gen_random_uuid(), t_id, 'AI-RISK', 'Hochrisiko-KI-Systeme', 'Risikomanagementsystem, Qualitaet der Trainingsdaten, Human Oversight', 'ai_act', false, 'event_trigger', 365, 3.0, 60, 80, 15),
|
|
(gen_random_uuid(), t_id, 'GESCH-BAS', 'Geschaeftsgeheimnisschutz', 'GeschGehG, Schutzkonzept, NDAs, technische Massnahmen', 'geschgehg', false, 'annual', 365, 2.0, 30, 70, 16),
|
|
(gen_random_uuid(), t_id, 'HIN-BAS', 'Hinweisgeberschutz', 'HinSchG, Meldekanal, Vertraulichkeit, Repressalienverbot', 'hinschg', false, 'annual', 365, 2.0, 30, 70, 17),
|
|
(gen_random_uuid(), t_id, 'MAIL-SEC', 'E-Mail-Sicherheit', 'Phishing, Spam, Verschluesselung, Sichere Kommunikation', 'iso27001', false, 'micro', 180, 1.5, 15, 70, 18),
|
|
(gen_random_uuid(), t_id, 'SOC-ENG', 'Social Engineering Abwehr', 'Manipulationstechniken, Pretexting, Baiting, Tailgating', 'iso27001', false, 'micro', 180, 1.5, 15, 70, 19),
|
|
(gen_random_uuid(), t_id, 'PHISH', 'Phishing-Erkennung', 'Phishing-Merkmale, Pruefschritte, Meldeprozess', 'iso27001', false, 'micro', 180, 1.5, 10, 70, 20);
|
|
|
|
-- Get module IDs for CTM
|
|
SELECT id INTO m_gdpr_bas FROM training_modules WHERE tenant_id = t_id AND module_code = 'GDPR-BAS';
|
|
SELECT id INTO m_gdpr_adv FROM training_modules WHERE tenant_id = t_id AND module_code = 'GDPR-ADV';
|
|
SELECT id INTO m_gdpr_art FROM training_modules WHERE tenant_id = t_id AND module_code = 'GDPR-ART';
|
|
SELECT id INTO m_gdpr_dpia FROM training_modules WHERE tenant_id = t_id AND module_code = 'GDPR-DPIA';
|
|
SELECT id INTO m_nis2_m1 FROM training_modules WHERE tenant_id = t_id AND module_code = 'NIS2-M1';
|
|
SELECT id INTO m_nis2_m2 FROM training_modules WHERE tenant_id = t_id AND module_code = 'NIS2-M2';
|
|
SELECT id INTO m_nis2_m3 FROM training_modules WHERE tenant_id = t_id AND module_code = 'NIS2-M3';
|
|
SELECT id INTO m_nis2_m4 FROM training_modules WHERE tenant_id = t_id AND module_code = 'NIS2-M4';
|
|
SELECT id INTO m_nis2_m5 FROM training_modules WHERE tenant_id = t_id AND module_code = 'NIS2-M5';
|
|
SELECT id INTO m_isms_bas FROM training_modules WHERE tenant_id = t_id AND module_code = 'ISMS-BAS';
|
|
SELECT id INTO m_isms_int FROM training_modules WHERE tenant_id = t_id AND module_code = 'ISMS-INT';
|
|
SELECT id INTO m_isms_aud FROM training_modules WHERE tenant_id = t_id AND module_code = 'ISMS-AUD';
|
|
SELECT id INTO m_ai_bas FROM training_modules WHERE tenant_id = t_id AND module_code = 'AI-BAS';
|
|
SELECT id INTO m_ai_adv FROM training_modules WHERE tenant_id = t_id AND module_code = 'AI-ADV';
|
|
SELECT id INTO m_ai_risk FROM training_modules WHERE tenant_id = t_id AND module_code = 'AI-RISK';
|
|
SELECT id INTO m_gesch_bas FROM training_modules WHERE tenant_id = t_id AND module_code = 'GESCH-BAS';
|
|
SELECT id INTO m_hin_bas FROM training_modules WHERE tenant_id = t_id AND module_code = 'HIN-BAS';
|
|
SELECT id INTO m_mail_sec FROM training_modules WHERE tenant_id = t_id AND module_code = 'MAIL-SEC';
|
|
SELECT id INTO m_soc_eng FROM training_modules WHERE tenant_id = t_id AND module_code = 'SOC-ENG';
|
|
SELECT id INTO m_phish FROM training_modules WHERE tenant_id = t_id AND module_code = 'PHISH';
|
|
|
|
-- CTM: R1 Geschaeftsfuehrung
|
|
INSERT INTO training_matrix (tenant_id, role_code, module_id, is_mandatory, priority) VALUES
|
|
(t_id, 'R1', m_gdpr_bas, true, 1), (t_id, 'R1', m_nis2_m1, true, 1),
|
|
(t_id, 'R1', m_nis2_m5, true, 2), (t_id, 'R1', m_isms_bas, true, 2),
|
|
(t_id, 'R1', m_ai_bas, true, 3), (t_id, 'R1', m_gesch_bas, true, 3);
|
|
|
|
-- CTM: R2 IT-Leitung
|
|
INSERT INTO training_matrix (tenant_id, role_code, module_id, is_mandatory, priority) VALUES
|
|
(t_id, 'R2', m_nis2_m2, true, 1), (t_id, 'R2', m_nis2_m5, true, 1),
|
|
(t_id, 'R2', m_isms_bas, true, 2), (t_id, 'R2', m_isms_int, true, 2),
|
|
(t_id, 'R2', m_mail_sec, true, 3), (t_id, 'R2', m_soc_eng, true, 3),
|
|
(t_id, 'R2', m_phish, true, 3);
|
|
|
|
-- CTM: R3 DSB
|
|
INSERT INTO training_matrix (tenant_id, role_code, module_id, is_mandatory, priority) VALUES
|
|
(t_id, 'R3', m_gdpr_bas, true, 1), (t_id, 'R3', m_gdpr_adv, true, 1),
|
|
(t_id, 'R3', m_gdpr_art, true, 1), (t_id, 'R3', m_gdpr_dpia, true, 2),
|
|
(t_id, 'R3', m_isms_bas, true, 3);
|
|
|
|
-- CTM: R4 ISB
|
|
INSERT INTO training_matrix (tenant_id, role_code, module_id, is_mandatory, priority) VALUES
|
|
(t_id, 'R4', m_nis2_m2, true, 1), (t_id, 'R4', m_nis2_m3, true, 1),
|
|
(t_id, 'R4', m_nis2_m4, true, 1), (t_id, 'R4', m_nis2_m5, true, 1),
|
|
(t_id, 'R4', m_isms_bas, true, 2), (t_id, 'R4', m_isms_int, true, 2),
|
|
(t_id, 'R4', m_isms_aud, true, 2);
|
|
|
|
-- CTM: R5 HR
|
|
INSERT INTO training_matrix (tenant_id, role_code, module_id, is_mandatory, priority) VALUES
|
|
(t_id, 'R5', m_gdpr_bas, true, 1), (t_id, 'R5', m_gdpr_art, true, 1),
|
|
(t_id, 'R5', m_hin_bas, true, 2);
|
|
|
|
-- CTM: R6 Einkauf
|
|
INSERT INTO training_matrix (tenant_id, role_code, module_id, is_mandatory, priority) VALUES
|
|
(t_id, 'R6', m_gdpr_bas, true, 1), (t_id, 'R6', m_nis2_m3, true, 1),
|
|
(t_id, 'R6', m_gesch_bas, true, 2);
|
|
|
|
-- CTM: R7 Fachabteilung
|
|
INSERT INTO training_matrix (tenant_id, role_code, module_id, is_mandatory, priority) VALUES
|
|
(t_id, 'R7', m_gdpr_bas, true, 1), (t_id, 'R7', m_ai_bas, true, 2),
|
|
(t_id, 'R7', m_mail_sec, true, 3), (t_id, 'R7', m_soc_eng, true, 3);
|
|
|
|
-- CTM: R8 IT-Admin
|
|
INSERT INTO training_matrix (tenant_id, role_code, module_id, is_mandatory, priority) VALUES
|
|
(t_id, 'R8', m_nis2_m2, true, 1), (t_id, 'R8', m_nis2_m5, true, 1),
|
|
(t_id, 'R8', m_isms_bas, true, 2), (t_id, 'R8', m_mail_sec, true, 3),
|
|
(t_id, 'R8', m_soc_eng, true, 3), (t_id, 'R8', m_phish, true, 3);
|
|
|
|
-- CTM: R9 Alle Mitarbeiter
|
|
INSERT INTO training_matrix (tenant_id, role_code, module_id, is_mandatory, priority) VALUES
|
|
(t_id, 'R9', m_gdpr_bas, true, 1), (t_id, 'R9', m_mail_sec, true, 2),
|
|
(t_id, 'R9', m_soc_eng, true, 2), (t_id, 'R9', m_phish, true, 2);
|
|
|
|
RAISE NOTICE 'Training Engine seed data inserted successfully';
|
|
END $$;
|