this repo has no description
1use crate::state::AppState; 2use axum::{ 3 Json, 4 extract::State, 5 http::StatusCode, 6 response::{IntoResponse, Response}, 7}; 8use serde::Deserialize; 9use serde_json::json; 10use tracing::error; 11 12#[derive(Deserialize)] 13pub struct UpdateAccountEmailInput { 14 pub account: String, 15 pub email: String, 16} 17 18pub async fn update_account_email( 19 State(state): State<AppState>, 20 headers: axum::http::HeaderMap, 21 Json(input): Json<UpdateAccountEmailInput>, 22) -> Response { 23 let auth_header = headers.get("Authorization"); 24 if auth_header.is_none() { 25 return ( 26 StatusCode::UNAUTHORIZED, 27 Json(json!({"error": "AuthenticationRequired"})), 28 ) 29 .into_response(); 30 } 31 let account = input.account.trim(); 32 let email = input.email.trim(); 33 if account.is_empty() || email.is_empty() { 34 return ( 35 StatusCode::BAD_REQUEST, 36 Json(json!({"error": "InvalidRequest", "message": "account and email are required"})), 37 ) 38 .into_response(); 39 } 40 let result = sqlx::query!("UPDATE users SET email = $1 WHERE did = $2", email, account) 41 .execute(&state.db) 42 .await; 43 match result { 44 Ok(r) => { 45 if r.rows_affected() == 0 { 46 return ( 47 StatusCode::NOT_FOUND, 48 Json(json!({"error": "AccountNotFound", "message": "Account not found"})), 49 ) 50 .into_response(); 51 } 52 (StatusCode::OK, Json(json!({}))).into_response() 53 } 54 Err(e) => { 55 error!("DB error updating email: {:?}", e); 56 ( 57 StatusCode::INTERNAL_SERVER_ERROR, 58 Json(json!({"error": "InternalError"})), 59 ) 60 .into_response() 61 } 62 } 63} 64 65#[derive(Deserialize)] 66pub struct UpdateAccountHandleInput { 67 pub did: String, 68 pub handle: String, 69} 70 71pub async fn update_account_handle( 72 State(state): State<AppState>, 73 headers: axum::http::HeaderMap, 74 Json(input): Json<UpdateAccountHandleInput>, 75) -> Response { 76 let auth_header = headers.get("Authorization"); 77 if auth_header.is_none() { 78 return ( 79 StatusCode::UNAUTHORIZED, 80 Json(json!({"error": "AuthenticationRequired"})), 81 ) 82 .into_response(); 83 } 84 let did = input.did.trim(); 85 let handle = input.handle.trim(); 86 if did.is_empty() || handle.is_empty() { 87 return ( 88 StatusCode::BAD_REQUEST, 89 Json(json!({"error": "InvalidRequest", "message": "did and handle are required"})), 90 ) 91 .into_response(); 92 } 93 if !handle 94 .chars() 95 .all(|c| c.is_ascii_alphanumeric() || c == '.' || c == '-' || c == '_') 96 { 97 return ( 98 StatusCode::BAD_REQUEST, 99 Json(json!({"error": "InvalidHandle", "message": "Handle contains invalid characters"})), 100 ) 101 .into_response(); 102 } 103 let old_handle = sqlx::query_scalar!("SELECT handle FROM users WHERE did = $1", did) 104 .fetch_optional(&state.db) 105 .await 106 .ok() 107 .flatten(); 108 let existing = sqlx::query!("SELECT id FROM users WHERE handle = $1 AND did != $2", handle, did) 109 .fetch_optional(&state.db) 110 .await; 111 if let Ok(Some(_)) = existing { 112 return ( 113 StatusCode::BAD_REQUEST, 114 Json(json!({"error": "HandleTaken", "message": "Handle is already in use"})), 115 ) 116 .into_response(); 117 } 118 let result = sqlx::query!("UPDATE users SET handle = $1 WHERE did = $2", handle, did) 119 .execute(&state.db) 120 .await; 121 match result { 122 Ok(r) => { 123 if r.rows_affected() == 0 { 124 return ( 125 StatusCode::NOT_FOUND, 126 Json(json!({"error": "AccountNotFound", "message": "Account not found"})), 127 ) 128 .into_response(); 129 } 130 if let Some(old) = old_handle { 131 let _ = state.cache.delete(&format!("handle:{}", old)).await; 132 } 133 let _ = state.cache.delete(&format!("handle:{}", handle)).await; 134 (StatusCode::OK, Json(json!({}))).into_response() 135 } 136 Err(e) => { 137 error!("DB error updating handle: {:?}", e); 138 ( 139 StatusCode::INTERNAL_SERVER_ERROR, 140 Json(json!({"error": "InternalError"})), 141 ) 142 .into_response() 143 } 144 } 145} 146 147#[derive(Deserialize)] 148pub struct UpdateAccountPasswordInput { 149 pub did: String, 150 pub password: String, 151} 152 153pub async fn update_account_password( 154 State(state): State<AppState>, 155 headers: axum::http::HeaderMap, 156 Json(input): Json<UpdateAccountPasswordInput>, 157) -> Response { 158 let auth_header = headers.get("Authorization"); 159 if auth_header.is_none() { 160 return ( 161 StatusCode::UNAUTHORIZED, 162 Json(json!({"error": "AuthenticationRequired"})), 163 ) 164 .into_response(); 165 } 166 let did = input.did.trim(); 167 let password = input.password.trim(); 168 if did.is_empty() || password.is_empty() { 169 return ( 170 StatusCode::BAD_REQUEST, 171 Json(json!({"error": "InvalidRequest", "message": "did and password are required"})), 172 ) 173 .into_response(); 174 } 175 let password_hash = match bcrypt::hash(password, bcrypt::DEFAULT_COST) { 176 Ok(h) => h, 177 Err(e) => { 178 error!("Failed to hash password: {:?}", e); 179 return ( 180 StatusCode::INTERNAL_SERVER_ERROR, 181 Json(json!({"error": "InternalError"})), 182 ) 183 .into_response(); 184 } 185 }; 186 let result = sqlx::query!("UPDATE users SET password_hash = $1 WHERE did = $2", password_hash, did) 187 .execute(&state.db) 188 .await; 189 match result { 190 Ok(r) => { 191 if r.rows_affected() == 0 { 192 return ( 193 StatusCode::NOT_FOUND, 194 Json(json!({"error": "AccountNotFound", "message": "Account not found"})), 195 ) 196 .into_response(); 197 } 198 (StatusCode::OK, Json(json!({}))).into_response() 199 } 200 Err(e) => { 201 error!("DB error updating password: {:?}", e); 202 ( 203 StatusCode::INTERNAL_SERVER_ERROR, 204 Json(json!({"error": "InternalError"})), 205 ) 206 .into_response() 207 } 208 } 209}