Something went wrong. Try again.
fork of hydrant
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144use axum::{Json, Router, extract::State, http::StatusCode, routing::get};use serde::{Deserialize, Serialize};use smol_str::SmolStr;
use crate::{api::MultiQuery, control::Hydrant};
pub fn router() -> Router<Hydrant> { Router::new() .route( "/xrpc/blue.microcosm.links.getBacklinks", get(handle_get_backlinks), ) .route( "/xrpc/blue.microcosm.links.getBacklinksCount", get(handle_get_backlinks_count), )}
#[derive(Deserialize)]pub struct GetBacklinksParams { pub subject: String, /// filter by source collection, optionally with a path suffix `collection:path` pub source: Option<String>, #[serde(default)] pub did: Vec<String>, pub limit: Option<u64>, pub cursor: Option<String>, pub reverse: Option<bool>,}
#[derive(Serialize)]pub struct Backlink { pub uri: SmolStr, pub cid: SmolStr,}
#[derive(Serialize)]pub struct GetBacklinksOutput { pub backlinks: Vec<Backlink>, #[serde(skip_serializing_if = "Option::is_none")] pub cursor: Option<String>,}
#[derive(Deserialize)]pub struct GetBacklinksCountParams { pub subject: String, pub source: Option<String>, #[serde(default)] pub did: Vec<String>,}
#[derive(Serialize)]pub struct GetBacklinksCountOutput { pub count: u64,}
pub async fn handle_get_backlinks( State(hydrant): State<Hydrant>, MultiQuery(params): MultiQuery<GetBacklinksParams>,) -> Result<Json<GetBacklinksOutput>, StatusCode> { let limit = params.limit.unwrap_or(50).min(100) as usize; let reverse = params.reverse.unwrap_or(false);
let cursor_bytes = params .cursor .as_deref() .and_then(|c| data_encoding::BASE64URL_NOPAD.decode(c.as_bytes()).ok());
let mut fetch = hydrant .backlinks .fetch(params.subject) .limit(limit) .reverse(reverse); if !params.did.is_empty() { fetch = fetch.dids(params.did); } if let Some(ref src) = params.source { fetch = fetch.source(src); } if let Some(c) = cursor_bytes { fetch = fetch.cursor(c); }
let page = fetch .run() .await .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
let next_cursor = page .next_cursor .map(|b| data_encoding::BASE64URL_NOPAD.encode(&b)); let backlinks = page .backlinks .into_iter() .map(|e| Backlink { uri: e.uri, cid: e.cid, }) .collect();
Ok(Json(GetBacklinksOutput { backlinks, cursor: next_cursor, }))}
pub async fn handle_get_backlinks_count( State(hydrant): State<Hydrant>, MultiQuery(params): MultiQuery<GetBacklinksCountParams>,) -> Result<Json<GetBacklinksCountOutput>, StatusCode> { let mut count_q = hydrant.backlinks.count(params.subject); if !params.did.is_empty() { count_q = count_q.dids(params.did); } if let Some(ref src) = params.source { count_q = count_q.source(src); }
let count = count_q .run() .await .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
Ok(Json(GetBacklinksCountOutput { count }))}
#[cfg(test)]mod tests { use super::*;
#[test] fn backlinks_query_accepts_single_and_repeated_dids() { let fetch: GetBacklinksParams = serde_html_form::from_str( "subject=at%3A%2F%2Ftarget&did=did%3Aplc%3Aone&did=did%3Aweb%3Atwo", ) .unwrap(); assert_eq!(fetch.did, ["did:plc:one", "did:web:two"]);
let count: GetBacklinksCountParams = serde_html_form::from_str("subject=at%3A%2F%2Ftarget&did=did%3Aplc%3Aone").unwrap(); assert_eq!(count.did, ["did:plc:one"]); }}