mod db; mod handlers; mod models; use askama::Template; use axum::{ Router, extract::State, response::{Html, IntoResponse, Response}, routing::{get, post}, }; use sqlx::SqlitePool; use tower_http::services::ServeDir; use tower_sessions::{SessionManagerLayer, cookie::SameSite}; use tower_sessions_sqlx_store::SqliteStore; use handlers::auth::{ get_current_user, login_page, login_submit, logout, register_page, register_submit, }; use handlers::thread::{ create_post, create_thread, list_threads, new_thread_page, view_profile, view_thread, }; pub struct CurrentUser { pub username: String, } #[derive(Template)] #[template(path = "index.html")] struct IndexTemplate { title: String, current_user: Option, } async fn index(State(pool): State, session: tower_sessions::Session) -> Response { let user: Option = get_current_user(&session, &pool).await; let current_user = user.map(|u| CurrentUser { username: u.username, }); let tmpl = IndexTemplate { title: "home".into(), current_user, }; match tmpl.render() { Ok(html) => Html(html).into_response(), Err(_) => ( axum::http::StatusCode::INTERNAL_SERVER_ERROR, "render error", ) .into_response(), } } #[tokio::main] async fn main() { dotenvy::dotenv().ok(); let database_url = std::env::var("SARMENTINE_DATABASE_URL") .unwrap_or_else(|_| "sqlite:./sarmentine.db".into()); let pool: SqlitePool = db::connect(&database_url).await; let session_store = SqliteStore::new(pool.clone()); session_store .migrate() .await .expect("session store migration failed"); let session_secret = std::env::var("SARMENTINE_SESSION_SECRET") .unwrap_or_else(|_| "change-me-in-production-use-a-long-random-string!!".into()); let session_layer = SessionManagerLayer::new(session_store) .with_secure(false) .with_same_site(SameSite::Lax) .with_signed(tower_sessions::cookie::Key::from(session_secret.as_bytes())); let static_dir = std::env::var("SARMENTINE_STATIC_DIR").unwrap_or_else(|_| "static".into()); let bind_addr = std::env::var("SARMENTINE_BIND_ADDR").unwrap_or_else(|_| "127.0.0.1:3000".into()); let app = Router::new() .route("/", get(index)) .route("/threads", get(list_threads)) .route("/threads/new", get(new_thread_page)) .route("/threads", post(create_thread)) .route("/threads/:id", get(view_thread)) .route("/threads/:id/posts", post(create_post)) .route("/profile/:username", get(view_profile)) .route("/auth/register", get(register_page)) .route("/auth/register", post(register_submit)) .route("/auth/login", get(login_page)) .route("/auth/login", post(login_submit)) .route("/auth/logout", post(logout)) .nest_service("/static", ServeDir::new(&static_dir)) .layer(session_layer) .with_state(pool); let listener = tokio::net::TcpListener::bind(&bind_addr).await.unwrap(); println!("listening on http://{}", bind_addr); axum::serve(listener, app).await.unwrap(); }