diff --git a/crates/core/database/src/lib.rs b/crates/core/database/src/lib.rs index afc591e3..90365598 100644 --- a/crates/core/database/src/lib.rs +++ b/crates/core/database/src/lib.rs @@ -43,7 +43,7 @@ macro_rules! auto_derived { macro_rules! auto_derived_partial { ( $item:item, $name:expr ) => { - #[derive(OptionalStruct, Serialize, Deserialize, Debug, Clone, Default, Eq, PartialEq)] + #[derive(OptionalStruct, Serialize, Deserialize, Debug, Clone, Eq, PartialEq)] #[optional_derive(Serialize, Deserialize, Debug, Clone, Default, Eq, PartialEq)] #[optional_name = $name] #[opt_skip_serializing_none] diff --git a/crates/core/database/src/models/bots/model.rs b/crates/core/database/src/models/bots/model.rs index 702f8d8b..debfdfe8 100644 --- a/crates/core/database/src/models/bots/model.rs +++ b/crates/core/database/src/models/bots/model.rs @@ -50,6 +50,24 @@ auto_derived!( } ); +#[allow(clippy::derivable_impls)] +impl Default for Bot { + fn default() -> Self { + Self { + id: Default::default(), + owner: Default::default(), + token: Default::default(), + public: Default::default(), + analytics: Default::default(), + discoverable: Default::default(), + interactions_url: Default::default(), + terms_of_service_url: Default::default(), + privacy_policy_url: Default::default(), + flags: Default::default(), + } + } +} + #[allow(clippy::disallowed_methods)] impl Bot { /// Create a new bot diff --git a/crates/core/database/src/models/channel_webhooks/model.rs b/crates/core/database/src/models/channel_webhooks/model.rs index 87ea23d0..52a03317 100644 --- a/crates/core/database/src/models/channel_webhooks/model.rs +++ b/crates/core/database/src/models/channel_webhooks/model.rs @@ -36,6 +36,20 @@ auto_derived!( } ); +#[allow(clippy::derivable_impls)] +impl Default for Webhook { + fn default() -> Self { + Self { + id: Default::default(), + name: Default::default(), + avatar: None, + channel_id: Default::default(), + permissions: Default::default(), + token: Default::default(), + } + } +} + #[allow(clippy::disallowed_methods)] impl Webhook { pub async fn create(&self, db: &Database) -> Result<()> { diff --git a/crates/core/database/src/models/channels/model.rs b/crates/core/database/src/models/channels/model.rs index ed936142..ac07e16d 100644 --- a/crates/core/database/src/models/channels/model.rs +++ b/crates/core/database/src/models/channels/model.rs @@ -1,12 +1,14 @@ use std::collections::HashMap; +use revolt_models::v0::MessageAuthor; use revolt_permissions::OverrideField; use revolt_result::Result; use serde::{Deserialize, Serialize}; -use crate::{events::client::EventV1, Database, File, IntoDocumentPath}; +use crate::{events::client::EventV1, Database, File, IntoDocumentPath, SystemMessage, User}; auto_derived!( + #[serde(tag = "channel_type")] pub enum Channel { /// Personal "Saved Notes" channel which allows users to save messages SavedMessages { @@ -190,40 +192,48 @@ impl Channel { pub async fn add_user_to_group( &mut self, db: &Database, - user_id: &str, - _by_id: &str, + user: &User, + by_id: &str, ) -> Result<()> { if let Channel::Group { recipients, .. } = self { - if recipients.contains(&String::from(user_id)) { + if recipients.contains(&String::from(&user.id)) { return Err(create_error!(AlreadyInGroup)); } - recipients.push(String::from(user_id)); + recipients.push(String::from(&user.id)); } match &self { Channel::Group { id, .. } => { - db.add_user_to_group(id, user_id).await?; + db.add_user_to_group(id, &user.id).await?; EventV1::ChannelGroupJoin { id: id.to_string(), - user: user_id.to_string(), + user: user.id.to_string(), } .p(id.to_string()) .await; EventV1::ChannelCreate(self.clone().into()) - .private(user_id.to_string()) + .private(user.id.to_string()) .await; - /* TODO: SystemMessage::UserAdded { - id: user.to_string(), - by: by.to_string(), + SystemMessage::UserAdded { + id: user.id.to_string(), + by: by_id.to_string(), } .into_message(id.to_string()) - .create(db, self, None) + .send( + db, + MessageAuthor::System { + username: &user.username, + avatar: user.avatar.as_ref().map(|file| file.id.as_ref()), + }, + self, + false, + ) .await - .ok(); */ + .ok(); Ok(()) } @@ -469,19 +479,20 @@ impl Channel { pub async fn remove_user_from_group( &self, db: &Database, - user_id: &str, - _by_id: Option<&str>, + user: &User, + by_id: Option<&str>, silent: bool, ) -> Result<()> { match &self { Channel::Group { id, + name, owner, recipients, .. } => { - if user_id == owner { - if let Some(new_owner) = recipients.iter().find(|x| *x != user_id) { + if &user.id == owner { + if let Some(new_owner) = recipients.iter().find(|x| *x != &user.id) { db.update_channel( id, &PartialChannel { @@ -492,14 +503,22 @@ impl Channel { ) .await?; - /* TODO: SystemMessage::ChannelOwnershipChanged { + SystemMessage::ChannelOwnershipChanged { from: owner.to_string(), - to: new_owner.into(), + to: new_owner.to_string(), } .into_message(id.to_string()) - .create(db, self, None) + .send( + db, + MessageAuthor::System { + username: name, + avatar: None, + }, + self, + false, + ) .await - .ok(); */ + .ok(); } else { db.delete_channel(self).await?; return Ok(()); @@ -508,26 +527,34 @@ impl Channel { EventV1::ChannelGroupLeave { id: id.to_string(), - user: user_id.to_string(), + user: user.id.to_string(), } .p(id.to_string()) .await; if !silent { - /* TODO: if let Some(_by) = by_id { + if let Some(by) = by_id { SystemMessage::UserRemove { - id: user_id.to_string(), + id: user.id.to_string(), by: by.to_string(), } } else { SystemMessage::UserLeft { - id: user_id.to_string(), + id: user.id.to_string(), } } .into_message(id.to_string()) - .create(db, self, None) + .send( + db, + MessageAuthor::System { + username: &user.username, + avatar: user.avatar.as_ref().map(|file| file.id.as_ref()), + }, + self, + false, + ) .await - .ok(); */ + .ok(); } Ok(()) diff --git a/crates/core/database/src/models/messages/model.rs b/crates/core/database/src/models/messages/model.rs index c99a675e..1e83cd08 100644 --- a/crates/core/database/src/models/messages/model.rs +++ b/crates/core/database/src/models/messages/model.rs @@ -172,6 +172,29 @@ auto_derived!( } ); +#[allow(clippy::derivable_impls)] +impl Default for Message { + fn default() -> Self { + Self { + id: Default::default(), + nonce: None, + channel: Default::default(), + author: Default::default(), + webhook: None, + content: None, + system: None, + attachments: None, + edited: None, + embeds: None, + mentions: None, + replies: None, + reactions: Default::default(), + interactions: Default::default(), + masquerade: None, + } + } +} + #[allow(clippy::disallowed_methods)] impl Message { /// Send a message without any notifications diff --git a/crates/core/database/src/models/server_members/model.rs b/crates/core/database/src/models/server_members/model.rs index 7722dbb8..dc86cf5e 100644 --- a/crates/core/database/src/models/server_members/model.rs +++ b/crates/core/database/src/models/server_members/model.rs @@ -15,8 +15,7 @@ auto_derived_partial!( pub id: MemberCompositeKey, /// Time at which this user joined the server - #[serde(skip_serializing_if = "Option::is_none")] - pub joined_at: Option, + pub joined_at: Timestamp, /// Member's nickname #[serde(skip_serializing_if = "Option::is_none")] @@ -61,6 +60,19 @@ auto_derived!( } ); +impl Default for Member { + fn default() -> Self { + Self { + id: Default::default(), + joined_at: Timestamp::now_utc(), + nickname: None, + avatar: None, + roles: vec![], + timeout: None, + } + } +} + #[allow(clippy::disallowed_methods)] impl Member { /// Create a new member in a server @@ -75,6 +87,10 @@ impl Member { return Err(create_error!(Banned)); } + if db.fetch_member(&server.id, &user.id).await.is_ok() { + return Err(create_error!(AlreadyInServer)); + } + let member = Member { id: MemberCompositeKey { server: server.id.to_string(), @@ -155,13 +171,13 @@ impl Member { db.update_member(&self.id, &partial, remove.clone()).await?; - /* // TODO: EventV1::ServerMemberUpdate { - id: self.id.clone(), - data: partial, - clear: remove, + EventV1::ServerMemberUpdate { + id: self.id.clone().into(), + data: partial.into(), + clear: remove.into_iter().map(|field| field.into()).collect(), } .p(self.id.server.clone()) - .await; */ + .await; Ok(()) } diff --git a/crates/core/database/src/models/users/model.rs b/crates/core/database/src/models/users/model.rs index 7967ad5c..c32e374e 100644 --- a/crates/core/database/src/models/users/model.rs +++ b/crates/core/database/src/models/users/model.rs @@ -133,6 +133,26 @@ pub static DISCRIMINATOR_SEARCH_SPACE: Lazy> = Lazy::new(|| { set.into_iter().collect() }); +#[allow(clippy::derivable_impls)] +impl Default for User { + fn default() -> Self { + Self { + id: Default::default(), + username: Default::default(), + discriminator: Default::default(), + display_name: Default::default(), + avatar: Default::default(), + relations: Default::default(), + badges: Default::default(), + status: Default::default(), + profile: Default::default(), + flags: Default::default(), + privileged: Default::default(), + bot: Default::default(), + } + } +} + #[allow(clippy::disallowed_methods)] impl User { /// Create a new user diff --git a/crates/core/models/src/v0/messages.rs b/crates/core/models/src/v0/messages.rs index ceb8715f..438d8b26 100644 --- a/crates/core/models/src/v0/messages.rs +++ b/crates/core/models/src/v0/messages.rs @@ -158,6 +158,10 @@ auto_derived!( pub enum MessageAuthor<'a> { User(&'a User), Webhook(&'a Webhook), + System { + username: &'a str, + avatar: Option<&'a str>, + }, } impl Interactions { @@ -172,6 +176,7 @@ impl<'a> MessageAuthor<'a> { match self { MessageAuthor::User(user) => &user.id, MessageAuthor::Webhook(webhook) => &webhook.id, + MessageAuthor::System { .. } => "00000000000000000000000000", } } @@ -179,6 +184,7 @@ impl<'a> MessageAuthor<'a> { match self { MessageAuthor::User(user) => user.avatar.as_ref().map(|file| file.id.as_str()), MessageAuthor::Webhook(webhook) => webhook.avatar.as_ref().map(|file| file.id.as_str()), + MessageAuthor::System { avatar, .. } => *avatar, } } @@ -186,6 +192,7 @@ impl<'a> MessageAuthor<'a> { match self { MessageAuthor::User(user) => &user.username, MessageAuthor::Webhook(webhook) => &webhook.name, + MessageAuthor::System { username, .. } => username, } } } diff --git a/crates/core/models/src/v0/server_members.rs b/crates/core/models/src/v0/server_members.rs index bb91ad26..3fbacce9 100644 --- a/crates/core/models/src/v0/server_members.rs +++ b/crates/core/models/src/v0/server_members.rs @@ -10,8 +10,7 @@ auto_derived_partial!( pub id: MemberCompositeKey, /// Time at which this user joined the server - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - pub joined_at: Option, + pub joined_at: Timestamp, /// Member's nickname #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] diff --git a/crates/core/result/src/lib.rs b/crates/core/result/src/lib.rs index 9db79cf1..140a7a09 100644 --- a/crates/core/result/src/lib.rs +++ b/crates/core/result/src/lib.rs @@ -88,6 +88,7 @@ pub enum ErrorType { TooManyRoles { max: usize, }, + AlreadyInServer, // ? Bot related errors ReachedMaximumBots, diff --git a/crates/core/result/src/rocket.rs b/crates/core/result/src/rocket.rs index 43b20373..b6efd8cb 100644 --- a/crates/core/result/src/rocket.rs +++ b/crates/core/result/src/rocket.rs @@ -43,6 +43,7 @@ impl<'r> Responder<'r, 'static> for Error { ErrorType::UnknownServer => Status::NotFound, ErrorType::InvalidRole => Status::NotFound, ErrorType::Banned => Status::Forbidden, + ErrorType::AlreadyInServer => Status::Conflict, ErrorType::TooManyServers { .. } => Status::BadRequest, ErrorType::TooManyEmoji { .. } => Status::BadRequest, diff --git a/crates/delta/src/routes/bots/invite.rs b/crates/delta/src/routes/bots/invite.rs index dabfabf3..76e64f55 100644 --- a/crates/delta/src/routes/bots/invite.rs +++ b/crates/delta/src/routes/bots/invite.rs @@ -45,6 +45,8 @@ pub async fn invite_bot( return Err(create_error!(BotIsPrivate)); } + let bot_user = db.fetch_user(&bot.id).await?; + match dest.into_inner() { InviteBotDestination::Server { server } => { let server = db.fetch_server(&server).await?; @@ -54,8 +56,7 @@ pub async fn invite_bot( .await .throw_if_lacking_channel_permission(ChannelPermission::ManageServer)?; - let user = db.fetch_user(&bot.id).await?; - Member::create(db, &server, &user) + Member::create(db, &server, &bot_user) .await .map(|_| EmptyResponse) } @@ -68,7 +69,7 @@ pub async fn invite_bot( .throw_if_lacking_channel_permission(ChannelPermission::InviteOthers)?; channel - .add_user_to_group(db, &bot.id, &user.id) + .add_user_to_group(db, &bot_user, &user.id) .await .map(|_| EmptyResponse) } diff --git a/crates/quark/src/traits/channels/channel.rs b/crates/quark/src/traits/channels/channel.rs index e51c5ce0..d4436574 100644 --- a/crates/quark/src/traits/channels/channel.rs +++ b/crates/quark/src/traits/channels/channel.rs @@ -1,4 +1,4 @@ -use crate::models::{channel::{Channel, FieldsChannel, PartialChannel}}; +use crate::models::channel::{Channel, FieldsChannel, PartialChannel}; use crate::{OverrideField, Result}; #[async_trait] @@ -13,9 +13,6 @@ pub trait AbstractChannel: Sync + Send { async fn insert_channel(&self, channel: &Channel) -> Result<()>; /// Update an existing channel using some data - /// ! TODO: we need separate Channel::update which also sends out the relevant events - /// ! also applies to other methods I guess, try to restrict event bound methods to - /// ! the models themselves instead of the abstract database async fn update_channel( &self, id: &str,