From bf3e0270670582ec0f183af4a7f1f55458253b94 Mon Sep 17 00:00:00 2001 From: Paul Makles Date: Sun, 4 Jun 2023 18:52:49 +0100 Subject: [PATCH] feat: channel model implementation (#250) Co-authored-by: ToastXC <100072983+toastxc@users.noreply.github.com> --- Cargo.lock | 3 + crates/core/database/Cargo.toml | 2 +- crates/core/database/src/drivers/reference.rs | 6 +- crates/core/database/src/events/client.rs | 68 +-- .../core/database/src/models/channels/mod.rs | 5 + .../database/src/models/channels/model.rs | 529 ++++++++++++++++++ .../core/database/src/models/channels/ops.rs | 50 ++ .../src/models/channels/ops/mongodb.rs | 193 +++++++ .../src/models/channels/ops/reference.rs | 157 ++++++ crates/core/database/src/models/mod.rs | 5 +- crates/core/database/src/util/bridge/v0.rs | 117 ++++ crates/core/database/src/util/reference.rs | 7 +- crates/core/models/Cargo.toml | 3 + crates/core/models/src/v0/channels.rs | 208 +++++++ crates/core/models/src/v0/mod.rs | 2 + crates/core/permissions/Cargo.toml | 5 +- crates/core/permissions/src/models/server.rs | 47 +- crates/delta/Cargo.toml | 1 + .../routes/servers/permissions_set_default.rs | 15 +- .../src/routes/webhooks/webhook_delete.rs | 10 +- .../routes/webhooks/webhook_delete_token.rs | 6 +- .../delta/src/routes/webhooks/webhook_edit.rs | 10 +- .../src/routes/webhooks/webhook_edit_token.rs | 5 +- .../src/routes/webhooks/webhook_execute.rs | 10 +- .../routes/webhooks/webhook_execute_github.rs | 10 +- .../src/routes/webhooks/webhook_fetch.rs | 10 +- .../routes/webhooks/webhook_fetch_token.rs | 6 +- 27 files changed, 1391 insertions(+), 99 deletions(-) create mode 100644 crates/core/database/src/models/channels/mod.rs create mode 100644 crates/core/database/src/models/channels/model.rs create mode 100644 crates/core/database/src/models/channels/ops.rs create mode 100644 crates/core/database/src/models/channels/ops/mongodb.rs create mode 100644 crates/core/database/src/models/channels/ops/reference.rs create mode 100644 crates/core/models/src/v0/channels.rs diff --git a/Cargo.lock b/Cargo.lock index f38735e6..81fcda9f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2904,6 +2904,7 @@ dependencies = [ "reqwest", "revolt-database", "revolt-models", + "revolt-permissions", "revolt-quark", "revolt-result", "revolt_rocket_okapi", @@ -2923,6 +2924,7 @@ dependencies = [ name = "revolt-models" version = "0.6.0-rc.1" dependencies = [ + "revolt-permissions", "revolt_optional_struct", "schemars", "serde", @@ -2936,6 +2938,7 @@ dependencies = [ "async-std", "async-trait", "auto_ops", + "bson", "num_enum 0.6.1", "once_cell", "schemars", diff --git a/crates/core/database/Cargo.toml b/crates/core/database/Cargo.toml index 21be972d..b3e44360 100644 --- a/crates/core/database/Cargo.toml +++ b/crates/core/database/Cargo.toml @@ -25,7 +25,7 @@ default = [ "mongodb", "async-std-runtime" ] revolt-result = { version = "0.6.0-rc.1", path = "../result" } revolt-models = { version = "0.6.0-rc.1", path = "../models" } revolt-presence = { version = "0.6.0-rc.1", path = "../presence" } -revolt-permissions = { version = "0.6.0-rc.1", path = "../permissions", features = [ "serde" ] } +revolt-permissions = { version = "0.6.0-rc.1", path = "../permissions", features = [ "serde", "bson" ] } # Utility log = "0.4" diff --git a/crates/core/database/src/drivers/reference.rs b/crates/core/database/src/drivers/reference.rs index ef7e6adb..b2d9f033 100644 --- a/crates/core/database/src/drivers/reference.rs +++ b/crates/core/database/src/drivers/reference.rs @@ -3,7 +3,8 @@ use std::{collections::HashMap, sync::Arc}; use futures::lock::Mutex; use crate::{ - AccountStrike, Bot, File, Member, MemberCompositeKey, Server, User, UserSettings, Webhook, + AccountStrike, Bot, Channel, File, Member, MemberCompositeKey, Server, User, UserSettings, + Webhook, }; database_derived!( @@ -18,13 +19,12 @@ database_derived!( pub server_members: Arc>>, pub servers: Arc>>, pub files: Arc>>, - pub server_bans: Arc>>, pub safety_reports: Arc>>, pub safety_snapshots: Arc>>, pub emoji: Arc>>, pub messages: Arc>>, - pub channels: Arc>>, + pub channels: Arc>>, pub channel_invites: Arc>>, pub channel_unreads: Arc>>, } diff --git a/crates/core/database/src/events/client.rs b/crates/core/database/src/events/client.rs index 38ed1f9d..b19d3ef7 100644 --- a/crates/core/database/src/events/client.rs +++ b/crates/core/database/src/events/client.rs @@ -1,7 +1,9 @@ use authifier::AuthifierEvent; use serde::{Deserialize, Serialize}; -use revolt_models::v0::{FieldsWebhook, PartialWebhook, Webhook}; +use revolt_models::v0::{ + Channel, FieldsChannel, FieldsWebhook, PartialChannel, PartialWebhook, Webhook, +}; use revolt_result::Error; use crate::Database; @@ -101,38 +103,6 @@ pub enum EventV1 { /// Bulk delete messages BulkMessageDelete { channel: String, ids: Vec }, - /// New channel - ChannelCreate(Channel), - - /// Update existing channel - ChannelUpdate { - id: String, - data: PartialChannel, - clear: Vec, - }, - - /// Delete channel - ChannelDelete { id: String }, - - /// User joins a group - ChannelGroupJoin { id: String, user: String }, - - /// User leaves a group - ChannelGroupLeave { id: String, user: String }, - - /// User started typing in a channel - ChannelStartTyping { id: String, user: String }, - - /// User stopped typing in a channel - ChannelStopTyping { id: String, user: String }, - - /// User acknowledged message in channel - ChannelAck { - id: String, - user: String, - message_id: String, - }, - /// New server ServerCreate { id: String, @@ -212,6 +182,38 @@ pub enum EventV1 { /// New report ReportCreate(Report), */ + /// New channel + ChannelCreate(Channel), + + /// Update existing channel + ChannelUpdate { + id: String, + data: PartialChannel, + clear: Vec, + }, + + /// Delete channel + ChannelDelete { id: String }, + + /// User joins a group + ChannelGroupJoin { id: String, user: String }, + + /// User leaves a group + ChannelGroupLeave { id: String, user: String }, + + /// User started typing in a channel + ChannelStartTyping { id: String, user: String }, + + /// User stopped typing in a channel + ChannelStopTyping { id: String, user: String }, + + /// User acknowledged message in channel + ChannelAck { + id: String, + user: String, + message_id: String, + }, + /// New webhook WebhookCreate(Webhook), diff --git a/crates/core/database/src/models/channels/mod.rs b/crates/core/database/src/models/channels/mod.rs new file mode 100644 index 00000000..4d801b73 --- /dev/null +++ b/crates/core/database/src/models/channels/mod.rs @@ -0,0 +1,5 @@ +mod model; +mod ops; + +pub use model::*; +pub use ops::*; diff --git a/crates/core/database/src/models/channels/model.rs b/crates/core/database/src/models/channels/model.rs new file mode 100644 index 00000000..9da02b7a --- /dev/null +++ b/crates/core/database/src/models/channels/model.rs @@ -0,0 +1,529 @@ +use std::collections::HashMap; + +use revolt_permissions::OverrideField; +use revolt_result::Result; +use serde::{Deserialize, Serialize}; + +use crate::{events::client::EventV1, Database, File, IntoDocumentPath}; + +auto_derived!( + pub enum Channel { + /// Personal "Saved Notes" channel which allows users to save messages + SavedMessages { + /// Unique Id + #[serde(rename = "_id")] + id: String, + /// Id of the user this channel belongs to + user: String, + }, + /// Direct message channel between two users + DirectMessage { + /// Unique Id + #[serde(rename = "_id")] + id: String, + + /// Whether this direct message channel is currently open on both sides + active: bool, + /// 2-tuple of user ids participating in direct message + recipients: Vec, + /// Id of the last message sent in this channel + #[serde(skip_serializing_if = "Option::is_none")] + last_message_id: Option, + }, + /// Group channel between 1 or more participants + Group { + /// Unique Id + #[serde(rename = "_id")] + id: String, + + /// Display name of the channel + name: String, + /// User id of the owner of the group + owner: String, + /// Channel description + #[serde(skip_serializing_if = "Option::is_none")] + description: Option, + /// Array of user ids participating in channel + recipients: Vec, + + /// Custom icon attachment + #[serde(skip_serializing_if = "Option::is_none")] + icon: Option, + /// Id of the last message sent in this channel + #[serde(skip_serializing_if = "Option::is_none")] + last_message_id: Option, + + /// Permissions assigned to members of this group + /// (does not apply to the owner of the group) + #[serde(skip_serializing_if = "Option::is_none")] + permissions: Option, + + /// Whether this group is marked as not safe for work + #[serde(skip_serializing_if = "crate::if_false", default)] + nsfw: bool, + }, + /// Text channel belonging to a server + TextChannel { + /// Unique Id + #[serde(rename = "_id")] + id: String, + /// Id of the server this channel belongs to + server: String, + + /// Display name of the channel + name: String, + /// Channel description + #[serde(skip_serializing_if = "Option::is_none")] + description: Option, + + /// Custom icon attachment + #[serde(skip_serializing_if = "Option::is_none")] + icon: Option, + /// Id of the last message sent in this channel + #[serde(skip_serializing_if = "Option::is_none")] + last_message_id: Option, + + /// Default permissions assigned to users in this channel + #[serde(skip_serializing_if = "Option::is_none")] + default_permissions: Option, + /// Permissions assigned based on role to this channel + #[serde( + default = "HashMap::::new", + skip_serializing_if = "HashMap::::is_empty" + )] + role_permissions: HashMap, + + /// Whether this channel is marked as not safe for work + #[serde(skip_serializing_if = "crate::if_false", default)] + nsfw: bool, + }, + /// Voice channel belonging to a server + VoiceChannel { + /// Unique Id + #[serde(rename = "_id")] + id: String, + /// Id of the server this channel belongs to + server: String, + + /// Display name of the channel + name: String, + #[serde(skip_serializing_if = "Option::is_none")] + /// Channel description + description: Option, + /// Custom icon attachment + #[serde(skip_serializing_if = "Option::is_none")] + icon: Option, + + /// Default permissions assigned to users in this channel + #[serde(skip_serializing_if = "Option::is_none")] + default_permissions: Option, + /// Permissions assigned based on role to this channel + #[serde( + default = "HashMap::::new", + skip_serializing_if = "HashMap::::is_empty" + )] + role_permissions: HashMap, + + /// Whether this channel is marked as not safe for work + #[serde(skip_serializing_if = "crate::if_false", default)] + nsfw: bool, + }, + } +); + +auto_derived!( + #[derive(Default)] + pub struct PartialChannel { + #[serde(skip_serializing_if = "Option::is_none")] + pub name: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub owner: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub description: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub icon: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub nsfw: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub active: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub permissions: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub role_permissions: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub default_permissions: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub last_message_id: Option, + } + + /// Optional fields on channel object + pub enum FieldsChannel { + Description, + Icon, + DefaultPermissions, + } +); + +impl Channel { + /// Create a channel + pub async fn create(&self, db: &Database) -> Result<()> { + db.insert_channel(self).await?; + + Ok(()) + } + + /// Add user to a group + pub async fn add_user_to_group( + &mut self, + db: &Database, + user_id: &str, + _by_id: &str, + ) -> Result<()> { + if let Channel::Group { recipients, .. } = self { + if recipients.contains(&String::from(user_id)) { + return Err(create_error!(AlreadyInGroup)); + } + + recipients.push(String::from(user_id)); + } + + match &self { + Channel::Group { id, .. } => { + db.add_user_to_group(id, user_id).await?; + + EventV1::ChannelGroupJoin { + id: id.to_string(), + user: user_id.to_string(), + } + .p(id.to_string()) + .await; + + EventV1::ChannelCreate(self.clone().into()) + .private(user_id.to_string()) + .await; + + /* TODO: SystemMessage::UserAdded { + id: user.to_string(), + by: by.to_string(), + } + .into_message(id.to_string()) + .create(db, self, None) + .await + .ok(); */ + + Ok(()) + } + _ => Err(create_error!(InvalidOperation)), + } + } + + /// Map out whether it is a direct DM + pub fn is_direct_dm(&self) -> bool { + matches!(self, Channel::DirectMessage { .. }) + } + + /// Check whether has a user as a recipient + pub fn contains_user(&self, user_id: &str) -> bool { + match self { + Channel::Group { recipients, .. } => recipients.contains(&String::from(user_id)), + _ => false, + } + } + + /// Get list of recipients + pub fn users(&self) -> Result> { + match self { + Channel::Group { recipients, .. } => Ok(recipients.to_owned()), + _ => Err(create_error!(NotFound)), + } + } + + /// Get a reference to this channel's id + pub fn id(&self) -> String { + match self { + Channel::DirectMessage { id, .. } + | Channel::Group { id, .. } + | Channel::SavedMessages { id, .. } + | Channel::TextChannel { id, .. } + | Channel::VoiceChannel { id, .. } => id.clone(), + } + } + + /// Set role permission on a channel + pub async fn set_role_permission( + &mut self, + db: &Database, + role_id: &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_id, permissions) + .await?; + + role_permissions.insert(role_id.to_string(), permissions); + + EventV1::ChannelUpdate { + id: id.clone(), + data: PartialChannel { + role_permissions: Some(role_permissions.clone()), + ..Default::default() + } + .into(), + clear: vec![], + } + .p(server.clone()) + .await; + + Ok(()) + } + _ => Err(create_error!(InvalidOperation)), + } + } + + /// Update channel data + pub async fn update<'a>( + &mut self, + db: &Database, + partial: PartialChannel, + remove: Vec, + ) -> Result<()> { + for field in &remove { + self.remove_field(field); + } + + self.apply_options(partial.clone()); + + db.update_channel(&self.id(), &partial, remove.clone()) + .await?; + + Ok(()) + } + + /// Remove a field from Channel object + pub fn remove_field(&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(); + } + _ => {} + }, + } + } + + /// Remove multiple fields from Channel object + pub fn remove_fields(&mut self, partial: Vec) { + for field in partial { + self.remove_field(&field) + } + } + + /// Apply partial channel to channel + pub fn apply_options(&mut self, partial: PartialChannel) { + match self { + Self::SavedMessages { .. } => {} + 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); + } + } + } + } + + /// Remove user from a group + pub async fn remove_user_from_group( + &self, + db: &Database, + user_id: &str, + _by_id: Option<&str>, + silent: bool, + ) -> Result<()> { + match &self { + Channel::Group { + id, + owner, + recipients, + .. + } => { + if user_id == owner { + if let Some(new_owner) = recipients.iter().find(|x| *x != user_id) { + db.update_channel( + id, + &PartialChannel { + owner: Some(new_owner.into()), + ..Default::default() + }, + vec![], + ) + .await?; + + /* TODO: SystemMessage::ChannelOwnershipChanged { + from: owner.to_string(), + to: new_owner.into(), + } + .into_message(id.to_string()) + .create(db, self, None) + .await + .ok(); */ + } else { + db.delete_channel(self).await?; + return Ok(()); + } + } + + EventV1::ChannelGroupLeave { + id: id.to_string(), + user: user_id.to_string(), + } + .p(id.to_string()) + .await; + + if !silent { + /* TODO: if let Some(_by) = by_id { + SystemMessage::UserRemove { + id: user_id.to_string(), + by: by.to_string(), + } + } else { + SystemMessage::UserLeft { + id: user_id.to_string(), + } + } + .into_message(id.to_string()) + .create(db, self, None) + .await + .ok(); */ + } + + Ok(()) + } + + _ => Err(create_error!(InvalidOperation)), + } + } + + /// Delete a channel + pub async fn delete(&self, db: &Database) -> Result<()> { + db.delete_channel(self).await + } +} + +impl IntoDocumentPath for FieldsChannel { + fn as_path(&self) -> Option<&'static str> { + Some(match self { + FieldsChannel::Description => "description", + FieldsChannel::Icon => "icon", + FieldsChannel::DefaultPermissions => "default_permissions", + }) + } +} diff --git a/crates/core/database/src/models/channels/ops.rs b/crates/core/database/src/models/channels/ops.rs new file mode 100644 index 00000000..2598043e --- /dev/null +++ b/crates/core/database/src/models/channels/ops.rs @@ -0,0 +1,50 @@ +use crate::{revolt_result::Result, Channel, FieldsChannel, PartialChannel}; +use revolt_permissions::OverrideField; +mod mongodb; +mod reference; + +#[async_trait] +pub trait AbstractChannels: Sync + Send { + /// Insert a new channel in the database + async fn insert_channel(&self, channel: &Channel) -> Result<()>; + + /// Fetch a channel from the database + async fn fetch_channel(&self, channel_id: &str) -> Result; + + /// Fetch all channels from the database + async fn fetch_channels<'a>(&self, ids: &'a [String]) -> Result>; + + /// Fetch all direct messages for a user + async fn find_direct_messages(&self, user_id: &str) -> Result>; + + // Fetch saved messages channel + async fn find_saved_messages_channel(&self, user_id: &str) -> Result; + + // Fetch direct message channel (DM or Saved Messages) + async fn find_direct_message_channel(&self, user_a: &str, user_b: &str) -> Result; + + /// Insert a user to a group + async fn add_user_to_group(&self, channel_id: &str, user_id: &str) -> Result<()>; + + /// Insert channel role permissions + async fn set_channel_role_permission( + &self, + channel_id: &str, + role_id: &str, + permissions: OverrideField, + ) -> Result<()>; + + // Update channel + async fn update_channel( + &self, + id: &str, + channel_id: &PartialChannel, + remove: Vec, + ) -> Result<()>; + + // Remove a user from a group + async fn remove_user_from_group(&self, channel_id: &str, user_id: &str) -> Result<()>; + + // Delete a channel + async fn delete_channel(&self, channel_id: &Channel) -> Result<()>; +} diff --git a/crates/core/database/src/models/channels/ops/mongodb.rs b/crates/core/database/src/models/channels/ops/mongodb.rs new file mode 100644 index 00000000..d631a7bc --- /dev/null +++ b/crates/core/database/src/models/channels/ops/mongodb.rs @@ -0,0 +1,193 @@ +use super::AbstractChannels; +use crate::{Channel, FieldsChannel, IntoDocumentPath, MongoDb, PartialChannel}; +use bson::Document; +use futures::StreamExt; +use revolt_permissions::OverrideField; +use revolt_result::Result; + +static COL: &str = "channels"; + +#[async_trait] +impl AbstractChannels for MongoDb { + /// Insert a new channel in the database + async fn insert_channel(&self, channel: &Channel) -> Result<()> { + query!(self, insert_one, COL, &channel).map(|_| ()) + } + + /// Fetch a channel from the database + async fn fetch_channel(&self, channel_id: &str) -> Result { + query!(self, find_one_by_id, COL, channel_id)?.ok_or_else(|| create_error!(NotFound)) + } + + /// Fetch all channels from the database + async fn fetch_channels<'a>(&self, ids: &'a [String]) -> Result> { + Ok(self + .col::(COL) + .find( + doc! { + "_id": { + "$in": ids + } + }, + None, + ) + .await + .map_err(|_| create_database_error!("fetch", "channels"))? + .filter_map(|s| async { + if cfg!(debug_assertions) { + Some(s.unwrap()) + } else { + s.ok() + } + }) + .collect() + .await) + } + + /// Fetch all direct messages for a user + async fn find_direct_messages(&self, user_id: &str) -> Result> { + query!( + self, + find, + COL, + doc! { + "$or": [ + { + "$or": [ + { + "channel_type": "DirectMessage" + }, + { + "channel_type": "Group" + } + ], + "recipients": user_id + }, + { + "channel_type": "SavedMessages", + "user": user_id + } + ] + } + ) + } + + // Fetch saved messages channel + async fn find_saved_messages_channel(&self, user_id: &str) -> Result { + query!( + self, + find_one, + COL, + doc! { + "channel_type": "SavedMessages", + "user": user_id + } + )? + .ok_or_else(|| create_error!(InternalError)) + } + + // Fetch direct message channel (DM or Saved Messages) + async fn find_direct_message_channel(&self, user_a: &str, user_b: &str) -> Result { + let doc = match (user_a, user_b) { + self_user if self_user.0 == self_user.1 => { + doc! { + "channel_type": "SavedMessages", + "user": self_user.0 + } + } + users => { + doc! { + "channel_type": "DirectMessage", + "recipients": { + "$all": [ users.0, users.1 ] + } + } + } + }; + query!(self, find_one, COL, doc)?.ok_or_else(|| create_error!(NotFound)) + } + + /// Insert a user to a group + async fn add_user_to_group(&self, channel: &str, user: &str) -> Result<()> { + self.col::(COL) + .update_one( + doc! { + "_id": channel + }, + doc! { + "$push": { + "recipients": user + } + }, + None, + ) + .await + .map(|_| ()) + .map_err(|_| create_database_error!("update_one", "channel")) + } + + /// Insert channel role permissions + async fn set_channel_role_permission( + &self, + channel: &str, + role: &str, + permissions: OverrideField, + ) -> Result<()> { + self.col::(COL) + .update_one( + doc! { "_id": channel }, + doc! { + "$set": { + "role_permissions.".to_owned() + role: permissions + } + }, + None, + ) + .await + .map(|_| ()) + .map_err(|_| create_database_error!("update_one", "channel")) + } + + // Update channel + async fn update_channel( + &self, + id: &str, + channel: &PartialChannel, + remove: Vec, + ) -> Result<()> { + query!( + self, + update_one_by_id, + COL, + id, + channel, + remove.iter().map(|x| x as &dyn IntoDocumentPath).collect(), + None + ) + .map(|_| ()) + } + + // Remove a user from a group + async fn remove_user_from_group(&self, channel: &str, user: &str) -> Result<()> { + self.col::(COL) + .update_one( + doc! { + "_id": channel + }, + doc! { + "$pull": { + "recipients": user + } + }, + None, + ) + .await + .map(|_| ()) + .map_err(|_| create_database_error!("update_one", "channels")) + } + + // Delete a channel + async fn delete_channel(&self, channel: &Channel) -> Result<()> { + query!(self, delete_one_by_id, COL, &channel.id()).map(|_| ()) + } +} diff --git a/crates/core/database/src/models/channels/ops/reference.rs b/crates/core/database/src/models/channels/ops/reference.rs new file mode 100644 index 00000000..a2c849a4 --- /dev/null +++ b/crates/core/database/src/models/channels/ops/reference.rs @@ -0,0 +1,157 @@ +use std::collections::hash_map::Entry; + +use super::AbstractChannels; +use crate::ReferenceDb; +use crate::{Channel, FieldsChannel, PartialChannel}; +use revolt_permissions::OverrideField; +use revolt_result::Result; + +#[async_trait] +impl AbstractChannels for ReferenceDb { + /// Insert a new channel in the database + async fn insert_channel(&self, channel: &Channel) -> Result<()> { + let mut channels = self.channels.lock().await; + if let Entry::Vacant(entry) = channels.entry(channel.id()) { + entry.insert(channel.clone()); + Ok(()) + } else { + Err(create_database_error!("insert", "channel")) + } + } + + /// Fetch a channel from the database + async fn fetch_channel(&self, channel_id: &str) -> Result { + let channels = self.channels.lock().await; + channels + .get(channel_id) + .cloned() + .ok_or_else(|| create_error!(NotFound)) + } + + /// Fetch all channels from the database + async fn fetch_channels<'a>(&self, ids: &'a [String]) -> Result> { + let channels = self.channels.lock().await; + ids.iter() + .map(|id| { + channels + .get(id) + .cloned() + .ok_or_else(|| create_error!(NotFound)) + }) + .collect() + } + + /// Fetch all direct messages for a user + async fn find_direct_messages(&self, user_id: &str) -> Result> { + let channels = self.channels.lock().await; + Ok(channels + .values() + .filter(|channel| channel.contains_user(user_id)) + .cloned() + .collect()) + } + + // Fetch saved messages channel + async fn find_saved_messages_channel(&self, user_id: &str) -> Result { + let channels = self.channels.lock().await; + channels + .get(user_id) + .cloned() + .ok_or_else(|| create_database_error!("fetch", "channel")) + } + + // Fetch direct message channel (DM or Saved Messages) + async fn find_direct_message_channel(&self, user_a: &str, user_b: &str) -> Result { + let channels = self.channels.lock().await; + for (_, data) in channels.iter() { + if data.contains_user(user_a) && data.contains_user(user_b) { + return Ok(data.to_owned()); + } + } + Err(create_error!(NotFound)) + } + /// Insert a user to a group + async fn add_user_to_group(&self, channel_id: &str, user_id: &str) -> Result<()> { + let mut channels = self.channels.lock().await; + + if let Some(Channel::Group { recipients, .. }) = channels.get_mut(channel_id) { + recipients.push(String::from(user_id)); + Ok(()) + } else { + Err(create_error!(InvalidOperation)) + } + } + /// Insert channel role permissions + async fn set_channel_role_permission( + &self, + channel_id: &str, + role_id: &str, + permissions: OverrideField, + ) -> Result<()> { + let mut channels = self.channels.lock().await; + + if let Some(mut channel) = channels.get_mut(channel_id) { + match &mut channel { + Channel::TextChannel { + role_permissions, .. + } + | Channel::VoiceChannel { + role_permissions, .. + } => { + if role_permissions.get(role_id).is_some() { + role_permissions.remove(role_id); + role_permissions.insert(String::from(role_id), permissions); + + Ok(()) + } else { + Err(create_error!(NotFound)) + } + } + _ => Err(create_error!(NotFound)), + } + } else { + Err(create_error!(NotFound)) + } + } + + // Update channel + async fn update_channel( + &self, + id: &str, + channel: &PartialChannel, + remove: Vec, + ) -> Result<()> { + let mut channels = self.channels.lock().await; + if let Some(channel_data) = channels.get_mut(id) { + channel_data.apply_options(channel.to_owned()); + channel_data.remove_fields(remove); + Ok(()) + } else { + Err(create_error!(NotFound)) + } + } + + // Remove a user from a group + async fn remove_user_from_group(&self, channel: &str, user: &str) -> Result<()> { + let mut channels = self.channels.lock().await; + if let Some(channel_data) = channels.get_mut(channel) { + if channel_data.users()?.contains(&String::from(user)) { + channel_data.users()?.retain(|x| x != user); + return Ok(()); + } else { + return Err(create_error!(NotFound)); + } + } + Err(create_error!(NotFound)) + } + + // Delete a channel + async fn delete_channel(&self, channel: &Channel) -> Result<()> { + let mut channels = self.channels.lock().await; + if channels.remove(&channel.id()).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 786c4559..5e565d83 100644 --- a/crates/core/database/src/models/mod.rs +++ b/crates/core/database/src/models/mod.rs @@ -1,6 +1,7 @@ mod admin_migrations; mod bots; mod channel_webhooks; +mod channels; mod files; mod safety_strikes; mod server_members; @@ -11,6 +12,7 @@ mod users; pub use admin_migrations::*; pub use bots::*; pub use channel_webhooks::*; +pub use channels::*; pub use files::*; pub use safety_strikes::*; pub use server_members::*; @@ -25,13 +27,14 @@ pub trait AbstractDatabase: + Send + admin_migrations::AbstractMigrations + bots::AbstractBots + + channels::AbstractChannels + + channel_webhooks::AbstractWebhooks + files::AbstractAttachments + safety_strikes::AbstractAccountStrikes + server_members::AbstractServerMembers + servers::AbstractServers + user_settings::AbstractUserSettings + users::AbstractUsers - + channel_webhooks::AbstractWebhooks { } diff --git a/crates/core/database/src/util/bridge/v0.rs b/crates/core/database/src/util/bridge/v0.rs index a35d40a6..4d6c13c9 100644 --- a/crates/core/database/src/util/bridge/v0.rs +++ b/crates/core/database/src/util/bridge/v0.rs @@ -80,6 +80,123 @@ impl From for FieldsWebhook { } } +impl From for Channel { + fn from(value: crate::Channel) -> Self { + match value { + crate::Channel::SavedMessages { id, user } => Channel::SavedMessages { id, user }, + crate::Channel::DirectMessage { + id, + active, + recipients, + last_message_id, + } => Channel::DirectMessage { + id, + active, + recipients, + last_message_id, + }, + crate::Channel::Group { + id, + name, + owner, + description, + recipients, + icon, + last_message_id, + permissions, + nsfw, + } => Channel::Group { + id, + name, + owner, + description, + recipients, + icon: icon.map(|file| file.into()), + last_message_id, + permissions, + nsfw, + }, + crate::Channel::TextChannel { + id, + server, + name, + description, + icon, + last_message_id, + default_permissions, + role_permissions, + nsfw, + } => Channel::TextChannel { + id, + server, + name, + description, + icon: icon.map(|file| file.into()), + last_message_id, + default_permissions, + role_permissions, + nsfw, + }, + crate::Channel::VoiceChannel { + id, + server, + name, + description, + icon, + default_permissions, + role_permissions, + nsfw, + } => Channel::VoiceChannel { + id, + server, + name, + description, + icon: icon.map(|file| file.into()), + default_permissions, + role_permissions, + nsfw, + }, + } + } +} + +impl From for PartialChannel { + fn from(value: crate::PartialChannel) -> Self { + PartialChannel { + name: value.name, + owner: value.owner, + description: value.description, + icon: value.icon.map(|file| file.into()), + nsfw: value.nsfw, + active: value.active, + permissions: value.permissions, + role_permissions: value.role_permissions, + default_permissions: value.default_permissions, + last_message_id: value.last_message_id, + } + } +} + +impl From for crate::FieldsChannel { + fn from(value: FieldsChannel) -> Self { + match value { + FieldsChannel::Description => crate::FieldsChannel::Description, + FieldsChannel::Icon => crate::FieldsChannel::Icon, + FieldsChannel::DefaultPermissions => crate::FieldsChannel::DefaultPermissions, + } + } +} + +impl From for FieldsChannel { + fn from(value: crate::FieldsChannel) -> Self { + match value { + crate::FieldsChannel::Description => FieldsChannel::Description, + crate::FieldsChannel::Icon => FieldsChannel::Icon, + crate::FieldsChannel::DefaultPermissions => FieldsChannel::DefaultPermissions, + } + } +} + impl From for File { fn from(value: crate::File) -> Self { File { diff --git a/crates/core/database/src/util/reference.rs b/crates/core/database/src/util/reference.rs index 5a6149a3..0b155f91 100644 --- a/crates/core/database/src/util/reference.rs +++ b/crates/core/database/src/util/reference.rs @@ -7,7 +7,7 @@ use schemars::{ JsonSchema, }; -use crate::{Bot, Database}; +use crate::{Bot, Database, Webhook}; /// Reference to some object in the database #[derive(Serialize, Deserialize)] @@ -26,6 +26,11 @@ impl Reference { pub async fn as_bot(&self, db: &Database) -> Result { db.fetch_bot(&self.id).await } + + /// Fetch webhook from Ref + pub async fn as_webhook(&self, db: &Database) -> Result { + db.fetch_webhook(&self.id).await + } } #[cfg(feature = "rocket-impl")] diff --git a/crates/core/models/Cargo.toml b/crates/core/models/Cargo.toml index 1cb23bc9..aad94c97 100644 --- a/crates/core/models/Cargo.toml +++ b/crates/core/models/Cargo.toml @@ -17,6 +17,9 @@ partials = [ "dep:revolt_optional_struct", "serde", "schemas" ] default = [ "serde", "partials" ] [dependencies] +# Core +revolt-permissions = { version = "0.6.0-rc.1", path = "../permissions", features = [ "serde" ] } + # Serialisation revolt_optional_struct = { version = "0.2.0", optional = true } serde = { version = "1", features = ["derive"], optional = true } diff --git a/crates/core/models/src/v0/channels.rs b/crates/core/models/src/v0/channels.rs new file mode 100644 index 00000000..b2f7d3c6 --- /dev/null +++ b/crates/core/models/src/v0/channels.rs @@ -0,0 +1,208 @@ +use super::File; + +use revolt_permissions::OverrideField; +use std::collections::HashMap; + +auto_derived!( + /// Channel + pub enum Channel { + /// Personal "Saved Notes" channel which allows users to save messages + SavedMessages { + /// Unique Id + #[cfg_attr(feature = "serde", serde(rename = "_id"))] + id: String, + /// Id of the user this channel belongs to + user: String, + }, + /// Direct message channel between two users + DirectMessage { + /// Unique Id + #[cfg_attr(feature = "serde", serde(rename = "_id"))] + id: String, + + /// Whether this direct message channel is currently open on both sides + active: bool, + /// 2-tuple of user ids participating in direct message + recipients: Vec, + /// Id of the last message sent in this channel + #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] + last_message_id: Option, + }, + /// Group channel between 1 or more participants + Group { + /// Unique Id + #[cfg_attr(feature = "serde", serde(rename = "_id"))] + id: String, + + /// Display name of the channel + name: String, + /// User id of the owner of the group + owner: String, + /// Channel description + #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] + description: Option, + /// Array of user ids participating in channel + recipients: Vec, + + /// Custom icon attachment + #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] + icon: Option, + /// Id of the last message sent in this channel + #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] + last_message_id: Option, + + /// Permissions assigned to members of this group + /// (does not apply to the owner of the group) + #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] + permissions: Option, + + /// Whether this group is marked as not safe for work + #[cfg_attr( + feature = "serde", + serde(skip_serializing_if = "crate::if_false", default) + )] + nsfw: bool, + }, + /// Text channel belonging to a server + TextChannel { + /// Unique Id + #[cfg_attr(feature = "serde", serde(rename = "_id"))] + id: String, + /// Id of the server this channel belongs to + server: String, + + /// Display name of the channel + name: String, + /// Channel description + #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] + description: Option, + + /// Custom icon attachment + #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] + icon: Option, + /// Id of the last message sent in this channel + #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] + last_message_id: Option, + + /// Default permissions assigned to users in this channel + #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] + default_permissions: Option, + /// Permissions assigned based on role to this channel + #[cfg_attr( + feature = "serde", + serde( + default = "HashMap::::new", + skip_serializing_if = "HashMap::::is_empty" + ) + )] + role_permissions: HashMap, + + /// Whether this channel is marked as not safe for work + #[cfg_attr( + feature = "serde", + serde(skip_serializing_if = "crate::if_false", default) + )] + nsfw: bool, + }, + /// Voice channel belonging to a server + VoiceChannel { + /// Unique Id + #[cfg_attr(feature = "serde", serde(rename = "_id"))] + id: String, + /// Id of the server this channel belongs to + server: String, + + /// Display name of the channel + name: String, + #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] + /// Channel description + description: Option, + /// Custom icon attachment + #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] + icon: Option, + + /// Default permissions assigned to users in this channel + #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] + default_permissions: Option, + /// Permissions assigned based on role to this channel + #[cfg_attr( + feature = "serde", + serde( + default = "HashMap::::new", + skip_serializing_if = "HashMap::::is_empty" + ) + )] + role_permissions: HashMap, + + /// Whether this channel is marked as not safe for work + #[cfg_attr( + feature = "serde", + serde(skip_serializing_if = "crate::if_false", default) + )] + nsfw: bool, + }, + } + + /// Partial representation of a channel + #[derive(Default)] + pub struct PartialChannel { + #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] + pub name: Option, + #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] + pub owner: Option, + #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] + pub description: Option, + #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] + pub icon: Option, + #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] + pub nsfw: Option, + #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] + pub active: Option, + #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] + pub permissions: Option, + #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] + pub role_permissions: Option>, + #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] + pub default_permissions: Option, + #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] + pub last_message_id: Option, + } + + /// Optional fields on channel object + pub enum FieldsChannel { + Description, + Icon, + DefaultPermissions, + } + + /// New webhook information + #[cfg_attr(feature = "validator", derive(validator::Validate))] + pub struct DataEditChannel { + /// Channel name + #[cfg_attr(feature = "validator", validate(length(min = 1, max = 32)))] + pub name: Option, + + /// Channel description + #[cfg_attr(feature = "validator", validate(length(min = 0, max = 1024)))] + pub description: Option, + + /// Group owner + pub owner: Option, + + /// Icon + /// + /// Provide an Autumn attachment Id. + #[cfg_attr(feature = "validator", validate(length(min = 1, max = 128)))] + pub icon: Option, + + /// Whether this channel is age-restricted + pub nsfw: Option, + + /// Whether this channel is archived + pub archived: Option, + + /// Fields to remove from channel + #[cfg_attr(feature = "serde", serde(default))] + pub remove: Option>, + } +); diff --git a/crates/core/models/src/v0/mod.rs b/crates/core/models/src/v0/mod.rs index 74b2cdac..479af102 100644 --- a/crates/core/models/src/v0/mod.rs +++ b/crates/core/models/src/v0/mod.rs @@ -1,11 +1,13 @@ mod account_strikes; mod bots; mod channel_webhooks; +mod channels; mod files; mod users; pub use account_strikes::*; pub use bots::*; pub use channel_webhooks::*; +pub use channels::*; pub use files::*; pub use users::*; diff --git a/crates/core/permissions/Cargo.toml b/crates/core/permissions/Cargo.toml index 62dfa977..d70eec15 100644 --- a/crates/core/permissions/Cargo.toml +++ b/crates/core/permissions/Cargo.toml @@ -9,10 +9,12 @@ description = "Revolt Backend: Permission Logic" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [features] +bson = ["dep:bson"] serde = [ "dep:serde" ] schemas = [ "dep:schemars" ] try-from-primitive = [ "dep:num_enum" ] + [dev-dependencies] # Async async-std = { version = "1.8.0", features = ["attributes"] } @@ -28,6 +30,7 @@ async-trait = "0.1.51" # Serialisation serde = { version = "1", features = ["derive"], optional = true } +bson = { version = "2.1.0", optional = true} # Spec Generation -schemars = { version = "0.8.8", optional = true } +schemars = { version = "0.8.8", optional = true } \ No newline at end of file diff --git a/crates/core/permissions/src/models/server.rs b/crates/core/permissions/src/models/server.rs index 7c11e98d..722b1205 100644 --- a/crates/core/permissions/src/models/server.rs +++ b/crates/core/permissions/src/models/server.rs @@ -1,6 +1,10 @@ +#[cfg(feature = "schemas")] +use schemars::JsonSchema; + /// Representation of a single permission override -#[derive(Debug, Clone, Copy, Eq, PartialEq)] +#[derive(Debug, Clone, Eq, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "schemas", derive(JsonSchema))] pub struct Override { /// Allow bit flags pub allow: u64, @@ -8,10 +12,43 @@ pub struct Override { pub deny: u64, } +/// Data permissions Field - contains both allow and deny +#[derive(Debug, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "schemas", derive(JsonSchema))] +pub struct DataPermissionsField { + pub permissions: Override, +} + +/// Data permissions Value - contains allow +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "schemas", derive(JsonSchema))] +pub struct DataPermissionsValue { + pub permissions: u64, +} + +/// Data permissions Poly - can contain either Value or Field +#[derive(Debug, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "schemas", derive(JsonSchema))] +#[serde(untagged)] +pub enum DataPermissionPoly { + Value { + /// Permission values to set for members in a `Group` + permissions: u64, + }, + Field { + /// Allow / deny values to set for members in this `TextChannel` or `VoiceChannel` + permissions: Override, + }, +} + /// Representation of a single permission override /// as it appears on models and in the database -#[derive(/*JsonSchema, */ Debug, Clone, Copy, Default, Eq, PartialEq)] +#[derive(Debug, Clone, Copy, Default, Eq, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "schemas", derive(JsonSchema))] pub struct OverrideField { /// Allow bit flags a: i64, @@ -49,8 +86,10 @@ impl From for Override { } } -/*impl From for Bson { +#[cfg(feature = "bson")] +use bson::Bson; +impl From for Bson { fn from(v: OverrideField) -> Self { Self::Document(bson::to_document(&v).unwrap()) } -}*/ +} diff --git a/crates/delta/Cargo.toml b/crates/delta/Cargo.toml index 4257fc03..f59d2233 100644 --- a/crates/delta/Cargo.toml +++ b/crates/delta/Cargo.toml @@ -59,6 +59,7 @@ revolt-quark = { path = "../quark" } revolt-database = { path = "../core/database", features = [ "rocket-impl", "redis-is-patched" ] } revolt-models = { path = "../core/models", features = [ "schemas", "validator" ] } revolt-result = { path = "../core/result", features = [ "rocket", "okapi" ] } +revolt-permissions = { path = "../core/permissions", features = [ "schemas" ] } [build-dependencies] vergen = "7.5.0" diff --git a/crates/delta/src/routes/servers/permissions_set_default.rs b/crates/delta/src/routes/servers/permissions_set_default.rs index cd651387..134cbc52 100644 --- a/crates/delta/src/routes/servers/permissions_set_default.rs +++ b/crates/delta/src/routes/servers/permissions_set_default.rs @@ -1,18 +1,9 @@ -use rocket::serde::json::Json; -use serde::Deserialize; - +use revolt_permissions::DataPermissionsValue; use revolt_quark::{ models::{server::PartialServer, Server, User}, perms, Db, Permission, Ref, Result, }; - -/// # Permission Value -#[derive(Deserialize, JsonSchema)] -pub struct DataSetServerDefaultPermission { - /// Default member permission value - permissions: u64, -} - +use rocket::serde::json::Json; /// # Set Default Permission /// /// Sets permissions for the default role in this server. @@ -22,7 +13,7 @@ pub async fn req( db: &Db, user: User, target: Ref, - data: Json, + data: Json, ) -> Result> { let data = data.into_inner(); diff --git a/crates/delta/src/routes/webhooks/webhook_delete.rs b/crates/delta/src/routes/webhooks/webhook_delete.rs index 03c79f63..7de6f5f5 100644 --- a/crates/delta/src/routes/webhooks/webhook_delete.rs +++ b/crates/delta/src/routes/webhooks/webhook_delete.rs @@ -1,4 +1,4 @@ -use revolt_database::Database; +use revolt_database::{util::reference::Reference, Database}; use revolt_quark::{models::User, perms, Db, Error, Permission, Result}; use rocket::State; use rocket_empty::EmptyResponse; @@ -12,13 +12,9 @@ pub async fn webhook_delete( db: &State, legacy_db: &Db, user: User, - webhook_id: String, + webhook_id: Reference, ) -> Result { - let webhook = db - .fetch_webhook(&webhook_id) - .await - .map_err(Error::from_core)?; - + let webhook = webhook_id.as_webhook(db).await.map_err(Error::from_core)?; let channel = legacy_db.fetch_channel(&webhook.channel_id).await?; perms(&user) diff --git a/crates/delta/src/routes/webhooks/webhook_delete_token.rs b/crates/delta/src/routes/webhooks/webhook_delete_token.rs index 2b2ed0cd..c5435302 100644 --- a/crates/delta/src/routes/webhooks/webhook_delete_token.rs +++ b/crates/delta/src/routes/webhooks/webhook_delete_token.rs @@ -1,4 +1,4 @@ -use revolt_database::Database; +use revolt_database::{util::reference::Reference, Database}; use revolt_result::Result; use rocket::State; use rocket_empty::EmptyResponse; @@ -10,10 +10,10 @@ use rocket_empty::EmptyResponse; #[delete("//")] pub async fn webhook_delete_token( db: &State, - webhook_id: String, + webhook_id: Reference, token: String, ) -> Result { - let webhook = db.fetch_webhook(&webhook_id).await?; + let webhook = webhook_id.as_webhook(db).await?; webhook.assert_token(&token)?; webhook.delete(db).await.map(|_| EmptyResponse) } diff --git a/crates/delta/src/routes/webhooks/webhook_edit.rs b/crates/delta/src/routes/webhooks/webhook_edit.rs index 3c8a1301..2d50a891 100644 --- a/crates/delta/src/routes/webhooks/webhook_edit.rs +++ b/crates/delta/src/routes/webhooks/webhook_edit.rs @@ -1,4 +1,4 @@ -use revolt_database::{Database, PartialWebhook}; +use revolt_database::{util::reference::Reference, Database, PartialWebhook}; use revolt_models::v0::{DataEditWebhook, Webhook}; use revolt_quark::{models::User, perms, Db, Error, Permission, Result}; use rocket::{serde::json::Json, State}; @@ -12,7 +12,7 @@ use validator::Validate; pub async fn webhook_edit( db: &State, legacy_db: &Db, - webhook_id: String, + webhook_id: Reference, user: User, data: Json, ) -> Result> { @@ -20,11 +20,7 @@ pub async fn webhook_edit( data.validate() .map_err(|error| Error::FailedValidation { error })?; - let mut webhook = db - .fetch_webhook(&webhook_id) - .await - .map_err(Error::from_core)?; - + let mut webhook = webhook_id.as_webhook(db).await.map_err(Error::from_core)?; let channel = legacy_db.fetch_channel(&webhook.channel_id).await?; perms(&user) diff --git a/crates/delta/src/routes/webhooks/webhook_edit_token.rs b/crates/delta/src/routes/webhooks/webhook_edit_token.rs index 2bf0d2f7..18250f6f 100644 --- a/crates/delta/src/routes/webhooks/webhook_edit_token.rs +++ b/crates/delta/src/routes/webhooks/webhook_edit_token.rs @@ -1,3 +1,4 @@ +use revolt_database::util::reference::Reference; use revolt_database::{Database, PartialWebhook}; use revolt_models::v0::{DataEditWebhook, Webhook}; use revolt_models::validator::Validate; @@ -11,7 +12,7 @@ use rocket::{serde::json::Json, State}; #[patch("//", data = "")] pub async fn webhook_edit_token( db: &State, - webhook_id: String, + webhook_id: Reference, token: String, data: Json, ) -> Result> { @@ -22,7 +23,7 @@ pub async fn webhook_edit_token( }) })?; - let mut webhook = db.fetch_webhook(&webhook_id).await?; + let mut webhook = webhook_id.as_webhook(db).await?; webhook.assert_token(&token)?; if data.name.is_none() && data.avatar.is_none() && data.remove.is_empty() { diff --git a/crates/delta/src/routes/webhooks/webhook_execute.rs b/crates/delta/src/routes/webhooks/webhook_execute.rs index 60a56f93..b79503ab 100644 --- a/crates/delta/src/routes/webhooks/webhook_execute.rs +++ b/crates/delta/src/routes/webhooks/webhook_execute.rs @@ -1,4 +1,4 @@ -use revolt_database::Database; +use revolt_database::{util::reference::Reference, Database}; use revolt_quark::{ models::message::{DataMessageSend, Message}, types::push::MessageAuthor, @@ -17,7 +17,7 @@ use validator::Validate; pub async fn webhook_execute( db: &State, legacy_db: &Db, - webhook_id: String, + webhook_id: Reference, token: String, data: Json, idempotency: IdempotencyKey, @@ -26,11 +26,7 @@ pub async fn webhook_execute( data.validate() .map_err(|error| Error::FailedValidation { error })?; - let webhook = db - .fetch_webhook(&webhook_id) - .await - .map_err(Error::from_core)?; - + let webhook = webhook_id.as_webhook(db).await.map_err(Error::from_core)?; webhook.assert_token(&token).map_err(Error::from_core)?; // TODO: webhooks can currently always send masquerades, files, embeds, reactions (interactions) diff --git a/crates/delta/src/routes/webhooks/webhook_execute_github.rs b/crates/delta/src/routes/webhooks/webhook_execute_github.rs index dd890242..c165b92d 100644 --- a/crates/delta/src/routes/webhooks/webhook_execute_github.rs +++ b/crates/delta/src/routes/webhooks/webhook_execute_github.rs @@ -1,4 +1,4 @@ -use revolt_database::Database; +use revolt_database::{util::reference::Reference, Database}; use revolt_models::v0::Webhook; use revolt_quark::{ models::{message::SendableEmbed, Message}, @@ -752,16 +752,12 @@ fn convert_event(data: &str, event_name: &str) -> Result { pub async fn webhook_execute_github( db: &State, legacy_db: &Db, - webhook_id: String, + webhook_id: Reference, token: String, event: EventHeader<'_>, data: String, ) -> Result<()> { - let webhook = db - .fetch_webhook(&webhook_id) - .await - .map_err(Error::from_core)?; - + let webhook = webhook_id.as_webhook(db).await.map_err(Error::from_core)?; webhook.assert_token(&token).map_err(Error::from_core)?; let channel = legacy_db.fetch_channel(&webhook.channel_id).await?; diff --git a/crates/delta/src/routes/webhooks/webhook_fetch.rs b/crates/delta/src/routes/webhooks/webhook_fetch.rs index e1d3ae02..9f54d66e 100644 --- a/crates/delta/src/routes/webhooks/webhook_fetch.rs +++ b/crates/delta/src/routes/webhooks/webhook_fetch.rs @@ -1,4 +1,4 @@ -use revolt_database::Database; +use revolt_database::{util::reference::Reference, Database}; use revolt_models::v0::{ResponseWebhook, Webhook}; use revolt_quark::{models::User, perms, Db, Error, Permission, Result}; use rocket::{serde::json::Json, State}; @@ -11,14 +11,10 @@ use rocket::{serde::json::Json, State}; pub async fn webhook_fetch( db: &State, legacy_db: &Db, - webhook_id: String, + webhook_id: Reference, user: User, ) -> Result> { - let webhook = db - .fetch_webhook(&webhook_id) - .await - .map_err(Error::from_core)?; - + let webhook = webhook_id.as_webhook(db).await.map_err(Error::from_core)?; let channel = legacy_db.fetch_channel(&webhook.channel_id).await?; perms(&user) diff --git a/crates/delta/src/routes/webhooks/webhook_fetch_token.rs b/crates/delta/src/routes/webhooks/webhook_fetch_token.rs index bc460f45..3ccec4f3 100644 --- a/crates/delta/src/routes/webhooks/webhook_fetch_token.rs +++ b/crates/delta/src/routes/webhooks/webhook_fetch_token.rs @@ -1,4 +1,4 @@ -use revolt_database::Database; +use revolt_database::{util::reference::Reference, Database}; use revolt_models::v0::Webhook; use revolt_result::Result; use rocket::{serde::json::Json, State}; @@ -10,10 +10,10 @@ use rocket::{serde::json::Json, State}; #[get("//")] pub async fn webhook_fetch_token( db: &State, - webhook_id: String, + webhook_id: Reference, token: String, ) -> Result> { - let webhook = db.fetch_webhook(&webhook_id).await?; + let webhook = webhook_id.as_webhook(db).await?; webhook.assert_token(&token)?; Ok(Json(webhook.into())) }