atproto blogging
1//! XRPC endpoint handlers for the appview.
2
3use jacquard::CowStr;
4use jacquard::IntoStatic;
5use jacquard::cowstr::ToCowStr;
6use jacquard::types::string::AtUri;
7use smol_str::SmolStr;
8
9use crate::server::AppState;
10
11use self::actor::resolve_actor;
12use self::repo::XrpcErrorResponse;
13
14pub mod actor;
15pub mod bsky;
16pub mod collab;
17pub mod domain;
18pub mod edit;
19pub mod identity;
20pub mod notebook;
21pub mod repo;
22
23/// Resolved AT URI components with canonical DID-based URI.
24pub struct ResolvedUri {
25 /// The resolved DID (authority converted from handle if needed)
26 pub did: SmolStr,
27 /// The collection from the URI
28 pub collection: SmolStr,
29 /// The record key from the URI
30 pub rkey: SmolStr,
31 /// Canonical DID-based URI for database lookups
32 pub canonical_uri: String,
33}
34
35/// Parse an AT URI and resolve its authority to a DID.
36///
37/// This handles the common case where a URI might use a handle as the authority
38/// (e.g. `at://alice.bsky.social/...`) but the database stores URIs with DIDs
39/// (e.g. `at://did:plc:abc123/...`).
40pub async fn resolve_uri(
41 state: &AppState,
42 uri: &AtUri<'_>,
43) -> Result<ResolvedUri, XrpcErrorResponse> {
44 let authority = uri.authority();
45 let collection = uri
46 .collection()
47 .ok_or_else(|| XrpcErrorResponse::invalid_request("URI must include collection"))?;
48 let rkey = uri
49 .rkey()
50 .ok_or_else(|| XrpcErrorResponse::invalid_request("URI must include rkey"))?;
51
52 // Resolve authority to DID (might be a handle)
53 let did = resolve_actor(state, authority).await?;
54
55 // Construct canonical DID-based URI for DB lookup
56 let canonical_uri = format!("at://{}/{}/{}", did, collection, rkey.as_ref());
57
58 Ok(ResolvedUri {
59 did: SmolStr::new(&did),
60 collection: SmolStr::new(collection.as_ref()),
61 rkey: SmolStr::new(rkey.as_ref()),
62 canonical_uri,
63 })
64}
65
66/// Convert SmolStr to Option<CowStr> if non-empty
67pub fn non_empty_str(s: &SmolStr) -> Option<CowStr<'static>> {
68 if s.is_empty() {
69 None
70 } else {
71 Some(s.to_cowstr().into_static())
72 }
73}