this repo has no description
1use crate::state::AppState; 2use axum::{ 3 Json, 4 extract::{Query, State}, 5 http::StatusCode, 6 response::{IntoResponse, Response}, 7}; 8use cid::Cid; 9use jacquard_repo::commit::Commit; 10use jacquard_repo::storage::BlockStore; 11use serde::{Deserialize, Serialize}; 12use serde_json::json; 13use std::str::FromStr; 14use tracing::error; 15async fn get_rev_from_commit(state: &AppState, cid_str: &str) -> Option<String> { 16 let cid = Cid::from_str(cid_str).ok()?; 17 let block = state.block_store.get(&cid).await.ok()??; 18 let commit = Commit::from_cbor(&block).ok()?; 19 Some(commit.rev().to_string()) 20} 21#[derive(Deserialize)] 22pub struct GetLatestCommitParams { 23 pub did: String, 24} 25#[derive(Serialize)] 26pub struct GetLatestCommitOutput { 27 pub cid: String, 28 pub rev: String, 29} 30pub async fn get_latest_commit( 31 State(state): State<AppState>, 32 Query(params): Query<GetLatestCommitParams>, 33) -> Response { 34 let did = params.did.trim(); 35 if did.is_empty() { 36 return ( 37 StatusCode::BAD_REQUEST, 38 Json(json!({"error": "InvalidRequest", "message": "did is required"})), 39 ) 40 .into_response(); 41 } 42 let result = sqlx::query!( 43 r#" 44 SELECT r.repo_root_cid 45 FROM repos r 46 JOIN users u ON r.user_id = u.id 47 WHERE u.did = $1 48 "#, 49 did 50 ) 51 .fetch_optional(&state.db) 52 .await; 53 match result { 54 Ok(Some(row)) => { 55 let rev = get_rev_from_commit(&state, &row.repo_root_cid).await 56 .unwrap_or_else(|| chrono::Utc::now().timestamp_millis().to_string()); 57 ( 58 StatusCode::OK, 59 Json(GetLatestCommitOutput { 60 cid: row.repo_root_cid, 61 rev, 62 }), 63 ) 64 .into_response() 65 } 66 Ok(None) => ( 67 StatusCode::NOT_FOUND, 68 Json(json!({"error": "RepoNotFound", "message": "Could not find repo for DID"})), 69 ) 70 .into_response(), 71 Err(e) => { 72 error!("DB error in get_latest_commit: {:?}", e); 73 ( 74 StatusCode::INTERNAL_SERVER_ERROR, 75 Json(json!({"error": "InternalError"})), 76 ) 77 .into_response() 78 } 79 } 80} 81#[derive(Deserialize)] 82pub struct ListReposParams { 83 pub limit: Option<i64>, 84 pub cursor: Option<String>, 85} 86#[derive(Serialize)] 87#[serde(rename_all = "camelCase")] 88pub struct RepoInfo { 89 pub did: String, 90 pub head: String, 91 pub rev: String, 92 pub active: bool, 93} 94#[derive(Serialize)] 95pub struct ListReposOutput { 96 pub cursor: Option<String>, 97 pub repos: Vec<RepoInfo>, 98} 99pub async fn list_repos( 100 State(state): State<AppState>, 101 Query(params): Query<ListReposParams>, 102) -> Response { 103 let limit = params.limit.unwrap_or(50).clamp(1, 1000); 104 let cursor_did = params.cursor.as_deref().unwrap_or(""); 105 let result = sqlx::query!( 106 r#" 107 SELECT u.did, r.repo_root_cid 108 FROM repos r 109 JOIN users u ON r.user_id = u.id 110 WHERE u.did > $1 111 ORDER BY u.did ASC 112 LIMIT $2 113 "#, 114 cursor_did, 115 limit + 1 116 ) 117 .fetch_all(&state.db) 118 .await; 119 match result { 120 Ok(rows) => { 121 let has_more = rows.len() as i64 > limit; 122 let mut repos: Vec<RepoInfo> = Vec::new(); 123 for row in rows.iter().take(limit as usize) { 124 let rev = get_rev_from_commit(&state, &row.repo_root_cid).await 125 .unwrap_or_else(|| chrono::Utc::now().timestamp_millis().to_string()); 126 repos.push(RepoInfo { 127 did: row.did.clone(), 128 head: row.repo_root_cid.clone(), 129 rev, 130 active: true, 131 }); 132 } 133 let next_cursor = if has_more { 134 repos.last().map(|r| r.did.clone()) 135 } else { 136 None 137 }; 138 ( 139 StatusCode::OK, 140 Json(ListReposOutput { 141 cursor: next_cursor, 142 repos, 143 }), 144 ) 145 .into_response() 146 } 147 Err(e) => { 148 error!("DB error in list_repos: {:?}", e); 149 ( 150 StatusCode::INTERNAL_SERVER_ERROR, 151 Json(json!({"error": "InternalError"})), 152 ) 153 .into_response() 154 } 155 } 156} 157#[derive(Deserialize)] 158pub struct GetRepoStatusParams { 159 pub did: String, 160} 161#[derive(Serialize)] 162pub struct GetRepoStatusOutput { 163 pub did: String, 164 pub active: bool, 165 pub rev: Option<String>, 166} 167pub async fn get_repo_status( 168 State(state): State<AppState>, 169 Query(params): Query<GetRepoStatusParams>, 170) -> Response { 171 let did = params.did.trim(); 172 if did.is_empty() { 173 return ( 174 StatusCode::BAD_REQUEST, 175 Json(json!({"error": "InvalidRequest", "message": "did is required"})), 176 ) 177 .into_response(); 178 } 179 let result = sqlx::query!( 180 r#" 181 SELECT u.did, r.repo_root_cid 182 FROM users u 183 LEFT JOIN repos r ON u.id = r.user_id 184 WHERE u.did = $1 185 "#, 186 did 187 ) 188 .fetch_optional(&state.db) 189 .await; 190 match result { 191 Ok(Some(row)) => { 192 let rev = get_rev_from_commit(&state, &row.repo_root_cid).await; 193 ( 194 StatusCode::OK, 195 Json(GetRepoStatusOutput { 196 did: row.did, 197 active: true, 198 rev, 199 }), 200 ) 201 .into_response() 202 } 203 Ok(None) => ( 204 StatusCode::NOT_FOUND, 205 Json(json!({"error": "RepoNotFound", "message": "Could not find repo for DID"})), 206 ) 207 .into_response(), 208 Err(e) => { 209 error!("DB error in get_repo_status: {:?}", e); 210 ( 211 StatusCode::INTERNAL_SERVER_ERROR, 212 Json(json!({"error": "InternalError"})), 213 ) 214 .into_response() 215 } 216 } 217}