mirror of https://github.com/Chocobozzz/PeerTube
Merge branch 'feature/admin-comments' into develop
commit
19149d45b8
|
@ -62,6 +62,13 @@ export class AdminComponent implements OnInit {
|
|||
iconName: 'cross'
|
||||
})
|
||||
}
|
||||
if (this.hasVideoCommentsRight()) {
|
||||
moderationItems.children.push({
|
||||
label: $localize`Video comments`,
|
||||
routerLink: '/admin/moderation/video-comments/list',
|
||||
iconName: 'message-circle'
|
||||
})
|
||||
}
|
||||
if (this.hasAccountsBlocklistRight()) {
|
||||
moderationItems.children.push({
|
||||
label: $localize`Muted accounts`,
|
||||
|
@ -140,4 +147,8 @@ export class AdminComponent implements OnInit {
|
|||
hasDebugRight () {
|
||||
return this.auth.getUser().hasRight(UserRight.MANAGE_DEBUG)
|
||||
}
|
||||
|
||||
hasVideoCommentsRight () {
|
||||
return this.auth.getUser().hasRight(UserRight.SEE_ALL_COMMENTS)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ import { SharedFormModule } from '@app/shared/shared-forms'
|
|||
import { SharedGlobalIconModule } from '@app/shared/shared-icons'
|
||||
import { SharedMainModule } from '@app/shared/shared-main'
|
||||
import { SharedModerationModule } from '@app/shared/shared-moderation'
|
||||
import { SharedVideoCommentModule } from '@app/shared/shared-video-comment'
|
||||
import { AdminRoutingModule } from './admin-routing.module'
|
||||
import { AdminComponent } from './admin.component'
|
||||
import { ConfigComponent, EditCustomConfigComponent } from './config'
|
||||
|
@ -18,6 +19,7 @@ import { VideoRedundancyInformationComponent } from './follows/video-redundancie
|
|||
import { AbuseListComponent, VideoBlockListComponent } from './moderation'
|
||||
import { InstanceAccountBlocklistComponent, InstanceServerBlocklistComponent } from './moderation/instance-blocklist'
|
||||
import { ModerationComponent } from './moderation/moderation.component'
|
||||
import { VideoCommentListComponent } from './moderation/video-comment-list'
|
||||
import { PluginListInstalledComponent } from './plugins/plugin-list-installed/plugin-list-installed.component'
|
||||
import { PluginSearchComponent } from './plugins/plugin-search/plugin-search.component'
|
||||
import { PluginShowInstalledComponent } from './plugins/plugin-show-installed/plugin-show-installed.component'
|
||||
|
@ -37,6 +39,7 @@ import { UserCreateComponent, UserListComponent, UserPasswordComponent, UsersCom
|
|||
SharedModerationModule,
|
||||
SharedGlobalIconModule,
|
||||
SharedAbuseListModule,
|
||||
SharedVideoCommentModule,
|
||||
|
||||
TableModule,
|
||||
SelectButtonModule,
|
||||
|
@ -62,6 +65,7 @@ import { UserCreateComponent, UserListComponent, UserPasswordComponent, UsersCom
|
|||
ModerationComponent,
|
||||
VideoBlockListComponent,
|
||||
AbuseListComponent,
|
||||
VideoCommentListComponent,
|
||||
|
||||
InstanceServerBlocklistComponent,
|
||||
InstanceAccountBlocklistComponent,
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import { Routes } from '@angular/router'
|
||||
import { AbuseListComponent } from '@app/+admin/moderation/abuse-list'
|
||||
import { InstanceAccountBlocklistComponent, InstanceServerBlocklistComponent } from '@app/+admin/moderation/instance-blocklist'
|
||||
import { ModerationComponent } from '@app/+admin/moderation/moderation.component'
|
||||
import { AbuseListComponent } from '@app/+admin/moderation/abuse-list'
|
||||
import { VideoBlockListComponent } from '@app/+admin/moderation/video-block-list'
|
||||
import { VideoCommentListComponent } from './video-comment-list'
|
||||
import { UserRightGuard } from '@app/core'
|
||||
import { UserRight } from '@shared/models'
|
||||
|
||||
|
@ -37,6 +38,7 @@ export const ModerationRoutes: Routes = [
|
|||
}
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
path: 'video-blacklist',
|
||||
redirectTo: 'video-blocks/list',
|
||||
|
@ -64,10 +66,28 @@ export const ModerationRoutes: Routes = [
|
|||
data: {
|
||||
userRight: UserRight.MANAGE_VIDEO_BLACKLIST,
|
||||
meta: {
|
||||
title: $localize`Videos blocked`
|
||||
title: $localize`Blocked videos`
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
path: 'video-comments',
|
||||
redirectTo: 'video-comments/list',
|
||||
pathMatch: 'full'
|
||||
},
|
||||
{
|
||||
path: 'video-comments/list',
|
||||
component: VideoCommentListComponent,
|
||||
canActivate: [ UserRightGuard ],
|
||||
data: {
|
||||
userRight: UserRight.SEE_ALL_COMMENTS,
|
||||
meta: {
|
||||
title: $localize`Video comments`
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
path: 'blocklist/accounts',
|
||||
component: InstanceAccountBlocklistComponent,
|
||||
|
|
|
@ -1,12 +1,8 @@
|
|||
@import 'mixins';
|
||||
|
||||
my-global-icon {
|
||||
@include apply-svg-color(#7d7d7d);
|
||||
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
position: relative;
|
||||
top: -1px;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.input-group {
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
export * from './video-comment-list.component'
|
|
@ -0,0 +1,118 @@
|
|||
<h1>
|
||||
<my-global-icon iconName="message-circle" aria-hidden="true"></my-global-icon>
|
||||
<ng-container i18n>Video comments</ng-container>
|
||||
|
||||
<my-feed [syndicationItems]="syndicationItems"></my-feed>
|
||||
</h1>
|
||||
|
||||
<em>This view also shows comments from muted accounts.</em>
|
||||
|
||||
<p-table
|
||||
[value]="comments" [lazy]="true" [paginator]="totalRecords > 0" [totalRecords]="totalRecords" [rows]="rowsPerPage" [rowsPerPageOptions]="rowsPerPageOptions"
|
||||
[sortField]="sort.field" [sortOrder]="sort.order" (onLazyLoad)="loadLazy($event)" dataKey="id"
|
||||
[showCurrentPageReport]="true" i18n-currentPageReportTemplate
|
||||
currentPageReportTemplate="Showing {{'{first}'}} to {{'{last}'}} of {{'{totalRecords}'}} comments"
|
||||
(onPage)="onPage($event)" [expandedRowKeys]="expandedRows"
|
||||
>
|
||||
<ng-template pTemplate="caption">
|
||||
<div class="caption">
|
||||
<div class="ml-auto">
|
||||
<div class="input-group has-feedback has-clear">
|
||||
<div class="input-group-prepend c-hand" ngbDropdown placement="bottom-left auto" container="body">
|
||||
<div class="input-group-text" ngbDropdownToggle>
|
||||
<span class="caret" aria-haspopup="menu" role="button"></span>
|
||||
</div>
|
||||
|
||||
<div role="menu" ngbDropdownMenu>
|
||||
<h6 class="dropdown-header" i18n>Advanced comments filters</h6>
|
||||
<a [routerLink]="[ '/admin/moderation/video-comments/list' ]" [queryParams]="{ 'search': 'local:true' }" class="dropdown-item" i18n>Local comments</a>
|
||||
<a [routerLink]="[ '/admin/moderation/video-comments/list' ]" [queryParams]="{ 'search': 'local:false' }" class="dropdown-item" i18n>Remote comments</a>
|
||||
</div>
|
||||
</div>
|
||||
<input
|
||||
type="text" name="table-filter" id="table-filter" i18n-placeholder placeholder="Filter..."
|
||||
(keyup)="onInputSearch($event)"
|
||||
>
|
||||
<a class="glyphicon glyphicon-remove-sign form-control-feedback form-control-clear" (click)="resetTableFilter()"></a>
|
||||
<span class="sr-only" i18n>Clear filters</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
<ng-template pTemplate="header">
|
||||
<tr>
|
||||
<th style="width: 40px"></th>
|
||||
<th style="width: 300px" i18n>Account</th>
|
||||
<th style="width: 300px" i18n>Video</th>
|
||||
<th i18n>Comment</th>
|
||||
<th style="width: 150px;" i18n pSortableColumn="createdAt">Date <p-sortIcon field="createdAt"></p-sortIcon></th>
|
||||
<th style="width: 150px;"></th>
|
||||
</tr>
|
||||
</ng-template>
|
||||
|
||||
<ng-template pTemplate="body" let-videoComment let-expanded="expanded">
|
||||
<tr>
|
||||
<td class="expand-cell c-hand" [pRowToggler]="videoComment" i18n-ngbTooltip ngbTooltip="More information" placement="top-left" container="body">
|
||||
<span class="expander">
|
||||
<i [ngClass]="expanded ? 'glyphicon glyphicon-menu-down' : 'glyphicon glyphicon-menu-right'"></i>
|
||||
</span>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<a [href]="videoComment.account.localUrl" i18n-title title="Open account in a new tab" target="_blank" rel="noopener noreferrer">
|
||||
<div class="chip two-lines">
|
||||
<img
|
||||
class="avatar"
|
||||
[src]="videoComment.accountAvatarUrl"
|
||||
alt=""
|
||||
>
|
||||
<div>
|
||||
{{ videoComment.account.displayName }}
|
||||
<span>{{ videoComment.by }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</td>
|
||||
|
||||
<td class="video">
|
||||
<em i18n>Commented video</em>
|
||||
|
||||
<a [href]="videoComment.localUrl" target="_blank" rel="noopener noreferrer">{{ videoComment.video.name }}</a>
|
||||
</td>
|
||||
|
||||
<td class="comment-html">
|
||||
<div [innerHTML]="videoComment.textHtml"></div>
|
||||
</td>
|
||||
|
||||
<td>{{ videoComment.createdAt | date: 'short' }}</td>
|
||||
|
||||
<td class="action-cell">
|
||||
<my-action-dropdown
|
||||
[ngClass]="{ 'show': expanded }" placement="bottom-right" container="body"
|
||||
i18n-label label="Actions" [actions]="videoCommentActions" [entry]="videoComment"
|
||||
></my-action-dropdown>
|
||||
</td>
|
||||
</tr>
|
||||
</ng-template>
|
||||
|
||||
<ng-template pTemplate="rowexpansion" let-videoComment>
|
||||
<tr>
|
||||
<td class="expand-cell" colspan="5">
|
||||
<div [innerHTML]="videoComment.textHtml"></div>
|
||||
</td>
|
||||
</tr>
|
||||
</ng-template>
|
||||
|
||||
<ng-template pTemplate="emptymessage">
|
||||
<tr>
|
||||
<td colspan="5">
|
||||
<div class="no-results">
|
||||
<ng-container *ngIf="search" i18n>No comments found matching current filters.</ng-container>
|
||||
<ng-container *ngIf="!search" i18n>No comments found.</ng-container>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</ng-template>
|
||||
</p-table>
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
@import 'mixins';
|
||||
|
||||
h1 {
|
||||
my-feed {
|
||||
margin-left: 5px;
|
||||
display: inline-block;
|
||||
|
||||
::ng-deep {
|
||||
my-global-icon {
|
||||
width: 15px !important;
|
||||
top: 0 !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
my-global-icon {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.input-group {
|
||||
@include peertube-input-group(300px);
|
||||
|
||||
.dropdown-toggle::after {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.caption {
|
||||
justify-content: flex-end;
|
||||
|
||||
input {
|
||||
@include peertube-input-text(250px);
|
||||
flex-grow: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.video {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
em {
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
a {
|
||||
@include ellipsis
|
||||
}
|
||||
}
|
||||
|
||||
.comment-html {
|
||||
::ng-deep {
|
||||
> div {
|
||||
max-height: 22px;
|
||||
}
|
||||
|
||||
div, p {
|
||||
@include ellipsis;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,169 @@
|
|||
import { SortMeta } from 'primeng/api'
|
||||
import { filter } from 'rxjs/operators'
|
||||
import { AfterViewInit, Component, OnInit } from '@angular/core'
|
||||
import { ActivatedRoute, Params, Router } from '@angular/router'
|
||||
import { AuthService, ConfirmService, MarkdownService, Notifier, RestPagination, RestTable } from '@app/core'
|
||||
import { DropdownAction } from '@app/shared/shared-main'
|
||||
import { BulkService } from '@app/shared/shared-moderation'
|
||||
import { VideoCommentAdmin, VideoCommentService } from '@app/shared/shared-video-comment'
|
||||
import { FeedFormat, UserRight } from '@shared/models'
|
||||
|
||||
@Component({
|
||||
selector: 'my-video-comment-list',
|
||||
templateUrl: './video-comment-list.component.html',
|
||||
styleUrls: [ '../../../shared/shared-moderation/moderation.scss', './video-comment-list.component.scss' ]
|
||||
})
|
||||
export class VideoCommentListComponent extends RestTable implements OnInit, AfterViewInit {
|
||||
comments: VideoCommentAdmin[]
|
||||
totalRecords = 0
|
||||
sort: SortMeta = { field: 'createdAt', order: -1 }
|
||||
pagination: RestPagination = { count: this.rowsPerPage, start: 0 }
|
||||
|
||||
videoCommentActions: DropdownAction<VideoCommentAdmin>[][] = []
|
||||
|
||||
syndicationItems = [
|
||||
{
|
||||
format: FeedFormat.RSS,
|
||||
label: 'media rss 2.0',
|
||||
url: VideoCommentService.BASE_FEEDS_URL + FeedFormat.RSS.toLowerCase()
|
||||
},
|
||||
{
|
||||
format: FeedFormat.ATOM,
|
||||
label: 'atom 1.0',
|
||||
url: VideoCommentService.BASE_FEEDS_URL + FeedFormat.ATOM.toLowerCase()
|
||||
},
|
||||
{
|
||||
format: FeedFormat.JSON,
|
||||
label: 'json 1.0',
|
||||
url: VideoCommentService.BASE_FEEDS_URL + FeedFormat.JSON.toLowerCase()
|
||||
}
|
||||
]
|
||||
|
||||
get authUser () {
|
||||
return this.auth.getUser()
|
||||
}
|
||||
|
||||
constructor (
|
||||
private auth: AuthService,
|
||||
private notifier: Notifier,
|
||||
private confirmService: ConfirmService,
|
||||
private videoCommentService: VideoCommentService,
|
||||
private markdownRenderer: MarkdownService,
|
||||
private route: ActivatedRoute,
|
||||
private router: Router,
|
||||
private bulkService: BulkService
|
||||
) {
|
||||
super()
|
||||
|
||||
this.videoCommentActions = [
|
||||
[
|
||||
{
|
||||
label: $localize`Delete this comment`,
|
||||
handler: comment => this.deleteComment(comment),
|
||||
isDisplayed: () => this.authUser.hasRight(UserRight.REMOVE_ANY_VIDEO_COMMENT)
|
||||
},
|
||||
|
||||
{
|
||||
label: $localize`Delete all comments of this account`,
|
||||
description: $localize`Comments are deleted after a few minutes`,
|
||||
handler: comment => this.deleteUserComments(comment),
|
||||
isDisplayed: () => this.authUser.hasRight(UserRight.REMOVE_ANY_VIDEO_COMMENT)
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
|
||||
ngOnInit () {
|
||||
this.initialize()
|
||||
|
||||
this.route.queryParams
|
||||
.pipe(filter(params => params.search !== undefined && params.search !== null))
|
||||
.subscribe(params => {
|
||||
this.search = params.search
|
||||
this.setTableFilter(params.search)
|
||||
this.loadData()
|
||||
})
|
||||
}
|
||||
|
||||
ngAfterViewInit () {
|
||||
if (this.search) this.setTableFilter(this.search)
|
||||
}
|
||||
|
||||
onInputSearch (event: Event) {
|
||||
this.onSearch(event)
|
||||
this.setQueryParams((event.target as HTMLInputElement).value)
|
||||
}
|
||||
|
||||
setQueryParams (search: string) {
|
||||
const queryParams: Params = {}
|
||||
|
||||
if (search) Object.assign(queryParams, { search })
|
||||
this.router.navigate([ '/admin/moderation/video-comments/list' ], { queryParams })
|
||||
}
|
||||
|
||||
resetTableFilter () {
|
||||
this.setTableFilter('')
|
||||
this.setQueryParams('')
|
||||
this.resetSearch()
|
||||
}
|
||||
/* END Table filter functions */
|
||||
|
||||
getIdentifier () {
|
||||
return 'VideoCommentListComponent'
|
||||
}
|
||||
|
||||
toHtml (text: string) {
|
||||
return this.markdownRenderer.textMarkdownToHTML(text, true, true)
|
||||
}
|
||||
|
||||
protected loadData () {
|
||||
this.videoCommentService.getAdminVideoComments({
|
||||
pagination: this.pagination,
|
||||
sort: this.sort,
|
||||
search: this.search
|
||||
}).subscribe(
|
||||
async resultList => {
|
||||
this.totalRecords = resultList.total
|
||||
|
||||
this.comments = []
|
||||
|
||||
for (const c of resultList.data) {
|
||||
this.comments.push(
|
||||
new VideoCommentAdmin(c, await this.toHtml(c.text))
|
||||
)
|
||||
}
|
||||
},
|
||||
|
||||
err => this.notifier.error(err.message)
|
||||
)
|
||||
}
|
||||
|
||||
private deleteComment (comment: VideoCommentAdmin) {
|
||||
this.videoCommentService.deleteVideoComment(comment.video.id, comment.id)
|
||||
.subscribe(
|
||||
() => this.loadData(),
|
||||
|
||||
err => this.notifier.error(err.message)
|
||||
)
|
||||
}
|
||||
|
||||
private async deleteUserComments (comment: VideoCommentAdmin) {
|
||||
const message = $localize`Do you really want to delete all comments of ${comment.by}?`
|
||||
const res = await this.confirmService.confirm(message, $localize`Delete`)
|
||||
if (res === false) return
|
||||
|
||||
const options = {
|
||||
accountName: comment.by,
|
||||
scope: 'instance' as 'instance'
|
||||
}
|
||||
|
||||
this.bulkService.removeCommentsOf(options)
|
||||
.subscribe(
|
||||
() => {
|
||||
this.notifier.success($localize`Comments of ${options.accountName} will be deleted in a few minutes`)
|
||||
},
|
||||
|
||||
err => this.notifier.error(err.message)
|
||||
)
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
<div class="video-feed">
|
||||
<div class="feed">
|
||||
<my-global-icon
|
||||
*ngIf="syndicationItems.length !== 0" [ngbPopover]="feedsList" [autoClose]="true" placement="bottom left auto"
|
||||
class="icon-syndication" role="button" iconName="syndication"
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
@import '_variables';
|
||||
@import '_mixins';
|
||||
|
||||
.video-feed {
|
||||
.feed {
|
||||
width: min-content;
|
||||
|
||||
a {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { getAbsoluteAPIUrl } from '@app/helpers'
|
||||
import { Actor } from '@app/shared/shared-main'
|
||||
import { Account as AccountInterface, VideoComment as VideoCommentServerModel } from '@shared/models'
|
||||
import { Account as AccountInterface, VideoComment as VideoCommentServerModel, VideoCommentAdmin as VideoCommentAdminServerModel } from '@shared/models'
|
||||
|
||||
export class VideoComment implements VideoCommentServerModel {
|
||||
id: number
|
||||
|
@ -46,3 +46,60 @@ export class VideoComment implements VideoCommentServerModel {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class VideoCommentAdmin implements VideoCommentAdminServerModel {
|
||||
id: number
|
||||
url: string
|
||||
text: string
|
||||
textHtml: string
|
||||
|
||||
threadId: number
|
||||
inReplyToCommentId: number
|
||||
|
||||
createdAt: Date | string
|
||||
updatedAt: Date | string
|
||||
|
||||
account: AccountInterface & { localUrl?: string }
|
||||
localUrl: string
|
||||
|
||||
video: {
|
||||
id: number
|
||||
uuid: string
|
||||
name: string
|
||||
localUrl: string
|
||||
}
|
||||
|
||||
by: string
|
||||
accountAvatarUrl: string
|
||||
|
||||
constructor (hash: VideoCommentAdminServerModel, textHtml: string) {
|
||||
this.id = hash.id
|
||||
this.url = hash.url
|
||||
this.text = hash.text
|
||||
this.textHtml = textHtml
|
||||
|
||||
this.threadId = hash.threadId
|
||||
this.inReplyToCommentId = hash.inReplyToCommentId
|
||||
|
||||
this.createdAt = new Date(hash.createdAt.toString())
|
||||
this.updatedAt = new Date(hash.updatedAt.toString())
|
||||
|
||||
this.video = {
|
||||
id: hash.video.id,
|
||||
uuid: hash.video.uuid,
|
||||
name: hash.video.name,
|
||||
localUrl: '/videos/watch/' + hash.video.uuid
|
||||
}
|
||||
|
||||
this.localUrl = this.video.localUrl + ';threadId=' + this.threadId
|
||||
|
||||
this.account = hash.account
|
||||
|
||||
if (this.account) {
|
||||
this.by = Actor.CREATE_BY_STRING(this.account.name, this.account.host)
|
||||
this.accountAvatarUrl = Actor.GET_ACTOR_AVATAR_URL(this.account)
|
||||
|
||||
this.account.localUrl = '/accounts/' + this.by
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,23 +2,26 @@ import { Observable } from 'rxjs'
|
|||
import { catchError, map } from 'rxjs/operators'
|
||||
import { HttpClient, HttpParams } from '@angular/common/http'
|
||||
import { Injectable } from '@angular/core'
|
||||
import { ComponentPaginationLight, RestExtractor, RestService } from '@app/core'
|
||||
import { ComponentPaginationLight, RestExtractor, RestPagination, RestService } from '@app/core'
|
||||
import { objectLineFeedToHtml } from '@app/helpers'
|
||||
import {
|
||||
FeedFormat,
|
||||
ResultList,
|
||||
VideoComment as VideoCommentServerModel,
|
||||
VideoCommentAdmin,
|
||||
VideoCommentCreate,
|
||||
VideoCommentThreadTree as VideoCommentThreadTreeServerModel
|
||||
} from '@shared/models'
|
||||
import { environment } from '../../../environments/environment'
|
||||
import { VideoCommentThreadTree } from './video-comment-thread-tree.model'
|
||||
import { VideoComment } from './video-comment.model'
|
||||
import { SortMeta } from 'primeng/api'
|
||||
|
||||
@Injectable()
|
||||
export class VideoCommentService {
|
||||
static BASE_FEEDS_URL = environment.apiUrl + '/feeds/video-comments.'
|
||||
|
||||
private static BASE_VIDEO_URL = environment.apiUrl + '/api/v1/videos/'
|
||||
private static BASE_FEEDS_URL = environment.apiUrl + '/feeds/video-comments.'
|
||||
|
||||
constructor (
|
||||
private authHttp: HttpClient,
|
||||
|
@ -48,6 +51,27 @@ export class VideoCommentService {
|
|||
)
|
||||
}
|
||||
|
||||
getAdminVideoComments (options: {
|
||||
pagination: RestPagination,
|
||||
sort: SortMeta,
|
||||
search?: string
|
||||
}): Observable<ResultList<VideoCommentAdmin>> {
|
||||
const { pagination, sort, search } = options
|
||||
const url = VideoCommentService.BASE_VIDEO_URL + 'comments'
|
||||
|
||||
let params = new HttpParams()
|
||||
params = this.restService.addRestGetParams(params, pagination, sort)
|
||||
|
||||
if (search) {
|
||||
params = this.buildParamsFromSearch(search, params)
|
||||
}
|
||||
|
||||
return this.authHttp.get<ResultList<VideoCommentAdmin>>(url, { params })
|
||||
.pipe(
|
||||
catchError(res => this.restExtractor.handleError(res))
|
||||
)
|
||||
}
|
||||
|
||||
getVideoCommentThreads (parameters: {
|
||||
videoId: number | string,
|
||||
componentPagination: ComponentPaginationLight,
|
||||
|
@ -146,4 +170,24 @@ export class VideoCommentService {
|
|||
|
||||
return tree as VideoCommentThreadTree
|
||||
}
|
||||
|
||||
private buildParamsFromSearch (search: string, params: HttpParams) {
|
||||
const filters = this.restService.parseQueryStringFilter(search, {
|
||||
isLocal: {
|
||||
prefix: 'local:',
|
||||
isBoolean: true,
|
||||
handler: v => {
|
||||
if (v === 'true') return v
|
||||
if (v === 'false') return v
|
||||
|
||||
return undefined
|
||||
}
|
||||
},
|
||||
|
||||
searchAccount: { prefix: 'account:' },
|
||||
searchVideo: { prefix: 'video:' }
|
||||
})
|
||||
|
||||
return this.restService.addObjectParams(params, filters)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import * as express from 'express'
|
||||
import { ResultList } from '../../../../shared/models'
|
||||
import { ResultList, UserRight } from '../../../../shared/models'
|
||||
import { VideoCommentCreate } from '../../../../shared/models/videos/video-comment.model'
|
||||
import { auditLoggerFactory, CommentAuditView, getAuditIdFromRes } from '../../../helpers/audit-logger'
|
||||
import { getFormattedObjects } from '../../../helpers/utils'
|
||||
|
@ -11,6 +11,7 @@ import {
|
|||
asyncMiddleware,
|
||||
asyncRetryTransactionMiddleware,
|
||||
authenticate,
|
||||
ensureUserHasRight,
|
||||
optionalAuthenticate,
|
||||
paginationValidator,
|
||||
setDefaultPagination,
|
||||
|
@ -19,9 +20,11 @@ import {
|
|||
import {
|
||||
addVideoCommentReplyValidator,
|
||||
addVideoCommentThreadValidator,
|
||||
listVideoCommentsValidator,
|
||||
listVideoCommentThreadsValidator,
|
||||
listVideoThreadCommentsValidator,
|
||||
removeVideoCommentValidator,
|
||||
videoCommentsValidator,
|
||||
videoCommentThreadsSortValidator
|
||||
} from '../../../middlewares/validators'
|
||||
import { AccountModel } from '../../../models/account/account'
|
||||
|
@ -61,6 +64,17 @@ videoCommentRouter.delete('/:videoId/comments/:commentId',
|
|||
asyncRetryTransactionMiddleware(removeVideoComment)
|
||||
)
|
||||
|
||||
videoCommentRouter.get('/comments',
|
||||
authenticate,
|
||||
ensureUserHasRight(UserRight.SEE_ALL_COMMENTS),
|
||||
paginationValidator,
|
||||
videoCommentsValidator,
|
||||
setDefaultSort,
|
||||
setDefaultPagination,
|
||||
listVideoCommentsValidator,
|
||||
asyncMiddleware(listComments)
|
||||
)
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export {
|
||||
|
@ -69,6 +83,26 @@ export {
|
|||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async function listComments (req: express.Request, res: express.Response) {
|
||||
const options = {
|
||||
start: req.query.start,
|
||||
count: req.query.count,
|
||||
sort: req.query.sort,
|
||||
|
||||
isLocal: req.query.isLocal,
|
||||
search: req.query.search,
|
||||
searchAccount: req.query.searchAccount,
|
||||
searchVideo: req.query.searchVideo
|
||||
}
|
||||
|
||||
const resultList = await VideoCommentModel.listCommentsForApi(options)
|
||||
|
||||
return res.json({
|
||||
total: resultList.total,
|
||||
data: resultList.data.map(c => c.toFormattedAdminJSON())
|
||||
})
|
||||
}
|
||||
|
||||
async function listVideoThreads (req: express.Request, res: express.Response) {
|
||||
const video = res.locals.onlyVideo
|
||||
const user = res.locals.oauth ? res.locals.oauth.token.User : undefined
|
||||
|
|
|
@ -63,7 +63,10 @@ const SORTABLE_COLUMNS = {
|
|||
JOBS: [ 'createdAt' ],
|
||||
VIDEO_CHANNELS: [ 'id', 'name', 'updatedAt', 'createdAt' ],
|
||||
VIDEO_IMPORTS: [ 'createdAt' ],
|
||||
|
||||
VIDEO_COMMENT_THREADS: [ 'createdAt', 'totalReplies' ],
|
||||
VIDEO_COMMENTS: [ 'createdAt' ],
|
||||
|
||||
VIDEO_RATES: [ 'createdAt' ],
|
||||
BLACKLISTS: [ 'id', 'name', 'duration', 'views', 'likes', 'dislikes', 'uuid', 'createdAt' ],
|
||||
FOLLOWERS: [ 'createdAt', 'state', 'score' ],
|
||||
|
|
|
@ -10,6 +10,7 @@ const SORTABLE_VIDEOS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.VIDEOS)
|
|||
const SORTABLE_VIDEOS_SEARCH_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.VIDEOS_SEARCH)
|
||||
const SORTABLE_VIDEO_CHANNELS_SEARCH_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.VIDEO_CHANNELS_SEARCH)
|
||||
const SORTABLE_VIDEO_IMPORTS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.VIDEO_IMPORTS)
|
||||
const SORTABLE_VIDEO_COMMENTS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.VIDEO_COMMENT_THREADS)
|
||||
const SORTABLE_VIDEO_COMMENT_THREADS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.VIDEO_COMMENT_THREADS)
|
||||
const SORTABLE_VIDEO_RATES_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.VIDEO_RATES)
|
||||
const SORTABLE_BLACKLISTS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.BLACKLISTS)
|
||||
|
@ -33,6 +34,7 @@ const videosSortValidator = checkSort(SORTABLE_VIDEOS_COLUMNS)
|
|||
const videoImportsSortValidator = checkSort(SORTABLE_VIDEO_IMPORTS_COLUMNS)
|
||||
const videosSearchSortValidator = checkSort(SORTABLE_VIDEOS_SEARCH_COLUMNS)
|
||||
const videoChannelsSearchSortValidator = checkSort(SORTABLE_VIDEO_CHANNELS_SEARCH_COLUMNS)
|
||||
const videoCommentsValidator = checkSort(SORTABLE_VIDEO_COMMENTS_COLUMNS)
|
||||
const videoCommentThreadsSortValidator = checkSort(SORTABLE_VIDEO_COMMENT_THREADS_COLUMNS)
|
||||
const videoRatesSortValidator = checkSort(SORTABLE_VIDEO_RATES_COLUMNS)
|
||||
const blacklistSortValidator = checkSort(SORTABLE_BLACKLISTS_COLUMNS)
|
||||
|
@ -55,6 +57,7 @@ export {
|
|||
abusesSortValidator,
|
||||
videoChannelsSortValidator,
|
||||
videoImportsSortValidator,
|
||||
videoCommentsValidator,
|
||||
videosSearchSortValidator,
|
||||
videosSortValidator,
|
||||
blacklistSortValidator,
|
||||
|
|
|
@ -41,6 +41,7 @@ import { Hooks } from '@server/lib/plugins/hooks'
|
|||
const usersListValidator = [
|
||||
query('blocked')
|
||||
.optional()
|
||||
.customSanitizer(toBooleanOrNull)
|
||||
.isBoolean().withMessage('Should be a valid boolean banned state'),
|
||||
|
||||
(req: express.Request, res: express.Response, next: express.NextFunction) => {
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import * as express from 'express'
|
||||
import { body, param } from 'express-validator'
|
||||
import { body, param, query } from 'express-validator'
|
||||
import { MUserAccountUrl } from '@server/types/models'
|
||||
import { UserRight } from '../../../../shared'
|
||||
import { isIdOrUUIDValid, isIdValid } from '../../../helpers/custom-validators/misc'
|
||||
import { exists, isBooleanValid, isIdOrUUIDValid, isIdValid, toBooleanOrNull } from '../../../helpers/custom-validators/misc'
|
||||
import {
|
||||
doesVideoCommentExist,
|
||||
doesVideoCommentThreadExist,
|
||||
|
@ -15,6 +15,34 @@ import { Hooks } from '../../../lib/plugins/hooks'
|
|||
import { MCommentOwnerVideoReply, MVideo, MVideoFullLight } from '../../../types/models/video'
|
||||
import { areValidationErrors } from '../utils'
|
||||
|
||||
const listVideoCommentsValidator = [
|
||||
query('isLocal')
|
||||
.optional()
|
||||
.customSanitizer(toBooleanOrNull)
|
||||
.custom(isBooleanValid)
|
||||
.withMessage('Should have a valid is local boolean'),
|
||||
|
||||
query('search')
|
||||
.optional()
|
||||
.custom(exists).withMessage('Should have a valid search'),
|
||||
|
||||
query('searchAccount')
|
||||
.optional()
|
||||
.custom(exists).withMessage('Should have a valid account search'),
|
||||
|
||||
query('searchVideo')
|
||||
.optional()
|
||||
.custom(exists).withMessage('Should have a valid video search'),
|
||||
|
||||
(req: express.Request, res: express.Response, next: express.NextFunction) => {
|
||||
logger.debug('Checking listVideoCommentsValidator parameters.', { parameters: req.query })
|
||||
|
||||
if (areValidationErrors(req, res)) return
|
||||
|
||||
return next()
|
||||
}
|
||||
]
|
||||
|
||||
const listVideoCommentThreadsValidator = [
|
||||
param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'),
|
||||
|
||||
|
@ -116,6 +144,7 @@ export {
|
|||
listVideoCommentThreadsValidator,
|
||||
listVideoThreadCommentsValidator,
|
||||
addVideoCommentThreadValidator,
|
||||
listVideoCommentsValidator,
|
||||
addVideoCommentReplyValidator,
|
||||
videoCommentGetValidator,
|
||||
removeVideoCommentValidator
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import * as Bluebird from 'bluebird'
|
||||
import { uniq } from 'lodash'
|
||||
import { FindOptions, Op, Order, ScopeOptions, Sequelize, Transaction, WhereOptions } from 'sequelize'
|
||||
import { FindAndCountOptions, FindOptions, Op, Order, ScopeOptions, Sequelize, Transaction, WhereOptions } from 'sequelize'
|
||||
import {
|
||||
AllowNull,
|
||||
BelongsTo,
|
||||
|
@ -20,13 +20,14 @@ import { MAccount, MAccountId, MUserAccountId } from '@server/types/models'
|
|||
import { VideoPrivacy } from '@shared/models'
|
||||
import { ActivityTagObject, ActivityTombstoneObject } from '../../../shared/models/activitypub/objects/common-objects'
|
||||
import { VideoCommentObject } from '../../../shared/models/activitypub/objects/video-comment-object'
|
||||
import { VideoComment } from '../../../shared/models/videos/video-comment.model'
|
||||
import { VideoComment, VideoCommentAdmin } from '../../../shared/models/videos/video-comment.model'
|
||||
import { actorNameAlphabet } from '../../helpers/custom-validators/activitypub/actor'
|
||||
import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc'
|
||||
import { regexpCapture } from '../../helpers/regexp'
|
||||
import { CONSTRAINTS_FIELDS, WEBSERVER } from '../../initializers/constants'
|
||||
import {
|
||||
MComment,
|
||||
MCommentAdminFormattable,
|
||||
MCommentAP,
|
||||
MCommentFormattable,
|
||||
MCommentId,
|
||||
|
@ -40,7 +41,14 @@ import {
|
|||
import { VideoCommentAbuseModel } from '../abuse/video-comment-abuse'
|
||||
import { AccountModel } from '../account/account'
|
||||
import { ActorModel, unusedActorAttributesForAPI } from '../activitypub/actor'
|
||||
import { buildBlockedAccountSQL, buildBlockedAccountSQLOptimized, buildLocalAccountIdsIn, getCommentSort, throwIfNotValid } from '../utils'
|
||||
import {
|
||||
buildBlockedAccountSQL,
|
||||
buildBlockedAccountSQLOptimized,
|
||||
buildLocalAccountIdsIn,
|
||||
getCommentSort,
|
||||
searchAttribute,
|
||||
throwIfNotValid
|
||||
} from '../utils'
|
||||
import { VideoModel } from './video'
|
||||
import { VideoChannelModel } from './video-channel'
|
||||
|
||||
|
@ -303,6 +311,98 @@ export class VideoCommentModel extends Model<VideoCommentModel> {
|
|||
return VideoCommentModel.scope([ ScopeNames.WITH_IN_REPLY_TO, ScopeNames.WITH_ACCOUNT ]).findOne(query)
|
||||
}
|
||||
|
||||
static listCommentsForApi (parameters: {
|
||||
start: number
|
||||
count: number
|
||||
sort: string
|
||||
|
||||
isLocal?: boolean
|
||||
search?: string
|
||||
searchAccount?: string
|
||||
searchVideo?: string
|
||||
}) {
|
||||
const { start, count, sort, isLocal, search, searchAccount, searchVideo } = parameters
|
||||
|
||||
const where: WhereOptions = {
|
||||
deletedAt: null
|
||||
}
|
||||
|
||||
const whereAccount: WhereOptions = {}
|
||||
const whereActor: WhereOptions = {}
|
||||
const whereVideo: WhereOptions = {}
|
||||
|
||||
if (isLocal === true) {
|
||||
Object.assign(whereActor, {
|
||||
serverId: null
|
||||
})
|
||||
} else if (isLocal === false) {
|
||||
Object.assign(whereActor, {
|
||||
serverId: {
|
||||
[Op.ne]: null
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (search) {
|
||||
Object.assign(where, {
|
||||
[Op.or]: [
|
||||
searchAttribute(search, 'text'),
|
||||
searchAttribute(search, '$Account.Actor.preferredUsername$'),
|
||||
searchAttribute(search, '$Account.name$'),
|
||||
searchAttribute(search, '$Video.name$')
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
if (searchAccount) {
|
||||
Object.assign(whereActor, {
|
||||
[Op.or]: [
|
||||
searchAttribute(searchAccount, '$Account.Actor.preferredUsername$'),
|
||||
searchAttribute(searchAccount, '$Account.name$')
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
if (searchVideo) {
|
||||
Object.assign(whereVideo, searchAttribute(searchVideo, 'name'))
|
||||
}
|
||||
|
||||
const query: FindAndCountOptions = {
|
||||
offset: start,
|
||||
limit: count,
|
||||
order: getCommentSort(sort),
|
||||
where,
|
||||
include: [
|
||||
{
|
||||
model: AccountModel.unscoped(),
|
||||
required: true,
|
||||
where: whereAccount,
|
||||
include: [
|
||||
{
|
||||
attributes: {
|
||||
exclude: unusedActorAttributesForAPI
|
||||
},
|
||||
model: ActorModel, // Default scope includes avatar and server
|
||||
required: true,
|
||||
where: whereActor
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
model: VideoModel.unscoped(),
|
||||
required: true,
|
||||
where: whereVideo
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
return VideoCommentModel
|
||||
.findAndCountAll(query)
|
||||
.then(({ rows, count }) => {
|
||||
return { total: count, data: rows }
|
||||
})
|
||||
}
|
||||
|
||||
static async listThreadsForApi (parameters: {
|
||||
videoId: number
|
||||
isVideoOwned: boolean
|
||||
|
@ -656,19 +756,51 @@ export class VideoCommentModel extends Model<VideoCommentModel> {
|
|||
id: this.id,
|
||||
url: this.url,
|
||||
text: this.text,
|
||||
|
||||
threadId: this.getThreadId(),
|
||||
inReplyToCommentId: this.inReplyToCommentId || null,
|
||||
videoId: this.videoId,
|
||||
|
||||
createdAt: this.createdAt,
|
||||
updatedAt: this.updatedAt,
|
||||
deletedAt: this.deletedAt,
|
||||
|
||||
isDeleted: this.isDeleted(),
|
||||
|
||||
totalRepliesFromVideoAuthor: this.get('totalRepliesFromVideoAuthor') || 0,
|
||||
totalReplies: this.get('totalReplies') || 0,
|
||||
account: this.Account ? this.Account.toFormattedJSON() : null
|
||||
|
||||
account: this.Account
|
||||
? this.Account.toFormattedJSON()
|
||||
: null
|
||||
} as VideoComment
|
||||
}
|
||||
|
||||
toFormattedAdminJSON (this: MCommentAdminFormattable) {
|
||||
return {
|
||||
id: this.id,
|
||||
url: this.url,
|
||||
text: this.text,
|
||||
|
||||
threadId: this.getThreadId(),
|
||||
inReplyToCommentId: this.inReplyToCommentId || null,
|
||||
videoId: this.videoId,
|
||||
|
||||
createdAt: this.createdAt,
|
||||
updatedAt: this.updatedAt,
|
||||
|
||||
video: {
|
||||
id: this.Video.id,
|
||||
uuid: this.Video.uuid,
|
||||
name: this.Video.name
|
||||
},
|
||||
|
||||
account: this.Account
|
||||
? this.Account.toFormattedJSON()
|
||||
: null
|
||||
} as VideoCommentAdmin
|
||||
}
|
||||
|
||||
toActivityPubObject (this: MCommentAP, threadParentComments: MCommentOwner[]): VideoCommentObject | ActivityTombstoneObject {
|
||||
let inReplyTo: string
|
||||
// New thread, so in AS we reply to the video
|
||||
|
|
|
@ -154,18 +154,6 @@ describe('Test users API validators', function () {
|
|||
await checkBadSortPagination(server.url, path, server.accessToken)
|
||||
})
|
||||
|
||||
it('Should fail with a bad blocked/banned user filter', async function () {
|
||||
await makeGetRequest({
|
||||
url: server.url,
|
||||
path,
|
||||
query: {
|
||||
blocked: 42
|
||||
},
|
||||
token: server.accessToken,
|
||||
statusCodeExpected: 400
|
||||
})
|
||||
})
|
||||
|
||||
it('Should fail with a non authenticated user', async function () {
|
||||
await makeGetRequest({
|
||||
url: server.url,
|
||||
|
|
|
@ -296,6 +296,54 @@ describe('Test video comments API validator', function () {
|
|||
it('Should return conflict on comment thread add')
|
||||
})
|
||||
|
||||
describe('When listing admin comments threads', function () {
|
||||
const path = '/api/v1/videos/comments'
|
||||
|
||||
it('Should fail with a bad start pagination', async function () {
|
||||
await checkBadStartPagination(server.url, path, server.accessToken)
|
||||
})
|
||||
|
||||
it('Should fail with a bad count pagination', async function () {
|
||||
await checkBadCountPagination(server.url, path, server.accessToken)
|
||||
})
|
||||
|
||||
it('Should fail with an incorrect sort', async function () {
|
||||
await checkBadSortPagination(server.url, path, server.accessToken)
|
||||
})
|
||||
|
||||
it('Should fail with a non authenticated user', async function () {
|
||||
await makeGetRequest({
|
||||
url: server.url,
|
||||
path,
|
||||
statusCodeExpected: 401
|
||||
})
|
||||
})
|
||||
|
||||
it('Should fail with a non admin user', async function () {
|
||||
await makeGetRequest({
|
||||
url: server.url,
|
||||
path,
|
||||
token: userAccessToken,
|
||||
statusCodeExpected: 403
|
||||
})
|
||||
})
|
||||
|
||||
it('Should succeed with the correct params', async function () {
|
||||
await makeGetRequest({
|
||||
url: server.url,
|
||||
path,
|
||||
token: server.accessToken,
|
||||
query: {
|
||||
isLocal: false,
|
||||
search: 'toto',
|
||||
searchAccount: 'toto',
|
||||
searchVideo: 'toto'
|
||||
},
|
||||
statusCodeExpected: 200
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
after(async function () {
|
||||
await cleanupTests([ server ])
|
||||
})
|
||||
|
|
|
@ -10,7 +10,6 @@ import {
|
|||
checkLiveCleanup,
|
||||
checkLiveSegmentHash,
|
||||
checkResolutionsInMasterPlaylist,
|
||||
checkSegmentHash,
|
||||
cleanupTests,
|
||||
createLive,
|
||||
doubleFollow,
|
||||
|
|
|
@ -158,7 +158,7 @@ describe('Test multiple servers', function () {
|
|||
})
|
||||
|
||||
it('Should upload the video on server 2 and propagate on each server', async function () {
|
||||
this.timeout(50000)
|
||||
this.timeout(100000)
|
||||
|
||||
const user = {
|
||||
username: 'user1',
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
|
||||
|
||||
import * as chai from 'chai'
|
||||
import 'mocha'
|
||||
import { VideoComment, VideoCommentThreadTree } from '../../../../shared/models/videos/video-comment.model'
|
||||
import * as chai from 'chai'
|
||||
|
||||
import { cleanupTests, testImage } from '../../../../shared/extra-utils'
|
||||
import {
|
||||
createUser,
|
||||
|
@ -18,9 +18,11 @@ import {
|
|||
addVideoCommentReply,
|
||||
addVideoCommentThread,
|
||||
deleteVideoComment,
|
||||
getAdminVideoComments,
|
||||
getVideoCommentThreads,
|
||||
getVideoThreadComments
|
||||
} from '../../../../shared/extra-utils/videos/video-comments'
|
||||
import { VideoComment, VideoCommentAdmin, VideoCommentThreadTree } from '../../../../shared/models/videos/video-comment.model'
|
||||
|
||||
const expect = chai.expect
|
||||
|
||||
|
@ -59,186 +61,248 @@ describe('Test video comments', function () {
|
|||
userAccessTokenServer1 = await getAccessToken(server.url, 'user1', 'password')
|
||||
})
|
||||
|
||||
it('Should not have threads on this video', async function () {
|
||||
const res = await getVideoCommentThreads(server.url, videoUUID, 0, 5)
|
||||
describe('User comments', function () {
|
||||
|
||||
expect(res.body.total).to.equal(0)
|
||||
expect(res.body.data).to.be.an('array')
|
||||
expect(res.body.data).to.have.lengthOf(0)
|
||||
it('Should not have threads on this video', async function () {
|
||||
const res = await getVideoCommentThreads(server.url, videoUUID, 0, 5)
|
||||
|
||||
expect(res.body.total).to.equal(0)
|
||||
expect(res.body.data).to.be.an('array')
|
||||
expect(res.body.data).to.have.lengthOf(0)
|
||||
})
|
||||
|
||||
it('Should create a thread in this video', async function () {
|
||||
const text = 'my super first comment'
|
||||
|
||||
const res = await addVideoCommentThread(server.url, server.accessToken, videoUUID, text)
|
||||
const comment = res.body.comment
|
||||
|
||||
expect(comment.inReplyToCommentId).to.be.null
|
||||
expect(comment.text).equal('my super first comment')
|
||||
expect(comment.videoId).to.equal(videoId)
|
||||
expect(comment.id).to.equal(comment.threadId)
|
||||
expect(comment.account.name).to.equal('root')
|
||||
expect(comment.account.host).to.equal('localhost:' + server.port)
|
||||
expect(comment.account.url).to.equal('http://localhost:' + server.port + '/accounts/root')
|
||||
expect(comment.totalReplies).to.equal(0)
|
||||
expect(comment.totalRepliesFromVideoAuthor).to.equal(0)
|
||||
expect(dateIsValid(comment.createdAt as string)).to.be.true
|
||||
expect(dateIsValid(comment.updatedAt as string)).to.be.true
|
||||
})
|
||||
|
||||
it('Should list threads of this video', async function () {
|
||||
const res = await getVideoCommentThreads(server.url, videoUUID, 0, 5)
|
||||
|
||||
expect(res.body.total).to.equal(1)
|
||||
expect(res.body.data).to.be.an('array')
|
||||
expect(res.body.data).to.have.lengthOf(1)
|
||||
|
||||
const comment: VideoComment = res.body.data[0]
|
||||
expect(comment.inReplyToCommentId).to.be.null
|
||||
expect(comment.text).equal('my super first comment')
|
||||
expect(comment.videoId).to.equal(videoId)
|
||||
expect(comment.id).to.equal(comment.threadId)
|
||||
expect(comment.account.name).to.equal('root')
|
||||
expect(comment.account.host).to.equal('localhost:' + server.port)
|
||||
|
||||
await testImage(server.url, 'avatar-resized', comment.account.avatar.path, '.png')
|
||||
|
||||
expect(comment.totalReplies).to.equal(0)
|
||||
expect(comment.totalRepliesFromVideoAuthor).to.equal(0)
|
||||
expect(dateIsValid(comment.createdAt as string)).to.be.true
|
||||
expect(dateIsValid(comment.updatedAt as string)).to.be.true
|
||||
|
||||
threadId = comment.threadId
|
||||
})
|
||||
|
||||
it('Should get all the thread created', async function () {
|
||||
const res = await getVideoThreadComments(server.url, videoUUID, threadId)
|
||||
|
||||
const rootComment = res.body.comment
|
||||
expect(rootComment.inReplyToCommentId).to.be.null
|
||||
expect(rootComment.text).equal('my super first comment')
|
||||
expect(rootComment.videoId).to.equal(videoId)
|
||||
expect(dateIsValid(rootComment.createdAt as string)).to.be.true
|
||||
expect(dateIsValid(rootComment.updatedAt as string)).to.be.true
|
||||
})
|
||||
|
||||
it('Should create multiple replies in this thread', async function () {
|
||||
const text1 = 'my super answer to thread 1'
|
||||
const childCommentRes = await addVideoCommentReply(server.url, server.accessToken, videoId, threadId, text1)
|
||||
const childCommentId = childCommentRes.body.comment.id
|
||||
|
||||
const text2 = 'my super answer to answer of thread 1'
|
||||
await addVideoCommentReply(server.url, server.accessToken, videoId, childCommentId, text2)
|
||||
|
||||
const text3 = 'my second answer to thread 1'
|
||||
await addVideoCommentReply(server.url, server.accessToken, videoId, threadId, text3)
|
||||
})
|
||||
|
||||
it('Should get correctly the replies', async function () {
|
||||
const res = await getVideoThreadComments(server.url, videoUUID, threadId)
|
||||
|
||||
const tree: VideoCommentThreadTree = res.body
|
||||
expect(tree.comment.text).equal('my super first comment')
|
||||
expect(tree.children).to.have.lengthOf(2)
|
||||
|
||||
const firstChild = tree.children[0]
|
||||
expect(firstChild.comment.text).to.equal('my super answer to thread 1')
|
||||
expect(firstChild.children).to.have.lengthOf(1)
|
||||
|
||||
const childOfFirstChild = firstChild.children[0]
|
||||
expect(childOfFirstChild.comment.text).to.equal('my super answer to answer of thread 1')
|
||||
expect(childOfFirstChild.children).to.have.lengthOf(0)
|
||||
|
||||
const secondChild = tree.children[1]
|
||||
expect(secondChild.comment.text).to.equal('my second answer to thread 1')
|
||||
expect(secondChild.children).to.have.lengthOf(0)
|
||||
|
||||
replyToDeleteId = secondChild.comment.id
|
||||
})
|
||||
|
||||
it('Should create other threads', async function () {
|
||||
const text1 = 'super thread 2'
|
||||
await addVideoCommentThread(server.url, server.accessToken, videoUUID, text1)
|
||||
|
||||
const text2 = 'super thread 3'
|
||||
await addVideoCommentThread(server.url, server.accessToken, videoUUID, text2)
|
||||
})
|
||||
|
||||
it('Should list the threads', async function () {
|
||||
const res = await getVideoCommentThreads(server.url, videoUUID, 0, 5, 'createdAt')
|
||||
|
||||
expect(res.body.total).to.equal(3)
|
||||
expect(res.body.data).to.be.an('array')
|
||||
expect(res.body.data).to.have.lengthOf(3)
|
||||
|
||||
expect(res.body.data[0].text).to.equal('my super first comment')
|
||||
expect(res.body.data[0].totalReplies).to.equal(3)
|
||||
expect(res.body.data[1].text).to.equal('super thread 2')
|
||||
expect(res.body.data[1].totalReplies).to.equal(0)
|
||||
expect(res.body.data[2].text).to.equal('super thread 3')
|
||||
expect(res.body.data[2].totalReplies).to.equal(0)
|
||||
})
|
||||
|
||||
it('Should delete a reply', async function () {
|
||||
await deleteVideoComment(server.url, server.accessToken, videoId, replyToDeleteId)
|
||||
|
||||
const res = await getVideoThreadComments(server.url, videoUUID, threadId)
|
||||
|
||||
const tree: VideoCommentThreadTree = res.body
|
||||
expect(tree.comment.text).equal('my super first comment')
|
||||
expect(tree.children).to.have.lengthOf(2)
|
||||
|
||||
const firstChild = tree.children[0]
|
||||
expect(firstChild.comment.text).to.equal('my super answer to thread 1')
|
||||
expect(firstChild.children).to.have.lengthOf(1)
|
||||
|
||||
const childOfFirstChild = firstChild.children[0]
|
||||
expect(childOfFirstChild.comment.text).to.equal('my super answer to answer of thread 1')
|
||||
expect(childOfFirstChild.children).to.have.lengthOf(0)
|
||||
|
||||
const deletedChildOfFirstChild = tree.children[1]
|
||||
expect(deletedChildOfFirstChild.comment.text).to.equal('')
|
||||
expect(deletedChildOfFirstChild.comment.isDeleted).to.be.true
|
||||
expect(deletedChildOfFirstChild.comment.deletedAt).to.not.be.null
|
||||
expect(deletedChildOfFirstChild.comment.account).to.be.null
|
||||
expect(deletedChildOfFirstChild.children).to.have.lengthOf(0)
|
||||
})
|
||||
|
||||
it('Should delete a complete thread', async function () {
|
||||
await deleteVideoComment(server.url, server.accessToken, videoId, threadId)
|
||||
|
||||
const res = await getVideoCommentThreads(server.url, videoUUID, 0, 5, 'createdAt')
|
||||
expect(res.body.total).to.equal(3)
|
||||
expect(res.body.data).to.be.an('array')
|
||||
expect(res.body.data).to.have.lengthOf(3)
|
||||
|
||||
expect(res.body.data[0].text).to.equal('')
|
||||
expect(res.body.data[0].isDeleted).to.be.true
|
||||
expect(res.body.data[0].deletedAt).to.not.be.null
|
||||
expect(res.body.data[0].account).to.be.null
|
||||
expect(res.body.data[0].totalReplies).to.equal(3)
|
||||
expect(res.body.data[1].text).to.equal('super thread 2')
|
||||
expect(res.body.data[1].totalReplies).to.equal(0)
|
||||
expect(res.body.data[2].text).to.equal('super thread 3')
|
||||
expect(res.body.data[2].totalReplies).to.equal(0)
|
||||
})
|
||||
|
||||
it('Should count replies from the video author correctly', async function () {
|
||||
const text = 'my super first comment'
|
||||
await addVideoCommentThread(server.url, server.accessToken, videoUUID, text)
|
||||
let res = await getVideoCommentThreads(server.url, videoUUID, 0, 5)
|
||||
const comment: VideoComment = res.body.data[0]
|
||||
const threadId2 = comment.threadId
|
||||
|
||||
const text2 = 'a first answer to thread 4 by a third party'
|
||||
await addVideoCommentReply(server.url, userAccessTokenServer1, videoId, threadId2, text2)
|
||||
|
||||
const text3 = 'my second answer to thread 4'
|
||||
await addVideoCommentReply(server.url, server.accessToken, videoId, threadId2, text3)
|
||||
|
||||
res = await getVideoThreadComments(server.url, videoUUID, threadId2)
|
||||
const tree: VideoCommentThreadTree = res.body
|
||||
expect(tree.comment.totalReplies).to.equal(tree.comment.totalRepliesFromVideoAuthor + 1)
|
||||
})
|
||||
})
|
||||
|
||||
it('Should create a thread in this video', async function () {
|
||||
const text = 'my super first comment'
|
||||
describe('All instance comments', function () {
|
||||
async function getComments (options: any = {}) {
|
||||
const res = await getAdminVideoComments(Object.assign({
|
||||
url: server.url,
|
||||
token: server.accessToken,
|
||||
start: 0,
|
||||
count: 10
|
||||
}, options))
|
||||
|
||||
const res = await addVideoCommentThread(server.url, server.accessToken, videoUUID, text)
|
||||
const comment = res.body.comment
|
||||
return { comments: res.body.data as VideoCommentAdmin[], total: res.body.total as number }
|
||||
}
|
||||
|
||||
expect(comment.inReplyToCommentId).to.be.null
|
||||
expect(comment.text).equal('my super first comment')
|
||||
expect(comment.videoId).to.equal(videoId)
|
||||
expect(comment.id).to.equal(comment.threadId)
|
||||
expect(comment.account.name).to.equal('root')
|
||||
expect(comment.account.host).to.equal('localhost:' + server.port)
|
||||
expect(comment.account.url).to.equal('http://localhost:' + server.port + '/accounts/root')
|
||||
expect(comment.totalReplies).to.equal(0)
|
||||
expect(comment.totalRepliesFromVideoAuthor).to.equal(0)
|
||||
expect(dateIsValid(comment.createdAt as string)).to.be.true
|
||||
expect(dateIsValid(comment.updatedAt as string)).to.be.true
|
||||
})
|
||||
it('Should list instance comments as admin', async function () {
|
||||
const { comments } = await getComments({ start: 0, count: 1 })
|
||||
|
||||
it('Should list threads of this video', async function () {
|
||||
const res = await getVideoCommentThreads(server.url, videoUUID, 0, 5)
|
||||
expect(comments[0].text).to.equal('my second answer to thread 4')
|
||||
})
|
||||
|
||||
expect(res.body.total).to.equal(1)
|
||||
expect(res.body.data).to.be.an('array')
|
||||
expect(res.body.data).to.have.lengthOf(1)
|
||||
it('Should filter instance comments by isLocal', async function () {
|
||||
const { total, comments } = await getComments({ isLocal: false })
|
||||
|
||||
const comment: VideoComment = res.body.data[0]
|
||||
expect(comment.inReplyToCommentId).to.be.null
|
||||
expect(comment.text).equal('my super first comment')
|
||||
expect(comment.videoId).to.equal(videoId)
|
||||
expect(comment.id).to.equal(comment.threadId)
|
||||
expect(comment.account.name).to.equal('root')
|
||||
expect(comment.account.host).to.equal('localhost:' + server.port)
|
||||
expect(comments).to.have.lengthOf(0)
|
||||
expect(total).to.equal(0)
|
||||
})
|
||||
|
||||
await testImage(server.url, 'avatar-resized', comment.account.avatar.path, '.png')
|
||||
it('Should search instance comments by account', async function () {
|
||||
const { total, comments } = await getComments({ searchAccount: 'user' })
|
||||
|
||||
expect(comment.totalReplies).to.equal(0)
|
||||
expect(comment.totalRepliesFromVideoAuthor).to.equal(0)
|
||||
expect(dateIsValid(comment.createdAt as string)).to.be.true
|
||||
expect(dateIsValid(comment.updatedAt as string)).to.be.true
|
||||
expect(comments).to.have.lengthOf(1)
|
||||
expect(total).to.equal(1)
|
||||
|
||||
threadId = comment.threadId
|
||||
})
|
||||
expect(comments[0].text).to.equal('a first answer to thread 4 by a third party')
|
||||
})
|
||||
|
||||
it('Should get all the thread created', async function () {
|
||||
const res = await getVideoThreadComments(server.url, videoUUID, threadId)
|
||||
it('Should search instance comments by video', async function () {
|
||||
{
|
||||
const { total, comments } = await getComments({ searchVideo: 'video' })
|
||||
|
||||
const rootComment = res.body.comment
|
||||
expect(rootComment.inReplyToCommentId).to.be.null
|
||||
expect(rootComment.text).equal('my super first comment')
|
||||
expect(rootComment.videoId).to.equal(videoId)
|
||||
expect(dateIsValid(rootComment.createdAt as string)).to.be.true
|
||||
expect(dateIsValid(rootComment.updatedAt as string)).to.be.true
|
||||
})
|
||||
expect(comments).to.have.lengthOf(7)
|
||||
expect(total).to.equal(7)
|
||||
}
|
||||
|
||||
it('Should create multiple replies in this thread', async function () {
|
||||
const text1 = 'my super answer to thread 1'
|
||||
const childCommentRes = await addVideoCommentReply(server.url, server.accessToken, videoId, threadId, text1)
|
||||
const childCommentId = childCommentRes.body.comment.id
|
||||
{
|
||||
const { total, comments } = await getComments({ searchVideo: 'hello' })
|
||||
|
||||
const text2 = 'my super answer to answer of thread 1'
|
||||
await addVideoCommentReply(server.url, server.accessToken, videoId, childCommentId, text2)
|
||||
expect(comments).to.have.lengthOf(0)
|
||||
expect(total).to.equal(0)
|
||||
}
|
||||
})
|
||||
|
||||
const text3 = 'my second answer to thread 1'
|
||||
await addVideoCommentReply(server.url, server.accessToken, videoId, threadId, text3)
|
||||
})
|
||||
it('Should search instance comments', async function () {
|
||||
const { total, comments } = await getComments({ search: 'super thread 3' })
|
||||
|
||||
it('Should get correctly the replies', async function () {
|
||||
const res = await getVideoThreadComments(server.url, videoUUID, threadId)
|
||||
|
||||
const tree: VideoCommentThreadTree = res.body
|
||||
expect(tree.comment.text).equal('my super first comment')
|
||||
expect(tree.children).to.have.lengthOf(2)
|
||||
|
||||
const firstChild = tree.children[0]
|
||||
expect(firstChild.comment.text).to.equal('my super answer to thread 1')
|
||||
expect(firstChild.children).to.have.lengthOf(1)
|
||||
|
||||
const childOfFirstChild = firstChild.children[0]
|
||||
expect(childOfFirstChild.comment.text).to.equal('my super answer to answer of thread 1')
|
||||
expect(childOfFirstChild.children).to.have.lengthOf(0)
|
||||
|
||||
const secondChild = tree.children[1]
|
||||
expect(secondChild.comment.text).to.equal('my second answer to thread 1')
|
||||
expect(secondChild.children).to.have.lengthOf(0)
|
||||
|
||||
replyToDeleteId = secondChild.comment.id
|
||||
})
|
||||
|
||||
it('Should create other threads', async function () {
|
||||
const text1 = 'super thread 2'
|
||||
await addVideoCommentThread(server.url, server.accessToken, videoUUID, text1)
|
||||
|
||||
const text2 = 'super thread 3'
|
||||
await addVideoCommentThread(server.url, server.accessToken, videoUUID, text2)
|
||||
})
|
||||
|
||||
it('Should list the threads', async function () {
|
||||
const res = await getVideoCommentThreads(server.url, videoUUID, 0, 5, 'createdAt')
|
||||
|
||||
expect(res.body.total).to.equal(3)
|
||||
expect(res.body.data).to.be.an('array')
|
||||
expect(res.body.data).to.have.lengthOf(3)
|
||||
|
||||
expect(res.body.data[0].text).to.equal('my super first comment')
|
||||
expect(res.body.data[0].totalReplies).to.equal(3)
|
||||
expect(res.body.data[1].text).to.equal('super thread 2')
|
||||
expect(res.body.data[1].totalReplies).to.equal(0)
|
||||
expect(res.body.data[2].text).to.equal('super thread 3')
|
||||
expect(res.body.data[2].totalReplies).to.equal(0)
|
||||
})
|
||||
|
||||
it('Should delete a reply', async function () {
|
||||
await deleteVideoComment(server.url, server.accessToken, videoId, replyToDeleteId)
|
||||
|
||||
const res = await getVideoThreadComments(server.url, videoUUID, threadId)
|
||||
|
||||
const tree: VideoCommentThreadTree = res.body
|
||||
expect(tree.comment.text).equal('my super first comment')
|
||||
expect(tree.children).to.have.lengthOf(2)
|
||||
|
||||
const firstChild = tree.children[0]
|
||||
expect(firstChild.comment.text).to.equal('my super answer to thread 1')
|
||||
expect(firstChild.children).to.have.lengthOf(1)
|
||||
|
||||
const childOfFirstChild = firstChild.children[0]
|
||||
expect(childOfFirstChild.comment.text).to.equal('my super answer to answer of thread 1')
|
||||
expect(childOfFirstChild.children).to.have.lengthOf(0)
|
||||
|
||||
const deletedChildOfFirstChild = tree.children[1]
|
||||
expect(deletedChildOfFirstChild.comment.text).to.equal('')
|
||||
expect(deletedChildOfFirstChild.comment.isDeleted).to.be.true
|
||||
expect(deletedChildOfFirstChild.comment.deletedAt).to.not.be.null
|
||||
expect(deletedChildOfFirstChild.comment.account).to.be.null
|
||||
expect(deletedChildOfFirstChild.children).to.have.lengthOf(0)
|
||||
})
|
||||
|
||||
it('Should delete a complete thread', async function () {
|
||||
await deleteVideoComment(server.url, server.accessToken, videoId, threadId)
|
||||
|
||||
const res = await getVideoCommentThreads(server.url, videoUUID, 0, 5, 'createdAt')
|
||||
expect(res.body.total).to.equal(3)
|
||||
expect(res.body.data).to.be.an('array')
|
||||
expect(res.body.data).to.have.lengthOf(3)
|
||||
|
||||
expect(res.body.data[0].text).to.equal('')
|
||||
expect(res.body.data[0].isDeleted).to.be.true
|
||||
expect(res.body.data[0].deletedAt).to.not.be.null
|
||||
expect(res.body.data[0].account).to.be.null
|
||||
expect(res.body.data[0].totalReplies).to.equal(3)
|
||||
expect(res.body.data[1].text).to.equal('super thread 2')
|
||||
expect(res.body.data[1].totalReplies).to.equal(0)
|
||||
expect(res.body.data[2].text).to.equal('super thread 3')
|
||||
expect(res.body.data[2].totalReplies).to.equal(0)
|
||||
})
|
||||
|
||||
it('Should count replies from the video author correctly', async function () {
|
||||
const text = 'my super first comment'
|
||||
await addVideoCommentThread(server.url, server.accessToken, videoUUID, text)
|
||||
let res = await getVideoCommentThreads(server.url, videoUUID, 0, 5)
|
||||
const comment: VideoComment = res.body.data[0]
|
||||
const threadId2 = comment.threadId
|
||||
|
||||
const text2 = 'a first answer to thread 4 by a third party'
|
||||
await addVideoCommentReply(server.url, userAccessTokenServer1, videoId, threadId2, text2)
|
||||
|
||||
const text3 = 'my second answer to thread 4'
|
||||
await addVideoCommentReply(server.url, server.accessToken, videoId, threadId2, text3)
|
||||
|
||||
res = await getVideoThreadComments(server.url, videoUUID, threadId2)
|
||||
const tree: VideoCommentThreadTree = res.body
|
||||
expect(tree.comment.totalReplies).to.equal(tree.comment.totalRepliesFromVideoAuthor + 1)
|
||||
expect(comments).to.have.lengthOf(1)
|
||||
expect(total).to.equal(1)
|
||||
expect(comments[0].text).to.equal('super thread 3')
|
||||
})
|
||||
})
|
||||
|
||||
after(async function () {
|
||||
|
|
|
@ -628,7 +628,7 @@ describe('Test video playlists', function () {
|
|||
let video3: string
|
||||
|
||||
before(async function () {
|
||||
this.timeout(30000)
|
||||
this.timeout(60000)
|
||||
|
||||
groupUser1 = [ Object.assign({}, servers[0], { accessToken: userAccessTokenServer1 }) ]
|
||||
groupWithoutToken1 = [ Object.assign({}, servers[0], { accessToken: undefined }) ]
|
||||
|
@ -656,6 +656,8 @@ describe('Test video playlists', function () {
|
|||
video2 = (await uploadVideoAndGetId({ server: servers[1], videoName: 'video 90' })).uuid
|
||||
video3 = (await uploadVideoAndGetId({ server: servers[0], videoName: 'video 91', nsfw: true })).uuid
|
||||
|
||||
await waitJobs(servers)
|
||||
|
||||
await addVideo({ videoId: video1, startTimestamp: 15, stopTimestamp: 28 })
|
||||
await addVideo({ videoId: video2, startTimestamp: 35 })
|
||||
await addVideo({ videoId: video3 })
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { VideoCommentModel } from '../../../models/video/video-comment'
|
||||
import { PickWith, PickWithOpt } from '@shared/core-utils'
|
||||
import { VideoCommentModel } from '../../../models/video/video-comment'
|
||||
import { MAccountDefault, MAccountFormattable, MAccountUrl } from '../account'
|
||||
import { MVideoAccountLight, MVideoFeed, MVideoIdUrl, MVideoUrl } from './video'
|
||||
import { MVideo, MVideoAccountLight, MVideoFeed, MVideoIdUrl, MVideoUrl } from './video'
|
||||
|
||||
type Use<K extends keyof VideoCommentModel, M> = PickWith<VideoCommentModel, K, M>
|
||||
|
||||
|
@ -59,6 +59,11 @@ export type MCommentFormattable =
|
|||
MCommentTotalReplies &
|
||||
Use<'Account', MAccountFormattable>
|
||||
|
||||
export type MCommentAdminFormattable =
|
||||
MComment &
|
||||
Use<'Account', MAccountFormattable> &
|
||||
Use<'Video', MVideo>
|
||||
|
||||
export type MCommentAP =
|
||||
MComment &
|
||||
Use<'Account', MAccountUrl> &
|
||||
|
|
|
@ -22,7 +22,8 @@ const userRoleRights: { [ id in UserRole ]: UserRight[] } = {
|
|||
UserRight.SEE_ALL_VIDEOS,
|
||||
UserRight.MANAGE_ACCOUNTS_BLOCKLIST,
|
||||
UserRight.MANAGE_SERVERS_BLOCKLIST,
|
||||
UserRight.MANAGE_USERS
|
||||
UserRight.MANAGE_USERS,
|
||||
UserRight.SEE_ALL_COMMENTS
|
||||
],
|
||||
|
||||
[UserRole.USER]: []
|
||||
|
|
|
@ -1,7 +1,41 @@
|
|||
/* eslint-disable @typescript-eslint/no-floating-promises */
|
||||
|
||||
import * as request from 'supertest'
|
||||
import { makeDeleteRequest } from '../requests/requests'
|
||||
import { makeDeleteRequest, makeGetRequest } from '../requests/requests'
|
||||
|
||||
function getAdminVideoComments (options: {
|
||||
url: string
|
||||
token: string
|
||||
start: number
|
||||
count: number
|
||||
sort?: string
|
||||
isLocal?: boolean
|
||||
search?: string
|
||||
searchAccount?: string
|
||||
searchVideo?: string
|
||||
}) {
|
||||
const { url, token, start, count, sort, isLocal, search, searchAccount, searchVideo } = options
|
||||
const path = '/api/v1/videos/comments'
|
||||
|
||||
const query = {
|
||||
start,
|
||||
count,
|
||||
sort: sort || '-createdAt'
|
||||
}
|
||||
|
||||
if (isLocal !== undefined) Object.assign(query, { isLocal })
|
||||
if (search !== undefined) Object.assign(query, { search })
|
||||
if (searchAccount !== undefined) Object.assign(query, { searchAccount })
|
||||
if (searchVideo !== undefined) Object.assign(query, { searchVideo })
|
||||
|
||||
return makeGetRequest({
|
||||
url,
|
||||
path,
|
||||
token,
|
||||
query,
|
||||
statusCodeExpected: 200
|
||||
})
|
||||
}
|
||||
|
||||
function getVideoCommentThreads (url: string, videoId: number | string, start: number, count: number, sort?: string, token?: string) {
|
||||
const path = '/api/v1/videos/' + videoId + '/comment-threads'
|
||||
|
@ -88,6 +122,7 @@ function deleteVideoComment (
|
|||
|
||||
export {
|
||||
getVideoCommentThreads,
|
||||
getAdminVideoComments,
|
||||
getVideoThreadComments,
|
||||
addVideoCommentThread,
|
||||
addVideoCommentReply,
|
||||
|
|
|
@ -32,6 +32,7 @@ export const enum UserRight {
|
|||
|
||||
GET_ANY_LIVE,
|
||||
SEE_ALL_VIDEOS,
|
||||
SEE_ALL_COMMENTS,
|
||||
CHANGE_VIDEO_OWNERSHIP,
|
||||
|
||||
MANAGE_PLUGINS,
|
||||
|
|
|
@ -16,6 +16,26 @@ export interface VideoComment {
|
|||
account: Account
|
||||
}
|
||||
|
||||
export interface VideoCommentAdmin {
|
||||
id: number
|
||||
url: string
|
||||
text: string
|
||||
|
||||
threadId: number
|
||||
inReplyToCommentId: number
|
||||
|
||||
createdAt: Date | string
|
||||
updatedAt: Date | string
|
||||
|
||||
account: Account
|
||||
|
||||
video: {
|
||||
id: number
|
||||
uuid: string
|
||||
name: string
|
||||
}
|
||||
}
|
||||
|
||||
export interface VideoCommentThreadTree {
|
||||
comment: VideoComment
|
||||
children: VideoCommentThreadTree[]
|
||||
|
|
Loading…
Reference in New Issue