-- ============================================================================ -- Africa Alert — Supabase tenant_id reconciliation script -- Paste into Supabase SQL Editor, then RUN. -- Safe to re-run (idempotent). -- Tenant UUID hard-coded below (matches local system_settings.tenant_id). -- ============================================================================ DO $script$ DECLARE v_tenant_id uuid := '5b39bcb5-506a-456d-b7cb-91730b397cc1'; BEGIN -- Sanity check: tenant must exist in the tenants table. IF NOT EXISTS (SELECT 1 FROM public.tenants WHERE id = v_tenant_id) THEN RAISE EXCEPTION 'Tenant % not found in public.tenants. Insert it first or re-run the registration cycle on the server.', v_tenant_id; END IF; --------------------------------------------------------------------------- -- 1. Backfill null tenant_id on every business table that carries one -- (skip 'tenants' itself, plus 'notices' / 'sync_logs' / 'sync_metadata' -- / 'sync_config' which don't have a tenant_id column). --------------------------------------------------------------------------- UPDATE public.admission_enquiries SET tenant_id = v_tenant_id WHERE tenant_id IS NULL; UPDATE public.applicants SET tenant_id = v_tenant_id WHERE tenant_id IS NULL; UPDATE public.assignments SET tenant_id = v_tenant_id WHERE tenant_id IS NULL; UPDATE public.attendance SET tenant_id = v_tenant_id WHERE tenant_id IS NULL; UPDATE public.attendance_new SET tenant_id = v_tenant_id WHERE tenant_id IS NULL; UPDATE public.calendar_events SET tenant_id = v_tenant_id WHERE tenant_id IS NULL; UPDATE public.classes SET tenant_id = v_tenant_id WHERE tenant_id IS NULL; UPDATE public.club_announcements SET tenant_id = v_tenant_id WHERE tenant_id IS NULL; UPDATE public.club_attendance SET tenant_id = v_tenant_id WHERE tenant_id IS NULL; UPDATE public.club_chats SET tenant_id = v_tenant_id WHERE tenant_id IS NULL; UPDATE public.club_memberships SET tenant_id = v_tenant_id WHERE tenant_id IS NULL; UPDATE public.clubs SET tenant_id = v_tenant_id WHERE tenant_id IS NULL; UPDATE public.complaints SET tenant_id = v_tenant_id WHERE tenant_id IS NULL; UPDATE public.course_enrolments SET tenant_id = v_tenant_id WHERE tenant_id IS NULL; UPDATE public.course_grades SET tenant_id = v_tenant_id WHERE tenant_id IS NULL; UPDATE public.courses SET tenant_id = v_tenant_id WHERE tenant_id IS NULL; UPDATE public.crossword_clues SET tenant_id = v_tenant_id WHERE tenant_id IS NULL; UPDATE public.crossword_games SET tenant_id = v_tenant_id WHERE tenant_id IS NULL; UPDATE public.crossword_sessions SET tenant_id = v_tenant_id WHERE tenant_id IS NULL; UPDATE public.departments SET tenant_id = v_tenant_id WHERE tenant_id IS NULL; UPDATE public.enrollments SET tenant_id = v_tenant_id WHERE tenant_id IS NULL; UPDATE public.events SET tenant_id = v_tenant_id WHERE tenant_id IS NULL; UPDATE public.exam_answers SET tenant_id = v_tenant_id WHERE tenant_id IS NULL; UPDATE public.exam_attempts SET tenant_id = v_tenant_id WHERE tenant_id IS NULL; UPDATE public.exam_groups SET tenant_id = v_tenant_id WHERE tenant_id IS NULL; UPDATE public.exam_schedules SET tenant_id = v_tenant_id WHERE tenant_id IS NULL; UPDATE public.expenses SET tenant_id = v_tenant_id WHERE tenant_id IS NULL; UPDATE public.fee_groups SET tenant_id = v_tenant_id WHERE tenant_id IS NULL; UPDATE public.grades SET tenant_id = v_tenant_id WHERE tenant_id IS NULL; UPDATE public.hostel_attendance SET tenant_id = v_tenant_id WHERE tenant_id IS NULL; UPDATE public.hostel_fees SET tenant_id = v_tenant_id WHERE tenant_id IS NULL; UPDATE public.hostels SET tenant_id = v_tenant_id WHERE tenant_id IS NULL; UPDATE public.item_categories SET tenant_id = v_tenant_id WHERE tenant_id IS NULL; UPDATE public.item_issues SET tenant_id = v_tenant_id WHERE tenant_id IS NULL; UPDATE public.item_stock SET tenant_id = v_tenant_id WHERE tenant_id IS NULL; UPDATE public.items SET tenant_id = v_tenant_id WHERE tenant_id IS NULL; UPDATE public.leave_requests SET tenant_id = v_tenant_id WHERE tenant_id IS NULL; UPDATE public.leave_types SET tenant_id = v_tenant_id WHERE tenant_id IS NULL; UPDATE public.library_books SET tenant_id = v_tenant_id WHERE tenant_id IS NULL; UPDATE public.library_fines SET tenant_id = v_tenant_id WHERE tenant_id IS NULL; UPDATE public.library_issues SET tenant_id = v_tenant_id WHERE tenant_id IS NULL; UPDATE public.library_reservations SET tenant_id = v_tenant_id WHERE tenant_id IS NULL; UPDATE public.messages SET tenant_id = v_tenant_id WHERE tenant_id IS NULL; UPDATE public.messages_new SET tenant_id = v_tenant_id WHERE tenant_id IS NULL; UPDATE public.parent_students SET tenant_id = v_tenant_id WHERE tenant_id IS NULL; UPDATE public.payments SET tenant_id = v_tenant_id WHERE tenant_id IS NULL; UPDATE public.payroll_runs SET tenant_id = v_tenant_id WHERE tenant_id IS NULL; UPDATE public.payslips SET tenant_id = v_tenant_id WHERE tenant_id IS NULL; UPDATE public.phone_call_logs SET tenant_id = v_tenant_id WHERE tenant_id IS NULL; UPDATE public.pickup_points SET tenant_id = v_tenant_id WHERE tenant_id IS NULL; UPDATE public.postal_dispatch SET tenant_id = v_tenant_id WHERE tenant_id IS NULL; UPDATE public.question_banks SET tenant_id = v_tenant_id WHERE tenant_id IS NULL; UPDATE public.room_assignments SET tenant_id = v_tenant_id WHERE tenant_id IS NULL; UPDATE public.room_types SET tenant_id = v_tenant_id WHERE tenant_id IS NULL; UPDATE public.rooms SET tenant_id = v_tenant_id WHERE tenant_id IS NULL; UPDATE public.routes SET tenant_id = v_tenant_id WHERE tenant_id IS NULL; UPDATE public.salary_grades SET tenant_id = v_tenant_id WHERE tenant_id IS NULL; UPDATE public.settings SET tenant_id = v_tenant_id WHERE tenant_id IS NULL; UPDATE public.staff_attendance SET tenant_id = v_tenant_id WHERE tenant_id IS NULL; UPDATE public.staff_records SET tenant_id = v_tenant_id WHERE tenant_id IS NULL; UPDATE public.staff_roles SET tenant_id = v_tenant_id WHERE tenant_id IS NULL; UPDATE public.stock_transactions SET tenant_id = v_tenant_id WHERE tenant_id IS NULL; UPDATE public.store_locations SET tenant_id = v_tenant_id WHERE tenant_id IS NULL; UPDATE public.student_fees SET tenant_id = v_tenant_id WHERE tenant_id IS NULL; UPDATE public.subjects SET tenant_id = v_tenant_id WHERE tenant_id IS NULL; UPDATE public.subjects_new SET tenant_id = v_tenant_id WHERE tenant_id IS NULL; UPDATE public.submissions SET tenant_id = v_tenant_id WHERE tenant_id IS NULL; UPDATE public.suppliers SET tenant_id = v_tenant_id WHERE tenant_id IS NULL; UPDATE public.system_settings SET tenant_id = v_tenant_id WHERE tenant_id IS NULL; UPDATE public.transport_allocations SET tenant_id = v_tenant_id WHERE tenant_id IS NULL; UPDATE public.transport_attendance SET tenant_id = v_tenant_id WHERE tenant_id IS NULL; UPDATE public.users SET tenant_id = v_tenant_id WHERE tenant_id IS NULL; UPDATE public.vacancies SET tenant_id = v_tenant_id WHERE tenant_id IS NULL; UPDATE public.vehicle_routes SET tenant_id = v_tenant_id WHERE tenant_id IS NULL; UPDATE public.vehicles SET tenant_id = v_tenant_id WHERE tenant_id IS NULL; UPDATE public.visitor_logs SET tenant_id = v_tenant_id WHERE tenant_id IS NULL; UPDATE public.audit_logs SET tenant_id = v_tenant_id WHERE tenant_id IS NULL; --------------------------------------------------------------------------- -- 2. Enforce NOT NULL now that all rows have a tenant. -- Wrapped in dynamic SQL + DO blocks so missing columns don't blow up. --------------------------------------------------------------------------- PERFORM ( SELECT string_agg( format('ALTER TABLE public.%I ALTER COLUMN tenant_id SET NOT NULL', t.table_name), '; ' ) FROM information_schema.columns WHERE table_schema = 'public' AND column_name = 'tenant_id' AND is_nullable = 'YES' AND table_name <> 'tenants' )::text; IF (SELECT string_agg( format('ALTER TABLE public.%I ALTER COLUMN tenant_id SET NOT NULL', t.table_name), '; ') FROM information_schema.columns WHERE table_schema='public' AND column_name='tenant_id' AND is_nullable='YES' AND table_name <> 'tenants') IS NOT NULL THEN EXECUTE ( SELECT string_agg( format('ALTER TABLE public.%I ALTER COLUMN tenant_id SET NOT NULL', t.table_name), '; ' ) FROM information_schema.columns WHERE table_schema = 'public' AND column_name = 'tenant_id' AND is_nullable = 'YES' AND table_name <> 'tenants' ); END IF; --------------------------------------------------------------------------- -- 3. Indexes on tenant_id (RLS checks every row; you want these). --------------------------------------------------------------------------- PERFORM ( SELECT string_agg( format('CREATE INDEX IF NOT EXISTS %I ON public.%I (tenant_id)', t.table_name || '_tenant_id_idx', t.table_name), '; ' ) FROM information_schema.columns WHERE table_schema = 'public' AND column_name = 'tenant_id' AND table_name <> 'tenants' ); IF (SELECT string_agg( format('CREATE INDEX IF NOT EXISTS %I ON public.%I (tenant_id)', t.table_name || '_tenant_id_idx', t.table_name), '; ') FROM information_schema.columns WHERE table_schema='public' AND column_name='tenant_id' AND table_name <> 'tenants') IS NOT NULL THEN EXECUTE ( SELECT string_agg( format('CREATE INDEX IF NOT EXISTS %I ON public.%I (tenant_id)', t.table_name || '_tenant_id_idx', t.table_name), '; ' ) FROM information_schema.columns WHERE table_schema = 'public' AND column_name = 'tenant_id' AND table_name <> 'tenants' ); END IF; RAISE NOTICE 'Tenant % reconciled: rows backfilled, NOT NULL enforced, indexes created.', v_tenant_id; END $script$; -- ============================================================================ -- 4. Enable RLS + tenant-isolation policies on every tenant_id-bearing table. -- service_role key bypasses RLS, so the sync engine still has full access. -- Run as a separate batch (RLS changes can't be inside the DO block). -- ============================================================================ DO $rls$ DECLARE r record; BEGIN FOR r IN SELECT c.table_name FROM information_schema.columns c WHERE c.table_schema = 'public' AND c.column_name = 'tenant_id' AND c.table_name <> 'tenants' LOOP EXECUTE format('ALTER TABLE public.%I ENABLE ROW LEVEL SECURITY', r.table_name); -- Drop any prior version of our policy to keep this idempotent. EXECUTE format('DROP POLICY IF EXISTS tenant_isolation ON public.%I', r.table_name); EXECUTE format( 'CREATE POLICY tenant_isolation ON public.%I USING (tenant_id::text = current_setting(''app.current_tenant'', true)) WITH CHECK (tenant_id::text = current_setting(''app.current_tenant'', true))', r.table_name ); END LOOP; END $rls$;