From b8ddcbb0c8b1ae1267967e8f9358c9a35e3501a5 Mon Sep 17 00:00:00 2001 From: xdj <13580441+gary02@users.noreply.github.com> Date: Wed, 15 May 2024 15:38:00 +0800 Subject: [PATCH] feat(report-comment): auto collapse comment conditionally when submiting report see https://www.notion.so/matterslab/7ce2f0e3e20f4f838e61c267fb7a1be3 re #3911 --- src/connectors/__test__/systemService.test.ts | 117 ++++++++++++++++-- src/connectors/systemService.ts | 72 +++++++++++ 2 files changed, 177 insertions(+), 12 deletions(-) diff --git a/src/connectors/__test__/systemService.test.ts b/src/connectors/__test__/systemService.test.ts index ad701eb5c..ab900f31e 100644 --- a/src/connectors/__test__/systemService.test.ts +++ b/src/connectors/__test__/systemService.test.ts @@ -2,8 +2,8 @@ import type { Connections, Asset } from 'definitions' import { v4 } from 'uuid' -import { NODE_TYPES } from 'common/enums' -import { SystemService } from 'connectors' +import { NODE_TYPES, COMMENT_TYPE, COMMENT_STATE } from 'common/enums' +import { SystemService, AtomService } from 'connectors' import { genConnections, closeConnections } from './utils' @@ -19,11 +19,13 @@ const assetValidation = { let connections: Connections let systemService: SystemService +let atomService: AtomService beforeAll(async () => { connections = await genConnections() systemService = new SystemService(connections) -}, 50000) + atomService = new AtomService(connections) +}, 30000) afterAll(async () => { await closeConnections(connections) @@ -77,14 +79,105 @@ test('copy asset map', async () => { await systemService.copyAssetMapEntities({ source, target }) }) -test('submit report', async () => { - const report = await systemService.submitReport({ - targetType: NODE_TYPES.Article, - targetId: '1', - reporterId: '1', - reason: 'other', +describe('report', () => { + test('submit report', async () => { + const report = await systemService.submitReport({ + targetType: NODE_TYPES.Article, + targetId: '1', + reporterId: '1', + reason: 'other', + }) + expect(report.id).toBeDefined() + expect(report.articleId).not.toBeNull() + expect(report.commentId).toBeNull() + }) + test('collapse comment if more than 3 different users report it', async () => { + const commentId = '1' + const comment = await atomService.findUnique({ + table: 'comment', + where: { id: commentId }, + }) + expect(comment.type).toBe(COMMENT_TYPE.article) + expect(comment.state).toBe(COMMENT_STATE.active) + + // only 2 reports, comment should not be collapsed + + await systemService.submitReport({ + targetType: NODE_TYPES.Comment, + targetId: commentId, + reporterId: '2', + reason: 'other', + }) + await systemService.submitReport({ + targetType: NODE_TYPES.Comment, + targetId: commentId, + reporterId: '3', + reason: 'other', + }) + + const commentAfter2Reports = await atomService.findUnique({ + table: 'comment', + where: { id: commentId }, + }) + expect(commentAfter2Reports.state).toBe(COMMENT_STATE.active) + + // only 3 reports from 2 different users, comment should not be collapsed + + await systemService.submitReport({ + targetType: NODE_TYPES.Comment, + targetId: commentId, + reporterId: '3', + reason: 'other', + }) + + const commentAfter3Reports = await atomService.findUnique({ + table: 'comment', + where: { id: commentId }, + }) + expect(commentAfter3Reports.state).toBe(COMMENT_STATE.active) + + // 4 reports from 3 different users, comment should be collapsed + + await systemService.submitReport({ + targetType: NODE_TYPES.Comment, + targetId: commentId, + reporterId: '4', + reason: 'other', + }) + + const commentAfter4Reports = await atomService.findUnique({ + table: 'comment', + where: { id: commentId }, + }) + expect(commentAfter4Reports.state).toBe(COMMENT_STATE.collapsed) + }) + + test('collapse comment if article author report it', async () => { + const commentId = '2' + const comment = await atomService.findUnique({ + table: 'comment', + where: { id: commentId }, + }) + expect(comment.type).toBe(COMMENT_TYPE.article) + expect(comment.state).toBe(COMMENT_STATE.active) + + const { authorId } = await atomService.findUnique({ + table: 'article', + where: { id: comment.targetId }, + }) + + await systemService.submitReport({ + targetType: NODE_TYPES.Comment, + targetId: commentId, + reporterId: authorId, + reason: 'other', + }) + + const commentAfterReport = await atomService.findUnique({ + table: 'comment', + where: { id: commentId }, + }) + + expect(commentAfterReport.state).toBe(COMMENT_STATE.collapsed) }) - expect(report.id).toBeDefined() - expect(report.articleId).not.toBeNull() - expect(report.commentId).toBeNull() }) diff --git a/src/connectors/systemService.ts b/src/connectors/systemService.ts index 07c8f9f34..25a79d365 100644 --- a/src/connectors/systemService.ts +++ b/src/connectors/systemService.ts @@ -13,6 +13,7 @@ import type { } from 'definitions' import type { Knex } from 'knex' +import { invalidateFQC } from '@matters/apollo-response-cache' import { v4 } from 'uuid' import { @@ -23,6 +24,8 @@ import { USER_ROLE, FEATURE_NAME, FEATURE_FLAG, + COMMENT_STATE, + COMMENT_TYPE, NODE_TYPES, } from 'common/enums' import { getLogger } from 'common/logger' @@ -475,6 +478,13 @@ export class SystemService extends BaseService { return updateItem } + /** + * Create a report of target. + * + * @remarks + * The target could be an article or a comment. + * When the target is a comment, collapse the comment base on reports amount and reporters. + */ public submitReport = async ({ targetType, targetId, @@ -503,7 +513,69 @@ export class SystemService extends BaseService { reason, }) .returning('*') + + await this.tryCollapseComment(targetId) + return ret[0] } } + + /** + * Collapse the article comment if its reports are created by more than 3 different users or 1 article author + * + * @returns true if the comment is collapsed, otherwise false + * + */ + private tryCollapseComment = async (commentId: string): Promise => { + const comment = await this.models.findUnique({ + table: 'comment', + where: { id: commentId }, + }) + + if ( + !comment || + comment.state === COMMENT_STATE.collapsed || + comment.type !== COMMENT_TYPE.article + ) { + return false + } + + const reports = await this.knex('report') + .select(['id', 'reporterId']) + .distinctOn('reporterId') + .where({ commentId }) + + if (reports.length >= 3) { + await this.models.update({ + table: 'comment', + where: { id: commentId }, + data: { state: COMMENT_STATE.collapsed }, + }) + await invalidateFQC({ + node: { id: commentId, type: NODE_TYPES.Comment }, + redis: this.redis, + }) + return true + } + + const { authorId } = await this.models.findUnique({ + table: 'article', + where: { id: comment.targetId }, + }) + + if (authorId && reports.find((r) => r.reporterId === authorId)) { + await this.models.update({ + table: 'comment', + where: { id: commentId }, + data: { state: COMMENT_STATE.collapsed }, + }) + await invalidateFQC({ + node: { id: commentId, type: NODE_TYPES.Comment }, + redis: this.redis, + }) + return true + } + + return false + } }