feat: edit approval
This commit is contained in:
parent
c13e172938
commit
0c3ea546fd
9 changed files with 750 additions and 185 deletions
42
src/db.rs
42
src/db.rs
|
|
@ -1,3 +1,4 @@
|
|||
use base64::engine::Engine;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sha2::{Digest, Sha256};
|
||||
use std::{net::IpAddr, path::Path};
|
||||
|
|
@ -7,11 +8,18 @@ const DB_DIR: &str = "db";
|
|||
|
||||
pub type Time = u64;
|
||||
|
||||
pub const BASE64: base64::engine::fast_portable::FastPortable =
|
||||
base64::engine::fast_portable::FastPortable::from(
|
||||
&base64::alphabet::URL_SAFE,
|
||||
base64::engine::fast_portable::NO_PAD,
|
||||
);
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Dbs {
|
||||
pub comment: Tree<CommentId, Comment>,
|
||||
pub comment: Tree<CommentId, (Comment, CommentStatus)>,
|
||||
pub comment_approved: Tree<(TopicHash, Time, CommentId), ()>,
|
||||
pub comment_pending: Tree<(TopicHash, Time, CommentId), Option<IpAddr>>,
|
||||
/// -> (client_addr, is_edit)
|
||||
pub comment_pending: Tree<(TopicHash, Time, CommentId), (Option<IpAddr>, bool)>,
|
||||
/// client_addr -> (last_mutation, mutation_count)
|
||||
pub client_mutation: Tree<IpAddr, (Time, u32)>,
|
||||
}
|
||||
|
|
@ -34,6 +42,14 @@ pub fn load_dbs(path: Option<&Path>) -> Dbs {
|
|||
}
|
||||
}
|
||||
|
||||
#[repr(u8)]
|
||||
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
||||
pub enum CommentStatus {
|
||||
Pending = 0,
|
||||
Approved = 1,
|
||||
ApprovedEdited(Comment) = 2,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
||||
pub struct Comment {
|
||||
pub author: String,
|
||||
|
|
@ -54,13 +70,20 @@ impl MutationToken {
|
|||
}
|
||||
|
||||
pub fn to_base64(&self) -> String {
|
||||
base64::encode_config(self.0, base64::URL_SAFE_NO_PAD)
|
||||
let mut buf = vec![0; 24];
|
||||
let size = BASE64.encode(&self.0, &mut buf);
|
||||
buf.truncate(size);
|
||||
String::from_utf8(buf).unwrap()
|
||||
}
|
||||
|
||||
pub fn from_base64(s: &str) -> Result<Self, base64::DecodeError> {
|
||||
std::panic::catch_unwind(|| {
|
||||
let mut buf = [0; 16];
|
||||
base64::decode_config_slice(s, base64::URL_SAFE_NO_PAD, &mut buf)?;
|
||||
BASE64.decode(
|
||||
s.as_bytes(),
|
||||
&mut buf,
|
||||
BASE64.decoded_length_estimate(s.len()),
|
||||
)?;
|
||||
Ok(Self(buf))
|
||||
})
|
||||
.map_err(|_| base64::DecodeError::InvalidLength)?
|
||||
|
|
@ -107,13 +130,20 @@ impl CommentId {
|
|||
}
|
||||
|
||||
pub fn to_base64(&self) -> String {
|
||||
base64::encode_config(self.0, base64::URL_SAFE_NO_PAD)
|
||||
let mut buf = vec![0; 24];
|
||||
let size = BASE64.encode(&self.0, &mut buf);
|
||||
buf.truncate(size);
|
||||
String::from_utf8(buf).unwrap()
|
||||
}
|
||||
|
||||
pub fn from_base64(s: &str) -> Result<Self, base64::DecodeError> {
|
||||
std::panic::catch_unwind(|| {
|
||||
let mut buf = [0; 16];
|
||||
base64::decode_config_slice(s, base64::URL_SAFE_NO_PAD, &mut buf)?;
|
||||
BASE64.decode(
|
||||
s.as_bytes(),
|
||||
&mut buf,
|
||||
BASE64.decoded_length_estimate(s.len()),
|
||||
)?;
|
||||
Ok(Self(buf))
|
||||
})
|
||||
.map_err(|_| base64::DecodeError::InvalidLength)?
|
||||
|
|
|
|||
422
src/helpers.rs
422
src/helpers.rs
|
|
@ -11,43 +11,145 @@ pub fn new_pending_comment(
|
|||
dbs: &Dbs,
|
||||
) -> Result<CommentId, sled::Error> {
|
||||
let comment_id = CommentId::new();
|
||||
dbs.comment.insert(&comment_id, comment)?;
|
||||
dbs.comment
|
||||
.insert(&comment_id, &(comment.clone(), CommentStatus::Pending))?;
|
||||
dbs.comment_pending.insert(
|
||||
&(
|
||||
comment.topic_hash.clone(),
|
||||
comment.post_time,
|
||||
comment_id.clone(),
|
||||
),
|
||||
&addr,
|
||||
&(addr, false),
|
||||
)?;
|
||||
Ok(comment_id)
|
||||
}
|
||||
|
||||
pub fn approve_comment(comment_id: CommentId, dbs: &Dbs) -> Result<(), sled::Error> {
|
||||
if let Some(comment) = dbs.comment.get(&comment_id)? {
|
||||
dbs.comment_pending.remove(&(
|
||||
comment.topic_hash.clone(),
|
||||
comment.post_time,
|
||||
comment_id.clone(),
|
||||
))?;
|
||||
dbs.comment_approved
|
||||
.insert(&(comment.topic_hash, comment.post_time, comment_id), &())?;
|
||||
// TODO when approval disabled
|
||||
pub fn edit_comment(
|
||||
comment_id: CommentId,
|
||||
old_comment: Comment,
|
||||
comment_status: CommentStatus,
|
||||
edited_comment: Comment,
|
||||
addr: Option<IpAddr>,
|
||||
dbs: &Dbs,
|
||||
) -> Result<(), sled::Error> {
|
||||
match comment_status {
|
||||
CommentStatus::Pending => {
|
||||
dbs.comment
|
||||
.insert(&comment_id, &(edited_comment, CommentStatus::Pending))?;
|
||||
// TODO should we update ip address in comment_pending?
|
||||
}
|
||||
CommentStatus::Approved => {
|
||||
dbs.comment_pending.insert(
|
||||
&(
|
||||
edited_comment.topic_hash.clone(),
|
||||
edited_comment.post_time,
|
||||
comment_id.clone(),
|
||||
),
|
||||
&(addr, true),
|
||||
)?;
|
||||
dbs.comment.insert(
|
||||
&comment_id,
|
||||
&(old_comment, CommentStatus::ApprovedEdited(edited_comment)),
|
||||
)?;
|
||||
}
|
||||
CommentStatus::ApprovedEdited(_old_edited_comment) => {
|
||||
dbs.comment.insert(
|
||||
&comment_id,
|
||||
&(old_comment, CommentStatus::ApprovedEdited(edited_comment)),
|
||||
)?;
|
||||
// TODO should we update ip address in comment_pending?
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn remove_comment(comment_id: CommentId, dbs: &Dbs) -> Result<Option<Comment>, sled::Error> {
|
||||
if let Some(comment) = dbs.comment.remove(&comment_id)? {
|
||||
pub fn approve_comment(comment_id: CommentId, dbs: &Dbs) -> Result<(), sled::Error> {
|
||||
if let Some((comment, CommentStatus::Pending)) = dbs.comment.get(&comment_id)? {
|
||||
dbs.comment_pending.remove(&(
|
||||
comment.topic_hash.clone(),
|
||||
comment.post_time,
|
||||
comment_id.clone(),
|
||||
))?;
|
||||
dbs.comment_approved.remove(&(
|
||||
comment.topic_hash.clone(),
|
||||
comment.post_time,
|
||||
comment_id,
|
||||
dbs.comment_approved.insert(
|
||||
&(
|
||||
comment.topic_hash.clone(),
|
||||
comment.post_time,
|
||||
comment_id.clone(),
|
||||
),
|
||||
&(),
|
||||
)?;
|
||||
dbs.comment
|
||||
.insert(&comment_id, &(comment, CommentStatus::Approved))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn approve_edit(comment_id: CommentId, dbs: &Dbs) -> Result<Option<Comment>, sled::Error> {
|
||||
if let Some((comment, CommentStatus::ApprovedEdited(edited_comment))) =
|
||||
dbs.comment.get(&comment_id)?
|
||||
{
|
||||
dbs.comment_pending.remove(&(
|
||||
edited_comment.topic_hash.clone(),
|
||||
edited_comment.post_time,
|
||||
comment_id.clone(),
|
||||
))?;
|
||||
dbs.comment
|
||||
.insert(&comment_id, &(edited_comment, CommentStatus::Approved))?;
|
||||
return Ok(Some(comment));
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
pub fn remove_comment(
|
||||
comment_id: CommentId,
|
||||
dbs: &Dbs,
|
||||
) -> Result<Option<(Comment, CommentStatus)>, sled::Error> {
|
||||
if let Some((comment, edited_comment)) = dbs.comment.remove(&comment_id)? {
|
||||
match &edited_comment {
|
||||
CommentStatus::Pending => {
|
||||
dbs.comment_pending.remove(&(
|
||||
comment.topic_hash.clone(),
|
||||
comment.post_time,
|
||||
comment_id,
|
||||
))?;
|
||||
}
|
||||
CommentStatus::Approved => {
|
||||
dbs.comment_approved.remove(&(
|
||||
comment.topic_hash.clone(),
|
||||
comment.post_time,
|
||||
comment_id,
|
||||
))?;
|
||||
}
|
||||
CommentStatus::ApprovedEdited(edited_comment) => {
|
||||
dbs.comment_pending.remove(&(
|
||||
edited_comment.topic_hash.clone(),
|
||||
edited_comment.post_time,
|
||||
comment_id.clone(),
|
||||
))?;
|
||||
dbs.comment_approved.remove(&(
|
||||
comment.topic_hash.clone(),
|
||||
comment.post_time,
|
||||
comment_id,
|
||||
))?;
|
||||
}
|
||||
}
|
||||
return Ok(Some((comment, edited_comment)));
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
pub fn remove_edit(comment_id: CommentId, dbs: &Dbs) -> Result<Option<Comment>, sled::Error> {
|
||||
if let Some((comment, CommentStatus::ApprovedEdited(edited_comment))) =
|
||||
dbs.comment.get(&comment_id)?
|
||||
{
|
||||
dbs.comment_pending.remove(&(
|
||||
edited_comment.topic_hash.clone(),
|
||||
edited_comment.post_time,
|
||||
comment_id.clone(),
|
||||
))?;
|
||||
dbs.comment
|
||||
.insert(&comment_id, &(comment.clone(), CommentStatus::Approved))?;
|
||||
return Ok(Some(comment));
|
||||
}
|
||||
Ok(None)
|
||||
|
|
@ -57,15 +159,15 @@ pub fn iter_comments_by_topic<'a, V: typed_sled::KV>(
|
|||
topic_hash: TopicHash,
|
||||
tree: &'a Tree<(TopicHash, Time, CommentId), V>,
|
||||
dbs: &'a Dbs,
|
||||
) -> impl Iterator<Item = (CommentId, Comment, V)> + 'a {
|
||||
) -> impl Iterator<Item = (CommentId, Comment, CommentStatus, V)> + 'a {
|
||||
tree.range(
|
||||
(topic_hash.clone(), 0, CommentId::zero())..=(topic_hash, Time::MAX, CommentId::max()),
|
||||
)
|
||||
.filter_map(|entry| {
|
||||
let ((_topic_hash, _time, comment_id), val) = entry
|
||||
.map_err(|e| error!("Reading comment_by_topic_and_time: {:?}", e))
|
||||
.map_err(|e| error!("Reading comment index: {:?}", e))
|
||||
.ok()?;
|
||||
let comment = dbs
|
||||
let (comment, comment_status) = dbs
|
||||
.comment
|
||||
.get(&comment_id)
|
||||
.map_err(|e| error!("Reading comment: {:?}", e))
|
||||
|
|
@ -74,23 +176,27 @@ pub fn iter_comments_by_topic<'a, V: typed_sled::KV>(
|
|||
error!("Comment not found");
|
||||
None
|
||||
})?;
|
||||
Some((comment_id, comment, val))
|
||||
Some((comment_id, comment, comment_status, val))
|
||||
})
|
||||
}
|
||||
|
||||
pub fn iter_approved_comments_by_topic(
|
||||
topic_hash: TopicHash,
|
||||
dbs: &Dbs,
|
||||
) -> impl Iterator<Item = (CommentId, Comment)> + '_ {
|
||||
) -> impl Iterator<Item = (CommentId, Comment, CommentStatus)> + '_ {
|
||||
iter_comments_by_topic(topic_hash, &dbs.comment_approved, dbs)
|
||||
.map(|(comment_id, comment, ())| (comment_id, comment))
|
||||
.map(|(comment_id, comment, comment_status, ())| (comment_id, comment, comment_status))
|
||||
}
|
||||
|
||||
pub fn iter_pending_comments_by_topic(
|
||||
topic_hash: TopicHash,
|
||||
dbs: &Dbs,
|
||||
) -> impl Iterator<Item = (CommentId, Comment, Option<IpAddr>)> + '_ {
|
||||
iter_comments_by_topic(topic_hash, &dbs.comment_pending, dbs)
|
||||
) -> impl Iterator<Item = (CommentId, Comment, Option<IpAddr>, CommentStatus)> + '_ {
|
||||
iter_comments_by_topic(topic_hash, &dbs.comment_pending, dbs).map(
|
||||
|(comment_id, comment, comment_status, (addr, _is_edit))| {
|
||||
(comment_id, comment, addr, comment_status)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns Some(time_left) if the client is banned.
|
||||
|
|
@ -248,9 +354,11 @@ mod test {
|
|||
|
||||
#[test]
|
||||
fn test_comment() {
|
||||
// Post a comment
|
||||
|
||||
let comment = Comment {
|
||||
topic_hash: TopicHash::from_topic("test"),
|
||||
author: String::from("Emmanuel Goldstein"),
|
||||
author: String::from("Jerry"),
|
||||
email: None,
|
||||
last_edit_time: None,
|
||||
mutation_token: MutationToken::new(),
|
||||
|
|
@ -262,7 +370,13 @@ mod test {
|
|||
let comment_id = new_pending_comment(&comment, None, &dbs).unwrap();
|
||||
|
||||
let mut iter = dbs.comment.iter();
|
||||
assert_eq!(iter.next(), Some(Ok((comment_id.clone(), comment.clone()))));
|
||||
assert_eq!(
|
||||
iter.next(),
|
||||
Some(Ok((
|
||||
comment_id.clone(),
|
||||
(comment.clone(), CommentStatus::Pending)
|
||||
)))
|
||||
);
|
||||
assert_eq!(iter.next(), None);
|
||||
|
||||
let mut iter = dbs.comment_pending.iter();
|
||||
|
|
@ -274,13 +388,265 @@ mod test {
|
|||
comment.post_time,
|
||||
comment_id.clone()
|
||||
),
|
||||
None
|
||||
(None, false)
|
||||
)))
|
||||
);
|
||||
assert_eq!(iter.next(), None);
|
||||
|
||||
// Edit the comment
|
||||
|
||||
let comment2 = Comment {
|
||||
topic_hash: TopicHash::from_topic("test"),
|
||||
author: String::from("Jerry Smith"),
|
||||
email: Some(String::from("jerry.smith@example.tld")),
|
||||
last_edit_time: Some(137),
|
||||
mutation_token: comment.mutation_token.clone(),
|
||||
post_time: 42,
|
||||
text: String::from("Good bye world!"),
|
||||
};
|
||||
|
||||
edit_comment(
|
||||
comment_id.clone(),
|
||||
comment.clone(),
|
||||
CommentStatus::Pending,
|
||||
comment2.clone(),
|
||||
None,
|
||||
&dbs,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let mut iter = dbs.comment.iter();
|
||||
assert_eq!(
|
||||
iter.next(),
|
||||
Some(Ok((
|
||||
comment_id.clone(),
|
||||
(comment2.clone(), CommentStatus::Pending)
|
||||
)))
|
||||
);
|
||||
assert_eq!(iter.next(), None);
|
||||
|
||||
let mut iter = dbs.comment_pending.iter();
|
||||
assert_eq!(
|
||||
iter.next(),
|
||||
Some(Ok((
|
||||
(
|
||||
comment.topic_hash.clone(),
|
||||
comment.post_time,
|
||||
comment_id.clone()
|
||||
),
|
||||
(None, false)
|
||||
)))
|
||||
);
|
||||
assert_eq!(iter.next(), None);
|
||||
|
||||
// Approve the comment
|
||||
|
||||
approve_comment(comment_id.clone(), &dbs).unwrap();
|
||||
|
||||
let mut iter = dbs.comment.iter();
|
||||
assert_eq!(
|
||||
iter.next(),
|
||||
Some(Ok((
|
||||
comment_id.clone(),
|
||||
(comment2.clone(), CommentStatus::Approved)
|
||||
)))
|
||||
);
|
||||
assert_eq!(iter.next(), None);
|
||||
|
||||
let mut iter = dbs.comment_pending.iter();
|
||||
assert_eq!(iter.next(), None);
|
||||
|
||||
let mut iter = dbs.comment_approved.iter();
|
||||
assert_eq!(
|
||||
iter.next(),
|
||||
Some(Ok((
|
||||
(
|
||||
comment.topic_hash.clone(),
|
||||
comment.post_time,
|
||||
comment_id.clone()
|
||||
),
|
||||
()
|
||||
)))
|
||||
);
|
||||
assert_eq!(iter.next(), None);
|
||||
|
||||
// Edit the approved comment
|
||||
|
||||
let comment3 = Comment {
|
||||
topic_hash: TopicHash::from_topic("test"),
|
||||
author: String::from("Jerry Smith is back"),
|
||||
email: Some(String::from("jerry.smith@example.tld")),
|
||||
last_edit_time: Some(666),
|
||||
mutation_token: comment.mutation_token.clone(),
|
||||
post_time: 42,
|
||||
text: String::from("Hello again!"),
|
||||
};
|
||||
|
||||
edit_comment(
|
||||
comment_id.clone(),
|
||||
comment2.clone(),
|
||||
CommentStatus::Approved,
|
||||
comment3.clone(),
|
||||
None,
|
||||
&dbs,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let mut iter = dbs.comment.iter();
|
||||
assert_eq!(
|
||||
iter.next(),
|
||||
Some(Ok((
|
||||
comment_id.clone(),
|
||||
(
|
||||
comment2.clone(),
|
||||
CommentStatus::ApprovedEdited(comment3.clone())
|
||||
)
|
||||
)))
|
||||
);
|
||||
assert_eq!(iter.next(), None);
|
||||
|
||||
let mut iter = dbs.comment_pending.iter();
|
||||
assert_eq!(
|
||||
iter.next(),
|
||||
Some(Ok((
|
||||
(
|
||||
comment.topic_hash.clone(),
|
||||
comment.post_time,
|
||||
comment_id.clone()
|
||||
),
|
||||
(None, true)
|
||||
)))
|
||||
);
|
||||
assert_eq!(iter.next(), None);
|
||||
|
||||
let mut iter = dbs.comment_approved.iter();
|
||||
assert_eq!(
|
||||
iter.next(),
|
||||
Some(Ok((
|
||||
(
|
||||
comment.topic_hash.clone(),
|
||||
comment.post_time,
|
||||
comment_id.clone()
|
||||
),
|
||||
()
|
||||
)))
|
||||
);
|
||||
assert_eq!(iter.next(), None);
|
||||
|
||||
// Edit the edited approved comment
|
||||
|
||||
let comment4 = Comment {
|
||||
topic_hash: TopicHash::from_topic("test"),
|
||||
author: String::from("Jerry Smith is still back"),
|
||||
email: Some(String::from("jerry.smith@example.tld")),
|
||||
last_edit_time: Some(1337),
|
||||
mutation_token: comment.mutation_token.clone(),
|
||||
post_time: 42,
|
||||
text: String::from("Hello again one more time!"),
|
||||
};
|
||||
|
||||
edit_comment(
|
||||
comment_id.clone(),
|
||||
comment2.clone(),
|
||||
CommentStatus::ApprovedEdited(comment3.clone()),
|
||||
comment4.clone(),
|
||||
None,
|
||||
&dbs,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let mut iter = dbs.comment.iter();
|
||||
assert_eq!(
|
||||
iter.next(),
|
||||
Some(Ok((
|
||||
comment_id.clone(),
|
||||
(comment2, CommentStatus::ApprovedEdited(comment4.clone()))
|
||||
)))
|
||||
);
|
||||
assert_eq!(iter.next(), None);
|
||||
|
||||
let mut iter = dbs.comment_pending.iter();
|
||||
assert_eq!(
|
||||
iter.next(),
|
||||
Some(Ok((
|
||||
(
|
||||
comment.topic_hash.clone(),
|
||||
comment.post_time,
|
||||
comment_id.clone()
|
||||
),
|
||||
(None, true)
|
||||
)))
|
||||
);
|
||||
assert_eq!(iter.next(), None);
|
||||
|
||||
let mut iter = dbs.comment_approved.iter();
|
||||
assert_eq!(
|
||||
iter.next(),
|
||||
Some(Ok((
|
||||
(
|
||||
comment.topic_hash.clone(),
|
||||
comment.post_time,
|
||||
comment_id.clone()
|
||||
),
|
||||
()
|
||||
)))
|
||||
);
|
||||
assert_eq!(iter.next(), None);
|
||||
|
||||
// Approve the edit
|
||||
|
||||
approve_edit(comment_id.clone(), &dbs).unwrap();
|
||||
|
||||
let mut iter = dbs.comment.iter();
|
||||
assert_eq!(
|
||||
iter.next(),
|
||||
Some(Ok((
|
||||
comment_id.clone(),
|
||||
(comment4.clone(), CommentStatus::Approved)
|
||||
)))
|
||||
);
|
||||
assert_eq!(iter.next(), None);
|
||||
|
||||
let mut iter = dbs.comment_pending.iter();
|
||||
assert_eq!(iter.next(), None);
|
||||
|
||||
let mut iter = dbs.comment_approved.iter();
|
||||
assert_eq!(
|
||||
iter.next(),
|
||||
Some(Ok((
|
||||
(
|
||||
comment.topic_hash.clone(),
|
||||
comment.post_time,
|
||||
comment_id.clone()
|
||||
),
|
||||
()
|
||||
)))
|
||||
);
|
||||
assert_eq!(iter.next(), None);
|
||||
|
||||
// Edit and remove the edit
|
||||
|
||||
edit_comment(
|
||||
comment_id.clone(),
|
||||
comment4.clone(),
|
||||
CommentStatus::Approved,
|
||||
comment.clone(),
|
||||
None,
|
||||
&dbs,
|
||||
)
|
||||
.unwrap();
|
||||
remove_edit(comment_id.clone(), &dbs).unwrap();
|
||||
|
||||
let mut iter = dbs.comment.iter();
|
||||
assert_eq!(
|
||||
iter.next(),
|
||||
Some(Ok((
|
||||
comment_id.clone(),
|
||||
(comment4.clone(), CommentStatus::Approved)
|
||||
)))
|
||||
);
|
||||
assert_eq!(iter.next(), None);
|
||||
|
||||
let mut iter = dbs.comment_pending.iter();
|
||||
assert_eq!(iter.next(), None);
|
||||
|
||||
|
|
|
|||
|
|
@ -65,11 +65,21 @@ pub struct ApproveQuery {
|
|||
pub approve: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
pub struct ApproveEditQuery {
|
||||
pub approve_edit: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
pub struct RemoveQuery {
|
||||
pub remove: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
pub struct RemoveEditQuery {
|
||||
pub remove_edit: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
pub struct EditQuery {
|
||||
pub edit: String,
|
||||
|
|
|
|||
156
src/server.rs
156
src/server.rs
|
|
@ -119,7 +119,7 @@ async fn serve_edit_comment<'a>(
|
|||
return serve_comments(req, config, templates, dbs, client_langs, context, 400).await;
|
||||
};
|
||||
|
||||
let Some(comment) = dbs.comment.get(&comment_id).unwrap() else {
|
||||
let Some((comment, _edited_comment)) = dbs.comment.get(&comment_id).unwrap() else {
|
||||
context.insert("log", &["not found comment"]);
|
||||
return serve_comments(req, config, templates, dbs, client_langs, context, 404).await;
|
||||
};
|
||||
|
|
@ -189,6 +189,13 @@ async fn serve_comments<'a>(
|
|||
.ok();
|
||||
}
|
||||
}
|
||||
if let Ok(query) = req.query::<ApproveEditQuery>() {
|
||||
if let Ok(comment_id) = CommentId::from_base64(&query.approve_edit) {
|
||||
helpers::approve_edit(comment_id, &dbs)
|
||||
.map_err(|e| error!("Approving edit: {:?}", e))
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
if let Ok(query) = req.query::<RemoveQuery>() {
|
||||
if let Ok(comment_id) = CommentId::from_base64(&query.remove) {
|
||||
helpers::remove_comment(comment_id, &dbs)
|
||||
|
|
@ -196,9 +203,16 @@ async fn serve_comments<'a>(
|
|||
.ok();
|
||||
}
|
||||
}
|
||||
if let Ok(query) = req.query::<RemoveEditQuery>() {
|
||||
if let Ok(comment_id) = CommentId::from_base64(&query.remove_edit) {
|
||||
helpers::remove_edit(comment_id, &dbs)
|
||||
.map_err(|e| error!("Removing edit: {:?}", e))
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
if let Ok(query) = req.query::<EditQuery>() {
|
||||
if let Ok(comment_id) = CommentId::from_base64(&query.edit) {
|
||||
if let Some(comment) = dbs.comment.get(&comment_id).unwrap() {
|
||||
if let Some((comment, _comment_status)) = dbs.comment.get(&comment_id).unwrap() {
|
||||
context.insert("edit_comment", &comment_id.to_base64());
|
||||
context.insert("edit_comment_author", &comment.author);
|
||||
context.insert("edit_comment_email", &comment.email);
|
||||
|
|
@ -210,14 +224,38 @@ async fn serve_comments<'a>(
|
|||
context.insert(
|
||||
"comments_pending",
|
||||
&helpers::iter_pending_comments_by_topic(topic_hash.clone(), &dbs)
|
||||
.map(|(comment_id, comment, addr)| CommentWithId {
|
||||
addr: addr.map(|addr| addr.to_string()),
|
||||
author: comment.author,
|
||||
editable: admin,
|
||||
id: comment_id.to_base64(),
|
||||
needs_approval: true,
|
||||
post_time: comment.post_time,
|
||||
text: comment.text,
|
||||
.map(|(comment_id, comment, addr, comment_status)| {
|
||||
if let CommentStatus::ApprovedEdited(edited_comment) = comment_status {
|
||||
CommentWithId {
|
||||
addr: addr.map(|addr| addr.to_string()),
|
||||
author: edited_comment.author,
|
||||
editable: true,
|
||||
id: comment_id.to_base64(),
|
||||
last_edit_time: edited_comment.last_edit_time,
|
||||
needs_approval: true,
|
||||
original: Some(OriginalComment {
|
||||
author: comment.author,
|
||||
editable: true,
|
||||
last_edit_time: comment.last_edit_time,
|
||||
post_time: comment.post_time,
|
||||
text: comment.text,
|
||||
}),
|
||||
post_time: edited_comment.post_time,
|
||||
text: edited_comment.text,
|
||||
}
|
||||
} else {
|
||||
CommentWithId {
|
||||
addr: addr.map(|addr| addr.to_string()),
|
||||
author: comment.author,
|
||||
editable: true,
|
||||
id: comment_id.to_base64(),
|
||||
last_edit_time: comment.last_edit_time,
|
||||
needs_approval: true,
|
||||
original: None,
|
||||
post_time: comment.post_time,
|
||||
text: comment.text,
|
||||
}
|
||||
}
|
||||
})
|
||||
.collect::<Vec<CommentWithId>>(),
|
||||
);
|
||||
|
|
@ -226,12 +264,14 @@ async fn serve_comments<'a>(
|
|||
context.insert(
|
||||
"comments",
|
||||
&helpers::iter_approved_comments_by_topic(topic_hash, &dbs)
|
||||
.map(|(comment_id, comment)| CommentWithId {
|
||||
.map(|(comment_id, comment, _comment_status)| CommentWithId {
|
||||
addr: None,
|
||||
author: comment.author,
|
||||
editable: admin,
|
||||
id: comment_id.to_base64(),
|
||||
last_edit_time: comment.last_edit_time,
|
||||
needs_approval: false,
|
||||
original: None,
|
||||
post_time: comment.post_time,
|
||||
text: comment.text,
|
||||
})
|
||||
|
|
@ -272,10 +312,10 @@ async fn serve_admin<'a>(
|
|||
&dbs.comment_pending
|
||||
.iter()
|
||||
.filter_map(|entry| {
|
||||
let ((_topic_hash, _time, comment_id), addr) = entry
|
||||
let ((_topic_hash, _time, comment_id), (addr, _is_edit)) = entry
|
||||
.map_err(|e| error!("Reading comment_pending: {:?}", e))
|
||||
.ok()?;
|
||||
let comment = dbs
|
||||
let (comment, comment_status) = dbs
|
||||
.comment
|
||||
.get(&comment_id)
|
||||
.map_err(|e| error!("Reading comment: {:?}", e))
|
||||
|
|
@ -284,15 +324,37 @@ async fn serve_admin<'a>(
|
|||
error!("Comment not found");
|
||||
None
|
||||
})?;
|
||||
Some(CommentWithId {
|
||||
addr: addr.map(|addr| addr.to_string()),
|
||||
author: comment.author,
|
||||
editable: true,
|
||||
id: comment_id.to_base64(),
|
||||
needs_approval: true,
|
||||
post_time: comment.post_time,
|
||||
text: comment.text,
|
||||
})
|
||||
if let CommentStatus::ApprovedEdited(edited_comment) = comment_status {
|
||||
Some(CommentWithId {
|
||||
addr: addr.map(|addr| addr.to_string()),
|
||||
author: edited_comment.author,
|
||||
editable: true,
|
||||
id: comment_id.to_base64(),
|
||||
last_edit_time: edited_comment.last_edit_time,
|
||||
needs_approval: true,
|
||||
original: Some(OriginalComment {
|
||||
author: comment.author,
|
||||
editable: true,
|
||||
last_edit_time: comment.last_edit_time,
|
||||
post_time: comment.post_time,
|
||||
text: comment.text,
|
||||
}),
|
||||
post_time: edited_comment.post_time,
|
||||
text: edited_comment.text,
|
||||
})
|
||||
} else {
|
||||
Some(CommentWithId {
|
||||
addr: addr.map(|addr| addr.to_string()),
|
||||
author: comment.author,
|
||||
editable: true,
|
||||
id: comment_id.to_base64(),
|
||||
last_edit_time: comment.last_edit_time,
|
||||
needs_approval: true,
|
||||
original: None,
|
||||
post_time: comment.post_time,
|
||||
text: comment.text,
|
||||
})
|
||||
}
|
||||
})
|
||||
.collect::<Vec<CommentWithId>>(),
|
||||
);
|
||||
|
|
@ -456,6 +518,7 @@ async fn handle_post_comments(
|
|||
.unwrap()],
|
||||
);
|
||||
}
|
||||
// TODO add message to client log and change http code
|
||||
Err(e) => error!("Adding pending comment: {:?}", e),
|
||||
}
|
||||
} else {
|
||||
|
|
@ -466,16 +529,20 @@ async fn handle_post_comments(
|
|||
context.insert("new_comment_errors", &errors);
|
||||
}
|
||||
CommentQuery::EditComment(query) => {
|
||||
helpers::check_comment(config, locales, &client_langs, &query.comment, &mut errors);
|
||||
let Ok(topic) = req.param("topic") else {
|
||||
return Err(tide::Error::from_str(404, "No topic"))
|
||||
};
|
||||
|
||||
let Ok(comment_id) = CommentId::from_base64(&query.id) else {
|
||||
return Err(tide::Error::from_str(400, "Invalid comment id"));
|
||||
};
|
||||
|
||||
let Some(mut comment) = dbs.comment.get(&comment_id).unwrap() else {
|
||||
let Some((old_comment, old_edited_comment)) = dbs.comment.get(&comment_id).unwrap() else {
|
||||
return Err(tide::Error::from_str(404, "Not found"));
|
||||
};
|
||||
|
||||
helpers::check_comment(config, locales, &client_langs, &query.comment, &mut errors);
|
||||
|
||||
let mutation_token = if admin {
|
||||
None
|
||||
} else {
|
||||
|
|
@ -491,7 +558,7 @@ async fn handle_post_comments(
|
|||
};
|
||||
|
||||
if let Err(e) =
|
||||
helpers::check_can_edit_comment(config, &comment, &mutation_token)
|
||||
helpers::check_can_edit_comment(config, &old_comment, &mutation_token)
|
||||
{
|
||||
errors.push(e.to_string());
|
||||
}
|
||||
|
|
@ -535,6 +602,8 @@ async fn handle_post_comments(
|
|||
.unwrap()
|
||||
.as_secs();
|
||||
|
||||
let mut comment = old_comment.clone();
|
||||
|
||||
comment.author = if query.comment.author.is_empty() {
|
||||
petname::Petnames::large().generate_one(2, " ")
|
||||
} else {
|
||||
|
|
@ -548,7 +617,42 @@ async fn handle_post_comments(
|
|||
comment.text = query.comment.text;
|
||||
comment.last_edit_time = Some(time);
|
||||
|
||||
dbs.comment.insert(&comment_id, &comment).unwrap();
|
||||
match helpers::edit_comment(
|
||||
comment_id.clone(),
|
||||
old_comment,
|
||||
old_edited_comment,
|
||||
comment.clone(),
|
||||
client_addr,
|
||||
&dbs,
|
||||
) {
|
||||
Ok(()) => {
|
||||
context.insert(
|
||||
"log",
|
||||
&[locales
|
||||
.tr(
|
||||
&client_langs,
|
||||
if config.comment_approve {
|
||||
"edit_comment-success_pending"
|
||||
} else {
|
||||
"edit_comment-success"
|
||||
},
|
||||
Some(&FluentArgs::from_iter([(
|
||||
"edit_link",
|
||||
format!(
|
||||
"{}t/{}/edit/{}/{}",
|
||||
&config.root_url,
|
||||
topic,
|
||||
comment_id.to_base64(),
|
||||
comment.mutation_token.to_base64(),
|
||||
),
|
||||
)])),
|
||||
)
|
||||
.unwrap()],
|
||||
);
|
||||
}
|
||||
// TODO add message to client log and change http code
|
||||
Err(e) => error!("Editing comment: {:?}", e),
|
||||
}
|
||||
} else {
|
||||
context.insert("edit_comment", &comment_id.to_base64());
|
||||
if let Some(mutation_token) = &mutation_token {
|
||||
|
|
|
|||
|
|
@ -41,7 +41,18 @@ pub struct CommentWithId {
|
|||
pub author: String,
|
||||
pub editable: bool,
|
||||
pub id: String,
|
||||
pub last_edit_time: Option<Time>,
|
||||
pub needs_approval: bool,
|
||||
pub original: Option<OriginalComment>,
|
||||
pub post_time: Time,
|
||||
pub text: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
pub struct OriginalComment {
|
||||
pub author: String,
|
||||
pub editable: bool,
|
||||
pub last_edit_time: Option<Time>,
|
||||
pub post_time: Time,
|
||||
pub text: String,
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue