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