1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
|
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();
}
|