use std::{collections::HashMap, sync::Arc}; use axum::{ extract::{Query, State}, http::StatusCode, response::{IntoResponse, Redirect, Response}, routing::get, Router, }; use data::Data; use maud::{html, Markup}; use nanoid::nanoid; use sqlx::{sqlite::SqlitePoolOptions, Pool, Sqlite}; use tower_http::{services::ServeDir, trace::TraceLayer}; use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; pub mod clock; pub mod data; pub mod db; pub mod forecast; pub mod templates; pub struct AppState { pub data: Data, pub pool: Pool, } pub struct AppError(anyhow::Error); impl IntoResponse for AppError { fn into_response(self) -> axum::response::Response { ( StatusCode::INTERNAL_SERVER_ERROR, format!("Something went wrong: {}", self.0), ) .into_response() } } impl From for AppError where E: Into, { fn from(err: E) -> Self { Self(err.into()) } } async fn root_handler() -> Result { Ok(templates::root()) } async fn new_account_handler(state: State>) -> Result { let id = nanoid!(10); db::insert_account(&id, &state.pool).await?; Ok(templates::new_account(id)) } async fn main_handler(state: State>) -> Result { Ok(templates::main_page(state)) } async fn to_handler( state: State>, query: Query>, ) -> Result { let rejection_html = html! { p { "The provided account ID doesn't exist. " } }; if let Some(id) = query.get("id") { let exists = db::get_account(id, &state.pool).await?; if exists { Ok(Redirect::to(&format!("/{}", id)).into_response()) } else { Ok(rejection_html.into_response()) } } else { Ok(rejection_html.into_response()) } } #[tokio::main] async fn main() { tracing_subscriber::registry() .with( tracing_subscriber::EnvFilter::try_from_default_env().unwrap_or_else(|_| { format!( "{}=debug,tower_http=debug,axum::rejection=trace", env!("CARGO_CRATE_NAME") ) .into() }), ) .with(tracing_subscriber::fmt::layer()) .init(); let pool = SqlitePoolOptions::new() .max_connections(10) .connect("sqlite://beacon.db") .await .unwrap(); sqlx::migrate!("./migrations").run(&pool).await.unwrap(); let app = Router::new() .route("/", get(root_handler).post(new_account_handler)) .route("/to", get(to_handler)) .route("/{id}", get(main_handler)) .layer(TraceLayer::new_for_http()) .nest_service("/static", ServeDir::new("static")) .with_state(Arc::new(AppState { data: Data::new(), pool, })); let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap(); println!("Listening on http://localhost:3000!"); axum::serve(listener, app).await.unwrap(); }