feat(safety): implement report content API

This commit is contained in:
Paul Makles
2023-02-21 12:38:35 +01:00
parent 49598daf70
commit 056c0380b2
17 changed files with 284 additions and 23 deletions

6
Cargo.lock generated
View File

@@ -2792,7 +2792,7 @@ dependencies = [
[[package]]
name = "revolt-bonfire"
version = "0.5.7"
version = "0.5.8"
dependencies = [
"async-std",
"async-tungstenite",
@@ -2808,7 +2808,7 @@ dependencies = [
[[package]]
name = "revolt-delta"
version = "0.5.7"
version = "0.5.8"
dependencies = [
"async-channel",
"async-std",
@@ -2848,7 +2848,7 @@ dependencies = [
[[package]]
name = "revolt-quark"
version = "0.5.7"
version = "0.5.8"
dependencies = [
"async-lock",
"async-recursion",

View File

@@ -1,6 +1,6 @@
[package]
name = "revolt-bonfire"
version = "0.5.7"
version = "0.5.8"
license = "AGPL-3.0-or-later"
edition = "2021"

View File

@@ -1,6 +1,6 @@
[package]
name = "revolt-delta"
version = "0.5.7"
version = "0.5.8"
license = "AGPL-3.0-or-later"
authors = ["Paul Makles <paulmakles@gmail.com>"]
edition = "2018"

View File

@@ -1,25 +1,13 @@
use revolt_quark::models::User;
use revolt_quark::models::report::ReportedContent;
use revolt_quark::models::snapshot::{Snapshot, SnapshotContent};
use revolt_quark::models::{Report, User};
use revolt_quark::{Db, Error, Result};
use serde::Deserialize;
use ulid::Ulid;
use validator::Validate;
use rocket::serde::json::Json;
#[derive(Deserialize, JsonSchema)]
enum UserReportReason {
NoneSpecified,
InappropriateProfile,
}
// TODO: move me into models
#[derive(Deserialize, JsonSchema)]
pub enum ReportedContent {
User {
id: String,
report_reason: UserReportReason,
},
}
/// # Report Data
#[derive(Validate, Deserialize, JsonSchema)]
pub struct DataReportContent {
@@ -46,7 +34,76 @@ pub async fn report_content(db: &Db, user: User, data: Json<DataReportContent>)
return Err(Error::IsBot);
}
// find the content and create the report here
// Find the content and create a snapshot of it
// Also retrieve any references to Files
let (content, files): (SnapshotContent, Vec<String>) = match &data.content {
ReportedContent::Message { id, .. } => {
let message = db.fetch_message(id).await?;
// Collect message attachments
let files = message
.attachments
.as_ref()
.map(|attachments| attachments.iter().map(|x| x.id.to_string()).collect())
.unwrap_or_default();
(SnapshotContent::Message(message), files)
}
ReportedContent::Server { id, .. } => {
let server = db.fetch_server(id).await?;
// Collect server's icon and banner
let files = [&server.icon, &server.banner]
.iter()
.filter_map(|x| x.as_ref().map(|x| x.id.to_string()))
.collect();
(SnapshotContent::Server(server), files)
}
ReportedContent::User { id, .. } => {
let user = db.fetch_user(id).await?;
// Collect user's avatar and profile background
let files = [
user.avatar.as_ref(),
user.profile
.as_ref()
.and_then(|profile| profile.background.as_ref()),
]
.iter()
.filter_map(|x| x.as_ref().map(|x| x.id.to_string()))
.collect();
(SnapshotContent::User(user), files)
}
};
// Mark all the attachments as reported
for file in files {
db.mark_attachment_as_reported(&file).await?;
}
// Generate an id for the report
let id = Ulid::new().to_string();
// Save a snapshot of the content
let snapshot = Snapshot {
id: Ulid::new().to_string(),
report_id: id.to_string(),
content,
};
db.insert_snapshot(&snapshot).await?;
// Save the report
let report = Report {
id,
author_id: user.id,
content: data.content,
additional_context: data.additional_context,
};
db.insert_report(&report).await?;
Ok(())
}

View File

@@ -1,6 +1,6 @@
[package]
name = "revolt-quark"
version = "0.5.7"
version = "0.5.8"
license = "AGPL-3.0-or-later"
edition = "2021"

View File

@@ -28,6 +28,11 @@ pub mod users {
pub mod user_settings;
}
pub mod safety {
pub mod report;
pub mod snapshot;
}
#[derive(Debug, Clone)]
pub struct DummyDb;

View File

@@ -0,0 +1,12 @@
use crate::models::Report;
use crate::{AbstractReport, Result};
use super::super::DummyDb;
#[async_trait]
impl AbstractReport for DummyDb {
async fn insert_report(&self, report: &Report) -> Result<()> {
info!("Insert {:?}", report);
Ok(())
}
}

View File

@@ -0,0 +1,12 @@
use crate::models::Snapshot;
use crate::{AbstractSnapshot, Result};
use super::super::DummyDb;
#[async_trait]
impl AbstractSnapshot for DummyDb {
async fn insert_snapshot(&self, snapshot: &Snapshot) -> Result<()> {
info!("Insert {:?}", snapshot);
Ok(())
}
}

View File

@@ -39,6 +39,11 @@ pub mod users {
pub mod user_settings;
}
pub mod safety {
pub mod report;
pub mod snapshot;
}
#[derive(Debug, Clone)]
pub struct MongoDb(pub mongodb::Client);

View File

@@ -0,0 +1,13 @@
use crate::models::Report;
use crate::{AbstractReport, Result};
use super::super::MongoDb;
static COL: &str = "safety_reports";
#[async_trait]
impl AbstractReport for MongoDb {
async fn insert_report(&self, report: &Report) -> Result<()> {
self.insert_one(COL, report).await.map(|_| ())
}
}

View File

@@ -0,0 +1,13 @@
use crate::models::Snapshot;
use crate::{AbstractSnapshot, Result};
use super::super::MongoDb;
static COL: &str = "safety_snapshots";
#[async_trait]
impl AbstractSnapshot for MongoDb {
async fn insert_snapshot(&self, snapshot: &Snapshot) -> Result<()> {
self.insert_one(COL, snapshot).await.map(|_| ())
}
}

View File

@@ -27,9 +27,15 @@ mod users {
pub mod user_settings;
}
mod safety {
pub mod report;
pub mod snapshot;
}
pub use admin::*;
pub use channels::*;
pub use media::*;
pub use safety::*;
pub use servers::*;
pub use users::*;
@@ -41,9 +47,11 @@ pub use channel_unread::ChannelUnread;
pub use emoji::Emoji;
pub use message::Message;
pub use migrations::MigrationInfo;
pub use report::Report;
pub use server::Server;
pub use server_ban::ServerBan;
pub use server_member::Member;
pub use simple::SimpleModel;
pub use snapshot::Snapshot;
pub use user::User;
pub use user_settings::UserSettings;

View File

@@ -0,0 +1,85 @@
use serde::{Deserialize, Serialize};
/// Reason for reporting content (message or server)
#[derive(Serialize, Deserialize, JsonSchema, Debug)]
pub enum ContentReportReason {
/// No reason has been specified
NoneSpecified,
/// Blatantly illegal content
Illegal,
/// Content that promotes harm to others / self
PromotesHarm,
/// Spam or platform abuse
SpamAbuse,
/// Distribution of malware
Malware,
/// Harassment or abuse targeted at another user
Harassment,
}
/// Reason for reporting a user
#[derive(Serialize, Deserialize, JsonSchema, Debug)]
pub enum UserReportReason {
/// No reason has been specified
NoneSpecified,
/// 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,
}
#[derive(Serialize, Deserialize, JsonSchema, Debug)]
#[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,
},
}
/// User-generated platform moderation report.
#[derive(Serialize, Deserialize, JsonSchema, Debug)]
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,
}

View File

@@ -0,0 +1,25 @@
use serde::{Deserialize, Serialize};
use crate::models::{Message, Server, User};
/// Enum to map into different models
/// that can be saved in a snapshot
#[derive(Serialize, Deserialize, JsonSchema, Debug)]
#[serde(tag = "_type")]
pub enum SnapshotContent {
Message(Message),
Server(Server),
User(User),
}
/// Snapshot of some content
#[derive(Serialize, Deserialize, JsonSchema, Debug)]
pub struct Snapshot {
/// Unique Id
#[serde(rename = "_id")]
pub id: String,
/// Report parent Id
pub report_id: String,
/// Snapshot of content
pub content: SnapshotContent,
}

View File

@@ -26,6 +26,11 @@ mod users {
pub mod user_settings;
}
mod safety {
pub mod report;
pub mod snapshot;
}
pub use admin::migrations::AbstractMigrations;
pub use media::attachment::AbstractAttachment;
@@ -44,6 +49,9 @@ pub use users::bot::AbstractBot;
pub use users::user::AbstractUser;
pub use users::user_settings::AbstractUserSettings;
pub use safety::report::AbstractReport;
pub use safety::snapshot::AbstractSnapshot;
pub trait AbstractDatabase:
Sync
+ Send
@@ -60,5 +68,7 @@ pub trait AbstractDatabase:
+ AbstractBot
+ AbstractUser
+ AbstractUserSettings
+ AbstractReport
+ AbstractSnapshot
{
}

View File

@@ -0,0 +1,8 @@
use crate::models::Report;
use crate::Result;
#[async_trait]
pub trait AbstractReport: Sync + Send {
/// Insert a new report into the database
async fn insert_report(&self, report: &Report) -> Result<()>;
}

View File

@@ -0,0 +1,8 @@
use crate::models::Snapshot;
use crate::Result;
#[async_trait]
pub trait AbstractSnapshot: Sync + Send {
/// Insert a new snapshot into the database
async fn insert_snapshot(&self, snapshot: &Snapshot) -> Result<()>;
}