Something went wrong. Try again.
atproto Thingiverse but good
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231use dioxus::prelude::ServerFnError;use dioxus::prelude::*;use jacquard_common::xrpc::XrpcClient;use polymodel_api::space_polymodel::actor::bootstrap_profile::BootstrapProfile;use polymodel_api::space_polymodel::actor::get_session::{GetSession, GetSessionOutput};
mod about;mod auth;mod author_byline;mod browse;mod client;mod examples;mod fonts;mod foundation;mod profile;mod publish;mod search;mod session;mod shell;mod thing_card;mod thing_detail;mod thing_routes;mod viewer;mod viewer_route;
// Server-only indexing infrastructure (Hydrant firehose + SQLite projection).#[cfg(any(feature = "server", test))]mod indexing;// Server-only LDraw resource identity, resolver, and verification contract.#[cfg(any(feature = "server", test))]mod ldraw;// Server-only XRPC read endpoints (appview query layer).#[cfg(any(feature = "server", test))]mod appview;#[cfg(any(feature = "server", test))]mod oauth;
use about::About;use browse::Browse;use client::PolymodelClient;use foundation::Foundation;use profile::{Profile, ProfileSelf};use publish::Publish;use search::Search;use session::SessionIdentity;use shell::{AppShell, ToastShelf};use thing_detail::ThingDetail;use viewer_route::Viewer;
const FAVICON: Asset = asset!("/assets/favicon.jpg");const THEME_CSS: Asset = asset!("/assets/styling/theme.css");const BASE_CSS: Asset = asset!("/assets/styling/base.css");const PRIMITIVES_CSS: Asset = asset!("/assets/styling/primitives.css");const SHELL_CSS: Asset = asset!("/assets/styling/shell.css");const HOME_CSS: Asset = asset!("/assets/styling/home.css");const THING_DETAIL_CSS: Asset = asset!("/assets/styling/thing-detail.css");const PROFILE_CSS: Asset = asset!("/assets/styling/profile.css");const VIEWER_CSS: Asset = asset!("/assets/styling/viewer.css");const PUBLISH_CSS: Asset = asset!("/assets/styling/publish.css");const PLACEHOLDERS_CSS: Asset = asset!("/assets/styling/placeholders.css");const ABOUT_CSS: Asset = asset!("/assets/styling/about.css");
#[cfg(feature = "server")]fn main() { // Initialise tracing and env before dioxus::serve so our filter takes // precedence over any subscriber the Dioxus runtime installs. let _ = dotenvy::dotenv(); let _ = tracing_subscriber::fmt() .with_env_filter( tracing_subscriber::EnvFilter::try_from_default_env().unwrap_or_else(|_| { tracing_subscriber::EnvFilter::new( "debug,hydrant=warn,h2=warn,hyper_util=warn,rustls=warn", ) }), ) .try_init(); // The closure runs inside the server's Tokio runtime at startup, so the // indexing tasks spawned here live for the process lifetime alongside Axum. dioxus::serve(|| async { let _ = rustls::crypto::aws_lc_rs::default_provider().install_default(); // Hydrant selects AWS-LC while other deps enable ring; choose before websocket TLS setup. let cfg = indexing::config::ServerConfig::load(); let db = indexing::db::init_db(&cfg) .await .expect("failed to initialize sqlite projection database"); if cfg.sample_data.should_seed(&cfg.database_url) { indexing::sample_data::seed_if_empty(&db) .await .expect("failed to seed local/demo sample data"); } // Indexing is a soft dependency; serve read-only from the projection if // the firehose can't start (e.g. the hydrant DB is locked by another dev server). if let Err(e) = indexing::start_indexing(db.clone()).await { tracing::error!("indexing pipeline disabled, serving from existing projection: {e:#}"); } let oauth = oauth::bootstrap_oauth(db.clone(), cfg.base_url.as_deref()) .expect("failed to bootstrap OAuth client"); // The same SQLite pool backs both indexing and the appview read handlers. let app_state = appview::state::AppState::new(db, oauth); Ok(dioxus::server::router(App) .layer(axum::Extension(app_state.clone())) .merge( appview::router() .merge(jacquard_axum::oauth::routes::< jacquard::identity::PublicResolver, oauth::SqliteAuthStore, appview::state::AppState, >(&app_state.oauth_config.clone())) .with_state(app_state), )) });}
#[cfg(not(feature = "server"))]fn main() { #[cfg(all(target_family = "wasm", target_os = "unknown"))] { console_error_panic_hook::set_once(); let mut wasm_log_config = tracing_wasm::WASMLayerConfigBuilder::new(); wasm_log_config.set_max_level(tracing::Level::INFO); tracing_wasm::set_as_global_default_with_config(wasm_log_config.build()); }
dioxus::launch(App);}
#[derive(Clone, Debug, PartialEq, Routable)]pub(crate) enum Route { #[layout(AppShell)] #[route("/")] Browse {}, #[route("/foundation")] Foundation {}, #[route("/about")] About {}, #[route("/search?:q")] Search { q: String }, #[route("/publish?:draft")] Publish { draft: String }, #[route("/profile")] ProfileSelf {}, #[route("/profile/:actor")] Profile { actor: String }, #[route("/viewer/:model_id")] Viewer { model_id: String }, #[route("/:repo/thing/:rkey")] ThingDetail { repo: String, rkey: String },}
#[component]fn App() -> Element { let client = use_context_provider(PolymodelClient::default); let mut session = use_context_provider(|| Signal::new(SessionIdentity::Unknown)); let toasts = use_context_provider(|| Signal::new(auth::ToastState::default()));
let ssr_session_seed = use_server_future(server_session_seed)?; if let Some(Ok(output)) = ssr_session_seed() { session.set(SessionIdentity::from_get_session(output)); }
let _session_seed = use_resource(move || { let client = client.clone(); let mut session = session; async move { #[cfg(feature = "server")] if dioxus::prelude::dioxus_fullstack::FullstackContext::current().is_some() { return; }
match session.read().clone() { SessionIdentity::Authenticated(_) => { if let Err(error) = client.send(BootstrapProfile).await { tracing::warn!(%error, "failed to bootstrap Polymodel profile"); } return; } SessionIdentity::Anonymous => return, SessionIdentity::Unknown => {} }
match client.send(GetSession).await { Ok(response) => match response.into_output() { Ok(output) => { let identity = SessionIdentity::from_get_session(output); let authenticated = matches!(identity, SessionIdentity::Authenticated(_)); session.set(identity); // First-auth profile bootstrap: create a polymodel profile from // Bluesky basics if the signed-in actor has none yet. Idempotent // server-side (returns the existing profile when present). if authenticated && let Err(error) = client.send(BootstrapProfile).await { tracing::warn!(%error, "failed to bootstrap Polymodel profile"); } } Err(error) => { tracing::warn!(%error, "failed to decode Polymodel session"); session.set(SessionIdentity::Anonymous); } }, Err(error) => { tracing::warn!(%error, "failed to fetch Polymodel session"); session.set(SessionIdentity::Anonymous); } } } });
rsx! { document::Meta { name: "viewport", content: "width=device-width, initial-scale=1" } document::Title { "Polymodel" } document::Link { rel: "icon", href: FAVICON } Stylesheet { href: THEME_CSS } Stylesheet { href: BASE_CSS } Stylesheet { href: PRIMITIVES_CSS } Stylesheet { href: SHELL_CSS } Stylesheet { href: HOME_CSS } Stylesheet { href: THING_DETAIL_CSS } Stylesheet { href: PROFILE_CSS } Stylesheet { href: VIEWER_CSS } Stylesheet { href: PUBLISH_CSS } Stylesheet { href: PLACEHOLDERS_CSS } Stylesheet { href: ABOUT_CSS }
Router::<Route> {} ToastShelf { toasts } }}
#[server]async fn server_session_seed() -> Result<GetSessionOutput, ServerFnError> { appview::ssr::get_session_output_from_fullstack_context().await}