From f9f2a75e8952e2a4584aefd9c47687bbdd44e8f4 Mon Sep 17 00:00:00 2001 From: vance Date: Sun, 23 Oct 2022 23:07:58 -0700 Subject: [PATCH] initial commit --- .gitignore | 4 + Cargo.toml | 17 +++ README.md | 2 + migrations/20221024052654_posts.sql | 8 ++ src/main.rs | 173 ++++++++++++++++++++++++++++ 5 files changed, 204 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.toml create mode 100644 README.md create mode 100644 migrations/20221024052654_posts.sql create mode 100644 src/main.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7fa2fa3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +/target +/.idea +Cargo.lock +*.db* \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..8154aa8 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "hermit" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +actix-web = "*" +sqlx = { version = "*", features = [ "runtime-actix-rustls", "sqlite" ] } +serde = { version = "*", features = [ "derive" ] } +ammonia = "*" + +[profile.release] +lto = "thin" +# Tell `rustc` to optimize for small code size. +opt-level = "z" \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..5a3f122 --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# hermit +a simple rust social platform \ No newline at end of file diff --git a/migrations/20221024052654_posts.sql b/migrations/20221024052654_posts.sql new file mode 100644 index 0000000..ee0272d --- /dev/null +++ b/migrations/20221024052654_posts.sql @@ -0,0 +1,8 @@ +CREATE TABLE IF NOT EXISTS posts +( + id INTEGER PRIMARY KEY NOT NULL, + parent_id INTEGER, + author TEXT NOT NULL, + title TEXT NOT NULL, + content TEXT NOT NULL +); \ No newline at end of file diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..a7b4c38 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,173 @@ +use actix_web::{http::header, web, App, HttpResponse, HttpServer, Responder}; +use ammonia::clean; +use serde::Deserialize; +use sqlx::{ + sqlite::{Sqlite, SqlitePool}, + Pool, +}; +use std::env; + +struct HermitState { + pool: Pool, +} + +struct Post { + id: i64, + parent_id: Option, + author: String, + title: String, + content: String, +} + +#[derive(Deserialize)] +struct PostForm { + author: String, + title: String, + content: String, +} + +fn render(posts: Vec, details: bool) -> String { + let mut page = r#" + + + + booru + + + Home +
+
+
+
+ +
"# + .to_owned(); + + for post in posts { + page.push_str(&format!( + "💬{} - {}
\n", + post.id, post.title, post.author + )); + if details { + page.push_str(&format!("

{}

", post.content)) + } + } + + page.push_str( + r#" + + + "#, + ); + + page +} + +async fn get(state: web::Data, post_id: Option>) -> impl Responder { + match post_id { + None => { + let posts = sqlx::query_as!( + Post, + r#" + SELECT * FROM posts + WHERE parent_id IS NULL + ORDER BY id DESC + "# + ) + .fetch_all(&state.pool) + .await + .unwrap(); + HttpResponse::Ok().body(render(posts, false)) + } + Some(id) => { + let post_id = id.into_inner(); + let posts = sqlx::query_as!( + Post, + r#" + SELECT * FROM posts + WHERE id = ?1 OR parent_id = ?1 + ORDER BY id ASC + "#, + post_id + ) + .fetch_all(&state.pool) + .await + .unwrap(); + HttpResponse::Ok().body(render(posts, true)) + } + } +} + +async fn post( + state: web::Data, + post_id: Option>, + form: web::Form, +) -> impl Responder { + let author = clean(form.author.as_str()); + if author.is_empty() { + return HttpResponse::BadRequest().body("Author required"); + } + let title = clean(form.title.as_str()); + let content = clean(form.content.as_str()); + + let id = match post_id { + None => { + if title.is_empty() { + return HttpResponse::BadRequest().body("Title required for top posts"); + } + sqlx::query!( + r#" + INSERT INTO posts (author, title, content) + VALUES (?, ?, ?) + "#, + author, + title, + content + ) + .execute(&state.pool) + .await + .unwrap() + .last_insert_rowid() + } + Some(id) => { + let post_id = id.into_inner(); + sqlx::query!( + r#" + INSERT INTO posts (parent_id, author, title, content) + VALUES (?, ?, ?, ?) + "#, + post_id, + author, + title, + content + ) + .execute(&state.pool) + .await + .unwrap(); + post_id as i64 + } + }; + HttpResponse::Found() + .insert_header((header::LOCATION, format!("/{}", id))) + .finish() +} + +#[actix_web::main] +async fn main() -> Result<(), std::io::Error> { + let hermit = web::Data::new(HermitState { + pool: SqlitePool::connect(&env::var("DATABASE_URL").expect("Please set DATABASE_URL.")) + .await + .unwrap(), + }); + + HttpServer::new(move || { + App::new().app_data(hermit.clone()).service( + web::resource(["/", "/{post_id}"]) + .route(web::get().to(get)) + .route(web::post().to(post)), + ) + }) + .bind(("0.0.0.0", 8069))? + .run() + .await +}