Add server API to abuse messages

This commit is contained in:
Chocobozzz 2020-07-24 15:05:51 +02:00 committed by Chocobozzz
parent 20516920d2
commit edbc932546
28 changed files with 1054 additions and 209 deletions

View File

@ -1,20 +1,24 @@
import * as express from 'express' import * as express from 'express'
import { createAccountAbuse, createVideoAbuse, createVideoCommentAbuse } from '@server/lib/moderation' import { createAccountAbuse, createVideoAbuse, createVideoCommentAbuse } from '@server/lib/moderation'
import { AbuseModel } from '@server/models/abuse/abuse' import { AbuseModel } from '@server/models/abuse/abuse'
import { AbuseMessageModel } from '@server/models/abuse/abuse-message'
import { getServerActor } from '@server/models/application/application' import { getServerActor } from '@server/models/application/application'
import { AbuseCreate, abusePredefinedReasonsMap, AbuseState, UserRight } from '../../../shared' import { AbuseCreate, abusePredefinedReasonsMap, AbuseState, UserRight } from '../../../shared'
import { getFormattedObjects } from '../../helpers/utils' import { getFormattedObjects } from '../../helpers/utils'
import { sequelizeTypescript } from '../../initializers/database' import { sequelizeTypescript } from '../../initializers/database'
import { import {
abuseGetValidator, abuseGetValidator,
abuseListValidator, abuseListForAdminsValidator,
abuseReportValidator, abuseReportValidator,
abusesSortValidator, abusesSortValidator,
abuseUpdateValidator, abuseUpdateValidator,
addAbuseMessageValidator,
asyncMiddleware, asyncMiddleware,
asyncRetryTransactionMiddleware, asyncRetryTransactionMiddleware,
authenticate, authenticate,
deleteAbuseMessageValidator,
ensureUserHasRight, ensureUserHasRight,
getAbuseValidator,
paginationValidator, paginationValidator,
setDefaultPagination, setDefaultPagination,
setDefaultSort setDefaultSort
@ -30,8 +34,8 @@ abuseRouter.get('/',
abusesSortValidator, abusesSortValidator,
setDefaultSort, setDefaultSort,
setDefaultPagination, setDefaultPagination,
abuseListValidator, abuseListForAdminsValidator,
asyncMiddleware(listAbuses) asyncMiddleware(listAbusesForAdmins)
) )
abuseRouter.put('/:id', abuseRouter.put('/:id',
authenticate, authenticate,
@ -51,13 +55,33 @@ abuseRouter.delete('/:id',
asyncRetryTransactionMiddleware(deleteAbuse) asyncRetryTransactionMiddleware(deleteAbuse)
) )
abuseRouter.get('/:id/messages',
authenticate,
asyncMiddleware(getAbuseValidator),
asyncRetryTransactionMiddleware(listAbuseMessages)
)
abuseRouter.post('/:id/messages',
authenticate,
asyncMiddleware(getAbuseValidator),
addAbuseMessageValidator,
asyncRetryTransactionMiddleware(addAbuseMessage)
)
abuseRouter.delete('/:id/messages/:messageId',
authenticate,
asyncMiddleware(getAbuseValidator),
asyncMiddleware(deleteAbuseMessageValidator),
asyncRetryTransactionMiddleware(deleteAbuseMessage)
)
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
export { export {
abuseRouter, abuseRouter,
// FIXME: deprecated in 2.3. Remove these exports // FIXME: deprecated in 2.3. Remove these exports
listAbuses, listAbusesForAdmins,
updateAbuse, updateAbuse,
deleteAbuse, deleteAbuse,
reportAbuse reportAbuse
@ -65,11 +89,11 @@ export {
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
async function listAbuses (req: express.Request, res: express.Response) { async function listAbusesForAdmins (req: express.Request, res: express.Response) {
const user = res.locals.oauth.token.user const user = res.locals.oauth.token.user
const serverActor = await getServerActor() const serverActor = await getServerActor()
const resultList = await AbuseModel.listForApi({ const resultList = await AbuseModel.listForAdminApi({
start: req.query.start, start: req.query.start,
count: req.query.count, count: req.query.count,
sort: req.query.sort, sort: req.query.sort,
@ -87,7 +111,10 @@ async function listAbuses (req: express.Request, res: express.Response) {
user user
}) })
return res.json(getFormattedObjects(resultList.data, resultList.total)) return res.json({
total: resultList.total,
data: resultList.data.map(d => d.toFormattedAdminJSON())
})
} }
async function updateAbuse (req: express.Request, res: express.Response) { async function updateAbuse (req: express.Request, res: express.Response) {
@ -100,6 +127,8 @@ async function updateAbuse (req: express.Request, res: express.Response) {
return abuse.save({ transaction: t }) return abuse.save({ transaction: t })
}) })
// TODO: Notification
// 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.type('json').status(204).end()
@ -166,3 +195,41 @@ async function reportAbuse (req: express.Request, res: express.Response) {
return res.json({ abuse: { id } }) return res.json({ abuse: { id } })
} }
async function listAbuseMessages (req: express.Request, res: express.Response) {
const abuse = res.locals.abuse
const resultList = await AbuseMessageModel.listForApi(abuse.id)
return res.json(getFormattedObjects(resultList.data, resultList.total))
}
async function addAbuseMessage (req: express.Request, res: express.Response) {
const abuse = res.locals.abuse
const user = res.locals.oauth.token.user
const abuseMessage = await AbuseMessageModel.create({
message: req.body.message,
byModerator: abuse.reporterAccountId !== user.Account.id,
accountId: user.Account.id,
abuseId: abuse.id
})
// TODO: Notification
return res.json({
abuseMessage: {
id: abuseMessage.id
}
})
}
async function deleteAbuseMessage (req: express.Request, res: express.Response) {
const abuseMessage = res.locals.abuseMessage
await sequelizeTypescript.transaction(t => {
return abuseMessage.destroy({ transaction: t })
})
return res.sendStatus(204)
}

View File

@ -1,10 +1,20 @@
import * as express from 'express' import * as express from 'express'
import * as RateLimit from 'express-rate-limit' import * as RateLimit from 'express-rate-limit'
import { tokensRouter } from '@server/controllers/api/users/token'
import { Hooks } from '@server/lib/plugins/hooks'
import { MUser, MUserAccountDefault } from '@server/types/models'
import { UserCreate, UserRight, UserRole, UserUpdate } from '../../../../shared' import { UserCreate, UserRight, UserRole, UserUpdate } from '../../../../shared'
import { UserAdminFlag } from '../../../../shared/models/users/user-flag.model'
import { UserRegister } from '../../../../shared/models/users/user-register.model'
import { auditLoggerFactory, getAuditIdFromRes, UserAuditView } from '../../../helpers/audit-logger'
import { logger } from '../../../helpers/logger' import { logger } from '../../../helpers/logger'
import { generateRandomString, getFormattedObjects } from '../../../helpers/utils' import { generateRandomString, getFormattedObjects } from '../../../helpers/utils'
import { CONFIG } from '../../../initializers/config'
import { WEBSERVER } from '../../../initializers/constants' import { WEBSERVER } from '../../../initializers/constants'
import { sequelizeTypescript } from '../../../initializers/database'
import { Emailer } from '../../../lib/emailer' import { Emailer } from '../../../lib/emailer'
import { Notifier } from '../../../lib/notifier'
import { deleteUserToken } from '../../../lib/oauth-model'
import { Redis } from '../../../lib/redis' import { Redis } from '../../../lib/redis'
import { createUserAccountAndChannelAndPlaylist, sendVerifyUserEmail } from '../../../lib/user' import { createUserAccountAndChannelAndPlaylist, sendVerifyUserEmail } from '../../../lib/user'
import { import {
@ -18,9 +28,9 @@ import {
setDefaultPagination, setDefaultPagination,
setDefaultSort, setDefaultSort,
userAutocompleteValidator, userAutocompleteValidator,
usersListValidator,
usersAddValidator, usersAddValidator,
usersGetValidator, usersGetValidator,
usersListValidator,
usersRegisterValidator, usersRegisterValidator,
usersRemoveValidator, usersRemoveValidator,
usersSortValidator, usersSortValidator,
@ -35,22 +45,13 @@ import {
usersVerifyEmailValidator usersVerifyEmailValidator
} from '../../../middlewares/validators' } from '../../../middlewares/validators'
import { UserModel } from '../../../models/account/user' import { UserModel } from '../../../models/account/user'
import { auditLoggerFactory, getAuditIdFromRes, UserAuditView } from '../../../helpers/audit-logger'
import { meRouter } from './me' import { meRouter } from './me'
import { deleteUserToken } from '../../../lib/oauth-model' import { myAbusesRouter } from './my-abuses'
import { myBlocklistRouter } from './my-blocklist' import { myBlocklistRouter } from './my-blocklist'
import { myVideoPlaylistsRouter } from './my-video-playlists'
import { myVideosHistoryRouter } from './my-history' import { myVideosHistoryRouter } from './my-history'
import { myNotificationsRouter } from './my-notifications' import { myNotificationsRouter } from './my-notifications'
import { Notifier } from '../../../lib/notifier'
import { mySubscriptionsRouter } from './my-subscriptions' import { mySubscriptionsRouter } from './my-subscriptions'
import { CONFIG } from '../../../initializers/config' import { myVideoPlaylistsRouter } from './my-video-playlists'
import { sequelizeTypescript } from '../../../initializers/database'
import { UserAdminFlag } from '../../../../shared/models/users/user-flag.model'
import { UserRegister } from '../../../../shared/models/users/user-register.model'
import { MUser, MUserAccountDefault } from '@server/types/models'
import { Hooks } from '@server/lib/plugins/hooks'
import { tokensRouter } from '@server/controllers/api/users/token'
const auditLogger = auditLoggerFactory('users') const auditLogger = auditLoggerFactory('users')
@ -72,6 +73,7 @@ usersRouter.use('/', mySubscriptionsRouter)
usersRouter.use('/', myBlocklistRouter) usersRouter.use('/', myBlocklistRouter)
usersRouter.use('/', myVideosHistoryRouter) usersRouter.use('/', myVideosHistoryRouter)
usersRouter.use('/', myVideoPlaylistsRouter) usersRouter.use('/', myVideoPlaylistsRouter)
usersRouter.use('/', myAbusesRouter)
usersRouter.use('/', meRouter) usersRouter.use('/', meRouter)
usersRouter.get('/autocomplete', usersRouter.get('/autocomplete',

View File

@ -0,0 +1,48 @@
import * as express from 'express'
import { AbuseModel } from '@server/models/abuse/abuse'
import {
abuseListForUserValidator,
abusesSortValidator,
asyncMiddleware,
authenticate,
paginationValidator,
setDefaultPagination,
setDefaultSort
} from '../../../middlewares'
const myAbusesRouter = express.Router()
myAbusesRouter.get('/me/abuses',
authenticate,
paginationValidator,
abusesSortValidator,
setDefaultSort,
setDefaultPagination,
abuseListForUserValidator,
asyncMiddleware(listMyAbuses)
)
// ---------------------------------------------------------------------------
export {
myAbusesRouter
}
// ---------------------------------------------------------------------------
async function listMyAbuses (req: express.Request, res: express.Response) {
const resultList = await AbuseModel.listForUserApi({
start: req.query.start,
count: req.query.count,
sort: req.query.sort,
id: req.query.id,
search: req.query.search,
state: req.query.state,
user: res.locals.oauth.token.User
})
return res.json({
total: resultList.total,
data: resultList.data.map(d => d.toFormattedAdminJSON())
})
}

View File

@ -2,7 +2,6 @@ import * as express from 'express'
import { AbuseModel } from '@server/models/abuse/abuse' import { AbuseModel } from '@server/models/abuse/abuse'
import { getServerActor } from '@server/models/application/application' import { getServerActor } from '@server/models/application/application'
import { AbuseCreate, UserRight, VideoAbuseCreate } from '../../../../shared' import { AbuseCreate, UserRight, VideoAbuseCreate } from '../../../../shared'
import { getFormattedObjects } from '../../../helpers/utils'
import { import {
abusesSortValidator, abusesSortValidator,
asyncMiddleware, asyncMiddleware,
@ -63,7 +62,7 @@ async function listVideoAbuses (req: express.Request, res: express.Response) {
const user = res.locals.oauth.token.user const user = res.locals.oauth.token.user
const serverActor = await getServerActor() const serverActor = await getServerActor()
const resultList = await AbuseModel.listForApi({ const resultList = await AbuseModel.listForAdminApi({
start: req.query.start, start: req.query.start,
count: req.query.count, count: req.query.count,
sort: req.query.sort, sort: req.query.sort,
@ -81,7 +80,10 @@ async function listVideoAbuses (req: express.Request, res: express.Response) {
user user
}) })
return res.json(getFormattedObjects(resultList.data, resultList.total)) return res.json({
total: resultList.total,
data: resultList.data.map(d => d.toFormattedAdminJSON())
})
} }
async function updateVideoAbuse (req: express.Request, res: express.Response) { async function updateVideoAbuse (req: express.Request, res: express.Response) {

View File

@ -5,7 +5,7 @@ import { chain } from 'lodash'
import * as path from 'path' import * as path from 'path'
import * as winston from 'winston' import * as winston from 'winston'
import { AUDIT_LOG_FILENAME } from '@server/initializers/constants' import { AUDIT_LOG_FILENAME } from '@server/initializers/constants'
import { Abuse, User, VideoChannel, VideoDetails, VideoImport } from '../../shared' import { AdminAbuse, User, VideoChannel, VideoDetails, VideoImport } from '../../shared'
import { CustomConfig } from '../../shared/models/server/custom-config.model' import { CustomConfig } from '../../shared/models/server/custom-config.model'
import { VideoComment } from '../../shared/models/videos/video-comment.model' import { VideoComment } from '../../shared/models/videos/video-comment.model'
import { CONFIG } from '../initializers/config' import { CONFIG } from '../initializers/config'
@ -219,7 +219,7 @@ const abuseKeysToKeep = [
'createdAt' 'createdAt'
] ]
class AbuseAuditView extends EntityAuditView { class AbuseAuditView extends EntityAuditView {
constructor (private readonly abuse: Abuse) { constructor (private readonly abuse: AdminAbuse) {
super(abuseKeysToKeep, 'abuse', abuse) super(abuseKeysToKeep, 'abuse', abuse)
} }
} }

View File

@ -4,6 +4,7 @@ import { ABUSE_STATES, CONSTRAINTS_FIELDS } from '../../initializers/constants'
import { exists, isArray } from './misc' import { exists, isArray } from './misc'
const ABUSES_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.ABUSES const ABUSES_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.ABUSES
const ABUSE_MESSAGES_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.ABUSE_MESSAGES
function isAbuseReasonValid (value: string) { function isAbuseReasonValid (value: string) {
return exists(value) && validator.isLength(value, ABUSES_CONSTRAINTS_FIELDS.REASON) return exists(value) && validator.isLength(value, ABUSES_CONSTRAINTS_FIELDS.REASON)
@ -46,13 +47,18 @@ function isAbuseVideoIsValid (value: AbuseVideoIs) {
) )
} }
function isAbuseMessageValid (value: string) {
return exists(value) && validator.isLength(value, ABUSE_MESSAGES_CONSTRAINTS_FIELDS.MESSAGE)
}
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
export { export {
isAbuseReasonValid, isAbuseReasonValid,
isAbuseFilterValid, isAbuseFilterValid,
isAbusePredefinedReasonValid, isAbusePredefinedReasonValid,
areAbusePredefinedReasonsValid as isAbusePredefinedReasonsValid, isAbuseMessageValid,
areAbusePredefinedReasonsValid,
isAbuseTimestampValid, isAbuseTimestampValid,
isAbuseTimestampCoherent, isAbuseTimestampCoherent,
isAbuseModerationCommentValid, isAbuseModerationCommentValid,

View File

@ -206,6 +206,9 @@ const CONSTRAINTS_FIELDS = {
REASON: { min: 2, max: 3000 }, // Length REASON: { min: 2, max: 3000 }, // Length
MODERATION_COMMENT: { min: 2, max: 3000 } // Length MODERATION_COMMENT: { min: 2, max: 3000 } // Length
}, },
ABUSE_MESSAGES: {
MESSAGE: { min: 2, max: 3000 } // Length
},
VIDEO_BLACKLIST: { VIDEO_BLACKLIST: {
REASON: { min: 2, max: 300 } // Length REASON: { min: 2, max: 300 } // Length
}, },

View File

@ -1,6 +1,7 @@
import { QueryTypes, Transaction } from 'sequelize' import { QueryTypes, Transaction } from 'sequelize'
import { Sequelize as SequelizeTypescript } from 'sequelize-typescript' import { Sequelize as SequelizeTypescript } from 'sequelize-typescript'
import { AbuseModel } from '@server/models/abuse/abuse' import { AbuseModel } from '@server/models/abuse/abuse'
import { AbuseMessageModel } from '@server/models/abuse/abuse-message'
import { VideoAbuseModel } from '@server/models/abuse/video-abuse' import { VideoAbuseModel } from '@server/models/abuse/video-abuse'
import { VideoCommentAbuseModel } from '@server/models/abuse/video-comment-abuse' import { VideoCommentAbuseModel } from '@server/models/abuse/video-comment-abuse'
import { isTestInstance } from '../helpers/core-utils' import { isTestInstance } from '../helpers/core-utils'
@ -87,6 +88,7 @@ async function initDatabaseModels (silent: boolean) {
TagModel, TagModel,
AccountVideoRateModel, AccountVideoRateModel,
UserModel, UserModel,
AbuseMessageModel,
AbuseModel, AbuseModel,
VideoCommentAbuseModel, VideoCommentAbuseModel,
VideoAbuseModel, VideoAbuseModel,

View File

@ -5,7 +5,7 @@ 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 { Abuse, EmailPayload } from '@shared/models' import { UserAbuse, EmailPayload } 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'
@ -283,7 +283,7 @@ class Emailer {
} }
addAbuseModeratorsNotification (to: string[], parameters: { addAbuseModeratorsNotification (to: string[], parameters: {
abuse: Abuse abuse: UserAbuse
abuseInstance: MAbuseFull abuseInstance: MAbuseFull
reporter: string reporter: string
}) { }) {

View File

@ -213,7 +213,7 @@ async function createAbuse (options: {
await sendAbuse(reporterAccount.Actor, abuseInstance, abuseInstance.FlaggedAccount, transaction) await sendAbuse(reporterAccount.Actor, abuseInstance, abuseInstance.FlaggedAccount, transaction)
} }
const abuseJSON = abuseInstance.toFormattedJSON() const abuseJSON = abuseInstance.toFormattedAdminJSON()
auditLogger.create(reporterAccount.Actor.getIdentifier(), new AbuseAuditView(abuseJSON)) auditLogger.create(reporterAccount.Actor.getIdentifier(), new AbuseAuditView(abuseJSON))
Notifier.Instance.notifyOnNewAbuse({ Notifier.Instance.notifyOnNewAbuse({

View File

@ -10,7 +10,7 @@ import {
} from '@server/types/models/user' } from '@server/types/models/user'
import { MVideoBlacklistLightVideo, MVideoBlacklistVideo } from '@server/types/models/video/video-blacklist' import { MVideoBlacklistLightVideo, MVideoBlacklistVideo } from '@server/types/models/video/video-blacklist'
import { MVideoImportVideo } from '@server/types/models/video/video-import' import { MVideoImportVideo } from '@server/types/models/video/video-import'
import { Abuse } from '@shared/models' import { UserAbuse } from '@shared/models'
import { UserNotificationSettingValue, UserNotificationType, UserRight } from '../../shared/models/users' import { UserNotificationSettingValue, UserNotificationType, UserRight } from '../../shared/models/users'
import { VideoPrivacy, VideoState } from '../../shared/models/videos' import { VideoPrivacy, VideoState } from '../../shared/models/videos'
import { logger } from '../helpers/logger' import { logger } from '../helpers/logger'
@ -73,7 +73,7 @@ class Notifier {
.catch(err => logger.error('Cannot notify mentions of comment %s.', comment.url, { err })) .catch(err => logger.error('Cannot notify mentions of comment %s.', comment.url, { err }))
} }
notifyOnNewAbuse (parameters: { abuse: Abuse, abuseInstance: MAbuseFull, reporter: string }): void { notifyOnNewAbuse (parameters: { abuse: UserAbuse, abuseInstance: MAbuseFull, reporter: string }): void {
this.notifyModeratorsOfNewAbuse(parameters) this.notifyModeratorsOfNewAbuse(parameters)
.catch(err => logger.error('Cannot notify of new abuse %d.', parameters.abuseInstance.id, { err })) .catch(err => logger.error('Cannot notify of new abuse %d.', parameters.abuseInstance.id, { err }))
} }
@ -350,7 +350,7 @@ class Notifier {
} }
private async notifyModeratorsOfNewAbuse (parameters: { private async notifyModeratorsOfNewAbuse (parameters: {
abuse: Abuse abuse: UserAbuse
abuseInstance: MAbuseFull abuseInstance: MAbuseFull
reporter: string reporter: string
}) { }) {

View File

@ -9,11 +9,7 @@ function ensureUserHasRight (userRight: UserRight) {
const message = `User ${user.username} does not have right ${UserRight[userRight]} to access to ${req.path}.` const message = `User ${user.username} does not have right ${UserRight[userRight]} to access to ${req.path}.`
logger.info(message) logger.info(message)
return res.status(403) return res.status(403).json({ error: message })
.json({
error: message
})
.end()
} }
return next() return next()

View File

@ -2,8 +2,9 @@ import * as express from 'express'
import { body, param, query } from 'express-validator' import { body, param, query } from 'express-validator'
import { import {
isAbuseFilterValid, isAbuseFilterValid,
isAbuseMessageValid,
isAbuseModerationCommentValid, isAbuseModerationCommentValid,
isAbusePredefinedReasonsValid, areAbusePredefinedReasonsValid,
isAbusePredefinedReasonValid, isAbusePredefinedReasonValid,
isAbuseReasonValid, isAbuseReasonValid,
isAbuseStateValid, isAbuseStateValid,
@ -15,7 +16,8 @@ import { exists, isIdOrUUIDValid, isIdValid, toIntOrNull } from '@server/helpers
import { doesCommentIdExist } from '@server/helpers/custom-validators/video-comments' import { doesCommentIdExist } from '@server/helpers/custom-validators/video-comments'
import { logger } from '@server/helpers/logger' import { logger } from '@server/helpers/logger'
import { doesAbuseExist, doesAccountIdExist, doesVideoAbuseExist, doesVideoExist } from '@server/helpers/middlewares' import { doesAbuseExist, doesAccountIdExist, doesVideoAbuseExist, doesVideoExist } from '@server/helpers/middlewares'
import { AbuseCreate } from '@shared/models' import { AbuseMessageModel } from '@server/models/abuse/abuse-message'
import { AbuseCreate, UserRight } from '@shared/models'
import { areValidationErrors } from './utils' import { areValidationErrors } from './utils'
const abuseReportValidator = [ const abuseReportValidator = [
@ -53,7 +55,7 @@ const abuseReportValidator = [
body('predefinedReasons') body('predefinedReasons')
.optional() .optional()
.custom(isAbusePredefinedReasonsValid) .custom(areAbusePredefinedReasonsValid)
.withMessage('Should have a valid list of predefined reasons'), .withMessage('Should have a valid list of predefined reasons'),
async (req: express.Request, res: express.Response, next: express.NextFunction) => { async (req: express.Request, res: express.Response, next: express.NextFunction) => {
@ -111,7 +113,7 @@ const abuseUpdateValidator = [
} }
] ]
const abuseListValidator = [ const abuseListForAdminsValidator = [
query('id') query('id')
.optional() .optional()
.custom(isIdValid).withMessage('Should have a valid id'), .custom(isIdValid).withMessage('Should have a valid id'),
@ -146,7 +148,7 @@ const abuseListValidator = [
.custom(exists).withMessage('Should have a valid video channel search'), .custom(exists).withMessage('Should have a valid video channel search'),
(req: express.Request, res: express.Response, next: express.NextFunction) => { (req: express.Request, res: express.Response, next: express.NextFunction) => {
logger.debug('Checking abuseListValidator parameters', { parameters: req.body }) logger.debug('Checking abuseListForAdminsValidator parameters', { parameters: req.body })
if (areValidationErrors(req, res)) return if (areValidationErrors(req, res)) return
@ -154,6 +156,91 @@ const abuseListValidator = [
} }
] ]
const abuseListForUserValidator = [
query('id')
.optional()
.custom(isIdValid).withMessage('Should have a valid id'),
query('search')
.optional()
.custom(exists).withMessage('Should have a valid search'),
query('state')
.optional()
.custom(isAbuseStateValid).withMessage('Should have a valid abuse state'),
(req: express.Request, res: express.Response, next: express.NextFunction) => {
logger.debug('Checking abuseListForUserValidator parameters', { parameters: req.body })
if (areValidationErrors(req, res)) return
return next()
}
]
const getAbuseValidator = [
param('id').custom(isIdValid).not().isEmpty().withMessage('Should have a valid id'),
async (req: express.Request, res: express.Response, next: express.NextFunction) => {
logger.debug('Checking getAbuseValidator parameters', { parameters: req.body })
if (areValidationErrors(req, res)) return
if (!await doesAbuseExist(req.params.id, res)) return
const user = res.locals.oauth.token.user
const abuse = res.locals.abuse
if (user.hasRight(UserRight.MANAGE_ABUSES) !== true && abuse.reporterAccountId !== user.Account.id) {
const message = `User ${user.username} does not have right to get abuse ${abuse.id}`
logger.warn(message)
return res.status(403).json({ error: message })
}
return next()
}
]
const addAbuseMessageValidator = [
body('message').custom(isAbuseMessageValid).not().isEmpty().withMessage('Should have a valid abuse message'),
(req: express.Request, res: express.Response, next: express.NextFunction) => {
logger.debug('Checking addAbuseMessageValidator parameters', { parameters: req.body })
if (areValidationErrors(req, res)) return
return next()
}
]
const deleteAbuseMessageValidator = [
param('messageId').custom(isIdValid).not().isEmpty().withMessage('Should have a valid message id'),
async (req: express.Request, res: express.Response, next: express.NextFunction) => {
logger.debug('Checking deleteAbuseMessageValidator parameters', { parameters: req.body })
if (areValidationErrors(req, res)) return
const user = res.locals.oauth.token.user
const abuse = res.locals.abuse
const messageId = parseInt(req.params.messageId + '', 10)
const abuseMessage = await AbuseMessageModel.loadByIdAndAbuseId(messageId, abuse.id)
if (!abuseMessage) {
return res.status(404).json({ error: 'Abuse message not found' })
}
if (user.hasRight(UserRight.MANAGE_ABUSES) !== true && abuseMessage.accountId !== user.Account.id) {
return res.status(403).json({ error: 'Cannot delete this abuse message' })
}
res.locals.abuseMessage = abuseMessage
return next()
}
]
// FIXME: deprecated in 2.3. Remove these validators // FIXME: deprecated in 2.3. Remove these validators
const videoAbuseReportValidator = [ const videoAbuseReportValidator = [
@ -167,7 +254,7 @@ const videoAbuseReportValidator = [
.withMessage('Should have a valid reason'), .withMessage('Should have a valid reason'),
body('predefinedReasons') body('predefinedReasons')
.optional() .optional()
.custom(isAbusePredefinedReasonsValid) .custom(areAbusePredefinedReasonsValid)
.withMessage('Should have a valid list of predefined reasons'), .withMessage('Should have a valid list of predefined reasons'),
body('startAt') body('startAt')
.optional() .optional()
@ -266,10 +353,14 @@ const videoAbuseListValidator = [
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
export { export {
abuseListValidator, abuseListForAdminsValidator,
abuseReportValidator, abuseReportValidator,
abuseGetValidator, abuseGetValidator,
addAbuseMessageValidator,
abuseUpdateValidator, abuseUpdateValidator,
deleteAbuseMessageValidator,
abuseListForUserValidator,
getAbuseValidator,
videoAbuseReportValidator, videoAbuseReportValidator,
videoAbuseGetValidator, videoAbuseGetValidator,
videoAbuseUpdateValidator, videoAbuseUpdateValidator,

View File

@ -0,0 +1,103 @@
import { AllowNull, BelongsTo, Column, CreatedAt, DataType, ForeignKey, Is, Model, Table, UpdatedAt } from 'sequelize-typescript'
import { isAbuseMessageValid } from '@server/helpers/custom-validators/abuses'
import { AbuseMessage } from '@shared/models'
import { AccountModel, ScopeNames as AccountScopeNames } from '../account/account'
import { throwIfNotValid, getSort } from '../utils'
import { AbuseModel } from './abuse'
import { MAbuseMessageFormattable, MAbuseMessage } from '@server/types/models'
@Table({
tableName: 'abuseMessage',
indexes: [
{
fields: [ 'abuseId' ]
},
{
fields: [ 'accountId' ]
}
]
})
export class AbuseMessageModel extends Model<AbuseMessageModel> {
@AllowNull(false)
@Is('AbuseMessage', value => throwIfNotValid(value, isAbuseMessageValid, 'message'))
@Column(DataType.TEXT)
message: string
@AllowNull(false)
@Column
byModerator: boolean
@CreatedAt
createdAt: Date
@UpdatedAt
updatedAt: Date
@ForeignKey(() => AccountModel)
@Column
accountId: number
@BelongsTo(() => AccountModel, {
foreignKey: {
name: 'accountId',
allowNull: true
},
onDelete: 'set null'
})
Account: AccountModel
@ForeignKey(() => AbuseModel)
@Column
abuseId: number
@BelongsTo(() => AbuseModel, {
foreignKey: {
name: 'abuseId',
allowNull: false
},
onDelete: 'cascade'
})
Abuse: AbuseModel
static listForApi (abuseId: number) {
const options = {
where: { abuseId },
order: getSort('createdAt'),
include: [
{
model: AccountModel.scope(AccountScopeNames.SUMMARY),
required: false
}
]
}
return AbuseMessageModel.findAndCountAll(options)
.then(({ rows, count }) => ({ data: rows, total: count }))
}
static loadByIdAndAbuseId (messageId: number, abuseId: number): Promise<MAbuseMessage> {
return AbuseMessageModel.findOne({
where: {
id: messageId,
abuseId
}
})
}
toFormattedJSON (this: MAbuseMessageFormattable): AbuseMessage {
const account = this.Account
? this.Account.toFormattedSummaryJSON()
: null
return {
id: this.id,
byModerator: this.byModerator,
message: this.message,
account
}
}
}

View File

@ -26,8 +26,10 @@ export type BuildAbusesQueryOptions = {
state?: AbuseState state?: AbuseState
// accountIds // accountIds
serverAccountId: number serverAccountId?: number
userAccountId: number userAccountId?: number
reporterAccountId?: number
} }
function buildAbuseListQuery (options: BuildAbusesQueryOptions, type: 'count' | 'id') { function buildAbuseListQuery (options: BuildAbusesQueryOptions, type: 'count' | 'id') {
@ -45,7 +47,14 @@ function buildAbuseListQuery (options: BuildAbusesQueryOptions, type: 'count' |
'LEFT JOIN "videoComment" ON "commentAbuse"."videoCommentId" = "videoComment"."id"' 'LEFT JOIN "videoComment" ON "commentAbuse"."videoCommentId" = "videoComment"."id"'
] ]
whereAnd.push('"abuse"."reporterAccountId" NOT IN (' + buildBlockedAccountSQL([ options.serverAccountId, options.userAccountId ]) + ')') if (options.serverAccountId || options.userAccountId) {
whereAnd.push('"abuse"."reporterAccountId" NOT IN (' + buildBlockedAccountSQL([ options.serverAccountId, options.userAccountId ]) + ')')
}
if (options.reporterAccountId) {
whereAnd.push('"abuse"."reporterAccountId" = :reporterAccountId')
replacements.reporterAccountId = options.reporterAccountId
}
if (options.search) { if (options.search) {
const searchWhereOr = [ const searchWhereOr = [

View File

@ -18,7 +18,6 @@ import {
} from 'sequelize-typescript' } from 'sequelize-typescript'
import { isAbuseModerationCommentValid, isAbuseReasonValid, isAbuseStateValid } from '@server/helpers/custom-validators/abuses' import { isAbuseModerationCommentValid, isAbuseReasonValid, isAbuseStateValid } from '@server/helpers/custom-validators/abuses'
import { import {
Abuse,
AbuseFilter, AbuseFilter,
AbuseObject, AbuseObject,
AbusePredefinedReasons, AbusePredefinedReasons,
@ -26,11 +25,14 @@ import {
AbusePredefinedReasonsString, AbusePredefinedReasonsString,
AbuseState, AbuseState,
AbuseVideoIs, AbuseVideoIs,
VideoAbuse, AdminVideoAbuse,
VideoCommentAbuse AdminAbuse,
AdminVideoCommentAbuse,
UserAbuse,
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, MAbuseAP, MAbuseFormattable, MUserAccountId } from '../../types/models' import { MAbuse, MAbuseAdminFormattable, MAbuseAP, MUserAccountId, MAbuseUserFormattable } 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'
@ -51,6 +53,16 @@ export enum ScopeNames {
return { return {
attributes: { attributes: {
include: [ include: [
[
literal(
'(' +
'SELECT count(*) ' +
'FROM "abuseMessage" ' +
'WHERE "abuseId" = "AbuseModel"."id"' +
')'
),
'countMessages'
],
[ [
// we don't care about this count for deleted videos, so there are not included // we don't care about this count for deleted videos, so there are not included
literal( literal(
@ -285,7 +297,7 @@ export class AbuseModel extends Model<AbuseModel> {
return AbuseModel.findOne(query) return AbuseModel.findOne(query)
} }
static async listForApi (parameters: { static async listForAdminApi (parameters: {
start: number start: number
count: number count: number
sort: string sort: string
@ -353,71 +365,98 @@ export class AbuseModel extends Model<AbuseModel> {
return { total, data } return { total, data }
} }
toFormattedJSON (this: MAbuseFormattable): Abuse { static async listForUserApi (parameters: {
user: MUserAccountId
start: number
count: number
sort: string
id?: number
search?: string
state?: AbuseState
}) {
const {
start,
count,
sort,
search,
user,
state,
id
} = parameters
const queryOptions: BuildAbusesQueryOptions = {
start,
count,
sort,
id,
search,
state,
reporterAccountId: user.Account.id
}
const [ total, data ] = await Promise.all([
AbuseModel.internalCountForApi(queryOptions),
AbuseModel.internalListForApi(queryOptions)
])
return { total, data }
}
buildBaseVideoCommentAbuse (this: MAbuseUserFormattable) {
if (!this.VideoCommentAbuse) return null
const abuseModel = this.VideoCommentAbuse
const entity = abuseModel.VideoComment
return {
id: entity.id,
threadId: entity.getThreadId(),
text: entity.text ?? '',
deleted: entity.isDeleted(),
video: {
id: entity.Video.id,
name: entity.Video.name,
uuid: entity.Video.uuid
}
}
}
buildBaseVideoAbuse (this: MAbuseUserFormattable): UserVideoAbuse {
if (!this.VideoAbuse) return null
const abuseModel = this.VideoAbuse
const entity = abuseModel.Video || abuseModel.deletedVideo
return {
id: entity.id,
uuid: entity.uuid,
name: entity.name,
nsfw: entity.nsfw,
startAt: abuseModel.startAt,
endAt: abuseModel.endAt,
deleted: !abuseModel.Video,
blacklisted: abuseModel.Video?.isBlacklisted() || false,
thumbnailPath: abuseModel.Video?.getMiniatureStaticPath(),
channel: abuseModel.Video?.VideoChannel.toFormattedJSON() || abuseModel.deletedVideo?.channel,
}
}
buildBaseAbuse (this: MAbuseUserFormattable, countMessages: number): UserAbuse {
const predefinedReasons = AbuseModel.getPredefinedReasonsStrings(this.predefinedReasons) const predefinedReasons = AbuseModel.getPredefinedReasonsStrings(this.predefinedReasons)
const countReportsForVideo = this.get('countReportsForVideo') as number
const nthReportForVideo = this.get('nthReportForVideo') as number
const countReportsForReporter = this.get('countReportsForReporter') as number
const countReportsForReportee = this.get('countReportsForReportee') as number
let video: VideoAbuse = null
let comment: VideoCommentAbuse = null
if (this.VideoAbuse) {
const abuseModel = this.VideoAbuse
const entity = abuseModel.Video || abuseModel.deletedVideo
video = {
id: entity.id,
uuid: entity.uuid,
name: entity.name,
nsfw: entity.nsfw,
startAt: abuseModel.startAt,
endAt: abuseModel.endAt,
deleted: !abuseModel.Video,
blacklisted: abuseModel.Video?.isBlacklisted() || false,
thumbnailPath: abuseModel.Video?.getMiniatureStaticPath(),
channel: abuseModel.Video?.VideoChannel.toFormattedJSON() || abuseModel.deletedVideo?.channel,
countReports: countReportsForVideo,
nthReport: nthReportForVideo
}
}
if (this.VideoCommentAbuse) {
const abuseModel = this.VideoCommentAbuse
const entity = abuseModel.VideoComment
comment = {
id: entity.id,
threadId: entity.getThreadId(),
text: entity.text ?? '',
deleted: entity.isDeleted(),
video: {
id: entity.Video.id,
name: entity.Video.name,
uuid: entity.Video.uuid
}
}
}
return { return {
id: this.id, id: this.id,
reason: this.reason, reason: this.reason,
predefinedReasons, predefinedReasons,
reporterAccount: this.ReporterAccount
? this.ReporterAccount.toFormattedJSON()
: null,
flaggedAccount: this.FlaggedAccount flaggedAccount: this.FlaggedAccount
? this.FlaggedAccount.toFormattedJSON() ? this.FlaggedAccount.toFormattedJSON()
: null, : null,
@ -429,11 +468,41 @@ export class AbuseModel extends Model<AbuseModel> {
moderationComment: this.moderationComment, moderationComment: this.moderationComment,
countMessages,
createdAt: this.createdAt,
updatedAt: this.updatedAt
}
}
toFormattedAdminJSON (this: MAbuseAdminFormattable): AdminAbuse {
const countReportsForVideo = this.get('countReportsForVideo') as number
const nthReportForVideo = this.get('nthReportForVideo') as number
const countReportsForReporter = this.get('countReportsForReporter') as number
const countReportsForReportee = this.get('countReportsForReportee') as number
const countMessages = this.get('countMessages') as number
const baseVideo = this.buildBaseVideoAbuse()
const video: AdminVideoAbuse = baseVideo
? Object.assign(baseVideo, {
countReports: countReportsForVideo,
nthReport: nthReportForVideo
})
: null
const comment: AdminVideoCommentAbuse = this.buildBaseVideoCommentAbuse()
const abuse = this.buildBaseAbuse(countMessages || 0)
return Object.assign(abuse, {
video, video,
comment, comment,
createdAt: this.createdAt, reporterAccount: this.ReporterAccount
updatedAt: this.updatedAt, ? this.ReporterAccount.toFormattedJSON()
: null,
countReportsForReporter: (countReportsForReporter || 0), countReportsForReporter: (countReportsForReporter || 0),
countReportsForReportee: (countReportsForReportee || 0), countReportsForReportee: (countReportsForReportee || 0),
@ -443,7 +512,20 @@ export class AbuseModel extends Model<AbuseModel> {
endAt: null, endAt: null,
count: countReportsForVideo || 0, count: countReportsForVideo || 0,
nth: nthReportForVideo || 0 nth: nthReportForVideo || 0
} })
}
toFormattedUserJSON (this: MAbuseUserFormattable): UserAbuse {
const countMessages = this.get('countMessages') as number
const video = this.buildBaseVideoAbuse()
const comment: AdminVideoCommentAbuse = this.buildBaseVideoCommentAbuse()
const abuse = this.buildBaseAbuse(countMessages || 0)
return Object.assign(abuse, {
video,
comment
})
} }
toActivityPubObject (this: MAbuseAP): AbuseObject { toActivityPubObject (this: MAbuseAP): AbuseObject {

View File

@ -13,7 +13,11 @@ import {
setAccessTokensToServers, setAccessTokensToServers,
updateAbuse, updateAbuse,
uploadVideo, uploadVideo,
userLogin userLogin,
generateUserAccessToken,
addAbuseMessage,
listAbuseMessages,
deleteAbuseMessage
} from '../../../../shared/extra-utils' } from '../../../../shared/extra-utils'
import { import {
checkBadCountPagination, checkBadCountPagination,
@ -26,7 +30,9 @@ describe('Test abuses API validators', function () {
let server: ServerInfo let server: ServerInfo
let userAccessToken = '' let userAccessToken = ''
let userAccessToken2 = ''
let abuseId: number let abuseId: number
let messageId: number
// --------------------------------------------------------------- // ---------------------------------------------------------------
@ -42,11 +48,15 @@ describe('Test abuses API validators', function () {
await createUser({ url: server.url, accessToken: server.accessToken, username: username, password: password }) await createUser({ url: server.url, accessToken: server.accessToken, username: username, password: password })
userAccessToken = await userLogin(server, { username, password }) userAccessToken = await userLogin(server, { username, password })
{
userAccessToken2 = await generateUserAccessToken(server, 'user_2')
}
const res = await uploadVideo(server.url, server.accessToken, {}) const res = await uploadVideo(server.url, server.accessToken, {})
server.video = res.body.video server.video = res.body.video
}) })
describe('When listing abuses', function () { describe('When listing abuses for admins', function () {
const path = basePath const path = basePath
it('Should fail with a bad start pagination', async function () { it('Should fail with a bad start pagination', async function () {
@ -113,47 +123,89 @@ describe('Test abuses API validators', function () {
}) })
}) })
describe('When listing abuses for users', function () {
const path = '/api/v1/users/me/abuses'
it('Should fail with a bad start pagination', async function () {
await checkBadStartPagination(server.url, path, userAccessToken)
})
it('Should fail with a bad count pagination', async function () {
await checkBadCountPagination(server.url, path, userAccessToken)
})
it('Should fail with an incorrect sort', async function () {
await checkBadSortPagination(server.url, path, userAccessToken)
})
it('Should fail with a non authenticated user', async function () {
await makeGetRequest({
url: server.url,
path,
statusCodeExpected: 401
})
})
it('Should fail with a bad id filter', async function () {
await makeGetRequest({ url: server.url, path, token: userAccessToken, query: { id: 'toto' } })
})
it('Should fail with a bad state filter', async function () {
await makeGetRequest({ url: server.url, path, token: userAccessToken, query: { state: 'toto' } })
await makeGetRequest({ url: server.url, path, token: userAccessToken, query: { state: 0 } })
})
it('Should succeed with the correct params', async function () {
const query = {
id: 13,
state: 2
}
await makeGetRequest({ url: server.url, path, token: userAccessToken, query, statusCodeExpected: 200 })
})
})
describe('When reporting an abuse', function () { describe('When reporting an abuse', function () {
const path = basePath const path = basePath
it('Should fail with nothing', async function () { it('Should fail with nothing', async function () {
const fields = {} const fields = {}
await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields }) await makePostBodyRequest({ url: server.url, path, token: userAccessToken, fields })
}) })
it('Should fail with a wrong video', async function () { it('Should fail with a wrong video', async function () {
const fields = { video: { id: 'blabla' }, reason: 'my super reason' } const fields = { video: { id: 'blabla' }, reason: 'my super reason' }
await makePostBodyRequest({ url: server.url, path: path, token: server.accessToken, fields }) await makePostBodyRequest({ url: server.url, path: path, token: userAccessToken, fields })
}) })
it('Should fail with an unknown video', async function () { it('Should fail with an unknown video', async function () {
const fields = { video: { id: 42 }, reason: 'my super reason' } const fields = { video: { id: 42 }, reason: 'my super reason' }
await makePostBodyRequest({ url: server.url, path: path, token: server.accessToken, fields, statusCodeExpected: 404 }) await makePostBodyRequest({ url: server.url, path: path, token: userAccessToken, fields, statusCodeExpected: 404 })
}) })
it('Should fail with a wrong comment', async function () { it('Should fail with a wrong comment', async function () {
const fields = { comment: { id: 'blabla' }, reason: 'my super reason' } const fields = { comment: { id: 'blabla' }, reason: 'my super reason' }
await makePostBodyRequest({ url: server.url, path: path, token: server.accessToken, fields }) await makePostBodyRequest({ url: server.url, path: path, token: userAccessToken, fields })
}) })
it('Should fail with an unknown comment', async function () { it('Should fail with an unknown comment', async function () {
const fields = { comment: { id: 42 }, reason: 'my super reason' } const fields = { comment: { id: 42 }, reason: 'my super reason' }
await makePostBodyRequest({ url: server.url, path: path, token: server.accessToken, fields, statusCodeExpected: 404 }) await makePostBodyRequest({ url: server.url, path: path, token: userAccessToken, fields, statusCodeExpected: 404 })
}) })
it('Should fail with a wrong account', async function () { it('Should fail with a wrong account', async function () {
const fields = { account: { id: 'blabla' }, reason: 'my super reason' } const fields = { account: { id: 'blabla' }, reason: 'my super reason' }
await makePostBodyRequest({ url: server.url, path: path, token: server.accessToken, fields }) await makePostBodyRequest({ url: server.url, path: path, token: userAccessToken, fields })
}) })
it('Should fail with an unknown account', async function () { it('Should fail with an unknown account', async function () {
const fields = { account: { id: 42 }, reason: 'my super reason' } const fields = { account: { id: 42 }, reason: 'my super reason' }
await makePostBodyRequest({ url: server.url, path: path, token: server.accessToken, fields, statusCodeExpected: 404 }) await makePostBodyRequest({ url: server.url, path: path, token: userAccessToken, fields, statusCodeExpected: 404 })
}) })
it('Should fail with not account, comment or video', async function () { it('Should fail with not account, comment or video', async function () {
const fields = { reason: 'my super reason' } const fields = { reason: 'my super reason' }
await makePostBodyRequest({ url: server.url, path: path, token: server.accessToken, fields, statusCodeExpected: 400 }) await makePostBodyRequest({ url: server.url, path: path, token: userAccessToken, fields, statusCodeExpected: 400 })
}) })
it('Should fail with a non authenticated user', async function () { it('Should fail with a non authenticated user', async function () {
@ -165,38 +217,38 @@ describe('Test abuses API validators', function () {
it('Should fail with a reason too short', async function () { it('Should fail with a reason too short', async function () {
const fields = { video: { id: server.video.id }, reason: 'h' } const fields = { video: { id: server.video.id }, reason: 'h' }
await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields }) await makePostBodyRequest({ url: server.url, path, token: userAccessToken, fields })
}) })
it('Should fail with a too big reason', async function () { it('Should fail with a too big reason', async function () {
const fields = { video: { id: server.video.id }, reason: 'super'.repeat(605) } const fields = { video: { id: server.video.id }, reason: 'super'.repeat(605) }
await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields }) await makePostBodyRequest({ url: server.url, path, token: userAccessToken, fields })
}) })
it('Should succeed with the correct parameters (basic)', async function () { it('Should succeed with the correct parameters (basic)', async function () {
const fields: AbuseCreate = { video: { id: server.video.id }, reason: 'my super reason' } const fields: AbuseCreate = { video: { id: server.video.id }, reason: 'my super reason' }
const res = await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields, statusCodeExpected: 200 }) const res = await makePostBodyRequest({ url: server.url, path, token: userAccessToken, fields, statusCodeExpected: 200 })
abuseId = res.body.abuse.id abuseId = res.body.abuse.id
}) })
it('Should fail with a wrong predefined reason', async function () { it('Should fail with a wrong predefined reason', async function () {
const fields = { video: { id: server.video.id }, reason: 'my super reason', predefinedReasons: [ 'wrongPredefinedReason' ] } const fields = { video: { id: server.video.id }, reason: 'my super reason', predefinedReasons: [ 'wrongPredefinedReason' ] }
await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields }) await makePostBodyRequest({ url: server.url, path, token: userAccessToken, fields })
}) })
it('Should fail with negative timestamps', async function () { it('Should fail with negative timestamps', async function () {
const fields = { video: { id: server.video.id, startAt: -1 }, reason: 'my super reason' } const fields = { video: { id: server.video.id, startAt: -1 }, reason: 'my super reason' }
await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields }) await makePostBodyRequest({ url: server.url, path, token: userAccessToken, fields })
}) })
it('Should fail mith misordered startAt/endAt', async function () { it('Should fail mith misordered startAt/endAt', async function () {
const fields = { video: { id: server.video.id, startAt: 5, endAt: 1 }, reason: 'my super reason' } const fields = { video: { id: server.video.id, startAt: 5, endAt: 1 }, reason: 'my super reason' }
await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields }) await makePostBodyRequest({ url: server.url, path, token: userAccessToken, fields })
}) })
it('Should succeed with the corret parameters (advanced)', async function () { it('Should succeed with the corret parameters (advanced)', async function () {
@ -210,7 +262,7 @@ describe('Test abuses API validators', function () {
predefinedReasons: [ 'serverRules' ] predefinedReasons: [ 'serverRules' ]
} }
await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields, statusCodeExpected: 200 }) await makePostBodyRequest({ url: server.url, path, token: userAccessToken, fields, statusCodeExpected: 200 })
}) })
}) })
@ -244,6 +296,73 @@ describe('Test abuses API validators', function () {
}) })
}) })
describe('When creating an abuse message', function () {
const message = 'my super message'
it('Should fail with an invalid abuse id', async function () {
await addAbuseMessage(server.url, userAccessToken2, 888, message, 404)
})
it('Should fail with a non authenticated user', async function () {
await addAbuseMessage(server.url, 'fake_token', abuseId, message, 401)
})
it('Should fail with an invalid logged in user', async function () {
await addAbuseMessage(server.url, userAccessToken2, abuseId, message, 403)
})
it('Should fail with an invalid message', async function () {
await addAbuseMessage(server.url, userAccessToken, abuseId, 'a'.repeat(5000), 400)
})
it('Should suceed with the correct params', async function () {
const res = await addAbuseMessage(server.url, userAccessToken, abuseId, message)
messageId = res.body.abuseMessage.id
})
})
describe('When listing abuse message', function () {
it('Should fail with an invalid abuse id', async function () {
await listAbuseMessages(server.url, userAccessToken, 888, 404)
})
it('Should fail with a non authenticated user', async function () {
await listAbuseMessages(server.url, 'fake_token', abuseId, 401)
})
it('Should fail with an invalid logged in user', async function () {
await listAbuseMessages(server.url, userAccessToken2, abuseId, 403)
})
it('Should succeed with the correct params', async function () {
await listAbuseMessages(server.url, userAccessToken, abuseId)
})
})
describe('When deleting an abuse message', function () {
it('Should fail with an invalid abuse id', async function () {
await deleteAbuseMessage(server.url, userAccessToken, 888, messageId, 404)
})
it('Should fail with an invalid message id', async function () {
await deleteAbuseMessage(server.url, userAccessToken, abuseId, 888, 404)
})
it('Should fail with a non authenticated user', async function () {
await deleteAbuseMessage(server.url, 'fake_token', abuseId, messageId, 401)
})
it('Should fail with an invalid logged in user', async function () {
await deleteAbuseMessage(server.url, userAccessToken2, abuseId, messageId, 403)
})
it('Should succeed with the correct params', async function () {
await deleteAbuseMessage(server.url, userAccessToken, abuseId, messageId)
})
})
describe('When deleting a video abuse', function () { describe('When deleting a video abuse', function () {
it('Should fail with a non authenticated user', async function () { it('Should fail with a non authenticated user', async function () {

View File

@ -2,7 +2,7 @@
import 'mocha' import 'mocha'
import * as chai from 'chai' import * as chai from 'chai'
import { Abuse, AbuseFilter, AbusePredefinedReasonsString, AbuseState, VideoComment, Account } from '@shared/models' import { AbuseFilter, AbusePredefinedReasonsString, AbuseState, Account, AdminAbuse, UserAbuse, VideoComment, AbuseMessage } from '@shared/models'
import { import {
addVideoCommentThread, addVideoCommentThread,
cleanupTests, cleanupTests,
@ -10,11 +10,15 @@ import {
deleteAbuse, deleteAbuse,
deleteVideoComment, deleteVideoComment,
flushAndRunMultipleServers, flushAndRunMultipleServers,
getAbusesList, generateUserAccessToken,
getAccount,
getAdminAbusesList,
getUserAbusesList,
getVideoCommentThreads, getVideoCommentThreads,
getVideoIdFromUUID, getVideoIdFromUUID,
getVideosList, getVideosList,
immutableAssign, immutableAssign,
removeUser,
removeVideo, removeVideo,
reportAbuse, reportAbuse,
ServerInfo, ServerInfo,
@ -23,9 +27,9 @@ import {
uploadVideo, uploadVideo,
uploadVideoAndGetId, uploadVideoAndGetId,
userLogin, userLogin,
getAccount, addAbuseMessage,
removeUser, listAbuseMessages,
generateUserAccessToken deleteAbuseMessage
} from '../../../../shared/extra-utils/index' } from '../../../../shared/extra-utils/index'
import { doubleFollow } from '../../../../shared/extra-utils/server/follows' import { doubleFollow } from '../../../../shared/extra-utils/server/follows'
import { waitJobs } from '../../../../shared/extra-utils/server/jobs' import { waitJobs } from '../../../../shared/extra-utils/server/jobs'
@ -40,8 +44,8 @@ const expect = chai.expect
describe('Test abuses', function () { describe('Test abuses', function () {
let servers: ServerInfo[] = [] let servers: ServerInfo[] = []
let abuseServer1: Abuse let abuseServer1: AdminAbuse
let abuseServer2: Abuse let abuseServer2: AdminAbuse
before(async function () { before(async function () {
this.timeout(50000) this.timeout(50000)
@ -87,7 +91,7 @@ describe('Test abuses', function () {
}) })
it('Should not have abuses', async function () { it('Should not have abuses', async function () {
const res = await getAbusesList({ url: servers[0].url, token: servers[0].accessToken }) const res = await getAdminAbusesList({ url: servers[0].url, token: servers[0].accessToken })
expect(res.body.total).to.equal(0) expect(res.body.total).to.equal(0)
expect(res.body.data).to.be.an('array') expect(res.body.data).to.be.an('array')
@ -105,13 +109,13 @@ describe('Test abuses', function () {
}) })
it('Should have 1 video abuses on server 1 and 0 on server 2', async function () { it('Should have 1 video abuses on server 1 and 0 on server 2', async function () {
const res1 = await getAbusesList({ url: servers[0].url, token: servers[0].accessToken }) const res1 = await getAdminAbusesList({ url: servers[0].url, token: servers[0].accessToken })
expect(res1.body.total).to.equal(1) expect(res1.body.total).to.equal(1)
expect(res1.body.data).to.be.an('array') expect(res1.body.data).to.be.an('array')
expect(res1.body.data.length).to.equal(1) expect(res1.body.data.length).to.equal(1)
const abuse: Abuse = res1.body.data[0] const abuse: AdminAbuse = res1.body.data[0]
expect(abuse.reason).to.equal('my super bad reason') expect(abuse.reason).to.equal('my super bad reason')
expect(abuse.reporterAccount.name).to.equal('root') expect(abuse.reporterAccount.name).to.equal('root')
@ -131,7 +135,7 @@ describe('Test abuses', function () {
expect(abuse.countReportsForReporter).to.equal(1) expect(abuse.countReportsForReporter).to.equal(1)
expect(abuse.countReportsForReportee).to.equal(1) expect(abuse.countReportsForReportee).to.equal(1)
const res2 = await getAbusesList({ url: servers[1].url, token: servers[1].accessToken }) const res2 = await getAdminAbusesList({ url: servers[1].url, token: servers[1].accessToken })
expect(res2.body.total).to.equal(0) expect(res2.body.total).to.equal(0)
expect(res2.body.data).to.be.an('array') expect(res2.body.data).to.be.an('array')
expect(res2.body.data.length).to.equal(0) expect(res2.body.data.length).to.equal(0)
@ -141,19 +145,20 @@ describe('Test abuses', function () {
this.timeout(10000) this.timeout(10000)
const reason = 'my super bad reason 2' const reason = 'my super bad reason 2'
await reportAbuse({ url: servers[0].url, token: servers[0].accessToken, videoId: servers[1].video.id, reason }) const videoId = await getVideoIdFromUUID(servers[0].url, servers[1].video.uuid)
await reportAbuse({ url: servers[0].url, token: servers[0].accessToken, videoId, reason })
// We wait requests propagation // We wait requests propagation
await waitJobs(servers) await waitJobs(servers)
}) })
it('Should have 2 video abuses on server 1 and 1 on server 2', async function () { it('Should have 2 video abuses on server 1 and 1 on server 2', async function () {
const res1 = await getAbusesList({ url: servers[0].url, token: servers[0].accessToken }) const res1 = await getAdminAbusesList({ url: servers[0].url, token: servers[0].accessToken })
expect(res1.body.total).to.equal(2) expect(res1.body.total).to.equal(2)
expect(res1.body.data.length).to.equal(2) expect(res1.body.data.length).to.equal(2)
const abuse1: Abuse = res1.body.data[0] const abuse1: AdminAbuse = res1.body.data[0]
expect(abuse1.reason).to.equal('my super bad reason') expect(abuse1.reason).to.equal('my super bad reason')
expect(abuse1.reporterAccount.name).to.equal('root') expect(abuse1.reporterAccount.name).to.equal('root')
expect(abuse1.reporterAccount.host).to.equal(servers[0].host) expect(abuse1.reporterAccount.host).to.equal(servers[0].host)
@ -171,7 +176,7 @@ describe('Test abuses', function () {
expect(abuse1.state.label).to.equal('Pending') expect(abuse1.state.label).to.equal('Pending')
expect(abuse1.moderationComment).to.be.null expect(abuse1.moderationComment).to.be.null
const abuse2: Abuse = res1.body.data[1] const abuse2: AdminAbuse = res1.body.data[1]
expect(abuse2.reason).to.equal('my super bad reason 2') expect(abuse2.reason).to.equal('my super bad reason 2')
expect(abuse2.reporterAccount.name).to.equal('root') expect(abuse2.reporterAccount.name).to.equal('root')
@ -188,7 +193,7 @@ describe('Test abuses', function () {
expect(abuse2.state.label).to.equal('Pending') expect(abuse2.state.label).to.equal('Pending')
expect(abuse2.moderationComment).to.be.null expect(abuse2.moderationComment).to.be.null
const res2 = await getAbusesList({ url: servers[1].url, token: servers[1].accessToken }) const res2 = await getAdminAbusesList({ url: servers[1].url, token: servers[1].accessToken })
expect(res2.body.total).to.equal(1) expect(res2.body.total).to.equal(1)
expect(res2.body.data.length).to.equal(1) expect(res2.body.data.length).to.equal(1)
@ -213,7 +218,7 @@ describe('Test abuses', function () {
await reportAbuse({ url: servers[1].url, token: servers[1].accessToken, videoId, reason: 'will mute this' }) await reportAbuse({ url: servers[1].url, token: servers[1].accessToken, videoId, reason: 'will mute this' })
await waitJobs(servers) await waitJobs(servers)
const res = await getAbusesList({ url: servers[0].url, token: servers[0].accessToken }) const res = await getAdminAbusesList({ url: servers[0].url, token: servers[0].accessToken })
expect(res.body.total).to.equal(3) expect(res.body.total).to.equal(3)
} }
@ -222,7 +227,7 @@ describe('Test abuses', function () {
{ {
await addAccountToServerBlocklist(servers[0].url, servers[0].accessToken, accountToBlock) await addAccountToServerBlocklist(servers[0].url, servers[0].accessToken, accountToBlock)
const res = await getAbusesList({ url: servers[0].url, token: servers[0].accessToken }) const res = await getAdminAbusesList({ url: servers[0].url, token: servers[0].accessToken })
expect(res.body.total).to.equal(2) expect(res.body.total).to.equal(2)
const abuse = res.body.data.find(a => a.reason === 'will mute this') const abuse = res.body.data.find(a => a.reason === 'will mute this')
@ -232,7 +237,7 @@ describe('Test abuses', function () {
{ {
await removeAccountFromServerBlocklist(servers[0].url, servers[0].accessToken, accountToBlock) await removeAccountFromServerBlocklist(servers[0].url, servers[0].accessToken, accountToBlock)
const res = await getAbusesList({ url: servers[0].url, token: servers[0].accessToken }) const res = await getAdminAbusesList({ url: servers[0].url, token: servers[0].accessToken })
expect(res.body.total).to.equal(3) expect(res.body.total).to.equal(3)
} }
}) })
@ -243,7 +248,7 @@ describe('Test abuses', function () {
{ {
await addServerToServerBlocklist(servers[0].url, servers[0].accessToken, servers[1].host) await addServerToServerBlocklist(servers[0].url, servers[0].accessToken, servers[1].host)
const res = await getAbusesList({ url: servers[0].url, token: servers[0].accessToken }) const res = await getAdminAbusesList({ url: servers[0].url, token: servers[0].accessToken })
expect(res.body.total).to.equal(2) expect(res.body.total).to.equal(2)
const abuse = res.body.data.find(a => a.reason === 'will mute this') const abuse = res.body.data.find(a => a.reason === 'will mute this')
@ -253,7 +258,7 @@ describe('Test abuses', function () {
{ {
await removeServerFromServerBlocklist(servers[0].url, servers[0].accessToken, serverToBlock) await removeServerFromServerBlocklist(servers[0].url, servers[0].accessToken, serverToBlock)
const res = await getAbusesList({ url: servers[0].url, token: servers[0].accessToken }) const res = await getAdminAbusesList({ url: servers[0].url, token: servers[0].accessToken })
expect(res.body.total).to.equal(3) expect(res.body.total).to.equal(3)
} }
}) })
@ -265,11 +270,11 @@ describe('Test abuses', function () {
await waitJobs(servers) await waitJobs(servers)
const res = await getAbusesList({ url: servers[1].url, token: servers[1].accessToken }) const res = await getAdminAbusesList({ url: servers[1].url, token: servers[1].accessToken })
expect(res.body.total).to.equal(2, "wrong number of videos returned") expect(res.body.total).to.equal(2, "wrong number of videos returned")
expect(res.body.data).to.have.lengthOf(2, "wrong number of videos returned") expect(res.body.data).to.have.lengthOf(2, "wrong number of videos returned")
const abuse: Abuse = res.body.data[0] const abuse: AdminAbuse = res.body.data[0]
expect(abuse.id).to.equal(abuseServer2.id, "wrong origin server id for first video") expect(abuse.id).to.equal(abuseServer2.id, "wrong origin server id for first video")
expect(abuse.video.id).to.equal(abuseServer2.video.id, "wrong video id") expect(abuse.video.id).to.equal(abuseServer2.video.id, "wrong video id")
expect(abuse.video.channel).to.exist expect(abuse.video.channel).to.exist
@ -303,8 +308,8 @@ describe('Test abuses', function () {
await reportAbuse({ url: servers[0].url, token: userAccessToken, videoId: servers[0].video.id, reason: reason4 }) await reportAbuse({ url: servers[0].url, token: userAccessToken, videoId: servers[0].video.id, reason: reason4 })
{ {
const res2 = await getAbusesList({ url: servers[0].url, token: servers[0].accessToken }) const res2 = await getAdminAbusesList({ url: servers[0].url, token: servers[0].accessToken })
const abuses = res2.body.data as Abuse[] const abuses = res2.body.data as AdminAbuse[]
const abuseVideo3 = res2.body.data.find(a => a.video.id === video3.id) const abuseVideo3 = res2.body.data.find(a => a.video.id === video3.id)
expect(abuseVideo3).to.not.be.undefined expect(abuseVideo3).to.not.be.undefined
@ -333,10 +338,10 @@ describe('Test abuses', function () {
endAt: 5 endAt: 5
})).body.abuse })).body.abuse
const res = await getAbusesList({ url: servers[0].url, token: servers[0].accessToken }) const res = await getAdminAbusesList({ url: servers[0].url, token: servers[0].accessToken })
{ {
const abuse = (res.body.data as Abuse[]).find(a => a.id === createdAbuse.id) const abuse = (res.body.data as AdminAbuse[]).find(a => a.id === createdAbuse.id)
expect(abuse.reason).to.equals(reason5) expect(abuse.reason).to.equals(reason5)
expect(abuse.predefinedReasons).to.deep.equals(predefinedReasons5, "predefined reasons do not match the one reported") expect(abuse.predefinedReasons).to.deep.equals(predefinedReasons5, "predefined reasons do not match the one reported")
expect(abuse.video.startAt).to.equal(1, "starting timestamp doesn't match the one reported") expect(abuse.video.startAt).to.equal(1, "starting timestamp doesn't match the one reported")
@ -352,14 +357,14 @@ describe('Test abuses', function () {
await waitJobs(servers) await waitJobs(servers)
{ {
const res = await getAbusesList({ url: servers[1].url, token: servers[1].accessToken }) const res = await getAdminAbusesList({ url: servers[1].url, token: servers[1].accessToken })
expect(res.body.total).to.equal(1) expect(res.body.total).to.equal(1)
expect(res.body.data.length).to.equal(1) expect(res.body.data.length).to.equal(1)
expect(res.body.data[0].id).to.not.equal(abuseServer2.id) expect(res.body.data[0].id).to.not.equal(abuseServer2.id)
} }
{ {
const res = await getAbusesList({ url: servers[0].url, token: servers[0].accessToken }) const res = await getAdminAbusesList({ url: servers[0].url, token: servers[0].accessToken })
expect(res.body.total).to.equal(6) expect(res.body.total).to.equal(6)
} }
}) })
@ -367,7 +372,7 @@ describe('Test abuses', function () {
it('Should list and filter video abuses', async function () { it('Should list and filter video abuses', async function () {
this.timeout(10000) this.timeout(10000)
async function list (query: Omit<Parameters<typeof getAbusesList>[0], 'url' | 'token'>) { async function list (query: Omit<Parameters<typeof getAdminAbusesList>[0], 'url' | 'token'>) {
const options = { const options = {
url: servers[0].url, url: servers[0].url,
token: servers[0].accessToken token: servers[0].accessToken
@ -375,9 +380,9 @@ describe('Test abuses', function () {
Object.assign(options, query) Object.assign(options, query)
const res = await getAbusesList(options) const res = await getAdminAbusesList(options)
return res.body.data as Abuse[] return res.body.data as AdminAbuse[]
} }
expect(await list({ id: 56 })).to.have.lengthOf(0) expect(await list({ id: 56 })).to.have.lengthOf(0)
@ -446,12 +451,12 @@ describe('Test abuses', function () {
it('Should have 1 comment abuse on server 1 and 0 on server 2', async function () { it('Should have 1 comment abuse on server 1 and 0 on server 2', async function () {
{ {
const comment = await getComment(servers[0].url, servers[0].video.id) const comment = await getComment(servers[0].url, servers[0].video.id)
const res = await getAbusesList({ url: servers[0].url, token: servers[0].accessToken, filter: 'comment' }) const res = await getAdminAbusesList({ url: servers[0].url, token: servers[0].accessToken, filter: 'comment' })
expect(res.body.total).to.equal(1) expect(res.body.total).to.equal(1)
expect(res.body.data).to.have.lengthOf(1) expect(res.body.data).to.have.lengthOf(1)
const abuse: Abuse = res.body.data[0] const abuse: AdminAbuse = res.body.data[0]
expect(abuse.reason).to.equal('it is a bad comment') expect(abuse.reason).to.equal('it is a bad comment')
expect(abuse.reporterAccount.name).to.equal('root') expect(abuse.reporterAccount.name).to.equal('root')
@ -471,7 +476,7 @@ describe('Test abuses', function () {
} }
{ {
const res = await getAbusesList({ url: servers[1].url, token: servers[1].accessToken, filter: 'comment' }) const res = await getAdminAbusesList({ url: servers[1].url, token: servers[1].accessToken, filter: 'comment' })
expect(res.body.total).to.equal(0) expect(res.body.total).to.equal(0)
expect(res.body.data.length).to.equal(0) expect(res.body.data.length).to.equal(0)
} }
@ -491,16 +496,16 @@ describe('Test abuses', function () {
it('Should have 2 comment abuses on server 1 and 1 on server 2', async function () { it('Should have 2 comment abuses on server 1 and 1 on server 2', async function () {
const commentServer2 = await getComment(servers[0].url, servers[1].video.id) const commentServer2 = await getComment(servers[0].url, servers[1].video.id)
const res1 = await getAbusesList({ url: servers[0].url, token: servers[0].accessToken, filter: 'comment' }) const res1 = await getAdminAbusesList({ url: servers[0].url, token: servers[0].accessToken, filter: 'comment' })
expect(res1.body.total).to.equal(2) expect(res1.body.total).to.equal(2)
expect(res1.body.data.length).to.equal(2) expect(res1.body.data.length).to.equal(2)
const abuse: Abuse = res1.body.data[0] const abuse: AdminAbuse = res1.body.data[0]
expect(abuse.reason).to.equal('it is a bad comment') expect(abuse.reason).to.equal('it is a bad comment')
expect(abuse.countReportsForReporter).to.equal(6) expect(abuse.countReportsForReporter).to.equal(6)
expect(abuse.countReportsForReportee).to.equal(5) expect(abuse.countReportsForReportee).to.equal(5)
const abuse2: Abuse = res1.body.data[1] const abuse2: AdminAbuse = res1.body.data[1]
expect(abuse2.reason).to.equal('it is a really bad comment') expect(abuse2.reason).to.equal('it is a really bad comment')
@ -523,7 +528,7 @@ describe('Test abuses', function () {
expect(abuse2.countReportsForReporter).to.equal(6) expect(abuse2.countReportsForReporter).to.equal(6)
expect(abuse2.countReportsForReportee).to.equal(2) expect(abuse2.countReportsForReportee).to.equal(2)
const res2 = await getAbusesList({ url: servers[1].url, token: servers[1].accessToken, filter: 'comment' }) const res2 = await getAdminAbusesList({ url: servers[1].url, token: servers[1].accessToken, filter: 'comment' })
expect(res2.body.total).to.equal(1) expect(res2.body.total).to.equal(1)
expect(res2.body.data.length).to.equal(1) expect(res2.body.data.length).to.equal(1)
@ -550,11 +555,11 @@ describe('Test abuses', function () {
await waitJobs(servers) await waitJobs(servers)
const res = await getAbusesList({ url: servers[0].url, token: servers[0].accessToken, filter: 'comment' }) const res = await getAdminAbusesList({ url: servers[0].url, token: servers[0].accessToken, filter: 'comment' })
expect(res.body.total).to.equal(2) expect(res.body.total).to.equal(2)
expect(res.body.data).to.have.lengthOf(2) expect(res.body.data).to.have.lengthOf(2)
const abuse = (res.body.data as Abuse[]).find(a => a.comment?.id === commentServer2.id) const abuse = (res.body.data as AdminAbuse[]).find(a => a.comment?.id === commentServer2.id)
expect(abuse).to.not.be.undefined expect(abuse).to.not.be.undefined
expect(abuse.comment.text).to.be.empty expect(abuse.comment.text).to.be.empty
@ -570,36 +575,46 @@ describe('Test abuses', function () {
await waitJobs(servers) await waitJobs(servers)
{ {
const res = await getAbusesList({ url: servers[1].url, token: servers[1].accessToken, filter: 'comment' }) const res = await getAdminAbusesList({ url: servers[1].url, token: servers[1].accessToken, filter: 'comment' })
expect(res.body.total).to.equal(0) expect(res.body.total).to.equal(0)
expect(res.body.data.length).to.equal(0) expect(res.body.data.length).to.equal(0)
} }
{ {
const res = await getAbusesList({ url: servers[0].url, token: servers[0].accessToken, filter: 'comment' }) const res = await getAdminAbusesList({ url: servers[0].url, token: servers[0].accessToken, filter: 'comment' })
expect(res.body.total).to.equal(2) expect(res.body.total).to.equal(2)
} }
}) })
it('Should list and filter video abuses', async function () { it('Should list and filter video abuses', async function () {
{ {
const res = await getAbusesList({ url: servers[0].url, token: servers[0].accessToken, filter: 'comment', searchReportee: 'foo' }) const res = await getAdminAbusesList({
url: servers[0].url,
token: servers[0].accessToken,
filter: 'comment',
searchReportee: 'foo'
})
expect(res.body.total).to.equal(0) expect(res.body.total).to.equal(0)
} }
{ {
const res = await getAbusesList({ url: servers[0].url, token: servers[0].accessToken, filter: 'comment', searchReportee: 'ot' }) const res = await getAdminAbusesList({
url: servers[0].url,
token: servers[0].accessToken,
filter: 'comment',
searchReportee: 'ot'
})
expect(res.body.total).to.equal(2) expect(res.body.total).to.equal(2)
} }
{ {
const baseParams = { url: servers[0].url, token: servers[0].accessToken, filter: 'comment' as AbuseFilter, start: 1, count: 1 } const baseParams = { url: servers[0].url, token: servers[0].accessToken, filter: 'comment' as AbuseFilter, start: 1, count: 1 }
const res1 = await getAbusesList(immutableAssign(baseParams, { sort: 'createdAt' })) const res1 = await getAdminAbusesList(immutableAssign(baseParams, { sort: 'createdAt' }))
expect(res1.body.data).to.have.lengthOf(1) expect(res1.body.data).to.have.lengthOf(1)
expect(res1.body.data[0].comment.text).to.be.empty expect(res1.body.data[0].comment.text).to.be.empty
const res2 = await getAbusesList(immutableAssign(baseParams, { sort: '-createdAt' })) const res2 = await getAdminAbusesList(immutableAssign(baseParams, { sort: '-createdAt' }))
expect(res2.body.data).to.have.lengthOf(1) expect(res2.body.data).to.have.lengthOf(1)
expect(res2.body.data[0].comment.text).to.equal('comment server 1') expect(res2.body.data[0].comment.text).to.equal('comment server 1')
} }
@ -638,12 +653,12 @@ describe('Test abuses', function () {
it('Should have 1 account abuse on server 1 and 0 on server 2', async function () { it('Should have 1 account abuse on server 1 and 0 on server 2', async function () {
{ {
const res = await getAbusesList({ url: servers[0].url, token: servers[0].accessToken, filter: 'account' }) const res = await getAdminAbusesList({ url: servers[0].url, token: servers[0].accessToken, filter: 'account' })
expect(res.body.total).to.equal(1) expect(res.body.total).to.equal(1)
expect(res.body.data).to.have.lengthOf(1) expect(res.body.data).to.have.lengthOf(1)
const abuse: Abuse = res.body.data[0] const abuse: AdminAbuse = res.body.data[0]
expect(abuse.reason).to.equal('it is a bad account') expect(abuse.reason).to.equal('it is a bad account')
expect(abuse.reporterAccount.name).to.equal('root') expect(abuse.reporterAccount.name).to.equal('root')
@ -657,7 +672,7 @@ describe('Test abuses', function () {
} }
{ {
const res = await getAbusesList({ url: servers[1].url, token: servers[1].accessToken, filter: 'comment' }) const res = await getAdminAbusesList({ url: servers[1].url, token: servers[1].accessToken, filter: 'comment' })
expect(res.body.total).to.equal(0) expect(res.body.total).to.equal(0)
expect(res.body.data.length).to.equal(0) expect(res.body.data.length).to.equal(0)
} }
@ -675,14 +690,14 @@ describe('Test abuses', function () {
}) })
it('Should have 2 comment abuses on server 1 and 1 on server 2', async function () { it('Should have 2 comment abuses on server 1 and 1 on server 2', async function () {
const res1 = await getAbusesList({ url: servers[0].url, token: servers[0].accessToken, filter: 'account' }) const res1 = await getAdminAbusesList({ url: servers[0].url, token: servers[0].accessToken, filter: 'account' })
expect(res1.body.total).to.equal(2) expect(res1.body.total).to.equal(2)
expect(res1.body.data.length).to.equal(2) expect(res1.body.data.length).to.equal(2)
const abuse: Abuse = res1.body.data[0] const abuse: AdminAbuse = res1.body.data[0]
expect(abuse.reason).to.equal('it is a bad account') expect(abuse.reason).to.equal('it is a bad account')
const abuse2: Abuse = res1.body.data[1] const abuse2: AdminAbuse = res1.body.data[1]
expect(abuse2.reason).to.equal('it is a really bad account') expect(abuse2.reason).to.equal('it is a really bad account')
expect(abuse2.reporterAccount.name).to.equal('root') expect(abuse2.reporterAccount.name).to.equal('root')
@ -696,7 +711,7 @@ describe('Test abuses', function () {
expect(abuse2.moderationComment).to.be.null expect(abuse2.moderationComment).to.be.null
const res2 = await getAbusesList({ url: servers[1].url, token: servers[1].accessToken, filter: 'account' }) const res2 = await getAdminAbusesList({ url: servers[1].url, token: servers[1].accessToken, filter: 'account' })
expect(res2.body.total).to.equal(1) expect(res2.body.total).to.equal(1)
expect(res2.body.data.length).to.equal(1) expect(res2.body.data.length).to.equal(1)
@ -721,11 +736,11 @@ describe('Test abuses', function () {
await waitJobs(servers) await waitJobs(servers)
const res = await getAbusesList({ url: servers[0].url, token: servers[0].accessToken, filter: 'account' }) const res = await getAdminAbusesList({ url: servers[0].url, token: servers[0].accessToken, filter: 'account' })
expect(res.body.total).to.equal(2) expect(res.body.total).to.equal(2)
expect(res.body.data).to.have.lengthOf(2) expect(res.body.data).to.have.lengthOf(2)
const abuse = (res.body.data as Abuse[]).find(a => a.reason === 'it is a really bad account') const abuse = (res.body.data as AdminAbuse[]).find(a => a.reason === 'it is a really bad account')
expect(abuse).to.not.be.undefined expect(abuse).to.not.be.undefined
}) })
@ -737,13 +752,13 @@ describe('Test abuses', function () {
await waitJobs(servers) await waitJobs(servers)
{ {
const res = await getAbusesList({ url: servers[1].url, token: servers[1].accessToken, filter: 'account' }) const res = await getAdminAbusesList({ url: servers[1].url, token: servers[1].accessToken, filter: 'account' })
expect(res.body.total).to.equal(0) expect(res.body.total).to.equal(0)
expect(res.body.data.length).to.equal(0) expect(res.body.data.length).to.equal(0)
} }
{ {
const res = await getAbusesList({ url: servers[0].url, token: servers[0].accessToken, filter: 'account' }) const res = await getAdminAbusesList({ url: servers[0].url, token: servers[0].accessToken, filter: 'account' })
expect(res.body.total).to.equal(2) expect(res.body.total).to.equal(2)
abuseServer1 = res.body.data[0] abuseServer1 = res.body.data[0]
@ -757,7 +772,7 @@ describe('Test abuses', function () {
const body = { state: AbuseState.REJECTED } const body = { state: AbuseState.REJECTED }
await updateAbuse(servers[0].url, servers[0].accessToken, abuseServer1.id, body) await updateAbuse(servers[0].url, servers[0].accessToken, abuseServer1.id, body)
const res = await getAbusesList({ url: servers[0].url, token: servers[0].accessToken, id: abuseServer1.id }) const res = await getAdminAbusesList({ url: servers[0].url, token: servers[0].accessToken, id: abuseServer1.id })
expect(res.body.data[0].state.id).to.equal(AbuseState.REJECTED) expect(res.body.data[0].state.id).to.equal(AbuseState.REJECTED)
}) })
@ -765,12 +780,184 @@ describe('Test abuses', function () {
const body = { state: AbuseState.ACCEPTED, moderationComment: 'It is valid' } const body = { state: AbuseState.ACCEPTED, moderationComment: 'It is valid' }
await updateAbuse(servers[0].url, servers[0].accessToken, abuseServer1.id, body) await updateAbuse(servers[0].url, servers[0].accessToken, abuseServer1.id, body)
const res = await getAbusesList({ url: servers[0].url, token: servers[0].accessToken, id: abuseServer1.id }) const res = await getAdminAbusesList({ url: servers[0].url, token: servers[0].accessToken, id: abuseServer1.id })
expect(res.body.data[0].state.id).to.equal(AbuseState.ACCEPTED) expect(res.body.data[0].state.id).to.equal(AbuseState.ACCEPTED)
expect(res.body.data[0].moderationComment).to.equal('It is valid') expect(res.body.data[0].moderationComment).to.equal('It is valid')
}) })
}) })
describe('My abuses', async function () {
let abuseId1: number
let userAccessToken: string
before(async function () {
userAccessToken = await generateUserAccessToken(servers[0], 'user_42')
await reportAbuse({ url: servers[0].url, token: userAccessToken, videoId: servers[0].video.id, reason: 'user reason 1' })
const videoId = await getVideoIdFromUUID(servers[0].url, servers[1].video.uuid)
await reportAbuse({ url: servers[0].url, token: userAccessToken, videoId, reason: 'user reason 2' })
})
it('Should correctly list my abuses', async function () {
{
const res = await getUserAbusesList({ url: servers[0].url, token: userAccessToken, start: 0, count: 5, sort: 'createdAt' })
expect(res.body.total).to.equal(2)
const abuses: UserAbuse[] = res.body.data
expect(abuses[0].reason).to.equal('user reason 1')
expect(abuses[1].reason).to.equal('user reason 2')
abuseId1 = abuses[0].id
}
{
const res = await getUserAbusesList({ url: servers[0].url, token: userAccessToken, start: 1, count: 1, sort: 'createdAt' })
expect(res.body.total).to.equal(2)
const abuses: UserAbuse[] = res.body.data
expect(abuses[0].reason).to.equal('user reason 2')
}
{
const res = await getUserAbusesList({ url: servers[0].url, token: userAccessToken, start: 1, count: 1, sort: '-createdAt' })
expect(res.body.total).to.equal(2)
const abuses: UserAbuse[] = res.body.data
expect(abuses[0].reason).to.equal('user reason 1')
}
})
it('Should correctly filter my abuses by id', async function () {
const res = await getUserAbusesList({ url: servers[0].url, token: userAccessToken, id: abuseId1 })
expect(res.body.total).to.equal(1)
const abuses: UserAbuse[] = res.body.data
expect(abuses[0].reason).to.equal('user reason 1')
})
it('Should correctly filter my abuses by search', async function () {
const res = await getUserAbusesList({
url: servers[0].url,
token: userAccessToken,
search: 'server 2'
})
expect(res.body.total).to.equal(1)
const abuses: UserAbuse[] = res.body.data
expect(abuses[0].reason).to.equal('user reason 2')
})
it('Should correctly filter my abuses by state', async function () {
const body = { state: AbuseState.REJECTED }
await updateAbuse(servers[0].url, servers[0].accessToken, abuseId1, body)
const res = await getUserAbusesList({
url: servers[0].url,
token: userAccessToken,
state: AbuseState.REJECTED
})
expect(res.body.total).to.equal(1)
const abuses: UserAbuse[] = res.body.data
expect(abuses[0].reason).to.equal('user reason 1')
})
})
describe('Abuse messages', async function () {
let abuseId: number
let userAccessToken: string
let abuseMessageUserId: number
let abuseMessageModerationId: number
before(async function () {
userAccessToken = await generateUserAccessToken(servers[0], 'user_43')
const res = await reportAbuse({
url: servers[0].url,
token: userAccessToken,
videoId: servers[0].video.id,
reason: 'user 43 reason 1'
})
abuseId = res.body.abuse.id
})
it('Should create some messages on the abuse', async function () {
await addAbuseMessage(servers[0].url, userAccessToken, abuseId, 'message 1')
await addAbuseMessage(servers[0].url, servers[0].accessToken, abuseId, 'message 2')
await addAbuseMessage(servers[0].url, servers[0].accessToken, abuseId, 'message 3')
await addAbuseMessage(servers[0].url, userAccessToken, abuseId, 'message 4')
})
it('Should have the correct messages count when listing abuses', async function () {
const results = await Promise.all([
getAdminAbusesList({ url: servers[0].url, token: servers[0].accessToken, start: 0, count: 50 }),
getUserAbusesList({ url: servers[0].url, token: userAccessToken, start: 0, count: 50 })
])
for (const res of results) {
const abuses: AdminAbuse[] = res.body.data
const abuse = abuses.find(a => a.id === abuseId)
expect(abuse.countMessages).to.equal(4)
}
})
it('Should correctly list messages of this abuse', async function () {
const results = await Promise.all([
listAbuseMessages(servers[0].url, servers[0].accessToken, abuseId),
listAbuseMessages(servers[0].url, userAccessToken, abuseId)
])
for (const res of results) {
expect(res.body.total).to.equal(4)
const abuseMessages: AbuseMessage[] = res.body.data
expect(abuseMessages[0].message).to.equal('message 1')
expect(abuseMessages[0].byModerator).to.be.false
expect(abuseMessages[0].account.name).to.equal('user_43')
abuseMessageUserId = abuseMessages[0].id
expect(abuseMessages[1].message).to.equal('message 2')
expect(abuseMessages[1].byModerator).to.be.true
expect(abuseMessages[1].account.name).to.equal('root')
expect(abuseMessages[2].message).to.equal('message 3')
expect(abuseMessages[2].byModerator).to.be.true
expect(abuseMessages[2].account.name).to.equal('root')
abuseMessageModerationId = abuseMessages[2].id
expect(abuseMessages[3].message).to.equal('message 4')
expect(abuseMessages[3].byModerator).to.be.false
expect(abuseMessages[3].account.name).to.equal('user_43')
}
})
it('Should delete messages', async function () {
await deleteAbuseMessage(servers[0].url, servers[0].accessToken, abuseId, abuseMessageModerationId)
await deleteAbuseMessage(servers[0].url, userAccessToken, abuseId, abuseMessageUserId)
const results = await Promise.all([
listAbuseMessages(servers[0].url, servers[0].accessToken, abuseId),
listAbuseMessages(servers[0].url, userAccessToken, abuseId)
])
for (const res of results) {
expect(res.body.total).to.equal(2)
const abuseMessages: AbuseMessage[] = res.body.data
expect(abuseMessages[0].message).to.equal('message 2')
expect(abuseMessages[1].message).to.equal('message 4')
}
})
})
after(async function () { after(async function () {
await cleanupTests(servers) await cleanupTests(servers)
}) })

View File

@ -11,8 +11,8 @@ import {
createUser, createUser,
deleteMe, deleteMe,
flushAndRunServer, flushAndRunServer,
getAbusesList,
getAccountRatings, getAccountRatings,
getAdminAbusesList,
getBlacklistedVideosList, getBlacklistedVideosList,
getCustomConfig, getCustomConfig,
getMyUserInformation, getMyUserInformation,
@ -928,7 +928,7 @@ describe('Test users', function () {
const reason = 'my super bad reason' const reason = 'my super bad reason'
await reportAbuse({ url: server.url, token: user17AccessToken, videoId, reason }) await reportAbuse({ url: server.url, token: user17AccessToken, videoId, reason })
const res1 = await getAbusesList({ url: server.url, token: server.accessToken }) const res1 = await getAdminAbusesList({ url: server.url, token: server.accessToken })
const abuseId = res1.body.data[0].id const abuseId = res1.body.data[0].id
const res2 = await getUserInformation(server.url, server.accessToken, user17Id, true) const res2 = await getUserInformation(server.url, server.accessToken, user17Id, true)

View File

@ -2,7 +2,7 @@
import 'mocha' import 'mocha'
import * as chai from 'chai' import * as chai from 'chai'
import { Abuse, AbusePredefinedReasonsString, AbuseState } from '@shared/models' import { AbusePredefinedReasonsString, AbuseState, AdminAbuse } from '@shared/models'
import { import {
cleanupTests, cleanupTests,
createUser, createUser,
@ -33,7 +33,7 @@ const expect = chai.expect
describe('Test video abuses', function () { describe('Test video abuses', function () {
let servers: ServerInfo[] = [] let servers: ServerInfo[] = []
let abuseServer2: Abuse let abuseServer2: AdminAbuse
before(async function () { before(async function () {
this.timeout(50000) this.timeout(50000)
@ -97,7 +97,7 @@ describe('Test video abuses', function () {
expect(res1.body.data).to.be.an('array') expect(res1.body.data).to.be.an('array')
expect(res1.body.data.length).to.equal(1) expect(res1.body.data.length).to.equal(1)
const abuse: Abuse = res1.body.data[0] const abuse: AdminAbuse = res1.body.data[0]
expect(abuse.reason).to.equal('my super bad reason') expect(abuse.reason).to.equal('my super bad reason')
expect(abuse.reporterAccount.name).to.equal('root') expect(abuse.reporterAccount.name).to.equal('root')
expect(abuse.reporterAccount.host).to.equal('localhost:' + servers[0].port) expect(abuse.reporterAccount.host).to.equal('localhost:' + servers[0].port)
@ -130,7 +130,7 @@ describe('Test video abuses', function () {
expect(res1.body.data).to.be.an('array') expect(res1.body.data).to.be.an('array')
expect(res1.body.data.length).to.equal(2) expect(res1.body.data.length).to.equal(2)
const abuse1: Abuse = res1.body.data[0] const abuse1: AdminAbuse = res1.body.data[0]
expect(abuse1.reason).to.equal('my super bad reason') expect(abuse1.reason).to.equal('my super bad reason')
expect(abuse1.reporterAccount.name).to.equal('root') expect(abuse1.reporterAccount.name).to.equal('root')
expect(abuse1.reporterAccount.host).to.equal('localhost:' + servers[0].port) expect(abuse1.reporterAccount.host).to.equal('localhost:' + servers[0].port)
@ -141,7 +141,7 @@ describe('Test video abuses', function () {
expect(abuse1.video.countReports).to.equal(1) expect(abuse1.video.countReports).to.equal(1)
expect(abuse1.video.nthReport).to.equal(1) expect(abuse1.video.nthReport).to.equal(1)
const abuse2: Abuse = res1.body.data[1] const abuse2: AdminAbuse = res1.body.data[1]
expect(abuse2.reason).to.equal('my super bad reason 2') expect(abuse2.reason).to.equal('my super bad reason 2')
expect(abuse2.reporterAccount.name).to.equal('root') expect(abuse2.reporterAccount.name).to.equal('root')
expect(abuse2.reporterAccount.host).to.equal('localhost:' + servers[0].port) expect(abuse2.reporterAccount.host).to.equal('localhost:' + servers[0].port)
@ -245,7 +245,7 @@ describe('Test video abuses', function () {
expect(res.body.data.length).to.equal(2, "wrong number of videos returned") expect(res.body.data.length).to.equal(2, "wrong number of videos returned")
expect(res.body.data[0].id).to.equal(abuseServer2.id, "wrong origin server id for first video") expect(res.body.data[0].id).to.equal(abuseServer2.id, "wrong origin server id for first video")
const abuse: Abuse = res.body.data[0] const abuse: AdminAbuse = res.body.data[0]
expect(abuse.video.id).to.equal(abuseServer2.video.id, "wrong video id") expect(abuse.video.id).to.equal(abuseServer2.video.id, "wrong video id")
expect(abuse.video.channel).to.exist expect(abuse.video.channel).to.exist
expect(abuse.video.deleted).to.be.true expect(abuse.video.deleted).to.be.true
@ -279,7 +279,7 @@ describe('Test video abuses', function () {
const res2 = await getVideoAbusesList({ url: servers[0].url, token: servers[0].accessToken }) const res2 = await getVideoAbusesList({ url: servers[0].url, token: servers[0].accessToken })
{ {
for (const abuse of res2.body.data as Abuse[]) { for (const abuse of res2.body.data as AdminAbuse[]) {
if (abuse.video.id === video3.id) { if (abuse.video.id === video3.id) {
expect(abuse.video.countReports).to.equal(1, "wrong reports count for video 3") expect(abuse.video.countReports).to.equal(1, "wrong reports count for video 3")
expect(abuse.video.nthReport).to.equal(1, "wrong report position in report list for video 3") expect(abuse.video.nthReport).to.equal(1, "wrong report position in report list for video 3")
@ -311,7 +311,7 @@ describe('Test video abuses', function () {
const res = await getVideoAbusesList({ url: servers[0].url, token: servers[0].accessToken }) const res = await getVideoAbusesList({ url: servers[0].url, token: servers[0].accessToken })
{ {
const abuse = (res.body.data as Abuse[]).find(a => a.id === createdAbuse.id) const abuse = (res.body.data as AdminAbuse[]).find(a => a.id === createdAbuse.id)
expect(abuse.reason).to.equals(reason5) expect(abuse.reason).to.equals(reason5)
expect(abuse.predefinedReasons).to.deep.equals(predefinedReasons5, "predefined reasons do not match the one reported") expect(abuse.predefinedReasons).to.deep.equals(predefinedReasons5, "predefined reasons do not match the one reported")
expect(abuse.video.startAt).to.equal(1, "starting timestamp doesn't match the one reported") expect(abuse.video.startAt).to.equal(1, "starting timestamp doesn't match the one reported")
@ -350,7 +350,7 @@ describe('Test video abuses', function () {
const res = await getVideoAbusesList(options) const res = await getVideoAbusesList(options)
return res.body.data as Abuse[] return res.body.data as AdminAbuse[]
} }
expect(await list({ id: 56 })).to.have.lengthOf(0) expect(await list({ id: 56 })).to.have.lengthOf(0)

View File

@ -0,0 +1,20 @@
import { AbuseMessageModel } from '@server/models/abuse/abuse-message'
import { PickWith } from '@shared/core-utils'
import { AbuseModel } from '../../../models/abuse/abuse'
import { MAccountFormattable } from '../account'
type Use<K extends keyof AbuseMessageModel, M> = PickWith<AbuseMessageModel, K, M>
// ############################################################################
export type MAbuseMessage = Omit<AbuseMessageModel, 'Account' | 'Abuse' | 'toFormattedJSON'>
export type MAbuseMessageId = Pick<AbuseModel, 'id'>
// ############################################################################
// Format for API
export type MAbuseMessageFormattable =
MAbuseMessage &
Use<'Account', MAccountFormattable>

View File

@ -95,9 +95,15 @@ export type MAbuseFull =
// Format for API or AP object // Format for API or AP object
export type MAbuseFormattable = export type MAbuseAdminFormattable =
MAbuse & MAbuse &
Use<'ReporterAccount', MAccountFormattable> & Use<'ReporterAccount', MAccountFormattable> &
Use<'FlaggedAccount', MAccountFormattable> & Use<'FlaggedAccount', MAccountFormattable> &
Use<'VideoAbuse', MVideoAbuseFormattable> & Use<'VideoAbuse', MVideoAbuseFormattable> &
Use<'VideoCommentAbuse', MCommentAbuseFormattable> Use<'VideoCommentAbuse', MCommentAbuseFormattable>
export type MAbuseUserFormattable =
MAbuse &
Use<'FlaggedAccount', MAccountFormattable> &
Use<'VideoAbuse', MVideoAbuseFormattable> &
Use<'VideoCommentAbuse', MCommentAbuseFormattable>

View File

@ -1 +1,2 @@
export * from './abuse' export * from './abuse'
export * from './abuse-message'

View File

@ -1,6 +1,7 @@
import { RegisterServerAuthExternalOptions } from '@server/types' import { RegisterServerAuthExternalOptions } from '@server/types'
import { import {
MAbuse, MAbuse,
MAbuseMessage,
MAccountBlocklist, MAccountBlocklist,
MActorUrl, MActorUrl,
MStreamingPlaylist, MStreamingPlaylist,
@ -78,6 +79,7 @@ declare module 'express' {
videoCaption?: MVideoCaptionVideo videoCaption?: MVideoCaptionVideo
abuse?: MAbuse abuse?: MAbuse
abuseMessage?: MAbuseMessage
videoStreamingPlaylist?: MStreamingPlaylist videoStreamingPlaylist?: MStreamingPlaylist

View File

@ -54,7 +54,7 @@ function reportAbuse (options: {
}) })
} }
function getAbusesList (options: { function getAdminAbusesList (options: {
url: string url: string
token: string token: string
@ -117,6 +117,48 @@ function getAbusesList (options: {
}) })
} }
function getUserAbusesList (options: {
url: string
token: string
start?: number
count?: number
sort?: string
id?: number
search?: string
state?: AbuseState
}) {
const {
url,
token,
start,
count,
sort,
id,
search,
state
} = options
const path = '/api/v1/users/me/abuses'
const query = {
id,
search,
state,
start,
count,
sort: sort || 'createdAt'
}
return makeGetRequest({
url,
path,
token,
query,
statusCodeExpected: 200
})
}
function updateAbuse ( function updateAbuse (
url: string, url: string,
token: string, token: string,
@ -146,11 +188,49 @@ function deleteAbuse (url: string, token: string, abuseId: number, statusCodeExp
}) })
} }
function listAbuseMessages (url: string, token: string, abuseId: number, statusCodeExpected = 200) {
const path = '/api/v1/abuses/' + abuseId + '/messages'
return makeGetRequest({
url,
token,
path,
statusCodeExpected
})
}
function deleteAbuseMessage (url: string, token: string, abuseId: number, messageId: number, statusCodeExpected = 204) {
const path = '/api/v1/abuses/' + abuseId + '/messages/' + messageId
return makeDeleteRequest({
url,
token,
path,
statusCodeExpected
})
}
function addAbuseMessage (url: string, token: string, abuseId: number, message: string, statusCodeExpected = 200) {
const path = '/api/v1/abuses/' + abuseId + '/messages'
return makePostBodyRequest({
url,
token,
path,
fields: { message },
statusCodeExpected
})
}
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
export { export {
reportAbuse, reportAbuse,
getAbusesList, getAdminAbusesList,
updateAbuse, updateAbuse,
deleteAbuse deleteAbuse,
getUserAbusesList,
listAbuseMessages,
deleteAbuseMessage,
addAbuseMessage
} }

View File

@ -0,0 +1,9 @@
import { AccountSummary } from '@shared/models'
export interface AbuseMessage {
id: number
message: string
byModerator: boolean
account: AccountSummary
}

View File

@ -4,7 +4,7 @@ import { AbusePredefinedReasonsString } from './abuse-reason.model'
import { VideoConstant } from '../../videos/video-constant.model' import { VideoConstant } from '../../videos/video-constant.model'
import { VideoChannel } from '../../videos/channel/video-channel.model' import { VideoChannel } from '../../videos/channel/video-channel.model'
export interface VideoAbuse { export interface AdminVideoAbuse {
id: number id: number
name: string name: string
uuid: string uuid: string
@ -23,7 +23,7 @@ export interface VideoAbuse {
nthReport: number nthReport: number
} }
export interface VideoCommentAbuse { export interface AdminVideoCommentAbuse {
id: number id: number
threadId: number threadId: number
@ -38,7 +38,7 @@ export interface VideoCommentAbuse {
deleted: boolean deleted: boolean
} }
export interface Abuse { export interface AdminAbuse {
id: number id: number
reason: string reason: string
@ -50,8 +50,8 @@ export interface Abuse {
state: VideoConstant<AbuseState> state: VideoConstant<AbuseState>
moderationComment?: string moderationComment?: string
video?: VideoAbuse video?: AdminVideoAbuse
comment?: VideoCommentAbuse comment?: AdminVideoCommentAbuse
createdAt: Date createdAt: Date
updatedAt: Date updatedAt: Date
@ -59,6 +59,8 @@ export interface Abuse {
countReportsForReporter?: number countReportsForReporter?: number
countReportsForReportee?: number countReportsForReportee?: number
countMessages: number
// FIXME: deprecated in 2.3, remove the following properties // FIXME: deprecated in 2.3, remove the following properties
// @deprecated // @deprecated
@ -71,3 +73,10 @@ export interface Abuse {
// @deprecated // @deprecated
nth?: number nth?: number
} }
export type UserVideoAbuse = Omit<AdminVideoAbuse, 'countReports' | 'nthReport'>
export type UserVideoCommentAbuse = AdminVideoCommentAbuse
export type UserAbuse = Omit<AdminAbuse, 'reporterAccount' | 'countReportsForReportee' | 'countReportsForReporter' | 'startAt' | 'endAt'
| 'count' | 'nth'>

View File

@ -1,5 +1,6 @@
export * from './abuse-create.model' export * from './abuse-create.model'
export * from './abuse-filter.type' export * from './abuse-filter.type'
export * from './abuse-message.model'
export * from './abuse-reason.model' export * from './abuse-reason.model'
export * from './abuse-state.model' export * from './abuse-state.model'
export * from './abuse-update.model' export * from './abuse-update.model'