~ knotless Search/
↑↓ move↵ open⇧↵ all results

johnandersen777.bsky.social/knotless

git that lives in your Atmosphere account

Forked from chadtmiller.com/knotless

0
0
72 lines · 2.3 KB Raw History
//! Invites to private repositories, left for their runners: nothing
//! written in a space reaches the firehose, so an owner says here that a
//! repository names a runner, and the runner asks what it was told
//! ([`knotless::invites`]). Each signs for itself with a service token. An
//! invite is only a hint, which anyone can leave: the runner reads the
//! repository's record, as a member of its space, before running anything.

use crate::gateway::caller;
use crate::moderation::{bad, index};
use crate::{App, AppError};
use axum::Json;
use axum::extract::{Query, State};
use axum::http::HeaderMap;
use knotless::invites::{INVITE, LIST};
use serde::Deserialize;
use serde_json::{Value, json};
use std::sync::Arc;

#[derive(Deserialize)]
pub struct Invite {
    runner: String,
    name: String,
}

pub async fn invite(
    State(app): State<Arc<App>>,
    headers: HeaderMap,
    Json(body): Json<Invite>,
) -> Result<Json<Value>, AppError> {
    let index = index(&app)?;
    let did = caller(&app, &headers, INVITE).await?;
    if !body.runner.starts_with("did:") {
        return Err(bad("a runner is named by its DID"));
    }
    if !knotless::keys::valid_name(&body.name) {
        return Err(bad("a repository's name is letters, digits and ._~-"));
    }
    let left = index.invite(&body.runner, &did, &body.name);
    left.map_err(anyhow::Error::from)
        .map_err(AppError::internal)?;
    Ok(Json(json!({})))
}

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

pub async fn list(
    State(app): State<Arc<App>>,
    headers: HeaderMap,
    Query(p): Query<ListParams>,
) -> Result<Json<Value>, AppError> {
    let index = index(&app)?;
    let runner = caller(&app, &headers, LIST).await?;
    let limit = p.limit.unwrap_or(100).clamp(1, 500);
    let after = match p.cursor.as_deref().map(str::parse::<i64>) {
        None => 0,
        Some(Ok(id)) => id,
        Some(Err(_)) => return Err(bad("a cursor is one this method gave")),
    };
    let page = index
        .invites(&runner, after, limit)
        .map_err(anyhow::Error::from)
        .map_err(AppError::internal)?;
    let last = page.last().map_or(after, |(id, _)| *id);
    let invites: Vec<Value> = page.into_iter().map(|(_, invite)| invite).collect();
    Ok(Json(
        json!({ "invites": invites, "cursor": last.to_string() }),
    ))
}