lightweight com.atproto.sync.listReposByCollection
45
fork

Configure Feed

Select the types of activity you want to include in your feed.

implement listReposByCollection

phil 7aa8bf46 e2a3db7e

+47 -10
+47 -10
src/server/handler.rs
··· 6 6 use axum::{Json, extract::State, http::StatusCode}; 7 7 use jacquard_api::com_atproto::sync::{ 8 8 get_repo_status::{GetRepoStatusOutput, GetRepoStatusRequest}, 9 - list_repos_by_collection::{ListReposByCollectionOutput, ListReposByCollectionRequest}, 9 + list_repos_by_collection::{ListReposByCollectionOutput, ListReposByCollectionRequest, Repo}, 10 10 }; 11 11 use jacquard_axum::ExtractXrpc; 12 + use jacquard_common::types::string::Did; 12 13 13 14 use crate::storage::DbRef; 14 15 15 16 /// Handler for `GET /xrpc/com.atproto.sync.listReposByCollection`. 16 17 /// 17 - /// Performs a cursor-paginated prefix scan over the rbc keyspace via 18 - /// `db::index::scan_rbc`, returning up to `limit` (default 500, max 2000) 19 - /// DIDs that have at least one record in the requested collection. 18 + /// Performs a cursor-paginated prefix scan over the rbc keyspace, returning 19 + /// up to `limit` DIDs that have at least one record in `collection`. 20 + /// 21 + /// The cursor is the last DID from the previous page. On each request we 22 + /// scan for `limit + 1` results: if the extra result appears there is a next 23 + /// page, and we return the last DID of the current page as the next cursor. 20 24 pub async fn list_repos_by_collection( 21 - State(_db): State<DbRef>, 22 - ExtractXrpc(_req): ExtractXrpc<ListReposByCollectionRequest>, 25 + State(db): State<DbRef>, 26 + ExtractXrpc(req): ExtractXrpc<ListReposByCollectionRequest>, 23 27 ) -> Result<Json<ListReposByCollectionOutput<'static>>, StatusCode> { 24 - // req.collection — Nsid<'static> 25 - // req.cursor — Option<CowStr<'static>> 26 - // req.limit — Option<i64> 27 - todo!("scan rbc index and return paginated repo list") 28 + let limit = req.limit.unwrap_or(500).clamp(1, 2000) as usize; 29 + 30 + // Parse the cursor as a DID, if one was provided. 31 + let cursor: Option<Did<'static>> = req 32 + .cursor 33 + .as_ref() 34 + .map(Did::new_owned) 35 + .transpose() 36 + .map_err(|_| StatusCode::BAD_REQUEST)?; 37 + 38 + // Scan one extra to detect whether a next page exists. 39 + let mut dids = crate::storage::index::scan_rbc(&db, req.collection, cursor, limit + 1) 40 + .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; 41 + 42 + // If we got more than limit results, set the next-page cursor to the last 43 + // DID of the current page and drop the extra. 44 + let next_cursor = if dids.len() > limit { 45 + let cursor_did = dids[limit - 1].clone(); 46 + dids.truncate(limit); 47 + Some(cursor_did.into()) // Did<'static> → CowStr<'static> 48 + } else { 49 + None 50 + }; 51 + 52 + let repos = dids 53 + .into_iter() 54 + .map(|did| Repo { 55 + did, 56 + extra_data: None, 57 + }) 58 + .collect(); 59 + 60 + Ok(Json(ListReposByCollectionOutput { 61 + cursor: next_cursor, 62 + repos, 63 + extra_data: None, 64 + })) 28 65 } 29 66 30 67 /// Handler for `GET /xrpc/com.atproto.sync.getRepoStatus`.