initial commit

This commit is contained in:
vance 2022-10-23 23:07:58 -07:00
commit f9f2a75e89
5 changed files with 204 additions and 0 deletions

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
/target
/.idea
Cargo.lock
*.db*

17
Cargo.toml Normal file
View File

@ -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"

2
README.md Normal file
View File

@ -0,0 +1,2 @@
# hermit
a simple rust social platform

View File

@ -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
);

173
src/main.rs Normal file
View File

@ -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<Sqlite>,
}
struct Post {
id: i64,
parent_id: Option<i64>,
author: String,
title: String,
content: String,
}
#[derive(Deserialize)]
struct PostForm {
author: String,
title: String,
content: String,
}
fn render(posts: Vec<Post>, details: bool) -> String {
let mut page = r#"
<html>
<head>
<meta charset="utf-8">
<title>booru</title>
</head>
<body>
<a href="/">Home</a>
<form method="POST">
<input type="text" placeholder="op" name="author"><br />
<input type="text" placeholder="title" name="title"><br />
<textarea placeholder="your post goes here!" name="content"></textarea><br />
<input type="submit" value="Post!">
</form>"#
.to_owned();
for post in posts {
page.push_str(&format!(
"<a href=\"/{}\">💬{}</a> - {}<br />\n",
post.id, post.title, post.author
));
if details {
page.push_str(&format!("<p>{}</p>", post.content))
}
}
page.push_str(
r#"
</body>
</html>
"#,
);
page
}
async fn get(state: web::Data<HermitState>, post_id: Option<web::Path<u32>>) -> 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<HermitState>,
post_id: Option<web::Path<u32>>,
form: web::Form<PostForm>,
) -> 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
}