From 7becde6dc7d11142644782b0e493f1f06bccaa15 Mon Sep 17 00:00:00 2001 From: Leonard Lorenz Date: Sat, 21 Nov 2020 20:51:32 +0100 Subject: [PATCH] Global configuration, templates load once, render many times --- content/templates/blog-all-posts.html | 30 ++++++ content/templates/blog-by-id.html | 29 ++++++ content/templates/blog.html | 31 ++++++ content/templates/edit-form.html | 42 +++++++++ content/templates/edit.html | 22 +++++ content/templates/index.html | 21 +++++ content/templates/submit.html | 33 +++++++ site/Cargo.lock | 1 + site/Cargo.toml | 22 +++-- site/src/api.rs | 11 ++- site/src/config.rs | 15 --- site/src/db.rs | 5 +- site/src/html.rs | 1 + site/src/main.rs | 27 ++++-- site/src/routes.rs | 130 ++++++++++---------------- 15 files changed, 300 insertions(+), 120 deletions(-) create mode 100644 content/templates/blog-all-posts.html create mode 100644 content/templates/blog-by-id.html create mode 100644 content/templates/blog.html create mode 100644 content/templates/edit-form.html create mode 100644 content/templates/edit.html create mode 100644 content/templates/index.html create mode 100644 content/templates/submit.html delete mode 100644 site/src/config.rs diff --git a/content/templates/blog-all-posts.html b/content/templates/blog-all-posts.html new file mode 100644 index 0000000..796b045 --- /dev/null +++ b/content/templates/blog-all-posts.html @@ -0,0 +1,30 @@ + + + + + + + + + + All posts + + + + + +

{{ username }}' blog

+

Last 5 posts

+ + + diff --git a/content/templates/blog-by-id.html b/content/templates/blog-by-id.html new file mode 100644 index 0000000..1de1257 --- /dev/null +++ b/content/templates/blog-by-id.html @@ -0,0 +1,29 @@ + + + + + + + + + + {{ post.title }} | {{ username }}' blog + + + + + +

{{ username }}' blog

+

All Posts

+ + + diff --git a/content/templates/blog.html b/content/templates/blog.html new file mode 100644 index 0000000..b37aecb --- /dev/null +++ b/content/templates/blog.html @@ -0,0 +1,31 @@ + + + + + + + + + + {{ username }}' blog + + + + + +

{{ username }}' blog

+

All Posts

+ + + diff --git a/content/templates/edit-form.html b/content/templates/edit-form.html new file mode 100644 index 0000000..5215c94 --- /dev/null +++ b/content/templates/edit-form.html @@ -0,0 +1,42 @@ + + + + + + + + + Edit '{{ title }}' + + + + + + + +
+ + + +
+ + +
+ +
+ +
+ + +
+
+ + +
+ + + diff --git a/content/templates/edit.html b/content/templates/edit.html new file mode 100644 index 0000000..8a02d1a --- /dev/null +++ b/content/templates/edit.html @@ -0,0 +1,22 @@ + + + + + + + + + Edit posts... + + + + +

{{ username }}' blog

+

Edit posts

+ + + diff --git a/content/templates/index.html b/content/templates/index.html new file mode 100644 index 0000000..a0b9750 --- /dev/null +++ b/content/templates/index.html @@ -0,0 +1,21 @@ + + + + + + + + + {{ username }}' site + + + + + +

Hi, I'm {{ username }}

+

+ I have a blog.
+ If you have questions or input for me please send me an E-Mail to {{ email }} +

+ + diff --git a/content/templates/submit.html b/content/templates/submit.html new file mode 100644 index 0000000..449c02e --- /dev/null +++ b/content/templates/submit.html @@ -0,0 +1,33 @@ + + + + + + + + + Submit post + + + + + + + +
+ + + +
+ + +
+ +
+ + + diff --git a/site/Cargo.lock b/site/Cargo.lock index 168f2a8..19e7678 100644 --- a/site/Cargo.lock +++ b/site/Cargo.lock @@ -568,6 +568,7 @@ dependencies = [ "chrono", "diesel 1.4.5", "diesel_codegen", + "once_cell", "serde", "serde_derive", "serde_json", diff --git a/site/Cargo.toml b/site/Cargo.toml index acc67a0..32a2b8a 100644 --- a/site/Cargo.toml +++ b/site/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "crablog" -version = "0.1.0" +version = "0.2.0" authors = ["Leonard Lorenz "] edition = "2018" @@ -8,16 +8,18 @@ edition = "2018" [dependencies] chrono = { version = "*", features = ["serde"] } -actix-web = "*" -actix-files = "*" +actix-web = "3.2.0" +actix-files = "0.4.0" -serde = { version = "*", features = ["derive"] } -serde_json = "*" -serde_derive = "*" +serde = { version = "1.0.117", features = ["derive"] } +serde_json = "1.0.59" +serde_derive = "1.0.117" -diesel = { version = "*", default-features = false, features = ["sqlite", "chrono"] } -diesel_codegen = { version = "*", default-features = false } +diesel = { version = "1.4.5", default-features = false, features = ["sqlite", "chrono"] } +diesel_codegen = { version = "0.16.1", default-features = false } -uuid = { version = "*", features = ["serde", "v5"] } +uuid = { version = "0.8.1", features = ["serde", "v5"] } -tera = "*" +tera = "1.5.0" + +once_cell = "1.5.2" diff --git a/site/src/api.rs b/site/src/api.rs index 65e5307..db074a4 100644 --- a/site/src/api.rs +++ b/site/src/api.rs @@ -1,7 +1,8 @@ use crate::db::*; -use crate::routes::{authorized, id_valid, replace_newlines}; +use crate::routes::{id_valid, replace_newlines}; use actix_web::{get, http::StatusCode, post, web, web::Form, HttpResponse, Responder}; use serde::Deserialize; +use super::CONFIG_MAP; #[derive(Deserialize)] struct NewPostForm { @@ -17,7 +18,7 @@ struct BlogActionForm { #[post("/api/blog/create")] async fn blog_create_post(form: Form) -> impl Responder { - if authorized(&form.token) { + if *CONFIG_MAP.read().unwrap().get("SUBMIT_TOKEN").unwrap() == form.token { create_post(&form.title.as_str(), replace_newlines(&form.body).as_str()); println!("New blog post created."); } else { @@ -35,7 +36,7 @@ async fn blog_edit_post( form: Form, ) -> impl Responder { let (valid, id) = id_valid(post_id); - if valid && authorized(&form.token) { + if valid && *CONFIG_MAP.read().unwrap().get("AUTH_TOKEN").unwrap() == form.token { edit_post_by_id( id as i32, &form.title.as_str(), @@ -58,7 +59,7 @@ async fn blog_delete_post( form: Form, ) -> impl Responder { let (valid, id) = id_valid(post_id); - if valid && authorized(&form.token) { + if valid && *CONFIG_MAP.read().unwrap().get("AUTH_TOKEN").unwrap() == form.token { println!("Deleted post: {}", id); delete_post_by_id(id as i32); } else { @@ -77,7 +78,7 @@ async fn blog_hide_post( form: Form, ) -> impl Responder { let (valid, id) = id_valid(post_id); - if valid && authorized(&form.token) { + if valid && *CONFIG_MAP.read().unwrap().get("AUTH_TOKEN").unwrap() == form.token { println!("Hid post: {}", id); hide_post_by_id(id as i32); } else { diff --git a/site/src/config.rs b/site/src/config.rs deleted file mode 100644 index a713a76..0000000 --- a/site/src/config.rs +++ /dev/null @@ -1,15 +0,0 @@ -use std::string::String; - -/// gets a value from an environment variable and returns it. -/// if this call was mandatory and it couldn't get a value, it will exit -/// the program and write an error message. -pub fn get_from_env(variable: &str, mandatory: bool) -> String { - std::env::var(variable).unwrap_or_else(|_| { - if mandatory { - println!("Error, couldn't read environment variable: {}", variable); - std::process::exit(1); - } else { - panic!("Error, couldn't read environment variable: {}", variable); - } - }) -} diff --git a/site/src/db.rs b/site/src/db.rs index b87206c..a6484a7 100644 --- a/site/src/db.rs +++ b/site/src/db.rs @@ -1,15 +1,14 @@ mod models; mod schema; -use crate::config; use diesel::prelude::*; use diesel::sqlite::SqliteConnection; use models::*; +use std::env; /// Returns an SqliteConnection if connection successful. fn establish_connection() -> SqliteConnection { - let root_path = config::get_from_env("ROOT_PATH", true); - let db_path = root_path + "/db.sqlite3"; + let db_path = env::var("ROOT_PATH").unwrap() + "/db.sqlite3"; SqliteConnection::establish(&db_path) .unwrap_or_else(|_| panic!("Error, connection to {} failed.", &db_path)) } diff --git a/site/src/html.rs b/site/src/html.rs index d89c5ba..0af4976 100644 --- a/site/src/html.rs +++ b/site/src/html.rs @@ -39,6 +39,7 @@ pub const BLOG: &str = r#"

{{ username }}' blog

+

All Posts

    {% for post in posts %}
    diff --git a/site/src/main.rs b/site/src/main.rs index 99bf263..9e13e06 100644 --- a/site/src/main.rs +++ b/site/src/main.rs @@ -1,8 +1,6 @@ mod api; -mod config; mod db; mod routes; -mod html; #[macro_use] extern crate diesel; @@ -12,16 +10,33 @@ extern crate tera; use actix_files as fs; use actix_web::{App, HttpServer}; -use config::get_from_env; +use tera::Tera; +use std::{env, sync::RwLock, collections::HashMap}; +use once_cell::sync::Lazy; + +pub static CONFIG_MAP: Lazy>> = Lazy::new(|| { + let mut config: HashMap = HashMap::new(); + config.insert(String::from("SUBMIT_TOKEN"), env::var("SUBMIT_TOKEN").expect("SUBMIT_TOKEN variable not set.")); + config.insert(String::from("ROOT_PATH"), env::var("ROOT_PATH").expect("ROOT_PATH variable not set.")); + config.insert(String::from("USERNAME"), env::var("USERNAME").expect("USERNAME variable not set.")); + config.insert(String::from("EMAIL"), env::var("EMAIL").expect("EMAIL variable not set.")); + config.insert(String::from("BIND_PORT"), env::var("BIND_PORT").expect("BIND_PORT variable not set.")); + RwLock::new(config) +}); #[actix_web::main] async fn main() -> std::io::Result<()> { + HttpServer::new(|| { - let root_path = get_from_env("ROOT_PATH", true); + + let tera = + Tera::new(format!("{}{}", CONFIG_MAP.read().unwrap().get("ROOT_PATH").unwrap(), "/templates/*").as_str()).unwrap(); App::new() + .data(tera) .service(routes::root) .service(routes::blog) + .service(routes::blog_all) .service(routes::blog_by_id) .service(routes::blog_submit) .service(routes::blog_edit) @@ -31,9 +46,9 @@ async fn main() -> std::io::Result<()> { .service(api::blog_edit_post) .service(api::blog_hide_post) .service(api::blog_delete_post) - .service(fs::Files::new("/static", root_path + "/static")) + .service(fs::Files::new("/static", "../content/static")) }) - .bind("0.0.0.0:8000")? + .bind(format!("0.0.0.0:{}", CONFIG_MAP.read().unwrap().get("BIND_PORT").unwrap()))? .run() .await } diff --git a/site/src/routes.rs b/site/src/routes.rs index 2346c2b..f3cf216 100644 --- a/site/src/routes.rs +++ b/site/src/routes.rs @@ -1,18 +1,8 @@ -use crate::config; use crate::db; -use crate::html; -use actix_web::{get, http::StatusCode, web, HttpResponse, Responder}; -use tera::{Context, Tera}; - -/// authorizes a request by comparing it to the SUBMIT_TOKEN environment variable -pub fn authorized(form_token: &str) -> bool { - let token = config::get_from_env("SUBMIT_TOKEN", true); - if token == form_token { - return true; - } - false -} +use actix_web::{get, http::StatusCode, web, HttpResponse, Error, error}; +use tera::Context; +use super::CONFIG_MAP; /// tests if the post id is a valid i32 integer bigger than zero /// assert(!(id_valid("2147483648").0)) @@ -44,107 +34,90 @@ pub fn replace_br_tags(x: &str) -> String { } #[get("/")] -async fn root() -> impl Responder { +async fn root(tmpl: web::Data) -> Result { let mut context = Context::new(); + context.insert("username", CONFIG_MAP.read().unwrap().get("USERNAME").unwrap()); + context.insert("email", CONFIG_MAP.read().unwrap().get("EMAIL").unwrap()); - context.insert("username", &config::get_from_env("USERNAME", true)); - context.insert("email", &config::get_from_env("EMAIL", true)); + let result = tmpl.render("index.html", &context) + .map_err(|e| error::ErrorInternalServerError(format!("Template error\n{}", e)))?; - let result = Tera::one_off( - html::INDEX, - &context, - false, - ) - .unwrap_or_else(|e| panic!("Error, couldn't render blog template.\n{}", e)); - - HttpResponse::Ok().content_type("text/html").body(result) + Ok(HttpResponse::Ok().content_type("text/html").body(result)) } #[get("/blog")] -async fn blog() -> impl Responder { +async fn blog(tmpl: web::Data) -> Result { let posts = db::get_last_five_posts(); - let username = config::get_from_env("USERNAME", true); let mut context = Context::new(); context.insert("posts", &posts); - context.insert("username", &username); - context.insert("sitetitle", &format!("{}' blog'", &username)); - context.insert("sitedescription", &format!("Last 5 posts of {}' blog", &username)); + context.insert("username", CONFIG_MAP.read().unwrap().get("USERNAME").unwrap()); - // one-off render blog template with context - let result = Tera::one_off( - html::BLOG, - &context, - false, - ) - .unwrap_or_else(|e| panic!("Error, couldn't render blog template.\n{}", e)); + let result = tmpl.render("blog.html", &context) + .map_err(|_| error::ErrorInternalServerError("Template error"))?; - HttpResponse::Ok().content_type("text/html").body(result) + Ok(HttpResponse::Ok().content_type("text/html").body(result)) +} + +#[get("/blog/all")] +async fn blog_all(tmpl: web::Data) -> Result { + let posts = db::get_all_posts(); + + let mut context = Context::new(); + context.insert("posts", &posts); + context.insert("username", CONFIG_MAP.read().unwrap().get("USERNAME").unwrap()); + + let result = tmpl.render("blog-all-posts.html", &context) + .map_err(|_| error::ErrorInternalServerError("Template error"))?; + + Ok(HttpResponse::Ok().content_type("text/html").body(result)) } #[get("/blog/id/{post_id}")] -async fn blog_by_id(web::Path(post_id): web::Path) -> impl Responder { +async fn blog_by_id(tmpl: web::Data, web::Path(post_id): web::Path) -> Result { let (valid, id) = id_valid(post_id); if valid { let post = db::get_post_by_id(id as i32); - let username = config::get_from_env("USERNAME", true); let mut context = Context::new(); - context.insert("posts", &[&post]); - context.insert("username", &username); - context.insert("sitetitle", &post.title); - context.insert("sitedescription", &post.body); + context.insert("post", &post); + context.insert("username", CONFIG_MAP.read().unwrap().get("USERNAME").unwrap()); - // one-off render blog template with context - let result = Tera::one_off( - html::BLOG, - &context, - false, - ) - .unwrap_or_else(|e| panic!("Error, couldn't render blog template.\n{}", e)); + let result = tmpl.render("blog-by-id.html", &context) + .map_err(|_| error::ErrorInternalServerError("Template error"))?; - return HttpResponse::Ok().content_type("text/html").body(result); + return Ok(HttpResponse::Ok().content_type("text/html").body(result)) } else { - return HttpResponse::new(StatusCode::NOT_FOUND); + return Ok(HttpResponse::new(StatusCode::NOT_FOUND)) } } #[get("/blog/submit")] -async fn blog_submit() -> impl Responder { +async fn blog_submit(tmpl: web::Data) -> Result { let mut context = Context::new(); context.insert("title", ""); context.insert("body", ""); - // one-off render blog template with context - let result = Tera::one_off( - html::SUBMIT, - &context, - false, - ) - .unwrap_or_else(|e| panic!("Error, couldn't render blog template.\n{}", e)); + let result = tmpl.render("submit.html", &context) + .map_err(|_| error::ErrorInternalServerError("Template error"))?; - HttpResponse::Ok().content_type("text/html").body(result) + return Ok(HttpResponse::Ok().content_type("text/html").body(result)) } #[get("/blog/edit")] -async fn blog_edit() -> impl Responder { +async fn blog_edit(tmpl: web::Data) -> Result { let mut context = Context::new(); context.insert("posts", &db::get_all_posts()); - context.insert("username", &config::get_from_env("USERNAME", true)); + context.insert("username", CONFIG_MAP.read().unwrap().get("USERNAME").unwrap()); - // one-off render blog template with context - let result = Tera::one_off( - html::EDIT, - &context, - false, - ) - .unwrap_or_else(|e| panic!("Error, couldn't render submit template.\n{}", e)); + let result = tmpl.render("edit.html", &context) + .map_err(|_| error::ErrorInternalServerError("Template error"))?; - return HttpResponse::Ok().content_type("text/html").body(result); + Ok(HttpResponse::Ok().content_type("text/html").body(result)) } #[get("/blog/edit/{post_id}")] -async fn blog_edit_by_id(web::Path(post_id): web::Path) -> impl Responder { +async fn blog_edit_by_id(tmpl: web::Data, web::Path(post_id): web::Path) -> Result { let (valid, id) = id_valid(post_id); if valid { let mut post = db::get_post_by_id(id as i32); @@ -157,16 +130,11 @@ async fn blog_edit_by_id(web::Path(post_id): web::Path) -> context.insert("body", &post.body); context.insert("id", &id); - // one-off render blog template with context - let result = Tera::one_off( - html::POST_EDIT_FORM, - &context, - false, - ) - .unwrap_or_else(|e| panic!("Error, couldn't render submit template.\n{}", e)); + let result = tmpl.render("edit-form.html", &context) + .map_err(|_| error::ErrorInternalServerError("Template error"))?; - return HttpResponse::Ok().content_type("text/html").body(result); + Ok(HttpResponse::Ok().content_type("text/html").body(result)) } else { - return HttpResponse::new(StatusCode::UNAUTHORIZED); + Ok(HttpResponse::new(StatusCode::UNAUTHORIZED)) } }