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/.
55 lines
2.7 KiB
PL/PgSQL
55 lines
2.7 KiB
PL/PgSQL
-- 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;
|