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