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
55
apps/api/migrations/025_archive_search.sql
Normal file
55
apps/api/migrations/025_archive_search.sql
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
-- 025: WP15 archive full-text search (firehose research recommendation:
|
||||
-- Postgres FTS phase-1) over archived_feed_items.
|
||||
--
|
||||
-- Search surface:
|
||||
-- * tsvector over text_content + channel/attribution metadata, kept in sync
|
||||
-- by trigger (the normalizer writes text_content on insert AND on edits,
|
||||
-- so a plain trigger beats a generated column: 015's reject_workspace_change
|
||||
-- trigger guards workspace_id, and edits must reindex text).
|
||||
-- * GIN indexes: tsvector lookup + pg_trgm similarity for typo tolerance.
|
||||
-- * The API ORs websearch_to_tsquery matching with pg_trgm similarity so a
|
||||
-- misspelled term still matches via trigram distance (similarity >= 0.3).
|
||||
-- * RLS is untouched: archived_feed_items keeps its tenant_isolation policy
|
||||
-- from 015; search runs inside withTenant() so FORCE RLS binds honestly.
|
||||
--
|
||||
-- CREATE EXTENSION requires privileges sp_app lacks; this migration runs as
|
||||
-- the table owner only (015 grants are preserved below for completeness).
|
||||
CREATE EXTENSION IF NOT EXISTS pg_trgm;
|
||||
|
||||
ALTER TABLE archived_feed_items ADD COLUMN search_vector tsvector NOT NULL DEFAULT '';
|
||||
|
||||
-- String-valued attribution keys worth indexing as metadata (normalizer maps
|
||||
-- Telegram author/origin fields here). Kept narrow on purpose.
|
||||
CREATE FUNCTION archived_attribution_text(attribution jsonb)
|
||||
RETURNS text LANGUAGE sql IMMUTABLE PARALLEL SAFE AS $$
|
||||
SELECT coalesce(string_agg(value, ' '), '')
|
||||
FROM jsonb_each_text(attribution)
|
||||
WHERE key IN ('channelUsername', 'channelTitle', 'author', 'authorSignature',
|
||||
'forwardFromChat', 'forwardFromUser', 'signature')
|
||||
$$;
|
||||
|
||||
CREATE FUNCTION archived_feed_items_search_vector() RETURNS trigger
|
||||
LANGUAGE plpgsql AS $$
|
||||
BEGIN
|
||||
NEW.search_vector :=
|
||||
setweight(to_tsvector('simple', coalesce(NEW.channel_id, '')), 'A')
|
||||
|| setweight(to_tsvector('simple', archived_attribution_text(NEW.attribution)), 'B')
|
||||
|| to_tsvector('simple', coalesce(NEW.text_content, ''));
|
||||
RETURN NEW;
|
||||
END $$;
|
||||
|
||||
CREATE TRIGGER trg_archived_feed_items_search
|
||||
BEFORE INSERT OR UPDATE OF text_content, channel_id, attribution
|
||||
ON archived_feed_items FOR EACH ROW
|
||||
EXECUTE FUNCTION archived_feed_items_search_vector();
|
||||
|
||||
-- Exact/lexical lookups. 'simple' config on purpose: the corpus is a
|
||||
-- multilingual firehose, no stemming dictionary is "right", so phase-1 is
|
||||
-- exact term matching plus trigram fuzziness.
|
||||
CREATE INDEX archived_feed_items_search ON archived_feed_items USING gin (search_vector);
|
||||
|
||||
-- Fuzzy path (typo tolerance) + unindexed-corpus ILIKE support on text.
|
||||
CREATE INDEX archived_feed_items_text_trgm ON archived_feed_items USING gin (text_content gin_trgm_ops);
|
||||
CREATE INDEX archived_feed_items_channel_trgm ON archived_feed_items USING gin (channel_id gin_trgm_ops);
|
||||
|
||||
GRANT SELECT ON archived_feed_items TO sp_app;
|
||||
Loading…
Add table
Add a link
Reference in a new issue