mirror of
https://github.com/wassname/talk.git
synced 2026-06-27 17:50:42 +08:00
[CORL-1180] Moderation + Editing Improvements (#3020)
* fix: handle moderating edited comments better * fix: add back deleted audit trail entry Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
This commit is contained in:
@@ -47,7 +47,7 @@ const ApproveCommentMutation = createMutation(
|
||||
}
|
||||
}
|
||||
}
|
||||
...ModeratedByContainer_comment
|
||||
...ModerateCardContainer_comment
|
||||
}
|
||||
moderationQueues(
|
||||
storyID: $storyID
|
||||
@@ -83,7 +83,22 @@ const ApproveCommentMutation = createMutation(
|
||||
proxy.setValue("APPROVED", "status");
|
||||
proxy.setValue(true, "viewerDidModerate");
|
||||
},
|
||||
updater: (store) => {
|
||||
updater: (store, data) => {
|
||||
// If no comment came back or the returned comment status was the same,
|
||||
// don't remove it from the connections!
|
||||
if (
|
||||
!data.approveComment ||
|
||||
!data.approveComment.comment ||
|
||||
data.approveComment.comment.status !== "APPROVED"
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Ensure that the comment retains the viewerDidModerate state after it
|
||||
// comes back from the update.
|
||||
const proxy = store.get(input.commentID)!;
|
||||
proxy.setValue(true, "viewerDidModerate");
|
||||
|
||||
const connections = [
|
||||
getQueueConnection(
|
||||
store,
|
||||
|
||||
@@ -47,7 +47,7 @@ const RejectCommentMutation = createMutation(
|
||||
}
|
||||
}
|
||||
}
|
||||
...ModeratedByContainer_comment
|
||||
...ModerateCardContainer_comment
|
||||
}
|
||||
moderationQueues(
|
||||
storyID: $storyID
|
||||
@@ -83,7 +83,22 @@ const RejectCommentMutation = createMutation(
|
||||
proxy.setValue("REJECTED", "status");
|
||||
proxy.setValue(true, "viewerDidModerate");
|
||||
},
|
||||
updater: (store) => {
|
||||
updater: (store, data) => {
|
||||
// If no comment came back or the returned comment status was the same,
|
||||
// don't remove it from the connections!
|
||||
if (
|
||||
!data.rejectComment ||
|
||||
!data.rejectComment.comment ||
|
||||
data.rejectComment.comment.status !== "REJECTED"
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Ensure that the comment retains the viewerDidModerate state after it
|
||||
// comes back from the update.
|
||||
const proxy = store.get(input.commentID)!;
|
||||
proxy.setValue(true, "viewerDidModerate");
|
||||
|
||||
const connections = [
|
||||
getQueueConnection(store, "REPORTED", input.storyID),
|
||||
getQueueConnection(store, "PENDING", input.storyID),
|
||||
|
||||
@@ -375,7 +375,7 @@ it("approves comment in reported queue", async () => {
|
||||
});
|
||||
return {
|
||||
comment: {
|
||||
id: reportedComments[0].id,
|
||||
...reportedComments[0],
|
||||
status: GQLCOMMENT_STATUS.APPROVED,
|
||||
statusHistory: {
|
||||
edges: [
|
||||
@@ -473,7 +473,7 @@ it("rejects comment in reported queue", async () => {
|
||||
});
|
||||
return {
|
||||
comment: {
|
||||
id: reportedComments[0].id,
|
||||
...reportedComments[0],
|
||||
status: GQLCOMMENT_STATUS.REJECTED,
|
||||
statusHistory: {
|
||||
edges: [
|
||||
|
||||
@@ -245,7 +245,7 @@ it("approves comment in rejected queue", async () => {
|
||||
});
|
||||
return {
|
||||
comment: {
|
||||
id: rejectedComments[0].id,
|
||||
...rejectedComments[0],
|
||||
status: GQLCOMMENT_STATUS.APPROVED,
|
||||
statusHistory: {
|
||||
edges: [
|
||||
|
||||
@@ -95,7 +95,7 @@ it("approves single comment", async () => {
|
||||
});
|
||||
return {
|
||||
comment: {
|
||||
id: comment.id,
|
||||
...comment,
|
||||
status: GQLCOMMENT_STATUS.APPROVED,
|
||||
statusHistory: {
|
||||
edges: [
|
||||
@@ -148,9 +148,8 @@ it("rejects single comment", async () => {
|
||||
});
|
||||
return {
|
||||
comment: {
|
||||
id: comment.id,
|
||||
...comment,
|
||||
status: GQLCOMMENT_STATUS.REJECTED,
|
||||
author: comment.author,
|
||||
statusHistory: {
|
||||
edges: [
|
||||
{
|
||||
|
||||
@@ -47,6 +47,13 @@ export function getLatestRevision(
|
||||
return comment.revisions[comment.revisions.length - 1];
|
||||
}
|
||||
|
||||
export function hasRevision(
|
||||
comment: Pick<Comment, "revisions">,
|
||||
revisionID: string
|
||||
): boolean {
|
||||
return comment.revisions.some((revision) => revision.id === revisionID);
|
||||
}
|
||||
|
||||
export function calculateRejectionRate(counts: CommentStatusCounts): number {
|
||||
const published = calculateTotalPublishedCommentCount(counts);
|
||||
const rejected = counts[GQLCOMMENT_STATUS.REJECTED];
|
||||
|
||||
@@ -5,7 +5,12 @@ import {
|
||||
createCommentModerationAction,
|
||||
CreateCommentModerationActionInput,
|
||||
} from "coral-server/models/action/moderation/comment";
|
||||
import { updateCommentStatus } from "coral-server/models/comment";
|
||||
import {
|
||||
getLatestRevision,
|
||||
hasRevision,
|
||||
retrieveComment,
|
||||
updateCommentStatus,
|
||||
} from "coral-server/models/comment";
|
||||
import { Tenant } from "coral-server/models/tenant";
|
||||
|
||||
export type Moderate = CreateCommentModerationActionInput;
|
||||
@@ -18,18 +23,32 @@ export default async function moderate(
|
||||
) {
|
||||
// TODO: wrap these operations in a transaction?
|
||||
|
||||
// Create the moderation action in the audit log.
|
||||
const action = await createCommentModerationAction(
|
||||
mongo,
|
||||
tenant.id,
|
||||
input,
|
||||
now
|
||||
);
|
||||
if (!action) {
|
||||
// TODO: wrap in better error?
|
||||
throw new Error("could not create moderation action");
|
||||
// Get the comment that we're moderating.
|
||||
const comment = await retrieveComment(mongo, tenant.id, input.commentID);
|
||||
if (!comment) {
|
||||
throw new CommentNotFoundError(input.commentID, input.commentRevisionID);
|
||||
}
|
||||
|
||||
// Get the latest revision on that comment.
|
||||
const revision = getLatestRevision(comment);
|
||||
|
||||
// Ensure that the latest revision is the same revision that we're moderating.
|
||||
if (revision.id !== input.commentRevisionID) {
|
||||
// The revision has been updated since then! Ensure that this revision ID
|
||||
// does exist.
|
||||
if (!hasRevision(comment, input.commentRevisionID)) {
|
||||
throw new CommentNotFoundError(input.commentID, input.commentRevisionID);
|
||||
}
|
||||
|
||||
// The comment has this revision, it just isn't the latest one. Return the
|
||||
// same comment back because we didn't modify anything.
|
||||
return { before: comment, after: null };
|
||||
}
|
||||
|
||||
// TODO: (wyattjoh) this is a pretty race condition prone check here, replace
|
||||
// with a more concrete query that can prevent the comment being edited in the
|
||||
// time it takes to go from the above block to the next block.
|
||||
|
||||
// Update the Comment's status.
|
||||
const result = await updateCommentStatus(
|
||||
mongo,
|
||||
@@ -42,5 +61,17 @@ export default async function moderate(
|
||||
throw new CommentNotFoundError(input.commentID, input.commentRevisionID);
|
||||
}
|
||||
|
||||
// Create the moderation action in the audit log.
|
||||
const action = await createCommentModerationAction(
|
||||
mongo,
|
||||
tenant.id,
|
||||
input,
|
||||
now
|
||||
);
|
||||
if (!action) {
|
||||
// TODO: wrap in better error?
|
||||
throw new Error("could not create moderation action");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -32,6 +32,11 @@ const approveComment = async (
|
||||
now
|
||||
);
|
||||
|
||||
// If the comment hasn't been updated, skip the rest of the steps.
|
||||
if (!result.after) {
|
||||
return result.before;
|
||||
}
|
||||
|
||||
// Update all the comment counts on stories and users.
|
||||
const counts = await updateAllCommentCounts(mongo, redis, {
|
||||
...result,
|
||||
|
||||
@@ -37,6 +37,11 @@ const rejectComment = async (
|
||||
now
|
||||
);
|
||||
|
||||
// If the comment hasn't been updated, skip the rest of the steps.
|
||||
if (!result.after) {
|
||||
return result.before;
|
||||
}
|
||||
|
||||
// Update all the comment counts on stories and users.
|
||||
const counts = await updateAllCommentCounts(mongo, redis, {
|
||||
...result,
|
||||
|
||||
Reference in New Issue
Block a user