diff --git a/crates/bonfire/src/events/impl.rs b/crates/bonfire/src/events/impl.rs index 426c7e58..36518e0e 100644 --- a/crates/bonfire/src/events/impl.rs +++ b/crates/bonfire/src/events/impl.rs @@ -1,5 +1,6 @@ use std::collections::HashSet; +use futures::future::join_all; use revolt_database::{ events::client::{EventV1, ReadyPayloadFields}, util::permissions::DatabasePermissionQuery, @@ -198,13 +199,12 @@ impl State { .collect(); // Make all users appear from our perspective. - let mut users: Vec = users + let mut users: Vec = join_all(users .into_iter() - .map(|other_user| { + .map(|other_user| async { let is_online = online_ids.contains(&other_user.id); - other_user.into_known(&user, is_online) - }) - .collect(); + other_user.into_known(&user, is_online).await + })).await; // Make sure we see our own user correctly. users.push(user.into_self(true).await); diff --git a/crates/core/config/Revolt.toml b/crates/core/config/Revolt.toml index 8db462af..71c09800 100644 --- a/crates/core/config/Revolt.toml +++ b/crates/core/config/Revolt.toml @@ -64,6 +64,9 @@ hcaptcha_sitekey = "" # Maximum concurrent connections (to proxy server) max_concurrent_connections = 50 +[api.users] + + [pushd] # this changes the names of the queues to not overlap # prod/beta if they happen to be on the same exchange/instance. diff --git a/crates/core/config/src/lib.rs b/crates/core/config/src/lib.rs index 445fc597..6dd730e6 100644 --- a/crates/core/config/src/lib.rs +++ b/crates/core/config/src/lib.rs @@ -175,12 +175,18 @@ pub struct ApiWorkers { pub max_concurrent_connections: usize, } +#[derive(Deserialize, Debug, Clone)] +pub struct ApiUsers { + pub early_adopter_cutoff: Option +} + #[derive(Deserialize, Debug, Clone)] pub struct Api { pub registration: ApiRegistration, pub smtp: ApiSmtp, pub security: ApiSecurity, pub workers: ApiWorkers, + pub users: ApiUsers, } #[derive(Deserialize, Debug, Clone)] diff --git a/crates/core/database/src/models/users/model.rs b/crates/core/database/src/models/users/model.rs index 7b155415..4fbf3029 100644 --- a/crates/core/database/src/models/users/model.rs +++ b/crates/core/database/src/models/users/model.rs @@ -3,11 +3,12 @@ use std::{collections::HashSet, str::FromStr, time::Duration}; use crate::{events::client::EventV1, Database, File, RatelimitEvent, AMQP}; use authifier::config::{EmailVerificationConfig, Template}; +use futures::future::join_all; use iso8601_timestamp::Timestamp; use once_cell::sync::Lazy; use rand::seq::SliceRandom; use revolt_config::{config, FeaturesLimits}; -use revolt_models::v0::{self, UserFlags}; +use revolt_models::v0::{self, UserFlags, UserBadges}; use revolt_presence::filter_online; use revolt_result::{create_error, Result}; use serde_json::json; @@ -348,15 +349,16 @@ impl User { ) -> Result> { let online_ids = filter_online(ids).await; - Ok(db + Ok(join_all( + db .fetch_users(ids) .await? .into_iter() - .map(|user| { + .map(|user| async { let is_online = online_ids.contains(&user.id); - user.into_known(perspective, is_online) + user.into_known(perspective, is_online).await }) - .collect()) + ).await) } /// Find a free discriminator for a given username @@ -807,4 +809,18 @@ impl User { ) .await } + + /// Gets the user's badges along with calculating any dynamic badges + pub async fn get_badges(&self) -> u32 { + let config = config().await; + let badges = self.badges.unwrap_or_default() as u32; + + if let Some(cutoff) = config.api.users.early_adopter_cutoff { + if Ulid::from_string(&self.id).unwrap().timestamp_ms() < cutoff { + return badges + UserBadges::EarlyAdopter as u32 + }; + }; + + badges + } } diff --git a/crates/core/database/src/util/bridge/v0.rs b/crates/core/database/src/util/bridge/v0.rs index 776d21fe..8872ef10 100644 --- a/crates/core/database/src/util/bridge/v0.rs +++ b/crates/core/database/src/util/bridge/v0.rs @@ -994,6 +994,8 @@ impl crate::User { (RelationshipStatus::None, false) }; + let badges = self.get_badges().await; + User { username: self.username, discriminator: self.discriminator, @@ -1012,7 +1014,7 @@ impl crate::User { } else { vec![] }, - badges: self.badges.unwrap_or_default() as u32, + badges, online: can_see_profile && revolt_presence::is_online(&self.id).await && !matches!( @@ -1038,7 +1040,7 @@ impl crate::User { /// Convert user object into user model assuming mutual connection /// /// Relations will never be included, i.e. when we process ourselves - pub fn into_known<'a, P>(self, perspective: P, is_online: bool) -> User + pub async fn into_known<'a, P>(self, perspective: P, is_online: bool) -> User where P: Into>, { @@ -1068,13 +1070,15 @@ impl crate::User { (RelationshipStatus::None, false) }; + let badges = self.get_badges().await; + User { username: self.username, discriminator: self.discriminator, display_name: self.display_name, avatar: self.avatar.map(|file| file.into()), relations: vec![], - badges: self.badges.unwrap_or_default() as u32, + badges, online: can_see_profile && is_online && !matches!( @@ -1098,14 +1102,16 @@ impl crate::User { } /// Convert user object into user model without presence information - pub fn into_known_static(self, is_online: bool) -> User { + pub async fn into_known_static<'a>(self, is_online: bool) -> User { + let badges = self.get_badges().await; + User { username: self.username, discriminator: self.discriminator, display_name: self.display_name, avatar: self.avatar.map(|file| file.into()), relations: vec![], - badges: self.badges.unwrap_or_default() as u32, + badges, online: is_online && !matches!( self.status, @@ -1124,6 +1130,8 @@ impl crate::User { } pub async fn into_self(self, force_online: bool) -> User { + let badges = self.get_badges().await; + User { username: self.username, discriminator: self.discriminator, @@ -1138,7 +1146,7 @@ impl crate::User { .collect() }) .unwrap_or_default(), - badges: self.badges.unwrap_or_default() as u32, + badges, online: (force_online || revolt_presence::is_online(&self.id).await) && !matches!( self.status, diff --git a/crates/delta/src/routes/channels/message_send.rs b/crates/delta/src/routes/channels/message_send.rs index 9fd3065c..c44c5b1a 100644 --- a/crates/delta/src/routes/channels/message_send.rs +++ b/crates/delta/src/routes/channels/message_send.rs @@ -84,7 +84,7 @@ pub async fn message_send( // Create model user / members let model_user = user .clone() - .into_known_static(revolt_presence::is_online(&user.id).await); + .into_known_static(revolt_presence::is_online(&user.id).await).await; let model_member: Option = query .member_ref()