use std::net::{IpAddr, SocketAddr, TcpListener}; use std::time::Duration; use axum::routing::get; use axum::{Extension, Router}; use moka::future::Cache; use tokio::task::JoinHandle; use tower_cookies::CookieManagerLayer; use tracing::{error, info}; use crate::config::Config; use crate::database::Database; use crate::Result; mod api; mod extract; mod file; mod http; mod pages; pub struct WebServer { config: Config, database: Database, addr: SocketAddr, } impl WebServer { pub async fn new(config: Config, database: Database) -> Result { let addr = format!("[::]:{}", config.web_port).parse::()?; Ok(Self { config, database, addr, }) } pub async fn run(&self) -> Result> { let config = self.config.clone(); let database = self.database.clone(); let listener = TcpListener::bind(self.addr)?; info!( "Listening for HTTP traffic on [::]:{}", self.config.web_port ); let app = Self::router(config, database); let server = axum::Server::from_tcp(listener)?; let web_handle = tokio::spawn(async move { if let Err(err) = server .serve(app.into_make_service_with_connect_info::()) .await { error!("{err}"); } }); Ok(web_handle) } fn router(config: Config, database: Database) -> Router { let cache: Cache = Cache::builder() .time_to_live(Duration::from_secs(60 * 15)) .max_capacity(config.dns_cache_size) .build(); Router::new() .nest("/", pages::router()) .nest("/api", api::router()) .layer(Extension(config)) .layer(Extension(cache)) .layer(Extension(database)) .layer(CookieManagerLayer::new()) .route("/js/*path", get(file::js)) .route("/css/*path", get(file::css)) .route("/fonts/*path", get(file::fonts)) .route("/image/*path", get(file::image)) .route("/favicon.ico", get(file::favicon)) .route("/robots.txt", get(file::robots)) } }