-- t-paliad-147: Bulk team email — paliad.email_broadcasts. -- -- Records every bulk-send sent from /team's "E-Mail an Auswahl" flow. -- Powers the /admin/broadcasts viewer (global_admin sees all rows; -- senders see their own). -- -- recipient_filter snapshots the filter chips the sender had selected -- (project_ids, offices, roles) so a future deploy that tweaks the -- filter UX can still render past sends. recipient_user_ids snapshots -- the resolved user list — the actual addressees, immune to later -- team-membership changes. -- -- Sections: -- 1. CREATE paliad.email_broadcasts. -- 2. Indexes. -- 3. RLS. -- ============================================================================ -- 1. paliad.email_broadcasts -- ============================================================================ CREATE TABLE paliad.email_broadcasts ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), -- Renderable subject (post-template). Stored verbatim for audit. subject text NOT NULL, -- Body source as the sender typed it (Markdown). NOT the per-recipient -- rendered output — those are reconstructable by re-rendering with the -- snapshotted recipient row, but the source is what we audit. body text NOT NULL, -- The sender. FK to paliad.users (not auth.users) so deleting an auth -- row leaves the audit trail intact via paliad.users. sender_id uuid NOT NULL REFERENCES paliad.users(id), -- Optional template the sender started from. NULL when freeform. template_key text, -- Snapshot of filter chips selected at send time. Keys: project_ids -- (uuid[]), offices (text[]), roles (text[]). jsonb for forward-compat. recipient_filter jsonb NOT NULL DEFAULT '{}'::jsonb, -- Resolved addressee list — the user_ids that received (or attempted) -- the mail. Immune to subsequent team-membership changes. recipient_user_ids uuid[] NOT NULL DEFAULT '{}'::uuid[], -- Per-send result counts (sent, failed, total). jsonb so we can grow -- the report shape without a migration. send_report jsonb NOT NULL DEFAULT '{}'::jsonb, sent_at timestamptz NOT NULL DEFAULT now(), created_at timestamptz NOT NULL DEFAULT now() ); -- ============================================================================ -- 2. Indexes -- ============================================================================ CREATE INDEX email_broadcasts_sent_at_idx ON paliad.email_broadcasts (sent_at DESC); CREATE INDEX email_broadcasts_sender_idx ON paliad.email_broadcasts (sender_id, sent_at DESC); -- ============================================================================ -- 3. RLS -- ============================================================================ ALTER TABLE paliad.email_broadcasts ENABLE ROW LEVEL SECURITY; -- Senders can read their own rows; global_admin can read everything. -- The Go service layer (BroadcastService) is the load-bearing gate; RLS -- here is defence-in-depth for any future auth-context query path. CREATE POLICY email_broadcasts_select ON paliad.email_broadcasts FOR SELECT USING ( sender_id = auth.uid() OR EXISTS ( SELECT 1 FROM paliad.users u WHERE u.id = auth.uid() AND u.global_role = 'global_admin' ) ); -- Inserts only by the sender themselves (defence-in-depth — the service -- enforces project_lead-OR-global_admin authorship; RLS only enforces the -- self-attribution bit). CREATE POLICY email_broadcasts_insert ON paliad.email_broadcasts FOR INSERT WITH CHECK (sender_id = auth.uid());