Implement abuses check params

This commit is contained in:
Chocobozzz 2020-07-07 10:57:04 +02:00 committed by Chocobozzz
parent d95d155988
commit 57f6896f67
22 changed files with 665 additions and 162 deletions

View File

@ -23,7 +23,7 @@ import { AccountModel } from '../../models/account/account'
const abuseRouter = express.Router() const abuseRouter = express.Router()
abuseRouter.get('/abuse', abuseRouter.get('/',
authenticate, authenticate,
ensureUserHasRight(UserRight.MANAGE_ABUSES), ensureUserHasRight(UserRight.MANAGE_ABUSES),
paginationValidator, paginationValidator,
@ -33,18 +33,18 @@ abuseRouter.get('/abuse',
abuseListValidator, abuseListValidator,
asyncMiddleware(listAbuses) asyncMiddleware(listAbuses)
) )
abuseRouter.put('/:videoId/abuse/:id', abuseRouter.put('/:id',
authenticate, authenticate,
ensureUserHasRight(UserRight.MANAGE_ABUSES), ensureUserHasRight(UserRight.MANAGE_ABUSES),
asyncMiddleware(abuseUpdateValidator), asyncMiddleware(abuseUpdateValidator),
asyncRetryTransactionMiddleware(updateAbuse) asyncRetryTransactionMiddleware(updateAbuse)
) )
abuseRouter.post('/:videoId/abuse', abuseRouter.post('/',
authenticate, authenticate,
asyncMiddleware(abuseReportValidator), asyncMiddleware(abuseReportValidator),
asyncRetryTransactionMiddleware(reportAbuse) asyncRetryTransactionMiddleware(reportAbuse)
) )
abuseRouter.delete('/:videoId/abuse/:id', abuseRouter.delete('/:id',
authenticate, authenticate,
ensureUserHasRight(UserRight.MANAGE_ABUSES), ensureUserHasRight(UserRight.MANAGE_ABUSES),
asyncMiddleware(abuseGetValidator), asyncMiddleware(abuseGetValidator),
@ -74,7 +74,7 @@ async function listAbuses (req: express.Request, res: express.Response) {
count: req.query.count, count: req.query.count,
sort: req.query.sort, sort: req.query.sort,
id: req.query.id, id: req.query.id,
filter: 'video', filter: req.query.filter,
predefinedReason: req.query.predefinedReason, predefinedReason: req.query.predefinedReason,
search: req.query.search, search: req.query.search,
state: req.query.state, state: req.query.state,

View File

@ -1,6 +1,6 @@
import validator from 'validator' import validator from 'validator'
import { abusePredefinedReasonsMap, AbusePredefinedReasonsString, AbuseVideoIs } from '@shared/models' import { AbuseFilter, abusePredefinedReasonsMap, AbusePredefinedReasonsString, AbuseVideoIs, AbuseCreate } from '@shared/models'
import { CONSTRAINTS_FIELDS, ABUSE_STATES } from '../../initializers/constants' import { ABUSE_STATES, CONSTRAINTS_FIELDS } from '../../initializers/constants'
import { exists, isArray } from './misc' import { exists, isArray } from './misc'
const VIDEO_ABUSES_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.ABUSES const VIDEO_ABUSES_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.ABUSES
@ -13,7 +13,11 @@ function isAbusePredefinedReasonValid (value: AbusePredefinedReasonsString) {
return exists(value) && value in abusePredefinedReasonsMap return exists(value) && value in abusePredefinedReasonsMap
} }
function isAbusePredefinedReasonsValid (value: AbusePredefinedReasonsString[]) { function isAbuseFilterValid (value: AbuseFilter) {
return value === 'video' || value === 'comment' || value === 'account'
}
function areAbusePredefinedReasonsValid (value: AbusePredefinedReasonsString[]) {
return exists(value) && isArray(value) && value.every(v => v in abusePredefinedReasonsMap) return exists(value) && isArray(value) && value.every(v => v in abusePredefinedReasonsMap)
} }
@ -22,7 +26,9 @@ function isAbuseTimestampValid (value: number) {
} }
function isAbuseTimestampCoherent (endAt: number, { req }) { function isAbuseTimestampCoherent (endAt: number, { req }) {
return exists(req.body.startAt) && endAt > req.body.startAt const startAt = (req.body as AbuseCreate).video.startAt
return exists(startAt) && endAt > startAt
} }
function isAbuseModerationCommentValid (value: string) { function isAbuseModerationCommentValid (value: string) {
@ -44,8 +50,9 @@ function isAbuseVideoIsValid (value: AbuseVideoIs) {
export { export {
isAbuseReasonValid, isAbuseReasonValid,
isAbuseFilterValid,
isAbusePredefinedReasonValid, isAbusePredefinedReasonValid,
isAbusePredefinedReasonsValid, areAbusePredefinedReasonsValid as isAbusePredefinedReasonsValid,
isAbuseTimestampValid, isAbuseTimestampValid,
isAbuseTimestampCoherent, isAbuseTimestampCoherent,
isAbuseModerationCommentValid, isAbuseModerationCommentValid,

View File

@ -1,6 +1,8 @@
import 'multer' import * as express from 'express'
import validator from 'validator' import validator from 'validator'
import { VideoCommentModel } from '@server/models/video/video-comment'
import { CONSTRAINTS_FIELDS } from '../../initializers/constants' import { CONSTRAINTS_FIELDS } from '../../initializers/constants'
import { MVideoId } from '@server/types/models'
const VIDEO_COMMENTS_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.VIDEO_COMMENTS const VIDEO_COMMENTS_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.VIDEO_COMMENTS
@ -8,8 +10,83 @@ function isValidVideoCommentText (value: string) {
return value === null || validator.isLength(value, VIDEO_COMMENTS_CONSTRAINTS_FIELDS.TEXT) return value === null || validator.isLength(value, VIDEO_COMMENTS_CONSTRAINTS_FIELDS.TEXT)
} }
async function doesVideoCommentThreadExist (idArg: number | string, video: MVideoId, res: express.Response) {
const id = parseInt(idArg + '', 10)
const videoComment = await VideoCommentModel.loadById(id)
if (!videoComment) {
res.status(404)
.json({ error: 'Video comment thread not found' })
.end()
return false
}
if (videoComment.videoId !== video.id) {
res.status(400)
.json({ error: 'Video comment is not associated to this video.' })
.end()
return false
}
if (videoComment.inReplyToCommentId !== null) {
res.status(400)
.json({ error: 'Video comment is not a thread.' })
.end()
return false
}
res.locals.videoCommentThread = videoComment
return true
}
async function doesVideoCommentExist (idArg: number | string, video: MVideoId, res: express.Response) {
const id = parseInt(idArg + '', 10)
const videoComment = await VideoCommentModel.loadByIdAndPopulateVideoAndAccountAndReply(id)
if (!videoComment) {
res.status(404)
.json({ error: 'Video comment thread not found' })
.end()
return false
}
if (videoComment.videoId !== video.id) {
res.status(400)
.json({ error: 'Video comment is not associated to this video.' })
.end()
return false
}
res.locals.videoCommentFull = videoComment
return true
}
async function doesCommentIdExist (idArg: number | string, res: express.Response) {
const id = parseInt(idArg + '', 10)
const videoComment = await VideoCommentModel.loadById(id)
if (!videoComment) {
res.status(404)
.json({ error: 'Video comment thread not found' })
return false
}
res.locals.videoComment = videoComment
return true
}
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
export { export {
isValidVideoCommentText isValidVideoCommentText,
doesVideoCommentThreadExist,
doesVideoCommentExist,
doesCommentIdExist
} }

View File

@ -17,7 +17,6 @@ async function doesVideoAbuseExist (abuseIdArg: number | string, videoUUID: stri
if (abuse === null) { if (abuse === null) {
res.status(404) res.status(404)
.json({ error: 'Video abuse not found' }) .json({ error: 'Video abuse not found' })
.end()
return false return false
} }
@ -26,8 +25,18 @@ async function doesVideoAbuseExist (abuseIdArg: number | string, videoUUID: stri
return true return true
} }
async function doesAbuseExist (abuseIdArg: number | string, videoUUID: string, res: Response) { async function doesAbuseExist (abuseId: number | string, res: Response) {
const abuse = await AbuseModel.loadById(parseInt(abuseId + '', 10))
if (!abuse) {
res.status(404)
.json({ error: 'Video abuse not found' })
return false
}
res.locals.abuse = abuse
return true
} }
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------

View File

@ -3,8 +3,8 @@ import { AccountModel } from '../../models/account/account'
import * as Bluebird from 'bluebird' import * as Bluebird from 'bluebird'
import { MAccountDefault } from '../../types/models' import { MAccountDefault } from '../../types/models'
function doesAccountIdExist (id: number, res: Response, sendNotFound = true) { function doesAccountIdExist (id: number | string, res: Response, sendNotFound = true) {
const promise = AccountModel.load(id) const promise = AccountModel.load(parseInt(id + '', 10))
return doesAccountExist(promise, res, sendNotFound) return doesAccountExist(promise, res, sendNotFound)
} }

View File

@ -1,6 +1,7 @@
import * as express from 'express' import * as express from 'express'
import { body, param, query } from 'express-validator' import { body, param, query } from 'express-validator'
import { import {
isAbuseFilterValid,
isAbuseModerationCommentValid, isAbuseModerationCommentValid,
isAbusePredefinedReasonsValid, isAbusePredefinedReasonsValid,
isAbusePredefinedReasonValid, isAbusePredefinedReasonValid,
@ -11,29 +12,28 @@ import {
isAbuseVideoIsValid isAbuseVideoIsValid
} from '@server/helpers/custom-validators/abuses' } from '@server/helpers/custom-validators/abuses'
import { exists, isIdOrUUIDValid, isIdValid, toIntOrNull } from '@server/helpers/custom-validators/misc' import { exists, isIdOrUUIDValid, isIdValid, toIntOrNull } from '@server/helpers/custom-validators/misc'
import { doesCommentIdExist } from '@server/helpers/custom-validators/video-comments'
import { logger } from '@server/helpers/logger' import { logger } from '@server/helpers/logger'
import { doesAbuseExist, doesVideoAbuseExist, doesVideoExist } from '@server/helpers/middlewares' import { doesAbuseExist, doesAccountIdExist, doesVideoAbuseExist, doesVideoExist } from '@server/helpers/middlewares'
import { AbuseCreate } from '@shared/models'
import { areValidationErrors } from './utils' import { areValidationErrors } from './utils'
const abuseReportValidator = [ const abuseReportValidator = [
param('videoId') body('account.id')
.custom(isIdOrUUIDValid)
.not()
.isEmpty()
.withMessage('Should have a valid videoId'),
body('reason')
.custom(isAbuseReasonValid)
.withMessage('Should have a valid reason'),
body('predefinedReasons')
.optional() .optional()
.custom(isAbusePredefinedReasonsValid) .custom(isIdValid)
.withMessage('Should have a valid list of predefined reasons'), .withMessage('Should have a valid accountId'),
body('startAt')
body('video.id')
.optional()
.custom(isIdOrUUIDValid)
.withMessage('Should have a valid videoId'),
body('video.startAt')
.optional() .optional()
.customSanitizer(toIntOrNull) .customSanitizer(toIntOrNull)
.custom(isAbuseTimestampValid) .custom(isAbuseTimestampValid)
.withMessage('Should have valid starting time value'), .withMessage('Should have valid starting time value'),
body('endAt') body('video.endAt')
.optional() .optional()
.customSanitizer(toIntOrNull) .customSanitizer(toIntOrNull)
.custom(isAbuseTimestampValid) .custom(isAbuseTimestampValid)
@ -42,47 +42,70 @@ const abuseReportValidator = [
.custom(isAbuseTimestampCoherent) .custom(isAbuseTimestampCoherent)
.withMessage('Should have a startAt timestamp beginning before endAt'), .withMessage('Should have a startAt timestamp beginning before endAt'),
body('comment.id')
.optional()
.custom(isIdValid)
.withMessage('Should have a valid commentId'),
body('reason')
.custom(isAbuseReasonValid)
.withMessage('Should have a valid reason'),
body('predefinedReasons')
.optional()
.custom(isAbusePredefinedReasonsValid)
.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) => {
logger.debug('Checking abuseReport parameters', { parameters: req.body }) logger.debug('Checking abuseReport parameters', { parameters: req.body })
if (areValidationErrors(req, res)) return if (areValidationErrors(req, res)) return
if (!await doesVideoExist(req.params.videoId, res)) return
// TODO: check comment or video (exlusive) const body: AbuseCreate = req.body
if (body.video?.id && !await doesVideoExist(body.video.id, res)) return
if (body.account?.id && !await doesAccountIdExist(body.account.id, res)) return
if (body.comment?.id && !await doesCommentIdExist(body.comment.id, res)) return
if (!body.video?.id && !body.account?.id && !body.comment?.id) {
res.status(400)
.json({ error: 'video id or account id or comment id is required.' })
return
}
return next() return next()
} }
] ]
const abuseGetValidator = [ const abuseGetValidator = [
param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'),
param('id').custom(isIdValid).not().isEmpty().withMessage('Should have a valid id'), param('id').custom(isIdValid).not().isEmpty().withMessage('Should have a valid id'),
async (req: express.Request, res: express.Response, next: express.NextFunction) => { async (req: express.Request, res: express.Response, next: express.NextFunction) => {
logger.debug('Checking abuseGetValidator parameters', { parameters: req.body }) logger.debug('Checking abuseGetValidator parameters', { parameters: req.body })
if (areValidationErrors(req, res)) return if (areValidationErrors(req, res)) return
// if (!await doesAbuseExist(req.params.id, req.params.videoId, res)) return if (!await doesAbuseExist(req.params.id, res)) return
return next() return next()
} }
] ]
const abuseUpdateValidator = [ const abuseUpdateValidator = [
param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'),
param('id').custom(isIdValid).not().isEmpty().withMessage('Should have a valid id'), param('id').custom(isIdValid).not().isEmpty().withMessage('Should have a valid id'),
body('state') body('state')
.optional() .optional()
.custom(isAbuseStateValid).withMessage('Should have a valid video abuse state'), .custom(isAbuseStateValid).withMessage('Should have a valid abuse state'),
body('moderationComment') body('moderationComment')
.optional() .optional()
.custom(isAbuseModerationCommentValid).withMessage('Should have a valid video moderation comment'), .custom(isAbuseModerationCommentValid).withMessage('Should have a valid moderation comment'),
async (req: express.Request, res: express.Response, next: express.NextFunction) => { async (req: express.Request, res: express.Response, next: express.NextFunction) => {
logger.debug('Checking abuseUpdateValidator parameters', { parameters: req.body }) logger.debug('Checking abuseUpdateValidator parameters', { parameters: req.body })
if (areValidationErrors(req, res)) return if (areValidationErrors(req, res)) return
// if (!await doesAbuseExist(req.params.id, req.params.videoId, res)) return if (!await doesAbuseExist(req.params.id, res)) return
return next() return next()
} }
@ -92,6 +115,10 @@ const abuseListValidator = [
query('id') query('id')
.optional() .optional()
.custom(isIdValid).withMessage('Should have a valid id'), .custom(isIdValid).withMessage('Should have a valid id'),
query('filter')
.optional()
.custom(isAbuseFilterValid)
.withMessage('Should have a valid filter'),
query('predefinedReason') query('predefinedReason')
.optional() .optional()
.custom(isAbusePredefinedReasonValid) .custom(isAbusePredefinedReasonValid)
@ -151,10 +178,7 @@ const videoAbuseReportValidator = [
.optional() .optional()
.customSanitizer(toIntOrNull) .customSanitizer(toIntOrNull)
.custom(isAbuseTimestampValid) .custom(isAbuseTimestampValid)
.withMessage('Should have valid ending time value') .withMessage('Should have valid ending time value'),
.bail()
.custom(isAbuseTimestampCoherent)
.withMessage('Should have a startAt timestamp beginning before endAt'),
async (req: express.Request, res: express.Response, next: express.NextFunction) => { async (req: express.Request, res: express.Response, next: express.NextFunction) => {
logger.debug('Checking videoAbuseReport parameters', { parameters: req.body }) logger.debug('Checking videoAbuseReport parameters', { parameters: req.body })

View File

@ -3,13 +3,16 @@ import { body, param } from 'express-validator'
import { MUserAccountUrl } from '@server/types/models' import { MUserAccountUrl } from '@server/types/models'
import { UserRight } from '../../../../shared' import { UserRight } from '../../../../shared'
import { isIdOrUUIDValid, isIdValid } from '../../../helpers/custom-validators/misc' import { isIdOrUUIDValid, isIdValid } from '../../../helpers/custom-validators/misc'
import { isValidVideoCommentText } from '../../../helpers/custom-validators/video-comments' import {
doesVideoCommentExist,
doesVideoCommentThreadExist,
isValidVideoCommentText
} from '../../../helpers/custom-validators/video-comments'
import { logger } from '../../../helpers/logger' import { logger } from '../../../helpers/logger'
import { doesVideoExist } from '../../../helpers/middlewares' import { doesVideoExist } from '../../../helpers/middlewares'
import { AcceptResult, isLocalVideoCommentReplyAccepted, isLocalVideoThreadAccepted } from '../../../lib/moderation' import { AcceptResult, isLocalVideoCommentReplyAccepted, isLocalVideoThreadAccepted } from '../../../lib/moderation'
import { Hooks } from '../../../lib/plugins/hooks' import { Hooks } from '../../../lib/plugins/hooks'
import { VideoCommentModel } from '../../../models/video/video-comment' import { MCommentOwnerVideoReply, MVideo, MVideoFullLight } from '../../../types/models/video'
import { MCommentOwnerVideoReply, MVideo, MVideoFullLight, MVideoId } from '../../../types/models/video'
import { areValidationErrors } from '../utils' import { areValidationErrors } from '../utils'
const listVideoCommentThreadsValidator = [ const listVideoCommentThreadsValidator = [
@ -120,67 +123,10 @@ export {
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
async function doesVideoCommentThreadExist (idArg: number | string, video: MVideoId, res: express.Response) {
const id = parseInt(idArg + '', 10)
const videoComment = await VideoCommentModel.loadById(id)
if (!videoComment) {
res.status(404)
.json({ error: 'Video comment thread not found' })
.end()
return false
}
if (videoComment.videoId !== video.id) {
res.status(400)
.json({ error: 'Video comment is not associated to this video.' })
.end()
return false
}
if (videoComment.inReplyToCommentId !== null) {
res.status(400)
.json({ error: 'Video comment is not a thread.' })
.end()
return false
}
res.locals.videoCommentThread = videoComment
return true
}
async function doesVideoCommentExist (idArg: number | string, video: MVideoId, res: express.Response) {
const id = parseInt(idArg + '', 10)
const videoComment = await VideoCommentModel.loadByIdAndPopulateVideoAndAccountAndReply(id)
if (!videoComment) {
res.status(404)
.json({ error: 'Video comment thread not found' })
.end()
return false
}
if (videoComment.videoId !== video.id) {
res.status(400)
.json({ error: 'Video comment is not associated to this video.' })
.end()
return false
}
res.locals.videoCommentFull = videoComment
return true
}
function isVideoCommentsEnabled (video: MVideo, res: express.Response) { function isVideoCommentsEnabled (video: MVideo, res: express.Response) {
if (video.commentsEnabled !== true) { if (video.commentsEnabled !== true) {
res.status(409) res.status(409)
.json({ error: 'Video comments are disabled for this video.' }) .json({ error: 'Video comments are disabled for this video.' })
.end()
return false return false
} }
@ -192,7 +138,7 @@ function checkUserCanDeleteVideoComment (user: MUserAccountUrl, videoComment: MC
if (videoComment.isDeleted()) { if (videoComment.isDeleted()) {
res.status(409) res.status(409)
.json({ error: 'This comment is already deleted' }) .json({ error: 'This comment is already deleted' })
.end()
return false return false
} }

View File

@ -19,16 +19,17 @@ import {
import { isAbuseModerationCommentValid, isAbuseReasonValid, isAbuseStateValid } from '@server/helpers/custom-validators/abuses' import { isAbuseModerationCommentValid, isAbuseReasonValid, isAbuseStateValid } from '@server/helpers/custom-validators/abuses'
import { import {
Abuse, Abuse,
AbuseFilter,
AbuseObject, AbuseObject,
AbusePredefinedReasons, AbusePredefinedReasons,
abusePredefinedReasonsMap, abusePredefinedReasonsMap,
AbusePredefinedReasonsString, AbusePredefinedReasonsString,
AbuseState, AbuseState,
AbuseVideoIs, AbuseVideoIs,
VideoAbuse VideoAbuse,
VideoCommentAbuse
} from '@shared/models' } from '@shared/models'
import { AbuseFilter } from '@shared/models/moderation/abuse/abuse-filter' import { ABUSE_STATES, CONSTRAINTS_FIELDS } from '../../initializers/constants'
import { CONSTRAINTS_FIELDS, ABUSE_STATES } from '../../initializers/constants'
import { MAbuse, MAbuseAP, MAbuseFormattable, MUserAccountId } from '../../types/models' import { MAbuse, MAbuseAP, MAbuseFormattable, MUserAccountId } from '../../types/models'
import { AccountModel, ScopeNames as AccountScopeNames } from '../account/account' import { AccountModel, ScopeNames as AccountScopeNames } from '../account/account'
import { buildBlockedAccountSQL, getSort, searchAttribute, throwIfNotValid } from '../utils' import { buildBlockedAccountSQL, getSort, searchAttribute, throwIfNotValid } from '../utils'
@ -38,6 +39,7 @@ import { VideoBlacklistModel } from '../video/video-blacklist'
import { ScopeNames as VideoChannelScopeNames, SummaryOptions, VideoChannelModel } from '../video/video-channel' import { ScopeNames as VideoChannelScopeNames, SummaryOptions, VideoChannelModel } from '../video/video-channel'
import { VideoAbuseModel } from './video-abuse' import { VideoAbuseModel } from './video-abuse'
import { VideoCommentAbuseModel } from './video-comment-abuse' import { VideoCommentAbuseModel } from './video-comment-abuse'
import { VideoCommentModel } from '../video/video-comment'
export enum ScopeNames { export enum ScopeNames {
FOR_API = 'FOR_API' FOR_API = 'FOR_API'
@ -66,19 +68,18 @@ export enum ScopeNames {
serverAccountId: number serverAccountId: number
userAccountId: number userAccountId: number
}) => { }) => {
const onlyBlacklisted = options.videoIs === 'blacklisted' const whereAnd: WhereOptions[] = []
const videoRequired = !!(onlyBlacklisted || options.searchVideo || options.searchVideoChannel)
const where = { whereAnd.push({
reporterAccountId: { reporterAccountId: {
[Op.notIn]: literal('(' + buildBlockedAccountSQL([ options.serverAccountId, options.userAccountId ]) + ')') [Op.notIn]: literal('(' + buildBlockedAccountSQL([ options.serverAccountId, options.userAccountId ]) + ')')
} }
} })
if (options.search) { if (options.search) {
const escapedSearch = AbuseModel.sequelize.escape('%' + options.search + '%') const escapedSearch = AbuseModel.sequelize.escape('%' + options.search + '%')
Object.assign(where, { whereAnd.push({
[Op.or]: [ [Op.or]: [
{ {
[Op.and]: [ [Op.and]: [
@ -110,11 +111,11 @@ export enum ScopeNames {
}) })
} }
if (options.id) Object.assign(where, { id: options.id }) if (options.id) whereAnd.push({ id: options.id })
if (options.state) Object.assign(where, { state: options.state }) if (options.state) whereAnd.push({ state: options.state })
if (options.videoIs === 'deleted') { if (options.videoIs === 'deleted') {
Object.assign(where, { whereAnd.push({
'$VideoAbuse.deletedVideo$': { '$VideoAbuse.deletedVideo$': {
[Op.not]: null [Op.not]: null
} }
@ -122,13 +123,23 @@ export enum ScopeNames {
} }
if (options.predefinedReasonId) { if (options.predefinedReasonId) {
Object.assign(where, { whereAnd.push({
predefinedReasons: { predefinedReasons: {
[Op.contains]: [ options.predefinedReasonId ] [Op.contains]: [ options.predefinedReasonId ]
} }
}) })
} }
if (options.filter === 'account') {
whereAnd.push({
videoId: null,
commentId: null
})
}
const onlyBlacklisted = options.videoIs === 'blacklisted'
const videoRequired = !!(onlyBlacklisted || options.searchVideo || options.searchVideoChannel)
return { return {
attributes: { attributes: {
include: [ include: [
@ -222,6 +233,23 @@ export enum ScopeNames {
required: true, required: true,
where: searchAttribute(options.searchReportee, 'name') where: searchAttribute(options.searchReportee, 'name')
}, },
{
model: VideoCommentAbuseModel.unscoped(),
required: options.filter === 'comment',
include: [
{
model: VideoCommentModel.unscoped(),
required: false,
include: [
{
model: VideoModel.unscoped(),
attributes: [ 'name', 'id', 'uuid' ],
required: true
}
]
}
]
},
{ {
model: VideoAbuseModel, model: VideoAbuseModel,
required: options.filter === 'video' || !!options.videoIs || videoRequired, required: options.filter === 'video' || !!options.videoIs || videoRequired,
@ -241,8 +269,7 @@ export enum ScopeNames {
include: [ include: [
{ {
model: AccountModel.scope(AccountScopeNames.SUMMARY), model: AccountModel.scope(AccountScopeNames.SUMMARY),
required: true, required: true
where: searchAttribute(options.searchReportee, 'name')
} }
] ]
}, },
@ -256,7 +283,9 @@ export enum ScopeNames {
] ]
} }
], ],
where where: {
[Op.and]: whereAnd
}
} }
} }
})) }))
@ -348,6 +377,7 @@ export class AbuseModel extends Model<AbuseModel> {
}) })
VideoAbuse: VideoAbuseModel VideoAbuse: VideoAbuseModel
// FIXME: deprecated in 2.3. Remove these validators
static loadByIdAndVideoId (id: number, videoId?: number, uuid?: string): Bluebird<MAbuse> { static loadByIdAndVideoId (id: number, videoId?: number, uuid?: string): Bluebird<MAbuse> {
const videoWhere: WhereOptions = {} const videoWhere: WhereOptions = {}
@ -369,6 +399,16 @@ export class AbuseModel extends Model<AbuseModel> {
return AbuseModel.findOne(query) return AbuseModel.findOne(query)
} }
static loadById (id: number): Bluebird<MAbuse> {
const query = {
where: {
id
}
}
return AbuseModel.findOne(query)
}
static listForApi (parameters: { static listForApi (parameters: {
start: number start: number
count: number count: number
@ -454,6 +494,7 @@ export class AbuseModel extends Model<AbuseModel> {
const countReportsForReporteeDeletedVideo = this.get('countReportsForReportee__deletedVideo') as number const countReportsForReporteeDeletedVideo = this.get('countReportsForReportee__deletedVideo') as number
let video: VideoAbuse let video: VideoAbuse
let comment: VideoCommentAbuse
if (this.VideoAbuse) { if (this.VideoAbuse) {
const abuseModel = this.VideoAbuse const abuseModel = this.VideoAbuse
@ -475,6 +516,24 @@ export class AbuseModel extends Model<AbuseModel> {
} }
} }
if (this.VideoCommentAbuse) {
const abuseModel = this.VideoCommentAbuse
const entity = abuseModel.VideoComment || abuseModel.deletedComment
comment = {
id: entity.id,
text: entity.text,
deleted: !abuseModel.VideoComment,
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,
@ -490,7 +549,7 @@ export class AbuseModel extends Model<AbuseModel> {
moderationComment: this.moderationComment, moderationComment: this.moderationComment,
video, video,
comment: null, comment,
createdAt: this.createdAt, createdAt: this.createdAt,
updatedAt: this.updatedAt, updatedAt: this.updatedAt,

View File

@ -25,7 +25,7 @@ export class VideoCommentAbuseModel extends Model<VideoCommentAbuseModel> {
@AllowNull(true) @AllowNull(true)
@Default(null) @Default(null)
@Column(DataType.JSONB) @Column(DataType.JSONB)
deletedComment: VideoComment deletedComment: VideoComment & { Video: { name: string, id: number, uuid: string }}
@ForeignKey(() => AbuseModel) @ForeignKey(() => AbuseModel)
@Column @Column

View File

@ -1,7 +1,22 @@
import * as Bluebird from 'bluebird' import * as Bluebird from 'bluebird'
import { uniq } from 'lodash' import { uniq } from 'lodash'
import { FindOptions, Op, Order, ScopeOptions, Sequelize, Transaction } from 'sequelize' import { FindOptions, Op, Order, ScopeOptions, Sequelize, Transaction } from 'sequelize'
import { AllowNull, BelongsTo, Column, CreatedAt, DataType, ForeignKey, Is, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript' import {
AllowNull,
BeforeDestroy,
BelongsTo,
Column,
CreatedAt,
DataType,
ForeignKey,
HasMany,
Is,
Model,
Scopes,
Table,
UpdatedAt
} from 'sequelize-typescript'
import { logger } from '@server/helpers/logger'
import { getServerActor } from '@server/models/application/application' import { getServerActor } from '@server/models/application/application'
import { MAccount, MAccountId, MUserAccountId } from '@server/types/models' import { MAccount, MAccountId, MUserAccountId } from '@server/types/models'
import { VideoPrivacy } from '@shared/models' import { VideoPrivacy } from '@shared/models'
@ -24,6 +39,7 @@ import {
MCommentOwnerVideoReply, MCommentOwnerVideoReply,
MVideoImmutable MVideoImmutable
} from '../../types/models/video' } from '../../types/models/video'
import { VideoCommentAbuseModel } from '../abuse/video-comment-abuse'
import { AccountModel } from '../account/account' import { AccountModel } from '../account/account'
import { ActorModel, unusedActorAttributesForAPI } from '../activitypub/actor' import { ActorModel, unusedActorAttributesForAPI } from '../activitypub/actor'
import { buildBlockedAccountSQL, buildLocalAccountIdsIn, getCommentSort, throwIfNotValid } from '../utils' import { buildBlockedAccountSQL, buildLocalAccountIdsIn, getCommentSort, throwIfNotValid } from '../utils'
@ -224,6 +240,53 @@ export class VideoCommentModel extends Model<VideoCommentModel> {
}) })
Account: AccountModel Account: AccountModel
@HasMany(() => VideoCommentAbuseModel, {
foreignKey: {
name: 'commentId',
allowNull: true
},
onDelete: 'set null'
})
CommentAbuses: VideoCommentAbuseModel[]
@BeforeDestroy
static async saveEssentialDataToAbuses (instance: VideoCommentModel, options) {
const tasks: Promise<any>[] = []
if (!Array.isArray(instance.CommentAbuses)) {
instance.CommentAbuses = await instance.$get('CommentAbuses')
if (instance.CommentAbuses.length === 0) return undefined
}
if (!instance.Video) {
instance.Video = await instance.$get('Video')
}
logger.info('Saving video comment %s for abuse.', instance.url)
const details = Object.assign(instance.toFormattedJSON(), {
Video: {
id: instance.Video.id,
name: instance.Video.name,
uuid: instance.Video.uuid
}
})
for (const abuse of instance.CommentAbuses) {
abuse.deletedComment = details
tasks.push(abuse.save({ transaction: options.transaction }))
}
Promise.all(tasks)
.catch(err => {
logger.error('Some errors when saving details of comment %s in its abuses before destroy hook.', instance.url, { err })
})
return undefined
}
static loadById (id: number, t?: Transaction): Bluebird<MComment> { static loadById (id: number, t?: Transaction): Bluebird<MComment> {
const query: FindOptions = { const query: FindOptions = {
where: { where: {

View File

@ -803,14 +803,14 @@ export class VideoModel extends Model<VideoModel> {
static async saveEssentialDataToAbuses (instance: VideoModel, options) { static async saveEssentialDataToAbuses (instance: VideoModel, options) {
const tasks: Promise<any>[] = [] const tasks: Promise<any>[] = []
logger.info('Saving video abuses details of video %s.', instance.url)
if (!Array.isArray(instance.VideoAbuses)) { if (!Array.isArray(instance.VideoAbuses)) {
instance.VideoAbuses = await instance.$get('VideoAbuses') instance.VideoAbuses = await instance.$get('VideoAbuses')
if (instance.VideoAbuses.length === 0) return undefined if (instance.VideoAbuses.length === 0) return undefined
} }
logger.info('Saving video abuses details of video %s.', instance.url)
const details = instance.toFormattedDetailsJSON() const details = instance.toFormattedDetailsJSON()
for (const abuse of instance.VideoAbuses) { for (const abuse of instance.VideoAbuses) {

View File

@ -0,0 +1,271 @@
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
import 'mocha'
import { AbuseCreate, AbuseState } from '@shared/models'
import {
cleanupTests,
createUser,
deleteAbuse,
flushAndRunServer,
makeGetRequest,
makePostBodyRequest,
ServerInfo,
setAccessTokensToServers,
updateAbuse,
uploadVideo,
userLogin
} from '../../../../shared/extra-utils'
import {
checkBadCountPagination,
checkBadSortPagination,
checkBadStartPagination
} from '../../../../shared/extra-utils/requests/check-api-params'
// FIXME: deprecated in 2.3. Remove this controller
describe('Test video abuses API validators', function () {
const basePath = '/api/v1/abuses/'
let server: ServerInfo
let userAccessToken = ''
let abuseId: number
// ---------------------------------------------------------------
before(async function () {
this.timeout(30000)
server = await flushAndRunServer(1)
await setAccessTokensToServers([ server ])
const username = 'user1'
const password = 'my super password'
await createUser({ url: server.url, accessToken: server.accessToken, username: username, password: password })
userAccessToken = await userLogin(server, { username, password })
const res = await uploadVideo(server.url, server.accessToken, {})
server.video = res.body.video
})
describe('When listing abuses', function () {
const path = basePath
it('Should fail with a bad start pagination', async function () {
await checkBadStartPagination(server.url, path, server.accessToken)
})
it('Should fail with a bad count pagination', async function () {
await checkBadCountPagination(server.url, path, server.accessToken)
})
it('Should fail with an incorrect sort', async function () {
await checkBadSortPagination(server.url, path, server.accessToken)
})
it('Should fail with a non authenticated user', async function () {
await makeGetRequest({
url: server.url,
path,
statusCodeExpected: 401
})
})
it('Should fail with a non admin user', async function () {
await makeGetRequest({
url: server.url,
path,
token: userAccessToken,
statusCodeExpected: 403
})
})
it('Should fail with a bad id filter', async function () {
await makeGetRequest({ url: server.url, path, token: server.accessToken, query: { id: 'toto' } })
})
it('Should fail with a bad filter', async function () {
await makeGetRequest({ url: server.url, path, token: server.accessToken, query: { filter: 'toto' } })
await makeGetRequest({ url: server.url, path, token: server.accessToken, query: { filter: 'videos' } })
})
it('Should fail with bad predefined reason', async function () {
await makeGetRequest({ url: server.url, path, token: server.accessToken, query: { predefinedReason: 'violentOrRepulsives' } })
})
it('Should fail with a bad state filter', async function () {
await makeGetRequest({ url: server.url, path, token: server.accessToken, query: { state: 'toto' } })
await makeGetRequest({ url: server.url, path, token: server.accessToken, query: { state: 0 } })
})
it('Should fail with a bad videoIs filter', async function () {
await makeGetRequest({ url: server.url, path, token: server.accessToken, query: { videoIs: 'toto' } })
})
it('Should succeed with the correct params', async function () {
const query = {
id: 13,
predefinedReason: 'violentOrRepulsive',
filter: 'comment',
state: 2,
videoIs: 'deleted'
}
await makeGetRequest({ url: server.url, path, token: server.accessToken, 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 })
})
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 })
})
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 })
})
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 })
})
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 })
})
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 })
})
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 })
})
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 })
})
it('Should fail with a non authenticated user', async function () {
const fields = { video: { id: server.video.id }, reason: 'my super reason' }
await makePostBodyRequest({ url: server.url, path, token: 'hello', fields, statusCodeExpected: 401 })
})
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 })
})
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 })
})
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 })
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 })
})
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 })
})
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 })
})
it('Should succeed with the corret parameters (advanced)', async function () {
const fields: AbuseCreate = {
video: {
id: server.video.id,
startAt: 1,
endAt: 5
},
reason: 'my super reason',
predefinedReasons: [ 'serverRules' ]
}
await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields, statusCodeExpected: 200 })
})
})
describe('When updating an abuse', function () {
it('Should fail with a non authenticated user', async function () {
await updateAbuse(server.url, 'blabla', abuseId, {}, 401)
})
it('Should fail with a non admin user', async function () {
await updateAbuse(server.url, userAccessToken, abuseId, {}, 403)
})
it('Should fail with a bad abuse id', async function () {
await updateAbuse(server.url, server.accessToken, 45, {}, 404)
})
it('Should fail with a bad state', async function () {
const body = { state: 5 }
await updateAbuse(server.url, server.accessToken, abuseId, body, 400)
})
it('Should fail with a bad moderation comment', async function () {
const body = { moderationComment: 'b'.repeat(3001) }
await updateAbuse(server.url, server.accessToken, abuseId, body, 400)
})
it('Should succeed with the correct params', async function () {
const body = { state: AbuseState.ACCEPTED }
await updateAbuse(server.url, server.accessToken, abuseId, body)
})
})
describe('When deleting a video abuse', function () {
it('Should fail with a non authenticated user', async function () {
await deleteAbuse(server.url, 'blabla', abuseId, 401)
})
it('Should fail with a non admin user', async function () {
await deleteAbuse(server.url, userAccessToken, abuseId, 403)
})
it('Should fail with a bad abuse id', async function () {
await deleteAbuse(server.url, server.accessToken, 45, 404)
})
it('Should succeed with the correct params', async function () {
await deleteAbuse(server.url, server.accessToken, abuseId)
})
})
after(async function () {
await cleanupTests([ server ])
})
})

View File

@ -1,3 +1,4 @@
import './abuses'
import './accounts' import './accounts'
import './blocklist' import './blocklist'
import './bulk' import './bulk'

View File

@ -152,12 +152,6 @@ describe('Test video abuses API validators', function () {
await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields }) await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields })
}) })
it('Should fail mith misordered startAt/endAt', async function () {
const fields = { reason: 'my super reason', startAt: 5, endAt: 1 }
await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields })
})
it('Should succeed with the corret parameters (advanced)', async function () { it('Should succeed with the corret parameters (advanced)', async function () {
const fields: VideoAbuseCreate = { reason: 'my super reason', predefinedReasons: [ 'serverRules' ], startAt: 1, endAt: 5 } const fields: VideoAbuseCreate = { reason: 'my super reason', predefinedReasons: [ 'serverRules' ], startAt: 1, endAt: 5 }

View File

@ -3,7 +3,7 @@ import { VideoCommentAbuseModel } from '@server/models/abuse/video-comment-abuse
import { PickWith } from '@shared/core-utils' import { PickWith } from '@shared/core-utils'
import { AbuseModel } from '../../../models/abuse/abuse' import { AbuseModel } from '../../../models/abuse/abuse'
import { MAccountDefault, MAccountFormattable, MAccountLight, MAccountUrl } from '../account' import { MAccountDefault, MAccountFormattable, MAccountLight, MAccountUrl } from '../account'
import { MCommentOwner, MCommentUrl, MVideoUrl, MCommentOwnerVideo } from '../video' import { MCommentOwner, MCommentUrl, MVideoUrl, MCommentOwnerVideo, MComment, MCommentVideo } from '../video'
import { MVideo, MVideoAccountLightBlacklistAllFiles } from '../video/video' import { MVideo, MVideoAccountLightBlacklistAllFiles } from '../video/video'
type Use<K extends keyof AbuseModel, M> = PickWith<AbuseModel, K, M> type Use<K extends keyof AbuseModel, M> = PickWith<AbuseModel, K, M>
@ -51,6 +51,10 @@ export type MCommentAbuseUrl =
MCommentAbuse & MCommentAbuse &
UseCommentAbuse<'VideoComment', MCommentUrl> UseCommentAbuse<'VideoComment', MCommentUrl>
export type MCommentAbuseFormattable =
MCommentAbuse &
UseCommentAbuse<'VideoComment', MComment & PickWith<MCommentVideo, 'Video', Pick<MVideo, 'id' | 'uuid' | 'name'>>>
// ############################################################################ // ############################################################################
export type MAbuseId = Pick<AbuseModel, 'id'> export type MAbuseId = Pick<AbuseModel, 'id'>
@ -94,4 +98,5 @@ export type MAbuseFull =
export type MAbuseFormattable = export type MAbuseFormattable =
MAbuse & MAbuse &
Use<'ReporterAccount', MAccountFormattable> & Use<'ReporterAccount', MAccountFormattable> &
Use<'VideoAbuse', MVideoAbuseFormattable> Use<'VideoAbuse', MVideoAbuseFormattable> &
Use<'VideoCommentAbuse', MCommentAbuseFormattable>

View File

@ -91,6 +91,7 @@ declare module 'express' {
accountVideoRate?: MAccountVideoRateAccountVideo accountVideoRate?: MAccountVideoRateAccountVideo
videoComment?: MComment
videoCommentFull?: MCommentOwnerVideoReply videoCommentFull?: MCommentOwnerVideoReply
videoCommentThread?: MComment videoCommentThread?: MComment

View File

@ -1,25 +1,57 @@
import * as request from 'supertest'
import { AbusePredefinedReasonsString, AbuseState, AbuseUpdate, AbuseVideoIs } from '@shared/models'
import { makeDeleteRequest, makeGetRequest, makePutBodyRequest } from '../requests/requests'
function reportAbuse ( import { AbuseFilter, AbusePredefinedReasonsString, AbuseState, AbuseUpdate, AbuseVideoIs } from '@shared/models'
url: string, import { makeDeleteRequest, makeGetRequest, makePostBodyRequest, makePutBodyRequest } from '../requests/requests'
token: string,
videoId: number | string,
reason: string,
predefinedReasons?: AbusePredefinedReasonsString[],
startAt?: number,
endAt?: number,
specialStatus = 200
) {
const path = '/api/v1/videos/' + videoId + '/abuse'
return request(url) function reportAbuse (options: {
.post(path) url: string
.set('Accept', 'application/json') token: string
.set('Authorization', 'Bearer ' + token)
.send({ reason, predefinedReasons, startAt, endAt }) reason: string
.expect(specialStatus)
accountId?: number
videoId?: number
commentId?: number
predefinedReasons?: AbusePredefinedReasonsString[]
startAt?: number
endAt?: number
statusCodeExpected?: number
}) {
const path = '/api/v1/abuses'
const video = options.videoId ? {
id: options.videoId,
startAt: options.startAt,
endAt: options.endAt
} : undefined
const comment = options.commentId ? {
id: options.commentId
} : undefined
const account = options.accountId ? {
id: options.accountId
} : undefined
const body = {
account,
video,
comment,
reason: options.reason,
predefinedReasons: options.predefinedReasons
}
return makePostBodyRequest({
url: options.url,
path,
token: options.token,
fields: body,
statusCodeExpected: options.statusCodeExpected || 200
})
} }
function getAbusesList (options: { function getAbusesList (options: {
@ -28,6 +60,7 @@ function getAbusesList (options: {
id?: number id?: number
predefinedReason?: AbusePredefinedReasonsString predefinedReason?: AbusePredefinedReasonsString
search?: string search?: string
filter?: AbuseFilter,
state?: AbuseState state?: AbuseState
videoIs?: AbuseVideoIs videoIs?: AbuseVideoIs
searchReporter?: string searchReporter?: string
@ -41,6 +74,7 @@ function getAbusesList (options: {
id, id,
predefinedReason, predefinedReason,
search, search,
filter,
state, state,
videoIs, videoIs,
searchReporter, searchReporter,
@ -48,7 +82,7 @@ function getAbusesList (options: {
searchVideo, searchVideo,
searchVideoChannel searchVideoChannel
} = options } = options
const path = '/api/v1/videos/abuse' const path = '/api/v1/abuses'
const query = { const query = {
sort: 'createdAt', sort: 'createdAt',
@ -56,6 +90,7 @@ function getAbusesList (options: {
predefinedReason, predefinedReason,
search, search,
state, state,
filter,
videoIs, videoIs,
searchReporter, searchReporter,
searchReportee, searchReportee,
@ -75,12 +110,11 @@ function getAbusesList (options: {
function updateAbuse ( function updateAbuse (
url: string, url: string,
token: string, token: string,
videoId: string | number, abuseId: number,
videoAbuseId: number,
body: AbuseUpdate, body: AbuseUpdate,
statusCodeExpected = 204 statusCodeExpected = 204
) { ) {
const path = '/api/v1/videos/' + videoId + '/abuse/' + videoAbuseId const path = '/api/v1/abuses/' + abuseId
return makePutBodyRequest({ return makePutBodyRequest({
url, url,
@ -91,8 +125,8 @@ function updateAbuse (
}) })
} }
function deleteAbuse (url: string, token: string, videoId: string | number, videoAbuseId: number, statusCodeExpected = 204) { function deleteAbuse (url: string, token: string, abuseId: number, statusCodeExpected = 204) {
const path = '/api/v1/videos/' + videoId + '/abuse/' + videoAbuseId const path = '/api/v1/abuses/' + abuseId
return makeDeleteRequest({ return makeDeleteRequest({
url, url,

View File

@ -1,11 +1,14 @@
import { AbusePredefinedReasonsString } from './abuse-reason.model' import { AbusePredefinedReasonsString } from './abuse-reason.model'
export interface AbuseCreate { export interface AbuseCreate {
accountId: number
reason: string reason: string
predefinedReasons?: AbusePredefinedReasonsString[] predefinedReasons?: AbusePredefinedReasonsString[]
account?: {
id: number
}
video?: { video?: {
id: number id: number
startAt?: number startAt?: number

View File

@ -1 +0,0 @@
export type AbuseFilter = 'video' | 'comment'

View File

@ -0,0 +1 @@
export type AbuseFilter = 'video' | 'comment' | 'account'

View File

@ -9,6 +9,7 @@ export interface VideoAbuse {
name: string name: string
uuid: string uuid: string
nsfw: boolean nsfw: boolean
deleted: boolean deleted: boolean
blacklisted: boolean blacklisted: boolean
@ -21,8 +22,15 @@ export interface VideoAbuse {
export interface VideoCommentAbuse { export interface VideoCommentAbuse {
id: number id: number
account?: Account
video: {
id: number
name: string
uuid: string
}
text: string text: string
deleted: boolean deleted: boolean
} }

View File

@ -1,4 +1,5 @@
export * from './abuse-create.model' export * from './abuse-create.model'
export * from './abuse-filter.type'
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'