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