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) = hydrant .state .db .run(move |_db| { 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); let repo = 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 { repos.push(repo); } else { next_cursor = repos.last().map(|r| r.did.clone()); break; } } Ok::<_, miette::Report>((repos, next_cursor)) }) .await .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, })) }