Constellation, Spacedust, Slingshot, UFOs: atproto crates and services for microcosm

serve service did doc

Changed files
+48 -3
slingshot
+9 -1
slingshot/src/main.rs
··· 23 23 /// where to keep disk caches 24 24 #[arg(long)] 25 25 cache_dir: PathBuf, 26 + /// the domain pointing to this server 27 + /// 28 + /// if present: 29 + /// - a did:web document will be served at /.well-known/did.json 30 + /// - TODO: HTTPS certs will be automatically configured with Acme/letsencrypt 31 + /// - TODO: a rate-limiter will be installed 32 + #[arg(long)] 33 + host: Option<String>, 26 34 } 27 35 28 36 #[tokio::main] ··· 78 86 let server_shutdown = shutdown.clone(); 79 87 let server_cache_handle = cache.clone(); 80 88 tasks.spawn(async move { 81 - serve(server_cache_handle, repo, server_shutdown).await?; 89 + serve(server_cache_handle, repo, args.host, server_shutdown).await?; 82 90 Ok(()) 83 91 }); 84 92
+39 -2
slingshot/src/server.rs
··· 1 1 use crate::{CachedRecord, Repo, error::ServerError}; 2 2 use foyer::HybridCache; 3 + use serde::Serialize; 3 4 use std::sync::Arc; 4 5 use tokio_util::sync::CancellationToken; 5 6 6 - use poem::{Route, Server, listener::TcpListener}; 7 + use poem::{Endpoint, Route, Server, endpoint::make_sync, listener::TcpListener}; 7 8 use poem_openapi::{ 8 9 ApiResponse, Object, OpenApi, OpenApiService, param::Query, payload::Json, types::Example, 9 10 }; ··· 191 192 // those are a little bit important 192 193 } 193 194 195 + #[derive(Debug, Clone, Serialize)] 196 + #[serde(rename_all = "camelCase")] 197 + struct AppViewService { 198 + id: String, 199 + r#type: String, 200 + service_endpoint: String, 201 + } 202 + #[derive(Debug, Clone, Serialize)] 203 + struct AppViewDoc { 204 + id: String, 205 + service: [AppViewService; 1], 206 + } 207 + /// Serve a did document for did:web for this to be an xrpc appview 208 + /// 209 + /// No slingshot endpoints currently require auth, so it's not necessary to do 210 + /// service proxying, however clients may wish to: 211 + /// 212 + /// - PDS proxying offers a level of client IP anonymity from slingshot 213 + /// - slingshot *may* implement more generous per-user rate-limits for proxied requests in the future 214 + fn get_did_doc(host: String) -> impl Endpoint { 215 + let doc = poem::web::Json(AppViewDoc { 216 + id: format!("did:web:{host}"), 217 + service: [AppViewService { 218 + id: "#slingshot".to_string(), 219 + r#type: "SlingshotRecordProxy".to_string(), 220 + service_endpoint: format!("https://{host}"), 221 + }], 222 + }); 223 + make_sync(move |_| doc.clone()) 224 + } 225 + 194 226 pub async fn serve( 195 227 cache: HybridCache<String, CachedRecord>, 196 228 repo: Repo, 229 + host: Option<String>, 197 230 _shutdown: CancellationToken, 198 231 ) -> Result<(), ServerError> { 199 232 let repo = Arc::new(repo); ··· 202 235 .server("http://localhost:3000") 203 236 .url_prefix("/xrpc"); 204 237 205 - let app = Route::new() 238 + let mut app = Route::new() 206 239 .nest("/", api_service.scalar()) 207 240 .nest("/openapi.json", api_service.spec_endpoint()) 208 241 .nest("/xrpc/", api_service); 242 + 243 + if let Some(host) = host { 244 + app = app.at("/.well-known/did.json", get_did_doc(host)); 245 + }; 209 246 210 247 Server::new(TcpListener::bind("127.0.0.1:3000")) 211 248 .run(app)