use std::env; use std::ffi::OsStr; use std::fs::{read_dir, File}; use std::io::prelude::*; use std::io::{Error, ErrorKind}; use std::path::PathBuf; use async_std::fs::read_to_string; use chrono::prelude::Local; use dotenv; use serde::{Deserialize, Serialize}; use serde_json; use tera::{Context, Tera}; use tide::utils::After; use tide::{Body, Response, StatusCode}; use uuid::Uuid; #[derive(Debug, Serialize, Deserialize)] struct Post { id: String, title: String, body: String, date: String, } impl Post { fn save(&mut self) -> std::io::Result<()> { let mut path: PathBuf = get_posts_directory()?; let filename = format!("{}.json", self.id); path = path.join(&filename); let mut file = File::create(&path)?; file.write_all(serde_json::to_string(&self)?.as_bytes())?; Ok(()) } } fn get_posts_directory() -> Result { match env::var("POSTS_DIR") { Ok(dir) => Ok(dir.into()), Err(_) => Err(Error::new( ErrorKind::Other, "Posts directory environment variable not set", )), } } async fn read_all_posts() -> Result, Error> { let path = match get_posts_directory() { Ok(p) => Ok(p), Err(_) => Err(Error::new(ErrorKind::Other, "POSTS_DIR variable is not set")) }?; let mut posts: Vec = vec![]; let files = match read_dir(path) { Ok(f) => Ok(f), Err(_) => Err(Error::new(ErrorKind::Other, "Posts directory does not exist")) }?; for file in files { let file = file?; if let Some("json") = file.path().extension().and_then(OsStr::to_str) { let contents = match read_to_string(file.path()).await { Ok(c) => Ok(c), Err(_) => Err(Error::new(ErrorKind::Other, format!("Error reading post {:?}", file.path()))) }?; let mut post: Post = match serde_json::from_str(&contents) { Ok(p) => Ok(p), Err(_) => Err(Error::new(ErrorKind::Other, format!("Error deserializing post"))) }?; post.body = post.body.replace("\n", "
"); posts.push(post); } } Ok(posts) } #[async_std::main] async fn main() -> std::io::Result<()> { dotenv::dotenv().ok(); tide::log::start(); let mut app = tide::new(); app.with(After(|mut res: Response| async { if let Some(err) = res.downcast_error::() { let tera = Tera::new("templates/**/*.html")?; let mut context = Context::new(); context.insert("error", &err.to_string()); let html = tera.render("error.html", &context)?; res.set_body(html); res.set_status(StatusCode::InternalServerError); } Ok(res) })); app.with(After(|mut res: Response| async { res.set_content_type(tide::http::mime::HTML); Ok(res) })); app.at("/").get(|_| async { let tera = Tera::new("templates/**/*.html")?; let posts = read_all_posts().await?; let mut context = Context::new(); context.insert("posts", &posts); let html = tera.render("index.html", &context)?; Ok(Body::from_string(html)) }); app.at("/posts") .post(|mut req: tide::Request<()>| async move { let mut post: Post = req.body_form().await?; post.id = Uuid::new_v4().to_string(); post.date = Local::now().date().naive_local().to_string(); post.body = post.body.trim().to_owned(); post.save()?; Ok(tide::Redirect::new("/")) }); app.listen("127.0.0.1:8080").await?; Ok(()) }