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<str>;
fn get_content(&self) -> Cow<str>;
}
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<str> {
self.name.to_string_lossy()
}
fn get_content(&self) -> Cow<str> {
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<Item = impl Post>;
}
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<Item = impl Post> {
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<String>
}
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<Arc<impl Posts>>) -> Html<String> {
let view = PostsView::with_posts(&*posts);
Html(view.render().unwrap())
}
#[derive(Template)]
#[template(path = "index.html")]
struct IndexTemplate;
async fn index_handler() -> Html<String> {
Html(IndexTemplate {}.render().unwrap())
}
#[derive(Template)]
#[template(path = "policies.html")]
struct PoliciesTemplate;
async fn policies_handler() -> Html<String> {
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();
}