-- 041: outreach — per-workspace list of public accounts to contact by hand. -- -- The Hub never sends these messages. A person opens the contact's profile, -- pastes the drafted text into the platform's own DM box and marks the row -- sent; replies are marked the same way. Only public accounts belong here: -- names and contact details of private persons stay out of the Hub. CREATE TABLE outreach_contacts ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), workspace_id uuid NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE ON UPDATE RESTRICT, name text NOT NULL CHECK (char_length(name) BETWEEN 1 AND 200), platform text NOT NULL CHECK (platform ~ '^[a-z0-9][a-z0-9-]{1,31}$'), handle text NOT NULL CHECK (char_length(handle) BETWEEN 1 AND 200), profile_url text NOT NULL CHECK (profile_url ~ '^https://[^\s]+$' AND char_length(profile_url) <= 500), category text CHECK (category IS NULL OR char_length(category) <= 100), language text CHECK (language IS NULL OR language ~ '^[a-z]{2,3}(-[A-Za-z0-9]{2,8})?$'), message text NOT NULL DEFAULT '' CHECK (char_length(message) <= 4000), notes text NOT NULL DEFAULT '' CHECK (char_length(notes) <= 2000), status text NOT NULL DEFAULT 'todo' CHECK (status IN ('todo', 'sent', 'replied', 'declined')), sent_at timestamptz, sent_by uuid REFERENCES users(id) ON DELETE SET NULL, replied_at timestamptz, created_by uuid REFERENCES users(id) ON DELETE SET NULL, created_at timestamptz NOT NULL DEFAULT now(), updated_at timestamptz NOT NULL DEFAULT now(), UNIQUE (id, workspace_id) ); CREATE UNIQUE INDEX outreach_contacts_account ON outreach_contacts (workspace_id, platform, lower(handle)); CREATE INDEX outreach_contacts_status ON outreach_contacts (workspace_id, status); CREATE TRIGGER trg_outreach_contacts_ws_immutable BEFORE UPDATE ON outreach_contacts FOR EACH ROW EXECUTE FUNCTION reject_workspace_change(); ALTER TABLE outreach_contacts ENABLE ROW LEVEL SECURITY; ALTER TABLE outreach_contacts FORCE ROW LEVEL SECURITY; CREATE POLICY tenant_isolation ON outreach_contacts USING (workspace_id = NULLIF(current_setting('app.workspace_id', true), '')::uuid) WITH CHECK (workspace_id = NULLIF(current_setting('app.workspace_id', true), '')::uuid); GRANT SELECT, INSERT, UPDATE, DELETE ON outreach_contacts TO sp_app; -- Reads for anyone who already sees the inbox; edits for the roles that write drafts. INSERT INTO role_permissions (role, permission) SELECT 'Owner', 'outreach.view' UNION ALL SELECT 'Admin', 'outreach.view' UNION ALL SELECT 'Editor', 'outreach.view' UNION ALL SELECT 'Moderator', 'outreach.view' UNION ALL SELECT 'Owner', 'outreach.manage' UNION ALL SELECT 'Admin', 'outreach.manage' UNION ALL SELECT 'Editor', 'outreach.manage' ON CONFLICT DO NOTHING;