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