feat(core/database): add account strikes model
This commit is contained in:
@@ -2,12 +2,13 @@ use std::{collections::HashMap, sync::Arc};
|
|||||||
|
|
||||||
use futures::lock::Mutex;
|
use futures::lock::Mutex;
|
||||||
|
|
||||||
use crate::{Bot, File, Member, MemberCompositeKey, Server, User, UserSettings};
|
use crate::{AccountStrike, Bot, File, Member, MemberCompositeKey, Server, User, UserSettings};
|
||||||
|
|
||||||
database_derived!(
|
database_derived!(
|
||||||
/// Reference implementation
|
/// Reference implementation
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct ReferenceDb {
|
pub struct ReferenceDb {
|
||||||
|
pub account_strikes: Arc<Mutex<HashMap<String, AccountStrike>>>,
|
||||||
pub bots: Arc<Mutex<HashMap<String, Bot>>>,
|
pub bots: Arc<Mutex<HashMap<String, Bot>>>,
|
||||||
pub user_settings: Arc<Mutex<HashMap<String, UserSettings>>>,
|
pub user_settings: Arc<Mutex<HashMap<String, UserSettings>>>,
|
||||||
pub users: Arc<Mutex<HashMap<String, User>>>,
|
pub users: Arc<Mutex<HashMap<String, User>>>,
|
||||||
|
|||||||
@@ -64,6 +64,10 @@ pub async fn create_database(db: &MongoDb) {
|
|||||||
.await
|
.await
|
||||||
.expect("Failed to create safety_snapshots collection.");
|
.expect("Failed to create safety_snapshots collection.");
|
||||||
|
|
||||||
|
db.create_collection("safety_strikes", None)
|
||||||
|
.await
|
||||||
|
.expect("Failed to create safety_strikes collection.");
|
||||||
|
|
||||||
db.create_collection("bots", None)
|
db.create_collection("bots", None)
|
||||||
.await
|
.await
|
||||||
.expect("Failed to create bots collection.");
|
.expect("Failed to create bots collection.");
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ struct MigrationInfo {
|
|||||||
revision: i32,
|
revision: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const LATEST_REVISION: i32 = 21;
|
pub const LATEST_REVISION: i32 = 22;
|
||||||
|
|
||||||
pub async fn migrate_database(db: &MongoDb) {
|
pub async fn migrate_database(db: &MongoDb) {
|
||||||
let migrations = db.col::<Document>("migrations");
|
let migrations = db.col::<Document>("migrations");
|
||||||
@@ -696,26 +696,15 @@ pub async fn run_migrations(db: &MongoDb, revision: i32) -> i32 {
|
|||||||
if revision <= 19 {
|
if revision <= 19 {
|
||||||
info!("Running migration [revision 19 / 27-02-2023]: Create report / snapshot collections, migrate to new model if applicable.");
|
info!("Running migration [revision 19 / 27-02-2023]: Create report / snapshot collections, migrate to new model if applicable.");
|
||||||
|
|
||||||
// TODO: make these fail once production is migrated
|
db.db()
|
||||||
if db
|
|
||||||
.db()
|
|
||||||
.create_collection("safety_reports", None)
|
.create_collection("safety_reports", None)
|
||||||
.await
|
.await
|
||||||
.is_err()
|
.unwrap();
|
||||||
{
|
|
||||||
info!("Failed to create safety_reports collection but this is expected in production.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if db
|
db.db()
|
||||||
.db()
|
|
||||||
.create_collection("safety_snapshots", None)
|
.create_collection("safety_snapshots", None)
|
||||||
.await
|
.await
|
||||||
.is_err()
|
.unwrap();
|
||||||
{
|
|
||||||
info!(
|
|
||||||
"Failed to create safety_snapshots collection but this is expected in production."
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
db.col::<Document>("safety_reports")
|
db.col::<Document>("safety_reports")
|
||||||
.update_many(
|
.update_many(
|
||||||
@@ -753,6 +742,15 @@ pub async fn run_migrations(db: &MongoDb, revision: i32) -> i32 {
|
|||||||
.expect("Failed to create safety snapshot index.");
|
.expect("Failed to create safety snapshot index.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if revision <= 21 {
|
||||||
|
info!("Running migration [revision 21 / 31-05-2023]: Add collection `safety_strikes`.");
|
||||||
|
|
||||||
|
db.db()
|
||||||
|
.create_collection("safety_strikes", None)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
// Need to migrate fields on attachments, change `user_id`, `object_id`, etc to `parent`.
|
// Need to migrate fields on attachments, change `user_id`, `object_id`, etc to `parent`.
|
||||||
|
|
||||||
// Reminder to update LATEST_REVISION when adding new migrations.
|
// Reminder to update LATEST_REVISION when adding new migrations.
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
mod admin_migrations;
|
mod admin_migrations;
|
||||||
mod bots;
|
mod bots;
|
||||||
mod files;
|
mod files;
|
||||||
|
mod safety_strikes;
|
||||||
mod server_members;
|
mod server_members;
|
||||||
mod servers;
|
mod servers;
|
||||||
mod user_settings;
|
mod user_settings;
|
||||||
@@ -9,6 +10,7 @@ mod users;
|
|||||||
pub use admin_migrations::*;
|
pub use admin_migrations::*;
|
||||||
pub use bots::*;
|
pub use bots::*;
|
||||||
pub use files::*;
|
pub use files::*;
|
||||||
|
pub use safety_strikes::*;
|
||||||
pub use server_members::*;
|
pub use server_members::*;
|
||||||
pub use servers::*;
|
pub use servers::*;
|
||||||
pub use user_settings::*;
|
pub use user_settings::*;
|
||||||
@@ -22,6 +24,7 @@ pub trait AbstractDatabase:
|
|||||||
+ admin_migrations::AbstractMigrations
|
+ admin_migrations::AbstractMigrations
|
||||||
+ bots::AbstractBots
|
+ bots::AbstractBots
|
||||||
+ files::AbstractAttachments
|
+ files::AbstractAttachments
|
||||||
|
+ safety_strikes::AbstractAccountStrikes
|
||||||
+ server_members::AbstractServerMembers
|
+ server_members::AbstractServerMembers
|
||||||
+ servers::AbstractServers
|
+ servers::AbstractServers
|
||||||
+ user_settings::AbstractUserSettings
|
+ user_settings::AbstractUserSettings
|
||||||
|
|||||||
5
crates/core/database/src/models/safety_strikes/mod.rs
Normal file
5
crates/core/database/src/models/safety_strikes/mod.rs
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
mod model;
|
||||||
|
mod ops;
|
||||||
|
|
||||||
|
pub use model::*;
|
||||||
|
pub use ops::*;
|
||||||
104
crates/core/database/src/models/safety_strikes/model.rs
Normal file
104
crates/core/database/src/models/safety_strikes/model.rs
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
use revolt_result::Result;
|
||||||
|
|
||||||
|
use crate::Database;
|
||||||
|
|
||||||
|
auto_derived_partial!(
|
||||||
|
/// Account Strike
|
||||||
|
pub struct AccountStrike {
|
||||||
|
/// Strike Id
|
||||||
|
#[serde(rename = "_id")]
|
||||||
|
pub id: String,
|
||||||
|
/// User Id of reported user
|
||||||
|
pub user_id: String,
|
||||||
|
|
||||||
|
/// Attached reason
|
||||||
|
pub reason: String,
|
||||||
|
},
|
||||||
|
"PartialAccountStrike"
|
||||||
|
);
|
||||||
|
|
||||||
|
impl AccountStrike {
|
||||||
|
/// Update this strike
|
||||||
|
pub async fn update(&mut self, db: &Database, partial: PartialAccountStrike) -> Result<()> {
|
||||||
|
db.update_account_strike(&self.id, &partial).await?;
|
||||||
|
self.apply_options(partial);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Delete this strike
|
||||||
|
pub async fn delete(&self, db: &Database) -> Result<()> {
|
||||||
|
db.delete_account_strike(&self.id).await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use std::collections::HashSet;
|
||||||
|
|
||||||
|
use crate::{AccountStrike, PartialAccountStrike};
|
||||||
|
|
||||||
|
#[async_std::test]
|
||||||
|
async fn crud() {
|
||||||
|
database_test!(|db| async move {
|
||||||
|
let user_id = "user";
|
||||||
|
let strike_a = "a";
|
||||||
|
let strike_b = "b";
|
||||||
|
|
||||||
|
let strike = AccountStrike {
|
||||||
|
id: strike_a.to_string(),
|
||||||
|
user_id: user_id.to_string(),
|
||||||
|
reason: "reason 1".to_string(),
|
||||||
|
};
|
||||||
|
|
||||||
|
db.insert_account_strike(&strike).await.unwrap();
|
||||||
|
|
||||||
|
let mut updated_strike = strike.clone();
|
||||||
|
updated_strike
|
||||||
|
.update(
|
||||||
|
&db,
|
||||||
|
PartialAccountStrike {
|
||||||
|
reason: Some("new reason".to_string()),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
db.insert_account_strike(&AccountStrike {
|
||||||
|
id: strike_b.to_string(),
|
||||||
|
user_id: user_id.to_string(),
|
||||||
|
reason: "reason 2".to_string(),
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let strikes = db.fetch_account_strikes_by_user(user_id).await.unwrap();
|
||||||
|
|
||||||
|
let ids = strikes
|
||||||
|
.iter()
|
||||||
|
.cloned()
|
||||||
|
.map(|strike| strike.id)
|
||||||
|
.collect::<HashSet<String>>();
|
||||||
|
|
||||||
|
assert!(ids.contains(strike_a));
|
||||||
|
assert!(ids.contains(strike_b));
|
||||||
|
|
||||||
|
let fetched_strike = strikes
|
||||||
|
.into_iter()
|
||||||
|
.find(|strike| strike.id == strike_a)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(fetched_strike, updated_strike);
|
||||||
|
assert_ne!(fetched_strike, strike);
|
||||||
|
|
||||||
|
strike.delete(&db).await.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
1,
|
||||||
|
db.fetch_account_strikes_by_user(user_id)
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.len()
|
||||||
|
)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
21
crates/core/database/src/models/safety_strikes/ops.rs
Normal file
21
crates/core/database/src/models/safety_strikes/ops.rs
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
use revolt_result::Result;
|
||||||
|
|
||||||
|
use crate::{AccountStrike, PartialAccountStrike};
|
||||||
|
|
||||||
|
mod mongodb;
|
||||||
|
mod reference;
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
pub trait AbstractAccountStrikes: Sync + Send {
|
||||||
|
/// Insert new strike into the database
|
||||||
|
async fn insert_account_strike(&self, strike: &AccountStrike) -> Result<()>;
|
||||||
|
|
||||||
|
/// Fetch strikes by user id
|
||||||
|
async fn fetch_account_strikes_by_user(&self, user_id: &str) -> Result<Vec<AccountStrike>>;
|
||||||
|
|
||||||
|
/// Update strike with new information
|
||||||
|
async fn update_account_strike(&self, id: &str, partial: &PartialAccountStrike) -> Result<()>;
|
||||||
|
|
||||||
|
/// Delete a strike from the database
|
||||||
|
async fn delete_account_strike(&self, id: &str) -> Result<()>;
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
use futures::StreamExt;
|
||||||
|
use revolt_result::Result;
|
||||||
|
|
||||||
|
use crate::MongoDb;
|
||||||
|
use crate::{AccountStrike, PartialAccountStrike};
|
||||||
|
|
||||||
|
use super::AbstractAccountStrikes;
|
||||||
|
|
||||||
|
static COL: &str = "bots";
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl AbstractAccountStrikes for MongoDb {
|
||||||
|
/// Insert new strike into the database
|
||||||
|
async fn insert_account_strike(&self, strike: &AccountStrike) -> Result<()> {
|
||||||
|
query!(self, insert_one, COL, &strike).map(|_| ())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fetch strikes by user id
|
||||||
|
async fn fetch_account_strikes_by_user(&self, user_id: &str) -> Result<Vec<AccountStrike>> {
|
||||||
|
Ok(self
|
||||||
|
.col::<AccountStrike>(COL)
|
||||||
|
.find(
|
||||||
|
doc! {
|
||||||
|
"user_id": user_id,
|
||||||
|
},
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map_err(|_| create_database_error!("find", COL))?
|
||||||
|
.filter_map(|s| async {
|
||||||
|
if cfg!(debug_assertions) {
|
||||||
|
Some(s.unwrap())
|
||||||
|
} else {
|
||||||
|
s.ok()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
.await)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Update strike with new information
|
||||||
|
async fn update_account_strike(&self, id: &str, partial: &PartialAccountStrike) -> Result<()> {
|
||||||
|
query!(self, update_one_by_id, COL, id, partial, vec![], None).map(|_| ())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Delete a strike from the database
|
||||||
|
async fn delete_account_strike(&self, id: &str) -> Result<()> {
|
||||||
|
query!(self, delete_one_by_id, COL, id).map(|_| ())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
use revolt_result::Result;
|
||||||
|
|
||||||
|
use crate::ReferenceDb;
|
||||||
|
use crate::{AccountStrike, PartialAccountStrike};
|
||||||
|
|
||||||
|
use super::AbstractAccountStrikes;
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl AbstractAccountStrikes for ReferenceDb {
|
||||||
|
/// Insert new strike into the database
|
||||||
|
async fn insert_account_strike(&self, strike: &AccountStrike) -> Result<()> {
|
||||||
|
let mut strikes = self.account_strikes.lock().await;
|
||||||
|
if strikes.contains_key(&strike.id) {
|
||||||
|
Err(create_database_error!("insert", "strike"))
|
||||||
|
} else {
|
||||||
|
strikes.insert(strike.id.to_string(), strike.clone());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fetch strikes by user id
|
||||||
|
async fn fetch_account_strikes_by_user(&self, user_id: &str) -> Result<Vec<AccountStrike>> {
|
||||||
|
let strikes = self.account_strikes.lock().await;
|
||||||
|
Ok(strikes
|
||||||
|
.values()
|
||||||
|
.filter(|strike| strike.user_id == user_id)
|
||||||
|
.cloned()
|
||||||
|
.collect())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Update strike with new information
|
||||||
|
async fn update_account_strike(&self, id: &str, partial: &PartialAccountStrike) -> Result<()> {
|
||||||
|
let mut strikes = self.account_strikes.lock().await;
|
||||||
|
if let Some(strike) = strikes.get_mut(id) {
|
||||||
|
strike.apply_options(partial.clone());
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(create_error!(NotFound))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Delete a strike from the database
|
||||||
|
async fn delete_account_strike(&self, id: &str) -> Result<()> {
|
||||||
|
let mut strikes = self.account_strikes.lock().await;
|
||||||
|
if strikes.remove(id).is_some() {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(create_error!(NotFound))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user