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}