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
+
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(),
}
}