feat(core): implement channel invite model

closes #268
This commit is contained in:
Paul Makles
2023-08-01 15:53:43 +01:00
parent c48109ca66
commit a516c7adcf
10 changed files with 293 additions and 3 deletions

View File

@@ -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<Mutex<HashMap<String, Bot>>>,
pub channels: Arc<Mutex<HashMap<String, Channel>>>,
pub channel_invites: Arc<Mutex<HashMap<String, Invite>>>,
pub channel_webhooks: Arc<Mutex<HashMap<String, Webhook>>>,
pub user_settings: Arc<Mutex<HashMap<String, UserSettings>>>,
pub users: Arc<Mutex<HashMap<String, User>>>,
@@ -20,8 +24,6 @@ database_derived!(
pub safety_snapshots: Arc<Mutex<HashMap<String, ()>>>,
pub emoji: Arc<Mutex<HashMap<String, ()>>>,
pub messages: Arc<Mutex<HashMap<String, ()>>>,
pub channels: Arc<Mutex<HashMap<String, Channel>>>,
pub channel_invites: Arc<Mutex<HashMap<String, ()>>>,
pub channel_unreads: Arc<Mutex<HashMap<String, ()>>>,
}
);

View File

@@ -0,0 +1,5 @@
mod model;
mod ops;
pub use model::*;
pub use ops::*;

View File

@@ -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<Invite> {
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<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(create_error!(NotFound))
}
}

View File

@@ -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<Invite>;
/// Fetch all invites for a server
async fn fetch_invites_for_server(&self, server_id: &str) -> Result<Vec<Invite>>;
/// Delete an invite by its id
async fn delete_invite(&self, code: &str) -> Result<()>;
}

View File

@@ -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<Invite> {
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<Vec<Invite>> {
Ok(self
.col::<Invite>(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(|_| ())
}
}

View File

@@ -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<Invite> {
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<Vec<Invite>> {
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))
}
}
}

View File

@@ -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

View File

@@ -34,6 +34,33 @@ impl From<crate::Bot> for Bot {
}
}
impl From<crate::Invite> 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<crate::Webhook> for Webhook {
fn from(value: crate::Webhook) -> Self {
Webhook {