this repo has no description
1use crate::state::AppState; 2use axum::{ 3 extract::State, 4 http::StatusCode, 5 response::{IntoResponse, Response}, 6 Json, 7}; 8use serde::{Deserialize, Serialize}; 9use serde_json::{json, Value}; 10 11const APP_BSKY_NAMESPACE: &str = "app.bsky"; 12 13#[derive(Serialize)] 14pub struct GetPreferencesOutput { 15 pub preferences: Vec<Value>, 16} 17 18pub async fn get_preferences( 19 State(state): State<AppState>, 20 headers: axum::http::HeaderMap, 21) -> Response { 22 let token = match crate::auth::extract_bearer_token_from_header( 23 headers.get("Authorization").and_then(|h| h.to_str().ok()), 24 ) { 25 Some(t) => t, 26 None => { 27 return ( 28 StatusCode::UNAUTHORIZED, 29 Json(json!({"error": "AuthenticationRequired"})), 30 ) 31 .into_response(); 32 } 33 }; 34 35 let auth_user = match crate::auth::validate_bearer_token(&state.db, &token).await { 36 Ok(user) => user, 37 Err(_) => { 38 return ( 39 StatusCode::UNAUTHORIZED, 40 Json(json!({"error": "AuthenticationFailed"})), 41 ) 42 .into_response(); 43 } 44 }; 45 46 let user_id: uuid::Uuid = 47 match sqlx::query_scalar!("SELECT id FROM users WHERE did = $1", auth_user.did) 48 .fetch_optional(&state.db) 49 .await 50 { 51 Ok(Some(id)) => id, 52 _ => { 53 return ( 54 StatusCode::INTERNAL_SERVER_ERROR, 55 Json(json!({"error": "InternalError", "message": "User not found"})), 56 ) 57 .into_response(); 58 } 59 }; 60 61 let prefs_result = sqlx::query!( 62 "SELECT name, value_json FROM account_preferences WHERE user_id = $1", 63 user_id 64 ) 65 .fetch_all(&state.db) 66 .await; 67 68 let prefs = match prefs_result { 69 Ok(rows) => rows, 70 Err(_) => { 71 return ( 72 StatusCode::INTERNAL_SERVER_ERROR, 73 Json(json!({"error": "InternalError", "message": "Failed to fetch preferences"})), 74 ) 75 .into_response(); 76 } 77 }; 78 79 let preferences: Vec<Value> = prefs 80 .into_iter() 81 .filter(|row| { 82 row.name == APP_BSKY_NAMESPACE || row.name.starts_with(&format!("{}.", APP_BSKY_NAMESPACE)) 83 }) 84 .filter_map(|row| { 85 if row.name == "app.bsky.actor.defs#declaredAgePref" { 86 return None; 87 } 88 serde_json::from_value(row.value_json).ok() 89 }) 90 .collect(); 91 92 (StatusCode::OK, Json(GetPreferencesOutput { preferences })).into_response() 93} 94 95#[derive(Deserialize)] 96pub struct PutPreferencesInput { 97 pub preferences: Vec<Value>, 98} 99 100pub async fn put_preferences( 101 State(state): State<AppState>, 102 headers: axum::http::HeaderMap, 103 Json(input): Json<PutPreferencesInput>, 104) -> Response { 105 let token = match crate::auth::extract_bearer_token_from_header( 106 headers.get("Authorization").and_then(|h| h.to_str().ok()), 107 ) { 108 Some(t) => t, 109 None => { 110 return ( 111 StatusCode::UNAUTHORIZED, 112 Json(json!({"error": "AuthenticationRequired"})), 113 ) 114 .into_response(); 115 } 116 }; 117 118 let auth_user = match crate::auth::validate_bearer_token(&state.db, &token).await { 119 Ok(user) => user, 120 Err(_) => { 121 return ( 122 StatusCode::UNAUTHORIZED, 123 Json(json!({"error": "AuthenticationFailed"})), 124 ) 125 .into_response(); 126 } 127 }; 128 129 let user_id: uuid::Uuid = 130 match sqlx::query_scalar!("SELECT id FROM users WHERE did = $1", auth_user.did) 131 .fetch_optional(&state.db) 132 .await 133 { 134 Ok(Some(id)) => id, 135 _ => { 136 return ( 137 StatusCode::INTERNAL_SERVER_ERROR, 138 Json(json!({"error": "InternalError", "message": "User not found"})), 139 ) 140 .into_response(); 141 } 142 }; 143 144 for pref in &input.preferences { 145 let pref_type = match pref.get("$type").and_then(|t| t.as_str()) { 146 Some(t) => t, 147 None => { 148 return ( 149 StatusCode::BAD_REQUEST, 150 Json(json!({"error": "InvalidRequest", "message": "Preference missing $type field"})), 151 ) 152 .into_response(); 153 } 154 }; 155 156 if !pref_type.starts_with(APP_BSKY_NAMESPACE) { 157 return ( 158 StatusCode::BAD_REQUEST, 159 Json(json!({"error": "InvalidRequest", "message": format!("Invalid preference namespace: {}", pref_type)})), 160 ) 161 .into_response(); 162 } 163 164 if pref_type == "app.bsky.actor.defs#declaredAgePref" { 165 return ( 166 StatusCode::BAD_REQUEST, 167 Json(json!({"error": "InvalidRequest", "message": "declaredAgePref is read-only"})), 168 ) 169 .into_response(); 170 } 171 } 172 173 let mut tx = match state.db.begin().await { 174 Ok(tx) => tx, 175 Err(_) => { 176 return ( 177 StatusCode::INTERNAL_SERVER_ERROR, 178 Json(json!({"error": "InternalError", "message": "Failed to start transaction"})), 179 ) 180 .into_response(); 181 } 182 }; 183 184 let delete_result = sqlx::query!( 185 "DELETE FROM account_preferences WHERE user_id = $1 AND (name = $2 OR name LIKE $3)", 186 user_id, 187 APP_BSKY_NAMESPACE, 188 format!("{}.%", APP_BSKY_NAMESPACE) 189 ) 190 .execute(&mut *tx) 191 .await; 192 193 if delete_result.is_err() { 194 let _ = tx.rollback().await; 195 return ( 196 StatusCode::INTERNAL_SERVER_ERROR, 197 Json(json!({"error": "InternalError", "message": "Failed to clear preferences"})), 198 ) 199 .into_response(); 200 } 201 202 for pref in input.preferences { 203 let pref_type = pref.get("$type").and_then(|t| t.as_str()).unwrap(); 204 205 let insert_result = sqlx::query!( 206 "INSERT INTO account_preferences (user_id, name, value_json) VALUES ($1, $2, $3)", 207 user_id, 208 pref_type, 209 pref 210 ) 211 .execute(&mut *tx) 212 .await; 213 214 if insert_result.is_err() { 215 let _ = tx.rollback().await; 216 return ( 217 StatusCode::INTERNAL_SERVER_ERROR, 218 Json(json!({"error": "InternalError", "message": "Failed to save preference"})), 219 ) 220 .into_response(); 221 } 222 } 223 224 if let Err(_) = tx.commit().await { 225 return ( 226 StatusCode::INTERNAL_SERVER_ERROR, 227 Json(json!({"error": "InternalError", "message": "Failed to commit transaction"})), 228 ) 229 .into_response(); 230 } 231 232 StatusCode::OK.into_response() 233}