this repo has no description
1use crate::api::error::ApiError; 2use crate::state::AppState; 3use crate::sync::util::assert_repo_availability; 4use axum::{ 5 Json, 6 body::Body, 7 extract::{Query, State}, 8 http::StatusCode, 9 http::header, 10 response::{IntoResponse, Response}, 11}; 12use serde::{Deserialize, Serialize}; 13use tracing::error; 14 15#[derive(Deserialize)] 16pub struct GetBlobParams { 17 pub did: String, 18 pub cid: String, 19} 20 21pub async fn get_blob( 22 State(state): State<AppState>, 23 Query(params): Query<GetBlobParams>, 24) -> Response { 25 let did = params.did.trim(); 26 let cid = params.cid.trim(); 27 if did.is_empty() { 28 return ApiError::InvalidRequest("did is required".into()).into_response(); 29 } 30 if cid.is_empty() { 31 return ApiError::InvalidRequest("cid is required".into()).into_response(); 32 } 33 34 let _account = match assert_repo_availability(&state.db, did, false).await { 35 Ok(a) => a, 36 Err(e) => return e.into_response(), 37 }; 38 39 let blob_result = sqlx::query!( 40 "SELECT storage_key, mime_type, size_bytes FROM blobs WHERE cid = $1", 41 cid 42 ) 43 .fetch_optional(&state.db) 44 .await; 45 match blob_result { 46 Ok(Some(row)) => { 47 let storage_key = &row.storage_key; 48 let mime_type = &row.mime_type; 49 let size_bytes = row.size_bytes; 50 match state.blob_store.get(storage_key).await { 51 Ok(data) => Response::builder() 52 .status(StatusCode::OK) 53 .header(header::CONTENT_TYPE, mime_type) 54 .header(header::CONTENT_LENGTH, size_bytes.to_string()) 55 .header("x-content-type-options", "nosniff") 56 .header("content-security-policy", "default-src 'none'; sandbox") 57 .body(Body::from(data)) 58 .unwrap(), 59 Err(e) => { 60 error!("Failed to fetch blob from storage: {:?}", e); 61 ApiError::BlobNotFound(Some("Blob not found in storage".into())).into_response() 62 } 63 } 64 } 65 Ok(None) => ApiError::BlobNotFound(Some("Blob not found".into())).into_response(), 66 Err(e) => { 67 error!("DB error in get_blob: {:?}", e); 68 ApiError::InternalError(Some("Database error".into())).into_response() 69 } 70 } 71} 72 73#[derive(Deserialize)] 74pub struct ListBlobsParams { 75 pub did: String, 76 pub since: Option<String>, 77 pub limit: Option<i64>, 78 pub cursor: Option<String>, 79} 80 81#[derive(Serialize)] 82pub struct ListBlobsOutput { 83 #[serde(skip_serializing_if = "Option::is_none")] 84 pub cursor: Option<String>, 85 pub cids: Vec<String>, 86} 87 88pub async fn list_blobs( 89 State(state): State<AppState>, 90 Query(params): Query<ListBlobsParams>, 91) -> Response { 92 let did = params.did.trim(); 93 if did.is_empty() { 94 return ApiError::InvalidRequest("did is required".into()).into_response(); 95 } 96 97 let account = match assert_repo_availability(&state.db, did, false).await { 98 Ok(a) => a, 99 Err(e) => return e.into_response(), 100 }; 101 102 let limit = params.limit.unwrap_or(500).clamp(1, 1000); 103 let cursor_cid = params.cursor.as_deref().unwrap_or(""); 104 let user_id = account.user_id; 105 106 let cids_result: Result<Vec<String>, sqlx::Error> = if let Some(since) = &params.since { 107 sqlx::query_scalar!( 108 r#" 109 SELECT DISTINCT unnest(blobs) as "cid!" 110 FROM repo_seq 111 WHERE did = $1 AND rev > $2 AND blobs IS NOT NULL 112 "#, 113 did, 114 since 115 ) 116 .fetch_all(&state.db) 117 .await 118 .map(|mut cids| { 119 cids.sort(); 120 cids.into_iter() 121 .filter(|c| c.as_str() > cursor_cid) 122 .take((limit + 1) as usize) 123 .collect() 124 }) 125 } else { 126 sqlx::query!( 127 r#" 128 SELECT cid FROM blobs 129 WHERE created_by_user = $1 AND cid > $2 130 ORDER BY cid ASC 131 LIMIT $3 132 "#, 133 user_id, 134 cursor_cid, 135 limit + 1 136 ) 137 .fetch_all(&state.db) 138 .await 139 .map(|rows| rows.into_iter().map(|r| r.cid).collect()) 140 }; 141 match cids_result { 142 Ok(cids) => { 143 let has_more = cids.len() as i64 > limit; 144 let cids: Vec<String> = cids.into_iter().take(limit as usize).collect(); 145 let next_cursor = if has_more { cids.last().cloned() } else { None }; 146 ( 147 StatusCode::OK, 148 Json(ListBlobsOutput { 149 cursor: next_cursor, 150 cids, 151 }), 152 ) 153 .into_response() 154 } 155 Err(e) => { 156 error!("DB error in list_blobs: {:?}", e); 157 ApiError::InternalError(Some("Database error".into())).into_response() 158 } 159 } 160}