mirror of
https://github.com/wassname/talk.git
synced 2026-08-09 12:30:42 +08:00
Merge branch 'next' into next-auth
This commit is contained in:
@@ -11,12 +11,12 @@ import {
|
||||
encodeActionCounts,
|
||||
invertEncodedActionCounts,
|
||||
} from "talk-server/models/action";
|
||||
import { updateAssetActionCounts } from "talk-server/models/asset";
|
||||
import {
|
||||
retrieveComment,
|
||||
updateCommentActionCounts,
|
||||
} from "talk-server/models/comment";
|
||||
import { Comment } from "talk-server/models/comment";
|
||||
import { updateStoryActionCounts } from "talk-server/models/story";
|
||||
import { Tenant } from "talk-server/models/tenant";
|
||||
import { User } from "talk-server/models/user";
|
||||
|
||||
@@ -47,11 +47,11 @@ export async function addCommentActions(
|
||||
actionCounts
|
||||
);
|
||||
|
||||
// Update the Asset with the updated action counts.
|
||||
await updateAssetActionCounts(
|
||||
// Update the Story with the updated action counts.
|
||||
await updateStoryActionCounts(
|
||||
mongo,
|
||||
tenant.id,
|
||||
comment.asset_id,
|
||||
comment.story_id,
|
||||
actionCounts
|
||||
);
|
||||
|
||||
@@ -109,11 +109,11 @@ export async function removeCommentAction(
|
||||
actionCounts
|
||||
);
|
||||
|
||||
// Update the Asset with the updated action counts.
|
||||
await updateAssetActionCounts(
|
||||
// Update the Story with the updated action counts.
|
||||
await updateStoryActionCounts(
|
||||
mongo,
|
||||
tenant.id,
|
||||
comment.asset_id,
|
||||
comment.story_id,
|
||||
actionCounts
|
||||
);
|
||||
|
||||
|
||||
@@ -2,10 +2,6 @@ import { Db } from "mongodb";
|
||||
|
||||
import { Omit } from "talk-common/types";
|
||||
import { ACTION_ITEM_TYPE, CreateActionInput } from "talk-server/models/action";
|
||||
import {
|
||||
retrieveAsset,
|
||||
updateCommentStatusCount,
|
||||
} from "talk-server/models/asset";
|
||||
import {
|
||||
createComment,
|
||||
CreateCommentInput,
|
||||
@@ -14,6 +10,10 @@ import {
|
||||
pushChildCommentIDOntoParent,
|
||||
retrieveComment,
|
||||
} from "talk-server/models/comment";
|
||||
import {
|
||||
retrieveStory,
|
||||
updateCommentStatusCount,
|
||||
} from "talk-server/models/story";
|
||||
import { Tenant } from "talk-server/models/tenant";
|
||||
import { User } from "talk-server/models/user";
|
||||
import { addCommentActions } from "talk-server/services/comments/actions";
|
||||
@@ -32,14 +32,14 @@ export async function create(
|
||||
input: CreateComment,
|
||||
req?: Request
|
||||
) {
|
||||
// Grab the asset that we'll use to check moderation pieces with.
|
||||
const asset = await retrieveAsset(mongo, tenant.id, input.asset_id);
|
||||
if (!asset) {
|
||||
// Grab the story that we'll use to check moderation pieces with.
|
||||
const story = await retrieveStory(mongo, tenant.id, input.story_id);
|
||||
if (!story) {
|
||||
// TODO: (wyattjoh) return better error.
|
||||
throw new Error("asset referenced does not exist");
|
||||
throw new Error("story referenced does not exist");
|
||||
}
|
||||
|
||||
// TODO: (wyattjoh) Check that the asset was visible.
|
||||
// TODO: (wyattjoh) Check that the story was visible.
|
||||
|
||||
const grandparentIDs: string[] = [];
|
||||
if (input.parent_id) {
|
||||
@@ -62,7 +62,7 @@ export async function create(
|
||||
|
||||
// Run the comment through the moderation phases.
|
||||
const { actions, status, metadata } = await processForModeration({
|
||||
asset,
|
||||
story,
|
||||
tenant,
|
||||
comment: input,
|
||||
author,
|
||||
@@ -104,8 +104,8 @@ export async function create(
|
||||
);
|
||||
}
|
||||
|
||||
// Increment the status count for the particular status on the Asset.
|
||||
await updateCommentStatusCount(mongo, tenant.id, asset.id, {
|
||||
// Increment the status count for the particular status on the Story.
|
||||
await updateCommentStatusCount(mongo, tenant.id, story.id, {
|
||||
[status]: 1,
|
||||
});
|
||||
|
||||
@@ -131,16 +131,16 @@ export async function edit(
|
||||
throw new Error("comment not found");
|
||||
}
|
||||
|
||||
// Grab the asset that we'll use to check moderation pieces with.
|
||||
const asset = await retrieveAsset(mongo, tenant.id, comment.asset_id);
|
||||
if (!asset) {
|
||||
// Grab the story that we'll use to check moderation pieces with.
|
||||
const story = await retrieveStory(mongo, tenant.id, comment.story_id);
|
||||
if (!story) {
|
||||
// TODO: (wyattjoh) return better error.
|
||||
throw new Error("asset referenced does not exist");
|
||||
throw new Error("story referenced does not exist");
|
||||
}
|
||||
|
||||
// Run the comment through the moderation phases.
|
||||
const { status, metadata, actions } = await processForModeration({
|
||||
asset,
|
||||
story,
|
||||
tenant,
|
||||
comment: input,
|
||||
author,
|
||||
@@ -185,9 +185,9 @@ export async function edit(
|
||||
}
|
||||
|
||||
if (comment.status !== editedComment.status) {
|
||||
// Increment the status count for the particular status on the Asset, and
|
||||
// Increment the status count for the particular status on the Story, and
|
||||
// decrement the status on the comment's previous status.
|
||||
await updateCommentStatusCount(mongo, tenant.id, asset.id, {
|
||||
await updateCommentStatusCount(mongo, tenant.id, story.id, {
|
||||
[comment.status]: -1,
|
||||
[editedComment.status]: 1,
|
||||
});
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Omit, Promiseable } from "talk-common/types";
|
||||
import { GQLCOMMENT_STATUS } from "talk-server/graph/tenant/schema/__generated__/types";
|
||||
import { CreateActionInput } from "talk-server/models/action";
|
||||
import { Asset } from "talk-server/models/asset";
|
||||
import { Comment } from "talk-server/models/comment";
|
||||
import { Story } from "talk-server/models/story";
|
||||
import { Tenant } from "talk-server/models/tenant";
|
||||
import { User } from "talk-server/models/user";
|
||||
import { Request } from "talk-server/types/express";
|
||||
@@ -18,7 +18,7 @@ export interface PhaseResult {
|
||||
}
|
||||
|
||||
export interface ModerationPhaseContext {
|
||||
asset: Asset;
|
||||
story: Story;
|
||||
tenant: Tenant;
|
||||
comment: Partial<Comment>;
|
||||
author: User;
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
import {
|
||||
IntermediateModerationPhase,
|
||||
IntermediatePhaseResult,
|
||||
} from "talk-server/services/comments/moderation";
|
||||
|
||||
// This phase checks to see if the asset being processed is closed or not.
|
||||
export const assetClosed: IntermediateModerationPhase = ({
|
||||
asset,
|
||||
}): IntermediatePhaseResult | void => {
|
||||
// Check to see if the asset has closed commenting...
|
||||
if (asset.closedAt && asset.closedAt.valueOf() <= Date.now()) {
|
||||
// TODO: (wyattjoh) return better error.
|
||||
throw new Error("asset is currently closed for commenting");
|
||||
}
|
||||
};
|
||||
@@ -35,7 +35,7 @@ const testCharCount = (
|
||||
};
|
||||
|
||||
export const commentLength: IntermediateModerationPhase = ({
|
||||
asset,
|
||||
story,
|
||||
tenant,
|
||||
comment,
|
||||
}): IntermediatePhaseResult | void => {
|
||||
@@ -44,7 +44,7 @@ export const commentLength: IntermediateModerationPhase = ({
|
||||
// Reject if the comment is too long or too short.
|
||||
if (
|
||||
testCharCount(tenant, length) ||
|
||||
(asset.settings && testCharCount(asset.settings, length))
|
||||
(story.settings && testCharCount(story.settings, length))
|
||||
) {
|
||||
return {
|
||||
status: GQLCOMMENT_STATUS.REJECTED,
|
||||
|
||||
@@ -8,13 +8,13 @@ const testDisabledCommenting = (settings: Partial<ModerationSettings>) =>
|
||||
settings.disableCommenting;
|
||||
|
||||
export const commentingDisabled: IntermediateModerationPhase = ({
|
||||
asset,
|
||||
story,
|
||||
tenant,
|
||||
}): IntermediatePhaseResult | void => {
|
||||
// Check to see if the asset has closed commenting.
|
||||
// Check to see if the story has closed commenting.
|
||||
if (
|
||||
testDisabledCommenting(tenant) ||
|
||||
(asset.settings && testDisabledCommenting(asset.settings))
|
||||
(story.settings && testDisabledCommenting(story.settings))
|
||||
) {
|
||||
// TODO: (wyattjoh) return better error.
|
||||
throw new Error("commenting has been disabled tenant wide");
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { IntermediateModerationPhase } from "talk-server/services/comments/moderation";
|
||||
|
||||
import { assetClosed } from "./assetClosed";
|
||||
import { commentingDisabled } from "./commentingDisabled";
|
||||
import { commentLength } from "./commentLength";
|
||||
import { karma } from "./karma";
|
||||
@@ -8,6 +7,7 @@ import { links } from "./links";
|
||||
import { preModerate } from "./preModerate";
|
||||
import { spam } from "./spam";
|
||||
import { staff } from "./staff";
|
||||
import { storyClosed } from "./storyClosed";
|
||||
import { toxic } from "./toxic";
|
||||
import { wordList } from "./wordList";
|
||||
|
||||
@@ -16,7 +16,7 @@ import { wordList } from "./wordList";
|
||||
*/
|
||||
export const moderationPhases: IntermediateModerationPhase[] = [
|
||||
commentLength,
|
||||
assetClosed,
|
||||
storyClosed,
|
||||
commentingDisabled,
|
||||
wordList,
|
||||
staff,
|
||||
|
||||
@@ -25,14 +25,14 @@ const testPremodLinksEnable = (
|
||||
// This phase checks the comment if it has any links in it if the check is
|
||||
// enabled.
|
||||
export const links: IntermediateModerationPhase = ({
|
||||
asset,
|
||||
story,
|
||||
tenant,
|
||||
comment,
|
||||
}): IntermediatePhaseResult | void => {
|
||||
if (
|
||||
comment.body &&
|
||||
(testPremodLinksEnable(tenant, comment.body) ||
|
||||
(asset.settings && testPremodLinksEnable(asset.settings, comment.body)))
|
||||
(story.settings && testPremodLinksEnable(story.settings, comment.body)))
|
||||
) {
|
||||
// Add the flag related to Trust to the comment.
|
||||
return {
|
||||
|
||||
@@ -14,16 +14,16 @@ const testModerationMode = (settings: Partial<ModerationSettings>) =>
|
||||
// This phase checks to see if the settings have premod enabled, if they do,
|
||||
// the comment is premod, otherwise, it's just none.
|
||||
export const preModerate: IntermediateModerationPhase = ({
|
||||
asset,
|
||||
story,
|
||||
tenant,
|
||||
}): IntermediatePhaseResult | void => {
|
||||
// If the settings say that we're in premod mode, then the comment is in
|
||||
// premod status.
|
||||
|
||||
// TODO: (wyattjoh) pull from the asset settings.
|
||||
// TODO: (wyattjoh) pull from the story settings.
|
||||
if (
|
||||
testModerationMode(tenant) ||
|
||||
(asset.settings && testModerationMode(asset.settings))
|
||||
(story.settings && testModerationMode(story.settings))
|
||||
) {
|
||||
return {
|
||||
status: GQLCOMMENT_STATUS.PREMOD,
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
} from "talk-server/services/comments/moderation";
|
||||
|
||||
export const spam: IntermediateModerationPhase = async ({
|
||||
asset,
|
||||
story,
|
||||
tenant,
|
||||
comment,
|
||||
author,
|
||||
@@ -96,7 +96,7 @@ export const spam: IntermediateModerationPhase = async ({
|
||||
referrer, // REQUIRED
|
||||
user_agent: userAgent, // REQUIRED
|
||||
comment_content: comment.body,
|
||||
permalink: asset.url,
|
||||
permalink: story.url,
|
||||
comment_author: author.displayName || author.username || "",
|
||||
comment_type: "comment",
|
||||
is_test: false,
|
||||
|
||||
+12
-12
@@ -1,16 +1,16 @@
|
||||
import { Asset } from "talk-server/models/asset";
|
||||
import { Comment } from "talk-server/models/comment";
|
||||
import { Story } from "talk-server/models/story";
|
||||
import { Tenant } from "talk-server/models/tenant";
|
||||
import { User } from "talk-server/models/user";
|
||||
import { assetClosed } from "talk-server/services/comments/moderation/phases/assetClosed";
|
||||
import { storyClosed } from "talk-server/services/comments/moderation/phases/storyClosed";
|
||||
|
||||
describe("assetClosed", () => {
|
||||
it("throws an error when the asset is closed", () => {
|
||||
const asset = { closedAt: new Date() };
|
||||
describe("storyClosed", () => {
|
||||
it("throws an error when the story is closed", () => {
|
||||
const story = { closedAt: new Date() };
|
||||
|
||||
expect(() =>
|
||||
assetClosed({
|
||||
asset: asset as Asset,
|
||||
storyClosed({
|
||||
story: story as Story,
|
||||
tenant: (null as any) as Tenant,
|
||||
comment: (null as any) as Comment,
|
||||
author: (null as any) as User,
|
||||
@@ -18,12 +18,12 @@ describe("assetClosed", () => {
|
||||
).toThrow();
|
||||
});
|
||||
|
||||
it("does not throw an error when the asset is not closed", () => {
|
||||
it("does not throw an error when the story is not closed", () => {
|
||||
const now = new Date();
|
||||
|
||||
expect(
|
||||
assetClosed({
|
||||
asset: { closedAt: new Date(now.getTime() + 60000) } as Asset,
|
||||
storyClosed({
|
||||
story: { closedAt: new Date(now.getTime() + 60000) } as Story,
|
||||
tenant: (null as any) as Tenant,
|
||||
comment: (null as any) as Comment,
|
||||
author: (null as any) as User,
|
||||
@@ -31,8 +31,8 @@ describe("assetClosed", () => {
|
||||
).toBeUndefined();
|
||||
|
||||
expect(
|
||||
assetClosed({
|
||||
asset: {} as Asset,
|
||||
storyClosed({
|
||||
story: {} as Story,
|
||||
tenant: (null as any) as Tenant,
|
||||
comment: (null as any) as Comment,
|
||||
author: (null as any) as User,
|
||||
@@ -0,0 +1,15 @@
|
||||
import {
|
||||
IntermediateModerationPhase,
|
||||
IntermediatePhaseResult,
|
||||
} from "talk-server/services/comments/moderation";
|
||||
|
||||
// This phase checks to see if the story being processed is closed or not.
|
||||
export const storyClosed: IntermediateModerationPhase = ({
|
||||
story,
|
||||
}): IntermediatePhaseResult | void => {
|
||||
// Check to see if the story has closed commenting...
|
||||
if (story.closedAt && story.closedAt.valueOf() <= Date.now()) {
|
||||
// TODO: (wyattjoh) return better error.
|
||||
throw new Error("story is currently closed for commenting");
|
||||
}
|
||||
};
|
||||
@@ -19,7 +19,7 @@ export const wordList: IntermediateModerationPhase = ({
|
||||
return;
|
||||
}
|
||||
|
||||
// Decide the status based on whether or not the current asset/settings
|
||||
// Decide the status based on whether or not the current story/settings
|
||||
// has pre-mod enabled or not. If the comment was rejected based on the
|
||||
// wordList, then reject it, otherwise if the moderation setting is
|
||||
// premod, set it to `premod`.
|
||||
|
||||
@@ -8,7 +8,7 @@ import titleScraper from "metascraper-title";
|
||||
import { Db } from "mongodb";
|
||||
|
||||
import logger from "talk-server/logger";
|
||||
import { updateAsset } from "talk-server/models/asset";
|
||||
import { updateStory } from "talk-server/models/story";
|
||||
import Task from "talk-server/services/queue/Task";
|
||||
import { modifiedScraper } from "./rules/modified";
|
||||
import { sectionScraper } from "./rules/section";
|
||||
@@ -20,8 +20,8 @@ export interface ScrapeProcessorOptions {
|
||||
}
|
||||
|
||||
export interface ScraperData {
|
||||
assetID: string;
|
||||
assetURL: string;
|
||||
storyID: string;
|
||||
storyURL: string;
|
||||
tenantID: string;
|
||||
}
|
||||
|
||||
@@ -30,17 +30,17 @@ const createJobProcessor = (
|
||||
scraper: Scraper
|
||||
) => async (job: Job<ScraperData>) => {
|
||||
// Pull out the job data.
|
||||
const { assetID: id, assetURL: url, tenantID } = job.data;
|
||||
const { storyID: id, storyURL: url, tenantID } = job.data;
|
||||
|
||||
logger.debug(
|
||||
{
|
||||
job_id: job.id,
|
||||
job_name: JOB_NAME,
|
||||
asset_id: id,
|
||||
asset_url: url,
|
||||
story_id: id,
|
||||
story_url: url,
|
||||
tenant_id: tenantID,
|
||||
},
|
||||
"starting to scrap the asset"
|
||||
"starting to scrap the story"
|
||||
);
|
||||
|
||||
// Get the metadata from the scraped html.
|
||||
@@ -50,17 +50,17 @@ const createJobProcessor = (
|
||||
{
|
||||
job_id: job.id,
|
||||
job_name: JOB_NAME,
|
||||
asset_id: id,
|
||||
asset_url: url,
|
||||
story_id: id,
|
||||
story_url: url,
|
||||
tenant_id: tenantID,
|
||||
},
|
||||
"asset at specified url not found, can not scrape"
|
||||
"story at specified url not found, can not scrape"
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Update the Asset with the scraped details.
|
||||
const asset = await updateAsset(options.mongo, tenantID, id, {
|
||||
// Update the Story with the scraped details.
|
||||
const story = await updateStory(options.mongo, tenantID, id, {
|
||||
title: meta.title || undefined,
|
||||
description: meta.description || undefined,
|
||||
image: meta.image ? meta.image : undefined,
|
||||
@@ -70,16 +70,16 @@ const createJobProcessor = (
|
||||
section: meta.section || undefined,
|
||||
scraped: new Date(),
|
||||
});
|
||||
if (!asset) {
|
||||
if (!story) {
|
||||
logger.error(
|
||||
{
|
||||
job_id: job.id,
|
||||
job_name: JOB_NAME,
|
||||
asset_id: id,
|
||||
asset_url: url,
|
||||
story_id: id,
|
||||
story_url: url,
|
||||
tenant_id: tenantID,
|
||||
},
|
||||
"asset at specified id not found, can not update with metadata"
|
||||
"story at specified id not found, can not update with metadata"
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -88,11 +88,11 @@ const createJobProcessor = (
|
||||
{
|
||||
job_id: job.id,
|
||||
job_name: JOB_NAME,
|
||||
asset_id: asset.id,
|
||||
asset_url: url,
|
||||
story_id: story.id,
|
||||
story_url: url,
|
||||
tenant_id: tenantID,
|
||||
},
|
||||
"scraped the asset"
|
||||
"scraped the story"
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
+12
-12
@@ -1,35 +1,35 @@
|
||||
import { Db } from "mongodb";
|
||||
|
||||
import {
|
||||
findOrCreateAsset,
|
||||
FindOrCreateAssetInput,
|
||||
} from "talk-server/models/asset";
|
||||
findOrCreateStory,
|
||||
FindOrCreateStoryInput,
|
||||
} from "talk-server/models/story";
|
||||
import { Tenant } from "talk-server/models/tenant";
|
||||
import Task from "talk-server/services/queue/Task";
|
||||
import { ScraperData } from "talk-server/services/queue/tasks/scraper";
|
||||
|
||||
export type FindOrCreateAsset = FindOrCreateAssetInput;
|
||||
export type FindOrCreateStory = FindOrCreateStoryInput;
|
||||
|
||||
export async function findOrCreate(
|
||||
db: Db,
|
||||
tenant: Tenant,
|
||||
input: FindOrCreateAsset,
|
||||
input: FindOrCreateStory,
|
||||
scraper: Task<ScraperData>
|
||||
) {
|
||||
// TODO: check to see if the tenant has enabled lazy asset creation.
|
||||
// TODO: check to see if the tenant has enabled lazy story creation.
|
||||
|
||||
const asset = await findOrCreateAsset(db, tenant.id, input);
|
||||
if (!asset) {
|
||||
const story = await findOrCreateStory(db, tenant.id, input);
|
||||
if (!story) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!asset.scraped) {
|
||||
if (!story.scraped) {
|
||||
await scraper.add({
|
||||
assetID: asset.id,
|
||||
assetURL: asset.url,
|
||||
storyID: story.id,
|
||||
storyURL: story.url,
|
||||
tenantID: tenant.id,
|
||||
});
|
||||
}
|
||||
|
||||
return asset;
|
||||
return story;
|
||||
}
|
||||
Reference in New Issue
Block a user