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.

routes.rs 6.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  1. use chrono::prelude::Local;
  2. use rust_embed::RustEmbed;
  3. use serde::{Deserialize, Serialize};
  4. use tera::Context;
  5. use tide::{convert::json, http::Mime};
  6. use tide::{http::mime, Body};
  7. use tide::{Redirect, Request, Response, Result, StatusCode};
  8. use std::env;
  9. use crate::{fs, hash_password, post::Post, templates::render_template, util, State};
  10. #[derive(Debug, Serialize, Deserialize)]
  11. struct User {
  12. username: String,
  13. password: String,
  14. }
  15. pub async fn index(req: Request<State>) -> Result {
  16. let posts: Vec<Post> = fs::get_all_posts().await?;
  17. let mut context = Context::new();
  18. context.insert("posts", &posts);
  19. render_html_response("index.html", &context, req)
  20. }
  21. pub async fn single_post(req: Request<State>) -> Result {
  22. let mut context = Context::new();
  23. let slug = req.param("slug")?;
  24. let post = fs::get_one_post(slug, is_logged_in(&req)).await?;
  25. context.insert("post", &post);
  26. render_html_response("single.html", &context, req)
  27. }
  28. pub async fn new_post(req: Request<State>) -> Result {
  29. let context = Context::new();
  30. render_html_response("new.html", &context, req)
  31. }
  32. pub async fn edit_post(req: Request<State>) -> Result {
  33. let mut context = Context::new();
  34. let slug = req.param("slug")?;
  35. let mut post = fs::get_one_post(slug, is_logged_in(&req)).await?;
  36. post.body = post.body.replace("<br>", "\n");
  37. post.html = post.generate_html();
  38. context.insert("post", &post);
  39. render_html_response("edit.html", &context, req)
  40. }
  41. pub async fn create_post(mut req: Request<State>) -> Result {
  42. let mut post: Post = req.body_form().await?;
  43. post.date = Local::now().date().naive_local().to_string();
  44. post.body = post.body.trim().to_owned();
  45. post.html = post.generate_html();
  46. post.save().await?;
  47. Ok(Redirect::new("/").into())
  48. }
  49. pub async fn update_post(mut req: Request<State>) -> Result {
  50. let mut post: Post = req.body_form().await?;
  51. post.html = post.generate_html();
  52. post.save().await?;
  53. req.session_mut()
  54. .insert("flash_success", String::from("Post updated successfully"))?;
  55. redirect(&format!("/posts/{}", post.slug))
  56. }
  57. pub async fn delete_post(mut req: Request<State>) -> Result {
  58. let slug: String = req.param("slug")?;
  59. fs::delete_post(slug)?;
  60. req.session_mut()
  61. .insert("flash_success", String::from("Post deleted successfully"))?;
  62. render_json_response(json!({ "success": "true" }))
  63. }
  64. pub async fn preview_post(mut req: Request<State>) -> Result {
  65. let body: String = req.body_string().await?;
  66. let html = util::generate_html(&body);
  67. render_json_response(json!({ "body": html }))
  68. }
  69. pub async fn login_page(req: Request<State>) -> Result {
  70. render_html_response("login.html", &Context::new(), req)
  71. }
  72. pub async fn login(mut req: Request<State>) -> Result {
  73. let username = env::var("ADMIN_USERNAME")?;
  74. let password = env::var("ADMIN_PASSWORD")?;
  75. let user: User = req.body_form().await?;
  76. if user.username == username && hash_password(&user.password, &user.username) == password {
  77. req.session_mut().insert("logged_in", true)?;
  78. redirect("/")
  79. } else {
  80. req.session_mut().remove("logged_in");
  81. req.session_mut()
  82. .insert("flash_error", "Invalid credentials")?;
  83. let login_path = req.state().login_path.clone();
  84. redirect(&login_path)
  85. }
  86. }
  87. pub async fn logout(mut req: Request<State>) -> Result {
  88. req.session_mut().insert("logged_in", false)?;
  89. Ok(Redirect::new("/").into())
  90. }
  91. #[derive(RustEmbed)]
  92. #[folder = "../static"]
  93. struct Asset;
  94. pub async fn static_file(req: Request<State>) -> Result {
  95. let filename: String = req.param("filename")?;
  96. match Asset::get(&filename) {
  97. Some(file) => {
  98. let content_type = if filename.ends_with(".css") {
  99. mime::CSS
  100. } else if filename.ends_with("js") {
  101. mime::JAVASCRIPT
  102. } else if filename.ends_with("otf") {
  103. mime::Mime::from("font/opentype")
  104. } else {
  105. mime::PLAIN
  106. };
  107. render_static_response(file.to_vec(), content_type)
  108. }
  109. None => {
  110. let res = Response::builder(StatusCode::NotFound)
  111. .body("404 not found")
  112. .content_type(mime::PLAIN)
  113. .build();
  114. Ok(res)
  115. }
  116. }
  117. }
  118. pub fn render_html_response(
  119. template: &str,
  120. context: &Context,
  121. req: Request<State>,
  122. ) -> Result<Response> {
  123. let mut context = context.clone();
  124. let logged_in: bool = req.session().get("logged_in").unwrap_or(false);
  125. let login_path = &req.state().login_path;
  126. context.insert("logged_in", &logged_in);
  127. context.insert("login_path", login_path);
  128. context.extend(prepare_flash_messages(req));
  129. let html = render_template(template, context)?;
  130. let res = Response::builder(StatusCode::Ok)
  131. .body(html)
  132. .content_type(mime::HTML)
  133. .build();
  134. Ok(res)
  135. }
  136. pub fn render_json_response(json: serde_json::Value) -> Result<Response> {
  137. let res = Response::builder(StatusCode::Ok)
  138. .body(json)
  139. .content_type(mime::JSON)
  140. .build();
  141. Ok(res)
  142. }
  143. pub fn render_static_response(contents: Vec<u8>, content_type: Mime) -> Result<Response> {
  144. let res = Response::builder(StatusCode::Ok)
  145. .body(Body::from_bytes(contents))
  146. .content_type(content_type)
  147. .build();
  148. Ok(res)
  149. }
  150. fn redirect(path: &str) -> Result<Response> {
  151. Ok(Redirect::new(path).into())
  152. }
  153. fn prepare_flash_messages(mut req: Request<State>) -> Context {
  154. let mut context = Context::new();
  155. context.insert("flash_error", &false);
  156. context.insert("flash_success", &false);
  157. for key in vec!["flash_error", "flash_success"] {
  158. match req.session_mut().get::<String>(key) {
  159. Some(value) => {
  160. req.session_mut().remove(key);
  161. let _ = &context.insert(key, &value);
  162. }
  163. None => {}
  164. }
  165. }
  166. context
  167. }
  168. fn is_logged_in(req: &Request<State>) -> bool {
  169. req.session().get("logged_in").unwrap_or(false)
  170. }