initial commit
This commit is contained in:
		
						commit
						f9f2a75e89
					
				
							
								
								
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@ -0,0 +1,4 @@
 | 
			
		||||
/target
 | 
			
		||||
/.idea
 | 
			
		||||
Cargo.lock
 | 
			
		||||
*.db*
 | 
			
		||||
							
								
								
									
										17
									
								
								Cargo.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								Cargo.toml
									
									
									
									
									
										Normal 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"
 | 
			
		||||
							
								
								
									
										8
									
								
								migrations/20221024052654_posts.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								migrations/20221024052654_posts.sql
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										173
									
								
								src/main.rs
									
									
									
									
									
										Normal 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
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user