Add blacklist reason field
This commit is contained in:
parent
efc9e8450a
commit
26b7305a23
|
@ -9,7 +9,7 @@
|
||||||
<ng-template pTemplate="header">
|
<ng-template pTemplate="header">
|
||||||
<tr>
|
<tr>
|
||||||
<th style="width: 40px"></th>
|
<th style="width: 40px"></th>
|
||||||
<th i18n style="width: 80px;">State</th>
|
<th i18n pSortableColumn="state" style="width: 80px;">State <p-sortIcon field="state"></p-sortIcon></th>
|
||||||
<th i18n>Reason</th>
|
<th i18n>Reason</th>
|
||||||
<th i18n>Reporter</th>
|
<th i18n>Reporter</th>
|
||||||
<th i18n pSortableColumn="createdAt">Created <p-sortIcon field="createdAt"></p-sortIcon></th>
|
<th i18n pSortableColumn="createdAt">Created <p-sortIcon field="createdAt"></p-sortIcon></th>
|
||||||
|
|
|
@ -4,30 +4,43 @@
|
||||||
|
|
||||||
<p-table
|
<p-table
|
||||||
[value]="blacklist" [lazy]="true" [paginator]="true" [totalRecords]="totalRecords" [rows]="rowsPerPage"
|
[value]="blacklist" [lazy]="true" [paginator]="true" [totalRecords]="totalRecords" [rows]="rowsPerPage"
|
||||||
[sortField]="sort.field" [sortOrder]="sort.order" (onLazyLoad)="loadLazy($event)"
|
[sortField]="sort.field" [sortOrder]="sort.order" (onLazyLoad)="loadLazy($event)" dataKey="id"
|
||||||
>
|
>
|
||||||
<ng-template pTemplate="header">
|
<ng-template pTemplate="header">
|
||||||
<tr>
|
<tr>
|
||||||
<th i18n pSortableColumn="name">Name <p-sortIcon field="name"></p-sortIcon></th>
|
<th style="width: 40px"></th>
|
||||||
<th i18n>Description</th>
|
<th i18n pSortableColumn="name">Video name <p-sortIcon field="name"></p-sortIcon></th>
|
||||||
<th i18n pSortableColumn="views">Views <p-sortIcon field="views"></p-sortIcon></th>
|
|
||||||
<th i18n>NSFW</th>
|
<th i18n>NSFW</th>
|
||||||
<th i18n>UUID</th>
|
<th i18n>UUID</th>
|
||||||
<th i18n pSortableColumn="createdAt">Created <p-sortIcon field="createdAt"></p-sortIcon></th>
|
<th i18n pSortableColumn="createdAt">Date <p-sortIcon field="createdAt"></p-sortIcon></th>
|
||||||
<th></th>
|
<th style="width: 50px;"></th>
|
||||||
</tr>
|
</tr>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|
||||||
<ng-template pTemplate="body" let-videoBlacklist>
|
<ng-template pTemplate="body" let-videoBlacklist let-expanded="expanded">
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ videoBlacklist.name }}</td>
|
<td>
|
||||||
<td>{{ videoBlacklist.description }}</td>
|
<span *ngIf="videoBlacklist.reason" class="expander" [pRowToggler]="videoBlacklist">
|
||||||
<td>{{ videoBlacklist.views }}</td>
|
<i [ngClass]="expanded ? 'glyphicon glyphicon-menu-down' : 'glyphicon glyphicon-menu-right'"></i>
|
||||||
<td>{{ videoBlacklist.nsfw }}</td>
|
</span>
|
||||||
<td>{{ videoBlacklist.uuid }}</td>
|
</td>
|
||||||
|
|
||||||
|
<td>{{ videoBlacklist.video.name }}</td>
|
||||||
|
<td>{{ videoBlacklist.video.nsfw }}</td>
|
||||||
|
<td>{{ videoBlacklist.video.uuid }}</td>
|
||||||
<td>{{ videoBlacklist.createdAt }}</td>
|
<td>{{ videoBlacklist.createdAt }}</td>
|
||||||
|
|
||||||
<td class="action-cell">
|
<td class="action-cell">
|
||||||
<my-delete-button i18n-label label="Unblacklist" (click)="removeVideoFromBlacklist(videoBlacklist)"></my-delete-button>
|
<my-action-dropdown i18n-label label="Actions" [actions]="videoBlacklistActions" [entry]="videoBlacklist"></my-action-dropdown>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
|
<ng-template pTemplate="rowexpansion" let-videoBlacklist>
|
||||||
|
<tr class="blacklist-reason">
|
||||||
|
<td colspan="6">
|
||||||
|
<span i18n class="blacklist-reason-label">Blacklist reason:</span>
|
||||||
|
{{ videoBlacklist.reason }}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
@import '_variables';
|
||||||
|
@import '_mixins';
|
||||||
|
|
||||||
|
.blacklist-reason-label {
|
||||||
|
font-weight: $font-semibold;
|
||||||
|
}
|
|
@ -5,11 +5,12 @@ import { ConfirmService } from '../../../core'
|
||||||
import { RestPagination, RestTable, VideoBlacklistService } from '../../../shared'
|
import { RestPagination, RestTable, VideoBlacklistService } from '../../../shared'
|
||||||
import { BlacklistedVideo } from '../../../../../../shared'
|
import { BlacklistedVideo } from '../../../../../../shared'
|
||||||
import { I18n } from '@ngx-translate/i18n-polyfill'
|
import { I18n } from '@ngx-translate/i18n-polyfill'
|
||||||
|
import { DropdownAction } from '@app/shared/buttons/action-dropdown.component'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'my-video-blacklist-list',
|
selector: 'my-video-blacklist-list',
|
||||||
templateUrl: './video-blacklist-list.component.html',
|
templateUrl: './video-blacklist-list.component.html',
|
||||||
styleUrls: []
|
styleUrls: [ './video-blacklist-list.component.scss' ]
|
||||||
})
|
})
|
||||||
export class VideoBlacklistListComponent extends RestTable implements OnInit {
|
export class VideoBlacklistListComponent extends RestTable implements OnInit {
|
||||||
blacklist: BlacklistedVideo[] = []
|
blacklist: BlacklistedVideo[] = []
|
||||||
|
@ -18,6 +19,8 @@ export class VideoBlacklistListComponent extends RestTable implements OnInit {
|
||||||
sort: SortMeta = { field: 'createdAt', order: 1 }
|
sort: SortMeta = { field: 'createdAt', order: 1 }
|
||||||
pagination: RestPagination = { count: this.rowsPerPage, start: 0 }
|
pagination: RestPagination = { count: this.rowsPerPage, start: 0 }
|
||||||
|
|
||||||
|
videoBlacklistActions: DropdownAction<BlacklistedVideo>[] = []
|
||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
private notificationsService: NotificationsService,
|
private notificationsService: NotificationsService,
|
||||||
private confirmService: ConfirmService,
|
private confirmService: ConfirmService,
|
||||||
|
@ -25,6 +28,13 @@ export class VideoBlacklistListComponent extends RestTable implements OnInit {
|
||||||
private i18n: I18n
|
private i18n: I18n
|
||||||
) {
|
) {
|
||||||
super()
|
super()
|
||||||
|
|
||||||
|
this.videoBlacklistActions = [
|
||||||
|
{
|
||||||
|
label: this.i18n('Unblacklist'),
|
||||||
|
handler: videoBlacklist => this.removeVideoFromBlacklist(videoBlacklist)
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit () {
|
ngOnInit () {
|
||||||
|
@ -33,17 +43,17 @@ export class VideoBlacklistListComponent extends RestTable implements OnInit {
|
||||||
|
|
||||||
async removeVideoFromBlacklist (entry: BlacklistedVideo) {
|
async removeVideoFromBlacklist (entry: BlacklistedVideo) {
|
||||||
const confirmMessage = this.i18n(
|
const confirmMessage = this.i18n(
|
||||||
'Do you really want to remove this video from the blacklist ? It will be available again in the videos list.'
|
'Do you really want to remove this video from the blacklist? It will be available again in the videos list.'
|
||||||
)
|
)
|
||||||
|
|
||||||
const res = await this.confirmService.confirm(confirmMessage, this.i18n('Unblacklist'))
|
const res = await this.confirmService.confirm(confirmMessage, this.i18n('Unblacklist'))
|
||||||
if (res === false) return
|
if (res === false) return
|
||||||
|
|
||||||
this.videoBlacklistService.removeVideoFromBlacklist(entry.videoId).subscribe(
|
this.videoBlacklistService.removeVideoFromBlacklist(entry.video.id).subscribe(
|
||||||
() => {
|
() => {
|
||||||
this.notificationsService.success(
|
this.notificationsService.success(
|
||||||
this.i18n('Success'),
|
this.i18n('Success'),
|
||||||
this.i18n('Video {{name}} removed from the blacklist.', { name: entry.name })
|
this.i18n('Video {{name}} removed from the blacklist.', { name: entry.video.name })
|
||||||
)
|
)
|
||||||
this.loadData()
|
this.loadData()
|
||||||
},
|
},
|
||||||
|
|
|
@ -5,6 +5,7 @@ export * from './login-validators.service'
|
||||||
export * from './reset-password-validators.service'
|
export * from './reset-password-validators.service'
|
||||||
export * from './user-validators.service'
|
export * from './user-validators.service'
|
||||||
export * from './video-abuse-validators.service'
|
export * from './video-abuse-validators.service'
|
||||||
|
export * from './video-blacklist-validators.service'
|
||||||
export * from './video-channel-validators.service'
|
export * from './video-channel-validators.service'
|
||||||
export * from './video-comment-validators.service'
|
export * from './video-comment-validators.service'
|
||||||
export * from './video-validators.service'
|
export * from './video-validators.service'
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
import { I18n } from '@ngx-translate/i18n-polyfill'
|
||||||
|
import { Validators } from '@angular/forms'
|
||||||
|
import { Injectable } from '@angular/core'
|
||||||
|
import { BuildFormValidator } from '@app/shared'
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class VideoBlacklistValidatorsService {
|
||||||
|
readonly VIDEO_BLACKLIST_REASON: BuildFormValidator
|
||||||
|
|
||||||
|
constructor (private i18n: I18n) {
|
||||||
|
this.VIDEO_BLACKLIST_REASON = {
|
||||||
|
VALIDATORS: [ Validators.minLength(2), Validators.maxLength(300) ],
|
||||||
|
MESSAGES: {
|
||||||
|
'minlength': this.i18n('Blacklist reason must be at least 2 characters long.'),
|
||||||
|
'maxlength': this.i18n('Blacklist reason cannot be more than 300 characters long.')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -36,7 +36,7 @@ import {
|
||||||
ReactiveFileComponent,
|
ReactiveFileComponent,
|
||||||
ResetPasswordValidatorsService,
|
ResetPasswordValidatorsService,
|
||||||
UserValidatorsService,
|
UserValidatorsService,
|
||||||
VideoAbuseValidatorsService,
|
VideoAbuseValidatorsService, VideoBlacklistValidatorsService,
|
||||||
VideoChannelValidatorsService,
|
VideoChannelValidatorsService,
|
||||||
VideoCommentValidatorsService,
|
VideoCommentValidatorsService,
|
||||||
VideoValidatorsService
|
VideoValidatorsService
|
||||||
|
@ -133,6 +133,7 @@ import { NgbDropdownModule, NgbModalModule, NgbPopoverModule, NgbTabsetModule, N
|
||||||
MarkdownService,
|
MarkdownService,
|
||||||
VideoChannelService,
|
VideoChannelService,
|
||||||
VideoCaptionService,
|
VideoCaptionService,
|
||||||
|
VideoImportService,
|
||||||
|
|
||||||
FormValidatorService,
|
FormValidatorService,
|
||||||
CustomConfigValidatorsService,
|
CustomConfigValidatorsService,
|
||||||
|
@ -144,7 +145,7 @@ import { NgbDropdownModule, NgbModalModule, NgbPopoverModule, NgbTabsetModule, N
|
||||||
VideoCommentValidatorsService,
|
VideoCommentValidatorsService,
|
||||||
VideoValidatorsService,
|
VideoValidatorsService,
|
||||||
VideoCaptionsValidatorsService,
|
VideoCaptionsValidatorsService,
|
||||||
VideoImportService,
|
VideoBlacklistValidatorsService,
|
||||||
|
|
||||||
I18nPrimengCalendarService,
|
I18nPrimengCalendarService,
|
||||||
ScreenService,
|
ScreenService,
|
||||||
|
|
|
@ -36,8 +36,10 @@ export class VideoBlacklistService {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
blacklistVideo (videoId: number) {
|
blacklistVideo (videoId: number, reason?: string) {
|
||||||
return this.authHttp.post(VideoBlacklistService.BASE_VIDEOS_URL + videoId + '/blacklist', {})
|
const body = reason ? { reason } : {}
|
||||||
|
|
||||||
|
return this.authHttp.post(VideoBlacklistService.BASE_VIDEOS_URL + videoId + '/blacklist', body)
|
||||||
.pipe(
|
.pipe(
|
||||||
map(this.restExtractor.extractDataBool),
|
map(this.restExtractor.extractDataBool),
|
||||||
catchError(res => this.restExtractor.handleError(res))
|
catchError(res => this.restExtractor.handleError(res))
|
||||||
|
|
|
@ -44,7 +44,7 @@ export class VideoDetails extends Video implements VideoDetailsServerModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
isBlackistableBy (user: AuthUser) {
|
isBlackistableBy (user: AuthUser) {
|
||||||
return user && user.hasRight(UserRight.MANAGE_VIDEO_BLACKLIST) === true && this.isLocal === false
|
return user && user.hasRight(UserRight.MANAGE_VIDEO_BLACKLIST) === true
|
||||||
}
|
}
|
||||||
|
|
||||||
isUpdatableBy (user: AuthUser) {
|
isUpdatableBy (user: AuthUser) {
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
<ng-template #modal>
|
||||||
|
<div class="modal-header">
|
||||||
|
<h4 i18n class="modal-title">Blacklist video</h4>
|
||||||
|
<span class="close" aria-label="Close" role="button" (click)="hide()"></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-body">
|
||||||
|
|
||||||
|
<form novalidate [formGroup]="form" (ngSubmit)="blacklist()">
|
||||||
|
<div class="form-group">
|
||||||
|
<textarea i18n-placeholder placeholder="Reason..." formControlName="reason" [ngClass]="{ 'input-error': formErrors['reason'] }">
|
||||||
|
</textarea>
|
||||||
|
<div *ngIf="formErrors.reason" class="form-error">
|
||||||
|
{{ formErrors.reason }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group inputs">
|
||||||
|
<span i18n class="action-button action-button-cancel" (click)="hide()">
|
||||||
|
Cancel
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<input
|
||||||
|
type="submit" i18n-value value="Submit" class="action-button-submit"
|
||||||
|
[disabled]="!form.valid"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
|
@ -0,0 +1,6 @@
|
||||||
|
@import 'variables';
|
||||||
|
@import 'mixins';
|
||||||
|
|
||||||
|
textarea {
|
||||||
|
@include peertube-textarea(100%, 100px);
|
||||||
|
}
|
|
@ -0,0 +1,66 @@
|
||||||
|
import { Component, Input, OnInit, ViewChild } from '@angular/core'
|
||||||
|
import { NotificationsService } from 'angular2-notifications'
|
||||||
|
import { FormReactive, VideoBlacklistService, VideoBlacklistValidatorsService } from '../../../shared/index'
|
||||||
|
import { VideoDetails } from '../../../shared/video/video-details.model'
|
||||||
|
import { I18n } from '@ngx-translate/i18n-polyfill'
|
||||||
|
import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service'
|
||||||
|
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
|
||||||
|
import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref'
|
||||||
|
import { RedirectService } from '@app/core'
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'my-video-blacklist',
|
||||||
|
templateUrl: './video-blacklist.component.html',
|
||||||
|
styleUrls: [ './video-blacklist.component.scss' ]
|
||||||
|
})
|
||||||
|
export class VideoBlacklistComponent extends FormReactive implements OnInit {
|
||||||
|
@Input() video: VideoDetails = null
|
||||||
|
|
||||||
|
@ViewChild('modal') modal: NgbModal
|
||||||
|
|
||||||
|
error: string = null
|
||||||
|
|
||||||
|
private openedModal: NgbModalRef
|
||||||
|
|
||||||
|
constructor (
|
||||||
|
protected formValidatorService: FormValidatorService,
|
||||||
|
private modalService: NgbModal,
|
||||||
|
private videoBlacklistValidatorsService: VideoBlacklistValidatorsService,
|
||||||
|
private videoBlacklistService: VideoBlacklistService,
|
||||||
|
private notificationsService: NotificationsService,
|
||||||
|
private redirectService: RedirectService,
|
||||||
|
private i18n: I18n
|
||||||
|
) {
|
||||||
|
super()
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit () {
|
||||||
|
this.buildForm({
|
||||||
|
reason: this.videoBlacklistValidatorsService.VIDEO_BLACKLIST_REASON
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
show () {
|
||||||
|
this.openedModal = this.modalService.open(this.modal, { keyboard: false })
|
||||||
|
}
|
||||||
|
|
||||||
|
hide () {
|
||||||
|
this.openedModal.close()
|
||||||
|
this.openedModal = null
|
||||||
|
}
|
||||||
|
|
||||||
|
blacklist () {
|
||||||
|
const reason = this.form.value[ 'reason' ] || undefined
|
||||||
|
|
||||||
|
this.videoBlacklistService.blacklistVideo(this.video.id, reason)
|
||||||
|
.subscribe(
|
||||||
|
() => {
|
||||||
|
this.notificationsService.success(this.i18n('Success'), this.i18n('Video blacklisted.'))
|
||||||
|
this.hide()
|
||||||
|
this.redirectService.redirectToHomepage()
|
||||||
|
},
|
||||||
|
|
||||||
|
err => this.notificationsService.error(this.i18n('Error'), err.message)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -90,16 +90,16 @@
|
||||||
<span class="icon icon-alert"></span> <ng-container i18n>Report</ng-container>
|
<span class="icon icon-alert"></span> <ng-container i18n>Report</ng-container>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<a *ngIf="isVideoBlacklistable()" class="dropdown-item" i18n-title title="Blacklist this video" href="#" (click)="blacklistVideo($event)">
|
|
||||||
<span class="icon icon-blacklist"></span> <ng-container i18n>Blacklist</ng-container>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<a *ngIf="isVideoUpdatable()" class="dropdown-item" i18n-title title="Update this video" href="#" [routerLink]="[ '/videos/update', video.uuid ]">
|
<a *ngIf="isVideoUpdatable()" class="dropdown-item" i18n-title title="Update this video" href="#" [routerLink]="[ '/videos/update', video.uuid ]">
|
||||||
<span class="icon icon-edit"></span> <ng-container i18n>Update</ng-container>
|
<span class="icon icon-edit"></span> <ng-container i18n>Update</ng-container>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
<a *ngIf="isVideoBlacklistable()" class="dropdown-item" i18n-title title="Blacklist this video" href="#" (click)="showBlacklistModal($event)">
|
||||||
|
<span class="icon icon-blacklist"></span> <ng-container i18n>Blacklist</ng-container>
|
||||||
|
</a>
|
||||||
|
|
||||||
<a *ngIf="isVideoRemovable()" class="dropdown-item" i18n-title title="Delete this video" href="#" (click)="removeVideo($event)">
|
<a *ngIf="isVideoRemovable()" class="dropdown-item" i18n-title title="Delete this video" href="#" (click)="removeVideo($event)">
|
||||||
<span class="icon icon-blacklist"></span> <ng-container i18n>Delete</ng-container>
|
<span class="icon icon-delete"></span> <ng-container i18n>Delete</ng-container>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -205,4 +205,5 @@
|
||||||
<my-video-share #videoShareModal [video]="video"></my-video-share>
|
<my-video-share #videoShareModal [video]="video"></my-video-share>
|
||||||
<my-video-download #videoDownloadModal [video]="video"></my-video-download>
|
<my-video-download #videoDownloadModal [video]="video"></my-video-download>
|
||||||
<my-video-report #videoReportModal [video]="video"></my-video-report>
|
<my-video-report #videoReportModal [video]="video"></my-video-report>
|
||||||
|
<my-video-blacklist #videoBlacklistModal [video]="video"></my-video-blacklist>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|
|
@ -258,6 +258,10 @@
|
||||||
&.icon-blacklist {
|
&.icon-blacklist {
|
||||||
background-image: url('../../../assets/images/video/blacklist.svg');
|
background-image: url('../../../assets/images/video/blacklist.svg');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.icon-delete {
|
||||||
|
background-image: url('../../../assets/images/global/delete-black.svg');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@ import { MarkdownService } from '../shared'
|
||||||
import { VideoDownloadComponent } from './modal/video-download.component'
|
import { VideoDownloadComponent } from './modal/video-download.component'
|
||||||
import { VideoReportComponent } from './modal/video-report.component'
|
import { VideoReportComponent } from './modal/video-report.component'
|
||||||
import { VideoShareComponent } from './modal/video-share.component'
|
import { VideoShareComponent } from './modal/video-share.component'
|
||||||
|
import { VideoBlacklistComponent } from './modal/video-blacklist.component'
|
||||||
import { addContextMenu, getVideojsOptions, loadLocale } from '../../../assets/player/peertube-player'
|
import { addContextMenu, getVideojsOptions, loadLocale } from '../../../assets/player/peertube-player'
|
||||||
import { ServerService } from '@app/core'
|
import { ServerService } from '@app/core'
|
||||||
import { I18n } from '@ngx-translate/i18n-polyfill'
|
import { I18n } from '@ngx-translate/i18n-polyfill'
|
||||||
|
@ -41,6 +42,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
|
||||||
@ViewChild('videoShareModal') videoShareModal: VideoShareComponent
|
@ViewChild('videoShareModal') videoShareModal: VideoShareComponent
|
||||||
@ViewChild('videoReportModal') videoReportModal: VideoReportComponent
|
@ViewChild('videoReportModal') videoReportModal: VideoReportComponent
|
||||||
@ViewChild('videoSupportModal') videoSupportModal: VideoSupportComponent
|
@ViewChild('videoSupportModal') videoSupportModal: VideoSupportComponent
|
||||||
|
@ViewChild('videoBlacklistModal') videoBlacklistModal: VideoBlacklistComponent
|
||||||
|
|
||||||
otherVideosDisplayed: Video[] = []
|
otherVideosDisplayed: Video[] = []
|
||||||
|
|
||||||
|
@ -156,26 +158,6 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async blacklistVideo (event: Event) {
|
|
||||||
event.preventDefault()
|
|
||||||
|
|
||||||
const res = await this.confirmService.confirm(this.i18n('Do you really want to blacklist this video?'), this.i18n('Blacklist'))
|
|
||||||
if (res === false) return
|
|
||||||
|
|
||||||
this.videoBlacklistService.blacklistVideo(this.video.id)
|
|
||||||
.subscribe(
|
|
||||||
() => {
|
|
||||||
this.notificationsService.success(
|
|
||||||
this.i18n('Success'),
|
|
||||||
this.i18n('Video {{videoName}} had been blacklisted.', { videoName: this.video.name })
|
|
||||||
)
|
|
||||||
this.redirectService.redirectToHomepage()
|
|
||||||
},
|
|
||||||
|
|
||||||
error => this.notificationsService.error(this.i18n('Error'), error.message)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
showMoreDescription () {
|
showMoreDescription () {
|
||||||
if (this.completeVideoDescription === undefined) {
|
if (this.completeVideoDescription === undefined) {
|
||||||
return this.loadCompleteDescription()
|
return this.loadCompleteDescription()
|
||||||
|
@ -230,6 +212,11 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
|
||||||
this.videoDownloadModal.show()
|
this.videoDownloadModal.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
showBlacklistModal (event: Event) {
|
||||||
|
event.preventDefault()
|
||||||
|
this.videoBlacklistModal.show()
|
||||||
|
}
|
||||||
|
|
||||||
isUserLoggedIn () {
|
isUserLoggedIn () {
|
||||||
return this.authService.isLoggedIn()
|
return this.authService.isLoggedIn()
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,7 @@ import { VideoWatchRoutingModule } from './video-watch-routing.module'
|
||||||
import { VideoWatchComponent } from './video-watch.component'
|
import { VideoWatchComponent } from './video-watch.component'
|
||||||
import { NgxQRCodeModule } from 'ngx-qrcode2'
|
import { NgxQRCodeModule } from 'ngx-qrcode2'
|
||||||
import { NgbTooltipModule } from '@ng-bootstrap/ng-bootstrap'
|
import { NgbTooltipModule } from '@ng-bootstrap/ng-bootstrap'
|
||||||
|
import { VideoBlacklistComponent } from '@app/videos/+video-watch/modal/video-blacklist.component'
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
|
@ -31,6 +32,7 @@ import { NgbTooltipModule } from '@ng-bootstrap/ng-bootstrap'
|
||||||
VideoDownloadComponent,
|
VideoDownloadComponent,
|
||||||
VideoShareComponent,
|
VideoShareComponent,
|
||||||
VideoReportComponent,
|
VideoReportComponent,
|
||||||
|
VideoBlacklistComponent,
|
||||||
VideoSupportComponent,
|
VideoSupportComponent,
|
||||||
VideoCommentsComponent,
|
VideoCommentsComponent,
|
||||||
VideoCommentAddComponent,
|
VideoCommentAddComponent,
|
||||||
|
|
14
client/src/assets/images/global/delete-black.svg
Normal file
14
client/src/assets/images/global/delete-black.svg
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<defs></defs>
|
||||||
|
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||||
|
<g id="Artboard-4" transform="translate(-224.000000, -159.000000)">
|
||||||
|
<g id="25" transform="translate(224.000000, 159.000000)">
|
||||||
|
<path d="M5,7 L5,20.0081158 C5,21.1082031 5.89706013,22 7.00585866,22 L16.9941413,22 C18.1019465,22 19,21.1066027 19,20.0081158 L19,7" id="Path-296" stroke="#000" stroke-width="2"></path>
|
||||||
|
<rect id="Rectangle-424" fill="#000" x="2" y="4" width="20" height="2" rx="1"></rect>
|
||||||
|
<path d="M9,10.9970301 C9,10.4463856 9.44386482,10 10,10 C10.5522847,10 11,10.4530363 11,10.9970301 L11,17.0029699 C11,17.5536144 10.5561352,18 10,18 C9.44771525,18 9,17.5469637 9,17.0029699 L9,10.9970301 Z M13,10.9970301 C13,10.4463856 13.4438648,10 14,10 C14.5522847,10 15,10.4530363 15,10.9970301 L15,17.0029699 C15,17.5536144 14.5561352,18 14,18 C13.4477153,18 13,17.5469637 13,17.0029699 L13,10.9970301 Z" id="Combined-Shape" fill="#000"></path>
|
||||||
|
<path d="M9,5 L9,2.99895656 C9,2.44724809 9.45097518,2 9.99077797,2 L14.009222,2 C14.5564136,2 15,2.44266033 15,2.99895656 L15,5" id="Path-33" stroke="#000" stroke-width="2" stroke-linejoin="round"></path>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.4 KiB |
|
@ -201,14 +201,14 @@ async function getUserVideos (req: express.Request, res: express.Response, next:
|
||||||
user.Account.id,
|
user.Account.id,
|
||||||
req.query.start as number,
|
req.query.start as number,
|
||||||
req.query.count as number,
|
req.query.count as number,
|
||||||
req.query.sort as VideoSortField,
|
req.query.sort as VideoSortField
|
||||||
false // Display my NSFW videos
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const additionalAttributes = {
|
const additionalAttributes = {
|
||||||
waitTranscoding: true,
|
waitTranscoding: true,
|
||||||
state: true,
|
state: true,
|
||||||
scheduledUpdate: true
|
scheduledUpdate: true,
|
||||||
|
blacklistInfo: true
|
||||||
}
|
}
|
||||||
return res.json(getFormattedObjects(resultList.data, resultList.total, { additionalAttributes }))
|
return res.json(getFormattedObjects(resultList.data, resultList.total, { additionalAttributes }))
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,21 @@
|
||||||
import * as express from 'express'
|
import * as express from 'express'
|
||||||
import { BlacklistedVideo, UserRight } from '../../../../shared'
|
import { BlacklistedVideo, UserRight, VideoBlacklistCreate } from '../../../../shared'
|
||||||
import { logger } from '../../../helpers/logger'
|
import { logger } from '../../../helpers/logger'
|
||||||
import { getFormattedObjects } from '../../../helpers/utils'
|
import { getFormattedObjects } from '../../../helpers/utils'
|
||||||
import {
|
import {
|
||||||
asyncMiddleware, authenticate, blacklistSortValidator, ensureUserHasRight, paginationValidator, setBlacklistSort, setDefaultPagination,
|
asyncMiddleware,
|
||||||
videosBlacklistAddValidator, videosBlacklistRemoveValidator
|
authenticate,
|
||||||
|
blacklistSortValidator,
|
||||||
|
ensureUserHasRight,
|
||||||
|
paginationValidator,
|
||||||
|
setBlacklistSort,
|
||||||
|
setDefaultPagination,
|
||||||
|
videosBlacklistAddValidator,
|
||||||
|
videosBlacklistRemoveValidator,
|
||||||
|
videosBlacklistUpdateValidator
|
||||||
} from '../../../middlewares'
|
} from '../../../middlewares'
|
||||||
import { VideoBlacklistModel } from '../../../models/video/video-blacklist'
|
import { VideoBlacklistModel } from '../../../models/video/video-blacklist'
|
||||||
|
import { sequelizeTypescript } from '../../../initializers'
|
||||||
|
|
||||||
const blacklistRouter = express.Router()
|
const blacklistRouter = express.Router()
|
||||||
|
|
||||||
|
@ -27,6 +36,13 @@ blacklistRouter.get('/blacklist',
|
||||||
asyncMiddleware(listBlacklist)
|
asyncMiddleware(listBlacklist)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
blacklistRouter.put('/:videoId/blacklist',
|
||||||
|
authenticate,
|
||||||
|
ensureUserHasRight(UserRight.MANAGE_VIDEO_BLACKLIST),
|
||||||
|
asyncMiddleware(videosBlacklistUpdateValidator),
|
||||||
|
asyncMiddleware(updateVideoBlacklistController)
|
||||||
|
)
|
||||||
|
|
||||||
blacklistRouter.delete('/:videoId/blacklist',
|
blacklistRouter.delete('/:videoId/blacklist',
|
||||||
authenticate,
|
authenticate,
|
||||||
ensureUserHasRight(UserRight.MANAGE_VIDEO_BLACKLIST),
|
ensureUserHasRight(UserRight.MANAGE_VIDEO_BLACKLIST),
|
||||||
|
@ -42,17 +58,32 @@ export {
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
async function addVideoToBlacklist (req: express.Request, res: express.Response, next: express.NextFunction) {
|
async function addVideoToBlacklist (req: express.Request, res: express.Response) {
|
||||||
const videoInstance = res.locals.video
|
const videoInstance = res.locals.video
|
||||||
|
const body: VideoBlacklistCreate = req.body
|
||||||
|
|
||||||
const toCreate = {
|
const toCreate = {
|
||||||
videoId: videoInstance.id
|
videoId: videoInstance.id,
|
||||||
|
reason: body.reason
|
||||||
}
|
}
|
||||||
|
|
||||||
await VideoBlacklistModel.create(toCreate)
|
await VideoBlacklistModel.create(toCreate)
|
||||||
return res.type('json').status(204).end()
|
return res.type('json').status(204).end()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function updateVideoBlacklistController (req: express.Request, res: express.Response) {
|
||||||
|
const videoBlacklist = res.locals.videoBlacklist as VideoBlacklistModel
|
||||||
|
logger.info(videoBlacklist)
|
||||||
|
|
||||||
|
if (req.body.reason !== undefined) videoBlacklist.reason = req.body.reason
|
||||||
|
|
||||||
|
await sequelizeTypescript.transaction(t => {
|
||||||
|
return videoBlacklist.save({ transaction: t })
|
||||||
|
})
|
||||||
|
|
||||||
|
return res.type('json').status(204).end()
|
||||||
|
}
|
||||||
|
|
||||||
async function listBlacklist (req: express.Request, res: express.Response, next: express.NextFunction) {
|
async function listBlacklist (req: express.Request, res: express.Response, next: express.NextFunction) {
|
||||||
const resultList = await VideoBlacklistModel.listForApi(req.query.start, req.query.count, req.query.sort)
|
const resultList = await VideoBlacklistModel.listForApi(req.query.start, req.query.count, req.query.sort)
|
||||||
|
|
||||||
|
@ -60,16 +91,13 @@ async function listBlacklist (req: express.Request, res: express.Response, next:
|
||||||
}
|
}
|
||||||
|
|
||||||
async function removeVideoFromBlacklistController (req: express.Request, res: express.Response, next: express.NextFunction) {
|
async function removeVideoFromBlacklistController (req: express.Request, res: express.Response, next: express.NextFunction) {
|
||||||
const blacklistedVideo = res.locals.blacklistedVideo as VideoBlacklistModel
|
const videoBlacklist = res.locals.videoBlacklist as VideoBlacklistModel
|
||||||
|
|
||||||
try {
|
await sequelizeTypescript.transaction(t => {
|
||||||
await blacklistedVideo.destroy()
|
return videoBlacklist.destroy({ transaction: t })
|
||||||
|
})
|
||||||
|
|
||||||
logger.info('Video %s removed from blacklist.', res.locals.video.uuid)
|
logger.info('Video %s removed from blacklist.', res.locals.video.uuid)
|
||||||
|
|
||||||
return res.sendStatus(204)
|
return res.type('json').status(204).end()
|
||||||
} catch (err) {
|
|
||||||
logger.error('Some error while removing video %s from blacklist.', res.locals.video.uuid, { err })
|
|
||||||
throw err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
32
server/helpers/custom-validators/video-blacklist.ts
Normal file
32
server/helpers/custom-validators/video-blacklist.ts
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
import { Response } from 'express'
|
||||||
|
import * as validator from 'validator'
|
||||||
|
import { CONSTRAINTS_FIELDS } from '../../initializers'
|
||||||
|
import { VideoBlacklistModel } from '../../models/video/video-blacklist'
|
||||||
|
|
||||||
|
const VIDEO_BLACKLIST_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.VIDEO_BLACKLIST
|
||||||
|
|
||||||
|
function isVideoBlacklistReasonValid (value: string) {
|
||||||
|
return value === null || validator.isLength(value, VIDEO_BLACKLIST_CONSTRAINTS_FIELDS.REASON)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function isVideoBlacklistExist (videoId: number, res: Response) {
|
||||||
|
const videoBlacklist = await VideoBlacklistModel.loadByVideoId(videoId)
|
||||||
|
|
||||||
|
if (videoBlacklist === null) {
|
||||||
|
res.status(404)
|
||||||
|
.json({ error: 'Blacklisted video not found' })
|
||||||
|
.end()
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
res.locals.videoBlacklist = videoBlacklist
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
export {
|
||||||
|
isVideoBlacklistReasonValid,
|
||||||
|
isVideoBlacklistExist
|
||||||
|
}
|
|
@ -15,7 +15,7 @@ let config: IConfig = require('config')
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
const LAST_MIGRATION_VERSION = 250
|
const LAST_MIGRATION_VERSION = 255
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
@ -34,7 +34,7 @@ const SORTABLE_COLUMNS = {
|
||||||
USERS: [ 'id', 'username', 'createdAt' ],
|
USERS: [ 'id', 'username', 'createdAt' ],
|
||||||
ACCOUNTS: [ 'createdAt' ],
|
ACCOUNTS: [ 'createdAt' ],
|
||||||
JOBS: [ 'createdAt' ],
|
JOBS: [ 'createdAt' ],
|
||||||
VIDEO_ABUSES: [ 'id', 'createdAt' ],
|
VIDEO_ABUSES: [ 'id', 'createdAt', 'state' ],
|
||||||
VIDEO_CHANNELS: [ 'id', 'name', 'updatedAt', 'createdAt' ],
|
VIDEO_CHANNELS: [ 'id', 'name', 'updatedAt', 'createdAt' ],
|
||||||
VIDEOS: [ 'name', 'duration', 'createdAt', 'publishedAt', 'views', 'likes' ],
|
VIDEOS: [ 'name', 'duration', 'createdAt', 'publishedAt', 'views', 'likes' ],
|
||||||
VIDEO_IMPORTS: [ 'createdAt' ],
|
VIDEO_IMPORTS: [ 'createdAt' ],
|
||||||
|
@ -261,6 +261,9 @@ const CONSTRAINTS_FIELDS = {
|
||||||
REASON: { min: 2, max: 300 }, // Length
|
REASON: { min: 2, max: 300 }, // Length
|
||||||
MODERATION_COMMENT: { min: 2, max: 300 } // Length
|
MODERATION_COMMENT: { min: 2, max: 300 } // Length
|
||||||
},
|
},
|
||||||
|
VIDEO_BLACKLIST: {
|
||||||
|
REASON: { min: 2, max: 300 } // Length
|
||||||
|
},
|
||||||
VIDEO_CHANNELS: {
|
VIDEO_CHANNELS: {
|
||||||
NAME: { min: 3, max: 120 }, // Length
|
NAME: { min: 3, max: 120 }, // Length
|
||||||
DESCRIPTION: { min: 3, max: 500 }, // Length
|
DESCRIPTION: { min: 3, max: 500 }, // Length
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
import * as Sequelize from 'sequelize'
|
||||||
|
import { CONSTRAINTS_FIELDS } from '../constants'
|
||||||
|
import { VideoAbuseState } from '../../../shared/models/videos'
|
||||||
|
|
||||||
|
async function up (utils: {
|
||||||
|
transaction: Sequelize.Transaction
|
||||||
|
queryInterface: Sequelize.QueryInterface
|
||||||
|
sequelize: Sequelize.Sequelize
|
||||||
|
}): Promise<any> {
|
||||||
|
|
||||||
|
{
|
||||||
|
const data = {
|
||||||
|
type: Sequelize.STRING(CONSTRAINTS_FIELDS.VIDEO_BLACKLIST.REASON.max),
|
||||||
|
allowNull: true,
|
||||||
|
defaultValue: null
|
||||||
|
}
|
||||||
|
await utils.queryInterface.addColumn('videoBlacklist', 'reason', data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function down (options) {
|
||||||
|
throw new Error('Not implemented.')
|
||||||
|
}
|
||||||
|
|
||||||
|
export { up, down }
|
|
@ -108,6 +108,55 @@ class Emailer {
|
||||||
return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
|
return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async addVideoBlacklistReportJob (videoId: number, reason?: string) {
|
||||||
|
const video = await VideoModel.loadAndPopulateAccountAndServerAndTags(videoId)
|
||||||
|
if (!video) throw new Error('Unknown Video id during Blacklist report.')
|
||||||
|
// It's not our user
|
||||||
|
if (video.remote === true) return
|
||||||
|
|
||||||
|
const user = await UserModel.loadById(video.VideoChannel.Account.userId)
|
||||||
|
|
||||||
|
const reasonString = reason ? ` for the following reason: ${reason}` : ''
|
||||||
|
const blockedString = `Your video ${video.name} on ${CONFIG.WEBSERVER.HOST} has been blacklisted${reasonString}.`
|
||||||
|
|
||||||
|
const text = 'Hi,\n\n' +
|
||||||
|
blockedString +
|
||||||
|
'\n\n' +
|
||||||
|
'Cheers,\n' +
|
||||||
|
`PeerTube.`
|
||||||
|
|
||||||
|
const to = user.email
|
||||||
|
const emailPayload: EmailPayload = {
|
||||||
|
to: [ to ],
|
||||||
|
subject: `[PeerTube] Video ${video.name} blacklisted`,
|
||||||
|
text
|
||||||
|
}
|
||||||
|
|
||||||
|
return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
|
||||||
|
}
|
||||||
|
|
||||||
|
async addVideoUnblacklistReportJob (videoId: number) {
|
||||||
|
const video = await VideoModel.loadAndPopulateAccountAndServerAndTags(videoId)
|
||||||
|
if (!video) throw new Error('Unknown Video id during Blacklist report.')
|
||||||
|
|
||||||
|
const user = await UserModel.loadById(video.VideoChannel.Account.userId)
|
||||||
|
|
||||||
|
const text = 'Hi,\n\n' +
|
||||||
|
`Your video ${video.name} on ${CONFIG.WEBSERVER.HOST} has been unblacklisted.` +
|
||||||
|
'\n\n' +
|
||||||
|
'Cheers,\n' +
|
||||||
|
`PeerTube.`
|
||||||
|
|
||||||
|
const to = user.email
|
||||||
|
const emailPayload: EmailPayload = {
|
||||||
|
to: [ to ],
|
||||||
|
subject: `[PeerTube] Video ${video.name} unblacklisted`,
|
||||||
|
text
|
||||||
|
}
|
||||||
|
|
||||||
|
return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
|
||||||
|
}
|
||||||
|
|
||||||
addUserBlockJob (user: UserModel, blocked: boolean, reason?: string) {
|
addUserBlockJob (user: UserModel, blocked: boolean, reason?: string) {
|
||||||
const reasonString = reason ? ` for the following reason: ${reason}` : ''
|
const reasonString = reason ? ` for the following reason: ${reason}` : ''
|
||||||
const blockedWord = blocked ? 'blocked' : 'unblocked'
|
const blockedWord = blocked ? 'blocked' : 'unblocked'
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
import * as express from 'express'
|
import * as express from 'express'
|
||||||
import { param } from 'express-validator/check'
|
import { body, param } from 'express-validator/check'
|
||||||
import { isIdOrUUIDValid } from '../../helpers/custom-validators/misc'
|
import { isIdOrUUIDValid } from '../../helpers/custom-validators/misc'
|
||||||
import { isVideoExist } from '../../helpers/custom-validators/videos'
|
import { isVideoExist } from '../../helpers/custom-validators/videos'
|
||||||
import { logger } from '../../helpers/logger'
|
import { logger } from '../../helpers/logger'
|
||||||
import { VideoModel } from '../../models/video/video'
|
|
||||||
import { VideoBlacklistModel } from '../../models/video/video-blacklist'
|
|
||||||
import { areValidationErrors } from './utils'
|
import { areValidationErrors } from './utils'
|
||||||
|
import { isVideoBlacklistExist, isVideoBlacklistReasonValid } from '../../helpers/custom-validators/video-blacklist'
|
||||||
|
|
||||||
const videosBlacklistRemoveValidator = [
|
const videosBlacklistRemoveValidator = [
|
||||||
param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'),
|
param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'),
|
||||||
|
@ -15,7 +14,7 @@ const videosBlacklistRemoveValidator = [
|
||||||
|
|
||||||
if (areValidationErrors(req, res)) return
|
if (areValidationErrors(req, res)) return
|
||||||
if (!await isVideoExist(req.params.videoId, res)) return
|
if (!await isVideoExist(req.params.videoId, res)) return
|
||||||
if (!await checkVideoIsBlacklisted(res.locals.video, res)) return
|
if (!await isVideoBlacklistExist(res.locals.video.id, res)) return
|
||||||
|
|
||||||
return next()
|
return next()
|
||||||
}
|
}
|
||||||
|
@ -23,13 +22,32 @@ const videosBlacklistRemoveValidator = [
|
||||||
|
|
||||||
const videosBlacklistAddValidator = [
|
const videosBlacklistAddValidator = [
|
||||||
param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'),
|
param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'),
|
||||||
|
body('reason')
|
||||||
|
.optional()
|
||||||
|
.custom(isVideoBlacklistReasonValid).withMessage('Should have a valid reason'),
|
||||||
|
|
||||||
async (req: express.Request, res: express.Response, next: express.NextFunction) => {
|
async (req: express.Request, res: express.Response, next: express.NextFunction) => {
|
||||||
logger.debug('Checking videosBlacklist parameters', { parameters: req.params })
|
logger.debug('Checking videosBlacklistAdd parameters', { parameters: req.params })
|
||||||
|
|
||||||
if (areValidationErrors(req, res)) return
|
if (areValidationErrors(req, res)) return
|
||||||
if (!await isVideoExist(req.params.videoId, res)) return
|
if (!await isVideoExist(req.params.videoId, res)) return
|
||||||
if (!checkVideoIsBlacklistable(res.locals.video, res)) return
|
|
||||||
|
return next()
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
const videosBlacklistUpdateValidator = [
|
||||||
|
param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'),
|
||||||
|
body('reason')
|
||||||
|
.optional()
|
||||||
|
.custom(isVideoBlacklistReasonValid).withMessage('Should have a valid reason'),
|
||||||
|
|
||||||
|
async (req: express.Request, res: express.Response, next: express.NextFunction) => {
|
||||||
|
logger.debug('Checking videosBlacklistUpdate parameters', { parameters: req.params })
|
||||||
|
|
||||||
|
if (areValidationErrors(req, res)) return
|
||||||
|
if (!await isVideoExist(req.params.videoId, res)) return
|
||||||
|
if (!await isVideoBlacklistExist(res.locals.video.id, res)) return
|
||||||
|
|
||||||
return next()
|
return next()
|
||||||
}
|
}
|
||||||
|
@ -39,31 +57,6 @@ const videosBlacklistAddValidator = [
|
||||||
|
|
||||||
export {
|
export {
|
||||||
videosBlacklistAddValidator,
|
videosBlacklistAddValidator,
|
||||||
videosBlacklistRemoveValidator
|
videosBlacklistRemoveValidator,
|
||||||
}
|
videosBlacklistUpdateValidator
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
function checkVideoIsBlacklistable (video: VideoModel, res: express.Response) {
|
|
||||||
if (video.isOwned() === true) {
|
|
||||||
res.status(403)
|
|
||||||
.json({ error: 'Cannot blacklist a local video' })
|
|
||||||
.end()
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
async function checkVideoIsBlacklisted (video: VideoModel, res: express.Response) {
|
|
||||||
const blacklistedVideo = await VideoBlacklistModel.loadByVideoId(video.id)
|
|
||||||
if (!blacklistedVideo) {
|
|
||||||
res.status(404)
|
|
||||||
.send('Blacklisted video not found')
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
res.locals.blacklistedVideo = blacklistedVideo
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,23 @@
|
||||||
import { BelongsTo, Column, CreatedAt, ForeignKey, Model, Table, UpdatedAt } from 'sequelize-typescript'
|
import {
|
||||||
|
AfterCreate,
|
||||||
|
AfterDestroy,
|
||||||
|
AllowNull,
|
||||||
|
BelongsTo,
|
||||||
|
Column,
|
||||||
|
CreatedAt, DataType,
|
||||||
|
ForeignKey,
|
||||||
|
Is,
|
||||||
|
Model,
|
||||||
|
Table,
|
||||||
|
UpdatedAt
|
||||||
|
} from 'sequelize-typescript'
|
||||||
import { SortType } from '../../helpers/utils'
|
import { SortType } from '../../helpers/utils'
|
||||||
import { getSortOnModel } from '../utils'
|
import { getSortOnModel, throwIfNotValid } from '../utils'
|
||||||
import { VideoModel } from './video'
|
import { VideoModel } from './video'
|
||||||
|
import { isVideoBlacklistReasonValid } from '../../helpers/custom-validators/video-blacklist'
|
||||||
|
import { Emailer } from '../../lib/emailer'
|
||||||
|
import { BlacklistedVideo } from '../../../shared/models/videos'
|
||||||
|
import { CONSTRAINTS_FIELDS } from '../../initializers'
|
||||||
|
|
||||||
@Table({
|
@Table({
|
||||||
tableName: 'videoBlacklist',
|
tableName: 'videoBlacklist',
|
||||||
|
@ -14,6 +30,11 @@ import { VideoModel } from './video'
|
||||||
})
|
})
|
||||||
export class VideoBlacklistModel extends Model<VideoBlacklistModel> {
|
export class VideoBlacklistModel extends Model<VideoBlacklistModel> {
|
||||||
|
|
||||||
|
@AllowNull(true)
|
||||||
|
@Is('VideoBlacklistReason', value => throwIfNotValid(value, isVideoBlacklistReasonValid, 'reason'))
|
||||||
|
@Column(DataType.STRING(CONSTRAINTS_FIELDS.VIDEO_BLACKLIST.REASON.max))
|
||||||
|
reason: string
|
||||||
|
|
||||||
@CreatedAt
|
@CreatedAt
|
||||||
createdAt: Date
|
createdAt: Date
|
||||||
|
|
||||||
|
@ -32,6 +53,16 @@ export class VideoBlacklistModel extends Model<VideoBlacklistModel> {
|
||||||
})
|
})
|
||||||
Video: VideoModel
|
Video: VideoModel
|
||||||
|
|
||||||
|
@AfterCreate
|
||||||
|
static sendBlacklistEmailNotification (instance: VideoBlacklistModel) {
|
||||||
|
return Emailer.Instance.addVideoBlacklistReportJob(instance.videoId, instance.reason)
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterDestroy
|
||||||
|
static sendUnblacklistEmailNotification (instance: VideoBlacklistModel) {
|
||||||
|
return Emailer.Instance.addVideoUnblacklistReportJob(instance.videoId)
|
||||||
|
}
|
||||||
|
|
||||||
static listForApi (start: number, count: number, sort: SortType) {
|
static listForApi (start: number, count: number, sort: SortType) {
|
||||||
const query = {
|
const query = {
|
||||||
offset: start,
|
offset: start,
|
||||||
|
@ -59,22 +90,26 @@ export class VideoBlacklistModel extends Model<VideoBlacklistModel> {
|
||||||
return VideoBlacklistModel.findOne(query)
|
return VideoBlacklistModel.findOne(query)
|
||||||
}
|
}
|
||||||
|
|
||||||
toFormattedJSON () {
|
toFormattedJSON (): BlacklistedVideo {
|
||||||
const video = this.Video
|
const video = this.Video
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: this.id,
|
id: this.id,
|
||||||
videoId: this.videoId,
|
|
||||||
createdAt: this.createdAt,
|
createdAt: this.createdAt,
|
||||||
updatedAt: this.updatedAt,
|
updatedAt: this.updatedAt,
|
||||||
name: video.name,
|
reason: this.reason,
|
||||||
uuid: video.uuid,
|
|
||||||
description: video.description,
|
video: {
|
||||||
duration: video.duration,
|
id: video.id,
|
||||||
views: video.views,
|
name: video.name,
|
||||||
likes: video.likes,
|
uuid: video.uuid,
|
||||||
dislikes: video.dislikes,
|
description: video.description,
|
||||||
nsfw: video.nsfw
|
duration: video.duration,
|
||||||
|
views: video.views,
|
||||||
|
likes: video.likes,
|
||||||
|
dislikes: video.dislikes,
|
||||||
|
nsfw: video.nsfw
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -93,6 +93,7 @@ import { VideoShareModel } from './video-share'
|
||||||
import { VideoTagModel } from './video-tag'
|
import { VideoTagModel } from './video-tag'
|
||||||
import { ScheduleVideoUpdateModel } from './schedule-video-update'
|
import { ScheduleVideoUpdateModel } from './schedule-video-update'
|
||||||
import { VideoCaptionModel } from './video-caption'
|
import { VideoCaptionModel } from './video-caption'
|
||||||
|
import { VideoBlacklistModel } from './video-blacklist'
|
||||||
|
|
||||||
// FIXME: Define indexes here because there is an issue with TS and Sequelize.literal when called directly in the annotation
|
// FIXME: Define indexes here because there is an issue with TS and Sequelize.literal when called directly in the annotation
|
||||||
const indexes: Sequelize.DefineIndexesOptions[] = [
|
const indexes: Sequelize.DefineIndexesOptions[] = [
|
||||||
|
@ -581,6 +582,15 @@ export class VideoModel extends Model<VideoModel> {
|
||||||
})
|
})
|
||||||
ScheduleVideoUpdate: ScheduleVideoUpdateModel
|
ScheduleVideoUpdate: ScheduleVideoUpdateModel
|
||||||
|
|
||||||
|
@HasOne(() => VideoBlacklistModel, {
|
||||||
|
foreignKey: {
|
||||||
|
name: 'videoId',
|
||||||
|
allowNull: false
|
||||||
|
},
|
||||||
|
onDelete: 'cascade'
|
||||||
|
})
|
||||||
|
VideoBlacklist: VideoBlacklistModel
|
||||||
|
|
||||||
@HasMany(() => VideoCaptionModel, {
|
@HasMany(() => VideoCaptionModel, {
|
||||||
foreignKey: {
|
foreignKey: {
|
||||||
name: 'videoId',
|
name: 'videoId',
|
||||||
|
@ -755,7 +765,7 @@ export class VideoModel extends Model<VideoModel> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
static listUserVideosForApi (accountId: number, start: number, count: number, sort: string, hideNSFW: boolean, withFiles = false) {
|
static listUserVideosForApi (accountId: number, start: number, count: number, sort: string, withFiles = false) {
|
||||||
const query: IFindOptions<VideoModel> = {
|
const query: IFindOptions<VideoModel> = {
|
||||||
offset: start,
|
offset: start,
|
||||||
limit: count,
|
limit: count,
|
||||||
|
@ -777,6 +787,10 @@ export class VideoModel extends Model<VideoModel> {
|
||||||
{
|
{
|
||||||
model: ScheduleVideoUpdateModel,
|
model: ScheduleVideoUpdateModel,
|
||||||
required: false
|
required: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
model: VideoBlacklistModel,
|
||||||
|
required: false
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -788,12 +802,6 @@ export class VideoModel extends Model<VideoModel> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hideNSFW === true) {
|
|
||||||
query.where = {
|
|
||||||
nsfw: false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return VideoModel.findAndCountAll(query).then(({ rows, count }) => {
|
return VideoModel.findAndCountAll(query).then(({ rows, count }) => {
|
||||||
return {
|
return {
|
||||||
data: rows,
|
data: rows,
|
||||||
|
@ -1177,7 +1185,8 @@ export class VideoModel extends Model<VideoModel> {
|
||||||
additionalAttributes: {
|
additionalAttributes: {
|
||||||
state?: boolean,
|
state?: boolean,
|
||||||
waitTranscoding?: boolean,
|
waitTranscoding?: boolean,
|
||||||
scheduledUpdate?: boolean
|
scheduledUpdate?: boolean,
|
||||||
|
blacklistInfo?: boolean
|
||||||
}
|
}
|
||||||
}): Video {
|
}): Video {
|
||||||
const formattedAccount = this.VideoChannel.Account.toFormattedJSON()
|
const formattedAccount = this.VideoChannel.Account.toFormattedJSON()
|
||||||
|
@ -1254,6 +1263,11 @@ export class VideoModel extends Model<VideoModel> {
|
||||||
privacy: this.ScheduleVideoUpdate.privacy || undefined
|
privacy: this.ScheduleVideoUpdate.privacy || undefined
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (options.additionalAttributes.blacklistInfo === true) {
|
||||||
|
videoObject.blacklisted = !!this.VideoBlacklist
|
||||||
|
videoObject.blacklistedReason = this.VideoBlacklist ? this.VideoBlacklist.reason : null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return videoObject
|
return videoObject
|
||||||
|
|
|
@ -3,13 +3,24 @@
|
||||||
import 'mocha'
|
import 'mocha'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
createUser, flushTests, getBlacklistedVideosList, killallServers, makePostBodyRequest, removeVideoFromBlacklist, runServer,
|
createUser,
|
||||||
ServerInfo, setAccessTokensToServers, uploadVideo, userLogin
|
flushTests,
|
||||||
|
getBlacklistedVideosList,
|
||||||
|
killallServers,
|
||||||
|
makePostBodyRequest,
|
||||||
|
makePutBodyRequest,
|
||||||
|
removeVideoFromBlacklist,
|
||||||
|
runServer,
|
||||||
|
ServerInfo,
|
||||||
|
setAccessTokensToServers,
|
||||||
|
uploadVideo,
|
||||||
|
userLogin
|
||||||
} from '../../utils'
|
} from '../../utils'
|
||||||
import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '../../utils/requests/check-api-params'
|
import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '../../utils/requests/check-api-params'
|
||||||
|
|
||||||
describe('Test video blacklist API validators', function () {
|
describe('Test video blacklist API validators', function () {
|
||||||
let server: ServerInfo
|
let server: ServerInfo
|
||||||
|
let notBlacklistedVideoId: number
|
||||||
let userAccessToken = ''
|
let userAccessToken = ''
|
||||||
|
|
||||||
// ---------------------------------------------------------------
|
// ---------------------------------------------------------------
|
||||||
|
@ -28,8 +39,15 @@ describe('Test video blacklist API validators', function () {
|
||||||
await createUser(server.url, server.accessToken, username, password)
|
await createUser(server.url, server.accessToken, username, password)
|
||||||
userAccessToken = await userLogin(server, { username, password })
|
userAccessToken = await userLogin(server, { username, password })
|
||||||
|
|
||||||
const res = await uploadVideo(server.url, server.accessToken, {})
|
{
|
||||||
server.video = res.body.video
|
const res = await uploadVideo(server.url, server.accessToken, {})
|
||||||
|
server.video = res.body.video
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const res = await uploadVideo(server.url, server.accessToken, {})
|
||||||
|
notBlacklistedVideoId = res.body.video.uuid
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('When adding a video in blacklist', function () {
|
describe('When adding a video in blacklist', function () {
|
||||||
|
@ -59,20 +77,70 @@ describe('Test video blacklist API validators', function () {
|
||||||
await makePostBodyRequest({ url: server.url, path, token: userAccessToken, fields, statusCodeExpected: 403 })
|
await makePostBodyRequest({ url: server.url, path, token: userAccessToken, fields, statusCodeExpected: 403 })
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Should fail with a local video', async function () {
|
it('Should fail with an invalid reason', async function () {
|
||||||
const path = basePath + server.video.id + '/blacklist'
|
const path = basePath + server.video.uuid + '/blacklist'
|
||||||
|
const fields = { reason: 'a'.repeat(305) }
|
||||||
|
|
||||||
|
await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields })
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should succeed with the correct params', async function () {
|
||||||
|
const path = basePath + server.video.uuid + '/blacklist'
|
||||||
|
const fields = { }
|
||||||
|
|
||||||
|
await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields, statusCodeExpected: 204 })
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('When updating a video in blacklist', function () {
|
||||||
|
const basePath = '/api/v1/videos/'
|
||||||
|
|
||||||
|
it('Should fail with a wrong video', async function () {
|
||||||
|
const wrongPath = '/api/v1/videos/blabla/blacklist'
|
||||||
const fields = {}
|
const fields = {}
|
||||||
await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields, statusCodeExpected: 403 })
|
await makePutBodyRequest({ url: server.url, path: wrongPath, token: server.accessToken, fields })
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should fail with a video not blacklisted', async function () {
|
||||||
|
const path = '/api/v1/videos/' + notBlacklistedVideoId + '/blacklist'
|
||||||
|
const fields = {}
|
||||||
|
await makePutBodyRequest({ url: server.url, path, token: server.accessToken, fields, statusCodeExpected: 404 })
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should fail with a non authenticated user', async function () {
|
||||||
|
const path = basePath + server.video + '/blacklist'
|
||||||
|
const fields = {}
|
||||||
|
await makePutBodyRequest({ url: server.url, path, token: 'hello', fields, statusCodeExpected: 401 })
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should fail with a non admin user', async function () {
|
||||||
|
const path = basePath + server.video + '/blacklist'
|
||||||
|
const fields = {}
|
||||||
|
await makePutBodyRequest({ url: server.url, path, token: userAccessToken, fields, statusCodeExpected: 403 })
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should fail with an invalid reason', async function () {
|
||||||
|
const path = basePath + server.video.uuid + '/blacklist'
|
||||||
|
const fields = { reason: 'a'.repeat(305) }
|
||||||
|
|
||||||
|
await makePutBodyRequest({ url: server.url, path, token: server.accessToken, fields })
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should succeed with the correct params', async function () {
|
||||||
|
const path = basePath + server.video.uuid + '/blacklist'
|
||||||
|
const fields = { reason: 'hello' }
|
||||||
|
|
||||||
|
await makePutBodyRequest({ url: server.url, path, token: server.accessToken, fields, statusCodeExpected: 204 })
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('When removing a video in blacklist', function () {
|
describe('When removing a video in blacklist', function () {
|
||||||
it('Should fail with a non authenticated user', async function () {
|
it('Should fail with a non authenticated user', async function () {
|
||||||
await removeVideoFromBlacklist(server.url, 'fake token', server.video.id, 401)
|
await removeVideoFromBlacklist(server.url, 'fake token', server.video.uuid, 401)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Should fail with a non admin user', async function () {
|
it('Should fail with a non admin user', async function () {
|
||||||
await removeVideoFromBlacklist(server.url, userAccessToken, server.video.id, 403)
|
await removeVideoFromBlacklist(server.url, userAccessToken, server.video.uuid, 403)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Should fail with an incorrect id', async function () {
|
it('Should fail with an incorrect id', async function () {
|
||||||
|
@ -81,7 +149,11 @@ describe('Test video blacklist API validators', function () {
|
||||||
|
|
||||||
it('Should fail with a not blacklisted video', async function () {
|
it('Should fail with a not blacklisted video', async function () {
|
||||||
// The video was not added to the blacklist so it should fail
|
// The video was not added to the blacklist so it should fail
|
||||||
await removeVideoFromBlacklist(server.url, server.accessToken, server.video.id, 404)
|
await removeVideoFromBlacklist(server.url, server.accessToken, notBlacklistedVideoId, 404)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should succeed with the correct params', async function () {
|
||||||
|
await removeVideoFromBlacklist(server.url, server.accessToken, server.video.uuid, 204)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -3,9 +3,10 @@
|
||||||
import * as chai from 'chai'
|
import * as chai from 'chai'
|
||||||
import 'mocha'
|
import 'mocha'
|
||||||
import {
|
import {
|
||||||
|
addVideoToBlacklist,
|
||||||
askResetPassword,
|
askResetPassword,
|
||||||
blockUser,
|
blockUser,
|
||||||
createUser,
|
createUser, removeVideoFromBlacklist,
|
||||||
reportVideoAbuse,
|
reportVideoAbuse,
|
||||||
resetPassword,
|
resetPassword,
|
||||||
runServer,
|
runServer,
|
||||||
|
@ -22,7 +23,9 @@ const expect = chai.expect
|
||||||
describe('Test emails', function () {
|
describe('Test emails', function () {
|
||||||
let server: ServerInfo
|
let server: ServerInfo
|
||||||
let userId: number
|
let userId: number
|
||||||
|
let userAccessToken: string
|
||||||
let videoUUID: string
|
let videoUUID: string
|
||||||
|
let videoUserUUID: string
|
||||||
let verificationString: string
|
let verificationString: string
|
||||||
const emails: object[] = []
|
const emails: object[] = []
|
||||||
const user = {
|
const user = {
|
||||||
|
@ -48,6 +51,16 @@ describe('Test emails', function () {
|
||||||
{
|
{
|
||||||
const res = await createUser(server.url, server.accessToken, user.username, user.password)
|
const res = await createUser(server.url, server.accessToken, user.username, user.password)
|
||||||
userId = res.body.user.id
|
userId = res.body.user.id
|
||||||
|
|
||||||
|
userAccessToken = await userLogin(server, user)
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const attributes = {
|
||||||
|
name: 'my super user video'
|
||||||
|
}
|
||||||
|
const res = await uploadVideo(server.url, userAccessToken, attributes)
|
||||||
|
videoUserUUID = res.body.video.uuid
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -158,6 +171,42 @@ describe('Test emails', function () {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('When blacklisting a video', function () {
|
||||||
|
it('Should send the notification email', async function () {
|
||||||
|
this.timeout(10000)
|
||||||
|
|
||||||
|
const reason = 'my super reason'
|
||||||
|
await addVideoToBlacklist(server.url, server.accessToken, videoUserUUID, reason)
|
||||||
|
|
||||||
|
await waitJobs(server)
|
||||||
|
expect(emails).to.have.lengthOf(5)
|
||||||
|
|
||||||
|
const email = emails[4]
|
||||||
|
|
||||||
|
expect(email['from'][0]['address']).equal('test-admin@localhost')
|
||||||
|
expect(email['to'][0]['address']).equal('user_1@example.com')
|
||||||
|
expect(email['subject']).contains(' blacklisted')
|
||||||
|
expect(email['text']).contains('my super user video')
|
||||||
|
expect(email['text']).contains('my super reason')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should send the notification email', async function () {
|
||||||
|
this.timeout(10000)
|
||||||
|
|
||||||
|
await removeVideoFromBlacklist(server.url, server.accessToken, videoUserUUID)
|
||||||
|
|
||||||
|
await waitJobs(server)
|
||||||
|
expect(emails).to.have.lengthOf(6)
|
||||||
|
|
||||||
|
const email = emails[5]
|
||||||
|
|
||||||
|
expect(email['from'][0]['address']).equal('test-admin@localhost')
|
||||||
|
expect(email['to'][0]['address']).equal('user_1@example.com')
|
||||||
|
expect(email['subject']).contains(' unblacklisted')
|
||||||
|
expect(email['text']).contains('my super user video')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
after(async function () {
|
after(async function () {
|
||||||
killallServers([ server ])
|
killallServers([ server ])
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
/* tslint:disable:no-unused-expressions */
|
/* tslint:disable:no-unused-expression */
|
||||||
|
|
||||||
import * as chai from 'chai'
|
import * as chai from 'chai'
|
||||||
import * as lodash from 'lodash'
|
import * as lodash from 'lodash'
|
||||||
|
@ -7,29 +7,33 @@ import {
|
||||||
addVideoToBlacklist,
|
addVideoToBlacklist,
|
||||||
flushAndRunMultipleServers,
|
flushAndRunMultipleServers,
|
||||||
getBlacklistedVideosList,
|
getBlacklistedVideosList,
|
||||||
|
getMyVideos,
|
||||||
getSortedBlacklistedVideosList,
|
getSortedBlacklistedVideosList,
|
||||||
getVideosList,
|
getVideosList,
|
||||||
killallServers,
|
killallServers,
|
||||||
removeVideoFromBlacklist,
|
removeVideoFromBlacklist,
|
||||||
ServerInfo,
|
ServerInfo,
|
||||||
setAccessTokensToServers,
|
setAccessTokensToServers,
|
||||||
|
updateVideoBlacklist,
|
||||||
uploadVideo
|
uploadVideo
|
||||||
} from '../../utils/index'
|
} from '../../utils/index'
|
||||||
import { doubleFollow } from '../../utils/server/follows'
|
import { doubleFollow } from '../../utils/server/follows'
|
||||||
import { waitJobs } from '../../utils/server/jobs'
|
import { waitJobs } from '../../utils/server/jobs'
|
||||||
|
import { VideoAbuse } from '../../../../shared/models/videos'
|
||||||
|
|
||||||
const expect = chai.expect
|
const expect = chai.expect
|
||||||
const orderBy = lodash.orderBy
|
const orderBy = lodash.orderBy
|
||||||
|
|
||||||
describe('Test video blacklist management', function () {
|
describe('Test video blacklist management', function () {
|
||||||
let servers: ServerInfo[] = []
|
let servers: ServerInfo[] = []
|
||||||
|
let videoId: number
|
||||||
|
|
||||||
async function blacklistVideosOnServer (server: ServerInfo) {
|
async function blacklistVideosOnServer (server: ServerInfo) {
|
||||||
const res = await getVideosList(server.url)
|
const res = await getVideosList(server.url)
|
||||||
|
|
||||||
const videos = res.body.data
|
const videos = res.body.data
|
||||||
for (let video of videos) {
|
for (let video of videos) {
|
||||||
await addVideoToBlacklist(server.url, server.accessToken, video.id)
|
await addVideoToBlacklist(server.url, server.accessToken, video.id, 'super reason')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -62,53 +66,85 @@ describe('Test video blacklist management', function () {
|
||||||
|
|
||||||
expect(res.body.total).to.equal(2)
|
expect(res.body.total).to.equal(2)
|
||||||
|
|
||||||
const videos = res.body.data
|
const blacklistedVideos = res.body.data
|
||||||
expect(videos).to.be.an('array')
|
expect(blacklistedVideos).to.be.an('array')
|
||||||
expect(videos.length).to.equal(2)
|
expect(blacklistedVideos.length).to.equal(2)
|
||||||
|
|
||||||
|
for (const blacklistedVideo of blacklistedVideos) {
|
||||||
|
expect(blacklistedVideo.reason).to.equal('super reason')
|
||||||
|
videoId = blacklistedVideo.video.id
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Should get the correct sort when sorting by descending id', async function () {
|
it('Should get the correct sort when sorting by descending id', async function () {
|
||||||
const res = await getSortedBlacklistedVideosList(servers[0].url, servers[0].accessToken, '-id')
|
const res = await getSortedBlacklistedVideosList(servers[0].url, servers[0].accessToken, '-id')
|
||||||
expect(res.body.total).to.equal(2)
|
expect(res.body.total).to.equal(2)
|
||||||
|
|
||||||
const videos = res.body.data
|
const blacklistedVideos = res.body.data
|
||||||
expect(videos).to.be.an('array')
|
expect(blacklistedVideos).to.be.an('array')
|
||||||
expect(videos.length).to.equal(2)
|
expect(blacklistedVideos.length).to.equal(2)
|
||||||
|
|
||||||
const result = orderBy(res.body.data, [ 'id' ], [ 'desc' ])
|
const result = orderBy(res.body.data, [ 'id' ], [ 'desc' ])
|
||||||
|
|
||||||
expect(videos).to.deep.equal(result)
|
expect(blacklistedVideos).to.deep.equal(result)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Should get the correct sort when sorting by descending video name', async function () {
|
it('Should get the correct sort when sorting by descending video name', async function () {
|
||||||
const res = await getSortedBlacklistedVideosList(servers[0].url, servers[0].accessToken, '-name')
|
const res = await getSortedBlacklistedVideosList(servers[0].url, servers[0].accessToken, '-name')
|
||||||
expect(res.body.total).to.equal(2)
|
expect(res.body.total).to.equal(2)
|
||||||
|
|
||||||
const videos = res.body.data
|
const blacklistedVideos = res.body.data
|
||||||
expect(videos).to.be.an('array')
|
expect(blacklistedVideos).to.be.an('array')
|
||||||
expect(videos.length).to.equal(2)
|
expect(blacklistedVideos.length).to.equal(2)
|
||||||
|
|
||||||
const result = orderBy(res.body.data, [ 'name' ], [ 'desc' ])
|
const result = orderBy(res.body.data, [ 'name' ], [ 'desc' ])
|
||||||
|
|
||||||
expect(videos).to.deep.equal(result)
|
expect(blacklistedVideos).to.deep.equal(result)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Should get the correct sort when sorting by ascending creation date', async function () {
|
it('Should get the correct sort when sorting by ascending creation date', async function () {
|
||||||
const res = await getSortedBlacklistedVideosList(servers[0].url, servers[0].accessToken, 'createdAt')
|
const res = await getSortedBlacklistedVideosList(servers[0].url, servers[0].accessToken, 'createdAt')
|
||||||
expect(res.body.total).to.equal(2)
|
expect(res.body.total).to.equal(2)
|
||||||
|
|
||||||
const videos = res.body.data
|
const blacklistedVideos = res.body.data
|
||||||
expect(videos).to.be.an('array')
|
expect(blacklistedVideos).to.be.an('array')
|
||||||
expect(videos.length).to.equal(2)
|
expect(blacklistedVideos.length).to.equal(2)
|
||||||
|
|
||||||
const result = orderBy(res.body.data, [ 'createdAt' ])
|
const result = orderBy(res.body.data, [ 'createdAt' ])
|
||||||
|
|
||||||
expect(videos).to.deep.equal(result)
|
expect(blacklistedVideos).to.deep.equal(result)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('When updating blacklisted videos', function () {
|
||||||
|
it('Should change the reason', async function () {
|
||||||
|
await updateVideoBlacklist(servers[0].url, servers[0].accessToken, videoId, 'my super reason updated')
|
||||||
|
|
||||||
|
const res = await getSortedBlacklistedVideosList(servers[0].url, servers[0].accessToken, '-name')
|
||||||
|
const video = res.body.data.find(b => b.video.id === videoId)
|
||||||
|
|
||||||
|
expect(video.reason).to.equal('my super reason updated')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('When listing my videos', function () {
|
||||||
|
it('Should display blacklisted videos', async function () {
|
||||||
|
await blacklistVideosOnServer(servers[1])
|
||||||
|
|
||||||
|
const res = await getMyVideos(servers[1].url, servers[1].accessToken, 0, 5)
|
||||||
|
|
||||||
|
expect(res.body.total).to.equal(2)
|
||||||
|
expect(res.body.data).to.have.lengthOf(2)
|
||||||
|
|
||||||
|
for (const video of res.body.data) {
|
||||||
|
expect(video.blacklisted).to.be.true
|
||||||
|
expect(video.blacklistedReason).to.equal('super reason')
|
||||||
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('When removing a blacklisted video', function () {
|
describe('When removing a blacklisted video', function () {
|
||||||
let videoToRemove
|
let videoToRemove: VideoAbuse
|
||||||
let blacklist = []
|
let blacklist = []
|
||||||
|
|
||||||
it('Should not have any video in videos list on server 1', async function () {
|
it('Should not have any video in videos list on server 1', async function () {
|
||||||
|
@ -125,7 +161,7 @@ describe('Test video blacklist management', function () {
|
||||||
blacklist = res.body.data.slice(1)
|
blacklist = res.body.data.slice(1)
|
||||||
|
|
||||||
// Remove it
|
// Remove it
|
||||||
await removeVideoFromBlacklist(servers[0].url, servers[0].accessToken, videoToRemove.videoId)
|
await removeVideoFromBlacklist(servers[0].url, servers[0].accessToken, videoToRemove.video.id)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Should have the ex-blacklisted video in videos list on server 1', async function () {
|
it('Should have the ex-blacklisted video in videos list on server 1', async function () {
|
||||||
|
@ -136,8 +172,8 @@ describe('Test video blacklist management', function () {
|
||||||
expect(videos).to.be.an('array')
|
expect(videos).to.be.an('array')
|
||||||
expect(videos.length).to.equal(1)
|
expect(videos.length).to.equal(1)
|
||||||
|
|
||||||
expect(videos[0].name).to.equal(videoToRemove.name)
|
expect(videos[0].name).to.equal(videoToRemove.video.name)
|
||||||
expect(videos[0].id).to.equal(videoToRemove.videoId)
|
expect(videos[0].id).to.equal(videoToRemove.video.id)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Should not have the ex-blacklisted video in videos blacklist list on server 1', async function () {
|
it('Should not have the ex-blacklisted video in videos blacklist list on server 1', async function () {
|
||||||
|
|
|
@ -1,15 +1,26 @@
|
||||||
import * as request from 'supertest'
|
import * as request from 'supertest'
|
||||||
|
|
||||||
function addVideoToBlacklist (url: string, token: string, videoId: number, specialStatus = 204) {
|
function addVideoToBlacklist (url: string, token: string, videoId: number | string, reason?: string, specialStatus = 204) {
|
||||||
const path = '/api/v1/videos/' + videoId + '/blacklist'
|
const path = '/api/v1/videos/' + videoId + '/blacklist'
|
||||||
|
|
||||||
return request(url)
|
return request(url)
|
||||||
.post(path)
|
.post(path)
|
||||||
|
.send({ reason })
|
||||||
.set('Accept', 'application/json')
|
.set('Accept', 'application/json')
|
||||||
.set('Authorization', 'Bearer ' + token)
|
.set('Authorization', 'Bearer ' + token)
|
||||||
.expect(specialStatus)
|
.expect(specialStatus)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function updateVideoBlacklist (url: string, token: string, videoId: number, reason?: string, specialStatus = 204) {
|
||||||
|
const path = '/api/v1/videos/' + videoId + '/blacklist'
|
||||||
|
|
||||||
|
return request(url)
|
||||||
|
.put(path)
|
||||||
|
.send({ reason })
|
||||||
|
.set('Accept', 'application/json')
|
||||||
|
.set('Authorization', 'Bearer ' + token)
|
||||||
|
.expect(specialStatus)}
|
||||||
|
|
||||||
function removeVideoFromBlacklist (url: string, token: string, videoId: number | string, specialStatus = 204) {
|
function removeVideoFromBlacklist (url: string, token: string, videoId: number | string, specialStatus = 204) {
|
||||||
const path = '/api/v1/videos/' + videoId + '/blacklist'
|
const path = '/api/v1/videos/' + videoId + '/blacklist'
|
||||||
|
|
||||||
|
@ -50,5 +61,6 @@ export {
|
||||||
addVideoToBlacklist,
|
addVideoToBlacklist,
|
||||||
removeVideoFromBlacklist,
|
removeVideoFromBlacklist,
|
||||||
getBlacklistedVideosList,
|
getBlacklistedVideosList,
|
||||||
getSortedBlacklistedVideosList
|
getSortedBlacklistedVideosList,
|
||||||
|
updateVideoBlacklist
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,8 @@ export * from './video-abuse-create.model'
|
||||||
export * from './video-abuse.model'
|
export * from './video-abuse.model'
|
||||||
export * from './video-abuse-update.model'
|
export * from './video-abuse-update.model'
|
||||||
export * from './video-blacklist.model'
|
export * from './video-blacklist.model'
|
||||||
|
export * from './video-blacklist-create.model'
|
||||||
|
export * from './video-blacklist-update.model'
|
||||||
export * from './video-channel-create.model'
|
export * from './video-channel-create.model'
|
||||||
export * from './video-channel-update.model'
|
export * from './video-channel-update.model'
|
||||||
export * from './video-channel.model'
|
export * from './video-channel.model'
|
||||||
|
|
3
shared/models/videos/video-blacklist-create.model.ts
Normal file
3
shared/models/videos/video-blacklist-create.model.ts
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
export interface VideoBlacklistCreate {
|
||||||
|
reason?: string
|
||||||
|
}
|
3
shared/models/videos/video-blacklist-update.model.ts
Normal file
3
shared/models/videos/video-blacklist-update.model.ts
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
export interface VideoBlacklistUpdate {
|
||||||
|
reason?: string
|
||||||
|
}
|
|
@ -1,14 +1,18 @@
|
||||||
export interface BlacklistedVideo {
|
export interface BlacklistedVideo {
|
||||||
id: number
|
id: number
|
||||||
videoId: number
|
|
||||||
createdAt: Date
|
createdAt: Date
|
||||||
updatedAt: Date
|
updatedAt: Date
|
||||||
name: string
|
reason?: string
|
||||||
uuid: string
|
|
||||||
description: string
|
video: {
|
||||||
duration: number
|
id: number
|
||||||
views: number
|
name: string
|
||||||
likes: number
|
uuid: string
|
||||||
dislikes: number
|
description: string
|
||||||
nsfw: boolean
|
duration: number
|
||||||
|
views: number
|
||||||
|
likes: number
|
||||||
|
dislikes: number
|
||||||
|
nsfw: boolean
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,6 +43,9 @@ export interface Video {
|
||||||
state?: VideoConstant<VideoState>
|
state?: VideoConstant<VideoState>
|
||||||
scheduledUpdate?: VideoScheduleUpdate
|
scheduledUpdate?: VideoScheduleUpdate
|
||||||
|
|
||||||
|
blacklisted?: boolean
|
||||||
|
blacklistedReason?: string
|
||||||
|
|
||||||
account: {
|
account: {
|
||||||
id: number
|
id: number
|
||||||
uuid: string
|
uuid: string
|
||||||
|
|
Loading…
Reference in New Issue
Block a user