feat: add discriminator and display name fields

This commit is contained in:
Paul Makles
2023-06-09 16:34:18 +01:00
parent aba5c7d8af
commit 31c7dc0577
16 changed files with 229 additions and 64 deletions

View File

@@ -9,11 +9,12 @@ impl AbstractUser for DummyDb {
Ok(User {
id: id.into(),
username: "username".into(),
discriminator: "0000".into(),
..Default::default()
})
}
async fn fetch_user_by_username(&self, username: &str) -> Result<User> {
async fn fetch_user_by_username(&self, username: &str, _discriminator: &str) -> Result<User> {
self.fetch_user(username).await
}
@@ -45,8 +46,8 @@ impl AbstractUser for DummyDb {
Ok(vec![self.fetch_user("id").await.unwrap()])
}
async fn is_username_taken(&self, _username: &str) -> Result<bool> {
Ok(false)
async fn fetch_discriminators_in_use(&self, _username: &str) -> Result<Vec<String>> {
Ok(vec![])
}
async fn fetch_mutual_user_ids(&self, _user_a: &str, _user_b: &str) -> Result<Vec<String>> {

View File

@@ -8,7 +8,9 @@ use crate::{perms, Database, Error, Result};
use futures::try_join;
use impl_ops::impl_op_ex_commutative;
use rand::seq::SliceRandom;
use revolt_presence::filter_online;
use std::collections::HashSet;
use std::ops;
impl_op_ex_commutative!(+ |a: &i32, b: &Badges| -> i32 { *a | *b as i32 });
@@ -169,15 +171,7 @@ impl User {
}
/// Sanitise and validate a username can be used
pub async fn validate_username(db: &Database, username: String) -> Result<String> {
// Trim surrounding spaces
let username = username.trim().to_string();
// Make sure username is still at least 3 characters
if username.len() < 2 {
return Err(Error::InvalidUsername);
}
pub fn validate_username(username: String) -> Result<String> {
// Copy the username for validation
let username_lowercase = username.to_lowercase();
@@ -199,25 +193,74 @@ impl User {
}
}
// Make sure the username isn't taken
if db.is_username_taken(&username).await? {
Ok(username)
}
// Find a free discriminator for a given username
pub async fn find_discriminator(
db: &Database,
username: &str,
preferred: Option<String>,
) -> Result<String> {
let search_space: HashSet<String> = (0..9999).map(|v| format!("{:0>4}", v)).collect();
let used_discriminators: HashSet<String> = db
.fetch_discriminators_in_use(username)
.await?
.into_iter()
.collect();
let available_discriminators: Vec<&String> =
search_space.difference(&used_discriminators).collect();
if available_discriminators.is_empty() {
return Err(Error::UsernameTaken);
}
Ok(username)
if let Some(preferred) = preferred {
if available_discriminators.contains(&&preferred) {
return Ok(preferred.to_string());
}
}
let mut rng = rand::thread_rng();
Ok(available_discriminators
.choose(&mut rng)
.expect("we can assert this has an element")
.to_string())
}
/// Update a user's username
pub async fn update_username(&mut self, db: &Database, username: String) -> Result<()> {
self.update(
db,
PartialUser {
username: Some(User::validate_username(db, username).await?),
..Default::default()
},
vec![],
)
.await
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()),
)
.await?,
),
username: Some(username),
..Default::default()
},
vec![],
)
.await
}
}
/// Apply a certain relationship between two users

View File

@@ -9,14 +9,16 @@ use crate::{AbstractUser, Error, Result};
use super::super::MongoDb;
static FIND_USERNAME_OPTIONS: Lazy<FindOneOptions> = Lazy::new(|| FindOneOptions::builder()
.collation(
Collation::builder()
.locale("en")
.strength(CollationStrength::Secondary)
.build()
)
.build());
static FIND_USERNAME_OPTIONS: Lazy<FindOneOptions> = Lazy::new(|| {
FindOneOptions::builder()
.collation(
Collation::builder()
.locale("en")
.strength(CollationStrength::Secondary)
.build(),
)
.build()
});
static COL: &str = "users";
@@ -26,11 +28,12 @@ impl AbstractUser for MongoDb {
self.find_one_by_id(COL, id).await
}
async fn fetch_user_by_username(&self, username: &str) -> Result<User> {
async fn fetch_user_by_username(&self, username: &str, discriminator: &str) -> Result<User> {
self.find_one_with_options(
COL,
doc! {
"username": username
"username": username,
"discriminator": discriminator
},
FIND_USERNAME_OPTIONS.clone(),
)
@@ -106,13 +109,34 @@ impl AbstractUser for MongoDb {
Ok(users)
}
async fn is_username_taken(&self, username: &str) -> Result<bool> {
// ! FIXME: move this up to generic
match self.fetch_user_by_username(username).await {
Ok(_) => Ok(true),
Err(Error::NotFound) => Ok(false),
Err(error) => Err(error),
}
async fn fetch_discriminators_in_use(&self, username: &str) -> Result<Vec<String>> {
Ok(self
.col::<Document>(COL)
.find(
doc! {
"username": username
},
FindOptions::builder()
.collation(
Collation::builder()
.locale("en")
.strength(CollationStrength::Secondary)
.build(),
)
.projection(doc! { "_id": 0, "discriminator": 1 })
.build(),
)
.await
.map_err(|_| Error::DatabaseError {
operation: "find",
with: "users",
})?
.filter_map(|s| async { s.ok() })
.collect::<Vec<Document>>()
.await
.into_iter()
.filter_map(|x| x.get_str("discriminator").ok().map(|x| x.to_string()))
.collect::<Vec<String>>())
}
async fn fetch_mutual_user_ids(&self, user_a: &str, user_b: &str) -> Result<Vec<String>> {

View File

@@ -128,6 +128,10 @@ pub struct User {
pub id: String,
/// Username
pub username: String,
/// Discriminator
pub discriminator: String,
/// Display name
pub display_name: String,
#[serde(skip_serializing_if = "Option::is_none")]
/// Avatar attachment
pub avatar: Option<File>,

View File

@@ -7,7 +7,7 @@ pub trait AbstractUser: Sync + Send {
async fn fetch_user(&self, id: &str) -> Result<User>;
/// Fetch a user from the database by their username
async fn fetch_user_by_username(&self, username: &str) -> Result<User>;
async fn fetch_user_by_username(&self, username: &str, discriminator: &str) -> Result<User>;
/// Fetch a user from the database by their session token
async fn fetch_user_by_token(&self, token: &str) -> Result<User>;
@@ -29,8 +29,8 @@ pub trait AbstractUser: Sync + Send {
/// Fetch multiple users by their ids
async fn fetch_users<'a>(&self, ids: &'a [String]) -> Result<Vec<User>>;
/// Check whether a username is already in use by another user
async fn is_username_taken(&self, username: &str) -> Result<bool>;
/// Fetch all discriminators in use for a username
async fn fetch_discriminators_in_use(&self, username: &str) -> Result<Vec<String>>;
/// Fetch ids of users that both users are friends with
async fn fetch_mutual_user_ids(&self, user_a: &str, user_b: &str) -> Result<Vec<String>>;