diff --git a/Cargo.lock b/Cargo.lock index 433f240..1646564 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -589,6 +589,12 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388" +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + [[package]] name = "libc" version = "0.2.126" @@ -648,6 +654,12 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" +[[package]] +name = "md5" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771" + [[package]] name = "memchr" version = "2.5.0" @@ -971,9 +983,11 @@ name = "soudan" version = "0.1.0" dependencies = [ "actix-web", + "md5", "rusqlite", "serde", "serde_json", + "validator", ] [[package]] @@ -1112,6 +1126,21 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "validator" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f07b0a1390e01c0fc35ebb26b28ced33c9a3808f7f9fbe94d3cc01e233bfeed5" +dependencies = [ + "idna", + "lazy_static", + "regex", + "serde", + "serde_derive", + "serde_json", + "url", +] + [[package]] name = "vcpkg" version = "0.2.15" diff --git a/Cargo.toml b/Cargo.toml index d703844..0f5491e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,3 +10,5 @@ actix-web = "4" rusqlite = "0.27.0" serde = { version = "1", features = ["derive"] } serde_json = "1" +validator = "0.15.0" +md5 = "0.7.0" diff --git a/src/comment.rs b/src/comment.rs index 75189e1..56bdf1d 100644 --- a/src/comment.rs +++ b/src/comment.rs @@ -1,7 +1,48 @@ use serde::{Deserialize, Serialize}; -#[derive(Debug, Deserialize, Serialize)] +// Master comment type that is stored in database +#[derive(Deserialize, Serialize)] pub struct Comment { pub author: Option, // None/null is Anonymous + pub email: Option, pub text: String } + +impl Comment { + pub fn send(&self) -> CommentSend { + CommentSend { + author: self.author.clone(), + gravatar: match self.email.clone() { + Some(email) => Some(format!("{:x}", md5::compute(email.to_lowercase()))), + None => None, + }, + text: self.text.clone(), + } + } +} + +// Comment type for API responses +#[derive(Serialize)] +pub struct CommentSend { + pub author: Option, + pub gravatar: Option, + pub text: String +} + +// Comment type received containing new comment data +#[derive(Deserialize)] +pub struct CommentReceive { + pub author: Option, + pub email: Option, + pub text: String, +} + +impl CommentReceive { + pub fn to_master(&self) -> Comment { + Comment { + author: self.author.clone(), + email: self.email.clone(), + text: self.text.clone(), + } + } +} diff --git a/src/database.rs b/src/database.rs index 93a06a1..acf84f8 100644 --- a/src/database.rs +++ b/src/database.rs @@ -1,5 +1,5 @@ use rusqlite::{params, Connection, Result}; -use crate::comment::Comment; +use crate::comment::{Comment, CommentSend}; pub struct Database { conn: Connection, @@ -11,6 +11,7 @@ impl Database { conn.execute( "CREATE TABLE comment ( id INTEGER PRIMARY KEY, + email TEXT, author TEXT, text TEXT NOT NULL )", @@ -21,20 +22,37 @@ impl Database { pub fn get_comments(&self) -> Result> { self.conn - .prepare("SELECT author, text FROM comment")? + .prepare("SELECT author, email, text FROM comment")? .query_map([], |row| { Ok(Comment { author: row.get(0)?, - text: row.get(1)?, + email: row.get(1)?, + text: row.get(2)?, }) })? .collect() } + pub fn get_send_comments(&self) -> Result> { + self.conn + .prepare("SELECT author, email, text FROM comment")? + .query_map([], |row| { + Ok(CommentSend { + author: row.get(0)?, + gravatar: match row.get::>(1)? { + Some(email) => Some(format!("{:x}", md5::compute(email.to_lowercase()))), + None => None, + }, + text: row.get(2)?, + }) + })? + .collect() + } + pub fn create_comment(&self, comment: &Comment) -> Result<()> { self.conn.execute( - "INSERT INTO comment (author, text) VALUES (?1, ?2)", - params![&comment.author, &comment.text], + "INSERT INTO comment (author, email, text) VALUES (?1, ?2, ?3)", + params![&comment.author, &comment.email, &comment.text], )?; Ok(()) } diff --git a/src/main.rs b/src/main.rs index 84022ae..1a7e176 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,5 @@ mod comment; -pub use comment::Comment; +pub use comment::*; mod database; pub use database::Database; @@ -14,22 +14,22 @@ struct AppState { #[get("/")] async fn get_comments(data: web::Data) -> impl Responder { let db = &data.db.lock().unwrap(); - HttpResponse::Ok().json(&db.get_comments().unwrap()) + HttpResponse::Ok().json(&db.get_send_comments().unwrap()) } #[post("/")] -async fn post_comments(data: web::Data, bytes: web::Bytes) -> impl Responder { +async fn post_comment(data: web::Data, bytes: web::Bytes) -> impl Responder { match String::from_utf8(bytes.to_vec()) { Ok(text) => { let db = match data.db.lock() { Ok(db) => db, Err(_) => return HttpResponse::InternalServerError(), }; - let comment: Comment = match serde_json::from_str(&text) { + let comment: CommentReceive = match serde_json::from_str(&text) { Ok(comment) => comment, Err(_) => return HttpResponse::BadRequest(), }; - db.create_comment(&comment).unwrap(); + db.create_comment(&comment.to_master()).unwrap(); HttpResponse::Ok() }, Err(_) => HttpResponse::BadRequest().into() @@ -43,7 +43,7 @@ async fn main() -> Result<(), std::io::Error> { HttpServer::new(move || { App::new() .service(get_comments) - .service(post_comments) + .service(post_comment) .app_data(state.clone()) }) .bind(("127.0.0.1", 8080))?