this repo has no description
1use crate::api::error::ApiError; 2use crate::auth::{extract_bearer_token_from_header, validate_bearer_token_allow_takendown}; 3use crate::state::AppState; 4use crate::sync::car::encode_car_header; 5use crate::sync::util::assert_repo_availability; 6use axum::{ 7 Json, 8 extract::{Query, State}, 9 http::{HeaderMap, StatusCode}, 10 response::{IntoResponse, Response}, 11}; 12use cid::Cid; 13use ipld_core::ipld::Ipld; 14use jacquard_repo::storage::BlockStore; 15use serde::{Deserialize, Serialize}; 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 ApiError::InvalidRequest("did is required".into()).into_response(); 52 } 53 let is_admin_or_self = check_admin_or_self(&state, &headers, did).await; 54 let account = match assert_repo_availability(&state.db, did, is_admin_or_self).await { 55 Ok(a) => a, 56 Err(e) => return e.into_response(), 57 }; 58 match account.repo_root_cid { 59 Some(root) => (StatusCode::OK, Json(GetHeadOutput { root })).into_response(), 60 None => ApiError::RepoNotFound(Some(format!("Could not find root for DID: {}", did))) 61 .into_response(), 62 } 63} 64 65#[derive(Deserialize)] 66pub struct GetCheckoutParams { 67 pub did: String, 68} 69 70pub async fn get_checkout( 71 State(state): State<AppState>, 72 headers: HeaderMap, 73 Query(params): Query<GetCheckoutParams>, 74) -> Response { 75 let did = params.did.trim(); 76 if did.is_empty() { 77 return ApiError::InvalidRequest("did is required".into()).into_response(); 78 } 79 let is_admin_or_self = check_admin_or_self(&state, &headers, did).await; 80 let account = match assert_repo_availability(&state.db, did, is_admin_or_self).await { 81 Ok(a) => a, 82 Err(e) => return e.into_response(), 83 }; 84 let Some(head_str) = account.repo_root_cid else { 85 return ApiError::RepoNotFound(Some("Repo not initialized".into())).into_response(); 86 }; 87 let Ok(head_cid) = Cid::from_str(&head_str) else { 88 return ApiError::InternalError(Some("Invalid head CID".into())).into_response(); 89 }; 90 let Ok(mut car_bytes) = encode_car_header(&head_cid) else { 91 return ApiError::InternalError(Some("Failed to encode CAR header".into())).into_response(); 92 }; 93 let mut stack = vec![head_cid]; 94 let mut visited = std::collections::HashSet::new(); 95 let mut remaining = MAX_REPO_BLOCKS_TRAVERSAL; 96 while let Some(cid) = stack.pop() { 97 if visited.contains(&cid) { 98 continue; 99 } 100 visited.insert(cid); 101 if remaining == 0 { 102 break; 103 } 104 remaining -= 1; 105 if let Ok(Some(block)) = state.block_store.get(&cid).await { 106 let cid_bytes = cid.to_bytes(); 107 let total_len = cid_bytes.len() + block.len(); 108 let mut writer = Vec::new(); 109 crate::sync::car::write_varint(&mut writer, total_len as u64) 110 .expect("Writing to Vec<u8> should never fail"); 111 writer 112 .write_all(&cid_bytes) 113 .expect("Writing to Vec<u8> should never fail"); 114 writer 115 .write_all(&block) 116 .expect("Writing to Vec<u8> should never fail"); 117 car_bytes.extend_from_slice(&writer); 118 if let Ok(value) = serde_ipld_dagcbor::from_slice::<Ipld>(&block) { 119 extract_links_ipld(&value, &mut stack); 120 } 121 } 122 } 123 ( 124 StatusCode::OK, 125 [(axum::http::header::CONTENT_TYPE, "application/vnd.ipld.car")], 126 car_bytes, 127 ) 128 .into_response() 129} 130 131fn extract_links_ipld(value: &Ipld, stack: &mut Vec<Cid>) { 132 match value { 133 Ipld::Link(cid) => { 134 stack.push(*cid); 135 } 136 Ipld::Map(map) => { 137 for v in map.values() { 138 extract_links_ipld(v, stack); 139 } 140 } 141 Ipld::List(arr) => { 142 for v in arr { 143 extract_links_ipld(v, stack); 144 } 145 } 146 _ => {} 147 } 148}