Fetch video likes/dislikes too
authorChocobozzz <florian.bigard@gmail.com>
Thu, 23 Nov 2017 15:55:13 +0000 (16:55 +0100)
committerChocobozzz <florian.bigard@gmail.com>
Mon, 27 Nov 2017 18:40:53 +0000 (19:40 +0100)
server/controllers/activitypub/inbox.ts
server/helpers/activitypub.ts
server/lib/activitypub/process/process-add.ts
server/lib/activitypub/process/process.ts
server/lib/jobs/activitypub-http-job-scheduler/activitypub-http-fetcher-handler.ts
server/models/account/account-video-rate-interface.ts
server/models/video/video-interface.ts
server/models/video/video.ts
shared/models/activitypub/activitypub-ordered-collection.ts
shared/models/activitypub/activitypub-root.ts
shared/models/activitypub/objects/video-torrent-object.ts

index 30e7f706b95448bf45d2ad012625d22c3b1f6775..243ae738191e7f67ce538359c988fd820e01d633 100644 (file)
@@ -38,7 +38,7 @@ async function inboxController (req: express.Request, res: express.Response, nex
   if ([ 'Collection', 'CollectionPage' ].indexOf(rootActivity.type) !== -1) {
     activities = (rootActivity as ActivityPubCollection).items
   } else if ([ 'OrderedCollection', 'OrderedCollectionPage' ].indexOf(rootActivity.type) !== -1) {
-    activities = (rootActivity as ActivityPubOrderedCollection).orderedItems
+    activities = (rootActivity as ActivityPubOrderedCollection<Activity>).orderedItems
   } else {
     activities = [ rootActivity as Activity ]
   }
index 54c460200fe127abe5d528c3d90ce2d685dc34ac..1ea6422ca111f441dccc17c22543e84d7bf1248a 100644 (file)
@@ -24,6 +24,14 @@ function activityPubContextify <T> (data: T) {
   })
 }
 
+function activityPubCollection (results: any[]) {
+  return {
+    type: 'OrderedCollection',
+    totalItems: results.length,
+    orderedItems: results
+  }
+}
+
 function activityPubCollectionPagination (url: string, page: any, result: ResultList<any>) {
   let next: string
   let prev: string
@@ -74,5 +82,6 @@ function buildSignedActivity (byAccount: AccountInstance, data: Object) {
 export {
   activityPubContextify,
   activityPubCollectionPagination,
+  activityPubCollection,
   buildSignedActivity
 }
index 332c18cc06c2be71e7b7c1aaf5149963cff93a04..433e68eb6c8a6dadffc01284515e5ebb069fa5ae 100644 (file)
@@ -1,11 +1,13 @@
 import * as Bluebird from 'bluebird'
 import { VideoTorrentObject } from '../../../../shared'
 import { ActivityAdd } from '../../../../shared/models/activitypub/activity'
+import { VideoRateType } from '../../../../shared/models/videos/video-rate.type'
 import { retryTransactionWrapper } from '../../../helpers/database-utils'
 import { logger } from '../../../helpers/logger'
 import { database as db } from '../../../initializers'
 import { AccountInstance } from '../../../models/account/account-interface'
 import { VideoChannelInstance } from '../../../models/video/video-channel-interface'
+import { VideoInstance } from '../../../models/video/video-interface'
 import { getOrCreateAccountAndServer } from '../account'
 import { getOrCreateVideoChannel } from '../video-channels'
 import { generateThumbnailFromUrl } from '../videos'
@@ -35,13 +37,29 @@ export {
 
 // ---------------------------------------------------------------------------
 
-function processAddVideo (account: AccountInstance, activity: ActivityAdd, videoChannel: VideoChannelInstance, video: VideoTorrentObject) {
+async function processAddVideo (
+  account: AccountInstance,
+  activity: ActivityAdd,
+  videoChannel: VideoChannelInstance,
+  videoToCreateData: VideoTorrentObject
+) {
   const options = {
-    arguments: [ account, activity, videoChannel, video ],
+    arguments: [ account, activity, videoChannel, videoToCreateData ],
     errorMessage: 'Cannot insert the remote video with many retries.'
   }
 
-  return retryTransactionWrapper(addRemoteVideo, options)
+  const video = await retryTransactionWrapper(addRemoteVideo, options)
+
+  // Process outside the transaction because we could fetch remote data
+  if (videoToCreateData.likes && Array.isArray(videoToCreateData.likes.orderedItems)) {
+    await createRates(videoToCreateData.likes.orderedItems, video, 'like')
+  }
+
+  if (videoToCreateData.dislikes && Array.isArray(videoToCreateData.dislikes.orderedItems)) {
+    await createRates(videoToCreateData.dislikes.orderedItems, video, 'dislike')
+  }
+
+  return video
 }
 
 function addRemoteVideo (account: AccountInstance,
@@ -86,3 +104,30 @@ function addRemoteVideo (account: AccountInstance,
     return videoCreated
   })
 }
+
+async function createRates (accountUrls: string[], video: VideoInstance, rate: VideoRateType) {
+  let rateCounts = 0
+  const tasks: Bluebird<any>[] = []
+
+  for (const accountUrl of accountUrls) {
+    const account = await getOrCreateAccountAndServer(accountUrl)
+    const p = db.AccountVideoRate
+      .create({
+        videoId: video.id,
+        accountId: account.id,
+        type: rate
+      })
+      .then(() => rateCounts += 1)
+
+    tasks.push(p)
+  }
+
+  await Promise.all(tasks)
+
+  logger.info('Adding %d %s to video %s.', rateCounts, rate, video.uuid)
+
+  // This is "likes" and "dislikes"
+  await video.increment(rate + 's', { by: rateCounts })
+
+  return
+}
index 942bce0e6245e9c41e8d94e5e465ca390e45c999..40f19c7010ed22e38d20c181eabd68ee82ea16bc 100644 (file)
@@ -31,7 +31,11 @@ async function processActivities (activities: Activity[], inboxAccount?: Account
       continue
     }
 
-    await activityProcessor(activity, inboxAccount)
+    try {
+      await activityProcessor(activity, inboxAccount)
+    } catch (err) {
+      logger.warn('Cannot process activity %s.', activity.type, err)
+    }
   }
 }
 
index bda3195924064cf4e8a644926a93f386f7e9c05e..9adceab84b04f42a1dfc477f716119d5c5513cbe 100644 (file)
@@ -49,7 +49,7 @@ async function process (payload: ActivityPubHttpPayload, jobId: number) {
 }
 
 function onError (err: Error, jobId: number) {
-  logger.error('Error when broadcasting ActivityPub request in job %d.', jobId, err)
+  logger.error('Error when fetcher ActivityPub request in job %d.', jobId, err)
   return Promise.resolve()
 }
 
index 316056246fe217f39df387408461e32f0c1c4966..1f395bc454db7a473bbe44318283559434d62e1d 100644 (file)
@@ -2,6 +2,7 @@ import * as Sequelize from 'sequelize'
 import * as Promise from 'bluebird'
 
 import { VideoRateType } from '../../../shared/models/videos/video-rate.type'
+import { AccountInstance } from './account-interface'
 
 export namespace AccountVideoRateMethods {
   export type Load = (accountId: number, videoId: number, transaction: Sequelize.Transaction) => Promise<AccountVideoRateInstance>
@@ -15,6 +16,8 @@ export interface AccountVideoRateAttributes {
   type: VideoRateType
   accountId: number
   videoId: number
+
+  Account?: AccountInstance
 }
 
 export interface AccountVideoRateInstance
index b97f163ab0d796ae2e931853231cfae6b4a7941c..89e528acf2ed60489ae3efb683cf0e4a999f948b 100644 (file)
@@ -8,6 +8,8 @@ import { TagAttributes, TagInstance } from './tag-interface'
 import { VideoChannelInstance } from './video-channel-interface'
 import { VideoFileAttributes, VideoFileInstance } from './video-file-interface'
 import { VideoShareInstance } from './video-share-interface'
+import { UserVideoRate } from '../../../shared/models/videos/user-video-rate.model'
+import { AccountVideoRateInstance } from '../account/account-video-rate-interface'
 
 export namespace VideoMethods {
   export type GetThumbnailName = (this: VideoInstance) => string
@@ -123,6 +125,7 @@ export interface VideoAttributes {
   Tags?: TagInstance[]
   VideoFiles?: VideoFileInstance[]
   VideoShares?: VideoShareInstance[]
+  AccountVideoRates?: AccountVideoRateInstance[]
 }
 
 export interface VideoInstance extends VideoClass, VideoAttributes, Sequelize.Instance<VideoAttributes> {
index 052fc0ae8e493f1de3b29cb2f0bef1a425f1d47c..592fc2d595801b7549146132d6c24a793dd7dc1e 100644 (file)
@@ -47,6 +47,7 @@ import { VideoFileInstance, VideoFileModel } from './video-file-interface'
 import { VideoAttributes, VideoInstance, VideoMethods } from './video-interface'
 import { sendDeleteVideo } from '../../lib/index'
 import * as Bluebird from 'bluebird'
+import { activityPubCollection } from '../../helpers/activitypub'
 
 const Buffer = safeBuffer.Buffer
 
@@ -359,6 +360,14 @@ function associate (models) {
     },
     onDelete: 'cascade'
   })
+
+  Video.hasMany(models.AccountVideoRate, {
+    foreignKey: {
+      name: 'videoId',
+      allowNull: false
+    },
+    onDelete: 'cascade'
+  })
 }
 
 function afterDestroy (video: VideoInstance) {
@@ -575,6 +584,25 @@ toActivityPubObject = function (this: VideoInstance) {
     }
   }
 
+  let likesObject
+  let dislikesObject
+
+  if (Array.isArray(this.AccountVideoRates)) {
+    const likes: string[] = []
+    const dislikes: string[] = []
+
+    for (const rate of this.AccountVideoRates) {
+      if (rate.type === 'like') {
+        likes.push(rate.Account.url)
+      } else if (rate.type === 'dislike') {
+        dislikes.push(rate.Account.url)
+      }
+    }
+
+    likesObject = activityPubCollection(likes)
+    dislikesObject = activityPubCollection(dislikes)
+  }
+
   const url = []
   for (const file of this.VideoFiles) {
     url.push({
@@ -630,7 +658,9 @@ toActivityPubObject = function (this: VideoInstance) {
       width: THUMBNAILS_SIZE.width,
       height: THUMBNAILS_SIZE.height
     },
-    url // FIXME: needed?
+    url,
+    likes: likesObject,
+    dislikes: dislikesObject
   }
 
   return videoObject
@@ -845,8 +875,12 @@ listAllAndSharedByAccountForOutbox = function (accountId: number, start: number,
           }
         ]
       },
-      Video['sequelize'].models.Tag,
-      Video['sequelize'].models.VideoFile
+      {
+        model: Video['sequelize'].models.AccountVideoRate,
+        include: [ Video['sequelize'].models.Account ]
+      },
+      Video['sequelize'].models.VideoFile,
+      Video['sequelize'].models.Tag
     ]
   }
 
@@ -1106,6 +1140,10 @@ loadAndPopulateAccountAndServerAndTags = function (id: number) {
           }
         ]
       },
+      {
+        model: Video['sequelize'].models.AccountVideoRate,
+        include: [ Video['sequelize'].models.Account ]
+      },
       Video['sequelize'].models.Tag,
       Video['sequelize'].models.VideoFile
     ]
@@ -1129,6 +1167,10 @@ loadByUUIDAndPopulateAccountAndServerAndTags = function (uuid: string) {
           }
         ]
       },
+      {
+        model: Video['sequelize'].models.AccountVideoRate,
+        include: [ Video['sequelize'].models.Account ]
+      },
       Video['sequelize'].models.Tag,
       Video['sequelize'].models.VideoFile
     ]
index 4080fd740e10b6620ebefd0ecf25536f4ac8a8b6..487d8cee07f10cc4b7f18ef1cbae2becaf00a832 100644 (file)
@@ -1,9 +1,9 @@
 import { Activity } from './activity'
 
-export interface ActivityPubOrderedCollection {
+export interface ActivityPubOrderedCollection<T> {
   '@context': string[]
   type: 'OrderedCollection' | 'OrderedCollectionPage'
   totalItems: number
   partOf?: string
-  orderedItems: Activity[]
+  orderedItems: T[]
 }
index 6a67f31010d6c06415022f51c5cd9dc5f6c2efc5..22dff83a21e77e4e8fa2be32248bcf9f8071278e 100644 (file)
@@ -2,4 +2,4 @@ import { Activity } from './activity'
 import { ActivityPubCollection } from './activitypub-collection'
 import { ActivityPubOrderedCollection } from './activitypub-ordered-collection'
 
-export type RootActivity = Activity | ActivityPubCollection | ActivityPubOrderedCollection
+export type RootActivity = Activity | ActivityPubCollection | ActivityPubOrderedCollection<Activity>
index ae8f807c8a4f7af49a08319c7858b35d13d1188e..a4e032d042f58e773d1496efb871f4fdde947f29 100644 (file)
@@ -4,6 +4,7 @@ import {
   ActivityTagObject,
   ActivityUrlObject
 } from './common-objects'
+import { ActivityPubOrderedCollection } from '../activitypub-ordered-collection'
 
 export interface VideoTorrentObject {
   type: 'Video'
@@ -24,4 +25,6 @@ export interface VideoTorrentObject {
   icon: ActivityIconObject
   url: ActivityUrlObject[]
   actor?: string
+  likes?: ActivityPubOrderedCollection<string>
+  dislikes?: ActivityPubOrderedCollection<string>
 }