this repo has no description
1use crate::auth::{extract_bearer_token_from_header, validate_bearer_token_allow_takendown}; 2use crate::state::AppState; 3use crate::sync::car::encode_car_header; 4use crate::sync::util::assert_repo_availability; 5use axum::{ 6 Json, 7 extract::{Query, State}, 8 http::{HeaderMap, StatusCode}, 9 response::{IntoResponse, Response}, 10}; 11use cid::Cid; 12use ipld_core::ipld::Ipld; 13use jacquard_repo::storage::BlockStore; 14use serde::{Deserialize, Serialize}; 15use serde_json::json; 16use std::io::Write; 17use std::str::FromStr; 18 19const MAX_REPO_BLOCKS_TRAVERSAL: usize = 20_000; 20 21async fn check_admin_or_self(state: &AppState, headers: &HeaderMap, did: &str) -> bool { 22 let token = match extract_bearer_token_from_header( 23 headers.get("Authorization").and_then(|h| h.to_str().ok()), 24 ) { 25 Some(t) => t, 26 None => return false, 27 }; 28 match validate_bearer_token_allow_takendown(&state.db, &token).await { 29 Ok(auth_user) => auth_user.is_admin || auth_user.did == did, 30 Err(_) => false, 31 } 32} 33 34#[derive(Deserialize)] 35pub struct GetHeadParams { 36 pub did: String, 37} 38 39#[derive(Serialize)] 40pub struct GetHeadOutput { 41 pub root: String, 42} 43 44pub async fn get_head( 45 State(state): State<AppState>, 46 headers: HeaderMap, 47 Query(params): Query<GetHeadParams>, 48) -> Response { 49 let did = params.did.trim(); 50 if did.is_empty() { 51 return ( 52 StatusCode::BAD_REQUEST, 53 Json(json!({"error": "InvalidRequest", "message": "did is required"})), 54 ) 55 .into_response(); 56 } 57 let is_admin_or_self = check_admin_or_self(&state, &headers, did).await; 58 let account = match assert_repo_availability(&state.db, did, is_admin_or_self).await { 59 Ok(a) => a, 60 Err(e) => return e.into_response(), 61 }; 62 match account.repo_root_cid { 63 Some(root) => (StatusCode::OK, Json(GetHeadOutput { root })).into_response(), 64 None => ( 65 StatusCode::BAD_REQUEST, 66 Json(json!({"error": "HeadNotFound", "message": format!("Could not find root for DID: {}", did)})), 67 ) 68 .into_response(), 69 } 70} 71 72#[derive(Deserialize)] 73pub struct GetCheckoutParams { 74 pub did: String, 75} 76 77pub async fn get_checkout( 78 State(state): State<AppState>, 79 headers: HeaderMap, 80 Query(params): Query<GetCheckoutParams>, 81) -> Response { 82 let did = params.did.trim(); 83 if did.is_empty() { 84 return ( 85 StatusCode::BAD_REQUEST, 86 Json(json!({"error": "InvalidRequest", "message": "did is required"})), 87 ) 88 .into_response(); 89 } 90 let is_admin_or_self = check_admin_or_self(&state, &headers, did).await; 91 let account = match assert_repo_availability(&state.db, did, is_admin_or_self).await { 92 Ok(a) => a, 93 Err(e) => return e.into_response(), 94 }; 95 let head_str = match account.repo_root_cid { 96 Some(r) => r, 97 None => { 98 return ( 99 StatusCode::BAD_REQUEST, 100 Json(json!({"error": "RepoNotFound", "message": "Repo not initialized"})), 101 ) 102 .into_response(); 103 } 104 }; 105 let head_cid = match Cid::from_str(&head_str) { 106 Ok(c) => c, 107 Err(_) => { 108 return ( 109 StatusCode::INTERNAL_SERVER_ERROR, 110 Json(json!({"error": "InternalError", "message": "Invalid head CID"})), 111 ) 112 .into_response(); 113 } 114 }; 115 let mut car_bytes = match encode_car_header(&head_cid) { 116 Ok(h) => h, 117 Err(e) => { 118 return ( 119 StatusCode::INTERNAL_SERVER_ERROR, 120 Json(json!({"error": "InternalError", "message": format!("Failed to encode CAR header: {}", e)})), 121 ) 122 .into_response(); 123 } 124 }; 125 let mut stack = vec![head_cid]; 126 let mut visited = std::collections::HashSet::new(); 127 let mut remaining = MAX_REPO_BLOCKS_TRAVERSAL; 128 while let Some(cid) = stack.pop() { 129 if visited.contains(&cid) { 130 continue; 131 } 132 visited.insert(cid); 133 if remaining == 0 { 134 break; 135 } 136 remaining -= 1; 137 if let Ok(Some(block)) = state.block_store.get(&cid).await { 138 let cid_bytes = cid.to_bytes(); 139 let total_len = cid_bytes.len() + block.len(); 140 let mut writer = Vec::new(); 141 crate::sync::car::write_varint(&mut writer, total_len as u64) 142 .expect("Writing to Vec<u8> should never fail"); 143 writer 144 .write_all(&cid_bytes) 145 .expect("Writing to Vec<u8> should never fail"); 146 writer 147 .write_all(&block) 148 .expect("Writing to Vec<u8> should never fail"); 149 car_bytes.extend_from_slice(&writer); 150 if let Ok(value) = serde_ipld_dagcbor::from_slice::<Ipld>(&block) { 151 extract_links_ipld(&value, &mut stack); 152 } 153 } 154 } 155 ( 156 StatusCode::OK, 157 [(axum::http::header::CONTENT_TYPE, "application/vnd.ipld.car")], 158 car_bytes, 159 ) 160 .into_response() 161} 162 163fn extract_links_ipld(value: &Ipld, stack: &mut Vec<Cid>) { 164 match value { 165 Ipld::Link(cid) => { 166 stack.push(*cid); 167 } 168 Ipld::Map(map) => { 169 for v in map.values() { 170 extract_links_ipld(v, stack); 171 } 172 } 173 Ipld::List(arr) => { 174 for v in arr { 175 extract_links_ipld(v, stack); 176 } 177 } 178 _ => {} 179 } 180}