61d3883479
Steps 1 and 3 of docs/calendar-auth-migration.md. The calendar is the last module still authenticating against its own users/sessions tables; this is the groundwork that lets step 4 swap it for the shared admin identity. An event now records its creator twice: created_by_id, the legacy INT into the calendar database's own users table, and created_by_user_id, the admin module's VARCHAR(36) id. The two live in different databases, so there is no foreign key and no join - a cross-schema reference would tie the schemas' lifecycles together, and the name is instead resolved through one lookup per result set against the admin database. The creator is only ever rendered as a name; nothing authorises on it. That is what makes the planned account backfill unnecessary - dropped by decision - and what makes the read degrade rather than fail: an admin id that no longer resolves falls back, and an unreachable admin database costs a name rather than the response. The public calendar is read anonymously by nachklang.art and has never depended on the admin database being up. Since there is no backfill, step 5 dropping the legacy users table would have erased the authorship of every pre-cutover event. Migration 002 brings that part of step 5 forward and snapshots the names onto the events themselves, so the data is safe well before the table holding it goes away. The same SELECT and row mapper existed in four copies; collapsed to one of each first, so the dual read is written once rather than four times. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
107 lines
5.8 KiB
SQL
107 lines
5.8 KiB
SQL
-- Local dev only. Real schema, provided directly by the repo owner
|
|
-- (calendars, events, event_versions, sessions, users) - not a guess.
|
|
-- Columns added by this repo's own migrations under sql/calendar/ are folded
|
|
-- in here rather than appended, so a fresh dev container matches production
|
|
-- after every migration has been applied. Keep the two in step.
|
|
USE nachklang_calendar;
|
|
|
|
CREATE TABLE `calendars` (
|
|
`calendar_id` int(11) NOT NULL AUTO_INCREMENT,
|
|
`name` text NOT NULL,
|
|
`includes_calendars` text DEFAULT NULL,
|
|
PRIMARY KEY (`calendar_id`)
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
|
|
|
CREATE TABLE `users` (
|
|
`user_id` int(11) NOT NULL AUTO_INCREMENT,
|
|
`full_name` text NOT NULL,
|
|
`password_hash` text DEFAULT NULL,
|
|
`email` text NOT NULL,
|
|
`is_active` tinyint(1) DEFAULT 0,
|
|
`pw_reset_token_hash` text DEFAULT NULL,
|
|
`activation_token` text DEFAULT NULL,
|
|
PRIMARY KEY (`user_id`),
|
|
UNIQUE KEY `email` (`email`) USING HASH
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
|
|
|
CREATE TABLE `sessions` (
|
|
`session_id` int(11) NOT NULL AUTO_INCREMENT,
|
|
`user_id` int(11) NOT NULL,
|
|
`session_key_hash` text DEFAULT NULL,
|
|
`created_date` datetime DEFAULT current_timestamp(),
|
|
`valid_until` datetime DEFAULT (current_timestamp() + interval 30 day),
|
|
`last_ip` text DEFAULT NULL,
|
|
PRIMARY KEY (`session_id`),
|
|
KEY `sessions_users_user_id_fk` (`user_id`),
|
|
CONSTRAINT `sessions_users_user_id_fk` FOREIGN KEY (`user_id`) REFERENCES `users` (`user_id`)
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
|
|
|
CREATE TABLE `events` (
|
|
`event_id` int(11) NOT NULL AUTO_INCREMENT,
|
|
`calendar_id` int(11) NOT NULL,
|
|
`uuid` text NOT NULL,
|
|
`created_date` datetime DEFAULT current_timestamp(),
|
|
`created_by_id` int(11) NOT NULL,
|
|
-- Bridge to the admin module's user ids; see sql/calendar/001_add_admin_user_bridge.sql.
|
|
`created_by_user_id` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
|
-- Archived creator name; see sql/calendar/002_snapshot_legacy_creator_names.sql.
|
|
`created_by_name` varchar(255) DEFAULT NULL,
|
|
PRIMARY KEY (`event_id`),
|
|
KEY `events_calendars_calendar_id_fk` (`calendar_id`),
|
|
KEY `events_users_user_id_fk` (`created_by_id`),
|
|
KEY `events_created_by_user_idx` (`created_by_user_id`),
|
|
CONSTRAINT `events_calendars_calendar_id_fk` FOREIGN KEY (`calendar_id`) REFERENCES `calendars` (`calendar_id`),
|
|
CONSTRAINT `events_users_user_id_fk` FOREIGN KEY (`created_by_id`) REFERENCES `users` (`user_id`)
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
|
|
|
CREATE TABLE `event_versions` (
|
|
`event_version_id` int(11) NOT NULL AUTO_INCREMENT,
|
|
`event_id` int(11) NOT NULL,
|
|
`name` text DEFAULT NULL,
|
|
`description` text DEFAULT NULL,
|
|
`start_datetime` datetime DEFAULT NULL,
|
|
`end_datetime` datetime DEFAULT NULL,
|
|
`whole_day` tinyint(1) DEFAULT NULL,
|
|
`repeat_frequency` text DEFAULT NULL,
|
|
`location` text DEFAULT NULL,
|
|
`url` text DEFAULT NULL,
|
|
`version_created_by_id` int(11) DEFAULT NULL,
|
|
-- Bridge to the admin module's user ids; see sql/calendar/001_add_admin_user_bridge.sql.
|
|
`version_created_by_user_id` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
|
-- Archived editor name; see sql/calendar/002_snapshot_legacy_creator_names.sql.
|
|
`version_created_by_name` varchar(255) DEFAULT NULL,
|
|
`status` text DEFAULT NULL,
|
|
`version_created_at` datetime DEFAULT current_timestamp(),
|
|
PRIMARY KEY (`event_version_id`),
|
|
KEY `event_versions_events_event_id_fk` (`event_id`),
|
|
KEY `event_versions_users_user_id_fk` (`version_created_by_id`),
|
|
KEY `event_versions_created_by_user_idx` (`version_created_by_user_id`),
|
|
CONSTRAINT `event_versions_events_event_id_fk` FOREIGN KEY (`event_id`) REFERENCES `events` (`event_id`) ON DELETE CASCADE ON UPDATE CASCADE,
|
|
CONSTRAINT `event_versions_users_user_id_fk` FOREIGN KEY (`version_created_by_id`) REFERENCES `users` (`user_id`)
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
|
|
|
INSERT INTO calendars (calendar_id, name, includes_calendars) VALUES
|
|
(1, 'public', '[]'),
|
|
(2, 'members', '[]'),
|
|
(3, 'management', '[]'),
|
|
(4, 'choir', '[]'),
|
|
(5, 'birthdays', '[]');
|
|
|
|
-- Dev admin, password: devpassword
|
|
INSERT INTO users (email, password_hash, full_name, is_active) VALUES
|
|
('dev@nachklang.art', '$2b$10$vmj7POS/68SGE.eI7pGjMegrw0vNNZ2HVSUTra5NRsl8iOLwiMgZK', 'Dev Admin', 1);
|
|
|
|
-- Two rows are left on the legacy path and one carries an admin user id, so
|
|
-- dev exercises both branches of the step 3 dual-read rather than only the
|
|
-- happy one. It is deliberately a PUBLIC event, so the anonymous listing the
|
|
-- website uses covers both. The id is the dev admin from 04-admin-schema.sql.
|
|
INSERT INTO events (calendar_id, uuid, created_by_id, created_by_user_id, created_by_name) VALUES
|
|
(1, UUID(), 1, NULL, 'Dev Admin'),
|
|
(1, UUID(), 1, 'dev-user-0000-0000-0000-000000000001', NULL),
|
|
(1, UUID(), 1, NULL, 'Dev Admin');
|
|
|
|
INSERT INTO event_versions (event_id, name, description, start_datetime, end_datetime, whole_day, location, url, status, version_created_by_id, version_created_by_user_id, version_created_by_name) VALUES
|
|
(1, 'Frühlingskonzert 2026', 'Erstes Konzert der Reihe', '2026-04-18 19:00:00', '2026-04-18 21:00:00', 0, 'Musikhochschule, Karlsruhe', 'https://www.nachklang.art/events/fruehlingskonzert-2026', 'PUBLIC', 1, NULL, 'Dev Admin'),
|
|
(2, 'Sommerkonzert 2026', 'Zweites Konzert der Reihe', '2026-07-11 19:00:00', '2026-07-11 21:00:00', 0, 'Christuskirche, Karlsruhe', 'https://www.nachklang.art/events/sommerkonzert-2026', 'PUBLIC', 1, 'dev-user-0000-0000-0000-000000000001', NULL),
|
|
(3, 'Adventskonzert 2026', 'Drittes Konzert der Reihe', '2026-12-05 19:00:00', '2026-12-05 21:00:00', 0, 'Stadtkirche, Karlsruhe', 'https://www.nachklang.art/events/adventskonzert-2026', 'DRAFT', 1, NULL, 'Dev Admin');
|