From b2d3344ddde079031674a59631c935c4e6b38b7f Mon Sep 17 00:00:00 2001 From: Paul Makles Date: Sun, 7 Apr 2024 19:38:08 +0100 Subject: [PATCH] refactor(quark): port edit_user, fetch_dms, fetch_profile, fetch_self, fetch_user_flags, fetch_user, find_mutual, open_dm #283 --- .../database/src/models/channels/model.rs | 38 +++++++ .../core/database/src/models/users/model.rs | 14 ++- crates/core/database/src/util/bridge/v0.rs | 13 +++ crates/core/models/src/v0/users.rs | 84 +++++++++++++- crates/core/permissions/src/models/mod.rs | 11 ++ .../delta/src/routes/users/change_username.rs | 2 +- crates/delta/src/routes/users/edit_user.rs | 104 +++++------------- crates/delta/src/routes/users/fetch_dms.rs | 15 +-- .../delta/src/routes/users/fetch_profile.rs | 34 +++--- crates/delta/src/routes/users/fetch_self.rs | 10 +- crates/delta/src/routes/users/fetch_user.rs | 26 +++-- .../src/routes/users/fetch_user_flags.rs | 19 ++-- crates/delta/src/routes/users/find_mutual.rs | 46 ++++---- .../src/routes/users/get_default_avatar.rs | 2 +- crates/delta/src/routes/users/mod.rs | 18 +-- crates/delta/src/routes/users/open_dm.rs | 58 +++------- 16 files changed, 286 insertions(+), 208 deletions(-) diff --git a/crates/core/database/src/models/channels/model.rs b/crates/core/database/src/models/channels/model.rs index 92368144..eafd88a6 100644 --- a/crates/core/database/src/models/channels/model.rs +++ b/crates/core/database/src/models/channels/model.rs @@ -5,6 +5,7 @@ use revolt_models::v0::{self, MessageAuthor}; use revolt_permissions::OverrideField; use revolt_result::Result; use serde::{Deserialize, Serialize}; +use ulid::Ulid; use crate::{ events::client::EventV1, tasks::ack::AckEvent, Database, File, IntoDocumentPath, PartialServer, @@ -295,6 +296,43 @@ impl Channel { Ok(channel) } + /// Create a DM (or return the existing one / saved messages) + pub async fn create_dm(db: &Database, user_a: &User, user_b: &User) -> Result { + // Try to find existing channel + if let Ok(channel) = db.find_direct_message_channel(&user_a.id, &user_b.id).await { + Ok(channel) + } else { + let channel = if user_a.id == user_b.id { + // Create a new saved messages channel + Channel::SavedMessages { + id: Ulid::new().to_string(), + user: user_a.id.to_string(), + } + } else { + // Create a new DM channel + Channel::DirectMessage { + id: Ulid::new().to_string(), + active: true, // show by default + recipients: vec![user_a.id.clone(), user_b.id.clone()], + last_message_id: None, + } + }; + + db.insert_channel(&channel).await?; + + match &channel { + Channel::DirectMessage { .. } => { + let event = EventV1::ChannelCreate(channel.clone().into()); + event.clone().private(user_a.id.clone()).await; + event.private(user_b.id.clone()).await; + } + _ => {} + }; + + Ok(channel) + } + } + /// Add user to a group pub async fn add_user_to_group( &mut self, diff --git a/crates/core/database/src/models/users/model.rs b/crates/core/database/src/models/users/model.rs index 59a79c54..fc7b31f6 100644 --- a/crates/core/database/src/models/users/model.rs +++ b/crates/core/database/src/models/users/model.rs @@ -94,20 +94,22 @@ auto_derived!( } /// User's active status + #[derive(Default)] pub struct UserStatus { /// Custom status text - #[serde(skip_serializing_if = "String::is_empty", default)] - pub text: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub text: Option, /// Current presence option #[serde(skip_serializing_if = "Option::is_none")] pub presence: Option, } /// User's profile + #[derive(Default)] pub struct UserProfile { /// Text content on user's profile - #[serde(skip_serializing_if = "String::is_empty", default)] - pub content: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub content: Option, /// Background visible on user's profile #[serde(skip_serializing_if = "Option::is_none")] pub background: Option, @@ -541,7 +543,7 @@ impl User { FieldsUser::Avatar => self.avatar = None, FieldsUser::StatusText => { if let Some(x) = self.status.as_mut() { - x.text = String::new(); + x.text = None; } } FieldsUser::StatusPresence => { @@ -551,7 +553,7 @@ impl User { } FieldsUser::ProfileContent => { if let Some(x) = self.profile.as_mut() { - x.content = String::new(); + x.content = None; } } FieldsUser::ProfileBackground => { diff --git a/crates/core/database/src/util/bridge/v0.rs b/crates/core/database/src/util/bridge/v0.rs index 4ed2c0b5..c1cb80a1 100644 --- a/crates/core/database/src/util/bridge/v0.rs +++ b/crates/core/database/src/util/bridge/v0.rs @@ -15,6 +15,7 @@ impl crate::Bot { description: user .profile .map(|profile| profile.content) + .flatten() .unwrap_or_default(), } } @@ -892,6 +893,18 @@ impl From for Presence { } } +impl From for crate::Presence { + fn from(value: Presence) -> crate::Presence { + match value { + Presence::Online => crate::Presence::Online, + Presence::Idle => crate::Presence::Idle, + Presence::Focus => crate::Presence::Focus, + Presence::Busy => crate::Presence::Busy, + Presence::Invisible => crate::Presence::Invisible, + } + } +} + impl From for UserStatus { fn from(value: crate::UserStatus) -> Self { UserStatus { diff --git a/crates/core/models/src/v0/users.rs b/crates/core/models/src/v0/users.rs index a90bbed4..1966e35c 100644 --- a/crates/core/models/src/v0/users.rs +++ b/crates/core/models/src/v0/users.rs @@ -3,12 +3,21 @@ use regex::Regex; use super::File; +#[cfg(feature = "validator")] +use validator::Validate; + /// Regex for valid usernames /// /// Block zero width space /// Block lookalike characters pub static RE_USERNAME: Lazy = Lazy::new(|| Regex::new(r"^(\p{L}|[\d_.-])+$").unwrap()); +/// Regex for valid display names +/// +/// Block zero width space +/// Block newline and carriage return +pub static RE_DISPLAY_NAME: Lazy = Lazy::new(|| Regex::new(r"^[^\u200B\n\r]+$").unwrap()); + auto_derived_partial!( /// User pub struct User { @@ -123,20 +132,26 @@ auto_derived!( } /// User's active status + #[derive(Default)] + #[cfg_attr(feature = "validator", derive(Validate))] pub struct UserStatus { /// Custom status text - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "String::is_empty"))] - pub text: String, + #[validate(length(min = 0, max = 128))] + #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] + pub text: Option, /// Current presence option #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] pub presence: Option, } /// User's profile + #[derive(Default)] + #[cfg_attr(feature = "validator", derive(Validate))] pub struct UserProfile { /// Text content on user's profile - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "String::is_empty"))] - pub content: String, + #[validate(length(min = 0, max = 2000))] + #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] + pub content: Option, /// Background visible on user's profile #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] pub background: Option, @@ -182,6 +197,67 @@ auto_derived!( Spam = 8, } + /// New user profile data + #[cfg_attr(feature = "validator", derive(Validate))] + pub struct DataUserProfile { + /// Text to set as user profile description + #[cfg_attr(feature = "validator", validate(length(min = 0, max = 2000)))] + #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] + pub content: Option, + /// Attachment Id for background + #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] + #[cfg_attr(feature = "validator", validate(length(min = 1, max = 128)))] + pub background: Option, + } + + /// New user information + #[cfg_attr(feature = "validator", derive(Validate))] + pub struct DataEditUser { + /// New display name + #[cfg_attr( + feature = "validator", + validate(length(min = 2, max = 32), regex = "RE_DISPLAY_NAME") + )] + pub display_name: Option, + /// Attachment Id for avatar + #[cfg_attr(feature = "validator", validate(length(min = 1, max = 128)))] + pub avatar: Option, + + /// New user status + #[cfg_attr(feature = "validator", validate)] + pub status: Option, + /// New user profile data + /// + /// This is applied as a partial. + #[cfg_attr(feature = "validator", validate)] + pub profile: Option, + + /// Bitfield of user badges + #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] + pub badges: Option, + /// Enum of user flags + #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] + pub flags: Option, + + /// Fields to remove from user object + #[cfg_attr(feature = "validator", validate(length(min = 1)))] + pub remove: Option>, + } + + /// User flag reponse + pub struct FlagResponse { + /// Flags + pub flags: i32, + } + + /// Mutual friends and servers response + pub struct MutualResponse { + /// Array of mutual user IDs that both users are friends with + pub users: Vec, + /// Array of mutual server IDs that both users are in + pub servers: Vec, + } + /// Bot information for if the user is a bot pub struct BotInformation { /// Id of the owner of this bot diff --git a/crates/core/permissions/src/models/mod.rs b/crates/core/permissions/src/models/mod.rs index 13c2e3f1..43f73cc9 100644 --- a/crates/core/permissions/src/models/mod.rs +++ b/crates/core/permissions/src/models/mod.rs @@ -53,6 +53,17 @@ impl PermissionValue { self.has(permission as u64) } + /// Throw if missing user permission + pub fn throw_if_lacking_user_permission(&self, permission: UserPermission) -> Result<()> { + if self.has_user_permission(permission) { + Ok(()) + } else { + Err(create_error!(MissingPermission { + permission: permission.to_string() + })) + } + } + /// Throw if missing channel permission pub fn throw_if_lacking_channel_permission(&self, permission: ChannelPermission) -> Result<()> { if self.has_channel_permission(permission) { diff --git a/crates/delta/src/routes/users/change_username.rs b/crates/delta/src/routes/users/change_username.rs index aab3da85..7c3f98c9 100644 --- a/crates/delta/src/routes/users/change_username.rs +++ b/crates/delta/src/routes/users/change_username.rs @@ -30,7 +30,7 @@ pub struct DataChangeUsername { /// Change your username. #[openapi(tag = "User Information")] #[patch("/@me/username", data = "")] -pub async fn req( +pub async fn change_username( db: &State, account: Account, mut user: User, diff --git a/crates/delta/src/routes/users/edit_user.rs b/crates/delta/src/routes/users/edit_user.rs index 644a727d..9815bbfb 100644 --- a/crates/delta/src/routes/users/edit_user.rs +++ b/crates/delta/src/routes/users/edit_user.rs @@ -1,79 +1,28 @@ -use once_cell::sync::Lazy; -use regex::Regex; -use revolt_quark::models::user::{FieldsUser, PartialUser, User}; -use revolt_quark::models::File; -use revolt_quark::{Database, Error, Ref, Result}; - -use revolt_quark::models::user::UserStatus; +use revolt_database::FieldsUser; +use revolt_database::{util::reference::Reference, Database, File, PartialUser, User}; +use revolt_models::v0; +use revolt_result::{create_error, Result}; use rocket::serde::json::Json; use rocket::State; -use serde::{Deserialize, Serialize}; use validator::Validate; -/// Regex for valid display names -/// -/// Block zero width space -/// Block newline and carriage return -pub static RE_DISPLAY_NAME: Lazy = Lazy::new(|| Regex::new(r"^[^\u200B\n\r]+$").unwrap()); - -/// # Profile Data -#[derive(Validate, Serialize, Deserialize, Debug, JsonSchema)] -pub struct UserProfileData { - /// Text to set as user profile description - #[validate(length(min = 0, max = 2000))] - #[serde(skip_serializing_if = "Option::is_none")] - content: Option, - /// Attachment Id for background - #[serde(skip_serializing_if = "Option::is_none")] - #[validate(length(min = 1, max = 128))] - background: Option, -} - -/// # User Data -#[derive(Validate, Serialize, Deserialize, JsonSchema)] -pub struct DataEditUser { - /// New display name - #[validate(length(min = 2, max = 32), regex = "RE_DISPLAY_NAME")] - display_name: Option, - /// Attachment Id for avatar - #[validate(length(min = 1, max = 128))] - avatar: Option, - - /// New user status - #[validate] - status: Option, - /// New user profile data - /// - /// This is applied as a partial. - #[validate] - profile: Option, - - /// Bitfield of user badges - #[serde(skip_serializing_if = "Option::is_none")] - badges: Option, - /// Enum of user flags - #[serde(skip_serializing_if = "Option::is_none")] - flags: Option, - - /// Fields to remove from user object - #[validate(length(min = 1))] - remove: Option>, -} - /// # Edit User /// /// Edit currently authenticated user. #[openapi(tag = "User Information")] #[patch("/", data = "")] -pub async fn req( +pub async fn edit( db: &State, mut user: User, - target: Ref, - data: Json, -) -> Result> { + target: Reference, + data: Json, +) -> 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() + }) + })?; // If we want to edit a different user than self, ensure we have // permissions and subsequently replace the user in question @@ -85,13 +34,13 @@ pub async fn req( .unwrap_or_default(); if !is_bot_owner && !user.privileged { - return Err(Error::NotPrivileged); + return Err(create_error!(NotPrivileged)); } } // Otherwise, filter out invalid edit fields if !user.privileged && (data.badges.is_some() || data.flags.is_some()) { - return Err(Error::NotPrivileged); + return Err(create_error!(NotPrivileged)); } // Exit out early if nothing is changed @@ -103,18 +52,18 @@ pub async fn req( && data.flags.is_none() && data.remove.is_none() { - return Ok(Json(user)); + return Ok(Json(user.into_self().await)); } // 1. Remove fields from object if let Some(fields) = &data.remove { - if fields.contains(&FieldsUser::Avatar) { + if fields.contains(&v0::FieldsUser::Avatar) { if let Some(avatar) = &user.avatar { db.mark_attachment_as_deleted(&avatar.id).await?; } } - if fields.contains(&FieldsUser::ProfileBackground) { + if fields.contains(&v0::FieldsUser::ProfileBackground) { if let Some(profile) = &user.profile { if let Some(background) = &profile.background { db.mark_attachment_as_deleted(&background.id).await?; @@ -123,7 +72,8 @@ pub async fn req( } for field in fields { - user.remove(field); + let field: FieldsUser = field.clone().into(); + user.remove_field(&field); } } @@ -147,7 +97,7 @@ pub async fn req( } if let Some(presence) = status.presence { - new_status.presence = Some(presence); + new_status.presence = Some(presence.into()); } partial.status = Some(new_status); @@ -167,8 +117,14 @@ pub async fn req( partial.profile = Some(new_profile); } - user.update(db, partial, data.remove.unwrap_or_default()) - .await?; + user.update( + db, + partial, + data.remove + .map(|v| v.into_iter().map(Into::into).collect()) + .unwrap_or_default(), + ) + .await?; - Ok(Json(user.foreign())) + Ok(Json(user.into_self().await)) } diff --git a/crates/delta/src/routes/users/fetch_dms.rs b/crates/delta/src/routes/users/fetch_dms.rs index 2f11de44..d0b39e63 100644 --- a/crates/delta/src/routes/users/fetch_dms.rs +++ b/crates/delta/src/routes/users/fetch_dms.rs @@ -1,8 +1,6 @@ -use revolt_quark::{ - models::{Channel, User}, - Database, Result, -}; - +use revolt_database::{Database, User}; +use revolt_models::v0; +use revolt_result::Result; use rocket::{serde::json::Json, State}; /// # Fetch Direct Message Channels @@ -10,6 +8,9 @@ use rocket::{serde::json::Json, State}; /// This fetches your direct messages, including any DM and group DM conversations. #[openapi(tag = "Direct Messaging")] #[get("/dms")] -pub async fn req(db: &State, user: User) -> Result>> { - db.find_direct_messages(&user.id).await.map(Json) +pub async fn direct_messages(db: &State, user: User) -> Result>> { + db.find_direct_messages(&user.id) + .await + .map(|v| v.into_iter().map(Into::into).collect()) + .map(Json) } diff --git a/crates/delta/src/routes/users/fetch_profile.rs b/crates/delta/src/routes/users/fetch_profile.rs index cdd44ffa..66acacfa 100644 --- a/crates/delta/src/routes/users/fetch_profile.rs +++ b/crates/delta/src/routes/users/fetch_profile.rs @@ -1,8 +1,10 @@ -use revolt_quark::{ - models::{user::UserProfile, User}, - perms, Database, Error, Ref, Result, +use revolt_database::{ + util::{permissions::DatabasePermissionQuery, reference::Reference}, + Database, User, }; - +use revolt_models::v0; +use revolt_permissions::{calculate_user_permissions, UserPermission}; +use revolt_result::Result; use rocket::{serde::json::Json, State}; /// # Fetch User Profile @@ -12,17 +14,21 @@ use rocket::{serde::json::Json, State}; /// Will fail if you do not have permission to access the other user's profile. #[openapi(tag = "User Information")] #[get("//profile")] -pub async fn req(db: &State, user: User, target: Ref) -> Result> { +pub async fn profile( + db: &State, + user: User, + target: Reference, +) -> Result> { + if user.id == target.id { + return Ok(Json(user.profile.map(Into::into).unwrap_or_default())); + } + let target = target.as_user(db).await?; - if perms(&user) - .user(&target) - .calc_user(db) + let mut query = DatabasePermissionQuery::new(db, &user).user(&target); + calculate_user_permissions(&mut query) .await - .get_view_profile() - { - Ok(Json(target.profile.unwrap_or_default())) - } else { - Err(Error::NotFound) - } + .throw_if_lacking_user_permission(UserPermission::ViewProfile)?; + + Ok(Json(target.profile.map(Into::into).unwrap_or_default())) } diff --git a/crates/delta/src/routes/users/fetch_self.rs b/crates/delta/src/routes/users/fetch_self.rs index 818b3243..11eb497b 100755 --- a/crates/delta/src/routes/users/fetch_self.rs +++ b/crates/delta/src/routes/users/fetch_self.rs @@ -1,6 +1,6 @@ -use revolt_quark::models::User; -use revolt_quark::Result; - +use revolt_database::User; +use revolt_models::v0; +use revolt_result::Result; use rocket::serde::json::Json; /// # Fetch Self @@ -8,6 +8,6 @@ use rocket::serde::json::Json; /// Retrieve your user information. #[openapi(tag = "User Information")] #[get("/@me")] -pub async fn req(user: User) -> Result> { - Ok(Json(user.foreign())) +pub async fn fetch(user: User) -> Result> { + Ok(Json(user.into_self().await)) } diff --git a/crates/delta/src/routes/users/fetch_user.rs b/crates/delta/src/routes/users/fetch_user.rs index ccfcbf94..e8886ca9 100644 --- a/crates/delta/src/routes/users/fetch_user.rs +++ b/crates/delta/src/routes/users/fetch_user.rs @@ -1,5 +1,11 @@ -use revolt_quark::{models::User, perms, Database, Error, Ref, Result}; +use revolt_database::{ + util::{permissions::DatabasePermissionQuery, reference::Reference}, + Database, User, +}; +use revolt_models::v0; +use revolt_permissions::{calculate_user_permissions, UserPermission}; +use revolt_result::Result; use rocket::{serde::json::Json, State}; /// # Fetch User @@ -7,17 +13,17 @@ use rocket::{serde::json::Json, State}; /// Retrieve a user's information. #[openapi(tag = "User Information")] #[get("/")] -pub async fn req(db: &State, user: User, target: Ref) -> Result> { - if target.id == user.id { - return Ok(Json(user)); +pub async fn fetch(db: &State, user: User, target: Reference) -> Result> { + if user.id == target.id { + return Ok(Json(user.into_self().await)); } let target = target.as_user(db).await?; - let permissions = perms(&user).user(&target).calc_user(db).await; - if permissions.get_access() { - Ok(Json(target.with_perspective(&user, &permissions))) - } else { - Err(Error::NotFound) - } + let mut query = DatabasePermissionQuery::new(db, &user).user(&target); + calculate_user_permissions(&mut query) + .await + .throw_if_lacking_user_permission(UserPermission::Access)?; + + Ok(Json(target.into(db, &user).await)) } diff --git a/crates/delta/src/routes/users/fetch_user_flags.rs b/crates/delta/src/routes/users/fetch_user_flags.rs index 4025b649..39c731d0 100644 --- a/crates/delta/src/routes/users/fetch_user_flags.rs +++ b/crates/delta/src/routes/users/fetch_user_flags.rs @@ -1,26 +1,23 @@ -use revolt_quark::{Database, Ref, Result}; - +use revolt_database::{util::reference::Reference, Database}; +use revolt_models::v0; +use revolt_result::Result; use rocket::{serde::json::Json, State}; use serde::Serialize; -/// # Flag Response -#[derive(Serialize, JsonSchema)] -pub struct FlagResponse { - /// Flags - flags: i32, -} - /// # Fetch User Flags /// /// Retrieve a user's flags. #[openapi(tag = "User Information")] #[get("//flags")] -pub async fn fetch_user_flags(db: &State, target: Ref) -> Result> { +pub async fn fetch_user_flags( + db: &State, + target: Reference, +) -> Result> { let flags = if let Ok(target) = target.as_user(db).await { target.flags.unwrap_or_default() } else { 0 }; - Ok(Json(FlagResponse { flags })) + Ok(Json(v0::FlagResponse { flags })) } diff --git a/crates/delta/src/routes/users/find_mutual.rs b/crates/delta/src/routes/users/find_mutual.rs index 7e5bfaac..8f3de313 100644 --- a/crates/delta/src/routes/users/find_mutual.rs +++ b/crates/delta/src/routes/users/find_mutual.rs @@ -1,42 +1,36 @@ -use revolt_quark::models::User; -use revolt_quark::{perms, Database, Error, Ref, Result}; +use revolt_database::util::permissions::DatabasePermissionQuery; +use revolt_database::util::reference::Reference; +use revolt_database::{Database, User}; +use revolt_models::v0; +use revolt_permissions::{calculate_user_permissions, UserPermission}; +use revolt_result::{create_error, Result}; use rocket::serde::json::Json; use rocket::State; -use serde::Serialize; - -/// # Mutual Friends and Servers Response -#[derive(Serialize, JsonSchema)] -pub struct MutualResponse { - /// Array of mutual user IDs that both users are friends with - users: Vec, - /// Array of mutual server IDs that both users are in - servers: Vec, -} /// # Fetch Mutual Friends And Servers /// /// Retrieve a list of mutual friends and servers with another user. #[openapi(tag = "Relationships")] #[get("//mutual")] -pub async fn req(db: &State, user: User, target: Ref) -> Result> { +pub async fn mutual( + db: &State, + user: User, + target: Reference, +) -> Result> { if target.id == user.id { - return Err(Error::InvalidOperation); + return Err(create_error!(InvalidOperation)); } let target = target.as_user(db).await?; - if perms(&user) - .user(&target) - .calc_user(db) + let mut query = DatabasePermissionQuery::new(db, &user).user(&target); + calculate_user_permissions(&mut query) .await - .get_view_profile() - { - Ok(Json(MutualResponse { - users: db.fetch_mutual_user_ids(&user.id, &target.id).await?, - servers: db.fetch_mutual_server_ids(&user.id, &target.id).await?, - })) - } else { - Err(Error::NotFound) - } + .throw_if_lacking_user_permission(UserPermission::ViewProfile)?; + + Ok(Json(v0::MutualResponse { + users: db.fetch_mutual_user_ids(&user.id, &target.id).await?, + servers: db.fetch_mutual_server_ids(&user.id, &target.id).await?, + })) } diff --git a/crates/delta/src/routes/users/get_default_avatar.rs b/crates/delta/src/routes/users/get_default_avatar.rs index 5b39b320..8bd32e2a 100644 --- a/crates/delta/src/routes/users/get_default_avatar.rs +++ b/crates/delta/src/routes/users/get_default_avatar.rs @@ -56,7 +56,7 @@ impl revolt_rocket_okapi::response::OpenApiResponderInner for CachedFile { /// This returns a default avatar based on the given id. #[openapi(tag = "User Information")] #[get("//default_avatar")] -pub async fn req(target: String) -> CachedFile { +pub async fn default_avatar(target: String) -> CachedFile { CachedFile(( ContentType::PNG, revolt_quark::util::pfp::avatar(target.chars().last().unwrap()), diff --git a/crates/delta/src/routes/users/mod.rs b/crates/delta/src/routes/users/mod.rs index 5292fe52..ba7445ac 100644 --- a/crates/delta/src/routes/users/mod.rs +++ b/crates/delta/src/routes/users/mod.rs @@ -20,18 +20,18 @@ mod unblock_user; pub fn routes() -> (Vec, OpenApi) { openapi_get_routes_spec![ // User Information - fetch_self::req, - fetch_user::req, + fetch_self::fetch, + fetch_user::fetch, fetch_user_flags::fetch_user_flags, - edit_user::req, - change_username::req, - get_default_avatar::req, - fetch_profile::req, + edit_user::edit, + change_username::change_username, + get_default_avatar::default_avatar, + fetch_profile::profile, // Direct Messaging - fetch_dms::req, - open_dm::req, + fetch_dms::direct_messages, + open_dm::open_dm, // Relationships - find_mutual::req, + find_mutual::mutual, add_friend::add, remove_friend::remove, block_user::block, diff --git a/crates/delta/src/routes/users/open_dm.rs b/crates/delta/src/routes/users/open_dm.rs index 938f5e5d..a01fa09a 100644 --- a/crates/delta/src/routes/users/open_dm.rs +++ b/crates/delta/src/routes/users/open_dm.rs @@ -1,10 +1,11 @@ -use revolt_quark::{ - models::{Channel, User}, - perms, Database, Error, Ref, Result, UserPermission, +use revolt_database::{ + util::{permissions::DatabasePermissionQuery, reference::Reference}, + Channel, Database, User, }; - +use revolt_models::v0; +use revolt_permissions::{calculate_user_permissions, UserPermission}; +use revolt_result::Result; use rocket::{serde::json::Json, State}; -use ulid::Ulid; /// # Open Direct Message /// @@ -13,43 +14,20 @@ use ulid::Ulid; /// If the target is oneself, a saved messages channel is returned. #[openapi(tag = "Direct Messaging")] #[get("//dm")] -pub async fn req(db: &State, user: User, target: Ref) -> Result> { +pub async fn open_dm( + db: &State, + user: User, + target: Reference, +) -> Result> { let target = target.as_user(db).await?; - // If the target is oneself, open saved messages. - if target.id == user.id { - return if let Ok(channel) = db.find_direct_message_channel(&user.id, &target.id).await { - Ok(Json(channel)) - } else { - let new_channel = Channel::SavedMessages { - id: Ulid::new().to_string(), - user: user.id, - }; - - new_channel.create(db).await?; - Ok(Json(new_channel)) - }; - } - - // Otherwise try to find or create a DM. - if let Ok(channel) = db.find_direct_message_channel(&user.id, &target.id).await { - Ok(Json(channel)) - } else if perms(&user) - .user(&target) - .calc_user(db) + let mut query = DatabasePermissionQuery::new(db, &user).user(&target); + calculate_user_permissions(&mut query) .await - .get_send_message() - { - let new_channel = Channel::DirectMessage { - id: Ulid::new().to_string(), - active: false, - recipients: vec![user.id, target.id], - last_message_id: None, - }; + .throw_if_lacking_user_permission(UserPermission::SendMessage)?; - new_channel.create(db).await?; - Ok(Json(new_channel)) - } else { - Error::from_user_permission(UserPermission::SendMessage) - } + Channel::create_dm(db, &user, &target) + .await + .map(Into::into) + .map(Json) }