-- Nachklang e.V. Feedback module — initial schema for FEEDBACK_DB -- Apply manually against the FEEDBACK_DB database (separate from CALENDAR_DB). -- See nachklang-feedback/IMPLEMENTATION_PLAN.md §2 for the full rationale -- behind every design decision below (snapshot columns, denormalisation, -- absence-over-sentinels, hashed IPs only). -- -- Apply with e.g.: -- mysql -h -u -p < 001_init.sql -- -- Deliberately no USE statement here: the target database is selected via -- the mysql command line above (whatever FEEDBACK_DB is actually named in -- .env), not hardcoded to a literal schema name. -- 1. events ------------------------------------------------------------- CREATE TABLE events ( event_id INT AUTO_INCREMENT PRIMARY KEY, slug VARCHAR(80) NOT NULL, name VARCHAR(255) NOT NULL, subtitle VARCHAR(255) NULL, event_date DATE NOT NULL, feedback_deadline DATETIME NOT NULL, is_published TINYINT(1) NOT NULL DEFAULT 0, intro_text TEXT NULL, created_by_email VARCHAR(255) NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, UNIQUE KEY uq_events_slug (slug), KEY idx_events_eligibility (is_published, event_date, feedback_deadline) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; -- 2. songs (per-event setlist) ------------------------------------------ CREATE TABLE songs ( song_id INT AUTO_INCREMENT PRIMARY KEY, event_id INT NOT NULL, title VARCHAR(255) NOT NULL, composer VARCHAR(255) NULL, position INT NOT NULL DEFAULT 0, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, CONSTRAINT fk_songs_event FOREIGN KEY (event_id) REFERENCES events(event_id) ON DELETE CASCADE, KEY idx_songs_event_position (event_id, position) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; -- 3. questions (global reusable library) --------------------------------- CREATE TABLE questions ( question_id INT AUTO_INCREMENT PRIMARY KEY, label VARCHAR(500) NOT NULL, help_text VARCHAR(500) NULL, question_type ENUM('SONG_PICK','SONG_RATING','FREE_TEXT') NOT NULL, is_archived TINYINT(1) NOT NULL DEFAULT 0, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, KEY idx_questions_archived_type (is_archived, question_type) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; -- 4. event_questions (join + ordering) ----------------------------------- CREATE TABLE event_questions ( event_question_id INT AUTO_INCREMENT PRIMARY KEY, event_id INT NOT NULL, question_id INT NOT NULL, position INT NOT NULL DEFAULT 0, is_active TINYINT(1) NOT NULL DEFAULT 1, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, CONSTRAINT fk_eq_event FOREIGN KEY (event_id) REFERENCES events(event_id) ON DELETE CASCADE, CONSTRAINT fk_eq_question FOREIGN KEY (question_id) REFERENCES questions(question_id) ON DELETE RESTRICT, UNIQUE KEY uq_eq_event_question (event_id, question_id), KEY idx_eq_event_position (event_id, position, is_active) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; -- 5. submissions ---------------------------------------------------------- CREATE TABLE submissions ( submission_id INT AUTO_INCREMENT PRIMARY KEY, event_id INT NOT NULL, submitted_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, ip_hash CHAR(64) NULL, has_guestbook TINYINT(1) NOT NULL DEFAULT 0, has_newsletter TINYINT(1) NOT NULL DEFAULT 0, CONSTRAINT fk_sub_event FOREIGN KEY (event_id) REFERENCES events(event_id) ON DELETE CASCADE, KEY idx_sub_event_time (event_id, submitted_at), KEY idx_sub_iphash_time (ip_hash, submitted_at) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; -- 6. submission_answers ---------------------------------------------------- -- Deliberate denormalisation: question label/type and song title are -- snapshotted at submission time so later edits to the question library -- never retroactively change what a past submission means. CREATE TABLE submission_answers ( answer_id INT AUTO_INCREMENT PRIMARY KEY, submission_id INT NOT NULL, event_id INT NOT NULL, question_id INT NULL, event_question_id INT NULL, question_label_snapshot VARCHAR(500) NOT NULL, question_type ENUM('SONG_PICK','SONG_RATING','FREE_TEXT') NOT NULL, position_snapshot INT NOT NULL DEFAULT 0, song_id INT NULL, song_title_snapshot VARCHAR(255) NULL, rating TINYINT NULL, text_answer TEXT NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, CONSTRAINT fk_ans_submission FOREIGN KEY (submission_id) REFERENCES submissions(submission_id) ON DELETE CASCADE, CONSTRAINT fk_ans_event FOREIGN KEY (event_id) REFERENCES events(event_id) ON DELETE CASCADE, CONSTRAINT fk_ans_question FOREIGN KEY (question_id) REFERENCES questions(question_id) ON DELETE SET NULL, CONSTRAINT fk_ans_song FOREIGN KEY (song_id) REFERENCES songs(song_id) ON DELETE SET NULL, CONSTRAINT chk_ans_rating CHECK (rating IS NULL OR (rating BETWEEN 1 AND 5)), KEY idx_ans_submission (submission_id), KEY idx_ans_report (event_id, question_id, song_id), KEY idx_ans_type (event_id, question_type) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; -- 7. guest_book_entries ----------------------------------------------------- -- Private, admin-only. No public wall in v1. CREATE TABLE guest_book_entries ( entry_id INT AUTO_INCREMENT PRIMARY KEY, submission_id INT NOT NULL, event_id INT NOT NULL, display_name VARCHAR(255) NULL, message TEXT NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, CONSTRAINT fk_gb_submission FOREIGN KEY (submission_id) REFERENCES submissions(submission_id) ON DELETE CASCADE, CONSTRAINT fk_gb_event FOREIGN KEY (event_id) REFERENCES events(event_id) ON DELETE CASCADE, KEY idx_gb_event_time (event_id, created_at) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; -- 8. newsletter_signups ----------------------------------------------------- CREATE TABLE newsletter_signups ( signup_id INT AUTO_INCREMENT PRIMARY KEY, submission_id INT NOT NULL, event_id INT NOT NULL, first_name VARCHAR(120) NOT NULL, last_name VARCHAR(120) NOT NULL, email VARCHAR(255) NOT NULL, consent_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, consent_text_version VARCHAR(40) NOT NULL, sync_status ENUM('PENDING','SENT','FAILED','SKIPPED') NOT NULL DEFAULT 'PENDING', sync_attempts INT NOT NULL DEFAULT 0, synced_at DATETIME NULL, external_id VARCHAR(120) NULL, last_error TEXT NULL, CONSTRAINT fk_nl_submission FOREIGN KEY (submission_id) REFERENCES submissions(submission_id) ON DELETE CASCADE, CONSTRAINT fk_nl_event FOREIGN KEY (event_id) REFERENCES events(event_id) ON DELETE CASCADE, KEY idx_nl_sync_status (sync_status), KEY idx_nl_email (email) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;