You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

main.rs 6.3KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  1. use std::env;
  2. use std::ffi::OsStr;
  3. use std::fs::{read_dir, File};
  4. use std::io::prelude::*;
  5. use std::io::{Error, ErrorKind};
  6. use std::path::PathBuf;
  7. use async_std::fs::read_to_string;
  8. use chrono::prelude::Local;
  9. use dotenv;
  10. use serde::{Deserialize, Serialize};
  11. use serde_json;
  12. use tera::{Context, Tera};
  13. use tide::utils::After;
  14. use tide::{Body, Response, StatusCode};
  15. use uuid::Uuid;
  16. #[derive(Debug, Serialize, Deserialize)]
  17. struct Post {
  18. id: String,
  19. title: String,
  20. body: String,
  21. date: String,
  22. }
  23. #[derive(Debug, Serialize, Deserialize)]
  24. struct User {
  25. username: String,
  26. password: String,
  27. }
  28. impl Post {
  29. fn save(&mut self) -> std::io::Result<()> {
  30. let mut path: PathBuf = get_posts_directory()?;
  31. let filename = format!("{}.json", self.id);
  32. path = path.join(&filename);
  33. let mut file = File::create(&path)?;
  34. file.write_all(serde_json::to_string(&self)?.as_bytes())?;
  35. Ok(())
  36. }
  37. }
  38. fn get_posts_directory() -> Result<PathBuf, Error> {
  39. match env::var("POSTS_DIR") {
  40. Ok(dir) => Ok(dir.into()),
  41. Err(_) => Err(Error::new(
  42. ErrorKind::Other,
  43. "Posts directory environment variable not set",
  44. )),
  45. }
  46. }
  47. async fn read_all_posts() -> Result<Vec<Post>, Error> {
  48. let path = match get_posts_directory() {
  49. Ok(p) => Ok(p),
  50. Err(_) => Err(Error::new(
  51. ErrorKind::Other,
  52. "POSTS_DIR variable is not set",
  53. )),
  54. }?;
  55. let mut posts: Vec<Post> = vec![];
  56. let files = match read_dir(path) {
  57. Ok(f) => Ok(f),
  58. Err(_) => Err(Error::new(
  59. ErrorKind::Other,
  60. "Posts directory does not exist",
  61. )),
  62. }?;
  63. for file in files {
  64. let file = file?;
  65. if let Some("json") = file.path().extension().and_then(OsStr::to_str) {
  66. let contents = match read_to_string(file.path()).await {
  67. Ok(c) => Ok(c),
  68. Err(_) => Err(Error::new(
  69. ErrorKind::Other,
  70. format!("Error reading post {:?}", file.path()),
  71. )),
  72. }?;
  73. let mut post: Post = match serde_json::from_str(&contents) {
  74. Ok(p) => Ok(p),
  75. Err(_) => Err(Error::new(
  76. ErrorKind::Other,
  77. format!("Error deserializing post"),
  78. )),
  79. }?;
  80. post.body = post.body.replace("\n", "<br>");
  81. posts.push(post);
  82. }
  83. }
  84. Ok(posts)
  85. }
  86. fn render(template: &str, context: &Context) -> Result<Body, tide::Error> {
  87. let tera = Tera::new("templates/**/*.html")?;
  88. let html = tera.render(template, &context)?;
  89. Ok(Body::from_string(html))
  90. }
  91. #[async_std::main]
  92. async fn main() -> std::io::Result<()> {
  93. dotenv::dotenv().ok();
  94. tide::log::start();
  95. let mut app = tide::new();
  96. app.with(After(|mut res: Response| async {
  97. if let Some(err) = res.downcast_error::<async_std::io::Error>() {
  98. let mut context = Context::new();
  99. context.insert("error", &err.to_string());
  100. res.set_body(render("error.html", &context)?);
  101. res.set_status(StatusCode::InternalServerError);
  102. }
  103. Ok(res)
  104. }));
  105. app.with(After(|mut res: Response| async {
  106. res.set_content_type(tide::http::mime::HTML);
  107. Ok(res)
  108. }));
  109. app.with(tide::sessions::SessionMiddleware::new(
  110. tide::sessions::MemoryStore::new(),
  111. std::env::var("TIDE_SECRET")
  112. .expect(
  113. "Please provide a TIDE_SECRET value of at \
  114. least 32 bytes in order to run this example",
  115. )
  116. .as_bytes(),
  117. ));
  118. app.at("/").get(|req: tide::Request<()>| async move {
  119. let posts = read_all_posts().await?;
  120. let mut context = Context::new();
  121. context.insert("posts", &posts);
  122. let logged_in: bool = req.session().get("logged_in").unwrap_or(false);
  123. context.insert("logged_in", &logged_in);
  124. render("index.html", &context)
  125. });
  126. app.at("/posts")
  127. .post(|mut req: tide::Request<()>| async move {
  128. let logged_in: bool = req.session().get("logged_in").unwrap_or(false);
  129. if !logged_in {
  130. Ok(tide::Redirect::new("/login"))
  131. } else {
  132. let mut post: Post = req.body_form().await?;
  133. post.id = Uuid::new_v4().to_string();
  134. post.date = Local::now().date().naive_local().to_string();
  135. post.body = post.body.trim().to_owned();
  136. post.save()?;
  137. Ok(tide::Redirect::new("/"))
  138. }
  139. });
  140. app.at("/login")
  141. .get(|mut req: tide::Request<()>| async move {
  142. let mut context = Context::new();
  143. let logged_in: bool = req.session().get("logged_in").unwrap_or(false);
  144. context.insert("logged_in", &logged_in);
  145. match req.session_mut().get::<String>("flash_error") {
  146. Some(error) => {
  147. req.session_mut().remove("flash_error");
  148. &context.insert("error", &error);
  149. }
  150. None => {}
  151. }
  152. render("login.html", &context)
  153. })
  154. .post(|mut req: tide::Request<()>| async move {
  155. let logged_in: bool = req.session().get("logged_in").unwrap_or(false);
  156. if logged_in {
  157. return Ok(tide::Redirect::new("/"));
  158. }
  159. let username = env::var("ADMIN_USERNAME")?;
  160. let password = env::var("ADMIN_PASSWORD")?;
  161. let user: User = req.body_form().await?;
  162. if user.username == username && user.password == password {
  163. req.session_mut().remove("logged_in");
  164. req.session_mut().insert("logged_in", true)?;
  165. Ok(tide::Redirect::new("/"))
  166. } else {
  167. req.session_mut().remove("logged_in");
  168. req.session_mut()
  169. .insert("flash_error", "Invalid credentials")?;
  170. Ok(tide::Redirect::new("/login"))
  171. }
  172. });
  173. app.at("/logout")
  174. .get(|mut req: tide::Request<()>| async move {
  175. req.session_mut().remove("logged_in");
  176. req.session_mut().insert("logged_in", false)?;
  177. Ok(tide::Redirect::new("/"))
  178. });
  179. app.listen("127.0.0.1:8080").await?;
  180. Ok(())
  181. }