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(
100 json!({"error": "InvalidHandle", "message": "Handle contains invalid characters"}),
101 ),
102 )
103 .into_response();
104 }
105 let old_handle = sqlx::query_scalar!("SELECT handle FROM users WHERE did = $1", did)
106 .fetch_optional(&state.db)
107 .await
108 .ok()
109 .flatten();
110 let existing = sqlx::query!(
111 "SELECT id FROM users WHERE handle = $1 AND did != $2",
112 handle,
113 did
114 )
115 .fetch_optional(&state.db)
116 .await;
117 if let Ok(Some(_)) = existing {
118 return (
119 StatusCode::BAD_REQUEST,
120 Json(json!({"error": "HandleTaken", "message": "Handle is already in use"})),
121 )
122 .into_response();
123 }
124 let result = sqlx::query!("UPDATE users SET handle = $1 WHERE did = $2", handle, did)
125 .execute(&state.db)
126 .await;
127 match result {
128 Ok(r) => {
129 if r.rows_affected() == 0 {
130 return (
131 StatusCode::NOT_FOUND,
132 Json(json!({"error": "AccountNotFound", "message": "Account not found"})),
133 )
134 .into_response();
135 }
136 if let Some(old) = old_handle {
137 let _ = state.cache.delete(&format!("handle:{}", old)).await;
138 }
139 let _ = state.cache.delete(&format!("handle:{}", handle)).await;
140 (StatusCode::OK, Json(json!({}))).into_response()
141 }
142 Err(e) => {
143 error!("DB error updating handle: {:?}", e);
144 (
145 StatusCode::INTERNAL_SERVER_ERROR,
146 Json(json!({"error": "InternalError"})),
147 )
148 .into_response()
149 }
150 }
151}
152
153#[derive(Deserialize)]
154pub struct UpdateAccountPasswordInput {
155 pub did: String,
156 pub password: String,
157}
158
159pub async fn update_account_password(
160 State(state): State<AppState>,
161 headers: axum::http::HeaderMap,
162 Json(input): Json<UpdateAccountPasswordInput>,
163) -> Response {
164 let auth_header = headers.get("Authorization");
165 if auth_header.is_none() {
166 return (
167 StatusCode::UNAUTHORIZED,
168 Json(json!({"error": "AuthenticationRequired"})),
169 )
170 .into_response();
171 }
172 let did = input.did.trim();
173 let password = input.password.trim();
174 if did.is_empty() || password.is_empty() {
175 return (
176 StatusCode::BAD_REQUEST,
177 Json(json!({"error": "InvalidRequest", "message": "did and password are required"})),
178 )
179 .into_response();
180 }
181 let password_hash = match bcrypt::hash(password, bcrypt::DEFAULT_COST) {
182 Ok(h) => h,
183 Err(e) => {
184 error!("Failed to hash password: {:?}", e);
185 return (
186 StatusCode::INTERNAL_SERVER_ERROR,
187 Json(json!({"error": "InternalError"})),
188 )
189 .into_response();
190 }
191 };
192 let result = sqlx::query!(
193 "UPDATE users SET password_hash = $1 WHERE did = $2",
194 password_hash,
195 did
196 )
197 .execute(&state.db)
198 .await;
199 match result {
200 Ok(r) => {
201 if r.rows_affected() == 0 {
202 return (
203 StatusCode::NOT_FOUND,
204 Json(json!({"error": "AccountNotFound", "message": "Account not found"})),
205 )
206 .into_response();
207 }
208 (StatusCode::OK, Json(json!({}))).into_response()
209 }
210 Err(e) => {
211 error!("DB error updating password: {:?}", e);
212 (
213 StatusCode::INTERNAL_SERVER_ERROR,
214 Json(json!({"error": "InternalError"})),
215 )
216 .into_response()
217 }
218 }
219}