Tactical Media Broadcast Hub — initial release
Self-hosted multi-platform broadcast newsroom for activist collectives: publishing pipeline with group approval and DST-proof scheduling, TweetDeck-style column wall with hostile-content-safe rendering, live control room with Auto Director, bridged chat governance with polls and quorum, distributed sandboxed transcode workers, encrypted content-addressed archive, monthly analytics. MIT licensed. See README.md and docs/.
This commit is contained in:
commit
aa5e262f3a
880 changed files with 111042 additions and 0 deletions
88
apps/api/migrations/015_archive.sql
Normal file
88
apps/api/migrations/015_archive.sql
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
-- 015: WP15 Telegram live channel archive. All identities are tenant pinned.
|
||||
CREATE TABLE feed_subscriptions (
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
workspace_id uuid NOT NULL,
|
||||
type text NOT NULL CHECK (type = 'telegram_channel'),
|
||||
connection_id uuid NOT NULL,
|
||||
channel_id text NOT NULL CHECK (btrim(channel_id) <> ''),
|
||||
health text NOT NULL DEFAULT 'active' CHECK (health IN ('active', 'bot_removed', 'admin_required')),
|
||||
health_checked_at timestamptz,
|
||||
created_at timestamptz NOT NULL DEFAULT now(),
|
||||
updated_at timestamptz NOT NULL DEFAULT now(),
|
||||
UNIQUE (workspace_id, connection_id, channel_id),
|
||||
UNIQUE (id, workspace_id),
|
||||
FOREIGN KEY (connection_id, workspace_id) REFERENCES connections (id, workspace_id) ON DELETE CASCADE ON UPDATE RESTRICT
|
||||
);
|
||||
CREATE INDEX feed_subscriptions_channel ON feed_subscriptions (workspace_id, channel_id);
|
||||
|
||||
CREATE TABLE archived_feed_items (
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
workspace_id uuid NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE,
|
||||
remote_ref text NOT NULL CHECK (remote_ref ~ '^(-?[0-9]+|@[A-Za-z0-9_]+):[0-9]+$'),
|
||||
channel_id text NOT NULL,
|
||||
message_id bigint NOT NULL CHECK (message_id > 0),
|
||||
media_group_id text,
|
||||
text_content text NOT NULL DEFAULT '',
|
||||
content_hash text NOT NULL CHECK (content_hash ~ '^[0-9a-f]{64}$'),
|
||||
revision integer NOT NULL DEFAULT 1 CHECK (revision > 0),
|
||||
media_refs jsonb NOT NULL DEFAULT '[]'::jsonb CHECK (jsonb_typeof(media_refs) = 'array'),
|
||||
attribution jsonb NOT NULL DEFAULT '{}'::jsonb CHECK (jsonb_typeof(attribution) = 'object'),
|
||||
protected_content boolean NOT NULL DEFAULT false,
|
||||
posted_at timestamptz NOT NULL,
|
||||
created_at timestamptz NOT NULL DEFAULT now(),
|
||||
updated_at timestamptz NOT NULL DEFAULT now(),
|
||||
UNIQUE (workspace_id, remote_ref),
|
||||
UNIQUE (id, workspace_id),
|
||||
CHECK (remote_ref = channel_id || ':' || message_id::text)
|
||||
);
|
||||
CREATE INDEX archived_feed_items_cursor ON archived_feed_items (workspace_id, posted_at DESC, id DESC);
|
||||
|
||||
CREATE TABLE archived_feed_revisions (
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
workspace_id uuid NOT NULL,
|
||||
item_id uuid NOT NULL,
|
||||
revision integer NOT NULL CHECK (revision > 0),
|
||||
content_hash text NOT NULL CHECK (content_hash ~ '^[0-9a-f]{64}$'),
|
||||
text_content text NOT NULL,
|
||||
media_refs jsonb NOT NULL CHECK (jsonb_typeof(media_refs) = 'array'),
|
||||
attribution jsonb NOT NULL CHECK (jsonb_typeof(attribution) = 'object'),
|
||||
created_at timestamptz NOT NULL DEFAULT now(),
|
||||
UNIQUE (workspace_id, item_id, revision),
|
||||
FOREIGN KEY (item_id, workspace_id) REFERENCES archived_feed_items (id, workspace_id) ON DELETE CASCADE ON UPDATE RESTRICT
|
||||
);
|
||||
|
||||
-- drafts predates tenant-pinned references.
|
||||
ALTER TABLE drafts ADD CONSTRAINT drafts_id_workspace_key UNIQUE (id, workspace_id);
|
||||
|
||||
CREATE TABLE archived_feed_drafts (
|
||||
workspace_id uuid NOT NULL,
|
||||
item_id uuid NOT NULL,
|
||||
draft_id uuid NOT NULL UNIQUE,
|
||||
created_at timestamptz NOT NULL DEFAULT now(),
|
||||
PRIMARY KEY (workspace_id, item_id, draft_id),
|
||||
FOREIGN KEY (item_id, workspace_id) REFERENCES archived_feed_items (id, workspace_id) ON DELETE CASCADE ON UPDATE RESTRICT,
|
||||
FOREIGN KEY (draft_id, workspace_id) REFERENCES drafts (id, workspace_id) ON DELETE CASCADE ON UPDATE RESTRICT
|
||||
);
|
||||
|
||||
CREATE TRIGGER trg_feed_subscriptions_ws_immutable BEFORE UPDATE ON feed_subscriptions
|
||||
FOR EACH ROW EXECUTE FUNCTION reject_workspace_change();
|
||||
CREATE TRIGGER trg_archived_feed_items_ws_immutable BEFORE UPDATE ON archived_feed_items
|
||||
FOR EACH ROW EXECUTE FUNCTION reject_workspace_change();
|
||||
CREATE TRIGGER trg_archived_feed_revisions_ws_immutable BEFORE UPDATE ON archived_feed_revisions
|
||||
FOR EACH ROW EXECUTE FUNCTION reject_workspace_change();
|
||||
CREATE TRIGGER trg_archived_feed_drafts_ws_immutable BEFORE UPDATE ON archived_feed_drafts
|
||||
FOR EACH ROW EXECUTE FUNCTION reject_workspace_change();
|
||||
|
||||
ALTER TABLE feed_subscriptions ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE archived_feed_items ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE archived_feed_revisions ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE archived_feed_drafts ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE feed_subscriptions FORCE ROW LEVEL SECURITY;
|
||||
ALTER TABLE archived_feed_items FORCE ROW LEVEL SECURITY;
|
||||
ALTER TABLE archived_feed_revisions FORCE ROW LEVEL SECURITY;
|
||||
ALTER TABLE archived_feed_drafts FORCE ROW LEVEL SECURITY;
|
||||
CREATE POLICY tenant_isolation ON feed_subscriptions USING (workspace_id = NULLIF(current_setting('app.workspace_id', true), '')::uuid) WITH CHECK (workspace_id = NULLIF(current_setting('app.workspace_id', true), '')::uuid);
|
||||
CREATE POLICY tenant_isolation ON archived_feed_items USING (workspace_id = NULLIF(current_setting('app.workspace_id', true), '')::uuid) WITH CHECK (workspace_id = NULLIF(current_setting('app.workspace_id', true), '')::uuid);
|
||||
CREATE POLICY tenant_isolation ON archived_feed_revisions USING (workspace_id = NULLIF(current_setting('app.workspace_id', true), '')::uuid) WITH CHECK (workspace_id = NULLIF(current_setting('app.workspace_id', true), '')::uuid);
|
||||
CREATE POLICY tenant_isolation ON archived_feed_drafts 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 feed_subscriptions, archived_feed_items, archived_feed_revisions, archived_feed_drafts TO sp_app;
|
||||
Loading…
Add table
Add a link
Reference in a new issue