1
0
Fork 0

Use video abuse filters on client side

This commit is contained in:
Chocobozzz 2020-05-06 17:39:07 +02:00 committed by Chocobozzz
parent d170c5c580
commit feb34f6b6b
13 changed files with 343 additions and 154 deletions

View file

@ -19,8 +19,8 @@
<a [routerLink]="[ '/admin/moderation/video-abuses/list' ]" [queryParams]="{ 'search': 'state:pending' }" class="dropdown-item" i18n>Unsolved reports</a> <a [routerLink]="[ '/admin/moderation/video-abuses/list' ]" [queryParams]="{ 'search': 'state:pending' }" class="dropdown-item" i18n>Unsolved reports</a>
<a [routerLink]="[ '/admin/moderation/video-abuses/list' ]" [queryParams]="{ 'search': 'state:accepted' }" class="dropdown-item" i18n>Accepted reports</a> <a [routerLink]="[ '/admin/moderation/video-abuses/list' ]" [queryParams]="{ 'search': 'state:accepted' }" class="dropdown-item" i18n>Accepted reports</a>
<a [routerLink]="[ '/admin/moderation/video-abuses/list' ]" [queryParams]="{ 'search': 'state:rejected' }" class="dropdown-item" i18n>Refused reports</a> <a [routerLink]="[ '/admin/moderation/video-abuses/list' ]" [queryParams]="{ 'search': 'state:rejected' }" class="dropdown-item" i18n>Refused reports</a>
<a [routerLink]="[ '/admin/moderation/video-abuses/list' ]" [queryParams]="{ 'search': 'is:blacklisted' }" class="dropdown-item" i18n>Reports with blacklisted videos</a> <a [routerLink]="[ '/admin/moderation/video-abuses/list' ]" [queryParams]="{ 'search': 'videoIs:blacklisted' }" class="dropdown-item" i18n>Reports with blacklisted videos</a>
<a [routerLink]="[ '/admin/moderation/video-abuses/list' ]" [queryParams]="{ 'search': 'is:deleted' }" class="dropdown-item" i18n>Reports with deleted videos</a> <a [routerLink]="[ '/admin/moderation/video-abuses/list' ]" [queryParams]="{ 'search': 'videoIs:deleted' }" class="dropdown-item" i18n>Reports with deleted videos</a>
</div> </div>
</div> </div>
<input <input

View file

@ -1,10 +1,21 @@
import { Injectable } from '@angular/core'
import { HttpParams } from '@angular/common/http'
import { SortMeta } from 'primeng/api' import { SortMeta } from 'primeng/api'
import { ComponentPagination, ComponentPaginationLight } from './component-pagination.model' import { HttpParams } from '@angular/common/http'
import { Injectable } from '@angular/core'
import { ComponentPaginationLight } from './component-pagination.model'
import { RestPagination } from './rest-pagination' import { RestPagination } from './rest-pagination'
interface QueryStringFilterPrefixes {
[key: string]: {
prefix: string
handler?: (v: string) => string | number
multiple?: boolean
}
}
type ParseQueryStringFilterResult = {
[key: string]: string | number | (string | number)[]
}
@Injectable() @Injectable()
export class RestService { export class RestService {
@ -53,4 +64,48 @@ export class RestService {
return { start, count } return { start, count }
} }
parseQueryStringFilter (q: string, prefixes: QueryStringFilterPrefixes): ParseQueryStringFilterResult {
if (!q) return {}
// Tokenize the strings using spaces
const tokens = q.split(' ').filter(token => !!token)
// Build prefix array
const prefixeStrings = Object.values(prefixes)
.map(p => p.prefix)
// Search is the querystring minus defined filters
const searchTokens = tokens.filter(t => {
return prefixeStrings.every(prefixString => t.startsWith(prefixString) === false)
})
const additionalFilters: ParseQueryStringFilterResult = {}
for (const prefixKey of Object.keys(prefixes)) {
const prefixObj = prefixes[prefixKey]
const prefix = prefixObj.prefix
const matchedTokens = tokens.filter(t => t.startsWith(prefix))
.map(t => t.slice(prefix.length)) // Keep the value filter
.map(t => {
if (prefixObj.handler) return prefixObj.handler(t)
return t
})
.filter(t => !!t)
if (matchedTokens.length === 0) continue
additionalFilters[prefixKey] = prefixObj.multiple === true
? matchedTokens
: matchedTokens[0]
}
return {
search: searchTokens.join(' '),
...additionalFilters
}
}
} }

View file

@ -3,7 +3,7 @@ import { HttpClient, HttpParams } from '@angular/common/http'
import { Injectable } from '@angular/core' import { Injectable } from '@angular/core'
import { SortMeta } from 'primeng/api' import { SortMeta } from 'primeng/api'
import { Observable } from 'rxjs' import { Observable } from 'rxjs'
import { ResultList, VideoAbuse, VideoAbuseUpdate } from '../../../../../shared' import { ResultList, VideoAbuse, VideoAbuseUpdate, VideoAbuseState } from '../../../../../shared'
import { environment } from '../../../environments/environment' import { environment } from '../../../environments/environment'
import { RestExtractor, RestPagination, RestService } from '../rest' import { RestExtractor, RestPagination, RestService } from '../rest'
@ -28,7 +28,34 @@ export class VideoAbuseService {
let params = new HttpParams() let params = new HttpParams()
params = this.restService.addRestGetParams(params, pagination, sort) params = this.restService.addRestGetParams(params, pagination, sort)
if (search) params = params.append('search', search) if (search) {
const filters = this.restService.parseQueryStringFilter(search, {
id: { prefix: '#' },
state: {
prefix: 'state:',
handler: v => {
if (v === 'accepted') return VideoAbuseState.ACCEPTED
if (v === 'pending') return VideoAbuseState.PENDING
if (v === 'rejected') return VideoAbuseState.REJECTED
return undefined
}
},
videoIs: {
prefix: 'videoIs:',
handler: v => {
if (v === 'deleted') return v
if (v === 'blacklisted') return v
return undefined
}
},
searchReporter: { prefix: 'reporter:' },
searchReportee: { prefix: 'reportee:' }
})
params = this.restService.addObjectParams(params, filters)
}
return this.authHttp.get<ResultList<VideoAbuse>>(url, { params }) return this.authHttp.get<ResultList<VideoAbuse>>(url, { params })
.pipe( .pipe(

View file

@ -14,7 +14,8 @@ import {
videoAbuseGetValidator, videoAbuseGetValidator,
videoAbuseReportValidator, videoAbuseReportValidator,
videoAbusesSortValidator, videoAbusesSortValidator,
videoAbuseUpdateValidator videoAbuseUpdateValidator,
videoAbuseListValidator
} from '../../../middlewares' } from '../../../middlewares'
import { AccountModel } from '../../../models/account/account' import { AccountModel } from '../../../models/account/account'
import { VideoAbuseModel } from '../../../models/video/video-abuse' import { VideoAbuseModel } from '../../../models/video/video-abuse'
@ -34,6 +35,7 @@ abuseVideoRouter.get('/abuse',
videoAbusesSortValidator, videoAbusesSortValidator,
setDefaultSort, setDefaultSort,
setDefaultPagination, setDefaultPagination,
videoAbuseListValidator,
asyncMiddleware(listVideoAbuses) asyncMiddleware(listVideoAbuses)
) )
abuseVideoRouter.put('/:videoId/abuse/:id', abuseVideoRouter.put('/:videoId/abuse/:id',
@ -70,7 +72,14 @@ async function listVideoAbuses (req: express.Request, res: express.Response) {
start: req.query.start, start: req.query.start,
count: req.query.count, count: req.query.count,
sort: req.query.sort, sort: req.query.sort,
id: req.query.id,
search: req.query.search, search: req.query.search,
state: req.query.state,
videoIs: req.query.videoIs,
searchReporter: req.query.searchReporter,
searchReportee: req.query.searchReportee,
searchVideo: req.query.searchVideo,
searchVideoChannel: req.query.searchVideoChannel,
serverAccountId: serverActor.Account.id, serverAccountId: serverActor.Account.id,
user user
}) })

View file

@ -1,6 +1,8 @@
import validator from 'validator' import validator from 'validator'
import { CONSTRAINTS_FIELDS, VIDEO_ABUSE_STATES } from '../../initializers/constants' import { CONSTRAINTS_FIELDS, VIDEO_ABUSE_STATES } from '../../initializers/constants'
import { exists } from './misc' import { exists } from './misc'
import { VideoAbuseVideoIs } from '@shared/models/videos/abuse/video-abuse-video-is.type'
const VIDEO_ABUSES_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.VIDEO_ABUSES const VIDEO_ABUSES_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.VIDEO_ABUSES
@ -16,10 +18,18 @@ function isVideoAbuseStateValid (value: string) {
return exists(value) && VIDEO_ABUSE_STATES[value] !== undefined return exists(value) && VIDEO_ABUSE_STATES[value] !== undefined
} }
function isAbuseVideoIsValid (value: VideoAbuseVideoIs) {
return exists(value) && (
value === 'deleted' ||
value === 'blacklisted'
)
}
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
export { export {
isVideoAbuseStateValid, isVideoAbuseStateValid,
isVideoAbuseReasonValid, isVideoAbuseReasonValid,
isAbuseVideoIsValid,
isVideoAbuseModerationCommentValid isVideoAbuseModerationCommentValid
} }

View file

@ -1,14 +1,15 @@
import * as express from 'express' import * as express from 'express'
import { body, param } from 'express-validator' import { body, param, query } from 'express-validator'
import { isIdOrUUIDValid, isIdValid } from '../../../helpers/custom-validators/misc' import { exists, isIdOrUUIDValid, isIdValid } from '../../../helpers/custom-validators/misc'
import { logger } from '../../../helpers/logger'
import { areValidationErrors } from '../utils'
import { import {
isAbuseVideoIsValid,
isVideoAbuseModerationCommentValid, isVideoAbuseModerationCommentValid,
isVideoAbuseReasonValid, isVideoAbuseReasonValid,
isVideoAbuseStateValid isVideoAbuseStateValid
} from '../../../helpers/custom-validators/video-abuses' } from '../../../helpers/custom-validators/video-abuses'
import { logger } from '../../../helpers/logger'
import { doesVideoAbuseExist, doesVideoExist } from '../../../helpers/middlewares' import { doesVideoAbuseExist, doesVideoExist } from '../../../helpers/middlewares'
import { areValidationErrors } from '../utils'
const videoAbuseReportValidator = [ const videoAbuseReportValidator = [
param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'), param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'),
@ -58,9 +59,45 @@ const videoAbuseUpdateValidator = [
} }
] ]
const videoAbuseListValidator = [
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(isVideoAbuseStateValid).withMessage('Should have a valid video abuse state'),
query('videoIs')
.optional()
.custom(isAbuseVideoIsValid).withMessage('Should have a valid "video is" attribute'),
query('searchReporter')
.optional()
.custom(exists).withMessage('Should have a valid reporter search'),
query('searchReportee')
.optional()
.custom(exists).withMessage('Should have a valid reportee search'),
query('searchVideo')
.optional()
.custom(exists).withMessage('Should have a valid video search'),
query('searchVideoChannel')
.optional()
.custom(exists).withMessage('Should have a valid video channel search'),
(req: express.Request, res: express.Response, next: express.NextFunction) => {
logger.debug('Checking videoAbuseListValidator parameters', { parameters: req.body })
if (areValidationErrors(req, res)) return
return next()
}
]
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
export { export {
videoAbuseListValidator,
videoAbuseReportValidator, videoAbuseReportValidator,
videoAbuseGetValidator, videoAbuseGetValidator,
videoAbuseUpdateValidator videoAbuseUpdateValidator

View file

@ -207,60 +207,13 @@ function buildDirectionAndField (value: string) {
return { direction, field } return { direction, field }
} }
function searchAttribute (sourceField, targetField) { function searchAttribute (sourceField?: string, targetField?: string) {
if (sourceField) { if (!sourceField) return {}
return {
[targetField]: {
[Op.iLike]: `%${sourceField}%`
}
}
} else {
return {}
}
}
interface QueryStringFilterPrefixes {
[key: string]: string | { prefix: string, handler: Function, multiple?: boolean }
}
function parseQueryStringFilter (q: string, prefixes: QueryStringFilterPrefixes): {
search: string
[key: string]: string | number | string[] | number[]
} {
const tokens = q // tokenize only if we have a querystring
? [].concat.apply([], q.split('"').map((v, i) => i % 2 ? v : v.split(' '))).filter(Boolean) // split by space unless using double quotes
: []
const objectMap = (obj, fn) => Object.fromEntries(
Object.entries(obj).map(
([ k, v ], i) => [ k, fn(v, k, i) ]
)
)
return { return {
// search is the querystring minus defined filters [targetField]: {
search: tokens.filter(e => !Object.values(prefixes).some(p => { [Op.iLike]: `%${sourceField}%`
if (typeof p === 'string') { }
return e.startsWith(p)
} else {
return e.startsWith(p.prefix)
}
})).join(' '),
// filters defined in prefixes are added under their own name
...objectMap(prefixes, p => {
if (typeof p === 'string') {
return tokens.filter(e => e.startsWith(p)).map(e => e.slice(p.length)) // we keep the matched item, and remove its prefix
} else {
const _tokens = tokens.filter(e => e.startsWith(p.prefix)).map(e => e.slice(p.prefix.length)).map(p.handler)
// multiple is false by default, meaning we usually just keep the first occurence of a given prefix
if (!p.multiple && _tokens.length > 0) {
return _tokens[0]
} else if (!p.multiple) {
return ''
}
return _tokens
}
})
} }
} }
@ -286,8 +239,7 @@ export {
getFollowsSort, getFollowsSort,
buildDirectionAndField, buildDirectionAndField,
createSafeIn, createSafeIn,
searchAttribute, searchAttribute
parseQueryStringFilter
} }
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------

View file

@ -1,6 +1,21 @@
import * as Bluebird from 'bluebird'
import { literal, Op } from 'sequelize'
import { import {
AllowNull, BelongsTo, Column, CreatedAt, DataType, Default, ForeignKey, Is, Model, Table, UpdatedAt, Scopes AllowNull,
BelongsTo,
Column,
CreatedAt,
DataType,
Default,
ForeignKey,
Is,
Model,
Scopes,
Table,
UpdatedAt
} from 'sequelize-typescript' } from 'sequelize-typescript'
import { VideoAbuseVideoIs } from '@shared/models/videos/abuse/video-abuse-video-is.type'
import { VideoAbuseState, VideoDetails } from '../../../shared'
import { VideoAbuseObject } from '../../../shared/models/activitypub/objects' import { VideoAbuseObject } from '../../../shared/models/activitypub/objects'
import { VideoAbuse } from '../../../shared/models/videos' import { VideoAbuse } from '../../../shared/models/videos'
import { import {
@ -8,15 +23,12 @@ import {
isVideoAbuseReasonValid, isVideoAbuseReasonValid,
isVideoAbuseStateValid isVideoAbuseStateValid
} from '../../helpers/custom-validators/video-abuses' } from '../../helpers/custom-validators/video-abuses'
import { AccountModel } from '../account/account'
import { buildBlockedAccountSQL, getSort, throwIfNotValid, searchAttribute, parseQueryStringFilter } from '../utils'
import { VideoModel } from './video'
import { VideoAbuseState, VideoDetails } from '../../../shared'
import { CONSTRAINTS_FIELDS, VIDEO_ABUSE_STATES } from '../../initializers/constants' import { CONSTRAINTS_FIELDS, VIDEO_ABUSE_STATES } from '../../initializers/constants'
import { MUserAccountId, MVideoAbuse, MVideoAbuseFormattable, MVideoAbuseVideo } from '../../typings/models' import { MUserAccountId, MVideoAbuse, MVideoAbuseFormattable, MVideoAbuseVideo } from '../../typings/models'
import * as Bluebird from 'bluebird' import { AccountModel } from '../account/account'
import { literal, Op } from 'sequelize' import { buildBlockedAccountSQL, getSort, searchAttribute, throwIfNotValid } from '../utils'
import { ThumbnailModel } from './thumbnail' import { ThumbnailModel } from './thumbnail'
import { VideoModel } from './video'
import { VideoBlacklistModel } from './video-blacklist' import { VideoBlacklistModel } from './video-blacklist'
import { ScopeNames as VideoChannelScopeNames, SummaryOptions, VideoChannelModel } from './video-channel' import { ScopeNames as VideoChannelScopeNames, SummaryOptions, VideoChannelModel } from './video-channel'
@ -35,21 +47,22 @@ export enum ScopeNames {
// filters // filters
id?: number id?: number
state?: VideoAbuseState state?: VideoAbuseState
is?: 'deleted' | 'blacklisted' videoIs?: VideoAbuseVideoIs
// accountIds // accountIds
serverAccountId: number serverAccountId: number
userAccountId: number userAccountId: number
}) => { }) => {
let where = { const where = {
reporterAccountId: { reporterAccountId: {
[Op.notIn]: literal('(' + buildBlockedAccountSQL(options.serverAccountId, options.userAccountId) + ')') [Op.notIn]: literal('(' + buildBlockedAccountSQL(options.serverAccountId, options.userAccountId) + ')')
} }
} }
if (options.search) { if (options.search) {
where = Object.assign(where, { Object.assign(where, {
[Op.or]: [ [Op.or]: [
{ {
[Op.and]: [ [Op.and]: [
@ -80,26 +93,18 @@ export enum ScopeNames {
}) })
} }
if (options.id) { if (options.id) Object.assign(where, { id: options.id })
where = Object.assign(where, { if (options.state) Object.assign(where, { state: options.state })
id: options.id
if (options.videoIs === 'deleted') {
Object.assign(where, {
deletedVideo: {
[Op.not]: null
}
}) })
} }
if (options.state) { const onlyBlacklisted = options.videoIs === 'blacklisted'
where = Object.assign(where, {
state: options.state
})
}
let onlyBlacklisted = false
if (options.is === 'deleted') {
where = Object.assign(where, {
deletedVideo: { [Op.not]: null }
})
} else if (options.is === 'blacklisted') {
onlyBlacklisted = true
}
return { return {
attributes: { attributes: {
@ -189,7 +194,7 @@ export enum ScopeNames {
}, },
{ {
model: VideoModel, model: VideoModel,
required: onlyBlacklisted, required: !!(onlyBlacklisted || options.searchVideo || options.searchReportee || options.searchVideoChannel),
where: searchAttribute(options.searchVideo, 'name'), where: searchAttribute(options.searchVideo, 'name'),
include: [ include: [
{ {
@ -301,11 +306,36 @@ export class VideoAbuseModel extends Model<VideoAbuseModel> {
start: number start: number
count: number count: number
sort: string sort: string
search?: string
serverAccountId: number serverAccountId: number
user?: MUserAccountId user?: MUserAccountId
id?: number
state?: VideoAbuseState
videoIs?: VideoAbuseVideoIs
search?: string
searchReporter?: string
searchReportee?: string
searchVideo?: string
searchVideoChannel?: string
}) { }) {
const { start, count, sort, search, user, serverAccountId } = parameters const {
start,
count,
sort,
search,
user,
serverAccountId,
state,
videoIs,
searchReportee,
searchVideo,
searchVideoChannel,
searchReporter,
id
} = parameters
const userAccountId = user ? user.Account.id : undefined const userAccountId = user ? user.Account.id : undefined
const query = { const query = {
@ -317,37 +347,14 @@ export class VideoAbuseModel extends Model<VideoAbuseModel> {
} }
const filters = { const filters = {
...parseQueryStringFilter(search, { id,
id: { search,
prefix: '#', state,
handler: v => v videoIs,
}, searchReportee,
state: { searchVideo,
prefix: 'state:', searchVideoChannel,
handler: v => { searchReporter,
if (v === 'accepted') return VideoAbuseState.ACCEPTED
if (v === 'pending') return VideoAbuseState.PENDING
if (v === 'rejected') return VideoAbuseState.REJECTED
return undefined
}
},
is: {
prefix: 'is:',
handler: v => {
if (v === 'deleted') return v
if (v === 'blacklisted') return v
return undefined
}
},
searchReporter: {
prefix: 'reporter:',
handler: v => v
},
searchReportee: {
prefix: 'reportee:',
handler: v => v
}
}),
serverAccountId, serverAccountId,
userAccountId userAccountId
} }

View file

@ -76,6 +76,22 @@ describe('Test video abuses API validators', function () {
statusCodeExpected: 403 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 state filter', async function () {
await makeGetRequest({ url: server.url, path, token: server.accessToken, query: { state: 'toto' } })
})
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 () {
await makeGetRequest({ url: server.url, path, token: server.accessToken, query: { id: 13 }, statusCodeExpected: 200 })
})
}) })
describe('When reporting a video abuse', function () { describe('When reporting a video abuse', function () {

View file

@ -901,7 +901,7 @@ describe('Test users', function () {
const reason = 'my super bad reason' const reason = 'my super bad reason'
await reportVideoAbuse(server.url, user17AccessToken, videoId, reason) await reportVideoAbuse(server.url, user17AccessToken, videoId, reason)
const res1 = await getVideoAbusesList(server.url, server.accessToken) const res1 = await getVideoAbusesList({ url: server.url, token: server.accessToken })
const abuseId = res1.body.data[0].id const abuseId = res1.body.data[0].id
const res2 = await getUserInformation(server.url, server.accessToken, user17Id, true) const res2 = await getUserInformation(server.url, server.accessToken, user17Id, true)

View file

@ -71,7 +71,7 @@ describe('Test video abuses', function () {
}) })
it('Should not have video abuses', async function () { it('Should not have video abuses', async function () {
const res = await getVideoAbusesList(servers[0].url, servers[0].accessToken) const res = await getVideoAbusesList({ url: servers[0].url, token: servers[0].accessToken })
expect(res.body.total).to.equal(0) expect(res.body.total).to.equal(0)
expect(res.body.data).to.be.an('array') expect(res.body.data).to.be.an('array')
@ -89,7 +89,7 @@ describe('Test video abuses', function () {
}) })
it('Should have 1 video abuses on server 1 and 0 on server 2', async function () { it('Should have 1 video abuses on server 1 and 0 on server 2', async function () {
const res1 = await getVideoAbusesList(servers[0].url, servers[0].accessToken) const res1 = await getVideoAbusesList({ url: servers[0].url, token: servers[0].accessToken })
expect(res1.body.total).to.equal(1) expect(res1.body.total).to.equal(1)
expect(res1.body.data).to.be.an('array') expect(res1.body.data).to.be.an('array')
@ -106,7 +106,7 @@ describe('Test video abuses', function () {
expect(abuse.countReportsForReporter).to.equal(1) expect(abuse.countReportsForReporter).to.equal(1)
expect(abuse.countReportsForReportee).to.equal(1) expect(abuse.countReportsForReportee).to.equal(1)
const res2 = await getVideoAbusesList(servers[1].url, servers[1].accessToken) const res2 = await getVideoAbusesList({ url: servers[1].url, token: servers[1].accessToken })
expect(res2.body.total).to.equal(0) expect(res2.body.total).to.equal(0)
expect(res2.body.data).to.be.an('array') expect(res2.body.data).to.be.an('array')
expect(res2.body.data.length).to.equal(0) expect(res2.body.data.length).to.equal(0)
@ -123,7 +123,7 @@ describe('Test video abuses', function () {
}) })
it('Should have 2 video abuses on server 1 and 1 on server 2', async function () { it('Should have 2 video abuses on server 1 and 1 on server 2', async function () {
const res1 = await getVideoAbusesList(servers[0].url, servers[0].accessToken) const res1 = await getVideoAbusesList({ url: servers[0].url, token: servers[0].accessToken })
expect(res1.body.total).to.equal(2) expect(res1.body.total).to.equal(2)
expect(res1.body.data).to.be.an('array') expect(res1.body.data).to.be.an('array')
expect(res1.body.data.length).to.equal(2) expect(res1.body.data.length).to.equal(2)
@ -148,7 +148,7 @@ describe('Test video abuses', function () {
expect(abuse2.state.label).to.equal('Pending') expect(abuse2.state.label).to.equal('Pending')
expect(abuse2.moderationComment).to.be.null expect(abuse2.moderationComment).to.be.null
const res2 = await getVideoAbusesList(servers[1].url, servers[1].accessToken) const res2 = await getVideoAbusesList({ url: servers[1].url, token: servers[1].accessToken })
expect(res2.body.total).to.equal(1) expect(res2.body.total).to.equal(1)
expect(res2.body.data).to.be.an('array') expect(res2.body.data).to.be.an('array')
expect(res2.body.data.length).to.equal(1) expect(res2.body.data.length).to.equal(1)
@ -166,7 +166,7 @@ describe('Test video abuses', function () {
const body = { state: VideoAbuseState.REJECTED } const body = { state: VideoAbuseState.REJECTED }
await updateVideoAbuse(servers[1].url, servers[1].accessToken, abuseServer2.video.uuid, abuseServer2.id, body) await updateVideoAbuse(servers[1].url, servers[1].accessToken, abuseServer2.video.uuid, abuseServer2.id, body)
const res = await getVideoAbusesList(servers[1].url, servers[1].accessToken) const res = await getVideoAbusesList({ url: servers[1].url, token: servers[1].accessToken })
expect(res.body.data[0].state.id).to.equal(VideoAbuseState.REJECTED) expect(res.body.data[0].state.id).to.equal(VideoAbuseState.REJECTED)
}) })
@ -174,7 +174,7 @@ describe('Test video abuses', function () {
const body = { state: VideoAbuseState.ACCEPTED, moderationComment: 'It is valid' } const body = { state: VideoAbuseState.ACCEPTED, moderationComment: 'It is valid' }
await updateVideoAbuse(servers[1].url, servers[1].accessToken, abuseServer2.video.uuid, abuseServer2.id, body) await updateVideoAbuse(servers[1].url, servers[1].accessToken, abuseServer2.video.uuid, abuseServer2.id, body)
const res = await getVideoAbusesList(servers[1].url, servers[1].accessToken) const res = await getVideoAbusesList({ url: servers[1].url, token: servers[1].accessToken })
expect(res.body.data[0].state.id).to.equal(VideoAbuseState.ACCEPTED) expect(res.body.data[0].state.id).to.equal(VideoAbuseState.ACCEPTED)
expect(res.body.data[0].moderationComment).to.equal('It is valid') expect(res.body.data[0].moderationComment).to.equal('It is valid')
}) })
@ -186,7 +186,7 @@ describe('Test video abuses', function () {
await reportVideoAbuse(servers[1].url, servers[1].accessToken, servers[0].video.uuid, 'will mute this') await reportVideoAbuse(servers[1].url, servers[1].accessToken, servers[0].video.uuid, 'will mute this')
await waitJobs(servers) await waitJobs(servers)
const res = await getVideoAbusesList(servers[0].url, servers[0].accessToken) const res = await getVideoAbusesList({ url: servers[0].url, token: servers[0].accessToken })
expect(res.body.total).to.equal(3) expect(res.body.total).to.equal(3)
} }
@ -195,7 +195,7 @@ describe('Test video abuses', function () {
{ {
await addAccountToServerBlocklist(servers[0].url, servers[0].accessToken, accountToBlock) await addAccountToServerBlocklist(servers[0].url, servers[0].accessToken, accountToBlock)
const res = await getVideoAbusesList(servers[0].url, servers[0].accessToken) const res = await getVideoAbusesList({ url: servers[0].url, token: servers[0].accessToken })
expect(res.body.total).to.equal(2) expect(res.body.total).to.equal(2)
const abuse = res.body.data.find(a => a.reason === 'will mute this') const abuse = res.body.data.find(a => a.reason === 'will mute this')
@ -205,7 +205,7 @@ describe('Test video abuses', function () {
{ {
await removeAccountFromServerBlocklist(servers[0].url, servers[0].accessToken, accountToBlock) await removeAccountFromServerBlocklist(servers[0].url, servers[0].accessToken, accountToBlock)
const res = await getVideoAbusesList(servers[0].url, servers[0].accessToken) const res = await getVideoAbusesList({ url: servers[0].url, token: servers[0].accessToken })
expect(res.body.total).to.equal(3) expect(res.body.total).to.equal(3)
} }
}) })
@ -216,7 +216,7 @@ describe('Test video abuses', function () {
{ {
await addServerToServerBlocklist(servers[0].url, servers[0].accessToken, servers[1].host) await addServerToServerBlocklist(servers[0].url, servers[0].accessToken, servers[1].host)
const res = await getVideoAbusesList(servers[0].url, servers[0].accessToken) const res = await getVideoAbusesList({ url: servers[0].url, token: servers[0].accessToken })
expect(res.body.total).to.equal(2) expect(res.body.total).to.equal(2)
const abuse = res.body.data.find(a => a.reason === 'will mute this') const abuse = res.body.data.find(a => a.reason === 'will mute this')
@ -226,7 +226,7 @@ describe('Test video abuses', function () {
{ {
await removeServerFromServerBlocklist(servers[0].url, servers[0].accessToken, serverToBlock) await removeServerFromServerBlocklist(servers[0].url, servers[0].accessToken, serverToBlock)
const res = await getVideoAbusesList(servers[0].url, servers[0].accessToken) const res = await getVideoAbusesList({ url: servers[0].url, token: servers[0].accessToken })
expect(res.body.total).to.equal(3) expect(res.body.total).to.equal(3)
} }
}) })
@ -238,7 +238,7 @@ describe('Test video abuses', function () {
await waitJobs(servers) await waitJobs(servers)
const res = await getVideoAbusesList(servers[1].url, servers[1].accessToken) const res = await getVideoAbusesList({ url: servers[1].url, token: servers[1].accessToken })
expect(res.body.total).to.equal(2, "wrong number of videos returned") expect(res.body.total).to.equal(2, "wrong number of videos returned")
expect(res.body.data.length).to.equal(2, "wrong number of videos returned") expect(res.body.data.length).to.equal(2, "wrong number of videos returned")
expect(res.body.data[0].id).to.equal(abuseServer2.id, "wrong origin server id for first video") expect(res.body.data[0].id).to.equal(abuseServer2.id, "wrong origin server id for first video")
@ -274,7 +274,7 @@ describe('Test video abuses', function () {
const reason4 = 'my super bad reason 4' const reason4 = 'my super bad reason 4'
await reportVideoAbuse(servers[0].url, userAccessToken, servers[0].video.id, reason4) await reportVideoAbuse(servers[0].url, userAccessToken, servers[0].video.id, reason4)
const res2 = await getVideoAbusesList(servers[0].url, servers[0].accessToken) const res2 = await getVideoAbusesList({ url: servers[0].url, token: servers[0].accessToken })
{ {
for (const abuse of res2.body.data as VideoAbuse[]) { for (const abuse of res2.body.data as VideoAbuse[]) {
@ -299,18 +299,56 @@ describe('Test video abuses', function () {
await waitJobs(servers) await waitJobs(servers)
{ {
const res = await getVideoAbusesList(servers[1].url, servers[1].accessToken) const res = await getVideoAbusesList({ url: servers[1].url, token: servers[1].accessToken })
expect(res.body.total).to.equal(1) expect(res.body.total).to.equal(1)
expect(res.body.data.length).to.equal(1) expect(res.body.data.length).to.equal(1)
expect(res.body.data[0].id).to.not.equal(abuseServer2.id) expect(res.body.data[0].id).to.not.equal(abuseServer2.id)
} }
{ {
const res = await getVideoAbusesList(servers[0].url, servers[0].accessToken) const res = await getVideoAbusesList({ url: servers[0].url, token: servers[0].accessToken })
expect(res.body.total).to.equal(5) expect(res.body.total).to.equal(5)
} }
}) })
it('Should list and filter video abuses', async function () {
async function list (query: Omit<Parameters<typeof getVideoAbusesList>[0], 'url' | 'token'>) {
const options = {
url: servers[0].url,
token: servers[0].accessToken
}
Object.assign(options, query)
const res = await getVideoAbusesList(options)
return res.body.data as VideoAbuse[]
}
expect(await list({ id: 56 })).to.have.lengthOf(0)
expect(await list({ id: 1 })).to.have.lengthOf(1)
expect(await list({ search: 'my super name for server 1' })).to.have.lengthOf(3)
expect(await list({ search: 'aaaaaaaaaaaaaaaaaaaaaaaaaa' })).to.have.lengthOf(0)
expect(await list({ searchVideo: 'my second super name for server 1' })).to.have.lengthOf(1)
expect(await list({ searchVideoChannel: 'root' })).to.have.lengthOf(3)
expect(await list({ searchVideoChannel: 'aaaa' })).to.have.lengthOf(0)
expect(await list({ searchReporter: 'user2' })).to.have.lengthOf(1)
expect(await list({ searchReporter: 'root' })).to.have.lengthOf(4)
expect(await list({ searchReportee: 'root' })).to.have.lengthOf(3)
expect(await list({ searchReportee: 'aaaa' })).to.have.lengthOf(0)
expect(await list({ videoIs: 'deleted' })).to.have.lengthOf(1)
expect(await list({ videoIs: 'blacklisted' })).to.have.lengthOf(0)
expect(await list({ state: VideoAbuseState.ACCEPTED })).to.have.lengthOf(0)
expect(await list({ state: VideoAbuseState.PENDING })).to.have.lengthOf(5)
})
after(async function () { after(async function () {
await cleanupTests(servers) await cleanupTests(servers)
}) })

View file

@ -1,6 +1,8 @@
import * as request from 'supertest' import * as request from 'supertest'
import { VideoAbuseUpdate } from '../../models/videos/abuse/video-abuse-update.model' import { VideoAbuseUpdate } from '../../models/videos/abuse/video-abuse-update.model'
import { makeDeleteRequest, makePutBodyRequest } from '../requests/requests' import { makeDeleteRequest, makePutBodyRequest, makeGetRequest } from '../requests/requests'
import { VideoAbuseState } from '@shared/models'
import { VideoAbuseVideoIs } from '@shared/models/videos/abuse/video-abuse-video-is.type'
function reportVideoAbuse (url: string, token: string, videoId: number | string, reason: string, specialStatus = 200) { function reportVideoAbuse (url: string, token: string, videoId: number | string, reason: string, specialStatus = 200) {
const path = '/api/v1/videos/' + videoId + '/abuse' const path = '/api/v1/videos/' + videoId + '/abuse'
@ -13,16 +15,51 @@ function reportVideoAbuse (url: string, token: string, videoId: number | string,
.expect(specialStatus) .expect(specialStatus)
} }
function getVideoAbusesList (url: string, token: string) { function getVideoAbusesList (options: {
url: string
token: string
id?: number
search?: string
state?: VideoAbuseState
videoIs?: VideoAbuseVideoIs
searchReporter?: string
searchReportee?: string
searchVideo?: string
searchVideoChannel?: string
}) {
const {
url,
token,
id,
search,
state,
videoIs,
searchReporter,
searchReportee,
searchVideo,
searchVideoChannel
} = options
const path = '/api/v1/videos/abuse' const path = '/api/v1/videos/abuse'
return request(url) const query = {
.get(path) sort: 'createdAt',
.query({ sort: 'createdAt' }) id,
.set('Accept', 'application/json') search,
.set('Authorization', 'Bearer ' + token) state,
.expect(200) videoIs,
.expect('Content-Type', /json/) searchReporter,
searchReportee,
searchVideo,
searchVideoChannel
}
return makeGetRequest({
url,
path,
token,
query,
statusCodeExpected: 200
})
} }
function updateVideoAbuse ( function updateVideoAbuse (

View file

@ -0,0 +1 @@
export type VideoAbuseVideoIs = 'deleted' | 'blacklisted'