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