From a516c7adcf637ff705bd48fea870e61689a14b0e Mon Sep 17 00:00:00 2001 From: Paul Makles Date: Tue, 1 Aug 2023 15:53:43 +0100 Subject: [PATCH] feat(core): implement channel invite model closes #268 --- crates/core/database/src/drivers/reference.rs | 8 +- .../src/models/channel_invites/mod.rs | 5 + .../src/models/channel_invites/model.rs | 101 ++++++++++++++++++ .../src/models/channel_invites/ops.rs | 21 ++++ .../src/models/channel_invites/ops/mongodb.rs | 50 +++++++++ .../models/channel_invites/ops/reference.rs | 52 +++++++++ crates/core/database/src/models/mod.rs | 3 + crates/core/database/src/util/bridge/v0.rs | 27 +++++ crates/core/models/src/v0/channel_invites.rs | 27 +++++ crates/core/models/src/v0/mod.rs | 2 + 10 files changed, 293 insertions(+), 3 deletions(-) create mode 100644 crates/core/database/src/models/channel_invites/mod.rs create mode 100644 crates/core/database/src/models/channel_invites/model.rs create mode 100644 crates/core/database/src/models/channel_invites/ops.rs create mode 100644 crates/core/database/src/models/channel_invites/ops/mongodb.rs create mode 100644 crates/core/database/src/models/channel_invites/ops/reference.rs create mode 100644 crates/core/models/src/v0/channel_invites.rs diff --git a/crates/core/database/src/drivers/reference.rs b/crates/core/database/src/drivers/reference.rs index 8f5b845c..864f62ea 100644 --- a/crates/core/database/src/drivers/reference.rs +++ b/crates/core/database/src/drivers/reference.rs @@ -2,13 +2,17 @@ use std::{collections::HashMap, sync::Arc}; use futures::lock::Mutex; -use crate::{Bot, Channel, File, Member, MemberCompositeKey, Server, User, UserSettings, Webhook}; +use crate::{ + Bot, Channel, File, Invite, Member, MemberCompositeKey, Server, User, UserSettings, Webhook, +}; database_derived!( /// Reference implementation #[derive(Default)] pub struct ReferenceDb { pub bots: Arc>>, + pub channels: Arc>>, + pub channel_invites: Arc>>, pub channel_webhooks: Arc>>, pub user_settings: Arc>>, pub users: Arc>>, @@ -20,8 +24,6 @@ database_derived!( pub safety_snapshots: Arc>>, pub emoji: Arc>>, pub messages: Arc>>, - pub channels: Arc>>, - pub channel_invites: Arc>>, pub channel_unreads: Arc>>, } ); diff --git a/crates/core/database/src/models/channel_invites/mod.rs b/crates/core/database/src/models/channel_invites/mod.rs new file mode 100644 index 00000000..4d801b73 --- /dev/null +++ b/crates/core/database/src/models/channel_invites/mod.rs @@ -0,0 +1,5 @@ +mod model; +mod ops; + +pub use model::*; +pub use ops::*; diff --git a/crates/core/database/src/models/channel_invites/model.rs b/crates/core/database/src/models/channel_invites/model.rs new file mode 100644 index 00000000..48603e52 --- /dev/null +++ b/crates/core/database/src/models/channel_invites/model.rs @@ -0,0 +1,101 @@ +use revolt_result::{create_error, Result}; + +use crate::Database; + +/* static 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', +]; */ + +auto_derived!( + /// Invite + pub enum Invite { + /// Invite to a specific server channel + Server { + /// Invite code + #[serde(rename = "_id")] + code: String, + /// Id of the server this invite points to + server: String, + /// Id of user who created this invite + creator: String, + /// Id of the server channel this invite points to + channel: String, + }, + /// Invite to a group channel + Group { + /// Invite code + #[serde(rename = "_id")] + code: String, + /// Id of user who created this invite + creator: String, + /// Id of the group channel this invite points to + channel: String, + }, /* User { + code: String, + user: String + } */ + } +); + +#[allow(clippy::disallowed_methods)] +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_channel_invite(db: &Database, creator_id: String, target: &Channel) -> Result { + let code = nanoid::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 { + 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(create_error!(NotFound)) + } +} diff --git a/crates/core/database/src/models/channel_invites/ops.rs b/crates/core/database/src/models/channel_invites/ops.rs new file mode 100644 index 00000000..82b2bd8a --- /dev/null +++ b/crates/core/database/src/models/channel_invites/ops.rs @@ -0,0 +1,21 @@ +use revolt_result::Result; + +use crate::Invite; + +mod mongodb; +mod reference; + +#[async_trait] +pub trait AbstractChannelInvites: Sync + Send { + /// Insert a new invite into the database + async fn insert_invite(&self, invite: &Invite) -> Result<()>; + + /// Fetch an invite by its id + async fn fetch_invite(&self, code: &str) -> Result; + + /// Fetch all invites for a server + async fn fetch_invites_for_server(&self, server_id: &str) -> Result>; + + /// Delete an invite by its id + async fn delete_invite(&self, code: &str) -> Result<()>; +} diff --git a/crates/core/database/src/models/channel_invites/ops/mongodb.rs b/crates/core/database/src/models/channel_invites/ops/mongodb.rs new file mode 100644 index 00000000..f6d55fff --- /dev/null +++ b/crates/core/database/src/models/channel_invites/ops/mongodb.rs @@ -0,0 +1,50 @@ +use futures::StreamExt; +use revolt_result::Result; + +use crate::Invite; +use crate::MongoDb; + +use super::AbstractChannelInvites; + +static COL: &str = "channel_invites"; + +#[async_trait] +impl AbstractChannelInvites for MongoDb { + /// Insert a new invite into the database + async fn insert_invite(&self, invite: &Invite) -> Result<()> { + query!(self, insert_one, COL, &invite).map(|_| ()) + } + + /// Fetch an invite by the code + async fn fetch_invite(&self, code: &str) -> Result { + query!(self, find_one_by_id, COL, code)?.ok_or_else(|| create_error!(NotFound)) + } + + /// Fetch all invites for a server + async fn fetch_invites_for_server(&self, server_id: &str) -> Result> { + Ok(self + .col::(COL) + .find( + doc! { + "server": server_id, + }, + None, + ) + .await + .map_err(|_| create_database_error!("find", COL))? + .filter_map(|s| async { + if cfg!(debug_assertions) { + Some(s.unwrap()) + } else { + s.ok() + } + }) + .collect() + .await) + } + + /// Delete an invite by its code + async fn delete_invite(&self, code: &str) -> Result<()> { + query!(self, delete_one_by_id, COL, code).map(|_| ()) + } +} diff --git a/crates/core/database/src/models/channel_invites/ops/reference.rs b/crates/core/database/src/models/channel_invites/ops/reference.rs new file mode 100644 index 00000000..3b1f5979 --- /dev/null +++ b/crates/core/database/src/models/channel_invites/ops/reference.rs @@ -0,0 +1,52 @@ +use revolt_result::Result; + +use crate::Invite; +use crate::ReferenceDb; + +use super::AbstractChannelInvites; + +#[async_trait] +impl AbstractChannelInvites for ReferenceDb { + /// Insert a new invite into the database + async fn insert_invite(&self, invite: &Invite) -> Result<()> { + let mut invites = self.channel_invites.lock().await; + if invites.contains_key(invite.code()) { + Err(create_database_error!("insert", "invite")) + } else { + invites.insert(invite.code().to_string(), invite.clone()); + Ok(()) + } + } + + /// Fetch an invite by the code + async fn fetch_invite(&self, code: &str) -> Result { + let invites = self.channel_invites.lock().await; + invites + .get(code) + .cloned() + .ok_or_else(|| create_error!(NotFound)) + } + + /// Fetch all invites for a server + async fn fetch_invites_for_server(&self, server_id: &str) -> Result> { + let invites = self.channel_invites.lock().await; + Ok(invites + .values() + .filter(|invite| match invite { + Invite::Server { server, .. } => server == server_id, + _ => false, + }) + .cloned() + .collect()) + } + + /// Delete an invite by its code + async fn delete_invite(&self, code: &str) -> Result<()> { + let mut invites = self.channel_invites.lock().await; + if invites.remove(code).is_some() { + Ok(()) + } else { + Err(create_error!(NotFound)) + } + } +} diff --git a/crates/core/database/src/models/mod.rs b/crates/core/database/src/models/mod.rs index 79a96986..e32805e2 100644 --- a/crates/core/database/src/models/mod.rs +++ b/crates/core/database/src/models/mod.rs @@ -1,5 +1,6 @@ mod admin_migrations; mod bots; +mod channel_invites; mod channel_webhooks; mod channels; mod files; @@ -11,6 +12,7 @@ mod users; pub use admin_migrations::*; pub use bots::*; +pub use channel_invites::*; pub use channel_webhooks::*; pub use channels::*; pub use files::*; @@ -28,6 +30,7 @@ pub trait AbstractDatabase: + admin_migrations::AbstractMigrations + bots::AbstractBots + channels::AbstractChannels + + channel_invites::AbstractChannelInvites + channel_webhooks::AbstractWebhooks + files::AbstractAttachments + ratelimit_events::AbstractRatelimitEvents diff --git a/crates/core/database/src/util/bridge/v0.rs b/crates/core/database/src/util/bridge/v0.rs index af251351..1d7eab40 100644 --- a/crates/core/database/src/util/bridge/v0.rs +++ b/crates/core/database/src/util/bridge/v0.rs @@ -34,6 +34,33 @@ impl From for Bot { } } +impl From for Invite { + fn from(value: crate::Invite) -> Self { + match value { + crate::Invite::Group { + code, + creator, + channel, + } => Invite::Group { + code, + creator, + channel, + }, + crate::Invite::Server { + code, + server, + creator, + channel, + } => Invite::Server { + code, + server, + creator, + channel, + }, + } + } +} + impl From for Webhook { fn from(value: crate::Webhook) -> Self { Webhook { diff --git a/crates/core/models/src/v0/channel_invites.rs b/crates/core/models/src/v0/channel_invites.rs new file mode 100644 index 00000000..0c007ae8 --- /dev/null +++ b/crates/core/models/src/v0/channel_invites.rs @@ -0,0 +1,27 @@ +auto_derived!( + /// Invite + pub enum Invite { + /// Invite to a specific server channel + Server { + /// Invite code + #[cfg_attr(feature = "serde", serde(rename = "_id"))] + code: String, + /// Id of the server this invite points to + server: String, + /// Id of user who created this invite + creator: String, + /// Id of the server channel this invite points to + channel: String, + }, + /// Invite to a group channel + Group { + /// Invite code + #[cfg_attr(feature = "serde", serde(rename = "_id"))] + code: String, + /// Id of user who created this invite + creator: String, + /// Id of the group channel this invite points to + channel: String, + }, + } +); diff --git a/crates/core/models/src/v0/mod.rs b/crates/core/models/src/v0/mod.rs index 46380a21..5db2be7a 100644 --- a/crates/core/models/src/v0/mod.rs +++ b/crates/core/models/src/v0/mod.rs @@ -1,4 +1,5 @@ mod bots; +mod channel_invites; mod channel_webhooks; mod channels; mod files; @@ -7,6 +8,7 @@ mod servers; mod users; pub use bots::*; +pub use channel_invites::*; pub use channel_webhooks::*; pub use channels::*; pub use files::*;