···33//! Serves XRPC endpoints via axum routers built with `jacquard-axum`'s
44//! `IntoRouter` helper.
5566+mod admin;
67mod get_repo_status;
78mod hello;
89mod list_repos;
910mod list_repos_by_collection;
10111212+use admin::admin_status;
1113use get_repo_status::get_repo_status;
1214use list_repos::list_repos;
1315use list_repos_by_collection::list_repos_by_collection;
14161517use std::net::SocketAddr;
16181919+use jacquard_common::url::Host;
2020+1721use crate::com_atproto::sync::list_repos_by_collection::ListReposByCollectionRequest;
1822use jacquard_api::com_atproto::sync::{
1923 get_repo_status::GetRepoStatusRequest, list_repos::ListReposRequest,
···2327use crate::error::Result;
2428use crate::storage::DbRef;
25293030+/// Config for the admin endpoints. Only constructed when `--admin-password` is
3131+/// set; when absent the `/admin/*` routes are not registered at all.
3232+#[derive(Clone)]
3333+pub struct AdminConfig {
3434+ /// The relay/PDS host that lightrail is subscribed to and backfilling from.
3535+ pub subscribe_host: Host,
3636+ /// Required Bearer token for all admin endpoints.
3737+ pub admin_password: String,
3838+}
3939+2640/// Build and serve the axum application on `addr`.
2741///
2828-/// Routes:
4242+/// Routes always registered:
2943/// GET /xrpc/com.atproto.sync.getRepoStatus
3044/// GET /xrpc/com.atproto.sync.listRepos
3145/// GET /xrpc/com.atproto.sync.listReposByCollection
4646+///
4747+/// Registered only when `admin_config` is `Some`:
4848+/// GET /admin/status
3249pub async fn serve(
3350 addr: SocketAddr,
3451 db: DbRef,
3552 token: tokio_util::sync::CancellationToken,
5353+ admin_config: Option<AdminConfig>,
3654) -> Result<()> {
3737- let app = GetRepoStatusRequest::into_router(get_repo_status)
5555+ let base = GetRepoStatusRequest::into_router(get_repo_status)
3856 .merge(ListReposRequest::into_router(list_repos))
3957 .merge(ListReposByCollectionRequest::into_router(
4058 list_repos_by_collection,
4141- ))
4242- .with_state(db)
4343- .route("/", axum::routing::get(hello::hello));
5959+ ));
6060+6161+ let app = if let Some(config) = admin_config {
6262+ base.route("/admin/status", axum::routing::get(admin_status))
6363+ .route("/", axum::routing::get(hello::hello))
6464+ .with_state(db)
6565+ .layer(axum::Extension(config))
6666+ } else {
6767+ base.route("/", axum::routing::get(hello::hello))
6868+ .with_state(db)
6969+ };
44704571 let listener = tokio::net::TcpListener::bind(addr).await?;
4672 axum::serve(listener, app)
+7
src/storage/repo.rs
···260260 Ok(Some(info.status))
261261}
262262263263+/// Count the total number of repos under `rev`
264264+///
265265+/// Counts how many repos have been sync'd ever, sort of
266266+pub fn count_repos(db: &DbRef) -> usize {
267267+ db.ks.prefix(PREFIX_REPO_PREV).count()
268268+}
269269+263270/// Insert a [`RepoInfo`] with `state = Pending` for `did` if no record exists.
264271///
265272/// Returns `true` if a new record was inserted, `false` if one already existed.
+7
src/storage/resync_queue.rs
···166166 batch.insert(&db.ks, key(ts, &item.did), encode(item));
167167}
168168169169+/// Count the total number of entries currently in the resync queue.
170170+///
171171+/// Performs a full prefix scan; use only for admin/diagnostic views.
172172+pub fn count_queued(db: &DbRef) -> usize {
173173+ db.ks.prefix(key_prefix_all()).count()
174174+}
175175+169176/// Enqueue a repo for resync at the given Unix timestamp (seconds).
170177pub fn enqueue(db: &DbRef, ts: u64, item: &ResyncItem) -> StorageResult<()> {
171178 let mut batch = db.database.batch();