Something went wrong. Try again.
forked from oppi.li/at-advent
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310use atrium_api::agent::Agent;use atrium_api::types::string::Did;use atrium_identity::did::{CommonDidResolver, CommonDidResolverConfig, DEFAULT_PLC_DIRECTORY_URL};use atrium_identity::handle::{AtprotoHandleResolver, AtprotoHandleResolverConfig};use atrium_oauth::{ AtprotoLocalhostClientMetadata, AuthorizeOptions, CallbackParams, DefaultHttpClient, KnownScope, OAuthClient, OAuthClientConfig, OAuthResolverConfig, Scope,};use axum::extract::{Query, State};use axum::http::StatusCode;use axum::{Json, Router, extract::Path, middleware, routing::get};use bb8_redis::RedisConnectionManager;use dotenv::dotenv;use redis::AsyncCommands;use shared::atrium::dns_resolver::HickoryDnsTxtResolver;use shared::atrium::stores::{AtriumSessionStore, AtriumStateStore};use shared::cache::CacheConnection;use shared::models::db_models::TestModel;use sqlx::PgPool;use sqlx::postgres::PgPoolOptions;use std::sync::Arc;use std::{ env, net::{IpAddr, Ipv4Addr, SocketAddr}, time,};use time::Duration;use tower_http::trace::TraceLayer;use tower_sessions::{MemoryStore, Session, SessionManagerLayer};use tracing_subscriber::EnvFilter;
extern crate dotenv;
mod extractors;mod unlock;
#[derive(Clone)]struct AppState { postgres_pool: PgPool, redis_pool: bb8::Pool<RedisConnectionManager>, oauth_client: OAuthClientType, //Used to get did to handle leaving cause I figured we'd need it _handle_resolver: HandleResolver,}
/// OAuthClientType to make it easier to access the OAuthClient in web requeststype OAuthClientType = Arc< OAuthClient< AtriumStateStore, AtriumSessionStore, CommonDidResolver<DefaultHttpClient>, AtprotoHandleResolver<HickoryDnsTxtResolver, DefaultHttpClient>, >,>;
/// HandleResolver type to make it easier to access the resolver in web requeststype HandleResolver = Arc<CommonDidResolver<DefaultHttpClient>>;
#[tokio::main]async fn main() -> Result<(), Box<dyn std::error::Error>> { dotenv().ok();
//Sets up logging/tracing tracing_subscriber::fmt() .with_env_filter( EnvFilter::try_from_default_env() .or_else(|_| EnvFilter::try_new("info,axum_tracing_example=error,tower_http=warn")) .unwrap(), ) .init();
let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 7878); let host = addr.ip(); let port = addr.port(); let listener = tokio::net::TcpListener::bind(addr).await.unwrap();
let now = time::Instant::now(); let daily = time::Duration::from_secs(24 * 60 * 60); let state = unlock::Unlock::new(now, daily);
//sqlx pool let database_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set in the environment or .env");
// set up a postgres connection pool let postgres_pool = PgPoolOptions::new() .max_connections(5) .acquire_timeout(Duration::from_secs(3)) .connect(&database_url) .await .expect("can't connect to database");
// redis pool setup let redis_url = env::var("REDIS_URL").expect("REDIS_URL must be set in the environment or .env"); let manager = RedisConnectionManager::new(redis_url.clone()).unwrap(); let redis_pool = bb8::Pool::builder().build(manager).await.unwrap(); //cam be deleted, just an example for the test endpoint { // ping the database before starting let mut conn = redis_pool.get().await.unwrap(); conn.set::<&str, &str, ()>("foo", "bar").await.unwrap(); let result: String = conn.get("foo").await.unwrap(); assert_eq!(result, "bar"); }
//Atrium/atproto setup
//Create a new handle resolver for the home page let http_client = Arc::new(DefaultHttpClient::default());
let handle_resolver = CommonDidResolver::new(CommonDidResolverConfig { plc_directory_url: DEFAULT_PLC_DIRECTORY_URL.to_string(), http_client: http_client.clone(), }); let handle_resolver = Arc::new(handle_resolver);
// Create a new OAuth client let http_client = Arc::new(DefaultHttpClient::default()); let config = OAuthClientConfig { client_metadata: AtprotoLocalhostClientMetadata { redirect_uris: Some(vec![String::from(format!( //This must match the endpoint you use the callback function "http://{host}:{port}/oauth/callback" ))]), scopes: Some(vec![ Scope::Known(KnownScope::Atproto), Scope::Known(KnownScope::TransitionGeneric), ]), }, keys: None, resolver: OAuthResolverConfig { did_resolver: CommonDidResolver::new(CommonDidResolverConfig { plc_directory_url: DEFAULT_PLC_DIRECTORY_URL.to_string(), http_client: http_client.clone(), }), handle_resolver: AtprotoHandleResolver::new(AtprotoHandleResolverConfig { dns_txt_resolver: HickoryDnsTxtResolver::default(), http_client: http_client.clone(), }), authorization_server_metadata: Default::default(), protected_resource_metadata: Default::default(), }, state_store: AtriumStateStore::new(redis_pool.clone()), session_store: AtriumSessionStore::new(redis_pool.clone()), }; let client = Arc::new(OAuthClient::new(config).expect("failed to create OAuth client"));
//tower sessions setup. Using in memory for now, something is off about the redis one will implement our own via the trait using bb8 pool // https://docs.rs/tower-sessions/latest/tower_sessions/trait.SessionStore.html let session_store = MemoryStore::default(); let session_layer = SessionManagerLayer::new(session_store).with_secure(false);
let app_state = AppState { postgres_pool, redis_pool, oauth_client: client, _handle_resolver: handle_resolver, };
log::info!("listening on http://{}", addr); let app = Router::new() .route( "/day/{id}", get(handler).route_layer(middleware::from_fn_with_state( state.clone(), unlock::unlock, )), ) .route("/sql-test", get(sql_test_handler)) .route("/redis-test", get(redis_test_handler)) .route("/login/{handle}", get(login_test_handler)) .route("/oauth/callback", get(oauth_callback_handler)) .route("/logged-in", get(logged_in_test_handler)) .layer(session_layer) .with_state(app_state) .layer(TraceLayer::new_for_http()); axum::serve(listener, app).await?; Ok(())}
async fn handler(Path(id): Path<u32>) -> String { format!("hello day {id}")}
async fn sql_test_handler(State(pool): State<PgPool>) -> Json<Vec<TestModel>> { Json( sqlx::query_as::<_, TestModel>("SELECT id, test FROM test_table") .fetch_all(&pool) .await .unwrap(), )}
/// Pass in your handle like /login/baileytownsend.devasync fn login_test_handler( Path(handle): Path<String>, State(oauth_client): State<OAuthClientType>,) -> String { match atrium_api::types::string::Handle::new(handle) { Ok(handle) => { //Creates the oauth url to redirect to for the user to log in with their credentials let oauth_url = oauth_client .authorize( &handle, AuthorizeOptions { scopes: vec![ Scope::Known(KnownScope::Atproto), Scope::Known(KnownScope::TransitionGeneric), ], ..Default::default() }, ) .await; oauth_url.unwrap_or_else(|err| { log::error!("Error: {err}"); err.to_string() }) } Err(err) => err.to_string(), }}
///End point that takes back the OAuth call back and creates a sessionasync fn oauth_callback_handler( params: Query<CallbackParams>, State(oauth_client): State<OAuthClientType>, session: Session,) -> String { //HACK, yeah I gave up... hoping someone has a better solution let call_back_params = CallbackParams { code: params.code.clone(), state: params.state.clone(), iss: params.iss.clone(), }; match oauth_client.callback(call_back_params).await { Ok((bsky_session, _)) => { let agent = Agent::new(bsky_session); match agent.did().await { Some(did) => { session.insert("did", did.clone()).await.unwrap(); format!("Session created for {}", did.to_string()) // did.to_string() // session.insert("did", did).unwrap(); // Redirect::to("/") // .see_other() // .respond_to(&request) // .map_into_boxed_body() } None => String::from("No DID found"), } } Err(err) => { log::error!("Error: {err}"); err.to_string() } }}
async fn logged_in_test_handler( State(oauth_client): State<OAuthClientType>, session: Session,) -> String { let session_did = session.get::<String>("did").await.unwrap().unwrap(); let did = Did::new(session_did).expect("failed to parse did"); let client = oauth_client.restore(&did).await.unwrap(); let agent = Agent::new(client); let notifications = agent .api .app .bsky .notification .list_notifications( atrium_api::app::bsky::notification::list_notifications::ParametersData { cursor: None, limit: None, priority: None, reasons: None, seen_at: None, } .into(), ) .await .unwrap();
notifications .notifications .iter() .map(|n| { format!( "Author: {} Reason: {}, URI: {}", n.author.handle.as_str(), n.reason, n.uri ) }) .collect::<Vec<String>>() .join("\n")}
async fn redis_test_handler( CacheConnection(mut conn): CacheConnection<'_>,) -> Result<String, (StatusCode, String)> { let result: String = conn .fetch_redis("foo") .await .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; Ok(result)}