CREATE TABLE IF NOT EXISTS public.portal_function_visibility ( id UUID NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY, portal TEXT NOT NULL, function_key TEXT NOT NULL, enabled BOOLEAN NOT NULL DEFAULT true, owner_user_id UUID NULL, created_by UUID NULL, created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), CONSTRAINT portal_function_visibility_portal_check CHECK (portal IN ('board', 'homeowner')), CONSTRAINT portal_function_visibility_scope_check CHECK ( (portal = 'homeowner') OR (owner_user_id IS NULL) ), CONSTRAINT portal_function_visibility_unique_rule UNIQUE (portal, function_key, owner_user_id) ); ALTER TABLE public.portal_function_visibility ENABLE ROW LEVEL SECURITY; CREATE INDEX IF NOT EXISTS idx_portal_function_visibility_portal ON public.portal_function_visibility (portal, function_key); CREATE INDEX IF NOT EXISTS idx_portal_function_visibility_owner_user ON public.portal_function_visibility (owner_user_id) WHERE owner_user_id IS NOT NULL; CREATE OR REPLACE FUNCTION public.can_view_portal_function(_portal TEXT, _function_key TEXT, _user_id UUID DEFAULT auth.uid()) RETURNS BOOLEAN LANGUAGE sql STABLE SECURITY DEFINER SET search_path = public AS $$ SELECT COALESCE(( SELECT pfv.enabled FROM public.portal_function_visibility pfv WHERE pfv.portal = _portal AND pfv.function_key = _function_key AND (pfv.owner_user_id = _user_id OR pfv.owner_user_id IS NULL) ORDER BY (pfv.owner_user_id = _user_id) DESC LIMIT 1 ), true) $$; CREATE POLICY "Admins and managers manage portal visibility" ON public.portal_function_visibility FOR ALL TO authenticated USING ( public.has_role(auth.uid(), 'admin'::public.app_role) OR public.has_role(auth.uid(), 'manager'::public.app_role) ) WITH CHECK ( public.has_role(auth.uid(), 'admin'::public.app_role) OR public.has_role(auth.uid(), 'manager'::public.app_role) ); CREATE POLICY "Users can read applicable portal visibility" ON public.portal_function_visibility FOR SELECT TO authenticated USING (owner_user_id IS NULL OR owner_user_id = auth.uid()); DROP TRIGGER IF EXISTS update_portal_function_visibility_updated_at ON public.portal_function_visibility; CREATE TRIGGER update_portal_function_visibility_updated_at BEFORE UPDATE ON public.portal_function_visibility FOR EACH ROW EXECUTE FUNCTION public.update_updated_at_column();