use std::path::Path;
use serde_json::json;
use crate::atproto::{SettledRepo, write_json};
use crate::nginx::conf::Block;
use super::{empty, map, reroute};
pub(crate) const LIST_SIZES: &[usize] = &[1, 10, 50, 100, 500, 1000];
pub(crate) const LIST_LIMIT_DEFAULT: usize = 500;
pub(crate) const LIST_LIMIT_MAX: usize = 1000;
pub(crate) const EMPTY_REPOS: &str = "list/empty.json";
pub(crate) fn listed(repo: &SettledRepo) -> serde_json::Value {
json!({
"did": repo.did,
"head": repo.commit.to_string(),
"rev": repo.rev,
"active": true,
})
}
pub(crate) fn emit_pages(
root: &Path,
sorted: &[&SettledRepo],
) -> anyhow::Result<(Vec<(String, String)>, Vec<(String, String)>)> {
write_json(&root.join(EMPTY_REPOS), &json!({ "repos": [] }))?;
let mut list_keys: Vec<(String, String)> = Vec::new();
let mut list_one: Vec<(String, String)> = Vec::new();
for &size in LIST_SIZES {
for cursor in (0..sorted.len().max(1)).step_by(size) {
let page: Vec<serde_json::Value> = sorted
.iter()
.skip(cursor)
.take(size)
.map(|repo| listed(repo))
.collect();
let mut body = json!({ "repos": page });
if cursor + size < sorted.len() {
body["cursor"] = json!((cursor + size).to_string());
}
let file = format!("list/{size}-{cursor}.json");
write_json(&root.join(&file), &body)?;
if size == 1 {
list_one.push((cursor.to_string(), file.clone()));
if cursor == 0 {
list_one.push((String::new(), file));
}
} else {
list_keys.push((format!("{size}|{cursor}"), file.clone()));
if cursor == 0 {
list_keys.push((format!("{size}|"), file));
}
}
}
}
if !sorted.is_empty() {
list_one.push((sorted.len().to_string(), EMPTY_REPOS.into()));
}
Ok((list_keys, list_one))
}
pub(crate) fn maps(
list_keys: Vec<(String, String)>,
list_one: Vec<(String, String)>,
) -> [Block; 3] {
[
map("$arg_limit", "$ks_atp_list_page", list_page_rows()),
map("$ks_atp_list_page|$arg_cursor", "$ks_atp_list", list_keys),
map("$arg_cursor", "$ks_atp_list_one", list_one),
]
}
pub(crate) fn location() -> Block {
reroute(
Block::exact("/xrpc/com.atproto.sync.listRepos")
.block(empty("$ks_atp_list_page", "invalid_request"))
.block(
Block::when("$ks_atp_list", "=", "")
.directive("set", ["$ks_atp_list", "$ks_atp_list_one"]),
)
.block(empty("$ks_atp_list", "invalid_request")),
"/_ks_atproto/$ks_atp_list",
)
}
fn list_page_size(limit: usize) -> usize {
LIST_SIZES
.iter()
.copied()
.rfind(|&size| size <= limit)
.unwrap_or(1)
}
pub(crate) fn list_page_rows() -> Vec<(String, String)> {
std::iter::once((String::new(), LIST_LIMIT_DEFAULT.to_string()))
.chain(
(1..=LIST_LIMIT_MAX)
.map(|limit| (limit.to_string(), list_page_size(limit).to_string())),
)
.collect()
}