Update internals for reply support, add error reasons, minor changes

main
Elnu 2 years ago
parent 4a39b10afb
commit 1bea87f47f

@ -1,3 +1,4 @@
<link rel="icon" href="data:,">
<meta name="soudan-content-id" content="a">
<link rel="stylesheet" href="https://unpkg.com/sakura.css/css/sakura-dark.css" type="text/css">
<link rel="stylesheet" href="/style.css">

@ -1,3 +1,4 @@
<link rel="icon" href="data:,">
<meta name="soudan-content-id" content="b">
<link rel="stylesheet" href="https://unpkg.com/sakura.css/css/sakura-dark.css" type="text/css">
<link rel="stylesheet" href="/style.css">

@ -4,6 +4,7 @@ document.getElementById("soudan").innerHTML = `<h3>Make a comment</h3>
<label for="email">Email:</label> <input type="email" name="email">
<label for="text">Comment:</label>
<textarea name="text" required></textarea>
<input type="hidden" name="parent">
<input type="submit">
</form>
<h3 id="soudan-comments-header">Comments</h3>

@ -6,6 +6,8 @@ use validator::Validate;
#[derive(Serialize, Deserialize, Validate)]
#[serde(rename_all = "camelCase")]
pub struct Comment {
#[serde(skip_deserializing)]
pub id: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub author: Option<String>, // 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<DateTime<Utc>>,
#[serde(skip_serializing)]
pub content_id: String,
#[serde(skip_serializing)]
pub parent: Option<i64>,
#[serde(skip_serializing_if = "<[_]>::is_empty")]
#[serde(skip_deserializing)]
pub replies: Vec<Comment>,
}
fn serialize_gravatar<S>(email: &Option<String>, s: S) -> Result<S::Ok, S::Error>

@ -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<Vec<Comment>> {
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::<usize, Option<i64>>(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::<Result<Vec<Comment>>>()?;
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()

@ -23,20 +23,19 @@ fn get_db<'a>(
data: &'a web::Data<AppState>,
request: &HttpRequest,
) -> Result<MutexGuard<'a, Database>, 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(),
}
}

Loading…
Cancel
Save