/* This file is part of jellything (https://codeberg.org/metamuffin/jellything) which is licensed under the GNU Affero General Public License (version 3); see /COPYING. Copyright (C) 2026 metamuffin */ use super::format_form_error; use crate::{auth::hash_password, request_info::RequestInfo, ui::error::MyResult}; use anyhow::anyhow; use jellycommon::{ jellyobject::{Object, Path, Tag}, routes::u_account_settings, *, }; use jellydb::{Filter, Query}; use jellyui::{components::user::UserSettings, tr}; use rocket::{ FromForm, form::{self, Contextual, Form, validate::len}, get, post, response::{Flash, Redirect, content::RawHtml}, }; use std::ops::Range; #[derive(FromForm)] pub struct SettingsForm { #[field(validate = option_len(4..64))] password: Option, #[field(validate = option_len(4..32))] name: Option, theme_accent: Option, theme_preset: Option, } fn option_len<'v>(value: &Option, range: Range) -> form::Result<'v, ()> { value.as_ref().map(|v| len(v, range)).unwrap_or(Ok(())) } #[get("/account/settings")] pub fn r_account_settings(ri: RequestInfo) -> MyResult> { let user = ri.require_user()?; Ok(ri.respond_ui(&UserSettings { ri: &ri.render_info(), user, })) } #[post("/account/settings", data = "
")] pub fn r_account_settings_post( ri: RequestInfo, form: Form>, ) -> MyResult> { let form = match &form.value { Some(v) => v, None => { return Ok(Flash::error( Redirect::to(u_account_settings()), format_form_error(form), )); } }; let mut out = String::new(); if let Some(password) = &form.password { let login = ri .require_user()? .get(USER_LOGIN) .ok_or(anyhow!("user has no login"))?; let password = hash_password(login, password); update_user(&ri, |user| user.insert(USER_PASSWORD, &password))?; out += &*tr(ri.lang, "settings.account.password.changed"); out += "\n"; } if let Some(name) = &form.name { update_user(&ri, |user| user.insert(USER_NAME, name))?; out += &*tr(ri.lang, "settings.account.display_name.changed"); out += "\n"; } if let Some(preset) = &form.theme_preset { let tag = Tag::new( preset .as_bytes() .try_into() .map_err(|_| anyhow!("invalid theme preset"))?, ); update_user(&ri, |user| user.insert(USER_THEME_PRESET, tag))?; out += &*tr(ri.lang, "settings.appearance.theme.changed"); out += "\n"; } if let Some(accent) = form.theme_accent { update_user(&ri, |user| user.insert(USER_THEME_ACCENT, accent))?; } // if let Some(player_preference) = form.player_preference { // update_user_player_preference(&ri.session, player_preference.0)?; // out += &*tr(ri.lang, "settings.player_preference.changed"); // out += "\n"; // } let out = if out.is_empty() { tr(ri.lang, "settings.no_change").to_string() } else { out }; Ok(Flash::success(Redirect::to(u_account_settings()), out)) } fn update_user(ri: &RequestInfo, update: impl Fn(&Object) -> Box) -> MyResult<()> { let login = ri .require_user()? .get(USER_LOGIN) .ok_or(anyhow!("user has no login"))?; ri.state.database.transaction(&mut |txn| { let user_row = txn .query_single(Query { filter: Filter::Match(Path(vec![USER_LOGIN.0]), login.into()), ..Default::default() })? .ok_or(anyhow!("user vanished"))?; let user = txn.get(user_row)?.unwrap(); let new_user = update(&user); txn.update(user_row, new_user)?; Ok(()) })?; Ok(()) }