chore(monorepo): delta, january, quark
This commit is contained in:
11
crates/quark/src/impl/dummy/admin/migrations.rs
Normal file
11
crates/quark/src/impl/dummy/admin/migrations.rs
Normal file
@@ -0,0 +1,11 @@
|
||||
use crate::{AbstractMigrations, Result};
|
||||
|
||||
use super::super::DummyDb;
|
||||
|
||||
#[async_trait]
|
||||
impl AbstractMigrations for DummyDb {
|
||||
async fn migrate_database(&self) -> Result<()> {
|
||||
info!("Migrating the database.");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
42
crates/quark/src/impl/dummy/autumn/attachment.rs
Normal file
42
crates/quark/src/impl/dummy/autumn/attachment.rs
Normal file
@@ -0,0 +1,42 @@
|
||||
use crate::models::attachment::File;
|
||||
use crate::{AbstractAttachment, Result};
|
||||
|
||||
use super::super::DummyDb;
|
||||
|
||||
#[async_trait]
|
||||
impl AbstractAttachment for DummyDb {
|
||||
async fn find_and_use_attachment(
|
||||
&self,
|
||||
attachment_id: &str,
|
||||
tag: &str,
|
||||
_parent_type: &str,
|
||||
parent_id: &str,
|
||||
) -> Result<File> {
|
||||
Ok(File {
|
||||
id: attachment_id.into(),
|
||||
tag: tag.into(),
|
||||
filename: "file.txt".into(),
|
||||
content_type: "plain/text".into(),
|
||||
size: 100,
|
||||
|
||||
object_id: Some(parent_id.into()),
|
||||
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
|
||||
async fn insert_attachment(&self, attachment: &File) -> Result<()> {
|
||||
info!("Insert {attachment:?}");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn mark_attachment_as_reported(&self, id: &str) -> Result<()> {
|
||||
info!("Marked {id} as reported");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn mark_attachment_as_deleted(&self, id: &str) -> Result<()> {
|
||||
info!("Marked {id} as deleted");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
90
crates/quark/src/impl/dummy/channels/channel.rs
Normal file
90
crates/quark/src/impl/dummy/channels/channel.rs
Normal file
@@ -0,0 +1,90 @@
|
||||
use std::collections::HashSet;
|
||||
|
||||
use crate::models::channel::{Channel, FieldsChannel, PartialChannel};
|
||||
use crate::{AbstractAttachment, AbstractChannel, Error, OverrideField, Result};
|
||||
|
||||
use super::super::DummyDb;
|
||||
|
||||
#[async_trait]
|
||||
impl AbstractChannel for DummyDb {
|
||||
async fn fetch_channel(&self, id: &str) -> Result<Channel> {
|
||||
Ok(Channel::Group {
|
||||
id: id.into(),
|
||||
|
||||
name: "group".into(),
|
||||
owner: "owner".into(),
|
||||
description: None,
|
||||
recipients: vec!["owner".into()],
|
||||
|
||||
icon: Some(
|
||||
self.find_and_use_attachment("dummy", "dummy", "dummy", "dummy")
|
||||
.await?,
|
||||
),
|
||||
last_message_id: None,
|
||||
|
||||
permissions: None,
|
||||
|
||||
nsfw: false,
|
||||
})
|
||||
}
|
||||
|
||||
async fn fetch_channels<'a>(&self, _ids: &'a [String]) -> Result<Vec<Channel>> {
|
||||
Ok(vec![self.fetch_channel("sus").await.unwrap()])
|
||||
}
|
||||
|
||||
async fn insert_channel(&self, channel: &Channel) -> Result<()> {
|
||||
info!("Insert {channel:?}");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn update_channel(
|
||||
&self,
|
||||
id: &str,
|
||||
channel: &PartialChannel,
|
||||
remove: Vec<FieldsChannel>,
|
||||
) -> Result<()> {
|
||||
info!("Update {id} with {channel:?} and remove {remove:?}");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn delete_channel(&self, channel: &Channel) -> Result<()> {
|
||||
info!("Delete {channel:?}");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn find_direct_messages(&self, user_id: &str) -> Result<Vec<Channel>> {
|
||||
Ok(vec![self.fetch_channel(user_id).await?])
|
||||
}
|
||||
|
||||
async fn find_saved_messages_channel(&self, user: &str) -> Result<Channel> {
|
||||
self.fetch_channel(user).await
|
||||
}
|
||||
|
||||
async fn find_direct_message_channel(&self, _user_a: &str, _user_b: &str) -> Result<Channel> {
|
||||
Err(Error::NotFound)
|
||||
}
|
||||
|
||||
async fn add_user_to_group(&self, channel: &str, user: &str) -> Result<()> {
|
||||
info!("Added {user} to {channel}");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn remove_user_from_group(&self, channel: &str, user: &str) -> Result<()> {
|
||||
info!("Removed {user} from {channel}");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn set_channel_role_permission(
|
||||
&self,
|
||||
channel: &str,
|
||||
role: &str,
|
||||
permissions: OverrideField,
|
||||
) -> Result<()> {
|
||||
info!("Updating permissions for role {role} in {channel} with {permissions:?}");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn check_channels_exist(&self, _channels: &HashSet<String>) -> Result<bool> {
|
||||
Ok(true)
|
||||
}
|
||||
}
|
||||
30
crates/quark/src/impl/dummy/channels/channel_invite.rs
Normal file
30
crates/quark/src/impl/dummy/channels/channel_invite.rs
Normal file
@@ -0,0 +1,30 @@
|
||||
use crate::models::Invite;
|
||||
use crate::{AbstractChannelInvite, Result};
|
||||
|
||||
use super::super::DummyDb;
|
||||
|
||||
#[async_trait]
|
||||
impl AbstractChannelInvite for DummyDb {
|
||||
async fn fetch_invite(&self, code: &str) -> Result<Invite> {
|
||||
Ok(Invite::Server {
|
||||
code: code.into(),
|
||||
server: "server".into(),
|
||||
creator: "creator".into(),
|
||||
channel: "channel".into(),
|
||||
})
|
||||
}
|
||||
|
||||
async fn insert_invite(&self, invite: &Invite) -> Result<()> {
|
||||
info!("Insert {invite:?}");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn delete_invite(&self, code: &str) -> Result<()> {
|
||||
info!("Delete {code}");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn fetch_invites_for_server(&self, server: &str) -> Result<Vec<Invite>> {
|
||||
Ok(vec![self.fetch_invite(server).await.unwrap()])
|
||||
}
|
||||
}
|
||||
31
crates/quark/src/impl/dummy/channels/channel_unread.rs
Normal file
31
crates/quark/src/impl/dummy/channels/channel_unread.rs
Normal file
@@ -0,0 +1,31 @@
|
||||
use crate::models::channel_unread::ChannelUnread;
|
||||
use crate::{AbstractChannelUnread, Result};
|
||||
|
||||
use super::super::DummyDb;
|
||||
|
||||
#[async_trait]
|
||||
impl AbstractChannelUnread for DummyDb {
|
||||
async fn acknowledge_message(&self, channel: &str, user: &str, message: &str) -> Result<()> {
|
||||
info!("Acknowledged {message} in {channel} for {user}");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn acknowledge_channels(&self, user: &str, channels: &[String]) -> Result<()> {
|
||||
info!("Acknowledged {channels:?} for {user}");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn add_mention_to_unread<'a>(
|
||||
&self,
|
||||
channel: &str,
|
||||
user: &str,
|
||||
ids: &[String],
|
||||
) -> Result<()> {
|
||||
info!("Added mentions for {user} in {channel}: {ids:?}");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn fetch_unreads(&self, _user: &str) -> Result<Vec<ChannelUnread>> {
|
||||
Ok(vec![])
|
||||
}
|
||||
}
|
||||
67
crates/quark/src/impl/dummy/channels/message.rs
Normal file
67
crates/quark/src/impl/dummy/channels/message.rs
Normal file
@@ -0,0 +1,67 @@
|
||||
use crate::models::message::{AppendMessage, Message, MessageSort, PartialMessage};
|
||||
use crate::{AbstractMessage, Result};
|
||||
|
||||
use super::super::DummyDb;
|
||||
|
||||
#[async_trait]
|
||||
impl AbstractMessage for DummyDb {
|
||||
async fn fetch_message(&self, id: &str) -> Result<Message> {
|
||||
Ok(Message {
|
||||
id: id.into(),
|
||||
channel: "channel".into(),
|
||||
author: "author".into(),
|
||||
content: Some("message content".into()),
|
||||
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
|
||||
async fn insert_message(&self, message: &Message) -> Result<()> {
|
||||
info!("Insert {message:?}");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn update_message(&self, id: &str, message: &PartialMessage) -> Result<()> {
|
||||
info!("Update {id} with {message:?}");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn append_message(&self, id: &str, append: &AppendMessage) -> Result<()> {
|
||||
info!("Append {id} with {append:?}");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn delete_message(&self, id: &str) -> Result<()> {
|
||||
info!("Delete {id}");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn delete_messages(&self, channel: &str, ids: Vec<String>) -> Result<()> {
|
||||
info!("Delete {ids:?} in {channel}");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn fetch_messages(
|
||||
&self,
|
||||
channel: &str,
|
||||
_limit: Option<i64>,
|
||||
_before: Option<String>,
|
||||
_after: Option<String>,
|
||||
_sort: Option<MessageSort>,
|
||||
_nearby: Option<String>,
|
||||
) -> Result<Vec<Message>> {
|
||||
Ok(vec![self.fetch_message(channel).await.unwrap()])
|
||||
}
|
||||
|
||||
async fn search_messages(
|
||||
&self,
|
||||
channel: &str,
|
||||
_query: &str,
|
||||
_limit: Option<i64>,
|
||||
_before: Option<String>,
|
||||
_after: Option<String>,
|
||||
_sort: MessageSort,
|
||||
) -> Result<Vec<Message>> {
|
||||
Ok(vec![self.fetch_message(channel).await.unwrap()])
|
||||
}
|
||||
}
|
||||
33
crates/quark/src/impl/dummy/mod.rs
Normal file
33
crates/quark/src/impl/dummy/mod.rs
Normal file
@@ -0,0 +1,33 @@
|
||||
use crate::AbstractDatabase;
|
||||
|
||||
pub mod admin {
|
||||
pub mod migrations;
|
||||
}
|
||||
|
||||
pub mod autumn {
|
||||
pub mod attachment;
|
||||
}
|
||||
|
||||
pub mod channels {
|
||||
pub mod channel;
|
||||
pub mod channel_invite;
|
||||
pub mod channel_unread;
|
||||
pub mod message;
|
||||
}
|
||||
|
||||
pub mod servers {
|
||||
pub mod server;
|
||||
pub mod server_ban;
|
||||
pub mod server_member;
|
||||
}
|
||||
|
||||
pub mod users {
|
||||
pub mod bot;
|
||||
pub mod user;
|
||||
pub mod user_settings;
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct DummyDb;
|
||||
|
||||
impl AbstractDatabase for DummyDb {}
|
||||
78
crates/quark/src/impl/dummy/servers/server.rs
Normal file
78
crates/quark/src/impl/dummy/servers/server.rs
Normal file
@@ -0,0 +1,78 @@
|
||||
use crate::models::server::{FieldsRole, FieldsServer, PartialRole, PartialServer, Role, Server};
|
||||
use crate::{AbstractServer, Result, DEFAULT_PERMISSION_SERVER};
|
||||
|
||||
use super::super::DummyDb;
|
||||
|
||||
#[async_trait]
|
||||
impl AbstractServer for DummyDb {
|
||||
async fn fetch_server(&self, id: &str) -> Result<Server> {
|
||||
Ok(Server {
|
||||
id: id.into(),
|
||||
owner: "owner".into(),
|
||||
|
||||
name: "server".into(),
|
||||
description: Some("server description".into()),
|
||||
|
||||
channels: vec!["channel".into()],
|
||||
categories: None,
|
||||
system_messages: None,
|
||||
|
||||
roles: std::collections::HashMap::new(),
|
||||
default_permissions: *DEFAULT_PERMISSION_SERVER as i64,
|
||||
|
||||
icon: None,
|
||||
banner: None,
|
||||
|
||||
flags: None,
|
||||
|
||||
nsfw: false,
|
||||
analytics: true,
|
||||
discoverable: true,
|
||||
})
|
||||
}
|
||||
|
||||
async fn fetch_servers<'a>(&self, _ids: &'a [String]) -> Result<Vec<Server>> {
|
||||
Ok(vec![self.fetch_server("sus").await.unwrap()])
|
||||
}
|
||||
|
||||
async fn insert_server(&self, server: &Server) -> Result<()> {
|
||||
info!("Insert {server:?}");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn update_server(
|
||||
&self,
|
||||
id: &str,
|
||||
server: &PartialServer,
|
||||
remove: Vec<FieldsServer>,
|
||||
) -> Result<()> {
|
||||
info!("Update {id} with {server:?} and remove {remove:?}");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn delete_server(&self, server: &Server) -> Result<()> {
|
||||
info!("Delete {server:?}");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn insert_role(&self, server_id: &str, role_id: &str, role: &Role) -> Result<()> {
|
||||
info!("Create {role:?} on {server_id} as {role_id}");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn update_role(
|
||||
&self,
|
||||
server_id: &str,
|
||||
role_id: &str,
|
||||
role: &PartialRole,
|
||||
remove: Vec<FieldsRole>,
|
||||
) -> Result<()> {
|
||||
info!("Update {role_id} on {server_id} with {role:?} and remove {remove:?}");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn delete_role(&self, server_id: &str, role_id: &str) -> Result<()> {
|
||||
info!("Delete {role_id} on {server_id}");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
32
crates/quark/src/impl/dummy/servers/server_ban.rs
Normal file
32
crates/quark/src/impl/dummy/servers/server_ban.rs
Normal file
@@ -0,0 +1,32 @@
|
||||
use crate::models::server_member::MemberCompositeKey;
|
||||
use crate::models::ServerBan;
|
||||
use crate::{AbstractServerBan, Result};
|
||||
|
||||
use super::super::DummyDb;
|
||||
|
||||
#[async_trait]
|
||||
impl AbstractServerBan for DummyDb {
|
||||
async fn fetch_ban(&self, server: &str, user: &str) -> Result<ServerBan> {
|
||||
Ok(ServerBan {
|
||||
id: MemberCompositeKey {
|
||||
server: server.into(),
|
||||
user: user.into(),
|
||||
},
|
||||
reason: Some("ban reason".into()),
|
||||
})
|
||||
}
|
||||
|
||||
async fn fetch_bans(&self, server: &str) -> Result<Vec<ServerBan>> {
|
||||
Ok(vec![self.fetch_ban(server, "user").await.unwrap()])
|
||||
}
|
||||
|
||||
async fn insert_ban(&self, ban: &ServerBan) -> Result<()> {
|
||||
info!("Insert {ban:?}");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn delete_ban(&self, id: &MemberCompositeKey) -> Result<()> {
|
||||
info!("Delete {id:?}");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
59
crates/quark/src/impl/dummy/servers/server_member.rs
Normal file
59
crates/quark/src/impl/dummy/servers/server_member.rs
Normal file
@@ -0,0 +1,59 @@
|
||||
use crate::models::server_member::{FieldsMember, Member, MemberCompositeKey, PartialMember};
|
||||
use crate::{AbstractServerMember, Result};
|
||||
|
||||
use super::super::DummyDb;
|
||||
|
||||
#[async_trait]
|
||||
impl AbstractServerMember for DummyDb {
|
||||
async fn fetch_member(&self, server: &str, user: &str) -> Result<Member> {
|
||||
Ok(Member {
|
||||
id: MemberCompositeKey {
|
||||
server: server.into(),
|
||||
user: user.into(),
|
||||
},
|
||||
nickname: None,
|
||||
avatar: None,
|
||||
roles: None,
|
||||
})
|
||||
}
|
||||
|
||||
async fn insert_member(&self, member: &Member) -> Result<()> {
|
||||
info!("Create {member:?}");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn update_member(
|
||||
&self,
|
||||
id: &MemberCompositeKey,
|
||||
member: &PartialMember,
|
||||
remove: Vec<FieldsMember>,
|
||||
) -> Result<()> {
|
||||
info!("Update {id:?} with {member:?} and remove {remove:?}");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn delete_member(&self, id: &MemberCompositeKey) -> Result<()> {
|
||||
info!("Delete {id:?}");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn fetch_all_members<'a>(&self, server: &str) -> Result<Vec<Member>> {
|
||||
Ok(vec![self.fetch_member(server, "member").await.unwrap()])
|
||||
}
|
||||
|
||||
async fn fetch_all_memberships<'a>(&self, user: &str) -> Result<Vec<Member>> {
|
||||
Ok(vec![self.fetch_member("server", user).await.unwrap()])
|
||||
}
|
||||
|
||||
async fn fetch_members<'a>(&self, server: &str, _ids: &'a [String]) -> Result<Vec<Member>> {
|
||||
Ok(vec![self.fetch_member(server, "member").await.unwrap()])
|
||||
}
|
||||
|
||||
async fn fetch_member_count(&self, _server: &str) -> Result<usize> {
|
||||
Ok(100)
|
||||
}
|
||||
|
||||
async fn fetch_server_count(&self, _user: &str) -> Result<usize> {
|
||||
Ok(5)
|
||||
}
|
||||
}
|
||||
46
crates/quark/src/impl/dummy/users/bot.rs
Normal file
46
crates/quark/src/impl/dummy/users/bot.rs
Normal file
@@ -0,0 +1,46 @@
|
||||
use crate::models::bot::{Bot, FieldsBot, PartialBot};
|
||||
use crate::{AbstractBot, Result};
|
||||
|
||||
use super::super::DummyDb;
|
||||
|
||||
#[async_trait]
|
||||
impl AbstractBot for DummyDb {
|
||||
async fn fetch_bot(&self, id: &str) -> Result<Bot> {
|
||||
Ok(Bot {
|
||||
id: id.into(),
|
||||
owner: "user".into(),
|
||||
token: "token".into(),
|
||||
public: true,
|
||||
analytics: true,
|
||||
discoverable: true,
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
|
||||
async fn fetch_bot_by_token(&self, _token: &str) -> Result<Bot> {
|
||||
self.fetch_bot("bot").await
|
||||
}
|
||||
|
||||
async fn insert_bot(&self, bot: &Bot) -> Result<()> {
|
||||
info!("Insert {bot:?}");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn update_bot(&self, id: &str, bot: &PartialBot, remove: Vec<FieldsBot>) -> Result<()> {
|
||||
info!("Update {id} with {bot:?} and remove {remove:?}");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn delete_bot(&self, id: &str) -> Result<()> {
|
||||
info!("Delete {id}");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn fetch_bots_by_user(&self, user_id: &str) -> Result<Vec<Bot>> {
|
||||
Ok(vec![self.fetch_bot(user_id).await.unwrap()])
|
||||
}
|
||||
|
||||
async fn get_number_of_bots_by_user(&self, _user_id: &str) -> Result<usize> {
|
||||
Ok(1)
|
||||
}
|
||||
}
|
||||
78
crates/quark/src/impl/dummy/users/user.rs
Normal file
78
crates/quark/src/impl/dummy/users/user.rs
Normal file
@@ -0,0 +1,78 @@
|
||||
use crate::models::user::{FieldsUser, PartialUser, RelationshipStatus, User};
|
||||
use crate::{AbstractUser, Result};
|
||||
|
||||
use super::super::DummyDb;
|
||||
|
||||
#[async_trait]
|
||||
impl AbstractUser for DummyDb {
|
||||
async fn fetch_user(&self, id: &str) -> Result<User> {
|
||||
Ok(User {
|
||||
id: id.into(),
|
||||
username: "username".into(),
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
|
||||
async fn fetch_user_by_username(&self, username: &str) -> Result<User> {
|
||||
self.fetch_user(username).await
|
||||
}
|
||||
|
||||
async fn fetch_user_by_token(&self, token: &str) -> Result<User> {
|
||||
self.fetch_user(token).await
|
||||
}
|
||||
|
||||
async fn insert_user(&self, user: &User) -> Result<()> {
|
||||
info!("Insert {:?}", user);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn update_user(
|
||||
&self,
|
||||
id: &str,
|
||||
user: &PartialUser,
|
||||
remove: Vec<FieldsUser>,
|
||||
) -> Result<()> {
|
||||
info!("Update {id} with {user:?} and remove {remove:?}");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn delete_user(&self, id: &str) -> Result<()> {
|
||||
info!("Delete {id}");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn fetch_users<'a>(&self, _id: &'a [String]) -> Result<Vec<User>> {
|
||||
Ok(vec![self.fetch_user("id").await.unwrap()])
|
||||
}
|
||||
|
||||
async fn is_username_taken(&self, _username: &str) -> Result<bool> {
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
async fn fetch_mutual_user_ids(&self, _user_a: &str, _user_b: &str) -> Result<Vec<String>> {
|
||||
Ok(vec!["a".into()])
|
||||
}
|
||||
|
||||
async fn fetch_mutual_channel_ids(&self, _user_a: &str, _user_b: &str) -> Result<Vec<String>> {
|
||||
Ok(vec!["b".into()])
|
||||
}
|
||||
|
||||
async fn fetch_mutual_server_ids(&self, _user_a: &str, _user_b: &str) -> Result<Vec<String>> {
|
||||
Ok(vec!["c".into()])
|
||||
}
|
||||
|
||||
async fn set_relationship(
|
||||
&self,
|
||||
user_id: &str,
|
||||
target_id: &str,
|
||||
relationship: &RelationshipStatus,
|
||||
) -> Result<()> {
|
||||
info!("Set relationship from {user_id} to {target_id} as {relationship:?}");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn pull_relationship(&self, user_id: &str, target_id: &str) -> Result<()> {
|
||||
info!("Removing relationship from {user_id} to {target_id}");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
25
crates/quark/src/impl/dummy/users/user_settings.rs
Normal file
25
crates/quark/src/impl/dummy/users/user_settings.rs
Normal file
@@ -0,0 +1,25 @@
|
||||
use crate::models::UserSettings;
|
||||
use crate::{AbstractUserSettings, Result};
|
||||
|
||||
use super::super::DummyDb;
|
||||
|
||||
#[async_trait]
|
||||
impl AbstractUserSettings for DummyDb {
|
||||
async fn fetch_user_settings(
|
||||
&'_ self,
|
||||
_id: &str,
|
||||
_filter: &'_ [String],
|
||||
) -> Result<UserSettings> {
|
||||
Ok(std::collections::HashMap::new())
|
||||
}
|
||||
|
||||
async fn set_user_settings(&self, id: &str, settings: &UserSettings) -> Result<()> {
|
||||
info!("Set {id} to {settings:?}");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn delete_user_settings(&self, id: &str) -> Result<()> {
|
||||
info!("Delete {id}");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
1
crates/quark/src/impl/generic/admin/migrations.rs
Normal file
1
crates/quark/src/impl/generic/admin/migrations.rs
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
33
crates/quark/src/impl/generic/autumn/attachment.rs
Normal file
33
crates/quark/src/impl/generic/autumn/attachment.rs
Normal file
@@ -0,0 +1,33 @@
|
||||
use crate::{models::File, Database, Result};
|
||||
|
||||
impl File {
|
||||
pub async fn use_attachment(db: &Database, id: &str, parent: &str) -> Result<File> {
|
||||
db.find_and_use_attachment(id, "attachments", "message", parent)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn use_background(db: &Database, id: &str, parent: &str) -> Result<File> {
|
||||
db.find_and_use_attachment(id, "backgrounds", "user", parent)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn use_avatar(db: &Database, id: &str, parent: &str) -> Result<File> {
|
||||
db.find_and_use_attachment(id, "avatars", "user", parent)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn use_icon(db: &Database, id: &str, parent: &str) -> Result<File> {
|
||||
db.find_and_use_attachment(id, "icons", "object", parent)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn use_server_icon(db: &Database, id: &str, parent: &str) -> Result<File> {
|
||||
db.find_and_use_attachment(id, "icons", "object", parent)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn use_banner(db: &Database, id: &str, parent: &str) -> Result<File> {
|
||||
db.find_and_use_attachment(id, "banners", "server", parent)
|
||||
.await
|
||||
}
|
||||
}
|
||||
380
crates/quark/src/impl/generic/channels/channel.rs
Normal file
380
crates/quark/src/impl/generic/channels/channel.rs
Normal file
@@ -0,0 +1,380 @@
|
||||
use crate::{
|
||||
events::client::EventV1,
|
||||
models::{
|
||||
channel::{FieldsChannel, PartialChannel},
|
||||
message::SystemMessage,
|
||||
Channel,
|
||||
},
|
||||
tasks::ack::AckEvent,
|
||||
Database, Error, OverrideField, Result,
|
||||
};
|
||||
|
||||
impl Channel {
|
||||
/// Get a reference to this channel's id
|
||||
pub fn id(&'_ self) -> &'_ str {
|
||||
match self {
|
||||
Channel::DirectMessage { id, .. }
|
||||
| Channel::Group { id, .. }
|
||||
| Channel::SavedMessages { id, .. }
|
||||
| Channel::TextChannel { id, .. }
|
||||
| Channel::VoiceChannel { id, .. } => id,
|
||||
}
|
||||
}
|
||||
|
||||
/// Represent channel as its id
|
||||
pub fn as_id(self) -> String {
|
||||
match self {
|
||||
Channel::DirectMessage { id, .. }
|
||||
| Channel::Group { id, .. }
|
||||
| Channel::SavedMessages { id, .. }
|
||||
| Channel::TextChannel { id, .. }
|
||||
| Channel::VoiceChannel { id, .. } => id,
|
||||
}
|
||||
}
|
||||
|
||||
/// Map out whether it is a direct DM
|
||||
pub fn is_direct_dm(&self) -> bool {
|
||||
matches!(self, Channel::DirectMessage { .. })
|
||||
}
|
||||
|
||||
/// Create a channel
|
||||
pub async fn create(&self, db: &Database) -> Result<()> {
|
||||
db.insert_channel(self).await?;
|
||||
|
||||
let event = EventV1::ChannelCreate(self.clone());
|
||||
match self {
|
||||
Self::SavedMessages { user, .. } => event.private(user.clone()).await,
|
||||
Self::DirectMessage { recipients, .. } | Self::Group { recipients, .. } => {
|
||||
for recipient in recipients {
|
||||
event.clone().private(recipient.clone()).await;
|
||||
}
|
||||
}
|
||||
Self::TextChannel { server, .. } | Self::VoiceChannel { server, .. } => {
|
||||
event.p(server.clone()).await;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Update channel data
|
||||
pub async fn update<'a>(
|
||||
&mut self,
|
||||
db: &Database,
|
||||
partial: PartialChannel,
|
||||
remove: Vec<FieldsChannel>,
|
||||
) -> Result<()> {
|
||||
for field in &remove {
|
||||
self.remove(field);
|
||||
}
|
||||
|
||||
self.apply_options(partial.clone());
|
||||
|
||||
let id = self.id().to_string();
|
||||
db.update_channel(&id, &partial, remove.clone()).await?;
|
||||
|
||||
EventV1::ChannelUpdate {
|
||||
id: id.clone(),
|
||||
data: partial,
|
||||
clear: remove,
|
||||
}
|
||||
.p(match self {
|
||||
Self::TextChannel { server, .. } | Self::VoiceChannel { server, .. } => server.clone(),
|
||||
_ => id,
|
||||
})
|
||||
.await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Delete a channel
|
||||
pub async fn delete(self, db: &Database) -> Result<()> {
|
||||
let id = self.id().to_string();
|
||||
EventV1::ChannelDelete { id: id.clone() }.p(id).await;
|
||||
db.delete_channel(&self).await
|
||||
}
|
||||
|
||||
/// Remove a field from Channel object
|
||||
pub fn remove(&mut self, field: &FieldsChannel) {
|
||||
match field {
|
||||
FieldsChannel::Description => match self {
|
||||
Self::Group { description, .. }
|
||||
| Self::TextChannel { description, .. }
|
||||
| Self::VoiceChannel { description, .. } => {
|
||||
description.take();
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
FieldsChannel::Icon => match self {
|
||||
Self::Group { icon, .. }
|
||||
| Self::TextChannel { icon, .. }
|
||||
| Self::VoiceChannel { icon, .. } => {
|
||||
icon.take();
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
FieldsChannel::DefaultPermissions => match self {
|
||||
Self::TextChannel {
|
||||
default_permissions,
|
||||
..
|
||||
}
|
||||
| Self::VoiceChannel {
|
||||
default_permissions,
|
||||
..
|
||||
} => {
|
||||
default_permissions.take();
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Apply partial channel to channel
|
||||
pub fn apply_options(&mut self, partial: PartialChannel) {
|
||||
// ! FIXME: maybe flatten channel object?
|
||||
match self {
|
||||
Self::DirectMessage { active, .. } => {
|
||||
if let Some(v) = partial.active {
|
||||
*active = v;
|
||||
}
|
||||
}
|
||||
Self::Group {
|
||||
name,
|
||||
owner,
|
||||
description,
|
||||
icon,
|
||||
nsfw,
|
||||
permissions,
|
||||
..
|
||||
} => {
|
||||
if let Some(v) = partial.name {
|
||||
*name = v;
|
||||
}
|
||||
|
||||
if let Some(v) = partial.owner {
|
||||
*owner = v;
|
||||
}
|
||||
|
||||
if let Some(v) = partial.description {
|
||||
description.replace(v);
|
||||
}
|
||||
|
||||
if let Some(v) = partial.icon {
|
||||
icon.replace(v);
|
||||
}
|
||||
|
||||
if let Some(v) = partial.nsfw {
|
||||
*nsfw = v;
|
||||
}
|
||||
|
||||
if let Some(v) = partial.permissions {
|
||||
permissions.replace(v);
|
||||
}
|
||||
}
|
||||
Self::TextChannel {
|
||||
name,
|
||||
description,
|
||||
icon,
|
||||
nsfw,
|
||||
default_permissions,
|
||||
role_permissions,
|
||||
..
|
||||
}
|
||||
| Self::VoiceChannel {
|
||||
name,
|
||||
description,
|
||||
icon,
|
||||
nsfw,
|
||||
default_permissions,
|
||||
role_permissions,
|
||||
..
|
||||
} => {
|
||||
if let Some(v) = partial.name {
|
||||
*name = v;
|
||||
}
|
||||
|
||||
if let Some(v) = partial.description {
|
||||
description.replace(v);
|
||||
}
|
||||
|
||||
if let Some(v) = partial.icon {
|
||||
icon.replace(v);
|
||||
}
|
||||
|
||||
if let Some(v) = partial.nsfw {
|
||||
*nsfw = v;
|
||||
}
|
||||
|
||||
if let Some(v) = partial.role_permissions {
|
||||
*role_permissions = v;
|
||||
}
|
||||
|
||||
if let Some(v) = partial.default_permissions {
|
||||
default_permissions.replace(v);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
/// Acknowledge a message
|
||||
pub async fn ack(&self, user: &str, message: &str) -> Result<()> {
|
||||
EventV1::ChannelAck {
|
||||
id: self.id().to_string(),
|
||||
user: user.to_string(),
|
||||
message_id: message.to_string(),
|
||||
}
|
||||
.private(user.to_string())
|
||||
.await;
|
||||
|
||||
crate::tasks::ack::queue(
|
||||
self.id().to_string(),
|
||||
user.to_string(),
|
||||
AckEvent::AckMessage {
|
||||
id: message.to_string(),
|
||||
},
|
||||
)
|
||||
.await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Add user to a group
|
||||
pub async fn add_user_to_group(&mut self, db: &Database, user: &str, by: &str) -> Result<()> {
|
||||
if let Channel::Group { recipients, .. } = self {
|
||||
recipients.push(user.to_string());
|
||||
}
|
||||
|
||||
match &self {
|
||||
Channel::Group { id, .. } => {
|
||||
db.add_user_to_group(id, user).await?;
|
||||
|
||||
EventV1::ChannelGroupJoin {
|
||||
id: id.to_string(),
|
||||
user: user.to_string(),
|
||||
}
|
||||
.p(id.to_string())
|
||||
.await;
|
||||
|
||||
EventV1::ChannelCreate(self.clone())
|
||||
.private(user.to_string())
|
||||
.await;
|
||||
|
||||
SystemMessage::UserAdded {
|
||||
id: user.to_string(),
|
||||
by: by.to_string(),
|
||||
}
|
||||
.into_message(id.to_string())
|
||||
.create(db, self, None)
|
||||
.await
|
||||
.ok();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
_ => Err(Error::InvalidOperation),
|
||||
}
|
||||
}
|
||||
|
||||
/// Remove user from a group
|
||||
pub async fn remove_user_from_group(
|
||||
&self,
|
||||
db: &Database,
|
||||
user: &str,
|
||||
by: Option<&str>,
|
||||
) -> Result<()> {
|
||||
match &self {
|
||||
Channel::Group {
|
||||
id,
|
||||
owner,
|
||||
recipients,
|
||||
..
|
||||
} => {
|
||||
if user == owner {
|
||||
if let Some(new_owner) = recipients.iter().find(|x| *x != user) {
|
||||
db.update_channel(
|
||||
id,
|
||||
&PartialChannel {
|
||||
owner: Some(new_owner.into()),
|
||||
..Default::default()
|
||||
},
|
||||
vec![],
|
||||
)
|
||||
.await?;
|
||||
} else {
|
||||
db.delete_channel(self).await?;
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
db.remove_user_from_group(id, user).await?;
|
||||
|
||||
EventV1::ChannelGroupLeave {
|
||||
id: id.to_string(),
|
||||
user: user.to_string(),
|
||||
}
|
||||
.p(id.to_string())
|
||||
.await;
|
||||
|
||||
if let Some(by) = by {
|
||||
SystemMessage::UserRemove {
|
||||
id: user.to_string(),
|
||||
by: by.to_string(),
|
||||
}
|
||||
} else {
|
||||
SystemMessage::UserLeft {
|
||||
id: user.to_string(),
|
||||
}
|
||||
}
|
||||
.into_message(id.to_string())
|
||||
.create(db, self, None)
|
||||
.await
|
||||
.ok();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
_ => Err(Error::InvalidOperation),
|
||||
}
|
||||
}
|
||||
|
||||
/// Set role permission on a channel
|
||||
pub async fn set_role_permission(
|
||||
&mut self,
|
||||
db: &Database,
|
||||
role: &str,
|
||||
permissions: OverrideField,
|
||||
) -> Result<()> {
|
||||
match self {
|
||||
Channel::TextChannel {
|
||||
id,
|
||||
server,
|
||||
role_permissions,
|
||||
..
|
||||
}
|
||||
| Channel::VoiceChannel {
|
||||
id,
|
||||
server,
|
||||
role_permissions,
|
||||
..
|
||||
} => {
|
||||
db.set_channel_role_permission(id, role, permissions)
|
||||
.await?;
|
||||
|
||||
role_permissions.insert(role.to_string(), permissions);
|
||||
|
||||
EventV1::ChannelUpdate {
|
||||
id: id.clone(),
|
||||
data: PartialChannel {
|
||||
role_permissions: Some(role_permissions.clone()),
|
||||
..Default::default()
|
||||
},
|
||||
clear: vec![],
|
||||
}
|
||||
.p(server.clone())
|
||||
.await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
_ => Err(Error::InvalidOperation),
|
||||
}
|
||||
}
|
||||
}
|
||||
74
crates/quark/src/impl/generic/channels/channel_invite.rs
Normal file
74
crates/quark/src/impl/generic/channels/channel_invite.rs
Normal file
@@ -0,0 +1,74 @@
|
||||
use nanoid::nanoid;
|
||||
|
||||
use crate::{
|
||||
models::{Channel, Invite, User},
|
||||
Database, Error, Result,
|
||||
};
|
||||
|
||||
lazy_static! {
|
||||
static ref ALPHABET: [char; 54] = [
|
||||
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
|
||||
'J', 'K', 'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd',
|
||||
'e', 'f', 'g', 'h', 'j', 'k', 'm', 'n', 'p', 'q', 'r', 's', 't', 'v', 'w', 'x', 'y', 'z'
|
||||
];
|
||||
}
|
||||
|
||||
impl Invite {
|
||||
/// Get the invite code for this invite
|
||||
pub fn code(&'_ self) -> &'_ str {
|
||||
match self {
|
||||
Invite::Server { code, .. } | Invite::Group { code, .. } => code,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the ID of the user who created this invite
|
||||
pub fn creator(&'_ self) -> &'_ str {
|
||||
match self {
|
||||
Invite::Server { creator, .. } | Invite::Group { creator, .. } => creator,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new invite from given information
|
||||
pub async fn create(db: &Database, creator: &User, target: &Channel) -> Result<Invite> {
|
||||
let code = nanoid!(8, &*ALPHABET);
|
||||
let invite = match &target {
|
||||
Channel::Group { id, .. } => Ok(Invite::Group {
|
||||
code,
|
||||
creator: creator.id.clone(),
|
||||
channel: id.clone(),
|
||||
}),
|
||||
Channel::TextChannel { id, server, .. } | Channel::VoiceChannel { id, server, .. } => {
|
||||
Ok(Invite::Server {
|
||||
code,
|
||||
creator: creator.id.clone(),
|
||||
server: server.clone(),
|
||||
channel: id.clone(),
|
||||
})
|
||||
}
|
||||
_ => Err(Error::InvalidOperation),
|
||||
}?;
|
||||
|
||||
db.insert_invite(&invite).await?;
|
||||
Ok(invite)
|
||||
}
|
||||
|
||||
/// Resolve an invite by its ID or by a public server ID
|
||||
pub async fn find(db: &Database, code: &str) -> Result<Invite> {
|
||||
if let Ok(invite) = db.fetch_invite(code).await {
|
||||
return Ok(invite);
|
||||
} else if let Ok(server) = db.fetch_server(code).await {
|
||||
if server.discoverable {
|
||||
if let Some(channel) = server.channels.into_iter().next() {
|
||||
return Ok(Invite::Server {
|
||||
code: code.to_string(),
|
||||
server: server.id,
|
||||
creator: server.owner,
|
||||
channel,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Err(Error::NotFound)
|
||||
}
|
||||
}
|
||||
1
crates/quark/src/impl/generic/channels/channel_unread.rs
Normal file
1
crates/quark/src/impl/generic/channels/channel_unread.rs
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
284
crates/quark/src/impl/generic/channels/message.rs
Normal file
284
crates/quark/src/impl/generic/channels/message.rs
Normal file
@@ -0,0 +1,284 @@
|
||||
use std::collections::HashSet;
|
||||
|
||||
use serde_json::json;
|
||||
use ulid::Ulid;
|
||||
|
||||
use crate::{
|
||||
events::client::EventV1,
|
||||
models::{
|
||||
message::{
|
||||
AppendMessage, BulkMessageResponse, PartialMessage, SendableEmbed, SystemMessage,
|
||||
},
|
||||
Channel, Message, User,
|
||||
},
|
||||
presence::presence_filter_online,
|
||||
tasks::ack::AckEvent,
|
||||
types::{
|
||||
january::{Embed, Text},
|
||||
push::PushNotification,
|
||||
},
|
||||
Database, Result,
|
||||
};
|
||||
|
||||
impl Message {
|
||||
/// Create a message
|
||||
pub async fn create_no_web_push(
|
||||
&mut self,
|
||||
db: &Database,
|
||||
channel: &str,
|
||||
is_direct_dm: bool,
|
||||
) -> Result<()> {
|
||||
db.insert_message(self).await?;
|
||||
|
||||
// Fan out events
|
||||
EventV1::Message(self.clone()).p(channel.to_string()).await;
|
||||
|
||||
// Update last_message_id
|
||||
crate::tasks::last_message_id::queue(
|
||||
channel.to_string(),
|
||||
self.id.to_string(),
|
||||
is_direct_dm,
|
||||
)
|
||||
.await;
|
||||
|
||||
// Add mentions for affected users
|
||||
if let Some(mentions) = &self.mentions {
|
||||
for user in mentions {
|
||||
crate::tasks::ack::queue(
|
||||
channel.to_string(),
|
||||
user.to_string(),
|
||||
AckEvent::AddMention {
|
||||
ids: vec![self.id.to_string()],
|
||||
},
|
||||
)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Create a message and Web Push events
|
||||
pub async fn create(
|
||||
&mut self,
|
||||
db: &Database,
|
||||
channel: &Channel,
|
||||
sender: Option<&User>,
|
||||
) -> Result<()> {
|
||||
self.create_no_web_push(db, channel.id(), channel.is_direct_dm())
|
||||
.await?;
|
||||
|
||||
// Push out Web Push notifications
|
||||
crate::tasks::web_push::queue(
|
||||
{
|
||||
let mut target_ids = vec![];
|
||||
match &channel {
|
||||
Channel::DirectMessage { recipients, .. }
|
||||
| Channel::Group { recipients, .. } => {
|
||||
target_ids = (&recipients.iter().cloned().collect::<HashSet<String>>()
|
||||
- &presence_filter_online(recipients).await)
|
||||
.into_iter()
|
||||
.collect::<Vec<String>>();
|
||||
}
|
||||
Channel::TextChannel { .. } => {
|
||||
if let Some(mentions) = &self.mentions {
|
||||
target_ids.append(&mut mentions.clone());
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
target_ids
|
||||
},
|
||||
json!(PushNotification::new(self.clone(), sender, channel.id())).to_string(),
|
||||
)
|
||||
.await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Update message data
|
||||
pub async fn update(&mut self, db: &Database, partial: PartialMessage) -> Result<()> {
|
||||
self.apply_options(partial.clone());
|
||||
db.update_message(&self.id, &partial).await?;
|
||||
|
||||
EventV1::MessageUpdate {
|
||||
id: self.id.clone(),
|
||||
channel: self.channel.clone(),
|
||||
data: partial,
|
||||
}
|
||||
.p(self.channel.clone())
|
||||
.await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Append message data
|
||||
pub async fn append(
|
||||
db: &Database,
|
||||
id: String,
|
||||
channel: String,
|
||||
append: AppendMessage,
|
||||
) -> Result<()> {
|
||||
db.append_message(&id, &append).await?;
|
||||
|
||||
EventV1::MessageAppend {
|
||||
id,
|
||||
channel: channel.to_string(),
|
||||
append,
|
||||
}
|
||||
.p(channel)
|
||||
.await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Delete a message
|
||||
pub async fn delete(self, db: &Database) -> Result<()> {
|
||||
db.delete_message(&self.id).await?;
|
||||
EventV1::MessageDelete {
|
||||
id: self.id,
|
||||
channel: self.channel.clone(),
|
||||
}
|
||||
.p(self.channel)
|
||||
.await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Bulk delete messages
|
||||
pub async fn bulk_delete(db: &Database, channel: &str, ids: Vec<String>) -> Result<()> {
|
||||
db.delete_messages(channel, ids.clone()).await?;
|
||||
EventV1::BulkMessageDelete {
|
||||
channel: channel.to_string(),
|
||||
ids,
|
||||
}
|
||||
.p(channel.to_string())
|
||||
.await;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub trait IntoUsers {
|
||||
fn get_user_ids(&self) -> Vec<String>;
|
||||
}
|
||||
|
||||
impl IntoUsers for Message {
|
||||
fn get_user_ids(&self) -> Vec<String> {
|
||||
let mut ids = vec![self.author.clone()];
|
||||
|
||||
if let Some(msg) = &self.system {
|
||||
match msg {
|
||||
SystemMessage::UserAdded { id, by, .. }
|
||||
| SystemMessage::UserRemove { id, by, .. } => {
|
||||
ids.push(id.clone());
|
||||
ids.push(by.clone());
|
||||
}
|
||||
SystemMessage::UserJoined { id, .. }
|
||||
| SystemMessage::UserLeft { id, .. }
|
||||
| SystemMessage::UserKicked { id, .. }
|
||||
| SystemMessage::UserBanned { id, .. } => ids.push(id.clone()),
|
||||
SystemMessage::ChannelRenamed { by, .. }
|
||||
| SystemMessage::ChannelDescriptionChanged { by, .. }
|
||||
| SystemMessage::ChannelIconChanged { by, .. } => ids.push(by.clone()),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
ids
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoUsers for Vec<Message> {
|
||||
fn get_user_ids(&self) -> Vec<String> {
|
||||
let mut ids = vec![];
|
||||
for message in self {
|
||||
ids.append(&mut message.get_user_ids());
|
||||
}
|
||||
|
||||
ids
|
||||
}
|
||||
}
|
||||
|
||||
impl SystemMessage {
|
||||
pub fn into_message(self, channel: String) -> Message {
|
||||
Message {
|
||||
id: Ulid::new().to_string(),
|
||||
channel,
|
||||
author: "00000000000000000000000000".to_string(),
|
||||
system: Some(self),
|
||||
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SystemMessage> for String {
|
||||
fn from(s: SystemMessage) -> String {
|
||||
match s {
|
||||
SystemMessage::Text { content } => content,
|
||||
SystemMessage::UserAdded { .. } => "User added to the channel.".to_string(),
|
||||
SystemMessage::UserRemove { .. } => "User removed from the channel.".to_string(),
|
||||
SystemMessage::UserJoined { .. } => "User joined the channel.".to_string(),
|
||||
SystemMessage::UserLeft { .. } => "User left the channel.".to_string(),
|
||||
SystemMessage::UserKicked { .. } => "User kicked from the channel.".to_string(),
|
||||
SystemMessage::UserBanned { .. } => "User banned from the channel.".to_string(),
|
||||
SystemMessage::ChannelRenamed { .. } => "Channel renamed.".to_string(),
|
||||
SystemMessage::ChannelDescriptionChanged { .. } => {
|
||||
"Channel description changed.".to_string()
|
||||
}
|
||||
SystemMessage::ChannelIconChanged { .. } => "Channel icon changed.".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SendableEmbed {
|
||||
pub async fn into_embed(self, db: &Database, message_id: String) -> Result<Embed> {
|
||||
let media = if let Some(id) = self.media {
|
||||
Some(
|
||||
db.find_and_use_attachment(&id, "attachments", "message", &message_id)
|
||||
.await?,
|
||||
)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
Ok(Embed::Text(Text {
|
||||
icon_url: self.icon_url,
|
||||
url: self.url,
|
||||
title: self.title,
|
||||
description: self.description,
|
||||
media,
|
||||
colour: self.colour,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
impl BulkMessageResponse {
|
||||
pub async fn transform(
|
||||
db: &Database,
|
||||
channel: &Channel,
|
||||
messages: Vec<Message>,
|
||||
include_users: Option<bool>,
|
||||
) -> Result<BulkMessageResponse> {
|
||||
if let Some(true) = include_users {
|
||||
let user_ids = messages.get_user_ids();
|
||||
let users = db.fetch_users(&user_ids).await?;
|
||||
|
||||
Ok(match channel {
|
||||
Channel::TextChannel { server, .. } | Channel::VoiceChannel { server, .. } => {
|
||||
BulkMessageResponse::MessagesAndUsers {
|
||||
messages,
|
||||
users,
|
||||
members: Some(db.fetch_members(server, &user_ids).await?),
|
||||
}
|
||||
}
|
||||
_ => BulkMessageResponse::MessagesAndUsers {
|
||||
messages,
|
||||
users,
|
||||
members: None,
|
||||
},
|
||||
})
|
||||
} else {
|
||||
Ok(BulkMessageResponse::JustMessages(messages))
|
||||
}
|
||||
}
|
||||
}
|
||||
28
crates/quark/src/impl/generic/mod.rs
Normal file
28
crates/quark/src/impl/generic/mod.rs
Normal file
@@ -0,0 +1,28 @@
|
||||
//! Database agnostic implementations.
|
||||
|
||||
pub mod admin {
|
||||
pub mod migrations;
|
||||
}
|
||||
|
||||
pub mod autumn {
|
||||
pub mod attachment;
|
||||
}
|
||||
|
||||
pub mod channels {
|
||||
pub mod channel;
|
||||
pub mod channel_invite;
|
||||
pub mod channel_unread;
|
||||
pub mod message;
|
||||
}
|
||||
|
||||
pub mod servers {
|
||||
pub mod server;
|
||||
pub mod server_ban;
|
||||
pub mod server_member;
|
||||
}
|
||||
|
||||
pub mod users {
|
||||
pub mod bot;
|
||||
pub mod user;
|
||||
pub mod user_settings;
|
||||
}
|
||||
323
crates/quark/src/impl/generic/servers/server.rs
Normal file
323
crates/quark/src/impl/generic/servers/server.rs
Normal file
@@ -0,0 +1,323 @@
|
||||
use ulid::Ulid;
|
||||
|
||||
use crate::{
|
||||
events::client::EventV1,
|
||||
models::{
|
||||
message::SystemMessage,
|
||||
server::{
|
||||
FieldsRole, FieldsServer, PartialRole, PartialServer, Role, SystemMessageChannels,
|
||||
},
|
||||
server_member::{MemberCompositeKey, RemovalIntention},
|
||||
Channel, Member, Server, ServerBan, User,
|
||||
},
|
||||
perms, Database, Error, OverrideField, Permission, Result,
|
||||
};
|
||||
|
||||
impl Role {
|
||||
/// Into optional struct
|
||||
pub fn into_optional(self) -> PartialRole {
|
||||
PartialRole {
|
||||
name: Some(self.name),
|
||||
permissions: Some(self.permissions),
|
||||
colour: self.colour,
|
||||
hoist: Some(self.hoist),
|
||||
rank: Some(self.rank),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a role
|
||||
pub async fn create(&self, db: &Database, server_id: &str) -> Result<String> {
|
||||
let role_id = Ulid::new().to_string();
|
||||
db.insert_role(server_id, &role_id, self).await?;
|
||||
|
||||
EventV1::ServerRoleUpdate {
|
||||
id: server_id.to_string(),
|
||||
role_id: role_id.to_string(),
|
||||
data: self.clone().into_optional(),
|
||||
clear: vec![],
|
||||
}
|
||||
.p(server_id.to_string())
|
||||
.await;
|
||||
|
||||
Ok(role_id)
|
||||
}
|
||||
|
||||
/// Update server data
|
||||
pub async fn update<'a>(
|
||||
&mut self,
|
||||
db: &Database,
|
||||
server_id: &str,
|
||||
role_id: &str,
|
||||
partial: PartialRole,
|
||||
remove: Vec<FieldsRole>,
|
||||
) -> Result<()> {
|
||||
for field in &remove {
|
||||
self.remove(field);
|
||||
}
|
||||
|
||||
self.apply_options(partial.clone());
|
||||
|
||||
db.update_role(server_id, role_id, &partial, remove.clone())
|
||||
.await?;
|
||||
|
||||
EventV1::ServerRoleUpdate {
|
||||
id: server_id.to_string(),
|
||||
role_id: role_id.to_string(),
|
||||
data: partial,
|
||||
clear: vec![],
|
||||
}
|
||||
.p(server_id.to_string())
|
||||
.await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Delete a role
|
||||
pub async fn delete(self, db: &Database, server_id: &str, role_id: &str) -> Result<()> {
|
||||
EventV1::ServerRoleDelete {
|
||||
id: server_id.to_string(),
|
||||
role_id: role_id.to_string(),
|
||||
}
|
||||
.p(server_id.to_string())
|
||||
.await;
|
||||
|
||||
db.delete_role(server_id, role_id).await
|
||||
}
|
||||
|
||||
/// Remove field from Role
|
||||
pub fn remove(&mut self, field: &FieldsRole) {
|
||||
match field {
|
||||
FieldsRole::Colour => self.colour = None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Server {
|
||||
/// Create a server
|
||||
pub async fn create(&self, db: &Database) -> Result<()> {
|
||||
db.insert_server(self).await
|
||||
}
|
||||
|
||||
/// Update server data
|
||||
pub async fn update<'a>(
|
||||
&mut self,
|
||||
db: &Database,
|
||||
partial: PartialServer,
|
||||
remove: Vec<FieldsServer>,
|
||||
) -> Result<()> {
|
||||
for field in &remove {
|
||||
self.remove(field);
|
||||
}
|
||||
|
||||
self.apply_options(partial.clone());
|
||||
|
||||
db.update_server(&self.id, &partial, remove.clone()).await?;
|
||||
|
||||
EventV1::ServerUpdate {
|
||||
id: self.id.clone(),
|
||||
data: partial,
|
||||
clear: remove,
|
||||
}
|
||||
.p(self.id.clone())
|
||||
.await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Delete a server
|
||||
pub async fn delete(self, db: &Database) -> Result<()> {
|
||||
EventV1::ServerDelete {
|
||||
id: self.id.clone(),
|
||||
}
|
||||
.p(self.id.clone())
|
||||
.await;
|
||||
|
||||
db.delete_server(&self).await
|
||||
}
|
||||
|
||||
/// Remove a field from Server
|
||||
pub fn remove(&mut self, field: &FieldsServer) {
|
||||
match field {
|
||||
FieldsServer::Description => self.description = None,
|
||||
FieldsServer::Categories => self.categories = None,
|
||||
FieldsServer::SystemMessages => self.system_messages = None,
|
||||
FieldsServer::Icon => self.icon = None,
|
||||
FieldsServer::Banner => self.banner = None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Set role permission on a server
|
||||
pub async fn set_role_permission(
|
||||
&mut self,
|
||||
db: &Database,
|
||||
role_id: &str,
|
||||
permissions: OverrideField,
|
||||
) -> Result<()> {
|
||||
if let Some(role) = self.roles.get_mut(role_id) {
|
||||
role.update(
|
||||
db,
|
||||
&self.id,
|
||||
role_id,
|
||||
PartialRole {
|
||||
permissions: Some(permissions),
|
||||
..Default::default()
|
||||
},
|
||||
vec![],
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::NotFound)
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new member in a server
|
||||
pub async fn create_member(
|
||||
&self,
|
||||
db: &Database,
|
||||
user: User,
|
||||
channels: Option<Vec<Channel>>,
|
||||
) -> Result<Vec<Channel>> {
|
||||
if db.fetch_ban(&self.id, &user.id).await.is_ok() {
|
||||
return Err(Error::Banned);
|
||||
}
|
||||
|
||||
let member = Member {
|
||||
id: MemberCompositeKey {
|
||||
server: self.id.clone(),
|
||||
user: user.id.clone(),
|
||||
},
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
db.insert_member(&member).await?;
|
||||
|
||||
let should_fetch = channels.is_none();
|
||||
let mut channels = channels.unwrap_or_default();
|
||||
|
||||
if should_fetch {
|
||||
let perm = perms(&user).server(self).member(&member);
|
||||
let existing_channels = db.fetch_channels(&self.channels).await?;
|
||||
for channel in existing_channels {
|
||||
if perm
|
||||
.clone()
|
||||
.channel(&channel)
|
||||
.has_permission(db, Permission::ViewChannel)
|
||||
.await?
|
||||
{
|
||||
channels.push(channel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
EventV1::ServerMemberJoin {
|
||||
id: self.id.clone(),
|
||||
user: user.id.clone(),
|
||||
}
|
||||
.p(self.id.clone())
|
||||
.await;
|
||||
|
||||
EventV1::ServerCreate {
|
||||
id: self.id.clone(),
|
||||
server: self.clone(),
|
||||
channels: channels.clone(),
|
||||
}
|
||||
.private(user.id.clone())
|
||||
.await;
|
||||
|
||||
if let Some(id) = self
|
||||
.system_messages
|
||||
.as_ref()
|
||||
.and_then(|x| x.user_joined.as_ref())
|
||||
{
|
||||
SystemMessage::UserJoined {
|
||||
id: user.id.clone(),
|
||||
}
|
||||
.into_message(id.to_string())
|
||||
.create_no_web_push(db, id, false)
|
||||
.await
|
||||
.ok();
|
||||
}
|
||||
|
||||
Ok(channels)
|
||||
}
|
||||
|
||||
/// Remove a member from a server
|
||||
pub async fn remove_member(
|
||||
&self,
|
||||
db: &Database,
|
||||
member: Member,
|
||||
intention: RemovalIntention,
|
||||
) -> Result<()> {
|
||||
db.delete_member(&member.id).await?;
|
||||
|
||||
EventV1::ServerMemberLeave {
|
||||
id: self.id.to_string(),
|
||||
user: member.id.user.clone(),
|
||||
}
|
||||
.p(member.id.server)
|
||||
.await;
|
||||
|
||||
if let Some(id) = self.system_messages.as_ref().and_then(|x| match intention {
|
||||
RemovalIntention::Leave => x.user_left.as_ref(),
|
||||
RemovalIntention::Kick => x.user_kicked.as_ref(),
|
||||
RemovalIntention::Ban => x.user_banned.as_ref(),
|
||||
}) {
|
||||
match intention {
|
||||
RemovalIntention::Leave => SystemMessage::UserLeft { id: member.id.user },
|
||||
RemovalIntention::Kick => SystemMessage::UserKicked { id: member.id.user },
|
||||
RemovalIntention::Ban => SystemMessage::UserBanned { id: member.id.user },
|
||||
}
|
||||
.into_message(id.to_string())
|
||||
.create_no_web_push(db, id, false)
|
||||
.await
|
||||
.ok();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Ban a member from a server
|
||||
pub async fn ban_member(
|
||||
self,
|
||||
db: &Database,
|
||||
member: Member,
|
||||
reason: Option<String>,
|
||||
) -> Result<ServerBan> {
|
||||
let ban = ServerBan {
|
||||
id: member.id.clone(),
|
||||
reason,
|
||||
};
|
||||
|
||||
self.remove_member(db, member, RemovalIntention::Ban)
|
||||
.await?;
|
||||
|
||||
db.insert_ban(&ban).await?;
|
||||
Ok(ban)
|
||||
}
|
||||
}
|
||||
|
||||
impl SystemMessageChannels {
|
||||
pub fn into_channel_ids(self) -> Vec<String> {
|
||||
let mut ids = vec![];
|
||||
|
||||
if let Some(id) = self.user_joined {
|
||||
ids.push(id);
|
||||
}
|
||||
|
||||
if let Some(id) = self.user_left {
|
||||
ids.push(id);
|
||||
}
|
||||
|
||||
if let Some(id) = self.user_kicked {
|
||||
ids.push(id);
|
||||
}
|
||||
|
||||
if let Some(id) = self.user_banned {
|
||||
ids.push(id);
|
||||
}
|
||||
|
||||
ids
|
||||
}
|
||||
}
|
||||
1
crates/quark/src/impl/generic/servers/server_ban.rs
Normal file
1
crates/quark/src/impl/generic/servers/server_ban.rs
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
62
crates/quark/src/impl/generic/servers/server_member.rs
Normal file
62
crates/quark/src/impl/generic/servers/server_member.rs
Normal file
@@ -0,0 +1,62 @@
|
||||
use crate::{
|
||||
events::client::EventV1,
|
||||
models::{
|
||||
server_member::{FieldsMember, PartialMember},
|
||||
Member, Server,
|
||||
},
|
||||
Database, Result,
|
||||
};
|
||||
|
||||
impl Member {
|
||||
/// Update member data
|
||||
pub async fn update<'a>(
|
||||
&mut self,
|
||||
db: &Database,
|
||||
partial: PartialMember,
|
||||
remove: Vec<FieldsMember>,
|
||||
) -> Result<()> {
|
||||
for field in &remove {
|
||||
self.remove(field);
|
||||
}
|
||||
|
||||
self.apply_options(partial.clone());
|
||||
|
||||
db.update_member(&self.id, &partial, remove.clone()).await?;
|
||||
|
||||
EventV1::ServerMemberUpdate {
|
||||
id: self.id.clone(),
|
||||
data: partial,
|
||||
clear: remove,
|
||||
}
|
||||
.p(self.id.server.clone())
|
||||
.await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get this user's current ranking
|
||||
pub fn get_ranking(&self, server: &Server) -> i64 {
|
||||
if let Some(roles) = &self.roles {
|
||||
let mut value = i64::MAX;
|
||||
for role in roles {
|
||||
if let Some(role) = server.roles.get(role) {
|
||||
if role.rank < value {
|
||||
value = role.rank;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
value
|
||||
} else {
|
||||
i64::MAX
|
||||
}
|
||||
}
|
||||
|
||||
pub fn remove(&mut self, field: &FieldsMember) {
|
||||
match field {
|
||||
FieldsMember::Avatar => self.avatar = None,
|
||||
FieldsMember::Nickname => self.nickname = None,
|
||||
FieldsMember::Roles => self.roles = None,
|
||||
}
|
||||
}
|
||||
}
|
||||
24
crates/quark/src/impl/generic/users/bot.rs
Normal file
24
crates/quark/src/impl/generic/users/bot.rs
Normal file
@@ -0,0 +1,24 @@
|
||||
use nanoid::nanoid;
|
||||
|
||||
use crate::{
|
||||
models::{bot::FieldsBot, Bot},
|
||||
Database, Result,
|
||||
};
|
||||
|
||||
impl Bot {
|
||||
/// Remove a field from this object
|
||||
pub fn remove(&mut self, field: &FieldsBot) {
|
||||
match field {
|
||||
FieldsBot::Token => self.token = nanoid!(64),
|
||||
FieldsBot::InteractionsURL => {
|
||||
self.interactions_url.take();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Delete this bot
|
||||
pub async fn delete(&self, db: &Database) -> Result<()> {
|
||||
db.fetch_user(&self.id).await?.mark_deleted(db).await?;
|
||||
db.delete_bot(&self.id).await
|
||||
}
|
||||
}
|
||||
430
crates/quark/src/impl/generic/users/user.rs
Normal file
430
crates/quark/src/impl/generic/users/user.rs
Normal file
@@ -0,0 +1,430 @@
|
||||
use crate::events::client::EventV1;
|
||||
use crate::models::user::{
|
||||
Badges, FieldsUser, PartialUser, Presence, RelationshipStatus, User, UserHint,
|
||||
};
|
||||
use crate::permissions::defn::UserPerms;
|
||||
use crate::permissions::r#impl::user::get_relationship;
|
||||
use crate::{perms, Database, Error, Result};
|
||||
|
||||
use futures::try_join;
|
||||
use impl_ops::impl_op_ex_commutative;
|
||||
use okapi::openapi3::{SecurityScheme, SecuritySchemeData};
|
||||
use rocket_okapi::gen::OpenApiGenerator;
|
||||
use rocket_okapi::request::{OpenApiFromRequest, RequestHeaderInput};
|
||||
use std::ops;
|
||||
|
||||
impl_op_ex_commutative!(+ |a: &i32, b: &Badges| -> i32 { *a | *b as i32 });
|
||||
|
||||
impl User {
|
||||
/// 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);
|
||||
}
|
||||
|
||||
self.apply_options(partial.clone());
|
||||
|
||||
db.update_user(&self.id, &partial, remove.clone()).await?;
|
||||
|
||||
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(&mut self, field: &FieldsUser) {
|
||||
match field {
|
||||
FieldsUser::Avatar => self.avatar = None,
|
||||
FieldsUser::StatusText => {
|
||||
if let Some(x) = self.status.as_mut() {
|
||||
x.text = None;
|
||||
}
|
||||
}
|
||||
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 = None;
|
||||
}
|
||||
}
|
||||
FieldsUser::ProfileBackground => {
|
||||
if let Some(x) = self.profile.as_mut() {
|
||||
x.background = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Mutate the user object to remove redundant information
|
||||
pub fn foreign(mut self) -> User {
|
||||
self.profile = None;
|
||||
self.relations = None;
|
||||
|
||||
let mut badges = self.badges.unwrap_or(0);
|
||||
if let Ok(id) = ulid::Ulid::from_string(&self.id) {
|
||||
// Yes, this is hard-coded
|
||||
// No, I don't care + ratio
|
||||
if id.datetime().timestamp_millis() < 1629638578431 {
|
||||
badges = badges + Badges::EarlyAdopter;
|
||||
}
|
||||
}
|
||||
|
||||
self.badges = Some(badges);
|
||||
|
||||
if let Some(status) = &self.status {
|
||||
if let Some(presence) = &status.presence {
|
||||
if presence == &Presence::Invisible {
|
||||
self.status = None;
|
||||
self.online = Some(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Mutate the user object to include relationship (if it does not already exist)
|
||||
#[must_use]
|
||||
pub fn with_relationship(self, perspective: &User) -> User {
|
||||
let mut user = self.foreign();
|
||||
|
||||
if user.relationship.is_none() {
|
||||
user.relationship = Some(get_relationship(perspective, &user.id));
|
||||
}
|
||||
|
||||
user
|
||||
}
|
||||
|
||||
/// Mutate user object with given permission
|
||||
#[must_use]
|
||||
pub fn apply_permission(mut self, permission: &UserPerms) -> User {
|
||||
if !permission.get_view_profile() {
|
||||
self.status = None;
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Helper function to apply relationship and permission
|
||||
#[must_use]
|
||||
pub fn with_perspective(self, perspective: &User, permission: &UserPerms) -> User {
|
||||
self.with_relationship(perspective)
|
||||
.apply_permission(permission)
|
||||
}
|
||||
|
||||
/// Helper function to calculate perspective
|
||||
pub async fn with_auto_perspective(self, db: &Database, perspective: &User) -> User {
|
||||
let user = self.with_relationship(perspective);
|
||||
let permissions = perms(perspective).user(&user).calc_user(db).await;
|
||||
user.apply_permission(&permissions)
|
||||
}
|
||||
|
||||
/// Check whether two users have a mutual connection
|
||||
///
|
||||
/// This will check if user and user_b share a server or a group.
|
||||
pub async fn has_mutual_connection(&self, db: &Database, user_b: &str) -> Result<bool> {
|
||||
Ok(!db
|
||||
.fetch_mutual_server_ids(&self.id, user_b)
|
||||
.await?
|
||||
.is_empty()
|
||||
|| !db
|
||||
.fetch_mutual_channel_ids(&self.id, user_b)
|
||||
.await?
|
||||
.is_empty())
|
||||
}
|
||||
|
||||
/// Check if this user can acquire another server
|
||||
pub async fn can_acquire_server(&self, db: &Database) -> Result<bool> {
|
||||
// ! FIXME: hardcoded max server count
|
||||
Ok(db.fetch_server_count(&self.id).await? <= 100)
|
||||
}
|
||||
|
||||
/// Update a user's username
|
||||
pub async fn update_username(&mut self, db: &Database, username: String) -> Result<()> {
|
||||
let username = username.trim().to_string();
|
||||
|
||||
if db.is_username_taken(&username).await? {
|
||||
return Err(Error::UsernameTaken);
|
||||
}
|
||||
|
||||
self.update(
|
||||
db,
|
||||
PartialUser {
|
||||
username: Some(username),
|
||||
..Default::default()
|
||||
},
|
||||
vec![],
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Apply a certain relationship between two users
|
||||
pub async fn apply_relationship(
|
||||
&self,
|
||||
db: &Database,
|
||||
target: &mut User,
|
||||
local: RelationshipStatus,
|
||||
remote: RelationshipStatus,
|
||||
) -> Result<()> {
|
||||
if try_join!(
|
||||
db.set_relationship(&self.id, &target.id, &local),
|
||||
db.set_relationship(&target.id, &self.id, &remote)
|
||||
)
|
||||
.is_err()
|
||||
{
|
||||
return Err(Error::DatabaseError {
|
||||
operation: "update_one",
|
||||
with: "user",
|
||||
});
|
||||
}
|
||||
|
||||
EventV1::UserRelationship {
|
||||
id: target.id.clone(),
|
||||
user: self.clone().with_relationship(target),
|
||||
status: remote,
|
||||
}
|
||||
.private(target.id.clone())
|
||||
.await;
|
||||
|
||||
EventV1::UserRelationship {
|
||||
id: self.id.clone(),
|
||||
user: target.clone().with_relationship(self),
|
||||
status: local.clone(),
|
||||
}
|
||||
.private(self.id.clone())
|
||||
.await;
|
||||
|
||||
target.relationship.replace(local);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Add another user as a friend
|
||||
pub async fn add_friend(&self, db: &Database, target: &mut User) -> Result<()> {
|
||||
match get_relationship(self, &target.id) {
|
||||
RelationshipStatus::User => Err(Error::NoEffect),
|
||||
RelationshipStatus::Friend => Err(Error::AlreadyFriends),
|
||||
RelationshipStatus::Outgoing => Err(Error::AlreadySentRequest),
|
||||
RelationshipStatus::Blocked => Err(Error::Blocked),
|
||||
RelationshipStatus::BlockedOther => Err(Error::BlockedByOther),
|
||||
RelationshipStatus::Incoming => {
|
||||
self.apply_relationship(
|
||||
db,
|
||||
target,
|
||||
RelationshipStatus::Friend,
|
||||
RelationshipStatus::Friend,
|
||||
)
|
||||
.await
|
||||
}
|
||||
RelationshipStatus::None => {
|
||||
self.apply_relationship(
|
||||
db,
|
||||
target,
|
||||
RelationshipStatus::Outgoing,
|
||||
RelationshipStatus::Incoming,
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Remove another user as a friend
|
||||
pub async fn remove_friend(&self, db: &Database, target: &mut User) -> Result<()> {
|
||||
match get_relationship(self, &target.id) {
|
||||
RelationshipStatus::Friend
|
||||
| RelationshipStatus::Outgoing
|
||||
| RelationshipStatus::Incoming => {
|
||||
self.apply_relationship(
|
||||
db,
|
||||
target,
|
||||
RelationshipStatus::None,
|
||||
RelationshipStatus::None,
|
||||
)
|
||||
.await
|
||||
}
|
||||
_ => Err(Error::NoEffect),
|
||||
}
|
||||
}
|
||||
|
||||
/// Block another user
|
||||
pub async fn block_user(&self, db: &Database, target: &mut User) -> Result<()> {
|
||||
match get_relationship(self, &target.id) {
|
||||
RelationshipStatus::User | RelationshipStatus::Blocked => Err(Error::NoEffect),
|
||||
RelationshipStatus::BlockedOther => {
|
||||
self.apply_relationship(
|
||||
db,
|
||||
target,
|
||||
RelationshipStatus::Blocked,
|
||||
RelationshipStatus::Blocked,
|
||||
)
|
||||
.await
|
||||
}
|
||||
RelationshipStatus::None
|
||||
| RelationshipStatus::Friend
|
||||
| RelationshipStatus::Incoming
|
||||
| RelationshipStatus::Outgoing => {
|
||||
self.apply_relationship(
|
||||
db,
|
||||
target,
|
||||
RelationshipStatus::Blocked,
|
||||
RelationshipStatus::BlockedOther,
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Unblock another user
|
||||
pub async fn unblock_user(&self, db: &Database, target: &mut User) -> Result<()> {
|
||||
match get_relationship(self, &target.id) {
|
||||
RelationshipStatus::Blocked => match get_relationship(target, &self.id) {
|
||||
RelationshipStatus::Blocked => {
|
||||
self.apply_relationship(
|
||||
db,
|
||||
target,
|
||||
RelationshipStatus::BlockedOther,
|
||||
RelationshipStatus::Blocked,
|
||||
)
|
||||
.await
|
||||
}
|
||||
RelationshipStatus::BlockedOther => {
|
||||
self.apply_relationship(
|
||||
db,
|
||||
target,
|
||||
RelationshipStatus::None,
|
||||
RelationshipStatus::None,
|
||||
)
|
||||
.await
|
||||
}
|
||||
_ => Err(Error::InternalError),
|
||||
},
|
||||
_ => Err(Error::NoEffect),
|
||||
}
|
||||
}
|
||||
|
||||
/// Check whether this user has another user blocked
|
||||
pub fn has_blocked(&self, user: &str) -> bool {
|
||||
matches!(
|
||||
get_relationship(self, user),
|
||||
RelationshipStatus::Blocked | RelationshipStatus::BlockedOther
|
||||
)
|
||||
}
|
||||
|
||||
/// 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
|
||||
}
|
||||
|
||||
/// Find a user from a given token and hint
|
||||
#[async_recursion]
|
||||
pub async fn from_token(db: &Database, token: &str, hint: UserHint) -> Result<User> {
|
||||
match hint {
|
||||
UserHint::Bot => db.fetch_user(&db.fetch_bot_by_token(token).await?.id).await,
|
||||
UserHint::User => db.fetch_user_by_token(token).await,
|
||||
UserHint::Any => {
|
||||
if let Ok(user) = User::from_token(db, token, UserHint::User).await {
|
||||
Ok(user)
|
||||
} else {
|
||||
User::from_token(db, token, UserHint::Bot).await
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
use rauth::entities::Session;
|
||||
use rocket::http::Status;
|
||||
use rocket::request::{self, FromRequest, Outcome, Request};
|
||||
|
||||
#[rocket::async_trait]
|
||||
impl<'r> FromRequest<'r> for User {
|
||||
type Error = rauth::util::Error;
|
||||
|
||||
async fn from_request(request: &'r Request<'_>) -> request::Outcome<Self, Self::Error> {
|
||||
let user: &Option<User> = request
|
||||
.local_cache_async(async {
|
||||
let db = request
|
||||
.rocket()
|
||||
.state::<Database>()
|
||||
.expect("Database state not reachable!");
|
||||
|
||||
let header_bot_token = request
|
||||
.headers()
|
||||
.get("x-bot-token")
|
||||
.next()
|
||||
.map(|x| x.to_string());
|
||||
|
||||
if let Some(bot_token) = header_bot_token {
|
||||
if let Ok(user) = User::from_token(db, &bot_token, UserHint::Bot).await {
|
||||
return Some(user);
|
||||
}
|
||||
} else if let Outcome::Success(session) = request.guard::<Session>().await {
|
||||
// This uses a guard so can't really easily be refactored into from_token at this stage.
|
||||
if let Ok(user) = db.fetch_user(&session.user_id).await {
|
||||
return Some(user);
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
})
|
||||
.await;
|
||||
|
||||
if let Some(user) = user {
|
||||
Outcome::Success(user.clone())
|
||||
} else {
|
||||
Outcome::Failure((Status::Unauthorized, rauth::util::Error::InvalidSession))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'r> OpenApiFromRequest<'r> for User {
|
||||
fn from_request_input(
|
||||
_gen: &mut OpenApiGenerator,
|
||||
_name: String,
|
||||
_required: bool,
|
||||
) -> rocket_okapi::Result<RequestHeaderInput> {
|
||||
let mut requirements = schemars::Map::new();
|
||||
requirements.insert("Api Key".to_owned(), vec![]);
|
||||
|
||||
Ok(RequestHeaderInput::Security(
|
||||
"Api Key".to_owned(),
|
||||
SecurityScheme {
|
||||
data: SecuritySchemeData::ApiKey {
|
||||
name: "x-session-token".to_owned(),
|
||||
location: "header".to_owned(),
|
||||
},
|
||||
description: Some("Session Token".to_owned()),
|
||||
extensions: schemars::Map::new(),
|
||||
},
|
||||
requirements,
|
||||
))
|
||||
}
|
||||
}
|
||||
22
crates/quark/src/impl/generic/users/user_settings.rs
Normal file
22
crates/quark/src/impl/generic/users/user_settings.rs
Normal file
@@ -0,0 +1,22 @@
|
||||
use crate::{events::client::EventV1, models::UserSettings, Database, Result};
|
||||
|
||||
#[async_trait]
|
||||
pub trait UserSettingsImpl {
|
||||
async fn set(self, db: &Database, user: &str) -> Result<()>;
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl UserSettingsImpl for UserSettings {
|
||||
async fn set(self, db: &Database, user: &str) -> Result<()> {
|
||||
db.set_user_settings(user, &self).await?;
|
||||
|
||||
EventV1::UserSettingsUpdate {
|
||||
id: user.to_string(),
|
||||
update: self,
|
||||
}
|
||||
.private(user.to_string())
|
||||
.await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
3
crates/quark/src/impl/mod.rs
Normal file
3
crates/quark/src/impl/mod.rs
Normal file
@@ -0,0 +1,3 @@
|
||||
pub mod dummy;
|
||||
pub mod generic;
|
||||
pub mod mongo;
|
||||
26
crates/quark/src/impl/mongo/admin/migrations.rs
Normal file
26
crates/quark/src/impl/mongo/admin/migrations.rs
Normal file
@@ -0,0 +1,26 @@
|
||||
use crate::{AbstractMigrations, Result};
|
||||
|
||||
use super::super::MongoDb;
|
||||
|
||||
mod init;
|
||||
mod scripts;
|
||||
|
||||
#[async_trait]
|
||||
impl AbstractMigrations for MongoDb {
|
||||
async fn migrate_database(&self) -> Result<()> {
|
||||
info!("Migrating the database.");
|
||||
|
||||
let list = self
|
||||
.list_database_names(None, None)
|
||||
.await
|
||||
.expect("Failed to fetch database names.");
|
||||
|
||||
if list.iter().any(|x| x == "revolt") {
|
||||
scripts::migrate_database(self).await;
|
||||
} else {
|
||||
init::create_database(self).await;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
187
crates/quark/src/impl/mongo/admin/migrations/init.rs
Normal file
187
crates/quark/src/impl/mongo/admin/migrations/init.rs
Normal file
@@ -0,0 +1,187 @@
|
||||
use crate::r#impl::mongo::MongoDb;
|
||||
|
||||
use super::scripts::LATEST_REVISION;
|
||||
|
||||
use log::info;
|
||||
use mongodb::bson::doc;
|
||||
use mongodb::options::CreateCollectionOptions;
|
||||
|
||||
pub async fn create_database(db: &MongoDb) {
|
||||
info!("Creating database.");
|
||||
let db = db.db();
|
||||
|
||||
db.create_collection("accounts", None)
|
||||
.await
|
||||
.expect("Failed to create accounts collection.");
|
||||
|
||||
db.create_collection("users", None)
|
||||
.await
|
||||
.expect("Failed to create users collection.");
|
||||
|
||||
db.create_collection("channels", None)
|
||||
.await
|
||||
.expect("Failed to create channels collection.");
|
||||
|
||||
db.create_collection("messages", None)
|
||||
.await
|
||||
.expect("Failed to create messages collection.");
|
||||
|
||||
db.create_collection("servers", None)
|
||||
.await
|
||||
.expect("Failed to create servers collection.");
|
||||
|
||||
db.create_collection("server_members", None)
|
||||
.await
|
||||
.expect("Failed to create server_members collection.");
|
||||
|
||||
db.create_collection("server_bans", None)
|
||||
.await
|
||||
.expect("Failed to create server_bans collection.");
|
||||
|
||||
db.create_collection("channel_invites", None)
|
||||
.await
|
||||
.expect("Failed to create channel_invites collection.");
|
||||
|
||||
db.create_collection("channel_unreads", None)
|
||||
.await
|
||||
.expect("Failed to create channel_unreads collection.");
|
||||
|
||||
db.create_collection("migrations", None)
|
||||
.await
|
||||
.expect("Failed to create migrations collection.");
|
||||
|
||||
db.create_collection("attachments", None)
|
||||
.await
|
||||
.expect("Failed to create attachments collection.");
|
||||
|
||||
db.create_collection("user_settings", None)
|
||||
.await
|
||||
.expect("Failed to create user_settings collection.");
|
||||
|
||||
db.create_collection("bots", None)
|
||||
.await
|
||||
.expect("Failed to create bots collection.");
|
||||
|
||||
db.create_collection(
|
||||
"pubsub",
|
||||
CreateCollectionOptions::builder()
|
||||
.capped(true)
|
||||
.size(1_000_000)
|
||||
.build(),
|
||||
)
|
||||
.await
|
||||
.expect("Failed to create pubsub collection.");
|
||||
|
||||
db.run_command(
|
||||
doc! {
|
||||
"createIndexes": "users",
|
||||
"indexes": [
|
||||
{
|
||||
"key": {
|
||||
"username": 1_i32
|
||||
},
|
||||
"name": "username",
|
||||
"unique": true,
|
||||
"collation": {
|
||||
"locale": "en",
|
||||
"strength": 2_i32
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.expect("Failed to create username index.");
|
||||
|
||||
db.run_command(
|
||||
doc! {
|
||||
"createIndexes": "messages",
|
||||
"indexes": [
|
||||
{
|
||||
"key": {
|
||||
"content": "text"
|
||||
},
|
||||
"name": "content"
|
||||
},
|
||||
{
|
||||
"key": {
|
||||
"channel": 1_i32
|
||||
},
|
||||
"name": "channel"
|
||||
},
|
||||
{
|
||||
"key": {
|
||||
"channel": 1_i32,
|
||||
"_id": 1_i32
|
||||
},
|
||||
"name": "channel_id_compound"
|
||||
}
|
||||
]
|
||||
},
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.expect("Failed to create message index.");
|
||||
|
||||
db.run_command(
|
||||
doc! {
|
||||
"createIndexes": "channel_unreads",
|
||||
"indexes": [
|
||||
{
|
||||
"key": {
|
||||
"_id.channel": 1_i32,
|
||||
"_id.user": 1_i32,
|
||||
},
|
||||
"name": "compound_id"
|
||||
},
|
||||
{
|
||||
"key": {
|
||||
"_id.user": 1_i32,
|
||||
},
|
||||
"name": "user_id"
|
||||
}
|
||||
]
|
||||
},
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.expect("Failed to create channel_unreads index.");
|
||||
|
||||
db.run_command(
|
||||
doc! {
|
||||
"createIndexes": "server_members",
|
||||
"indexes": [
|
||||
{
|
||||
"key": {
|
||||
"_id.server": 1_i32,
|
||||
"_id.user": 1_i32,
|
||||
},
|
||||
"name": "compound_id"
|
||||
},
|
||||
{
|
||||
"key": {
|
||||
"_id.user": 1_i32,
|
||||
},
|
||||
"name": "user_id"
|
||||
}
|
||||
]
|
||||
},
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.expect("Failed to create server_members index.");
|
||||
|
||||
db.collection("migrations")
|
||||
.insert_one(
|
||||
doc! {
|
||||
"_id": 0_i32,
|
||||
"revision": LATEST_REVISION
|
||||
},
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.expect("Failed to save migration info.");
|
||||
|
||||
info!("Created database.");
|
||||
}
|
||||
610
crates/quark/src/impl/mongo/admin/migrations/scripts.rs
Normal file
610
crates/quark/src/impl/mongo/admin/migrations/scripts.rs
Normal file
@@ -0,0 +1,610 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use bson::Bson;
|
||||
use futures::StreamExt;
|
||||
use log::info;
|
||||
use mongodb::{
|
||||
bson::{doc, from_bson, from_document, to_document, Document},
|
||||
options::FindOptions,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{r#impl::mongo::MongoDb, Permission, DEFAULT_PERMISSION_SERVER};
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct MigrationInfo {
|
||||
_id: i32,
|
||||
revision: i32,
|
||||
}
|
||||
|
||||
pub const LATEST_REVISION: i32 = 15;
|
||||
|
||||
pub async fn migrate_database(db: &MongoDb) {
|
||||
let migrations = db.col::<Document>("migrations");
|
||||
let data = migrations
|
||||
.find_one(None, None)
|
||||
.await
|
||||
.expect("Failed to fetch migration data.");
|
||||
|
||||
if let Some(doc) = data {
|
||||
let info: MigrationInfo =
|
||||
from_document(doc).expect("Failed to read migration information.");
|
||||
|
||||
let revision = run_migrations(db, info.revision).await;
|
||||
|
||||
migrations
|
||||
.update_one(
|
||||
doc! {
|
||||
"_id": info._id
|
||||
},
|
||||
doc! {
|
||||
"$set": {
|
||||
"revision": revision
|
||||
}
|
||||
},
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.expect("Failed to commit migration information.");
|
||||
|
||||
info!("Migration complete. Currently at revision {}.", revision);
|
||||
} else {
|
||||
panic!("Database was configured incorrectly, possibly because initalization failed.")
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn run_migrations(db: &MongoDb, revision: i32) -> i32 {
|
||||
info!("Starting database migration.");
|
||||
|
||||
if revision <= 0 {
|
||||
info!("Running migration [revision 0]: Test migration system.");
|
||||
}
|
||||
|
||||
if revision <= 1 {
|
||||
info!("Running migration [revision 1 / 2021-04-24]: Migrate to Autumn v1.0.0.");
|
||||
|
||||
let messages = db.col::<Document>("messages");
|
||||
let attachments = db.col::<Document>("attachments");
|
||||
|
||||
messages
|
||||
.update_many(
|
||||
doc! { "attachment": { "$exists": 1_i32 } },
|
||||
doc! { "$set": { "attachment.tag": "attachments", "attachment.size": 0_i32 } },
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.expect("Failed to update messages.");
|
||||
|
||||
attachments
|
||||
.update_many(
|
||||
doc! {},
|
||||
doc! { "$set": { "tag": "attachments", "size": 0_i32 } },
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.expect("Failed to update attachments.");
|
||||
}
|
||||
|
||||
if revision <= 2 {
|
||||
info!("Running migration [revision 2 / 2021-05-08]: Add servers collection.");
|
||||
|
||||
db.db()
|
||||
.create_collection("servers", None)
|
||||
.await
|
||||
.expect("Failed to create servers collection.");
|
||||
}
|
||||
|
||||
if revision <= 3 {
|
||||
info!("Running migration [revision 3 / 2021-05-25]: Support multiple file uploads, add channel_unreads and user_settings.");
|
||||
|
||||
let messages = db.col::<Document>("messages");
|
||||
let mut cursor = messages
|
||||
.find(
|
||||
doc! {
|
||||
"attachment": {
|
||||
"$exists": 1_i32
|
||||
}
|
||||
},
|
||||
FindOptions::builder()
|
||||
.projection(doc! {
|
||||
"_id": 1_i32,
|
||||
"attachments": [ "$attachment" ]
|
||||
})
|
||||
.build(),
|
||||
)
|
||||
.await
|
||||
.expect("Failed to fetch messages.");
|
||||
|
||||
while let Some(result) = cursor.next().await {
|
||||
let doc = result.unwrap();
|
||||
let id = doc.get_str("_id").unwrap();
|
||||
let attachments = doc.get_array("attachments").unwrap();
|
||||
|
||||
messages
|
||||
.update_one(
|
||||
doc! { "_id": id },
|
||||
doc! { "$unset": { "attachment": 1_i32 }, "$set": { "attachments": attachments } },
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
db.db()
|
||||
.create_collection("channel_unreads", None)
|
||||
.await
|
||||
.expect("Failed to create channel_unreads collection.");
|
||||
|
||||
db.db()
|
||||
.create_collection("user_settings", None)
|
||||
.await
|
||||
.expect("Failed to create user_settings collection.");
|
||||
}
|
||||
|
||||
if revision <= 4 {
|
||||
info!("Running migration [revision 4 / 2021-06-01]: Add more server collections.");
|
||||
|
||||
db.db()
|
||||
.create_collection("server_members", None)
|
||||
.await
|
||||
.expect("Failed to create server_members collection.");
|
||||
|
||||
db.db()
|
||||
.create_collection("server_bans", None)
|
||||
.await
|
||||
.expect("Failed to create server_bans collection.");
|
||||
|
||||
db.db()
|
||||
.create_collection("channel_invites", None)
|
||||
.await
|
||||
.expect("Failed to create channel_invites collection.");
|
||||
}
|
||||
|
||||
if revision <= 5 {
|
||||
info!("Running migration [revision 5 / 2021-06-26]: Add permissions.");
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct Server {
|
||||
pub default_permissions: (i32, i32),
|
||||
}
|
||||
|
||||
let server = Server {
|
||||
default_permissions: (0_i32, 0_i32),
|
||||
};
|
||||
|
||||
db.col::<Document>("servers")
|
||||
.update_many(
|
||||
doc! {},
|
||||
doc! {
|
||||
"$set": to_document(&server).unwrap()
|
||||
},
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.expect("Failed to migrate servers.");
|
||||
}
|
||||
|
||||
if revision <= 6 {
|
||||
info!("Running migration [revision 6 / 2021-07-09]: Add message text index.");
|
||||
|
||||
db.db()
|
||||
.run_command(
|
||||
doc! {
|
||||
"createIndexes": "messages",
|
||||
"indexes": [
|
||||
{
|
||||
"key": {
|
||||
"content": "text"
|
||||
},
|
||||
"name": "content"
|
||||
}
|
||||
]
|
||||
},
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.expect("Failed to create message index.");
|
||||
}
|
||||
|
||||
if revision <= 7 {
|
||||
info!("Running migration [revision 7 / 2021-08-11]: Add message text index.");
|
||||
|
||||
db.db()
|
||||
.create_collection("bots", None)
|
||||
.await
|
||||
.expect("Failed to create bots collection.");
|
||||
}
|
||||
|
||||
if revision <= 8 {
|
||||
info!("Running migration [revision 8 / 2021-09-10]: Update to rAuth version 1.");
|
||||
|
||||
db.db()
|
||||
.run_command(
|
||||
doc! {
|
||||
"dropIndexes": "accounts",
|
||||
"index": ["email", "email_normalised"]
|
||||
},
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.expect("Failed to delete legacy account indexes.");
|
||||
|
||||
let col = db.col::<Document>("sessions");
|
||||
let mut cursor = db
|
||||
.col::<Document>("accounts")
|
||||
.find(doc! {}, None)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
while let Some(doc) = cursor.next().await {
|
||||
if let Ok(account) = doc {
|
||||
let id = account.get_str("_id").unwrap();
|
||||
if let Some(sessions) = account.get("sessions") {
|
||||
#[derive(Deserialize)]
|
||||
struct Session {
|
||||
id: String,
|
||||
token: String,
|
||||
friendly_name: String,
|
||||
subscription: Option<Document>,
|
||||
}
|
||||
|
||||
let sessions = from_bson::<Vec<Session>>(sessions.clone()).unwrap();
|
||||
for session in sessions {
|
||||
info!("Converting session {} to new format.", &session.id);
|
||||
|
||||
let mut doc = doc! {
|
||||
"_id": session.id,
|
||||
"token": session.token,
|
||||
"user_id": id,
|
||||
"name": session.friendly_name,
|
||||
};
|
||||
|
||||
if let Some(sub) = session.subscription {
|
||||
doc.insert("subscription", sub);
|
||||
}
|
||||
|
||||
col.insert_one(doc, None).await.ok();
|
||||
}
|
||||
} else {
|
||||
info!("Account doesn't have any sessions!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
db.col::<Document>("accounts")
|
||||
.update_many(
|
||||
doc! {},
|
||||
doc! {
|
||||
"$unset": {
|
||||
"sessions": 1_i32,
|
||||
},
|
||||
"$set": {
|
||||
"mfa": {
|
||||
"recovery_codes": []
|
||||
}
|
||||
}
|
||||
},
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
if revision <= 9 {
|
||||
info!("Running migration [revision 9 / 2021-09-14]: Switch from last_message to last_message_id.");
|
||||
|
||||
let mut cursor = db
|
||||
.col::<Document>("channels")
|
||||
.find(doc! {}, None)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
while let Some(doc) = cursor.next().await {
|
||||
if let Ok(channel) = doc {
|
||||
let channel_id = channel.get_str("_id").unwrap();
|
||||
if let Some(last_message) = channel.get("last_message") {
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct Obj {
|
||||
#[serde(rename = "_id")]
|
||||
id: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
#[serde(untagged)]
|
||||
pub enum LastMessage {
|
||||
Obj(Obj),
|
||||
Id(String),
|
||||
}
|
||||
|
||||
let lm = from_bson::<LastMessage>(last_message.clone()).unwrap();
|
||||
let id = match lm {
|
||||
LastMessage::Obj(Obj { id }) => id,
|
||||
LastMessage::Id(id) => id,
|
||||
};
|
||||
|
||||
info!("Converting session {} to new format.", &channel_id);
|
||||
db.col::<Document>("channels")
|
||||
.update_one(
|
||||
doc! {
|
||||
"_id": channel_id
|
||||
},
|
||||
doc! {
|
||||
"$set": {
|
||||
"last_message_id": id
|
||||
},
|
||||
"$unset": {
|
||||
"last_message": 1_i32,
|
||||
}
|
||||
},
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
} else {
|
||||
info!("{} has no last_message.", &channel_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if revision <= 10 {
|
||||
info!("Running migration [revision 10 / 2021-11-01]: Remove nonce values on channels and servers.");
|
||||
|
||||
db.col::<Document>("servers")
|
||||
.update_many(
|
||||
doc! {},
|
||||
doc! {
|
||||
"$unset": {
|
||||
"nonce": 1_i32,
|
||||
}
|
||||
},
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
db.col::<Document>("channels")
|
||||
.update_many(
|
||||
doc! {},
|
||||
doc! {
|
||||
"$unset": {
|
||||
"nonce": 1_i32,
|
||||
}
|
||||
},
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
if revision <= 11 {
|
||||
info!("Running migration [revision 11 / 2021-11-14]: Add indexes to database.");
|
||||
|
||||
db.db()
|
||||
.run_command(
|
||||
doc! {
|
||||
"createIndexes": "messages",
|
||||
"indexes": [
|
||||
{
|
||||
"key": {
|
||||
"channel": 1_i32
|
||||
},
|
||||
"name": "channel"
|
||||
}
|
||||
]
|
||||
},
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.expect("Failed to create message index.");
|
||||
|
||||
db.db()
|
||||
.run_command(
|
||||
doc! {
|
||||
"createIndexes": "channel_unreads",
|
||||
"indexes": [
|
||||
{
|
||||
"key": {
|
||||
"_id.channel": 1_i32,
|
||||
"_id.user": 1_i32,
|
||||
},
|
||||
"name": "compound_id"
|
||||
},
|
||||
{
|
||||
"key": {
|
||||
"_id.user": 1_i32,
|
||||
},
|
||||
"name": "user_id"
|
||||
}
|
||||
]
|
||||
},
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.expect("Failed to create channel_unreads index.");
|
||||
|
||||
db.db()
|
||||
.run_command(
|
||||
doc! {
|
||||
"createIndexes": "server_members",
|
||||
"indexes": [
|
||||
{
|
||||
"key": {
|
||||
"_id.server": 1_i32,
|
||||
"_id.user": 1_i32,
|
||||
},
|
||||
"name": "compound_id"
|
||||
},
|
||||
{
|
||||
"key": {
|
||||
"_id.user": 1_i32,
|
||||
},
|
||||
"name": "user_id"
|
||||
}
|
||||
]
|
||||
},
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.expect("Failed to create server_members index.");
|
||||
}
|
||||
|
||||
if revision <= 12 {
|
||||
info!("Running migration [revision 12 / 2021-11-21]: Add indexes to database.");
|
||||
|
||||
db.db()
|
||||
.run_command(
|
||||
doc! {
|
||||
"createIndexes": "messages",
|
||||
"indexes": [
|
||||
{
|
||||
"key": {
|
||||
"channel": 1_i32,
|
||||
"_id": 1_i32
|
||||
},
|
||||
"name": "channel_id_compound"
|
||||
}
|
||||
]
|
||||
},
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.expect("Failed to create message index.");
|
||||
}
|
||||
|
||||
if revision <= 13 {
|
||||
info!("Running migration [revision 13 / 22-02-2022]: Wipe legacy permission values.");
|
||||
|
||||
warn!("This is a destructive operation and will wipe existing permission data (excl. defaults for SendMessage).");
|
||||
warn!("Taking a backup is advised.");
|
||||
warn!("Continuing in 10 seconds...");
|
||||
async_std::task::sleep(Duration::from_secs(10)).await;
|
||||
|
||||
let servers = db.col::<Document>("servers");
|
||||
let mut cursor = servers.find(doc! {}, None).await.unwrap();
|
||||
|
||||
while let Some(Ok(mut document)) = cursor.next().await {
|
||||
let id = document.get_str("_id").unwrap().to_string();
|
||||
info!("Updating server {id}");
|
||||
|
||||
let mut update = doc! {};
|
||||
|
||||
// Try to pluck channel permission SendMessage (0x2)
|
||||
// Structure of default_permissions used to be [server, channel]
|
||||
let has_send = document
|
||||
.get_array("default_permissions")
|
||||
.map(|x| {
|
||||
x.get(1)
|
||||
.map(|x| x.as_i32().map(|x| (x as u32 & 0x2) == 0x2))
|
||||
})
|
||||
.ok()
|
||||
.flatten()
|
||||
.flatten()
|
||||
.unwrap_or_default();
|
||||
|
||||
update.insert(
|
||||
"default_permissions",
|
||||
(*DEFAULT_PERMISSION_SERVER
|
||||
// Remove Send Message permission if it wasn't originally granted
|
||||
^ (if has_send {
|
||||
0
|
||||
} else {
|
||||
Permission::SendMessage as u64
|
||||
})) as i64,
|
||||
);
|
||||
|
||||
if let Some(Bson::Document(mut roles)) = document.remove("roles") {
|
||||
for role in roles.keys().cloned().collect::<Vec<String>>() {
|
||||
if let Some(Bson::Document(role)) = roles.get_mut(role) {
|
||||
role.insert(
|
||||
"permissions",
|
||||
doc! {
|
||||
"a": 0_i64,
|
||||
"d": 0_i64,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
update.insert("roles", roles);
|
||||
}
|
||||
|
||||
servers
|
||||
.update_one(doc! { "_id": id }, doc! { "$set": update }, None)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
let channels = db.col::<Document>("channels");
|
||||
let mut cursor = channels.find(doc! {}, None).await.unwrap();
|
||||
|
||||
while let Some(Ok(document)) = cursor.next().await {
|
||||
let id = document.get_str("_id").unwrap().to_string();
|
||||
info!("Updating channel {id}");
|
||||
|
||||
let mut unset = doc! {
|
||||
"permissions": 1_i32,
|
||||
"role_permissions": 1_i32,
|
||||
};
|
||||
|
||||
// Try to pluck channel permission SendMessage (0x2)
|
||||
let has_send = document
|
||||
.get_i32("default_permissions")
|
||||
.map(|x| (x as u32 & 0x2) == 0x2)
|
||||
.unwrap_or(true);
|
||||
|
||||
if has_send {
|
||||
// Let parent permissions fall through.
|
||||
unset.insert("default_permissions", 1_i32);
|
||||
}
|
||||
|
||||
let mut update = doc! {
|
||||
"$unset": unset
|
||||
};
|
||||
|
||||
if !has_send {
|
||||
// Block send message permission.
|
||||
update.insert(
|
||||
"$set",
|
||||
doc! {
|
||||
"default_permissions": {
|
||||
"a": 0_i64,
|
||||
"d": Permission::SendMessage as i64
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
channels
|
||||
.update_one(doc! { "_id": id }, update, None)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
if revision <= 14 {
|
||||
info!("Running migration [revision 14 / 21-04-2022]: Split content into content and system fields.");
|
||||
|
||||
db.col::<Document>("messages")
|
||||
.update_many(
|
||||
doc! {
|
||||
"content": {
|
||||
"$type": "object"
|
||||
}
|
||||
},
|
||||
doc! {
|
||||
"$rename": {
|
||||
"content": "system"
|
||||
}
|
||||
},
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
// Need to migrate fields on attachments, change `user_id`, `object_id`, etc to `parent`.
|
||||
|
||||
// Reminder to update LATEST_REVISION when adding new migrations.
|
||||
LATEST_REVISION
|
||||
}
|
||||
125
crates/quark/src/impl/mongo/autumn/attachment.rs
Normal file
125
crates/quark/src/impl/mongo/autumn/attachment.rs
Normal file
@@ -0,0 +1,125 @@
|
||||
use bson::Document;
|
||||
|
||||
use crate::models::attachment::File;
|
||||
use crate::{AbstractAttachment, Error, Result};
|
||||
|
||||
use super::super::MongoDb;
|
||||
|
||||
static COL: &str = "attachments";
|
||||
|
||||
impl MongoDb {
|
||||
pub async fn delete_many_attachments(&self, projection: Document) -> Result<()> {
|
||||
self.col::<Document>(COL)
|
||||
.update_many(
|
||||
projection,
|
||||
doc! {
|
||||
"$set": {
|
||||
"deleted": true
|
||||
}
|
||||
},
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.map(|_| ())
|
||||
.map_err(|_| Error::DatabaseError {
|
||||
operation: "update_many",
|
||||
with: "attachment",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl AbstractAttachment for MongoDb {
|
||||
async fn find_and_use_attachment(
|
||||
&self,
|
||||
attachment_id: &str,
|
||||
tag: &str,
|
||||
parent_type: &str,
|
||||
parent_id: &str,
|
||||
) -> Result<File> {
|
||||
let key = format!("{}_id", parent_type);
|
||||
match self
|
||||
.find_one::<File>(
|
||||
COL,
|
||||
doc! {
|
||||
"_id": attachment_id,
|
||||
"tag": tag,
|
||||
&key: {
|
||||
"$exists": false
|
||||
}
|
||||
},
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(file) => {
|
||||
self.col::<Document>(COL)
|
||||
.update_one(
|
||||
doc! {
|
||||
"_id": &file.id
|
||||
},
|
||||
doc! {
|
||||
"$set": {
|
||||
key: parent_id
|
||||
}
|
||||
},
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.map_err(|_| Error::DatabaseError {
|
||||
operation: "update_one",
|
||||
with: "attachment",
|
||||
})?;
|
||||
|
||||
Ok(file)
|
||||
}
|
||||
Err(Error::NotFound) => Err(Error::UnknownAttachment),
|
||||
Err(error) => Err(error),
|
||||
}
|
||||
}
|
||||
|
||||
async fn insert_attachment(&self, attachment: &File) -> Result<()> {
|
||||
self.insert_one(COL, attachment).await.map(|_| ())
|
||||
}
|
||||
|
||||
async fn mark_attachment_as_reported(&self, id: &str) -> Result<()> {
|
||||
self.col::<Document>(COL)
|
||||
.update_one(
|
||||
doc! {
|
||||
"_id": id
|
||||
},
|
||||
doc! {
|
||||
"$set": {
|
||||
"reported": true
|
||||
}
|
||||
},
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.map(|_| ())
|
||||
.map_err(|_| Error::DatabaseError {
|
||||
operation: "update_one",
|
||||
with: "attachment",
|
||||
})
|
||||
}
|
||||
|
||||
async fn mark_attachment_as_deleted(&self, id: &str) -> Result<()> {
|
||||
self.col::<Document>(COL)
|
||||
.update_one(
|
||||
doc! {
|
||||
"_id": id
|
||||
},
|
||||
doc! {
|
||||
"$set": {
|
||||
"deleted": true
|
||||
}
|
||||
},
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.map(|_| ())
|
||||
.map_err(|_| Error::DatabaseError {
|
||||
operation: "update_one",
|
||||
with: "attachment",
|
||||
})
|
||||
}
|
||||
}
|
||||
323
crates/quark/src/impl/mongo/channels/channel.rs
Normal file
323
crates/quark/src/impl/mongo/channels/channel.rs
Normal file
@@ -0,0 +1,323 @@
|
||||
use std::collections::HashSet;
|
||||
|
||||
use bson::{Bson, Document};
|
||||
|
||||
use crate::models::channel::{Channel, FieldsChannel, PartialChannel};
|
||||
use crate::r#impl::mongo::IntoDocumentPath;
|
||||
use crate::{AbstractChannel, AbstractServer, Error, OverrideField, Result};
|
||||
|
||||
use super::super::MongoDb;
|
||||
|
||||
static COL: &str = "channels";
|
||||
|
||||
impl MongoDb {
|
||||
pub async fn delete_associated_channel_objects(&self, id: Bson) -> Result<()> {
|
||||
// Delete all invites to these channels.
|
||||
self.col::<Document>("channel_invites")
|
||||
.delete_many(
|
||||
doc! {
|
||||
"channel": &id
|
||||
},
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.map_err(|_| Error::DatabaseError {
|
||||
operation: "delete_many",
|
||||
with: "channel_invites",
|
||||
})?;
|
||||
|
||||
// Delete unread message objects on channels.
|
||||
self.col::<Document>("channel_unreads")
|
||||
.delete_many(
|
||||
doc! {
|
||||
"_id.channel": &id
|
||||
},
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.map_err(|_| Error::DatabaseError {
|
||||
operation: "delete_many",
|
||||
with: "channel_unreads",
|
||||
})
|
||||
.map(|_| ())
|
||||
|
||||
// update many attachments with parent id
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl AbstractChannel for MongoDb {
|
||||
async fn fetch_channel(&self, id: &str) -> Result<Channel> {
|
||||
self.find_one_by_id(COL, id).await
|
||||
}
|
||||
|
||||
async fn fetch_channels<'a>(&self, ids: &'a [String]) -> Result<Vec<Channel>> {
|
||||
self.find(
|
||||
COL,
|
||||
doc! {
|
||||
"_id": {
|
||||
"$in": ids
|
||||
}
|
||||
},
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn insert_channel(&self, channel: &Channel) -> Result<()> {
|
||||
self.insert_one(COL, channel).await.map(|_| ())
|
||||
}
|
||||
|
||||
async fn update_channel(
|
||||
&self,
|
||||
id: &str,
|
||||
channel: &PartialChannel,
|
||||
remove: Vec<FieldsChannel>,
|
||||
) -> Result<()> {
|
||||
self.update_one_by_id(
|
||||
COL,
|
||||
id,
|
||||
channel,
|
||||
remove.iter().map(|x| x as &dyn IntoDocumentPath).collect(),
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.map(|_| ())
|
||||
}
|
||||
|
||||
async fn delete_channel(&self, channel: &Channel) -> Result<()> {
|
||||
let id = channel.id().to_string();
|
||||
let server_id = match channel {
|
||||
Channel::TextChannel { server, .. } | Channel::VoiceChannel { server, .. } => {
|
||||
Some(server)
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
|
||||
// Delete invites and unreads.
|
||||
self.delete_associated_channel_objects(Bson::String(id.to_string()))
|
||||
.await?;
|
||||
|
||||
// Delete messages.
|
||||
self.delete_bulk_messages(doc! {
|
||||
"channel": &id
|
||||
})
|
||||
.await?;
|
||||
|
||||
// Remove from server object.
|
||||
if let Some(server) = server_id {
|
||||
let server = self.fetch_server(server).await?;
|
||||
let mut update = doc! {
|
||||
"$pull": {
|
||||
"channels": &id
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(sys) = &server.system_messages {
|
||||
let mut unset = doc! {};
|
||||
|
||||
if let Some(cid) = &sys.user_joined {
|
||||
if &id == cid {
|
||||
unset.insert("system_messages.user_joined", 1_i32);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(cid) = &sys.user_left {
|
||||
if &id == cid {
|
||||
unset.insert("system_messages.user_left", 1_i32);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(cid) = &sys.user_kicked {
|
||||
if &id == cid {
|
||||
unset.insert("system_messages.user_kicked", 1_i32);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(cid) = &sys.user_banned {
|
||||
if &id == cid {
|
||||
unset.insert("system_messages.user_banned", 1_i32);
|
||||
}
|
||||
}
|
||||
|
||||
if !unset.is_empty() {
|
||||
update.insert("$unset", unset);
|
||||
}
|
||||
}
|
||||
|
||||
self.col::<Document>("servers")
|
||||
.update_one(
|
||||
doc! {
|
||||
"_id": server.id
|
||||
},
|
||||
update,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.map_err(|_| Error::DatabaseError {
|
||||
operation: "update_one",
|
||||
with: "servers",
|
||||
})?;
|
||||
}
|
||||
|
||||
// Delete associated attachments
|
||||
self.delete_many_attachments(doc! {
|
||||
"object_id": &id
|
||||
})
|
||||
.await?;
|
||||
|
||||
// Delete the channel itself
|
||||
self.delete_one_by_id(COL, &id).await.map(|_| ())
|
||||
}
|
||||
|
||||
async fn find_direct_messages(&self, user_id: &str) -> Result<Vec<Channel>> {
|
||||
self.find(
|
||||
COL,
|
||||
doc! {
|
||||
"$or": [
|
||||
{
|
||||
"$or": [
|
||||
{
|
||||
"channel_type": "DirectMessage"
|
||||
},
|
||||
{
|
||||
"channel_type": "Group"
|
||||
}
|
||||
],
|
||||
"recipients": user_id
|
||||
},
|
||||
{
|
||||
"channel_type": "SavedMessages",
|
||||
"user": user_id
|
||||
}
|
||||
]
|
||||
},
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn find_saved_messages_channel(&self, user_id: &str) -> Result<Channel> {
|
||||
self.find_one(
|
||||
COL,
|
||||
doc! {
|
||||
"channel_type": "SavedMessages",
|
||||
"user": user_id
|
||||
},
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn find_direct_message_channel(&self, user_a: &str, user_b: &str) -> Result<Channel> {
|
||||
self.find_one(
|
||||
COL,
|
||||
if user_a == user_b {
|
||||
doc! {
|
||||
"channel_type": "SavedMessages",
|
||||
"user": user_a
|
||||
}
|
||||
} else {
|
||||
doc! {
|
||||
"channel_type": "DirectMessage",
|
||||
"recipients": {
|
||||
"$all": [ user_a, user_b ]
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn add_user_to_group(&self, channel: &str, user: &str) -> Result<()> {
|
||||
self.col::<Document>(COL)
|
||||
.update_one(
|
||||
doc! {
|
||||
"_id": channel
|
||||
},
|
||||
doc! {
|
||||
"$push": {
|
||||
"recipients": user
|
||||
}
|
||||
},
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.map(|_| ())
|
||||
.map_err(|_| Error::DatabaseError {
|
||||
operation: "update_one",
|
||||
with: "channel",
|
||||
})
|
||||
}
|
||||
|
||||
async fn remove_user_from_group(&self, channel: &str, user: &str) -> Result<()> {
|
||||
self.col::<Document>(COL)
|
||||
.update_one(
|
||||
doc! {
|
||||
"_id": channel
|
||||
},
|
||||
doc! {
|
||||
"$pull": {
|
||||
"recipients": user
|
||||
}
|
||||
},
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.map(|_| ())
|
||||
.map_err(|_| Error::DatabaseError {
|
||||
operation: "update_one",
|
||||
with: "channel",
|
||||
})
|
||||
}
|
||||
|
||||
async fn set_channel_role_permission(
|
||||
&self,
|
||||
channel: &str,
|
||||
role: &str,
|
||||
permissions: OverrideField,
|
||||
) -> Result<()> {
|
||||
self.col::<Document>(COL)
|
||||
.update_one(
|
||||
doc! { "_id": channel },
|
||||
doc! {
|
||||
"$set": {
|
||||
"role_permissions.".to_owned() + role: permissions
|
||||
}
|
||||
},
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.map(|_| ())
|
||||
.map_err(|_| Error::DatabaseError {
|
||||
operation: "update_one",
|
||||
with: "channel",
|
||||
})
|
||||
}
|
||||
|
||||
async fn check_channels_exist(&self, channels: &HashSet<String>) -> Result<bool> {
|
||||
let count = channels.len() as u64;
|
||||
self.col::<Document>(COL)
|
||||
.count_documents(
|
||||
doc! {
|
||||
"_id": {
|
||||
"$in": channels.iter().cloned().collect::<Vec<String>>()
|
||||
}
|
||||
},
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.map(|x| x == count)
|
||||
.map_err(|_| Error::DatabaseError {
|
||||
operation: "count_documents",
|
||||
with: "channel",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoDocumentPath for FieldsChannel {
|
||||
fn as_path(&self) -> Option<&'static str> {
|
||||
Some(match self {
|
||||
FieldsChannel::DefaultPermissions => "default_permissions",
|
||||
FieldsChannel::Description => "description",
|
||||
FieldsChannel::Icon => "icon",
|
||||
})
|
||||
}
|
||||
}
|
||||
31
crates/quark/src/impl/mongo/channels/channel_invite.rs
Normal file
31
crates/quark/src/impl/mongo/channels/channel_invite.rs
Normal file
@@ -0,0 +1,31 @@
|
||||
use crate::models::Invite;
|
||||
use crate::{AbstractChannelInvite, Result};
|
||||
|
||||
use super::super::MongoDb;
|
||||
|
||||
static COL: &str = "channel_invites";
|
||||
|
||||
#[async_trait]
|
||||
impl AbstractChannelInvite for MongoDb {
|
||||
async fn fetch_invite(&self, code: &str) -> Result<Invite> {
|
||||
self.find_one_by_id(COL, code).await
|
||||
}
|
||||
|
||||
async fn insert_invite(&self, invite: &Invite) -> Result<()> {
|
||||
self.insert_one(COL, invite).await.map(|_| ())
|
||||
}
|
||||
|
||||
async fn delete_invite(&self, code: &str) -> Result<()> {
|
||||
self.delete_one_by_id(COL, code).await.map(|_| ())
|
||||
}
|
||||
|
||||
async fn fetch_invites_for_server(&self, server: &str) -> Result<Vec<Invite>> {
|
||||
self.find(
|
||||
COL,
|
||||
doc! {
|
||||
"server": server
|
||||
},
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
104
crates/quark/src/impl/mongo/channels/channel_unread.rs
Normal file
104
crates/quark/src/impl/mongo/channels/channel_unread.rs
Normal file
@@ -0,0 +1,104 @@
|
||||
use bson::Document;
|
||||
use mongodb::options::UpdateOptions;
|
||||
use ulid::Ulid;
|
||||
|
||||
use crate::models::channel_unread::ChannelUnread;
|
||||
use crate::{AbstractChannelUnread, Error, Result};
|
||||
|
||||
use super::super::MongoDb;
|
||||
|
||||
static COL: &str = "channel_unreads";
|
||||
|
||||
#[async_trait]
|
||||
impl AbstractChannelUnread for MongoDb {
|
||||
async fn acknowledge_message(&self, channel: &str, user: &str, message: &str) -> Result<()> {
|
||||
self.col::<Document>(COL)
|
||||
.update_one(
|
||||
doc! {
|
||||
"_id.channel": channel,
|
||||
"_id.user": user,
|
||||
},
|
||||
doc! {
|
||||
"$unset": {
|
||||
"mentions": 1_i32
|
||||
},
|
||||
"$set": {
|
||||
"last_id": message
|
||||
}
|
||||
},
|
||||
UpdateOptions::builder().upsert(true).build(),
|
||||
)
|
||||
.await
|
||||
.map(|_| ())
|
||||
.map_err(|_| Error::DatabaseError {
|
||||
operation: "update_one",
|
||||
with: "channel_unread",
|
||||
})
|
||||
}
|
||||
|
||||
async fn acknowledge_channels(&self, user: &str, channels: &[String]) -> Result<()> {
|
||||
self.col::<Document>(COL)
|
||||
.update_one(
|
||||
doc! {
|
||||
"_id.channel": {
|
||||
"$in": channels
|
||||
},
|
||||
"_id.user": user,
|
||||
},
|
||||
doc! {
|
||||
"$unset": {
|
||||
"mentions": 1_i32
|
||||
},
|
||||
"$set": {
|
||||
"last_id": Ulid::new().to_string()
|
||||
}
|
||||
},
|
||||
UpdateOptions::builder().upsert(true).build(),
|
||||
)
|
||||
.await
|
||||
.map(|_| ())
|
||||
.map_err(|_| Error::DatabaseError {
|
||||
operation: "update",
|
||||
with: "channel_unread",
|
||||
})
|
||||
}
|
||||
|
||||
async fn add_mention_to_unread<'a>(
|
||||
&self,
|
||||
channel: &str,
|
||||
user: &str,
|
||||
ids: &[String],
|
||||
) -> Result<()> {
|
||||
self.col::<Document>(COL)
|
||||
.update_one(
|
||||
doc! {
|
||||
"_id.channel": channel,
|
||||
"_id.user": user,
|
||||
},
|
||||
doc! {
|
||||
"$push": {
|
||||
"mentions": {
|
||||
"$each": ids
|
||||
}
|
||||
}
|
||||
},
|
||||
UpdateOptions::builder().upsert(true).build(),
|
||||
)
|
||||
.await
|
||||
.map(|_| ())
|
||||
.map_err(|_| Error::DatabaseError {
|
||||
operation: "update_one",
|
||||
with: "channel_unread",
|
||||
})
|
||||
}
|
||||
|
||||
async fn fetch_unreads(&self, user: &str) -> Result<Vec<ChannelUnread>> {
|
||||
self.find(
|
||||
COL,
|
||||
doc! {
|
||||
"_id.user": user
|
||||
},
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
285
crates/quark/src/impl/mongo/channels/message.rs
Normal file
285
crates/quark/src/impl/mongo/channels/message.rs
Normal file
@@ -0,0 +1,285 @@
|
||||
use bson::{to_bson, Document};
|
||||
use futures::try_join;
|
||||
use mongodb::options::FindOptions;
|
||||
|
||||
use crate::models::message::{AppendMessage, Message, MessageSort, PartialMessage};
|
||||
use crate::r#impl::mongo::DocumentId;
|
||||
use crate::{AbstractMessage, Error, Result};
|
||||
|
||||
use super::super::MongoDb;
|
||||
|
||||
static COL: &str = "messages";
|
||||
|
||||
impl MongoDb {
|
||||
pub async fn delete_bulk_messages(&self, projection: Document) -> Result<()> {
|
||||
let mut for_attachments = projection.clone();
|
||||
for_attachments.insert(
|
||||
"attachment",
|
||||
doc! {
|
||||
"$exists": 1_i32
|
||||
},
|
||||
);
|
||||
|
||||
// Check if there are any attachments we need to delete.
|
||||
let message_ids_with_attachments = self
|
||||
.find_with_options::<_, DocumentId>(
|
||||
COL,
|
||||
for_attachments,
|
||||
FindOptions::builder()
|
||||
.projection(doc! { "_id": 1_i32 })
|
||||
.build(),
|
||||
)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(|x| x.id)
|
||||
.collect::<Vec<String>>();
|
||||
|
||||
// If we found any, mark them as deleted.
|
||||
if !message_ids_with_attachments.is_empty() {
|
||||
self.col::<Document>("attachments")
|
||||
.update_many(
|
||||
doc! {
|
||||
"message_id": {
|
||||
"$in": message_ids_with_attachments
|
||||
}
|
||||
},
|
||||
doc! {
|
||||
"$set": {
|
||||
"deleted": true
|
||||
}
|
||||
},
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.map_err(|_| Error::DatabaseError {
|
||||
operation: "update_many",
|
||||
with: "attachments",
|
||||
})?;
|
||||
}
|
||||
|
||||
// And then delete said messages.
|
||||
self.col::<Document>(COL)
|
||||
.delete_many(projection, None)
|
||||
.await
|
||||
.map(|_| ())
|
||||
.map_err(|_| Error::DatabaseError {
|
||||
operation: "delete_many",
|
||||
with: "messages",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl AbstractMessage for MongoDb {
|
||||
async fn fetch_message(&self, id: &str) -> Result<Message> {
|
||||
self.find_one_by_id(COL, id).await
|
||||
}
|
||||
|
||||
async fn insert_message(&self, message: &Message) -> Result<()> {
|
||||
self.insert_one(COL, message).await.map(|_| ())
|
||||
}
|
||||
|
||||
async fn update_message(&self, id: &str, message: &PartialMessage) -> Result<()> {
|
||||
self.update_one_by_id(COL, id, message, vec![], None)
|
||||
.await
|
||||
.map(|_| ())
|
||||
}
|
||||
|
||||
async fn append_message(&self, id: &str, append: &AppendMessage) -> Result<()> {
|
||||
let mut query = doc! {};
|
||||
|
||||
if let Some(embeds) = &append.embeds {
|
||||
if !embeds.is_empty() {
|
||||
query.insert(
|
||||
"$push",
|
||||
doc! {
|
||||
"embeds": {
|
||||
"$each": to_bson(embeds)
|
||||
.map_err(|_| Error::DatabaseError {
|
||||
operation: "to_bson",
|
||||
with: "embeds"
|
||||
})?
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if query.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
self.col::<Document>(COL)
|
||||
.update_one(
|
||||
doc! {
|
||||
"_id": id
|
||||
},
|
||||
query,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.map_err(|_| Error::DatabaseError {
|
||||
operation: "update_one",
|
||||
with: "message",
|
||||
})
|
||||
.map(|_| ())
|
||||
}
|
||||
|
||||
async fn delete_message(&self, id: &str) -> Result<()> {
|
||||
self.delete_bulk_messages(doc! {
|
||||
"_id": id
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
async fn delete_messages(&self, channel: &str, ids: Vec<String>) -> Result<()> {
|
||||
self.delete_bulk_messages(doc! {
|
||||
"channel": channel,
|
||||
"_id": {
|
||||
"$in": ids
|
||||
}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
async fn fetch_messages(
|
||||
&self,
|
||||
channel: &str,
|
||||
limit: Option<i64>,
|
||||
before: Option<String>,
|
||||
after: Option<String>,
|
||||
sort: Option<MessageSort>,
|
||||
nearby: Option<String>,
|
||||
) -> Result<Vec<Message>> {
|
||||
let limit = limit.unwrap_or(50);
|
||||
Ok(if let Some(nearby) = nearby {
|
||||
let (a, b) = try_join!(
|
||||
self.find_with_options::<_, Message>(
|
||||
COL,
|
||||
doc! {
|
||||
"channel": channel,
|
||||
"_id": {
|
||||
"$gte": &nearby
|
||||
}
|
||||
},
|
||||
FindOptions::builder()
|
||||
.limit(limit / 2 + 1)
|
||||
.sort(doc! {
|
||||
"_id": 1_i32
|
||||
})
|
||||
.build(),
|
||||
),
|
||||
self.find_with_options::<_, Message>(
|
||||
COL,
|
||||
doc! {
|
||||
"channel": channel,
|
||||
"_id": {
|
||||
"$lt": &nearby
|
||||
}
|
||||
},
|
||||
FindOptions::builder()
|
||||
.limit(limit / 2)
|
||||
.sort(doc! {
|
||||
"_id": -1_i32
|
||||
})
|
||||
.build(),
|
||||
)
|
||||
)?;
|
||||
|
||||
[a, b].concat()
|
||||
} else {
|
||||
let mut query = doc! { "channel": channel };
|
||||
if let Some(before) = before {
|
||||
query.insert("_id", doc! { "$lt": before });
|
||||
}
|
||||
|
||||
if let Some(after) = after {
|
||||
query.insert("_id", doc! { "$gt": after });
|
||||
}
|
||||
|
||||
let sort: i32 = if let MessageSort::Latest = sort.unwrap_or(MessageSort::Latest) {
|
||||
-1
|
||||
} else {
|
||||
1
|
||||
};
|
||||
|
||||
self.find_with_options::<_, Message>(
|
||||
COL,
|
||||
query,
|
||||
FindOptions::builder()
|
||||
.limit(limit)
|
||||
.sort(doc! {
|
||||
"_id": sort
|
||||
})
|
||||
.build(),
|
||||
)
|
||||
.await?
|
||||
})
|
||||
}
|
||||
|
||||
async fn search_messages(
|
||||
&self,
|
||||
channel: &str,
|
||||
query: &str,
|
||||
limit: Option<i64>,
|
||||
before: Option<String>,
|
||||
after: Option<String>,
|
||||
sort: MessageSort,
|
||||
) -> Result<Vec<Message>> {
|
||||
let limit = limit.unwrap_or(50);
|
||||
|
||||
let mut filter = doc! {
|
||||
"channel": channel,
|
||||
"$text": {
|
||||
"$search": query
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(doc) = match (before, after) {
|
||||
(Some(before), Some(after)) => Some(doc! {
|
||||
"lt": before,
|
||||
"gt": after
|
||||
}),
|
||||
(Some(before), _) => Some(doc! {
|
||||
"lt": before
|
||||
}),
|
||||
(_, Some(after)) => Some(doc! {
|
||||
"gt": after
|
||||
}),
|
||||
_ => None,
|
||||
} {
|
||||
filter.insert("_id", doc);
|
||||
}
|
||||
|
||||
self.find_with_options(
|
||||
COL,
|
||||
filter,
|
||||
FindOptions::builder()
|
||||
.projection(if let MessageSort::Relevance = &sort {
|
||||
doc! {
|
||||
"score": {
|
||||
"$meta": "textScore"
|
||||
}
|
||||
}
|
||||
} else {
|
||||
doc! {}
|
||||
})
|
||||
.limit(limit)
|
||||
.sort(match &sort {
|
||||
MessageSort::Relevance => doc! {
|
||||
"score": {
|
||||
"$meta": "textScore"
|
||||
}
|
||||
},
|
||||
MessageSort::Latest => doc! {
|
||||
"_id": -1_i32
|
||||
},
|
||||
MessageSort::Oldest => doc! {
|
||||
"_id": 1_i32
|
||||
},
|
||||
})
|
||||
.build(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
250
crates/quark/src/impl/mongo/mod.rs
Normal file
250
crates/quark/src/impl/mongo/mod.rs
Normal file
@@ -0,0 +1,250 @@
|
||||
use std::ops::Deref;
|
||||
|
||||
use bson::{to_document, Document};
|
||||
use futures::StreamExt;
|
||||
use mongodb::{
|
||||
options::{FindOneOptions, FindOptions},
|
||||
results::{DeleteResult, InsertOneResult, UpdateResult},
|
||||
};
|
||||
use rocket::serde::DeserializeOwned;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{util::manipulation::prefix_keys, AbstractDatabase, Error, Result};
|
||||
|
||||
pub mod admin {
|
||||
pub mod migrations;
|
||||
}
|
||||
|
||||
pub mod autumn {
|
||||
pub mod attachment;
|
||||
}
|
||||
|
||||
pub mod channels {
|
||||
pub mod channel;
|
||||
pub mod channel_invite;
|
||||
pub mod channel_unread;
|
||||
pub mod message;
|
||||
}
|
||||
|
||||
pub mod servers {
|
||||
pub mod server;
|
||||
pub mod server_ban;
|
||||
pub mod server_member;
|
||||
}
|
||||
|
||||
pub mod users {
|
||||
pub mod bot;
|
||||
pub mod user;
|
||||
pub mod user_settings;
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MongoDb(pub mongodb::Client);
|
||||
|
||||
impl AbstractDatabase for MongoDb {}
|
||||
|
||||
impl Deref for MongoDb {
|
||||
type Target = mongodb::Client;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl MongoDb {
|
||||
pub fn db(&self) -> mongodb::Database {
|
||||
self.database("revolt")
|
||||
}
|
||||
|
||||
pub fn col<T>(&self, collection: &str) -> mongodb::Collection<T> {
|
||||
self.db().collection(collection)
|
||||
}
|
||||
|
||||
async fn insert_one<T: Serialize>(
|
||||
&self,
|
||||
collection: &'static str,
|
||||
document: T,
|
||||
) -> Result<InsertOneResult> {
|
||||
self.col::<T>(collection)
|
||||
.insert_one(document, None)
|
||||
.await
|
||||
.map_err(|_| Error::DatabaseError {
|
||||
operation: "insert_one",
|
||||
with: collection,
|
||||
})
|
||||
}
|
||||
|
||||
async fn find_with_options<O, T: DeserializeOwned + Unpin + Send + Sync>(
|
||||
&self,
|
||||
collection: &'static str,
|
||||
projection: Document,
|
||||
options: O,
|
||||
) -> Result<Vec<T>>
|
||||
where
|
||||
O: Into<Option<FindOptions>>,
|
||||
{
|
||||
Ok(self
|
||||
.col::<T>(collection)
|
||||
.find(projection, options)
|
||||
.await
|
||||
.map_err(|_| Error::DatabaseError {
|
||||
operation: "find",
|
||||
with: collection,
|
||||
})?
|
||||
.filter_map(|s| async { s.ok() })
|
||||
.collect::<Vec<T>>()
|
||||
.await)
|
||||
}
|
||||
|
||||
async fn find<T: DeserializeOwned + Unpin + Send + Sync>(
|
||||
&self,
|
||||
collection: &'static str,
|
||||
projection: Document,
|
||||
) -> Result<Vec<T>> {
|
||||
self.find_with_options(collection, projection, None).await
|
||||
}
|
||||
|
||||
async fn find_one_with_options<O, T: DeserializeOwned + Unpin + Send + Sync>(
|
||||
&self,
|
||||
collection: &'static str,
|
||||
projection: Document,
|
||||
options: O,
|
||||
) -> Result<T>
|
||||
where
|
||||
O: Into<Option<FindOneOptions>>,
|
||||
{
|
||||
self.col::<T>(collection)
|
||||
.find_one(projection, options)
|
||||
.await
|
||||
.map_err(|_| Error::DatabaseError {
|
||||
operation: "find_one",
|
||||
with: collection,
|
||||
})?
|
||||
.ok_or(Error::NotFound)
|
||||
}
|
||||
|
||||
async fn find_one<T: DeserializeOwned + Unpin + Send + Sync>(
|
||||
&self,
|
||||
collection: &'static str,
|
||||
projection: Document,
|
||||
) -> Result<T> {
|
||||
self.find_one_with_options(collection, projection, None)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn find_one_by_id<T: DeserializeOwned + Unpin + Send + Sync>(
|
||||
&self,
|
||||
collection: &'static str,
|
||||
id: &str,
|
||||
) -> Result<T> {
|
||||
self.find_one(
|
||||
collection,
|
||||
doc! {
|
||||
"_id": id
|
||||
},
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn update_one<P, T: Serialize>(
|
||||
&self,
|
||||
collection: &'static str,
|
||||
projection: Document,
|
||||
partial: T,
|
||||
remove: Vec<&dyn IntoDocumentPath>,
|
||||
prefix: P,
|
||||
) -> Result<UpdateResult>
|
||||
where
|
||||
P: Into<Option<String>>,
|
||||
{
|
||||
let prefix = prefix.into();
|
||||
|
||||
let mut unset = doc! {};
|
||||
for field in remove {
|
||||
if let Some(path) = field.as_path() {
|
||||
if let Some(prefix) = &prefix {
|
||||
unset.insert(prefix.to_owned() + path, 1_i32);
|
||||
} else {
|
||||
unset.insert(path, 1_i32);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let query = doc! {
|
||||
"$unset": unset,
|
||||
"$set": if let Some(prefix) = &prefix {
|
||||
to_document(&prefix_keys(&partial, prefix))
|
||||
} else {
|
||||
to_document(&partial)
|
||||
}.map_err(|_| Error::DatabaseError {
|
||||
operation: "to_document",
|
||||
with: collection
|
||||
})?
|
||||
};
|
||||
|
||||
self.col::<Document>(collection)
|
||||
.update_one(projection, query, None)
|
||||
.await
|
||||
.map_err(|_| Error::DatabaseError {
|
||||
operation: "update_one",
|
||||
with: collection,
|
||||
})
|
||||
}
|
||||
|
||||
async fn update_one_by_id<P, T: Serialize>(
|
||||
&self,
|
||||
collection: &'static str,
|
||||
id: &str,
|
||||
partial: T,
|
||||
remove: Vec<&dyn IntoDocumentPath>,
|
||||
prefix: P,
|
||||
) -> Result<UpdateResult>
|
||||
where
|
||||
P: Into<Option<String>>,
|
||||
{
|
||||
self.update_one(
|
||||
collection,
|
||||
doc! {
|
||||
"_id": id
|
||||
},
|
||||
partial,
|
||||
remove,
|
||||
prefix,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn delete_one(
|
||||
&self,
|
||||
collection: &'static str,
|
||||
projection: Document,
|
||||
) -> Result<DeleteResult> {
|
||||
self.col::<Document>(collection)
|
||||
.delete_one(projection, None)
|
||||
.await
|
||||
.map_err(|_| Error::DatabaseError {
|
||||
operation: "delete_one",
|
||||
with: collection,
|
||||
})
|
||||
}
|
||||
|
||||
async fn delete_one_by_id(&self, collection: &'static str, id: &str) -> Result<DeleteResult> {
|
||||
self.delete_one(
|
||||
collection,
|
||||
doc! {
|
||||
"_id": id
|
||||
},
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct DocumentId {
|
||||
#[serde(rename = "_id")]
|
||||
pub id: String,
|
||||
}
|
||||
|
||||
pub trait IntoDocumentPath: Send + Sync {
|
||||
fn as_path(&self) -> Option<&'static str>;
|
||||
}
|
||||
228
crates/quark/src/impl/mongo/servers/server.rs
Normal file
228
crates/quark/src/impl/mongo/servers/server.rs
Normal file
@@ -0,0 +1,228 @@
|
||||
use bson::{to_document, Bson, Document};
|
||||
|
||||
use crate::models::server::{FieldsRole, FieldsServer, PartialRole, PartialServer, Role, Server};
|
||||
use crate::r#impl::mongo::IntoDocumentPath;
|
||||
use crate::{AbstractServer, Database, Error, Result};
|
||||
|
||||
use super::super::MongoDb;
|
||||
|
||||
static COL: &str = "servers";
|
||||
|
||||
impl MongoDb {
|
||||
pub async fn delete_associated_server_objects(&self, server: &Server) -> Result<()> {
|
||||
// Check if there are any attachments we need to delete.
|
||||
self.delete_bulk_messages(doc! {
|
||||
"channel": {
|
||||
"$in": &server.channels
|
||||
}
|
||||
})
|
||||
.await?;
|
||||
|
||||
// Delete all channels.
|
||||
self.col::<Document>("channels")
|
||||
.delete_many(
|
||||
doc! {
|
||||
"server": &server.id
|
||||
},
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.map_err(|_| Error::DatabaseError {
|
||||
operation: "delete_many",
|
||||
with: "channels",
|
||||
})?;
|
||||
|
||||
// Delete any associated objects, e.g. unreads and invites.
|
||||
self.delete_associated_channel_objects(Bson::Document(doc! { "$in": &server.channels }))
|
||||
.await?;
|
||||
|
||||
// Delete members and bans.
|
||||
for with in &["server_members", "server_bans"] {
|
||||
self.col::<Document>(with)
|
||||
.delete_many(
|
||||
doc! {
|
||||
"_id.server": &server.id
|
||||
},
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.map_err(|_| Error::DatabaseError {
|
||||
operation: "delete_many",
|
||||
with,
|
||||
})?;
|
||||
}
|
||||
|
||||
// Update many attachments with parent id.
|
||||
self.delete_many_attachments(doc! {
|
||||
"object_id": &server.id
|
||||
})
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl AbstractServer for MongoDb {
|
||||
async fn fetch_server(&self, id: &str) -> Result<Server> {
|
||||
self.find_one_by_id(COL, id).await
|
||||
}
|
||||
|
||||
async fn fetch_servers<'a>(&self, ids: &'a [String]) -> Result<Vec<Server>> {
|
||||
self.find(
|
||||
COL,
|
||||
doc! {
|
||||
"_id": {
|
||||
"$in": ids
|
||||
}
|
||||
},
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn insert_server(&self, server: &Server) -> Result<()> {
|
||||
self.insert_one(COL, server).await.map(|_| ())
|
||||
}
|
||||
|
||||
async fn update_server(
|
||||
&self,
|
||||
id: &str,
|
||||
server: &PartialServer,
|
||||
remove: Vec<FieldsServer>,
|
||||
) -> Result<()> {
|
||||
self.update_one_by_id(
|
||||
COL,
|
||||
id,
|
||||
server,
|
||||
remove.iter().map(|x| x as &dyn IntoDocumentPath).collect(),
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.map(|_| ())
|
||||
}
|
||||
|
||||
async fn delete_server(&self, server: &Server) -> Result<()> {
|
||||
self.delete_associated_server_objects(server).await?;
|
||||
self.delete_one_by_id(COL, &server.id).await.map(|_| ())
|
||||
}
|
||||
|
||||
async fn insert_role(&self, server_id: &str, role_id: &str, role: &Role) -> Result<()> {
|
||||
self.col::<Database>(COL)
|
||||
.update_one(
|
||||
doc! {
|
||||
"_id": server_id
|
||||
},
|
||||
doc! {
|
||||
"$set": {
|
||||
"roles.".to_owned() + role_id: to_document(role)
|
||||
.map_err(|_| Error::DatabaseError {
|
||||
operation: "to_document",
|
||||
with: "role"
|
||||
})?
|
||||
}
|
||||
},
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.map(|_| ())
|
||||
.map_err(|_| Error::DatabaseError {
|
||||
operation: "update_one",
|
||||
with: "server",
|
||||
})
|
||||
}
|
||||
|
||||
async fn update_role(
|
||||
&self,
|
||||
server_id: &str,
|
||||
role_id: &str,
|
||||
role: &PartialRole,
|
||||
remove: Vec<FieldsRole>,
|
||||
) -> Result<()> {
|
||||
self.update_one_by_id(
|
||||
COL,
|
||||
server_id,
|
||||
role,
|
||||
remove.iter().map(|x| x as &dyn IntoDocumentPath).collect(),
|
||||
"roles.".to_owned() + role_id + ".",
|
||||
)
|
||||
.await
|
||||
.map(|_| ())
|
||||
}
|
||||
|
||||
async fn delete_role(&self, server_id: &str, role_id: &str) -> Result<()> {
|
||||
self.col::<Document>("server_members")
|
||||
.update_many(
|
||||
doc! {
|
||||
"_id.server": server_id
|
||||
},
|
||||
doc! {
|
||||
"$pull": {
|
||||
"roles": &role_id
|
||||
}
|
||||
},
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.map_err(|_| Error::DatabaseError {
|
||||
operation: "update_many",
|
||||
with: "server_members",
|
||||
})?;
|
||||
|
||||
self.col::<Document>("channels")
|
||||
.update_one(
|
||||
doc! {
|
||||
"server": server_id
|
||||
},
|
||||
doc! {
|
||||
"$unset": {
|
||||
"role_permissions.".to_owned() + role_id: 1_i32
|
||||
}
|
||||
},
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.map_err(|_| Error::DatabaseError {
|
||||
operation: "update_one",
|
||||
with: "channels",
|
||||
})?;
|
||||
|
||||
self.col::<Document>("servers")
|
||||
.update_one(
|
||||
doc! {
|
||||
"_id": server_id
|
||||
},
|
||||
doc! {
|
||||
"$unset": {
|
||||
"roles.".to_owned() + role_id: 1_i32
|
||||
}
|
||||
},
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.map_err(|_| Error::DatabaseError {
|
||||
operation: "update_one",
|
||||
with: "servers",
|
||||
})
|
||||
.map(|_| ())
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoDocumentPath for FieldsServer {
|
||||
fn as_path(&self) -> Option<&'static str> {
|
||||
Some(match self {
|
||||
FieldsServer::Banner => "banner",
|
||||
FieldsServer::Categories => "categories",
|
||||
FieldsServer::Description => "description",
|
||||
FieldsServer::Icon => "icon",
|
||||
FieldsServer::SystemMessages => "system_messages",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoDocumentPath for FieldsRole {
|
||||
fn as_path(&self) -> Option<&'static str> {
|
||||
Some(match self {
|
||||
FieldsRole::Colour => "colour",
|
||||
})
|
||||
}
|
||||
}
|
||||
47
crates/quark/src/impl/mongo/servers/server_ban.rs
Normal file
47
crates/quark/src/impl/mongo/servers/server_ban.rs
Normal file
@@ -0,0 +1,47 @@
|
||||
use crate::models::server_member::MemberCompositeKey;
|
||||
use crate::models::ServerBan;
|
||||
use crate::{AbstractServerBan, Result};
|
||||
|
||||
use super::super::MongoDb;
|
||||
|
||||
static COL: &str = "server_bans";
|
||||
|
||||
#[async_trait]
|
||||
impl AbstractServerBan for MongoDb {
|
||||
async fn fetch_ban(&self, server: &str, user: &str) -> Result<ServerBan> {
|
||||
self.find_one(
|
||||
COL,
|
||||
doc! {
|
||||
"_id.server": server,
|
||||
"_id.user": user
|
||||
},
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn fetch_bans(&self, server: &str) -> Result<Vec<ServerBan>> {
|
||||
self.find(
|
||||
COL,
|
||||
doc! {
|
||||
"_id.server": server
|
||||
},
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn insert_ban(&self, ban: &ServerBan) -> Result<()> {
|
||||
self.insert_one(COL, ban).await.map(|_| ())
|
||||
}
|
||||
|
||||
async fn delete_ban(&self, id: &MemberCompositeKey) -> Result<()> {
|
||||
self.delete_one(
|
||||
COL,
|
||||
doc! {
|
||||
"_id.server": &id.server,
|
||||
"_id.user": &id.user
|
||||
},
|
||||
)
|
||||
.await
|
||||
.map(|_| ())
|
||||
}
|
||||
}
|
||||
134
crates/quark/src/impl/mongo/servers/server_member.rs
Normal file
134
crates/quark/src/impl/mongo/servers/server_member.rs
Normal file
@@ -0,0 +1,134 @@
|
||||
use bson::Document;
|
||||
|
||||
use crate::models::server_member::{FieldsMember, Member, MemberCompositeKey, PartialMember};
|
||||
use crate::r#impl::mongo::IntoDocumentPath;
|
||||
use crate::{AbstractServerMember, Error, Result};
|
||||
|
||||
use super::super::MongoDb;
|
||||
|
||||
static COL: &str = "server_members";
|
||||
|
||||
#[async_trait]
|
||||
impl AbstractServerMember for MongoDb {
|
||||
async fn fetch_member(&self, server: &str, user: &str) -> Result<Member> {
|
||||
self.find_one(
|
||||
COL,
|
||||
doc! {
|
||||
"_id.server": server,
|
||||
"_id.user": user
|
||||
},
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn insert_member(&self, member: &Member) -> Result<()> {
|
||||
self.insert_one(COL, member).await.map(|_| ())
|
||||
}
|
||||
|
||||
async fn update_member(
|
||||
&self,
|
||||
id: &MemberCompositeKey,
|
||||
member: &PartialMember,
|
||||
remove: Vec<FieldsMember>,
|
||||
) -> Result<()> {
|
||||
self.update_one(
|
||||
COL,
|
||||
doc! {
|
||||
"_id.server": &id.server,
|
||||
"_id.user": &id.user
|
||||
},
|
||||
member,
|
||||
remove.iter().map(|x| x as &dyn IntoDocumentPath).collect(),
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.map(|_| ())
|
||||
}
|
||||
|
||||
async fn delete_member(&self, id: &MemberCompositeKey) -> Result<()> {
|
||||
self.delete_one(
|
||||
COL,
|
||||
doc! {
|
||||
"_id.server": &id.server,
|
||||
"_id.user": &id.user
|
||||
},
|
||||
)
|
||||
.await
|
||||
.map(|_| ())
|
||||
}
|
||||
|
||||
async fn fetch_all_members<'a>(&self, server: &str) -> Result<Vec<Member>> {
|
||||
self.find(
|
||||
COL,
|
||||
doc! {
|
||||
"_id.server": server
|
||||
},
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn fetch_all_memberships<'a>(&self, user: &str) -> Result<Vec<Member>> {
|
||||
self.find(
|
||||
COL,
|
||||
doc! {
|
||||
"_id.user": user
|
||||
},
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn fetch_members<'a>(&self, server: &str, ids: &'a [String]) -> Result<Vec<Member>> {
|
||||
self.find(
|
||||
COL,
|
||||
doc! {
|
||||
"_id.server": server,
|
||||
"_id.user": {
|
||||
"$in": ids
|
||||
}
|
||||
},
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn fetch_member_count(&self, server: &str) -> Result<usize> {
|
||||
self.col::<Document>(COL)
|
||||
.count_documents(
|
||||
doc! {
|
||||
"_id.server": server
|
||||
},
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.map(|c| c as usize)
|
||||
.map_err(|_| Error::DatabaseError {
|
||||
operation: "count_documents",
|
||||
with: "server_members",
|
||||
})
|
||||
}
|
||||
|
||||
async fn fetch_server_count(&self, user: &str) -> Result<usize> {
|
||||
self.col::<Document>(COL)
|
||||
.count_documents(
|
||||
doc! {
|
||||
"_id.user": user
|
||||
},
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.map(|c| c as usize)
|
||||
.map_err(|_| Error::DatabaseError {
|
||||
operation: "count_documents",
|
||||
with: "server_members",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoDocumentPath for FieldsMember {
|
||||
fn as_path(&self) -> Option<&'static str> {
|
||||
Some(match self {
|
||||
FieldsMember::Avatar => "avatar",
|
||||
FieldsMember::Nickname => "nickname",
|
||||
FieldsMember::Roles => "roles",
|
||||
})
|
||||
}
|
||||
}
|
||||
68
crates/quark/src/impl/mongo/users/bot.rs
Normal file
68
crates/quark/src/impl/mongo/users/bot.rs
Normal file
@@ -0,0 +1,68 @@
|
||||
use crate::models::bot::{Bot, FieldsBot, PartialBot};
|
||||
use crate::r#impl::mongo::IntoDocumentPath;
|
||||
use crate::{AbstractBot, Result};
|
||||
|
||||
use super::super::MongoDb;
|
||||
|
||||
static COL: &str = "bots";
|
||||
|
||||
#[async_trait]
|
||||
impl AbstractBot for MongoDb {
|
||||
async fn fetch_bot(&self, id: &str) -> Result<Bot> {
|
||||
self.find_one_by_id(COL, id).await
|
||||
}
|
||||
|
||||
async fn fetch_bot_by_token(&self, token: &str) -> Result<Bot> {
|
||||
self.find_one(
|
||||
COL,
|
||||
doc! {
|
||||
"token": token
|
||||
},
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn insert_bot(&self, bot: &Bot) -> Result<()> {
|
||||
self.insert_one(COL, &bot).await.map(|_| ())
|
||||
}
|
||||
|
||||
async fn update_bot(&self, id: &str, bot: &PartialBot, remove: Vec<FieldsBot>) -> Result<()> {
|
||||
self.update_one_by_id(
|
||||
COL,
|
||||
id,
|
||||
bot,
|
||||
remove.iter().map(|x| x as &dyn IntoDocumentPath).collect(),
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.map(|_| ())
|
||||
}
|
||||
|
||||
async fn delete_bot(&self, id: &str) -> Result<()> {
|
||||
self.delete_one_by_id(COL, id).await.map(|_| ())
|
||||
}
|
||||
|
||||
async fn fetch_bots_by_user(&self, user_id: &str) -> Result<Vec<Bot>> {
|
||||
self.find(
|
||||
COL,
|
||||
doc! {
|
||||
"owner": user_id
|
||||
},
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn get_number_of_bots_by_user(&self, user_id: &str) -> Result<usize> {
|
||||
// ! FIXME: move this to generic?
|
||||
self.fetch_bots_by_user(user_id).await.map(|x| x.len())
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoDocumentPath for FieldsBot {
|
||||
fn as_path(&self) -> Option<&'static str> {
|
||||
match self {
|
||||
FieldsBot::InteractionsURL => Some("interactions_url"),
|
||||
FieldsBot::Token => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
319
crates/quark/src/impl/mongo/users/user.rs
Normal file
319
crates/quark/src/impl/mongo/users/user.rs
Normal file
@@ -0,0 +1,319 @@
|
||||
use bson::Document;
|
||||
use futures::StreamExt;
|
||||
use mongodb::options::{Collation, CollationStrength, FindOneOptions, FindOptions};
|
||||
|
||||
use crate::models::user::{FieldsUser, PartialUser, RelationshipStatus, User};
|
||||
use crate::r#impl::mongo::IntoDocumentPath;
|
||||
use crate::{AbstractUser, Error, Result};
|
||||
|
||||
use super::super::MongoDb;
|
||||
|
||||
lazy_static! {
|
||||
static ref FIND_USERNAME_OPTIONS: FindOneOptions = FindOneOptions::builder()
|
||||
.collation(
|
||||
Collation::builder()
|
||||
.locale("en")
|
||||
.strength(CollationStrength::Secondary)
|
||||
.build()
|
||||
)
|
||||
.build();
|
||||
}
|
||||
|
||||
static COL: &str = "users";
|
||||
|
||||
#[async_trait]
|
||||
impl AbstractUser for MongoDb {
|
||||
async fn fetch_user(&self, id: &str) -> Result<User> {
|
||||
self.find_one_by_id(COL, id).await
|
||||
}
|
||||
|
||||
async fn fetch_user_by_username(&self, username: &str) -> Result<User> {
|
||||
self.find_one_with_options(
|
||||
COL,
|
||||
doc! {
|
||||
"username": username
|
||||
},
|
||||
FIND_USERNAME_OPTIONS.clone(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn fetch_user_by_token(&self, token: &str) -> Result<User> {
|
||||
let session = self
|
||||
.col::<Document>("sessions")
|
||||
.find_one(
|
||||
doc! {
|
||||
"token": token
|
||||
},
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.map_err(|_| Error::DatabaseError {
|
||||
operation: "find_one",
|
||||
with: "sessions",
|
||||
})?
|
||||
.ok_or(Error::InvalidSession)?;
|
||||
|
||||
self.fetch_user(session.get_str("user_id").unwrap()).await
|
||||
}
|
||||
|
||||
async fn insert_user(&self, user: &User) -> Result<()> {
|
||||
self.insert_one(COL, user).await.map(|_| ())
|
||||
}
|
||||
|
||||
async fn update_user(
|
||||
&self,
|
||||
id: &str,
|
||||
user: &PartialUser,
|
||||
remove: Vec<FieldsUser>,
|
||||
) -> Result<()> {
|
||||
self.update_one_by_id(
|
||||
COL,
|
||||
id,
|
||||
user,
|
||||
remove.iter().map(|x| x as &dyn IntoDocumentPath).collect(),
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.map(|_| ())
|
||||
}
|
||||
|
||||
async fn delete_user(&self, id: &str) -> Result<()> {
|
||||
self.delete_one_by_id(COL, id).await.map(|_| ())
|
||||
}
|
||||
|
||||
async fn fetch_users<'a>(&self, ids: &'a [String]) -> Result<Vec<User>> {
|
||||
let mut cursor = self
|
||||
.col::<User>(COL)
|
||||
.find(
|
||||
doc! {
|
||||
"_id": {
|
||||
"$in": ids
|
||||
}
|
||||
},
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.map_err(|_| Error::DatabaseError {
|
||||
operation: "find",
|
||||
with: "users",
|
||||
})?;
|
||||
|
||||
let mut users = vec![];
|
||||
while let Some(Ok(user)) = cursor.next().await {
|
||||
users.push(user);
|
||||
}
|
||||
|
||||
Ok(users)
|
||||
}
|
||||
|
||||
async fn is_username_taken(&self, username: &str) -> Result<bool> {
|
||||
// ! FIXME: move this up to generic
|
||||
match self.fetch_user_by_username(username).await {
|
||||
Ok(_) => Ok(true),
|
||||
Err(Error::NotFound) => Ok(false),
|
||||
Err(error) => Err(error),
|
||||
}
|
||||
}
|
||||
|
||||
async fn fetch_mutual_user_ids(&self, user_a: &str, user_b: &str) -> Result<Vec<String>> {
|
||||
Ok(self
|
||||
.col::<Document>(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(|_| Error::DatabaseError {
|
||||
operation: "find",
|
||||
with: "users",
|
||||
})?
|
||||
.filter_map(|s| async { s.ok() })
|
||||
.collect::<Vec<Document>>()
|
||||
.await
|
||||
.into_iter()
|
||||
.filter_map(|x| x.get_str("_id").ok().map(|x| x.to_string()))
|
||||
.collect::<Vec<String>>())
|
||||
}
|
||||
|
||||
async fn fetch_mutual_channel_ids(&self, user_a: &str, user_b: &str) -> Result<Vec<String>> {
|
||||
Ok(self
|
||||
.col::<Document>("channels")
|
||||
.find(
|
||||
doc! {
|
||||
"channel_type": {
|
||||
"$in": ["Group", "DirectMessage"]
|
||||
},
|
||||
"recipients": {
|
||||
"$all": [ user_a, user_b ]
|
||||
}
|
||||
},
|
||||
FindOptions::builder().projection(doc! { "_id": 1 }).build(),
|
||||
)
|
||||
.await
|
||||
.map_err(|_| Error::DatabaseError {
|
||||
operation: "find",
|
||||
with: "channels",
|
||||
})?
|
||||
.filter_map(|s| async { s.ok() })
|
||||
.collect::<Vec<Document>>()
|
||||
.await
|
||||
.into_iter()
|
||||
.filter_map(|x| x.get_str("_id").ok().map(|x| x.to_string()))
|
||||
.collect::<Vec<String>>())
|
||||
}
|
||||
|
||||
async fn fetch_mutual_server_ids(&self, user_a: &str, user_b: &str) -> Result<Vec<String>> {
|
||||
Ok(self
|
||||
.col::<Document>("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(|_| Error::DatabaseError {
|
||||
operation: "aggregate",
|
||||
with: "server_members",
|
||||
})?
|
||||
.filter_map(|s| async { s.ok() })
|
||||
.collect::<Vec<Document>>()
|
||||
.await
|
||||
.into_iter()
|
||||
.filter_map(|x| x.get_str("_id").ok().map(|i| i.to_string()))
|
||||
.collect::<Vec<String>>())
|
||||
}
|
||||
|
||||
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::<Document>(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(|_| Error::DatabaseError {
|
||||
operation: "update_one",
|
||||
with: "user",
|
||||
})
|
||||
}
|
||||
|
||||
async fn pull_relationship(&self, user_id: &str, target_id: &str) -> Result<()> {
|
||||
self.col::<Document>(COL)
|
||||
.update_one(
|
||||
doc! {
|
||||
"_id": user_id
|
||||
},
|
||||
doc! {
|
||||
"$pull": {
|
||||
"relations": {
|
||||
"_id": target_id
|
||||
}
|
||||
}
|
||||
},
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.map(|_| ())
|
||||
.map_err(|_| Error::DatabaseError {
|
||||
operation: "update_one",
|
||||
with: "user",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
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",
|
||||
})
|
||||
}
|
||||
}
|
||||
62
crates/quark/src/impl/mongo/users/user_settings.rs
Normal file
62
crates/quark/src/impl/mongo/users/user_settings.rs
Normal file
@@ -0,0 +1,62 @@
|
||||
use bson::{to_bson, Document};
|
||||
use mongodb::options::{FindOneOptions, UpdateOptions};
|
||||
|
||||
use crate::models::UserSettings;
|
||||
use crate::{AbstractUserSettings, Error, Result};
|
||||
|
||||
use super::super::MongoDb;
|
||||
|
||||
static COL: &str = "user_settings";
|
||||
|
||||
#[async_trait]
|
||||
impl AbstractUserSettings for MongoDb {
|
||||
async fn fetch_user_settings(&'_ self, id: &str, filter: &'_ [String]) -> Result<UserSettings> {
|
||||
let mut projection = doc! {
|
||||
"_id": 0,
|
||||
};
|
||||
|
||||
for key in filter {
|
||||
projection.insert(key, 1);
|
||||
}
|
||||
|
||||
self.find_one_with_options(
|
||||
COL,
|
||||
doc! {
|
||||
"_id": id
|
||||
},
|
||||
FindOneOptions::builder().projection(projection).build(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn set_user_settings(&self, id: &str, settings: &UserSettings) -> Result<()> {
|
||||
let mut set = doc! {};
|
||||
for (key, data) in settings {
|
||||
set.insert(
|
||||
key,
|
||||
vec![to_bson(&data.0).unwrap(), to_bson(&data.1).unwrap()],
|
||||
);
|
||||
}
|
||||
|
||||
self.col::<Document>(COL)
|
||||
.update_one(
|
||||
doc! {
|
||||
"_id": id
|
||||
},
|
||||
doc! {
|
||||
"$set": set
|
||||
},
|
||||
UpdateOptions::builder().upsert(true).build(),
|
||||
)
|
||||
.await
|
||||
.map(|_| ())
|
||||
.map_err(|_| Error::DatabaseError {
|
||||
operation: "update_one",
|
||||
with: "user_settings",
|
||||
})
|
||||
}
|
||||
|
||||
async fn delete_user_settings(&self, id: &str) -> Result<()> {
|
||||
self.delete_one_by_id(COL, id).await.map(|_| ())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user