]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/commitdiff
Add migrations for abuse messages
authorChocobozzz <me@florianbigard.com>
Tue, 28 Jul 2020 07:57:16 +0000 (09:57 +0200)
committerChocobozzz <chocobozzz@cpy.re>
Fri, 31 Jul 2020 09:35:19 +0000 (11:35 +0200)
18 files changed:
client/src/app/+my-account/my-account-settings/my-account-notification-preferences/my-account-notification-preferences.component.ts
client/src/app/core/renderer/html-renderer.service.ts
client/src/app/shared/shared-abuse-list/abuse-list-table.component.html
client/src/app/shared/shared-abuse-list/abuse-list-table.component.ts
client/src/app/shared/shared-abuse-list/abuse-message-modal.component.html
client/src/app/shared/shared-abuse-list/abuse-message-modal.component.scss
client/src/app/shared/shared-abuse-list/abuse-message-modal.component.ts
client/src/app/shared/shared-main/users/user-notification.model.ts
client/src/app/shared/shared-main/users/user-notification.service.ts
client/src/app/shared/shared-main/users/user-notifications.component.html
client/src/app/shared/shared-main/users/user-notifications.component.ts
scripts/dev/server.sh
server/initializers/constants.ts
server/initializers/migrations/0525-abuse-messages.ts [new file with mode: 0644]
server/lib/emailer.ts
server/lib/emails/abuse-new-message/html.pug
server/lib/emails/abuse-state-change/html.pug
server/lib/notifier.ts

index 8562e564b85ffa5bac14fcef09392c487c0a2ba2..89a04c07893525ee5fc0627ba98336499507736d 100644 (file)
@@ -42,7 +42,9 @@ export class MyAccountNotificationPreferencesComponent implements OnInit {
       newFollow: this.i18n('You or your channel(s) has a new follower'),
       commentMention: this.i18n('Someone mentioned you in video comments'),
       newInstanceFollower: this.i18n('Your instance has a new follower'),
-      autoInstanceFollowing: this.i18n('Your instance auto followed another instance')
+      autoInstanceFollowing: this.i18n('Your instance auto followed another instance'),
+      abuseNewMessage: this.i18n('An abuse received a new message'),
+      abuseStateChange: this.i18n('One of your abuse has been accepted or rejected by moderators')
     }
     this.notificationSettingKeys = Object.keys(this.labelNotifications) as (keyof UserNotificationSetting)[]
 
index f0527c759355312477af13bcae6e4e3ad77771f8..302d92ed961a7e2da2a4439947f535b888353fc6 100644 (file)
@@ -3,19 +3,29 @@ import { LinkifierService } from './linkifier.service'
 
 @Injectable()
 export class HtmlRendererService {
+  private sanitizeHtml: typeof import ('sanitize-html')
 
   constructor (private linkifier: LinkifierService) {
 
   }
 
+  async convertToBr (text: string) {
+    await this.loadSanitizeHtml()
+
+    const html = text.replace(/\r?\n/g, '<br />')
+
+    return this.sanitizeHtml(html, {
+      allowedTags: [ 'br' ]
+    })
+  }
+
   async toSafeHtml (text: string) {
-    // FIXME: import('..') returns a struct module, containing a "default" field corresponding to our sanitizeHtml function
-    const sanitizeHtml: typeof import ('sanitize-html') = (await import('sanitize-html') as any).default
+    await this.loadSanitizeHtml()
 
     // Convert possible markdown to html
     const html = this.linkifier.linkify(text)
 
-    return sanitizeHtml(html, {
+    return this.sanitizeHtml(html, {
       allowedTags: [ 'a', 'p', 'span', 'br', 'strong', 'em', 'ul', 'ol', 'li' ],
       allowedSchemes: [ 'http', 'https' ],
       allowedAttributes: {
@@ -37,4 +47,9 @@ export class HtmlRendererService {
       }
     })
   }
+
+  private async loadSanitizeHtml () {
+    // FIXME: import('..') returns a struct module, containing a "default" field corresponding to our sanitizeHtml function
+    this.sanitizeHtml = (await import('sanitize-html') as any).default
+  }
 }
index 17b3742d6d40d11010708b06343602b64d094ada..d90b93fff61ae260fe954b475e793faf1d401dec 100644 (file)
@@ -42,6 +42,7 @@
       <th style="width: 150px;" i18n pSortableColumn="createdAt">Created <p-sortIcon field="createdAt"></p-sortIcon></th>
       <th i18n pSortableColumn="state" style="width: 80px;">State <p-sortIcon field="state"></p-sortIcon></th>
       <th i18n style="width: 80px;">Messages</th>
+      <th i18n *ngIf="isAdminView()" style="width: 100px;">Internal note</th>
       <th style="width: 150px;"></th>
     </tr>
   </ng-template>
 
       </ng-container>
 
-
       <td class="c-hand" [pRowToggler]="abuse">{{ abuse.createdAt | date: 'short'  }}</td>
 
       <td class="c-hand abuse-states" [pRowToggler]="abuse">
         <span *ngIf="isAbuseAccepted(abuse)" [title]="abuse.state.label" class="glyphicon glyphicon-ok"></span>
         <span *ngIf="isAbuseRejected(abuse)" [title]="abuse.state.label" class="glyphicon glyphicon-remove"></span>
-        <span *ngIf="abuse.moderationComment" container="body" placement="left auto" [ngbTooltip]="abuse.moderationComment" class="glyphicon glyphicon-comment"></span>
       </td>
 
       <td class="c-hand abuse-messages" (click)="openAbuseMessagesModal(abuse)">
         </ng-container>
       </td>
 
+      <td *ngIf="isAdminView()" class="internal-note" container="body" placement="left auto" [ngbTooltip]="abuse.moderationComment">
+        {{ abuse.moderationComment }}
+      </td>
+
       <td class="action-cell">
         <my-action-dropdown
           [ngClass]="{ 'show': expanded }" placement="bottom-right top-right left auto" container="body"
index 1d17c9ec99c3b215b99bb1892f1ab3431aa1029a..21d2ea47da5e75757f040075bf21368cf18ac5b4 100644 (file)
@@ -278,7 +278,7 @@ export class AbuseListTableComponent extends RestTable implements OnInit, AfterV
         isDisplayed: abuse => this.isLocalAbuse(abuse)
       },
       {
-        label: this.i18n('Update note'),
+        label: this.i18n('Update internal note'),
         handler: abuse => this.openModerationCommentModal(abuse),
         isDisplayed: abuse => this.isAdminView() && !!abuse.moderationComment
       },
index cb965b71db0b05f987125bcf21f15f626dc2e9b2..17e9ce4cf31415412c0c613fb32e91b993c16311 100644 (file)
@@ -9,7 +9,7 @@
   </div>
 
   <div class="modal-body">
-    <div class="messages" #messagesBlock>
+    <div class="messages">
       <div
         *ngFor="let message of abuseMessages"
         class="message-block" [ngClass]="{ 'by-moderator': message.byModerator, 'by-me': isMessageByMe(message) }"
@@ -18,7 +18,7 @@
         <div class="author">{{ message.account.name }}</div>
 
         <div class="bubble">
-          <div class="content">{{ message.message }}</div>
+          <div class="content" [innerHTML]="message.messageHtml"></div>
           <div class="date">{{ message.createdAt | date }}</div>
         </div>
       </div>
index 4dd025fc464359175ab2197f85b952771efaf966..4163722ddd42b15887610f34969231a041ee540e 100644 (file)
@@ -20,6 +20,7 @@ textarea {
   display: flex;
   flex-direction: column;
   overflow-y: scroll;
+  max-height: 50vh;
 }
 
 .no-messages {
index 03f5ad735980b427905b3d10f2a17ed2d0fff2dd..6686d91f4ea3de8c0794956cc54fd2604c2d6fc2 100644 (file)
@@ -1,5 +1,5 @@
 import { Component, ElementRef, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core'
-import { AuthService, Notifier } from '@app/core'
+import { AuthService, Notifier, HtmlRendererService } from '@app/core'
 import { AbuseValidatorsService, FormReactive, FormValidatorService } from '@app/shared/shared-forms'
 import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
 import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref'
@@ -14,13 +14,12 @@ import { AbuseService } from '../shared-moderation'
 })
 export class AbuseMessageModalComponent extends FormReactive implements OnInit {
   @ViewChild('modal', { static: true }) modal: NgbModal
-  @ViewChild('messagesBlock', { static: false }) messagesBlock: ElementRef
 
   @Input() isAdminView: boolean
 
   @Output() countMessagesUpdated = new EventEmitter<{ abuseId: number, countMessages: number }>()
 
-  abuseMessages: AbuseMessage[] = []
+  abuseMessages: (AbuseMessage & { messageHtml: string })[] = []
   textareaMessage: string
   sendingMessage = false
   noResults = false
@@ -33,6 +32,7 @@ export class AbuseMessageModalComponent extends FormReactive implements OnInit {
     private abuseValidatorsService: AbuseValidatorsService,
     private modalService: NgbModal,
     private i18n: I18n,
+    private htmlRenderer: HtmlRendererService,
     private auth: AuthService,
     private notifier: Notifier,
     private abuseService: AbuseService
@@ -108,15 +108,21 @@ export class AbuseMessageModalComponent extends FormReactive implements OnInit {
   private loadMessages () {
     this.abuseService.listAbuseMessages(this.abuse)
       .subscribe(
-        res => {
-          this.abuseMessages = res.data
+        async res => {
+          this.abuseMessages = []
+
+          for (const m of res.data) {
+            this.abuseMessages.push(Object.assign(m, {
+              messageHtml: await this.htmlRenderer.convertToBr(m.message)
+            }))
+          }
+
           this.noResults = this.abuseMessages.length === 0
 
           setTimeout(() => {
-            if (!this.messagesBlock) return
-
-            const element = this.messagesBlock.nativeElement as HTMLElement
-            element.scrollIntoView({ block: 'end', inline: 'nearest' })
+            // Don't use ViewChild: it is not supported inside a ng-template
+            const messagesBlock = document.querySelector('.messages')
+            messagesBlock.scroll(0, messagesBlock.scrollHeight)
           })
         },
 
index 61b48a8066ab64aaa971e03655fe1e2b704a98f0..a068daaacf0c486117b4d91617c9e149ccad581a 100644 (file)
@@ -1,5 +1,14 @@
+import {
+  AbuseState,
+  ActorInfo,
+  FollowState,
+  UserNotification as UserNotificationServer,
+  UserNotificationType,
+  VideoInfo,
+  UserRight
+} from '@shared/models'
 import { Actor } from '../account/actor.model'
-import { ActorInfo, Avatar, FollowState, UserNotification as UserNotificationServer, UserNotificationType, VideoInfo } from '@shared/models'
+import { AuthUser } from '@app/core'
 
 export class UserNotification implements UserNotificationServer {
   id: number
@@ -27,6 +36,7 @@ export class UserNotification implements UserNotificationServer {
 
   abuse?: {
     id: number
+    state: AbuseState
 
     video?: VideoInfo
 
@@ -69,13 +79,14 @@ export class UserNotification implements UserNotificationServer {
   videoUrl?: string
   commentUrl?: any[]
   abuseUrl?: string
+  abuseQueryParams?: { [id: string]: string } = {}
   videoAutoBlacklistUrl?: string
   accountUrl?: string
   videoImportIdentifier?: string
   videoImportUrl?: string
   instanceFollowUrl?: string
 
-  constructor (hash: UserNotificationServer) {
+  constructor (hash: UserNotificationServer, user: AuthUser) {
     this.id = hash.id
     this.type = hash.type
     this.read = hash.read
@@ -122,12 +133,25 @@ export class UserNotification implements UserNotificationServer {
 
         case UserNotificationType.NEW_ABUSE_FOR_MODERATORS:
           this.abuseUrl = '/admin/moderation/abuses/list'
+          this.abuseQueryParams.search = '#' + this.abuse.id
 
           if (this.abuse.video) this.videoUrl = this.buildVideoUrl(this.abuse.video)
           else if (this.abuse.comment) this.commentUrl = this.buildCommentUrl(this.abuse.comment)
           else if (this.abuse.account) this.accountUrl = this.buildAccountUrl(this.abuse.account)
           break
 
+        case UserNotificationType.ABUSE_STATE_CHANGE:
+          this.abuseUrl = '/my-account/abuses'
+          this.abuseQueryParams.search = '#' + this.abuse.id
+          break
+
+        case UserNotificationType.ABUSE_NEW_MESSAGE:
+          this.abuseUrl = user.hasRight(UserRight.MANAGE_ABUSES)
+            ? '/admin/moderation/abuses/list'
+            : '/my-account/abuses'
+          this.abuseQueryParams.search = '#' + this.abuse.id
+          break
+
         case UserNotificationType.VIDEO_AUTO_BLACKLIST_FOR_MODERATORS:
           this.videoAutoBlacklistUrl = '/admin/moderation/video-auto-blacklist/list'
           // Backward compatibility where we did not assign videoBlacklist to this type of notification before
index ecc66ecdbb8221c3db89f513640ff5c1a6b23bbe..7b9dc34be417e9e09bfd1fceed60e062edf17daf 100644 (file)
@@ -1,7 +1,7 @@
 import { catchError, map, tap } from 'rxjs/operators'
 import { HttpClient, HttpParams } from '@angular/common/http'
 import { Injectable } from '@angular/core'
-import { ComponentPaginationLight, RestExtractor, RestService, User, UserNotificationSocket } from '@app/core'
+import { ComponentPaginationLight, RestExtractor, RestService, User, UserNotificationSocket, AuthService } from '@app/core'
 import { ResultList, UserNotification as UserNotificationServer, UserNotificationSetting } from '@shared/models'
 import { environment } from '../../../../environments/environment'
 import { UserNotification } from './user-notification.model'
@@ -14,6 +14,7 @@ export class UserNotificationService {
 
   constructor (
     private authHttp: HttpClient,
+    private auth: AuthService,
     private restExtractor: RestExtractor,
     private restService: RestService,
     private userNotificationSocket: UserNotificationSocket
@@ -84,6 +85,6 @@ export class UserNotificationService {
   }
 
   private formatNotification (notification: UserNotificationServer) {
-    return new UserNotification(notification)
+    return new UserNotification(notification, this.auth.getUser())
   }
 }
index 8127ae979c9fa18751585711f31a326a82a2b388..a56a0859b24e6a5b7a2bce15760cdc2f45ebcaa0 100644 (file)
         <my-global-icon iconName="flag" aria-hidden="true"></my-global-icon>
 
         <div class="message" *ngIf="notification.videoUrl" i18n>
-          <a (click)="markAsRead(notification)" [routerLink]="notification.abuseUrl">A new video abuse</a> has been created on video <a (click)="markAsRead(notification)" [routerLink]="notification.videoUrl">{{ notification.abuse.video.name }}</a>
+          <a (click)="markAsRead(notification)" [routerLink]="notification.abuseUrl" [queryParams]="notification.abuseQueryParams">A new video abuse</a> has been created on video <a (click)="markAsRead(notification)" [routerLink]="notification.videoUrl">{{ notification.abuse.video.name }}</a>
         </div>
 
         <div class="message" *ngIf="notification.commentUrl" i18n>
-          <a (click)="markAsRead(notification)" [routerLink]="notification.abuseUrl">A new comment abuse</a> has been created on video <a (click)="markAsRead(notification)" [routerLink]="notification.commentUrl">{{ notification.abuse.comment.video.name }}</a>
+          <a (click)="markAsRead(notification)" [routerLink]="notification.abuseUrl" [queryParams]="notification.abuseQueryParams">A new comment abuse</a> has been created on video <a (click)="markAsRead(notification)" [routerLink]="notification.commentUrl">{{ notification.abuse.comment.video.name }}</a>
         </div>
 
         <div class="message" *ngIf="notification.accountUrl" i18n>
-          <a (click)="markAsRead(notification)" [routerLink]="notification.abuseUrl">A new account abuse</a> has been created on account <a (click)="markAsRead(notification)" [routerLink]="notification.accountUrl">{{ notification.abuse.account.displayName }}</a>
+          <a (click)="markAsRead(notification)" [routerLink]="notification.abuseUrl" [queryParams]="notification.abuseQueryParams">A new account abuse</a> has been created on account <a (click)="markAsRead(notification)" [routerLink]="notification.accountUrl">{{ notification.abuse.account.displayName }}</a>
         </div>
 
         <!-- Deleted entity associated to the abuse -->
         <div class="message" *ngIf="!notification.videoUrl && !notification.commentUrl && !notification.accountUrl" i18n>
-          <a (click)="markAsRead(notification)" [routerLink]="notification.abuseUrl">A new abuse</a> has been created
+          <a (click)="markAsRead(notification)" [routerLink]="notification.abuseUrl" [queryParams]="notification.abuseQueryParams">A new abuse</a> has been created
+        </div>
+      </ng-container>
+
+      <ng-container *ngSwitchCase="UserNotificationType.ABUSE_STATE_CHANGE">
+        <my-global-icon iconName="flag" aria-hidden="true"></my-global-icon>
+
+        <div class="message" i18n>
+          <a (click)="markAsRead(notification)" [routerLink]="notification.abuseUrl" [queryParams]="notification.abuseQueryParams">Your abuse {{ notification.abuse.id }}</a> has been
+          <ng-container *ngIf="isAccepted(notification)">accepted</ng-container>
+          <ng-container *ngIf="!isAccepted(notification)">rejected</ng-container>
+        </div>
+      </ng-container>
+
+      <ng-container *ngSwitchCase="UserNotificationType.ABUSE_NEW_MESSAGE">
+        <my-global-icon iconName="flag" aria-hidden="true"></my-global-icon>
+
+        <div class="message" i18n>
+          <a (click)="markAsRead(notification)" [routerLink]="notification.abuseUrl" [queryParams]="notification.abuseQueryParams">Abuse {{ notification.abuse.id }}</a> has a new message
         </div>
       </ng-container>
 
index 7518dbdd0181b83da68e7ce6167ebd649af96709..387c49d94ee0c4bd2e0ecf458e691d496eed9b27 100644 (file)
@@ -1,7 +1,7 @@
 import { Subject } from 'rxjs'
 import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'
 import { ComponentPagination, hasMoreItems, Notifier } from '@app/core'
-import { UserNotificationType } from '@shared/models'
+import { UserNotificationType, AbuseState } from '@shared/models'
 import { UserNotification } from './user-notification.model'
 import { UserNotificationService } from './user-notification.service'
 
@@ -116,4 +116,8 @@ export class UserNotificationsComponent implements OnInit {
     this.sortField = column
     this.loadNotifications(true)
   }
+
+  isAccepted (notification: UserNotification) {
+    return notification.abuse.state === AbuseState.ACCEPTED
+  }
 }
index 680ca3d7915bf608c8c1730701e826575b8612af..5aac470eba73b1e84d948e54b13c990d1e395bbf 100755 (executable)
@@ -21,6 +21,7 @@ cp "./tsconfig.json" "./dist"
 
 npm run tsc -- --incremental --sourceMap
 cp -r ./server/static ./server/assets ./dist/server
+cp -r "./server/lib/emails" "./dist/server/lib"
 
 NODE_ENV=test node node_modules/.bin/concurrently -k \
   "node_modules/.bin/nodemon --delay 1 --watch ./dist dist/server" \
index a40a2239580c76aef1cc2a1ce65968d6aba194e6..ca6c2a7ffbb2ea6dc68d0d172ab7e760c47f3b74 100644 (file)
@@ -23,7 +23,7 @@ import { CONFIG, registerConfigChangedHandler } from './config'
 
 // ---------------------------------------------------------------------------
 
-const LAST_MIGRATION_VERSION = 520
+const LAST_MIGRATION_VERSION = 525
 
 // ---------------------------------------------------------------------------
 
diff --git a/server/initializers/migrations/0525-abuse-messages.ts b/server/initializers/migrations/0525-abuse-messages.ts
new file mode 100644 (file)
index 0000000..c8fd7cb
--- /dev/null
@@ -0,0 +1,54 @@
+import * as Sequelize from 'sequelize'
+
+async function up (utils: {
+  transaction: Sequelize.Transaction
+  queryInterface: Sequelize.QueryInterface
+  sequelize: Sequelize.Sequelize
+}): Promise<void> {
+  await utils.sequelize.query(`
+    CREATE TABLE IF NOT EXISTS "abuseMessage" (
+      "id" serial,
+      "message" text NOT NULL,
+      "byModerator" boolean NOT NULL,
+      "accountId" integer REFERENCES "account" ("id") ON DELETE SET NULL ON UPDATE CASCADE,
+      "abuseId" integer NOT NULL REFERENCES "abuse" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
+      "createdAt" timestamp WITH time zone NOT NULL,
+      "updatedAt" timestamp WITH time zone NOT NULL,
+      PRIMARY KEY ("id")
+    );
+  `)
+
+  const notificationSettingColumns = [ 'abuseStateChange', 'abuseNewMessage' ]
+
+  for (const column of notificationSettingColumns) {
+    const data = {
+      type: Sequelize.INTEGER,
+      defaultValue: null,
+      allowNull: true
+    }
+    await utils.queryInterface.addColumn('userNotificationSetting', column, data)
+  }
+
+  {
+    const query = 'UPDATE "userNotificationSetting" SET "abuseStateChange" = 3, "abuseNewMessage" = 3'
+    await utils.sequelize.query(query)
+  }
+
+  for (const column of notificationSettingColumns) {
+    const data = {
+      type: Sequelize.INTEGER,
+      defaultValue: null,
+      allowNull: false
+    }
+    await utils.queryInterface.changeColumn('userNotificationSetting', column, data)
+  }
+}
+
+function down (options) {
+  throw new Error('Not implemented.')
+}
+
+export {
+  up,
+  down
+}
index 9c49aa2f6549e76c4b22db95baed6ca2782f90e0..25b0aaeddcf8415ac603e9a0022f3d32344e678c 100644 (file)
@@ -11,7 +11,7 @@ import { isTestInstance, root } from '../helpers/core-utils'
 import { bunyanLogger, logger } from '../helpers/logger'
 import { CONFIG, isEmailEnabled } from '../initializers/config'
 import { WEBSERVER } from '../initializers/constants'
-import { MAbuseFull, MAbuseMessage, MActorFollowActors, MActorFollowFull, MUser } from '../types/models'
+import { MAbuseFull, MAbuseMessage, MAccountDefault, MActorFollowActors, MActorFollowFull, MUser } from '../types/models'
 import { MCommentOwnerVideo, MVideo, MVideoAccountLight } from '../types/models/video'
 import { JobQueue } from './job-queue'
 
@@ -362,9 +362,11 @@ class Emailer {
       ? 'Report #' + abuse.id + ' has been accepted'
       : 'Report #' + abuse.id + ' has been rejected'
 
+    const abuseUrl = WEBSERVER.URL + '/my-account/abuses?search=%23' + abuse.id
+
     const action = {
       text,
-      url: WEBSERVER.URL + '/my-account/abuses?search=%23' + abuse.id
+      url: abuseUrl
     }
 
     const emailPayload: EmailPayload = {
@@ -374,6 +376,7 @@ class Emailer {
       locals: {
         action,
         abuseId: abuse.id,
+        abuseUrl,
         isAccepted: abuse.state === AbuseState.ACCEPTED
       }
     }
@@ -381,15 +384,24 @@ class Emailer {
     return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
   }
 
-  addAbuseNewMessageNotification (to: string[], options: { target: 'moderator' | 'reporter', abuse: MAbuseFull, message: MAbuseMessage }) {
-    const { abuse, target, message } = options
+  addAbuseNewMessageNotification (
+    to: string[],
+    options: {
+      target: 'moderator' | 'reporter'
+      abuse: MAbuseFull
+      message: MAbuseMessage
+      accountMessage: MAccountDefault
+    }) {
+    const { abuse, target, message, accountMessage } = options
+
+    const text = 'New message on report #' + abuse.id
+    const abuseUrl = target === 'moderator'
+      ? WEBSERVER.URL + '/admin/moderation/abuses/list?search=%23' + abuse.id
+      : WEBSERVER.URL + '/my-account/abuses?search=%23' + abuse.id
 
-    const text = 'New message on abuse #' + abuse.id
     const action = {
       text,
-      url: target === 'moderator'
-        ? WEBSERVER.URL + '/admin/moderation/abuses/list?search=%23' + abuse.id
-        : WEBSERVER.URL + '/my-account/abuses?search=%23' + abuse.id
+      url: abuseUrl
     }
 
     const emailPayload: EmailPayload = {
@@ -397,7 +409,9 @@ class Emailer {
       to,
       subject: text,
       locals: {
+        abuseId: abuse.id,
         abuseUrl: action.url,
+        messageAccountName: accountMessage.getDisplayName(),
         messageText: message.message,
         action
       }
index a4180aba1cfe52b7762cabfb2113221b92ce5475..0841775d2479f1212dab11883b45ae50f4174baf 100644 (file)
@@ -2,10 +2,10 @@ extends ../common/greetings
 include ../common/mixins.pug
 
 block title
-  | New abuse message
+  | New message on abuse report
 
 block content
   p
-    | A new message was created on #[a(href=WEBSERVER.URL) abuse ##{abuseId} on #{WEBSERVER.HOST}]
+    | A new message by #{messageAccountName} was posted on #[a(href=abuseUrl) abuse report ##{abuseId}] on #{WEBSERVER.HOST}
   blockquote #{messageText}
   br(style="display: none;")
index a94c8521d04087f4607260694778bbc0bd49d225..ca89a2f054074d78ecf3e45f299039addf6f96cb 100644 (file)
@@ -2,8 +2,8 @@ extends ../common/greetings
 include ../common/mixins.pug
 
 block title
-  | Abuse state changed
+  | Abuse report state changed
 
 block content
   p
-    | #[a(href=abuseUrl) Your abuse ##{abuseId} on #{WEBSERVER.HOST}] has been #{isAccepted ? 'accepted' : 'rejected'}
+    | #[a(href=abuseUrl) Your abuse report ##{abuseId}] on #{WEBSERVER.HOST} has been #{isAccepted ? 'accepted' : 'rejected'}
index 5c50fcf01b355891c579f46cab29bb5b57209cd4..9c2f16c2768c68127f7b733635363ca861b8f501 100644 (file)
@@ -24,6 +24,7 @@ import { MCommentOwnerVideo, MVideoAccountLight, MVideoFullLight } from '../type
 import { isBlockedByServerOrAccount } from './blocklist'
 import { Emailer } from './emailer'
 import { PeerTubeSocket } from './peertube-socket'
+import { AccountModel } from '@server/models/account/account'
 
 class Notifier {
 
@@ -137,7 +138,7 @@ class Notifier {
       })
   }
 
-  notifyOnAbuseMessage (abuse: MAbuseFull, message: AbuseMessageModel): void {
+  notifyOnAbuseMessage (abuse: MAbuseFull, message: MAbuseMessage): void {
     this.notifyOfNewAbuseMessage(abuse, message)
       .catch(err => {
         logger.error('Cannot notify on new abuse %d message.', abuse.id, { err })
@@ -436,6 +437,8 @@ class Notifier {
     const url = this.getAbuseUrl(abuse)
     logger.info('Notifying reporter and moderators of new abuse message on %s.', url)
 
+    const accountMessage = await AccountModel.load(message.accountId)
+
     function settingGetter (user: MUserWithNotificationSetting) {
       return user.NotificationSetting.abuseNewMessage
     }
@@ -452,11 +455,11 @@ class Notifier {
     }
 
     function emailSenderReporter (emails: string[]) {
-      return Emailer.Instance.addAbuseNewMessageNotification(emails, { target: 'reporter', abuse, message })
+      return Emailer.Instance.addAbuseNewMessageNotification(emails, { target: 'reporter', abuse, message, accountMessage })
     }
 
     function emailSenderModerators (emails: string[]) {
-      return Emailer.Instance.addAbuseNewMessageNotification(emails, { target: 'moderator', abuse, message })
+      return Emailer.Instance.addAbuseNewMessageNotification(emails, { target: 'moderator', abuse, message, accountMessage })
     }
 
     async function buildReporterOptions () {