use std::str::FromStr;
use jacquard_common::types::{cid::Cid, string::Did, tid::Tid};
use smol_str::SmolStr;
use crate::api::xrpc::get_repo_status::repo_status_to_api;
use crate::ingest::stream::AccountStatus;
use super::*;
#[derive(Deserialize)]
pub struct ListRepos {
pub cursor: Option<SmolStr>,
pub limit: Option<i64>,
}
#[derive(Serialize)]
pub struct Repo {
#[serde(skip_serializing_if = "Option::is_none")]
pub active: Option<bool>,
pub did: Did,
pub head: Cid,
pub rev: Tid,
#[serde(skip_serializing_if = "Option::is_none")]
pub status: Option<AccountStatus<'static>>,
}
#[derive(Serialize)]
pub struct ListReposOutput {
#[serde(skip_serializing_if = "Option::is_none")]
pub cursor: Option<SmolStr>,
pub repos: Vec<Repo>,
}
pub async fn handle(
State(hydrant): State<Hydrant>,
ExtractXrpc(req): ExtractXrpc<ListRepos>,
) -> XrpcResult<Json<ListReposOutput>> {
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(bad_request)?;
let (repos, next_cursor) = hydrant
.state
.db
.run(move |_db| {
let mut repos: Vec<Repo> = Vec::new();
let mut next_cursor: Option<Did> = 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::ipld(commit_cid),
rev: atp_commit.rev,
status,
});
if repos.len() >= limit {
next_cursor = Some(did);
break;
}
}
Ok((repos, next_cursor))
})
.await
.map_err(internal_error)?;
Ok(Json(ListReposOutput {
cursor: next_cursor.map(|d| d.to_smolstr()),
repos,
}))
}