Federate video update
authorChocobozzz <florian.bigard@gmail.com>
Thu, 16 Nov 2017 14:55:01 +0000 (15:55 +0100)
committerChocobozzz <florian.bigard@gmail.com>
Mon, 27 Nov 2017 18:40:52 +0000 (19:40 +0100)
13 files changed:
server/lib/activitypub/misc.ts
server/lib/activitypub/process-add.ts
server/lib/activitypub/process-create.ts
server/lib/activitypub/process-delete.ts
server/lib/activitypub/process-update.ts
server/lib/activitypub/send-request.ts
server/models/account/account-follow.ts
server/models/video/video-channel-share-interface.ts
server/models/video/video-channel-share.ts
server/models/video/video-interface.ts
server/models/video/video-share-interface.ts
server/models/video/video-share.ts
server/models/video/video.ts

index 13838fc4c53d1b4b82ab902563021ccf365503ea..f853d742e05c9e9df72bd465faf62771786dd66c 100644 (file)
@@ -25,12 +25,8 @@ function videoChannelActivityObjectToDBAttributes (videoChannelObject: VideoChan
 
 async function videoActivityObjectToDBAttributes (
   videoChannel: VideoChannelInstance,
-  videoObject: VideoTorrentObject,
-  t: Sequelize.Transaction
+  videoObject: VideoTorrentObject
 ) {
-  const videoFromDatabase = await db.Video.loadByUUIDOrURL(videoObject.uuid, videoObject.id, t)
-  if (videoFromDatabase) throw new Error('Video with this UUID/Url already exists.')
-
   const duration = videoObject.duration.replace(/[^\d]+/, '')
   const videoData: VideoAttributes = {
     name: videoObject.name,
index df7139d466c8a80a82c4e8d8c6d0929ec87be9eb..72c5b19325bd00592097b694c83eba3cadb8b77a 100644 (file)
@@ -1,12 +1,12 @@
+import * as Bluebird from 'bluebird'
 import { VideoTorrentObject } from '../../../shared'
 import { ActivityAdd } from '../../../shared/models/activitypub/activity'
-import { generateThumbnailFromUrl, logger, retryTransactionWrapper, getOrCreateAccount } from '../../helpers'
+import { generateThumbnailFromUrl, getOrCreateAccount, logger, retryTransactionWrapper } from '../../helpers'
+import { getOrCreateVideoChannel } from '../../helpers/activitypub'
 import { database as db } from '../../initializers'
 import { AccountInstance } from '../../models/account/account-interface'
-import { videoActivityObjectToDBAttributes, videoFileActivityUrlToDBAttributes } from './misc'
-import Bluebird = require('bluebird')
-import { getOrCreateVideoChannel } from '../../helpers/activitypub'
 import { VideoChannelInstance } from '../../models/video/video-channel-interface'
+import { videoActivityObjectToDBAttributes, videoFileActivityUrlToDBAttributes } from './misc'
 
 async function processAddActivity (activity: ActivityAdd) {
   const activityObject = activity.object
@@ -51,7 +51,10 @@ function addRemoteVideo (account: AccountInstance, videoChannel: VideoChannelIns
 
     if (videoChannel.Account.id !== account.id) throw new Error('Video channel is not owned by this account.')
 
-    const videoData = await videoActivityObjectToDBAttributes(videoChannel, videoToCreateData, t)
+    const videoFromDatabase = await db.Video.loadByUUIDOrURL(videoToCreateData.uuid, videoToCreateData.id, t)
+    if (videoFromDatabase) throw new Error('Video with this UUID/Url already exists.')
+
+    const videoData = await videoActivityObjectToDBAttributes(videoChannel, videoToCreateData)
     const video = db.Video.build(videoData)
 
     // Don't block on request
index c4706a66b78ee4af872a339df0f45b763d22af19..faea3f48aea95b5566d2b240a9e18bda87529b37 100644 (file)
@@ -69,7 +69,7 @@ function addRemoteVideoAbuse (account: AccountInstance, videoAbuseToCreateData:
   logger.debug('Reporting remote abuse for video %s.', videoAbuseToCreateData.object)
 
   return db.sequelize.transaction(async t => {
-    const video = await db.Video.loadByUrl(videoAbuseToCreateData.object, t)
+    const video = await db.Video.loadByUrlAndPopulateAccount(videoAbuseToCreateData.object, t)
     if (!video) {
       logger.warn('Unknown video %s for remote video abuse.', videoAbuseToCreateData.object)
       return
index 377df432d72fad4522dd01d29ffcbb111950af34..d4487d2cc8ef4fa9e7dcefd50c2f0b70b7a79588 100644 (file)
@@ -15,7 +15,7 @@ async function processDeleteActivity (activity: ActivityDelete) {
   }
 
   {
-    let videoObject = await db.Video.loadByUrl(activity.id)
+    let videoObject = await db.Video.loadByUrlAndPopulateAccount(activity.id)
     if (videoObject !== undefined) {
       return processDeleteVideo(account, videoObject)
     }
index cd8a4b8e22f60c9c5ee43d3deb07002ff76243fd..4aefd1b9b550268cc51a2751d50b083f12a115ee 100644 (file)
@@ -50,14 +50,14 @@ async function updateRemoteVideo (account: AccountInstance, videoAttributesToUpd
         transaction: t
       }
 
-      const videoInstance = await db.Video.loadByUrl(videoAttributesToUpdate.id, t)
+      const videoInstance = await db.Video.loadByUrlAndPopulateAccount(videoAttributesToUpdate.id, t)
       if (!videoInstance) throw new Error('Video ' + videoAttributesToUpdate.id + ' not found.')
 
       if (videoInstance.VideoChannel.Account.id !== account.id) {
         throw new Error('Account ' + account.url + ' does not own video channel ' + videoInstance.VideoChannel.url)
       }
 
-      const videoData = await videoActivityObjectToDBAttributes(videoInstance.VideoChannel, videoAttributesToUpdate, t)
+      const videoData = await videoActivityObjectToDBAttributes(videoInstance.VideoChannel, videoAttributesToUpdate)
       videoInstance.set('name', videoData.name)
       videoInstance.set('category', videoData.category)
       videoInstance.set('licence', videoData.licence)
index f9b72f2a8d2dccc14ae12f8375a44a4c6f232f3f..d5d07011ab0d36eea214b68eabd59f4f6cca8c20 100644 (file)
@@ -24,13 +24,19 @@ async function sendUpdateVideoChannel (videoChannel: VideoChannelInstance, t: Se
   const videoChannelObject = videoChannel.toActivityPubObject()
   const data = await updateActivityData(videoChannel.url, videoChannel.Account, videoChannelObject)
 
-  return broadcastToFollowers(data, [ videoChannel.Account ], t)
+  const accountsInvolved = await db.VideoChannelShare.loadAccountsByShare(videoChannel.id)
+  accountsInvolved.push(videoChannel.Account)
+
+  return broadcastToFollowers(data, accountsInvolved, t)
 }
 
 async function sendDeleteVideoChannel (videoChannel: VideoChannelInstance, t: Sequelize.Transaction) {
   const data = await deleteActivityData(videoChannel.url, videoChannel.Account)
 
-  return broadcastToFollowers(data, [ videoChannel.Account ], t)
+  const accountsInvolved = await db.VideoChannelShare.loadAccountsByShare(videoChannel.id)
+  accountsInvolved.push(videoChannel.Account)
+
+  return broadcastToFollowers(data, accountsInvolved, t)
 }
 
 async function sendAddVideo (video: VideoInstance, t: Sequelize.Transaction) {
@@ -44,13 +50,19 @@ async function sendUpdateVideo (video: VideoInstance, t: Sequelize.Transaction)
   const videoObject = video.toActivityPubObject()
   const data = await updateActivityData(video.url, video.VideoChannel.Account, videoObject)
 
-  return broadcastToFollowers(data, [ video.VideoChannel.Account ], t)
+  const accountsInvolved = await db.VideoShare.loadAccountsByShare(video.id)
+  accountsInvolved.push(video.VideoChannel.Account)
+
+  return broadcastToFollowers(data, accountsInvolved, t)
 }
 
 async function sendDeleteVideo (video: VideoInstance, t: Sequelize.Transaction) {
   const data = await deleteActivityData(video.url, video.VideoChannel.Account)
 
-  return broadcastToFollowers(data, [ video.VideoChannel.Account ], t)
+  const accountsInvolved = await db.VideoShare.loadAccountsByShare(video.id)
+  accountsInvolved.push(video.VideoChannel.Account)
+
+  return broadcastToFollowers(data, accountsInvolved, t)
 }
 
 async function sendDeleteAccount (account: AccountInstance, t: Sequelize.Transaction) {
index 8a7474c9ddacefc0b1a805706ea5ac5dabe40220..cc9b7c42b873604234c1e248d31627c4b1ff05ec 100644 (file)
@@ -187,13 +187,13 @@ async function createListAcceptedFollowForApiQuery (
     let query = 'SELECT ' + selection + ' FROM "Accounts" ' +
       'INNER JOIN "AccountFollows" ON "AccountFollows"."' + firstJoin + '" = "Accounts"."id" ' +
       'INNER JOIN "Accounts" AS "Follows" ON "AccountFollows"."' + secondJoin + '" = "Follows"."id" ' +
-      'WHERE "Accounts"."id" IN ($accountIds) AND "AccountFollows"."state" = \'accepted\' '
+      'WHERE "Accounts"."id" = ANY ($accountIds) AND "AccountFollows"."state" = \'accepted\' '
 
     if (start !== undefined) query += 'LIMIT ' + start
     if (count !== undefined) query += ', ' + count
 
     const options = {
-      bind: { accountIds: accountIds.join(',') },
+      bind: { accountIds },
       type: Sequelize.QueryTypes.SELECT
     }
     tasks.push(AccountFollow['sequelize'].query(query, options))
index 9ac6d7b23c2d0f3893c7657170425d4a38552e86..8bb531af27b5412a24d655c405ab2ed56312d205 100644 (file)
@@ -1,11 +1,14 @@
+import * as Bluebird from 'bluebird'
 import * as Sequelize from 'sequelize'
 import { AccountInstance } from '../account/account-interface'
 import { VideoChannelInstance } from './video-channel-interface'
 
 export namespace VideoChannelShareMethods {
+  export type LoadAccountsByShare = (videoChannelId: number) => Bluebird<AccountInstance[]>
 }
 
 export interface VideoChannelShareClass {
+  loadAccountsByShare: VideoChannelShareMethods.LoadAccountsByShare
 }
 
 export interface VideoChannelShareAttributes {
index b6199279fb67b35e94708509ac3bd2d69a1d1fbc..01f84c806cb83ae72e7ec16cecb86d2abd24df05 100644 (file)
@@ -1,9 +1,10 @@
 import * as Sequelize from 'sequelize'
 
 import { addMethodsToModel } from '../utils'
-import { VideoChannelShareAttributes, VideoChannelShareInstance } from './video-channel-share-interface'
+import { VideoChannelShareAttributes, VideoChannelShareInstance, VideoChannelShareMethods } from './video-channel-share-interface'
 
 let VideoChannelShare: Sequelize.Model<VideoChannelShareInstance, VideoChannelShareAttributes>
+let loadAccountsByShare: VideoChannelShareMethods.LoadAccountsByShare
 
 export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) {
   VideoChannelShare = sequelize.define<VideoChannelShareInstance, VideoChannelShareAttributes>('VideoChannelShare',
@@ -21,7 +22,8 @@ export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.Da
   )
 
   const classMethods = [
-    associate
+    associate,
+    loadAccountsByShare
   ]
   addMethodsToModel(VideoChannelShare, classMethods)
 
@@ -47,3 +49,20 @@ function associate (models) {
     onDelete: 'cascade'
   })
 }
+
+loadAccountsByShare = function (videoChannelId: number) {
+  const query = {
+    where: {
+      videoChannelId
+    },
+    include: [
+      {
+        model: VideoChannelShare['sequelize'].models.Account,
+        required: true
+      }
+    ]
+  }
+
+  return VideoChannelShare.findAll(query)
+    .then(res => res.map(r => r.Account))
+}
index 4df33f801c44958ed8944787515cdc23a9039fd4..9f29c842cc972a807b6fb0348a06c00ec1f5bc69 100644 (file)
@@ -56,7 +56,7 @@ export namespace VideoMethods {
 
   export type Load = (id: number) => Bluebird<VideoInstance>
   export type LoadByUUID = (uuid: string, t?: Sequelize.Transaction) => Bluebird<VideoInstance>
-  export type LoadByUrl = (url: string, t?: Sequelize.Transaction) => Bluebird<VideoInstance>
+  export type LoadByUrlAndPopulateAccount = (url: string, t?: Sequelize.Transaction) => Bluebird<VideoInstance>
   export type LoadLocalVideoByUUID = (uuid: string, t?: Sequelize.Transaction) => Bluebird<VideoInstance>
   export type LoadByHostAndUUID = (fromHost: string, uuid: string, t?: Sequelize.Transaction) => Bluebird<VideoInstance>
   export type LoadAndPopulateAccount = (id: number) => Bluebird<VideoInstance>
@@ -82,7 +82,7 @@ export interface VideoClass {
   loadAndPopulateAccountAndServerAndTags: VideoMethods.LoadAndPopulateAccountAndServerAndTags
   loadByHostAndUUID: VideoMethods.LoadByHostAndUUID
   loadByUUID: VideoMethods.LoadByUUID
-  loadByUrl: VideoMethods.LoadByUrl
+  loadByUrlAndPopulateAccount: VideoMethods.LoadByUrlAndPopulateAccount
   loadByUUIDOrURL: VideoMethods.LoadByUUIDOrURL
   loadLocalVideoByUUID: VideoMethods.LoadLocalVideoByUUID
   loadByUUIDAndPopulateAccountAndServerAndTags: VideoMethods.LoadByUUIDAndPopulateAccountAndServerAndTags
index 7928b9a9cbb58db1224a2e128d5cbecfe811b551..56956884232a97d1f2bc495b299160333368608c 100644 (file)
@@ -1,11 +1,14 @@
 import * as Sequelize from 'sequelize'
 import { AccountInstance } from '../account/account-interface'
 import { VideoInstance } from './video-interface'
+import * as Bluebird from 'bluebird'
 
 export namespace VideoShareMethods {
+  export type LoadAccountsByShare = (videoChannelId: number) => Bluebird<AccountInstance[]>
 }
 
 export interface VideoShareClass {
+  loadAccountsByShare: VideoShareMethods.LoadAccountsByShare
 }
 
 export interface VideoShareAttributes {
index 358491fd2d1bb9a8eeb5bc661196be6f73bcd7e9..22ac31a4a04a917bccaa55c685bb9474c499dbac 100644 (file)
@@ -1,9 +1,10 @@
 import * as Sequelize from 'sequelize'
 
 import { addMethodsToModel } from '../utils'
-import { VideoShareAttributes, VideoShareInstance } from './video-share-interface'
+import { VideoShareAttributes, VideoShareInstance, VideoShareMethods } from './video-share-interface'
 
 let VideoShare: Sequelize.Model<VideoShareInstance, VideoShareAttributes>
+let loadAccountsByShare: VideoShareMethods.LoadAccountsByShare
 
 export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) {
   VideoShare = sequelize.define<VideoShareInstance, VideoShareAttributes>('VideoShare',
@@ -21,7 +22,8 @@ export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.Da
   )
 
   const classMethods = [
-    associate
+    associate,
+    loadAccountsByShare
   ]
   addMethodsToModel(VideoShare, classMethods)
 
@@ -47,3 +49,20 @@ function associate (models) {
     onDelete: 'cascade'
   })
 }
+
+loadAccountsByShare = function (videoId: number) {
+  const query = {
+    where: {
+      videoId
+    },
+    include: [
+      {
+        model: VideoShare['sequelize'].models.Account,
+        required: true
+      }
+    ]
+  }
+
+  return VideoShare.findAll(query)
+    .then(res => res.map(r => r.Account))
+}
index 64ee7ae344b94e53e06180cffb9b56cb57eb962c..5b0377c2e67e25df3553090cb4e50b0f38cee0f9 100644 (file)
@@ -84,6 +84,7 @@ let loadByHostAndUUID: VideoMethods.LoadByHostAndUUID
 let listOwnedAndPopulateAccountAndTags: VideoMethods.ListOwnedAndPopulateAccountAndTags
 let listOwnedByAccount: VideoMethods.ListOwnedByAccount
 let load: VideoMethods.Load
+let loadByUrlAndPopulateAccount: VideoMethods.LoadByUrlAndPopulateAccount
 let loadByUUID: VideoMethods.LoadByUUID
 let loadByUUIDOrURL: VideoMethods.LoadByUUIDOrURL
 let loadLocalVideoByUUID: VideoMethods.LoadLocalVideoByUUID
@@ -271,6 +272,7 @@ export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.Da
     listOwnedAndPopulateAccountAndTags,
     listOwnedByAccount,
     load,
+    loadByUrlAndPopulateAccount,
     loadAndPopulateAccount,
     loadAndPopulateAccountAndServerAndTags,
     loadByHostAndUUID,
@@ -936,6 +938,25 @@ loadByUUID = function (uuid: string, t?: Sequelize.Transaction) {
   return Video.findOne(query)
 }
 
+loadByUrlAndPopulateAccount = function (url: string, t?: Sequelize.Transaction) {
+  const query: Sequelize.FindOptions<VideoAttributes> = {
+    where: {
+      url
+    },
+    include: [
+      Video['sequelize'].models.VideoFile,
+      {
+        model: Video['sequelize'].models.VideoChannel,
+        include: [ Video['sequelize'].models.Account ]
+      }
+    ]
+  }
+
+  if (t !== undefined) query.transaction = t
+
+  return Video.findOne(query)
+}
+
 loadByUUIDOrURL = function (uuid: string, url: string, t?: Sequelize.Transaction) {
   const query: Sequelize.FindOptions<VideoAttributes> = {
     where: {