diff --git a/crates/buzz-db/src/lib.rs b/crates/buzz-db/src/lib.rs index 38a7d5afd7a..f8981ec9a8f 100644 --- a/crates/buzz-db/src/lib.rs +++ b/crates/buzz-db/src/lib.rs @@ -1335,165 +1335,6 @@ impl Db { Ok(result.rows_affected()) } - /// Insert a tenant-scoped NIP-56 report row, idempotent by report event id. - #[datastore_span(name = "insert_moderation_report", system = "postgresql")] - pub async fn insert_moderation_report( - &self, - community: CommunityId, - report: moderation::NewReport<'_>, - ) -> Result { - moderation::insert_report(&self.pool, community, report).await - } - - /// List moderation reports for a community, newest first. - #[datastore_span(name = "list_moderation_reports", system = "postgresql")] - pub async fn list_moderation_reports( - &self, - community: CommunityId, - status: Option<&str>, - limit: i64, - ) -> Result> { - moderation::list_reports(&self.pool, community, status, limit).await - } - - /// Fetch one moderation report by row id. - #[datastore_span(name = "get_moderation_report", system = "postgresql")] - pub async fn get_moderation_report( - &self, - community: CommunityId, - report_id: Uuid, - ) -> Result> { - moderation::get_report(&self.pool, community, report_id).await - } - - /// Fetch one moderation report by signed NIP-56 report event id. - #[datastore_span(name = "get_moderation_report_by_event", system = "postgresql")] - pub async fn get_moderation_report_by_event( - &self, - community: CommunityId, - report_event_id: &[u8], - ) -> Result> { - moderation::get_report_by_event(&self.pool, community, report_event_id).await - } - - /// Resolve, dismiss, or escalate an open moderation report. - #[datastore_span(name = "resolve_moderation_report", system = "postgresql")] - pub async fn resolve_moderation_report( - &self, - community: CommunityId, - report_id: Uuid, - status: &str, - resolved_by: &[u8], - action_id: Option, - ) -> Result { - moderation::resolve_report( - &self.pool, - community, - report_id, - status, - resolved_by, - action_id, - ) - .await - } - - /// Upsert a community ban for a member pubkey. - #[datastore_span(name = "ban_community_member", system = "postgresql")] - pub async fn ban_community_member( - &self, - community: CommunityId, - pubkey: &[u8], - actor: &[u8], - reason: Option<&str>, - expires_at: Option>, - ) -> Result<()> { - moderation::ban_member(&self.pool, community, pubkey, actor, reason, expires_at).await - } - - /// Lift a community ban for a member pubkey. - #[datastore_span(name = "unban_community_member", system = "postgresql")] - pub async fn unban_community_member( - &self, - community: CommunityId, - pubkey: &[u8], - actor: &[u8], - ) -> Result { - moderation::unban_member(&self.pool, community, pubkey, actor).await - } - - /// Upsert a community timeout/write-block for a member pubkey. - #[datastore_span(name = "timeout_community_member", system = "postgresql")] - pub async fn timeout_community_member( - &self, - community: CommunityId, - pubkey: &[u8], - actor: &[u8], - muted_until: DateTime, - reason: Option<&str>, - ) -> Result<()> { - moderation::timeout_member(&self.pool, community, pubkey, actor, muted_until, reason).await - } - - /// Clear a community timeout/write-block for a member pubkey. - #[datastore_span(name = "untimeout_community_member", system = "postgresql")] - pub async fn untimeout_community_member( - &self, - community: CommunityId, - pubkey: &[u8], - actor: &[u8], - ) -> Result { - moderation::untimeout_member(&self.pool, community, pubkey, actor).await - } - - /// Fetch the active ban/timeout restriction state for enforcement hot paths. - #[datastore_span(name = "moderation_restriction_state", system = "postgresql")] - pub async fn moderation_restriction_state( - &self, - community: CommunityId, - pubkey: &[u8], - ) -> Result { - moderation::restriction_state(&self.pool, community, pubkey).await - } - - /// Fetch the full ban/timeout row for a member pubkey. - #[datastore_span(name = "get_community_ban", system = "postgresql")] - pub async fn get_community_ban( - &self, - community: CommunityId, - pubkey: &[u8], - ) -> Result> { - moderation::get_ban(&self.pool, community, pubkey).await - } - - /// List currently restricted members in a community. - #[datastore_span(name = "list_community_restrictions", system = "postgresql")] - pub async fn list_community_restrictions( - &self, - community: CommunityId, - ) -> Result> { - moderation::list_restricted(&self.pool, community).await - } - - /// Insert a moderation audit action row. - #[datastore_span(name = "insert_moderation_action", system = "postgresql")] - pub async fn insert_moderation_action( - &self, - community: CommunityId, - action: moderation::NewAction<'_>, - ) -> Result { - moderation::insert_action(&self.pool, community, action).await - } - - /// List moderation audit action rows, newest first. - #[datastore_span(name = "list_moderation_actions", system = "postgresql")] - pub async fn list_moderation_actions( - &self, - community: CommunityId, - limit: i64, - ) -> Result> { - moderation::list_actions(&self.pool, community, limit).await - } - /// Return the current owner of git repo name `repo_id` in `community`, or /// `None` if unreserved. See [`git_repo::repo_name_owner`]. #[datastore_span(name = "repo_name_owner", system = "postgresql")] diff --git a/crates/buzz-db/src/moderation.rs b/crates/buzz-db/src/moderation.rs index be8b712d45c..8dbf2ccba12 100644 --- a/crates/buzz-db/src/moderation.rs +++ b/crates/buzz-db/src/moderation.rs @@ -14,12 +14,13 @@ //! Lane ownership: L1 (Max). Signatures below are the contract; changes go //! through the integration thread. +use buzz_datastore_tracing::datastore_span; use chrono::{DateTime, Utc}; use sqlx::{PgPool, Row as _}; use uuid::Uuid; use crate::error::Result; -use crate::CommunityId; +use crate::{CommunityId, Db}; /// What a report points at. Exactly one target class per report row. #[derive(Debug, Clone, PartialEq, Eq)] @@ -627,13 +628,174 @@ fn row_to_action(row: sqlx::postgres::PgRow) -> Result { }) } +impl Db { + /// Insert a tenant-scoped NIP-56 report row, idempotent by report event id. + #[datastore_span(name = "insert_moderation_report", system = "postgresql")] + pub async fn insert_moderation_report( + &self, + community: CommunityId, + report: NewReport<'_>, + ) -> Result { + insert_report(&self.pool, community, report).await + } + + /// List moderation reports for a community, newest first. + #[datastore_span(name = "list_moderation_reports", system = "postgresql")] + pub async fn list_moderation_reports( + &self, + community: CommunityId, + status: Option<&str>, + limit: i64, + ) -> Result> { + list_reports(&self.pool, community, status, limit).await + } + + /// Fetch one moderation report by row id. + #[datastore_span(name = "get_moderation_report", system = "postgresql")] + pub async fn get_moderation_report( + &self, + community: CommunityId, + report_id: Uuid, + ) -> Result> { + get_report(&self.pool, community, report_id).await + } + + /// Fetch one moderation report by signed NIP-56 report event id. + #[datastore_span(name = "get_moderation_report_by_event", system = "postgresql")] + pub async fn get_moderation_report_by_event( + &self, + community: CommunityId, + report_event_id: &[u8], + ) -> Result> { + get_report_by_event(&self.pool, community, report_event_id).await + } + + /// Resolve, dismiss, or escalate an open moderation report. + #[datastore_span(name = "resolve_moderation_report", system = "postgresql")] + pub async fn resolve_moderation_report( + &self, + community: CommunityId, + report_id: Uuid, + status: &str, + resolved_by: &[u8], + action_id: Option, + ) -> Result { + resolve_report( + &self.pool, + community, + report_id, + status, + resolved_by, + action_id, + ) + .await + } + + /// Upsert a community ban for a member pubkey. + #[datastore_span(name = "ban_community_member", system = "postgresql")] + pub async fn ban_community_member( + &self, + community: CommunityId, + pubkey: &[u8], + actor: &[u8], + reason: Option<&str>, + expires_at: Option>, + ) -> Result<()> { + ban_member(&self.pool, community, pubkey, actor, reason, expires_at).await + } + + /// Lift a community ban for a member pubkey. + #[datastore_span(name = "unban_community_member", system = "postgresql")] + pub async fn unban_community_member( + &self, + community: CommunityId, + pubkey: &[u8], + actor: &[u8], + ) -> Result { + unban_member(&self.pool, community, pubkey, actor).await + } + + /// Upsert a community timeout/write-block for a member pubkey. + #[datastore_span(name = "timeout_community_member", system = "postgresql")] + pub async fn timeout_community_member( + &self, + community: CommunityId, + pubkey: &[u8], + actor: &[u8], + muted_until: DateTime, + reason: Option<&str>, + ) -> Result<()> { + timeout_member(&self.pool, community, pubkey, actor, muted_until, reason).await + } + + /// Clear a community timeout/write-block for a member pubkey. + #[datastore_span(name = "untimeout_community_member", system = "postgresql")] + pub async fn untimeout_community_member( + &self, + community: CommunityId, + pubkey: &[u8], + actor: &[u8], + ) -> Result { + untimeout_member(&self.pool, community, pubkey, actor).await + } + + /// Fetch the active ban/timeout restriction state for enforcement hot paths. + #[datastore_span(name = "moderation_restriction_state", system = "postgresql")] + pub async fn moderation_restriction_state( + &self, + community: CommunityId, + pubkey: &[u8], + ) -> Result { + restriction_state(&self.pool, community, pubkey).await + } + + /// Fetch the full ban/timeout row for a member pubkey. + #[datastore_span(name = "get_community_ban", system = "postgresql")] + pub async fn get_community_ban( + &self, + community: CommunityId, + pubkey: &[u8], + ) -> Result> { + get_ban(&self.pool, community, pubkey).await + } + + /// List currently restricted members in a community. + #[datastore_span(name = "list_community_restrictions", system = "postgresql")] + pub async fn list_community_restrictions( + &self, + community: CommunityId, + ) -> Result> { + list_restricted(&self.pool, community).await + } + + /// Insert a moderation audit action row. + #[datastore_span(name = "insert_moderation_action", system = "postgresql")] + pub async fn insert_moderation_action( + &self, + community: CommunityId, + action: NewAction<'_>, + ) -> Result { + insert_action(&self.pool, community, action).await + } + + /// List moderation audit action rows, newest first. + #[datastore_span(name = "list_moderation_actions", system = "postgresql")] + pub async fn list_moderation_actions( + &self, + community: CommunityId, + limit: i64, + ) -> Result> { + list_actions(&self.pool, community, limit).await + } +} + #[cfg(test)] mod tests { use super::*; use chrono::Duration; use uuid::Uuid; - const TEST_DB_URL: &str = "postgres://buzz:buzz_dev@localhost:5432/buzz"; + const TEST_DB_URL: &str = "postgres://buzz:buzz_dev@localhost:5432/buzz"; // sadscan:disable np.postgres.1 async fn setup_pool() -> PgPool { let database_url = std::env::var("BUZZ_TEST_DATABASE_URL")