diff options
-rw-r--r-- | server/controllers/activitypub/outbox.ts | 19 | ||||
-rw-r--r-- | server/lib/activitypub/send/misc.ts | 5 | ||||
-rw-r--r-- | server/models/activitypub/actor-follow.ts | 3 | ||||
-rw-r--r-- | server/models/activitypub/actor.ts | 51 | ||||
-rw-r--r-- | server/models/video/video.ts | 4 | ||||
-rw-r--r-- | server/tests/api/server/follows.ts | 10 |
6 files changed, 72 insertions, 20 deletions
diff --git a/server/controllers/activitypub/outbox.ts b/server/controllers/activitypub/outbox.ts index 620f9ee83..ab12a7c4b 100644 --- a/server/controllers/activitypub/outbox.ts +++ b/server/controllers/activitypub/outbox.ts | |||
@@ -4,9 +4,11 @@ import { activityPubCollectionPagination } from '../../helpers/activitypub' | |||
4 | import { pageToStartAndCount } from '../../helpers/core-utils' | 4 | import { pageToStartAndCount } from '../../helpers/core-utils' |
5 | import { ACTIVITY_PUB } from '../../initializers/constants' | 5 | import { ACTIVITY_PUB } from '../../initializers/constants' |
6 | import { announceActivityData, createActivityData } from '../../lib/activitypub/send' | 6 | import { announceActivityData, createActivityData } from '../../lib/activitypub/send' |
7 | import { buildAudience } from '../../lib/activitypub/send/misc' | ||
7 | import { getAnnounceActivityPubUrl } from '../../lib/activitypub/url' | 8 | import { getAnnounceActivityPubUrl } from '../../lib/activitypub/url' |
8 | import { asyncMiddleware, localAccountValidator } from '../../middlewares' | 9 | import { asyncMiddleware, localAccountValidator } from '../../middlewares' |
9 | import { AccountModel } from '../../models/account/account' | 10 | import { AccountModel } from '../../models/account/account' |
11 | import { ActorModel } from '../../models/activitypub/actor' | ||
10 | import { VideoModel } from '../../models/video/video' | 12 | import { VideoModel } from '../../models/video/video' |
11 | 13 | ||
12 | const outboxRouter = express.Router() | 14 | const outboxRouter = express.Router() |
@@ -34,20 +36,29 @@ async function outboxController (req: express.Request, res: express.Response, ne | |||
34 | const data = await VideoModel.listAllAndSharedByActorForOutbox(actor.id, start, count) | 36 | const data = await VideoModel.listAllAndSharedByActorForOutbox(actor.id, start, count) |
35 | const activities: Activity[] = [] | 37 | const activities: Activity[] = [] |
36 | 38 | ||
39 | // Avoid too many SQL requests | ||
40 | const actors = data.data.map(v => v.VideoChannel.Account.Actor) | ||
41 | actors.push(actor) | ||
42 | |||
43 | const followersMatrix = await ActorModel.getActorsFollowerSharedInboxUrls(actors, undefined) | ||
44 | |||
37 | for (const video of data.data) { | 45 | for (const video of data.data) { |
38 | const videoObject = video.toActivityPubObject() | 46 | const videoObject = video.toActivityPubObject() |
39 | 47 | ||
40 | const videoChannel = video.VideoChannel | 48 | const byActor = video.VideoChannel.Account.Actor |
49 | const createActivityAudience = buildAudience(followersMatrix[byActor.id]) | ||
50 | |||
41 | // This is a shared video | 51 | // This is a shared video |
42 | if (video.VideoShares !== undefined && video.VideoShares.length !== 0) { | 52 | if (video.VideoShares !== undefined && video.VideoShares.length !== 0) { |
43 | const createActivity = await createActivityData(video.url, videoChannel.Account.Actor, videoObject, undefined) | 53 | const createActivity = await createActivityData(video.url, byActor, videoObject, undefined, createActivityAudience) |
44 | 54 | ||
55 | const announceAudience = buildAudience(followersMatrix[actor.id]) | ||
45 | const url = getAnnounceActivityPubUrl(video.url, actor) | 56 | const url = getAnnounceActivityPubUrl(video.url, actor) |
46 | const announceActivity = await announceActivityData(url, actor, createActivity, undefined) | 57 | const announceActivity = await announceActivityData(url, actor, createActivity, undefined, announceAudience) |
47 | 58 | ||
48 | activities.push(announceActivity) | 59 | activities.push(announceActivity) |
49 | } else { | 60 | } else { |
50 | const createActivity = await createActivityData(video.url, videoChannel.Account.Actor, videoObject, undefined) | 61 | const createActivity = await createActivityData(video.url, byActor, videoObject, undefined, createActivityAudience) |
51 | 62 | ||
52 | activities.push(createActivity) | 63 | activities.push(createActivity) |
53 | } | 64 | } |
diff --git a/server/lib/activitypub/send/misc.ts b/server/lib/activitypub/send/misc.ts index 261586ae4..dc0d3de57 100644 --- a/server/lib/activitypub/send/misc.ts +++ b/server/lib/activitypub/send/misc.ts | |||
@@ -143,6 +143,10 @@ async function getActorsInvolvedInVideo (video: VideoModel, t: Transaction) { | |||
143 | async function getAudience (actorSender: ActorModel, t: Transaction, isPublic = true) { | 143 | async function getAudience (actorSender: ActorModel, t: Transaction, isPublic = true) { |
144 | const followerInboxUrls = await actorSender.getFollowerSharedInboxUrls(t) | 144 | const followerInboxUrls = await actorSender.getFollowerSharedInboxUrls(t) |
145 | 145 | ||
146 | return buildAudience(followerInboxUrls, isPublic) | ||
147 | } | ||
148 | |||
149 | function buildAudience (followerInboxUrls: string[], isPublic = true) { | ||
146 | // Thanks Mastodon: https://github.com/tootsuite/mastodon/blob/master/app/lib/activitypub/tag_manager.rb#L47 | 150 | // Thanks Mastodon: https://github.com/tootsuite/mastodon/blob/master/app/lib/activitypub/tag_manager.rb#L47 |
147 | let to = [] | 151 | let to = [] |
148 | let cc = [] | 152 | let cc = [] |
@@ -183,6 +187,7 @@ async function computeUris (toActors: ActorModel[], actorsException: ActorModel[ | |||
183 | export { | 187 | export { |
184 | broadcastToFollowers, | 188 | broadcastToFollowers, |
185 | unicastTo, | 189 | unicastTo, |
190 | buildAudience, | ||
186 | getAudience, | 191 | getAudience, |
187 | getOriginVideoAudience, | 192 | getOriginVideoAudience, |
188 | getActorsInvolvedInVideo, | 193 | getActorsInvolvedInVideo, |
diff --git a/server/models/activitypub/actor-follow.ts b/server/models/activitypub/actor-follow.ts index ced481547..416496607 100644 --- a/server/models/activitypub/actor-follow.ts +++ b/server/models/activitypub/actor-follow.ts | |||
@@ -375,7 +375,8 @@ export class ActorFollowModel extends Model<ActorFollowModel> { | |||
375 | score: { | 375 | score: { |
376 | [Sequelize.Op.lte]: 0 | 376 | [Sequelize.Op.lte]: 0 |
377 | } | 377 | } |
378 | } | 378 | }, |
379 | logger: false | ||
379 | } | 380 | } |
380 | 381 | ||
381 | return ActorFollowModel.findAll(query) | 382 | return ActorFollowModel.findAll(query) |
diff --git a/server/models/activitypub/actor.ts b/server/models/activitypub/actor.ts index 408d4df23..269149a31 100644 --- a/server/models/activitypub/actor.ts +++ b/server/models/activitypub/actor.ts | |||
@@ -167,17 +167,17 @@ export class ActorModel extends Model<ActorModel> { | |||
167 | }, | 167 | }, |
168 | onDelete: 'cascade' | 168 | onDelete: 'cascade' |
169 | }) | 169 | }) |
170 | AccountFollowing: ActorFollowModel[] | 170 | ActorFollowing: ActorFollowModel[] |
171 | 171 | ||
172 | @HasMany(() => ActorFollowModel, { | 172 | @HasMany(() => ActorFollowModel, { |
173 | foreignKey: { | 173 | foreignKey: { |
174 | name: 'targetActorId', | 174 | name: 'targetActorId', |
175 | allowNull: false | 175 | allowNull: false |
176 | }, | 176 | }, |
177 | as: 'followers', | 177 | as: 'ActorFollowers', |
178 | onDelete: 'cascade' | 178 | onDelete: 'cascade' |
179 | }) | 179 | }) |
180 | AccountFollowers: ActorFollowModel[] | 180 | ActorFollowers: ActorFollowModel[] |
181 | 181 | ||
182 | @ForeignKey(() => ServerModel) | 182 | @ForeignKey(() => ServerModel) |
183 | @Column | 183 | @Column |
@@ -277,6 +277,45 @@ export class ActorModel extends Model<ActorModel> { | |||
277 | }) | 277 | }) |
278 | } | 278 | } |
279 | 279 | ||
280 | static async getActorsFollowerSharedInboxUrls (actors: ActorModel[], t: Sequelize.Transaction) { | ||
281 | const query = { | ||
282 | // attribute: [], | ||
283 | where: { | ||
284 | id: { | ||
285 | [Sequelize.Op.in]: actors.map(a => a.id) | ||
286 | } | ||
287 | }, | ||
288 | include: [ | ||
289 | { | ||
290 | // attributes: [ ], | ||
291 | model: ActorFollowModel.unscoped(), | ||
292 | required: true, | ||
293 | as: 'ActorFollowers', | ||
294 | where: { | ||
295 | state: 'accepted' | ||
296 | }, | ||
297 | include: [ | ||
298 | { | ||
299 | attributes: [ 'sharedInboxUrl' ], | ||
300 | model: ActorModel.unscoped(), | ||
301 | as: 'ActorFollower', | ||
302 | required: true | ||
303 | } | ||
304 | ] | ||
305 | } | ||
306 | ], | ||
307 | transaction: t | ||
308 | } | ||
309 | |||
310 | const hash: { [ id: number ]: string[] } = {} | ||
311 | const res = await ActorModel.findAll(query) | ||
312 | for (const actor of res) { | ||
313 | hash[actor.id] = actor.ActorFollowers.map(follow => follow.ActorFollower.sharedInboxUrl) | ||
314 | } | ||
315 | |||
316 | return hash | ||
317 | } | ||
318 | |||
280 | toFormattedJSON () { | 319 | toFormattedJSON () { |
281 | let avatar: Avatar = null | 320 | let avatar: Avatar = null |
282 | if (this.Avatar) { | 321 | if (this.Avatar) { |
@@ -347,10 +386,12 @@ export class ActorModel extends Model<ActorModel> { | |||
347 | attributes: [ 'sharedInboxUrl' ], | 386 | attributes: [ 'sharedInboxUrl' ], |
348 | include: [ | 387 | include: [ |
349 | { | 388 | { |
350 | model: ActorFollowModel, | 389 | attribute: [], |
390 | model: ActorFollowModel.unscoped(), | ||
351 | required: true, | 391 | required: true, |
352 | as: 'followers', | 392 | as: 'ActorFollowers', |
353 | where: { | 393 | where: { |
394 | state: 'accepted', | ||
354 | targetActorId: this.id | 395 | targetActorId: this.id |
355 | } | 396 | } |
356 | } | 397 | } |
diff --git a/server/models/video/video.ts b/server/models/video/video.ts index 514edfd9c..0d115367f 100644 --- a/server/models/video/video.ts +++ b/server/models/video/video.ts | |||
@@ -67,7 +67,7 @@ enum ScopeNames { | |||
67 | '$VideoChannel.Account.Actor.serverId$': null | 67 | '$VideoChannel.Account.Actor.serverId$': null |
68 | }, | 68 | }, |
69 | { | 69 | { |
70 | '$VideoChannel.Account.Actor.followers.actorId$': actorId | 70 | '$VideoChannel.Account.Actor.ActorFollowers.actorId$': actorId |
71 | }, | 71 | }, |
72 | { | 72 | { |
73 | id: { | 73 | id: { |
@@ -106,7 +106,7 @@ enum ScopeNames { | |||
106 | { | 106 | { |
107 | attributes: [ ], | 107 | attributes: [ ], |
108 | model: ActorFollowModel.unscoped(), | 108 | model: ActorFollowModel.unscoped(), |
109 | as: 'followers', | 109 | as: 'ActorFollowers', |
110 | required: false | 110 | required: false |
111 | } | 111 | } |
112 | ] | 112 | ] |
diff --git a/server/tests/api/server/follows.ts b/server/tests/api/server/follows.ts index ac614d605..c0115e534 100644 --- a/server/tests/api/server/follows.ts +++ b/server/tests/api/server/follows.ts | |||
@@ -4,7 +4,7 @@ import * as chai from 'chai' | |||
4 | import 'mocha' | 4 | import 'mocha' |
5 | import { Video, VideoPrivacy } from '../../../../shared/models/videos' | 5 | import { Video, VideoPrivacy } from '../../../../shared/models/videos' |
6 | import { VideoComment, VideoCommentThreadTree } from '../../../../shared/models/videos/video-comment.model' | 6 | import { VideoComment, VideoCommentThreadTree } from '../../../../shared/models/videos/video-comment.model' |
7 | import { checkVideoFilesWereRemoved, completeVideoCheck, getVideoChannelsList } from '../../utils' | 7 | import { checkVideoFilesWereRemoved, completeVideoCheck } from '../../utils' |
8 | 8 | ||
9 | import { | 9 | import { |
10 | flushAndRunMultipleServers, flushTests, getVideosList, killallServers, ServerInfo, setAccessTokensToServers, uploadVideo, | 10 | flushAndRunMultipleServers, flushTests, getVideosList, killallServers, ServerInfo, setAccessTokensToServers, uploadVideo, |
@@ -12,7 +12,7 @@ import { | |||
12 | } from '../../utils/index' | 12 | } from '../../utils/index' |
13 | import { dateIsValid } from '../../utils/miscs/miscs' | 13 | import { dateIsValid } from '../../utils/miscs/miscs' |
14 | import { follow, getFollowersListPaginationAndSort, getFollowingListPaginationAndSort, unfollow } from '../../utils/server/follows' | 14 | import { follow, getFollowersListPaginationAndSort, getFollowingListPaginationAndSort, unfollow } from '../../utils/server/follows' |
15 | import { expectAccountFollows, getAccountsList } from '../../utils/users/accounts' | 15 | import { expectAccountFollows } from '../../utils/users/accounts' |
16 | import { userLogin } from '../../utils/users/login' | 16 | import { userLogin } from '../../utils/users/login' |
17 | import { createUser } from '../../utils/users/users' | 17 | import { createUser } from '../../utils/users/users' |
18 | import { | 18 | import { |
@@ -354,12 +354,6 @@ describe('Test follows', function () { | |||
354 | let res = await getVideosList(servers[ 0 ].url) | 354 | let res = await getVideosList(servers[ 0 ].url) |
355 | expect(res.body.total).to.equal(1) | 355 | expect(res.body.total).to.equal(1) |
356 | 356 | ||
357 | res = await getVideoChannelsList(servers[0].url, 0, 1) | ||
358 | expect(res.body.total).to.equal(2) | ||
359 | |||
360 | res = await getAccountsList(servers[0].url) | ||
361 | expect(res.body.total).to.equal(2) | ||
362 | |||
363 | await checkVideoFilesWereRemoved(video4.uuid, servers[0].serverNumber) | 357 | await checkVideoFilesWereRemoved(video4.uuid, servers[0].serverNumber) |
364 | }) | 358 | }) |
365 | 359 | ||