Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion api_tests/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
"eslint-plugin-prettier": "^5.5.0",
"jest": "^30.0.0",
"joi": "^18.0.0",
"lemmy-js-client": "1.0.0-rename-community-tag.0",
"lemmy-js-client": "1.0.0-remove-children.0",
"lemmy-js-client-019": "npm:lemmy-js-client@0.19.9",
"prettier": "^3.5.3",
"ts-jest": "^29.4.0",
Expand Down
10 changes: 5 additions & 5 deletions api_tests/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

60 changes: 60 additions & 0 deletions api_tests/src/comment.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
reportComment,
randomString,
unfollows,
getComment,
getComments,
getCommentParentId,
resolveCommunity,
Expand Down Expand Up @@ -981,6 +982,65 @@ test("Lock comment", async () => {
).toBeDefined();
});

test("Remove children", async () => {
const alphaCommunity = await resolveCommunity(
alpha,
"!main@lemmy-alpha:8541",
);
if (!alphaCommunity) {
throw "Missing alpha community";
}

let post = await createPost(alpha, alphaCommunity.community.id);
let betaPost = await resolvePost(beta, post.post_view.post);

if (!betaPost) {
throw "unable to locate post on beta";
}
await followCommunity(beta, true, betaPost.community.id);

let comment1 = await createComment(beta, betaPost.post.id);
let comment2 = await createComment(
beta,
betaPost.post.id,
comment1.comment_view.comment.id,
);
await createComment(beta, betaPost.post.id, comment2.comment_view.comment.id);
await createComment(beta, betaPost.post.id, comment1.comment_view.comment.id);

// Wait until the comments have federated
await waitUntil(
() => getPost(alpha, post.post_view.post.id),
p => p.post_view.post.comments == 4,
);

let commentOnAlpha = await resolveComment(
alpha,
comment1.comment_view.comment,
);
if (!commentOnAlpha) {
throw "unable to locate comment on alpha";
}

await removeComment(alpha, true, commentOnAlpha.comment.id, true);

let post2 = await getPost(alpha, post.post_view.post.id);
expect(post2.post_view.post.comments).toBe(0);

// Wait until the remove has federated
await waitUntil(
() => getComment(beta, comment1.comment_view.comment.id),
c => c.comment_view.comment.removed,
);

// Make sure removal federates properly
let betaPost2 = await resolvePost(beta, post.post_view.post);
if (!betaPost2) {
throw "unable to locate post on beta";
}
expect(betaPost2.post.comments).toBe(0);
});

function checkCommentReportReason(rcv: ReportCombinedView, reason: string) {
switch (rcv.type_) {
case "comment":
Expand Down
13 changes: 13 additions & 0 deletions api_tests/src/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ import {
EditSite,
FeaturePost,
FollowCommunity,
GetComment,
GetComments,
GetCommunity,
GetCommunityResponse,
Expand Down Expand Up @@ -381,6 +382,16 @@ export async function lockComment(
return api.lockComment(form);
}

export async function getComment(
api: LemmyHttp,
comment_id: number,
): Promise<CommentResponse> {
let form: GetComment = {
id: comment_id,
};
return api.getComment(form);
}

export async function getComments(
api: LemmyHttp,
post_id?: number,
Expand Down Expand Up @@ -575,11 +586,13 @@ export async function removeComment(
api: LemmyHttp,
removed: boolean,
comment_id: number,
remove_children?: boolean,
): Promise<CommentResponse> {
let form: RemoveComment = {
comment_id,
removed,
reason: "remove",
remove_children,
};
return api.removeComment(form);
}
Expand Down
1 change: 1 addition & 0 deletions crates/api/api/src/site/purge/comment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ pub async fn purge_comment(
moderator: local_user_view.person.clone(),
community: comment_view.community,
reason: data.reason.clone(),
with_replies: false,
},
&context,
)?;
Expand Down
1 change: 1 addition & 0 deletions crates/api/api/src/site/purge/post.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ pub async fn purge_post(
moderator: local_user_view.person.clone(),
reason: data.reason.clone(),
removed: true,
with_replies: false,
},
&context,
)?;
Expand Down
102 changes: 75 additions & 27 deletions crates/api/api_crud/src/comment/remove.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,36 +55,83 @@ pub async fn remove_comment(
)
.await?;

// Don't allow removing or restoring comment which was deleted by user, as it would reveal
// the comment text in mod log.
if orig_comment.comment.deleted {
return Err(LemmyErrorType::CouldntUpdate.into());
}

// Do the remove
let removed = data.removed;
let updated_comment = Comment::update(
&mut context.pool(),
comment_id,
&CommentUpdateForm {
removed: Some(removed),
..Default::default()
},
)
.await?;
let updated_comment = if let Some(remove_children) = data.remove_children {
let updated_comments: Vec<Comment> = Comment::update_removed_for_comment_and_children(
&mut context.pool(),
&orig_comment.comment.path,
remove_children,
)
.await?;

let updated_comment = updated_comments
.iter()
.find(|c| c.id == comment_id)
.ok_or(LemmyErrorType::CouldntUpdate)?
.clone();

let forms: Vec<_> = updated_comments
.iter()
// Filter out deleted comments here so their content doesn't show up in the modlog.
.filter(|c| !c.deleted)
.map(|comment| {
ModlogInsertForm::mod_remove_comment(
local_user_view.person.id,
comment,
remove_children,
&data.reason,
)
})
.collect();

let actions = Modlog::create(&mut context.pool(), &forms).await?;
notify_mod_action(actions, &context);

CommentReport::resolve_all_for_object(&mut context.pool(), comment_id, local_user_view.person.id)
CommentReport::resolve_all_for_thread(
&mut context.pool(),
&orig_comment.comment.path,
local_user_view.person.id,
)
.await?;

// Mod tables
let form = ModlogInsertForm::mod_remove_comment(
local_user_view.person.id,
&orig_comment.comment,
removed,
&data.reason,
);
let actions = Modlog::create(&mut context.pool(), &[form]).await?;
notify_mod_action(actions, context.app_data());
updated_comment
} else {
// Don't allow removing or restoring comment which was deleted by user, as it would reveal
// the comment text in mod log.
if orig_comment.comment.deleted {
return Err(LemmyErrorType::CouldntUpdate.into());
}
Comment on lines +97 to +102
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this in an else block now? Then you'll be able to reveal the comment text in the modlog as long as delete children is true.

Probably just keep this top level.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I figured it'd be useful to be able to remove the replies to a deleted comment.


// Do the remove
let removed = data.removed;
let updated_comment = Comment::update(
&mut context.pool(),
comment_id,
&CommentUpdateForm {
removed: Some(removed),
..Default::default()
},
)
.await?;

CommentReport::resolve_all_for_object(
&mut context.pool(),
comment_id,
local_user_view.person.id,
)
.await?;

// Mod tables
let form = ModlogInsertForm::mod_remove_comment(
local_user_view.person.id,
&orig_comment.comment,
removed,
&data.reason,
);
let actions = Modlog::create(&mut context.pool(), &[form]).await?;
Comment on lines +124 to +130
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then we aren't creating a bunch of rows for each of these? I notice lock also works like this so its probably fine. I guess that's #6323

notify_mod_action(actions, context.app_data());

updated_comment
};

let updated_comment_id = updated_comment.id;

Expand All @@ -94,6 +141,7 @@ pub async fn remove_comment(
moderator: local_user_view.person.clone(),
community: orig_comment.community,
reason: data.reason.clone(),
with_replies: data.remove_children.unwrap_or_default(),
},
&context,
)?;
Expand Down
35 changes: 31 additions & 4 deletions crates/api/api_crud/src/post/remove.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ use lemmy_api_utils::{
};
use lemmy_db_schema::{
source::{
comment::Comment,
comment_report::CommentReport,
community::Community,
local_user::LocalUser,
modlog::{Modlog, ModlogInsertForm},
Expand All @@ -28,6 +30,7 @@ pub async fn remove_post(
local_user_view: LocalUserView,
) -> LemmyResult<Json<PostResponse>> {
let post_id = data.post_id;
let removed = data.remove_children.unwrap_or(data.removed);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Potentially dangerous, you can remove, see below comment.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unwrap_or is safe, though? I do this here to enforce the aforementioned problem with federation, otherwise only the comments will get removed locally but on federated instances the post be removed as well.


// We cannot use PostView to avoid a database read here, as it doesn't return removed items
// by default. So we would have to pass in `is_mod_or_admin`, but that is impossible without
Expand All @@ -46,8 +49,6 @@ pub async fn remove_post(
.await?;

// Update the post
let post_id = data.post_id;
let removed = data.removed;
let post = Post::update(
&mut context.pool(),
post_id,
Expand All @@ -67,15 +68,41 @@ pub async fn remove_post(
let action = Modlog::create(&mut context.pool(), &[form]).await?;
notify_mod_action(action, context.app_data());

if data.remove_children.is_some() {
let updated_comments: Vec<Comment> =
Comment::update_removed_for_post(&mut context.pool(), post_id, removed).await?;

let forms: Vec<_> = updated_comments
.iter()
// Filter out deleted comments here so their content doesn't show up in the modlog.
.filter(|c| !c.deleted)
.map(|comment| {
ModlogInsertForm::mod_remove_comment(
local_user_view.person.id,
comment,
removed,
&data.reason,
)
})
.collect();

let actions = Modlog::create(&mut context.pool(), &forms).await?;
notify_mod_action(actions, &context);

CommentReport::resolve_all_for_post(&mut context.pool(), post.id, local_user_view.person.id)
.await?;
}

ActivityChannel::submit_activity(
SendActivityData::RemovePost {
post,
moderator: local_user_view.person.clone(),
reason: data.reason.clone(),
removed: data.removed,
removed,
with_replies: data.remove_children.unwrap_or_default(),
},
&context,
)?;

build_post_response(&context, orig_post.community_id, local_user_view, post_id).await
build_post_response(&context, community.id, local_user_view, post_id).await
}
2 changes: 2 additions & 0 deletions crates/api/api_utils/src/send_activity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ pub enum SendActivityData {
moderator: Person,
reason: String,
removed: bool,
with_replies: bool,
},
LockPost(Post, Person, bool, String),
FeaturePost(Post, Person, bool),
Expand All @@ -50,6 +51,7 @@ pub enum SendActivityData {
moderator: Person,
community: Community,
reason: String,
with_replies: bool,
},
LockComment(Comment, Person, bool, String),
LikePostOrComment {
Expand Down
Loading