diff options
author | Chocobozzz <me@florianbigard.com> | 2021-02-19 09:50:13 +0100 |
---|---|---|
committer | Chocobozzz <me@florianbigard.com> | 2021-02-19 10:06:52 +0100 |
commit | 9d6b9d10ef8cbef39e89bc709285abffb0d8caa1 (patch) | |
tree | 3425b22556e00d1b15de15c72b2802cfc9374473 | |
parent | fae6e4da8f516a9d6c3bad9bf6f35811ccacbad8 (diff) | |
download | PeerTube-9d6b9d10ef8cbef39e89bc709285abffb0d8caa1.tar.gz PeerTube-9d6b9d10ef8cbef39e89bc709285abffb0d8caa1.tar.zst PeerTube-9d6b9d10ef8cbef39e89bc709285abffb0d8caa1.zip |
Fix video comments display with deleted comments
11 files changed, 120 insertions, 62 deletions
diff --git a/client/src/app/+videos/+video-watch/comment/video-comment.component.html b/client/src/app/+videos/+video-watch/comment/video-comment.component.html index 8847753a5..ba41b6f48 100644 --- a/client/src/app/+videos/+video-watch/comment/video-comment.component.html +++ b/client/src/app/+videos/+video-watch/comment/video-comment.component.html | |||
@@ -1,4 +1,4 @@ | |||
1 | <div *ngIf="isNotDeletedOrDeletedWithReplies()" class="root-comment"> | 1 | <div *ngIf="isCommentDisplayed()" class="root-comment"> |
2 | <div class="left"> | 2 | <div class="left"> |
3 | <a *ngIf="!comment.isDeleted" [href]="comment.account.url" target="_blank" rel="noopener noreferrer"> | 3 | <a *ngIf="!comment.isDeleted" [href]="comment.account.url" target="_blank" rel="noopener noreferrer"> |
4 | <img | 4 | <img |
diff --git a/client/src/app/+videos/+video-watch/comment/video-comment.component.ts b/client/src/app/+videos/+video-watch/comment/video-comment.component.ts index 0958b25c0..5c5d72b22 100644 --- a/client/src/app/+videos/+video-watch/comment/video-comment.component.ts +++ b/client/src/app/+videos/+video-watch/comment/video-comment.component.ts | |||
@@ -62,6 +62,7 @@ export class VideoCommentComponent implements OnInit, OnChanges { | |||
62 | if (!this.commentTree) { | 62 | if (!this.commentTree) { |
63 | this.commentTree = { | 63 | this.commentTree = { |
64 | comment: this.comment, | 64 | comment: this.comment, |
65 | hasDisplayedChildren: false, | ||
65 | children: [] | 66 | children: [] |
66 | } | 67 | } |
67 | 68 | ||
@@ -70,6 +71,7 @@ export class VideoCommentComponent implements OnInit, OnChanges { | |||
70 | 71 | ||
71 | this.commentTree.children.unshift({ | 72 | this.commentTree.children.unshift({ |
72 | comment: createdComment, | 73 | comment: createdComment, |
74 | hasDisplayedChildren: false, | ||
73 | children: [] | 75 | children: [] |
74 | }) | 76 | }) |
75 | 77 | ||
@@ -133,8 +135,11 @@ export class VideoCommentComponent implements OnInit, OnChanges { | |||
133 | ($event.target as HTMLImageElement).src = Account.GET_DEFAULT_AVATAR_URL() | 135 | ($event.target as HTMLImageElement).src = Account.GET_DEFAULT_AVATAR_URL() |
134 | } | 136 | } |
135 | 137 | ||
136 | isNotDeletedOrDeletedWithReplies () { | 138 | isCommentDisplayed () { |
137 | return !this.comment.isDeleted || this.comment.isDeleted && this.comment.totalReplies !== 0 | 139 | // Not deleted |
140 | return !this.comment.isDeleted || | ||
141 | this.comment.totalReplies !== 0 || // Or root comment thread has replies | ||
142 | (this.commentTree?.hasDisplayedChildren) // Or this is a reply that have other replies | ||
138 | } | 143 | } |
139 | 144 | ||
140 | private getUserIfNeeded (account: Account) { | 145 | private getUserIfNeeded (account: Account) { |
diff --git a/client/src/app/+videos/+video-watch/comment/video-comments.component.html b/client/src/app/+videos/+video-watch/comment/video-comments.component.html index f9ebfcc1f..4a6426d30 100644 --- a/client/src/app/+videos/+video-watch/comment/video-comments.component.html +++ b/client/src/app/+videos/+video-watch/comment/video-comments.component.html | |||
@@ -1,10 +1,10 @@ | |||
1 | <div> | 1 | <div> |
2 | <div class="title-block"> | 2 | <div class="title-block"> |
3 | <h2 class="title-page title-page-single"> | 3 | <h2 class="title-page title-page-single"> |
4 | <ng-container *ngIf="componentPagination.totalItems > 0; then hasComments; else noComments"></ng-container> | 4 | <ng-container *ngIf="totalNotDeletedComments > 0; then hasComments; else noComments"></ng-container> |
5 | <ng-template #hasComments> | 5 | <ng-template #hasComments> |
6 | <ng-container i18n *ngIf="componentPagination.totalItems === 1; else manyComments">1 Comment</ng-container> | 6 | <ng-container i18n *ngIf="totalNotDeletedComments === 1; else manyComments">1 Comment</ng-container> |
7 | <ng-template i18n #manyComments>{{ componentPagination.totalItems }} Comments</ng-template> | 7 | <ng-template i18n #manyComments>{{ totalNotDeletedComments }} Comments</ng-template> |
8 | </ng-template> | 8 | </ng-template> |
9 | <ng-template i18n #noComments>Comments</ng-template> | 9 | <ng-template i18n #noComments>Comments</ng-template> |
10 | </h2> | 10 | </h2> |
@@ -30,7 +30,7 @@ | |||
30 | [textValue]="commentThreadRedraftValue" | 30 | [textValue]="commentThreadRedraftValue" |
31 | ></my-video-comment-add> | 31 | ></my-video-comment-add> |
32 | 32 | ||
33 | <div *ngIf="componentPagination.totalItems === 0 && comments.length === 0" i18n>No comments.</div> | 33 | <div *ngIf="totalNotDeletedComments === 0 && comments.length === 0" i18n>No comments.</div> |
34 | 34 | ||
35 | <div | 35 | <div |
36 | class="comment-threads" | 36 | class="comment-threads" |
diff --git a/client/src/app/+videos/+video-watch/comment/video-comments.component.ts b/client/src/app/+videos/+video-watch/comment/video-comments.component.ts index f83a73ccd..d36dd9e34 100644 --- a/client/src/app/+videos/+video-watch/comment/video-comments.component.ts +++ b/client/src/app/+videos/+video-watch/comment/video-comments.component.ts | |||
@@ -21,15 +21,20 @@ export class VideoCommentsComponent implements OnInit, OnChanges, OnDestroy { | |||
21 | 21 | ||
22 | comments: VideoComment[] = [] | 22 | comments: VideoComment[] = [] |
23 | highlightedThread: VideoComment | 23 | highlightedThread: VideoComment |
24 | |||
24 | sort = '-createdAt' | 25 | sort = '-createdAt' |
26 | |||
25 | componentPagination: ComponentPagination = { | 27 | componentPagination: ComponentPagination = { |
26 | currentPage: 1, | 28 | currentPage: 1, |
27 | itemsPerPage: 10, | 29 | itemsPerPage: 10, |
28 | totalItems: null | 30 | totalItems: null |
29 | } | 31 | } |
32 | totalNotDeletedComments: number | ||
33 | |||
30 | inReplyToCommentId: number | 34 | inReplyToCommentId: number |
31 | commentReplyRedraftValue: string | 35 | commentReplyRedraftValue: string |
32 | commentThreadRedraftValue: string | 36 | commentThreadRedraftValue: string |
37 | |||
33 | threadComments: { [ id: number ]: VideoCommentThreadTree } = {} | 38 | threadComments: { [ id: number ]: VideoCommentThreadTree } = {} |
34 | threadLoading: { [ id: number ]: boolean } = {} | 39 | threadLoading: { [ id: number ]: boolean } = {} |
35 | 40 | ||
@@ -122,8 +127,8 @@ export class VideoCommentsComponent implements OnInit, OnChanges, OnDestroy { | |||
122 | obs.subscribe( | 127 | obs.subscribe( |
123 | res => { | 128 | res => { |
124 | this.comments = this.comments.concat(res.data) | 129 | this.comments = this.comments.concat(res.data) |
125 | // Client does not display removed comments | 130 | this.componentPagination.totalItems = res.total |
126 | this.componentPagination.totalItems = res.total - this.comments.filter(c => c.isDeleted).length | 131 | this.totalNotDeletedComments = res.totalNotDeletedComments |
127 | 132 | ||
128 | this.onDataSubject.next(res.data) | 133 | this.onDataSubject.next(res.data) |
129 | this.hooks.runAction('action:video-watch.video-threads.loaded', 'video-watch', { data: this.componentPagination }) | 134 | this.hooks.runAction('action:video-watch.video-threads.loaded', 'video-watch', { data: this.componentPagination }) |
@@ -241,6 +246,7 @@ export class VideoCommentsComponent implements OnInit, OnChanges, OnDestroy { | |||
241 | this.inReplyToCommentId = undefined | 246 | this.inReplyToCommentId = undefined |
242 | this.componentPagination.currentPage = 1 | 247 | this.componentPagination.currentPage = 1 |
243 | this.componentPagination.totalItems = null | 248 | this.componentPagination.totalItems = null |
249 | this.totalNotDeletedComments = null | ||
244 | 250 | ||
245 | this.syndicationItems = this.videoCommentService.getVideoCommentsFeeds(this.video.uuid) | 251 | this.syndicationItems = this.videoCommentService.getVideoCommentsFeeds(this.video.uuid) |
246 | this.loadMoreThreads() | 252 | this.loadMoreThreads() |
diff --git a/client/src/app/shared/shared-video-comment/video-comment-thread-tree.model.ts b/client/src/app/shared/shared-video-comment/video-comment-thread-tree.model.ts index 7c2aaeadd..9956c88a6 100644 --- a/client/src/app/shared/shared-video-comment/video-comment-thread-tree.model.ts +++ b/client/src/app/shared/shared-video-comment/video-comment-thread-tree.model.ts | |||
@@ -3,5 +3,6 @@ import { VideoComment } from './video-comment.model' | |||
3 | 3 | ||
4 | export class VideoCommentThreadTree implements VideoCommentThreadTreeServerModel { | 4 | export class VideoCommentThreadTree implements VideoCommentThreadTreeServerModel { |
5 | comment: VideoComment | 5 | comment: VideoComment |
6 | hasDisplayedChildren: boolean | ||
6 | children: VideoCommentThreadTree[] | 7 | children: VideoCommentThreadTree[] |
7 | } | 8 | } |
diff --git a/client/src/app/shared/shared-video-comment/video-comment.service.ts b/client/src/app/shared/shared-video-comment/video-comment.service.ts index c107a33ab..0f09778df 100644 --- a/client/src/app/shared/shared-video-comment/video-comment.service.ts +++ b/client/src/app/shared/shared-video-comment/video-comment.service.ts | |||
@@ -8,6 +8,7 @@ import { objectLineFeedToHtml } from '@app/helpers' | |||
8 | import { | 8 | import { |
9 | FeedFormat, | 9 | FeedFormat, |
10 | ResultList, | 10 | ResultList, |
11 | ThreadsResultList, | ||
11 | VideoComment as VideoCommentServerModel, | 12 | VideoComment as VideoCommentServerModel, |
12 | VideoCommentAdmin, | 13 | VideoCommentAdmin, |
13 | VideoCommentCreate, | 14 | VideoCommentCreate, |
@@ -76,7 +77,7 @@ export class VideoCommentService { | |||
76 | videoId: number | string, | 77 | videoId: number | string, |
77 | componentPagination: ComponentPaginationLight, | 78 | componentPagination: ComponentPaginationLight, |
78 | sort: string | 79 | sort: string |
79 | }): Observable<ResultList<VideoComment>> { | 80 | }): Observable<ThreadsResultList<VideoComment>> { |
80 | const { videoId, componentPagination, sort } = parameters | 81 | const { videoId, componentPagination, sort } = parameters |
81 | 82 | ||
82 | const pagination = this.restService.componentPaginationToRestPagination(componentPagination) | 83 | const pagination = this.restService.componentPaginationToRestPagination(componentPagination) |
@@ -85,7 +86,7 @@ export class VideoCommentService { | |||
85 | params = this.restService.addRestGetParams(params, pagination, sort) | 86 | params = this.restService.addRestGetParams(params, pagination, sort) |
86 | 87 | ||
87 | const url = VideoCommentService.BASE_VIDEO_URL + videoId + '/comment-threads' | 88 | const url = VideoCommentService.BASE_VIDEO_URL + videoId + '/comment-threads' |
88 | return this.authHttp.get<ResultList<VideoComment>>(url, { params }) | 89 | return this.authHttp.get<ThreadsResultList<VideoComment>>(url, { params }) |
89 | .pipe( | 90 | .pipe( |
90 | map(result => this.extractVideoComments(result)), | 91 | map(result => this.extractVideoComments(result)), |
91 | catchError(err => this.restExtractor.handleError(err)) | 92 | catchError(err => this.restExtractor.handleError(err)) |
@@ -158,7 +159,7 @@ export class VideoCommentService { | |||
158 | return new VideoComment(videoComment) | 159 | return new VideoComment(videoComment) |
159 | } | 160 | } |
160 | 161 | ||
161 | private extractVideoComments (result: ResultList<VideoCommentServerModel>) { | 162 | private extractVideoComments (result: ThreadsResultList<VideoCommentServerModel>) { |
162 | const videoCommentsJson = result.data | 163 | const videoCommentsJson = result.data |
163 | const totalComments = result.total | 164 | const totalComments = result.total |
164 | const comments: VideoComment[] = [] | 165 | const comments: VideoComment[] = [] |
@@ -167,16 +168,22 @@ export class VideoCommentService { | |||
167 | comments.push(new VideoComment(videoCommentJson)) | 168 | comments.push(new VideoComment(videoCommentJson)) |
168 | } | 169 | } |
169 | 170 | ||
170 | return { data: comments, total: totalComments } | 171 | return { data: comments, total: totalComments, totalNotDeletedComments: result.totalNotDeletedComments } |
171 | } | 172 | } |
172 | 173 | ||
173 | private extractVideoCommentTree (tree: VideoCommentThreadTreeServerModel) { | 174 | private extractVideoCommentTree (serverTree: VideoCommentThreadTreeServerModel): VideoCommentThreadTree { |
174 | if (!tree) return tree as VideoCommentThreadTree | 175 | if (!serverTree) return null |
175 | 176 | ||
176 | tree.comment = new VideoComment(tree.comment) | 177 | const tree = { |
177 | tree.children.forEach(c => this.extractVideoCommentTree(c)) | 178 | comment: new VideoComment(serverTree.comment), |
179 | children: serverTree.children.map(c => this.extractVideoCommentTree(c)) | ||
180 | } | ||
181 | |||
182 | const hasDisplayedChildren = tree.children.length === 0 | ||
183 | ? !tree.comment.isDeleted | ||
184 | : tree.children.some(c => c.hasDisplayedChildren) | ||
178 | 185 | ||
179 | return tree as VideoCommentThreadTree | 186 | return Object.assign(tree, { hasDisplayedChildren }) |
180 | } | 187 | } |
181 | 188 | ||
182 | private buildParamsFromSearch (search: string, params: HttpParams) { | 189 | private buildParamsFromSearch (search: string, params: HttpParams) { |
diff --git a/server/controllers/api/videos/comment.ts b/server/controllers/api/videos/comment.ts index 752a33596..b21698525 100644 --- a/server/controllers/api/videos/comment.ts +++ b/server/controllers/api/videos/comment.ts | |||
@@ -1,5 +1,5 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { ResultList, UserRight } from '../../../../shared/models' | 2 | import { ResultList, ThreadsResultList, UserRight } from '../../../../shared/models' |
3 | import { VideoCommentCreate } from '../../../../shared/models/videos/video-comment.model' | 3 | import { VideoCommentCreate } from '../../../../shared/models/videos/video-comment.model' |
4 | import { auditLoggerFactory, CommentAuditView, getAuditIdFromRes } from '../../../helpers/audit-logger' | 4 | import { auditLoggerFactory, CommentAuditView, getAuditIdFromRes } from '../../../helpers/audit-logger' |
5 | import { getFormattedObjects } from '../../../helpers/utils' | 5 | import { getFormattedObjects } from '../../../helpers/utils' |
@@ -30,6 +30,7 @@ import { | |||
30 | import { AccountModel } from '../../../models/account/account' | 30 | import { AccountModel } from '../../../models/account/account' |
31 | import { VideoCommentModel } from '../../../models/video/video-comment' | 31 | import { VideoCommentModel } from '../../../models/video/video-comment' |
32 | import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes' | 32 | import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes' |
33 | import { logger } from '@server/helpers/logger' | ||
33 | 34 | ||
34 | const auditLogger = auditLoggerFactory('comments') | 35 | const auditLogger = auditLoggerFactory('comments') |
35 | const videoCommentRouter = express.Router() | 36 | const videoCommentRouter = express.Router() |
@@ -108,7 +109,7 @@ async function listVideoThreads (req: express.Request, res: express.Response) { | |||
108 | const video = res.locals.onlyVideo | 109 | const video = res.locals.onlyVideo |
109 | const user = res.locals.oauth ? res.locals.oauth.token.User : undefined | 110 | const user = res.locals.oauth ? res.locals.oauth.token.User : undefined |
110 | 111 | ||
111 | let resultList: ResultList<VideoCommentModel> | 112 | let resultList: ThreadsResultList<VideoCommentModel> |
112 | 113 | ||
113 | if (video.commentsEnabled === true) { | 114 | if (video.commentsEnabled === true) { |
114 | const apiOptions = await Hooks.wrapObject({ | 115 | const apiOptions = await Hooks.wrapObject({ |
@@ -128,11 +129,15 @@ async function listVideoThreads (req: express.Request, res: express.Response) { | |||
128 | } else { | 129 | } else { |
129 | resultList = { | 130 | resultList = { |
130 | total: 0, | 131 | total: 0, |
132 | totalNotDeletedComments: 0, | ||
131 | data: [] | 133 | data: [] |
132 | } | 134 | } |
133 | } | 135 | } |
134 | 136 | ||
135 | return res.json(getFormattedObjects(resultList.data, resultList.total)) | 137 | return res.json({ |
138 | ...getFormattedObjects(resultList.data, resultList.total), | ||
139 | totalNotDeletedComments: resultList.totalNotDeletedComments | ||
140 | }) | ||
136 | } | 141 | } |
137 | 142 | ||
138 | async function listVideoThreadComments (req: express.Request, res: express.Response) { | 143 | async function listVideoThreadComments (req: express.Request, res: express.Response) { |
@@ -161,6 +166,8 @@ async function listVideoThreadComments (req: express.Request, res: express.Respo | |||
161 | } | 166 | } |
162 | } | 167 | } |
163 | 168 | ||
169 | logger.info('coucou', { resultList }) | ||
170 | |||
164 | if (resultList.data.length === 0) { | 171 | if (resultList.data.length === 0) { |
165 | return res.sendStatus(HttpStatusCode.NOT_FOUND_404) | 172 | return res.sendStatus(HttpStatusCode.NOT_FOUND_404) |
166 | } | 173 | } |
diff --git a/server/models/utils.ts b/server/models/utils.ts index 143c1a23c..5337ae75d 100644 --- a/server/models/utils.ts +++ b/server/models/utils.ts | |||
@@ -134,7 +134,7 @@ function buildBlockedAccountSQL (blockerIds: number[]) { | |||
134 | const blockerIdsString = blockerIds.join(', ') | 134 | const blockerIdsString = blockerIds.join(', ') |
135 | 135 | ||
136 | return 'SELECT "targetAccountId" AS "id" FROM "accountBlocklist" WHERE "accountId" IN (' + blockerIdsString + ')' + | 136 | return 'SELECT "targetAccountId" AS "id" FROM "accountBlocklist" WHERE "accountId" IN (' + blockerIdsString + ')' + |
137 | ' UNION ALL ' + | 137 | ' UNION ' + |
138 | 'SELECT "account"."id" AS "id" FROM account INNER JOIN "actor" ON account."actorId" = actor.id ' + | 138 | 'SELECT "account"."id" AS "id" FROM account INNER JOIN "actor" ON account."actorId" = actor.id ' + |
139 | 'INNER JOIN "serverBlocklist" ON "actor"."serverId" = "serverBlocklist"."targetServerId" ' + | 139 | 'INNER JOIN "serverBlocklist" ON "actor"."serverId" = "serverBlocklist"."targetServerId" ' + |
140 | 'WHERE "serverBlocklist"."accountId" IN (' + blockerIdsString + ')' | 140 | 'WHERE "serverBlocklist"."accountId" IN (' + blockerIdsString + ')' |
diff --git a/server/models/video/video-comment.ts b/server/models/video/video-comment.ts index 8d1c38826..cfd1d5b7a 100644 --- a/server/models/video/video-comment.ts +++ b/server/models/video/video-comment.ts | |||
@@ -414,7 +414,15 @@ export class VideoCommentModel extends Model { | |||
414 | 414 | ||
415 | const blockerAccountIds = await VideoCommentModel.buildBlockerAccountIds({ videoId, user, isVideoOwned }) | 415 | const blockerAccountIds = await VideoCommentModel.buildBlockerAccountIds({ videoId, user, isVideoOwned }) |
416 | 416 | ||
417 | const query = { | 417 | const accountBlockedWhere = { |
418 | accountId: { | ||
419 | [Op.notIn]: Sequelize.literal( | ||
420 | '(' + buildBlockedAccountSQL(blockerAccountIds) + ')' | ||
421 | ) | ||
422 | } | ||
423 | } | ||
424 | |||
425 | const queryList = { | ||
418 | offset: start, | 426 | offset: start, |
419 | limit: count, | 427 | limit: count, |
420 | order: getCommentSort(sort), | 428 | order: getCommentSort(sort), |
@@ -428,13 +436,7 @@ export class VideoCommentModel extends Model { | |||
428 | }, | 436 | }, |
429 | { | 437 | { |
430 | [Op.or]: [ | 438 | [Op.or]: [ |
431 | { | 439 | accountBlockedWhere, |
432 | accountId: { | ||
433 | [Op.notIn]: Sequelize.literal( | ||
434 | '(' + buildBlockedAccountSQL(blockerAccountIds) + ')' | ||
435 | ) | ||
436 | } | ||
437 | }, | ||
438 | { | 440 | { |
439 | accountId: null | 441 | accountId: null |
440 | } | 442 | } |
@@ -444,19 +446,27 @@ export class VideoCommentModel extends Model { | |||
444 | } | 446 | } |
445 | } | 447 | } |
446 | 448 | ||
447 | const scopes: (string | ScopeOptions)[] = [ | 449 | const scopesList: (string | ScopeOptions)[] = [ |
448 | ScopeNames.WITH_ACCOUNT_FOR_API, | 450 | ScopeNames.WITH_ACCOUNT_FOR_API, |
449 | { | 451 | { |
450 | method: [ ScopeNames.ATTRIBUTES_FOR_API, blockerAccountIds ] | 452 | method: [ ScopeNames.ATTRIBUTES_FOR_API, blockerAccountIds ] |
451 | } | 453 | } |
452 | ] | 454 | ] |
453 | 455 | ||
454 | return VideoCommentModel | 456 | const queryCount = { |
455 | .scope(scopes) | 457 | where: { |
456 | .findAndCountAll(query) | 458 | videoId, |
457 | .then(({ rows, count }) => { | 459 | deletedAt: null, |
458 | return { total: count, data: rows } | 460 | ...accountBlockedWhere |
459 | }) | 461 | } |
462 | } | ||
463 | |||
464 | return Promise.all([ | ||
465 | VideoCommentModel.scope(scopesList).findAndCountAll(queryList), | ||
466 | VideoCommentModel.count(queryCount) | ||
467 | ]).then(([ { rows, count }, totalNotDeletedComments ]) => { | ||
468 | return { total: count, data: rows, totalNotDeletedComments } | ||
469 | }) | ||
460 | } | 470 | } |
461 | 471 | ||
462 | static async listThreadCommentsForApi (parameters: { | 472 | static async listThreadCommentsForApi (parameters: { |
@@ -477,11 +487,18 @@ export class VideoCommentModel extends Model { | |||
477 | { id: threadId }, | 487 | { id: threadId }, |
478 | { originCommentId: threadId } | 488 | { originCommentId: threadId } |
479 | ], | 489 | ], |
480 | accountId: { | 490 | [Op.or]: [ |
481 | [Op.notIn]: Sequelize.literal( | 491 | { |
482 | '(' + buildBlockedAccountSQL(blockerAccountIds) + ')' | 492 | accountId: { |
483 | ) | 493 | [Op.notIn]: Sequelize.literal( |
484 | } | 494 | '(' + buildBlockedAccountSQL(blockerAccountIds) + ')' |
495 | ) | ||
496 | } | ||
497 | }, | ||
498 | { | ||
499 | accountId: null | ||
500 | } | ||
501 | ] | ||
485 | } | 502 | } |
486 | } | 503 | } |
487 | 504 | ||
@@ -492,8 +509,7 @@ export class VideoCommentModel extends Model { | |||
492 | } | 509 | } |
493 | ] | 510 | ] |
494 | 511 | ||
495 | return VideoCommentModel | 512 | return VideoCommentModel.scope(scopes) |
496 | .scope(scopes) | ||
497 | .findAndCountAll(query) | 513 | .findAndCountAll(query) |
498 | .then(({ rows, count }) => { | 514 | .then(({ rows, count }) => { |
499 | return { total: count, data: rows } | 515 | return { total: count, data: rows } |
diff --git a/server/tests/api/videos/video-comments.ts b/server/tests/api/videos/video-comments.ts index 141a80690..615e0ea45 100644 --- a/server/tests/api/videos/video-comments.ts +++ b/server/tests/api/videos/video-comments.ts | |||
@@ -67,6 +67,7 @@ describe('Test video comments', function () { | |||
67 | const res = await getVideoCommentThreads(server.url, videoUUID, 0, 5) | 67 | const res = await getVideoCommentThreads(server.url, videoUUID, 0, 5) |
68 | 68 | ||
69 | expect(res.body.total).to.equal(0) | 69 | expect(res.body.total).to.equal(0) |
70 | expect(res.body.totalNotDeletedComments).to.equal(0) | ||
70 | expect(res.body.data).to.be.an('array') | 71 | expect(res.body.data).to.be.an('array') |
71 | expect(res.body.data).to.have.lengthOf(0) | 72 | expect(res.body.data).to.have.lengthOf(0) |
72 | }) | 73 | }) |
@@ -94,6 +95,7 @@ describe('Test video comments', function () { | |||
94 | const res = await getVideoCommentThreads(server.url, videoUUID, 0, 5) | 95 | const res = await getVideoCommentThreads(server.url, videoUUID, 0, 5) |
95 | 96 | ||
96 | expect(res.body.total).to.equal(1) | 97 | expect(res.body.total).to.equal(1) |
98 | expect(res.body.totalNotDeletedComments).to.equal(1) | ||
97 | expect(res.body.data).to.be.an('array') | 99 | expect(res.body.data).to.be.an('array') |
98 | expect(res.body.data).to.have.lengthOf(1) | 100 | expect(res.body.data).to.have.lengthOf(1) |
99 | 101 | ||
@@ -172,6 +174,7 @@ describe('Test video comments', function () { | |||
172 | const res = await getVideoCommentThreads(server.url, videoUUID, 0, 5, 'createdAt') | 174 | const res = await getVideoCommentThreads(server.url, videoUUID, 0, 5, 'createdAt') |
173 | 175 | ||
174 | expect(res.body.total).to.equal(3) | 176 | expect(res.body.total).to.equal(3) |
177 | expect(res.body.totalNotDeletedComments).to.equal(6) | ||
175 | expect(res.body.data).to.be.an('array') | 178 | expect(res.body.data).to.be.an('array') |
176 | expect(res.body.data).to.have.lengthOf(3) | 179 | expect(res.body.data).to.have.lengthOf(3) |
177 | 180 | ||
@@ -186,26 +189,35 @@ describe('Test video comments', function () { | |||
186 | it('Should delete a reply', async function () { | 189 | it('Should delete a reply', async function () { |
187 | await deleteVideoComment(server.url, server.accessToken, videoId, replyToDeleteId) | 190 | await deleteVideoComment(server.url, server.accessToken, videoId, replyToDeleteId) |
188 | 191 | ||
189 | const res = await getVideoThreadComments(server.url, videoUUID, threadId) | 192 | { |
190 | 193 | const res = await getVideoCommentThreads(server.url, videoUUID, 0, 5, 'createdAt') | |
191 | const tree: VideoCommentThreadTree = res.body | ||
192 | expect(tree.comment.text).equal('my super first comment') | ||
193 | expect(tree.children).to.have.lengthOf(2) | ||
194 | |||
195 | const firstChild = tree.children[0] | ||
196 | expect(firstChild.comment.text).to.equal('my super answer to thread 1') | ||
197 | expect(firstChild.children).to.have.lengthOf(1) | ||
198 | 194 | ||
199 | const childOfFirstChild = firstChild.children[0] | 195 | expect(res.body.total).to.equal(3) |
200 | expect(childOfFirstChild.comment.text).to.equal('my super answer to answer of thread 1') | 196 | expect(res.body.totalNotDeletedComments).to.equal(5) |
201 | expect(childOfFirstChild.children).to.have.lengthOf(0) | 197 | } |
202 | 198 | ||
203 | const deletedChildOfFirstChild = tree.children[1] | 199 | { |
204 | expect(deletedChildOfFirstChild.comment.text).to.equal('') | 200 | const res = await getVideoThreadComments(server.url, videoUUID, threadId) |
205 | expect(deletedChildOfFirstChild.comment.isDeleted).to.be.true | 201 | |
206 | expect(deletedChildOfFirstChild.comment.deletedAt).to.not.be.null | 202 | const tree: VideoCommentThreadTree = res.body |
207 | expect(deletedChildOfFirstChild.comment.account).to.be.null | 203 | expect(tree.comment.text).equal('my super first comment') |
208 | expect(deletedChildOfFirstChild.children).to.have.lengthOf(0) | 204 | expect(tree.children).to.have.lengthOf(2) |
205 | |||
206 | const firstChild = tree.children[0] | ||
207 | expect(firstChild.comment.text).to.equal('my super answer to thread 1') | ||
208 | expect(firstChild.children).to.have.lengthOf(1) | ||
209 | |||
210 | const childOfFirstChild = firstChild.children[0] | ||
211 | expect(childOfFirstChild.comment.text).to.equal('my super answer to answer of thread 1') | ||
212 | expect(childOfFirstChild.children).to.have.lengthOf(0) | ||
213 | |||
214 | const deletedChildOfFirstChild = tree.children[1] | ||
215 | expect(deletedChildOfFirstChild.comment.text).to.equal('') | ||
216 | expect(deletedChildOfFirstChild.comment.isDeleted).to.be.true | ||
217 | expect(deletedChildOfFirstChild.comment.deletedAt).to.not.be.null | ||
218 | expect(deletedChildOfFirstChild.comment.account).to.be.null | ||
219 | expect(deletedChildOfFirstChild.children).to.have.lengthOf(0) | ||
220 | } | ||
209 | }) | 221 | }) |
210 | 222 | ||
211 | it('Should delete a complete thread', async function () { | 223 | it('Should delete a complete thread', async function () { |
diff --git a/shared/models/result-list.model.ts b/shared/models/result-list.model.ts index 2d5147a86..fcafcfb2f 100644 --- a/shared/models/result-list.model.ts +++ b/shared/models/result-list.model.ts | |||
@@ -2,3 +2,7 @@ export interface ResultList<T> { | |||
2 | total: number | 2 | total: number |
3 | data: T[] | 3 | data: T[] |
4 | } | 4 | } |
5 | |||
6 | export interface ThreadsResultList <T> extends ResultList <T> { | ||
7 | totalNotDeletedComments: number | ||
8 | } | ||