use tower_http::services::ServeDir; use axum::response::Html; use axum::extract::State; use std::sync::Arc; use askama_axum::Template; use axum::{routing::get, Router}; use std::borrow::Cow; use std::ffi::OsString; use std::fmt; use std::fs; use std::io::*; use std::path::Path; use std::path::PathBuf; trait Post: fmt::Debug { fn dump(&self); fn display_name(&self) -> Cow; fn get_content(&self) -> Cow; } struct MdFilePost { path: PathBuf, name: OsString, } impl MdFilePost { fn new(path: &Path) -> Self { Self { path: path.to_path_buf(), name: path.file_stem().unwrap_or_default().to_owned(), } } } impl Post for MdFilePost { fn dump(&self) { println!("{}: {}", &self.display_name(), &self.get_content()); } fn display_name(&self) -> Cow { self.name.to_string_lossy() } fn get_content(&self) -> Cow { let mut file = std::fs::File::open(&self.path).unwrap(); let mut markdown = String::new(); file.read_to_string(&mut markdown).unwrap(); Cow::Owned(markdown) } } impl fmt::Debug for MdFilePost { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "MdFilePost '{}'", self.display_name()) } } trait Posts { fn get_posts(&self) -> impl Iterator; } struct FsDirPosts { path: PathBuf, } impl FsDirPosts { fn new(path: &str) -> Self { Self { path: PathBuf::from(path), } } } impl Posts for FsDirPosts { fn get_posts(&self) -> impl Iterator { let dirs = fs::read_dir(&self.path).unwrap(); dirs.flatten().map(|d| MdFilePost::new(&d.path())) } } trait Page: Template { fn from_post(post: &impl Post) -> Self; } #[derive(Template)] #[template(path = "post.html")] struct MdPage { article: String, } impl Page for MdPage { fn from_post(post: &impl Post) -> Self { Self { article: post.get_content().into_owned(), } } } #[derive(Template)] #[template(path = "posts.html")] struct PostsView { post_titles: Vec } impl PostsView { fn with_posts(posts: &impl Posts) -> Self { Self { post_titles: posts.get_posts().map(|p| p.display_name().into_owned()).map(String::from).collect() } } } async fn posts_handler(State(posts): State>) -> Html { let view = PostsView::with_posts(&*posts); Html(view.render().unwrap()) } #[derive(Template)] #[template(path = "index.html")] struct IndexTemplate; async fn index_handler() -> Html { Html(IndexTemplate {}.render().unwrap()) } #[derive(Template)] #[template(path = "policies.html")] struct PoliciesTemplate; async fn policies_handler() -> Html { Html(PoliciesTemplate{}.render().unwrap()) } #[tokio::main] async fn main() { let repo = Arc::new(FsDirPosts::new(&format!("/data/ct/{}", "blog"))); let app = Router::new() .nest_service("/static", ServeDir::new("static")) .nest_service("/assets", ServeDir::new("/data/ct/assets")) .route("/posts", get(posts_handler)) .route("/policies", get(policies_handler)) .route("/", get(index_handler)) .with_state(repo); let listener = tokio::net::TcpListener::bind("0.0.0.0:8000").await.unwrap(); axum::serve(listener, app).await.unwrap(); }