From 6b488f347e8b8c3fccc11793124cab52e12b5a62 Mon Sep 17 00:00:00 2001 From: Paul Makles Date: Sun, 7 Apr 2024 22:42:46 +0100 Subject: [PATCH] feat(core/database): implement reports & snapshots --- crates/core/database/src/drivers/reference.rs | 7 +- crates/core/database/src/models/mod.rs | 6 + .../database/src/models/safety_reports/mod.rs | 5 + .../src/models/safety_reports/model.rs | 148 ++++++++++++++++++ .../database/src/models/safety_reports/ops.rs | 12 ++ .../src/models/safety_reports/ops/mongodb.rs | 16 ++ .../models/safety_reports/ops/reference.rs | 20 +++ .../src/models/safety_snapshots/mod.rs | 5 + .../src/models/safety_snapshots/model.rs | 35 +++++ .../src/models/safety_snapshots/ops.rs | 12 ++ .../models/safety_snapshots/ops/mongodb.rs | 16 ++ .../models/safety_snapshots/ops/reference.rs | 20 +++ 12 files changed, 299 insertions(+), 3 deletions(-) create mode 100644 crates/core/database/src/models/safety_reports/mod.rs create mode 100644 crates/core/database/src/models/safety_reports/model.rs create mode 100644 crates/core/database/src/models/safety_reports/ops.rs create mode 100644 crates/core/database/src/models/safety_reports/ops/mongodb.rs create mode 100644 crates/core/database/src/models/safety_reports/ops/reference.rs create mode 100644 crates/core/database/src/models/safety_snapshots/mod.rs create mode 100644 crates/core/database/src/models/safety_snapshots/model.rs create mode 100644 crates/core/database/src/models/safety_snapshots/ops.rs create mode 100644 crates/core/database/src/models/safety_snapshots/ops/mongodb.rs create mode 100644 crates/core/database/src/models/safety_snapshots/ops/reference.rs diff --git a/crates/core/database/src/drivers/reference.rs b/crates/core/database/src/drivers/reference.rs index bfef6490..90458d24 100644 --- a/crates/core/database/src/drivers/reference.rs +++ b/crates/core/database/src/drivers/reference.rs @@ -4,7 +4,8 @@ use futures::lock::Mutex; use crate::{ Bot, Channel, ChannelCompositeKey, ChannelUnread, Emoji, File, Invite, Member, - MemberCompositeKey, Message, RatelimitEvent, Server, ServerBan, User, UserSettings, Webhook, + MemberCompositeKey, Message, RatelimitEvent, Report, Server, ServerBan, Snapshot, User, + UserSettings, Webhook, }; database_derived!( @@ -25,7 +26,7 @@ database_derived!( pub server_bans: Arc>>, pub server_members: Arc>>, pub servers: Arc>>, - pub safety_reports: Arc>>, - pub safety_snapshots: Arc>>, + pub safety_reports: Arc>>, + pub safety_snapshots: Arc>>, } ); diff --git a/crates/core/database/src/models/mod.rs b/crates/core/database/src/models/mod.rs index b4f6662f..1f540fa1 100644 --- a/crates/core/database/src/models/mod.rs +++ b/crates/core/database/src/models/mod.rs @@ -8,6 +8,8 @@ mod emojis; mod files; mod messages; mod ratelimit_events; +mod safety_reports; +mod safety_snapshots; mod server_bans; mod server_members; mod servers; @@ -24,6 +26,8 @@ pub use emojis::*; pub use files::*; pub use messages::*; pub use ratelimit_events::*; +pub use safety_reports::*; +pub use safety_snapshots::*; pub use server_bans::*; pub use server_members::*; pub use servers::*; @@ -45,6 +49,8 @@ pub trait AbstractDatabase: + files::AbstractAttachments + messages::AbstractMessages + ratelimit_events::AbstractRatelimitEvents + + safety_reports::AbstractReport + + safety_snapshots::AbstractSnapshot + server_bans::AbstractServerBans + server_members::AbstractServerMembers + servers::AbstractServers diff --git a/crates/core/database/src/models/safety_reports/mod.rs b/crates/core/database/src/models/safety_reports/mod.rs new file mode 100644 index 00000000..4d801b73 --- /dev/null +++ b/crates/core/database/src/models/safety_reports/mod.rs @@ -0,0 +1,5 @@ +mod model; +mod ops; + +pub use model::*; +pub use ops::*; diff --git a/crates/core/database/src/models/safety_reports/model.rs b/crates/core/database/src/models/safety_reports/model.rs new file mode 100644 index 00000000..0d8ee5d7 --- /dev/null +++ b/crates/core/database/src/models/safety_reports/model.rs @@ -0,0 +1,148 @@ +use iso8601_timestamp::Timestamp; + +auto_derived!( + /// User-generated platform moderation report + pub struct Report { + /// Unique Id + #[serde(rename = "_id")] + pub id: String, + /// Id of the user creating this report + pub author_id: String, + /// Reported content + pub content: ReportedContent, + /// Additional report context + pub additional_context: String, + /// Status of the report + #[serde(flatten)] + pub status: ReportStatus, + /// Additional notes included on the report + #[serde(default)] + pub notes: String, + } + + /// Reason for reporting content (message or server) + pub enum ContentReportReason { + /// No reason has been specified + NoneSpecified, + + /// Illegal content catch-all reason + Illegal, + + /// Selling or facilitating use of drugs or other illegal goods + IllegalGoods, + + /// Extortion or blackmail + IllegalExtortion, + + /// Revenge or child pornography + IllegalPornography, + + /// Illegal hacking activity + IllegalHacking, + + /// Extreme violence, gore, or animal cruelty + /// With exception to violence potrayed in media / creative arts + ExtremeViolence, + + /// Content that promotes harm to others / self + PromotesHarm, + + /// Unsolicited advertisements + UnsolicitedSpam, + + /// This is a raid + Raid, + + /// Spam or platform abuse + SpamAbuse, + + /// Scams or fraud + ScamsFraud, + + /// Distribution of malware or malicious links + Malware, + + /// Harassment or abuse targeted at another user + Harassment, + } + + /// Reason for reporting a user + pub enum UserReportReason { + /// No reason has been specified + NoneSpecified, + + /// Unsolicited advertisements + UnsolicitedSpam, + + /// User is sending spam or otherwise abusing the platform + SpamAbuse, + + /// User's profile contains inappropriate content for a general audience + InappropriateProfile, + + /// User is impersonating another user + Impersonation, + + /// User is evading a ban + BanEvasion, + + /// User is not of minimum age to use the platform + Underage, + } + + /// The content being reported + #[serde(tag = "type")] + pub enum ReportedContent { + /// Report a message + Message { + /// ID of the message + id: String, + /// Reason for reporting message + report_reason: ContentReportReason, + }, + /// Report a server + Server { + /// ID of the server + id: String, + /// Reason for reporting server + report_reason: ContentReportReason, + }, + /// Report a user + User { + /// ID of the user + id: String, + /// Reason for reporting a user + report_reason: UserReportReason, + /// Message context + message_id: Option, + }, + } + + /// Status of the report + #[serde(tag = "status")] + pub enum ReportStatus { + /// Report is waiting for triage / action + Created {}, + + /// Report was rejected + Rejected { + rejection_reason: String, + closed_at: Option, + }, + + /// Report was actioned and resolved + Resolved { closed_at: Option }, + } + + /// Just the status of the report + pub enum ReportStatusString { + /// Report is waiting for triage / action + Created, + + /// Report was rejected + Rejected, + + /// Report was actioned and resolved + Resolved, + } +); diff --git a/crates/core/database/src/models/safety_reports/ops.rs b/crates/core/database/src/models/safety_reports/ops.rs new file mode 100644 index 00000000..ccb8fb5d --- /dev/null +++ b/crates/core/database/src/models/safety_reports/ops.rs @@ -0,0 +1,12 @@ +use revolt_result::Result; + +use crate::Report; + +mod mongodb; +mod reference; + +#[async_trait] +pub trait AbstractReport: Sync + Send { + /// Insert a new report into the database + async fn insert_report(&self, report: &Report) -> Result<()>; +} diff --git a/crates/core/database/src/models/safety_reports/ops/mongodb.rs b/crates/core/database/src/models/safety_reports/ops/mongodb.rs new file mode 100644 index 00000000..d293060e --- /dev/null +++ b/crates/core/database/src/models/safety_reports/ops/mongodb.rs @@ -0,0 +1,16 @@ +use revolt_result::Result; + +use crate::MongoDb; +use crate::Report; + +use super::AbstractReport; + +static COL: &str = "safety_reports"; + +#[async_trait] +impl AbstractReport for MongoDb { + /// Insert a new report into the database + async fn insert_report(&self, report: &Report) -> Result<()> { + query!(self, insert_one, COL, &report).map(|_| ()) + } +} diff --git a/crates/core/database/src/models/safety_reports/ops/reference.rs b/crates/core/database/src/models/safety_reports/ops/reference.rs new file mode 100644 index 00000000..6355e88d --- /dev/null +++ b/crates/core/database/src/models/safety_reports/ops/reference.rs @@ -0,0 +1,20 @@ +use revolt_result::Result; + +use crate::ReferenceDb; +use crate::Report; + +use super::AbstractReport; + +#[async_trait] +impl AbstractReport for ReferenceDb { + /// Insert a new report into the database + async fn insert_report(&self, report: &Report) -> Result<()> { + let mut reports = self.safety_reports.lock().await; + if reports.contains_key(&report.id) { + Err(create_database_error!("insert", "report")) + } else { + reports.insert(report.id.to_string(), report.clone()); + Ok(()) + } + } +} diff --git a/crates/core/database/src/models/safety_snapshots/mod.rs b/crates/core/database/src/models/safety_snapshots/mod.rs new file mode 100644 index 00000000..4d801b73 --- /dev/null +++ b/crates/core/database/src/models/safety_snapshots/mod.rs @@ -0,0 +1,5 @@ +mod model; +mod ops; + +pub use model::*; +pub use ops::*; diff --git a/crates/core/database/src/models/safety_snapshots/model.rs b/crates/core/database/src/models/safety_snapshots/model.rs new file mode 100644 index 00000000..4cf861f3 --- /dev/null +++ b/crates/core/database/src/models/safety_snapshots/model.rs @@ -0,0 +1,35 @@ +use revolt_models::v0::{Message, Server, User}; + +auto_derived!( + /// Snapshot of some content + pub struct Snapshot { + /// Unique Id + #[serde(rename = "_id")] + pub id: String, + /// Report parent Id + pub report_id: String, + /// Snapshot of content + pub content: SnapshotContent, + } + + /// Enum to map into different models + /// that can be saved in a snapshot + #[serde(tag = "_type")] + pub enum SnapshotContent { + Message { + /// Context before the message + #[serde(rename = "_prior_context", default)] + prior_context: Vec, + + /// Context after the message + #[serde(rename = "_leading_context", default)] + leading_context: Vec, + + /// Message + #[serde(flatten)] + message: Message, + }, + Server(Server), + User(User), + } +); diff --git a/crates/core/database/src/models/safety_snapshots/ops.rs b/crates/core/database/src/models/safety_snapshots/ops.rs new file mode 100644 index 00000000..0f9a852b --- /dev/null +++ b/crates/core/database/src/models/safety_snapshots/ops.rs @@ -0,0 +1,12 @@ +use revolt_result::Result; + +use crate::Snapshot; + +mod mongodb; +mod reference; + +#[async_trait] +pub trait AbstractSnapshot: Sync + Send { + /// Insert a new snapshot into the database + async fn insert_snapshot(&self, snapshot: &Snapshot) -> Result<()>; +} diff --git a/crates/core/database/src/models/safety_snapshots/ops/mongodb.rs b/crates/core/database/src/models/safety_snapshots/ops/mongodb.rs new file mode 100644 index 00000000..58c4fc35 --- /dev/null +++ b/crates/core/database/src/models/safety_snapshots/ops/mongodb.rs @@ -0,0 +1,16 @@ +use revolt_result::Result; + +use crate::MongoDb; +use crate::Snapshot; + +use super::AbstractSnapshot; + +static COL: &str = "safety_snapshots"; + +#[async_trait] +impl AbstractSnapshot for MongoDb { + /// Insert a new snapshot into the database + async fn insert_snapshot(&self, snapshot: &Snapshot) -> Result<()> { + query!(self, insert_one, COL, &snapshot).map(|_| ()) + } +} diff --git a/crates/core/database/src/models/safety_snapshots/ops/reference.rs b/crates/core/database/src/models/safety_snapshots/ops/reference.rs new file mode 100644 index 00000000..f26b9479 --- /dev/null +++ b/crates/core/database/src/models/safety_snapshots/ops/reference.rs @@ -0,0 +1,20 @@ +use revolt_result::Result; + +use crate::ReferenceDb; +use crate::Snapshot; + +use super::AbstractSnapshot; + +#[async_trait] +impl AbstractSnapshot for ReferenceDb { + /// Insert a new report into the database + async fn insert_snapshot(&self, snapshot: &Snapshot) -> Result<()> { + let mut snapshots = self.safety_snapshots.lock().await; + if snapshots.contains_key(&snapshot.id) { + Err(create_database_error!("insert", "snapshot")) + } else { + snapshots.insert(snapshot.id.to_string(), snapshot.clone()); + Ok(()) + } + } +}