···66use axum::{Json, extract::State, http::StatusCode};
77use jacquard_api::com_atproto::sync::{
88 get_repo_status::{GetRepoStatusOutput, GetRepoStatusRequest},
99- list_repos_by_collection::{ListReposByCollectionOutput, ListReposByCollectionRequest},
99+ list_repos_by_collection::{ListReposByCollectionOutput, ListReposByCollectionRequest, Repo},
1010};
1111use jacquard_axum::ExtractXrpc;
1212+use jacquard_common::types::string::Did;
12131314use crate::storage::DbRef;
14151516/// Handler for `GET /xrpc/com.atproto.sync.listReposByCollection`.
1617///
1717-/// Performs a cursor-paginated prefix scan over the rbc keyspace via
1818-/// `db::index::scan_rbc`, returning up to `limit` (default 500, max 2000)
1919-/// DIDs that have at least one record in the requested collection.
1818+/// Performs a cursor-paginated prefix scan over the rbc keyspace, returning
1919+/// up to `limit` DIDs that have at least one record in `collection`.
2020+///
2121+/// The cursor is the last DID from the previous page. On each request we
2222+/// scan for `limit + 1` results: if the extra result appears there is a next
2323+/// page, and we return the last DID of the current page as the next cursor.
2024pub async fn list_repos_by_collection(
2121- State(_db): State<DbRef>,
2222- ExtractXrpc(_req): ExtractXrpc<ListReposByCollectionRequest>,
2525+ State(db): State<DbRef>,
2626+ ExtractXrpc(req): ExtractXrpc<ListReposByCollectionRequest>,
2327) -> Result<Json<ListReposByCollectionOutput<'static>>, StatusCode> {
2424- // req.collection — Nsid<'static>
2525- // req.cursor — Option<CowStr<'static>>
2626- // req.limit — Option<i64>
2727- todo!("scan rbc index and return paginated repo list")
2828+ let limit = req.limit.unwrap_or(500).clamp(1, 2000) as usize;
2929+3030+ // Parse the cursor as a DID, if one was provided.
3131+ let cursor: Option<Did<'static>> = req
3232+ .cursor
3333+ .as_ref()
3434+ .map(Did::new_owned)
3535+ .transpose()
3636+ .map_err(|_| StatusCode::BAD_REQUEST)?;
3737+3838+ // Scan one extra to detect whether a next page exists.
3939+ let mut dids = crate::storage::index::scan_rbc(&db, req.collection, cursor, limit + 1)
4040+ .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
4141+4242+ // If we got more than limit results, set the next-page cursor to the last
4343+ // DID of the current page and drop the extra.
4444+ let next_cursor = if dids.len() > limit {
4545+ let cursor_did = dids[limit - 1].clone();
4646+ dids.truncate(limit);
4747+ Some(cursor_did.into()) // Did<'static> → CowStr<'static>
4848+ } else {
4949+ None
5050+ };
5151+5252+ let repos = dids
5353+ .into_iter()
5454+ .map(|did| Repo {
5555+ did,
5656+ extra_data: None,
5757+ })
5858+ .collect();
5959+6060+ Ok(Json(ListReposByCollectionOutput {
6161+ cursor: next_cursor,
6262+ repos,
6363+ extra_data: None,
6464+ }))
2865}
29663067/// Handler for `GET /xrpc/com.atproto.sync.getRepoStatus`.