-- Chattyness Row-Level Security Policies
-- PostgreSQL 18
--
-- RLS policies for data isolation and access control
-- Load via: psql -f schema/policies/001_rls.sql
--
-- IMPORTANT: The application must set session variables before queries:
--
--   For authenticated users:
--     SELECT public.set_current_user_id('user-uuid-here');
--     SELECT public.set_current_realm_id('realm-uuid-here');  -- when in a realm
--
--   For guest users:
--     SELECT public.set_current_user_id(NULL);
--     SELECT public.set_current_guest_session_id('guest-session-uuid-here');
--     SELECT public.set_current_realm_id('realm-uuid-here');  -- when in a realm
--
-- The chattyness_app role is used by the application backend.

\set ON_ERROR_STOP on

BEGIN;

-- =============================================================================
-- Grant Usage on Schemas to Application Role
-- =============================================================================

GRANT USAGE ON SCHEMA server TO chattyness_app;
GRANT USAGE ON SCHEMA auth TO chattyness_app;
GRANT USAGE ON SCHEMA realm TO chattyness_app;
GRANT USAGE ON SCHEMA scene TO chattyness_app;
GRANT USAGE ON SCHEMA chat TO chattyness_app;
GRANT USAGE ON SCHEMA audit TO chattyness_app;

-- =============================================================================
-- SERVER SCHEMA POLICIES
-- =============================================================================
-- Server data is readable by all, writable only by server admins.
-- =============================================================================

-- server.config
ALTER TABLE server.config ENABLE ROW LEVEL SECURITY;

CREATE POLICY server_config_select ON server.config
    FOR SELECT TO chattyness_app
    USING (true);

GRANT SELECT ON server.config TO chattyness_app;

-- server.staff
ALTER TABLE server.staff ENABLE ROW LEVEL SECURITY;

CREATE POLICY server_staff_select ON server.staff
    FOR SELECT TO chattyness_app
    USING (true);

GRANT SELECT ON server.staff TO chattyness_app;

-- server.props
ALTER TABLE server.props ENABLE ROW LEVEL SECURITY;

CREATE POLICY server_props_select ON server.props
    FOR SELECT TO chattyness_app
    USING (true);

CREATE POLICY server_props_insert ON server.props
    FOR INSERT TO chattyness_app
    WITH CHECK (public.is_server_admin());

CREATE POLICY server_props_update ON server.props
    FOR UPDATE TO chattyness_app
    USING (public.is_server_admin())
    WITH CHECK (public.is_server_admin());

CREATE POLICY server_props_delete ON server.props
    FOR DELETE TO chattyness_app
    USING (public.is_server_admin());

GRANT SELECT ON server.props TO chattyness_app;
GRANT INSERT, UPDATE, DELETE ON server.props TO chattyness_app;

-- server.audio
ALTER TABLE server.audio ENABLE ROW LEVEL SECURITY;

CREATE POLICY server_audio_select ON server.audio
    FOR SELECT TO chattyness_app
    USING (true);

CREATE POLICY server_audio_modify ON server.audio
    FOR ALL TO chattyness_app
    USING (public.is_server_admin())
    WITH CHECK (public.is_server_admin());

GRANT SELECT, INSERT, UPDATE, DELETE ON server.audio TO chattyness_app;

-- server.reserved_names
ALTER TABLE server.reserved_names ENABLE ROW LEVEL SECURITY;

CREATE POLICY server_reserved_names_select ON server.reserved_names
    FOR SELECT TO chattyness_app
    USING (true);

CREATE POLICY server_reserved_names_modify ON server.reserved_names
    FOR ALL TO chattyness_app
    USING (public.is_server_admin())
    WITH CHECK (public.is_server_admin());

GRANT SELECT, INSERT, UPDATE, DELETE ON server.reserved_names TO chattyness_app;

-- server.scripts
ALTER TABLE server.scripts ENABLE ROW LEVEL SECURITY;

CREATE POLICY server_scripts_select ON server.scripts
    FOR SELECT TO chattyness_app
    USING (true);

CREATE POLICY server_scripts_modify ON server.scripts
    FOR ALL TO chattyness_app
    USING (public.is_server_admin())
    WITH CHECK (public.is_server_admin());

GRANT SELECT, INSERT, UPDATE, DELETE ON server.scripts TO chattyness_app;

-- server.ip_bans
ALTER TABLE server.ip_bans ENABLE ROW LEVEL SECURITY;

CREATE POLICY server_ip_bans_select ON server.ip_bans
    FOR SELECT TO chattyness_app
    USING (true);

CREATE POLICY server_ip_bans_modify ON server.ip_bans
    FOR ALL TO chattyness_app
    USING (public.is_server_admin())
    WITH CHECK (public.is_server_admin());

GRANT SELECT, INSERT, UPDATE, DELETE ON server.ip_bans TO chattyness_app;

-- server.bans
ALTER TABLE server.bans ENABLE ROW LEVEL SECURITY;

CREATE POLICY server_bans_select ON server.bans
    FOR SELECT TO chattyness_app
    USING (true);

CREATE POLICY server_bans_modify ON server.bans
    FOR ALL TO chattyness_app
    USING (public.is_server_moderator())
    WITH CHECK (public.is_server_moderator());

GRANT SELECT, INSERT, UPDATE, DELETE ON server.bans TO chattyness_app;

-- server.mutes
ALTER TABLE server.mutes ENABLE ROW LEVEL SECURITY;

CREATE POLICY server_mutes_select ON server.mutes
    FOR SELECT TO chattyness_app
    USING (true);

CREATE POLICY server_mutes_modify ON server.mutes
    FOR ALL TO chattyness_app
    USING (public.is_server_moderator())
    WITH CHECK (public.is_server_moderator());

GRANT SELECT, INSERT, UPDATE, DELETE ON server.mutes TO chattyness_app;

-- server.content_filters
ALTER TABLE server.content_filters ENABLE ROW LEVEL SECURITY;

CREATE POLICY server_content_filters_select ON server.content_filters
    FOR SELECT TO chattyness_app
    USING (true);

CREATE POLICY server_content_filters_modify ON server.content_filters
    FOR ALL TO chattyness_app
    USING (public.is_server_admin())
    WITH CHECK (public.is_server_admin());

GRANT SELECT, INSERT, UPDATE, DELETE ON server.content_filters TO chattyness_app;

-- server.moderation_actions
ALTER TABLE server.moderation_actions ENABLE ROW LEVEL SECURITY;

CREATE POLICY server_moderation_actions_target ON server.moderation_actions
    FOR SELECT TO chattyness_app
    USING (target_user_id = public.current_user_id());

CREATE POLICY server_moderation_actions_mod ON server.moderation_actions
    FOR ALL TO chattyness_app
    USING (public.is_server_moderator())
    WITH CHECK (public.is_server_moderator());

GRANT SELECT, INSERT ON server.moderation_actions TO chattyness_app;

-- =============================================================================
-- AUTH SCHEMA POLICIES
-- =============================================================================

-- auth.users
ALTER TABLE auth.users ENABLE ROW LEVEL SECURITY;

CREATE POLICY auth_users_select ON auth.users
    FOR SELECT TO chattyness_app
    USING (
        id = public.current_user_id()
        OR NOT auth.has_blocked(id, public.current_user_id())
    );

CREATE POLICY auth_users_update ON auth.users
    FOR UPDATE TO chattyness_app
    USING (id = public.current_user_id())
    WITH CHECK (id = public.current_user_id());

CREATE POLICY auth_users_insert ON auth.users
    FOR INSERT TO chattyness_app
    WITH CHECK (true);

CREATE POLICY auth_users_admin ON auth.users
    FOR ALL TO chattyness_app
    USING (public.is_server_admin())
    WITH CHECK (public.is_server_admin());

GRANT SELECT, UPDATE ON auth.users TO chattyness_app;
GRANT INSERT, DELETE ON auth.users TO chattyness_app;

-- auth.sessions
ALTER TABLE auth.sessions ENABLE ROW LEVEL SECURITY;

CREATE POLICY auth_sessions_own ON auth.sessions
    FOR ALL TO chattyness_app
    USING (user_id = public.current_user_id())
    WITH CHECK (user_id = public.current_user_id());

CREATE POLICY auth_sessions_admin ON auth.sessions
    FOR SELECT TO chattyness_app
    USING (public.is_server_admin());

GRANT SELECT, INSERT, UPDATE, DELETE ON auth.sessions TO chattyness_app;

-- auth.guest_sessions
ALTER TABLE auth.guest_sessions ENABLE ROW LEVEL SECURITY;

CREATE POLICY auth_guest_sessions_all ON auth.guest_sessions
    FOR ALL TO chattyness_app
    USING (true)
    WITH CHECK (true);

GRANT SELECT, INSERT, UPDATE, DELETE ON auth.guest_sessions TO chattyness_app;

-- auth.tower_sessions
ALTER TABLE auth.tower_sessions ENABLE ROW LEVEL SECURITY;

CREATE POLICY auth_tower_sessions_all ON auth.tower_sessions
    FOR ALL TO chattyness_app
    USING (true)
    WITH CHECK (true);

GRANT SELECT, INSERT, UPDATE, DELETE ON auth.tower_sessions TO chattyness_app;

-- auth.friendships
ALTER TABLE auth.friendships ENABLE ROW LEVEL SECURITY;

CREATE POLICY auth_friendships_own ON auth.friendships
    FOR ALL TO chattyness_app
    USING (
        friend_a = public.current_user_id()
        OR friend_b = public.current_user_id()
    )
    WITH CHECK (
        friend_a = public.current_user_id()
        OR friend_b = public.current_user_id()
    );

GRANT SELECT, INSERT, UPDATE, DELETE ON auth.friendships TO chattyness_app;

-- auth.blocks
ALTER TABLE auth.blocks ENABLE ROW LEVEL SECURITY;

CREATE POLICY auth_blocks_own ON auth.blocks
    FOR ALL TO chattyness_app
    USING (blocker_id = public.current_user_id())
    WITH CHECK (blocker_id = public.current_user_id());

CREATE POLICY auth_blocks_mod ON auth.blocks
    FOR SELECT TO chattyness_app
    USING (public.is_server_moderator());

GRANT SELECT, INSERT, DELETE ON auth.blocks TO chattyness_app;

-- auth.mutes (user personal mutes)
ALTER TABLE auth.mutes ENABLE ROW LEVEL SECURITY;

CREATE POLICY auth_mutes_own ON auth.mutes
    FOR ALL TO chattyness_app
    USING (muter_id = public.current_user_id())
    WITH CHECK (muter_id = public.current_user_id());

GRANT SELECT, INSERT, DELETE ON auth.mutes TO chattyness_app;

-- auth.user_scripts
ALTER TABLE auth.user_scripts ENABLE ROW LEVEL SECURITY;

CREATE POLICY auth_user_scripts_own ON auth.user_scripts
    FOR ALL TO chattyness_app
    USING (user_id = public.current_user_id())
    WITH CHECK (user_id = public.current_user_id());

CREATE POLICY auth_user_scripts_admin ON auth.user_scripts
    FOR SELECT TO chattyness_app
    USING (public.is_server_admin());

GRANT SELECT, INSERT, UPDATE, DELETE ON auth.user_scripts TO chattyness_app;

-- auth.inventory
ALTER TABLE auth.inventory ENABLE ROW LEVEL SECURITY;

CREATE POLICY auth_inventory_own ON auth.inventory
    FOR ALL TO chattyness_app
    USING (user_id = public.current_user_id())
    WITH CHECK (user_id = public.current_user_id());

CREATE POLICY auth_inventory_view ON auth.inventory
    FOR SELECT TO chattyness_app
    USING (true);

GRANT SELECT, INSERT, UPDATE, DELETE ON auth.inventory TO chattyness_app;

-- auth.avatars
ALTER TABLE auth.avatars ENABLE ROW LEVEL SECURITY;

CREATE POLICY auth_avatars_own ON auth.avatars
    FOR ALL TO chattyness_app
    USING (user_id = public.current_user_id())
    WITH CHECK (user_id = public.current_user_id());

CREATE POLICY auth_avatars_view ON auth.avatars
    FOR SELECT TO chattyness_app
    USING (true);

GRANT SELECT, INSERT, UPDATE, DELETE ON auth.avatars TO chattyness_app;

-- auth.active_avatars
ALTER TABLE auth.active_avatars ENABLE ROW LEVEL SECURITY;

CREATE POLICY auth_active_avatars_own ON auth.active_avatars
    FOR ALL TO chattyness_app
    USING (user_id = public.current_user_id())
    WITH CHECK (user_id = public.current_user_id());

CREATE POLICY auth_active_avatars_view ON auth.active_avatars
    FOR SELECT TO chattyness_app
    USING (true);

GRANT SELECT, INSERT, UPDATE, DELETE ON auth.active_avatars TO chattyness_app;

-- =============================================================================
-- REALM SCHEMA POLICIES
-- =============================================================================

-- realm.realms
ALTER TABLE realm.realms ENABLE ROW LEVEL SECURITY;

CREATE POLICY realm_realms_select ON realm.realms
    FOR SELECT TO chattyness_app
    USING (
        privacy = 'public'
        OR privacy = 'unlisted'
        OR owner_id = public.current_user_id()
        OR public.has_realm_membership(id)
        OR public.is_server_admin()
    );

CREATE POLICY realm_realms_owner ON realm.realms
    FOR UPDATE TO chattyness_app
    USING (owner_id = public.current_user_id())
    WITH CHECK (owner_id = public.current_user_id());

CREATE POLICY realm_realms_insert ON realm.realms
    FOR INSERT TO chattyness_app
    WITH CHECK (owner_id = public.current_user_id());

CREATE POLICY realm_realms_delete ON realm.realms
    FOR DELETE TO chattyness_app
    USING (owner_id = public.current_user_id() OR public.is_server_admin());

CREATE POLICY realm_realms_admin ON realm.realms
    FOR ALL TO chattyness_app
    USING (public.is_server_admin())
    WITH CHECK (public.is_server_admin());

GRANT SELECT, INSERT, UPDATE, DELETE ON realm.realms TO chattyness_app;

-- realm.scenes
ALTER TABLE realm.scenes ENABLE ROW LEVEL SECURITY;

CREATE POLICY realm_scenes_select ON realm.scenes
    FOR SELECT TO chattyness_app
    USING (
        EXISTS (
            SELECT 1 FROM realm.realms r
            WHERE r.id = realm_id
            AND (
                r.privacy IN ('public', 'unlisted')
                OR r.owner_id = public.current_user_id()
                OR public.has_realm_membership(r.id)
                OR public.is_server_admin()
            )
        )
    );

CREATE POLICY realm_scenes_modify ON realm.scenes
    FOR ALL TO chattyness_app
    USING (
        EXISTS (
            SELECT 1 FROM realm.memberships m
            WHERE m.realm_id = realm.scenes.realm_id
            AND m.user_id = public.current_user_id()
            AND m.role IN ('owner', 'builder')
        )
        OR public.is_server_admin()
    )
    WITH CHECK (
        EXISTS (
            SELECT 1 FROM realm.memberships m
            WHERE m.realm_id = realm.scenes.realm_id
            AND m.user_id = public.current_user_id()
            AND m.role IN ('owner', 'builder')
        )
        OR public.is_server_admin()
    );

GRANT SELECT, INSERT, UPDATE, DELETE ON realm.scenes TO chattyness_app;

-- realm.memberships
ALTER TABLE realm.memberships ENABLE ROW LEVEL SECURITY;

CREATE POLICY realm_memberships_select ON realm.memberships
    FOR SELECT TO chattyness_app
    USING (
        user_id = public.current_user_id()
        OR EXISTS (
            SELECT 1 FROM realm.realms r
            WHERE r.id = realm_id
            AND (r.privacy = 'public' OR public.has_realm_membership(r.id))
        )
        OR public.is_server_admin()
    );

CREATE POLICY realm_memberships_own ON realm.memberships
    FOR ALL TO chattyness_app
    USING (user_id = public.current_user_id())
    WITH CHECK (user_id = public.current_user_id());

CREATE POLICY realm_memberships_mod ON realm.memberships
    FOR ALL TO chattyness_app
    USING (public.is_realm_moderator(realm_id))
    WITH CHECK (public.is_realm_moderator(realm_id));

GRANT SELECT, INSERT, UPDATE, DELETE ON realm.memberships TO chattyness_app;

-- realm.realm_scripts
ALTER TABLE realm.realm_scripts ENABLE ROW LEVEL SECURITY;

CREATE POLICY realm_realm_scripts_select ON realm.realm_scripts
    FOR SELECT TO chattyness_app
    USING (
        EXISTS (
            SELECT 1 FROM realm.realms r
            WHERE r.id = realm_id
        )
    );

CREATE POLICY realm_realm_scripts_modify ON realm.realm_scripts
    FOR ALL TO chattyness_app
    USING (
        EXISTS (
            SELECT 1 FROM realm.memberships m
            WHERE m.realm_id = realm.realm_scripts.realm_id
            AND m.user_id = public.current_user_id()
            AND m.role IN ('owner', 'builder')
        )
        OR public.is_server_admin()
    );

GRANT SELECT, INSERT, UPDATE, DELETE ON realm.realm_scripts TO chattyness_app;

-- realm.props
ALTER TABLE realm.props ENABLE ROW LEVEL SECURITY;

CREATE POLICY realm_props_select ON realm.props
    FOR SELECT TO chattyness_app
    USING (
        public.has_realm_membership(realm_id)
        OR public.is_server_admin()
    );

CREATE POLICY realm_props_modify ON realm.props
    FOR ALL TO chattyness_app
    USING (
        EXISTS (
            SELECT 1 FROM realm.memberships m
            WHERE m.realm_id = realm.props.realm_id
            AND m.user_id = public.current_user_id()
            AND m.role IN ('owner', 'builder')
        )
        OR public.is_server_admin()
    );

GRANT SELECT, INSERT, UPDATE, DELETE ON realm.props TO chattyness_app;

-- realm.reports
ALTER TABLE realm.reports ENABLE ROW LEVEL SECURITY;

CREATE POLICY realm_reports_own ON realm.reports
    FOR SELECT TO chattyness_app
    USING (reporter_id = public.current_user_id());

CREATE POLICY realm_reports_insert ON realm.reports
    FOR INSERT TO chattyness_app
    WITH CHECK (reporter_id = public.current_user_id());

CREATE POLICY realm_reports_mod ON realm.reports
    FOR ALL TO chattyness_app
    USING (
        public.is_server_moderator()
        OR public.is_realm_moderator(realm_id)
    );

GRANT SELECT, INSERT, UPDATE ON realm.reports TO chattyness_app;

-- realm.bans
ALTER TABLE realm.bans ENABLE ROW LEVEL SECURITY;

CREATE POLICY realm_bans_select ON realm.bans
    FOR SELECT TO chattyness_app
    USING (true);

CREATE POLICY realm_bans_modify ON realm.bans
    FOR ALL TO chattyness_app
    USING (
        public.is_server_moderator()
        OR public.is_realm_moderator(realm_id)
    );

GRANT SELECT, INSERT, UPDATE, DELETE ON realm.bans TO chattyness_app;

-- realm.mutes (realm moderation mutes)
ALTER TABLE realm.mutes ENABLE ROW LEVEL SECURITY;

CREATE POLICY realm_mutes_select ON realm.mutes
    FOR SELECT TO chattyness_app
    USING (true);

CREATE POLICY realm_mutes_modify ON realm.mutes
    FOR ALL TO chattyness_app
    USING (
        public.is_server_moderator()
        OR public.is_realm_moderator(realm_id)
    );

GRANT SELECT, INSERT, UPDATE, DELETE ON realm.mutes TO chattyness_app;

-- realm.content_filters
ALTER TABLE realm.content_filters ENABLE ROW LEVEL SECURITY;

CREATE POLICY realm_content_filters_select ON realm.content_filters
    FOR SELECT TO chattyness_app
    USING (
        public.has_realm_membership(realm_id)
        OR public.is_server_admin()
    );

CREATE POLICY realm_content_filters_modify ON realm.content_filters
    FOR ALL TO chattyness_app
    USING (
        EXISTS (
            SELECT 1 FROM realm.memberships m
            WHERE m.realm_id = realm.content_filters.realm_id
            AND m.user_id = public.current_user_id()
            AND m.role IN ('owner', 'moderator')
        )
        OR public.is_server_admin()
    );

GRANT SELECT, INSERT, UPDATE, DELETE ON realm.content_filters TO chattyness_app;

-- realm.moderation_actions
ALTER TABLE realm.moderation_actions ENABLE ROW LEVEL SECURITY;

CREATE POLICY realm_moderation_actions_target ON realm.moderation_actions
    FOR SELECT TO chattyness_app
    USING (target_user_id = public.current_user_id());

CREATE POLICY realm_moderation_actions_mod ON realm.moderation_actions
    FOR ALL TO chattyness_app
    USING (
        public.is_server_moderator()
        OR public.is_realm_moderator(realm_id)
    );

GRANT SELECT, INSERT ON realm.moderation_actions TO chattyness_app;

-- =============================================================================
-- SCENE SCHEMA POLICIES
-- =============================================================================

-- scene.instances
ALTER TABLE scene.instances ENABLE ROW LEVEL SECURITY;

CREATE POLICY scene_instances_select ON scene.instances
    FOR SELECT TO chattyness_app
    USING (
        instance_type = 'public'
        AND EXISTS (
            SELECT 1 FROM realm.scenes s
            JOIN realm.realms r ON r.id = s.realm_id
            WHERE s.id = scene_id
            AND (
                r.privacy IN ('public', 'unlisted')
                OR r.owner_id = public.current_user_id()
                OR public.has_realm_membership(r.id)
            )
        )
        OR
        (
            instance_type = 'private'
            AND (
                created_by = public.current_user_id()
                OR EXISTS (
                    SELECT 1 FROM scene.instance_invites ii
                    WHERE ii.instance_id = scene.instances.id
                    AND ii.invited_user_id = public.current_user_id()
                    AND ii.accepted_at IS NOT NULL
                )
            )
        )
        OR public.is_server_admin()
    );

CREATE POLICY scene_instances_insert ON scene.instances
    FOR INSERT TO chattyness_app
    WITH CHECK (true);

CREATE POLICY scene_instances_modify ON scene.instances
    FOR UPDATE TO chattyness_app
    USING (
        created_by = public.current_user_id()
        OR public.is_realm_moderator(
            (SELECT s.realm_id FROM realm.scenes s WHERE s.id = scene_id)
        )
    );

CREATE POLICY scene_instances_delete ON scene.instances
    FOR DELETE TO chattyness_app
    USING (
        created_by = public.current_user_id()
        OR public.is_realm_moderator(
            (SELECT s.realm_id FROM realm.scenes s WHERE s.id = scene_id)
        )
    );

GRANT SELECT, INSERT, UPDATE, DELETE ON scene.instances TO chattyness_app;

-- scene.instance_members
ALTER TABLE scene.instance_members ENABLE ROW LEVEL SECURITY;

CREATE POLICY scene_instance_members_select ON scene.instance_members
    FOR SELECT TO chattyness_app
    USING (
        EXISTS (
            SELECT 1 FROM realm.scenes s
            WHERE s.id = instance_id
        )
    );

CREATE POLICY scene_instance_members_own ON scene.instance_members
    FOR ALL TO chattyness_app
    USING (
        user_id = public.current_user_id()
        OR guest_session_id = public.current_guest_session_id()
    )
    WITH CHECK (
        user_id = public.current_user_id()
        OR guest_session_id = public.current_guest_session_id()
    );

GRANT SELECT, INSERT, UPDATE, DELETE ON scene.instance_members TO chattyness_app;

-- scene.instance_invites
ALTER TABLE scene.instance_invites ENABLE ROW LEVEL SECURITY;

CREATE POLICY scene_instance_invites_select ON scene.instance_invites
    FOR SELECT TO chattyness_app
    USING (
        invited_user_id = public.current_user_id()
        OR invited_by = public.current_user_id()
        OR public.is_server_admin()
    );

CREATE POLICY scene_instance_invites_own ON scene.instance_invites
    FOR ALL TO chattyness_app
    USING (
        invited_user_id = public.current_user_id()
        OR invited_by = public.current_user_id()
    )
    WITH CHECK (
        invited_by = public.current_user_id()
    );

GRANT SELECT, INSERT, UPDATE, DELETE ON scene.instance_invites TO chattyness_app;

-- scene.spots
ALTER TABLE scene.spots ENABLE ROW LEVEL SECURITY;

CREATE POLICY scene_spots_select ON scene.spots
    FOR SELECT TO chattyness_app
    USING (
        EXISTS (
            SELECT 1 FROM realm.scenes s
            WHERE s.id = scene_id
        )
    );

CREATE POLICY scene_spots_modify ON scene.spots
    FOR ALL TO chattyness_app
    USING (
        EXISTS (
            SELECT 1 FROM realm.scenes s
            JOIN realm.memberships m ON m.realm_id = s.realm_id
            WHERE s.id = scene_id
            AND m.user_id = public.current_user_id()
            AND m.role IN ('owner', 'builder')
        )
        OR public.is_server_admin()
    )
    WITH CHECK (
        EXISTS (
            SELECT 1 FROM realm.scenes s
            JOIN realm.memberships m ON m.realm_id = s.realm_id
            WHERE s.id = scene_id
            AND m.user_id = public.current_user_id()
            AND m.role IN ('owner', 'builder')
        )
        OR public.is_server_admin()
    );

GRANT SELECT, INSERT, UPDATE, DELETE ON scene.spots TO chattyness_app;

-- scene.spot_states
ALTER TABLE scene.spot_states ENABLE ROW LEVEL SECURITY;

CREATE POLICY scene_spot_states_select ON scene.spot_states
    FOR SELECT TO chattyness_app
    USING (
        EXISTS (
            SELECT 1 FROM scene.spots sp
            WHERE sp.id = spot_id
        )
    );

CREATE POLICY scene_spot_states_modify ON scene.spot_states
    FOR ALL TO chattyness_app
    USING (
        EXISTS (
            SELECT 1 FROM scene.spots sp
            JOIN realm.scenes s ON s.id = sp.scene_id
            JOIN realm.memberships m ON m.realm_id = s.realm_id
            WHERE sp.id = spot_id
            AND m.user_id = public.current_user_id()
            AND m.role IN ('owner', 'builder')
        )
        OR public.is_server_admin()
    )
    WITH CHECK (
        EXISTS (
            SELECT 1 FROM scene.spots sp
            JOIN realm.scenes s ON s.id = sp.scene_id
            JOIN realm.memberships m ON m.realm_id = s.realm_id
            WHERE sp.id = spot_id
            AND m.user_id = public.current_user_id()
            AND m.role IN ('owner', 'builder')
        )
        OR public.is_server_admin()
    );

GRANT SELECT, INSERT, UPDATE, DELETE ON scene.spot_states TO chattyness_app;

-- scene.scripts
ALTER TABLE scene.scripts ENABLE ROW LEVEL SECURITY;

CREATE POLICY scene_scripts_select ON scene.scripts
    FOR SELECT TO chattyness_app
    USING (
        EXISTS (
            SELECT 1 FROM realm.scenes s
            WHERE s.id = scene_id
        )
    );

CREATE POLICY scene_scripts_modify ON scene.scripts
    FOR ALL TO chattyness_app
    USING (
        EXISTS (
            SELECT 1 FROM realm.scenes s
            JOIN realm.memberships m ON m.realm_id = s.realm_id
            WHERE s.id = scene_id
            AND m.user_id = public.current_user_id()
            AND m.role IN ('owner', 'builder')
        )
        OR public.is_server_admin()
    );

GRANT SELECT, INSERT, UPDATE, DELETE ON scene.scripts TO chattyness_app;

-- scene.loose_props
ALTER TABLE scene.loose_props ENABLE ROW LEVEL SECURITY;

CREATE POLICY scene_loose_props_select ON scene.loose_props
    FOR SELECT TO chattyness_app
    USING (
        EXISTS (
            SELECT 1 FROM scene.instances i
            WHERE i.id = instance_id
        )
    );

CREATE POLICY scene_loose_props_modify ON scene.loose_props
    FOR ALL TO chattyness_app
    USING (true)
    WITH CHECK (true);

GRANT SELECT, INSERT, UPDATE, DELETE ON scene.loose_props TO chattyness_app;

-- scene.decorations
ALTER TABLE scene.decorations ENABLE ROW LEVEL SECURITY;

CREATE POLICY scene_decorations_select ON scene.decorations
    FOR SELECT TO chattyness_app
    USING (
        EXISTS (
            SELECT 1 FROM realm.scenes s
            WHERE s.id = scene_id
        )
    );

CREATE POLICY scene_decorations_modify ON scene.decorations
    FOR ALL TO chattyness_app
    USING (
        EXISTS (
            SELECT 1 FROM realm.scenes s
            JOIN realm.memberships m ON m.realm_id = s.realm_id
            WHERE s.id = scene_id
            AND m.user_id = public.current_user_id()
            AND m.role IN ('owner', 'builder')
        )
        OR public.is_server_admin()
    );

GRANT SELECT, INSERT, UPDATE, DELETE ON scene.decorations TO chattyness_app;

-- =============================================================================
-- CHAT SCHEMA POLICIES
-- =============================================================================

-- chat.messages
ALTER TABLE chat.messages ENABLE ROW LEVEL SECURITY;

CREATE POLICY chat_messages_select ON chat.messages
    FOR SELECT TO chattyness_app
    USING (
        (NOT is_deleted OR public.is_server_moderator())
        AND EXISTS (
            SELECT 1 FROM scene.instances i
            WHERE i.id = instance_id
        )
    );

CREATE POLICY chat_messages_insert ON chat.messages
    FOR INSERT TO chattyness_app
    WITH CHECK (
        user_id = public.current_user_id()
        OR guest_session_id = public.current_guest_session_id()
    );

CREATE POLICY chat_messages_update ON chat.messages
    FOR UPDATE TO chattyness_app
    USING (
        user_id = public.current_user_id()
        OR guest_session_id = public.current_guest_session_id()
        OR public.is_server_moderator()
    );

GRANT SELECT, INSERT, UPDATE ON chat.messages TO chattyness_app;

-- chat.whispers
ALTER TABLE chat.whispers ENABLE ROW LEVEL SECURITY;

CREATE POLICY chat_whispers_own ON chat.whispers
    FOR ALL TO chattyness_app
    USING (
        sender_id = public.current_user_id()
        OR recipient_id = public.current_user_id()
    )
    WITH CHECK (
        sender_id = public.current_user_id()
    );

GRANT SELECT, INSERT, UPDATE ON chat.whispers TO chattyness_app;

-- chat.reactions
ALTER TABLE chat.reactions ENABLE ROW LEVEL SECURITY;

CREATE POLICY chat_reactions_select ON chat.reactions
    FOR SELECT TO chattyness_app
    USING (true);

CREATE POLICY chat_reactions_own ON chat.reactions
    FOR ALL TO chattyness_app
    USING (user_id = public.current_user_id())
    WITH CHECK (user_id = public.current_user_id());

GRANT SELECT, INSERT, DELETE ON chat.reactions TO chattyness_app;

-- chat.shouts
ALTER TABLE chat.shouts ENABLE ROW LEVEL SECURITY;

CREATE POLICY chat_shouts_select ON chat.shouts
    FOR SELECT TO chattyness_app
    USING (true);

CREATE POLICY chat_shouts_insert ON chat.shouts
    FOR INSERT TO chattyness_app
    WITH CHECK (public.is_server_admin());

GRANT SELECT, INSERT ON chat.shouts TO chattyness_app;

-- =============================================================================
-- AUDIT SCHEMA POLICIES
-- =============================================================================

-- audit.events
ALTER TABLE audit.events ENABLE ROW LEVEL SECURITY;

CREATE POLICY audit_events_own ON audit.events
    FOR SELECT TO chattyness_app
    USING (user_id = public.current_user_id());

CREATE POLICY audit_events_admin ON audit.events
    FOR SELECT TO chattyness_app
    USING (public.is_server_admin());

CREATE POLICY audit_events_realm ON audit.events
    FOR SELECT TO chattyness_app
    USING (
        realm_id IS NOT NULL
        AND EXISTS (
            SELECT 1 FROM realm.realms r
            WHERE r.id = realm_id
            AND r.owner_id = public.current_user_id()
        )
    );

CREATE POLICY audit_events_insert ON audit.events
    FOR INSERT TO chattyness_app
    WITH CHECK (true);

GRANT SELECT, INSERT ON audit.events TO chattyness_app;

-- audit.login_history
ALTER TABLE audit.login_history ENABLE ROW LEVEL SECURITY;

CREATE POLICY audit_login_history_own ON audit.login_history
    FOR SELECT TO chattyness_app
    USING (user_id = public.current_user_id());

CREATE POLICY audit_login_history_admin ON audit.login_history
    FOR SELECT TO chattyness_app
    USING (public.is_server_admin());

CREATE POLICY audit_login_history_insert ON audit.login_history
    FOR INSERT TO chattyness_app
    WITH CHECK (true);

GRANT SELECT, INSERT ON audit.login_history TO chattyness_app;

COMMIT;
