Improve blacklist management
authorChocobozzz <me@florianbigard.com>
Tue, 14 Aug 2018 07:08:47 +0000 (09:08 +0200)
committerChocobozzz <me@florianbigard.com>
Tue, 14 Aug 2018 07:27:18 +0000 (09:27 +0200)
23 files changed:
client/src/app/+admin/video-abuses/video-abuse-list/video-abuse-list.component.html
client/src/app/+admin/video-abuses/video-abuse-list/video-abuse-list.component.ts
client/src/app/+admin/video-blacklist/video-blacklist-list/video-blacklist-list.component.html
client/src/app/+admin/video-blacklist/video-blacklist-list/video-blacklist-list.component.ts
client/src/app/+my-account/my-account-videos/my-account-videos.component.html
client/src/app/+my-account/my-account-videos/my-account-videos.component.scss
client/src/app/+page-not-found/page-not-found.component.scss
client/src/app/shared/video-blacklist/video-blacklist.service.ts
client/src/app/shared/video/video-details.model.ts
client/src/app/shared/video/video.model.ts
client/src/app/videos/+video-watch/video-watch.component.html
client/src/app/videos/+video-watch/video-watch.component.scss
client/src/app/videos/+video-watch/video-watch.component.ts
client/src/assets/images/global/undo.svg [new file with mode: 0644]
server/controllers/api/videos/blacklist.ts
server/middlewares/validators/videos.ts
server/models/video/video-abuse.ts
server/models/video/video-blacklist.ts
server/models/video/video.ts
server/tests/utils/videos/video-blacklist.ts
shared/models/users/user-right.enum.ts
shared/models/videos/video-abuse.model.ts
shared/models/videos/video-blacklist.model.ts

index aa0e18c70e94db873b2126a09de90fd5b6a71a3e..f213ab4b08f8fe57ae50165bc4b070cea5367026 100644 (file)
@@ -42,7 +42,7 @@
       <td>{{ videoAbuse.createdAt }}</td>
 
       <td>
-        <a [href]="videoAbuse.video.url" i18n-title title="Go to the video" target="_blank" rel="noopener noreferrer">
+        <a [href]="getVideoUrl(videoAbuse)" i18n-title title="Go to the video" target="_blank" rel="noopener noreferrer">
           {{ videoAbuse.video.name }}
         </a>
       </td>
index a850c0ec2cedd53cf832769a9ce583cc098b3662..377e9c80f25d8ef700181d8d0e1c50a0c4fe767b 100644 (file)
@@ -8,6 +8,7 @@ import { I18n } from '@ngx-translate/i18n-polyfill'
 import { DropdownAction } from '@app/shared/buttons/action-dropdown.component'
 import { ConfirmService } from '@app/core'
 import { ModerationCommentModalComponent } from './moderation-comment-modal.component'
+import { Video } from '@app/shared/video/video.model'
 
 @Component({
   selector: 'my-video-abuse-list',
@@ -79,6 +80,10 @@ export class VideoAbuseListComponent extends RestTable implements OnInit {
     return videoAbuse.state.id === VideoAbuseState.REJECTED
   }
 
+  getVideoUrl (videoAbuse: VideoAbuse) {
+    return Video.buildClientUrl(videoAbuse.video.uuid)
+  }
+
   async removeVideoAbuse (videoAbuse: VideoAbuse) {
     const res = await this.confirmService.confirm(this.i18n('Do you really want to delete this abuse?'), this.i18n('Delete'))
     if (res === false) return
index 78989dc586f5e269dce41ae12657b99144034967..05b3a300c1133f2884107eaa55a090c8f77b95f4 100644 (file)
@@ -10,8 +10,7 @@
     <tr>
       <th style="width: 40px"></th>
       <th i18n pSortableColumn="name">Video name <p-sortIcon field="name"></p-sortIcon></th>
-      <th i18n>NSFW</th>
-      <th i18n>UUID</th>
+      <th i18n>Sensitive</th>
       <th i18n pSortableColumn="createdAt">Date <p-sortIcon field="createdAt"></p-sortIcon></th>
       <th style="width: 50px;"></th>
     </tr>
         </span>
       </td>
 
-      <td>{{ videoBlacklist.video.name }}</td>
+      <td>
+        <a [href]="getVideoUrl(videoBlacklist)" i18n-title title="Go to the video" target="_blank" rel="noopener noreferrer">
+          {{ videoBlacklist.video.name }}
+        </a>
+      </td>
+
       <td>{{ videoBlacklist.video.nsfw }}</td>
-      <td>{{ videoBlacklist.video.uuid }}</td>
       <td>{{ videoBlacklist.createdAt }}</td>
 
       <td class="action-cell">
index 00b0ac57ef1a4a58e5ffb266c1649af7f56bb5b0..0618252b8ca76021a81048c1e2e97a0ef8a37686 100644 (file)
@@ -3,9 +3,10 @@ import { SortMeta } from 'primeng/components/common/sortmeta'
 import { NotificationsService } from 'angular2-notifications'
 import { ConfirmService } from '../../../core'
 import { RestPagination, RestTable, VideoBlacklistService } from '../../../shared'
-import { BlacklistedVideo } from '../../../../../../shared'
+import { VideoBlacklist } from '../../../../../../shared'
 import { I18n } from '@ngx-translate/i18n-polyfill'
 import { DropdownAction } from '@app/shared/buttons/action-dropdown.component'
+import { Video } from '@app/shared/video/video.model'
 
 @Component({
   selector: 'my-video-blacklist-list',
@@ -13,13 +14,13 @@ import { DropdownAction } from '@app/shared/buttons/action-dropdown.component'
   styleUrls: [ './video-blacklist-list.component.scss' ]
 })
 export class VideoBlacklistListComponent extends RestTable implements OnInit {
-  blacklist: BlacklistedVideo[] = []
+  blacklist: VideoBlacklist[] = []
   totalRecords = 0
   rowsPerPage = 10
   sort: SortMeta = { field: 'createdAt', order: 1 }
   pagination: RestPagination = { count: this.rowsPerPage, start: 0 }
 
-  videoBlacklistActions: DropdownAction<BlacklistedVideo>[] = []
+  videoBlacklistActions: DropdownAction<VideoBlacklist>[] = []
 
   constructor (
     private notificationsService: NotificationsService,
@@ -41,7 +42,11 @@ export class VideoBlacklistListComponent extends RestTable implements OnInit {
     this.loadSort()
   }
 
-  async removeVideoFromBlacklist (entry: BlacklistedVideo) {
+  getVideoUrl (videoBlacklist: VideoBlacklist) {
+    return Video.buildClientUrl(videoBlacklist.video.uuid)
+  }
+
+  async removeVideoFromBlacklist (entry: VideoBlacklist) {
     const confirmMessage = this.i18n(
       'Do you really want to remove this video from the blacklist? It will be available again in the videos list.'
     )
index 4823e2db97c24cf753af7ad320daf16d6839f0d4..8a6cb5c3298b38b1f90c2542c7cea2331cc4ec67 100644 (file)
         <a class="video-info-name" [routerLink]="['/videos/watch', video.uuid]" [attr.title]="video.name">{{ video.name }}</a>
         <span i18n class="video-info-date-views">{{ video.createdAt | myFromNow }} - {{ video.views | myNumberFormatter }} views</span>
         <div class="video-info-private">{{ video.privacy.label }}{{ getStateLabel(video) }}</div>
+        <div *ngIf="video.blacklisted" class="video-info-blacklisted">
+          <span class="blacklisted-label" i18n>Blacklisted</span>
+          <span class="blacklisted-reason" *ngIf="video.blacklistedReason">{{ video.blacklistedReason }}</span>
+        </div>
       </div>
 
       <!-- Display only once -->
index 9df28f472b929a48df3bf8946fb1a444ea82767a..64a04fa20a1273f33f48ed6c627e4c8db994ab92 100644 (file)
       font-weight: $font-semibold;
     }
 
-    .video-info-date-views, .video-info-private {
+    .video-info-date-views,
+    .video-info-private,
+    .video-info-blacklisted {
       font-size: 13px;
 
-      &.video-info-private {
+      &.video-info-private,
+      &.video-info-blacklisted .blacklisted-label {
         font-weight: $font-semibold;
       }
+
+      &.video-info-blacklisted {
+        color: red;
+
+        .blacklisted-reason {
+          &::before {
+            content: ' - ';
+          }
+        }
+      }
     }
   }
 
index 53b6142e15b4eaf5a19f5cf4a9bbb346179f23d3..f3f0354a3898007c819a968791e905145365e71a 100644 (file)
@@ -2,7 +2,7 @@ div {
   height: 100%;
   width: 100%;
   text-align: center;
-  margin-top: 150px;
+  padding-top: 150px;
 
   font-size: 32px;
 }
\ No newline at end of file
index a014260b1203edc39904f4a063db4406dcc5bb2d..7d39fd4f24fd771c657a71be030b6c9e32fa3a01 100644 (file)
@@ -3,7 +3,7 @@ import { HttpClient, HttpParams } from '@angular/common/http'
 import { Injectable } from '@angular/core'
 import { SortMeta } from 'primeng/components/common/sortmeta'
 import { Observable } from 'rxjs'
-import { BlacklistedVideo, ResultList } from '../../../../../shared'
+import { VideoBlacklist, ResultList } from '../../../../../shared'
 import { environment } from '../../../environments/environment'
 import { RestExtractor, RestPagination, RestService } from '../rest'
 
@@ -17,11 +17,11 @@ export class VideoBlacklistService {
     private restExtractor: RestExtractor
   ) {}
 
-  listBlacklist (pagination: RestPagination, sort: SortMeta): Observable<ResultList<BlacklistedVideo>> {
+  listBlacklist (pagination: RestPagination, sort: SortMeta): Observable<ResultList<VideoBlacklist>> {
     let params = new HttpParams()
     params = this.restService.addRestGetParams(params, pagination, sort)
 
-    return this.authHttp.get<ResultList<BlacklistedVideo>>(VideoBlacklistService.BASE_VIDEOS_URL + 'blacklist', { params })
+    return this.authHttp.get<ResultList<VideoBlacklist>>(VideoBlacklistService.BASE_VIDEOS_URL + 'blacklist', { params })
                .pipe(
                  map(res => this.restExtractor.convertResultListDateToHuman(res)),
                  catchError(res => this.restExtractor.handleError(res))
index bdcc0bbba0ba1b0038f68ecd0948f93451cf6541..d346f985cbe2be5fa712e5c8912b2f5d0658fea2 100644 (file)
@@ -44,7 +44,11 @@ export class VideoDetails extends Video implements VideoDetailsServerModel {
   }
 
   isBlackistableBy (user: AuthUser) {
-    return user && user.hasRight(UserRight.MANAGE_VIDEO_BLACKLIST) === true
+    return this.blacklisted !== true && user && user.hasRight(UserRight.MANAGE_VIDEO_BLACKLIST) === true
+  }
+
+  isUnblacklistableBy (user: AuthUser) {
+    return this.blacklisted === true && user && user.hasRight(UserRight.MANAGE_VIDEO_BLACKLIST) === true
   }
 
   isUpdatableBy (user: AuthUser) {
index 6b1a299ea8c231d3555334ca5415c8aca5ddb2c2..ec0afcccb5360b1a036101525b25853236a18e7b 100644 (file)
@@ -41,6 +41,8 @@ export class Video implements VideoServerModel {
   waitTranscoding?: boolean
   state?: VideoConstant<VideoState>
   scheduledUpdate?: VideoScheduleUpdate
+  blacklisted?: boolean
+  blacklistedReason?: string
 
   account: {
     id: number
@@ -62,6 +64,10 @@ export class Video implements VideoServerModel {
     avatar: Avatar
   }
 
+  static buildClientUrl (videoUUID: string) {
+    return '/videos/watch/' + videoUUID
+  }
+
   private static createDurationString (duration: number) {
     const hours = Math.floor(duration / 3600)
     const minutes = Math.floor((duration % 3600) / 60)
@@ -116,6 +122,9 @@ export class Video implements VideoServerModel {
 
     this.scheduledUpdate = hash.scheduledUpdate
     if (this.state) this.state.label = peertubeTranslate(this.state.label, translations)
+
+    this.blacklisted = hash.blacklisted
+    this.blacklistedReason = hash.blacklistedReason
   }
 
   isVideoNSFWForUser (user: User, serverConfig: ServerConfig) {
index f82f1c5542c5abdc8e3e0d3739a30d41123844e0..8d4a4a5ca7257a47413e7ca916badb043dae027b 100644 (file)
@@ -1,4 +1,4 @@
-<div class="row">
+<div class="root-row row">
   <!-- We need the video container for videojs so we just hide it -->
   <div id="video-element-wrapper">
     <div *ngIf="remoteServerDown" class="remote-server-down">
   </div>
 
   <div i18n class="alert alert-info" *ngIf="hasVideoScheduledPublication()">
-    This video will be published on {{ video.scheduledUpdate.updateAt | date: 'full' }}
+    This video will be published on {{ video.scheduledUpdate.updateAt | date: 'full' }}.
+  </div>
+
+  <div class="alert alert-danger" *ngIf="video?.blacklisted">
+    <div class="blacklisted-label" i18n>This video is blacklisted.</div>
+    {{ video.blacklistedReason }}
   </div>
 
   <!-- Video information -->
                   <span class="icon icon-blacklist"></span> <ng-container i18n>Blacklist</ng-container>
                 </a>
 
+                <a *ngIf="isVideoUnblacklistable()" class="dropdown-item" i18n-title title="Unblacklist this video" href="#" (click)="unblacklistVideo($event)">
+                  <span class="icon icon-unblacklist"></span> <ng-container i18n>Unblacklist</ng-container>
+                </a>
+
                 <a *ngIf="isVideoRemovable()" class="dropdown-item" i18n-title title="Delete this video" href="#" (click)="removeVideo($event)">
                   <span class="icon icon-delete"></span> <ng-container i18n>Delete</ng-container>
                 </a>
index e63ab7bbd86275f92023fad05959039794309c80..1354de32e0f24bcf0750ff64a451b59dfc921985 100644 (file)
@@ -1,6 +1,14 @@
 @import '_variables';
 @import '_mixins';
 
+.root-row {
+  flex-direction: column;
+}
+
+.blacklisted-label {
+  font-weight: $font-semibold;
+}
+
 #video-element-wrapper {
   background-color: #000;
   display: flex;
                   background-image: url('../../../assets/images/video/blacklist.svg');
                 }
 
+                &.icon-unblacklist {
+                  background-image: url('../../../assets/images/global/undo.svg');
+                }
+
                 &.icon-delete {
                   background-image: url('../../../assets/images/global/delete-black.svg');
                 }
index 878655d4a898f0b6e6500e4e3ffa18357e98d189..bea13ec999105292f6c5279723ac06c5a129ebd5 100644 (file)
@@ -121,7 +121,8 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
         this.videoCaptionService.listCaptions(uuid)
       )
         .pipe(
-          catchError(err => this.restExtractor.redirectTo404IfNotFound(err, [ 400, 404 ]))
+          // If 401, the video is private or blacklisted so redirect to 404
+          catchError(err => this.restExtractor.redirectTo404IfNotFound(err, [ 400, 401, 404 ]))
         )
         .subscribe(([ video, captionsResult ]) => {
           const startTime = this.route.snapshot.queryParams.start
@@ -217,6 +218,31 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
     this.videoBlacklistModal.show()
   }
 
+  async unblacklistVideo (event: Event) {
+    event.preventDefault()
+
+    const confirmMessage = this.i18n(
+      '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'))
+    if (res === false) return
+
+    this.videoBlacklistService.removeVideoFromBlacklist(this.video.id).subscribe(
+      () => {
+        this.notificationsService.success(
+          this.i18n('Success'),
+          this.i18n('Video {{name}} removed from the blacklist.', { name: this.video.name })
+        )
+
+        this.video.blacklisted = false
+        this.video.blacklistedReason = null
+      },
+
+      err => this.notificationsService.error(this.i18n('Error'), err.message)
+    )
+  }
+
   isUserLoggedIn () {
     return this.authService.isLoggedIn()
   }
@@ -229,6 +255,10 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
     return this.video.isBlackistableBy(this.user)
   }
 
+  isVideoUnblacklistable () {
+    return this.video.isUnblacklistableBy(this.user)
+  }
+
   getVideoPoster () {
     if (!this.video) return ''
 
diff --git a/client/src/assets/images/global/undo.svg b/client/src/assets/images/global/undo.svg
new file mode 100644 (file)
index 0000000..f1cca03
--- /dev/null
@@ -0,0 +1,11 @@
+<?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(-180.000000, -115.000000)" fill="#000">
+            <g id="4" transform="translate(180.000000, 115.000000)">
+                <path d="M10,19 C10.5522847,19 11,19.4477153 11,20 C11,20.5522847 10.5522847,21 10,21 C9.99404288,21 9.98809793,20.9999479 9.98216558,20.9998442 C5.01980239,20.990358 1,16.9646166 1,12 C1,7.02943725 5.02943725,3 10,3 C14.9705627,3 19,7.02943725 19,12 L17,12 C17,8.13400675 13.8659932,5 10,5 C6.13400675,5 3,8.13400675 3,12 C3,15.8659932 6.13400675,19 10,19 Z M14,12 L22,12 L18,16 L14,12 Z" id="Combined-Shape" transform="translate(11.500000, 12.000000) scale(-1, 1) translate(-11.500000, -12.000000) "></path>
+            </g>
+        </g>
+    </g>
+</svg>
index 358f339edaf8d46b207c6188607ca4089536a2cd..7f803c8e939a3668050b8438f7082b284e717634 100644 (file)
@@ -1,5 +1,5 @@
 import * as express from 'express'
-import { BlacklistedVideo, UserRight, VideoBlacklistCreate } from '../../../../shared'
+import { VideoBlacklist, UserRight, VideoBlacklistCreate } from '../../../../shared'
 import { logger } from '../../../helpers/logger'
 import { getFormattedObjects } from '../../../helpers/utils'
 import {
@@ -87,7 +87,7 @@ async function updateVideoBlacklistController (req: express.Request, res: expres
 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)
 
-  return res.json(getFormattedObjects<BlacklistedVideo, VideoBlacklistModel>(resultList.data, resultList.total))
+  return res.json(getFormattedObjects<VideoBlacklist, VideoBlacklistModel>(resultList.data, resultList.total))
 }
 
 async function removeVideoFromBlacklistController (req: express.Request, res: express.Response, next: express.NextFunction) {
index 203a00876c3a807a9b3c9b8acc58be78f857d0ab..77d601a4df11246321b722307f42505fe1d3b25a 100644 (file)
@@ -35,6 +35,8 @@ import { VideoShareModel } from '../../models/video/video-share'
 import { authenticate } from '../oauth'
 import { areValidationErrors } from './utils'
 import { cleanUpReqFiles } from '../../helpers/utils'
+import { VideoModel } from '../../models/video/video'
+import { UserModel } from '../../models/account/user'
 
 const videosAddValidator = getCommonVideoAttributes().concat([
   body('videofile')
@@ -131,7 +133,25 @@ const videosGetValidator = [
     if (areValidationErrors(req, res)) return
     if (!await isVideoExist(req.params.id, res)) return
 
-    const video = res.locals.video
+    const video: VideoModel = res.locals.video
+
+    // Video private or blacklisted
+    if (video.privacy === VideoPrivacy.PRIVATE || video.VideoBlacklist) {
+      authenticate(req, res, () => {
+        const user: UserModel = res.locals.oauth.token.User
+
+        // Only the owner or a user that have blacklist rights can see the video
+        if (video.VideoChannel.Account.userId !== user.id && !user.hasRight(UserRight.MANAGE_VIDEO_BLACKLIST)) {
+          return res.status(403)
+                    .json({ error: 'Cannot get this private or blacklisted video.' })
+                    .end()
+        }
+
+        return next()
+      })
+
+      return
+    }
 
     // Video is public, anyone can access it
     if (video.privacy === VideoPrivacy.PUBLIC) return next()
@@ -143,17 +163,6 @@ const videosGetValidator = [
       // Don't leak this unlisted video
       return res.status(404).end()
     }
-
-    // Video is private, check the user
-    authenticate(req, res, () => {
-      if (video.VideoChannel.Account.userId !== res.locals.oauth.token.User.id) {
-        return res.status(403)
-          .json({ error: 'Cannot get this private video of another user' })
-          .end()
-      }
-
-      return next()
-    })
   }
 ]
 
index 10a191372207930d2409a58213bc73b2b5179ea4..dbb88ca4565cd197f0d3af90153b8a759dd2fba3 100644 (file)
@@ -137,7 +137,6 @@ export class VideoAbuseModel extends Model<VideoAbuseModel> {
       video: {
         id: this.Video.id,
         uuid: this.Video.uuid,
-        url: this.Video.url,
         name: this.Video.name
       },
       createdAt: this.createdAt
index 1b8a338cb5e334e23dd5b1611ccec08bcfa88e5e..eabc37ef0e64d2d8cba9355ac36d25515b56898d 100644 (file)
@@ -16,7 +16,7 @@ import { getSortOnModel, throwIfNotValid } from '../utils'
 import { VideoModel } from './video'
 import { isVideoBlacklistReasonValid } from '../../helpers/custom-validators/video-blacklist'
 import { Emailer } from '../../lib/emailer'
-import { BlacklistedVideo } from '../../../shared/models/videos'
+import { VideoBlacklist } from '../../../shared/models/videos'
 import { CONSTRAINTS_FIELDS } from '../../initializers'
 
 @Table({
@@ -68,7 +68,12 @@ export class VideoBlacklistModel extends Model<VideoBlacklistModel> {
       offset: start,
       limit: count,
       order: getSortOnModel(sort.sortModel, sort.sortValue),
-      include: [ { model: VideoModel } ]
+      include: [
+        {
+          model: VideoModel,
+          required: true
+        }
+      ]
     }
 
     return VideoBlacklistModel.findAndCountAll(query)
@@ -90,7 +95,7 @@ export class VideoBlacklistModel extends Model<VideoBlacklistModel> {
     return VideoBlacklistModel.findOne(query)
   }
 
-  toFormattedJSON (): BlacklistedVideo {
+  toFormattedJSON (): VideoBlacklist {
     const video = this.Video
 
     return {
index f3a900bc981e9a1863f7bb77cb3de5c40d64cbd6..b13dee40327e0991c7c47c136b80701eb84d6b03 100644 (file)
@@ -127,7 +127,8 @@ export enum ScopeNames {
   WITH_ACCOUNT_DETAILS = 'WITH_ACCOUNT_DETAILS',
   WITH_TAGS = 'WITH_TAGS',
   WITH_FILES = 'WITH_FILES',
-  WITH_SCHEDULED_UPDATE = 'WITH_SCHEDULED_UPDATE'
+  WITH_SCHEDULED_UPDATE = 'WITH_SCHEDULED_UPDATE',
+  WITH_BLACKLISTED = 'WITH_BLACKLISTED'
 }
 
 type AvailableForListOptions = {
@@ -374,6 +375,15 @@ type AvailableForListOptions = {
   [ScopeNames.WITH_TAGS]: {
     include: [ () => TagModel ]
   },
+  [ScopeNames.WITH_BLACKLISTED]: {
+    include: [
+      {
+        attributes: [ 'id', 'reason' ],
+        model: () => VideoBlacklistModel,
+        required: false
+      }
+    ]
+  },
   [ScopeNames.WITH_FILES]: {
     include: [
       {
@@ -1004,7 +1014,13 @@ export class VideoModel extends Model<VideoModel> {
     }
 
     return VideoModel
-      .scope([ ScopeNames.WITH_TAGS, ScopeNames.WITH_FILES, ScopeNames.WITH_ACCOUNT_DETAILS, ScopeNames.WITH_SCHEDULED_UPDATE ])
+      .scope([
+        ScopeNames.WITH_TAGS,
+        ScopeNames.WITH_BLACKLISTED,
+        ScopeNames.WITH_FILES,
+        ScopeNames.WITH_ACCOUNT_DETAILS,
+        ScopeNames.WITH_SCHEDULED_UPDATE
+      ])
       .findById(id, options)
   }
 
@@ -1030,7 +1046,13 @@ export class VideoModel extends Model<VideoModel> {
     }
 
     return VideoModel
-      .scope([ ScopeNames.WITH_TAGS, ScopeNames.WITH_FILES, ScopeNames.WITH_ACCOUNT_DETAILS, ScopeNames.WITH_SCHEDULED_UPDATE ])
+      .scope([
+        ScopeNames.WITH_TAGS,
+        ScopeNames.WITH_BLACKLISTED,
+        ScopeNames.WITH_FILES,
+        ScopeNames.WITH_ACCOUNT_DETAILS,
+        ScopeNames.WITH_SCHEDULED_UPDATE
+      ])
       .findOne(options)
   }
 
@@ -1276,7 +1298,8 @@ export class VideoModel extends Model<VideoModel> {
   toFormattedDetailsJSON (): VideoDetails {
     const formattedJson = this.toFormattedJSON({
       additionalAttributes: {
-        scheduledUpdate: true
+        scheduledUpdate: true,
+        blacklistInfo: true
       }
     })
 
index 7819f4b25bcffec8e2ac20b67934b9e83859c71f..2c176fde064c79126525c5b944dab43d41ae12dc 100644 (file)
@@ -19,7 +19,8 @@ function updateVideoBlacklist (url: string, token: string, videoId: number, reas
     .send({ reason })
     .set('Accept', 'application/json')
     .set('Authorization', 'Bearer ' + token)
-    .expect(specialStatus)}
+    .expect(specialStatus)
+}
 
 function removeVideoFromBlacklist (url: string, token: string, videoId: number | string, specialStatus = 204) {
   const path = '/api/v1/videos/' + videoId + '/blacklist'
index ff6ec61f403d8949de71967abc7de0f44f17d5ee..142a0474b75f2321072d7bbbd2f790695bfd1669 100644 (file)
@@ -1,11 +1,14 @@
 export enum UserRight {
   ALL,
+
   MANAGE_USERS,
   MANAGE_SERVER_FOLLOW,
   MANAGE_VIDEO_ABUSES,
-  MANAGE_VIDEO_BLACKLIST,
   MANAGE_JOBS,
   MANAGE_CONFIGURATION,
+
+  MANAGE_VIDEO_BLACKLIST,
+
   REMOVE_ANY_VIDEO,
   REMOVE_ANY_VIDEO_CHANNEL,
   REMOVE_ANY_VIDEO_COMMENT,
index 1fecce03789a88a96736e72d98475ced38b96bd5..b2319aa0055dfd1dcb84f809ae0b5a79447d040d 100644 (file)
@@ -14,7 +14,6 @@ export interface VideoAbuse {
     id: number
     name: string
     uuid: string
-    url: string
   }
 
   createdAt: Date
index a060da357bbbb4c857716ed6aa4d60d7e0e9c9af..ef4e5e3a23cea522b67400696aa64c089e325177 100644 (file)
@@ -1,4 +1,4 @@
-export interface BlacklistedVideo {
+export interface VideoBlacklist {
   id: number
   createdAt: Date
   updatedAt: Date