diff --git a/Cargo.lock b/Cargo.lock index af401d26..20b88218 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2974,6 +2974,7 @@ dependencies = [ "bson", "num_enum 0.6.1", "once_cell", + "revolt-result", "schemars", "serde", ] diff --git a/crates/core/database/src/events/client.rs b/crates/core/database/src/events/client.rs index 376b9fe3..cebc0fee 100644 --- a/crates/core/database/src/events/client.rs +++ b/crates/core/database/src/events/client.rs @@ -2,9 +2,9 @@ use authifier::AuthifierEvent; use serde::{Deserialize, Serialize}; use revolt_models::v0::{ - Channel, Emoji, FieldsChannel, FieldsMember, FieldsRole, FieldsServer, FieldsWebhook, - MemberCompositeKey, PartialChannel, PartialMember, PartialRole, PartialServer, PartialWebhook, - Server, UserSettings, Webhook, + Channel, Emoji, FieldsChannel, FieldsMember, FieldsRole, FieldsServer, FieldsUser, + FieldsWebhook, MemberCompositeKey, PartialChannel, PartialMember, PartialRole, PartialServer, + PartialUser, PartialWebhook, Server, UserSettings, Webhook, }; use revolt_result::Error; @@ -145,7 +145,7 @@ pub enum EventV1 { /// Server role deleted ServerRoleDelete { id: String, role_id: String }, - /*/// Update existing user + /// Update existing user UserUpdate { id: String, data: PartialUser, @@ -153,7 +153,7 @@ pub enum EventV1 { event_id: Option, }, - /// Relationship with another user changed + /*/// Relationship with another user changed UserRelationship { id: String, user: User, diff --git a/crates/core/database/src/models/messages/model.rs b/crates/core/database/src/models/messages/model.rs index 50e43fc3..7d7e1ecf 100644 --- a/crates/core/database/src/models/messages/model.rs +++ b/crates/core/database/src/models/messages/model.rs @@ -1,8 +1,9 @@ use indexmap::{IndexMap, IndexSet}; use iso8601_timestamp::Timestamp; use revolt_models::v0::{Embed, MessageSort, MessageWebhook}; +use revolt_result::{create_error, Result}; -use crate::File; +use crate::{Database, File}; auto_derived_partial!( /// Message @@ -166,7 +167,17 @@ auto_derived!( ); #[allow(clippy::disallowed_methods)] -impl Message {} +impl Message { + /// Send a message without any notifications + pub async fn send_without_notifications(&mut self, db: &Database) -> Result<()> { + todo!() + } + + /// Send a message + pub async fn send(&mut self) -> Result<()> { + todo!() + } +} impl Interactions { /// Validate interactions info is correct diff --git a/crates/core/database/src/models/ratelimit_events/model.rs b/crates/core/database/src/models/ratelimit_events/model.rs index 6ae81217..b25858df 100644 --- a/crates/core/database/src/models/ratelimit_events/model.rs +++ b/crates/core/database/src/models/ratelimit_events/model.rs @@ -1,5 +1,10 @@ use std::fmt; +use revolt_result::Result; +use ulid::Ulid; + +use crate::Database; + auto_derived!( /// Ratelimit Event pub struct RatelimitEvent { @@ -23,3 +28,20 @@ impl fmt::Display for RatelimitEventType { fmt::Debug::fmt(self, f) } } + +#[allow(clippy::disallowed_methods)] +impl RatelimitEvent { + /// Create ratelimit event + pub async fn create( + db: &Database, + target_id: String, + event_type: RatelimitEventType, + ) -> Result<()> { + db.insert_ratelimit_event(&RatelimitEvent { + id: Ulid::new().to_string(), + target_id, + event_type, + }) + .await + } +} diff --git a/crates/core/database/src/models/server_members/model.rs b/crates/core/database/src/models/server_members/model.rs index 629ffac4..18d8031a 100644 --- a/crates/core/database/src/models/server_members/model.rs +++ b/crates/core/database/src/models/server_members/model.rs @@ -1,7 +1,11 @@ use iso8601_timestamp::Timestamp; -use revolt_result::Result; +use revolt_permissions::{calculate_channel_permissions, ChannelPermission}; +use revolt_result::{create_error, Result}; -use crate::{Database, File, Server}; +use crate::{ + events::client::EventV1, util::permissions::DatabasePermissionQuery, Database, File, Server, + User, +}; auto_derived_partial!( /// Server Member @@ -57,7 +61,85 @@ auto_derived!( } ); +#[allow(clippy::disallowed_methods)] impl Member { + /// Create a new member in a server + pub async fn create( + db: &Database, + server: &Server, + user: &User, + // channels: Option>, + //) -> Result> { + ) -> Result<()> { + if db.fetch_ban(&server.id, &user.id).await.is_ok() { + return Err(create_error!(Banned)); + } + + let member = Member { + id: MemberCompositeKey { + server: server.id.to_string(), + user: user.id.to_string(), + }, + ..Default::default() + }; + + db.insert_member(&member).await?; + + let mut channels = vec![]; + + if true { + let query = DatabasePermissionQuery::new(db, user).server(server); + let existing_channels = db.fetch_channels(&server.channels).await?; + + for channel in existing_channels { + let mut channel_query = query.clone().channel(&channel); + + if calculate_channel_permissions(&mut channel_query) + .await + .has_channel_permission(ChannelPermission::ViewChannel) + { + channels.push(channel); + } + } + } + + EventV1::ServerMemberJoin { + id: server.id.clone(), + user: user.id.clone(), + } + .p(server.id.clone()) + .await; + + EventV1::ServerCreate { + id: server.id.clone(), + server: server.clone().into(), + channels: channels + .clone() + .into_iter() + .map(|channel| channel.into()) + .collect(), + } + .private(user.id.clone()) + .await; + + if let Some(_id) = server + .system_messages + .as_ref() + .and_then(|x| x.user_joined.as_ref()) + { + /* TODO: SystemMessage::UserJoined { + id: user.id.clone(), + } + .into_message(id.to_string()) + .create_no_web_push(db, id, false) + .await + .ok(); */ + } + + // Ok(channels) + Ok(()) + } + /// Update member data pub async fn update<'a>( &mut self, diff --git a/crates/core/database/src/models/users/model.rs b/crates/core/database/src/models/users/model.rs index cd823a02..8b0fcfe0 100644 --- a/crates/core/database/src/models/users/model.rs +++ b/crates/core/database/src/models/users/model.rs @@ -1,6 +1,6 @@ use std::{collections::HashSet, time::Duration}; -use crate::{Database, File, RatelimitEvent}; +use crate::{events::client::EventV1, Database, File, RatelimitEvent}; use once_cell::sync::Lazy; use rand::seq::SliceRandom; @@ -51,6 +51,15 @@ auto_derived_partial!( ); auto_derived!( + /// Optional fields on user object + pub enum FieldsUser { + Avatar, + StatusText, + StatusPresence, + ProfileContent, + ProfileBackground, + } + /// User's relationship with another user (or themselves) pub enum RelationshipStatus { None, @@ -108,15 +117,6 @@ auto_derived!( /// Id of the owner of this bot pub owner: String, } - - /// Optional fields on user object - pub enum FieldsUser { - Avatar, - StatusText, - StatusPresence, - ProfileContent, - ProfileBackground, - } ); pub static DISCRIMINATOR_SEARCH_SPACE: Lazy> = Lazy::new(|| { @@ -229,11 +229,11 @@ impl User { return Err(create_error!(DiscriminatorChangeRatelimited)); } - db.insert_ratelimit_event(&RatelimitEvent { - id: Ulid::new().to_string(), + RatelimitEvent::create( + db, target_id, - event_type: crate::RatelimitEventType::DiscriminatorChange, - }) + crate::RatelimitEventType::DiscriminatorChange, + ) .await?; } } @@ -245,6 +245,40 @@ impl User { .to_string()) } + /// Update a user's username + pub async fn update_username(&mut self, db: &Database, username: String) -> Result<()> { + let username = User::validate_username(username)?; + if self.username.to_lowercase() == username.to_lowercase() { + self.update( + db, + PartialUser { + username: Some(username), + ..Default::default() + }, + vec![], + ) + .await + } else { + self.update( + db, + PartialUser { + discriminator: Some( + User::find_discriminator( + db, + &username, + Some((self.discriminator.to_string(), self.id.clone())), + ) + .await?, + ), + username: Some(username), + ..Default::default() + }, + vec![], + ) + .await + } + } + /// Check whether a username is already in use by another user #[allow(dead_code)] async fn is_username_taken(db: &Database, username: &str) -> Result { @@ -272,13 +306,14 @@ impl User { self.apply_options(partial.clone()); db.update_user(&self.id, &partial, remove.clone()).await?; - /* // TODO: EventV1::UserUpdate { + EventV1::UserUpdate { id: self.id.clone(), - data: partial, - clear: remove, + data: partial.into(), + clear: remove.into_iter().map(|v| v.into()).collect(), + event_id: Some(Ulid::new().to_string()), } .p_user(self.id.clone(), db) - .await; */ + .await; Ok(()) } diff --git a/crates/core/database/src/util/bridge/v0.rs b/crates/core/database/src/util/bridge/v0.rs index dddbaded..f2111c7e 100644 --- a/crates/core/database/src/util/bridge/v0.rs +++ b/crates/core/database/src/util/bridge/v0.rs @@ -34,6 +34,24 @@ impl From for Bot { } } +impl From for crate::FieldsBot { + fn from(value: FieldsBot) -> Self { + match value { + FieldsBot::InteractionsURL => crate::FieldsBot::InteractionsURL, + FieldsBot::Token => crate::FieldsBot::Token, + } + } +} + +impl From for FieldsBot { + fn from(value: crate::FieldsBot) -> Self { + match value { + crate::FieldsBot::InteractionsURL => FieldsBot::InteractionsURL, + crate::FieldsBot::Token => FieldsBot::Token, + } + } +} + impl From for Invite { fn from(value: crate::Invite) -> Self { match value { @@ -622,8 +640,8 @@ impl crate::User { }) .unwrap_or_default(), badges: self.badges.unwrap_or_default() as u32, - status: None, - profile: None, + status: self.status.map(|status| status.into()), + profile: self.profile.map(|profile| profile.into()), flags: self.flags.unwrap_or_default() as u32, privileged: self.privileged, bot: self.bot.map(|bot| bot.into()), @@ -634,6 +652,56 @@ impl crate::User { } } +impl From for PartialUser { + fn from(value: crate::PartialUser) -> Self { + PartialUser { + username: value.username, + discriminator: value.discriminator, + display_name: value.display_name, + avatar: value.avatar.map(|file| file.into()), + relations: value.relations.map(|relationships| { + relationships + .into_iter() + .map(|relationship| relationship.into()) + .collect() + }), + badges: value.badges.map(|badges| badges as u32), + status: value.status.map(|status| status.into()), + profile: value.profile.map(|profile| profile.into()), + flags: value.flags.map(|flags| flags as u32), + privileged: value.privileged, + bot: value.bot.map(|bot| bot.into()), + relationship: None, + online: None, + id: value.id, + } + } +} + +impl From for crate::FieldsUser { + fn from(value: FieldsUser) -> Self { + match value { + FieldsUser::Avatar => crate::FieldsUser::Avatar, + FieldsUser::ProfileBackground => crate::FieldsUser::ProfileBackground, + FieldsUser::ProfileContent => crate::FieldsUser::ProfileContent, + FieldsUser::StatusPresence => crate::FieldsUser::StatusPresence, + FieldsUser::StatusText => crate::FieldsUser::StatusText, + } + } +} + +impl From for FieldsUser { + fn from(value: crate::FieldsUser) -> Self { + match value { + crate::FieldsUser::Avatar => FieldsUser::Avatar, + crate::FieldsUser::ProfileBackground => FieldsUser::ProfileBackground, + crate::FieldsUser::ProfileContent => FieldsUser::ProfileContent, + crate::FieldsUser::StatusPresence => FieldsUser::StatusPresence, + crate::FieldsUser::StatusText => FieldsUser::StatusText, + } + } +} + impl From for RelationshipStatus { fn from(value: crate::RelationshipStatus) -> Self { match value { diff --git a/crates/core/database/src/util/permissions.rs b/crates/core/database/src/util/permissions.rs index c4e0d695..1c850a98 100644 --- a/crates/core/database/src/util/permissions.rs +++ b/crates/core/database/src/util/permissions.rs @@ -4,18 +4,19 @@ use revolt_permissions::{ calculate_user_permissions, ChannelType, Override, PermissionQuery, RelationshipStatus, }; -use crate::{Database, User}; +use crate::{Channel, Database, Member, Server, User}; /// Permissions calculator -pub struct PermissionCalculator<'a> { +#[derive(Clone)] +pub struct DatabasePermissionQuery<'a> { #[allow(dead_code)] database: &'a Database, perspective: &'a User, user: Option>, - // pub channel: Cow<'a, Channel>, - // pub server: Cow<'a, Server>, - // pub member: Cow<'a, Member>, + channel: Option>, + server: Option>, + member: Option>, // flag_known_relationship: Option<&'a RelationshipStatus>, cached_user_permission: Option, @@ -23,7 +24,7 @@ pub struct PermissionCalculator<'a> { } #[async_trait] -impl PermissionQuery for PermissionCalculator<'_> { +impl PermissionQuery for DatabasePermissionQuery<'_> { // * For calculating user permission /// Is our perspective user privileged? @@ -153,13 +154,16 @@ impl PermissionQuery for PermissionCalculator<'_> { } } -impl<'a> PermissionCalculator<'a> { +impl<'a> DatabasePermissionQuery<'a> { /// Create a new permission calculator - pub fn new(database: &'a Database, perspective: &'a User) -> PermissionCalculator<'a> { - PermissionCalculator { + pub fn new(database: &'a Database, perspective: &'a User) -> DatabasePermissionQuery<'a> { + DatabasePermissionQuery { database, perspective, user: None, + channel: None, + server: None, + member: None, cached_user_permission: None, cached_permission: None, @@ -167,7 +171,7 @@ impl<'a> PermissionCalculator<'a> { } /// Calculate the user permission value - pub async fn calc_user(mut self) -> PermissionCalculator<'a> { + pub async fn calc_user(mut self) -> DatabasePermissionQuery<'a> { if self.cached_user_permission.is_some() { return self; } @@ -176,14 +180,14 @@ impl<'a> PermissionCalculator<'a> { panic!("Expected `PermissionCalculator.user to exist."); } - PermissionCalculator { + DatabasePermissionQuery { cached_user_permission: Some(calculate_user_permissions(&mut self).await), ..self } } /// Calculate the permission value - pub async fn calc(self) -> PermissionCalculator<'a> { + pub async fn calc(self) -> DatabasePermissionQuery<'a> { if self.cached_permission.is_some() { return self; } @@ -192,15 +196,39 @@ impl<'a> PermissionCalculator<'a> { } /// Use user - pub fn user(self, user: Cow<'a, User>) -> PermissionCalculator { - PermissionCalculator { - user: Some(user), + pub fn user(self, user: &'a User) -> DatabasePermissionQuery { + DatabasePermissionQuery { + user: Some(Cow::Borrowed(user)), + ..self + } + } + + /// Use channel + pub fn channel(self, channel: &'a Channel) -> DatabasePermissionQuery { + DatabasePermissionQuery { + channel: Some(Cow::Borrowed(channel)), + ..self + } + } + + /// Use server + pub fn server(self, server: &'a Server) -> DatabasePermissionQuery { + DatabasePermissionQuery { + server: Some(Cow::Borrowed(server)), + ..self + } + } + + /// Use member + pub fn member(self, member: &'a Member) -> DatabasePermissionQuery { + DatabasePermissionQuery { + member: Some(Cow::Borrowed(member)), ..self } } } /// Short-hand for creating a permission calculator -pub fn perms<'a>(database: &'a Database, perspective: &'a User) -> PermissionCalculator<'a> { - PermissionCalculator::new(database, perspective) +pub fn perms<'a>(database: &'a Database, perspective: &'a User) -> DatabasePermissionQuery<'a> { + DatabasePermissionQuery::new(database, perspective) } diff --git a/crates/core/models/src/v0/bots.rs b/crates/core/models/src/v0/bots.rs index 72edc21f..6f42b81f 100644 --- a/crates/core/models/src/v0/bots.rs +++ b/crates/core/models/src/v0/bots.rs @@ -58,6 +58,12 @@ auto_derived!( pub flags: u32, } + /// Optional fields on bot object + pub enum FieldsBot { + Token, + InteractionsURL, + } + /// Flags that may be attributed to a bot #[repr(u32)] pub enum BotFlags { @@ -99,4 +105,40 @@ auto_derived!( )] pub name: String, } + + /// New Bot Details + #[cfg_attr(feature = "validator", derive(Validate))] + pub struct DataEditBot { + /// Bot username + #[cfg_attr( + feature = "validator", + validate(length(min = 2, max = 32), regex = "super::RE_USERNAME") + )] + #[serde(skip_serializing_if = "Option::is_none")] + pub name: Option, + /// Whether the bot can be added by anyone + pub public: Option, + /// Whether analytics should be gathered for this bot + /// + /// Must be enabled in order to show up on [Revolt Discover](https://rvlt.gg). + pub analytics: Option, + /// Interactions URL + #[cfg_attr(feature = "validator", validate(length(min = 1, max = 2048)))] + pub interactions_url: Option, + /// Fields to remove from bot object + #[cfg_attr(feature = "validator", validate(length(min = 1)))] + pub remove: Option>, + } + + /// Owned Bots Response + /// + /// Both lists are sorted by their IDs. + /// + /// TODO: user should be in bot object + pub struct OwnedBotsResponse { + /// Bot objects + pub bots: Vec, + /// User objects + pub users: Vec, + } ); diff --git a/crates/core/models/src/v0/users.rs b/crates/core/models/src/v0/users.rs index 51f1907e..07959ccf 100644 --- a/crates/core/models/src/v0/users.rs +++ b/crates/core/models/src/v0/users.rs @@ -9,7 +9,7 @@ use super::File; /// Block lookalike characters pub static RE_USERNAME: Lazy = Lazy::new(|| Regex::new(r"^(\p{L}|[\d_.-])+$").unwrap()); -auto_derived!( +auto_derived_partial!( /// User pub struct User { /// Unique Id @@ -65,6 +65,18 @@ auto_derived!( pub relationship: RelationshipStatus, /// Whether this user is currently online pub online: bool, + }, + "PartialUser" +); + +auto_derived!( + /// Optional fields on user object + pub enum FieldsUser { + Avatar, + StatusText, + StatusPresence, + ProfileContent, + ProfileBackground, } /// User's relationship with another user (or themselves) diff --git a/crates/core/permissions/Cargo.toml b/crates/core/permissions/Cargo.toml index 637fb052..67f9b361 100644 --- a/crates/core/permissions/Cargo.toml +++ b/crates/core/permissions/Cargo.toml @@ -3,16 +3,16 @@ name = "revolt-permissions" version = "0.6.5" edition = "2021" license = "AGPL-3.0-or-later" -authors = [ "Paul Makles " ] +authors = ["Paul Makles "] 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" ] +serde = ["dep:serde"] +schemas = ["dep:schemars"] +try-from-primitive = ["dep:num_enum"] [dev-dependencies] @@ -20,6 +20,9 @@ try-from-primitive = [ "dep:num_enum" ] async-std = { version = "1.8.0", features = ["attributes"] } [dependencies] +# Core +revolt-result = { version = "0.6.5", path = "../result" } + # Utility auto_ops = "0.3.0" once_cell = "1.17" @@ -30,7 +33,7 @@ async-trait = "0.1.51" # Serialisation serde = { version = "1", features = ["derive"], optional = true } -bson = { version = "2.1.0", optional = true} +bson = { version = "2.1.0", optional = true } # Spec Generation -schemars = { version = "0.8.8", optional = true } \ No newline at end of file +schemars = { version = "0.8.8", optional = true } diff --git a/crates/core/permissions/src/models/channel.rs b/crates/core/permissions/src/models/channel.rs index d0aa2ecc..17762b1e 100644 --- a/crates/core/permissions/src/models/channel.rs +++ b/crates/core/permissions/src/models/channel.rs @@ -1,5 +1,5 @@ use once_cell::sync::Lazy; -use std::ops::Add; +use std::{fmt, ops::Add}; /// Abstract channel type pub enum ChannelType { @@ -102,6 +102,12 @@ pub enum ChannelPermission { GrantAll = u64::MAX, } +impl fmt::Display for ChannelPermission { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Debug::fmt(self, f) + } +} + impl_op_ex!(+ |a: &ChannelPermission, b: &ChannelPermission| -> u64 { *a as u64 | *b as u64 }); impl_op_ex_commutative!(+ |a: &u64, b: &ChannelPermission| -> u64 { *a | *b as u64 }); @@ -136,4 +142,9 @@ pub static DEFAULT_PERMISSION_SERVER: Lazy = Lazy::new(|| { ) }); -pub static DEFAULT_WEBHOOK_PERMISSIONS: Lazy = Lazy::new(|| ChannelPermission::SendMessage + ChannelPermission::SendEmbeds + ChannelPermission::Masquerade + ChannelPermission::React); +pub static DEFAULT_WEBHOOK_PERMISSIONS: Lazy = Lazy::new(|| { + ChannelPermission::SendMessage + + ChannelPermission::SendEmbeds + + ChannelPermission::Masquerade + + ChannelPermission::React +}); diff --git a/crates/core/permissions/src/models/mod.rs b/crates/core/permissions/src/models/mod.rs index e234eac5..ab70b88a 100644 --- a/crates/core/permissions/src/models/mod.rs +++ b/crates/core/permissions/src/models/mod.rs @@ -3,6 +3,7 @@ mod server; mod user; pub use channel::*; +use revolt_result::{create_error, Result}; pub use server::*; pub use user::*; @@ -31,6 +32,30 @@ impl PermissionValue { pub fn restrict(&mut self, v: u64) { self.0 &= v; } + + /// Check whether certain a permission has been granted + pub fn has(&mut self, v: u64) -> bool { + (self.0 & v) == v + } + + /// Check whether certain a channel permission has been granted + pub fn has_channel_permission(&mut self, permission: ChannelPermission) -> bool { + self.has(permission as u64) + } + + /// Throw if missing channel permission + pub fn throw_if_lacking_channel_permission( + &mut self, + permission: ChannelPermission, + ) -> Result<()> { + if self.has_channel_permission(permission) { + Ok(()) + } else { + Err(create_error!(MissingPermission { + permission: permission.to_string() + })) + } + } } impl From for PermissionValue { diff --git a/crates/core/permissions/src/models/user.rs b/crates/core/permissions/src/models/user.rs index 80a8da8f..1aa4c873 100644 --- a/crates/core/permissions/src/models/user.rs +++ b/crates/core/permissions/src/models/user.rs @@ -1,3 +1,5 @@ +use std::fmt; + /// User's relationship with another user (or themselves) pub enum RelationshipStatus { None, @@ -21,5 +23,11 @@ pub enum UserPermission { Invite = 1 << 3, } +impl fmt::Display for UserPermission { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Debug::fmt(self, f) + } +} + impl_op_ex!(+ |a: &UserPermission, b: &UserPermission| -> u32 { *a as u32 | *b as u32 }); impl_op_ex_commutative!(+ |a: &u32, b: &UserPermission| -> u32 { *a | *b as u32 }); diff --git a/crates/delta/src/routes/bots/delete.rs b/crates/delta/src/routes/bots/delete.rs index 445deaa6..18616587 100644 --- a/crates/delta/src/routes/bots/delete.rs +++ b/crates/delta/src/routes/bots/delete.rs @@ -1,18 +1,21 @@ -use revolt_quark::{models::User, Db, EmptyResponse, Error, Ref, Result}; +use revolt_database::{util::reference::Reference, Database, User}; +use revolt_result::{create_error, Result}; +use rocket::State; +use rocket_empty::EmptyResponse; /// # Delete Bot /// /// Delete a bot by its id. #[openapi(tag = "Bots")] #[delete("/")] -pub async fn delete_bot(db: &Db, user: User, target: Ref) -> Result { - if user.bot.is_some() { - return Err(Error::IsBot); - } - +pub async fn delete_bot( + db: &State, + user: User, + target: Reference, +) -> Result { let bot = target.as_bot(db).await?; if bot.owner != user.id { - return Err(Error::NotFound); + return Err(create_error!(NotFound)); } bot.delete(db).await.map(|_| EmptyResponse) diff --git a/crates/delta/src/routes/bots/edit.rs b/crates/delta/src/routes/bots/edit.rs index 7e22adb2..4118cbc4 100644 --- a/crates/delta/src/routes/bots/edit.rs +++ b/crates/delta/src/routes/bots/edit.rs @@ -1,60 +1,32 @@ -use crate::util::regex::RE_USERNAME; - -use revolt_quark::{ - models::{ - bot::{FieldsBot, PartialBot}, - Bot, User, - }, - Db, Error, Ref, Result, -}; +use revolt_database::{util::reference::Reference, Database, PartialBot, User}; +use revolt_models::v0::{self, DataEditBot}; +use revolt_result::{create_error, Result}; +use rocket::State; use rocket::serde::json::Json; -use serde::{Deserialize, Serialize}; use validator::Validate; -/// # Bot Details -#[derive(Validate, Serialize, Deserialize, JsonSchema)] -pub struct DataEditBot { - /// Bot username - #[validate(length(min = 2, max = 32), regex = "RE_USERNAME")] - #[serde(skip_serializing_if = "Option::is_none")] - name: Option, - /// Whether the bot can be added by anyone - public: Option, - /// Whether analytics should be gathered for this bot - /// - /// Must be enabled in order to show up on [Revolt Discover](https://rvlt.gg). - analytics: Option, - /// Interactions URL - #[validate(length(min = 1, max = 2048))] - interactions_url: Option, - /// Fields to remove from bot object - #[validate(length(min = 1))] - remove: Option>, -} - /// # Edit Bot /// /// Edit bot details by its id. #[openapi(tag = "Bots")] #[patch("/", data = "")] pub async fn edit_bot( - db: &Db, + db: &State, user: User, - target: Ref, + target: Reference, data: Json, -) -> Result> { - if user.bot.is_some() { - return Err(Error::IsBot); - } - +) -> Result> { let data = data.into_inner(); - data.validate() - .map_err(|error| Error::FailedValidation { error })?; + data.validate().map_err(|error| { + create_error!(FailedValidation { + error: error.to_string() + }) + })?; let mut bot = target.as_bot(db).await?; if bot.owner != user.id { - return Err(Error::NotFound); + return Err(create_error!(NotFound)); } if let Some(name) = data.name { @@ -67,7 +39,7 @@ pub async fn edit_bot( && data.interactions_url.is_none() && data.remove.is_none() { - return Ok(Json(bot)); + return Ok(Json(bot.into())); } let DataEditBot { @@ -78,26 +50,23 @@ pub async fn edit_bot( .. } = data; - let mut partial = PartialBot { + let partial = PartialBot { public, analytics, interactions_url, ..Default::default() }; - if let Some(remove) = &remove { - for field in remove { - bot.remove(field); - } + bot.update( + db, + partial, + remove + .unwrap_or_default() + .into_iter() + .map(|v| v.into()) + .collect(), + ) + .await?; - if remove.iter().any(|x| x == &FieldsBot::Token) { - partial.token = Some(bot.token.clone()); - } - } - - db.update_bot(&bot.id, &partial, remove.unwrap_or_default()) - .await?; - - bot.apply_options(partial); - Ok(Json(bot)) + Ok(Json(bot.into())) } diff --git a/crates/delta/src/routes/bots/fetch.rs b/crates/delta/src/routes/bots/fetch.rs index 48aaaf5b..811fb60a 100644 --- a/crates/delta/src/routes/bots/fetch.rs +++ b/crates/delta/src/routes/bots/fetch.rs @@ -1,6 +1,6 @@ -use revolt_database::{util::reference::Reference, Database}; +use revolt_database::{util::reference::Reference, Database, User}; use revolt_models::v0::FetchBotResponse; -use revolt_quark::{models::User, Error, Result}; +use revolt_result::{create_error, Result}; use rocket::{serde::json::Json, State}; /// # Fetch Bot @@ -14,21 +14,16 @@ pub async fn fetch_bot( bot: Reference, ) -> Result> { if user.bot.is_some() { - return Err(Error::IsBot); + return Err(create_error!(IsBot)); } - let bot = bot.as_bot(db).await.map_err(Error::from_core)?; + let bot = bot.as_bot(db).await?; if bot.owner != user.id { - return Err(Error::NotFound); + return Err(create_error!(NotFound)); } Ok(Json(FetchBotResponse { - user: db - .fetch_user(&bot.id) - .await - .map_err(Error::from_core)? - .into(None) - .await, + user: db.fetch_user(&bot.id).await?.into(None).await, bot: bot.into(), })) } diff --git a/crates/delta/src/routes/bots/fetch_owned.rs b/crates/delta/src/routes/bots/fetch_owned.rs index f437fb7a..c99f8c19 100644 --- a/crates/delta/src/routes/bots/fetch_owned.rs +++ b/crates/delta/src/routes/bots/fetch_owned.rs @@ -1,31 +1,16 @@ -use revolt_quark::{ - models::{Bot, User}, - Db, Error, Result, -}; +use futures::future::join_all; +use revolt_database::{Database, User}; +use revolt_models::v0::OwnedBotsResponse; +use revolt_result::Result; use rocket::serde::json::Json; -use serde::Serialize; - -/// # Owned Bots Response -/// -/// Both lists are sorted by their IDs. -#[derive(Serialize, JsonSchema)] -pub struct OwnedBotsResponse { - /// Bot objects - bots: Vec, - /// User objects - users: Vec, -} +use rocket::State; /// # Fetch Owned Bots /// /// Fetch all of the bots that you have control over. #[openapi(tag = "Bots")] #[get("/@me")] -pub async fn fetch_owned_bots(db: &Db, user: User) -> Result> { - if user.bot.is_some() { - return Err(Error::IsBot); - } - +pub async fn fetch_owned_bots(db: &State, user: User) -> Result> { let mut bots = db.fetch_bots_by_user(&user.id).await?; let user_ids = bots .iter() @@ -38,5 +23,8 @@ pub async fn fetch_owned_bots(db: &Db, user: User) -> Result, user: Option, - target: Ref, + target: Reference, ) -> Result> { - let bot = db.fetch_bot(&target.id).await.map_err(Error::from_core)?; + let bot = db.fetch_bot(&target.id).await?; if !bot.public && user.map_or(true, |x| x.id != bot.owner) { - return Err(Error::NotFound); + return Err(create_error!(NotFound)); } - let user = db.fetch_user(&bot.id).await.map_err(Error::from_core)?; + let user = db.fetch_user(&bot.id).await?; Ok(Json(bot.into_public_bot(user))) } diff --git a/crates/delta/src/routes/bots/invite.rs b/crates/delta/src/routes/bots/invite.rs index 8cd61051..dabfabf3 100644 --- a/crates/delta/src/routes/bots/invite.rs +++ b/crates/delta/src/routes/bots/invite.rs @@ -1,6 +1,12 @@ -use revolt_quark::{models::User, perms, Db, EmptyResponse, Error, Permission, Ref, Result}; +use revolt_database::util::permissions::DatabasePermissionQuery; +use revolt_database::Member; +use revolt_database::{util::reference::Reference, Database, User}; +use revolt_permissions::{calculate_server_permissions, ChannelPermission}; +use revolt_result::{create_error, Result}; +use rocket::State; use rocket::serde::json::Json; +use rocket_empty::EmptyResponse; use serde::Deserialize; /// # Invite Destination @@ -25,42 +31,41 @@ pub enum InviteBotDestination { #[openapi(tag = "Bots")] #[post("//invite", data = "")] pub async fn invite_bot( - db: &Db, + db: &State, user: User, - target: Ref, + target: Reference, dest: Json, ) -> Result { if user.bot.is_some() { - return Err(Error::IsBot); + return Err(create_error!(IsBot)); } let bot = target.as_bot(db).await?; if !bot.public && bot.owner != user.id { - return Err(Error::BotIsPrivate); + return Err(create_error!(BotIsPrivate)); } match dest.into_inner() { InviteBotDestination::Server { server } => { let server = db.fetch_server(&server).await?; - perms(&user) - .server(&server) - .throw_permission(db, Permission::ManageServer) - .await?; + let mut query = DatabasePermissionQuery::new(db, &user).server(&server); + calculate_server_permissions(&mut query) + .await + .throw_if_lacking_channel_permission(ChannelPermission::ManageServer)?; let user = db.fetch_user(&bot.id).await?; - server - .create_member(db, user, None) + Member::create(db, &server, &user) .await .map(|_| EmptyResponse) } InviteBotDestination::Group { group } => { let mut channel = db.fetch_channel(&group).await?; - perms(&user) - .channel(&channel) - .throw_permission_and_view_channel(db, Permission::InviteOthers) - .await?; + let mut query = DatabasePermissionQuery::new(db, &user).channel(&channel); + calculate_server_permissions(&mut query) + .await + .throw_if_lacking_channel_permission(ChannelPermission::InviteOthers)?; channel .add_user_to_group(db, &bot.id, &user.id)