chore(monorepo): delta, january, quark

This commit is contained in:
Paul Makles
2022-06-02 00:04:22 +01:00
parent 5d8432e267
commit 2ce610e1e7
232 changed files with 18094 additions and 554 deletions

View 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(())
}
}

View 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(())
}
}

View 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)
}
}

View 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()])
}
}

View 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![])
}
}

View 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()])
}
}

View 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 {}

View 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(())
}
}

View 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(())
}
}

View 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)
}
}

View 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)
}
}

View 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(())
}
}

View 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(())
}
}

View File

@@ -0,0 +1 @@

View 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
}
}

View 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),
}
}
}

View 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)
}
}

View File

@@ -0,0 +1 @@

View 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))
}
}
}

View 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;
}

View 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
}
}

View File

@@ -0,0 +1 @@

View 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,
}
}
}

View 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
}
}

View 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,
))
}
}

View 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(())
}
}

View File

@@ -0,0 +1,3 @@
pub mod dummy;
pub mod generic;
pub mod mongo;

View 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(())
}
}

View 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.");
}

View 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
}

View 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",
})
}
}

View 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",
})
}
}

View 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
}
}

View 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
}
}

View 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
}
}

View 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>;
}

View 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",
})
}
}

View 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(|_| ())
}
}

View 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",
})
}
}

View 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,
}
}
}

View 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",
})
}
}

View 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(|_| ())
}
}