Added hiding, deleting, editing posts
This commit is contained in:
parent
22846c85b3
commit
4cbb673f15
4 changed files with 243 additions and 61 deletions
97
site/src/api.rs
Normal file
97
site/src/api.rs
Normal 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)
|
||||||
|
}
|
|
@ -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.");
|
||||||
|
}
|
||||||
|
|
|
@ -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))?
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/blog/edit")]
|
||||||
|
async fn blog_edit() -> impl Responder {
|
||||||
|
"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);
|
||||||
}
|
}
|
||||||
|
|
||||||
HttpResponse::MovedPermanently()
|
|
||||||
.set_header("LOCATION", "/blog")
|
|
||||||
.finish()
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue