diff options
-rw-r--r-- | server/helpers/custom-validators/activitypub/video-comments.ts | 19 | ||||
-rw-r--r-- | server/lib/activitypub/video-comments.ts | 15 | ||||
-rw-r--r-- | server/models/video/video-comment.ts | 19 | ||||
-rw-r--r-- | server/tests/api/videos/multiple-servers.ts | 45 | ||||
-rw-r--r-- | shared/models/activitypub/objects/common-objects.ts | 2 |
5 files changed, 84 insertions, 16 deletions
diff --git a/server/helpers/custom-validators/activitypub/video-comments.ts b/server/helpers/custom-validators/activitypub/video-comments.ts index e04c5388f..96655c3f8 100644 --- a/server/helpers/custom-validators/activitypub/video-comments.ts +++ b/server/helpers/custom-validators/activitypub/video-comments.ts | |||
@@ -3,11 +3,28 @@ 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 | |||
6 | function sanitizeAndCheckVideoCommentObject (comment: any) { | 14 | function sanitizeAndCheckVideoCommentObject (comment: any) { |
7 | if (!comment || comment.type !== 'Note') return false | 15 | if (!comment) return false |
16 | |||
17 | if (!isTypeValid(comment)) return false | ||
8 | 18 | ||
9 | normalizeComment(comment) | 19 | normalizeComment(comment) |
10 | 20 | ||
21 | if (comment.type === 'Tombstone') { | ||
22 | return isActivityPubUrlValid(comment.id) && | ||
23 | isDateValid(comment.published) && | ||
24 | isDateValid(comment.deleted) && | ||
25 | isActivityPubUrlValid(comment.url) | ||
26 | } | ||
27 | |||
11 | return isActivityPubUrlValid(comment.id) && | 28 | return isActivityPubUrlValid(comment.id) && |
12 | isCommentContentValid(comment.content) && | 29 | isCommentContentValid(comment.content) && |
13 | isActivityPubUrlValid(comment.inReplyTo) && | 30 | isActivityPubUrlValid(comment.inReplyTo) && |
diff --git a/server/lib/activitypub/video-comments.ts b/server/lib/activitypub/video-comments.ts index 3e8306fa4..1a15842cf 100644 --- a/server/lib/activitypub/video-comments.ts +++ b/server/lib/activitypub/video-comments.ts | |||
@@ -131,9 +131,9 @@ async function resolveParentComment (params: ResolveThreadParams) { | |||
131 | } | 131 | } |
132 | 132 | ||
133 | const actorUrl = body.attributedTo | 133 | const actorUrl = body.attributedTo |
134 | if (!actorUrl) throw new Error('Miss attributed to in comment') | 134 | if (!actorUrl && body.type !== 'Tombstone') throw new Error('Miss attributed to in comment') |
135 | 135 | ||
136 | if (checkUrlsSameHost(url, actorUrl) !== true) { | 136 | if (actorUrl && checkUrlsSameHost(url, actorUrl) !== true) { |
137 | throw new Error(`Actor url ${actorUrl} has not the same host than the comment url ${url}`) | 137 | throw new Error(`Actor url ${actorUrl} has not the same host than the comment url ${url}`) |
138 | } | 138 | } |
139 | 139 | ||
@@ -141,18 +141,19 @@ 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 = await getOrCreateActorAndServerAndModel(actorUrl, 'all') | 144 | const actor = actorUrl ? await getOrCreateActorAndServerAndModel(actorUrl, 'all') : null |
145 | const comment = new VideoCommentModel({ | 145 | const comment = new VideoCommentModel({ |
146 | url: body.id, | 146 | url: body.id, |
147 | text: body.content, | 147 | text: body.content ? body.content : '', |
148 | videoId: null, | 148 | videoId: null, |
149 | accountId: actor.Account.id, | 149 | accountId: actor ? actor.Account.id : null, |
150 | inReplyToCommentId: null, | 150 | inReplyToCommentId: null, |
151 | originCommentId: null, | 151 | originCommentId: null, |
152 | createdAt: new Date(body.published), | 152 | createdAt: new Date(body.published), |
153 | updatedAt: new Date(body.updated) | 153 | updatedAt: new Date(body.updated), |
154 | deletedAt: body.deleted ? new Date(body.deleted) : null | ||
154 | }) as MCommentOwner | 155 | }) as MCommentOwner |
155 | comment.Account = actor.Account | 156 | comment.Account = actor ? actor.Account : null |
156 | 157 | ||
157 | return resolveThread({ | 158 | return resolveThread({ |
158 | url: body.inReplyTo, | 159 | url: body.inReplyTo, |
diff --git a/server/models/video/video-comment.ts b/server/models/video/video-comment.ts index b44d65138..869a42afe 100644 --- a/server/models/video/video-comment.ts +++ b/server/models/video/video-comment.ts | |||
@@ -507,27 +507,30 @@ export class VideoCommentModel extends Model<VideoCommentModel> { | |||
507 | } | 507 | } |
508 | 508 | ||
509 | toActivityPubObject (this: MCommentAP, threadParentComments: MCommentOwner[]): VideoCommentObject | ActivityTombstoneObject { | 509 | toActivityPubObject (this: MCommentAP, threadParentComments: MCommentOwner[]): VideoCommentObject | ActivityTombstoneObject { |
510 | let inReplyTo: string | ||
511 | // New thread, so in AS we reply to the video | ||
512 | if (this.inReplyToCommentId === null) { | ||
513 | inReplyTo = this.Video.url | ||
514 | } else { | ||
515 | inReplyTo = this.InReplyToVideoComment.url | ||
516 | } | ||
517 | |||
510 | if (this.isDeleted()) { | 518 | if (this.isDeleted()) { |
511 | return { | 519 | return { |
512 | id: this.url, | 520 | id: this.url, |
513 | type: 'Tombstone', | 521 | type: 'Tombstone', |
514 | formerType: 'Note', | 522 | formerType: 'Note', |
523 | inReplyTo, | ||
515 | published: this.createdAt.toISOString(), | 524 | published: this.createdAt.toISOString(), |
516 | updated: this.updatedAt.toISOString(), | 525 | updated: this.updatedAt.toISOString(), |
517 | deleted: this.deletedAt.toISOString() | 526 | deleted: this.deletedAt.toISOString() |
518 | } | 527 | } |
519 | } | 528 | } |
520 | 529 | ||
521 | let inReplyTo: string | ||
522 | // New thread, so in AS we reply to the video | ||
523 | if (this.inReplyToCommentId === null) { | ||
524 | inReplyTo = this.Video.url | ||
525 | } else { | ||
526 | inReplyTo = this.InReplyToVideoComment.url | ||
527 | } | ||
528 | |||
529 | const tag: ActivityTagObject[] = [] | 530 | const tag: ActivityTagObject[] = [] |
530 | for (const parentComment of threadParentComments) { | 531 | for (const parentComment of threadParentComments) { |
532 | if (!parentComment.Account) continue | ||
533 | |||
531 | const actor = parentComment.Account.Actor | 534 | const actor = parentComment.Account.Actor |
532 | 535 | ||
533 | tag.push({ | 536 | tag.push({ |
diff --git a/server/tests/api/videos/multiple-servers.ts b/server/tests/api/videos/multiple-servers.ts index e7b57ba1f..836bdc658 100644 --- a/server/tests/api/videos/multiple-servers.ts +++ b/server/tests/api/videos/multiple-servers.ts | |||
@@ -15,6 +15,7 @@ import { | |||
15 | createUser, | 15 | createUser, |
16 | dateIsValid, | 16 | dateIsValid, |
17 | doubleFollow, | 17 | doubleFollow, |
18 | flushAndRunServer, | ||
18 | flushAndRunMultipleServers, | 19 | flushAndRunMultipleServers, |
19 | getLocalVideos, | 20 | getLocalVideos, |
20 | getVideo, | 21 | getVideo, |
@@ -938,7 +939,51 @@ describe('Test multiple servers', function () { | |||
938 | } | 939 | } |
939 | }) | 940 | }) |
940 | 941 | ||
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 | |||
941 | it('Should delete a remote thread by the origin server', async function () { | 984 | it('Should delete a remote thread by the origin server', async function () { |
985 | this.timeout(5000) | ||
986 | |||
942 | const res = await getVideoCommentThreads(servers[ 0 ].url, videoUUID, 0, 5) | 987 | const res = await getVideoCommentThreads(servers[ 0 ].url, videoUUID, 0, 5) |
943 | const threadId = res.body.data.find(c => c.text === 'my super second comment').id | 988 | const threadId = res.body.data.find(c => c.text === 'my super second comment').id |
944 | await deleteVideoComment(servers[ 0 ].url, servers[ 0 ].accessToken, videoUUID, threadId) | 989 | await deleteVideoComment(servers[ 0 ].url, servers[ 0 ].accessToken, videoUUID, threadId) |
diff --git a/shared/models/activitypub/objects/common-objects.ts b/shared/models/activitypub/objects/common-objects.ts index df287d570..de1116ab3 100644 --- a/shared/models/activitypub/objects/common-objects.ts +++ b/shared/models/activitypub/objects/common-objects.ts | |||
@@ -93,9 +93,11 @@ export interface ActivityPubAttributedTo { | |||
93 | export interface ActivityTombstoneObject { | 93 | export interface ActivityTombstoneObject { |
94 | '@context'?: any | 94 | '@context'?: any |
95 | id: string | 95 | id: string |
96 | url?: string | ||
96 | type: 'Tombstone' | 97 | type: 'Tombstone' |
97 | name?: string | 98 | name?: string |
98 | formerType?: string | 99 | formerType?: string |
100 | inReplyTo?: string | ||
99 | published: string | 101 | published: string |
100 | updated: string | 102 | updated: string |
101 | deleted: string | 103 | deleted: string |