Add abuse messages/states notifications
This commit is contained in:
parent
94148c9028
commit
594d3e48d8
|
@ -16,11 +16,11 @@
|
||||||
|
|
||||||
<div role="menu" ngbDropdownMenu>
|
<div role="menu" ngbDropdownMenu>
|
||||||
<h6 class="dropdown-header" i18n>Advanced report filters</h6>
|
<h6 class="dropdown-header" i18n>Advanced report filters</h6>
|
||||||
<a [routerLink]="[ '/admin/moderation/abuses/list' ]" [queryParams]="{ 'search': 'state:pending' }" class="dropdown-item" i18n>Unsolved reports</a>
|
<a [routerLink]="[ baseRoute ]" [queryParams]="{ 'search': 'state:pending' }" class="dropdown-item" i18n>Unsolved reports</a>
|
||||||
<a [routerLink]="[ '/admin/moderation/abuses/list' ]" [queryParams]="{ 'search': 'state:accepted' }" class="dropdown-item" i18n>Accepted reports</a>
|
<a [routerLink]="[ baseRoute ]" [queryParams]="{ 'search': 'state:accepted' }" class="dropdown-item" i18n>Accepted reports</a>
|
||||||
<a [routerLink]="[ '/admin/moderation/abuses/list' ]" [queryParams]="{ 'search': 'state:rejected' }" class="dropdown-item" i18n>Refused reports</a>
|
<a [routerLink]="[ baseRoute ]" [queryParams]="{ 'search': 'state:rejected' }" class="dropdown-item" i18n>Refused reports</a>
|
||||||
<a [routerLink]="[ '/admin/moderation/abuses/list' ]" [queryParams]="{ 'search': 'videoIs:blacklisted' }" class="dropdown-item" i18n>Reports with blocked videos</a>
|
<a [routerLink]="[ baseRoute ]" [queryParams]="{ 'search': 'videoIs:blacklisted' }" class="dropdown-item" i18n>Reports with blocked videos</a>
|
||||||
<a [routerLink]="[ '/admin/moderation/abuses/list' ]" [queryParams]="{ 'search': 'videoIs:deleted' }" class="dropdown-item" i18n>Reports with deleted videos</a>
|
<a [routerLink]="[ baseRoute ]" [queryParams]="{ 'search': 'videoIs:deleted' }" class="dropdown-item" i18n>Reports with deleted videos</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<input
|
<input
|
||||||
|
|
|
@ -25,6 +25,8 @@ import {
|
||||||
setDefaultSort
|
setDefaultSort
|
||||||
} from '../../middlewares'
|
} from '../../middlewares'
|
||||||
import { AccountModel } from '../../models/account/account'
|
import { AccountModel } from '../../models/account/account'
|
||||||
|
import { Notifier } from '@server/lib/notifier'
|
||||||
|
import { logger } from '@server/helpers/logger'
|
||||||
|
|
||||||
const abuseRouter = express.Router()
|
const abuseRouter = express.Router()
|
||||||
|
|
||||||
|
@ -123,19 +125,28 @@ async function listAbusesForAdmins (req: express.Request, res: express.Response)
|
||||||
|
|
||||||
async function updateAbuse (req: express.Request, res: express.Response) {
|
async function updateAbuse (req: express.Request, res: express.Response) {
|
||||||
const abuse = res.locals.abuse
|
const abuse = res.locals.abuse
|
||||||
|
let stateUpdated = false
|
||||||
|
|
||||||
if (req.body.moderationComment !== undefined) abuse.moderationComment = req.body.moderationComment
|
if (req.body.moderationComment !== undefined) abuse.moderationComment = req.body.moderationComment
|
||||||
if (req.body.state !== undefined) abuse.state = req.body.state
|
|
||||||
|
if (req.body.state !== undefined) {
|
||||||
|
abuse.state = req.body.state
|
||||||
|
stateUpdated = true
|
||||||
|
}
|
||||||
|
|
||||||
await sequelizeTypescript.transaction(t => {
|
await sequelizeTypescript.transaction(t => {
|
||||||
return abuse.save({ transaction: t })
|
return abuse.save({ transaction: t })
|
||||||
})
|
})
|
||||||
|
|
||||||
// TODO: Notification
|
if (stateUpdated === true) {
|
||||||
|
AbuseModel.loadFull(abuse.id)
|
||||||
|
.then(abuseFull => Notifier.Instance.notifyOnAbuseStateChange(abuseFull))
|
||||||
|
.catch(err => logger.error('Cannot notify on abuse state change', { err }))
|
||||||
|
}
|
||||||
|
|
||||||
// Do not send the delete to other instances, we updated OUR copy of this abuse
|
// Do not send the delete to other instances, we updated OUR copy of this abuse
|
||||||
|
|
||||||
return res.type('json').status(204).end()
|
return res.sendStatus(204)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function deleteAbuse (req: express.Request, res: express.Response) {
|
async function deleteAbuse (req: express.Request, res: express.Response) {
|
||||||
|
@ -147,7 +158,7 @@ async function deleteAbuse (req: express.Request, res: express.Response) {
|
||||||
|
|
||||||
// Do not send the delete to other instances, we delete OUR copy of this abuse
|
// Do not send the delete to other instances, we delete OUR copy of this abuse
|
||||||
|
|
||||||
return res.type('json').status(204).end()
|
return res.sendStatus(204)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function reportAbuse (req: express.Request, res: express.Response) {
|
async function reportAbuse (req: express.Request, res: express.Response) {
|
||||||
|
@ -219,7 +230,9 @@ async function addAbuseMessage (req: express.Request, res: express.Response) {
|
||||||
abuseId: abuse.id
|
abuseId: abuse.id
|
||||||
})
|
})
|
||||||
|
|
||||||
// TODO: Notification
|
AbuseModel.loadFull(abuse.id)
|
||||||
|
.then(abuseFull => Notifier.Instance.notifyOnAbuseMessage(abuseFull, abuseMessage))
|
||||||
|
.catch(err => logger.error('Cannot notify on new abuse message', { err }))
|
||||||
|
|
||||||
return res.json({
|
return res.json({
|
||||||
abuseMessage: {
|
abuseMessage: {
|
||||||
|
|
|
@ -77,7 +77,9 @@ async function updateNotificationSettings (req: express.Request, res: express.Re
|
||||||
newUserRegistration: body.newUserRegistration,
|
newUserRegistration: body.newUserRegistration,
|
||||||
commentMention: body.commentMention,
|
commentMention: body.commentMention,
|
||||||
newInstanceFollower: body.newInstanceFollower,
|
newInstanceFollower: body.newInstanceFollower,
|
||||||
autoInstanceFollowing: body.autoInstanceFollowing
|
autoInstanceFollowing: body.autoInstanceFollowing,
|
||||||
|
abuseNewMessage: body.abuseNewMessage,
|
||||||
|
abuseStateChange: body.abuseStateChange
|
||||||
}
|
}
|
||||||
|
|
||||||
await UserNotificationSettingModel.update(values, query)
|
await UserNotificationSettingModel.update(values, query)
|
||||||
|
|
|
@ -5,13 +5,13 @@ import { join } from 'path'
|
||||||
import { VideoChannelModel } from '@server/models/video/video-channel'
|
import { VideoChannelModel } from '@server/models/video/video-channel'
|
||||||
import { MVideoBlacklistLightVideo, MVideoBlacklistVideo } from '@server/types/models/video/video-blacklist'
|
import { MVideoBlacklistLightVideo, MVideoBlacklistVideo } from '@server/types/models/video/video-blacklist'
|
||||||
import { MVideoImport, MVideoImportVideo } from '@server/types/models/video/video-import'
|
import { MVideoImport, MVideoImportVideo } from '@server/types/models/video/video-import'
|
||||||
import { UserAbuse, EmailPayload } from '@shared/models'
|
import { AbuseState, EmailPayload, UserAbuse } from '@shared/models'
|
||||||
import { SendEmailOptions } from '../../shared/models/server/emailer.model'
|
import { SendEmailOptions } from '../../shared/models/server/emailer.model'
|
||||||
import { isTestInstance, root } from '../helpers/core-utils'
|
import { isTestInstance, root } from '../helpers/core-utils'
|
||||||
import { bunyanLogger, logger } from '../helpers/logger'
|
import { bunyanLogger, logger } from '../helpers/logger'
|
||||||
import { CONFIG, isEmailEnabled } from '../initializers/config'
|
import { CONFIG, isEmailEnabled } from '../initializers/config'
|
||||||
import { WEBSERVER } from '../initializers/constants'
|
import { WEBSERVER } from '../initializers/constants'
|
||||||
import { MAbuseFull, MActorFollowActors, MActorFollowFull, MUser } from '../types/models'
|
import { MAbuseFull, MAbuseMessage, MActorFollowActors, MActorFollowFull, MUser } from '../types/models'
|
||||||
import { MCommentOwnerVideo, MVideo, MVideoAccountLight } from '../types/models/video'
|
import { MCommentOwnerVideo, MVideo, MVideoAccountLight } from '../types/models/video'
|
||||||
import { JobQueue } from './job-queue'
|
import { JobQueue } from './job-queue'
|
||||||
|
|
||||||
|
@ -357,6 +357,55 @@ class Emailer {
|
||||||
return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
|
return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addAbuseStateChangeNotification (to: string[], abuse: MAbuseFull) {
|
||||||
|
const text = abuse.state === AbuseState.ACCEPTED
|
||||||
|
? 'Report #' + abuse.id + ' has been accepted'
|
||||||
|
: 'Report #' + abuse.id + ' has been rejected'
|
||||||
|
|
||||||
|
const action = {
|
||||||
|
text,
|
||||||
|
url: WEBSERVER.URL + '/my-account/abuses?search=%23' + abuse.id
|
||||||
|
}
|
||||||
|
|
||||||
|
const emailPayload: EmailPayload = {
|
||||||
|
template: 'abuse-state-change',
|
||||||
|
to,
|
||||||
|
subject: text,
|
||||||
|
locals: {
|
||||||
|
action,
|
||||||
|
abuseId: abuse.id,
|
||||||
|
isAccepted: abuse.state === AbuseState.ACCEPTED
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
|
||||||
|
}
|
||||||
|
|
||||||
|
addAbuseNewMessageNotification (to: string[], options: { target: 'moderator' | 'reporter', abuse: MAbuseFull, message: MAbuseMessage }) {
|
||||||
|
const { abuse, target, message } = options
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
const emailPayload: EmailPayload = {
|
||||||
|
template: 'abuse-new-message',
|
||||||
|
to,
|
||||||
|
subject: text,
|
||||||
|
locals: {
|
||||||
|
abuseUrl: action.url,
|
||||||
|
messageText: message.message,
|
||||||
|
action
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
|
||||||
|
}
|
||||||
|
|
||||||
async addVideoAutoBlacklistModeratorsNotification (to: string[], videoBlacklist: MVideoBlacklistLightVideo) {
|
async addVideoAutoBlacklistModeratorsNotification (to: string[], videoBlacklist: MVideoBlacklistLightVideo) {
|
||||||
const VIDEO_AUTO_BLACKLIST_URL = WEBSERVER.URL + '/admin/moderation/video-auto-blacklist/list'
|
const VIDEO_AUTO_BLACKLIST_URL = WEBSERVER.URL + '/admin/moderation/video-auto-blacklist/list'
|
||||||
const videoUrl = WEBSERVER.URL + videoBlacklist.Video.getWatchStaticPath()
|
const videoUrl = WEBSERVER.URL + videoBlacklist.Video.getWatchStaticPath()
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
extends ../common/greetings
|
||||||
|
include ../common/mixins.pug
|
||||||
|
|
||||||
|
block title
|
||||||
|
| New abuse message
|
||||||
|
|
||||||
|
block content
|
||||||
|
p
|
||||||
|
| A new message was created on #[a(href=WEBSERVER.URL) abuse ##{abuseId} on #{WEBSERVER.HOST}]
|
||||||
|
blockquote #{messageText}
|
||||||
|
br(style="display: none;")
|
|
@ -0,0 +1,9 @@
|
||||||
|
extends ../common/greetings
|
||||||
|
include ../common/mixins.pug
|
||||||
|
|
||||||
|
block title
|
||||||
|
| Abuse state changed
|
||||||
|
|
||||||
|
block content
|
||||||
|
p
|
||||||
|
| #[a(href=abuseUrl) Your abuse ##{abuseId} on #{WEBSERVER.HOST}] has been #{isAccepted ? 'accepted' : 'rejected'}
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { AbuseMessageModel } from '@server/models/abuse/abuse-message'
|
||||||
import { getServerActor } from '@server/models/application/application'
|
import { getServerActor } from '@server/models/application/application'
|
||||||
import { ServerBlocklistModel } from '@server/models/server/server-blocklist'
|
import { ServerBlocklistModel } from '@server/models/server/server-blocklist'
|
||||||
import {
|
import {
|
||||||
|
@ -18,7 +19,7 @@ import { CONFIG } from '../initializers/config'
|
||||||
import { AccountBlocklistModel } from '../models/account/account-blocklist'
|
import { AccountBlocklistModel } from '../models/account/account-blocklist'
|
||||||
import { UserModel } from '../models/account/user'
|
import { UserModel } from '../models/account/user'
|
||||||
import { UserNotificationModel } from '../models/account/user-notification'
|
import { UserNotificationModel } from '../models/account/user-notification'
|
||||||
import { MAbuseFull, MAccountServer, MActorFollowFull } from '../types/models'
|
import { MAbuseFull, MAbuseMessage, MAccountServer, MActorFollowFull } from '../types/models'
|
||||||
import { MCommentOwnerVideo, MVideoAccountLight, MVideoFullLight } from '../types/models/video'
|
import { MCommentOwnerVideo, MVideoAccountLight, MVideoFullLight } from '../types/models/video'
|
||||||
import { isBlockedByServerOrAccount } from './blocklist'
|
import { isBlockedByServerOrAccount } from './blocklist'
|
||||||
import { Emailer } from './emailer'
|
import { Emailer } from './emailer'
|
||||||
|
@ -129,6 +130,20 @@ class Notifier {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
notifyOnAbuseStateChange (abuse: MAbuseFull): void {
|
||||||
|
this.notifyReporterOfAbuseStateChange(abuse)
|
||||||
|
.catch(err => {
|
||||||
|
logger.error('Cannot notify reporter of abuse %d state change.', abuse.id, { err })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
notifyOnAbuseMessage (abuse: MAbuseFull, message: AbuseMessageModel): void {
|
||||||
|
this.notifyOfNewAbuseMessage(abuse, message)
|
||||||
|
.catch(err => {
|
||||||
|
logger.error('Cannot notify on new abuse %d message.', abuse.id, { err })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
private async notifySubscribersOfNewVideo (video: MVideoAccountLight) {
|
private async notifySubscribersOfNewVideo (video: MVideoAccountLight) {
|
||||||
// List all followers that are users
|
// List all followers that are users
|
||||||
const users = await UserModel.listUserSubscribersOf(video.VideoChannel.actorId)
|
const users = await UserModel.listUserSubscribersOf(video.VideoChannel.actorId)
|
||||||
|
@ -359,9 +374,7 @@ class Notifier {
|
||||||
const moderators = await UserModel.listWithRight(UserRight.MANAGE_ABUSES)
|
const moderators = await UserModel.listWithRight(UserRight.MANAGE_ABUSES)
|
||||||
if (moderators.length === 0) return
|
if (moderators.length === 0) return
|
||||||
|
|
||||||
const url = abuseInstance.VideoAbuse?.Video?.url ||
|
const url = this.getAbuseUrl(abuseInstance)
|
||||||
abuseInstance.VideoCommentAbuse?.VideoComment?.url ||
|
|
||||||
abuseInstance.FlaggedAccount.Actor.url
|
|
||||||
|
|
||||||
logger.info('Notifying %s user/moderators of new abuse %s.', moderators.length, url)
|
logger.info('Notifying %s user/moderators of new abuse %s.', moderators.length, url)
|
||||||
|
|
||||||
|
@ -387,6 +400,97 @@ class Notifier {
|
||||||
return this.notify({ users: moderators, settingGetter, notificationCreator, emailSender })
|
return this.notify({ users: moderators, settingGetter, notificationCreator, emailSender })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async notifyReporterOfAbuseStateChange (abuse: MAbuseFull) {
|
||||||
|
// Only notify our users
|
||||||
|
if (abuse.ReporterAccount.isOwned() !== true) return
|
||||||
|
|
||||||
|
const url = this.getAbuseUrl(abuse)
|
||||||
|
|
||||||
|
logger.info('Notifying reporter of abuse % of state change.', url)
|
||||||
|
|
||||||
|
const reporter = await UserModel.loadByAccountActorId(abuse.ReporterAccount.actorId)
|
||||||
|
|
||||||
|
function settingGetter (user: MUserWithNotificationSetting) {
|
||||||
|
return user.NotificationSetting.abuseStateChange
|
||||||
|
}
|
||||||
|
|
||||||
|
async function notificationCreator (user: MUserWithNotificationSetting) {
|
||||||
|
const notification = await UserNotificationModel.create<UserNotificationModelForApi>({
|
||||||
|
type: UserNotificationType.ABUSE_STATE_CHANGE,
|
||||||
|
userId: user.id,
|
||||||
|
abuseId: abuse.id
|
||||||
|
})
|
||||||
|
notification.Abuse = abuse
|
||||||
|
|
||||||
|
return notification
|
||||||
|
}
|
||||||
|
|
||||||
|
function emailSender (emails: string[]) {
|
||||||
|
return Emailer.Instance.addAbuseStateChangeNotification(emails, abuse)
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.notify({ users: [ reporter ], settingGetter, notificationCreator, emailSender })
|
||||||
|
}
|
||||||
|
|
||||||
|
private async notifyOfNewAbuseMessage (abuse: MAbuseFull, message: MAbuseMessage) {
|
||||||
|
const url = this.getAbuseUrl(abuse)
|
||||||
|
logger.info('Notifying reporter and moderators of new abuse message on %s.', url)
|
||||||
|
|
||||||
|
function settingGetter (user: MUserWithNotificationSetting) {
|
||||||
|
return user.NotificationSetting.abuseNewMessage
|
||||||
|
}
|
||||||
|
|
||||||
|
async function notificationCreator (user: MUserWithNotificationSetting) {
|
||||||
|
const notification = await UserNotificationModel.create<UserNotificationModelForApi>({
|
||||||
|
type: UserNotificationType.ABUSE_NEW_MESSAGE,
|
||||||
|
userId: user.id,
|
||||||
|
abuseId: abuse.id
|
||||||
|
})
|
||||||
|
notification.Abuse = abuse
|
||||||
|
|
||||||
|
return notification
|
||||||
|
}
|
||||||
|
|
||||||
|
function emailSenderReporter (emails: string[]) {
|
||||||
|
return Emailer.Instance.addAbuseNewMessageNotification(emails, { target: 'reporter', abuse, message })
|
||||||
|
}
|
||||||
|
|
||||||
|
function emailSenderModerators (emails: string[]) {
|
||||||
|
return Emailer.Instance.addAbuseNewMessageNotification(emails, { target: 'moderator', abuse, message })
|
||||||
|
}
|
||||||
|
|
||||||
|
async function buildReporterOptions () {
|
||||||
|
// Only notify our users
|
||||||
|
if (abuse.ReporterAccount.isOwned() !== true) return
|
||||||
|
|
||||||
|
const reporter = await UserModel.loadByAccountActorId(abuse.ReporterAccount.actorId)
|
||||||
|
// Don't notify my own message
|
||||||
|
if (reporter.Account.id === message.accountId) return
|
||||||
|
|
||||||
|
return { users: [ reporter ], settingGetter, notificationCreator, emailSender: emailSenderReporter }
|
||||||
|
}
|
||||||
|
|
||||||
|
async function buildModeratorsOptions () {
|
||||||
|
let moderators = await UserModel.listWithRight(UserRight.MANAGE_ABUSES)
|
||||||
|
// Don't notify my own message
|
||||||
|
moderators = moderators.filter(m => m.Account.id !== message.accountId)
|
||||||
|
|
||||||
|
if (moderators.length === 0) return
|
||||||
|
|
||||||
|
return { users: moderators, settingGetter, notificationCreator, emailSender: emailSenderModerators }
|
||||||
|
}
|
||||||
|
|
||||||
|
const [ reporterOptions, moderatorsOptions ] = await Promise.all([
|
||||||
|
buildReporterOptions(),
|
||||||
|
buildModeratorsOptions()
|
||||||
|
])
|
||||||
|
|
||||||
|
return Promise.all([
|
||||||
|
this.notify(reporterOptions),
|
||||||
|
this.notify(moderatorsOptions)
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
private async notifyModeratorsOfVideoAutoBlacklist (videoBlacklist: MVideoBlacklistLightVideo) {
|
private async notifyModeratorsOfVideoAutoBlacklist (videoBlacklist: MVideoBlacklistLightVideo) {
|
||||||
const moderators = await UserModel.listWithRight(UserRight.MANAGE_VIDEO_BLACKLIST)
|
const moderators = await UserModel.listWithRight(UserRight.MANAGE_VIDEO_BLACKLIST)
|
||||||
if (moderators.length === 0) return
|
if (moderators.length === 0) return
|
||||||
|
@ -599,6 +703,12 @@ class Notifier {
|
||||||
return isBlockedByServerOrAccount(targetAccount, user?.Account)
|
return isBlockedByServerOrAccount(targetAccount, user?.Account)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private getAbuseUrl (abuse: MAbuseFull) {
|
||||||
|
return abuse.VideoAbuse?.Video?.url ||
|
||||||
|
abuse.VideoCommentAbuse?.VideoComment?.url ||
|
||||||
|
abuse.FlaggedAccount.Actor.url
|
||||||
|
}
|
||||||
|
|
||||||
static get Instance () {
|
static get Instance () {
|
||||||
return this.instance || (this.instance = new this())
|
return this.instance || (this.instance = new this())
|
||||||
}
|
}
|
||||||
|
|
|
@ -141,6 +141,8 @@ function createDefaultUserNotificationSettings (user: MUserId, t: Transaction |
|
||||||
commentMention: UserNotificationSettingValue.WEB,
|
commentMention: UserNotificationSettingValue.WEB,
|
||||||
newFollow: UserNotificationSettingValue.WEB,
|
newFollow: UserNotificationSettingValue.WEB,
|
||||||
newInstanceFollower: UserNotificationSettingValue.WEB,
|
newInstanceFollower: UserNotificationSettingValue.WEB,
|
||||||
|
abuseNewMessage: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
|
||||||
|
abuseStateChange: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
|
||||||
autoInstanceFollowing: UserNotificationSettingValue.WEB
|
autoInstanceFollowing: UserNotificationSettingValue.WEB
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -32,14 +32,14 @@ import {
|
||||||
UserVideoAbuse
|
UserVideoAbuse
|
||||||
} from '@shared/models'
|
} from '@shared/models'
|
||||||
import { ABUSE_STATES, CONSTRAINTS_FIELDS } from '../../initializers/constants'
|
import { ABUSE_STATES, CONSTRAINTS_FIELDS } from '../../initializers/constants'
|
||||||
import { MAbuse, MAbuseAdminFormattable, MAbuseAP, MAbuseReporter, MAbuseUserFormattable, MUserAccountId } from '../../types/models'
|
import { MAbuseAdminFormattable, MAbuseAP, MAbuseFull, MAbuseReporter, MAbuseUserFormattable, MUserAccountId } from '../../types/models'
|
||||||
import { AccountModel, ScopeNames as AccountScopeNames, SummaryOptions as AccountSummaryOptions } from '../account/account'
|
import { AccountModel, ScopeNames as AccountScopeNames, SummaryOptions as AccountSummaryOptions } from '../account/account'
|
||||||
import { getSort, throwIfNotValid } from '../utils'
|
import { getSort, throwIfNotValid } from '../utils'
|
||||||
import { ThumbnailModel } from '../video/thumbnail'
|
import { ThumbnailModel } from '../video/thumbnail'
|
||||||
import { VideoModel } from '../video/video'
|
import { ScopeNames as VideoScopeNames, VideoModel } from '../video/video'
|
||||||
import { VideoBlacklistModel } from '../video/video-blacklist'
|
import { VideoBlacklistModel } from '../video/video-blacklist'
|
||||||
import { ScopeNames as VideoChannelScopeNames, SummaryOptions as ChannelSummaryOptions, VideoChannelModel } from '../video/video-channel'
|
import { ScopeNames as VideoChannelScopeNames, SummaryOptions as ChannelSummaryOptions, VideoChannelModel } from '../video/video-channel'
|
||||||
import { VideoCommentModel } from '../video/video-comment'
|
import { ScopeNames as CommentScopeNames, VideoCommentModel } from '../video/video-comment'
|
||||||
import { buildAbuseListQuery, BuildAbusesQueryOptions } from './abuse-query-builder'
|
import { buildAbuseListQuery, BuildAbusesQueryOptions } from './abuse-query-builder'
|
||||||
import { VideoAbuseModel } from './video-abuse'
|
import { VideoAbuseModel } from './video-abuse'
|
||||||
import { VideoCommentAbuseModel } from './video-comment-abuse'
|
import { VideoCommentAbuseModel } from './video-comment-abuse'
|
||||||
|
@ -307,6 +307,52 @@ export class AbuseModel extends Model<AbuseModel> {
|
||||||
return AbuseModel.findOne(query)
|
return AbuseModel.findOne(query)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static loadFull (id: number): Bluebird<MAbuseFull> {
|
||||||
|
const query = {
|
||||||
|
where: {
|
||||||
|
id
|
||||||
|
},
|
||||||
|
include: [
|
||||||
|
{
|
||||||
|
model: AccountModel.scope(AccountScopeNames.SUMMARY),
|
||||||
|
required: false,
|
||||||
|
as: 'ReporterAccount'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
model: AccountModel.scope(AccountScopeNames.SUMMARY),
|
||||||
|
as: 'FlaggedAccount'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
model: VideoAbuseModel,
|
||||||
|
required: false,
|
||||||
|
include: [
|
||||||
|
{
|
||||||
|
model: VideoModel.scope([ VideoScopeNames.WITH_ACCOUNT_DETAILS ])
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
model: VideoCommentAbuseModel,
|
||||||
|
required: false,
|
||||||
|
include: [
|
||||||
|
{
|
||||||
|
model: VideoCommentModel.scope([
|
||||||
|
CommentScopeNames.WITH_ACCOUNT
|
||||||
|
]),
|
||||||
|
include: [
|
||||||
|
{
|
||||||
|
model: VideoModel
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
return AbuseModel.findOne(query)
|
||||||
|
}
|
||||||
|
|
||||||
static async listForAdminApi (parameters: {
|
static async listForAdminApi (parameters: {
|
||||||
start: number
|
start: number
|
||||||
count: number
|
count: number
|
||||||
|
@ -455,7 +501,7 @@ export class AbuseModel extends Model<AbuseModel> {
|
||||||
blacklisted: abuseModel.Video?.isBlacklisted() || false,
|
blacklisted: abuseModel.Video?.isBlacklisted() || false,
|
||||||
thumbnailPath: abuseModel.Video?.getMiniatureStaticPath(),
|
thumbnailPath: abuseModel.Video?.getMiniatureStaticPath(),
|
||||||
|
|
||||||
channel: abuseModel.Video?.VideoChannel.toFormattedJSON() || abuseModel.deletedVideo?.channel,
|
channel: abuseModel.Video?.VideoChannel.toFormattedJSON() || abuseModel.deletedVideo?.channel
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,12 +12,12 @@ import {
|
||||||
Table,
|
Table,
|
||||||
UpdatedAt
|
UpdatedAt
|
||||||
} from 'sequelize-typescript'
|
} from 'sequelize-typescript'
|
||||||
|
import { MNotificationSettingFormattable } from '@server/types/models'
|
||||||
|
import { UserNotificationSetting, UserNotificationSettingValue } from '../../../shared/models/users/user-notification-setting.model'
|
||||||
|
import { isUserNotificationSettingValid } from '../../helpers/custom-validators/user-notifications'
|
||||||
|
import { clearCacheByUserId } from '../../lib/oauth-model'
|
||||||
import { throwIfNotValid } from '../utils'
|
import { throwIfNotValid } from '../utils'
|
||||||
import { UserModel } from './user'
|
import { UserModel } from './user'
|
||||||
import { isUserNotificationSettingValid } from '../../helpers/custom-validators/user-notifications'
|
|
||||||
import { UserNotificationSetting, UserNotificationSettingValue } from '../../../shared/models/users/user-notification-setting.model'
|
|
||||||
import { clearCacheByUserId } from '../../lib/oauth-model'
|
|
||||||
import { MNotificationSettingFormattable } from '@server/types/models'
|
|
||||||
|
|
||||||
@Table({
|
@Table({
|
||||||
tableName: 'userNotificationSetting',
|
tableName: 'userNotificationSetting',
|
||||||
|
@ -138,6 +138,24 @@ export class UserNotificationSettingModel extends Model<UserNotificationSettingM
|
||||||
@Column
|
@Column
|
||||||
commentMention: UserNotificationSettingValue
|
commentMention: UserNotificationSettingValue
|
||||||
|
|
||||||
|
@AllowNull(false)
|
||||||
|
@Default(null)
|
||||||
|
@Is(
|
||||||
|
'UserNotificationSettingAbuseStateChange',
|
||||||
|
value => throwIfNotValid(value, isUserNotificationSettingValid, 'abuseStateChange')
|
||||||
|
)
|
||||||
|
@Column
|
||||||
|
abuseStateChange: UserNotificationSettingValue
|
||||||
|
|
||||||
|
@AllowNull(false)
|
||||||
|
@Default(null)
|
||||||
|
@Is(
|
||||||
|
'UserNotificationSettingAbuseNewMessage',
|
||||||
|
value => throwIfNotValid(value, isUserNotificationSettingValid, 'abuseNewMessage')
|
||||||
|
)
|
||||||
|
@Column
|
||||||
|
abuseNewMessage: UserNotificationSettingValue
|
||||||
|
|
||||||
@ForeignKey(() => UserModel)
|
@ForeignKey(() => UserModel)
|
||||||
@Column
|
@Column
|
||||||
userId: number
|
userId: number
|
||||||
|
@ -175,7 +193,9 @@ export class UserNotificationSettingModel extends Model<UserNotificationSettingM
|
||||||
commentMention: this.commentMention,
|
commentMention: this.commentMention,
|
||||||
newFollow: this.newFollow,
|
newFollow: this.newFollow,
|
||||||
newInstanceFollower: this.newInstanceFollower,
|
newInstanceFollower: this.newInstanceFollower,
|
||||||
autoInstanceFollowing: this.autoInstanceFollowing
|
autoInstanceFollowing: this.autoInstanceFollowing,
|
||||||
|
abuseNewMessage: this.abuseNewMessage,
|
||||||
|
abuseStateChange: this.abuseStateChange
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -88,7 +88,7 @@ function buildAccountInclude (required: boolean, withActor = false) {
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
attributes: [ 'id' ],
|
attributes: [ 'id', 'state' ],
|
||||||
model: AbuseModel.unscoped(),
|
model: AbuseModel.unscoped(),
|
||||||
required: false,
|
required: false,
|
||||||
include: [
|
include: [
|
||||||
|
@ -504,6 +504,7 @@ export class UserNotificationModel extends Model<UserNotificationModel> {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: abuse.id,
|
id: abuse.id,
|
||||||
|
state: abuse.state,
|
||||||
video: videoAbuse,
|
video: videoAbuse,
|
||||||
comment: commentAbuse,
|
comment: commentAbuse,
|
||||||
account: accountAbuse
|
account: accountAbuse
|
||||||
|
|
|
@ -44,7 +44,7 @@ import { buildBlockedAccountSQL, buildLocalAccountIdsIn, getCommentSort, throwIf
|
||||||
import { VideoModel } from './video'
|
import { VideoModel } from './video'
|
||||||
import { VideoChannelModel } from './video-channel'
|
import { VideoChannelModel } from './video-channel'
|
||||||
|
|
||||||
enum ScopeNames {
|
export enum ScopeNames {
|
||||||
WITH_ACCOUNT = 'WITH_ACCOUNT',
|
WITH_ACCOUNT = 'WITH_ACCOUNT',
|
||||||
WITH_ACCOUNT_FOR_API = 'WITH_ACCOUNT_FOR_API',
|
WITH_ACCOUNT_FOR_API = 'WITH_ACCOUNT_FOR_API',
|
||||||
WITH_IN_REPLY_TO = 'WITH_IN_REPLY_TO',
|
WITH_IN_REPLY_TO = 'WITH_IN_REPLY_TO',
|
||||||
|
|
|
@ -173,7 +173,9 @@ describe('Test user notifications API validators', function () {
|
||||||
newFollow: UserNotificationSettingValue.WEB,
|
newFollow: UserNotificationSettingValue.WEB,
|
||||||
newUserRegistration: UserNotificationSettingValue.WEB,
|
newUserRegistration: UserNotificationSettingValue.WEB,
|
||||||
newInstanceFollower: UserNotificationSettingValue.WEB,
|
newInstanceFollower: UserNotificationSettingValue.WEB,
|
||||||
autoInstanceFollowing: UserNotificationSettingValue.WEB
|
autoInstanceFollowing: UserNotificationSettingValue.WEB,
|
||||||
|
abuseNewMessage: UserNotificationSettingValue.WEB,
|
||||||
|
abuseStateChange: UserNotificationSettingValue.WEB
|
||||||
}
|
}
|
||||||
|
|
||||||
it('Should fail with missing fields', async function () {
|
it('Should fail with missing fields', async function () {
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
import 'mocha'
|
import 'mocha'
|
||||||
import { v4 as uuidv4 } from 'uuid'
|
import { v4 as uuidv4 } from 'uuid'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
addVideoCommentThread,
|
addVideoCommentThread,
|
||||||
addVideoToBlacklist,
|
addVideoToBlacklist,
|
||||||
|
@ -21,7 +22,9 @@ import {
|
||||||
unfollow,
|
unfollow,
|
||||||
updateCustomConfig,
|
updateCustomConfig,
|
||||||
updateCustomSubConfig,
|
updateCustomSubConfig,
|
||||||
wait
|
wait,
|
||||||
|
updateAbuse,
|
||||||
|
addAbuseMessage
|
||||||
} from '../../../../shared/extra-utils'
|
} from '../../../../shared/extra-utils'
|
||||||
import { ServerInfo, uploadVideo } from '../../../../shared/extra-utils/index'
|
import { ServerInfo, uploadVideo } from '../../../../shared/extra-utils/index'
|
||||||
import { MockSmtpServer } from '../../../../shared/extra-utils/miscs/email'
|
import { MockSmtpServer } from '../../../../shared/extra-utils/miscs/email'
|
||||||
|
@ -38,12 +41,15 @@ import {
|
||||||
checkUserRegistered,
|
checkUserRegistered,
|
||||||
checkVideoAutoBlacklistForModerators,
|
checkVideoAutoBlacklistForModerators,
|
||||||
checkVideoIsPublished,
|
checkVideoIsPublished,
|
||||||
prepareNotificationsTest
|
prepareNotificationsTest,
|
||||||
|
checkAbuseStateChange,
|
||||||
|
checkNewAbuseMessage
|
||||||
} from '../../../../shared/extra-utils/users/user-notifications'
|
} from '../../../../shared/extra-utils/users/user-notifications'
|
||||||
import { addUserSubscription, removeUserSubscription } from '../../../../shared/extra-utils/users/user-subscriptions'
|
import { addUserSubscription, removeUserSubscription } from '../../../../shared/extra-utils/users/user-subscriptions'
|
||||||
import { CustomConfig } from '../../../../shared/models/server'
|
import { CustomConfig } from '../../../../shared/models/server'
|
||||||
import { UserNotification } from '../../../../shared/models/users'
|
import { UserNotification } from '../../../../shared/models/users'
|
||||||
import { VideoPrivacy } from '../../../../shared/models/videos'
|
import { VideoPrivacy } from '../../../../shared/models/videos'
|
||||||
|
import { AbuseState } from '@shared/models'
|
||||||
|
|
||||||
describe('Test moderation notifications', function () {
|
describe('Test moderation notifications', function () {
|
||||||
let servers: ServerInfo[] = []
|
let servers: ServerInfo[] = []
|
||||||
|
@ -65,7 +71,7 @@ describe('Test moderation notifications', function () {
|
||||||
adminNotificationsServer2 = res.adminNotificationsServer2
|
adminNotificationsServer2 = res.adminNotificationsServer2
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('Video abuse for moderators notification', function () {
|
describe('Abuse for moderators notification', function () {
|
||||||
let baseParams: CheckerBaseParams
|
let baseParams: CheckerBaseParams
|
||||||
|
|
||||||
before(() => {
|
before(() => {
|
||||||
|
@ -169,6 +175,122 @@ describe('Test moderation notifications', function () {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('Abuse state change notification', function () {
|
||||||
|
let baseParams: CheckerBaseParams
|
||||||
|
let abuseId: number
|
||||||
|
|
||||||
|
before(async function () {
|
||||||
|
baseParams = {
|
||||||
|
server: servers[0],
|
||||||
|
emails,
|
||||||
|
socketNotifications: userNotifications,
|
||||||
|
token: userAccessToken
|
||||||
|
}
|
||||||
|
|
||||||
|
const name = 'abuse ' + uuidv4()
|
||||||
|
const resVideo = await uploadVideo(servers[0].url, userAccessToken, { name })
|
||||||
|
const video = resVideo.body.video
|
||||||
|
|
||||||
|
const res = await reportAbuse({ url: servers[0].url, token: userAccessToken, videoId: video.id, reason: 'super reason' })
|
||||||
|
abuseId = res.body.abuse.id
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should send a notification to reporter if the abuse has been accepted', async function () {
|
||||||
|
this.timeout(10000)
|
||||||
|
|
||||||
|
await updateAbuse(servers[0].url, servers[0].accessToken, abuseId, { state: AbuseState.ACCEPTED })
|
||||||
|
await waitJobs(servers)
|
||||||
|
|
||||||
|
await checkAbuseStateChange(baseParams, abuseId, AbuseState.ACCEPTED, 'presence')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should send a notification to reporter if the abuse has been rejected', async function () {
|
||||||
|
this.timeout(10000)
|
||||||
|
|
||||||
|
await updateAbuse(servers[0].url, servers[0].accessToken, abuseId, { state: AbuseState.REJECTED })
|
||||||
|
await waitJobs(servers)
|
||||||
|
|
||||||
|
await checkAbuseStateChange(baseParams, abuseId, AbuseState.REJECTED, 'presence')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('New abuse message notification', function () {
|
||||||
|
let baseParamsUser: CheckerBaseParams
|
||||||
|
let baseParamsAdmin: CheckerBaseParams
|
||||||
|
let abuseId: number
|
||||||
|
let abuseId2: number
|
||||||
|
|
||||||
|
before(async function () {
|
||||||
|
baseParamsUser = {
|
||||||
|
server: servers[0],
|
||||||
|
emails,
|
||||||
|
socketNotifications: userNotifications,
|
||||||
|
token: userAccessToken
|
||||||
|
}
|
||||||
|
|
||||||
|
baseParamsAdmin = {
|
||||||
|
server: servers[0],
|
||||||
|
emails,
|
||||||
|
socketNotifications: adminNotifications,
|
||||||
|
token: servers[0].accessToken
|
||||||
|
}
|
||||||
|
|
||||||
|
const name = 'abuse ' + uuidv4()
|
||||||
|
const resVideo = await uploadVideo(servers[0].url, userAccessToken, { name })
|
||||||
|
const video = resVideo.body.video
|
||||||
|
|
||||||
|
{
|
||||||
|
const res = await reportAbuse({ url: servers[0].url, token: userAccessToken, videoId: video.id, reason: 'super reason' })
|
||||||
|
abuseId = res.body.abuse.id
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const res = await reportAbuse({ url: servers[0].url, token: userAccessToken, videoId: video.id, reason: 'super reason 2' })
|
||||||
|
abuseId2 = res.body.abuse.id
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should send a notification to reporter on new message', async function () {
|
||||||
|
this.timeout(10000)
|
||||||
|
|
||||||
|
const message = 'my super message to users'
|
||||||
|
await addAbuseMessage(servers[0].url, servers[0].accessToken, abuseId, message)
|
||||||
|
await waitJobs(servers)
|
||||||
|
|
||||||
|
await checkNewAbuseMessage(baseParamsUser, abuseId, message, 'user_1@example.com', 'presence')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should not send a notification to the admin if sent by the admin', async function () {
|
||||||
|
this.timeout(10000)
|
||||||
|
|
||||||
|
const message = 'my super message that should not be sent to the admin'
|
||||||
|
await addAbuseMessage(servers[0].url, servers[0].accessToken, abuseId, message)
|
||||||
|
await waitJobs(servers)
|
||||||
|
|
||||||
|
await checkNewAbuseMessage(baseParamsAdmin, abuseId, message, 'admin1@example.com', 'absence')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should send a notification to moderators', async function () {
|
||||||
|
this.timeout(10000)
|
||||||
|
|
||||||
|
const message = 'my super message to moderators'
|
||||||
|
await addAbuseMessage(servers[0].url, userAccessToken, abuseId2, message)
|
||||||
|
await waitJobs(servers)
|
||||||
|
|
||||||
|
await checkNewAbuseMessage(baseParamsAdmin, abuseId2, message, 'admin1@example.com', 'presence')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should not send a notification to reporter if sent by the reporter', async function () {
|
||||||
|
this.timeout(10000)
|
||||||
|
|
||||||
|
const message = 'my super message that should not be sent to reporter'
|
||||||
|
await addAbuseMessage(servers[0].url, userAccessToken, abuseId2, message)
|
||||||
|
await waitJobs(servers)
|
||||||
|
|
||||||
|
await checkNewAbuseMessage(baseParamsUser, abuseId2, message, 'user_1@example.com', 'absence')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
describe('Video blacklist on my video', function () {
|
describe('Video blacklist on my video', function () {
|
||||||
let baseParams: CheckerBaseParams
|
let baseParams: CheckerBaseParams
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,7 @@ import { AbuseModel } from '../../../models/abuse/abuse'
|
||||||
import { MAccountDefault, MAccountFormattable, MAccountLight, MAccountUrl, MAccount } from '../account'
|
import { MAccountDefault, MAccountFormattable, MAccountLight, MAccountUrl, MAccount } from '../account'
|
||||||
import { MCommentOwner, MCommentUrl, MVideoUrl, MCommentOwnerVideo, MComment, MCommentVideo } from '../video'
|
import { MCommentOwner, MCommentUrl, MVideoUrl, MCommentOwnerVideo, MComment, MCommentVideo } from '../video'
|
||||||
import { MVideo, MVideoAccountLightBlacklistAllFiles } from '../video/video'
|
import { MVideo, MVideoAccountLightBlacklistAllFiles } from '../video/video'
|
||||||
|
import { VideoCommentModel } from '@server/models/video/video-comment'
|
||||||
|
|
||||||
type Use<K extends keyof AbuseModel, M> = PickWith<AbuseModel, K, M>
|
type Use<K extends keyof AbuseModel, M> = PickWith<AbuseModel, K, M>
|
||||||
type UseVideoAbuse<K extends keyof VideoAbuseModel, M> = PickWith<VideoAbuseModel, K, M>
|
type UseVideoAbuse<K extends keyof VideoAbuseModel, M> = PickWith<VideoAbuseModel, K, M>
|
||||||
|
@ -34,7 +35,7 @@ export type MVideoAbuseVideoUrl =
|
||||||
|
|
||||||
export type MVideoAbuseVideoFull =
|
export type MVideoAbuseVideoFull =
|
||||||
MVideoAbuse &
|
MVideoAbuse &
|
||||||
UseVideoAbuse<'Video', MVideoAccountLightBlacklistAllFiles>
|
UseVideoAbuse<'Video', Omit<MVideoAccountLightBlacklistAllFiles, 'VideoFiles' | 'VideoStreamingPlaylists'>>
|
||||||
|
|
||||||
export type MVideoAbuseFormattable =
|
export type MVideoAbuseFormattable =
|
||||||
MVideoAbuse &
|
MVideoAbuse &
|
||||||
|
@ -49,7 +50,7 @@ export type MCommentAbuseAccount =
|
||||||
|
|
||||||
export type MCommentAbuseAccountVideo =
|
export type MCommentAbuseAccountVideo =
|
||||||
MCommentAbuse &
|
MCommentAbuse &
|
||||||
UseCommentAbuse<'VideoComment', MCommentOwnerVideo>
|
UseCommentAbuse<'VideoComment', MCommentOwner & PickWith<VideoCommentModel, 'Video', MVideo>>
|
||||||
|
|
||||||
export type MCommentAbuseUrl =
|
export type MCommentAbuseUrl =
|
||||||
MCommentAbuse &
|
MCommentAbuse &
|
||||||
|
@ -79,14 +80,6 @@ export type MAbuseAccountVideo =
|
||||||
Use<'VideoAbuse', MVideoAbuseVideoFull> &
|
Use<'VideoAbuse', MVideoAbuseVideoFull> &
|
||||||
Use<'ReporterAccount', MAccountDefault>
|
Use<'ReporterAccount', MAccountDefault>
|
||||||
|
|
||||||
export type MAbuseAP =
|
|
||||||
MAbuse &
|
|
||||||
Pick<AbuseModel, 'toActivityPubObject'> &
|
|
||||||
Use<'ReporterAccount', MAccountUrl> &
|
|
||||||
Use<'FlaggedAccount', MAccountUrl> &
|
|
||||||
Use<'VideoAbuse', MVideoAbuseVideo> &
|
|
||||||
Use<'VideoCommentAbuse', MCommentAbuseAccount>
|
|
||||||
|
|
||||||
export type MAbuseFull =
|
export type MAbuseFull =
|
||||||
MAbuse &
|
MAbuse &
|
||||||
Pick<AbuseModel, 'toActivityPubObject'> &
|
Pick<AbuseModel, 'toActivityPubObject'> &
|
||||||
|
@ -111,3 +104,11 @@ export type MAbuseUserFormattable =
|
||||||
Use<'FlaggedAccount', MAccountFormattable> &
|
Use<'FlaggedAccount', MAccountFormattable> &
|
||||||
Use<'VideoAbuse', MVideoAbuseFormattable> &
|
Use<'VideoAbuse', MVideoAbuseFormattable> &
|
||||||
Use<'VideoCommentAbuse', MCommentAbuseFormattable>
|
Use<'VideoCommentAbuse', MCommentAbuseFormattable>
|
||||||
|
|
||||||
|
export type MAbuseAP =
|
||||||
|
MAbuse &
|
||||||
|
Pick<AbuseModel, 'toActivityPubObject'> &
|
||||||
|
Use<'ReporterAccount', MAccountUrl> &
|
||||||
|
Use<'FlaggedAccount', MAccountUrl> &
|
||||||
|
Use<'VideoAbuse', MVideoAbuseVideo> &
|
||||||
|
Use<'VideoCommentAbuse', MCommentAbuseAccount>
|
||||||
|
|
|
@ -56,7 +56,7 @@ export module UserNotificationIncludes {
|
||||||
PickWith<VideoCommentModel, 'Video', Pick<VideoModel, 'id' | 'name' | 'uuid'>>>
|
PickWith<VideoCommentModel, 'Video', Pick<VideoModel, 'id' | 'name' | 'uuid'>>>
|
||||||
|
|
||||||
export type AbuseInclude =
|
export type AbuseInclude =
|
||||||
Pick<AbuseModel, 'id'> &
|
Pick<AbuseModel, 'id' | 'state'> &
|
||||||
PickWith<AbuseModel, 'VideoAbuse', VideoAbuseInclude> &
|
PickWith<AbuseModel, 'VideoAbuse', VideoAbuseInclude> &
|
||||||
PickWith<AbuseModel, 'VideoCommentAbuse', VideoCommentAbuseInclude> &
|
PickWith<AbuseModel, 'VideoCommentAbuse', VideoCommentAbuseInclude> &
|
||||||
PickWith<AbuseModel, 'FlaggedAccount', AccountIncludeActor>
|
PickWith<AbuseModel, 'FlaggedAccount', AccountIncludeActor>
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
import { expect } from 'chai'
|
import { expect } from 'chai'
|
||||||
import { inspect } from 'util'
|
import { inspect } from 'util'
|
||||||
|
import { AbuseState } from '@shared/models'
|
||||||
import { UserNotification, UserNotificationSetting, UserNotificationSettingValue, UserNotificationType } from '../../models/users'
|
import { UserNotification, UserNotificationSetting, UserNotificationSettingValue, UserNotificationType } from '../../models/users'
|
||||||
import { MockSmtpServer } from '../miscs/email'
|
import { MockSmtpServer } from '../miscs/email'
|
||||||
import { makeGetRequest, makePostBodyRequest, makePutBodyRequest } from '../requests/requests'
|
import { makeGetRequest, makePostBodyRequest, makePutBodyRequest } from '../requests/requests'
|
||||||
|
@ -464,6 +465,62 @@ async function checkNewVideoAbuseForModerators (base: CheckerBaseParams, videoUU
|
||||||
await checkNotification(base, notificationChecker, emailNotificationFinder, type)
|
await checkNotification(base, notificationChecker, emailNotificationFinder, type)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function checkNewAbuseMessage (base: CheckerBaseParams, abuseId: number, message: string, toEmail: string, type: CheckerType) {
|
||||||
|
const notificationType = UserNotificationType.ABUSE_NEW_MESSAGE
|
||||||
|
|
||||||
|
function notificationChecker (notification: UserNotification, type: CheckerType) {
|
||||||
|
if (type === 'presence') {
|
||||||
|
expect(notification).to.not.be.undefined
|
||||||
|
expect(notification.type).to.equal(notificationType)
|
||||||
|
|
||||||
|
expect(notification.abuse.id).to.equal(abuseId)
|
||||||
|
} else {
|
||||||
|
expect(notification).to.satisfy((n: UserNotification) => {
|
||||||
|
return n === undefined || n.type !== notificationType || n.abuse === undefined || n.abuse.id !== abuseId
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function emailNotificationFinder (email: object) {
|
||||||
|
const text = email['text']
|
||||||
|
const to = email['to'].filter(t => t.address === toEmail)
|
||||||
|
|
||||||
|
return text.indexOf(message) !== -1 && to.length !== 0
|
||||||
|
}
|
||||||
|
|
||||||
|
await checkNotification(base, notificationChecker, emailNotificationFinder, type)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function checkAbuseStateChange (base: CheckerBaseParams, abuseId: number, state: AbuseState, type: CheckerType) {
|
||||||
|
const notificationType = UserNotificationType.ABUSE_STATE_CHANGE
|
||||||
|
|
||||||
|
function notificationChecker (notification: UserNotification, type: CheckerType) {
|
||||||
|
if (type === 'presence') {
|
||||||
|
expect(notification).to.not.be.undefined
|
||||||
|
expect(notification.type).to.equal(notificationType)
|
||||||
|
|
||||||
|
expect(notification.abuse.id).to.equal(abuseId)
|
||||||
|
expect(notification.abuse.state).to.equal(state)
|
||||||
|
} else {
|
||||||
|
expect(notification).to.satisfy((n: UserNotification) => {
|
||||||
|
return n === undefined || n.abuse === undefined || n.abuse.id !== abuseId
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function emailNotificationFinder (email: object) {
|
||||||
|
const text = email['text']
|
||||||
|
|
||||||
|
const contains = state === AbuseState.ACCEPTED
|
||||||
|
? ' accepted'
|
||||||
|
: ' rejected'
|
||||||
|
|
||||||
|
return text.indexOf(contains) !== -1
|
||||||
|
}
|
||||||
|
|
||||||
|
await checkNotification(base, notificationChecker, emailNotificationFinder, type)
|
||||||
|
}
|
||||||
|
|
||||||
async function checkNewCommentAbuseForModerators (base: CheckerBaseParams, videoUUID: string, videoName: string, type: CheckerType) {
|
async function checkNewCommentAbuseForModerators (base: CheckerBaseParams, videoUUID: string, videoName: string, type: CheckerType) {
|
||||||
const notificationType = UserNotificationType.NEW_ABUSE_FOR_MODERATORS
|
const notificationType = UserNotificationType.NEW_ABUSE_FOR_MODERATORS
|
||||||
|
|
||||||
|
@ -579,6 +636,8 @@ function getAllNotificationsSettings () {
|
||||||
newFollow: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
|
newFollow: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
|
||||||
newUserRegistration: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
|
newUserRegistration: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
|
||||||
newInstanceFollower: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
|
newInstanceFollower: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
|
||||||
|
abuseNewMessage: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
|
||||||
|
abuseStateChange: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
|
||||||
autoInstanceFollowing: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL
|
autoInstanceFollowing: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL
|
||||||
} as UserNotificationSetting
|
} as UserNotificationSetting
|
||||||
}
|
}
|
||||||
|
@ -676,6 +735,8 @@ export {
|
||||||
updateMyNotificationSettings,
|
updateMyNotificationSettings,
|
||||||
checkNewVideoAbuseForModerators,
|
checkNewVideoAbuseForModerators,
|
||||||
checkVideoAutoBlacklistForModerators,
|
checkVideoAutoBlacklistForModerators,
|
||||||
|
checkNewAbuseMessage,
|
||||||
|
checkAbuseStateChange,
|
||||||
getUserNotifications,
|
getUserNotifications,
|
||||||
markAsReadNotifications,
|
markAsReadNotifications,
|
||||||
getLastNotification,
|
getLastNotification,
|
||||||
|
|
|
@ -5,16 +5,23 @@ export enum UserNotificationSettingValue {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UserNotificationSetting {
|
export interface UserNotificationSetting {
|
||||||
newVideoFromSubscription: UserNotificationSettingValue
|
|
||||||
newCommentOnMyVideo: UserNotificationSettingValue
|
|
||||||
abuseAsModerator: UserNotificationSettingValue
|
abuseAsModerator: UserNotificationSettingValue
|
||||||
videoAutoBlacklistAsModerator: UserNotificationSettingValue
|
videoAutoBlacklistAsModerator: UserNotificationSettingValue
|
||||||
|
newUserRegistration: UserNotificationSettingValue
|
||||||
|
|
||||||
|
newVideoFromSubscription: UserNotificationSettingValue
|
||||||
|
|
||||||
blacklistOnMyVideo: UserNotificationSettingValue
|
blacklistOnMyVideo: UserNotificationSettingValue
|
||||||
myVideoPublished: UserNotificationSettingValue
|
myVideoPublished: UserNotificationSettingValue
|
||||||
myVideoImportFinished: UserNotificationSettingValue
|
myVideoImportFinished: UserNotificationSettingValue
|
||||||
newUserRegistration: UserNotificationSettingValue
|
|
||||||
newFollow: UserNotificationSettingValue
|
|
||||||
commentMention: UserNotificationSettingValue
|
commentMention: UserNotificationSettingValue
|
||||||
|
newCommentOnMyVideo: UserNotificationSettingValue
|
||||||
|
|
||||||
|
newFollow: UserNotificationSettingValue
|
||||||
newInstanceFollower: UserNotificationSettingValue
|
newInstanceFollower: UserNotificationSettingValue
|
||||||
autoInstanceFollowing: UserNotificationSettingValue
|
autoInstanceFollowing: UserNotificationSettingValue
|
||||||
|
|
||||||
|
abuseStateChange: UserNotificationSettingValue
|
||||||
|
abuseNewMessage: UserNotificationSettingValue
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { FollowState } from '../actors'
|
import { FollowState } from '../actors'
|
||||||
|
import { AbuseState } from '../moderation'
|
||||||
|
|
||||||
export enum UserNotificationType {
|
export enum UserNotificationType {
|
||||||
NEW_VIDEO_FROM_SUBSCRIPTION = 1,
|
NEW_VIDEO_FROM_SUBSCRIPTION = 1,
|
||||||
|
@ -21,7 +22,11 @@ export enum UserNotificationType {
|
||||||
|
|
||||||
NEW_INSTANCE_FOLLOWER = 13,
|
NEW_INSTANCE_FOLLOWER = 13,
|
||||||
|
|
||||||
AUTO_INSTANCE_FOLLOWING = 14
|
AUTO_INSTANCE_FOLLOWING = 14,
|
||||||
|
|
||||||
|
ABUSE_STATE_CHANGE = 15,
|
||||||
|
|
||||||
|
ABUSE_NEW_MESSAGE = 16
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface VideoInfo {
|
export interface VideoInfo {
|
||||||
|
@ -66,6 +71,7 @@ export interface UserNotification {
|
||||||
|
|
||||||
abuse?: {
|
abuse?: {
|
||||||
id: number
|
id: number
|
||||||
|
state: AbuseState
|
||||||
|
|
||||||
video?: VideoInfo
|
video?: VideoInfo
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue