-- ===================================================== -- Missing Database Constraints -- -- Adds CHECK constraints for data sanity, UNIQUE index -- for email per account, and IBAN format validation. -- -- Fixes existing invalid data before adding constraints. -- ===================================================== -- Fix existing invalid data before adding constraints UPDATE public.members SET date_of_birth = NULL WHERE date_of_birth IS NOT NULL AND date_of_birth > current_date; UPDATE public.members SET exit_date = entry_date WHERE exit_date IS NOT NULL AND entry_date IS NOT NULL AND exit_date < entry_date; UPDATE public.members SET entry_date = current_date WHERE entry_date IS NOT NULL AND entry_date > current_date; -- Normalize IBANs in sepa_mandates to uppercase, strip spaces UPDATE public.sepa_mandates SET iban = upper(regexp_replace(iban, '\s', '', 'g')) WHERE iban IS NOT NULL AND iban != ''; -- Date sanity constraints ALTER TABLE public.members ADD CONSTRAINT chk_members_dob_not_future CHECK (date_of_birth IS NULL OR date_of_birth <= current_date); ALTER TABLE public.members ADD CONSTRAINT chk_members_exit_after_entry CHECK (exit_date IS NULL OR entry_date IS NULL OR exit_date >= entry_date); ALTER TABLE public.members ADD CONSTRAINT chk_members_entry_not_future CHECK (entry_date IS NULL OR entry_date <= current_date); -- Email uniqueness per account (partial index — allows NULLs and empty strings) CREATE UNIQUE INDEX IF NOT EXISTS uix_members_email_per_account ON public.members(account_id, lower(email)) WHERE email IS NOT NULL AND email != ''; -- IBAN format on sepa_mandates (2-letter country + 2 check digits + 11-30 alphanumeric) ALTER TABLE public.sepa_mandates ADD CONSTRAINT chk_sepa_iban_format CHECK (iban ~ '^[A-Z]{2}[0-9]{2}[A-Z0-9]{11,30}$'); -- Mandate reference must not be empty ALTER TABLE public.sepa_mandates ADD CONSTRAINT chk_sepa_mandate_reference_not_empty CHECK (mandate_reference IS NOT NULL AND mandate_reference != ''); -- Member roles: from_date should not be after until_date ALTER TABLE public.member_roles ADD CONSTRAINT chk_member_roles_date_range CHECK (until_date IS NULL OR from_date IS NULL OR until_date >= from_date); -- Dues categories: amount must be non-negative ALTER TABLE public.dues_categories ADD CONSTRAINT chk_dues_amount_non_negative CHECK (amount >= 0);