Server: add video abuse support
authorChocobozzz <florian.bigard@gmail.com>
Wed, 4 Jan 2017 19:59:23 +0000 (20:59 +0100)
committerChocobozzz <florian.bigard@gmail.com>
Wed, 4 Jan 2017 20:05:13 +0000 (21:05 +0100)
35 files changed:
client/src/app/admin/friends/friend-list/friend-list.component.ts
client/src/app/admin/friends/shared/friend.service.ts
config/default.yaml
server.js
server/controllers/api/pods.js
server/controllers/api/remote/videos.js
server/controllers/api/users.js
server/controllers/api/videos.js
server/helpers/custom-validators/index.js
server/helpers/custom-validators/remote/index.js [new file with mode: 0644]
server/helpers/custom-validators/remote/videos.js [new file with mode: 0644]
server/helpers/custom-validators/videos.js
server/helpers/utils.js
server/initializers/constants.js
server/lib/friends.js
server/middlewares/sort.js
server/middlewares/validators/remote.js [deleted file]
server/middlewares/validators/remote/index.js [new file with mode: 0644]
server/middlewares/validators/remote/signature.js [new file with mode: 0644]
server/middlewares/validators/remote/videos.js [new file with mode: 0644]
server/middlewares/validators/sort.js
server/middlewares/validators/videos.js
server/models/request-to-pod.js [new file with mode: 0644]
server/models/requestToPod.js [deleted file]
server/models/video-abuse.js [new file with mode: 0644]
server/models/video-tag.js [new file with mode: 0644]
server/models/video.js
server/models/videoTag.js [deleted file]
server/tests/api/check-params/index.js
server/tests/api/check-params/remotes.js
server/tests/api/check-params/video-abuses.js [new file with mode: 0644]
server/tests/api/friends-advanced.js
server/tests/api/friends-basic.js
server/tests/api/video-abuse.js [new file with mode: 0644]
server/tests/utils/video-abuses.js [new file with mode: 0644]

index 88c4800ee74be25da096aee52e5f780c19fbef21..bec10162cd1e7443f5b1df30cafe2a5efcac8c13 100644 (file)
@@ -30,7 +30,7 @@ export class FriendListComponent implements OnInit {
 
   private getFriends() {
     this.friendService.getFriends().subscribe(
-      friends => this.friends = friends,
+      res => this.friends = res.friends,
 
       err => alert(err.text)
     );
index 8a1ba6b02d3b84706eb3dc3ebba8e6a35ceef2a4..85ac04ba042f8b8e8f29f2703e55dba30ef77cad 100644 (file)
@@ -2,7 +2,7 @@ import { Injectable } from '@angular/core';
 import { Observable } from 'rxjs/Observable';
 
 import { Friend } from './friend.model';
-import { AuthHttp, RestExtractor } from '../../../shared';
+import { AuthHttp, RestExtractor, ResultList } from '../../../shared';
 
 @Injectable()
 export class FriendService {
@@ -13,11 +13,10 @@ export class FriendService {
     private restExtractor: RestExtractor
   ) {}
 
-  getFriends(): Observable<Friend[]> {
+  getFriends() {
     return this.authHttp.get(FriendService.BASE_FRIEND_URL)
-                        // Not implemented as a data list by the server yet
-                        // .map(this.restExtractor.extractDataList)
-                        .map((res) => res.json())
+                        .map(this.restExtractor.extractDataList)
+                        .map(this.extractFriends)
                         .catch((res) => this.restExtractor.handleError(res));
   }
 
@@ -36,4 +35,11 @@ export class FriendService {
                         .map(res => res.status)
                         .catch((res) => this.restExtractor.handleError(res));
   }
+
+  private extractFriends(result: ResultList) {
+    const friends: Friend[] = result.data;
+    const totalFriends = result.total;
+
+    return { friends, totalFriends };
+  }
 }
index 2dd5e05f9dd5b274ff569369fa74dc10ccb9bd89..0939ae4baab5b136b8bfef7808d12b846f2ac7e9 100644 (file)
@@ -10,8 +10,8 @@ database:
   hostname: 'localhost'
   port: 5432
   suffix: '_dev'
-  username: peertube
-  password: peertube
+  username: 'peertube'
+  password: 'peertube'
 
 # From the project root directory
 storage:
index f4ca539078e86205a90b91ba33339c231d24ac84..7503072af9be7b929ab64f2ad30e101dcae6e858 100644 (file)
--- a/server.js
+++ b/server.js
@@ -57,7 +57,8 @@ app.use(expressValidator({
     customValidators.misc,
     customValidators.pods,
     customValidators.users,
-    customValidators.videos
+    customValidators.videos,
+    customValidators.remote.videos
   )
 }))
 
index d9279f1d963f24b2afe7ff94411e63cef86d7e93..38702face2c9a0db53354394e438f2909b91c245 100644 (file)
@@ -5,6 +5,7 @@ const waterfall = require('async/waterfall')
 
 const db = require('../../initializers/database')
 const logger = require('../../helpers/logger')
+const utils = require('../../helpers/utils')
 const friends = require('../../lib/friends')
 const middlewares = require('../../middlewares')
 const admin = middlewares.admin
@@ -36,7 +37,7 @@ router.get('/quitfriends',
 )
 // Post because this is a secured request
 router.post('/remove',
-  signatureValidator,
+  signatureValidator.signature,
   checkSignature,
   removePods
 )
@@ -86,7 +87,7 @@ function listPods (req, res, next) {
   db.Pod.list(function (err, podsList) {
     if (err) return next(err)
 
-    res.json(getFormatedPods(podsList))
+    res.json(utils.getFormatedObjects(podsList, podsList.length))
   })
 }
 
@@ -130,15 +131,3 @@ function quitFriends (req, res, next) {
     res.type('json').status(204).end()
   })
 }
-
-// ---------------------------------------------------------------------------
-
-function getFormatedPods (pods) {
-  const formatedPods = []
-
-  pods.forEach(function (pod) {
-    formatedPods.push(pod.toFormatedJSON())
-  })
-
-  return formatedPods
-}
index 87c49bff918bdaa0c773224bd06530d5223dfd57..d02da44639a9e3946d46652b60a07d9ddd094653 100644 (file)
@@ -7,15 +7,16 @@ const waterfall = require('async/waterfall')
 const db = require('../../../initializers/database')
 const middlewares = require('../../../middlewares')
 const secureMiddleware = middlewares.secure
-const validators = middlewares.validators.remote
+const videosValidators = middlewares.validators.remote.videos
+const signatureValidators = middlewares.validators.remote.signature
 const logger = require('../../../helpers/logger')
 
 const router = express.Router()
 
 router.post('/',
-  validators.signature,
+  signatureValidators.signature,
   secureMiddleware.checkSignature,
-  validators.remoteVideos,
+  videosValidators.remoteVideos,
   remoteVideos
 )
 
@@ -32,19 +33,23 @@ function remoteVideos (req, res, next) {
   // We need to process in the same order to keep consistency
   // TODO: optimization
   eachSeries(requests, function (request, callbackEach) {
-    const videoData = request.data
+    const data = request.data
 
     switch (request.type) {
       case 'add':
-        addRemoteVideo(videoData, fromPod, callbackEach)
+        addRemoteVideo(data, fromPod, callbackEach)
         break
 
       case 'update':
-        updateRemoteVideo(videoData, fromPod, callbackEach)
+        updateRemoteVideo(data, fromPod, callbackEach)
         break
 
       case 'remove':
-        removeRemoteVideo(videoData, fromPod, callbackEach)
+        removeRemoteVideo(data, fromPod, callbackEach)
+        break
+
+      case 'report-abuse':
+        reportAbuseRemoteVideo(data, fromPod, callbackEach)
         break
 
       default:
@@ -164,13 +169,8 @@ function updateRemoteVideo (videoAttributesToUpdate, fromPod, finalCallback) {
     },
 
     function findVideo (t, callback) {
-      db.Video.loadByHostAndRemoteId(fromPod.host, videoAttributesToUpdate.remoteId, function (err, videoInstance) {
-        if (err || !videoInstance) {
-          logger.error('Cannot load video from host and remote id.', { error: err.message })
-          return callback(err)
-        }
-
-        return callback(null, t, videoInstance)
+      fetchVideo(fromPod.host, videoAttributesToUpdate.remoteId, function (err, videoInstance) {
+        return callback(err, t, videoInstance)
       })
     },
 
@@ -225,13 +225,45 @@ function updateRemoteVideo (videoAttributesToUpdate, fromPod, finalCallback) {
 
 function removeRemoteVideo (videoToRemoveData, fromPod, callback) {
   // We need the instance because we have to remove some other stuffs (thumbnail etc)
-  db.Video.loadByHostAndRemoteId(fromPod.host, videoToRemoveData.remoteId, function (err, video) {
+  fetchVideo(fromPod.host, videoToRemoveData.remoteId, function (err, video) {
+    if (err) return callback(err)
+
+    logger.debug('Removing remote video %s.', video.remoteId)
+    video.destroy().asCallback(callback)
+  })
+}
+
+function reportAbuseRemoteVideo (reportData, fromPod, callback) {
+  db.Video.load(reportData.videoRemoteId, function (err, video) {
     if (err || !video) {
-      logger.error('Cannot load video from host and remote id.', { error: err.message })
+      if (!err) err = new Error('video not found')
+
+      logger.error('Cannot load video from host and remote id.', { error: err })
       return callback(err)
     }
 
-    logger.debug('Removing remote video %s.', video.remoteId)
-    video.destroy().asCallback(callback)
+    logger.debug('Reporting remote abuse for video %s.', video.id)
+
+    const videoAbuseData = {
+      reporterUsername: reportData.reporterUsername,
+      reason: reportData.reportReason,
+      reporterPodId: fromPod.id,
+      videoId: video.id
+    }
+
+    db.VideoAbuse.create(videoAbuseData).asCallback(callback)
+  })
+}
+
+function fetchVideo (podHost, remoteId, callback) {
+  db.Video.loadByHostAndRemoteId(podHost, remoteId, function (err, video) {
+    if (err || !video) {
+      if (!err) err = new Error('video not found')
+
+      logger.error('Cannot load video from host and remote id.', { error: err })
+      return callback(err)
+    }
+
+    return callback(null, video)
   })
 }
index 53bf567904da072bebc7c7601827f30f91b66ce8..6cd0e84f7861046b999dea563bc6eaaabefdff9a 100644 (file)
@@ -6,6 +6,7 @@ const waterfall = require('async/waterfall')
 const constants = require('../../initializers/constants')
 const db = require('../../initializers/database')
 const logger = require('../../helpers/logger')
+const utils = require('../../helpers/utils')
 const middlewares = require('../../middlewares')
 const admin = middlewares.admin
 const oAuth = middlewares.oauth
@@ -82,7 +83,7 @@ function listUsers (req, res, next) {
   db.User.listForApi(req.query.start, req.query.count, req.query.sort, function (err, usersList, usersTotal) {
     if (err) return next(err)
 
-    res.json(getFormatedUsers(usersList, usersTotal))
+    res.json(utils.getFormatedObjects(usersList, usersTotal))
   })
 }
 
@@ -121,18 +122,3 @@ function updateUser (req, res, next) {
 function success (req, res, next) {
   res.end()
 }
-
-// ---------------------------------------------------------------------------
-
-function getFormatedUsers (users, usersTotal) {
-  const formatedUsers = []
-
-  users.forEach(function (user) {
-    formatedUsers.push(user.toFormatedJSON())
-  })
-
-  return {
-    total: usersTotal,
-    data: formatedUsers
-  }
-}
index 35d6979e5ecd483daf72cb9302454a2f439e14ab..6829804ecd82746dab55a30b722e26160516d9d1 100644 (file)
@@ -11,6 +11,7 @@ const db = require('../../initializers/database')
 const logger = require('../../helpers/logger')
 const friends = require('../../lib/friends')
 const middlewares = require('../../middlewares')
+const admin = middlewares.admin
 const oAuth = middlewares.oauth
 const pagination = middlewares.pagination
 const validators = middlewares.validators
@@ -43,6 +44,21 @@ const storage = multer.diskStorage({
 
 const reqFiles = multer({ storage: storage }).fields([{ name: 'videofile', maxCount: 1 }])
 
+router.get('/abuse',
+  oAuth.authenticate,
+  admin.ensureIsAdmin,
+  validatorsPagination.pagination,
+  validatorsSort.videoAbusesSort,
+  sort.setVideoAbusesSort,
+  pagination.setPagination,
+  listVideoAbuses
+)
+router.post('/:id/abuse',
+  oAuth.authenticate,
+  validatorsVideos.videoAbuseReport,
+  reportVideoAbuse
+)
+
 router.get('/',
   validatorsPagination.pagination,
   validatorsSort.videosSort,
@@ -283,7 +299,7 @@ function listVideos (req, res, next) {
   db.Video.listForApi(req.query.start, req.query.count, req.query.sort, function (err, videosList, videosTotal) {
     if (err) return next(err)
 
-    res.json(getFormatedVideos(videosList, videosTotal))
+    res.json(utils.getFormatedObjects(videosList, videosTotal))
   })
 }
 
@@ -306,22 +322,45 @@ function searchVideos (req, res, next) {
     function (err, videosList, videosTotal) {
       if (err) return next(err)
 
-      res.json(getFormatedVideos(videosList, videosTotal))
+      res.json(utils.getFormatedObjects(videosList, videosTotal))
     }
   )
 }
 
-// ---------------------------------------------------------------------------
-
-function getFormatedVideos (videos, videosTotal) {
-  const formatedVideos = []
+function listVideoAbuses (req, res, next) {
+  db.VideoAbuse.listForApi(req.query.start, req.query.count, req.query.sort, function (err, abusesList, abusesTotal) {
+    if (err) return next(err)
 
-  videos.forEach(function (video) {
-    formatedVideos.push(video.toFormatedJSON())
+    res.json(utils.getFormatedObjects(abusesList, abusesTotal))
   })
+}
 
-  return {
-    total: videosTotal,
-    data: formatedVideos
+function reportVideoAbuse (req, res, next) {
+  const videoInstance = res.locals.video
+  const reporterUsername = res.locals.oauth.token.User.username
+
+  const abuse = {
+    reporterUsername,
+    reason: req.body.reason,
+    videoId: videoInstance.id,
+    reporterPodId: null // This is our pod that reported this abuse
   }
+
+  db.VideoAbuse.create(abuse).asCallback(function (err) {
+    if (err) return next(err)
+
+    // We send the information to the destination pod
+    if (videoInstance.isOwned() === false) {
+      const reportData = {
+        reporterUsername,
+        reportReason: abuse.reason,
+        videoRemoteId: videoInstance.remoteId
+      }
+
+      friends.reportAbuseVideoToFriend(reportData, videoInstance)
+    }
+
+    return res.type('json').status(204).end()
+  })
 }
+
index 96b5b20b91ab50196f864dc310d11b73669341bb..9383e0304c21a161d2e357b174dd67b61ddba6e2 100644 (file)
@@ -2,12 +2,14 @@
 
 const miscValidators = require('./misc')
 const podsValidators = require('./pods')
+const remoteValidators = require('./remote')
 const usersValidators = require('./users')
 const videosValidators = require('./videos')
 
 const validators = {
   misc: miscValidators,
   pods: podsValidators,
+  remote: remoteValidators,
   users: usersValidators,
   videos: videosValidators
 }
diff --git a/server/helpers/custom-validators/remote/index.js b/server/helpers/custom-validators/remote/index.js
new file mode 100644 (file)
index 0000000..1939a95
--- /dev/null
@@ -0,0 +1,11 @@
+'use strict'
+
+const remoteVideosValidators = require('./videos')
+
+const validators = {
+  videos: remoteVideosValidators
+}
+
+// ---------------------------------------------------------------------------
+
+module.exports = validators
diff --git a/server/helpers/custom-validators/remote/videos.js b/server/helpers/custom-validators/remote/videos.js
new file mode 100644 (file)
index 0000000..c3ca00e
--- /dev/null
@@ -0,0 +1,74 @@
+'use strict'
+
+const videosValidators = require('../videos')
+const miscValidators = require('../misc')
+
+const remoteVideosValidators = {
+  isEachRemoteRequestVideosValid
+}
+
+function isEachRemoteRequestVideosValid (requests) {
+  return miscValidators.isArray(requests) &&
+    requests.every(function (request) {
+      const video = request.data
+      return (
+        isRequestTypeAddValid(request.type) &&
+        videosValidators.isVideoAuthorValid(video.author) &&
+        videosValidators.isVideoDateValid(video.createdAt) &&
+        videosValidators.isVideoDateValid(video.updatedAt) &&
+        videosValidators.isVideoDescriptionValid(video.description) &&
+        videosValidators.isVideoDurationValid(video.duration) &&
+        videosValidators.isVideoInfoHashValid(video.infoHash) &&
+        videosValidators.isVideoNameValid(video.name) &&
+        videosValidators.isVideoTagsValid(video.tags) &&
+        videosValidators.isVideoThumbnailDataValid(video.thumbnailData) &&
+        videosValidators.isVideoRemoteIdValid(video.remoteId) &&
+        videosValidators.isVideoExtnameValid(video.extname)
+      ) ||
+      (
+        isRequestTypeUpdateValid(request.type) &&
+        videosValidators.isVideoDateValid(video.createdAt) &&
+        videosValidators.isVideoDateValid(video.updatedAt) &&
+        videosValidators.isVideoDescriptionValid(video.description) &&
+        videosValidators.isVideoDurationValid(video.duration) &&
+        videosValidators.isVideoInfoHashValid(video.infoHash) &&
+        videosValidators.isVideoNameValid(video.name) &&
+        videosValidators.isVideoTagsValid(video.tags) &&
+        videosValidators.isVideoRemoteIdValid(video.remoteId) &&
+        videosValidators.isVideoExtnameValid(video.extname)
+      ) ||
+      (
+        isRequestTypeRemoveValid(request.type) &&
+        videosValidators.isVideoNameValid(video.name) &&
+        videosValidators.isVideoRemoteIdValid(video.remoteId)
+      ) ||
+      (
+        isRequestTypeReportAbuseValid(request.type) &&
+        videosValidators.isVideoRemoteIdValid(request.data.videoRemoteId) &&
+        videosValidators.isVideoAbuseReasonValid(request.data.reportReason) &&
+        videosValidators.isVideoAbuseReporterUsernameValid(request.data.reporterUsername)
+      )
+    })
+}
+
+// ---------------------------------------------------------------------------
+
+module.exports = remoteVideosValidators
+
+// ---------------------------------------------------------------------------
+
+function isRequestTypeAddValid (value) {
+  return value === 'add'
+}
+
+function isRequestTypeUpdateValid (value) {
+  return value === 'update'
+}
+
+function isRequestTypeRemoveValid (value) {
+  return value === 'remove'
+}
+
+function isRequestTypeReportAbuseValid (value) {
+  return value === 'report-abuse'
+}
index 8448386d94d759958d2ad460c38d3b7a9b21958f..7f727854dfc8e99c2eea8cea0ffa23d0c52cf3cb 100644 (file)
@@ -6,9 +6,9 @@ const constants = require('../../initializers/constants')
 const usersValidators = require('./users')
 const miscValidators = require('./misc')
 const VIDEOS_CONSTRAINTS_FIELDS = constants.CONSTRAINTS_FIELDS.VIDEOS
+const VIDEO_ABUSES_CONSTRAINTS_FIELDS = constants.CONSTRAINTS_FIELDS.VIDEO_ABUSES
 
 const videosValidators = {
-  isEachRemoteVideosValid,
   isVideoAuthorValid,
   isVideoDateValid,
   isVideoDescriptionValid,
@@ -17,45 +17,11 @@ const videosValidators = {
   isVideoNameValid,
   isVideoTagsValid,
   isVideoThumbnailValid,
-  isVideoThumbnailDataValid
-}
-
-function isEachRemoteVideosValid (requests) {
-  return miscValidators.isArray(requests) &&
-    requests.every(function (request) {
-      const video = request.data
-      return (
-        isRequestTypeAddValid(request.type) &&
-        isVideoAuthorValid(video.author) &&
-        isVideoDateValid(video.createdAt) &&
-        isVideoDateValid(video.updatedAt) &&
-        isVideoDescriptionValid(video.description) &&
-        isVideoDurationValid(video.duration) &&
-        isVideoInfoHashValid(video.infoHash) &&
-        isVideoNameValid(video.name) &&
-        isVideoTagsValid(video.tags) &&
-        isVideoThumbnailDataValid(video.thumbnailData) &&
-        isVideoRemoteIdValid(video.remoteId) &&
-        isVideoExtnameValid(video.extname)
-      ) ||
-      (
-        isRequestTypeUpdateValid(request.type) &&
-        isVideoDateValid(video.createdAt) &&
-        isVideoDateValid(video.updatedAt) &&
-        isVideoDescriptionValid(video.description) &&
-        isVideoDurationValid(video.duration) &&
-        isVideoInfoHashValid(video.infoHash) &&
-        isVideoNameValid(video.name) &&
-        isVideoTagsValid(video.tags) &&
-        isVideoRemoteIdValid(video.remoteId) &&
-        isVideoExtnameValid(video.extname)
-      ) ||
-      (
-        isRequestTypeRemoveValid(request.type) &&
-        isVideoNameValid(video.name) &&
-        isVideoRemoteIdValid(video.remoteId)
-      )
-    })
+  isVideoThumbnailDataValid,
+  isVideoExtnameValid,
+  isVideoRemoteIdValid,
+  isVideoAbuseReasonValid,
+  isVideoAbuseReporterUsernameValid
 }
 
 function isVideoAuthorValid (value) {
@@ -107,20 +73,14 @@ function isVideoRemoteIdValid (value) {
   return validator.isUUID(value, 4)
 }
 
-// ---------------------------------------------------------------------------
-
-module.exports = videosValidators
-
-// ---------------------------------------------------------------------------
-
-function isRequestTypeAddValid (value) {
-  return value === 'add'
+function isVideoAbuseReasonValid (value) {
+  return validator.isLength(value, VIDEO_ABUSES_CONSTRAINTS_FIELDS.REASON)
 }
 
-function isRequestTypeUpdateValid (value) {
-  return value === 'update'
+function isVideoAbuseReporterUsernameValid (value) {
+  return usersValidators.isUserUsernameValid(value)
 }
 
-function isRequestTypeRemoveValid (value) {
-  return value === 'remove'
-}
+// ---------------------------------------------------------------------------
+
+module.exports = videosValidators
index 7e0c9823c5a3842ebbd648ce572368902eb42376..9f4b145825758c304c4ef1aeb18402d16f90e364 100644 (file)
@@ -8,7 +8,8 @@ const utils = {
   badRequest,
   cleanForExit,
   generateRandomString,
-  isTestInstance
+  isTestInstance,
+  getFormatedObjects
 }
 
 function badRequest (req, res, next) {
@@ -32,6 +33,19 @@ function isTestInstance () {
   return (process.env.NODE_ENV === 'test')
 }
 
+function getFormatedObjects (objects, objectsTotal) {
+  const formatedObjects = []
+
+  objects.forEach(function (object) {
+    formatedObjects.push(object.toFormatedJSON())
+  })
+
+  return {
+    total: objectsTotal,
+    data: formatedObjects
+  }
+}
+
 // ---------------------------------------------------------------------------
 
 module.exports = utils
index 474a3727790d178ceb2cb956a19ee8da2cf368df..6ba8a9da067ff4acd0c3de1d18b67c5a2f0a3394 100644 (file)
@@ -19,6 +19,7 @@ const SEARCHABLE_COLUMNS = {
 // Sortable columns per schema
 const SORTABLE_COLUMNS = {
   USERS: [ 'username', '-username', 'createdAt', '-createdAt' ],
+  VIDEO_ABUSES: [ 'createdAt', '-createdAt' ],
   VIDEOS: [ 'name', '-name', 'duration', '-duration', 'createdAt', '-createdAt' ]
 }
 
@@ -65,6 +66,9 @@ const CONSTRAINTS_FIELDS = {
     USERNAME: { min: 3, max: 20 }, // Length
     PASSWORD: { min: 6, max: 255 } // Length
   },
+  VIDEO_ABUSES: {
+    REASON: { min: 2, max: 300 } // Length
+  },
   VIDEOS: {
     NAME: { min: 3, max: 50 }, // Length
     DESCRIPTION: { min: 3, max: 250 }, // Length
index 589b79660579af21660349ebdbcd8fc3ab1dfaf0..4afb91b8bf23512d978ce4f3befbfde9d4054796 100644 (file)
@@ -15,6 +15,7 @@ const requests = require('../helpers/requests')
 const friends = {
   addVideoToFriends,
   updateVideoToFriends,
+  reportAbuseVideoToFriend,
   hasFriends,
   getMyCertificate,
   makeFriends,
@@ -23,12 +24,20 @@ const friends = {
   sendOwnedVideosToPod
 }
 
-function addVideoToFriends (video) {
-  createRequest('add', constants.REQUEST_ENDPOINTS.VIDEOS, video)
+function addVideoToFriends (videoData) {
+  createRequest('add', constants.REQUEST_ENDPOINTS.VIDEOS, videoData)
 }
 
-function updateVideoToFriends (video) {
-  createRequest('update', constants.REQUEST_ENDPOINTS.VIDEOS, video)
+function updateVideoToFriends (videoData) {
+  createRequest('update', constants.REQUEST_ENDPOINTS.VIDEOS, videoData)
+}
+
+function removeVideoToFriends (videoParams) {
+  createRequest('remove', constants.REQUEST_ENDPOINTS.VIDEOS, videoParams)
+}
+
+function reportAbuseVideoToFriend (reportData, video) {
+  createRequest('report-abuse', constants.REQUEST_ENDPOINTS.VIDEOS, reportData, [ video.Author.podId ])
 }
 
 function hasFriends (callback) {
@@ -120,10 +129,6 @@ function quitFriends (callback) {
   })
 }
 
-function removeVideoToFriends (videoParams) {
-  createRequest('remove', constants.REQUEST_ENDPOINTS.VIDEOS, videoParams)
-}
-
 function sendOwnedVideosToPod (podId) {
   db.Video.listOwnedAndPopulateAuthorAndTags(function (err, videosList) {
     if (err) {
@@ -152,10 +157,10 @@ module.exports = friends
 // ---------------------------------------------------------------------------
 
 function computeForeignPodsList (host, podsScore, callback) {
-  getForeignPodsList(host, function (err, foreignPodsList) {
+  getForeignPodsList(host, function (err, res) {
     if (err) return callback(err)
 
-    if (!foreignPodsList) foreignPodsList = []
+    const foreignPodsList = res.data
 
     // Let's give 1 point to the pod we ask the friends list
     foreignPodsList.push({ host })
@@ -252,11 +257,11 @@ function makeRequestsToWinningPods (cert, podsList, callback) {
   })
 }
 
-// Wrapper that populate "to" argument with all our friends if it is not specified
-function createRequest (type, endpoint, data, to) {
-  if (to) return _createRequest(type, endpoint, data, to)
+// Wrapper that populate "toIds" argument with all our friends if it is not specified
+function createRequest (type, endpoint, data, toIds) {
+  if (toIds) return _createRequest(type, endpoint, data, toIds)
 
-  // If the "to" pods is not specified, we send the request to all our friends
+  // If the "toIds" pods is not specified, we send the request to all our friends
   db.Pod.listAllIds(function (err, podIds) {
     if (err) {
       logger.error('Cannot get pod ids', { error: err })
@@ -267,13 +272,13 @@ function createRequest (type, endpoint, data, to) {
   })
 }
 
-function _createRequest (type, endpoint, data, to) {
+function _createRequest (type, endpoint, data, toIds) {
   const pods = []
 
   // If there are no destination pods abort
-  if (to.length === 0) return
+  if (toIds.length === 0) return
 
-  to.forEach(function (toPod) {
+  toIds.forEach(function (toPod) {
     pods.push(db.Pod.build({ id: toPod }))
   })
 
index 477e10571e2a6f91cb588e7a097a0f4fc616c395..39e16726591b479e18d6c8eda18ede7c4568fe5a 100644 (file)
@@ -2,6 +2,7 @@
 
 const sortMiddleware = {
   setUsersSort,
+  setVideoAbusesSort,
   setVideosSort
 }
 
@@ -11,6 +12,12 @@ function setUsersSort (req, res, next) {
   return next()
 }
 
+function setVideoAbusesSort (req, res, next) {
+  if (!req.query.sort) req.query.sort = '-createdAt'
+
+  return next()
+}
+
 function setVideosSort (req, res, next) {
   if (!req.query.sort) req.query.sort = '-createdAt'
 
diff --git a/server/middlewares/validators/remote.js b/server/middlewares/validators/remote.js
deleted file mode 100644 (file)
index 858d193..0000000
+++ /dev/null
@@ -1,30 +0,0 @@
-'use strict'
-
-const checkErrors = require('./utils').checkErrors
-const logger = require('../../helpers/logger')
-
-const validatorsRemote = {
-  remoteVideos,
-  signature
-}
-
-function remoteVideos (req, res, next) {
-  req.checkBody('data').isEachRemoteVideosValid()
-
-  logger.debug('Checking remoteVideos parameters', { parameters: req.body })
-
-  checkErrors(req, res, next)
-}
-
-function signature (req, res, next) {
-  req.checkBody('signature.host', 'Should have a signature host').isURL()
-  req.checkBody('signature.signature', 'Should have a signature').notEmpty()
-
-  logger.debug('Checking signature parameters', { parameters: { signatureHost: req.body.signature.host } })
-
-  checkErrors(req, res, next)
-}
-
-// ---------------------------------------------------------------------------
-
-module.exports = validatorsRemote
diff --git a/server/middlewares/validators/remote/index.js b/server/middlewares/validators/remote/index.js
new file mode 100644 (file)
index 0000000..022a2fe
--- /dev/null
@@ -0,0 +1,13 @@
+'use strict'
+
+const remoteSignatureValidators = require('./signature')
+const remoteVideosValidators = require('./videos')
+
+const validators = {
+  signature: remoteSignatureValidators,
+  videos: remoteVideosValidators
+}
+
+// ---------------------------------------------------------------------------
+
+module.exports = validators
diff --git a/server/middlewares/validators/remote/signature.js b/server/middlewares/validators/remote/signature.js
new file mode 100644 (file)
index 0000000..5880a2c
--- /dev/null
@@ -0,0 +1,21 @@
+'use strict'
+
+const checkErrors = require('../utils').checkErrors
+const logger = require('../../../helpers/logger')
+
+const validatorsRemoteSignature = {
+  signature
+}
+
+function signature (req, res, next) {
+  req.checkBody('signature.host', 'Should have a signature host').isURL()
+  req.checkBody('signature.signature', 'Should have a signature').notEmpty()
+
+  logger.debug('Checking signature parameters', { parameters: { signatureHost: req.body.signature.host } })
+
+  checkErrors(req, res, next)
+}
+
+// ---------------------------------------------------------------------------
+
+module.exports = validatorsRemoteSignature
diff --git a/server/middlewares/validators/remote/videos.js b/server/middlewares/validators/remote/videos.js
new file mode 100644 (file)
index 0000000..cf9925b
--- /dev/null
@@ -0,0 +1,20 @@
+'use strict'
+
+const checkErrors = require('../utils').checkErrors
+const logger = require('../../../helpers/logger')
+
+const validatorsRemoteVideos = {
+  remoteVideos
+}
+
+function remoteVideos (req, res, next) {
+  req.checkBody('data').isEachRemoteRequestVideosValid()
+
+  logger.debug('Checking remoteVideos parameters', { parameters: req.body })
+
+  checkErrors(req, res, next)
+}
+
+// ---------------------------------------------------------------------------
+
+module.exports = validatorsRemoteVideos
index 431d3fffd1e8b3062e46bd2eea7e0e3f68a54c0d..b7eec031615597a549c17ed92dd5457b83c69e75 100644 (file)
@@ -6,29 +6,38 @@ const logger = require('../../helpers/logger')
 
 const validatorsSort = {
   usersSort,
+  videoAbusesSort,
   videosSort
 }
 
 function usersSort (req, res, next) {
   const sortableColumns = constants.SORTABLE_COLUMNS.USERS
 
-  req.checkQuery('sort', 'Should have correct sortable column').optional().isIn(sortableColumns)
+  checkSort(req, res, next, sortableColumns)
+}
 
-  logger.debug('Checking sort parameters', { parameters: req.query })
+function videoAbusesSort (req, res, next) {
+  const sortableColumns = constants.SORTABLE_COLUMNS.VIDEO_ABUSES
 
-  checkErrors(req, res, next)
+  checkSort(req, res, next, sortableColumns)
 }
 
 function videosSort (req, res, next) {
   const sortableColumns = constants.SORTABLE_COLUMNS.VIDEOS
 
+  checkSort(req, res, next, sortableColumns)
+}
+
+// ---------------------------------------------------------------------------
+
+module.exports = validatorsSort
+
+// ---------------------------------------------------------------------------
+
+function checkSort (req, res, next, sortableColumns) {
   req.checkQuery('sort', 'Should have correct sortable column').optional().isIn(sortableColumns)
 
   logger.debug('Checking sort parameters', { parameters: req.query })
 
   checkErrors(req, res, next)
 }
-
-// ---------------------------------------------------------------------------
-
-module.exports = validatorsSort
index 295ed05fa054ad99fe2d1d88abe97feb0dfe0f9b..ff18a99c27bbd5c9ee531bfdeef6aeca87a2dc63 100644 (file)
@@ -11,7 +11,9 @@ const validatorsVideos = {
   videosUpdate,
   videosGet,
   videosRemove,
-  videosSearch
+  videosSearch,
+
+  videoAbuseReport
 }
 
 function videosAdd (req, res, next) {
@@ -97,6 +99,17 @@ function videosSearch (req, res, next) {
   checkErrors(req, res, next)
 }
 
+function videoAbuseReport (req, res, next) {
+  req.checkParams('id', 'Should have a valid id').notEmpty().isUUID(4)
+  req.checkBody('reason', 'Should have a valid reason').isVideoAbuseReasonValid()
+
+  logger.debug('Checking videoAbuseReport parameters', { parameters: req.body })
+
+  checkErrors(req, res, function () {
+    checkVideoExists(req.params.id, res, next)
+  })
+}
+
 // ---------------------------------------------------------------------------
 
 module.exports = validatorsVideos
diff --git a/server/models/request-to-pod.js b/server/models/request-to-pod.js
new file mode 100644 (file)
index 0000000..f42a534
--- /dev/null
@@ -0,0 +1,42 @@
+'use strict'
+
+// ---------------------------------------------------------------------------
+
+module.exports = function (sequelize, DataTypes) {
+  const RequestToPod = sequelize.define('RequestToPod', {}, {
+    indexes: [
+      {
+        fields: [ 'requestId' ]
+      },
+      {
+        fields: [ 'podId' ]
+      },
+      {
+        fields: [ 'requestId', 'podId' ],
+        unique: true
+      }
+    ],
+    classMethods: {
+      removePodOf
+    }
+  })
+
+  return RequestToPod
+}
+
+// ---------------------------------------------------------------------------
+
+function removePodOf (requestsIds, podId, callback) {
+  if (!callback) callback = function () {}
+
+  const query = {
+    where: {
+      requestId: {
+        $in: requestsIds
+      },
+      podId: podId
+    }
+  }
+
+  this.destroy(query).asCallback(callback)
+}
diff --git a/server/models/requestToPod.js b/server/models/requestToPod.js
deleted file mode 100644 (file)
index f42a534..0000000
+++ /dev/null
@@ -1,42 +0,0 @@
-'use strict'
-
-// ---------------------------------------------------------------------------
-
-module.exports = function (sequelize, DataTypes) {
-  const RequestToPod = sequelize.define('RequestToPod', {}, {
-    indexes: [
-      {
-        fields: [ 'requestId' ]
-      },
-      {
-        fields: [ 'podId' ]
-      },
-      {
-        fields: [ 'requestId', 'podId' ],
-        unique: true
-      }
-    ],
-    classMethods: {
-      removePodOf
-    }
-  })
-
-  return RequestToPod
-}
-
-// ---------------------------------------------------------------------------
-
-function removePodOf (requestsIds, podId, callback) {
-  if (!callback) callback = function () {}
-
-  const query = {
-    where: {
-      requestId: {
-        $in: requestsIds
-      },
-      podId: podId
-    }
-  }
-
-  this.destroy(query).asCallback(callback)
-}
diff --git a/server/models/video-abuse.js b/server/models/video-abuse.js
new file mode 100644 (file)
index 0000000..766a756
--- /dev/null
@@ -0,0 +1,113 @@
+'use strict'
+
+const constants = require('../initializers/constants')
+const modelUtils = require('./utils')
+const customVideosValidators = require('../helpers/custom-validators').videos
+
+module.exports = function (sequelize, DataTypes) {
+  const VideoAbuse = sequelize.define('VideoAbuse',
+    {
+      reporterUsername: {
+        type: DataTypes.STRING,
+        allowNull: false,
+        validate: {
+          reporterUsernameValid: function (value) {
+            const res = customVideosValidators.isVideoAbuseReporterUsernameValid(value)
+            if (res === false) throw new Error('Video abuse reporter username is not valid.')
+          }
+        }
+      },
+      reason: {
+        type: DataTypes.STRING,
+        allowNull: false,
+        validate: {
+          reasonValid: function (value) {
+            const res = customVideosValidators.isVideoAbuseReasonValid(value)
+            if (res === false) throw new Error('Video abuse reason is not valid.')
+          }
+        }
+      }
+    },
+    {
+      indexes: [
+        {
+          fields: [ 'videoId' ]
+        },
+        {
+          fields: [ 'reporterPodId' ]
+        }
+      ],
+      classMethods: {
+        associate,
+
+        listForApi
+      },
+      instanceMethods: {
+        toFormatedJSON
+      }
+    }
+  )
+
+  return VideoAbuse
+}
+
+// ---------------------------------------------------------------------------
+
+function associate (models) {
+  this.belongsTo(models.Pod, {
+    foreignKey: {
+      name: 'reporterPodId',
+      allowNull: true
+    },
+    onDelete: 'cascade'
+  })
+
+  this.belongsTo(models.Video, {
+    foreignKey: {
+      name: 'videoId',
+      allowNull: false
+    },
+    onDelete: 'cascade'
+  })
+}
+
+function listForApi (start, count, sort, callback) {
+  const query = {
+    offset: start,
+    limit: count,
+    order: [ modelUtils.getSort(sort) ],
+    include: [
+      {
+        model: this.sequelize.models.Pod,
+        required: false
+      }
+    ]
+  }
+
+  return this.findAndCountAll(query).asCallback(function (err, result) {
+    if (err) return callback(err)
+
+    return callback(null, result.rows, result.count)
+  })
+}
+
+function toFormatedJSON () {
+  let reporterPodHost
+
+  if (this.Pod) {
+    reporterPodHost = this.Pod.host
+  } else {
+    // It means it's our video
+    reporterPodHost = constants.CONFIG.WEBSERVER.HOST
+  }
+
+  const json = {
+    id: this.id,
+    reporterPodHost,
+    reason: this.reason,
+    reporterUsername: this.reporterUsername,
+    videoId: this.videoId
+  }
+
+  return json
+}
diff --git a/server/models/video-tag.js b/server/models/video-tag.js
new file mode 100644 (file)
index 0000000..cd9277a
--- /dev/null
@@ -0,0 +1,18 @@
+'use strict'
+
+// ---------------------------------------------------------------------------
+
+module.exports = function (sequelize, DataTypes) {
+  const VideoTag = sequelize.define('VideoTag', {}, {
+    indexes: [
+      {
+        fields: [ 'videoId' ]
+      },
+      {
+        fields: [ 'tagId' ]
+      }
+    ]
+  })
+
+  return VideoTag
+}
index 3fe8368c76c7c88428836a2651bf65841e701a3a..4c197a83502d8aad01a2c429c42d81e48929a9aa 100644 (file)
@@ -248,6 +248,14 @@ function associate (models) {
     through: models.VideoTag,
     onDelete: 'cascade'
   })
+
+  this.hasMany(models.VideoAbuse, {
+    foreignKey: {
+      name: 'videoId',
+      allowNull: false
+    },
+    onDelete: 'cascade'
+  })
 }
 
 function generateMagnetUri () {
diff --git a/server/models/videoTag.js b/server/models/videoTag.js
deleted file mode 100644 (file)
index cd9277a..0000000
+++ /dev/null
@@ -1,18 +0,0 @@
-'use strict'
-
-// ---------------------------------------------------------------------------
-
-module.exports = function (sequelize, DataTypes) {
-  const VideoTag = sequelize.define('VideoTag', {}, {
-    indexes: [
-      {
-        fields: [ 'videoId' ]
-      },
-      {
-        fields: [ 'tagId' ]
-      }
-    ]
-  })
-
-  return VideoTag
-}
index 3d6f0926794084aaa8cf9c9558cef9bdd066bc46..d0824f08a7926c892feb7e3612b83de25f4b7226 100644 (file)
@@ -6,3 +6,4 @@ require('./remotes')
 require('./users')
 require('./requests')
 require('./videos')
+require('./video-abuses')
index 30ba3b697d939da099f5b18c3979b12593da76e5..c1ab9fb2bcd1f7f6f2845b67f2a29b3eb2d95015 100644 (file)
@@ -47,6 +47,10 @@ describe('Test remote videos API validators', function () {
     it('Should check when removing a video')
   })
 
+  describe('When reporting abuse on a video', function () {
+    it('Should check when reporting a video abuse')
+  })
+
   after(function (done) {
     process.kill(-server.app.pid)
 
diff --git a/server/tests/api/check-params/video-abuses.js b/server/tests/api/check-params/video-abuses.js
new file mode 100644 (file)
index 0000000..8cb4ccd
--- /dev/null
@@ -0,0 +1,180 @@
+'use strict'
+
+const request = require('supertest')
+const series = require('async/series')
+
+const loginUtils = require('../../utils/login')
+const requestsUtils = require('../../utils/requests')
+const serversUtils = require('../../utils/servers')
+const usersUtils = require('../../utils/users')
+const videosUtils = require('../../utils/videos')
+
+describe('Test video abuses API validators', function () {
+  let server = null
+  let userAccessToken = null
+
+  // ---------------------------------------------------------------
+
+  before(function (done) {
+    this.timeout(20000)
+
+    series([
+      function (next) {
+        serversUtils.flushTests(next)
+      },
+      function (next) {
+        serversUtils.runServer(1, function (server1) {
+          server = server1
+
+          next()
+        })
+      },
+      function (next) {
+        loginUtils.loginAndGetAccessToken(server, function (err, token) {
+          if (err) throw err
+          server.accessToken = token
+
+          next()
+        })
+      },
+      function (next) {
+        const username = 'user1'
+        const password = 'my super password'
+
+        usersUtils.createUser(server.url, server.accessToken, username, password, next)
+      },
+      function (next) {
+        const user = {
+          username: 'user1',
+          password: 'my super password'
+        }
+
+        loginUtils.getUserAccessToken(server, user, function (err, accessToken) {
+          if (err) throw err
+
+          userAccessToken = accessToken
+
+          next()
+        })
+      },
+      // Upload some videos on each pods
+      function (next) {
+        const name = 'my super name for pod'
+        const description = 'my super description for pod'
+        const tags = [ 'tag' ]
+        const file = 'video_short2.webm'
+        videosUtils.uploadVideo(server.url, server.accessToken, name, description, tags, file, next)
+      },
+      function (next) {
+        videosUtils.getVideosList(server.url, function (err, res) {
+          if (err) throw err
+
+          const videos = res.body.data
+          server.video = videos[0]
+
+          next()
+        })
+      }
+    ], done)
+  })
+
+  describe('When listing video abuses', function () {
+    const path = '/api/v1/videos/abuse'
+
+    it('Should fail with a bad start pagination', function (done) {
+      request(server.url)
+        .get(path)
+        .query({ start: 'hello' })
+        .set('Authorization', 'Bearer ' + server.accessToken)
+        .set('Accept', 'application/json')
+        .expect(400, done)
+    })
+
+    it('Should fail with a bad count pagination', function (done) {
+      request(server.url)
+        .get(path)
+        .query({ count: 'hello' })
+        .set('Accept', 'application/json')
+        .set('Authorization', 'Bearer ' + server.accessToken)
+        .expect(400, done)
+    })
+
+    it('Should fail with an incorrect sort', function (done) {
+      request(server.url)
+        .get(path)
+        .query({ sort: 'hello' })
+        .set('Accept', 'application/json')
+        .set('Authorization', 'Bearer ' + server.accessToken)
+        .expect(400, done)
+    })
+
+    it('Should fail with a non authenticated user', function (done) {
+      request(server.url)
+        .get(path)
+        .query({ sort: 'hello' })
+        .set('Accept', 'application/json')
+        .expect(401, done)
+    })
+
+    it('Should fail with a non admin user', function (done) {
+      request(server.url)
+        .get(path)
+        .query({ sort: 'hello' })
+        .set('Accept', 'application/json')
+        .set('Authorization', 'Bearer ' + userAccessToken)
+        .expect(403, done)
+    })
+  })
+
+  describe('When reporting a video abuse', function () {
+    const basePath = '/api/v1/videos/'
+
+    it('Should fail with nothing', function (done) {
+      const path = basePath + server.video + '/abuse'
+      const data = {}
+      requestsUtils.makePostBodyRequest(server.url, path, server.accessToken, data, done)
+    })
+
+    it('Should fail with a wrong video', function (done) {
+      const wrongPath = '/api/v1/videos/blabla/abuse'
+      const data = {}
+      requestsUtils.makePostBodyRequest(server.url, wrongPath, server.accessToken, data, done)
+    })
+
+    it('Should fail with a non authenticated user', function (done) {
+      const data = {}
+      const path = basePath + server.video + '/abuse'
+      requestsUtils.makePostBodyRequest(server.url, path, 'hello', data, done, 401)
+    })
+
+    it('Should fail with a reason too short', function (done) {
+      const data = {
+        reason: 'h'
+      }
+      const path = basePath + server.video + '/abuse'
+      requestsUtils.makePostBodyRequest(server.url, path, server.accessToken, data, done)
+    })
+
+    it('Should fail with a reason too big', function (done) {
+      const data = {
+        reason: '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef' +
+                '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef' +
+                '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef' +
+                '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef'
+      }
+      const path = basePath + server.video + '/abuse'
+      requestsUtils.makePostBodyRequest(server.url, path, server.accessToken, data, done)
+    })
+  })
+
+  after(function (done) {
+    process.kill(-server.app.pid)
+
+    // Keep the logs if the test failed
+    if (this.ok) {
+      serversUtils.flushTests(done)
+    } else {
+      done()
+    }
+  })
+})
index 0a2d58d82ee47e9ed342b01a9eead089d498dee5..708138bc97b3011c98bb6f120f9deae9a9150cb9 100644 (file)
@@ -86,7 +86,7 @@ describe('Test advanced friends', function () {
         getFriendsList(5, function (err, res) {
           if (err) throw err
 
-          expect(res.body.length).to.equal(0)
+          expect(res.body.data.length).to.equal(0)
 
           done()
         })
@@ -111,7 +111,7 @@ describe('Test advanced friends', function () {
           getFriendsList(i, function (err, res) {
             if (err) throw err
 
-            expect(res.body.length).to.equal(0)
+            expect(res.body.data.length).to.equal(0)
 
             callback()
           })
@@ -140,7 +140,7 @@ describe('Test advanced friends', function () {
           getFriendsList(i, function (err, res) {
             if (err) throw err
 
-            expect(res.body.length).to.equal(3)
+            expect(res.body.data.length).to.equal(3)
 
             callback()
           })
@@ -182,7 +182,7 @@ describe('Test advanced friends', function () {
           if (err) throw err
 
           // Pod 4 didn't know pod 1 and 2 removed it
-          expect(res.body.length).to.equal(3)
+          expect(res.body.data.length).to.equal(3)
           next()
         })
       },
@@ -200,7 +200,7 @@ describe('Test advanced friends', function () {
           if (err) throw err
 
           // Pod 4 should not be our friend
-          const result = res.body
+          const result = res.body.data
           expect(result.length).to.equal(3)
           for (const pod of result) {
             expect(pod.host).not.equal(servers[3].host)
index 3a904dbd7a1e224f97a63b5dc78109b156ed4dfd..6f37ff29101ec883110f4a15bf3b99656a4a8b7c 100644 (file)
@@ -28,7 +28,7 @@ describe('Test basic friends', function () {
     podsUtils.getFriendsList(serverToTest.url, function (err, res) {
       if (err) throw err
 
-      const result = res.body
+      const result = res.body.data
       expect(result).to.be.an('array')
       expect(result.length).to.equal(2)
 
@@ -65,7 +65,7 @@ describe('Test basic friends', function () {
       podsUtils.getFriendsList(server.url, function (err, res) {
         if (err) throw err
 
-        const result = res.body
+        const result = res.body.data
         expect(result).to.be.an('array')
         expect(result.length).to.equal(0)
         callback()
@@ -90,7 +90,7 @@ describe('Test basic friends', function () {
         podsUtils.getFriendsList(servers[1].url, function (err, res) {
           if (err) throw err
 
-          const result = res.body
+          const result = res.body.data
           expect(result).to.be.an('array')
           expect(result.length).to.equal(1)
 
@@ -107,7 +107,7 @@ describe('Test basic friends', function () {
         podsUtils.getFriendsList(servers[2].url, function (err, res) {
           if (err) throw err
 
-          const result = res.body
+          const result = res.body.data
           expect(result).to.be.an('array')
           expect(result.length).to.equal(1)
 
@@ -154,7 +154,7 @@ describe('Test basic friends', function () {
         podsUtils.getFriendsList(servers[1].url, function (err, res) {
           if (err) throw err
 
-          const result = res.body
+          const result = res.body.data
           expect(result).to.be.an('array')
           expect(result.length).to.equal(0)
 
@@ -167,7 +167,7 @@ describe('Test basic friends', function () {
           podsUtils.getFriendsList(url, function (err, res) {
             if (err) throw err
 
-            const result = res.body
+            const result = res.body.data
             expect(result).to.be.an('array')
             expect(result.length).to.equal(1)
             expect(result[0].host).not.to.be.equal(servers[1].host)
diff --git a/server/tests/api/video-abuse.js b/server/tests/api/video-abuse.js
new file mode 100644 (file)
index 0000000..58db17c
--- /dev/null
@@ -0,0 +1,191 @@
+'use strict'
+
+const chai = require('chai')
+const each = require('async/each')
+const expect = chai.expect
+const series = require('async/series')
+
+const loginUtils = require('../utils/login')
+const podsUtils = require('../utils/pods')
+const serversUtils = require('../utils/servers')
+const videosUtils = require('../utils/videos')
+const videoAbusesUtils = require('../utils/video-abuses')
+
+describe('Test video abuses', function () {
+  let servers = []
+
+  before(function (done) {
+    this.timeout(30000)
+
+    series([
+      // Run servers
+      function (next) {
+        serversUtils.flushAndRunMultipleServers(2, function (serversRun) {
+          servers = serversRun
+          next()
+        })
+      },
+      // Get the access tokens
+      function (next) {
+        each(servers, function (server, callbackEach) {
+          loginUtils.loginAndGetAccessToken(server, function (err, accessToken) {
+            if (err) return callbackEach(err)
+
+            server.accessToken = accessToken
+            callbackEach()
+          })
+        }, next)
+      },
+      // Pod 1 make friends too
+      function (next) {
+        const server = servers[0]
+        podsUtils.makeFriends(server.url, server.accessToken, next)
+      },
+      // Upload some videos on each pods
+      function (next) {
+        const name = 'my super name for pod 1'
+        const description = 'my super description for pod 1'
+        const tags = [ 'tag' ]
+        const file = 'video_short2.webm'
+        videosUtils.uploadVideo(servers[0].url, servers[0].accessToken, name, description, tags, file, next)
+      },
+      function (next) {
+        const name = 'my super name for pod 2'
+        const description = 'my super description for pod 2'
+        const tags = [ 'tag' ]
+        const file = 'video_short2.webm'
+        videosUtils.uploadVideo(servers[1].url, servers[1].accessToken, name, description, tags, file, next)
+      },
+      // Wait videos propagation
+      function (next) {
+        setTimeout(next, 11000)
+      },
+      function (next) {
+        videosUtils.getVideosList(servers[0].url, function (err, res) {
+          if (err) throw err
+
+          const videos = res.body.data
+
+          expect(videos.length).to.equal(2)
+
+          servers[0].video = videos.find(function (video) { return video.name === 'my super name for pod 1' })
+          servers[1].video = videos.find(function (video) { return video.name === 'my super name for pod 2' })
+
+          next()
+        })
+      }
+    ], done)
+  })
+
+  it('Should not have video abuses', function (done) {
+    videoAbusesUtils.getVideoAbusesList(servers[0].url, servers[0].accessToken, function (err, res) {
+      if (err) throw err
+
+      expect(res.body.total).to.equal(0)
+      expect(res.body.data).to.be.an('array')
+      expect(res.body.data.length).to.equal(0)
+
+      done()
+    })
+  })
+
+  it('Should report abuse on a local video', function (done) {
+    this.timeout(15000)
+
+    const reason = 'my super bad reason'
+    videoAbusesUtils.reportVideoAbuse(servers[0].url, servers[0].accessToken, servers[0].video.id, reason, function (err) {
+      if (err) throw err
+
+      // We wait requests propagation, even if the pod 1 is not supposed to make a request to pod 2
+      setTimeout(done, 11000)
+    })
+  })
+
+  it('Should have 1 video abuses on pod 1 and 0 on pod 2', function (done) {
+    videoAbusesUtils.getVideoAbusesList(servers[0].url, servers[0].accessToken, function (err, res) {
+      if (err) throw err
+
+      expect(res.body.total).to.equal(1)
+      expect(res.body.data).to.be.an('array')
+      expect(res.body.data.length).to.equal(1)
+
+      const abuse = res.body.data[0]
+      expect(abuse.reason).to.equal('my super bad reason')
+      expect(abuse.reporterUsername).to.equal('root')
+      expect(abuse.reporterPodHost).to.equal('localhost:9001')
+      expect(abuse.videoId).to.equal(servers[0].video.id)
+
+      videoAbusesUtils.getVideoAbusesList(servers[1].url, servers[1].accessToken, function (err, res) {
+        if (err) throw err
+
+        expect(res.body.total).to.equal(0)
+        expect(res.body.data).to.be.an('array')
+        expect(res.body.data.length).to.equal(0)
+
+        done()
+      })
+    })
+  })
+
+  it('Should report abuse on a remote video', function (done) {
+    this.timeout(15000)
+
+    const reason = 'my super bad reason 2'
+    videoAbusesUtils.reportVideoAbuse(servers[0].url, servers[0].accessToken, servers[1].video.id, reason, function (err) {
+      if (err) throw err
+
+      // We wait requests propagation
+      setTimeout(done, 11000)
+    })
+  })
+
+  it('Should have 2 video abuse on pod 1 and 1 on pod 2', function (done) {
+    videoAbusesUtils.getVideoAbusesList(servers[0].url, servers[0].accessToken, function (err, res) {
+      if (err) throw err
+
+      expect(res.body.total).to.equal(2)
+      expect(res.body.data).to.be.an('array')
+      expect(res.body.data.length).to.equal(2)
+
+      let abuse = res.body.data[0]
+      expect(abuse.reason).to.equal('my super bad reason')
+      expect(abuse.reporterUsername).to.equal('root')
+      expect(abuse.reporterPodHost).to.equal('localhost:9001')
+      expect(abuse.videoId).to.equal(servers[0].video.id)
+
+      abuse = res.body.data[1]
+      expect(abuse.reason).to.equal('my super bad reason 2')
+      expect(abuse.reporterUsername).to.equal('root')
+      expect(abuse.reporterPodHost).to.equal('localhost:9001')
+      expect(abuse.videoId).to.equal(servers[1].video.id)
+
+      videoAbusesUtils.getVideoAbusesList(servers[1].url, servers[1].accessToken, function (err, res) {
+        if (err) throw err
+
+        expect(res.body.total).to.equal(1)
+        expect(res.body.data).to.be.an('array')
+        expect(res.body.data.length).to.equal(1)
+
+        let abuse = res.body.data[0]
+        expect(abuse.reason).to.equal('my super bad reason 2')
+        expect(abuse.reporterUsername).to.equal('root')
+        expect(abuse.reporterPodHost).to.equal('localhost:9001')
+
+        done()
+      })
+    })
+  })
+
+  after(function (done) {
+    servers.forEach(function (server) {
+      process.kill(-server.app.pid)
+    })
+
+    // Keep the logs if the test failed
+    if (this.ok) {
+      serversUtils.flushTests(done)
+    } else {
+      done()
+    }
+  })
+})
diff --git a/server/tests/utils/video-abuses.js b/server/tests/utils/video-abuses.js
new file mode 100644 (file)
index 0000000..596c824
--- /dev/null
@@ -0,0 +1,73 @@
+'use strict'
+
+const request = require('supertest')
+
+const videosUtils = {
+  getVideoAbusesList,
+  getVideoAbusesListPagination,
+  getVideoAbusesListSort,
+  reportVideoAbuse
+}
+
+// ---------------------- Export functions --------------------
+
+function reportVideoAbuse (url, token, videoId, reason, specialStatus, end) {
+  if (!end) {
+    end = specialStatus
+    specialStatus = 204
+  }
+
+  const path = '/api/v1/videos/' + videoId + '/abuse'
+
+  request(url)
+    .post(path)
+    .set('Accept', 'application/json')
+    .set('Authorization', 'Bearer ' + token)
+    .send({ reason })
+    .expect(specialStatus)
+    .end(end)
+}
+
+function getVideoAbusesList (url, token, end) {
+  const path = '/api/v1/videos/abuse'
+
+  request(url)
+    .get(path)
+    .query({ sort: 'createdAt' })
+    .set('Accept', 'application/json')
+    .set('Authorization', 'Bearer ' + token)
+    .expect(200)
+    .expect('Content-Type', /json/)
+    .end(end)
+}
+
+function getVideoAbusesListPagination (url, token, start, count, end) {
+  const path = '/api/v1/videos/abuse'
+
+  request(url)
+    .get(path)
+    .query({ start: start })
+    .query({ count: count })
+    .set('Accept', 'application/json')
+    .set('Authorization', 'Bearer ' + token)
+    .expect(200)
+    .expect('Content-Type', /json/)
+    .end(end)
+}
+
+function getVideoAbusesListSort (url, token, sort, end) {
+  const path = '/api/v1/videos/abuse'
+
+  request(url)
+    .get(path)
+    .query({ sort: sort })
+    .set('Accept', 'application/json')
+    .set('Authorization', 'Bearer ' + token)
+    .expect(200)
+    .expect('Content-Type', /json/)
+    .end(end)
+}
+
+// ---------------------------------------------------------------------------
+
+module.exports = videosUtils