-- Wedding website bootstrap schema -- Requires: PostgreSQL 13+ CREATE EXTENSION IF NOT EXISTS pgcrypto; CREATE TABLE IF NOT EXISTS invitations ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), token text NOT NULL, display_name text NOT NULL, language text NOT NULL, contact_email text NULL, contact_phone text NULL, max_party_size integer NOT NULL DEFAULT 1, allow_member_edits boolean NOT NULL DEFAULT false, notes text NULL, created_at timestamptz NOT NULL DEFAULT now(), updated_at timestamptz NOT NULL DEFAULT now(), CONSTRAINT invitations_max_party_size_positive CHECK (max_party_size > 0) ); CREATE UNIQUE INDEX IF NOT EXISTS invitations_token_unique ON invitations (token); CREATE TABLE IF NOT EXISTS invitation_members ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), invitation_id uuid NOT NULL REFERENCES invitations(id) ON DELETE CASCADE, name text NOT NULL, is_primary_contact boolean NOT NULL DEFAULT false, attending boolean NULL, meal_preference text NULL, dietary_restrictions text NULL, notes text NULL, created_at timestamptz NOT NULL DEFAULT now(), updated_at timestamptz NOT NULL DEFAULT now() ); CREATE INDEX IF NOT EXISTS invitation_members_invitation_id_idx ON invitation_members (invitation_id); CREATE TABLE IF NOT EXISTS invitation_responses ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), invitation_id uuid NOT NULL REFERENCES invitations(id) ON DELETE CASCADE, submitted_at timestamptz NULL, arrival_date date NULL, departure_date date NULL, stay_location text NULL, coming_from text NULL, needs_transport_help boolean NULL, message_to_couple text NULL, created_at timestamptz NOT NULL DEFAULT now(), updated_at timestamptz NOT NULL DEFAULT now() ); CREATE UNIQUE INDEX IF NOT EXISTS invitation_responses_invitation_id_unique ON invitation_responses (invitation_id); CREATE TABLE IF NOT EXISTS photo_uploads ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), invitation_id uuid NOT NULL REFERENCES invitations(id) ON DELETE CASCADE, bucket text NOT NULL, object_key text NOT NULL, original_filename text NULL, content_type text NULL, size_bytes integer NULL, created_at timestamptz NOT NULL DEFAULT now(), updated_at timestamptz NOT NULL DEFAULT now(), CONSTRAINT photo_uploads_size_bytes_non_negative CHECK (size_bytes IS NULL OR size_bytes >= 0) ); CREATE UNIQUE INDEX IF NOT EXISTS photo_uploads_invitation_id_unique ON photo_uploads (invitation_id); CREATE TABLE IF NOT EXISTS song_requests ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), invitation_id uuid NOT NULL REFERENCES invitations(id) ON DELETE CASCADE, title text NOT NULL, artist text NULL, note text NULL, created_at timestamptz NOT NULL DEFAULT now() ); CREATE INDEX IF NOT EXISTS song_requests_invitation_id_idx ON song_requests (invitation_id); CREATE TABLE IF NOT EXISTS toast_requests ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), invitation_id uuid NOT NULL REFERENCES invitations(id) ON DELETE CASCADE, member_id uuid NULL REFERENCES invitation_members(id) ON DELETE SET NULL, speaker_name text NOT NULL, estimated_minutes integer NULL, note text NULL, status text NOT NULL DEFAULT 'pending', created_at timestamptz NOT NULL DEFAULT now(), updated_at timestamptz NOT NULL DEFAULT now(), CONSTRAINT toast_requests_status_valid CHECK (status IN ('pending', 'accepted', 'declined', 'scheduled')), CONSTRAINT toast_requests_estimated_minutes_positive CHECK (estimated_minutes IS NULL OR estimated_minutes > 0) ); CREATE INDEX IF NOT EXISTS toast_requests_invitation_id_idx ON toast_requests (invitation_id); CREATE TABLE IF NOT EXISTS guest_messages ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), invitation_id uuid NOT NULL REFERENCES invitations(id) ON DELETE CASCADE, type text NOT NULL, message text NOT NULL, created_at timestamptz NOT NULL DEFAULT now(), CONSTRAINT guest_messages_type_valid CHECK (type IN ('advice', 'memory', 'wish')) ); CREATE INDEX IF NOT EXISTS guest_messages_invitation_id_idx ON guest_messages (invitation_id); CREATE TABLE IF NOT EXISTS admin_audit_events ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), admin_email text NOT NULL, action text NOT NULL, target_type text NULL, target_id uuid NULL, metadata jsonb NULL, created_at timestamptz NOT NULL DEFAULT now() );