mirror of
https://github.com/wassname/talk.git
synced 2026-07-04 08:12:37 +08:00
769ab2a910
* Fix various Q&A moderation/tagging issues - Allow Staff members to have their questions answered and appropriately tagged - Properly filter answering for only top level questions (comments) - Add documentation around various moderation phases and comment creation steps - Remove unnecessary status filter when setting the status for a comment * fix: abstracted out perspective configs * fix: reworked tag injection * Fix sorting/unused imports Co-authored-by: Wyatt Johnson <accounts+github@wyattjoh.ca>
204 lines
5.5 KiB
TypeScript
204 lines
5.5 KiB
TypeScript
import { DateTime } from "luxon";
|
|
import { Db } from "mongodb";
|
|
|
|
import { Omit } from "coral-common/types";
|
|
import { Config } from "coral-server/config";
|
|
import { CommentNotFoundError, StoryNotFoundError } from "coral-server/errors";
|
|
import { CoralEventPublisherBroker } from "coral-server/events/publisher";
|
|
import logger from "coral-server/logger";
|
|
import {
|
|
encodeActionCounts,
|
|
EncodedCommentActionCounts,
|
|
filterDuplicateActions,
|
|
} from "coral-server/models/action/comment";
|
|
import { createCommentModerationAction } from "coral-server/models/action/moderation/comment";
|
|
import {
|
|
editComment,
|
|
EditCommentInput,
|
|
retrieveComment,
|
|
validateEditable,
|
|
} from "coral-server/models/comment";
|
|
import { retrieveStory } from "coral-server/models/story";
|
|
import { Tenant } from "coral-server/models/tenant";
|
|
import { User } from "coral-server/models/user";
|
|
import {
|
|
addCommentActions,
|
|
CreateAction,
|
|
} from "coral-server/services/comments/actions";
|
|
import { processForModeration } from "coral-server/services/comments/pipeline";
|
|
import { AugmentedRedis } from "coral-server/services/redis";
|
|
import { Request } from "coral-server/types/express";
|
|
|
|
import { publishChanges, updateAllCommentCounts } from "./helpers";
|
|
|
|
/**
|
|
* getLastCommentEditableUntilDate will return the `createdAt` date that will
|
|
* represent the _oldest_ date that a comment could have been created on in
|
|
* order to still be editable.
|
|
*
|
|
* @param tenant the tenant that contains settings related editing
|
|
* @param now the date that is the base, defaulting to the current time
|
|
*/
|
|
function getLastCommentEditableUntilDate(
|
|
tenant: Pick<Tenant, "editCommentWindowLength">,
|
|
now = new Date()
|
|
): Date {
|
|
return DateTime.fromJSDate(now)
|
|
.minus({ seconds: tenant.editCommentWindowLength })
|
|
.toJSDate();
|
|
}
|
|
|
|
export type EditComment = Omit<
|
|
EditCommentInput,
|
|
"status" | "authorID" | "lastEditableCommentCreatedAt" | "metadata"
|
|
>;
|
|
|
|
export default async function edit(
|
|
mongo: Db,
|
|
redis: AugmentedRedis,
|
|
config: Config,
|
|
broker: CoralEventPublisherBroker,
|
|
tenant: Tenant,
|
|
author: User,
|
|
input: EditComment,
|
|
now = new Date(),
|
|
req?: Request
|
|
) {
|
|
let log = logger.child({ commentID: input.id, tenantID: tenant.id }, true);
|
|
|
|
// Get the comment that we're editing. This comment is considered stale,
|
|
// because it wasn't involved in the atomic transaction.
|
|
const originalStaleComment = await retrieveComment(
|
|
mongo,
|
|
tenant.id,
|
|
input.id
|
|
);
|
|
if (!originalStaleComment) {
|
|
throw new CommentNotFoundError(input.id);
|
|
}
|
|
|
|
// The editable time is based on the current time, and the edit window
|
|
// length. By subtracting the current date from the edit window length, we
|
|
// get the maximum value for the `createdAt` time that would be permitted
|
|
// for the comment edit to succeed.
|
|
const lastEditableCommentCreatedAt = getLastCommentEditableUntilDate(
|
|
tenant,
|
|
now
|
|
);
|
|
|
|
// Validate and potentially return with a more useful error.
|
|
validateEditable(originalStaleComment, {
|
|
authorID: author.id,
|
|
lastEditableCommentCreatedAt,
|
|
});
|
|
|
|
// Grab the story that we'll use to check moderation pieces with.
|
|
const story = await retrieveStory(
|
|
mongo,
|
|
tenant.id,
|
|
originalStaleComment.storyID
|
|
);
|
|
if (!story) {
|
|
throw new StoryNotFoundError(originalStaleComment.storyID);
|
|
}
|
|
|
|
// Run the comment through the moderation phases.
|
|
const { body, status, metadata, actions } = await processForModeration({
|
|
action: "EDIT",
|
|
log,
|
|
mongo,
|
|
redis,
|
|
config,
|
|
story,
|
|
tenant,
|
|
comment: {
|
|
...originalStaleComment,
|
|
...input,
|
|
authorID: author.id,
|
|
},
|
|
author,
|
|
req,
|
|
now,
|
|
});
|
|
|
|
let actionCounts: EncodedCommentActionCounts = {};
|
|
if (actions.length > 0) {
|
|
// Encode the new action counts that are going to be added to the new
|
|
// revision.
|
|
actionCounts = encodeActionCounts(...filterDuplicateActions(actions));
|
|
}
|
|
|
|
// Perform the edit.
|
|
const result = await editComment(
|
|
mongo,
|
|
tenant.id,
|
|
{
|
|
id: input.id,
|
|
authorID: author.id,
|
|
body,
|
|
status,
|
|
metadata,
|
|
actionCounts,
|
|
lastEditableCommentCreatedAt,
|
|
},
|
|
now
|
|
);
|
|
if (!result) {
|
|
throw new CommentNotFoundError(input.id);
|
|
}
|
|
|
|
log = log.child({ revisionID: result.revision.id }, true);
|
|
|
|
// If there were actions to insert, then insert them into the commentActions
|
|
// collection.
|
|
if (actions.length > 0) {
|
|
await addCommentActions(
|
|
mongo,
|
|
tenant,
|
|
actions.map(
|
|
(action): CreateAction => ({
|
|
...action,
|
|
commentID: result.after.id,
|
|
commentRevisionID: result.revision.id,
|
|
storyID: story.id,
|
|
siteID: story.siteID,
|
|
})
|
|
),
|
|
now
|
|
);
|
|
}
|
|
|
|
// If the comment status changed as a result of a pipeline operation, create a
|
|
// moderation action (but don't associate it with a moderator).
|
|
if (result.before.status !== result.after.status) {
|
|
await createCommentModerationAction(
|
|
mongo,
|
|
tenant.id,
|
|
{
|
|
commentID: result.after.id,
|
|
commentRevisionID: result.revision.id,
|
|
status: result.after.status,
|
|
moderatorID: null,
|
|
},
|
|
now
|
|
);
|
|
}
|
|
|
|
// Update all the comment counts on stories and users.
|
|
const counts = await updateAllCommentCounts(mongo, redis, {
|
|
tenant,
|
|
actionCounts,
|
|
...result,
|
|
});
|
|
|
|
// Publish changes to the event publisher.
|
|
await publishChanges(broker, {
|
|
...result,
|
|
...counts,
|
|
commentRevisionID: result.revision.id,
|
|
});
|
|
|
|
// Return the resulting comment.
|
|
return result.after;
|
|
}
|