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