use std::io; use std::str::FromStr; use axum::{middleware, Router}; use clap::Parser; use sqlx::sqlite::{SqliteConnectOptions, SqlitePool, SqlitePoolOptions}; use tokio::net; use crate::{app::App, channel, clock, events, index, login}; pub type Result = std::result::Result; #[derive(Parser)] pub struct Args { #[arg(short, long, env, default_value = "localhost")] address: String, #[arg(short, long, env, default_value_t = 64209)] port: u16, #[arg(short, long, env, default_value = "sqlite://.hi")] database_url: String, } impl Args { pub async fn run(self) -> Result<()> { let pool = self.pool().await?; sqlx::migrate!().run(&pool).await?; let app = App::from(pool).await?; let app = routers() .route_layer(middleware::from_fn(clock::middleware)) .with_state(app); let listener = self.listener().await?; let started_msg = started_msg(&listener)?; let serve = axum::serve(listener, app); println!("{started_msg}"); serve.await?; Ok(()) } async fn listener(&self) -> io::Result { let listen_addr = self.listen_addr(); let listener = tokio::net::TcpListener::bind(listen_addr).await?; Ok(listener) } fn listen_addr(&self) -> impl net::ToSocketAddrs + '_ { (self.address.as_str(), self.port) } async fn pool(&self) -> sqlx::Result { let options = SqliteConnectOptions::from_str(&self.database_url)? .create_if_missing(true) .optimize_on_close(true, /* analysis_limit */ None); let pool = SqlitePoolOptions::new().connect_with(options).await?; Ok(pool) } } fn routers() -> Router { [channel::router(), events::router(), login::router()] .into_iter() .fold(index::routes::router(), Router::merge) } fn started_msg(listener: &net::TcpListener) -> io::Result { let local_addr = listener.local_addr()?; Ok(format!("listening on http://{local_addr}/")) } #[derive(Debug, thiserror::Error)] #[error(transparent)] pub enum Error { IoError(#[from] io::Error), DatabaseError(#[from] sqlx::Error), MigrateError(#[from] sqlx::migrate::MigrateError), }