···3838}
39394040#[derive(Debug, Deserialize)]
4141+pub struct AccountsParams {
4242+ pub cursor: Option<String>,
4343+ pub flash_success: Option<String>,
4444+ pub flash_error: Option<String>,
4545+}
4646+4747+#[derive(Debug, Deserialize)]
4148pub struct InviteCodesParams {
4249 pub cursor: Option<String>,
4350 pub flash_success: Option<String>,
···278285 render_template(&state, "admin/dashboard.hbs", data)
279286}
280287281281-/// GET /admin/accounts — Account list
288288+/// GET /admin/accounts — Account list (paginated, 100 per page)
282289pub async fn accounts_list(
283290 State(state): State<AppState>,
284291 Extension(session): Extension<AdminSession>,
285292 Extension(permissions): Extension<AdminPermissions>,
286286- Query(flash): Query<FlashParams>,
293293+ Query(params): Query<AccountsParams>,
287294) -> Response {
288295 if !permissions.can_view_accounts {
289296 return flash_redirect("/admin/", None, Some("Access denied"));
···292299 let pds = pds_url(&state);
293300 let password = admin_password(&state);
294301295295- // Get all repos first
302302+ // Build query params for listRepos with pagination
303303+ let limit = 5;
304304+ // Yeah I know this looks bad, but I'd like to have the limit in one spot instead of two
305305+ let limit_as_string = limit.to_string();
306306+ let limit_as_str = limit_as_string.as_str();
307307+ let mut query_params: Vec<(&str, &str)> = vec![("limit", limit_as_str)];
308308+ let cursor_val;
309309+ if let Some(ref c) = params.cursor {
310310+ cursor_val = c.clone();
311311+ query_params.push(("cursor", &cursor_val));
312312+ }
313313+296314 let repos = match pds_proxy::public_xrpc_get::<serde_json::Value>(
297315 pds,
298316 "com.atproto.sync.listRepos",
299299- &[("limit", "1000")],
317317+ &query_params,
300318 )
301319 .await
302320 {
···311329 }
312330 };
313331332332+ // Extract the next-page cursor from the response
333333+ let next_cursor = repos["cursor"].as_str().map(|s| s.to_string());
334334+314335 let dids: Vec<String> = repos["repos"]
315336 .as_array()
316337 .map(|arr| {
···349370 "active_page": "accounts",
350371 });
351372352352- if let Some(msg) = flash.flash_success {
373373+ // Pagination: "Next" link
374374+ if let Some(ref cursor) = next_cursor {
375375+ // If the count returned is not the same as the limit, then we are at the end of the list
376376+ if dids.len() == limit {
377377+ data["has_next"] = true.into();
378378+ let next_url = format!("/admin/accounts?cursor={}", urlencoding::encode(cursor));
379379+ data["next_url"] = next_url.into();
380380+ }
381381+ }
382382+383383+ // Pagination: "Previous" link (visible when not on page 1)
384384+ if params.cursor.is_some() {
385385+ data["has_prev"] = true.into();
386386+ data["prev_url"] = "/admin/accounts".to_string().into();
387387+ }
388388+389389+ if let Some(msg) = params.flash_success {
353390 data["flash_success"] = msg.into();
354391 }
355355- if let Some(msg) = flash.flash_error {
392392+ if let Some(msg) = params.flash_error {
356393 data["flash_error"] = msg.into();
357394 }
358395