Fix delete comment federation
This commit is contained in:
parent
649f0334e0
commit
73c0809326
|
@ -8,6 +8,7 @@ import { VideoModel } from '../../../models/video/video'
|
||||||
import { VideoChannelModel } from '../../../models/video/video-channel'
|
import { VideoChannelModel } from '../../../models/video/video-channel'
|
||||||
import { VideoCommentModel } from '../../../models/video/video-comment'
|
import { VideoCommentModel } from '../../../models/video/video-comment'
|
||||||
import { getOrCreateActorAndServerAndModel } from '../actor'
|
import { getOrCreateActorAndServerAndModel } from '../actor'
|
||||||
|
import { forwardActivity } from '../send/misc'
|
||||||
|
|
||||||
async function processDeleteActivity (activity: ActivityDelete) {
|
async function processDeleteActivity (activity: ActivityDelete) {
|
||||||
const objectUrl = typeof activity.object === 'string' ? activity.object : activity.object.id
|
const objectUrl = typeof activity.object === 'string' ? activity.object : activity.object.id
|
||||||
|
@ -33,7 +34,7 @@ async function processDeleteActivity (activity: ActivityDelete) {
|
||||||
{
|
{
|
||||||
const videoCommentInstance = await VideoCommentModel.loadByUrlAndPopulateAccount(objectUrl)
|
const videoCommentInstance = await VideoCommentModel.loadByUrlAndPopulateAccount(objectUrl)
|
||||||
if (videoCommentInstance) {
|
if (videoCommentInstance) {
|
||||||
return processDeleteVideoComment(actor, videoCommentInstance)
|
return processDeleteVideoComment(actor, videoCommentInstance, activity)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -116,21 +117,27 @@ async function deleteRemoteVideoChannel (videoChannelToRemove: VideoChannelModel
|
||||||
logger.info('Remote video channel with uuid %s removed.', videoChannelToRemove.Actor.uuid)
|
logger.info('Remote video channel with uuid %s removed.', videoChannelToRemove.Actor.uuid)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function processDeleteVideoComment (actor: ActorModel, videoComment: VideoCommentModel) {
|
async function processDeleteVideoComment (byActor: ActorModel, videoComment: VideoCommentModel, activity: ActivityDelete) {
|
||||||
const options = {
|
const options = {
|
||||||
arguments: [ actor, videoComment ],
|
arguments: [ byActor, videoComment, activity ],
|
||||||
errorMessage: 'Cannot remove the remote video comment with many retries.'
|
errorMessage: 'Cannot remove the remote video comment with many retries.'
|
||||||
}
|
}
|
||||||
|
|
||||||
await retryTransactionWrapper(deleteRemoteVideoComment, options)
|
await retryTransactionWrapper(deleteRemoteVideoComment, options)
|
||||||
}
|
}
|
||||||
|
|
||||||
function deleteRemoteVideoComment (actor: ActorModel, videoComment: VideoCommentModel) {
|
function deleteRemoteVideoComment (byActor: ActorModel, videoComment: VideoCommentModel, activity: ActivityDelete) {
|
||||||
logger.debug('Removing remote video comment "%s".', videoComment.url)
|
logger.debug('Removing remote video comment "%s".', videoComment.url)
|
||||||
|
|
||||||
return sequelizeTypescript.transaction(async t => {
|
return sequelizeTypescript.transaction(async t => {
|
||||||
await videoComment.destroy({ transaction: t })
|
await videoComment.destroy({ transaction: t })
|
||||||
|
|
||||||
|
if (videoComment.Video.isOwned()) {
|
||||||
|
// Don't resend the activity to the sender
|
||||||
|
const exceptions = [ byActor ]
|
||||||
|
await forwardActivity(activity, t, exceptions)
|
||||||
|
}
|
||||||
|
|
||||||
logger.info('Remote video comment %s removed.', videoComment.url)
|
logger.info('Remote video comment %s removed.', videoComment.url)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -96,7 +96,7 @@ function getOriginVideoAudience (video: VideoModel, actorsInvolvedInVideo: Actor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getOriginVideoCommentAudience (
|
function getVideoCommentAudience (
|
||||||
videoComment: VideoCommentModel,
|
videoComment: VideoCommentModel,
|
||||||
threadParentComments: VideoCommentModel[],
|
threadParentComments: VideoCommentModel[],
|
||||||
actorsInvolvedInVideo: ActorModel[],
|
actorsInvolvedInVideo: ActorModel[],
|
||||||
|
@ -160,7 +160,7 @@ function buildAudience (followerInboxUrls: string[], isPublic = true) {
|
||||||
return { to, cc }
|
return { to, cc }
|
||||||
}
|
}
|
||||||
|
|
||||||
function audiencify (object: any, audience: ActivityAudience) {
|
function audiencify <T> (object: T, audience: ActivityAudience) {
|
||||||
return Object.assign(object, audience)
|
return Object.assign(object, audience)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -192,7 +192,7 @@ export {
|
||||||
getObjectFollowersAudience,
|
getObjectFollowersAudience,
|
||||||
forwardActivity,
|
forwardActivity,
|
||||||
audiencify,
|
audiencify,
|
||||||
getOriginVideoCommentAudience,
|
getVideoCommentAudience,
|
||||||
computeUris,
|
computeUris,
|
||||||
broadcastToActors
|
broadcastToActors
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@ import {
|
||||||
getAudience,
|
getAudience,
|
||||||
getObjectFollowersAudience,
|
getObjectFollowersAudience,
|
||||||
getOriginVideoAudience,
|
getOriginVideoAudience,
|
||||||
getOriginVideoCommentAudience,
|
getVideoCommentAudience,
|
||||||
unicastTo
|
unicastTo
|
||||||
} from './misc'
|
} from './misc'
|
||||||
|
|
||||||
|
@ -47,7 +47,7 @@ async function sendCreateVideoCommentToOrigin (comment: VideoCommentModel, t: Tr
|
||||||
|
|
||||||
const actorsInvolvedInComment = await getActorsInvolvedInVideo(comment.Video, t)
|
const actorsInvolvedInComment = await getActorsInvolvedInVideo(comment.Video, t)
|
||||||
actorsInvolvedInComment.push(byActor)
|
actorsInvolvedInComment.push(byActor)
|
||||||
const audience = getOriginVideoCommentAudience(comment, threadParentComments, actorsInvolvedInComment)
|
const audience = getVideoCommentAudience(comment, threadParentComments, actorsInvolvedInComment)
|
||||||
|
|
||||||
const data = await createActivityData(comment.url, byActor, commentObject, t, audience)
|
const data = await createActivityData(comment.url, byActor, commentObject, t, audience)
|
||||||
|
|
||||||
|
@ -70,7 +70,7 @@ async function sendCreateVideoCommentToVideoFollowers (comment: VideoCommentMode
|
||||||
const actorsInvolvedInComment = await getActorsInvolvedInVideo(comment.Video, t)
|
const actorsInvolvedInComment = await getActorsInvolvedInVideo(comment.Video, t)
|
||||||
actorsInvolvedInComment.push(byActor)
|
actorsInvolvedInComment.push(byActor)
|
||||||
|
|
||||||
const audience = getOriginVideoCommentAudience(comment, threadParentComments, actorsInvolvedInComment)
|
const audience = getVideoCommentAudience(comment, threadParentComments, actorsInvolvedInComment, true)
|
||||||
const data = await createActivityData(comment.url, byActor, commentObject, t, audience)
|
const data = await createActivityData(comment.url, byActor, commentObject, t, audience)
|
||||||
|
|
||||||
// This was a reply, send it to the parent actors
|
// This was a reply, send it to the parent actors
|
||||||
|
@ -144,7 +144,7 @@ async function createActivityData (
|
||||||
}
|
}
|
||||||
|
|
||||||
return audiencify({
|
return audiencify({
|
||||||
type: 'Create',
|
type: 'Create' as 'Create',
|
||||||
id: url + '/activity',
|
id: url + '/activity',
|
||||||
actor: byActor.url,
|
actor: byActor.url,
|
||||||
object: audiencify(object, audience)
|
object: audiencify(object, audience)
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
import { Transaction } from 'sequelize'
|
import { Transaction } from 'sequelize'
|
||||||
import { ActivityDelete } from '../../../../shared/models/activitypub'
|
import { ActivityAudience, ActivityDelete } from '../../../../shared/models/activitypub'
|
||||||
import { ActorModel } from '../../../models/activitypub/actor'
|
import { ActorModel } from '../../../models/activitypub/actor'
|
||||||
import { VideoModel } from '../../../models/video/video'
|
import { VideoModel } from '../../../models/video/video'
|
||||||
import { VideoCommentModel } from '../../../models/video/video-comment'
|
import { VideoCommentModel } from '../../../models/video/video-comment'
|
||||||
import { VideoShareModel } from '../../../models/video/video-share'
|
import { VideoShareModel } from '../../../models/video/video-share'
|
||||||
import { getDeleteActivityPubUrl } from '../url'
|
import { getDeleteActivityPubUrl } from '../url'
|
||||||
import { broadcastToFollowers } from './misc'
|
import { audiencify, broadcastToActors, broadcastToFollowers, getActorsInvolvedInVideo, getVideoCommentAudience, unicastTo } from './misc'
|
||||||
|
|
||||||
async function sendDeleteVideo (video: VideoModel, t: Transaction) {
|
async function sendDeleteVideo (video: VideoModel, t: Transaction) {
|
||||||
const url = getDeleteActivityPubUrl(video.url)
|
const url = getDeleteActivityPubUrl(video.url)
|
||||||
|
@ -30,16 +30,30 @@ async function sendDeleteActor (byActor: ActorModel, t: Transaction) {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function sendDeleteVideoComment (videoComment: VideoCommentModel, t: Transaction) {
|
async function sendDeleteVideoComment (videoComment: VideoCommentModel, t: Transaction) {
|
||||||
|
const isVideoOrigin = videoComment.Video.isOwned()
|
||||||
|
|
||||||
const url = getDeleteActivityPubUrl(videoComment.url)
|
const url = getDeleteActivityPubUrl(videoComment.url)
|
||||||
|
|
||||||
const byActor = videoComment.Account.Actor
|
const byActor = videoComment.Account.Actor
|
||||||
const data = deleteActivityData(url, videoComment.url, byActor)
|
const threadParentComments = await VideoCommentModel.listThreadParentComments(videoComment, t)
|
||||||
|
|
||||||
const actorsInvolved = await VideoShareModel.loadActorsByShare(videoComment.Video.id, t)
|
const actorsInvolvedInComment = await getActorsInvolvedInVideo(videoComment.Video, t)
|
||||||
actorsInvolved.push(videoComment.Video.VideoChannel.Account.Actor)
|
actorsInvolvedInComment.push(byActor)
|
||||||
actorsInvolved.push(byActor)
|
|
||||||
|
|
||||||
return broadcastToFollowers(data, byActor, actorsInvolved, t)
|
const audience = getVideoCommentAudience(videoComment, threadParentComments, actorsInvolvedInComment, isVideoOrigin)
|
||||||
|
const data = deleteActivityData(url, videoComment.url, byActor, audience)
|
||||||
|
|
||||||
|
// This was a reply, send it to the parent actors
|
||||||
|
const actorsException = [ byActor ]
|
||||||
|
await broadcastToActors(data, byActor, threadParentComments.map(c => c.Account.Actor), actorsException)
|
||||||
|
|
||||||
|
// Broadcast to our followers
|
||||||
|
await broadcastToFollowers(data, byActor, [ byActor ], t)
|
||||||
|
|
||||||
|
// Send to actors involved in the comment
|
||||||
|
if (isVideoOrigin) return broadcastToFollowers(data, byActor, actorsInvolvedInComment, t, actorsException)
|
||||||
|
|
||||||
|
// Send to origin
|
||||||
|
return unicastTo(data, byActor, videoComment.Video.VideoChannel.Account.Actor.sharedInboxUrl)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
@ -52,11 +66,15 @@ export {
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
function deleteActivityData (url: string, object: string, byActor: ActorModel): ActivityDelete {
|
function deleteActivityData (url: string, object: string, byActor: ActorModel, audience?: ActivityAudience): ActivityDelete {
|
||||||
return {
|
const activity = {
|
||||||
type: 'Delete',
|
type: 'Delete' as 'Delete',
|
||||||
id: url,
|
id: url,
|
||||||
actor: byActor.url,
|
actor: byActor.url,
|
||||||
object
|
object
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (audience) return audiencify(activity, audience)
|
||||||
|
|
||||||
|
return activity
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,7 +46,7 @@ async function likeActivityData (
|
||||||
}
|
}
|
||||||
|
|
||||||
return audiencify({
|
return audiencify({
|
||||||
type: 'Like',
|
type: 'Like' as 'Like',
|
||||||
id: url,
|
id: url,
|
||||||
actor: byActor.url,
|
actor: byActor.url,
|
||||||
object: video.url
|
object: video.url
|
||||||
|
|
|
@ -108,7 +108,7 @@ async function undoActivityData (
|
||||||
}
|
}
|
||||||
|
|
||||||
return audiencify({
|
return audiencify({
|
||||||
type: 'Undo',
|
type: 'Undo' as 'Undo',
|
||||||
id: url,
|
id: url,
|
||||||
actor: byActor.url,
|
actor: byActor.url,
|
||||||
object
|
object
|
||||||
|
|
|
@ -67,7 +67,7 @@ async function updateActivityData (
|
||||||
}
|
}
|
||||||
|
|
||||||
return audiencify({
|
return audiencify({
|
||||||
type: 'Update',
|
type: 'Update' as 'Update',
|
||||||
id: url,
|
id: url,
|
||||||
actor: byActor.url,
|
actor: byActor.url,
|
||||||
object: audiencify(object, audience)
|
object: audiencify(object, audience)
|
||||||
|
|
|
@ -728,6 +728,8 @@ describe('Test multiple servers', function () {
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('Should comment these videos', function () {
|
describe('Should comment these videos', function () {
|
||||||
|
let childOfFirstChild: VideoCommentThreadTree
|
||||||
|
|
||||||
it('Should add comment (threads and replies)', async function () {
|
it('Should add comment (threads and replies)', async function () {
|
||||||
this.timeout(25000)
|
this.timeout(25000)
|
||||||
|
|
||||||
|
@ -821,7 +823,7 @@ describe('Test multiple servers', function () {
|
||||||
expect(firstChild.comment.account.host).equal('localhost:9002')
|
expect(firstChild.comment.account.host).equal('localhost:9002')
|
||||||
expect(firstChild.children).to.have.lengthOf(1)
|
expect(firstChild.children).to.have.lengthOf(1)
|
||||||
|
|
||||||
const childOfFirstChild = firstChild.children[0]
|
childOfFirstChild = firstChild.children[0]
|
||||||
expect(childOfFirstChild.comment.text).to.equal('my super answer to answer of thread 1')
|
expect(childOfFirstChild.comment.text).to.equal('my super answer to answer of thread 1')
|
||||||
expect(childOfFirstChild.comment.account.name).equal('root')
|
expect(childOfFirstChild.comment.account.name).equal('root')
|
||||||
expect(childOfFirstChild.comment.account.host).equal('localhost:9003')
|
expect(childOfFirstChild.comment.account.host).equal('localhost:9003')
|
||||||
|
@ -835,6 +837,33 @@ describe('Test multiple servers', function () {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('Should delete a reply', async function () {
|
||||||
|
this.timeout(10000)
|
||||||
|
|
||||||
|
await deleteVideoComment(servers[2].url, servers[2].accessToken, videoUUID, childOfFirstChild.comment.id)
|
||||||
|
|
||||||
|
await wait(5000)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should not have this comment anymore', async function () {
|
||||||
|
for (const server of servers) {
|
||||||
|
const res1 = await getVideoCommentThreads(server.url, videoUUID, 0, 5)
|
||||||
|
const threadId = res1.body.data.find(c => c.text === 'my super first comment').id
|
||||||
|
|
||||||
|
const res2 = await getVideoThreadComments(server.url, videoUUID, threadId)
|
||||||
|
|
||||||
|
const tree: VideoCommentThreadTree = res2.body
|
||||||
|
expect(tree.comment.text).equal('my super first comment')
|
||||||
|
|
||||||
|
const firstChild = tree.children[0]
|
||||||
|
expect(firstChild.comment.text).to.equal('my super answer to thread 1')
|
||||||
|
expect(firstChild.children).to.have.lengthOf(0)
|
||||||
|
|
||||||
|
const secondChild = tree.children[1]
|
||||||
|
expect(secondChild.comment.text).to.equal('my second answer to thread 1')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
it('Should delete the thread comments', async function () {
|
it('Should delete the thread comments', async function () {
|
||||||
this.timeout(10000)
|
this.timeout(10000)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue