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 { state.resolver.resolve(subject).await } pub async fn pds(state: &AppState, did: &str) -> anyhow::Result { 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> { 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))) }