Rework avatars.
Now we have a concept of an avatar at the server, realm, and scene level
and we have the groundwork for a realm store. New uesrs no longer props,
they get a default avatar. New system supports gender
{male,female,neutral} and {child,adult}.
This commit is contained in:
parent
e4abdb183f
commit
6fb90e42c3
55 changed files with 7392 additions and 512 deletions
|
|
@ -6,14 +6,16 @@ use tower_sessions::Session;
|
|||
|
||||
use chattyness_db::{
|
||||
models::{
|
||||
AccountStatus, AuthenticatedUser, CurrentUserResponse, GuestLoginRequest,
|
||||
GuestLoginResponse, JoinRealmRequest, JoinRealmResponse, LoginRequest, LoginResponse,
|
||||
LoginType, PasswordResetRequest, PasswordResetResponse, RealmRole, RealmSummary,
|
||||
RegisterGuestRequest, RegisterGuestResponse, SignupRequest, SignupResponse, UserSummary,
|
||||
AccountStatus, AgeCategory, AuthenticatedUser, CurrentUserResponse, GenderPreference,
|
||||
GuestLoginRequest, GuestLoginResponse, JoinRealmRequest, JoinRealmResponse, LoginRequest,
|
||||
LoginResponse, LoginType, PasswordResetRequest, PasswordResetResponse, RealmRole,
|
||||
RealmSummary, RegisterGuestRequest, RegisterGuestResponse, SignupRequest, SignupResponse,
|
||||
UserSummary,
|
||||
},
|
||||
queries::{guests, memberships, realms, users},
|
||||
};
|
||||
use chattyness_error::AppError;
|
||||
use chattyness_shared::{AgeConfig, GenderConfig, SignupConfig};
|
||||
|
||||
use crate::auth::{
|
||||
AuthUser, OptionalAuthUser,
|
||||
|
|
@ -249,6 +251,7 @@ pub async fn logout(session: Session) -> Result<Json<LogoutResponse>, AppError>
|
|||
pub async fn signup(
|
||||
rls_conn: crate::auth::RlsConn,
|
||||
State(pool): State<PgPool>,
|
||||
State(signup_config): State<SignupConfig>,
|
||||
session: Session,
|
||||
Json(req): Json<SignupRequest>,
|
||||
) -> Result<Json<SignupResponse>, AppError> {
|
||||
|
|
@ -273,6 +276,29 @@ pub async fn signup(
|
|||
.await?
|
||||
.ok_or_else(|| AppError::NotFound(format!("Realm '{}' not found", req.realm_slug)))?;
|
||||
|
||||
// Determine gender preference based on config
|
||||
let gender_preference = match signup_config.gender {
|
||||
GenderConfig::Ask => req.gender_preference,
|
||||
GenderConfig::DefaultNeutral => Some(GenderPreference::GenderNeutral),
|
||||
GenderConfig::DefaultMale => Some(GenderPreference::GenderMale),
|
||||
GenderConfig::DefaultFemale => Some(GenderPreference::GenderFemale),
|
||||
};
|
||||
|
||||
// Determine age category based on config
|
||||
let age_category = match signup_config.age {
|
||||
AgeConfig::Ask => req.age_category,
|
||||
AgeConfig::Infer => {
|
||||
// Infer age from birthday if provided
|
||||
if let Some(birthday) = req.birthday {
|
||||
Some(infer_age_category_from_birthday(birthday))
|
||||
} else {
|
||||
Some(AgeCategory::Adult) // Default to adult if no birthday
|
||||
}
|
||||
}
|
||||
AgeConfig::DefaultAdult => Some(AgeCategory::Adult),
|
||||
AgeConfig::DefaultChild => Some(AgeCategory::Child),
|
||||
};
|
||||
|
||||
// Create the user using RLS connection
|
||||
let email_opt = req.email.as_ref().and_then(|e| {
|
||||
let trimmed = e.trim();
|
||||
|
|
@ -284,12 +310,15 @@ pub async fn signup(
|
|||
});
|
||||
|
||||
let mut conn = rls_conn.acquire().await;
|
||||
let user_id = users::create_user_conn(
|
||||
let user_id = users::create_user_with_preferences_conn(
|
||||
&mut *conn,
|
||||
&req.username,
|
||||
email_opt,
|
||||
req.display_name.trim(),
|
||||
&req.password,
|
||||
req.birthday,
|
||||
gender_preference,
|
||||
age_category,
|
||||
)
|
||||
.await?;
|
||||
drop(conn);
|
||||
|
|
@ -530,3 +559,65 @@ pub async fn register_guest(
|
|||
username: req.username,
|
||||
}))
|
||||
}
|
||||
|
||||
/// Request to update user preferences.
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
pub struct UpdatePreferencesRequest {
|
||||
#[serde(default)]
|
||||
pub birthday: Option<chrono::NaiveDate>,
|
||||
#[serde(default)]
|
||||
pub gender_preference: Option<chattyness_db::models::GenderPreference>,
|
||||
#[serde(default)]
|
||||
pub age_category: Option<chattyness_db::models::AgeCategory>,
|
||||
}
|
||||
|
||||
/// Response after updating preferences.
|
||||
#[derive(Debug, serde::Serialize)]
|
||||
pub struct UpdatePreferencesResponse {
|
||||
pub success: bool,
|
||||
}
|
||||
|
||||
/// Update user preferences handler.
|
||||
///
|
||||
/// Updates the user's birthday, gender preference, and/or age category.
|
||||
/// These preferences are used for default avatar selection.
|
||||
pub async fn update_preferences(
|
||||
rls_conn: crate::auth::RlsConn,
|
||||
AuthUser(user): AuthUser,
|
||||
Json(req): Json<UpdatePreferencesRequest>,
|
||||
) -> Result<Json<UpdatePreferencesResponse>, AppError> {
|
||||
let mut conn = rls_conn.acquire().await;
|
||||
|
||||
// Update user preferences (requires RLS for auth.users UPDATE policy)
|
||||
users::update_user_preferences_conn(
|
||||
&mut *conn,
|
||||
user.id,
|
||||
req.birthday,
|
||||
req.gender_preference,
|
||||
req.age_category,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(Json(UpdatePreferencesResponse { success: true }))
|
||||
}
|
||||
|
||||
/// Infer age category from birthday.
|
||||
///
|
||||
/// Returns `Adult` if 18 years or older, `Child` otherwise.
|
||||
fn infer_age_category_from_birthday(birthday: chrono::NaiveDate) -> AgeCategory {
|
||||
use chrono::Datelike;
|
||||
let today = chrono::Utc::now().date_naive();
|
||||
let age = today.year() - birthday.year();
|
||||
let had_birthday_this_year =
|
||||
(today.month(), today.day()) >= (birthday.month(), birthday.day());
|
||||
let actual_age = if had_birthday_this_year {
|
||||
age
|
||||
} else {
|
||||
age - 1
|
||||
};
|
||||
if actual_age >= 18 {
|
||||
AgeCategory::Adult
|
||||
} else {
|
||||
AgeCategory::Child
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue