chadtmiller.com/knotless

Git hosted in your atproto account, on any stock PDS, fast enough for agents.

git clone knotless://chadtmiller.com/knotless

needs the knotless git helper. set it up

79 lines · 2.3 KB Raw History
//! What the appview answers from its index.

use crate::{App, AppError};
use axum::Json;
use axum::extract::{Query, State};
use axum::http::StatusCode;
use serde::Deserialize;
use serde_json::{Value, json};
use std::sync::Arc;

fn index(app: &App) -> Result<&crate::index::Index, AppError> {
    app.index
        .as_deref()
        .ok_or_else(|| AppError(StatusCode::NOT_FOUND, "this server keeps no index".into()))
}

#[derive(Deserialize)]
pub struct ForgeParams {
    repo: String,
    key: String,
    limit: Option<usize>,
    cursor: Option<String>,
}

/// The pulls, issues, comments and state changes about a repository: the
/// one its owner made with `key`, whatever it has been named. A page at a
/// time, by URI.
pub async fn get_forge(
    State(app): State<Arc<App>>,
    Query(p): Query<ForgeParams>,
) -> Result<Json<Value>, AppError> {
    let did = knotless::forge::parse_uri(&p.repo)
        .map_err(|e| AppError(StatusCode::BAD_REQUEST, e.to_string()))?
        .0;
    let mut entries = index(&app)?
        .forge(did, &p.key)
        .map_err(anyhow::Error::from)
        .map_err(AppError::internal)?;
    entries.sort_by(|a, b| a.uri.cmp(&b.uri));
    let after = p.cursor.unwrap_or_default();
    let limit = p.limit.unwrap_or(500).clamp(1, 1000);
    let mut page: Vec<_> = entries.into_iter().filter(|e| e.uri > after).collect();
    let more = page.len() > limit;
    page.truncate(limit);
    let mut out = json!({ "entries": page });
    if more {
        out["cursor"] = page[limit - 1].uri.clone().into();
    }
    Ok(Json(out))
}

#[derive(Deserialize)]
pub struct ListParams {
    q: Option<String>,
    limit: Option<usize>,
    cursor: Option<String>,
}

/// Repositories, newest first, matching `q` by name or description, a
/// page at a time.
pub async fn list_repos(
    State(app): State<Arc<App>>,
    Query(p): Query<ListParams>,
) -> Result<Json<Value>, AppError> {
    let limit = p.limit.unwrap_or(50).clamp(1, 500);
    let (repos, cursor) = index(&app)?
        .repos_page(
            p.q.as_deref().unwrap_or_default(),
            limit,
            p.cursor.as_deref(),
        )
        .map_err(anyhow::Error::from)
        .map_err(AppError::internal)?;
    let mut out = json!({ "repos": repos });
    if let Some(cursor) = cursor {
        out["cursor"] = cursor.into();
    }
    Ok(Json(out))
}