mirror of
https://github.com/stoatchat/stoatchat.git
synced 2026-07-14 13:36:59 +00:00
feat(core/database): implement user operations
This commit is contained in:
@@ -84,7 +84,7 @@ impl Bot {
|
||||
|
||||
/// Delete this bot
|
||||
pub async fn delete(&self, db: &Database) -> Result<()> {
|
||||
// db.fetch_user(&self.id).await?.mark_deleted(db).await?;
|
||||
db.fetch_user(&self.id).await?.mark_deleted(db).await?;
|
||||
db.delete_bot(&self.id).await
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
use crate::File;
|
||||
use crate::{Database, File};
|
||||
|
||||
use revolt_result::{Error, ErrorType, Result};
|
||||
|
||||
auto_derived_partial!(
|
||||
/// # User
|
||||
@@ -96,4 +98,99 @@ auto_derived!(
|
||||
/// Id of the owner of this bot
|
||||
pub owner: String,
|
||||
}
|
||||
|
||||
/// Optional fields on user object
|
||||
pub enum FieldsUser {
|
||||
Avatar,
|
||||
StatusText,
|
||||
StatusPresence,
|
||||
ProfileContent,
|
||||
ProfileBackground,
|
||||
}
|
||||
);
|
||||
|
||||
impl User {
|
||||
/// Check whether a username is already in use by another user
|
||||
async fn is_username_taken(db: &Database, username: &str) -> Result<bool> {
|
||||
match db.fetch_user_by_username(username).await {
|
||||
Ok(_) => Ok(true),
|
||||
Err(Error {
|
||||
error_type: ErrorType::NotFound,
|
||||
..
|
||||
}) => Ok(false),
|
||||
Err(error) => Err(error),
|
||||
}
|
||||
}
|
||||
|
||||
/// Update user data
|
||||
pub async fn update<'a>(
|
||||
&mut self,
|
||||
db: &Database,
|
||||
partial: PartialUser,
|
||||
remove: Vec<FieldsUser>,
|
||||
) -> Result<()> {
|
||||
for field in &remove {
|
||||
self.remove_field(field);
|
||||
}
|
||||
|
||||
self.apply_options(partial.clone());
|
||||
db.update_user(&self.id, &partial, remove.clone()).await?;
|
||||
|
||||
/* // TODO: EventV1::UserUpdate {
|
||||
id: self.id.clone(),
|
||||
data: partial,
|
||||
clear: remove,
|
||||
}
|
||||
.p_user(self.id.clone(), db)
|
||||
.await; */
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Remove a field from User object
|
||||
pub fn remove_field(&mut self, field: &FieldsUser) {
|
||||
match field {
|
||||
FieldsUser::Avatar => self.avatar = None,
|
||||
FieldsUser::StatusText => {
|
||||
if let Some(x) = self.status.as_mut() {
|
||||
x.text = String::new();
|
||||
}
|
||||
}
|
||||
FieldsUser::StatusPresence => {
|
||||
if let Some(x) = self.status.as_mut() {
|
||||
x.presence = None;
|
||||
}
|
||||
}
|
||||
FieldsUser::ProfileContent => {
|
||||
if let Some(x) = self.profile.as_mut() {
|
||||
x.content = String::new();
|
||||
}
|
||||
}
|
||||
FieldsUser::ProfileBackground => {
|
||||
if let Some(x) = self.profile.as_mut() {
|
||||
x.background = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Mark as deleted
|
||||
pub async fn mark_deleted(&mut self, db: &Database) -> Result<()> {
|
||||
self.update(
|
||||
db,
|
||||
PartialUser {
|
||||
username: Some(format!("Deleted User {}", self.id)),
|
||||
flags: Some(2),
|
||||
..Default::default()
|
||||
},
|
||||
vec![
|
||||
FieldsUser::Avatar,
|
||||
FieldsUser::StatusText,
|
||||
FieldsUser::StatusPresence,
|
||||
FieldsUser::ProfileContent,
|
||||
FieldsUser::ProfileBackground,
|
||||
],
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,57 @@
|
||||
use revolt_result::Result;
|
||||
|
||||
use crate::User;
|
||||
use crate::{FieldsUser, PartialUser, RelationshipStatus, User};
|
||||
|
||||
mod mongodb;
|
||||
mod reference;
|
||||
|
||||
#[async_trait]
|
||||
pub trait AbstractUsers: Sync + Send {
|
||||
/// Insert a new user into the database
|
||||
async fn insert_user(&self, user: &User) -> Result<()>;
|
||||
|
||||
/// Fetch a user from the database
|
||||
async fn fetch_user(&self, id: &str) -> Result<User>;
|
||||
|
||||
/// Fetch a user from the database by their username
|
||||
async fn fetch_user_by_username(&self, username: &str) -> Result<User>;
|
||||
|
||||
/// Fetch a user from the database by their session token
|
||||
async fn fetch_user_by_token(&self, token: &str) -> Result<User>;
|
||||
|
||||
/// Fetch multiple users by their ids
|
||||
async fn fetch_users<'a>(&self, ids: &'a [String]) -> Result<Vec<User>>;
|
||||
|
||||
/// Fetch ids of users that both users are friends with
|
||||
async fn fetch_mutual_user_ids(&self, user_a: &str, user_b: &str) -> Result<Vec<String>>;
|
||||
|
||||
/// Fetch ids of channels that both users are in
|
||||
async fn fetch_mutual_channel_ids(&self, user_a: &str, user_b: &str) -> Result<Vec<String>>;
|
||||
|
||||
/// Fetch ids of servers that both users share
|
||||
async fn fetch_mutual_server_ids(&self, user_a: &str, user_b: &str) -> Result<Vec<String>>;
|
||||
|
||||
/// Update a user by their id given some data
|
||||
async fn update_user(
|
||||
&self,
|
||||
id: &str,
|
||||
user: &PartialUser,
|
||||
remove: Vec<FieldsUser>,
|
||||
) -> Result<()>;
|
||||
|
||||
/// Set relationship with another user
|
||||
///
|
||||
/// This should use pull_relationship if relationship is None.
|
||||
async fn set_relationship(
|
||||
&self,
|
||||
user_id: &str,
|
||||
target_id: &str,
|
||||
relationship: &RelationshipStatus,
|
||||
) -> Result<()>;
|
||||
|
||||
/// Remove relationship with another user
|
||||
async fn pull_relationship(&self, user_id: &str, target_id: &str) -> Result<()>;
|
||||
|
||||
/// Delete a user by their id
|
||||
async fn delete_user(&self, id: &str) -> Result<()>;
|
||||
}
|
||||
|
||||
@@ -1,16 +1,300 @@
|
||||
use ::mongodb::options::{Collation, CollationStrength, FindOneOptions, FindOptions};
|
||||
use authifier::models::Session;
|
||||
use futures::StreamExt;
|
||||
use revolt_result::Result;
|
||||
|
||||
use crate::DocumentId;
|
||||
use crate::IntoDocumentPath;
|
||||
use crate::MongoDb;
|
||||
use crate::User;
|
||||
use crate::{FieldsUser, PartialUser, RelationshipStatus, User};
|
||||
|
||||
use super::AbstractUsers;
|
||||
|
||||
static COL: &str = "bots";
|
||||
static COL: &str = "users";
|
||||
|
||||
#[async_trait]
|
||||
impl AbstractUsers for MongoDb {
|
||||
/// Insert a new user into the database
|
||||
async fn insert_user(&self, user: &User) -> Result<()> {
|
||||
query!(self, insert_one, COL, &user).map(|_| ())
|
||||
}
|
||||
|
||||
/// Fetch a user from the database
|
||||
async fn fetch_user(&self, id: &str) -> Result<User> {
|
||||
query!(self, find_one_by_id, COL, id)?.ok_or_else(|| create_error!(NotFound))
|
||||
}
|
||||
|
||||
/// Fetch a user from the database by their username
|
||||
async fn fetch_user_by_username(&self, username: &str) -> Result<User> {
|
||||
query!(
|
||||
self,
|
||||
find_one_with_options,
|
||||
COL,
|
||||
doc! {
|
||||
"username": username
|
||||
},
|
||||
FindOneOptions::builder()
|
||||
.collation(
|
||||
Collation::builder()
|
||||
.locale("en")
|
||||
.strength(CollationStrength::Secondary)
|
||||
.build(),
|
||||
)
|
||||
.build()
|
||||
)?
|
||||
.ok_or_else(|| create_error!(NotFound))
|
||||
}
|
||||
|
||||
/// Fetch a user from the database by their session token
|
||||
async fn fetch_user_by_token(&self, token: &str) -> Result<User> {
|
||||
let session = self
|
||||
.col::<Session>("sessions")
|
||||
.find_one(
|
||||
doc! {
|
||||
"token": token
|
||||
},
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.map_err(|_| create_database_error!("find_one", "sessions"))?
|
||||
.ok_or_else(|| create_error!(InvalidSession))?;
|
||||
|
||||
self.fetch_user(&session.id).await
|
||||
}
|
||||
|
||||
/// Fetch multiple users by their ids
|
||||
async fn fetch_users<'a>(&self, ids: &'a [String]) -> Result<Vec<User>> {
|
||||
Ok(self
|
||||
.col::<User>(COL)
|
||||
.find(
|
||||
doc! {
|
||||
"_id": {
|
||||
"$in": ids
|
||||
}
|
||||
},
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.map_err(|_| create_database_error!("find", "user"))?
|
||||
.filter_map(|s| async {
|
||||
if cfg!(debug_assertions) {
|
||||
Some(s.unwrap())
|
||||
} else {
|
||||
s.ok()
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
.await)
|
||||
}
|
||||
|
||||
/// Fetch ids of users that both users are friends with
|
||||
async fn fetch_mutual_user_ids(&self, user_a: &str, user_b: &str) -> Result<Vec<String>> {
|
||||
Ok(self
|
||||
.col::<DocumentId>(COL)
|
||||
.find(
|
||||
doc! {
|
||||
"$and": [
|
||||
{ "relations": { "$elemMatch": { "_id": &user_a, "status": "Friend" } } },
|
||||
{ "relations": { "$elemMatch": { "_id": &user_b, "status": "Friend" } } }
|
||||
]
|
||||
},
|
||||
FindOptions::builder().projection(doc! { "_id": 1 }).build(),
|
||||
)
|
||||
.await
|
||||
.map_err(|_| create_database_error!("find", "users"))?
|
||||
.filter_map(|s| async { s.ok() })
|
||||
.map(|user| user.id)
|
||||
.collect()
|
||||
.await)
|
||||
}
|
||||
|
||||
/// Fetch ids of channels that both users are in
|
||||
async fn fetch_mutual_channel_ids(&self, user_a: &str, user_b: &str) -> Result<Vec<String>> {
|
||||
Ok(self
|
||||
.col::<DocumentId>("channels")
|
||||
.find(
|
||||
doc! {
|
||||
"channel_type": {
|
||||
"$in": ["Group", "DirectMessage"]
|
||||
},
|
||||
"recipients": {
|
||||
"$all": [ user_a, user_b ]
|
||||
}
|
||||
},
|
||||
FindOptions::builder().projection(doc! { "_id": 1 }).build(),
|
||||
)
|
||||
.await
|
||||
.map_err(|_| create_database_error!("find", "channels"))?
|
||||
.filter_map(|s| async { s.ok() })
|
||||
.map(|user| user.id)
|
||||
.collect()
|
||||
.await)
|
||||
}
|
||||
|
||||
/// Fetch ids of servers that both users share
|
||||
async fn fetch_mutual_server_ids(&self, user_a: &str, user_b: &str) -> Result<Vec<String>> {
|
||||
Ok(self
|
||||
.col::<DocumentId>("server_members")
|
||||
.aggregate(
|
||||
vec![
|
||||
doc! {
|
||||
"$match": {
|
||||
"_id.user": user_a
|
||||
}
|
||||
},
|
||||
doc! {
|
||||
"$lookup": {
|
||||
"from": "server_members",
|
||||
"as": "members",
|
||||
"let": {
|
||||
"server": "$_id.server"
|
||||
},
|
||||
"pipeline": [
|
||||
{
|
||||
"$match": {
|
||||
"$expr": {
|
||||
"$and": [
|
||||
{ "$eq": [ "$_id.user", user_b ] },
|
||||
{ "$eq": [ "$_id.server", "$$server" ] }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
doc! {
|
||||
"$match": {
|
||||
"members": {
|
||||
"$size": 1_i32
|
||||
}
|
||||
}
|
||||
},
|
||||
doc! {
|
||||
"$project": {
|
||||
"_id": "$_id.server"
|
||||
}
|
||||
},
|
||||
],
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.map_err(|_| create_database_error!("aggregate", "server_members"))?
|
||||
.filter_map(|s| async { s.ok() })
|
||||
.filter_map(|doc| async move { doc.get_str("_id").map(|id| id.to_string()).ok() })
|
||||
.collect()
|
||||
.await)
|
||||
}
|
||||
|
||||
/// Update a user by their id given some data
|
||||
async fn update_user(
|
||||
&self,
|
||||
id: &str,
|
||||
partial: &PartialUser,
|
||||
remove: Vec<FieldsUser>,
|
||||
) -> Result<()> {
|
||||
query!(
|
||||
self,
|
||||
update_one_by_id,
|
||||
COL,
|
||||
id,
|
||||
partial,
|
||||
remove.iter().map(|x| x as &dyn IntoDocumentPath).collect(),
|
||||
None
|
||||
)
|
||||
.map(|_| ())
|
||||
}
|
||||
|
||||
/// Set relationship with another user
|
||||
///
|
||||
/// This should use pull_relationship if relationship is None.
|
||||
async fn set_relationship(
|
||||
&self,
|
||||
user_id: &str,
|
||||
target_id: &str,
|
||||
relationship: &RelationshipStatus,
|
||||
) -> Result<()> {
|
||||
if let RelationshipStatus::None = relationship {
|
||||
return self.pull_relationship(user_id, target_id).await;
|
||||
}
|
||||
|
||||
self.col::<User>(COL)
|
||||
.update_one(
|
||||
doc! {
|
||||
"_id": user_id
|
||||
},
|
||||
vec![doc! {
|
||||
"$set": {
|
||||
"relations": {
|
||||
"$concatArrays": [
|
||||
{
|
||||
"$ifNull": [
|
||||
{
|
||||
"$filter": {
|
||||
"input": "$relations",
|
||||
"cond": {
|
||||
"$ne": [
|
||||
"$$this._id",
|
||||
target_id
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
[]
|
||||
]
|
||||
},
|
||||
[
|
||||
{
|
||||
"_id": target_id,
|
||||
"status": format!("{relationship:?}")
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
}],
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.map(|_| ())
|
||||
.map_err(|_| create_database_error!("update_one", "user"))
|
||||
}
|
||||
|
||||
/// Remove relationship with another user
|
||||
async fn pull_relationship(&self, user_id: &str, target_id: &str) -> Result<()> {
|
||||
self.col::<User>(COL)
|
||||
.update_one(
|
||||
doc! {
|
||||
"_id": user_id
|
||||
},
|
||||
doc! {
|
||||
"$pull": {
|
||||
"relations": {
|
||||
"_id": target_id
|
||||
}
|
||||
}
|
||||
},
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.map(|_| ())
|
||||
.map_err(|_| create_database_error!("update_one", "user"))
|
||||
}
|
||||
|
||||
/// Delete a user by their id
|
||||
async fn delete_user(&self, id: &str) -> Result<()> {
|
||||
query!(self, delete_one_by_id, COL, id).map(|_| ())
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoDocumentPath for FieldsUser {
|
||||
fn as_path(&self) -> Option<&'static str> {
|
||||
Some(match self {
|
||||
FieldsUser::Avatar => "avatar",
|
||||
FieldsUser::ProfileBackground => "profile.background",
|
||||
FieldsUser::ProfileContent => "profile.content",
|
||||
FieldsUser::StatusPresence => "status.presence",
|
||||
FieldsUser::StatusText => "status.text",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,23 @@
|
||||
use revolt_result::Result;
|
||||
|
||||
use crate::ReferenceDb;
|
||||
use crate::User;
|
||||
use crate::{FieldsUser, PartialUser, RelationshipStatus, User};
|
||||
|
||||
use super::AbstractUsers;
|
||||
|
||||
#[async_trait]
|
||||
impl AbstractUsers for ReferenceDb {
|
||||
/// Insert a new user into the database
|
||||
async fn insert_user(&self, user: &User) -> Result<()> {
|
||||
let mut users = self.users.lock().await;
|
||||
if users.contains_key(&user.id) {
|
||||
Err(create_database_error!("insert", "user"))
|
||||
} else {
|
||||
users.insert(user.id.to_string(), user.clone());
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Fetch a user from the database
|
||||
async fn fetch_user(&self, id: &str) -> Result<User> {
|
||||
let users = self.users.lock().await;
|
||||
@@ -15,4 +26,96 @@ impl AbstractUsers for ReferenceDb {
|
||||
.cloned()
|
||||
.ok_or_else(|| create_error!(NotFound))
|
||||
}
|
||||
|
||||
/// Fetch a user from the database by their username
|
||||
async fn fetch_user_by_username(&self, username: &str) -> Result<User> {
|
||||
let users = self.users.lock().await;
|
||||
let lowercase = username.to_lowercase();
|
||||
users
|
||||
.values()
|
||||
.find(|user| user.username.to_lowercase() == lowercase)
|
||||
.cloned()
|
||||
.ok_or_else(|| create_error!(NotFound))
|
||||
}
|
||||
|
||||
/// Fetch a user from the database by their session token
|
||||
async fn fetch_user_by_token(&self, _token: &str) -> Result<User> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
/// Fetch multiple users by their ids
|
||||
async fn fetch_users<'a>(&self, ids: &'a [String]) -> Result<Vec<User>> {
|
||||
let users = self.users.lock().await;
|
||||
ids.iter()
|
||||
.map(|id| {
|
||||
users
|
||||
.get(id)
|
||||
.cloned()
|
||||
.ok_or_else(|| create_error!(NotFound))
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Fetch ids of users that both users are friends with
|
||||
async fn fetch_mutual_user_ids(&self, _user_a: &str, _user_b: &str) -> Result<Vec<String>> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
/// Fetch ids of channels that both users are in
|
||||
async fn fetch_mutual_channel_ids(&self, _user_a: &str, _user_b: &str) -> Result<Vec<String>> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
/// Fetch ids of servers that both users share
|
||||
async fn fetch_mutual_server_ids(&self, _user_a: &str, _user_b: &str) -> Result<Vec<String>> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
/// Update a user by their id given some data
|
||||
async fn update_user(
|
||||
&self,
|
||||
id: &str,
|
||||
partial: &PartialUser,
|
||||
remove: Vec<FieldsUser>,
|
||||
) -> Result<()> {
|
||||
let mut users = self.users.lock().await;
|
||||
if let Some(user) = users.get_mut(id) {
|
||||
for field in remove {
|
||||
#[allow(clippy::disallowed_methods)]
|
||||
user.remove_field(&field);
|
||||
}
|
||||
|
||||
user.apply_options(partial.clone());
|
||||
Ok(())
|
||||
} else {
|
||||
Err(create_error!(NotFound))
|
||||
}
|
||||
}
|
||||
|
||||
/// Set relationship with another user
|
||||
///
|
||||
/// This should use pull_relationship if relationship is None.
|
||||
async fn set_relationship(
|
||||
&self,
|
||||
_user_id: &str,
|
||||
_target_id: &str,
|
||||
_relationship: &RelationshipStatus,
|
||||
) -> Result<()> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
/// Remove relationship with another user
|
||||
async fn pull_relationship(&self, _user_id: &str, _target_id: &str) -> Result<()> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
/// Delete a user by their id
|
||||
async fn delete_user(&self, id: &str) -> Result<()> {
|
||||
let mut users = self.users.lock().await;
|
||||
if users.remove(id).is_some() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(create_error!(NotFound))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user