Added hiding, deleting, editing posts

This commit is contained in:
Leonard Lorenz 2020-10-25 23:23:32 +01:00
parent 22846c85b3
commit 4cbb673f15
4 changed files with 243 additions and 61 deletions

97
site/src/api.rs Normal file
View file

@ -0,0 +1,97 @@
use crate::db::*;
use crate::routes::{authorized, id_valid, replace_newlines};
use actix_web::{get, http::StatusCode, post, web, web::Form, HttpResponse, Responder};
use serde::Deserialize;
#[derive(Deserialize)]
struct NewPostForm {
title: String,
body: String,
token: String,
}
#[derive(Deserialize)]
struct BlogActionForm {
token: String,
}
#[post("/api/blog/create")]
async fn blog_create_post(form: Form<NewPostForm>) -> impl Responder {
if authorized(&form.token) {
create_post(&form.title.as_str(), replace_newlines(&form.body).as_str());
println!("New blog post created.");
} else {
println!("Unauthorized new blog post");
}
HttpResponse::MovedPermanently()
.set_header("LOCATION", "/blog")
.finish()
}
#[post("/api/blog/posts/edit/{post_id}")]
async fn blog_edit_post(
web::Path(post_id): web::Path<std::string::String>,
form: Form<NewPostForm>,
) -> impl Responder {
let (valid, id) = id_valid(post_id);
if valid && authorized(&form.token) {
edit_post_by_id(
id as i32,
&form.title.as_str(),
replace_newlines(&form.body).as_str(),
);
println!("Edited post: {}", id);
} else {
println!("Unauthorized blog post edit.");
return HttpResponse::new(StatusCode::UNAUTHORIZED);
}
return HttpResponse::MovedPermanently()
.set_header("LOCATION", "/blog")
.finish();
}
#[post("/api/blog/posts/delete/{post_id}")]
async fn blog_delete_post(
web::Path(post_id): web::Path<std::string::String>,
form: Form<BlogActionForm>,
) -> impl Responder {
let (valid, id) = id_valid(post_id);
if valid && authorized(&form.token) {
println!("Deleted post: {}", id);
delete_post_by_id(id as i32);
} else {
println!("Unauthorized blog post deletion.");
return HttpResponse::new(StatusCode::UNAUTHORIZED);
}
return HttpResponse::MovedPermanently()
.set_header("LOCATION", "/blog")
.finish();
}
#[post("/api/blog/posts/hide/{post_id}")]
async fn blog_hide_post(
web::Path(post_id): web::Path<std::string::String>,
form: Form<BlogActionForm>,
) -> impl Responder {
let (valid, id) = id_valid(post_id);
if valid && authorized(&form.token) {
println!("Hid post: {}", id);
hide_post_by_id(id as i32);
} else {
println!("Unauthorized blog post hiding.");
return HttpResponse::new(StatusCode::UNAUTHORIZED);
}
return HttpResponse::MovedPermanently()
.set_header("LOCATION", "/blog")
.finish();
}
#[get("/api/blog/posts")]
async fn blog_get_posts_json() -> impl Responder {
let posts = get_posts();
HttpResponse::Ok().json(posts)
}

View file

@ -14,7 +14,7 @@ fn establish_connection() -> SqliteConnection {
} }
pub fn get_posts() -> std::vec::Vec<Post> { pub fn get_posts() -> std::vec::Vec<Post> {
use crate::db::schema::posts::dsl::*; use schema::posts::dsl::*;
let connection = establish_connection(); let connection = establish_connection();
posts posts
.filter(published.eq(true)) .filter(published.eq(true))
@ -24,16 +24,16 @@ pub fn get_posts() -> std::vec::Vec<Post> {
.expect("Error, couldn't load posts.") .expect("Error, couldn't load posts.")
} }
pub fn get_post_by_id(_id: i32) -> Post { pub fn get_post_by_id(post_id: i32) -> Post {
use crate::db::schema::posts::dsl::*; use schema::posts::dsl::*;
let connection = establish_connection(); let connection = establish_connection();
posts posts
.find(_id) .find(post_id)
.get_result(&connection) .get_result(&connection)
.expect("Error, couldn't find post") .expect("Error, couldn't find post.")
} }
pub fn add_post(title: &str, body: &str) { pub fn create_post(title: &str, body: &str) {
use chrono::prelude::*; use chrono::prelude::*;
use schema::posts; use schema::posts;
@ -51,3 +51,34 @@ pub fn add_post(title: &str, body: &str) {
.execute(&connection) .execute(&connection)
.unwrap_or_else(|_| panic!("Error, couldn't insert new Post.")); .unwrap_or_else(|_| panic!("Error, couldn't insert new Post."));
} }
pub fn edit_post_by_id(post_id: i32, new_title: &str, new_body: &str) {
use schema::posts::dsl::*;
let connection = establish_connection();
diesel::update(posts)
.filter(id.eq(post_id))
.set((title.eq(new_title), body.eq(new_body)))
.execute(&connection)
.expect("Error, couldn't update post.");
}
pub fn delete_post_by_id(post_id: i32) {
use schema::posts::dsl::*;
let connection = establish_connection();
diesel::delete(posts.filter(id.eq(post_id)))
.execute(&connection)
.expect("Error, couldn't update post.");
}
pub fn hide_post_by_id(post_id: i32) {
use schema::posts::dsl::*;
let connection = establish_connection();
diesel::update(posts)
.filter(id.eq(post_id))
.set(published.eq(false))
.execute(&connection)
.expect("Error, couldn't update post.");
}

View file

@ -1,3 +1,4 @@
mod api;
mod config; mod config;
mod db; mod db;
mod routes; mod routes;
@ -18,12 +19,17 @@ async fn main() -> std::io::Result<()> {
let root_path = get_from_env("ROOT_PATH", true); let root_path = get_from_env("ROOT_PATH", true);
App::new() App::new()
//.wrap(middleware::NormalizePath::default())
.service(routes::root) .service(routes::root)
.service(routes::blog) .service(routes::blog)
.service(routes::blog_permalink) .service(routes::blog_by_id)
.service(routes::blog_submit) .service(routes::blog_submit)
.service(routes::blog_new_post) .service(routes::blog_edit)
.service(routes::blog_edit_by_id)
.service(api::blog_get_posts_json)
.service(api::blog_create_post)
.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", root_path + "/static"))
}) })
.bind(String::from("localhost:") + &get_from_env("BIND_PORT", true))? .bind(String::from("localhost:") + &get_from_env("BIND_PORT", true))?

View file

@ -2,10 +2,38 @@ use crate::config;
use crate::db::*; use crate::db::*;
use actix_files as fs; use actix_files as fs;
use actix_web::{get, http::StatusCode, post, web, web::Form, HttpResponse, Responder}; use actix_web::{get, http::StatusCode, web, HttpResponse, Responder};
use serde::Deserialize;
use tera::{Context, Tera}; use tera::{Context, Tera};
pub fn authorized(form_token: &str) -> bool {
let token = config::get_from_env("SUBMIT_TOKEN", true);
if token == form_token {
return true;
}
false
}
pub fn id_valid(post_id: String) -> (bool, i32) {
match post_id.parse::<i32>() {
Err(_) => (false, 0),
Ok(id) => {
if id < 1 {
(false, id)
} else {
(true, id)
}
}
}
}
pub fn replace_newlines(x: &String) -> String {
x.replace("\n", "<br>")
}
pub fn replace_br_tags(x: &String) -> String {
x.replace("<br>", "\n")
}
#[get("/")] #[get("/")]
async fn root() -> impl Responder { async fn root() -> impl Responder {
let root_path = config::get_from_env("ROOT_PATH", true); let root_path = config::get_from_env("ROOT_PATH", true);
@ -36,23 +64,30 @@ async fn blog() -> impl Responder {
#[get("/blog/submit")] #[get("/blog/submit")]
async fn blog_submit() -> impl Responder { async fn blog_submit() -> impl Responder {
let root_path = config::get_from_env("ROOT_PATH", true); let root_path = config::get_from_env("ROOT_PATH", true);
HttpResponse::Ok()
.content_type("text/html") let mut context = Context::new();
.set_header("SameSite", "secure") context.insert("title", "");
.body( context.insert("body", "");
std::fs::read_to_string(root_path + "/html/submit.html")
.unwrap_or_else(|e| panic!("Error, couldn't load submit html file.\n{}", e)), // one-off render blog template with context
let result = Tera::one_off(
&(std::fs::read_to_string(root_path + "/templates/post-submit.html")
.unwrap_or_else(|e| panic!("Error, couldn't load blog template.\n{}", e))
.as_str()),
&context,
false,
) )
.unwrap_or_else(|e| panic!("Error, couldn't render blog template.\n{}", e));
HttpResponse::Ok().content_type("text/html").body(result)
} }
#[get("/blog/id/{post_id}")] #[get("/blog/id/{post_id}")]
async fn blog_permalink(web::Path(post_id): web::Path<std::string::String>) -> impl Responder { async fn blog_by_id(web::Path(post_id): web::Path<std::string::String>) -> impl Responder {
match post_id.parse::<u32>() { let (valid, id) = id_valid(post_id);
Err(_) => HttpResponse::new(StatusCode::NOT_FOUND), if valid {
Ok(i) => {
let root_path = config::get_from_env("ROOT_PATH", true); let root_path = config::get_from_env("ROOT_PATH", true);
let post = get_post_by_id(i as i32); let post = get_post_by_id(id as i32);
let mut context = Context::new(); let mut context = Context::new();
context.insert("posts", &[post]); context.insert("posts", &[post]);
@ -66,32 +101,45 @@ async fn blog_permalink(web::Path(post_id): web::Path<std::string::String>) -> i
false, false,
) )
.unwrap_or_else(|e| panic!("Error, couldn't render blog template.\n{}", e)); .unwrap_or_else(|e| panic!("Error, couldn't render blog template.\n{}", e));
HttpResponse::Ok().content_type("text/html").body(result) return HttpResponse::Ok().content_type("text/html").body(result);
}
}
}
#[derive(Deserialize)]
struct NewPostForm {
title: String,
body: String,
token: String,
}
#[post("/blog/posts/new")]
async fn blog_new_post(form: Form<NewPostForm>) -> impl Responder {
let token = config::get_from_env("SUBMIT_TOKEN", true);
let replace_newlines = |x: &String| x.replace("\n", "<br>");
if form.token == token {
add_post(&form.title.as_str(), replace_newlines(&form.body).as_str());
println!("New blog post created.");
} else { } else {
println!("Unauthorized new blog post"); return HttpResponse::new(StatusCode::NOT_FOUND);
}
} }
HttpResponse::MovedPermanently() #[get("/blog/edit")]
.set_header("LOCATION", "/blog") async fn blog_edit() -> impl Responder {
.finish() "edit"
}
#[get("/blog/edit/{post_id}")]
async fn blog_edit_by_id(web::Path(post_id): web::Path<std::string::String>) -> impl Responder {
let (valid, id) = id_valid(post_id);
if valid {
let root_path = config::get_from_env("ROOT_PATH", true);
let mut post = get_post_by_id(id as i32);
post.title = replace_br_tags(&post.title);
post.body = replace_br_tags(&post.body);
let mut context = Context::new();
context.insert("title", &post.title);
context.insert("body", &post.body);
context.insert("id", &id);
// one-off render blog template with context
let result = Tera::one_off(
&(std::fs::read_to_string(root_path + "/templates/post-edit.html")
.unwrap_or_else(|e| panic!("Error, couldn't load blog template.\n{}", e))
.as_str()),
&context,
false,
)
.unwrap_or_else(|e| panic!("Error, couldn't render submit template.\n{}", e));
return HttpResponse::Ok().content_type("text/html").body(result);
} else {
return HttpResponse::new(StatusCode::UNAUTHORIZED);
}
} }