Add migrations for abuse messages
This commit is contained in:
parent
594d3e48d8
commit
d573926e9b
|
@ -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)[]
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
@ -144,13 +145,11 @@
|
|||
|
||||
</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)">
|
||||
|
@ -161,6 +160,10 @@
|
|||
</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"
|
||||
|
|
|
@ -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
|
||||
},
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -20,6 +20,7 @@ textarea {
|
|||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow-y: scroll;
|
||||
max-height: 50vh;
|
||||
}
|
||||
|
||||
.no-messages {
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
},
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -46,20 +46,38 @@
|
|||
<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>
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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" \
|
||||
|
|
|
@ -23,7 +23,7 @@ import { CONFIG, registerConfigChangedHandler } from './config'
|
|||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const LAST_MIGRATION_VERSION = 520
|
||||
const LAST_MIGRATION_VERSION = 525
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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;")
|
||||
|
|
|
@ -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'}
|
||||
|
|
|
@ -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 () {
|
||||
|
|
Loading…
Reference in New Issue