From 7621a52eace874e890cbe88e25d6df0d81b96355 Mon Sep 17 00:00:00 2001 From: nick-funk Date: Fri, 10 Nov 2023 11:51:50 -0700 Subject: [PATCH] notify reporting users of the decisions made on DSA reports --- .../core/server/graph/mutators/DSAReports.ts | 3 +- .../src/core/server/locales/en-US/common.ftl | 18 +++ .../server/services/dsaReports/reports.ts | 28 +++- .../notifications/internal/context.ts | 125 +++++++++++++++++- 4 files changed, 163 insertions(+), 11 deletions(-) diff --git a/server/src/core/server/graph/mutators/DSAReports.ts b/server/src/core/server/graph/mutators/DSAReports.ts index 294c3f6ce7..404973f38d 100644 --- a/server/src/core/server/graph/mutators/DSAReports.ts +++ b/server/src/core/server/graph/mutators/DSAReports.ts @@ -61,7 +61,7 @@ export const DSAReports = (ctx: GraphContext) => ({ reportID, }: GQLChangeDSAReportStatusInput) => changeDSAReportStatus(ctx.mongo, ctx.tenant, { userID, status, reportID }), - makeDSAReportDecision: ({ + makeDSAReportDecision: async ({ userID, legality, legalGrounds, @@ -78,6 +78,7 @@ export const DSAReports = (ctx: GraphContext) => ({ ctx.broker, ctx.notifications, ctx.tenant, + await ctx.loaders.Comments.comment.load(commentID), { userID, legality, diff --git a/server/src/core/server/locales/en-US/common.ftl b/server/src/core/server/locales/en-US/common.ftl index 33369c6570..435594c1b2 100644 --- a/server/src/core/server/locales/en-US/common.ftl +++ b/server/src/core/server/locales/en-US/common.ftl @@ -46,6 +46,7 @@ notifications-commentWasApproved-title = Comment was approved notifications-commentWasApproved-body = The comment { $commentID } was approved. notifications-commentWasRejected-title = Comment was rejected notifications-commentWasRejected-body = The comment { $commentID } was rejected. + notifications-commentWasRejectedAndIllegal-title = Comment was deemed to contain illegal content and was rejected notifications-commentWasRejectedAndIllegal-body = The comment { $commentID } was rejected for containing illegal content. @@ -61,3 +62,20 @@ notifications-dsaIllegalRejectedReason-information =
{ $explanation } notifications-dsaIllegalRejectedReason-informationNotFound = The reasoning for this decision cannot be found. + +notifications-dsaReportDecisionMade-title = A decision was made on your DSA report +notifications-dsaReportDecision-legal = The report { $reportID } was determined to be legal. +notifications-dsaReportDecision-illegal = The report { $reportID } was determined to be illegal. +notifications-dsaReportDecision-legalInformation = + Grounds: +
+ { $grounds } +
+ Explanation: +
+ { $explanation } +notifications-dsaReportDecisionMade-body-withoutInfo = { $decision } +notifications-dsaReportDecisionMade-body-withInfo = + { $decision } +
+ { $information } diff --git a/server/src/core/server/services/dsaReports/reports.ts b/server/src/core/server/services/dsaReports/reports.ts index cdace3b8c5..c894dd644e 100644 --- a/server/src/core/server/services/dsaReports/reports.ts +++ b/server/src/core/server/services/dsaReports/reports.ts @@ -2,6 +2,7 @@ import { Config } from "coral-server/config"; import { DataCache } from "coral-server/data/cache/dataCache"; import { MongoContext } from "coral-server/data/context"; import { CoralEventPublisherBroker } from "coral-server/events/publisher"; +import { Comment } from "coral-server/models/comment"; import { changeDSAReportStatus as changeReportStatus, createDSAReport as createReport, @@ -9,6 +10,7 @@ import { createDSAReportShare as createReportShare, deleteDSAReportNote as deleteReportNote, makeDSAReportDecision as makeReportDecision, + retrieveDSAReport, } from "coral-server/models/dsaReport/report"; import { Tenant } from "coral-server/models/tenant"; import { rejectComment } from "coral-server/stacks"; @@ -180,6 +182,7 @@ export async function makeDSAReportDecision( broker: CoralEventPublisherBroker, notifications: InternalNotificationContext, tenant: Tenant, + comment: Readonly | null, input: MakeDSAReportDecisionInput, now = new Date() ) { @@ -190,11 +193,12 @@ export async function makeDSAReportDecision( userID, legalGrounds, detailedExplanation, + reportID, } = input; // REJECT if ILLEGAL if (input.legality === GQLDSAReportDecisionLegality.ILLEGAL) { - const comment = await rejectComment( + const rejectedComment = await rejectComment( mongo, redis, cache, @@ -210,12 +214,13 @@ export async function makeDSAReportDecision( false ); - if (comment.authorID) { + if (rejectedComment.authorID) { await notifications.create(tenant.id, tenant.locale, { - targetUserID: comment.authorID, + targetUserID: rejectedComment.authorID, type: NotificationType.ILLEGAL_REJECTED, - comment, + comment: rejectedComment, legal: { + legality: input.legality, grounds: legalGrounds, explanation: detailedExplanation, }, @@ -223,6 +228,21 @@ export async function makeDSAReportDecision( } } + const report = await retrieveDSAReport(mongo, tenant.id, reportID); + if (report) { + await notifications.create(tenant.id, tenant.locale, { + targetUserID: report.userID, + type: NotificationType.DSA_REPORT_DECISION_MADE, + comment, + report, + legal: { + legality: input.legality, + grounds: legalGrounds, + explanation: detailedExplanation, + }, + }); + } + const result = await makeReportDecision(mongo, tenant.id, input, now); const { dsaReport } = result; diff --git a/server/src/core/server/services/notifications/internal/context.ts b/server/src/core/server/services/notifications/internal/context.ts index 34db369da2..5d7cd8d0cf 100644 --- a/server/src/core/server/services/notifications/internal/context.ts +++ b/server/src/core/server/services/notifications/internal/context.ts @@ -12,14 +12,18 @@ import { import { retrieveUser } from "coral-server/models/user"; import { I18n, translate } from "coral-server/services/i18n"; +import { GQLDSAReportDecisionLegality } from "coral-server/graph/schema/__generated__/types"; + export enum NotificationType { COMMENT_FEATURED = "COMMENT_FEATURED", COMMENT_APPROVED = "COMMENT_APPROVED", COMMENT_REJECTED = "COMMENT_REJECTED", ILLEGAL_REJECTED = "ILLEGAL_REJECTED", + DSA_REPORT_DECISION_MADE = "DSA_REPORT_DECISION_MADE", } -export interface LegalExplanation { +export interface Legality { + legality: GQLDSAReportDecisionLegality; grounds?: string; explanation?: string; } @@ -28,10 +32,10 @@ export interface CreateNotificationInput { targetUserID: string; type: NotificationType; - comment?: Readonly; - report?: Readonly; + comment?: Readonly | null; + report?: Readonly | null; - legal?: LegalExplanation; + legal?: Legality; } interface CreationResult { @@ -55,7 +59,7 @@ export class InternalNotificationContext { lang: LanguageCode, input: CreateNotificationInput ) { - const { type, targetUserID, comment, legal } = input; + const { type, targetUserID, comment, report, legal } = input; const existingUser = retrieveUser(this.mongo, tenantID, targetUserID); if (!existingUser) { @@ -152,6 +156,21 @@ export class InternalNotificationContext { now ); result.attempted = true; + } else if ( + type === NotificationType.DSA_REPORT_DECISION_MADE && + comment && + report + ) { + result.notification = await this.createDSAReportDecisionMadeNotification( + lang, + tenantID, + targetUserID, + comment, + report, + legal, + now + ); + result.attempted = true; } if (!result.notification && result.attempted) { @@ -164,7 +183,7 @@ export class InternalNotificationContext { tenantID: string, targetUserID: string, comment: Readonly, - legal: LegalExplanation | undefined, + legal: Legality | undefined, now: Date ) { const reason = legal @@ -216,6 +235,100 @@ export class InternalNotificationContext { return notification; } + private async createDSAReportDecisionMadeNotification( + lang: LanguageCode, + tenantID: string, + targetUserID: string, + comment: Readonly, + report: Readonly, + legal: Legality | undefined, + now: Date + ) { + if (!legal) { + this.log.warn( + { reportID: report.id, commentID: comment.id, targetUserID }, + "attempted to notify of DSA report decision when legality was null or undefined" + ); + return null; + } + + let decision = ""; + let information: string | null = null; + if (legal.legality === GQLDSAReportDecisionLegality.LEGAL) { + decision = this.translatePhrase( + lang, + "notifications-dsaReportDecision-legal", + `The report ${report.id} was determined to be legal.`, + { + reportID: report.id, + } + ); + } + if (legal.legality === GQLDSAReportDecisionLegality.ILLEGAL) { + decision = this.translatePhrase( + lang, + "notifications-dsaReportDecision-illegal", + `The report ${report.id} was determined to be illegal.`, + { + reportID: report.id, + } + ); + information = this.translatePhrase( + lang, + "notifications-dsaReportDecision-legalInformation", + `Grounds: +
+ ${legal.grounds} +
+ Explanation: +
+ ${legal.explanation}`, + { + grounds: legal.grounds, + explanation: legal.explanation, + } + ); + } + + const body = this.translatePhrase( + lang, + information + ? "notifications-dsaReportDecisionMade-body-withInfo" + : "notifications-dsaReportDecisionMade-body-withoutInfo", + information + ? `${decision} +
+ ${information}` + : `${decision}`, + information + ? { + decision, + information, + } + : { + decision, + } + ).replace("\n", "
"); + + const notification = await createNotification(this.mongo, { + id: uuid(), + tenantID, + createdAt: now, + ownerID: targetUserID, + title: this.translatePhrase( + lang, + "notifications-dsaReportDecisionMade-title", + "A decision was made on your DSA report" + ), + body, + commentID: comment.id, + commentStatus: comment.status, + reportID: report.id, + }); + + return notification; + } + private translatePhrase( lang: LanguageCode, key: string,