use axum::{Json, extract::State}; use serde::Deserialize; use tracing::{error, info, warn}; use tranquil_pds::api::EmptyResponse; use tranquil_pds::api::error::ApiError; use tranquil_pds::auth::{Admin, Auth}; use tranquil_pds::state::AppState; use tranquil_pds::types::{Did, Handle, PlainPassword}; #[derive(Deserialize)] pub struct UpdateAccountEmailInput { pub account: String, pub email: String, } pub async fn update_account_email( State(state): State, _auth: Auth, Json(input): Json, ) -> Result, ApiError> { let account = input.account.trim(); let email = input.email.trim(); if account.is_empty() || email.is_empty() { return Err(ApiError::InvalidRequest( "account and email are required".into(), )); } let account_did: Did = account .parse() .map_err(|_| ApiError::InvalidDid("Invalid DID format".into()))?; match state .repos .user .admin_update_email(&account_did, email) .await { Ok(0) => Err(ApiError::AccountNotFound), Ok(_) => Ok(Json(EmptyResponse {})), Err(e) => { error!("DB error updating email: {:?}", e); Err(ApiError::InternalError(None)) } } } #[derive(Deserialize)] pub struct UpdateAccountHandleInput { pub did: Did, pub handle: String, } pub async fn update_account_handle( State(state): State, _auth: Auth, Json(input): Json, ) -> Result, ApiError> { let did = &input.did; let input_handle = input.handle.trim(); if input_handle.is_empty() { return Err(ApiError::InvalidRequest("handle is required".into())); } if !input_handle .chars() .all(|c| c.is_ascii_alphanumeric() || c == '.' || c == '-' || c == '_') { return Err(ApiError::InvalidHandle(None)); } let available_domains = tranquil_config::get().server.user_handle_domain_list(); let handle = if !input_handle.contains('.') { format!("{}.{}", input_handle, &available_domains[0]) } else { input_handle.to_string() }; let old_handle = state.repos.user.get_handle_by_did(did).await.ok().flatten(); let user_id = state .repos .user .get_id_by_did(did) .await .ok() .flatten() .ok_or(ApiError::AccountNotFound)?; let handle_for_check: Handle = handle.parse().map_err(|_| ApiError::InvalidHandle(None))?; if let Ok(true) = state .repos .user .check_handle_exists(&handle_for_check, user_id) .await { return Err(ApiError::HandleTaken); } match state .repos .user .admin_update_handle(did, &handle_for_check) .await { Ok(0) => Err(ApiError::AccountNotFound), Ok(_) => { if let Some(old) = old_handle { let _ = state .cache .delete(&tranquil_pds::cache_keys::handle_key(&old)) .await; } let _ = state .cache .delete(&tranquil_pds::cache_keys::handle_key(&handle_for_check)) .await; if let Err(e) = tranquil_pds::repo_ops::sequence_identity_event( &state, did, Some(&handle_for_check), ) .await { warn!( "Failed to sequence identity event for admin handle update: {}", e ); } if let Err(e) = crate::identity::did::update_plc_handle(&state, did, &handle_for_check).await { warn!("Failed to update PLC handle for admin handle update: {}", e); } Ok(Json(EmptyResponse {})) } Err(e) => { error!("DB error updating handle: {:?}", e); Err(ApiError::InternalError(None)) } } } #[derive(Deserialize)] pub struct UpdateAccountPasswordInput { pub did: Did, pub password: PlainPassword, } pub async fn update_account_password( State(state): State, _auth: Auth, Json(input): Json, ) -> Result, ApiError> { let did = &input.did; let password = input.password.trim(); if password.is_empty() { return Err(ApiError::InvalidRequest("password is required".into())); } let password_hash = crate::common::hash_or_internal_error(password)?; match state .repos .user .admin_update_password(did, &password_hash) .await { Ok(0) => Err(ApiError::AccountNotFound), Ok(_) => Ok(Json(EmptyResponse {})), Err(e) => { error!("DB error updating password: {:?}", e); Err(ApiError::InternalError(None)) } } } #[derive(Deserialize)] pub struct SetAdminStatusInput { pub did: Did, pub admin: bool, } pub async fn set_admin_status( State(state): State, auth: Auth, Json(input): Json, ) -> Result, ApiError> { info!( actor = %auth.did, target = %input.did, admin = input.admin, "admin status change" ); state .repos .user .set_admin_status(&input.did, input.admin) .await .map_err(|e| { error!("DB error setting admin status: {:?}", e); ApiError::InternalError(None) })?; Ok(Json(EmptyResponse {})) }