use std::str::FromStr; use jacquard_api::com_atproto::sync::list_repos::{ ListReposOutput, ListReposRequest, ListReposResponse, Repo, }; use jacquard_common::CowStr; use jacquard_common::types::cid::Cid; use jacquard_common::types::string::Did; use smol_str::ToSmolStr; use crate::api::xrpc::get_repo_status::repo_status_to_api; use super::*; pub async fn handle( State(hydrant): State, ExtractXrpc(req): ExtractXrpc, ) -> XrpcResult>> { let nsid = ListReposResponse::NSID; let limit = req.limit.unwrap_or(500).clamp(1, 1000) as usize; let cursor = req .cursor .as_deref() .map(Did::from_str) .transpose() .map_err(|e| bad_request(nsid, e))?; let (repos, next_cursor) = tokio::task::spawn_blocking(move || { let mut repos: Vec> = Vec::new(); let mut next_cursor: Option> = None; for item in hydrant.repos.iter_states(cursor.as_ref()) { let (did, state) = item?; // skip repos that haven't been synced at least once let Some(commit) = state.root else { continue; }; let Some(atp_commit) = commit.into_atp_commit(did.clone()) else { tracing::warn!(did = %did, "repo needs migration"); continue; }; let Ok(commit_cid) = atp_commit.to_cid() else { tracing::warn!(did = %did, "failed to compute commit CID"); continue; }; let status = repo_status_to_api(state.status); repos.push(Repo { active: Some(state.active), did: did.clone(), head: Cid::Str(CowStr::Owned(commit_cid.to_smolstr())), rev: atp_commit.rev, status, extra_data: None, }); if repos.len() >= limit { next_cursor = Some(did); break; } } Ok::<_, miette::Report>((repos, next_cursor)) }) .await .map_err(|e| internal_error(nsid, e))? .map_err(|e| internal_error(nsid, e))?; Ok(Json(ListReposOutput { cursor: next_cursor.map(|d| CowStr::Owned(d.as_str().to_smolstr())), repos, extra_data: None, })) }