//! Shared appview handler state. //! //! Read endpoints are backed entirely by the SQLite projection (records //! live in `record_json`, handles in `identities`, profiles in `profiles`). The //! read path therefore needs no Hydrant handle. `resolver` canonicalizes handle //! authorities to DIDs for `getProfile`; `bsky` fetches the //! `app.bsky.actor.defs#profileViewDetailed` union variant from the public //! AppView for actors with no polymodel profile. //! //! It also carries the confidential OAuth client, single-origin web config, and //! private cookie-jar key. `AppState` is the Axum router state type (local, so //! the [`FromRef`] impls for [`OAuthWebConfig`] and the cookie [`Key`] satisfy //! the orphan rule); it is cheaply `Clone` (pool/resolver/oauth are //! `Arc`-backed). use std::sync::Arc; use axum::extract::FromRef; use axum_extra::extract::cookie::Key; use jacquard::client::BasicClient; use jacquard::identity::PublicResolver; use jacquard::oauth::client::OAuthClient; use jacquard_axum::oauth::{OAuthWebConfig, OAuthWebState}; use sqlx::SqlitePool; use tokio::sync::Mutex; use crate::oauth::{OAuthBootstrap, SqliteAuthStore}; /// State shared by all XRPC read handlers and the OAuth routes. #[derive(Clone)] pub struct AppState { /// SQLite projection: content, social counts, profiles, identities. pub pool: SqlitePool, /// Handle→DID resolution for `getProfile` actor canonicalization. pub resolver: PublicResolver, /// Unauthenticated client aimed at the public Bluesky AppView /// (`https://public.api.bsky.app`) for the bluesky-only profile variant. /// `Arc`-wrapped because `BasicClient` (`Agent`) is not `Clone`. pub bsky: Arc, /// Confidential OAuth client over the SQLite auth store. pub oauth: Arc>, /// Single-origin OAuth web config (cookie name + route paths). pub oauth_config: OAuthWebConfig, /// Signing key for the private `jacquard_oauth_session` cookie jar. pub cookie_key: Key, /// Serializes server-mediated writes that make decisions from local /// projection state before issuing PDS mutations. This avoids app-process /// TOCTOU races such as deleting a child record while another PM-28 write is /// attaching it elsewhere. pub write_lock: Arc>, } impl AppState { /// Construct read + OAuth state from the shared SQLite pool and the /// startup-built OAuth bootstrap (keyset, config, cookie key). pub fn new(pool: SqlitePool, bootstrap: OAuthBootstrap) -> Self { Self { pool, resolver: jacquard::identity::slingshot_resolver_default(), bsky: Arc::new(jacquard::client::BasicClient::unauthenticated()), oauth: Arc::new(bootstrap.client), oauth_config: bootstrap.config, cookie_key: bootstrap.cookie_key, write_lock: Arc::new(Mutex::new(())), } } } /// Expose the OAuth client to `jacquard_axum::oauth` routes/extractors. impl OAuthWebState for AppState { fn oauth_client(&self) -> &OAuthClient { self.oauth.as_ref() } } impl FromRef for OAuthWebConfig { fn from_ref(state: &AppState) -> Self { state.oauth_config.clone() } } impl FromRef for Key { fn from_ref(state: &AppState) -> Self { state.cookie_key.clone() } }