this repo has no description
1use crate::api::proxy_client::proxy_client; 2use crate::state::AppState; 3use axum::{ 4 Json, 5 extract::{Query, RawQuery, State}, 6 http::StatusCode, 7 response::{IntoResponse, Response}, 8}; 9use serde::Deserialize; 10use serde_json::json; 11use tracing::{error, info}; 12 13#[derive(Deserialize)] 14pub struct DescribeRepoInput { 15 pub repo: String, 16} 17 18async fn proxy_describe_repo_to_appview(state: &AppState, raw_query: Option<&str>) -> Response { 19 let resolved = match state.appview_registry.get_appview_for_method("com.atproto.repo.describeRepo").await { 20 Some(r) => r, 21 None => { 22 return ( 23 StatusCode::NOT_FOUND, 24 Json(json!({"error": "NotFound", "message": "Repo not found"})), 25 ) 26 .into_response(); 27 } 28 }; 29 let target_url = match raw_query { 30 Some(q) => format!("{}/xrpc/com.atproto.repo.describeRepo?{}", resolved.url, q), 31 None => format!("{}/xrpc/com.atproto.repo.describeRepo", resolved.url), 32 }; 33 info!("Proxying describeRepo to AppView: {}", target_url); 34 let client = proxy_client(); 35 match client.get(&target_url).send().await { 36 Ok(resp) => { 37 let status = 38 StatusCode::from_u16(resp.status().as_u16()).unwrap_or(StatusCode::BAD_GATEWAY); 39 let content_type = resp 40 .headers() 41 .get("content-type") 42 .and_then(|v| v.to_str().ok()) 43 .map(|s| s.to_string()); 44 match resp.bytes().await { 45 Ok(body) => { 46 let mut builder = Response::builder().status(status); 47 if let Some(ct) = content_type { 48 builder = builder.header("content-type", ct); 49 } 50 builder 51 .body(axum::body::Body::from(body)) 52 .unwrap_or_else(|_| { 53 (StatusCode::INTERNAL_SERVER_ERROR, "Internal error").into_response() 54 }) 55 } 56 Err(e) => { 57 error!("Error reading AppView response: {:?}", e); 58 (StatusCode::BAD_GATEWAY, Json(json!({"error": "UpstreamError"}))).into_response() 59 } 60 } 61 } 62 Err(e) => { 63 error!("Error proxying to AppView: {:?}", e); 64 (StatusCode::BAD_GATEWAY, Json(json!({"error": "UpstreamError"}))).into_response() 65 } 66 } 67} 68 69pub async fn describe_repo( 70 State(state): State<AppState>, 71 Query(input): Query<DescribeRepoInput>, 72 RawQuery(raw_query): RawQuery, 73) -> Response { 74 let user_row = if input.repo.starts_with("did:") { 75 sqlx::query!( 76 "SELECT id, handle, did FROM users WHERE did = $1", 77 input.repo 78 ) 79 .fetch_optional(&state.db) 80 .await 81 .map(|opt| opt.map(|r| (r.id, r.handle, r.did))) 82 } else { 83 sqlx::query!( 84 "SELECT id, handle, did FROM users WHERE handle = $1", 85 input.repo 86 ) 87 .fetch_optional(&state.db) 88 .await 89 .map(|opt| opt.map(|r| (r.id, r.handle, r.did))) 90 }; 91 let (user_id, handle, did) = match user_row { 92 Ok(Some((id, handle, did))) => (id, handle, did), 93 _ => { 94 return proxy_describe_repo_to_appview(&state, raw_query.as_deref()).await; 95 } 96 }; 97 let collections_query = sqlx::query!( 98 "SELECT DISTINCT collection FROM records WHERE repo_id = $1", 99 user_id 100 ) 101 .fetch_all(&state.db) 102 .await; 103 let collections: Vec<String> = match collections_query { 104 Ok(rows) => rows.iter().map(|r| r.collection.clone()).collect(), 105 Err(_) => Vec::new(), 106 }; 107 let did_doc = json!({ 108 "id": did, 109 "alsoKnownAs": [format!("at://{}", handle)] 110 }); 111 Json(json!({ 112 "handle": handle, 113 "did": did, 114 "didDoc": did_doc, 115 "collections": collections, 116 "handleIsCorrect": true 117 })) 118 .into_response() 119}