-- Drop and recreate filtered_profiles view without SECURITY DEFINER -- This view applies field-level privacy using security definer functions -- but the view itself should NOT be security definer DROP VIEW IF EXISTS public.filtered_profiles; CREATE VIEW public.filtered_profiles AS SELECT p.id, p.user_id, p.username, p.display_name, p.privacy_level, p.created_at, p.updated_at, -- Field-level privacy using security definer functions CASE WHEN can_view_profile_field(COALESCE(auth.uid(), '00000000-0000-0000-0000-000000000000'::uuid), p.user_id, 'bio') THEN p.bio ELSE NULL END AS bio, CASE WHEN can_view_profile_field(COALESCE(auth.uid(), '00000000-0000-0000-0000-000000000000'::uuid), p.user_id, 'avatar_url') THEN p.avatar_url ELSE NULL END AS avatar_url, CASE WHEN can_view_profile_field(COALESCE(auth.uid(), '00000000-0000-0000-0000-000000000000'::uuid), p.user_id, 'avatar_image_id') THEN p.avatar_image_id ELSE NULL END AS avatar_image_id, CASE WHEN can_view_profile_field(COALESCE(auth.uid(), '00000000-0000-0000-0000-000000000000'::uuid), p.user_id, 'preferred_pronouns') THEN p.preferred_pronouns ELSE NULL END AS preferred_pronouns, CASE WHEN can_view_profile_field(COALESCE(auth.uid(), '00000000-0000-0000-0000-000000000000'::uuid), p.user_id, 'preferred_pronouns') THEN p.show_pronouns ELSE false END AS show_pronouns, CASE WHEN can_view_profile_field(COALESCE(auth.uid(), '00000000-0000-0000-0000-000000000000'::uuid), p.user_id, 'personal_location') THEN p.personal_location ELSE NULL END AS personal_location, CASE WHEN can_view_profile_field(COALESCE(auth.uid(), '00000000-0000-0000-0000-000000000000'::uuid), p.user_id, 'location_id') THEN p.location_id ELSE NULL END AS location_id, CASE WHEN can_view_profile_field(COALESCE(auth.uid(), '00000000-0000-0000-0000-000000000000'::uuid), p.user_id, 'home_park_id') THEN p.home_park_id ELSE NULL END AS home_park_id, CASE WHEN can_view_profile_field(COALESCE(auth.uid(), '00000000-0000-0000-0000-000000000000'::uuid), p.user_id, 'date_of_birth') THEN p.date_of_birth ELSE NULL END AS date_of_birth, p.timezone, p.preferred_language, p.theme_preference, CASE WHEN can_view_profile_field(COALESCE(auth.uid(), '00000000-0000-0000-0000-000000000000'::uuid), p.user_id, 'ride_count') THEN p.ride_count ELSE 0 END AS ride_count, CASE WHEN can_view_profile_field(COALESCE(auth.uid(), '00000000-0000-0000-0000-000000000000'::uuid), p.user_id, 'ride_count') THEN p.coaster_count ELSE 0 END AS coaster_count, CASE WHEN can_view_profile_field(COALESCE(auth.uid(), '00000000-0000-0000-0000-000000000000'::uuid), p.user_id, 'ride_count') THEN p.park_count ELSE 0 END AS park_count, CASE WHEN can_view_profile_field(COALESCE(auth.uid(), '00000000-0000-0000-0000-000000000000'::uuid), p.user_id, 'ride_count') THEN p.review_count ELSE 0 END AS review_count, CASE WHEN can_view_profile_field(COALESCE(auth.uid(), '00000000-0000-0000-0000-000000000000'::uuid), p.user_id, 'ride_count') THEN p.reputation_score ELSE 0 END AS reputation_score, CASE WHEN auth.uid() = p.user_id OR is_moderator(COALESCE(auth.uid(), '00000000-0000-0000-0000-000000000000'::uuid)) THEN p.banned ELSE false END AS banned FROM public.profiles p WHERE (NOT p.banned OR auth.uid() = p.user_id OR is_moderator(COALESCE(auth.uid(), '00000000-0000-0000-0000-000000000000'::uuid))) AND (p.privacy_level = 'public' OR auth.uid() = p.user_id OR is_moderator(COALESCE(auth.uid(), '00000000-0000-0000-0000-000000000000'::uuid))); COMMENT ON VIEW public.filtered_profiles IS 'Profile view with field-level privacy controls. Uses security definer functions for permission checks but view itself respects querying user context.';