Scalable and distributed custom feed generator, ott - on that topic
at main 103 lines 3.4 kB view raw
1use std::collections::BTreeMap; 2 3use axum::{routing::get, Json, Router}; 4use jacquard::types::did_doc::{DidDocument, Service, VerificationMethod}; 5use jacquard_api::app_bsky::feed::{ 6 get_feed_skeleton::{GetFeedSkeletonOutput, GetFeedSkeletonRequest}, 7 SkeletonFeedPost, 8}; 9use jacquard_axum::did_web::did_web_router; 10use jacquard_axum::ExtractXrpc; 11use jacquard_axum::{ 12 service_auth::{ExtractServiceAuth, ServiceAuthConfig}, 13 IntoRouter, 14}; 15use jacquard_common::types::string::Did; 16use jacquard_identity::resolver::ResolverOptions; 17use jacquard_identity::JacquardResolver; 18use ott_xrpc::{bsky::BskyClient, key::generate_key}; 19 20use serde_json::Value; 21use tracing::info; 22use tracing_subscriber::EnvFilter; 23 24use tower_http::normalize_path::NormalizePathLayer; 25 26async fn handle_wellknown_atproto_did() -> Json<serde_json::Value> { 27 Json::from(Value::from("did:web:ott.aleeve.dev")) 28} 29 30async fn handler( 31 ExtractServiceAuth(auth): ExtractServiceAuth, 32 ExtractXrpc(args): ExtractXrpc<GetFeedSkeletonRequest>, 33) -> Result<Json<GetFeedSkeletonOutput<'static>>, String> { 34 let posts: Vec<SkeletonFeedPost<'static>> = vec![SkeletonFeedPost { 35 post: "at://did:plc:klugggc44dmpomjkuzyahzjd/app.bsky.feed.post/3m2y6a5h6os27" 36 .parse() 37 .map_err(|_| "Failed to parse uri".to_string())?, 38 feed_context: None, 39 extra_data: BTreeMap::default(), 40 reason: None, 41 }]; 42 43 let output = GetFeedSkeletonOutput::<'static> { 44 feed: posts, 45 cursor: None, 46 req_id: None, 47 extra_data: BTreeMap::default(), 48 }; 49 Ok(Json(output.clone())) 50} 51 52#[tokio::main] 53async fn main() { 54 tracing_subscriber::fmt() 55 .with_ansi(true) // Colors enabled (default) 56 .with_max_level(tracing::Level::INFO) 57 .init(); 58 59 info!("Setup"); 60 let did_str = "did:web:ott.aleeve.dev"; 61 let did = Did::new_static(did_str); 62 63 let verification_method = VerificationMethod { 64 id: format!("{}#atproto", did_str).into(), 65 r#type: "Multikey".into(), 66 controller: Some("did:web:ott.aleeve.dev".into()), 67 public_key_multibase: Some(generate_key().into()), 68 extra_data: BTreeMap::default(), 69 }; 70 71 let service = Service { 72 id: "#bsky_fg".into(), 73 service_endpoint: Some("https://ott.aleeve.dev".into()), 74 r#type: "BskyFeedGenerator".into(), 75 extra_data: BTreeMap::default(), 76 }; 77 78 let did_doc: DidDocument = DidDocument { 79 id: did.clone().unwrap(), 80 also_known_as: Some(vec!["at://ott.aleeve.dev".into()]), 81 verification_method: Some(vec![verification_method]), 82 service: Some(vec![service]), 83 extra_data: BTreeMap::default(), 84 }; 85 86 let resolver = JacquardResolver::new(reqwest::Client::new(), ResolverOptions::default()); 87 let config: ServiceAuthConfig<JacquardResolver> = 88 ServiceAuthConfig::new(did.clone().unwrap(), resolver); 89 90 let app = Router::new() 91 .merge(GetFeedSkeletonRequest::into_router(handler)) 92 .with_state(config) 93 .merge(did_web_router(did_doc)) 94 .route( 95 "/.well-known/atproto-did", 96 get(handle_wellknown_atproto_did), 97 ) 98 .layer(NormalizePathLayer::trim_trailing_slash()); 99 100 let listener = tokio::net::TcpListener::bind("0.0.0.0:8080").await.unwrap(); 101 info!("Starting service"); 102 axum::serve(listener, app).await.unwrap(); 103}