//! "splorm, i wake and breathe" //! //! Genevieve Raine's webserver for her website, . Are you genny? //! One of us? I will write with this assumption. //! //! `splorm` is the main webserver, but it uses several subcrates to complete it's mission. //! //! [`gatherer_client`] manages a Unix Socket that communicates with gatherer over it's API. //! //! [`knowing`] does authentication. It manages it's own database tables and provides an //! API that makes user and session management easier. //! //! [`paige`] is a very basic page template library-thing. all it does is parse frontmatter //! from the page content itself. //! //! [`sessionhash`] has axum extractors that take the client's IP address, user-agent, and //! the current date to form truncated sha256 hash identifying it for the day. //! //! [`somethings`] holds structures common to subcrates of `splorm`, as well a structure //! holding a `rusqlite` database `Connection` //! //! [`timeparse`] does parsing of dates/times using the `time` crate. It also provides some //! formatting methods and a few methods to get the current time, so I don't have to depend //! on the `time` crate in `splorm`, too. //! //! [`wikarden`] is my digital garden/wiki. It manages a git repository of the pages, //! providing an API for all the CRUD operations. Does not do any authentication, that's //! handled within splorm using `knowing`, as mentioned in previous. use std::sync::Arc; use axum::{Extension, Router, middleware::from_fn, routing::get}; use tokio::{net::TcpListener, runtime::Builder}; use wikarden::Wikarden; use crate::{ config::{CommandLine, Config}, connector::GathererConnector, state::{ConnectorsState, SplormState}, }; mod config; pub mod connector; mod extractor; pub mod fs; mod handler; mod query; mod runtime_error; mod state; pub mod templated; #[doc(hidden)] pub use query::{Parameter, Query, QueryParseError}; #[doc(hidden)] pub use runtime_error::{InvalidPathReason, RuntimeError}; /// entrypoint /// /// We all know what `main()` is :) /// - creates and registeres the tracing subscriber /// - gets config: [CommandLine] and [Config] are created here. /// - Creates [GathererThread] and spawns it's thread /// - Creates [SplormState] /// - Starts the tokio multi-threaded runtime and blocks on [`async_main()`] fn main() { config::create_tracing_subscriber(); let cli = CommandLine::get(); let conf = Config::get(cli.conf_path()); tracing::trace!(?cli); tracing::trace!(?conf); let gatherer = GathererConnector::spawn(conf.settings().connectors().gatherer_path()).unwrap(); let state = SplormState::new(ConnectorsState::new(gatherer), conf.settings()); // Create and spawn the tokio multithreaded runtime. Execution stays // here until async_main() returns. let rt = Builder::new_multi_thread().enable_all().build().unwrap(); rt.block_on(async_main(cli, conf, state)) } /// async entrypoint /// /// This is where the application lives for 99% of it's life. /// - Creates wikarden (it's constructor requires async) /// - Makes sure the index for the wikarden exists, creating it if not /// - Sets up all our routes /// - Applies extensiosn and gives the router our state ([SplormState]) /// - Starts axum listening and serving routes async fn async_main(cli: CommandLine, conf: Config, state: SplormState) { state.init_database().await; let wikarden = Wikarden::new( conf.settings().wikarden_root(), wikarden::Settings::default() .commit_name(conf.settings().wikarden_gitname().to_owned()) .commit_email(conf.settings().wikarden_gitemail().to_owned()), ) .await .unwrap(); if cli.refresh_sections() { tracing::warn!("refreshing wikarden's section table. This might take awhile.."); wikarden.refresh_sections().await.unwrap(); tracing::info!("sections refreshed!"); } let wikarden_index = conf.settings().wikarden_index(); if !wikarden.page_exists(wikarden_index).await.unwrap() { wikarden .create( wikarden_index, "index", Some("splorm: automatic index creation"), ) .await .unwrap(); } // Setup the routing let bare_routes = Router::new() .route("/", get(handler::index)) .nest("/.me", handler::me::routes()) .nest("/api/", handler::api::routes()) .route("/quark", get(handler::quark::redirect_index_not_directory)) .nest("/quark/", handler::quark::routes()) .route("/{*path}", get(handler::static_path)); // Apply Extensions and state let arc_wikarden = Arc::new(wikarden); let routes = bare_routes .layer(from_fn(handler::log_errors)) .layer(Extension(conf.settings().clone())) .layer(Extension(arc_wikarden)) .with_state(state); // Create our listener and start axum serving and routing let addr = conf.listen_address(); tracing::info!("listening on {addr}"); let listener = TcpListener::bind(addr).await.unwrap(); axum::serve(listener, routes).await.unwrap(); }