diff options
23 files changed, 189 insertions, 45 deletions
diff --git a/client/src/app/+admin/video-abuses/video-abuse-list/video-abuse-list.component.html b/client/src/app/+admin/video-abuses/video-abuse-list/video-abuse-list.component.html index aa0e18c70..f213ab4b0 100644 --- a/client/src/app/+admin/video-abuses/video-abuse-list/video-abuse-list.component.html +++ b/client/src/app/+admin/video-abuses/video-abuse-list/video-abuse-list.component.html | |||
@@ -42,7 +42,7 @@ | |||
42 | <td>{{ videoAbuse.createdAt }}</td> | 42 | <td>{{ videoAbuse.createdAt }}</td> |
43 | 43 | ||
44 | <td> | 44 | <td> |
45 | <a [href]="videoAbuse.video.url" i18n-title title="Go to the video" target="_blank" rel="noopener noreferrer"> | 45 | <a [href]="getVideoUrl(videoAbuse)" i18n-title title="Go to the video" target="_blank" rel="noopener noreferrer"> |
46 | {{ videoAbuse.video.name }} | 46 | {{ videoAbuse.video.name }} |
47 | </a> | 47 | </a> |
48 | </td> | 48 | </td> |
diff --git a/client/src/app/+admin/video-abuses/video-abuse-list/video-abuse-list.component.ts b/client/src/app/+admin/video-abuses/video-abuse-list/video-abuse-list.component.ts index a850c0ec2..377e9c80f 100644 --- a/client/src/app/+admin/video-abuses/video-abuse-list/video-abuse-list.component.ts +++ b/client/src/app/+admin/video-abuses/video-abuse-list/video-abuse-list.component.ts | |||
@@ -8,6 +8,7 @@ import { I18n } from '@ngx-translate/i18n-polyfill' | |||
8 | import { DropdownAction } from '@app/shared/buttons/action-dropdown.component' | 8 | import { DropdownAction } from '@app/shared/buttons/action-dropdown.component' |
9 | import { ConfirmService } from '@app/core' | 9 | import { ConfirmService } from '@app/core' |
10 | import { ModerationCommentModalComponent } from './moderation-comment-modal.component' | 10 | import { ModerationCommentModalComponent } from './moderation-comment-modal.component' |
11 | import { Video } from '@app/shared/video/video.model' | ||
11 | 12 | ||
12 | @Component({ | 13 | @Component({ |
13 | selector: 'my-video-abuse-list', | 14 | selector: 'my-video-abuse-list', |
@@ -79,6 +80,10 @@ export class VideoAbuseListComponent extends RestTable implements OnInit { | |||
79 | return videoAbuse.state.id === VideoAbuseState.REJECTED | 80 | return videoAbuse.state.id === VideoAbuseState.REJECTED |
80 | } | 81 | } |
81 | 82 | ||
83 | getVideoUrl (videoAbuse: VideoAbuse) { | ||
84 | return Video.buildClientUrl(videoAbuse.video.uuid) | ||
85 | } | ||
86 | |||
82 | async removeVideoAbuse (videoAbuse: VideoAbuse) { | 87 | async removeVideoAbuse (videoAbuse: VideoAbuse) { |
83 | const res = await this.confirmService.confirm(this.i18n('Do you really want to delete this abuse?'), this.i18n('Delete')) | 88 | const res = await this.confirmService.confirm(this.i18n('Do you really want to delete this abuse?'), this.i18n('Delete')) |
84 | if (res === false) return | 89 | if (res === false) return |
diff --git a/client/src/app/+admin/video-blacklist/video-blacklist-list/video-blacklist-list.component.html b/client/src/app/+admin/video-blacklist/video-blacklist-list/video-blacklist-list.component.html index 78989dc58..05b3a300c 100644 --- a/client/src/app/+admin/video-blacklist/video-blacklist-list/video-blacklist-list.component.html +++ b/client/src/app/+admin/video-blacklist/video-blacklist-list/video-blacklist-list.component.html | |||
@@ -10,8 +10,7 @@ | |||
10 | <tr> | 10 | <tr> |
11 | <th style="width: 40px"></th> | 11 | <th style="width: 40px"></th> |
12 | <th i18n pSortableColumn="name">Video name <p-sortIcon field="name"></p-sortIcon></th> | 12 | <th i18n pSortableColumn="name">Video name <p-sortIcon field="name"></p-sortIcon></th> |
13 | <th i18n>NSFW</th> | 13 | <th i18n>Sensitive</th> |
14 | <th i18n>UUID</th> | ||
15 | <th i18n pSortableColumn="createdAt">Date <p-sortIcon field="createdAt"></p-sortIcon></th> | 14 | <th i18n pSortableColumn="createdAt">Date <p-sortIcon field="createdAt"></p-sortIcon></th> |
16 | <th style="width: 50px;"></th> | 15 | <th style="width: 50px;"></th> |
17 | </tr> | 16 | </tr> |
@@ -25,9 +24,13 @@ | |||
25 | </span> | 24 | </span> |
26 | </td> | 25 | </td> |
27 | 26 | ||
28 | <td>{{ videoBlacklist.video.name }}</td> | 27 | <td> |
28 | <a [href]="getVideoUrl(videoBlacklist)" i18n-title title="Go to the video" target="_blank" rel="noopener noreferrer"> | ||
29 | {{ videoBlacklist.video.name }} | ||
30 | </a> | ||
31 | </td> | ||
32 | |||
29 | <td>{{ videoBlacklist.video.nsfw }}</td> | 33 | <td>{{ videoBlacklist.video.nsfw }}</td> |
30 | <td>{{ videoBlacklist.video.uuid }}</td> | ||
31 | <td>{{ videoBlacklist.createdAt }}</td> | 34 | <td>{{ videoBlacklist.createdAt }}</td> |
32 | 35 | ||
33 | <td class="action-cell"> | 36 | <td class="action-cell"> |
diff --git a/client/src/app/+admin/video-blacklist/video-blacklist-list/video-blacklist-list.component.ts b/client/src/app/+admin/video-blacklist/video-blacklist-list/video-blacklist-list.component.ts index 00b0ac57e..0618252b8 100644 --- a/client/src/app/+admin/video-blacklist/video-blacklist-list/video-blacklist-list.component.ts +++ b/client/src/app/+admin/video-blacklist/video-blacklist-list/video-blacklist-list.component.ts | |||
@@ -3,9 +3,10 @@ import { SortMeta } from 'primeng/components/common/sortmeta' | |||
3 | import { NotificationsService } from 'angular2-notifications' | 3 | import { NotificationsService } from 'angular2-notifications' |
4 | import { ConfirmService } from '../../../core' | 4 | import { ConfirmService } from '../../../core' |
5 | import { RestPagination, RestTable, VideoBlacklistService } from '../../../shared' | 5 | import { RestPagination, RestTable, VideoBlacklistService } from '../../../shared' |
6 | import { BlacklistedVideo } from '../../../../../../shared' | 6 | import { VideoBlacklist } from '../../../../../../shared' |
7 | import { I18n } from '@ngx-translate/i18n-polyfill' | 7 | import { I18n } from '@ngx-translate/i18n-polyfill' |
8 | import { DropdownAction } from '@app/shared/buttons/action-dropdown.component' | 8 | import { DropdownAction } from '@app/shared/buttons/action-dropdown.component' |
9 | import { Video } from '@app/shared/video/video.model' | ||
9 | 10 | ||
10 | @Component({ | 11 | @Component({ |
11 | selector: 'my-video-blacklist-list', | 12 | selector: 'my-video-blacklist-list', |
@@ -13,13 +14,13 @@ import { DropdownAction } from '@app/shared/buttons/action-dropdown.component' | |||
13 | styleUrls: [ './video-blacklist-list.component.scss' ] | 14 | styleUrls: [ './video-blacklist-list.component.scss' ] |
14 | }) | 15 | }) |
15 | export class VideoBlacklistListComponent extends RestTable implements OnInit { | 16 | export class VideoBlacklistListComponent extends RestTable implements OnInit { |
16 | blacklist: BlacklistedVideo[] = [] | 17 | blacklist: VideoBlacklist[] = [] |
17 | totalRecords = 0 | 18 | totalRecords = 0 |
18 | rowsPerPage = 10 | 19 | rowsPerPage = 10 |
19 | sort: SortMeta = { field: 'createdAt', order: 1 } | 20 | sort: SortMeta = { field: 'createdAt', order: 1 } |
20 | pagination: RestPagination = { count: this.rowsPerPage, start: 0 } | 21 | pagination: RestPagination = { count: this.rowsPerPage, start: 0 } |
21 | 22 | ||
22 | videoBlacklistActions: DropdownAction<BlacklistedVideo>[] = [] | 23 | videoBlacklistActions: DropdownAction<VideoBlacklist>[] = [] |
23 | 24 | ||
24 | constructor ( | 25 | constructor ( |
25 | private notificationsService: NotificationsService, | 26 | private notificationsService: NotificationsService, |
@@ -41,7 +42,11 @@ export class VideoBlacklistListComponent extends RestTable implements OnInit { | |||
41 | this.loadSort() | 42 | this.loadSort() |
42 | } | 43 | } |
43 | 44 | ||
44 | async removeVideoFromBlacklist (entry: BlacklistedVideo) { | 45 | getVideoUrl (videoBlacklist: VideoBlacklist) { |
46 | return Video.buildClientUrl(videoBlacklist.video.uuid) | ||
47 | } | ||
48 | |||
49 | async removeVideoFromBlacklist (entry: VideoBlacklist) { | ||
45 | const confirmMessage = this.i18n( | 50 | const confirmMessage = this.i18n( |
46 | 'Do you really want to remove this video from the blacklist? It will be available again in the videos list.' | 51 | 'Do you really want to remove this video from the blacklist? It will be available again in the videos list.' |
47 | ) | 52 | ) |
diff --git a/client/src/app/+my-account/my-account-videos/my-account-videos.component.html b/client/src/app/+my-account/my-account-videos/my-account-videos.component.html index 4823e2db9..8a6cb5c32 100644 --- a/client/src/app/+my-account/my-account-videos/my-account-videos.component.html +++ b/client/src/app/+my-account/my-account-videos/my-account-videos.component.html | |||
@@ -18,6 +18,10 @@ | |||
18 | <a class="video-info-name" [routerLink]="['/videos/watch', video.uuid]" [attr.title]="video.name">{{ video.name }}</a> | 18 | <a class="video-info-name" [routerLink]="['/videos/watch', video.uuid]" [attr.title]="video.name">{{ video.name }}</a> |
19 | <span i18n class="video-info-date-views">{{ video.createdAt | myFromNow }} - {{ video.views | myNumberFormatter }} views</span> | 19 | <span i18n class="video-info-date-views">{{ video.createdAt | myFromNow }} - {{ video.views | myNumberFormatter }} views</span> |
20 | <div class="video-info-private">{{ video.privacy.label }}{{ getStateLabel(video) }}</div> | 20 | <div class="video-info-private">{{ video.privacy.label }}{{ getStateLabel(video) }}</div> |
21 | <div *ngIf="video.blacklisted" class="video-info-blacklisted"> | ||
22 | <span class="blacklisted-label" i18n>Blacklisted</span> | ||
23 | <span class="blacklisted-reason" *ngIf="video.blacklistedReason">{{ video.blacklistedReason }}</span> | ||
24 | </div> | ||
21 | </div> | 25 | </div> |
22 | 26 | ||
23 | <!-- Display only once --> | 27 | <!-- Display only once --> |
diff --git a/client/src/app/+my-account/my-account-videos/my-account-videos.component.scss b/client/src/app/+my-account/my-account-videos/my-account-videos.component.scss index 9df28f472..64a04fa20 100644 --- a/client/src/app/+my-account/my-account-videos/my-account-videos.component.scss +++ b/client/src/app/+my-account/my-account-videos/my-account-videos.component.scss | |||
@@ -76,12 +76,25 @@ | |||
76 | font-weight: $font-semibold; | 76 | font-weight: $font-semibold; |
77 | } | 77 | } |
78 | 78 | ||
79 | .video-info-date-views, .video-info-private { | 79 | .video-info-date-views, |
80 | .video-info-private, | ||
81 | .video-info-blacklisted { | ||
80 | font-size: 13px; | 82 | font-size: 13px; |
81 | 83 | ||
82 | &.video-info-private { | 84 | &.video-info-private, |
85 | &.video-info-blacklisted .blacklisted-label { | ||
83 | font-weight: $font-semibold; | 86 | font-weight: $font-semibold; |
84 | } | 87 | } |
88 | |||
89 | &.video-info-blacklisted { | ||
90 | color: red; | ||
91 | |||
92 | .blacklisted-reason { | ||
93 | &::before { | ||
94 | content: ' - '; | ||
95 | } | ||
96 | } | ||
97 | } | ||
85 | } | 98 | } |
86 | } | 99 | } |
87 | 100 | ||
diff --git a/client/src/app/+page-not-found/page-not-found.component.scss b/client/src/app/+page-not-found/page-not-found.component.scss index 53b6142e1..f3f0354a3 100644 --- a/client/src/app/+page-not-found/page-not-found.component.scss +++ b/client/src/app/+page-not-found/page-not-found.component.scss | |||
@@ -2,7 +2,7 @@ div { | |||
2 | height: 100%; | 2 | height: 100%; |
3 | width: 100%; | 3 | width: 100%; |
4 | text-align: center; | 4 | text-align: center; |
5 | margin-top: 150px; | 5 | padding-top: 150px; |
6 | 6 | ||
7 | font-size: 32px; | 7 | font-size: 32px; |
8 | } \ No newline at end of file | 8 | } \ No newline at end of file |
diff --git a/client/src/app/shared/video-blacklist/video-blacklist.service.ts b/client/src/app/shared/video-blacklist/video-blacklist.service.ts index a014260b1..7d39fd4f2 100644 --- a/client/src/app/shared/video-blacklist/video-blacklist.service.ts +++ b/client/src/app/shared/video-blacklist/video-blacklist.service.ts | |||
@@ -3,7 +3,7 @@ import { HttpClient, HttpParams } from '@angular/common/http' | |||
3 | import { Injectable } from '@angular/core' | 3 | import { Injectable } from '@angular/core' |
4 | import { SortMeta } from 'primeng/components/common/sortmeta' | 4 | import { SortMeta } from 'primeng/components/common/sortmeta' |
5 | import { Observable } from 'rxjs' | 5 | import { Observable } from 'rxjs' |
6 | import { BlacklistedVideo, ResultList } from '../../../../../shared' | 6 | import { VideoBlacklist, ResultList } from '../../../../../shared' |
7 | import { environment } from '../../../environments/environment' | 7 | import { environment } from '../../../environments/environment' |
8 | import { RestExtractor, RestPagination, RestService } from '../rest' | 8 | import { RestExtractor, RestPagination, RestService } from '../rest' |
9 | 9 | ||
@@ -17,11 +17,11 @@ export class VideoBlacklistService { | |||
17 | private restExtractor: RestExtractor | 17 | private restExtractor: RestExtractor |
18 | ) {} | 18 | ) {} |
19 | 19 | ||
20 | listBlacklist (pagination: RestPagination, sort: SortMeta): Observable<ResultList<BlacklistedVideo>> { | 20 | listBlacklist (pagination: RestPagination, sort: SortMeta): Observable<ResultList<VideoBlacklist>> { |
21 | let params = new HttpParams() | 21 | let params = new HttpParams() |
22 | params = this.restService.addRestGetParams(params, pagination, sort) | 22 | params = this.restService.addRestGetParams(params, pagination, sort) |
23 | 23 | ||
24 | return this.authHttp.get<ResultList<BlacklistedVideo>>(VideoBlacklistService.BASE_VIDEOS_URL + 'blacklist', { params }) | 24 | return this.authHttp.get<ResultList<VideoBlacklist>>(VideoBlacklistService.BASE_VIDEOS_URL + 'blacklist', { params }) |
25 | .pipe( | 25 | .pipe( |
26 | map(res => this.restExtractor.convertResultListDateToHuman(res)), | 26 | map(res => this.restExtractor.convertResultListDateToHuman(res)), |
27 | catchError(res => this.restExtractor.handleError(res)) | 27 | catchError(res => this.restExtractor.handleError(res)) |
diff --git a/client/src/app/shared/video/video-details.model.ts b/client/src/app/shared/video/video-details.model.ts index bdcc0bbba..d346f985c 100644 --- a/client/src/app/shared/video/video-details.model.ts +++ b/client/src/app/shared/video/video-details.model.ts | |||
@@ -44,7 +44,11 @@ export class VideoDetails extends Video implements VideoDetailsServerModel { | |||
44 | } | 44 | } |
45 | 45 | ||
46 | isBlackistableBy (user: AuthUser) { | 46 | isBlackistableBy (user: AuthUser) { |
47 | return user && user.hasRight(UserRight.MANAGE_VIDEO_BLACKLIST) === true | 47 | return this.blacklisted !== true && user && user.hasRight(UserRight.MANAGE_VIDEO_BLACKLIST) === true |
48 | } | ||
49 | |||
50 | isUnblacklistableBy (user: AuthUser) { | ||
51 | return this.blacklisted === true && user && user.hasRight(UserRight.MANAGE_VIDEO_BLACKLIST) === true | ||
48 | } | 52 | } |
49 | 53 | ||
50 | isUpdatableBy (user: AuthUser) { | 54 | isUpdatableBy (user: AuthUser) { |
diff --git a/client/src/app/shared/video/video.model.ts b/client/src/app/shared/video/video.model.ts index 6b1a299ea..ec0afcccb 100644 --- a/client/src/app/shared/video/video.model.ts +++ b/client/src/app/shared/video/video.model.ts | |||
@@ -41,6 +41,8 @@ export class Video implements VideoServerModel { | |||
41 | waitTranscoding?: boolean | 41 | waitTranscoding?: boolean |
42 | state?: VideoConstant<VideoState> | 42 | state?: VideoConstant<VideoState> |
43 | scheduledUpdate?: VideoScheduleUpdate | 43 | scheduledUpdate?: VideoScheduleUpdate |
44 | blacklisted?: boolean | ||
45 | blacklistedReason?: string | ||
44 | 46 | ||
45 | account: { | 47 | account: { |
46 | id: number | 48 | id: number |
@@ -62,6 +64,10 @@ export class Video implements VideoServerModel { | |||
62 | avatar: Avatar | 64 | avatar: Avatar |
63 | } | 65 | } |
64 | 66 | ||
67 | static buildClientUrl (videoUUID: string) { | ||
68 | return '/videos/watch/' + videoUUID | ||
69 | } | ||
70 | |||
65 | private static createDurationString (duration: number) { | 71 | private static createDurationString (duration: number) { |
66 | const hours = Math.floor(duration / 3600) | 72 | const hours = Math.floor(duration / 3600) |
67 | const minutes = Math.floor((duration % 3600) / 60) | 73 | const minutes = Math.floor((duration % 3600) / 60) |
@@ -116,6 +122,9 @@ export class Video implements VideoServerModel { | |||
116 | 122 | ||
117 | this.scheduledUpdate = hash.scheduledUpdate | 123 | this.scheduledUpdate = hash.scheduledUpdate |
118 | if (this.state) this.state.label = peertubeTranslate(this.state.label, translations) | 124 | if (this.state) this.state.label = peertubeTranslate(this.state.label, translations) |
125 | |||
126 | this.blacklisted = hash.blacklisted | ||
127 | this.blacklistedReason = hash.blacklistedReason | ||
119 | } | 128 | } |
120 | 129 | ||
121 | isVideoNSFWForUser (user: User, serverConfig: ServerConfig) { | 130 | isVideoNSFWForUser (user: User, serverConfig: ServerConfig) { |
diff --git a/client/src/app/videos/+video-watch/video-watch.component.html b/client/src/app/videos/+video-watch/video-watch.component.html index f82f1c554..8d4a4a5ca 100644 --- a/client/src/app/videos/+video-watch/video-watch.component.html +++ b/client/src/app/videos/+video-watch/video-watch.component.html | |||
@@ -1,4 +1,4 @@ | |||
1 | <div class="row"> | 1 | <div class="root-row row"> |
2 | <!-- We need the video container for videojs so we just hide it --> | 2 | <!-- We need the video container for videojs so we just hide it --> |
3 | <div id="video-element-wrapper"> | 3 | <div id="video-element-wrapper"> |
4 | <div *ngIf="remoteServerDown" class="remote-server-down"> | 4 | <div *ngIf="remoteServerDown" class="remote-server-down"> |
@@ -17,7 +17,12 @@ | |||
17 | </div> | 17 | </div> |
18 | 18 | ||
19 | <div i18n class="alert alert-info" *ngIf="hasVideoScheduledPublication()"> | 19 | <div i18n class="alert alert-info" *ngIf="hasVideoScheduledPublication()"> |
20 | This video will be published on {{ video.scheduledUpdate.updateAt | date: 'full' }} | 20 | This video will be published on {{ video.scheduledUpdate.updateAt | date: 'full' }}. |
21 | </div> | ||
22 | |||
23 | <div class="alert alert-danger" *ngIf="video?.blacklisted"> | ||
24 | <div class="blacklisted-label" i18n>This video is blacklisted.</div> | ||
25 | {{ video.blacklistedReason }} | ||
21 | </div> | 26 | </div> |
22 | 27 | ||
23 | <!-- Video information --> | 28 | <!-- Video information --> |
@@ -98,6 +103,10 @@ | |||
98 | <span class="icon icon-blacklist"></span> <ng-container i18n>Blacklist</ng-container> | 103 | <span class="icon icon-blacklist"></span> <ng-container i18n>Blacklist</ng-container> |
99 | </a> | 104 | </a> |
100 | 105 | ||
106 | <a *ngIf="isVideoUnblacklistable()" class="dropdown-item" i18n-title title="Unblacklist this video" href="#" (click)="unblacklistVideo($event)"> | ||
107 | <span class="icon icon-unblacklist"></span> <ng-container i18n>Unblacklist</ng-container> | ||
108 | </a> | ||
109 | |||
101 | <a *ngIf="isVideoRemovable()" class="dropdown-item" i18n-title title="Delete this video" href="#" (click)="removeVideo($event)"> | 110 | <a *ngIf="isVideoRemovable()" class="dropdown-item" i18n-title title="Delete this video" href="#" (click)="removeVideo($event)"> |
102 | <span class="icon icon-delete"></span> <ng-container i18n>Delete</ng-container> | 111 | <span class="icon icon-delete"></span> <ng-container i18n>Delete</ng-container> |
103 | </a> | 112 | </a> |
diff --git a/client/src/app/videos/+video-watch/video-watch.component.scss b/client/src/app/videos/+video-watch/video-watch.component.scss index e63ab7bbd..1354de32e 100644 --- a/client/src/app/videos/+video-watch/video-watch.component.scss +++ b/client/src/app/videos/+video-watch/video-watch.component.scss | |||
@@ -1,6 +1,14 @@ | |||
1 | @import '_variables'; | 1 | @import '_variables'; |
2 | @import '_mixins'; | 2 | @import '_mixins'; |
3 | 3 | ||
4 | .root-row { | ||
5 | flex-direction: column; | ||
6 | } | ||
7 | |||
8 | .blacklisted-label { | ||
9 | font-weight: $font-semibold; | ||
10 | } | ||
11 | |||
4 | #video-element-wrapper { | 12 | #video-element-wrapper { |
5 | background-color: #000; | 13 | background-color: #000; |
6 | display: flex; | 14 | display: flex; |
@@ -259,6 +267,10 @@ | |||
259 | background-image: url('../../../assets/images/video/blacklist.svg'); | 267 | background-image: url('../../../assets/images/video/blacklist.svg'); |
260 | } | 268 | } |
261 | 269 | ||
270 | &.icon-unblacklist { | ||
271 | background-image: url('../../../assets/images/global/undo.svg'); | ||
272 | } | ||
273 | |||
262 | &.icon-delete { | 274 | &.icon-delete { |
263 | background-image: url('../../../assets/images/global/delete-black.svg'); | 275 | background-image: url('../../../assets/images/global/delete-black.svg'); |
264 | } | 276 | } |
diff --git a/client/src/app/videos/+video-watch/video-watch.component.ts b/client/src/app/videos/+video-watch/video-watch.component.ts index 878655d4a..bea13ec99 100644 --- a/client/src/app/videos/+video-watch/video-watch.component.ts +++ b/client/src/app/videos/+video-watch/video-watch.component.ts | |||
@@ -121,7 +121,8 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
121 | this.videoCaptionService.listCaptions(uuid) | 121 | this.videoCaptionService.listCaptions(uuid) |
122 | ) | 122 | ) |
123 | .pipe( | 123 | .pipe( |
124 | catchError(err => this.restExtractor.redirectTo404IfNotFound(err, [ 400, 404 ])) | 124 | // If 401, the video is private or blacklisted so redirect to 404 |
125 | catchError(err => this.restExtractor.redirectTo404IfNotFound(err, [ 400, 401, 404 ])) | ||
125 | ) | 126 | ) |
126 | .subscribe(([ video, captionsResult ]) => { | 127 | .subscribe(([ video, captionsResult ]) => { |
127 | const startTime = this.route.snapshot.queryParams.start | 128 | const startTime = this.route.snapshot.queryParams.start |
@@ -217,6 +218,31 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
217 | this.videoBlacklistModal.show() | 218 | this.videoBlacklistModal.show() |
218 | } | 219 | } |
219 | 220 | ||
221 | async unblacklistVideo (event: Event) { | ||
222 | event.preventDefault() | ||
223 | |||
224 | const confirmMessage = this.i18n( | ||
225 | 'Do you really want to remove this video from the blacklist? It will be available again in the videos list.' | ||
226 | ) | ||
227 | |||
228 | const res = await this.confirmService.confirm(confirmMessage, this.i18n('Unblacklist')) | ||
229 | if (res === false) return | ||
230 | |||
231 | this.videoBlacklistService.removeVideoFromBlacklist(this.video.id).subscribe( | ||
232 | () => { | ||
233 | this.notificationsService.success( | ||
234 | this.i18n('Success'), | ||
235 | this.i18n('Video {{name}} removed from the blacklist.', { name: this.video.name }) | ||
236 | ) | ||
237 | |||
238 | this.video.blacklisted = false | ||
239 | this.video.blacklistedReason = null | ||
240 | }, | ||
241 | |||
242 | err => this.notificationsService.error(this.i18n('Error'), err.message) | ||
243 | ) | ||
244 | } | ||
245 | |||
220 | isUserLoggedIn () { | 246 | isUserLoggedIn () { |
221 | return this.authService.isLoggedIn() | 247 | return this.authService.isLoggedIn() |
222 | } | 248 | } |
@@ -229,6 +255,10 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
229 | return this.video.isBlackistableBy(this.user) | 255 | return this.video.isBlackistableBy(this.user) |
230 | } | 256 | } |
231 | 257 | ||
258 | isVideoUnblacklistable () { | ||
259 | return this.video.isUnblacklistableBy(this.user) | ||
260 | } | ||
261 | |||
232 | getVideoPoster () { | 262 | getVideoPoster () { |
233 | if (!this.video) return '' | 263 | if (!this.video) return '' |
234 | 264 | ||
diff --git a/client/src/assets/images/global/undo.svg b/client/src/assets/images/global/undo.svg new file mode 100644 index 000000000..f1cca03f7 --- /dev/null +++ b/client/src/assets/images/global/undo.svg | |||
@@ -0,0 +1,11 @@ | |||
1 | <?xml version="1.0" encoding="UTF-8"?> | ||
2 | <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"> | ||
3 | <defs></defs> | ||
4 | <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"> | ||
5 | <g id="Artboard-4" transform="translate(-180.000000, -115.000000)" fill="#000"> | ||
6 | <g id="4" transform="translate(180.000000, 115.000000)"> | ||
7 | <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> | ||
8 | </g> | ||
9 | </g> | ||
10 | </g> | ||
11 | </svg> | ||
diff --git a/server/controllers/api/videos/blacklist.ts b/server/controllers/api/videos/blacklist.ts index 358f339ed..7f803c8e9 100644 --- a/server/controllers/api/videos/blacklist.ts +++ b/server/controllers/api/videos/blacklist.ts | |||
@@ -1,5 +1,5 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { BlacklistedVideo, UserRight, VideoBlacklistCreate } from '../../../../shared' | 2 | import { VideoBlacklist, UserRight, VideoBlacklistCreate } from '../../../../shared' |
3 | import { logger } from '../../../helpers/logger' | 3 | import { logger } from '../../../helpers/logger' |
4 | import { getFormattedObjects } from '../../../helpers/utils' | 4 | import { getFormattedObjects } from '../../../helpers/utils' |
5 | import { | 5 | import { |
@@ -87,7 +87,7 @@ async function updateVideoBlacklistController (req: express.Request, res: expres | |||
87 | async function listBlacklist (req: express.Request, res: express.Response, next: express.NextFunction) { | 87 | async function listBlacklist (req: express.Request, res: express.Response, next: express.NextFunction) { |
88 | const resultList = await VideoBlacklistModel.listForApi(req.query.start, req.query.count, req.query.sort) | 88 | const resultList = await VideoBlacklistModel.listForApi(req.query.start, req.query.count, req.query.sort) |
89 | 89 | ||
90 | return res.json(getFormattedObjects<BlacklistedVideo, VideoBlacklistModel>(resultList.data, resultList.total)) | 90 | return res.json(getFormattedObjects<VideoBlacklist, VideoBlacklistModel>(resultList.data, resultList.total)) |
91 | } | 91 | } |
92 | 92 | ||
93 | async function removeVideoFromBlacklistController (req: express.Request, res: express.Response, next: express.NextFunction) { | 93 | async function removeVideoFromBlacklistController (req: express.Request, res: express.Response, next: express.NextFunction) { |
diff --git a/server/middlewares/validators/videos.ts b/server/middlewares/validators/videos.ts index 203a00876..77d601a4d 100644 --- a/server/middlewares/validators/videos.ts +++ b/server/middlewares/validators/videos.ts | |||
@@ -35,6 +35,8 @@ import { VideoShareModel } from '../../models/video/video-share' | |||
35 | import { authenticate } from '../oauth' | 35 | import { authenticate } from '../oauth' |
36 | import { areValidationErrors } from './utils' | 36 | import { areValidationErrors } from './utils' |
37 | import { cleanUpReqFiles } from '../../helpers/utils' | 37 | import { cleanUpReqFiles } from '../../helpers/utils' |
38 | import { VideoModel } from '../../models/video/video' | ||
39 | import { UserModel } from '../../models/account/user' | ||
38 | 40 | ||
39 | const videosAddValidator = getCommonVideoAttributes().concat([ | 41 | const videosAddValidator = getCommonVideoAttributes().concat([ |
40 | body('videofile') | 42 | body('videofile') |
@@ -131,7 +133,25 @@ const videosGetValidator = [ | |||
131 | if (areValidationErrors(req, res)) return | 133 | if (areValidationErrors(req, res)) return |
132 | if (!await isVideoExist(req.params.id, res)) return | 134 | if (!await isVideoExist(req.params.id, res)) return |
133 | 135 | ||
134 | const video = res.locals.video | 136 | const video: VideoModel = res.locals.video |
137 | |||
138 | // Video private or blacklisted | ||
139 | if (video.privacy === VideoPrivacy.PRIVATE || video.VideoBlacklist) { | ||
140 | authenticate(req, res, () => { | ||
141 | const user: UserModel = res.locals.oauth.token.User | ||
142 | |||
143 | // Only the owner or a user that have blacklist rights can see the video | ||
144 | if (video.VideoChannel.Account.userId !== user.id && !user.hasRight(UserRight.MANAGE_VIDEO_BLACKLIST)) { | ||
145 | return res.status(403) | ||
146 | .json({ error: 'Cannot get this private or blacklisted video.' }) | ||
147 | .end() | ||
148 | } | ||
149 | |||
150 | return next() | ||
151 | }) | ||
152 | |||
153 | return | ||
154 | } | ||
135 | 155 | ||
136 | // Video is public, anyone can access it | 156 | // Video is public, anyone can access it |
137 | if (video.privacy === VideoPrivacy.PUBLIC) return next() | 157 | if (video.privacy === VideoPrivacy.PUBLIC) return next() |
@@ -143,17 +163,6 @@ const videosGetValidator = [ | |||
143 | // Don't leak this unlisted video | 163 | // Don't leak this unlisted video |
144 | return res.status(404).end() | 164 | return res.status(404).end() |
145 | } | 165 | } |
146 | |||
147 | // Video is private, check the user | ||
148 | authenticate(req, res, () => { | ||
149 | if (video.VideoChannel.Account.userId !== res.locals.oauth.token.User.id) { | ||
150 | return res.status(403) | ||
151 | .json({ error: 'Cannot get this private video of another user' }) | ||
152 | .end() | ||
153 | } | ||
154 | |||
155 | return next() | ||
156 | }) | ||
157 | } | 166 | } |
158 | ] | 167 | ] |
159 | 168 | ||
diff --git a/server/models/video/video-abuse.ts b/server/models/video/video-abuse.ts index 10a191372..dbb88ca45 100644 --- a/server/models/video/video-abuse.ts +++ b/server/models/video/video-abuse.ts | |||
@@ -137,7 +137,6 @@ export class VideoAbuseModel extends Model<VideoAbuseModel> { | |||
137 | video: { | 137 | video: { |
138 | id: this.Video.id, | 138 | id: this.Video.id, |
139 | uuid: this.Video.uuid, | 139 | uuid: this.Video.uuid, |
140 | url: this.Video.url, | ||
141 | name: this.Video.name | 140 | name: this.Video.name |
142 | }, | 141 | }, |
143 | createdAt: this.createdAt | 142 | createdAt: this.createdAt |
diff --git a/server/models/video/video-blacklist.ts b/server/models/video/video-blacklist.ts index 1b8a338cb..eabc37ef0 100644 --- a/server/models/video/video-blacklist.ts +++ b/server/models/video/video-blacklist.ts | |||
@@ -16,7 +16,7 @@ import { getSortOnModel, throwIfNotValid } from '../utils' | |||
16 | import { VideoModel } from './video' | 16 | import { VideoModel } from './video' |
17 | import { isVideoBlacklistReasonValid } from '../../helpers/custom-validators/video-blacklist' | 17 | import { isVideoBlacklistReasonValid } from '../../helpers/custom-validators/video-blacklist' |
18 | import { Emailer } from '../../lib/emailer' | 18 | import { Emailer } from '../../lib/emailer' |
19 | import { BlacklistedVideo } from '../../../shared/models/videos' | 19 | import { VideoBlacklist } from '../../../shared/models/videos' |
20 | import { CONSTRAINTS_FIELDS } from '../../initializers' | 20 | import { CONSTRAINTS_FIELDS } from '../../initializers' |
21 | 21 | ||
22 | @Table({ | 22 | @Table({ |
@@ -68,7 +68,12 @@ export class VideoBlacklistModel extends Model<VideoBlacklistModel> { | |||
68 | offset: start, | 68 | offset: start, |
69 | limit: count, | 69 | limit: count, |
70 | order: getSortOnModel(sort.sortModel, sort.sortValue), | 70 | order: getSortOnModel(sort.sortModel, sort.sortValue), |
71 | include: [ { model: VideoModel } ] | 71 | include: [ |
72 | { | ||
73 | model: VideoModel, | ||
74 | required: true | ||
75 | } | ||
76 | ] | ||
72 | } | 77 | } |
73 | 78 | ||
74 | return VideoBlacklistModel.findAndCountAll(query) | 79 | return VideoBlacklistModel.findAndCountAll(query) |
@@ -90,7 +95,7 @@ export class VideoBlacklistModel extends Model<VideoBlacklistModel> { | |||
90 | return VideoBlacklistModel.findOne(query) | 95 | return VideoBlacklistModel.findOne(query) |
91 | } | 96 | } |
92 | 97 | ||
93 | toFormattedJSON (): BlacklistedVideo { | 98 | toFormattedJSON (): VideoBlacklist { |
94 | const video = this.Video | 99 | const video = this.Video |
95 | 100 | ||
96 | return { | 101 | return { |
diff --git a/server/models/video/video.ts b/server/models/video/video.ts index f3a900bc9..b13dee403 100644 --- a/server/models/video/video.ts +++ b/server/models/video/video.ts | |||
@@ -127,7 +127,8 @@ export enum ScopeNames { | |||
127 | WITH_ACCOUNT_DETAILS = 'WITH_ACCOUNT_DETAILS', | 127 | WITH_ACCOUNT_DETAILS = 'WITH_ACCOUNT_DETAILS', |
128 | WITH_TAGS = 'WITH_TAGS', | 128 | WITH_TAGS = 'WITH_TAGS', |
129 | WITH_FILES = 'WITH_FILES', | 129 | WITH_FILES = 'WITH_FILES', |
130 | WITH_SCHEDULED_UPDATE = 'WITH_SCHEDULED_UPDATE' | 130 | WITH_SCHEDULED_UPDATE = 'WITH_SCHEDULED_UPDATE', |
131 | WITH_BLACKLISTED = 'WITH_BLACKLISTED' | ||
131 | } | 132 | } |
132 | 133 | ||
133 | type AvailableForListOptions = { | 134 | type AvailableForListOptions = { |
@@ -374,6 +375,15 @@ type AvailableForListOptions = { | |||
374 | [ScopeNames.WITH_TAGS]: { | 375 | [ScopeNames.WITH_TAGS]: { |
375 | include: [ () => TagModel ] | 376 | include: [ () => TagModel ] |
376 | }, | 377 | }, |
378 | [ScopeNames.WITH_BLACKLISTED]: { | ||
379 | include: [ | ||
380 | { | ||
381 | attributes: [ 'id', 'reason' ], | ||
382 | model: () => VideoBlacklistModel, | ||
383 | required: false | ||
384 | } | ||
385 | ] | ||
386 | }, | ||
377 | [ScopeNames.WITH_FILES]: { | 387 | [ScopeNames.WITH_FILES]: { |
378 | include: [ | 388 | include: [ |
379 | { | 389 | { |
@@ -1004,7 +1014,13 @@ export class VideoModel extends Model<VideoModel> { | |||
1004 | } | 1014 | } |
1005 | 1015 | ||
1006 | return VideoModel | 1016 | return VideoModel |
1007 | .scope([ ScopeNames.WITH_TAGS, ScopeNames.WITH_FILES, ScopeNames.WITH_ACCOUNT_DETAILS, ScopeNames.WITH_SCHEDULED_UPDATE ]) | 1017 | .scope([ |
1018 | ScopeNames.WITH_TAGS, | ||
1019 | ScopeNames.WITH_BLACKLISTED, | ||
1020 | ScopeNames.WITH_FILES, | ||
1021 | ScopeNames.WITH_ACCOUNT_DETAILS, | ||
1022 | ScopeNames.WITH_SCHEDULED_UPDATE | ||
1023 | ]) | ||
1008 | .findById(id, options) | 1024 | .findById(id, options) |
1009 | } | 1025 | } |
1010 | 1026 | ||
@@ -1030,7 +1046,13 @@ export class VideoModel extends Model<VideoModel> { | |||
1030 | } | 1046 | } |
1031 | 1047 | ||
1032 | return VideoModel | 1048 | return VideoModel |
1033 | .scope([ ScopeNames.WITH_TAGS, ScopeNames.WITH_FILES, ScopeNames.WITH_ACCOUNT_DETAILS, ScopeNames.WITH_SCHEDULED_UPDATE ]) | 1049 | .scope([ |
1050 | ScopeNames.WITH_TAGS, | ||
1051 | ScopeNames.WITH_BLACKLISTED, | ||
1052 | ScopeNames.WITH_FILES, | ||
1053 | ScopeNames.WITH_ACCOUNT_DETAILS, | ||
1054 | ScopeNames.WITH_SCHEDULED_UPDATE | ||
1055 | ]) | ||
1034 | .findOne(options) | 1056 | .findOne(options) |
1035 | } | 1057 | } |
1036 | 1058 | ||
@@ -1276,7 +1298,8 @@ export class VideoModel extends Model<VideoModel> { | |||
1276 | toFormattedDetailsJSON (): VideoDetails { | 1298 | toFormattedDetailsJSON (): VideoDetails { |
1277 | const formattedJson = this.toFormattedJSON({ | 1299 | const formattedJson = this.toFormattedJSON({ |
1278 | additionalAttributes: { | 1300 | additionalAttributes: { |
1279 | scheduledUpdate: true | 1301 | scheduledUpdate: true, |
1302 | blacklistInfo: true | ||
1280 | } | 1303 | } |
1281 | }) | 1304 | }) |
1282 | 1305 | ||
diff --git a/server/tests/utils/videos/video-blacklist.ts b/server/tests/utils/videos/video-blacklist.ts index 7819f4b25..2c176fde0 100644 --- a/server/tests/utils/videos/video-blacklist.ts +++ b/server/tests/utils/videos/video-blacklist.ts | |||
@@ -19,7 +19,8 @@ function updateVideoBlacklist (url: string, token: string, videoId: number, reas | |||
19 | .send({ reason }) | 19 | .send({ reason }) |
20 | .set('Accept', 'application/json') | 20 | .set('Accept', 'application/json') |
21 | .set('Authorization', 'Bearer ' + token) | 21 | .set('Authorization', 'Bearer ' + token) |
22 | .expect(specialStatus)} | 22 | .expect(specialStatus) |
23 | } | ||
23 | 24 | ||
24 | function removeVideoFromBlacklist (url: string, token: string, videoId: number | string, specialStatus = 204) { | 25 | function removeVideoFromBlacklist (url: string, token: string, videoId: number | string, specialStatus = 204) { |
25 | const path = '/api/v1/videos/' + videoId + '/blacklist' | 26 | const path = '/api/v1/videos/' + videoId + '/blacklist' |
diff --git a/shared/models/users/user-right.enum.ts b/shared/models/users/user-right.enum.ts index ff6ec61f4..142a0474b 100644 --- a/shared/models/users/user-right.enum.ts +++ b/shared/models/users/user-right.enum.ts | |||
@@ -1,11 +1,14 @@ | |||
1 | export enum UserRight { | 1 | export enum UserRight { |
2 | ALL, | 2 | ALL, |
3 | |||
3 | MANAGE_USERS, | 4 | MANAGE_USERS, |
4 | MANAGE_SERVER_FOLLOW, | 5 | MANAGE_SERVER_FOLLOW, |
5 | MANAGE_VIDEO_ABUSES, | 6 | MANAGE_VIDEO_ABUSES, |
6 | MANAGE_VIDEO_BLACKLIST, | ||
7 | MANAGE_JOBS, | 7 | MANAGE_JOBS, |
8 | MANAGE_CONFIGURATION, | 8 | MANAGE_CONFIGURATION, |
9 | |||
10 | MANAGE_VIDEO_BLACKLIST, | ||
11 | |||
9 | REMOVE_ANY_VIDEO, | 12 | REMOVE_ANY_VIDEO, |
10 | REMOVE_ANY_VIDEO_CHANNEL, | 13 | REMOVE_ANY_VIDEO_CHANNEL, |
11 | REMOVE_ANY_VIDEO_COMMENT, | 14 | REMOVE_ANY_VIDEO_COMMENT, |
diff --git a/shared/models/videos/video-abuse.model.ts b/shared/models/videos/video-abuse.model.ts index 1fecce037..b2319aa00 100644 --- a/shared/models/videos/video-abuse.model.ts +++ b/shared/models/videos/video-abuse.model.ts | |||
@@ -14,7 +14,6 @@ export interface VideoAbuse { | |||
14 | id: number | 14 | id: number |
15 | name: string | 15 | name: string |
16 | uuid: string | 16 | uuid: string |
17 | url: string | ||
18 | } | 17 | } |
19 | 18 | ||
20 | createdAt: Date | 19 | createdAt: Date |
diff --git a/shared/models/videos/video-blacklist.model.ts b/shared/models/videos/video-blacklist.model.ts index a060da357..ef4e5e3a2 100644 --- a/shared/models/videos/video-blacklist.model.ts +++ b/shared/models/videos/video-blacklist.model.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | export interface BlacklistedVideo { | 1 | export interface VideoBlacklist { |
2 | id: number | 2 | id: number |
3 | createdAt: Date | 3 | createdAt: Date |
4 | updatedAt: Date | 4 | updatedAt: Date |