owpengram-server/deploy/migrations/20260714003098_moderation_reports.up.sql

112 lines
4.6 KiB
SQL

-- Unified immutable abuse-report submissions. Operational delivery/read/music
-- telemetry and auth-code delivery diagnostics intentionally use separate
-- tables and retention policies.
CREATE TABLE public.moderation_reports (
id bigserial PRIMARY KEY,
reporter_user_id bigint NOT NULL CHECK (reporter_user_id > 0),
source text NOT NULL CHECK (source IN (
'account_peer', 'profile_photo', 'messages_spam', 'messages',
'encrypted_spam', 'reaction', 'channel_spam', 'story', 'ephemeral',
'sponsored', 'antispam_false_positive'
)),
target_peer_type text NOT NULL CHECK (target_peer_type IN ('user', 'channel')),
target_peer_id bigint NOT NULL CHECK (target_peer_id > 0),
reason text NOT NULL CHECK (reason IN (
'spam', 'violence', 'pornography', 'child_abuse', 'other',
'copyright', 'geo_irrelevant', 'fake', 'illegal_drugs',
'personal_details'
)),
report_option text NOT NULL CHECK (
octet_length(report_option) BETWEEN 1 AND 32
),
report_comment text NOT NULL DEFAULT '' CHECK (
char_length(report_comment) <= 512
),
comment_hash bytea NOT NULL CHECK (octet_length(comment_hash) = 32),
fingerprint bytea NOT NULL CHECK (octet_length(fingerprint) = 32),
taxonomy_version smallint NOT NULL CHECK (taxonomy_version > 0),
created_at timestamptz NOT NULL,
CONSTRAINT moderation_reports_idempotency
UNIQUE (reporter_user_id, fingerprint)
);
CREATE INDEX moderation_reports_target_created_idx
ON public.moderation_reports (
target_peer_type, target_peer_id, created_at DESC, id DESC
);
CREATE INDEX moderation_reports_reporter_created_idx
ON public.moderation_reports (
reporter_user_id, created_at DESC, id DESC
);
CREATE TABLE public.moderation_report_items (
report_id bigint NOT NULL REFERENCES public.moderation_reports(id)
ON DELETE CASCADE,
ordinal smallint NOT NULL CHECK (ordinal BETWEEN 0 AND 99),
item_kind text NOT NULL CHECK (item_kind IN (
'peer', 'message', 'profile_photo', 'reaction', 'story',
'encrypted_chat', 'ephemeral', 'sponsored', 'antispam_decision'
)),
peer_type text NOT NULL CHECK (peer_type IN ('user', 'channel')),
peer_id bigint NOT NULL CHECK (peer_id > 0),
item_id bigint NOT NULL CHECK (item_id > 0),
secondary_id bigint NOT NULL DEFAULT 0 CHECK (secondary_id >= 0),
author_user_id bigint NOT NULL DEFAULT 0 CHECK (author_user_id >= 0),
evidence_schema_version smallint NOT NULL CHECK (
evidence_schema_version > 0
),
evidence jsonb NOT NULL CHECK (
jsonb_typeof(evidence) = 'object'
AND octet_length(evidence::text) <= 1048576
),
evidence_hash bytea NOT NULL CHECK (octet_length(evidence_hash) = 32),
PRIMARY KEY (report_id, ordinal),
CONSTRAINT moderation_report_items_identity
UNIQUE (
report_id, item_kind, peer_type, peer_id, item_id, secondary_id
)
);
CREATE INDEX moderation_report_items_lookup_idx
ON public.moderation_report_items (
item_kind, peer_type, peer_id, item_id, report_id
);
CREATE INDEX moderation_report_items_author_idx
ON public.moderation_report_items (
author_user_id, report_id
)
WHERE author_user_id > 0;
CREATE TABLE public.moderation_media_holds (
report_id bigint NOT NULL,
item_ordinal smallint NOT NULL,
media_kind text NOT NULL CHECK (media_kind IN ('photo', 'document', 'blob')),
storage_key text NOT NULL CHECK (
octet_length(storage_key) BETWEEN 1 AND 512
),
created_at timestamptz NOT NULL,
released_at timestamptz,
PRIMARY KEY (report_id, item_ordinal, media_kind, storage_key),
FOREIGN KEY (report_id, item_ordinal)
REFERENCES public.moderation_report_items(report_id, ordinal)
ON DELETE CASCADE,
CHECK (released_at IS NULL OR released_at >= created_at)
);
CREATE INDEX moderation_media_holds_active_key_idx
ON public.moderation_media_holds (media_kind, storage_key, report_id)
WHERE released_at IS NULL;
-- Crash-safe, one-way provenance for rows written by the pre-unified
-- ephemeral.reportMessage implementation. The legacy table remains immutable
-- until every deployed database has completed the application-level evidence
-- conversion; all new writes go exclusively to moderation_reports.
CREATE TABLE public.moderation_legacy_ephemeral_migrations (
legacy_report_id bigint PRIMARY KEY
REFERENCES public.ephemeral_abuse_reports(id) ON DELETE RESTRICT,
moderation_report_id bigint NOT NULL
REFERENCES public.moderation_reports(id) ON DELETE RESTRICT,
migrated_at timestamptz NOT NULL
);