Profilo utente (birthdate/sex/notes), kid agent, i18n, shared folders, UX
This commit is contained in:
@@ -95,12 +95,7 @@ pub async fn me(
|
||||
.ok_or_else(|| ApiError::not_found("user not found"))?;
|
||||
|
||||
let ui_mode = resolve_ui_mode(&skald, &user.role_id).await;
|
||||
let default_locale = skald
|
||||
.config()
|
||||
.get(skald_core::i18n::DEFAULT_LOCALE_KEY)
|
||||
.await?
|
||||
.filter(|s| !s.trim().is_empty())
|
||||
.unwrap_or_else(|| "en".into());
|
||||
let default_locale = skald_core::i18n::default_locale(skald.db()).await;
|
||||
|
||||
Ok(Json(MeResponse {
|
||||
username: user.username,
|
||||
|
||||
@@ -25,6 +25,12 @@ pub struct CreateUserBody {
|
||||
pub password: String,
|
||||
#[serde(default)]
|
||||
pub encrypted: bool,
|
||||
#[serde(default)]
|
||||
pub birthdate: Option<String>,
|
||||
#[serde(default)]
|
||||
pub sex: Option<String>,
|
||||
#[serde(default)]
|
||||
pub notes: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
@@ -32,6 +38,36 @@ pub struct CreatedUser {
|
||||
pub id: String,
|
||||
}
|
||||
|
||||
/// Empty/whitespace strings normalize to `None` (the form clears a field by
|
||||
/// blanking it), and the surviving values are validated: `birthdate` must be a
|
||||
/// real ISO `YYYY-MM-DD` date, not in the future; the free-text fields are
|
||||
/// length-capped so the prompt block stays sane.
|
||||
fn normalize_profile_fields(
|
||||
birthdate: Option<&str>,
|
||||
sex: Option<&str>,
|
||||
notes: Option<&str>,
|
||||
) -> Result<(Option<String>, Option<String>, Option<String>), ApiError> {
|
||||
let clean = |s: Option<&str>| s.map(str::trim).filter(|s| !s.is_empty()).map(str::to_owned);
|
||||
|
||||
let birthdate = clean(birthdate);
|
||||
if let Some(b) = &birthdate {
|
||||
let dob = chrono::NaiveDate::parse_from_str(b, "%Y-%m-%d")
|
||||
.map_err(|_| ApiError::bad_request("birthdate must be a YYYY-MM-DD date"))?;
|
||||
if dob > chrono::Utc::now().date_naive() {
|
||||
return Err(ApiError::bad_request("birthdate cannot be in the future"));
|
||||
}
|
||||
}
|
||||
let sex = clean(sex);
|
||||
if sex.as_deref().is_some_and(|s| s.len() > 50) {
|
||||
return Err(ApiError::bad_request("sex is too long (max 50 chars)"));
|
||||
}
|
||||
let notes = clean(notes);
|
||||
if notes.as_deref().is_some_and(|s| s.len() > 2000) {
|
||||
return Err(ApiError::bad_request("notes are too long (max 2000 chars)"));
|
||||
}
|
||||
Ok((birthdate, sex, notes))
|
||||
}
|
||||
|
||||
pub async fn create(
|
||||
State(skald): State<Arc<Skald>>,
|
||||
Json(body): Json<CreateUserBody>,
|
||||
@@ -43,11 +79,29 @@ pub async fn create(
|
||||
if body.password.is_empty() {
|
||||
return Err(ApiError::bad_request("password must not be empty"));
|
||||
}
|
||||
let (birthdate, sex, notes) = normalize_profile_fields(
|
||||
body.birthdate.as_deref(),
|
||||
body.sex.as_deref(),
|
||||
body.notes.as_deref(),
|
||||
)?;
|
||||
let id = skald
|
||||
.users()
|
||||
.register_user(username, body.display_name.as_deref(), &body.role_id, Some(&body.password), body.encrypted)
|
||||
.await?;
|
||||
|
||||
// Directory profile fields are not part of registration — set them in a
|
||||
// follow-up write (keeps `UserManager::register_user`'s signature stable).
|
||||
if birthdate.is_some() || sex.is_some() || notes.is_some() {
|
||||
skald_core::db::users::set_directory_fields(
|
||||
skald.db(),
|
||||
&id,
|
||||
birthdate.as_deref(),
|
||||
sex.as_deref(),
|
||||
notes.as_deref(),
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
// Provision the user's container now (blueprint §6). Best-effort: a failure here
|
||||
// is not fatal to user creation — boot reconciliation will retry.
|
||||
if let Err(e) = skald.container().ensure(&id).await {
|
||||
@@ -66,6 +120,12 @@ pub struct UpdateUserBody {
|
||||
pub role_id: String,
|
||||
#[serde(default)]
|
||||
pub active: bool,
|
||||
#[serde(default)]
|
||||
pub birthdate: Option<String>,
|
||||
#[serde(default)]
|
||||
pub sex: Option<String>,
|
||||
#[serde(default)]
|
||||
pub notes: Option<String>,
|
||||
}
|
||||
|
||||
pub async fn update(
|
||||
@@ -77,6 +137,11 @@ pub async fn update(
|
||||
if username.is_empty() {
|
||||
return Err(ApiError::bad_request("username must not be empty"));
|
||||
}
|
||||
let (birthdate, sex, notes) = normalize_profile_fields(
|
||||
body.birthdate.as_deref(),
|
||||
body.sex.as_deref(),
|
||||
body.notes.as_deref(),
|
||||
)?;
|
||||
skald_core::db::users::update_profile(
|
||||
skald.db(),
|
||||
&id,
|
||||
@@ -89,6 +154,16 @@ pub async fn update(
|
||||
// active is separate because it's a boolean flip
|
||||
skald_core::db::users::set_active(skald.db(), &id, body.active).await?;
|
||||
|
||||
// Directory profile fields are a separate write too (same shape as active).
|
||||
skald_core::db::users::set_directory_fields(
|
||||
skald.db(),
|
||||
&id,
|
||||
birthdate.as_deref(),
|
||||
sex.as_deref(),
|
||||
notes.as_deref(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(Json(serde_json::json!({ "ok": true })))
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user