diff --git a/Cargo.lock b/Cargo.lock index 45a6d332..506a25fa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6796,44 +6796,6 @@ dependencies = [ "zune-jpeg 0.4.21", ] -[[package]] -name = "revolt-pigeon" -version = "0.10.2" -dependencies = [ - "axum", - "axum-macros", - "axum_typed_multipart", - "ffprobe", - "image", - "imagesize", - "infer", - "jxl-oxide 0.8.1", - "kamadak-exif", - "lazy_static", - "moka", - "nanoid", - "revolt-config", - "revolt-database", - "revolt-files", - "revolt-ratelimits", - "revolt-result", - "revolt_clamav-client", - "serde", - "serde_json", - "sha2", - "simdutf8", - "strum_macros", - "tempfile", - "tokio 1.49.0", - "tower-http 0.5.2", - "tracing", - "tracing-subscriber", - "ulid 1.2.1", - "utoipa", - "utoipa-scalar", - "webp", -] - [[package]] name = "revolt-bonfire" version = "0.10.2" @@ -7003,6 +6965,34 @@ dependencies = [ "vergen", ] +[[package]] +name = "revolt-dove" +version = "0.10.2" +dependencies = [ + "async-recursion", + "axum", + "axum-extra", + "encoding_rs", + "lazy_static", + "mime", + "moka", + "regex", + "reqwest 0.12.23", + "revolt-config", + "revolt-files", + "revolt-models", + "revolt-result", + "scraper", + "serde", + "serde_json", + "tempfile", + "tokio 1.49.0", + "tracing", + "tracing-subscriber", + "utoipa", + "utoipa-scalar", +] + [[package]] name = "revolt-files" version = "0.10.2" @@ -7054,34 +7044,6 @@ dependencies = [ "utoipa-scalar", ] -[[package]] -name = "revolt-dove" -version = "0.10.2" -dependencies = [ - "async-recursion", - "axum", - "axum-extra", - "encoding_rs", - "lazy_static", - "mime", - "moka", - "regex", - "reqwest 0.12.23", - "revolt-config", - "revolt-files", - "revolt-models", - "revolt-result", - "scraper", - "serde", - "serde_json", - "tempfile", - "tokio 1.49.0", - "tracing", - "tracing-subscriber", - "utoipa", - "utoipa-scalar", -] - [[package]] name = "revolt-models" version = "0.10.2" @@ -7123,6 +7085,44 @@ dependencies = [ "serde", ] +[[package]] +name = "revolt-pigeon" +version = "0.10.2" +dependencies = [ + "axum", + "axum-macros", + "axum_typed_multipart", + "ffprobe", + "image", + "imagesize", + "infer", + "jxl-oxide 0.8.1", + "kamadak-exif", + "lazy_static", + "moka", + "nanoid", + "revolt-config", + "revolt-database", + "revolt-files", + "revolt-ratelimits", + "revolt-result", + "revolt_clamav-client", + "serde", + "serde_json", + "sha2", + "simdutf8", + "strum_macros", + "tempfile", + "tokio 1.49.0", + "tower-http 0.5.2", + "tracing", + "tracing-subscriber", + "ulid 1.2.1", + "utoipa", + "utoipa-scalar", + "webp", +] + [[package]] name = "revolt-presence" version = "0.10.2" diff --git a/crates/core/database/src/models/users/model.rs b/crates/core/database/src/models/users/model.rs index 0c2b6cee..9815658c 100644 --- a/crates/core/database/src/models/users/model.rs +++ b/crates/core/database/src/models/users/model.rs @@ -27,8 +27,11 @@ auto_derived_partial!( /// Display name #[serde(skip_serializing_if = "Option::is_none")] pub display_name: Option, + /// Preferred pronouns #[serde(skip_serializing_if = "Option::is_none")] + pub pronouns: Option>, /// Avatar attachment + #[serde(skip_serializing_if = "Option::is_none")] pub avatar: Option, /// Relationships with other users #[serde(skip_serializing_if = "Option::is_none")] @@ -72,6 +75,7 @@ auto_derived!( ProfileContent, ProfileBackground, DisplayName, + Pronouns, // internal fields Suspension, @@ -171,6 +175,7 @@ impl Default for User { username: Default::default(), discriminator: Default::default(), display_name: Default::default(), + pronouns: Default::default(), avatar: Default::default(), relations: Default::default(), badges: Default::default(), @@ -690,6 +695,7 @@ impl User { } } FieldsUser::DisplayName => self.display_name = None, + FieldsUser::Pronouns => self.pronouns = None, FieldsUser::Suspension => self.suspended_until = None, FieldsUser::None => {} } diff --git a/crates/core/database/src/models/users/ops/mongodb.rs b/crates/core/database/src/models/users/ops/mongodb.rs index 66de248e..d9f5efc3 100644 --- a/crates/core/database/src/models/users/ops/mongodb.rs +++ b/crates/core/database/src/models/users/ops/mongodb.rs @@ -368,6 +368,7 @@ impl IntoDocumentPath for FieldsUser { FieldsUser::StatusPresence => "status.presence", FieldsUser::StatusText => "status.text", FieldsUser::DisplayName => "display_name", + FieldsUser::Pronouns => "pronouns", FieldsUser::Suspension => "suspended_until", FieldsUser::None => "none", }) diff --git a/crates/core/database/src/util/bridge/v0.rs b/crates/core/database/src/util/bridge/v0.rs index aafb2313..446c9649 100644 --- a/crates/core/database/src/util/bridge/v0.rs +++ b/crates/core/database/src/util/bridge/v0.rs @@ -283,7 +283,7 @@ impl From for PartialChannel { role_permissions: value.role_permissions, default_permissions: value.default_permissions, last_message_id: value.last_message_id, - voice: value.voice.map(|voice| voice.into()) + voice: value.voice.map(|voice| voice.into()), } } } @@ -301,7 +301,7 @@ impl From for crate::PartialChannel { role_permissions: value.role_permissions, default_permissions: value.default_permissions, last_message_id: value.last_message_id, - voice: value.voice.map(|voice| voice.into()) + voice: value.voice.map(|voice| voice.into()), } } } @@ -515,7 +515,9 @@ impl From for SystemMessage { crate::SystemMessage::UserRemove { id, by } => Self::UserRemove { id, by }, crate::SystemMessage::MessagePinned { id, by } => Self::MessagePinned { id, by }, crate::SystemMessage::MessageUnpinned { id, by } => Self::MessageUnpinned { id, by }, - crate::SystemMessage::CallStarted { by, finished_at } => Self::CallStarted { by, finished_at } + crate::SystemMessage::CallStarted { by, finished_at } => { + Self::CallStarted { by, finished_at } + } } } } @@ -1004,6 +1006,9 @@ impl crate::User { username: self.username, discriminator: self.discriminator, display_name: self.display_name, + pronouns: self + .pronouns + .map(|pronouns| pronouns.iter().map(|pronoun| pronoun.to_owned()).collect()), avatar: self.avatar.map(|file| file.into()), relations: if let Some(crate::User { id, .. }) = perspective { if id == &self.id { @@ -1080,6 +1085,9 @@ impl crate::User { username: self.username, discriminator: self.discriminator, display_name: self.display_name, + pronouns: self + .pronouns + .map(|pronouns| pronouns.iter().map(|pronoun| pronoun.to_owned()).collect()), avatar: self.avatar.map(|file| file.into()), relations: vec![], badges, @@ -1113,6 +1121,9 @@ impl crate::User { username: self.username, discriminator: self.discriminator, display_name: self.display_name, + pronouns: self + .pronouns + .map(|pronouns| pronouns.iter().map(|pronoun| pronoun.to_owned()).collect()), avatar: self.avatar.map(|file| file.into()), relations: vec![], badges, @@ -1140,6 +1151,9 @@ impl crate::User { username: self.username, discriminator: self.discriminator, display_name: self.display_name, + pronouns: self + .pronouns + .map(|pronouns| pronouns.iter().map(|pronoun| pronoun.to_owned()).collect()), avatar: self.avatar.map(|file| file.into()), relations: self .relations @@ -1183,6 +1197,9 @@ impl From for crate::User { username: value.username, discriminator: value.discriminator, display_name: value.display_name, + pronouns: value + .pronouns + .map(|pronouns| pronouns.iter().map(|pronoun| pronoun.to_owned()).collect()), avatar: value.avatar.map(Into::into), relations: None, badges: Some(value.badges as i32), @@ -1203,6 +1220,9 @@ impl From for PartialUser { username: value.username, discriminator: value.discriminator, display_name: value.display_name, + pronouns: value + .pronouns + .map(|pronouns| pronouns.iter().map(|pronoun| pronoun.to_owned()).collect()), avatar: value.avatar.map(|file| file.into()), relations: value.relations.map(|relationships| { relationships @@ -1231,6 +1251,7 @@ impl From for crate::FieldsUser { FieldsUser::StatusPresence => crate::FieldsUser::StatusPresence, FieldsUser::StatusText => crate::FieldsUser::StatusText, FieldsUser::DisplayName => crate::FieldsUser::DisplayName, + FieldsUser::Pronouns => crate::FieldsUser::Pronouns, FieldsUser::Internal => crate::FieldsUser::None, } @@ -1246,6 +1267,7 @@ impl From for FieldsUser { crate::FieldsUser::StatusPresence => FieldsUser::StatusPresence, crate::FieldsUser::StatusText => FieldsUser::StatusText, crate::FieldsUser::DisplayName => FieldsUser::DisplayName, + crate::FieldsUser::Pronouns => FieldsUser::Pronouns, crate::FieldsUser::Suspension => FieldsUser::Internal, crate::FieldsUser::None => FieldsUser::Internal, @@ -1382,7 +1404,7 @@ impl From for crate::FieldsMessage { impl From for crate::VoiceInformation { fn from(value: VoiceInformation) -> Self { crate::VoiceInformation { - max_users: value.max_users + max_users: value.max_users, } } } @@ -1390,7 +1412,7 @@ impl From for crate::VoiceInformation { impl From for VoiceInformation { fn from(value: crate::VoiceInformation) -> Self { VoiceInformation { - max_users: value.max_users + max_users: value.max_users, } } -} \ No newline at end of file +} diff --git a/crates/core/models/src/v0/users.rs b/crates/core/models/src/v0/users.rs index 73957768..dd8ba8b4 100644 --- a/crates/core/models/src/v0/users.rs +++ b/crates/core/models/src/v0/users.rs @@ -5,7 +5,28 @@ use regex::Regex; use super::File; #[cfg(feature = "validator")] -use validator::Validate; +use validator::{Validate, ValidationError}; + +/// Validate user pronouns +/// +/// Ensure there are no more than 4 sets of pronouns, and each one is under 15 characters +fn validate_pronouns(pronouns: &Vec) -> Result<(), ValidationError> { + if pronouns.is_empty() || pronouns.len() > 4 { + return Err(ValidationError::new( + "Cannot have more than 4 or less than 1 set of pronouns.", + )); + } + + for pronoun in pronouns { + if pronoun.len() > 15 { + return Err(ValidationError::new( + "Pronouns cannot be more than 15 characters long.", + )); + } + } + + Ok(()) +} /// Regex for valid usernames /// @@ -32,6 +53,9 @@ auto_derived_partial!( /// Display name #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] pub display_name: Option, + /// Preferred pronouns + #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] + pub pronouns: Option>, #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] /// Avatar attachment pub avatar: Option, @@ -89,6 +113,7 @@ auto_derived!( ProfileContent, ProfileBackground, DisplayName, + Pronouns, /// Internal field, ignore this. Internal, @@ -225,6 +250,12 @@ auto_derived!( validate(length(min = 2, max = 32), regex = "RE_DISPLAY_NAME") )] pub display_name: Option, + #[cfg_attr( + feature = "validator", + validate(custom(function = "validate_pronouns")) + )] + #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] + pub pronouns: Option>, /// Attachment Id for avatar #[cfg_attr(feature = "validator", validate(length(min = 1, max = 128)))] pub avatar: Option, diff --git a/crates/delta/src/routes/users/edit_user.rs b/crates/delta/src/routes/users/edit_user.rs index 535fc05c..b86d5cf8 100644 --- a/crates/delta/src/routes/users/edit_user.rs +++ b/crates/delta/src/routes/users/edit_user.rs @@ -54,6 +54,7 @@ pub async fn edit( && data.badges.is_none() && data.flags.is_none() && data.remove.is_empty() + && data.pronouns.is_none() { return Ok(Json(user.into_self(false).await)); } @@ -80,6 +81,7 @@ pub async fn edit( let mut partial: PartialUser = PartialUser { display_name: data.display_name, + pronouns: data.pronouns, badges: data.badges, flags: data.flags, ..Default::default()