1
0
Fork 0

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 { createAccountAbuse, createVideoAbuse, createVideoCommentAbuse } from '@server/lib/moderation'
import { AbuseModel } from '@server/models/abuse/abuse'
import { AbuseMessageModel } from '@server/models/abuse/abuse-message'
import { getServerActor } from '@server/models/application/application'
import { AbuseCreate, abusePredefinedReasonsMap, AbuseState, UserRight } from '../../../shared'
import { getFormattedObjects } from '../../helpers/utils'
import { sequelizeTypescript } from '../../initializers/database'
import {
abuseGetValidator,
abuseListValidator,
abuseListForAdminsValidator,
abuseReportValidator,
abusesSortValidator,
abuseUpdateValidator,
addAbuseMessageValidator,
asyncMiddleware,
asyncRetryTransactionMiddleware,
authenticate,
deleteAbuseMessageValidator,
ensureUserHasRight,
getAbuseValidator,
paginationValidator,
setDefaultPagination,
setDefaultSort
@ -30,8 +34,8 @@ abuseRouter.get('/',
abusesSortValidator,
setDefaultSort,
setDefaultPagination,
abuseListValidator,
asyncMiddleware(listAbuses)
abuseListForAdminsValidator,
asyncMiddleware(listAbusesForAdmins)
)
abuseRouter.put('/:id',
authenticate,
@ -51,13 +55,33 @@ abuseRouter.delete('/:id',
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 {
abuseRouter,
// FIXME: deprecated in 2.3. Remove these exports
listAbuses,
listAbusesForAdmins,
updateAbuse,
deleteAbuse,
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 serverActor = await getServerActor()
const resultList = await AbuseModel.listForApi({
const resultList = await AbuseModel.listForAdminApi({
start: req.query.start,
count: req.query.count,
sort: req.query.sort,
@ -87,7 +111,10 @@ async function listAbuses (req: express.Request, res: express.Response) {
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) {
@ -100,6 +127,8 @@ async function updateAbuse (req: express.Request, res: express.Response) {
return abuse.save({ transaction: t })
})
// TODO: Notification
// Do not send the delete to other instances, we updated OUR copy of this abuse
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 } })
}
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 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 { 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 { generateRandomString, getFormattedObjects } from '../../../helpers/utils'
import { CONFIG } from '../../../initializers/config'
import { WEBSERVER } from '../../../initializers/constants'
import { sequelizeTypescript } from '../../../initializers/database'
import { Emailer } from '../../../lib/emailer'
import { Notifier } from '../../../lib/notifier'
import { deleteUserToken } from '../../../lib/oauth-model'
import { Redis } from '../../../lib/redis'
import { createUserAccountAndChannelAndPlaylist, sendVerifyUserEmail } from '../../../lib/user'
import {
@ -18,9 +28,9 @@ import {
setDefaultPagination,
setDefaultSort,
userAutocompleteValidator,
usersListValidator,
usersAddValidator,
usersGetValidator,
usersListValidator,
usersRegisterValidator,
usersRemoveValidator,
usersSortValidator,
@ -35,22 +45,13 @@ import {
usersVerifyEmailValidator
} from '../../../middlewares/validators'
import { UserModel } from '../../../models/account/user'
import { auditLoggerFactory, getAuditIdFromRes, UserAuditView } from '../../../helpers/audit-logger'
import { meRouter } from './me'
import { deleteUserToken } from '../../../lib/oauth-model'
import { myAbusesRouter } from './my-abuses'
import { myBlocklistRouter } from './my-blocklist'
import { myVideoPlaylistsRouter } from './my-video-playlists'
import { myVideosHistoryRouter } from './my-history'
import { myNotificationsRouter } from './my-notifications'
import { Notifier } from '../../../lib/notifier'
import { mySubscriptionsRouter } from './my-subscriptions'
import { CONFIG } from '../../../initializers/config'
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'
import { myVideoPlaylistsRouter } from './my-video-playlists'
const auditLogger = auditLoggerFactory('users')
@ -72,6 +73,7 @@ usersRouter.use('/', mySubscriptionsRouter)
usersRouter.use('/', myBlocklistRouter)
usersRouter.use('/', myVideosHistoryRouter)
usersRouter.use('/', myVideoPlaylistsRouter)
usersRouter.use('/', myAbusesRouter)
usersRouter.use('/', meRouter)
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 { getServerActor } from '@server/models/application/application'
import { AbuseCreate, UserRight, VideoAbuseCreate } from '../../../../shared'
import { getFormattedObjects } from '../../../helpers/utils'
import {
abusesSortValidator,
asyncMiddleware,
@ -63,7 +62,7 @@ async function listVideoAbuses (req: express.Request, res: express.Response) {
const user = res.locals.oauth.token.user
const serverActor = await getServerActor()
const resultList = await AbuseModel.listForApi({
const resultList = await AbuseModel.listForAdminApi({
start: req.query.start,
count: req.query.count,
sort: req.query.sort,
@ -81,7 +80,10 @@ async function listVideoAbuses (req: express.Request, res: express.Response) {
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) {

View File

@ -5,7 +5,7 @@ import { chain } from 'lodash'
import * as path from 'path'
import * as winston from 'winston'
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 { VideoComment } from '../../shared/models/videos/video-comment.model'
import { CONFIG } from '../initializers/config'
@ -219,7 +219,7 @@ const abuseKeysToKeep = [
'createdAt'
]
class AbuseAuditView extends EntityAuditView {
constructor (private readonly abuse: Abuse) {
constructor (private readonly abuse: AdminAbuse) {
super(abuseKeysToKeep, 'abuse', abuse)
}
}

View File

@ -4,6 +4,7 @@ import { ABUSE_STATES, CONSTRAINTS_FIELDS } from '../../initializers/constants'
import { exists, isArray } from './misc'
const ABUSES_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.ABUSES
const ABUSE_MESSAGES_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.ABUSE_MESSAGES
function isAbuseReasonValid (value: string) {
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 {
isAbuseReasonValid,
isAbuseFilterValid,
isAbusePredefinedReasonValid,
areAbusePredefinedReasonsValid as isAbusePredefinedReasonsValid,
isAbuseMessageValid,
areAbusePredefinedReasonsValid,
isAbuseTimestampValid,
isAbuseTimestampCoherent,
isAbuseModerationCommentValid,

View File

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

View File

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

View File

@ -5,7 +5,7 @@ import { join } from 'path'
import { VideoChannelModel } from '@server/models/video/video-channel'
import { MVideoBlacklistLightVideo, MVideoBlacklistVideo } from '@server/types/models/video/video-blacklist'
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 { isTestInstance, root } from '../helpers/core-utils'
import { bunyanLogger, logger } from '../helpers/logger'
@ -283,7 +283,7 @@ class Emailer {
}
addAbuseModeratorsNotification (to: string[], parameters: {
abuse: Abuse
abuse: UserAbuse
abuseInstance: MAbuseFull
reporter: string
}) {

View File

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

View File

@ -10,7 +10,7 @@ import {
} from '@server/types/models/user'
import { MVideoBlacklistLightVideo, MVideoBlacklistVideo } from '@server/types/models/video/video-blacklist'
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 { VideoPrivacy, VideoState } from '../../shared/models/videos'
import { logger } from '../helpers/logger'
@ -73,7 +73,7 @@ class Notifier {
.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)
.catch(err => logger.error('Cannot notify of new abuse %d.', parameters.abuseInstance.id, { err }))
}
@ -350,7 +350,7 @@ class Notifier {
}
private async notifyModeratorsOfNewAbuse (parameters: {
abuse: Abuse
abuse: UserAbuse
abuseInstance: MAbuseFull
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}.`
logger.info(message)
return res.status(403)
.json({
error: message
})
.end()
return res.status(403).json({ error: message })
}
return next()

View File

@ -2,8 +2,9 @@ import * as express from 'express'
import { body, param, query } from 'express-validator'
import {
isAbuseFilterValid,
isAbuseMessageValid,
isAbuseModerationCommentValid,
isAbusePredefinedReasonsValid,
areAbusePredefinedReasonsValid,
isAbusePredefinedReasonValid,
isAbuseReasonValid,
isAbuseStateValid,
@ -15,7 +16,8 @@ import { exists, isIdOrUUIDValid, isIdValid, toIntOrNull } from '@server/helpers
import { doesCommentIdExist } from '@server/helpers/custom-validators/video-comments'
import { logger } from '@server/helpers/logger'
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'
const abuseReportValidator = [
@ -53,7 +55,7 @@ const abuseReportValidator = [
body('predefinedReasons')
.optional()
.custom(isAbusePredefinedReasonsValid)
.custom(areAbusePredefinedReasonsValid)
.withMessage('Should have a valid list of predefined reasons'),
async (req: express.Request, res: express.Response, next: express.NextFunction) => {
@ -111,7 +113,7 @@ const abuseUpdateValidator = [
}
]
const abuseListValidator = [
const abuseListForAdminsValidator = [
query('id')
.optional()
.custom(isIdValid).withMessage('Should have a valid id'),
@ -146,7 +148,7 @@ const abuseListValidator = [
.custom(exists).withMessage('Should have a valid video channel search'),
(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
@ -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
const videoAbuseReportValidator = [
@ -167,7 +254,7 @@ const videoAbuseReportValidator = [
.withMessage('Should have a valid reason'),
body('predefinedReasons')
.optional()
.custom(isAbusePredefinedReasonsValid)
.custom(areAbusePredefinedReasonsValid)
.withMessage('Should have a valid list of predefined reasons'),
body('startAt')
.optional()
@ -266,10 +353,14 @@ const videoAbuseListValidator = [
// ---------------------------------------------------------------------------
export {
abuseListValidator,
abuseListForAdminsValidator,
abuseReportValidator,
abuseGetValidator,
addAbuseMessageValidator,
abuseUpdateValidator,
deleteAbuseMessageValidator,
abuseListForUserValidator,
getAbuseValidator,
videoAbuseReportValidator,
videoAbuseGetValidator,
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
// accountIds
serverAccountId: number
userAccountId: number
serverAccountId?: number
userAccountId?: number
reporterAccountId?: number
}
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"'
]
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) {
const searchWhereOr = [

View File

@ -18,7 +18,6 @@ import {
} from 'sequelize-typescript'
import { isAbuseModerationCommentValid, isAbuseReasonValid, isAbuseStateValid } from '@server/helpers/custom-validators/abuses'
import {
Abuse,
AbuseFilter,
AbuseObject,
AbusePredefinedReasons,
@ -26,11 +25,14 @@ import {
AbusePredefinedReasonsString,
AbuseState,
AbuseVideoIs,
VideoAbuse,
VideoCommentAbuse
AdminVideoAbuse,
AdminAbuse,
AdminVideoCommentAbuse,
UserAbuse,
UserVideoAbuse
} from '@shared/models'
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 { getSort, throwIfNotValid } from '../utils'
import { ThumbnailModel } from '../video/thumbnail'
@ -51,6 +53,16 @@ export enum ScopeNames {
return {
attributes: {
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
literal(
@ -285,7 +297,7 @@ export class AbuseModel extends Model<AbuseModel> {
return AbuseModel.findOne(query)
}
static async listForApi (parameters: {
static async listForAdminApi (parameters: {
start: number
count: number
sort: string
@ -353,71 +365,98 @@ export class AbuseModel extends Model<AbuseModel> {
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 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 {
id: this.id,
reason: this.reason,
predefinedReasons,
reporterAccount: this.ReporterAccount
? this.ReporterAccount.toFormattedJSON()
: null,
flaggedAccount: this.FlaggedAccount
? this.FlaggedAccount.toFormattedJSON()
: null,
@ -429,11 +468,41 @@ export class AbuseModel extends Model<AbuseModel> {
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,
comment,
createdAt: this.createdAt,
updatedAt: this.updatedAt,
reporterAccount: this.ReporterAccount
? this.ReporterAccount.toFormattedJSON()
: null,
countReportsForReporter: (countReportsForReporter || 0),
countReportsForReportee: (countReportsForReportee || 0),
@ -443,7 +512,20 @@ export class AbuseModel extends Model<AbuseModel> {
endAt: null,
count: countReportsForVideo || 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 {

View File

@ -13,7 +13,11 @@ import {
setAccessTokensToServers,
updateAbuse,
uploadVideo,
userLogin
userLogin,
generateUserAccessToken,
addAbuseMessage,
listAbuseMessages,
deleteAbuseMessage
} from '../../../../shared/extra-utils'
import {
checkBadCountPagination,
@ -26,7 +30,9 @@ describe('Test abuses API validators', function () {
let server: ServerInfo
let userAccessToken = ''
let userAccessToken2 = ''
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 })
userAccessToken = await userLogin(server, { username, password })
{
userAccessToken2 = await generateUserAccessToken(server, 'user_2')
}
const res = await uploadVideo(server.url, server.accessToken, {})
server.video = res.body.video
})
describe('When listing abuses', function () {
describe('When listing abuses for admins', function () {
const path = basePath
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 () {
const path = basePath
it('Should fail with nothing', async function () {
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 () {
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 () {
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 () {
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 () {
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 () {
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 () {
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 () {
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 () {
@ -165,38 +217,38 @@ describe('Test abuses API validators', function () {
it('Should fail with a reason too short', async function () {
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 () {
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 () {
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
})
it('Should fail with a wrong predefined reason', async function () {
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 () {
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 () {
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 () {
@ -210,7 +262,7 @@ describe('Test abuses API validators', function () {
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 () {
it('Should fail with a non authenticated user', async function () {

View File

@ -2,7 +2,7 @@
import 'mocha'
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 {
addVideoCommentThread,
cleanupTests,
@ -10,11 +10,15 @@ import {
deleteAbuse,
deleteVideoComment,
flushAndRunMultipleServers,
getAbusesList,
generateUserAccessToken,
getAccount,
getAdminAbusesList,
getUserAbusesList,
getVideoCommentThreads,
getVideoIdFromUUID,
getVideosList,
immutableAssign,
removeUser,
removeVideo,
reportAbuse,
ServerInfo,
@ -23,9 +27,9 @@ import {
uploadVideo,
uploadVideoAndGetId,
userLogin,
getAccount,
removeUser,
generateUserAccessToken
addAbuseMessage,
listAbuseMessages,
deleteAbuseMessage
} from '../../../../shared/extra-utils/index'
import { doubleFollow } from '../../../../shared/extra-utils/server/follows'
import { waitJobs } from '../../../../shared/extra-utils/server/jobs'
@ -40,8 +44,8 @@ const expect = chai.expect
describe('Test abuses', function () {
let servers: ServerInfo[] = []
let abuseServer1: Abuse
let abuseServer2: Abuse
let abuseServer1: AdminAbuse
let abuseServer2: AdminAbuse
before(async function () {
this.timeout(50000)
@ -87,7 +91,7 @@ describe('Test abuses', 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.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 () {
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.data).to.be.an('array')
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.reporterAccount.name).to.equal('root')
@ -131,7 +135,7 @@ describe('Test abuses', function () {
expect(abuse.countReportsForReporter).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.data).to.be.an('array')
expect(res2.body.data.length).to.equal(0)
@ -141,19 +145,20 @@ describe('Test abuses', function () {
this.timeout(10000)
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
await waitJobs(servers)
})
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.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.reporterAccount.name).to.equal('root')
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.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.reporterAccount.name).to.equal('root')
@ -188,7 +193,7 @@ describe('Test abuses', function () {
expect(abuse2.state.label).to.equal('Pending')
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.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 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)
}
@ -222,7 +227,7 @@ describe('Test abuses', function () {
{
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)
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)
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)
}
})
@ -243,7 +248,7 @@ describe('Test abuses', function () {
{
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)
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)
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)
}
})
@ -265,11 +270,11 @@ describe('Test abuses', function () {
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.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.video.id).to.equal(abuseServer2.video.id, "wrong video id")
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 })
{
const res2 = await getAbusesList({ url: servers[0].url, token: servers[0].accessToken })
const abuses = res2.body.data as Abuse[]
const res2 = await getAdminAbusesList({ url: servers[0].url, token: servers[0].accessToken })
const abuses = res2.body.data as AdminAbuse[]
const abuseVideo3 = res2.body.data.find(a => a.video.id === video3.id)
expect(abuseVideo3).to.not.be.undefined
@ -333,10 +338,10 @@ describe('Test abuses', function () {
endAt: 5
})).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.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")
@ -352,14 +357,14 @@ describe('Test abuses', function () {
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.data.length).to.equal(1)
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)
}
})
@ -367,7 +372,7 @@ describe('Test abuses', function () {
it('Should list and filter video abuses', async function () {
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 = {
url: servers[0].url,
token: servers[0].accessToken
@ -375,9 +380,9 @@ describe('Test abuses', function () {
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)
@ -446,12 +451,12 @@ describe('Test abuses', 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 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.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.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.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 () {
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.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.countReportsForReporter).to.equal(6)
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')
@ -523,7 +528,7 @@ describe('Test abuses', function () {
expect(abuse2.countReportsForReporter).to.equal(6)
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.data.length).to.equal(1)
@ -550,11 +555,11 @@ describe('Test abuses', function () {
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.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.comment.text).to.be.empty
@ -570,36 +575,46 @@ describe('Test abuses', function () {
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.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)
}
})
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)
}
{
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)
}
{
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[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[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 () {
{
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.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.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.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 () {
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.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')
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.reporterAccount.name).to.equal('root')
@ -696,7 +711,7 @@ describe('Test abuses', function () {
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.data.length).to.equal(1)
@ -721,11 +736,11 @@ describe('Test abuses', function () {
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.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
})
@ -737,13 +752,13 @@ describe('Test abuses', function () {
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.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)
abuseServer1 = res.body.data[0]
@ -757,7 +772,7 @@ describe('Test abuses', function () {
const body = { state: AbuseState.REJECTED }
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)
})
@ -765,12 +780,184 @@ describe('Test abuses', function () {
const body = { state: AbuseState.ACCEPTED, moderationComment: 'It is valid' }
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].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 () {
await cleanupTests(servers)
})

View File

@ -11,8 +11,8 @@ import {
createUser,
deleteMe,
flushAndRunServer,
getAbusesList,
getAccountRatings,
getAdminAbusesList,
getBlacklistedVideosList,
getCustomConfig,
getMyUserInformation,
@ -928,7 +928,7 @@ describe('Test users', function () {
const reason = 'my super bad 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 res2 = await getUserInformation(server.url, server.accessToken, user17Id, true)

View File

@ -2,7 +2,7 @@
import 'mocha'
import * as chai from 'chai'
import { Abuse, AbusePredefinedReasonsString, AbuseState } from '@shared/models'
import { AbusePredefinedReasonsString, AbuseState, AdminAbuse } from '@shared/models'
import {
cleanupTests,
createUser,
@ -33,7 +33,7 @@ const expect = chai.expect
describe('Test video abuses', function () {
let servers: ServerInfo[] = []
let abuseServer2: Abuse
let abuseServer2: AdminAbuse
before(async function () {
this.timeout(50000)
@ -97,7 +97,7 @@ describe('Test video abuses', function () {
expect(res1.body.data).to.be.an('array')
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.reporterAccount.name).to.equal('root')
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.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.reporterAccount.name).to.equal('root')
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.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.reporterAccount.name).to.equal('root')
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[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.channel).to.exist
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 })
{
for (const abuse of res2.body.data as Abuse[]) {
for (const abuse of res2.body.data as AdminAbuse[]) {
if (abuse.video.id === video3.id) {
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")
@ -311,7 +311,7 @@ describe('Test video abuses', function () {
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.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")
@ -350,7 +350,7 @@ describe('Test video abuses', function () {
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)

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
export type MAbuseFormattable =
export type MAbuseAdminFormattable =
MAbuse &
Use<'ReporterAccount', MAccountFormattable> &
Use<'FlaggedAccount', MAccountFormattable> &
Use<'VideoAbuse', MVideoAbuseFormattable> &
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-message'

View File

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

View File

@ -54,7 +54,7 @@ function reportAbuse (options: {
})
}
function getAbusesList (options: {
function getAdminAbusesList (options: {
url: 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 (
url: 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 {
reportAbuse,
getAbusesList,
getAdminAbusesList,
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 { VideoChannel } from '../../videos/channel/video-channel.model'
export interface VideoAbuse {
export interface AdminVideoAbuse {
id: number
name: string
uuid: string
@ -23,7 +23,7 @@ export interface VideoAbuse {
nthReport: number
}
export interface VideoCommentAbuse {
export interface AdminVideoCommentAbuse {
id: number
threadId: number
@ -38,7 +38,7 @@ export interface VideoCommentAbuse {
deleted: boolean
}
export interface Abuse {
export interface AdminAbuse {
id: number
reason: string
@ -50,8 +50,8 @@ export interface Abuse {
state: VideoConstant<AbuseState>
moderationComment?: string
video?: VideoAbuse
comment?: VideoCommentAbuse
video?: AdminVideoAbuse
comment?: AdminVideoCommentAbuse
createdAt: Date
updatedAt: Date
@ -59,6 +59,8 @@ export interface Abuse {
countReportsForReporter?: number
countReportsForReportee?: number
countMessages: number
// FIXME: deprecated in 2.3, remove the following properties
// @deprecated
@ -71,3 +73,10 @@ export interface Abuse {
// @deprecated
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-filter.type'
export * from './abuse-message.model'
export * from './abuse-reason.model'
export * from './abuse-state.model'
export * from './abuse-update.model'