use chrono::prelude::Local; use rust_embed::RustEmbed; use serde::{Deserialize, Serialize}; use tera::Context; use tide::{convert::json, http::Mime}; use tide::{http::mime, Body}; use tide::{Redirect, Request, Response, Result, StatusCode}; use std::env; use crate::{fs, hash_password, post::Post, templates::render_template, util, State}; #[derive(Debug, Serialize, Deserialize)] struct User { username: String, password: String, } pub async fn index(req: Request) -> Result { let posts: Vec = fs::get_all_posts().await?; let mut context = Context::new(); context.insert("posts", &posts); render_html_response("index.html", &context, req) } pub async fn single_post(req: Request) -> Result { let mut context = Context::new(); let slug = req.param("slug")?; let post = fs::get_one_post(slug, is_logged_in(&req)).await?; context.insert("post", &post); render_html_response("single.html", &context, req) } pub async fn new_post(req: Request) -> Result { let context = Context::new(); render_html_response("new.html", &context, req) } pub async fn edit_post(req: Request) -> Result { let mut context = Context::new(); let slug = req.param("slug")?; let mut post = fs::get_one_post(slug, is_logged_in(&req)).await?; post.body = post.body.replace("
", "\n"); post.html = post.generate_html(); context.insert("post", &post); render_html_response("edit.html", &context, req) } pub async fn create_post(mut req: Request) -> Result { let mut post: Post = req.body_form().await?; post.date = Local::now().date().naive_local().to_string(); post.body = post.body.trim().to_owned(); post.html = post.generate_html(); post.save().await?; Ok(Redirect::new("/").into()) } pub async fn update_post(mut req: Request) -> Result { let mut post: Post = req.body_form().await?; post.html = post.generate_html(); post.save().await?; req.session_mut() .insert("flash_success", String::from("Post updated successfully"))?; redirect(&format!("/posts/{}", post.slug)) } pub async fn delete_post(mut req: Request) -> Result { let slug: String = req.param("slug")?; fs::delete_post(slug)?; req.session_mut() .insert("flash_success", String::from("Post deleted successfully"))?; render_json_response(json!({ "success": "true" })) } pub async fn preview_post(mut req: Request) -> Result { let body: String = req.body_string().await?; let html = util::generate_html(&body); render_json_response(json!({ "body": html })) } pub async fn login_page(req: Request) -> Result { render_html_response("login.html", &Context::new(), req) } pub async fn login(mut req: Request) -> Result { let username = env::var("ADMIN_USERNAME")?; let password = env::var("ADMIN_PASSWORD")?; let user: User = req.body_form().await?; if user.username == username && hash_password(&user.password, &user.username) == password { req.session_mut().insert("logged_in", true)?; redirect("/") } else { req.session_mut().remove("logged_in"); req.session_mut() .insert("flash_error", "Invalid credentials")?; let login_path = req.state().login_path.clone(); redirect(&login_path) } } pub async fn logout(mut req: Request) -> Result { req.session_mut().insert("logged_in", false)?; Ok(Redirect::new("/").into()) } #[derive(RustEmbed)] #[folder = "../static"] struct Asset; pub async fn static_file(req: Request) -> Result { let filename: String = req.param("filename")?; match Asset::get(&filename) { Some(file) => { let content_type = if filename.ends_with(".css") { mime::CSS } else if filename.ends_with("js") { mime::JAVASCRIPT } else if filename.ends_with("otf") { mime::Mime::from("font/opentype") } else { mime::PLAIN }; render_static_response(file.to_vec(), content_type) } None => { let res = Response::builder(StatusCode::NotFound) .body("404 not found") .content_type(mime::PLAIN) .build(); Ok(res) } } } pub fn render_html_response( template: &str, context: &Context, req: Request, ) -> Result { let mut context = context.clone(); let logged_in: bool = req.session().get("logged_in").unwrap_or(false); let login_path = &req.state().login_path; context.insert("logged_in", &logged_in); context.insert("login_path", login_path); context.extend(prepare_flash_messages(req)); let html = render_template(template, context)?; let res = Response::builder(StatusCode::Ok) .body(html) .content_type(mime::HTML) .build(); Ok(res) } pub fn render_json_response(json: serde_json::Value) -> Result { let res = Response::builder(StatusCode::Ok) .body(json) .content_type(mime::JSON) .build(); Ok(res) } pub fn render_static_response(contents: Vec, content_type: Mime) -> Result { let res = Response::builder(StatusCode::Ok) .body(Body::from_bytes(contents)) .content_type(content_type) .build(); Ok(res) } fn redirect(path: &str) -> Result { Ok(Redirect::new(path).into()) } fn prepare_flash_messages(mut req: Request) -> Context { let mut context = Context::new(); context.insert("flash_error", &false); context.insert("flash_success", &false); for key in vec!["flash_error", "flash_success"] { match req.session_mut().get::(key) { Some(value) => { req.session_mut().remove(key); let _ = &context.insert(key, &value); } None => {} } } context } fn is_logged_in(req: &Request) -> bool { req.session().get("logged_in").unwrap_or(false) }