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}