feat(safety): implement report content API
This commit is contained in:
6
Cargo.lock
generated
6
Cargo.lock
generated
@@ -2792,7 +2792,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "revolt-bonfire"
|
name = "revolt-bonfire"
|
||||||
version = "0.5.7"
|
version = "0.5.8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-std",
|
"async-std",
|
||||||
"async-tungstenite",
|
"async-tungstenite",
|
||||||
@@ -2808,7 +2808,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "revolt-delta"
|
name = "revolt-delta"
|
||||||
version = "0.5.7"
|
version = "0.5.8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-channel",
|
"async-channel",
|
||||||
"async-std",
|
"async-std",
|
||||||
@@ -2848,7 +2848,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "revolt-quark"
|
name = "revolt-quark"
|
||||||
version = "0.5.7"
|
version = "0.5.8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-lock",
|
"async-lock",
|
||||||
"async-recursion",
|
"async-recursion",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "revolt-bonfire"
|
name = "revolt-bonfire"
|
||||||
version = "0.5.7"
|
version = "0.5.8"
|
||||||
license = "AGPL-3.0-or-later"
|
license = "AGPL-3.0-or-later"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "revolt-delta"
|
name = "revolt-delta"
|
||||||
version = "0.5.7"
|
version = "0.5.8"
|
||||||
license = "AGPL-3.0-or-later"
|
license = "AGPL-3.0-or-later"
|
||||||
authors = ["Paul Makles <paulmakles@gmail.com>"]
|
authors = ["Paul Makles <paulmakles@gmail.com>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|||||||
@@ -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 revolt_quark::{Db, Error, Result};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
use ulid::Ulid;
|
||||||
use validator::Validate;
|
use validator::Validate;
|
||||||
|
|
||||||
use rocket::serde::json::Json;
|
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
|
/// # Report Data
|
||||||
#[derive(Validate, Deserialize, JsonSchema)]
|
#[derive(Validate, Deserialize, JsonSchema)]
|
||||||
pub struct DataReportContent {
|
pub struct DataReportContent {
|
||||||
@@ -46,7 +34,76 @@ pub async fn report_content(db: &Db, user: User, data: Json<DataReportContent>)
|
|||||||
return Err(Error::IsBot);
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "revolt-quark"
|
name = "revolt-quark"
|
||||||
version = "0.5.7"
|
version = "0.5.8"
|
||||||
license = "AGPL-3.0-or-later"
|
license = "AGPL-3.0-or-later"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
|
|||||||
@@ -28,6 +28,11 @@ pub mod users {
|
|||||||
pub mod user_settings;
|
pub mod user_settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub mod safety {
|
||||||
|
pub mod report;
|
||||||
|
pub mod snapshot;
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct DummyDb;
|
pub struct DummyDb;
|
||||||
|
|
||||||
|
|||||||
12
crates/quark/src/impl/dummy/safety/report.rs
Normal file
12
crates/quark/src/impl/dummy/safety/report.rs
Normal 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(())
|
||||||
|
}
|
||||||
|
}
|
||||||
12
crates/quark/src/impl/dummy/safety/snapshot.rs
Normal file
12
crates/quark/src/impl/dummy/safety/snapshot.rs
Normal 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(())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -39,6 +39,11 @@ pub mod users {
|
|||||||
pub mod user_settings;
|
pub mod user_settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub mod safety {
|
||||||
|
pub mod report;
|
||||||
|
pub mod snapshot;
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct MongoDb(pub mongodb::Client);
|
pub struct MongoDb(pub mongodb::Client);
|
||||||
|
|
||||||
|
|||||||
13
crates/quark/src/impl/mongo/safety/report.rs
Normal file
13
crates/quark/src/impl/mongo/safety/report.rs
Normal 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(|_| ())
|
||||||
|
}
|
||||||
|
}
|
||||||
13
crates/quark/src/impl/mongo/safety/snapshot.rs
Normal file
13
crates/quark/src/impl/mongo/safety/snapshot.rs
Normal 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(|_| ())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -27,9 +27,15 @@ mod users {
|
|||||||
pub mod user_settings;
|
pub mod user_settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mod safety {
|
||||||
|
pub mod report;
|
||||||
|
pub mod snapshot;
|
||||||
|
}
|
||||||
|
|
||||||
pub use admin::*;
|
pub use admin::*;
|
||||||
pub use channels::*;
|
pub use channels::*;
|
||||||
pub use media::*;
|
pub use media::*;
|
||||||
|
pub use safety::*;
|
||||||
pub use servers::*;
|
pub use servers::*;
|
||||||
pub use users::*;
|
pub use users::*;
|
||||||
|
|
||||||
@@ -41,9 +47,11 @@ pub use channel_unread::ChannelUnread;
|
|||||||
pub use emoji::Emoji;
|
pub use emoji::Emoji;
|
||||||
pub use message::Message;
|
pub use message::Message;
|
||||||
pub use migrations::MigrationInfo;
|
pub use migrations::MigrationInfo;
|
||||||
|
pub use report::Report;
|
||||||
pub use server::Server;
|
pub use server::Server;
|
||||||
pub use server_ban::ServerBan;
|
pub use server_ban::ServerBan;
|
||||||
pub use server_member::Member;
|
pub use server_member::Member;
|
||||||
pub use simple::SimpleModel;
|
pub use simple::SimpleModel;
|
||||||
|
pub use snapshot::Snapshot;
|
||||||
pub use user::User;
|
pub use user::User;
|
||||||
pub use user_settings::UserSettings;
|
pub use user_settings::UserSettings;
|
||||||
|
|||||||
85
crates/quark/src/models/safety/report.rs
Normal file
85
crates/quark/src/models/safety/report.rs
Normal 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,
|
||||||
|
}
|
||||||
25
crates/quark/src/models/safety/snapshot.rs
Normal file
25
crates/quark/src/models/safety/snapshot.rs
Normal 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,
|
||||||
|
}
|
||||||
@@ -26,6 +26,11 @@ mod users {
|
|||||||
pub mod user_settings;
|
pub mod user_settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mod safety {
|
||||||
|
pub mod report;
|
||||||
|
pub mod snapshot;
|
||||||
|
}
|
||||||
|
|
||||||
pub use admin::migrations::AbstractMigrations;
|
pub use admin::migrations::AbstractMigrations;
|
||||||
|
|
||||||
pub use media::attachment::AbstractAttachment;
|
pub use media::attachment::AbstractAttachment;
|
||||||
@@ -44,6 +49,9 @@ pub use users::bot::AbstractBot;
|
|||||||
pub use users::user::AbstractUser;
|
pub use users::user::AbstractUser;
|
||||||
pub use users::user_settings::AbstractUserSettings;
|
pub use users::user_settings::AbstractUserSettings;
|
||||||
|
|
||||||
|
pub use safety::report::AbstractReport;
|
||||||
|
pub use safety::snapshot::AbstractSnapshot;
|
||||||
|
|
||||||
pub trait AbstractDatabase:
|
pub trait AbstractDatabase:
|
||||||
Sync
|
Sync
|
||||||
+ Send
|
+ Send
|
||||||
@@ -60,5 +68,7 @@ pub trait AbstractDatabase:
|
|||||||
+ AbstractBot
|
+ AbstractBot
|
||||||
+ AbstractUser
|
+ AbstractUser
|
||||||
+ AbstractUserSettings
|
+ AbstractUserSettings
|
||||||
|
+ AbstractReport
|
||||||
|
+ AbstractSnapshot
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|||||||
8
crates/quark/src/traits/safety/report.rs
Normal file
8
crates/quark/src/traits/safety/report.rs
Normal 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<()>;
|
||||||
|
}
|
||||||
8
crates/quark/src/traits/safety/snapshot.rs
Normal file
8
crates/quark/src/traits/safety/snapshot.rs
Normal 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<()>;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user