index.rs raw

use std::sync::Arc;

use poem::{
    IntoResponse, Result, handler,
    web::{Data, Query},
};
use sailfish::TemplateOnce;
use serde::Deserialize;

use crate::{App, repos::Summary};

#[derive(Deserialize)]
pub struct PageQuery {
    page: Option<usize>,
}

#[derive(TemplateOnce)]
#[template(path = "pages/index.stpl.html")]
struct IndexPage {
    repos: Vec<Summary>,
    page: usize,
    pages: usize,
}

#[handler]
pub async fn handler(
    app: Data<&Arc<App>>,
    Query(query): Query<PageQuery>,
) -> Result<impl IntoResponse> {
    let page = query.page.unwrap_or(1).max(1);
    let page_size = app.config.page_size as usize;
    let listing = app
        .blocking(move |store| {
            let names = store.public_names()?;
            let pages = names.len().div_ceil(page_size).max(1);
            if page > pages {
                return Ok(None);
            }
            let mut repos = Vec::new();
            for name in names.iter().skip((page - 1) * page_size).take(page_size) {
                match store.open(name) {
                    Ok(Some(repo)) => repos.push(repo.summary()),
                    // Unpublished since the listing was taken.
                    Ok(None) => {}
                    // One broken repository must not take the whole site
                    // down; it is reported and left out.
                    Err(err) => tracing::warn!(
                        repo = %name,
                        error = format!("{err:#}"),
                        "leaving an unopenable repository out of the index"
                    ),
                }
            }
            Ok(Some(IndexPage { repos, page, pages }))
        })
        .await?;
    let index = super::found(listing)?;
    Ok(super::page(&app, "Repositories", index)?)
}