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/.
89 lines
4.1 KiB
PL/PgSQL
89 lines
4.1 KiB
PL/PgSQL
-- 006: publishing pipeline (drafts -> approval -> scheduling -> publication)
|
|
CREATE TABLE drafts (
|
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
workspace_id uuid NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE,
|
|
author_user_id uuid NOT NULL REFERENCES users(id),
|
|
post jsonb NOT NULL,
|
|
state text NOT NULL DEFAULT 'editing'
|
|
CHECK (state IN ('editing', 'in_review', 'approved', 'rejected', 'scheduled', 'published')),
|
|
reviewer_user_id uuid REFERENCES users(id),
|
|
created_at timestamptz NOT NULL DEFAULT now(),
|
|
updated_at timestamptz NOT NULL DEFAULT now()
|
|
);
|
|
|
|
CREATE TABLE scheduled_publications (
|
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
workspace_id uuid NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE,
|
|
draft_id uuid NOT NULL REFERENCES drafts(id) ON DELETE CASCADE,
|
|
connection_id uuid NOT NULL REFERENCES connections(id),
|
|
publish_at timestamptz NOT NULL,
|
|
idempotency_key text NOT NULL UNIQUE,
|
|
state text NOT NULL DEFAULT 'pending'
|
|
CHECK (state IN ('pending', 'in_flight', 'done', 'failed')),
|
|
error text,
|
|
UNIQUE (draft_id)
|
|
);
|
|
|
|
CREATE TABLE publications (
|
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
workspace_id uuid NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE,
|
|
draft_id uuid NOT NULL REFERENCES drafts(id) ON DELETE CASCADE,
|
|
connection_id uuid NOT NULL REFERENCES connections(id),
|
|
idempotency_key text NOT NULL UNIQUE,
|
|
remote_id text NOT NULL,
|
|
remote_url text,
|
|
published_at timestamptz NOT NULL,
|
|
provider_metadata jsonb NOT NULL DEFAULT '{}'
|
|
);
|
|
|
|
CREATE INDEX drafts_workspace_state ON drafts (workspace_id, state);
|
|
CREATE INDEX scheduled_publications_due ON scheduled_publications (publish_at)
|
|
WHERE state = 'pending';
|
|
CREATE INDEX publications_draft ON publications (draft_id);
|
|
|
|
CREATE TRIGGER trg_draft_ws_immutable BEFORE UPDATE ON drafts
|
|
FOR EACH ROW EXECUTE FUNCTION reject_workspace_change();
|
|
CREATE TRIGGER trg_scheduled_pub_ws_immutable BEFORE UPDATE ON scheduled_publications
|
|
FOR EACH ROW EXECUTE FUNCTION reject_workspace_change();
|
|
CREATE TRIGGER trg_publication_ws_immutable BEFORE UPDATE ON publications
|
|
FOR EACH ROW EXECUTE FUNCTION reject_workspace_change();
|
|
|
|
ALTER TABLE drafts ENABLE ROW LEVEL SECURITY;
|
|
ALTER TABLE scheduled_publications ENABLE ROW LEVEL SECURITY;
|
|
ALTER TABLE publications ENABLE ROW LEVEL SECURITY;
|
|
ALTER TABLE drafts FORCE ROW LEVEL SECURITY;
|
|
ALTER TABLE scheduled_publications FORCE ROW LEVEL SECURITY;
|
|
ALTER TABLE publications FORCE ROW LEVEL SECURITY;
|
|
|
|
CREATE POLICY tenant_isolation ON drafts
|
|
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 scheduled_publications
|
|
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 publications
|
|
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 drafts, scheduled_publications, publications TO sp_app;
|
|
|
|
-- Narrow SECURITY DEFINER lookups let the scheduler discover tenant context
|
|
-- without weakening FORCE RLS for ordinary table access.
|
|
CREATE FUNCTION publishing_schedule_workspace(p_idempotency_key text) RETURNS uuid
|
|
LANGUAGE sql STABLE SECURITY DEFINER SET search_path = public AS $$
|
|
SELECT workspace_id
|
|
FROM scheduled_publications
|
|
WHERE idempotency_key = p_idempotency_key
|
|
$$;
|
|
|
|
CREATE FUNCTION publishing_due_schedules() RETURNS TABLE(idempotency_key text, publish_at timestamptz)
|
|
LANGUAGE sql STABLE SECURITY DEFINER SET search_path = public AS $$
|
|
SELECT sp.idempotency_key, sp.publish_at
|
|
FROM scheduled_publications sp
|
|
WHERE sp.state = 'pending' AND sp.publish_at <= now()
|
|
ORDER BY sp.publish_at
|
|
$$;
|
|
|
|
REVOKE ALL ON FUNCTION publishing_schedule_workspace(text) FROM PUBLIC;
|
|
REVOKE ALL ON FUNCTION publishing_due_schedules() FROM PUBLIC;
|
|
GRANT EXECUTE ON FUNCTION publishing_schedule_workspace(text), publishing_due_schedules() TO sp_app;
|