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

johnandersen777.bsky.social/zkwid

0
0
main
79 lines · 2.6 KB Raw History
use crate::AppState;
use atproto_client::client::{Auth, CallLimits, xrpc_call_with_limits};
use atproto_identity::model::Document;
use axum::http::{HeaderMap, Method, StatusCode};
use serde_json::Value;
use std::time::Duration;

#[derive(Debug, Clone, PartialEq)]
pub struct RecordNotFound(pub String);

impl std::fmt::Display for RecordNotFound {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "record not found: {}", self.0)
    }
}

impl std::error::Error for RecordNotFound {}

pub async fn resolve(state: &AppState, subject: &str) -> anyhow::Result<Document> {
    state.resolver.resolve(subject).await
}

pub async fn pds(state: &AppState, did: &str) -> anyhow::Result<String> {
    let document = resolve(state, did).await?;
    document
        .pds_endpoints_validated()
        .first()
        .map(|endpoint| endpoint.trim_end_matches('/').to_string())
        .ok_or_else(|| anyhow::anyhow!("{did} names no usable PDS"))
}

pub async fn get_record(state: &AppState, uri: &str) -> anyhow::Result<(Value, String)> {
    find_record(state, uri)
        .await?
        .ok_or_else(|| RecordNotFound(uri.to_string()).into())
}

pub async fn find_record(state: &AppState, uri: &str) -> anyhow::Result<Option<(Value, String)>> {
    let (repo, collection, rkey) = zkwid::lexicon::parse_at_uri(uri)?;
    let base = pds(state, &repo).await?;
    let url = reqwest::Url::parse_with_params(
        &format!("{base}/xrpc/com.atproto.repo.getRecord"),
        [
            ("repo", repo.as_str()),
            ("collection", collection.as_str()),
            ("rkey", rkey.as_str()),
        ],
    )?;
    let response = xrpc_call_with_limits(
        &state.http,
        &Auth::None,
        Method::GET,
        url.as_str(),
        None,
        &HeaderMap::new(),
        CallLimits::new(Duration::from_secs(30), 2 * 1024 * 1024),
    )
    .await?;
    let (error, message) = response.xrpc_error_fields();
    if response.status == StatusCode::NOT_FOUND || error == "RecordNotFound" {
        return Ok(None);
    }
    if !response.is_success() {
        anyhow::bail!("getRecord {uri}: {} {error} {message}", response.status);
    }
    let body = response
        .body
        .ok_or_else(|| anyhow::anyhow!("getRecord {uri}: body is not JSON"))?;
    let cid = body
        .get("cid")
        .and_then(Value::as_str)
        .ok_or_else(|| anyhow::anyhow!("getRecord {uri}: no cid"))?
        .to_string();
    let value = body
        .get("value")
        .cloned()
        .ok_or_else(|| anyhow::anyhow!("getRecord {uri}: no value"))?;
    Ok(Some((value, cid)))
}