Use video abuse filters on client side
authorChocobozzz <me@florianbigard.com>
Wed, 6 May 2020 15:39:07 +0000 (17:39 +0200)
committerChocobozzz <chocobozzz@cpy.re>
Thu, 7 May 2020 06:33:34 +0000 (08:33 +0200)
13 files changed:
client/src/app/+admin/moderation/video-abuse-list/video-abuse-list.component.html
client/src/app/shared/rest/rest.service.ts
client/src/app/shared/video-abuse/video-abuse.service.ts
server/controllers/api/videos/abuse.ts
server/helpers/custom-validators/video-abuses.ts
server/middlewares/validators/videos/video-abuses.ts
server/models/utils.ts
server/models/video/video-abuse.ts
server/tests/api/check-params/video-abuses.ts
server/tests/api/users/users.ts
server/tests/api/videos/video-abuse.ts
shared/extra-utils/videos/video-abuses.ts
shared/models/videos/abuse/video-abuse-video-is.type.ts [new file with mode: 0644]

index ba05073cf1b6d96a22ad407d26e18c4ffe9660fb..cffa7a40e6971eb1d7ed6aa45ab7024a5bbf1741 100644 (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: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': 'is: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:blacklisted' }" class="dropdown-item" i18n>Reports with blacklisted videos</a>
+              <a [routerLink]="[ '/admin/moderation/video-abuses/list' ]" [queryParams]="{ 'search': 'videoIs:deleted' }" class="dropdown-item" i18n>Reports with deleted videos</a>
             </div>
           </div>
           <input
index 5bd2b5e43a732443517bf49867b966526fbaae64..cd6db1f3c7d8d080a04980edcb4a931581964481 100644 (file)
@@ -1,10 +1,21 @@
-import { Injectable } from '@angular/core'
-import { HttpParams } from '@angular/common/http'
 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'
 
+interface QueryStringFilterPrefixes {
+  [key: string]: {
+    prefix: string
+    handler?: (v: string) => string | number
+    multiple?: boolean
+  }
+}
+
+type ParseQueryStringFilterResult = {
+  [key: string]: string | number | (string | number)[]
+}
+
 @Injectable()
 export class RestService {
 
@@ -53,4 +64,48 @@ export class RestService {
 
     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
+    }
+  }
 }
index 1ab6b5376139cb3cb6830aac94a6c88f5f93b65a..700a3023965437b8529ded080455f2a7ab5acbe4 100644 (file)
@@ -3,7 +3,7 @@ import { HttpClient, HttpParams } from '@angular/common/http'
 import { Injectable } from '@angular/core'
 import { SortMeta } from 'primeng/api'
 import { Observable } from 'rxjs'
-import { ResultList, VideoAbuse, VideoAbuseUpdate } from '../../../../../shared'
+import { ResultList, VideoAbuse, VideoAbuseUpdate, VideoAbuseState } from '../../../../../shared'
 import { environment } from '../../../environments/environment'
 import { RestExtractor, RestPagination, RestService } from '../rest'
 
@@ -28,7 +28,34 @@ export class VideoAbuseService {
     let params = new HttpParams()
     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 })
                .pipe(
index bc7df48c85b33cfcab095330ae161bc3d0ca823d..3fe7f7e51381132e6192484e01a1193304a06ea4 100644 (file)
@@ -14,7 +14,8 @@ import {
   videoAbuseGetValidator,
   videoAbuseReportValidator,
   videoAbusesSortValidator,
-  videoAbuseUpdateValidator
+  videoAbuseUpdateValidator,
+  videoAbuseListValidator
 } from '../../../middlewares'
 import { AccountModel } from '../../../models/account/account'
 import { VideoAbuseModel } from '../../../models/video/video-abuse'
@@ -34,6 +35,7 @@ abuseVideoRouter.get('/abuse',
   videoAbusesSortValidator,
   setDefaultSort,
   setDefaultPagination,
+  videoAbuseListValidator,
   asyncMiddleware(listVideoAbuses)
 )
 abuseVideoRouter.put('/:videoId/abuse/:id',
@@ -70,7 +72,14 @@ async function listVideoAbuses (req: express.Request, res: express.Response) {
     start: req.query.start,
     count: req.query.count,
     sort: req.query.sort,
+    id: req.query.id,
     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,
     user
   })
index 5c7bc6fd93d771b2b4d0fad191f4cb2fae8ef1dd..05e11b1c69c25908dda652f61b6d9f220105bb57 100644 (file)
@@ -1,6 +1,8 @@
 import validator from 'validator'
+
 import { CONSTRAINTS_FIELDS, VIDEO_ABUSE_STATES } from '../../initializers/constants'
 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
 
@@ -16,10 +18,18 @@ function isVideoAbuseStateValid (value: string) {
   return exists(value) && VIDEO_ABUSE_STATES[value] !== undefined
 }
 
+function isAbuseVideoIsValid (value: VideoAbuseVideoIs) {
+  return exists(value) && (
+    value === 'deleted' ||
+    value === 'blacklisted'
+  )
+}
+
 // ---------------------------------------------------------------------------
 
 export {
   isVideoAbuseStateValid,
   isVideoAbuseReasonValid,
+  isAbuseVideoIsValid,
   isVideoAbuseModerationCommentValid
 }
index 7c316fe1343a8b7b196e46c230bb405af499ba0d..901997bcb2d6fa7a78da8cadbbdb43c6a58bb98d 100644 (file)
@@ -1,14 +1,15 @@
 import * as express from 'express'
-import { body, param } from 'express-validator'
-import { isIdOrUUIDValid, isIdValid } from '../../../helpers/custom-validators/misc'
-import { logger } from '../../../helpers/logger'
-import { areValidationErrors } from '../utils'
+import { body, param, query } from 'express-validator'
+import { exists, isIdOrUUIDValid, isIdValid } from '../../../helpers/custom-validators/misc'
 import {
+  isAbuseVideoIsValid,
   isVideoAbuseModerationCommentValid,
   isVideoAbuseReasonValid,
   isVideoAbuseStateValid
 } from '../../../helpers/custom-validators/video-abuses'
+import { logger } from '../../../helpers/logger'
 import { doesVideoAbuseExist, doesVideoExist } from '../../../helpers/middlewares'
+import { areValidationErrors } from '../utils'
 
 const videoAbuseReportValidator = [
   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 {
+  videoAbuseListValidator,
   videoAbuseReportValidator,
   videoAbuseGetValidator,
   videoAbuseUpdateValidator
index fe4596d31b5ff5818033a5ce8a2448ce0eed3748..b2573cd3595da5daede96c3e16652c4753a91a52 100644 (file)
@@ -207,60 +207,13 @@ function buildDirectionAndField (value: string) {
   return { direction, field }
 }
 
-function searchAttribute (sourceField, targetField) {
-  if (sourceField) {
-    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) ]
-    )
-  )
+function searchAttribute (sourceField?: string, targetField?: string) {
+  if (!sourceField) return {}
 
   return {
-    // search is the querystring minus defined filters
-    search: tokens.filter(e => !Object.values(prefixes).some(p => {
-      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
-      }
-    })
+    [targetField]: {
+      [Op.iLike]: `%${sourceField}%`
+    }
   }
 }
 
@@ -286,8 +239,7 @@ export {
   getFollowsSort,
   buildDirectionAndField,
   createSafeIn,
-  searchAttribute,
-  parseQueryStringFilter
+  searchAttribute
 }
 
 // ---------------------------------------------------------------------------
index 6cd2c041808e125d3d9ce551c39b4f839ca32a66..0844f702dea74d5ebf733f5b09604ad2a1ee550b 100644 (file)
@@ -1,6 +1,21 @@
+import * as Bluebird from 'bluebird'
+import { literal, Op } from 'sequelize'
 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'
+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 { VideoAbuse } from '../../../shared/models/videos'
 import {
@@ -8,15 +23,12 @@ import {
   isVideoAbuseReasonValid,
   isVideoAbuseStateValid
 } 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 { MUserAccountId, MVideoAbuse, MVideoAbuseFormattable, MVideoAbuseVideo } from '../../typings/models'
-import * as Bluebird from 'bluebird'
-import { literal, Op } from 'sequelize'
+import { AccountModel } from '../account/account'
+import { buildBlockedAccountSQL, getSort, searchAttribute, throwIfNotValid } from '../utils'
 import { ThumbnailModel } from './thumbnail'
+import { VideoModel } from './video'
 import { VideoBlacklistModel } from './video-blacklist'
 import { ScopeNames as VideoChannelScopeNames, SummaryOptions, VideoChannelModel } from './video-channel'
 
@@ -35,21 +47,22 @@ export enum ScopeNames {
 
     // filters
     id?: number
+
     state?: VideoAbuseState
-    is?: 'deleted' | 'blacklisted'
+    videoIs?: VideoAbuseVideoIs
 
     // accountIds
     serverAccountId: number
     userAccountId: number
   }) => {
-    let where = {
+    const where = {
       reporterAccountId: {
         [Op.notIn]: literal('(' + buildBlockedAccountSQL(options.serverAccountId, options.userAccountId) + ')')
       }
     }
 
     if (options.search) {
-      where = Object.assign(where, {
+      Object.assign(where, {
         [Op.or]: [
           {
             [Op.and]: [
@@ -80,26 +93,18 @@ export enum ScopeNames {
       })
     }
 
-    if (options.id) {
-      where = Object.assign(where, {
-        id: options.id
-      })
-    }
+    if (options.id) Object.assign(where, { id: options.id })
+    if (options.state) Object.assign(where, { state: options.state })
 
-    if (options.state) {
-      where = Object.assign(where, {
-        state: options.state
+    if (options.videoIs === 'deleted') {
+      Object.assign(where, {
+        deletedVideo: {
+          [Op.not]: null
+        }
       })
     }
 
-    let onlyBlacklisted = false
-    if (options.is === 'deleted') {
-      where = Object.assign(where, {
-        deletedVideo: { [Op.not]: null }
-      })
-    } else if (options.is === 'blacklisted') {
-      onlyBlacklisted = true
-    }
+    const onlyBlacklisted = options.videoIs === 'blacklisted'
 
     return {
       attributes: {
@@ -189,7 +194,7 @@ export enum ScopeNames {
         },
         {
           model: VideoModel,
-          required: onlyBlacklisted,
+          required: !!(onlyBlacklisted || options.searchVideo || options.searchReportee || options.searchVideoChannel),
           where: searchAttribute(options.searchVideo, 'name'),
           include: [
             {
@@ -301,11 +306,36 @@ export class VideoAbuseModel extends Model<VideoAbuseModel> {
     start: number
     count: number
     sort: string
-    search?: string
+
     serverAccountId: number
     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 query = {
@@ -317,37 +347,14 @@ export class VideoAbuseModel extends Model<VideoAbuseModel> {
     }
 
     const filters = {
-      ...parseQueryStringFilter(search, {
-        id: {
-          prefix: '#',
-          handler: v => v
-        },
-        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
-          }
-        },
-        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
-        }
-      }),
+      id,
+      search,
+      state,
+      videoIs,
+      searchReportee,
+      searchVideo,
+      searchVideoChannel,
+      searchReporter,
       serverAccountId,
       userAccountId
     }
index bea2177f35be7dcfed4739f8ea6b456f4c9eb91c..e643cb95e529acf6f25cb5d47ef728dd40ae94c3 100644 (file)
@@ -76,6 +76,22 @@ describe('Test video abuses API validators', function () {
         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 () {
index 60fbd2a20a6b7672b5ba7ddec602450aa2c1ebfd..f3b73263256930cd5238d1ffd4119953d1d3949e 100644 (file)
@@ -901,7 +901,7 @@ describe('Test users', function () {
       const reason = 'my super bad 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 res2 = await getUserInformation(server.url, server.accessToken, user17Id, true)
index 26bc3783bbfb6c60a90042c6abcbc7abefd076cd..a96be97f63629f68d3f687ba043ad3fde511896b 100644 (file)
@@ -71,7 +71,7 @@ describe('Test video abuses', 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.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 () {
-    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.data).to.be.an('array')
@@ -106,7 +106,7 @@ describe('Test video abuses', function () {
     expect(abuse.countReportsForReporter).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.data).to.be.an('array')
     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 () {
-    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.data).to.be.an('array')
     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.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.data).to.be.an('array')
     expect(res2.body.data.length).to.equal(1)
@@ -166,7 +166,7 @@ describe('Test video abuses', function () {
     const body = { state: VideoAbuseState.REJECTED }
     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)
   })
 
@@ -174,7 +174,7 @@ describe('Test video abuses', function () {
     const body = { state: VideoAbuseState.ACCEPTED, moderationComment: 'It is valid' }
     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].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 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)
     }
 
@@ -195,7 +195,7 @@ describe('Test video abuses', function () {
     {
       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)
 
       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)
 
-      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)
     }
   })
@@ -216,7 +216,7 @@ describe('Test video abuses', function () {
     {
       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)
 
       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)
 
-      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)
     }
   })
@@ -238,7 +238,7 @@ describe('Test video abuses', function () {
 
     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.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")
@@ -274,7 +274,7 @@ describe('Test video abuses', function () {
     const reason4 = 'my super bad reason 4'
     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[]) {
@@ -299,18 +299,56 @@ describe('Test video abuses', function () {
     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.data.length).to.equal(1)
       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)
     }
   })
 
+  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 () {
     await cleanupTests(servers)
   })
index 7f011ec0ff0acd4baa7754d7809471882fe5ba1d..81582bfc7fd22e0f982c76e903c84536b5ad85fa 100644 (file)
@@ -1,6 +1,8 @@
 import * as request from 'supertest'
 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) {
   const path = '/api/v1/videos/' + videoId + '/abuse'
@@ -13,16 +15,51 @@ function reportVideoAbuse (url: string, token: string, videoId: number | string,
           .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'
 
-  return request(url)
-          .get(path)
-          .query({ sort: 'createdAt' })
-          .set('Accept', 'application/json')
-          .set('Authorization', 'Bearer ' + token)
-          .expect(200)
-          .expect('Content-Type', /json/)
+  const query = {
+    sort: 'createdAt',
+    id,
+    search,
+    state,
+    videoIs,
+    searchReporter,
+    searchReportee,
+    searchVideo,
+    searchVideoChannel
+  }
+
+  return makeGetRequest({
+    url,
+    path,
+    token,
+    query,
+    statusCodeExpected: 200
+  })
 }
 
 function updateVideoAbuse (
diff --git a/shared/models/videos/abuse/video-abuse-video-is.type.ts b/shared/models/videos/abuse/video-abuse-video-is.type.ts
new file mode 100644 (file)
index 0000000..e860189
--- /dev/null
@@ -0,0 +1 @@
+export type VideoAbuseVideoIs = 'deleted' | 'blacklisted'