From 0d0e8dd0904b380b70e19ebcb4763d65601c4632 Mon Sep 17 00:00:00 2001
From: Chocobozzz <florian.bigard@gmail.com>
Date: Fri, 10 Nov 2017 14:34:45 +0100
Subject: Continue activitypub

---
 server/controllers/activitypub/inbox.ts  |  30 +-
 server/controllers/activitypub/pods.ts   | 138 ++---
 server/controllers/activitypub/videos.ts | 928 +++++++++++--------------------
 server/controllers/api/videos/index.ts   |  30 +-
 4 files changed, 435 insertions(+), 691 deletions(-)

(limited to 'server/controllers')

diff --git a/server/controllers/activitypub/inbox.ts b/server/controllers/activitypub/inbox.ts
index 79d989c2c..eee217650 100644
--- a/server/controllers/activitypub/inbox.ts
+++ b/server/controllers/activitypub/inbox.ts
@@ -1,26 +1,15 @@
 import * as express from 'express'
-
-import {
-  processCreateActivity,
-  processUpdateActivity,
-  processFlagActivity
-} from '../../lib'
-import {
-  Activity,
-  ActivityType,
-  RootActivity,
-  ActivityPubCollection,
-  ActivityPubOrderedCollection
-} from '../../../shared'
-import {
-  signatureValidator,
-  checkSignature,
-  asyncMiddleware
-} from '../../middlewares'
+import { Activity, ActivityPubCollection, ActivityPubOrderedCollection, ActivityType, RootActivity } from '../../../shared'
 import { logger } from '../../helpers'
+import { isActivityValid } from '../../helpers/custom-validators/activitypub/activity'
+import { processCreateActivity, processFlagActivity, processUpdateActivity } from '../../lib'
+import { processAddActivity } from '../../lib/activitypub/process-add'
+import { asyncMiddleware, checkSignature, signatureValidator } from '../../middlewares'
+import { activityPubValidator } from '../../middlewares/validators/activitypub/activity'
 
 const processActivity: { [ P in ActivityType ]: (activity: Activity) => Promise<any> } = {
   Create: processCreateActivity,
+  Add: processAddActivity,
   Update: processUpdateActivity,
   Flag: processFlagActivity
 }
@@ -30,7 +19,7 @@ const inboxRouter = express.Router()
 inboxRouter.post('/',
   signatureValidator,
   asyncMiddleware(checkSignature),
-  // inboxValidator,
+  activityPubValidator,
   asyncMiddleware(inboxController)
 )
 
@@ -54,6 +43,9 @@ async function inboxController (req: express.Request, res: express.Response, nex
     activities = [ rootActivity as Activity ]
   }
 
+  // Only keep activities we are able to process
+  activities = activities.filter(a => isActivityValid(a))
+
   await processActivities(activities)
 
   res.status(204).end()
diff --git a/server/controllers/activitypub/pods.ts b/server/controllers/activitypub/pods.ts
index 326eb61ac..6cce57c1c 100644
--- a/server/controllers/activitypub/pods.ts
+++ b/server/controllers/activitypub/pods.ts
@@ -1,69 +1,69 @@
-import * as express from 'express'
-
-import { database as db } from '../../../initializers/database'
-import {
-  checkSignature,
-  signatureValidator,
-  setBodyHostPort,
-  remotePodsAddValidator,
-  asyncMiddleware
-} from '../../../middlewares'
-import { sendOwnedDataToPod } from '../../../lib'
-import { getMyPublicCert, getFormattedObjects } from '../../../helpers'
-import { CONFIG } from '../../../initializers'
-import { PodInstance } from '../../../models'
-import { PodSignature, Pod as FormattedPod } from '../../../../shared'
-
-const remotePodsRouter = express.Router()
-
-remotePodsRouter.post('/remove',
-  signatureValidator,
-  checkSignature,
-  asyncMiddleware(removePods)
-)
-
-remotePodsRouter.post('/list',
-  asyncMiddleware(remotePodsList)
-)
-
-remotePodsRouter.post('/add',
-  setBodyHostPort, // We need to modify the host before running the validator!
-  remotePodsAddValidator,
-  asyncMiddleware(addPods)
-)
-
-// ---------------------------------------------------------------------------
-
-export {
-  remotePodsRouter
-}
-
-// ---------------------------------------------------------------------------
-
-async function addPods (req: express.Request, res: express.Response, next: express.NextFunction) {
-  const information = req.body
-
-  const pod = db.Pod.build(information)
-  const podCreated = await pod.save()
-
-  await sendOwnedDataToPod(podCreated.id)
-
-  const cert = await getMyPublicCert()
-  return res.json({ cert, email: CONFIG.ADMIN.EMAIL })
-}
-
-async function remotePodsList (req: express.Request, res: express.Response, next: express.NextFunction) {
-  const pods = await db.Pod.list()
-
-  return res.json(getFormattedObjects<FormattedPod, PodInstance>(pods, pods.length))
-}
-
-async function removePods (req: express.Request, res: express.Response, next: express.NextFunction) {
-  const signature: PodSignature = req.body.signature
-  const host = signature.host
-
-  const pod = await db.Pod.loadByHost(host)
-  await pod.destroy()
-
-  return res.type('json').status(204).end()
-}
+// import * as express from 'express'
+//
+// import { database as db } from '../../../initializers/database'
+// import {
+//   checkSignature,
+//   signatureValidator,
+//   setBodyHostPort,
+//   remotePodsAddValidator,
+//   asyncMiddleware
+// } from '../../../middlewares'
+// import { sendOwnedDataToPod } from '../../../lib'
+// import { getMyPublicCert, getFormattedObjects } from '../../../helpers'
+// import { CONFIG } from '../../../initializers'
+// import { PodInstance } from '../../../models'
+// import { PodSignature, Pod as FormattedPod } from '../../../../shared'
+//
+// const remotePodsRouter = express.Router()
+//
+// remotePodsRouter.post('/remove',
+//   signatureValidator,
+//   checkSignature,
+//   asyncMiddleware(removePods)
+// )
+//
+// remotePodsRouter.post('/list',
+//   asyncMiddleware(remotePodsList)
+// )
+//
+// remotePodsRouter.post('/add',
+//   setBodyHostPort, // We need to modify the host before running the validator!
+//   remotePodsAddValidator,
+//   asyncMiddleware(addPods)
+// )
+//
+// // ---------------------------------------------------------------------------
+//
+// export {
+//   remotePodsRouter
+// }
+//
+// // ---------------------------------------------------------------------------
+//
+// async function addPods (req: express.Request, res: express.Response, next: express.NextFunction) {
+//   const information = req.body
+//
+//   const pod = db.Pod.build(information)
+//   const podCreated = await pod.save()
+//
+//   await sendOwnedDataToPod(podCreated.id)
+//
+//   const cert = await getMyPublicCert()
+//   return res.json({ cert, email: CONFIG.ADMIN.EMAIL })
+// }
+//
+// async function remotePodsList (req: express.Request, res: express.Response, next: express.NextFunction) {
+//   const pods = await db.Pod.list()
+//
+//   return res.json(getFormattedObjects<FormattedPod, PodInstance>(pods, pods.length))
+// }
+//
+// async function removePods (req: express.Request, res: express.Response, next: express.NextFunction) {
+//   const signature: PodSignature = req.body.signature
+//   const host = signature.host
+//
+//   const pod = await db.Pod.loadByHost(host)
+//   await pod.destroy()
+//
+//   return res.type('json').status(204).end()
+// }
diff --git a/server/controllers/activitypub/videos.ts b/server/controllers/activitypub/videos.ts
index cba47f0a1..9a1868ff7 100644
--- a/server/controllers/activitypub/videos.ts
+++ b/server/controllers/activitypub/videos.ts
@@ -1,589 +1,339 @@
-import * as express from 'express'
-import * as Bluebird from 'bluebird'
-import * as Sequelize from 'sequelize'
-
-import { database as db } from '../../../initializers/database'
-import {
-  REQUEST_ENDPOINT_ACTIONS,
-  REQUEST_ENDPOINTS,
-  REQUEST_VIDEO_EVENT_TYPES,
-  REQUEST_VIDEO_QADU_TYPES
-} from '../../../initializers'
-import {
-  checkSignature,
-  signatureValidator,
-  remoteVideosValidator,
-  remoteQaduVideosValidator,
-  remoteEventsVideosValidator
-} from '../../../middlewares'
-import { logger, retryTransactionWrapper, resetSequelizeInstance } from '../../../helpers'
-import { quickAndDirtyUpdatesVideoToFriends, fetchVideoChannelByHostAndUUID } from '../../../lib'
-import { PodInstance, VideoFileInstance } from '../../../models'
-import {
-  RemoteVideoRequest,
-  RemoteVideoCreateData,
-  RemoteVideoUpdateData,
-  RemoteVideoRemoveData,
-  RemoteVideoReportAbuseData,
-  RemoteQaduVideoRequest,
-  RemoteQaduVideoData,
-  RemoteVideoEventRequest,
-  RemoteVideoEventData,
-  RemoteVideoChannelCreateData,
-  RemoteVideoChannelUpdateData,
-  RemoteVideoChannelRemoveData,
-  RemoteVideoAuthorRemoveData,
-  RemoteVideoAuthorCreateData
-} from '../../../../shared'
-import { VideoInstance } from '../../../models/video/video-interface'
-
-const ENDPOINT_ACTIONS = REQUEST_ENDPOINT_ACTIONS[REQUEST_ENDPOINTS.VIDEOS]
-
-// Functions to call when processing a remote request
-// FIXME: use RemoteVideoRequestType as id type
-const functionsHash: { [ id: string ]: (...args) => Promise<any> } = {}
-functionsHash[ENDPOINT_ACTIONS.ADD_VIDEO] = addRemoteVideoRetryWrapper
-functionsHash[ENDPOINT_ACTIONS.UPDATE_VIDEO] = updateRemoteVideoRetryWrapper
-functionsHash[ENDPOINT_ACTIONS.REMOVE_VIDEO] = removeRemoteVideoRetryWrapper
-functionsHash[ENDPOINT_ACTIONS.ADD_CHANNEL] = addRemoteVideoChannelRetryWrapper
-functionsHash[ENDPOINT_ACTIONS.UPDATE_CHANNEL] = updateRemoteVideoChannelRetryWrapper
-functionsHash[ENDPOINT_ACTIONS.REMOVE_CHANNEL] = removeRemoteVideoChannelRetryWrapper
-functionsHash[ENDPOINT_ACTIONS.REPORT_ABUSE] = reportAbuseRemoteVideoRetryWrapper
-functionsHash[ENDPOINT_ACTIONS.ADD_AUTHOR] = addRemoteVideoAuthorRetryWrapper
-functionsHash[ENDPOINT_ACTIONS.REMOVE_AUTHOR] = removeRemoteVideoAuthorRetryWrapper
-
-const remoteVideosRouter = express.Router()
-
-remoteVideosRouter.post('/',
-  signatureValidator,
-  checkSignature,
-  remoteVideosValidator,
-  remoteVideos
-)
-
-remoteVideosRouter.post('/qadu',
-  signatureValidator,
-  checkSignature,
-  remoteQaduVideosValidator,
-  remoteVideosQadu
-)
-
-remoteVideosRouter.post('/events',
-  signatureValidator,
-  checkSignature,
-  remoteEventsVideosValidator,
-  remoteVideosEvents
-)
-
-// ---------------------------------------------------------------------------
-
-export {
-  remoteVideosRouter
-}
-
-// ---------------------------------------------------------------------------
-
-function remoteVideos (req: express.Request, res: express.Response, next: express.NextFunction) {
-  const requests: RemoteVideoRequest[] = req.body.data
-  const fromPod = res.locals.secure.pod
-
-  // We need to process in the same order to keep consistency
-  Bluebird.each(requests, request => {
-    const data = request.data
-
-    // Get the function we need to call in order to process the request
-    const fun = functionsHash[request.type]
-    if (fun === undefined) {
-      logger.error('Unknown remote request type %s.', request.type)
-      return
-    }
-
-    return fun.call(this, data, fromPod)
-  })
-  .catch(err => logger.error('Error managing remote videos.', err))
-
-  // Don't block the other pod
-  return res.type('json').status(204).end()
-}
-
-function remoteVideosQadu (req: express.Request, res: express.Response, next: express.NextFunction) {
-  const requests: RemoteQaduVideoRequest[] = req.body.data
-  const fromPod = res.locals.secure.pod
-
-  Bluebird.each(requests, request => {
-    const videoData = request.data
-
-    return quickAndDirtyUpdateVideoRetryWrapper(videoData, fromPod)
-  })
-  .catch(err => logger.error('Error managing remote videos.', err))
-
-  return res.type('json').status(204).end()
-}
-
-function remoteVideosEvents (req: express.Request, res: express.Response, next: express.NextFunction) {
-  const requests: RemoteVideoEventRequest[] = req.body.data
-  const fromPod = res.locals.secure.pod
-
-  Bluebird.each(requests, request => {
-    const eventData = request.data
-
-    return processVideosEventsRetryWrapper(eventData, fromPod)
-  })
-  .catch(err => logger.error('Error managing remote videos.', err))
-
-  return res.type('json').status(204).end()
-}
-
-async function processVideosEventsRetryWrapper (eventData: RemoteVideoEventData, fromPod: PodInstance) {
-  const options = {
-    arguments: [ eventData, fromPod ],
-    errorMessage: 'Cannot process videos events with many retries.'
-  }
-
-  await retryTransactionWrapper(processVideosEvents, options)
-}
-
-async function processVideosEvents (eventData: RemoteVideoEventData, fromPod: PodInstance) {
-  await db.sequelize.transaction(async t => {
-    const sequelizeOptions = { transaction: t }
-    const videoInstance = await fetchLocalVideoByUUID(eventData.uuid, t)
-
-    let columnToUpdate
-    let qaduType
-
-    switch (eventData.eventType) {
-    case REQUEST_VIDEO_EVENT_TYPES.VIEWS:
-      columnToUpdate = 'views'
-      qaduType = REQUEST_VIDEO_QADU_TYPES.VIEWS
-      break
-
-    case REQUEST_VIDEO_EVENT_TYPES.LIKES:
-      columnToUpdate = 'likes'
-      qaduType = REQUEST_VIDEO_QADU_TYPES.LIKES
-      break
-
-    case REQUEST_VIDEO_EVENT_TYPES.DISLIKES:
-      columnToUpdate = 'dislikes'
-      qaduType = REQUEST_VIDEO_QADU_TYPES.DISLIKES
-      break
-
-    default:
-      throw new Error('Unknown video event type.')
-    }
-
-    const query = {}
-    query[columnToUpdate] = eventData.count
-
-    await videoInstance.increment(query, sequelizeOptions)
-
-    const qadusParams = [
-      {
-        videoId: videoInstance.id,
-        type: qaduType
-      }
-    ]
-    await quickAndDirtyUpdatesVideoToFriends(qadusParams, t)
-  })
-
-  logger.info('Remote video event processed for video with uuid %s.', eventData.uuid)
-}
-
-async function quickAndDirtyUpdateVideoRetryWrapper (videoData: RemoteQaduVideoData, fromPod: PodInstance) {
-  const options = {
-    arguments: [ videoData, fromPod ],
-    errorMessage: 'Cannot update quick and dirty the remote video with many retries.'
-  }
-
-  await retryTransactionWrapper(quickAndDirtyUpdateVideo, options)
-}
-
-async function quickAndDirtyUpdateVideo (videoData: RemoteQaduVideoData, fromPod: PodInstance) {
-  let videoUUID = ''
-
-  await db.sequelize.transaction(async t => {
-    const videoInstance = await fetchVideoByHostAndUUID(fromPod.host, videoData.uuid, t)
-    const sequelizeOptions = { transaction: t }
-
-    videoUUID = videoInstance.uuid
-
-    if (videoData.views) {
-      videoInstance.set('views', videoData.views)
-    }
-
-    if (videoData.likes) {
-      videoInstance.set('likes', videoData.likes)
-    }
-
-    if (videoData.dislikes) {
-      videoInstance.set('dislikes', videoData.dislikes)
-    }
-
-    await videoInstance.save(sequelizeOptions)
-  })
-
-  logger.info('Remote video with uuid %s quick and dirty updated', videoUUID)
-}
-
-// Handle retries on fail
-async function addRemoteVideoRetryWrapper (videoToCreateData: RemoteVideoCreateData, fromPod: PodInstance) {
-  const options = {
-    arguments: [ videoToCreateData, fromPod ],
-    errorMessage: 'Cannot insert the remote video with many retries.'
-  }
-
-  await retryTransactionWrapper(addRemoteVideo, options)
-}
-
-async function addRemoteVideo (videoToCreateData: RemoteVideoCreateData, fromPod: PodInstance) {
-  logger.debug('Adding remote video "%s".', videoToCreateData.uuid)
-
-  await db.sequelize.transaction(async t => {
-    const sequelizeOptions = {
-      transaction: t
-    }
-
-    const videoFromDatabase = await db.Video.loadByUUID(videoToCreateData.uuid)
-    if (videoFromDatabase) throw new Error('UUID already exists.')
-
-    const videoChannel = await db.VideoChannel.loadByHostAndUUID(fromPod.host, videoToCreateData.channelUUID, t)
-    if (!videoChannel) throw new Error('Video channel ' + videoToCreateData.channelUUID + ' not found.')
-
-    const tags = videoToCreateData.tags
-    const tagInstances = await db.Tag.findOrCreateTags(tags, t)
-
-    const videoData = {
-      name: videoToCreateData.name,
-      uuid: videoToCreateData.uuid,
-      category: videoToCreateData.category,
-      licence: videoToCreateData.licence,
-      language: videoToCreateData.language,
-      nsfw: videoToCreateData.nsfw,
-      description: videoToCreateData.truncatedDescription,
-      channelId: videoChannel.id,
-      duration: videoToCreateData.duration,
-      createdAt: videoToCreateData.createdAt,
-      // FIXME: updatedAt does not seems to be considered by Sequelize
-      updatedAt: videoToCreateData.updatedAt,
-      views: videoToCreateData.views,
-      likes: videoToCreateData.likes,
-      dislikes: videoToCreateData.dislikes,
-      remote: true,
-      privacy: videoToCreateData.privacy
-    }
-
-    const video = db.Video.build(videoData)
-    await db.Video.generateThumbnailFromData(video, videoToCreateData.thumbnailData)
-    const videoCreated = await video.save(sequelizeOptions)
-
-    const tasks = []
-    for (const fileData of videoToCreateData.files) {
-      const videoFileInstance = db.VideoFile.build({
-        extname: fileData.extname,
-        infoHash: fileData.infoHash,
-        resolution: fileData.resolution,
-        size: fileData.size,
-        videoId: videoCreated.id
-      })
-
-      tasks.push(videoFileInstance.save(sequelizeOptions))
-    }
-
-    await Promise.all(tasks)
-
-    await videoCreated.setTags(tagInstances, sequelizeOptions)
-  })
-
-  logger.info('Remote video with uuid %s inserted.', videoToCreateData.uuid)
-}
-
-// Handle retries on fail
-async function updateRemoteVideoRetryWrapper (videoAttributesToUpdate: RemoteVideoUpdateData, fromPod: PodInstance) {
-  const options = {
-    arguments: [ videoAttributesToUpdate, fromPod ],
-    errorMessage: 'Cannot update the remote video with many retries'
-  }
-
-  await retryTransactionWrapper(updateRemoteVideo, options)
-}
-
-async function updateRemoteVideo (videoAttributesToUpdate: RemoteVideoUpdateData, fromPod: PodInstance) {
-  logger.debug('Updating remote video "%s".', videoAttributesToUpdate.uuid)
-  let videoInstance: VideoInstance
-  let videoFieldsSave: object
-
-  try {
-    await db.sequelize.transaction(async t => {
-      const sequelizeOptions = {
-        transaction: t
-      }
-
-      const videoInstance = await fetchVideoByHostAndUUID(fromPod.host, videoAttributesToUpdate.uuid, t)
-      videoFieldsSave = videoInstance.toJSON()
-      const tags = videoAttributesToUpdate.tags
-
-      const tagInstances = await db.Tag.findOrCreateTags(tags, t)
-
-      videoInstance.set('name', videoAttributesToUpdate.name)
-      videoInstance.set('category', videoAttributesToUpdate.category)
-      videoInstance.set('licence', videoAttributesToUpdate.licence)
-      videoInstance.set('language', videoAttributesToUpdate.language)
-      videoInstance.set('nsfw', videoAttributesToUpdate.nsfw)
-      videoInstance.set('description', videoAttributesToUpdate.truncatedDescription)
-      videoInstance.set('duration', videoAttributesToUpdate.duration)
-      videoInstance.set('createdAt', videoAttributesToUpdate.createdAt)
-      videoInstance.set('updatedAt', videoAttributesToUpdate.updatedAt)
-      videoInstance.set('views', videoAttributesToUpdate.views)
-      videoInstance.set('likes', videoAttributesToUpdate.likes)
-      videoInstance.set('dislikes', videoAttributesToUpdate.dislikes)
-      videoInstance.set('privacy', videoAttributesToUpdate.privacy)
-
-      await videoInstance.save(sequelizeOptions)
-
-      // Remove old video files
-      const videoFileDestroyTasks: Bluebird<void>[] = []
-      for (const videoFile of videoInstance.VideoFiles) {
-        videoFileDestroyTasks.push(videoFile.destroy(sequelizeOptions))
-      }
-      await Promise.all(videoFileDestroyTasks)
-
-      const videoFileCreateTasks: Bluebird<VideoFileInstance>[] = []
-      for (const fileData of videoAttributesToUpdate.files) {
-        const videoFileInstance = db.VideoFile.build({
-          extname: fileData.extname,
-          infoHash: fileData.infoHash,
-          resolution: fileData.resolution,
-          size: fileData.size,
-          videoId: videoInstance.id
-        })
-
-        videoFileCreateTasks.push(videoFileInstance.save(sequelizeOptions))
-      }
-
-      await Promise.all(videoFileCreateTasks)
-
-      await videoInstance.setTags(tagInstances, sequelizeOptions)
-    })
-
-    logger.info('Remote video with uuid %s updated', videoAttributesToUpdate.uuid)
-  } catch (err) {
-    if (videoInstance !== undefined && videoFieldsSave !== undefined) {
-      resetSequelizeInstance(videoInstance, videoFieldsSave)
-    }
-
-    // This is just a debug because we will retry the insert
-    logger.debug('Cannot update the remote video.', err)
-    throw err
-  }
-}
-
-async function removeRemoteVideoRetryWrapper (videoToRemoveData: RemoteVideoRemoveData, fromPod: PodInstance) {
-  const options = {
-    arguments: [ videoToRemoveData, fromPod ],
-    errorMessage: 'Cannot remove the remote video channel with many retries.'
-  }
-
-  await retryTransactionWrapper(removeRemoteVideo, options)
-}
-
-async function removeRemoteVideo (videoToRemoveData: RemoteVideoRemoveData, fromPod: PodInstance) {
-  logger.debug('Removing remote video "%s".', videoToRemoveData.uuid)
-
-  await db.sequelize.transaction(async t => {
-    // We need the instance because we have to remove some other stuffs (thumbnail etc)
-    const videoInstance = await fetchVideoByHostAndUUID(fromPod.host, videoToRemoveData.uuid, t)
-    await videoInstance.destroy({ transaction: t })
-  })
-
-  logger.info('Remote video with uuid %s removed.', videoToRemoveData.uuid)
-}
-
-async function addRemoteVideoAuthorRetryWrapper (authorToCreateData: RemoteVideoAuthorCreateData, fromPod: PodInstance) {
-  const options = {
-    arguments: [ authorToCreateData, fromPod ],
-    errorMessage: 'Cannot insert the remote video author with many retries.'
-  }
-
-  await retryTransactionWrapper(addRemoteVideoAuthor, options)
-}
-
-async function addRemoteVideoAuthor (authorToCreateData: RemoteVideoAuthorCreateData, fromPod: PodInstance) {
-  logger.debug('Adding remote video author "%s".', authorToCreateData.uuid)
-
-  await db.sequelize.transaction(async t => {
-    const authorInDatabase = await db.Author.loadAuthorByPodAndUUID(authorToCreateData.uuid, fromPod.id, t)
-    if (authorInDatabase) throw new Error('Author with UUID ' + authorToCreateData.uuid + ' already exists.')
-
-    const videoAuthorData = {
-      name: authorToCreateData.name,
-      uuid: authorToCreateData.uuid,
-      userId: null, // Not on our pod
-      podId: fromPod.id
-    }
-
-    const author = db.Author.build(videoAuthorData)
-    await author.save({ transaction: t })
-  })
-
-  logger.info('Remote video author with uuid %s inserted.', authorToCreateData.uuid)
-}
-
-async function removeRemoteVideoAuthorRetryWrapper (authorAttributesToRemove: RemoteVideoAuthorRemoveData, fromPod: PodInstance) {
-  const options = {
-    arguments: [ authorAttributesToRemove, fromPod ],
-    errorMessage: 'Cannot remove the remote video author with many retries.'
-  }
-
-  await retryTransactionWrapper(removeRemoteVideoAuthor, options)
-}
-
-async function removeRemoteVideoAuthor (authorAttributesToRemove: RemoteVideoAuthorRemoveData, fromPod: PodInstance) {
-  logger.debug('Removing remote video author "%s".', authorAttributesToRemove.uuid)
-
-  await db.sequelize.transaction(async t => {
-    const videoAuthor = await db.Author.loadAuthorByPodAndUUID(authorAttributesToRemove.uuid, fromPod.id, t)
-    await videoAuthor.destroy({ transaction: t })
-  })
-
-  logger.info('Remote video author with uuid %s removed.', authorAttributesToRemove.uuid)
-}
-
-async function addRemoteVideoChannelRetryWrapper (videoChannelToCreateData: RemoteVideoChannelCreateData, fromPod: PodInstance) {
-  const options = {
-    arguments: [ videoChannelToCreateData, fromPod ],
-    errorMessage: 'Cannot insert the remote video channel with many retries.'
-  }
-
-  await retryTransactionWrapper(addRemoteVideoChannel, options)
-}
-
-async function addRemoteVideoChannel (videoChannelToCreateData: RemoteVideoChannelCreateData, fromPod: PodInstance) {
-  logger.debug('Adding remote video channel "%s".', videoChannelToCreateData.uuid)
-
-  await db.sequelize.transaction(async t => {
-    const videoChannelInDatabase = await db.VideoChannel.loadByUUID(videoChannelToCreateData.uuid)
-    if (videoChannelInDatabase) {
-      throw new Error('Video channel with UUID ' + videoChannelToCreateData.uuid + ' already exists.')
-    }
-
-    const authorUUID = videoChannelToCreateData.ownerUUID
-    const podId = fromPod.id
-
-    const author = await db.Author.loadAuthorByPodAndUUID(authorUUID, podId, t)
-    if (!author) throw new Error('Unknown author UUID' + authorUUID + '.')
-
-    const videoChannelData = {
-      name: videoChannelToCreateData.name,
-      description: videoChannelToCreateData.description,
-      uuid: videoChannelToCreateData.uuid,
-      createdAt: videoChannelToCreateData.createdAt,
-      updatedAt: videoChannelToCreateData.updatedAt,
-      remote: true,
-      authorId: author.id
-    }
-
-    const videoChannel = db.VideoChannel.build(videoChannelData)
-    await videoChannel.save({ transaction: t })
-  })
-
-  logger.info('Remote video channel with uuid %s inserted.', videoChannelToCreateData.uuid)
-}
-
-async function updateRemoteVideoChannelRetryWrapper (videoChannelAttributesToUpdate: RemoteVideoChannelUpdateData, fromPod: PodInstance) {
-  const options = {
-    arguments: [ videoChannelAttributesToUpdate, fromPod ],
-    errorMessage: 'Cannot update the remote video channel with many retries.'
-  }
-
-  await retryTransactionWrapper(updateRemoteVideoChannel, options)
-}
-
-async function updateRemoteVideoChannel (videoChannelAttributesToUpdate: RemoteVideoChannelUpdateData, fromPod: PodInstance) {
-  logger.debug('Updating remote video channel "%s".', videoChannelAttributesToUpdate.uuid)
-
-  await db.sequelize.transaction(async t => {
-    const sequelizeOptions = { transaction: t }
-
-    const videoChannelInstance = await fetchVideoChannelByHostAndUUID(fromPod.host, videoChannelAttributesToUpdate.uuid, t)
-    videoChannelInstance.set('name', videoChannelAttributesToUpdate.name)
-    videoChannelInstance.set('description', videoChannelAttributesToUpdate.description)
-    videoChannelInstance.set('createdAt', videoChannelAttributesToUpdate.createdAt)
-    videoChannelInstance.set('updatedAt', videoChannelAttributesToUpdate.updatedAt)
-
-    await videoChannelInstance.save(sequelizeOptions)
-  })
-
-  logger.info('Remote video channel with uuid %s updated', videoChannelAttributesToUpdate.uuid)
-}
-
-async function removeRemoteVideoChannelRetryWrapper (videoChannelAttributesToRemove: RemoteVideoChannelRemoveData, fromPod: PodInstance) {
-  const options = {
-    arguments: [ videoChannelAttributesToRemove, fromPod ],
-    errorMessage: 'Cannot remove the remote video channel with many retries.'
-  }
-
-  await retryTransactionWrapper(removeRemoteVideoChannel, options)
-}
-
-async function removeRemoteVideoChannel (videoChannelAttributesToRemove: RemoteVideoChannelRemoveData, fromPod: PodInstance) {
-  logger.debug('Removing remote video channel "%s".', videoChannelAttributesToRemove.uuid)
-
-  await db.sequelize.transaction(async t => {
-    const videoChannel = await fetchVideoChannelByHostAndUUID(fromPod.host, videoChannelAttributesToRemove.uuid, t)
-    await videoChannel.destroy({ transaction: t })
-  })
-
-  logger.info('Remote video channel with uuid %s removed.', videoChannelAttributesToRemove.uuid)
-}
-
-async function reportAbuseRemoteVideoRetryWrapper (reportData: RemoteVideoReportAbuseData, fromPod: PodInstance) {
-  const options = {
-    arguments: [ reportData, fromPod ],
-    errorMessage: 'Cannot create remote abuse video with many retries.'
-  }
-
-  await retryTransactionWrapper(reportAbuseRemoteVideo, options)
-}
-
-async function reportAbuseRemoteVideo (reportData: RemoteVideoReportAbuseData, fromPod: PodInstance) {
-  logger.debug('Reporting remote abuse for video %s.', reportData.videoUUID)
-
-  await db.sequelize.transaction(async t => {
-    const videoInstance = await fetchLocalVideoByUUID(reportData.videoUUID, t)
-    const videoAbuseData = {
-      reporterUsername: reportData.reporterUsername,
-      reason: reportData.reportReason,
-      reporterPodId: fromPod.id,
-      videoId: videoInstance.id
-    }
-
-    await db.VideoAbuse.create(videoAbuseData)
-
-  })
-
-  logger.info('Remote abuse for video uuid %s created', reportData.videoUUID)
-}
-
-async function fetchLocalVideoByUUID (id: string, t: Sequelize.Transaction) {
-  try {
-    const video = await db.Video.loadLocalVideoByUUID(id, t)
-
-    if (!video) throw new Error('Video ' + id + ' not found')
-
-    return video
-  } catch (err) {
-    logger.error('Cannot load owned video from id.', { error: err.stack, id })
-    throw err
-  }
-}
-
-async function fetchVideoByHostAndUUID (podHost: string, uuid: string, t: Sequelize.Transaction) {
-  try {
-    const video = await db.Video.loadByHostAndUUID(podHost, uuid, t)
-    if (!video) throw new Error('Video not found')
-
-    return video
-  } catch (err) {
-    logger.error('Cannot load video from host and uuid.', { error: err.stack, podHost, uuid })
-    throw err
-  }
-}
+// import * as express from 'express'
+// import * as Bluebird from 'bluebird'
+// import * as Sequelize from 'sequelize'
+//
+// import { database as db } from '../../../initializers/database'
+// import {
+//   REQUEST_ENDPOINT_ACTIONS,
+//   REQUEST_ENDPOINTS,
+//   REQUEST_VIDEO_EVENT_TYPES,
+//   REQUEST_VIDEO_QADU_TYPES
+// } from '../../../initializers'
+// import {
+//   checkSignature,
+//   signatureValidator,
+//   remoteVideosValidator,
+//   remoteQaduVideosValidator,
+//   remoteEventsVideosValidator
+// } from '../../../middlewares'
+// import { logger, retryTransactionWrapper, resetSequelizeInstance } from '../../../helpers'
+// import { quickAndDirtyUpdatesVideoToFriends, fetchVideoChannelByHostAndUUID } from '../../../lib'
+// import { PodInstance, VideoFileInstance } from '../../../models'
+// import {
+//   RemoteVideoRequest,
+//   RemoteVideoCreateData,
+//   RemoteVideoUpdateData,
+//   RemoteVideoRemoveData,
+//   RemoteVideoReportAbuseData,
+//   RemoteQaduVideoRequest,
+//   RemoteQaduVideoData,
+//   RemoteVideoEventRequest,
+//   RemoteVideoEventData,
+//   RemoteVideoChannelCreateData,
+//   RemoteVideoChannelUpdateData,
+//   RemoteVideoChannelRemoveData,
+//   RemoteVideoAuthorRemoveData,
+//   RemoteVideoAuthorCreateData
+// } from '../../../../shared'
+// import { VideoInstance } from '../../../models/video/video-interface'
+//
+// const ENDPOINT_ACTIONS = REQUEST_ENDPOINT_ACTIONS[REQUEST_ENDPOINTS.VIDEOS]
+//
+// // Functions to call when processing a remote request
+// // FIXME: use RemoteVideoRequestType as id type
+// const functionsHash: { [ id: string ]: (...args) => Promise<any> } = {}
+// functionsHash[ENDPOINT_ACTIONS.ADD_VIDEO] = addRemoteVideoRetryWrapper
+// functionsHash[ENDPOINT_ACTIONS.UPDATE_VIDEO] = updateRemoteVideoRetryWrapper
+// functionsHash[ENDPOINT_ACTIONS.REMOVE_VIDEO] = removeRemoteVideoRetryWrapper
+// functionsHash[ENDPOINT_ACTIONS.ADD_CHANNEL] = addRemoteVideoChannelRetryWrapper
+// functionsHash[ENDPOINT_ACTIONS.UPDATE_CHANNEL] = updateRemoteVideoChannelRetryWrapper
+// functionsHash[ENDPOINT_ACTIONS.REMOVE_CHANNEL] = removeRemoteVideoChannelRetryWrapper
+// functionsHash[ENDPOINT_ACTIONS.REPORT_ABUSE] = reportAbuseRemoteVideoRetryWrapper
+// functionsHash[ENDPOINT_ACTIONS.ADD_AUTHOR] = addRemoteVideoAuthorRetryWrapper
+// functionsHash[ENDPOINT_ACTIONS.REMOVE_AUTHOR] = removeRemoteVideoAuthorRetryWrapper
+//
+// const remoteVideosRouter = express.Router()
+//
+// remoteVideosRouter.post('/',
+//   signatureValidator,
+//   checkSignature,
+//   remoteVideosValidator,
+//   remoteVideos
+// )
+//
+// remoteVideosRouter.post('/qadu',
+//   signatureValidator,
+//   checkSignature,
+//   remoteQaduVideosValidator,
+//   remoteVideosQadu
+// )
+//
+// remoteVideosRouter.post('/events',
+//   signatureValidator,
+//   checkSignature,
+//   remoteEventsVideosValidator,
+//   remoteVideosEvents
+// )
+//
+// // ---------------------------------------------------------------------------
+//
+// export {
+//   remoteVideosRouter
+// }
+//
+// // ---------------------------------------------------------------------------
+//
+// function remoteVideos (req: express.Request, res: express.Response, next: express.NextFunction) {
+//   const requests: RemoteVideoRequest[] = req.body.data
+//   const fromPod = res.locals.secure.pod
+//
+//   // We need to process in the same order to keep consistency
+//   Bluebird.each(requests, request => {
+//     const data = request.data
+//
+//     // Get the function we need to call in order to process the request
+//     const fun = functionsHash[request.type]
+//     if (fun === undefined) {
+//       logger.error('Unknown remote request type %s.', request.type)
+//       return
+//     }
+//
+//     return fun.call(this, data, fromPod)
+//   })
+//   .catch(err => logger.error('Error managing remote videos.', err))
+//
+//   // Don't block the other pod
+//   return res.type('json').status(204).end()
+// }
+//
+// function remoteVideosQadu (req: express.Request, res: express.Response, next: express.NextFunction) {
+//   const requests: RemoteQaduVideoRequest[] = req.body.data
+//   const fromPod = res.locals.secure.pod
+//
+//   Bluebird.each(requests, request => {
+//     const videoData = request.data
+//
+//     return quickAndDirtyUpdateVideoRetryWrapper(videoData, fromPod)
+//   })
+//   .catch(err => logger.error('Error managing remote videos.', err))
+//
+//   return res.type('json').status(204).end()
+// }
+//
+// function remoteVideosEvents (req: express.Request, res: express.Response, next: express.NextFunction) {
+//   const requests: RemoteVideoEventRequest[] = req.body.data
+//   const fromPod = res.locals.secure.pod
+//
+//   Bluebird.each(requests, request => {
+//     const eventData = request.data
+//
+//     return processVideosEventsRetryWrapper(eventData, fromPod)
+//   })
+//   .catch(err => logger.error('Error managing remote videos.', err))
+//
+//   return res.type('json').status(204).end()
+// }
+//
+// async function processVideosEventsRetryWrapper (eventData: RemoteVideoEventData, fromPod: PodInstance) {
+//   const options = {
+//     arguments: [ eventData, fromPod ],
+//     errorMessage: 'Cannot process videos events with many retries.'
+//   }
+//
+//   await retryTransactionWrapper(processVideosEvents, options)
+// }
+//
+// async function processVideosEvents (eventData: RemoteVideoEventData, fromPod: PodInstance) {
+//   await db.sequelize.transaction(async t => {
+//     const sequelizeOptions = { transaction: t }
+//     const videoInstance = await fetchLocalVideoByUUID(eventData.uuid, t)
+//
+//     let columnToUpdate
+//     let qaduType
+//
+//     switch (eventData.eventType) {
+//     case REQUEST_VIDEO_EVENT_TYPES.VIEWS:
+//       columnToUpdate = 'views'
+//       qaduType = REQUEST_VIDEO_QADU_TYPES.VIEWS
+//       break
+//
+//     case REQUEST_VIDEO_EVENT_TYPES.LIKES:
+//       columnToUpdate = 'likes'
+//       qaduType = REQUEST_VIDEO_QADU_TYPES.LIKES
+//       break
+//
+//     case REQUEST_VIDEO_EVENT_TYPES.DISLIKES:
+//       columnToUpdate = 'dislikes'
+//       qaduType = REQUEST_VIDEO_QADU_TYPES.DISLIKES
+//       break
+//
+//     default:
+//       throw new Error('Unknown video event type.')
+//     }
+//
+//     const query = {}
+//     query[columnToUpdate] = eventData.count
+//
+//     await videoInstance.increment(query, sequelizeOptions)
+//
+//     const qadusParams = [
+//       {
+//         videoId: videoInstance.id,
+//         type: qaduType
+//       }
+//     ]
+//     await quickAndDirtyUpdatesVideoToFriends(qadusParams, t)
+//   })
+//
+//   logger.info('Remote video event processed for video with uuid %s.', eventData.uuid)
+// }
+//
+// async function quickAndDirtyUpdateVideoRetryWrapper (videoData: RemoteQaduVideoData, fromPod: PodInstance) {
+//   const options = {
+//     arguments: [ videoData, fromPod ],
+//     errorMessage: 'Cannot update quick and dirty the remote video with many retries.'
+//   }
+//
+//   await retryTransactionWrapper(quickAndDirtyUpdateVideo, options)
+// }
+//
+// async function quickAndDirtyUpdateVideo (videoData: RemoteQaduVideoData, fromPod: PodInstance) {
+//   let videoUUID = ''
+//
+//   await db.sequelize.transaction(async t => {
+//     const videoInstance = await fetchVideoByHostAndUUID(fromPod.host, videoData.uuid, t)
+//     const sequelizeOptions = { transaction: t }
+//
+//     videoUUID = videoInstance.uuid
+//
+//     if (videoData.views) {
+//       videoInstance.set('views', videoData.views)
+//     }
+//
+//     if (videoData.likes) {
+//       videoInstance.set('likes', videoData.likes)
+//     }
+//
+//     if (videoData.dislikes) {
+//       videoInstance.set('dislikes', videoData.dislikes)
+//     }
+//
+//     await videoInstance.save(sequelizeOptions)
+//   })
+//
+//   logger.info('Remote video with uuid %s quick and dirty updated', videoUUID)
+// }
+//
+// async function removeRemoteVideoRetryWrapper (videoToRemoveData: RemoteVideoRemoveData, fromPod: PodInstance) {
+//   const options = {
+//     arguments: [ videoToRemoveData, fromPod ],
+//     errorMessage: 'Cannot remove the remote video channel with many retries.'
+//   }
+//
+//   await retryTransactionWrapper(removeRemoteVideo, options)
+// }
+//
+// async function removeRemoteVideo (videoToRemoveData: RemoteVideoRemoveData, fromPod: PodInstance) {
+//   logger.debug('Removing remote video "%s".', videoToRemoveData.uuid)
+//
+//   await db.sequelize.transaction(async t => {
+//     // We need the instance because we have to remove some other stuffs (thumbnail etc)
+//     const videoInstance = await fetchVideoByHostAndUUID(fromPod.host, videoToRemoveData.uuid, t)
+//     await videoInstance.destroy({ transaction: t })
+//   })
+//
+//   logger.info('Remote video with uuid %s removed.', videoToRemoveData.uuid)
+// }
+//
+// async function removeRemoteVideoAuthorRetryWrapper (authorAttributesToRemove: RemoteVideoAuthorRemoveData, fromPod: PodInstance) {
+//   const options = {
+//     arguments: [ authorAttributesToRemove, fromPod ],
+//     errorMessage: 'Cannot remove the remote video author with many retries.'
+//   }
+//
+//   await retryTransactionWrapper(removeRemoteVideoAuthor, options)
+// }
+//
+// async function removeRemoteVideoAuthor (authorAttributesToRemove: RemoteVideoAuthorRemoveData, fromPod: PodInstance) {
+//   logger.debug('Removing remote video author "%s".', authorAttributesToRemove.uuid)
+//
+//   await db.sequelize.transaction(async t => {
+//     const videoAuthor = await db.Author.loadAuthorByPodAndUUID(authorAttributesToRemove.uuid, fromPod.id, t)
+//     await videoAuthor.destroy({ transaction: t })
+//   })
+//
+//   logger.info('Remote video author with uuid %s removed.', authorAttributesToRemove.uuid)
+// }
+//
+// async function removeRemoteVideoChannelRetryWrapper (videoChannelAttributesToRemove: RemoteVideoChannelRemoveData, fromPod: PodInstance) {
+//   const options = {
+//     arguments: [ videoChannelAttributesToRemove, fromPod ],
+//     errorMessage: 'Cannot remove the remote video channel with many retries.'
+//   }
+//
+//   await retryTransactionWrapper(removeRemoteVideoChannel, options)
+// }
+//
+// async function removeRemoteVideoChannel (videoChannelAttributesToRemove: RemoteVideoChannelRemoveData, fromPod: PodInstance) {
+//   logger.debug('Removing remote video channel "%s".', videoChannelAttributesToRemove.uuid)
+//
+//   await db.sequelize.transaction(async t => {
+//     const videoChannel = await fetchVideoChannelByHostAndUUID(fromPod.host, videoChannelAttributesToRemove.uuid, t)
+//     await videoChannel.destroy({ transaction: t })
+//   })
+//
+//   logger.info('Remote video channel with uuid %s removed.', videoChannelAttributesToRemove.uuid)
+// }
+//
+// async function reportAbuseRemoteVideoRetryWrapper (reportData: RemoteVideoReportAbuseData, fromPod: PodInstance) {
+//   const options = {
+//     arguments: [ reportData, fromPod ],
+//     errorMessage: 'Cannot create remote abuse video with many retries.'
+//   }
+//
+//   await retryTransactionWrapper(reportAbuseRemoteVideo, options)
+// }
+//
+// async function reportAbuseRemoteVideo (reportData: RemoteVideoReportAbuseData, fromPod: PodInstance) {
+//   logger.debug('Reporting remote abuse for video %s.', reportData.videoUUID)
+//
+//   await db.sequelize.transaction(async t => {
+//     const videoInstance = await fetchLocalVideoByUUID(reportData.videoUUID, t)
+//     const videoAbuseData = {
+//       reporterUsername: reportData.reporterUsername,
+//       reason: reportData.reportReason,
+//       reporterPodId: fromPod.id,
+//       videoId: videoInstance.id
+//     }
+//
+//     await db.VideoAbuse.create(videoAbuseData)
+//
+//   })
+//
+//   logger.info('Remote abuse for video uuid %s created', reportData.videoUUID)
+// }
+//
+// async function fetchLocalVideoByUUID (id: string, t: Sequelize.Transaction) {
+//   try {
+//     const video = await db.Video.loadLocalVideoByUUID(id, t)
+//
+//     if (!video) throw new Error('Video ' + id + ' not found')
+//
+//     return video
+//   } catch (err) {
+//     logger.error('Cannot load owned video from id.', { error: err.stack, id })
+//     throw err
+//   }
+// }
+//
+// async function fetchVideoByHostAndUUID (podHost: string, uuid: string, t: Sequelize.Transaction) {
+//   try {
+//     const video = await db.Video.loadByHostAndUUID(podHost, uuid, t)
+//     if (!video) throw new Error('Video not found')
+//
+//     return video
+//   } catch (err) {
+//     logger.error('Cannot load video from host and uuid.', { error: err.stack, podHost, uuid })
+//     throw err
+//   }
+// }
diff --git a/server/controllers/api/videos/index.ts b/server/controllers/api/videos/index.ts
index 4dd09917b..964db151d 100644
--- a/server/controllers/api/videos/index.ts
+++ b/server/controllers/api/videos/index.ts
@@ -10,7 +10,8 @@ import {
   VIDEO_CATEGORIES,
   VIDEO_LICENCES,
   VIDEO_LANGUAGES,
-  VIDEO_PRIVACIES
+  VIDEO_PRIVACIES,
+  VIDEO_MIMETYPE_EXT
 } from '../../../initializers'
 import {
   addEventToRemoteVideo,
@@ -50,6 +51,7 @@ import { abuseVideoRouter } from './abuse'
 import { blacklistRouter } from './blacklist'
 import { rateVideoRouter } from './rate'
 import { videoChannelRouter } from './channel'
+import { getActivityPubUrl } from '../../../helpers/activitypub'
 
 const videosRouter = express.Router()
 
@@ -59,19 +61,18 @@ const storage = multer.diskStorage({
     cb(null, CONFIG.STORAGE.VIDEOS_DIR)
   },
 
-  filename: (req, file, cb) => {
-    let extension = ''
-    if (file.mimetype === 'video/webm') extension = 'webm'
-    else if (file.mimetype === 'video/mp4') extension = 'mp4'
-    else if (file.mimetype === 'video/ogg') extension = 'ogv'
-    generateRandomString(16)
-      .then(randomString => {
-        cb(null, randomString + '.' + extension)
-      })
-      .catch(err => {
-        logger.error('Cannot generate random string for file name.', err)
-        throw err
-      })
+  filename: async (req, file, cb) => {
+    const extension = VIDEO_MIMETYPE_EXT[file.mimetype]
+    let randomString = ''
+
+    try {
+      randomString = await generateRandomString(16)
+    } catch (err) {
+      logger.error('Cannot generate random string for file name.', err)
+      randomString = 'fake-random-string'
+    }
+
+    cb(null, randomString + '.' + extension)
   }
 })
 
@@ -190,6 +191,7 @@ async function addVideo (req: express.Request, res: express.Response, videoPhysi
       channelId: res.locals.videoChannel.id
     }
     const video = db.Video.build(videoData)
+    video.url = getActivityPubUrl('video', video.uuid)
 
     const videoFilePath = join(CONFIG.STORAGE.VIDEOS_DIR, videoPhysicalFile.filename)
     const videoFileHeight = await getVideoFileHeight(videoFilePath)
-- 
cgit v1.2.3