Update follower/following counts
authorChocobozzz <me@florianbigard.com>
Fri, 12 Jan 2018 10:47:45 +0000 (11:47 +0100)
committerChocobozzz <me@florianbigard.com>
Fri, 12 Jan 2018 10:47:45 +0000 (11:47 +0100)
server/initializers/constants.ts
server/initializers/migrations/0175-actor-follow-counts.ts [new file with mode: 0644]
server/lib/activitypub/actor.ts
server/lib/activitypub/process/process-follow.ts
server/models/activitypub/actor-follow.ts
server/models/activitypub/actor.ts
server/tests/api/server/follows.ts
server/tests/utils/users/accounts.ts

index eda3403f25592aa31747a5924b1a3a3d5297fed9..2c64efe1fbd2b5c6926c6c2627af3186882df22f 100644 (file)
@@ -9,7 +9,7 @@ import { isTestInstance, root, sanitizeHost, sanitizeUrl } from '../helpers/core
 
 // ---------------------------------------------------------------------------
 
-const LAST_MIGRATION_VERSION = 170
+const LAST_MIGRATION_VERSION = 175
 
 // ---------------------------------------------------------------------------
 
diff --git a/server/initializers/migrations/0175-actor-follow-counts.ts b/server/initializers/migrations/0175-actor-follow-counts.ts
new file mode 100644 (file)
index 0000000..06ef77b
--- /dev/null
@@ -0,0 +1,24 @@
+import * as Sequelize from 'sequelize'
+import { ACTOR_FOLLOW_SCORE } from '../index'
+
+async function up (utils: {
+  transaction: Sequelize.Transaction,
+  queryInterface: Sequelize.QueryInterface,
+  sequelize: Sequelize.Sequelize
+}): Promise<void> {
+  const query = 'UPDATE "actor" SET ' +
+  '"followersCount" = (SELECT COUNT(*) FROM "actorFollow" WHERE "actor"."id" = "actorFollow"."targetActorId"), ' +
+  '"followingCount" = (SELECT COUNT(*) FROM "actorFollow" WHERE "actor"."id" = "actorFollow"."actorId") ' +
+  'WHERE "actor"."serverId" IS NULL'
+
+  await utils.sequelize.query(query)
+}
+
+function down (options) {
+  throw new Error('Not implemented.')
+}
+
+export {
+  up,
+  down
+}
index 1e1eab54ae3d3b337b0bda2753630757c991f9df..b3fb75421e7651004503b7b03ecc19478ba9390b 100644 (file)
@@ -314,6 +314,7 @@ async function refreshActorIfNeeded (actor: ActorModel) {
   if (result === undefined) throw new Error('Cannot fetch remote actor in refresh actor.')
 
   return sequelizeTypescript.transaction(async t => {
+    logger.info('coucou', result.actor.toJSON())
     updateInstanceWithAnother(actor, result.actor)
 
     if (result.avatarName !== undefined) {
index 5085c5da9af690b7b90adeed282808234be5aa32..69f5c51b55960a805cf12b6774b24bf97a37ae79 100644 (file)
@@ -51,6 +51,9 @@ async function follow (actor: ActorModel, targetActorURL: string) {
       transaction: t
     })
 
+    actorFollow.ActorFollower = actor
+    actorFollow.ActorFollowing = targetActor
+
     if (actorFollow.state !== 'accepted') {
       actorFollow.state = 'accepted'
       await actorFollow.save({ transaction: t })
index de5bb6f7440633c0d924d300ed41dc6d4070be6f..435d22db5f6afb18eff1c4652f197cb362608094 100644 (file)
@@ -2,6 +2,7 @@ import * as Bluebird from 'bluebird'
 import { values } from 'lodash'
 import * as Sequelize from 'sequelize'
 import {
+  AfterCreate, AfterDestroy, AfterUpdate,
   AllowNull, BelongsTo, Column, CreatedAt, DataType, Default, ForeignKey, IsInt, Max, Model, Table,
   UpdatedAt
 } from 'sequelize-typescript'
@@ -79,6 +80,25 @@ export class ActorFollowModel extends Model<ActorFollowModel> {
   })
   ActorFollowing: ActorModel
 
+  @AfterCreate
+  @AfterUpdate
+  static incrementFollowerAndFollowingCount (instance: ActorFollowModel) {
+    if (instance.state !== 'accepted') return
+
+    return Promise.all([
+      ActorModel.incrementFollows(instance.actorId, 'followingCount', 1),
+      ActorModel.incrementFollows(instance.targetActorId, 'followersCount', 1)
+    ])
+  }
+
+  @AfterDestroy
+  static decrementFollowerAndFollowingCount (instance: ActorFollowModel) {
+    return Promise.all([
+      ActorModel.incrementFollows(instance.actorId, 'followingCount',-1),
+      ActorModel.incrementFollows(instance.targetActorId, 'followersCount', -1)
+    ])
+  }
+
   // Remove actor follows with a score of 0 (too many requests where they were unreachable)
   static async removeBadActorFollows () {
     const actorFollows = await ActorFollowModel.listBadActorFollows()
index 17f69f7a7aaa157caef66c60695211be230d0f48..b7be9c32c62d87d0087a74fe3cc05520fc1ad4b1 100644 (file)
@@ -264,6 +264,16 @@ export class ActorModel extends Model<ActorModel> {
     return ActorModel.scope(ScopeNames.FULL).findOne(query)
   }
 
+  static incrementFollows (id: number, column: 'followersCount' | 'followingCount', by: number) {
+    // FIXME: typings
+    return (ActorModel as any).increment(column, {
+      by,
+      where: {
+        id
+      }
+    })
+  }
+
   toFormattedJSON () {
     let avatar: Avatar = null
     if (this.Avatar) {
index e6dfd5f62a5e54771e522be690a139f6f0182801..27cf94985493d954f8c804a3db13ab28c0cc0f8e 100644 (file)
@@ -12,6 +12,7 @@ import {
 } from '../../utils/index'
 import { dateIsValid } from '../../utils/miscs/miscs'
 import { follow, getFollowersListPaginationAndSort, getFollowingListPaginationAndSort, unfollow } from '../../utils/server/follows'
+import { expectAccountFollows } from '../../utils/users/accounts'
 import { userLogin } from '../../utils/users/login'
 import { createUser } from '../../utils/users/users'
 import {
@@ -116,6 +117,19 @@ describe('Test follows', function () {
     expect(follows.length).to.equal(0)
   })
 
+  it('Should have the correct following counts', async function () {
+    await expectAccountFollows(servers[0].url, 'peertube@localhost:9001', 0, 2)
+    await expectAccountFollows(servers[0].url, 'peertube@localhost:9002', 1, 0)
+    await expectAccountFollows(servers[0].url, 'peertube@localhost:9003', 1, 0)
+
+    // Server 2 and 3 does not know server 1 follow another server (there was not a refresh)
+    await expectAccountFollows(servers[1].url, 'peertube@localhost:9001', 0, 1)
+    await expectAccountFollows(servers[1].url, 'peertube@localhost:9002', 1, 0)
+
+    await expectAccountFollows(servers[2].url, 'peertube@localhost:9001', 0, 1)
+    await expectAccountFollows(servers[2].url, 'peertube@localhost:9003', 1, 0)
+  })
+
   it('Should unfollow server 3 on server 1', async function () {
     this.timeout(5000)
 
@@ -144,6 +158,17 @@ describe('Test follows', function () {
     expect(follows.length).to.equal(0)
   })
 
+  it('Should have the correct following counts 2', async function () {
+    await expectAccountFollows(servers[0].url, 'peertube@localhost:9001', 0, 1)
+    await expectAccountFollows(servers[0].url, 'peertube@localhost:9002', 1, 0)
+
+    await expectAccountFollows(servers[1].url, 'peertube@localhost:9001', 0, 1)
+    await expectAccountFollows(servers[1].url, 'peertube@localhost:9002', 1, 0)
+
+    await expectAccountFollows(servers[2].url, 'peertube@localhost:9001', 0, 0)
+    await expectAccountFollows(servers[2].url, 'peertube@localhost:9003', 0, 0)
+  })
+
   it('Should upload a video on server 2 ans 3 and propagate only the video of server 2', async function () {
     this.timeout(10000)
 
@@ -223,6 +248,18 @@ describe('Test follows', function () {
       await wait(7000)
     })
 
+    it('Should have the correct following counts 2', async function () {
+      await expectAccountFollows(servers[0].url, 'peertube@localhost:9001', 0, 2)
+      await expectAccountFollows(servers[0].url, 'peertube@localhost:9002', 1, 0)
+      await expectAccountFollows(servers[0].url, 'peertube@localhost:9003', 1, 0)
+
+      await expectAccountFollows(servers[1].url, 'peertube@localhost:9001', 0, 1)
+      await expectAccountFollows(servers[1].url, 'peertube@localhost:9002', 1, 0)
+
+      await expectAccountFollows(servers[2].url, 'peertube@localhost:9001', 0, 1)
+      await expectAccountFollows(servers[2].url, 'peertube@localhost:9003', 1, 0)
+    })
+
     it('Should propagate videos', async function () {
       const res = await getVideosList(servers[ 0 ].url)
       expect(res.body.total).to.equal(7)
index 71712100e0db19db8bf2a93b63f606afb359bc67..0ec7992b3763372d0158a674d8722a660f20e60c 100644 (file)
@@ -1,3 +1,5 @@
+import { expect } from 'chai'
+import { Account } from '../../../../shared/models/actors'
 import { makeGetRequest } from '../requests/requests'
 
 function getAccountsList (url: string, sort = '-createdAt', statusCodeExpected = 200) {
@@ -21,9 +23,19 @@ function getAccount (url: string, accountId: number | string, statusCodeExpected
   })
 }
 
+async function expectAccountFollows (url: string, nameWithDomain: string, followersCount: number, followingCount: number) {
+  const res = await getAccountsList(url)
+  const account = res.body.data.find((a: Account) => a.name + '@' + a.host === nameWithDomain)
+
+  const message = `${nameWithDomain} on ${url}`
+  expect(account.followersCount).to.equal(followersCount, message)
+  expect(account.followingCount).to.equal(followingCount, message)
+}
+
 // ---------------------------------------------------------------------------
 
 export {
   getAccount,
+  expectAccountFollows,
   getAccountsList
 }