diff options
-rw-r--r-- | server/helpers/custom-validators/activitypub/video-comments.ts | 18 | ||||
-rw-r--r-- | server/lib/activitypub/send/send-delete.ts | 5 | ||||
-rw-r--r-- | server/lib/activitypub/video-comments.ts | 5 | ||||
-rw-r--r-- | server/middlewares/validators/videos/video-comments.ts | 7 | ||||
-rw-r--r-- | server/tests/api/server/follows.ts | 149 | ||||
-rw-r--r-- | server/tests/api/videos/multiple-servers.ts | 46 |
6 files changed, 131 insertions, 99 deletions
diff --git a/server/helpers/custom-validators/activitypub/video-comments.ts b/server/helpers/custom-validators/activitypub/video-comments.ts index 96655c3f8..ea780ca46 100644 --- a/server/helpers/custom-validators/activitypub/video-comments.ts +++ b/server/helpers/custom-validators/activitypub/video-comments.ts | |||
@@ -3,18 +3,10 @@ import { ACTIVITY_PUB } from '../../../initializers/constants' | |||
3 | import { exists, isArray, isDateValid } from '../misc' | 3 | import { exists, isArray, isDateValid } from '../misc' |
4 | import { isActivityPubUrlValid } from './misc' | 4 | import { isActivityPubUrlValid } from './misc' |
5 | 5 | ||
6 | function isTypeValid (comment: any): boolean { | ||
7 | if (comment.type === 'Note') return true | ||
8 | |||
9 | if (comment.type === 'Tombstone' && comment.formerType === 'Note') return true | ||
10 | |||
11 | return false | ||
12 | } | ||
13 | |||
14 | function sanitizeAndCheckVideoCommentObject (comment: any) { | 6 | function sanitizeAndCheckVideoCommentObject (comment: any) { |
15 | if (!comment) return false | 7 | if (!comment) return false |
16 | 8 | ||
17 | if (!isTypeValid(comment)) return false | 9 | if (!isCommentTypeValid(comment)) return false |
18 | 10 | ||
19 | normalizeComment(comment) | 11 | normalizeComment(comment) |
20 | 12 | ||
@@ -59,3 +51,11 @@ function normalizeComment (comment: any) { | |||
59 | 51 | ||
60 | return | 52 | return |
61 | } | 53 | } |
54 | |||
55 | function isCommentTypeValid (comment: any): boolean { | ||
56 | if (comment.type === 'Note') return true | ||
57 | |||
58 | if (comment.type === 'Tombstone' && comment.formerType === 'Note') return true | ||
59 | |||
60 | return false | ||
61 | } | ||
diff --git a/server/lib/activitypub/send/send-delete.ts b/server/lib/activitypub/send/send-delete.ts index a91756ff4..3225ebf32 100644 --- a/server/lib/activitypub/send/send-delete.ts +++ b/server/lib/activitypub/send/send-delete.ts | |||
@@ -53,16 +53,17 @@ async function sendDeleteVideoComment (videoComment: MCommentOwnerVideoReply, t: | |||
53 | : videoComment.Video.VideoChannel.Account.Actor | 53 | : videoComment.Video.VideoChannel.Account.Actor |
54 | 54 | ||
55 | const threadParentComments = await VideoCommentModel.listThreadParentComments(videoComment, t) | 55 | const threadParentComments = await VideoCommentModel.listThreadParentComments(videoComment, t) |
56 | const threadParentCommentsFiltered = threadParentComments.filter(c => !c.isDeleted()) | ||
56 | 57 | ||
57 | const actorsInvolvedInComment = await getActorsInvolvedInVideo(videoComment.Video, t) | 58 | const actorsInvolvedInComment = await getActorsInvolvedInVideo(videoComment.Video, t) |
58 | actorsInvolvedInComment.push(byActor) // Add the actor that commented the video | 59 | actorsInvolvedInComment.push(byActor) // Add the actor that commented the video |
59 | 60 | ||
60 | const audience = getVideoCommentAudience(videoComment, threadParentComments, actorsInvolvedInComment, isVideoOrigin) | 61 | const audience = getVideoCommentAudience(videoComment, threadParentCommentsFiltered, actorsInvolvedInComment, isVideoOrigin) |
61 | const activity = buildDeleteActivity(url, videoComment.url, byActor, audience) | 62 | const activity = buildDeleteActivity(url, videoComment.url, byActor, audience) |
62 | 63 | ||
63 | // This was a reply, send it to the parent actors | 64 | // This was a reply, send it to the parent actors |
64 | const actorsException = [ byActor ] | 65 | const actorsException = [ byActor ] |
65 | await broadcastToActors(activity, byActor, threadParentComments.map(c => c.Account.Actor), t, actorsException) | 66 | await broadcastToActors(activity, byActor, threadParentCommentsFiltered.map(c => c.Account.Actor), t, actorsException) |
66 | 67 | ||
67 | // Broadcast to our followers | 68 | // Broadcast to our followers |
68 | await broadcastToFollowers(activity, byActor, [ byActor ], t) | 69 | await broadcastToFollowers(activity, byActor, [ byActor ], t) |
diff --git a/server/lib/activitypub/video-comments.ts b/server/lib/activitypub/video-comments.ts index 1a15842cf..d5c078a29 100644 --- a/server/lib/activitypub/video-comments.ts +++ b/server/lib/activitypub/video-comments.ts | |||
@@ -141,7 +141,10 @@ async function resolveParentComment (params: ResolveThreadParams) { | |||
141 | throw new Error(`Comment url ${url} host is different from the AP object id ${body.id}`) | 141 | throw new Error(`Comment url ${url} host is different from the AP object id ${body.id}`) |
142 | } | 142 | } |
143 | 143 | ||
144 | const actor = actorUrl ? await getOrCreateActorAndServerAndModel(actorUrl, 'all') : null | 144 | const actor = actorUrl |
145 | ? await getOrCreateActorAndServerAndModel(actorUrl, 'all') | ||
146 | : null | ||
147 | |||
145 | const comment = new VideoCommentModel({ | 148 | const comment = new VideoCommentModel({ |
146 | url: body.id, | 149 | url: body.id, |
147 | text: body.content ? body.content : '', | 150 | text: body.content ? body.content : '', |
diff --git a/server/middlewares/validators/videos/video-comments.ts b/server/middlewares/validators/videos/video-comments.ts index 1d81eb5d8..eb07d9430 100644 --- a/server/middlewares/validators/videos/video-comments.ts +++ b/server/middlewares/validators/videos/video-comments.ts | |||
@@ -189,6 +189,13 @@ function isVideoCommentsEnabled (video: MVideo, res: express.Response) { | |||
189 | } | 189 | } |
190 | 190 | ||
191 | function checkUserCanDeleteVideoComment (user: MUser, videoComment: MCommentOwner, res: express.Response) { | 191 | function checkUserCanDeleteVideoComment (user: MUser, videoComment: MCommentOwner, res: express.Response) { |
192 | if (videoComment.isDeleted()) { | ||
193 | res.status(409) | ||
194 | .json({ error: 'This comment is already deleted' }) | ||
195 | .end() | ||
196 | return false | ||
197 | } | ||
198 | |||
192 | const account = videoComment.Account | 199 | const account = videoComment.Account |
193 | if (user.hasRight(UserRight.REMOVE_ANY_VIDEO_COMMENT) === false && account.userId !== user.id) { | 200 | if (user.hasRight(UserRight.REMOVE_ANY_VIDEO_COMMENT) === false && account.userId !== user.id) { |
194 | res.status(403) | 201 | res.status(403) |
diff --git a/server/tests/api/server/follows.ts b/server/tests/api/server/follows.ts index dd85722a0..60dbe2e6d 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 { cleanupTests, completeVideoCheck } from '../../../../shared/extra-utils' | 7 | import { cleanupTests, completeVideoCheck, deleteVideoComment } from '../../../../shared/extra-utils' |
8 | import { | 8 | import { |
9 | flushAndRunMultipleServers, | 9 | flushAndRunMultipleServers, |
10 | getVideosList, | 10 | getVideosList, |
@@ -356,19 +356,40 @@ describe('Test follows', function () { | |||
356 | } | 356 | } |
357 | 357 | ||
358 | { | 358 | { |
359 | const text = 'my super first comment' | 359 | { |
360 | const res = await addVideoCommentThread(servers[ 2 ].url, servers[ 2 ].accessToken, video4.id, text) | 360 | const text = 'my super first comment' |
361 | const threadId = res.body.comment.id | 361 | const res = await addVideoCommentThread(servers[ 2 ].url, servers[ 2 ].accessToken, video4.id, text) |
362 | const threadId = res.body.comment.id | ||
363 | |||
364 | const text1 = 'my super answer to thread 1' | ||
365 | const childCommentRes = await addVideoCommentReply(servers[ 2 ].url, servers[ 2 ].accessToken, video4.id, threadId, text1) | ||
366 | const childCommentId = childCommentRes.body.comment.id | ||
367 | |||
368 | const text2 = 'my super answer to answer of thread 1' | ||
369 | await addVideoCommentReply(servers[ 2 ].url, servers[ 2 ].accessToken, video4.id, childCommentId, text2) | ||
370 | |||
371 | const text3 = 'my second answer to thread 1' | ||
372 | await addVideoCommentReply(servers[ 2 ].url, servers[ 2 ].accessToken, video4.id, threadId, text3) | ||
373 | } | ||
374 | |||
375 | { | ||
376 | const text = 'will be deleted' | ||
377 | const res = await addVideoCommentThread(servers[ 2 ].url, servers[ 2 ].accessToken, video4.id, text) | ||
378 | const threadId = res.body.comment.id | ||
379 | |||
380 | const text1 = 'answer to deleted' | ||
381 | await addVideoCommentReply(servers[ 2 ].url, servers[ 2 ].accessToken, video4.id, threadId, text1) | ||
362 | 382 | ||
363 | const text1 = 'my super answer to thread 1' | 383 | const text2 = 'will also be deleted' |
364 | const childCommentRes = await addVideoCommentReply(servers[ 2 ].url, servers[ 2 ].accessToken, video4.id, threadId, text1) | 384 | const childCommentRes = await addVideoCommentReply(servers[ 2 ].url, servers[ 2 ].accessToken, video4.id, threadId, text2) |
365 | const childCommentId = childCommentRes.body.comment.id | 385 | const childCommentId = childCommentRes.body.comment.id |
366 | 386 | ||
367 | const text2 = 'my super answer to answer of thread 1' | 387 | const text3 = 'my second answer to deleted' |
368 | await addVideoCommentReply(servers[ 2 ].url, servers[ 2 ].accessToken, video4.id, childCommentId, text2) | 388 | await addVideoCommentReply(servers[ 2 ].url, servers[ 2 ].accessToken, video4.id, childCommentId, text3) |
369 | 389 | ||
370 | const text3 = 'my second answer to thread 1' | 390 | await deleteVideoComment(servers[ 2 ].url, servers[ 2 ].accessToken, video4.id, threadId) |
371 | await addVideoCommentReply(servers[ 2 ].url, servers[ 2 ].accessToken, video4.id, threadId, text3) | 391 | await deleteVideoComment(servers[ 2 ].url, servers[ 2 ].accessToken, video4.id, childCommentId) |
392 | } | ||
372 | } | 393 | } |
373 | 394 | ||
374 | { | 395 | { |
@@ -453,42 +474,80 @@ describe('Test follows', function () { | |||
453 | }) | 474 | }) |
454 | 475 | ||
455 | it('Should have propagated comments', async function () { | 476 | it('Should have propagated comments', async function () { |
456 | const res1 = await getVideoCommentThreads(servers[0].url, video4.id, 0, 5) | 477 | const res1 = await getVideoCommentThreads(servers[0].url, video4.id, 0, 5, 'createdAt') |
457 | 478 | ||
458 | expect(res1.body.total).to.equal(1) | 479 | expect(res1.body.total).to.equal(2) |
459 | expect(res1.body.data).to.be.an('array') | 480 | expect(res1.body.data).to.be.an('array') |
460 | expect(res1.body.data).to.have.lengthOf(1) | 481 | expect(res1.body.data).to.have.lengthOf(2) |
461 | 482 | ||
462 | const comment: VideoComment = res1.body.data[0] | 483 | { |
463 | expect(comment.inReplyToCommentId).to.be.null | 484 | const comment: VideoComment = res1.body.data[ 0 ] |
464 | expect(comment.text).equal('my super first comment') | 485 | expect(comment.inReplyToCommentId).to.be.null |
465 | expect(comment.videoId).to.equal(video4.id) | 486 | expect(comment.text).equal('my super first comment') |
466 | expect(comment.id).to.equal(comment.threadId) | 487 | expect(comment.videoId).to.equal(video4.id) |
467 | expect(comment.account.name).to.equal('root') | 488 | expect(comment.id).to.equal(comment.threadId) |
468 | expect(comment.account.host).to.equal('localhost:' + servers[2].port) | 489 | expect(comment.account.name).to.equal('root') |
469 | expect(comment.totalReplies).to.equal(3) | 490 | expect(comment.account.host).to.equal('localhost:' + servers[ 2 ].port) |
470 | expect(dateIsValid(comment.createdAt as string)).to.be.true | 491 | expect(comment.totalReplies).to.equal(3) |
471 | expect(dateIsValid(comment.updatedAt as string)).to.be.true | 492 | expect(dateIsValid(comment.createdAt as string)).to.be.true |
472 | 493 | expect(dateIsValid(comment.updatedAt as string)).to.be.true | |
473 | const threadId = comment.threadId | 494 | |
474 | 495 | const threadId = comment.threadId | |
475 | const res2 = await getVideoThreadComments(servers[0].url, video4.id, threadId) | 496 | |
476 | 497 | const res2 = await getVideoThreadComments(servers[ 0 ].url, video4.id, threadId) | |
477 | const tree: VideoCommentThreadTree = res2.body | 498 | |
478 | expect(tree.comment.text).equal('my super first comment') | 499 | const tree: VideoCommentThreadTree = res2.body |
479 | expect(tree.children).to.have.lengthOf(2) | 500 | expect(tree.comment.text).equal('my super first comment') |
480 | 501 | expect(tree.children).to.have.lengthOf(2) | |
481 | const firstChild = tree.children[0] | 502 | |
482 | expect(firstChild.comment.text).to.equal('my super answer to thread 1') | 503 | const firstChild = tree.children[ 0 ] |
483 | expect(firstChild.children).to.have.lengthOf(1) | 504 | expect(firstChild.comment.text).to.equal('my super answer to thread 1') |
484 | 505 | expect(firstChild.children).to.have.lengthOf(1) | |
485 | const childOfFirstChild = firstChild.children[0] | 506 | |
486 | expect(childOfFirstChild.comment.text).to.equal('my super answer to answer of thread 1') | 507 | const childOfFirstChild = firstChild.children[ 0 ] |
487 | expect(childOfFirstChild.children).to.have.lengthOf(0) | 508 | expect(childOfFirstChild.comment.text).to.equal('my super answer to answer of thread 1') |
488 | 509 | expect(childOfFirstChild.children).to.have.lengthOf(0) | |
489 | const secondChild = tree.children[1] | 510 | |
490 | expect(secondChild.comment.text).to.equal('my second answer to thread 1') | 511 | const secondChild = tree.children[ 1 ] |
491 | expect(secondChild.children).to.have.lengthOf(0) | 512 | expect(secondChild.comment.text).to.equal('my second answer to thread 1') |
513 | expect(secondChild.children).to.have.lengthOf(0) | ||
514 | } | ||
515 | |||
516 | { | ||
517 | const deletedComment: VideoComment = res1.body.data[1] | ||
518 | expect(deletedComment).to.not.be.undefined | ||
519 | expect(deletedComment.isDeleted).to.be.true | ||
520 | expect(deletedComment.deletedAt).to.not.be.null | ||
521 | expect(deletedComment.text).to.equal('') | ||
522 | expect(deletedComment.inReplyToCommentId).to.be.null | ||
523 | expect(deletedComment.account).to.be.null | ||
524 | expect(deletedComment.totalReplies).to.equal(3) | ||
525 | expect(dateIsValid(deletedComment.deletedAt as string)).to.be.true | ||
526 | |||
527 | const res2 = await getVideoThreadComments(servers[0].url, video4.id, deletedComment.threadId) | ||
528 | |||
529 | const tree: VideoCommentThreadTree = res2.body | ||
530 | const [ commentRoot, deletedChildRoot ] = tree.children | ||
531 | |||
532 | expect(deletedChildRoot).to.not.be.undefined | ||
533 | expect(deletedChildRoot.comment.isDeleted).to.be.true | ||
534 | expect(deletedChildRoot.comment.deletedAt).to.not.be.null | ||
535 | expect(deletedChildRoot.comment.text).to.equal('') | ||
536 | expect(deletedChildRoot.comment.inReplyToCommentId).to.equal(deletedComment.id) | ||
537 | expect(deletedChildRoot.comment.account).to.be.null | ||
538 | expect(deletedChildRoot.children).to.have.lengthOf(1) | ||
539 | |||
540 | const answerToDeletedChild = deletedChildRoot.children[0] | ||
541 | expect(answerToDeletedChild.comment).to.not.be.undefined | ||
542 | expect(answerToDeletedChild.comment.inReplyToCommentId).to.equal(deletedChildRoot.comment.id) | ||
543 | expect(answerToDeletedChild.comment.text).to.equal('my second answer to deleted') | ||
544 | expect(answerToDeletedChild.comment.account.name).to.equal('root') | ||
545 | |||
546 | expect(commentRoot.comment).to.not.be.undefined | ||
547 | expect(commentRoot.comment.inReplyToCommentId).to.equal(deletedComment.id) | ||
548 | expect(commentRoot.comment.text).to.equal('answer to deleted') | ||
549 | expect(commentRoot.comment.account.name).to.equal('root') | ||
550 | } | ||
492 | }) | 551 | }) |
493 | 552 | ||
494 | it('Should have propagated captions', async function () { | 553 | it('Should have propagated captions', async function () { |
diff --git a/server/tests/api/videos/multiple-servers.ts b/server/tests/api/videos/multiple-servers.ts index 836bdc658..22d87b88c 100644 --- a/server/tests/api/videos/multiple-servers.ts +++ b/server/tests/api/videos/multiple-servers.ts | |||
@@ -517,6 +517,8 @@ describe('Test multiple servers', function () { | |||
517 | // Wait the repeatable job | 517 | // Wait the repeatable job |
518 | await wait(6000) | 518 | await wait(6000) |
519 | 519 | ||
520 | await waitJobs(servers) | ||
521 | |||
520 | for (const server of servers) { | 522 | for (const server of servers) { |
521 | const res = await getVideosList(server.url) | 523 | const res = await getVideosList(server.url) |
522 | 524 | ||
@@ -551,6 +553,8 @@ describe('Test multiple servers', function () { | |||
551 | // Wait the repeatable job | 553 | // Wait the repeatable job |
552 | await wait(16000) | 554 | await wait(16000) |
553 | 555 | ||
556 | await waitJobs(servers) | ||
557 | |||
554 | let baseVideos = null | 558 | let baseVideos = null |
555 | 559 | ||
556 | for (const server of servers) { | 560 | for (const server of servers) { |
@@ -939,48 +943,6 @@ describe('Test multiple servers', function () { | |||
939 | } | 943 | } |
940 | }) | 944 | }) |
941 | 945 | ||
942 | it('Should retrieve all comments when subscribing to a new server', async function () { | ||
943 | this.timeout(120000) | ||
944 | |||
945 | const newServer = await flushAndRunServer(4) | ||
946 | |||
947 | await setAccessTokensToServers([newServer]) | ||
948 | await doubleFollow(newServer, servers[0]) | ||
949 | await doubleFollow(newServer, servers[2]) | ||
950 | await waitJobs([newServer, ...servers]) | ||
951 | |||
952 | const res = await getVideoCommentThreads(newServer.url, videoUUID, 0, 5) | ||
953 | |||
954 | expect(res.body.total).to.equal(2) | ||
955 | expect(res.body.data).to.be.an('array') | ||
956 | expect(res.body.data).to.have.lengthOf(2) | ||
957 | |||
958 | { | ||
959 | const comment: VideoComment = res.body.data[0] | ||
960 | expect(comment).to.not.be.undefined | ||
961 | expect(comment.inReplyToCommentId).to.be.null | ||
962 | expect(comment.account.name).to.equal('root') | ||
963 | expect(comment.account.host).to.equal('localhost:' + servers[2].port) | ||
964 | expect(comment.totalReplies).to.equal(0) | ||
965 | expect(dateIsValid(comment.createdAt as string)).to.be.true | ||
966 | expect(dateIsValid(comment.updatedAt as string)).to.be.true | ||
967 | } | ||
968 | |||
969 | { | ||
970 | const deletedComment: VideoComment = res.body.data[1] | ||
971 | expect(deletedComment).to.not.be.undefined | ||
972 | expect(deletedComment.isDeleted).to.be.true | ||
973 | expect(deletedComment.deletedAt).to.not.be.null | ||
974 | expect(deletedComment.text).to.equal('') | ||
975 | expect(deletedComment.inReplyToCommentId).to.be.null | ||
976 | expect(deletedComment.account).to.be.null | ||
977 | expect(deletedComment.totalReplies).to.equal(3) | ||
978 | expect(dateIsValid(deletedComment.createdAt as string)).to.be.true | ||
979 | expect(dateIsValid(deletedComment.updatedAt as string)).to.be.true | ||
980 | expect(dateIsValid(deletedComment.deletedAt as string)).to.be.true | ||
981 | } | ||
982 | }) | ||
983 | |||
984 | it('Should delete a remote thread by the origin server', async function () { | 946 | it('Should delete a remote thread by the origin server', async function () { |
985 | this.timeout(5000) | 947 | this.timeout(5000) |
986 | 948 | ||