feat: adapt T-HUB features — folders schema, Instagram connector, outreach contacts, media RLS force
From the hacklab/T-HUB fork (diverged at a6843e9):
- 046_file_folders: folder trees (group + personal scope, RLS forced,
workspace-immutability trigger) + created_by on chat_media_archive
- Instagram connector wired into connect + publishing
- outreach contacts: per-collective DM tracking (todo/sent/replied/declined),
RLS forced, role-scoped permissions, audit without message text,
migration renumbered 041→047
- FORCE row-level security on media_assets
The J1 api agent's folder service/controller work continues separately.
This commit is contained in:
parent
57762f543c
commit
8f01765df3
25 changed files with 3054 additions and 4 deletions
51
apps/api/migrations/047_outreach.sql
Normal file
51
apps/api/migrations/047_outreach.sql
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
-- 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;
|
||||
Loading…
Add table
Add a link
Reference in a new issue