-- Security Awareness Learning Platform Database Schema -- PostgreSQL 15+ -- Events table: Represents training events/sessions CREATE TABLE events ( id SERIAL PRIMARY KEY, name VARCHAR(255) NOT NULL, description TEXT, start_date TIMESTAMP, end_date TIMESTAMP, is_active BOOLEAN DEFAULT true, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); -- Participants table: Anonymous participants with pseudonyms CREATE TABLE participants ( id SERIAL PRIMARY KEY, pseudonym VARCHAR(100) NOT NULL, event_id INTEGER NOT NULL REFERENCES events(id) ON DELETE CASCADE, session_token VARCHAR(255) UNIQUE NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, last_active TIMESTAMP DEFAULT CURRENT_TIMESTAMP, UNIQUE(pseudonym, event_id) ); -- Lessons table: Catalog of available lessons CREATE TABLE lessons ( id SERIAL PRIMARY KEY, lesson_key VARCHAR(100) UNIQUE NOT NULL, title VARCHAR(255) NOT NULL, description TEXT, module_path VARCHAR(255) NOT NULL, config_path VARCHAR(255) NOT NULL, difficulty_level VARCHAR(50), estimated_duration INTEGER, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); -- Event_Lessons table: Lessons assigned to events with configuration CREATE TABLE event_lessons ( id SERIAL PRIMARY KEY, event_id INTEGER NOT NULL REFERENCES events(id) ON DELETE CASCADE, lesson_id INTEGER NOT NULL REFERENCES lessons(id) ON DELETE CASCADE, order_index INTEGER NOT NULL, max_points INTEGER NOT NULL DEFAULT 100, weight DECIMAL(5,2) DEFAULT 1.0, is_required BOOLEAN DEFAULT true, unlock_after_lesson_id INTEGER REFERENCES lessons(id), created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, UNIQUE(event_id, lesson_id), UNIQUE(event_id, order_index) ); -- Lesson_Progress table: Tracks participant progress through lessons CREATE TABLE lesson_progress ( id SERIAL PRIMARY KEY, participant_id INTEGER NOT NULL REFERENCES participants(id) ON DELETE CASCADE, event_lesson_id INTEGER NOT NULL REFERENCES event_lessons(id) ON DELETE CASCADE, status VARCHAR(50) NOT NULL DEFAULT 'not_started', started_at TIMESTAMP, completed_at TIMESTAMP, score INTEGER DEFAULT 0, attempts INTEGER DEFAULT 0, current_step INTEGER DEFAULT 0, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, UNIQUE(participant_id, event_lesson_id), CHECK (status IN ('not_started', 'in_progress', 'completed')) ); -- Lesson_Answers table: Stores participant answers CREATE TABLE lesson_answers ( id SERIAL PRIMARY KEY, lesson_progress_id INTEGER NOT NULL REFERENCES lesson_progress(id) ON DELETE CASCADE, question_key VARCHAR(100) NOT NULL, answer_data JSONB NOT NULL, is_correct BOOLEAN, points_awarded INTEGER DEFAULT 0, submitted_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, feedback TEXT ); -- Admin_Users table: Admin authentication CREATE TABLE admin_users ( id SERIAL PRIMARY KEY, username VARCHAR(100) UNIQUE NOT NULL, password_hash VARCHAR(255) NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, last_login TIMESTAMP ); -- Indexes for performance CREATE INDEX idx_participants_event ON participants(event_id); CREATE INDEX idx_participants_session ON participants(session_token); CREATE INDEX idx_participants_pseudonym ON participants(pseudonym); CREATE INDEX idx_event_lessons_event ON event_lessons(event_id); CREATE INDEX idx_event_lessons_lesson ON event_lessons(lesson_id); CREATE INDEX idx_event_lessons_order ON event_lessons(event_id, order_index); CREATE INDEX idx_lesson_progress_participant ON lesson_progress(participant_id); CREATE INDEX idx_lesson_progress_event_lesson ON lesson_progress(event_lesson_id); CREATE INDEX idx_lesson_progress_status ON lesson_progress(status); CREATE INDEX idx_lesson_answers_progress ON lesson_answers(lesson_progress_id); CREATE INDEX idx_lesson_answers_question ON lesson_answers(question_key); -- Trigger function to update updated_at timestamps CREATE OR REPLACE FUNCTION update_updated_at_column() RETURNS TRIGGER AS $$ BEGIN NEW.updated_at = CURRENT_TIMESTAMP; RETURN NEW; END; $$ language 'plpgsql'; -- Apply triggers to tables CREATE TRIGGER update_events_updated_at BEFORE UPDATE ON events FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); CREATE TRIGGER update_lessons_updated_at BEFORE UPDATE ON lessons FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); CREATE TRIGGER update_lesson_progress_updated_at BEFORE UPDATE ON lesson_progress FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); -- Insert default admin user (password: admin123 - CHANGE IN PRODUCTION!) -- bcrypt hash for 'admin123' with 10 rounds INSERT INTO admin_users (username, password_hash) VALUES ('admin', '$2b$10$mP8BvCik6In9lvWqxV57VuKglR3IqW4GfMoF.5fsT8HrTxRqscElW'); -- Comments for documentation COMMENT ON TABLE events IS 'Training events or sessions that participants can join'; COMMENT ON TABLE participants IS 'Anonymous participants identified by pseudonym within events'; COMMENT ON TABLE lessons IS 'Catalog of available lesson modules'; COMMENT ON TABLE event_lessons IS 'Lessons assigned to specific events with custom configuration'; COMMENT ON TABLE lesson_progress IS 'Tracks individual participant progress through lessons'; COMMENT ON TABLE lesson_answers IS 'Stores submitted answers with scoring information'; COMMENT ON TABLE admin_users IS 'Administrative users with full system access';