Avoid too many requests and fetching outbox
authorChocobozzz <me@florianbigard.com>
Thu, 18 Jan 2018 13:59:27 +0000 (14:59 +0100)
committerChocobozzz <me@florianbigard.com>
Thu, 18 Jan 2018 14:42:20 +0000 (15:42 +0100)
server/controllers/activitypub/outbox.ts
server/lib/activitypub/send/misc.ts
server/models/activitypub/actor-follow.ts
server/models/activitypub/actor.ts
server/models/video/video.ts
server/tests/api/server/follows.ts

index 620f9ee83c973fd71e8e90f89533e3b9a0e78b14..ab12a7c4b96a324b21b9cb225e5b8384cf4ff608 100644 (file)
@@ -4,9 +4,11 @@ import { activityPubCollectionPagination } from '../../helpers/activitypub'
 import { pageToStartAndCount } from '../../helpers/core-utils'
 import { ACTIVITY_PUB } from '../../initializers/constants'
 import { announceActivityData, createActivityData } from '../../lib/activitypub/send'
+import { buildAudience } from '../../lib/activitypub/send/misc'
 import { getAnnounceActivityPubUrl } from '../../lib/activitypub/url'
 import { asyncMiddleware, localAccountValidator } from '../../middlewares'
 import { AccountModel } from '../../models/account/account'
+import { ActorModel } from '../../models/activitypub/actor'
 import { VideoModel } from '../../models/video/video'
 
 const outboxRouter = express.Router()
@@ -34,20 +36,29 @@ async function outboxController (req: express.Request, res: express.Response, ne
   const data = await VideoModel.listAllAndSharedByActorForOutbox(actor.id, start, count)
   const activities: Activity[] = []
 
+  // Avoid too many SQL requests
+  const actors = data.data.map(v => v.VideoChannel.Account.Actor)
+  actors.push(actor)
+
+  const followersMatrix = await ActorModel.getActorsFollowerSharedInboxUrls(actors, undefined)
+
   for (const video of data.data) {
     const videoObject = video.toActivityPubObject()
 
-    const videoChannel = video.VideoChannel
+    const byActor = video.VideoChannel.Account.Actor
+    const createActivityAudience = buildAudience(followersMatrix[byActor.id])
+
     // This is a shared video
     if (video.VideoShares !== undefined && video.VideoShares.length !== 0) {
-      const createActivity = await createActivityData(video.url, videoChannel.Account.Actor, videoObject, undefined)
+      const createActivity = await createActivityData(video.url, byActor, videoObject, undefined, createActivityAudience)
 
+      const announceAudience = buildAudience(followersMatrix[actor.id])
       const url = getAnnounceActivityPubUrl(video.url, actor)
-      const announceActivity = await announceActivityData(url, actor, createActivity, undefined)
+      const announceActivity = await announceActivityData(url, actor, createActivity, undefined, announceAudience)
 
       activities.push(announceActivity)
     } else {
-      const createActivity = await createActivityData(video.url, videoChannel.Account.Actor, videoObject, undefined)
+      const createActivity = await createActivityData(video.url, byActor, videoObject, undefined, createActivityAudience)
 
       activities.push(createActivity)
     }
index 261586ae43b3bdbd26aaf7ca1d8b8cff4b07916d..dc0d3de57e0a0fd82fe98f8de4b30cf2cb097f87 100644 (file)
@@ -143,6 +143,10 @@ async function getActorsInvolvedInVideo (video: VideoModel, t: Transaction) {
 async function getAudience (actorSender: ActorModel, t: Transaction, isPublic = true) {
   const followerInboxUrls = await actorSender.getFollowerSharedInboxUrls(t)
 
+  return buildAudience(followerInboxUrls, isPublic)
+}
+
+function buildAudience (followerInboxUrls: string[], isPublic = true) {
   // Thanks Mastodon: https://github.com/tootsuite/mastodon/blob/master/app/lib/activitypub/tag_manager.rb#L47
   let to = []
   let cc = []
@@ -183,6 +187,7 @@ async function computeUris (toActors: ActorModel[], actorsException: ActorModel[
 export {
   broadcastToFollowers,
   unicastTo,
+  buildAudience,
   getAudience,
   getOriginVideoAudience,
   getActorsInvolvedInVideo,
index ced48154705187f57271454407e817c37edd9367..416496607f9e4ba30141561ac804c55efb6f43ff 100644 (file)
@@ -375,7 +375,8 @@ export class ActorFollowModel extends Model<ActorFollowModel> {
         score: {
           [Sequelize.Op.lte]: 0
         }
-      }
+      },
+      logger: false
     }
 
     return ActorFollowModel.findAll(query)
index 408d4df23490cb793851333f47cbea6237ce97a7..269149a314dd12e150b76812ab2f2cd027047778 100644 (file)
@@ -167,17 +167,17 @@ export class ActorModel extends Model<ActorModel> {
     },
     onDelete: 'cascade'
   })
-  AccountFollowing: ActorFollowModel[]
+  ActorFollowing: ActorFollowModel[]
 
   @HasMany(() => ActorFollowModel, {
     foreignKey: {
       name: 'targetActorId',
       allowNull: false
     },
-    as: 'followers',
+    as: 'ActorFollowers',
     onDelete: 'cascade'
   })
-  AccountFollowers: ActorFollowModel[]
+  ActorFollowers: ActorFollowModel[]
 
   @ForeignKey(() => ServerModel)
   @Column
@@ -277,6 +277,45 @@ export class ActorModel extends Model<ActorModel> {
     })
   }
 
+  static async getActorsFollowerSharedInboxUrls (actors: ActorModel[], t: Sequelize.Transaction) {
+    const query = {
+      // attribute: [],
+      where: {
+        id: {
+          [Sequelize.Op.in]: actors.map(a => a.id)
+        }
+      },
+      include: [
+        {
+          // attributes: [ ],
+          model: ActorFollowModel.unscoped(),
+          required: true,
+          as: 'ActorFollowers',
+          where: {
+            state: 'accepted'
+          },
+          include: [
+            {
+              attributes: [ 'sharedInboxUrl' ],
+              model: ActorModel.unscoped(),
+              as: 'ActorFollower',
+              required: true
+            }
+          ]
+        }
+      ],
+      transaction: t
+    }
+
+    const hash: { [ id: number ]: string[] } = {}
+    const res = await ActorModel.findAll(query)
+    for (const actor of res) {
+      hash[actor.id] = actor.ActorFollowers.map(follow => follow.ActorFollower.sharedInboxUrl)
+    }
+
+    return hash
+  }
+
   toFormattedJSON () {
     let avatar: Avatar = null
     if (this.Avatar) {
@@ -347,10 +386,12 @@ export class ActorModel extends Model<ActorModel> {
       attributes: [ 'sharedInboxUrl' ],
       include: [
         {
-          model: ActorFollowModel,
+          attribute: [],
+          model: ActorFollowModel.unscoped(),
           required: true,
-          as: 'followers',
+          as: 'ActorFollowers',
           where: {
+            state: 'accepted',
             targetActorId: this.id
           }
         }
index 514edfd9cdb93fa3ed9b069355fab274d65352cb..0d115367f81e27b5b7f569330784e3ecc00c47fd 100644 (file)
@@ -67,7 +67,7 @@ enum ScopeNames {
           '$VideoChannel.Account.Actor.serverId$': null
         },
         {
-          '$VideoChannel.Account.Actor.followers.actorId$': actorId
+          '$VideoChannel.Account.Actor.ActorFollowers.actorId$': actorId
         },
         {
           id: {
@@ -106,7 +106,7 @@ enum ScopeNames {
                   {
                     attributes: [ ],
                     model: ActorFollowModel.unscoped(),
-                    as: 'followers',
+                    as: 'ActorFollowers',
                     required: false
                   }
                 ]
index ac614d605311804d3d4b5357bbf02d22c2cf59f0..c0115e534c6db7e425895404a69573f89272563c 100644 (file)
@@ -4,7 +4,7 @@ import * as chai from 'chai'
 import 'mocha'
 import { Video, VideoPrivacy } from '../../../../shared/models/videos'
 import { VideoComment, VideoCommentThreadTree } from '../../../../shared/models/videos/video-comment.model'
-import { checkVideoFilesWereRemoved, completeVideoCheck, getVideoChannelsList } from '../../utils'
+import { checkVideoFilesWereRemoved, completeVideoCheck } from '../../utils'
 
 import {
   flushAndRunMultipleServers, flushTests, getVideosList, killallServers, ServerInfo, setAccessTokensToServers, uploadVideo,
@@ -12,7 +12,7 @@ import {
 } from '../../utils/index'
 import { dateIsValid } from '../../utils/miscs/miscs'
 import { follow, getFollowersListPaginationAndSort, getFollowingListPaginationAndSort, unfollow } from '../../utils/server/follows'
-import { expectAccountFollows, getAccountsList } from '../../utils/users/accounts'
+import { expectAccountFollows } from '../../utils/users/accounts'
 import { userLogin } from '../../utils/users/login'
 import { createUser } from '../../utils/users/users'
 import {
@@ -354,12 +354,6 @@ describe('Test follows', function () {
       let res = await getVideosList(servers[ 0 ].url)
       expect(res.body.total).to.equal(1)
 
-      res = await getVideoChannelsList(servers[0].url, 0, 1)
-      expect(res.body.total).to.equal(2)
-
-      res = await getAccountsList(servers[0].url)
-      expect(res.body.total).to.equal(2)
-
       await checkVideoFilesWereRemoved(video4.uuid, servers[0].serverNumber)
     })