From 1bea87f47f2694b01f3d3d5d4e05b615bb046d3f Mon Sep 17 00:00:00 2001 From: ElnuDev Date: Wed, 20 Jul 2022 14:48:44 -0700 Subject: [PATCH] Update internals for reply support, add error reasons, minor changes --- demo/a/index.html | 1 + demo/b/index.html | 1 + demo/soudan.js | 1 + src/comment.rs | 8 ++++++++ src/database.rs | 32 ++++++++++++++++++++++++++------ src/main.rs | 27 +++++++++++++-------------- 6 files changed, 50 insertions(+), 20 deletions(-) diff --git a/demo/a/index.html b/demo/a/index.html index d7060e8..25d385a 100644 --- a/demo/a/index.html +++ b/demo/a/index.html @@ -1,3 +1,4 @@ + diff --git a/demo/b/index.html b/demo/b/index.html index e50b5a9..ad2e0e0 100644 --- a/demo/b/index.html +++ b/demo/b/index.html @@ -1,3 +1,4 @@ + diff --git a/demo/soudan.js b/demo/soudan.js index bb7c97f..d2102c9 100644 --- a/demo/soudan.js +++ b/demo/soudan.js @@ -4,6 +4,7 @@ document.getElementById("soudan").innerHTML = `

Make a comment

+

Comments

diff --git a/src/comment.rs b/src/comment.rs index 6eb16a0..1dbeda0 100644 --- a/src/comment.rs +++ b/src/comment.rs @@ -6,6 +6,8 @@ use validator::Validate; #[derive(Serialize, Deserialize, Validate)] #[serde(rename_all = "camelCase")] pub struct Comment { + #[serde(skip_deserializing)] + pub id: Option, #[serde(skip_serializing_if = "Option::is_none")] pub author: Option, // None is Anonymous #[serde(rename(serialize = "gravatar"))] @@ -19,7 +21,13 @@ pub struct Comment { #[serde(with = "ts_seconds_option")] #[serde(skip_serializing_if = "Option::is_none")] pub timestamp: Option>, + #[serde(skip_serializing)] pub content_id: String, + #[serde(skip_serializing)] + pub parent: Option, + #[serde(skip_serializing_if = "<[_]>::is_empty")] + #[serde(skip_deserializing)] + pub replies: Vec, } fn serialize_gravatar(email: &Option, s: S) -> Result diff --git a/src/database.rs b/src/database.rs index b123ec1..063f6b9 100644 --- a/src/database.rs +++ b/src/database.rs @@ -18,7 +18,8 @@ impl Database { author TEXT, text TEXT NOT NULL, timestamp DATETIME DEFAULT CURRENT_TIMESTAMP, - content_id TEXT NOT NULL + content_id TEXT NOT NULL, + parent INTEGER )", params![], )?; @@ -27,14 +28,33 @@ impl Database { pub fn get_comments(&self, content_id: &str) -> Result> { self.conn - .prepare(&format!("SELECT author, email, text, timestamp FROM comment WHERE content_id='{content_id}' ORDER BY timestamp DESC"))? + .prepare(&format!("SELECT id, author, email, text, timestamp FROM comment WHERE content_id='{content_id}' AND parent IS NULL ORDER BY timestamp DESC"))? .query_map([], |row| { + let id = row.get::>(0)?.unwrap(); + let replies = self.conn + .prepare(&format!("SELECT id, author, email, text, timestamp FROM comment WHERE parent={id} ORDER BY timestamp DESC"))? + .query_map([], |row| { + Ok(Comment { + id: row.get(0)?, + author: row.get(1)?, + email: row.get(2)?, + text: row.get(3)?, + timestamp: row.get(4)?, + content_id: content_id.to_owned(), + parent: Some(id), + replies: Vec::new(), // no recursion + }) + })? + .collect::>>()?; Ok(Comment { - author: row.get(0)?, - email: row.get(1)?, - text: row.get(2)?, - timestamp: row.get(3)?, + id: Some(id), + author: row.get(1)?, + email: row.get(2)?, + text: row.get(3)?, + timestamp: row.get(4)?, content_id: content_id.to_owned(), + parent: None, + replies, }) })? .collect() diff --git a/src/main.rs b/src/main.rs index 8938432..b56be4f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -23,20 +23,19 @@ fn get_db<'a>( data: &'a web::Data, request: &HttpRequest, ) -> Result, HttpResponse> { - // all the .into() are converting from HttpResponseBuilder to HttpResponse let origin = match request.head().headers().get("Origin") { Some(origin) => match origin.to_str() { Ok(origin) => origin, - Err(_) => return Err(HttpResponse::BadRequest().into()), + Err(_) => return Err(HttpResponse::BadRequest().reason("bad origin").finish()), }, - None => return Err(HttpResponse::BadRequest().into()), + None => return Err(HttpResponse::BadRequest().reason("bad origin").finish()), }; match data.databases.get(origin) { Some(database) => Ok(match database.lock() { Ok(database) => database, - Err(_) => return Err(HttpResponse::InternalServerError().into()), + Err(_) => return Err(HttpResponse::InternalServerError().reason("database error").finish()), }), - None => return Err(HttpResponse::BadRequest().into()), + None => return Err(HttpResponse::BadRequest().reason("bad origin").finish()), } } @@ -69,19 +68,19 @@ async fn post_comment( Ok(text) => { let PostCommentsRequest { url, comment } = match serde_json::from_str(&text) { Ok(req) => req, - Err(_) => return HttpResponse::BadRequest().into(), + Err(_) => return HttpResponse::BadRequest().reason("invalid request body").finish(), }; if comment.validate().is_err() { - return HttpResponse::BadRequest().into(); + return HttpResponse::BadRequest().reason("invalid comment field(s)").finish(); } let origin = match request.head().headers().get("Origin") { Some(origin) => match origin.to_str() { Ok(origin) => origin, // If the Origin is not valid ASCII, it is a bad request not sent from a browser - Err(_) => return HttpResponse::BadRequest().into(), + Err(_) => return HttpResponse::BadRequest().reason("bad origin").finish(), }, // If there is no Origin header, it is a bad request not sent from a browser - None => return HttpResponse::BadRequest().into(), + None => return HttpResponse::BadRequest().reason("bad origin").finish(), }; // Check to see if provided URL is in scope. // This is to prevent malicious requests that try to get server to fetch external websites. @@ -93,18 +92,18 @@ async fn post_comment( break 'outer; } } - return HttpResponse::BadRequest().into(); + return HttpResponse::BadRequest().reason("url out of scope").finish(); } match get_page_data(&url).await { Ok(page_data_option) => match page_data_option { Some(page_data) => { if page_data.content_id != comment.content_id { - return HttpResponse::BadRequest().into(); + return HttpResponse::BadRequest().reason("content ids don't match").finish(); } } - None => return HttpResponse::BadRequest().into(), + None => return HttpResponse::BadRequest().reason("url invalid").finish(), // e.g. 404 }, - Err(_) => return HttpResponse::InternalServerError().into(), + Err(_) => return HttpResponse::InternalServerError().reason("failed to get page data").finish(), }; let database = match get_db(&data, &request) { Ok(database) => database, @@ -113,7 +112,7 @@ async fn post_comment( database.create_comment(&comment).unwrap(); HttpResponse::Ok().into() } - Err(_) => HttpResponse::BadRequest().into(), + Err(_) => HttpResponse::BadRequest().reason("failed to parse request body").finish(), } }