Addd admin models to database
This commit is contained in:
@@ -58,6 +58,9 @@ trust_cloudflare = false
|
|||||||
easypwned = ""
|
easypwned = ""
|
||||||
# Tenor API Key
|
# Tenor API Key
|
||||||
tenor_key = ""
|
tenor_key = ""
|
||||||
|
# admin api machine keys
|
||||||
|
# admin_keys = ["key_1", "key_2"]
|
||||||
|
admin_keys = []
|
||||||
|
|
||||||
[api.security.captcha]
|
[api.security.captcha]
|
||||||
# hCaptcha configuration
|
# hCaptcha configuration
|
||||||
@@ -188,6 +191,7 @@ default_bucket = "revolt-uploads"
|
|||||||
[features]
|
[features]
|
||||||
# Feature gate options
|
# Feature gate options
|
||||||
webhooks_enabled = false
|
webhooks_enabled = false
|
||||||
|
admin_api_enabled = false
|
||||||
# Enable push notifications for mass pings (everyone, online, roles)
|
# Enable push notifications for mass pings (everyone, online, roles)
|
||||||
# When false this will still ping in-client but will not send notifications from pushd
|
# When false this will still ping in-client but will not send notifications from pushd
|
||||||
mass_mentions_send_notifications = true
|
mass_mentions_send_notifications = true
|
||||||
|
|||||||
@@ -191,6 +191,7 @@ pub struct ApiSecurity {
|
|||||||
pub trust_cloudflare: bool,
|
pub trust_cloudflare: bool,
|
||||||
pub easypwned: String,
|
pub easypwned: String,
|
||||||
pub tenor_key: String,
|
pub tenor_key: String,
|
||||||
|
pub admin_keys: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Debug, Clone)]
|
#[derive(Deserialize, Debug, Clone)]
|
||||||
@@ -353,6 +354,7 @@ pub struct Features {
|
|||||||
pub webhooks_enabled: bool,
|
pub webhooks_enabled: bool,
|
||||||
pub mass_mentions_send_notifications: bool,
|
pub mass_mentions_send_notifications: bool,
|
||||||
pub mass_mentions_enabled: bool,
|
pub mass_mentions_enabled: bool,
|
||||||
|
pub admin_api_enabled: bool,
|
||||||
|
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub advanced: FeaturesAdvanced,
|
pub advanced: FeaturesAdvanced,
|
||||||
|
|||||||
@@ -1,17 +1,28 @@
|
|||||||
use std::{collections::HashMap, sync::Arc};
|
use std::{
|
||||||
|
collections::{BTreeMap, HashMap},
|
||||||
|
sync::Arc,
|
||||||
|
};
|
||||||
|
|
||||||
use futures::lock::Mutex;
|
use futures::lock::Mutex;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
Bot, Channel, ChannelCompositeKey, ChannelUnread, Emoji, File, FileHash, Invite, Member,
|
AdminAuditItem, AdminCase, AdminCaseComment, AdminObjectNote, AdminStrike, AdminToken,
|
||||||
MemberCompositeKey, Message, PolicyChange, RatelimitEvent, Report, Server, ServerBan, Snapshot,
|
AdminUser, Bot, Channel, ChannelCompositeKey, ChannelUnread, Emoji, File, FileHash, Invite,
|
||||||
User, UserSettings, Webhook,
|
Member, MemberCompositeKey, Message, PolicyChange, RatelimitEvent, Report, Server, ServerBan,
|
||||||
|
Snapshot, User, UserSettings, Webhook,
|
||||||
};
|
};
|
||||||
|
|
||||||
database_derived!(
|
database_derived!(
|
||||||
/// Reference implementation
|
/// Reference implementation
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct ReferenceDb {
|
pub struct ReferenceDb {
|
||||||
|
pub admin_audits: Arc<Mutex<BTreeMap<String, AdminAuditItem>>>,
|
||||||
|
pub admin_cases: Arc<Mutex<HashMap<String, AdminCase>>>,
|
||||||
|
pub admin_case_comments: Arc<Mutex<HashMap<String, AdminCaseComment>>>,
|
||||||
|
pub admin_object_notes: Arc<Mutex<HashMap<String, AdminObjectNote>>>,
|
||||||
|
pub admin_strikes: Arc<Mutex<HashMap<String, AdminStrike>>>,
|
||||||
|
pub admin_tokens: Arc<Mutex<HashMap<String, AdminToken>>>,
|
||||||
|
pub admin_users: Arc<Mutex<HashMap<String, AdminUser>>>,
|
||||||
pub bots: Arc<Mutex<HashMap<String, Bot>>>,
|
pub bots: Arc<Mutex<HashMap<String, Bot>>>,
|
||||||
pub channels: Arc<Mutex<HashMap<String, Channel>>>,
|
pub channels: Arc<Mutex<HashMap<String, Channel>>>,
|
||||||
pub channel_invites: Arc<Mutex<HashMap<String, Invite>>>,
|
pub channel_invites: Arc<Mutex<HashMap<String, Invite>>>,
|
||||||
|
|||||||
5
crates/core/database/src/models/admin_audits/mod.rs
Normal file
5
crates/core/database/src/models/admin_audits/mod.rs
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
mod models;
|
||||||
|
mod ops;
|
||||||
|
|
||||||
|
pub use models::*;
|
||||||
|
pub use ops::*;
|
||||||
17
crates/core/database/src/models/admin_audits/models.rs
Normal file
17
crates/core/database/src/models/admin_audits/models.rs
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
auto_derived! {
|
||||||
|
pub struct AdminAuditItem {
|
||||||
|
/// The audit item ID
|
||||||
|
#[serde(rename = "_id")]
|
||||||
|
pub id: String,
|
||||||
|
/// The moderator who performed the action
|
||||||
|
pub mod_id: String,
|
||||||
|
/// The action performed (previously 'permission')
|
||||||
|
pub action: String,
|
||||||
|
/// The relevant case ID, if applicable
|
||||||
|
pub case_id: Option<String>,
|
||||||
|
/// The object the action was taken against, if applicable
|
||||||
|
pub target: Option<String>,
|
||||||
|
/// The context of the action, if applicable (eg. search phrases)
|
||||||
|
pub context: Option<String>,
|
||||||
|
}
|
||||||
|
}
|
||||||
17
crates/core/database/src/models/admin_audits/ops.rs
Normal file
17
crates/core/database/src/models/admin_audits/ops.rs
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
mod mongodb;
|
||||||
|
mod reference;
|
||||||
|
|
||||||
|
use revolt_result::Result;
|
||||||
|
|
||||||
|
use crate::models::admin_audits::AdminAuditItem;
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
pub trait AbstractAdminAudits: Sync + Send {
|
||||||
|
async fn admin_audit_insert(&self, audit: AdminAuditItem) -> Result<()>;
|
||||||
|
|
||||||
|
async fn admin_audit_fetch(
|
||||||
|
&self,
|
||||||
|
before_id: Option<&str>,
|
||||||
|
limit: i64,
|
||||||
|
) -> Result<Vec<AdminAuditItem>>;
|
||||||
|
}
|
||||||
48
crates/core/database/src/models/admin_audits/ops/mongodb.rs
Normal file
48
crates/core/database/src/models/admin_audits/ops/mongodb.rs
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
use async_std::stream::StreamExt;
|
||||||
|
use revolt_result::Result;
|
||||||
|
|
||||||
|
use crate::AdminAuditItem;
|
||||||
|
use crate::MongoDb;
|
||||||
|
|
||||||
|
use super::AbstractAdminAudits;
|
||||||
|
|
||||||
|
static COL: &str = "admin_audits";
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl AbstractAdminAudits for MongoDb {
|
||||||
|
async fn admin_audit_insert(&self, audit: AdminAuditItem) -> Result<()> {
|
||||||
|
query!(self, insert_one, COL, audit).map(|_| ())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn admin_audit_fetch(
|
||||||
|
&self,
|
||||||
|
before_id: Option<&str>,
|
||||||
|
limit: i64,
|
||||||
|
) -> Result<Vec<AdminAuditItem>> {
|
||||||
|
if let Some(before) = before_id {
|
||||||
|
Ok(self
|
||||||
|
.col::<AdminAuditItem>(COL)
|
||||||
|
.find(doc! {
|
||||||
|
"_id": { "$lt": before}
|
||||||
|
})
|
||||||
|
.sort(doc! {"_id": -1})
|
||||||
|
.limit(limit)
|
||||||
|
.await
|
||||||
|
.map_err(|_| create_database_error!("find", COL))?
|
||||||
|
.filter_map(|f| f.ok())
|
||||||
|
.collect()
|
||||||
|
.await)
|
||||||
|
} else {
|
||||||
|
Ok(self
|
||||||
|
.col::<AdminAuditItem>(COL)
|
||||||
|
.find(doc! {})
|
||||||
|
.sort(doc! {"_id": -1})
|
||||||
|
.limit(limit)
|
||||||
|
.await
|
||||||
|
.map_err(|_| create_database_error!("find", COL))?
|
||||||
|
.filter_map(|f| f.ok())
|
||||||
|
.collect()
|
||||||
|
.await)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
use revolt_result::Result;
|
||||||
|
|
||||||
|
use crate::AdminAuditItem;
|
||||||
|
use crate::ReferenceDb;
|
||||||
|
|
||||||
|
use super::AbstractAdminAudits;
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl AbstractAdminAudits for ReferenceDb {
|
||||||
|
async fn admin_audit_insert(&self, audit: AdminAuditItem) -> Result<()> {
|
||||||
|
let mut admin_audits = self.admin_audits.lock().await;
|
||||||
|
if admin_audits.contains_key(&audit.id) {
|
||||||
|
Err(create_database_error!("insert", "admin_audits"))
|
||||||
|
} else {
|
||||||
|
admin_audits.insert(audit.id.to_string(), audit.clone());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn admin_audit_fetch(
|
||||||
|
&self,
|
||||||
|
before_id: Option<&str>,
|
||||||
|
limit: i64,
|
||||||
|
) -> Result<Vec<AdminAuditItem>> {
|
||||||
|
let admin_audits = self.admin_audits.lock().await;
|
||||||
|
if let Some(before) = before_id {
|
||||||
|
Ok(admin_audits
|
||||||
|
.iter()
|
||||||
|
.rev()
|
||||||
|
.skip_while(|(id, _)| id.as_str() <= before)
|
||||||
|
.by_ref()
|
||||||
|
.take(limit as usize)
|
||||||
|
.map(|(_, item)| item.clone())
|
||||||
|
.collect())
|
||||||
|
} else {
|
||||||
|
Ok(admin_audits
|
||||||
|
.iter()
|
||||||
|
.rev()
|
||||||
|
.by_ref()
|
||||||
|
.take(limit as usize)
|
||||||
|
.map(|(_, item)| item.clone())
|
||||||
|
.collect())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
mod models;
|
||||||
|
mod ops;
|
||||||
|
|
||||||
|
pub use models::*;
|
||||||
|
pub use ops::*;
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
auto_derived_partial! {
|
||||||
|
pub struct AdminCaseComment {
|
||||||
|
/// The comment ID
|
||||||
|
#[serde(rename = "_id")]
|
||||||
|
pub id: String,
|
||||||
|
/// The ID of the case this comment is attached to
|
||||||
|
pub case_id: String,
|
||||||
|
/// The author ID
|
||||||
|
pub user_id: String,
|
||||||
|
/// When the comment was edited, if applicable, in iso8601
|
||||||
|
pub edited_at: Option<String>,
|
||||||
|
/// The content
|
||||||
|
pub content: String
|
||||||
|
},
|
||||||
|
"PartialAdminCaseComment"
|
||||||
|
}
|
||||||
19
crates/core/database/src/models/admin_case_comments/ops.rs
Normal file
19
crates/core/database/src/models/admin_case_comments/ops.rs
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
mod mongodb;
|
||||||
|
mod reference;
|
||||||
|
|
||||||
|
use revolt_result::Result;
|
||||||
|
|
||||||
|
use crate::{models::admin_case_comments::AdminCaseComment, PartialAdminCaseComment};
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
pub trait AbstractAdminCaseComments: Sync + Send {
|
||||||
|
async fn admin_case_comment_insert(&self, comment: AdminCaseComment) -> Result<()>;
|
||||||
|
|
||||||
|
async fn admin_case_comment_update(
|
||||||
|
&self,
|
||||||
|
comment_id: &str,
|
||||||
|
partial: &PartialAdminCaseComment,
|
||||||
|
) -> Result<()>;
|
||||||
|
|
||||||
|
async fn admin_case_comment_fetch(&self, case_id: &str) -> Result<Vec<AdminCaseComment>>;
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
use revolt_result::Result;
|
||||||
|
|
||||||
|
use crate::MongoDb;
|
||||||
|
use crate::{AdminCaseComment, PartialAdminCaseComment};
|
||||||
|
|
||||||
|
use super::AbstractAdminCaseComments;
|
||||||
|
|
||||||
|
static COL: &str = "admin_case_comments";
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl AbstractAdminCaseComments for MongoDb {
|
||||||
|
async fn admin_case_comment_insert(&self, comment: AdminCaseComment) -> Result<()> {
|
||||||
|
query!(self, insert_one, COL, comment).map(|_| ())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn admin_case_comment_update(
|
||||||
|
&self,
|
||||||
|
comment_id: &str,
|
||||||
|
partial: &PartialAdminCaseComment,
|
||||||
|
) -> Result<()> {
|
||||||
|
query!(
|
||||||
|
self,
|
||||||
|
update_one_by_id,
|
||||||
|
COL,
|
||||||
|
comment_id,
|
||||||
|
partial,
|
||||||
|
vec![],
|
||||||
|
None
|
||||||
|
)
|
||||||
|
.map(|_| ())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn admin_case_comment_fetch(&self, case_id: &str) -> Result<Vec<AdminCaseComment>> {
|
||||||
|
query!(self, find, COL, doc! {"case_id": case_id})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
use iso8601_timestamp::Timestamp;
|
||||||
|
use revolt_result::Result;
|
||||||
|
|
||||||
|
use crate::ReferenceDb;
|
||||||
|
use crate::{AdminCaseComment, PartialAdminCaseComment};
|
||||||
|
|
||||||
|
use super::AbstractAdminCaseComments;
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl AbstractAdminCaseComments for ReferenceDb {
|
||||||
|
async fn admin_case_comment_insert(&self, comment: AdminCaseComment) -> Result<()> {
|
||||||
|
let mut admin_case_comments = self.admin_case_comments.lock().await;
|
||||||
|
if admin_case_comments.contains_key(&comment.id) {
|
||||||
|
Err(create_database_error!("insert", "admin_case_comments"))
|
||||||
|
} else {
|
||||||
|
admin_case_comments.insert(comment.id.to_string(), comment.clone());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn admin_case_comment_update(
|
||||||
|
&self,
|
||||||
|
comment_id: &str,
|
||||||
|
partial: &PartialAdminCaseComment,
|
||||||
|
) -> Result<()> {
|
||||||
|
let mut admin_case_comments = self.admin_case_comments.lock().await;
|
||||||
|
if let Some(comment) = admin_case_comments.get_mut(comment_id) {
|
||||||
|
comment.apply_options(partial.clone());
|
||||||
|
comment.edited_at = Some(Timestamp::now_utc().format().to_string());
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(create_error!(NotFound))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn admin_case_comment_fetch(&self, case_id: &str) -> Result<Vec<AdminCaseComment>> {
|
||||||
|
let admin_case_comments = self.admin_case_comments.lock().await;
|
||||||
|
Ok(admin_case_comments
|
||||||
|
.iter()
|
||||||
|
.filter_map(|(_, c)| {
|
||||||
|
if c.case_id == case_id {
|
||||||
|
Some(c.clone())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect())
|
||||||
|
}
|
||||||
|
}
|
||||||
5
crates/core/database/src/models/admin_cases/mod.rs
Normal file
5
crates/core/database/src/models/admin_cases/mod.rs
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
mod models;
|
||||||
|
mod ops;
|
||||||
|
|
||||||
|
pub use models::*;
|
||||||
|
pub use ops::*;
|
||||||
21
crates/core/database/src/models/admin_cases/models.rs
Normal file
21
crates/core/database/src/models/admin_cases/models.rs
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
auto_derived_partial! {
|
||||||
|
pub struct AdminCase {
|
||||||
|
/// The case ID
|
||||||
|
#[serde(rename = "_id")]
|
||||||
|
pub id: String,
|
||||||
|
/// The case Short ID
|
||||||
|
pub short_id: String,
|
||||||
|
|
||||||
|
/// The owner of the case
|
||||||
|
pub owner_id: String,
|
||||||
|
/// The title of the case
|
||||||
|
pub title: String,
|
||||||
|
/// The status of the case (open/closed)
|
||||||
|
pub status: String,
|
||||||
|
/// When the case was closed, in iso8601
|
||||||
|
pub closed_at: Option<String>,
|
||||||
|
/// The tags for the case
|
||||||
|
pub tags: Vec<String>,
|
||||||
|
},
|
||||||
|
"PartialAdminCase"
|
||||||
|
}
|
||||||
28
crates/core/database/src/models/admin_cases/ops.rs
Normal file
28
crates/core/database/src/models/admin_cases/ops.rs
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
mod mongodb;
|
||||||
|
mod reference;
|
||||||
|
use revolt_result::Result;
|
||||||
|
|
||||||
|
use crate::models::admin_cases::{AdminCase, PartialAdminCase};
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
pub trait AbstractAdminCases: Sync + Send {
|
||||||
|
async fn admin_case_create(&self, case: AdminCase) -> Result<()>;
|
||||||
|
|
||||||
|
async fn admin_case_assign_report(&self, case_id: &str, report_id: &str) -> Result<()>;
|
||||||
|
|
||||||
|
async fn admin_case_edit(&self, case_id: &str, partial: &PartialAdminCase) -> Result<()>;
|
||||||
|
|
||||||
|
async fn admin_case_fetch(&self, case_id: &str) -> Result<AdminCase>;
|
||||||
|
|
||||||
|
/// title is fuzzy, the rest of the arguments are direct matches
|
||||||
|
/// before_id and limit are for paginating
|
||||||
|
async fn admin_case_search(
|
||||||
|
&self,
|
||||||
|
title: Option<&str>,
|
||||||
|
status: Option<&str>,
|
||||||
|
owner_id: Option<&str>,
|
||||||
|
tags: Option<Vec<String>>,
|
||||||
|
before_id: Option<&str>,
|
||||||
|
limit: i64,
|
||||||
|
) -> Result<Vec<AdminCase>>;
|
||||||
|
}
|
||||||
80
crates/core/database/src/models/admin_cases/ops/mongodb.rs
Normal file
80
crates/core/database/src/models/admin_cases/ops/mongodb.rs
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
use async_std::stream::StreamExt;
|
||||||
|
use bson::Document;
|
||||||
|
use revolt_result::Result;
|
||||||
|
|
||||||
|
use crate::MongoDb;
|
||||||
|
use crate::{AdminCase, PartialAdminCase};
|
||||||
|
|
||||||
|
use super::AbstractAdminCases;
|
||||||
|
|
||||||
|
static COL: &str = "admin_cases";
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl AbstractAdminCases for MongoDb {
|
||||||
|
async fn admin_case_create(&self, case: AdminCase) -> Result<()> {
|
||||||
|
query!(self, insert_one, COL, case).map(|_| ())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn admin_case_assign_report(&self, case_id: &str, report_id: &str) -> Result<()> {
|
||||||
|
self.col::<Document>("safety_reports")
|
||||||
|
.update_one(
|
||||||
|
doc! {"_id": { "$regex": format!("{}$", report_id)}},
|
||||||
|
doc! {"case_id": case_id},
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map(|_| ())
|
||||||
|
.map_err(|_| create_database_error!("safety_reports", "update_one"))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn admin_case_edit(&self, case_id: &str, partial: &PartialAdminCase) -> Result<()> {
|
||||||
|
query!(self, update_one_by_id, COL, case_id, partial, vec![], None).map(|_| ())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn admin_case_fetch(&self, case_id: &str) -> Result<AdminCase> {
|
||||||
|
query!(self, find_one_by_id, COL, case_id)?
|
||||||
|
.ok_or_else(|| create_database_error!("find_one", COL))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// title is fuzzy, the rest of the arguments are direct matches
|
||||||
|
async fn admin_case_search(
|
||||||
|
&self,
|
||||||
|
title: Option<&str>,
|
||||||
|
status: Option<&str>,
|
||||||
|
owner_id: Option<&str>,
|
||||||
|
tags: Option<Vec<String>>,
|
||||||
|
before_id: Option<&str>,
|
||||||
|
limit: i64,
|
||||||
|
) -> Result<Vec<AdminCase>> {
|
||||||
|
let mut query = Document::new();
|
||||||
|
|
||||||
|
if let Some(title) = title {
|
||||||
|
query.insert("$text", doc! {"$search": title});
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(status) = status {
|
||||||
|
query.insert("status", status);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(owner) = owner_id {
|
||||||
|
query.insert("owner_id", owner);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(tags) = tags {
|
||||||
|
query.insert("tags", doc! {"$elemMatch": {"$in": tags}});
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(before) = before_id {
|
||||||
|
query.insert("_id", doc! {"$lt": before});
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(self
|
||||||
|
.col::<AdminCase>(COL)
|
||||||
|
.find(query)
|
||||||
|
.limit(limit)
|
||||||
|
.await
|
||||||
|
.map_err(|_| create_database_error!("find", COL))?
|
||||||
|
.filter_map(|f| f.ok())
|
||||||
|
.collect()
|
||||||
|
.await)
|
||||||
|
}
|
||||||
|
}
|
||||||
103
crates/core/database/src/models/admin_cases/ops/reference.rs
Normal file
103
crates/core/database/src/models/admin_cases/ops/reference.rs
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
use revolt_result::Result;
|
||||||
|
|
||||||
|
use crate::ReferenceDb;
|
||||||
|
use crate::{AdminCase, PartialAdminCase};
|
||||||
|
|
||||||
|
use super::AbstractAdminCases;
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl AbstractAdminCases for ReferenceDb {
|
||||||
|
async fn admin_case_create(&self, case: AdminCase) -> Result<()> {
|
||||||
|
let mut admin_cases = self.admin_cases.lock().await;
|
||||||
|
if admin_cases.contains_key(&case.id) {
|
||||||
|
Err(create_database_error!("insert", "admin_cases"))
|
||||||
|
} else {
|
||||||
|
admin_cases.insert(case.id.to_string(), case.clone());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn admin_case_assign_report(&self, case_id: &str, report_id: &str) -> Result<()> {
|
||||||
|
let mut reports = self.safety_reports.lock().await;
|
||||||
|
if let Some(report) = reports.get_mut(report_id) {
|
||||||
|
report.case_id = Some(case_id.to_string());
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(create_error!(NotFound))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn admin_case_edit(&self, case_id: &str, partial: &PartialAdminCase) -> Result<()> {
|
||||||
|
let mut admin_cases = self.admin_cases.lock().await;
|
||||||
|
if let Some(case) = admin_cases.get_mut(case_id) {
|
||||||
|
case.apply_options(partial.clone());
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(create_error!(NotFound))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn admin_case_fetch(&self, case_id: &str) -> Result<AdminCase> {
|
||||||
|
let admin_cases = self.admin_cases.lock().await;
|
||||||
|
if let Some(case) = admin_cases.get(case_id) {
|
||||||
|
Ok(case.clone())
|
||||||
|
} else {
|
||||||
|
Err(create_error!(NotFound))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// title is fuzzy, the rest of the arguments are direct matches
|
||||||
|
/// before_id and limit are for paginating
|
||||||
|
async fn admin_case_search(
|
||||||
|
&self,
|
||||||
|
title: Option<&str>,
|
||||||
|
status: Option<&str>,
|
||||||
|
owner_id: Option<&str>,
|
||||||
|
tags: Option<Vec<String>>,
|
||||||
|
before_id: Option<&str>,
|
||||||
|
limit: i64,
|
||||||
|
) -> Result<Vec<AdminCase>> {
|
||||||
|
let admin_cases = self.admin_cases.lock().await;
|
||||||
|
Ok(admin_cases
|
||||||
|
.iter()
|
||||||
|
.filter(|(_, case)| {
|
||||||
|
if let Some(title) = title {
|
||||||
|
case.title.to_lowercase().contains(title)
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.filter(|(_, case)| {
|
||||||
|
if let Some(status) = status {
|
||||||
|
case.status == status
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.filter(|(_, case)| {
|
||||||
|
if let Some(owner) = owner_id {
|
||||||
|
case.owner_id == owner
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.filter(|(_, case)| {
|
||||||
|
if let Some(tags) = &tags {
|
||||||
|
tags.iter().filter(|p| case.tags.contains(p)).count() > 0
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.skip_while(|(id, _)| {
|
||||||
|
if let Some(before) = before_id {
|
||||||
|
id.as_str() <= before
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.by_ref()
|
||||||
|
.take(limit as usize)
|
||||||
|
.map(|(_, case)| case.clone())
|
||||||
|
.collect())
|
||||||
|
}
|
||||||
|
}
|
||||||
5
crates/core/database/src/models/admin_notes/mod.rs
Normal file
5
crates/core/database/src/models/admin_notes/mod.rs
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
mod models;
|
||||||
|
mod ops;
|
||||||
|
|
||||||
|
pub use models::*;
|
||||||
|
pub use ops::*;
|
||||||
25
crates/core/database/src/models/admin_notes/models.rs
Normal file
25
crates/core/database/src/models/admin_notes/models.rs
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
auto_derived_partial! {
|
||||||
|
pub struct AdminObjectNote {
|
||||||
|
/// The ID of the note, which is the same as the ID of the object it's attached to
|
||||||
|
#[serde(rename = "_id")]
|
||||||
|
pub id: String,
|
||||||
|
/// When the note was edited, in iso8601
|
||||||
|
pub edited_at: String,
|
||||||
|
/// The last user to edit the note
|
||||||
|
pub last_edited_by_id: String,
|
||||||
|
/// The content of the note
|
||||||
|
pub content: String,
|
||||||
|
},
|
||||||
|
"PartialAdminObjectNote"
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AdminObjectNote {
|
||||||
|
pub fn to_partial(&self) -> PartialAdminObjectNote {
|
||||||
|
PartialAdminObjectNote {
|
||||||
|
id: Some(self.id.clone()),
|
||||||
|
edited_at: Some(self.edited_at.clone()),
|
||||||
|
last_edited_by_id: Some(self.last_edited_by_id.clone()),
|
||||||
|
content: Some(self.content.clone()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
12
crates/core/database/src/models/admin_notes/ops.rs
Normal file
12
crates/core/database/src/models/admin_notes/ops.rs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
mod mongodb;
|
||||||
|
mod reference;
|
||||||
|
use revolt_result::Result;
|
||||||
|
|
||||||
|
use crate::models::admin_notes::AdminObjectNote;
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
pub trait AbstractAdminNotes: Sync + Send {
|
||||||
|
async fn admin_note_update(&self, note: AdminObjectNote) -> Result<()>;
|
||||||
|
|
||||||
|
async fn admin_note_fetch(&self, target_id: &str) -> Result<AdminObjectNote>;
|
||||||
|
}
|
||||||
34
crates/core/database/src/models/admin_notes/ops/mongodb.rs
Normal file
34
crates/core/database/src/models/admin_notes/ops/mongodb.rs
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
use revolt_result::Result;
|
||||||
|
|
||||||
|
use crate::AdminObjectNote;
|
||||||
|
use crate::MongoDb;
|
||||||
|
|
||||||
|
use super::AbstractAdminNotes;
|
||||||
|
|
||||||
|
static COL: &str = "admin_notes";
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl AbstractAdminNotes for MongoDb {
|
||||||
|
async fn admin_note_update(&self, note: AdminObjectNote) -> Result<()> {
|
||||||
|
let resp: Result<()> = query!(self, insert_one, COL, note.clone()).map(|_| ());
|
||||||
|
if resp.is_err() {
|
||||||
|
query!(
|
||||||
|
self,
|
||||||
|
update_one_by_id,
|
||||||
|
COL,
|
||||||
|
note.id.as_str(),
|
||||||
|
note.to_partial(),
|
||||||
|
vec![],
|
||||||
|
None
|
||||||
|
)
|
||||||
|
.map(|_| ())
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn admin_note_fetch(&self, target_id: &str) -> Result<AdminObjectNote> {
|
||||||
|
query!(self, find_one_by_id, COL, target_id)?
|
||||||
|
.ok_or_else(|| create_database_error!("find_one", COL))
|
||||||
|
}
|
||||||
|
}
|
||||||
29
crates/core/database/src/models/admin_notes/ops/reference.rs
Normal file
29
crates/core/database/src/models/admin_notes/ops/reference.rs
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
use revolt_result::Result;
|
||||||
|
|
||||||
|
use crate::AdminObjectNote;
|
||||||
|
use crate::ReferenceDb;
|
||||||
|
|
||||||
|
use super::AbstractAdminNotes;
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl AbstractAdminNotes for ReferenceDb {
|
||||||
|
async fn admin_note_update(&self, note: AdminObjectNote) -> Result<()> {
|
||||||
|
let mut admin_notes = self.admin_object_notes.lock().await;
|
||||||
|
if let Some(existing_note) = admin_notes.get_mut(¬e.id) {
|
||||||
|
existing_note.apply_options(note.to_partial());
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
admin_notes.insert(note.id.clone(), note);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn admin_note_fetch(&self, target_id: &str) -> Result<AdminObjectNote> {
|
||||||
|
let admin_notes = self.admin_object_notes.lock().await;
|
||||||
|
if let Some(note) = admin_notes.get(target_id) {
|
||||||
|
Ok(note.clone())
|
||||||
|
} else {
|
||||||
|
Err(create_error!(NotFound))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
5
crates/core/database/src/models/admin_strikes/mod.rs
Normal file
5
crates/core/database/src/models/admin_strikes/mod.rs
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
mod models;
|
||||||
|
mod ops;
|
||||||
|
|
||||||
|
pub use models::*;
|
||||||
|
pub use ops::*;
|
||||||
22
crates/core/database/src/models/admin_strikes/models.rs
Normal file
22
crates/core/database/src/models/admin_strikes/models.rs
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
auto_derived_partial! {
|
||||||
|
pub struct AdminStrike {
|
||||||
|
/// The strike ID
|
||||||
|
#[serde(rename = "_id")]
|
||||||
|
pub id: String,
|
||||||
|
/// The object receiving the strike (user/server)
|
||||||
|
pub target_id: String,
|
||||||
|
/// The moderator who gave the strike
|
||||||
|
pub mod_id: String,
|
||||||
|
/// The case the strike was made under
|
||||||
|
pub case_id: Option<String>,
|
||||||
|
/// Action associated with the strike (eg. suspension/ban)
|
||||||
|
pub associated_action: Option<String>,
|
||||||
|
/// Has the strike been removed
|
||||||
|
pub overruled: bool,
|
||||||
|
/// The user-facing reason for the strike
|
||||||
|
pub reason: String,
|
||||||
|
/// Internal context for the strike
|
||||||
|
pub mod_context: Option<String>,
|
||||||
|
},
|
||||||
|
"PartialAdminStrike"
|
||||||
|
}
|
||||||
17
crates/core/database/src/models/admin_strikes/ops.rs
Normal file
17
crates/core/database/src/models/admin_strikes/ops.rs
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
mod mongodb;
|
||||||
|
mod reference;
|
||||||
|
use revolt_result::Result;
|
||||||
|
|
||||||
|
use crate::models::admin_strikes::models::{AdminStrike, PartialAdminStrike};
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
pub trait AbstractAdminStrikes: Sync + Send {
|
||||||
|
async fn admin_strike_insert(&self, strike: AdminStrike) -> Result<()>;
|
||||||
|
|
||||||
|
async fn admin_strike_update(&self, strike_id: &str, partial: PartialAdminStrike)
|
||||||
|
-> Result<()>;
|
||||||
|
|
||||||
|
async fn admin_strike_get(&self, strike_id: &str) -> Result<AdminStrike>;
|
||||||
|
|
||||||
|
async fn admin_strike_get_user(&self, user_id: &str) -> Result<Vec<AdminStrike>>;
|
||||||
|
}
|
||||||
41
crates/core/database/src/models/admin_strikes/ops/mongodb.rs
Normal file
41
crates/core/database/src/models/admin_strikes/ops/mongodb.rs
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
use revolt_result::Result;
|
||||||
|
|
||||||
|
use crate::MongoDb;
|
||||||
|
use crate::{AdminStrike, PartialAdminStrike};
|
||||||
|
|
||||||
|
use super::AbstractAdminStrikes;
|
||||||
|
|
||||||
|
static COL: &str = "admin_strikes";
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl AbstractAdminStrikes for MongoDb {
|
||||||
|
async fn admin_strike_insert(&self, strike: AdminStrike) -> Result<()> {
|
||||||
|
query!(self, insert_one, COL, strike).map(|_| ())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn admin_strike_update(
|
||||||
|
&self,
|
||||||
|
strike_id: &str,
|
||||||
|
partial: PartialAdminStrike,
|
||||||
|
) -> Result<()> {
|
||||||
|
query!(
|
||||||
|
self,
|
||||||
|
update_one_by_id,
|
||||||
|
COL,
|
||||||
|
strike_id,
|
||||||
|
partial,
|
||||||
|
vec![],
|
||||||
|
None
|
||||||
|
)
|
||||||
|
.map(|_| ())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn admin_strike_get(&self, strike_id: &str) -> Result<AdminStrike> {
|
||||||
|
query!(self, find_one_by_id, COL, strike_id)?
|
||||||
|
.ok_or_else(|| create_database_error!("find_one", COL))?
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn admin_strike_get_user(&self, user_id: &str) -> Result<Vec<AdminStrike>> {
|
||||||
|
query!(self, find, COL, doc! {"target_id": user_id})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
use revolt_result::Result;
|
||||||
|
|
||||||
|
use crate::ReferenceDb;
|
||||||
|
use crate::{AdminStrike, PartialAdminStrike};
|
||||||
|
|
||||||
|
use super::AbstractAdminStrikes;
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl AbstractAdminStrikes for ReferenceDb {
|
||||||
|
async fn admin_strike_insert(&self, strike: AdminStrike) -> Result<()> {
|
||||||
|
let mut admin_strikes = self.admin_strikes.lock().await;
|
||||||
|
if admin_strikes.contains_key(&strike.id) {
|
||||||
|
Err(create_database_error!("insert", "admin_strikes"))
|
||||||
|
} else {
|
||||||
|
admin_strikes.insert(strike.id.to_string(), strike.clone());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn admin_strike_update(
|
||||||
|
&self,
|
||||||
|
strike_id: &str,
|
||||||
|
partial: PartialAdminStrike,
|
||||||
|
) -> Result<()> {
|
||||||
|
let mut admin_strikes = self.admin_strikes.lock().await;
|
||||||
|
if let Some(strike) = admin_strikes.get_mut(strike_id) {
|
||||||
|
strike.apply_options(partial);
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(create_error!(NotFound))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn admin_strike_get(&self, strike_id: &str) -> Result<AdminStrike> {
|
||||||
|
let admin_strikes = self.admin_strikes.lock().await;
|
||||||
|
if let Some(strike) = admin_strikes.get(strike_id) {
|
||||||
|
Ok(strike.clone())
|
||||||
|
} else {
|
||||||
|
Err(create_error!(NotFound))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn admin_strike_get_user(&self, user_id: &str) -> Result<Vec<AdminStrike>> {
|
||||||
|
let admin_strikes = self.admin_strikes.lock().await;
|
||||||
|
Ok(admin_strikes
|
||||||
|
.iter()
|
||||||
|
.filter(|(_, strike)| strike.target_id == user_id)
|
||||||
|
.map(|(_, strike)| strike.clone())
|
||||||
|
.collect())
|
||||||
|
}
|
||||||
|
}
|
||||||
24
crates/core/database/src/models/admin_tokens/axum.rs
Normal file
24
crates/core/database/src/models/admin_tokens/axum.rs
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
use axum::{extract::FromRequestParts, http::request::Parts};
|
||||||
|
|
||||||
|
use revolt_result::{create_error, Error, Result};
|
||||||
|
|
||||||
|
use crate::{AdminMachineToken, Database};
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl FromRequestParts<Database> for AdminMachineToken {
|
||||||
|
type Rejection = Error;
|
||||||
|
|
||||||
|
async fn from_request_parts(parts: &mut Parts, _db: &Database) -> Result<AdminMachineToken> {
|
||||||
|
if let Some(Ok(token)) = parts.headers.get("x-admin-machine").map(|v| v.to_str()) {
|
||||||
|
let config = revolt_config::config().await;
|
||||||
|
let token = token.to_string();
|
||||||
|
if config.api.security.admin_keys.contains(&token) {
|
||||||
|
Ok(AdminMachineToken::new())
|
||||||
|
} else {
|
||||||
|
Err(create_error!(InvalidCredentials))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Err(create_error!(NotAuthenticated))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
13
crates/core/database/src/models/admin_tokens/mod.rs
Normal file
13
crates/core/database/src/models/admin_tokens/mod.rs
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
#[cfg(feature = "axum-impl")]
|
||||||
|
mod axum;
|
||||||
|
mod models;
|
||||||
|
mod ops;
|
||||||
|
#[cfg(feature = "rocket-impl")]
|
||||||
|
mod rocket;
|
||||||
|
|
||||||
|
#[cfg(feature = "axum-impl")]
|
||||||
|
pub use self::axum::*;
|
||||||
|
#[cfg(feature = "rocket-impl")]
|
||||||
|
pub use self::rocket::*;
|
||||||
|
pub use models::*;
|
||||||
|
pub use ops::*;
|
||||||
31
crates/core/database/src/models/admin_tokens/models.rs
Normal file
31
crates/core/database/src/models/admin_tokens/models.rs
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
auto_derived! {
|
||||||
|
pub struct AdminToken {
|
||||||
|
/// The token ID
|
||||||
|
#[serde(rename = "_id")]
|
||||||
|
pub id: String,
|
||||||
|
/// The user this token is attached to
|
||||||
|
pub user_id: String,
|
||||||
|
/// The token itself
|
||||||
|
pub token: String,
|
||||||
|
/// The expiry timestamp for this token, in iso6801
|
||||||
|
pub expiry: String
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This struct is used to validate machine tokens when doing machine to machine communication.
|
||||||
|
pub struct AdminMachineToken {
|
||||||
|
/// Placeholder field.
|
||||||
|
pub valid: bool
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AdminMachineToken {
|
||||||
|
pub fn new() -> AdminMachineToken {
|
||||||
|
AdminMachineToken { valid: true }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for AdminMachineToken {
|
||||||
|
fn default() -> Self {
|
||||||
|
AdminMachineToken::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
14
crates/core/database/src/models/admin_tokens/ops.rs
Normal file
14
crates/core/database/src/models/admin_tokens/ops.rs
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
mod mongodb;
|
||||||
|
mod reference;
|
||||||
|
use revolt_result::Result;
|
||||||
|
|
||||||
|
use crate::models::admin_tokens::AdminToken;
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
pub trait AbstractAdminTokens: Sync + Send {
|
||||||
|
async fn admin_token_create(&self, token: AdminToken) -> Result<()>;
|
||||||
|
|
||||||
|
async fn admin_token_revoke(&self, token_id: &str) -> Result<()>;
|
||||||
|
|
||||||
|
async fn admin_token_authenticate(&self, token: &str) -> Result<AdminToken>;
|
||||||
|
}
|
||||||
30
crates/core/database/src/models/admin_tokens/ops/mongodb.rs
Normal file
30
crates/core/database/src/models/admin_tokens/ops/mongodb.rs
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
use revolt_result::Result;
|
||||||
|
|
||||||
|
use crate::AdminToken;
|
||||||
|
use crate::MongoDb;
|
||||||
|
|
||||||
|
use super::AbstractAdminTokens;
|
||||||
|
|
||||||
|
static COL: &str = "admin_tokens";
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl AbstractAdminTokens for MongoDb {
|
||||||
|
async fn admin_token_create(&self, token: AdminToken) -> Result<()> {
|
||||||
|
query!(self, insert_one, COL, token).map(|_| ())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn admin_token_revoke(&self, token_id: &str) -> Result<()> {
|
||||||
|
query!(self, delete_one_by_id, COL, token_id).map(|k| {
|
||||||
|
if k.deleted_count > 0 {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(create_error!(NotFound))
|
||||||
|
}
|
||||||
|
})?
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn admin_token_authenticate(&self, token: &str) -> Result<AdminToken> {
|
||||||
|
query!(self, find_one, COL, doc! {"token": token})?
|
||||||
|
.ok_or_else(|| create_error!(InvalidCredentials))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
use revolt_result::Result;
|
||||||
|
|
||||||
|
use crate::AdminToken;
|
||||||
|
use crate::ReferenceDb;
|
||||||
|
|
||||||
|
use super::AbstractAdminTokens;
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl AbstractAdminTokens for ReferenceDb {
|
||||||
|
async fn admin_token_create(&self, token: AdminToken) -> Result<()> {
|
||||||
|
let mut admin_tokens = self.admin_tokens.lock().await;
|
||||||
|
if admin_tokens.contains_key(&token.id) {
|
||||||
|
Err(create_database_error!("insert", "admin_tokens"))
|
||||||
|
} else {
|
||||||
|
admin_tokens.insert(token.id.to_string(), token.clone());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn admin_token_revoke(&self, token_id: &str) -> Result<()> {
|
||||||
|
let mut admin_tokens = self.admin_tokens.lock().await;
|
||||||
|
if admin_tokens.remove(token_id).is_some() {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(create_error!(NotFound))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn admin_token_authenticate(&self, token: &str) -> Result<AdminToken> {
|
||||||
|
let admin_tokens = self.admin_tokens.lock().await;
|
||||||
|
let result = admin_tokens
|
||||||
|
.iter()
|
||||||
|
.filter_map(|(_, t)| {
|
||||||
|
if t.token == token {
|
||||||
|
Some(t.clone())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.next();
|
||||||
|
|
||||||
|
Ok(result.ok_or_else(|| create_error!(NotFound))?)
|
||||||
|
}
|
||||||
|
}
|
||||||
37
crates/core/database/src/models/admin_tokens/rocket.rs
Normal file
37
crates/core/database/src/models/admin_tokens/rocket.rs
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
use rocket::http::Status;
|
||||||
|
use rocket::request::{self, FromRequest, Outcome, Request};
|
||||||
|
|
||||||
|
use crate::AdminMachineToken;
|
||||||
|
|
||||||
|
#[rocket::async_trait]
|
||||||
|
impl<'r> FromRequest<'r> for AdminMachineToken {
|
||||||
|
type Error = authifier::Error;
|
||||||
|
|
||||||
|
async fn from_request(request: &'r Request<'_>) -> request::Outcome<Self, Self::Error> {
|
||||||
|
let user: &Option<AdminMachineToken> = request
|
||||||
|
.local_cache_async(async {
|
||||||
|
let config = revolt_config::config().await;
|
||||||
|
|
||||||
|
let token = request
|
||||||
|
.headers()
|
||||||
|
.get("x-admin-machine")
|
||||||
|
.next()
|
||||||
|
.map(|x| x.to_string());
|
||||||
|
|
||||||
|
if let Some(token) = token {
|
||||||
|
if config.api.security.admin_keys.contains(&token) {
|
||||||
|
return Some(AdminMachineToken::new());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
|
||||||
|
if let Some(user) = user {
|
||||||
|
Outcome::Success(user.clone())
|
||||||
|
} else {
|
||||||
|
Outcome::Error((Status::Unauthorized, authifier::Error::InvalidSession))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
19
crates/core/database/src/models/admin_users/axum.rs
Normal file
19
crates/core/database/src/models/admin_users/axum.rs
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
use axum::{extract::FromRequestParts, http::request::Parts};
|
||||||
|
|
||||||
|
use revolt_result::{create_error, Error, Result};
|
||||||
|
|
||||||
|
use crate::{AdminUser, Database};
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl FromRequestParts<Database> for AdminUser {
|
||||||
|
type Rejection = Error;
|
||||||
|
|
||||||
|
async fn from_request_parts(parts: &mut Parts, db: &Database) -> Result<AdminUser> {
|
||||||
|
if let Some(Ok(token)) = parts.headers.get("x-admin-user").map(|v| v.to_str()) {
|
||||||
|
let session = db.admin_token_authenticate(token).await?;
|
||||||
|
db.admin_user_fetch(&session.user_id).await
|
||||||
|
} else {
|
||||||
|
Err(create_error!(NotAuthenticated))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
13
crates/core/database/src/models/admin_users/mod.rs
Normal file
13
crates/core/database/src/models/admin_users/mod.rs
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
#[cfg(feature = "axum-impl")]
|
||||||
|
mod axum;
|
||||||
|
mod models;
|
||||||
|
mod ops;
|
||||||
|
#[cfg(feature = "rocket-impl")]
|
||||||
|
mod rocket;
|
||||||
|
|
||||||
|
#[cfg(feature = "axum-impl")]
|
||||||
|
pub use self::axum::*;
|
||||||
|
#[cfg(feature = "rocket-impl")]
|
||||||
|
pub use self::rocket::*;
|
||||||
|
pub use models::*;
|
||||||
|
pub use ops::*;
|
||||||
14
crates/core/database/src/models/admin_users/models.rs
Normal file
14
crates/core/database/src/models/admin_users/models.rs
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
auto_derived_partial! {
|
||||||
|
pub struct AdminUser {
|
||||||
|
/// The ID of the user
|
||||||
|
#[serde(rename = "_id")]
|
||||||
|
pub id: String,
|
||||||
|
/// The user's email
|
||||||
|
pub email: String,
|
||||||
|
/// Whether the user is active or not (ie. can they use the api)
|
||||||
|
pub active: bool,
|
||||||
|
/// The permissions of the user
|
||||||
|
pub permissions: u64,
|
||||||
|
},
|
||||||
|
"PartialAdminUser"
|
||||||
|
}
|
||||||
16
crates/core/database/src/models/admin_users/ops.rs
Normal file
16
crates/core/database/src/models/admin_users/ops.rs
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
mod mongodb;
|
||||||
|
mod reference;
|
||||||
|
use revolt_result::Result;
|
||||||
|
|
||||||
|
use crate::models::admin_users::models::{AdminUser, PartialAdminUser};
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
pub trait AbstractAdminUsers: Sync + Send {
|
||||||
|
async fn admin_user_insert(&self, user: AdminUser) -> Result<()>;
|
||||||
|
|
||||||
|
async fn admin_user_update(&self, user_id: &str, partial: PartialAdminUser) -> Result<()>;
|
||||||
|
|
||||||
|
async fn admin_user_fetch(&self, user_id: &str) -> Result<AdminUser>;
|
||||||
|
|
||||||
|
async fn admin_user_list(&self) -> Result<Vec<AdminUser>>;
|
||||||
|
}
|
||||||
27
crates/core/database/src/models/admin_users/ops/mongodb.rs
Normal file
27
crates/core/database/src/models/admin_users/ops/mongodb.rs
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
use revolt_result::Result;
|
||||||
|
|
||||||
|
use crate::MongoDb;
|
||||||
|
use crate::{AdminUser, PartialAdminUser};
|
||||||
|
|
||||||
|
use super::AbstractAdminUsers;
|
||||||
|
|
||||||
|
static COL: &str = "admin_users";
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl AbstractAdminUsers for MongoDb {
|
||||||
|
async fn admin_user_insert(&self, user: AdminUser) -> Result<()> {
|
||||||
|
query!(self, insert_one, COL, user).map(|_| ())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn admin_user_update(&self, user_id: &str, partial: PartialAdminUser) -> Result<()> {
|
||||||
|
query!(self, update_one_by_id, COL, user_id, partial, vec![], None).map(|_| ())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn admin_user_fetch(&self, user_id: &str) -> Result<AdminUser> {
|
||||||
|
query!(self, find_one_by_id, COL, user_id)?.ok_or_else(|| create_error!(NotFound))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn admin_user_list(&self) -> Result<Vec<AdminUser>> {
|
||||||
|
query!(self, find, COL, doc! {})
|
||||||
|
}
|
||||||
|
}
|
||||||
43
crates/core/database/src/models/admin_users/ops/reference.rs
Normal file
43
crates/core/database/src/models/admin_users/ops/reference.rs
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
use revolt_result::Result;
|
||||||
|
|
||||||
|
use crate::ReferenceDb;
|
||||||
|
use crate::{AdminUser, PartialAdminUser};
|
||||||
|
|
||||||
|
use super::AbstractAdminUsers;
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl AbstractAdminUsers for ReferenceDb {
|
||||||
|
async fn admin_user_insert(&self, user: AdminUser) -> Result<()> {
|
||||||
|
let mut admin_users = self.admin_users.lock().await;
|
||||||
|
if admin_users.contains_key(&user.id) {
|
||||||
|
Err(create_database_error!("insert", "admin_users"))
|
||||||
|
} else {
|
||||||
|
admin_users.insert(user.id.to_string(), user.clone());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn admin_user_update(&self, user_id: &str, partial: PartialAdminUser) -> Result<()> {
|
||||||
|
let mut admin_users = self.admin_users.lock().await;
|
||||||
|
if let Some(existing_user) = admin_users.get_mut(user_id) {
|
||||||
|
existing_user.apply_options(partial);
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(create_error!(NotFound))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn admin_user_fetch(&self, user_id: &str) -> Result<AdminUser> {
|
||||||
|
let admin_users = self.admin_users.lock().await;
|
||||||
|
if let Some(user) = admin_users.get(user_id) {
|
||||||
|
Ok(user.clone())
|
||||||
|
} else {
|
||||||
|
Err(create_error!(NotFound))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn admin_user_list(&self) -> Result<Vec<AdminUser>> {
|
||||||
|
let admin_users = self.admin_users.lock().await;
|
||||||
|
Ok(admin_users.iter().map(|(_, u)| u).cloned().collect())
|
||||||
|
}
|
||||||
|
}
|
||||||
39
crates/core/database/src/models/admin_users/rocket.rs
Normal file
39
crates/core/database/src/models/admin_users/rocket.rs
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
use rocket::http::Status;
|
||||||
|
use rocket::request::{self, FromRequest, Outcome, Request};
|
||||||
|
|
||||||
|
use crate::{AdminUser, Database};
|
||||||
|
|
||||||
|
#[rocket::async_trait]
|
||||||
|
impl<'r> FromRequest<'r> for AdminUser {
|
||||||
|
type Error = authifier::Error;
|
||||||
|
|
||||||
|
async fn from_request(request: &'r Request<'_>) -> request::Outcome<Self, Self::Error> {
|
||||||
|
let user: &Option<AdminUser> = request
|
||||||
|
.local_cache_async(async {
|
||||||
|
let db = request.rocket().state::<Database>().expect("`Database`");
|
||||||
|
|
||||||
|
let token = request
|
||||||
|
.headers()
|
||||||
|
.get("x-admin-user")
|
||||||
|
.next()
|
||||||
|
.map(|x| x.to_string());
|
||||||
|
|
||||||
|
if let Some(token) = token {
|
||||||
|
if let Ok(admin_token) = db.admin_token_authenticate(&token).await {
|
||||||
|
if let Ok(user) = db.admin_user_fetch(&admin_token.user_id).await {
|
||||||
|
return Some(user);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
|
||||||
|
if let Some(user) = user {
|
||||||
|
Outcome::Success(user.clone())
|
||||||
|
} else {
|
||||||
|
Outcome::Error((Status::Unauthorized, authifier::Error::InvalidSession))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,11 @@
|
|||||||
|
mod admin_audits;
|
||||||
|
mod admin_case_comments;
|
||||||
|
mod admin_cases;
|
||||||
mod admin_migrations;
|
mod admin_migrations;
|
||||||
|
mod admin_notes;
|
||||||
|
mod admin_strikes;
|
||||||
|
mod admin_tokens;
|
||||||
|
mod admin_users;
|
||||||
mod bots;
|
mod bots;
|
||||||
mod channel_invites;
|
mod channel_invites;
|
||||||
mod channel_unreads;
|
mod channel_unreads;
|
||||||
@@ -18,7 +25,14 @@ mod servers;
|
|||||||
mod user_settings;
|
mod user_settings;
|
||||||
mod users;
|
mod users;
|
||||||
|
|
||||||
|
pub use admin_audits::*;
|
||||||
|
pub use admin_case_comments::*;
|
||||||
|
pub use admin_cases::*;
|
||||||
pub use admin_migrations::*;
|
pub use admin_migrations::*;
|
||||||
|
pub use admin_notes::*;
|
||||||
|
pub use admin_strikes::*;
|
||||||
|
pub use admin_tokens::*;
|
||||||
|
pub use admin_users::*;
|
||||||
pub use bots::*;
|
pub use bots::*;
|
||||||
pub use channel_invites::*;
|
pub use channel_invites::*;
|
||||||
pub use channel_unreads::*;
|
pub use channel_unreads::*;
|
||||||
@@ -46,6 +60,13 @@ use crate::MongoDb;
|
|||||||
pub trait AbstractDatabase:
|
pub trait AbstractDatabase:
|
||||||
Sync
|
Sync
|
||||||
+ Send
|
+ Send
|
||||||
|
+ admin_audits::AbstractAdminAudits
|
||||||
|
+ admin_case_comments::AbstractAdminCaseComments
|
||||||
|
+ admin_cases::AbstractAdminCases
|
||||||
|
+ admin_notes::AbstractAdminNotes
|
||||||
|
+ admin_strikes::AbstractAdminStrikes
|
||||||
|
+ admin_users::AbstractAdminUsers
|
||||||
|
+ admin_tokens::AbstractAdminTokens
|
||||||
+ admin_migrations::AbstractMigrations
|
+ admin_migrations::AbstractMigrations
|
||||||
+ bots::AbstractBots
|
+ bots::AbstractBots
|
||||||
+ channels::AbstractChannels
|
+ channels::AbstractChannels
|
||||||
|
|||||||
@@ -18,5 +18,7 @@ auto_derived!(
|
|||||||
/// Additional notes included on the report
|
/// Additional notes included on the report
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub notes: String,
|
pub notes: String,
|
||||||
|
/// The case this report is assigned to
|
||||||
|
pub case_id: Option<String>,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -825,3 +825,27 @@ impl User {
|
|||||||
badges
|
badges
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct UserFlagsValue(pub u32);
|
||||||
|
|
||||||
|
impl UserFlagsValue {
|
||||||
|
pub fn has(&self, flag: UserFlags) -> bool {
|
||||||
|
self.has_value(flag as u32)
|
||||||
|
}
|
||||||
|
pub fn has_value(&self, bit: u32) -> bool {
|
||||||
|
let mask = 1 << bit;
|
||||||
|
self.0 & mask == mask
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set(&mut self, flag: UserFlags, toggle: bool) -> &mut Self {
|
||||||
|
self.set_value(flag as u32, toggle)
|
||||||
|
}
|
||||||
|
pub fn set_value(&mut self, bit: u32, toggle: bool) -> &mut Self {
|
||||||
|
if toggle {
|
||||||
|
self.0 |= 1 << bit;
|
||||||
|
} else {
|
||||||
|
self.0 &= !(1 << bit);
|
||||||
|
}
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -84,6 +84,8 @@ impl IntoResponse for Error {
|
|||||||
ErrorType::FileTypeNotAllowed => StatusCode::BAD_REQUEST,
|
ErrorType::FileTypeNotAllowed => StatusCode::BAD_REQUEST,
|
||||||
ErrorType::ImageProcessingFailed => StatusCode::INTERNAL_SERVER_ERROR,
|
ErrorType::ImageProcessingFailed => StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
ErrorType::NoEmbedData => StatusCode::BAD_REQUEST,
|
ErrorType::NoEmbedData => StatusCode::BAD_REQUEST,
|
||||||
|
|
||||||
|
ErrorType::ImATeaPot => StatusCode::IM_A_TEAPOT,
|
||||||
};
|
};
|
||||||
|
|
||||||
(status, Json(&self)).into_response()
|
(status, Json(&self)).into_response()
|
||||||
|
|||||||
@@ -175,6 +175,9 @@ pub enum ErrorType {
|
|||||||
FeatureDisabled {
|
FeatureDisabled {
|
||||||
feature: String,
|
feature: String,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// :)
|
||||||
|
ImATeaPot,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
|
|||||||
@@ -90,6 +90,8 @@ impl<'r> Responder<'r, 'static> for Error {
|
|||||||
ErrorType::FileTypeNotAllowed => Status::BadRequest,
|
ErrorType::FileTypeNotAllowed => Status::BadRequest,
|
||||||
ErrorType::ImageProcessingFailed => Status::InternalServerError,
|
ErrorType::ImageProcessingFailed => Status::InternalServerError,
|
||||||
ErrorType::NoEmbedData => Status::BadRequest,
|
ErrorType::NoEmbedData => Status::BadRequest,
|
||||||
|
|
||||||
|
ErrorType::ImATeaPot => Status::ImATeapot,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Serialize the error data structure into JSON.
|
// Serialize the error data structure into JSON.
|
||||||
|
|||||||
Reference in New Issue
Block a user