use std::str::FromStr; use anyhow::Result; use axum::routing::{get, post}; use axum::Router; use axum_login::AuthManagerLayerBuilder; use clap::Parser; use diesel_migrations::{embed_migrations, EmbeddedMigrations}; use tower_http::services::ServeDir; use tower_http::trace::{DefaultOnRequest, DefaultOnResponse, TraceLayer}; use tower_sessions::SessionManagerLayer; use tower_sessions_sqlx_store::sqlx::SqlitePool; use tower_sessions_sqlx_store::SqliteStore; use tracing::Level; use crate::config::CommandLineOptions; use crate::db; use crate::handler::documents::{ create_document_page, create_document_submit, documents_page, edit_document_page, edit_document_submit, view_document_page, }; use crate::handler::home::home_page; use crate::handler::login::logout; use crate::handler::projects::{create_project_page, create_project_submit, projects_page}; use crate::handler::{login_page, login_submit}; use crate::logging::setup_logging; use crate::provider::Provider; use crate::templates::make_template_loader; pub const MIGRATIONS: EmbeddedMigrations = embed_migrations!("./migrations/"); pub async fn run() -> Result<()> { dotenvy::dotenv()?; setup_logging(); let opts = CommandLineOptions::parse(); let template_loader = make_template_loader(opts.reload_templates); let db_url = dotenvy::var("DATABASE_URL")?; let mut db_conn = db::establish_connection(&db_url); db::migrate(&mut db_conn); let db_pool = db::build_connection_pool(&db_url); let session_layer = create_session_manager_layer().await?; let provider = Provider::new(db_pool, template_loader); let auth_backend = provider.clone(); let auth_layer = AuthManagerLayerBuilder::new(auth_backend, session_layer.clone()).build(); let trace_layer = TraceLayer::new_for_http() .on_request(DefaultOnRequest::new().level(Level::INFO)) .on_response(DefaultOnResponse::new().level(Level::INFO)); let app = Router::new() .nest_service("/static", ServeDir::new("static")) .route("/", get(home_page)) .route("/login", get(login_page)) .route("/login", post(login_submit)) .route("/logout", get(logout)) .route("/projects", get(projects_page)) .route("/projects/new", get(create_project_page)) .route("/projects/new", post(create_project_submit)) .route("/documents", get(documents_page)) .route("/documents/new", get(create_document_page)) .route("/documents/new", post(create_document_submit)) .route("/documents/view/:id", get(view_document_page)) .route("/documents/edit/:id", get(edit_document_page)) .route("/documents/edit/:id", post(edit_document_submit)) .layer(trace_layer) .layer(session_layer) .layer(auth_layer) .with_state(provider); let listener = tokio::net::TcpListener::bind("0.0.0.0:8080").await.unwrap(); axum::serve(listener, app).await.unwrap(); Ok(()) } pub async fn create_session_manager_layer() -> Result> { let session_db_url = dotenvy::var("SESSION_DB_URL").unwrap_or("sqlite:./sessions.db?mode=rwc".to_owned()); let pool = SqlitePool::connect(&session_db_url).await?; let session_store = SqliteStore::new(pool); session_store.migrate().await?; let use_secure_sessions: bool = FromStr::from_str(&dotenvy::var("SECURE_SESSIONS").unwrap_or("true".to_owned()))?; let session_layer = SessionManagerLayer::new(session_store).with_secure(use_secure_sessions); Ok(session_layer) }