1use std::io;
2use std::path::Path;
3use std::str::FromStr;
5
6use http_types::Mime;
7use tide::{Body, Request, Response, Result, StatusCode};
8use urlencoding::decode as url_decode;
9
10use crate::{
11 Files,
12 MimeType,
13 PageQuery,
14 Templates,
15 guess_mime_type,
16 is_markdown,
17 read_text_file_to_string,
18 markdown,
19};
20
21#[derive(Clone)]
22pub struct State {
23 storage_root: String,
24 file_paths: Vec<String>,
25 templates: Templates,
26}
27
28async fn files(req: Request<State>) -> Result<impl Into<Response>> {
29 let state = req.state();
31 let file_paths = &state.file_paths;
32 let templates = &state.templates;
33
34 let html_content = templates.files(file_paths);
35 Ok(html_content)
36}
37
38async fn page(req: Request<State>) -> Result<impl Into<Response>> {
39 let p: PageQuery = req.query()?;
41 if p.raw {
42 raw(req).await
43 } else {
44 preview(req).await
45 }
46}
47
48async fn preview(req: Request<State>) -> Result<Response> {
49 let state = req.state();
50 let storage_root = &state.storage_root;
51 let file_paths = &state.file_paths;
52 let templates = &state.templates;
53
54 let path = req.url().path();
55
56 let path_string = path.to_string();
57
58 let path_string = url_decode(&path_string).unwrap().into_owned();
60
61 if !file_paths.contains(&path_string) {
62 return Ok(Response::new(StatusCode::NotFound));
63 }
64
65 let file_path = Path::new(&storage_root.to_owned()).join(&path_string.strip_prefix('/').unwrap());
66 let content = read_text_file_to_string(&file_path).await?;
67
68 let p: PageQuery = req.query()?;
71 match p.format.as_str() {
72 "markdown" => {
73 let content = markdown(&content);
74 let html_content = templates.page(&path_string, &content).into_string();
75 return Ok(html_response(html_content));
76 },
77 "plain_text" => {
78 let html_content = templates.text(&path_string, &content).into_string();
79 return Ok(html_response(html_content));
80 },
81 _ => {},
82 }
83
84 let mime_type = guess_mime_type(&path_string);
88 match mime_type {
89 MimeType::Text => {
90 let html_content = if is_markdown(&path_string) {
91 let content = markdown(&content);
92 templates.page(&path_string, &content).into_string()
93 } else {
94 templates.text(&path_string, &content).into_string()
95 };
96 Ok(html_response(html_content))
97 },
98 _ => {
99 raw(req).await
100 },
101 }
102}
103
104fn html_response(html_content: String) -> Response {
105 let mime = Mime::from_str("text/html;charset=utf-8").unwrap();
106 Response::builder(200)
107 .body(html_content)
108 .content_type(mime)
109 .build()
110}
111
112async fn raw(req: Request<State>) -> Result<Response> {
113 let state = req.state();
114 let storage_root = &state.storage_root;
115 let file_paths = &state.file_paths;
116
117 let path = req.url().path();
118
119 let path_string = path.to_string();
120
121 let path_string = url_decode(&path_string).unwrap().into_owned();
123
124 if !file_paths.contains(&path_string) {
125 return Ok(Response::new(StatusCode::NotFound));
126 }
127
128 let file_path = Path::new(&storage_root.to_owned()).join(&path_string.strip_prefix('/').unwrap());
129
130 match Body::from_file(&file_path).await {
133 Ok(body) => Ok(Response::builder(StatusCode::Ok).body(body).build()),
134 Err(e) if e.kind() == io::ErrorKind::NotFound => {
135 Ok(Response::new(StatusCode::NotFound))
136 }
137 Err(e) => Err(e.into()),
138 }
139}
140
141pub fn make_app() -> tide::Server<State> {
142 let files_ = Files::load(".");
143 let file_paths = files_.paths;
144
145 let templates = Templates::build(&files_.special_file_paths);
146
147 let state = State {
148 storage_root: ".".to_string(),
149 file_paths: file_paths,
150 templates: templates,
151 };
152
153 let mut app = tide::with_state(state);
154 app.at("/").get(files);
155 app.at("/*").get(page);
156 app
157}