-- ============================================================================= -- Migration 015: Veranstaltungen (Events / General Calendar) -- General event calendar for Feuerwehr Dashboard, separate from the training -- calendar (uebungen). Supports categories, RSVPs, and iCal subscriptions. -- Depends on: 001_create_users_table.sql (uuid-ossp, pgcrypto extensions, -- users table, update_updated_at_column trigger function) -- ============================================================================= -- ----------------------------------------------------------------------------- -- 1. Event categories table -- ----------------------------------------------------------------------------- CREATE TABLE IF NOT EXISTS veranstaltung_kategorien ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), name VARCHAR(255) NOT NULL, beschreibung TEXT, farbe VARCHAR(7) NOT NULL DEFAULT '#1976d2', -- hex colour for UI chips icon VARCHAR(100), -- MUI icon name, e.g. 'Event', 'FireTruck' erstellt_von UUID REFERENCES users(id) ON DELETE SET NULL, erstellt_am TIMESTAMPTZ NOT NULL DEFAULT NOW(), aktualisiert_am TIMESTAMPTZ NOT NULL DEFAULT NOW() ); CREATE TRIGGER update_veranstaltung_kategorien_aktualisiert_am BEFORE UPDATE ON veranstaltung_kategorien FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); -- ----------------------------------------------------------------------------- -- 2. Main events table -- ----------------------------------------------------------------------------- CREATE TABLE IF NOT EXISTS veranstaltungen ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), titel VARCHAR(500) NOT NULL, beschreibung TEXT, ort VARCHAR(500), ort_url VARCHAR(1000), -- optional maps/navigation link kategorie_id UUID REFERENCES veranstaltung_kategorien(id) ON DELETE SET NULL, datum_von TIMESTAMPTZ NOT NULL, datum_bis TIMESTAMPTZ NOT NULL, ganztaegig BOOLEAN NOT NULL DEFAULT FALSE, -- zielgruppen: array of Authentik group names, e.g. '{dashboard_mitglied,dashboard_jugend}' zielgruppen TEXT[] NOT NULL DEFAULT '{}', alle_gruppen BOOLEAN NOT NULL DEFAULT FALSE, -- TRUE = visible to all members max_teilnehmer INTEGER CHECK (max_teilnehmer > 0), anmeldung_erforderlich BOOLEAN NOT NULL DEFAULT FALSE, anmeldung_bis TIMESTAMPTZ, erstellt_von UUID NOT NULL REFERENCES users(id) ON DELETE RESTRICT, abgesagt BOOLEAN NOT NULL DEFAULT FALSE, abgesagt_grund TEXT, abgesagt_am TIMESTAMPTZ, erstellt_am TIMESTAMPTZ NOT NULL DEFAULT NOW(), aktualisiert_am TIMESTAMPTZ NOT NULL DEFAULT NOW(), CONSTRAINT veranstaltung_datum_reihenfolge CHECK (datum_bis >= datum_von) ); CREATE INDEX IF NOT EXISTS idx_veranstaltungen_datum_von ON veranstaltungen(datum_von); CREATE INDEX IF NOT EXISTS idx_veranstaltungen_datum_bis ON veranstaltungen(datum_bis); CREATE INDEX IF NOT EXISTS idx_veranstaltungen_kategorie_id ON veranstaltungen(kategorie_id); CREATE INDEX IF NOT EXISTS idx_veranstaltungen_abgesagt ON veranstaltungen(abgesagt) WHERE abgesagt = FALSE; CREATE INDEX IF NOT EXISTS idx_veranstaltungen_alle_gruppen ON veranstaltungen(alle_gruppen); -- Compound index for the most common calendar-range query CREATE INDEX IF NOT EXISTS idx_veranstaltungen_datum_von_bis ON veranstaltungen(datum_von, datum_bis); CREATE TRIGGER update_veranstaltungen_aktualisiert_am BEFORE UPDATE ON veranstaltungen FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); -- ----------------------------------------------------------------------------- -- 3. RSVP / attendance table -- ----------------------------------------------------------------------------- CREATE TABLE IF NOT EXISTS veranstaltung_teilnahmen ( veranstaltung_id UUID NOT NULL REFERENCES veranstaltungen(id) ON DELETE CASCADE, user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, -- status values: zugesagt, abgesagt, erschienen, unbekannt status VARCHAR(20) NOT NULL DEFAULT 'unbekannt', notiz VARCHAR(500), aktualisiert_am TIMESTAMPTZ NOT NULL DEFAULT NOW(), PRIMARY KEY (veranstaltung_id, user_id) ); CREATE INDEX IF NOT EXISTS idx_veranstaltung_teilnahmen_veranstaltung_id ON veranstaltung_teilnahmen(veranstaltung_id); CREATE INDEX IF NOT EXISTS idx_veranstaltung_teilnahmen_user_id ON veranstaltung_teilnahmen(user_id); -- ----------------------------------------------------------------------------- -- 4. Per-user iCal subscription tokens -- One token per user — covers the full events calendar feed for that user. -- ----------------------------------------------------------------------------- CREATE TABLE IF NOT EXISTS veranstaltung_ical_tokens ( user_id UUID PRIMARY KEY REFERENCES users(id) ON DELETE CASCADE, token VARCHAR(128) UNIQUE NOT NULL DEFAULT encode(gen_random_bytes(32), 'hex'), erstellt_am TIMESTAMPTZ NOT NULL DEFAULT NOW(), zuletzt_verwendet_am TIMESTAMPTZ ); CREATE INDEX IF NOT EXISTS idx_veranstaltung_ical_tokens_token ON veranstaltung_ical_tokens(token); -- ----------------------------------------------------------------------------- -- 5. Seed default event categories -- ----------------------------------------------------------------------------- INSERT INTO veranstaltung_kategorien (name, farbe, icon) VALUES ('Allgemein', '#1976d2', 'Event'), ('Ausbildung', '#2e7d32', 'School'), ('Gesellschaft', '#e65100', 'People'), ('Feuerwehrjugend', '#f57c00', 'ChildCare'), ('Kommando', '#6a1b9a', 'Shield') ON CONFLICT DO NOTHING;