aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorChocobozzz <me@florianbigard.com>2020-11-18 15:29:38 +0100
committerChocobozzz <me@florianbigard.com>2020-11-18 15:29:38 +0100
commit0aa52e170727ac6bdf441bcaa2353ae0b8a354ed (patch)
tree52fa047cf9970590cab1dcc7a3e5caa8eb004171
parentff2cac9fa361a3c5489078f441ed54230c045971 (diff)
downloadPeerTube-0aa52e170727ac6bdf441bcaa2353ae0b8a354ed.tar.gz
PeerTube-0aa52e170727ac6bdf441bcaa2353ae0b8a354ed.tar.zst
PeerTube-0aa52e170727ac6bdf441bcaa2353ae0b8a354ed.zip
Add ability to display all channel/account videos
-rw-r--r--client/src/app/+accounts/account-video-channels/account-video-channels.component.ts18
-rw-r--r--client/src/app/+accounts/account-videos/account-videos.component.ts20
-rw-r--r--client/src/app/+video-channels/video-channel-videos/video-channel-videos.component.ts20
-rw-r--r--client/src/app/+videos/video-list/video-local.component.ts8
-rw-r--r--client/src/app/shared/shared-main/video/video.service.ts27
-rw-r--r--client/src/app/shared/shared-video-miniature/abstract-video-list.html2
-rw-r--r--client/src/app/shared/shared-video-miniature/abstract-video-list.scss12
-rw-r--r--client/src/app/shared/shared-video-miniature/abstract-video-list.ts32
-rw-r--r--client/src/sass/bootstrap.scss4
-rw-r--r--server/helpers/custom-validators/videos.ts2
-rw-r--r--server/middlewares/validators/videos/videos.ts5
-rw-r--r--server/models/video/video-query-builder.ts2
-rw-r--r--server/models/video/video.ts2
-rw-r--r--server/tests/api/check-params/videos-filter.ts29
-rw-r--r--server/tests/api/videos/videos-filter.ts14
-rw-r--r--shared/models/videos/video-query.type.ts2
-rw-r--r--support/doc/api/openapi.yaml3
17 files changed, 161 insertions, 41 deletions
diff --git a/client/src/app/+accounts/account-video-channels/account-video-channels.component.ts b/client/src/app/+accounts/account-video-channels/account-video-channels.component.ts
index 205245675..f2beb6689 100644
--- a/client/src/app/+accounts/account-video-channels/account-video-channels.component.ts
+++ b/client/src/app/+accounts/account-video-channels/account-video-channels.component.ts
@@ -3,7 +3,7 @@ import { concatMap, map, switchMap, tap } from 'rxjs/operators'
3import { Component, OnDestroy, OnInit } from '@angular/core' 3import { Component, OnDestroy, OnInit } from '@angular/core'
4import { ComponentPagination, hasMoreItems, ScreenService, User, UserService } from '@app/core' 4import { ComponentPagination, hasMoreItems, ScreenService, User, UserService } from '@app/core'
5import { Account, AccountService, Video, VideoChannel, VideoChannelService, VideoService } from '@app/shared/shared-main' 5import { Account, AccountService, Video, VideoChannel, VideoChannelService, VideoService } from '@app/shared/shared-main'
6import { VideoSortField } from '@shared/models' 6import { NSFWPolicyType, VideoSortField } from '@shared/models'
7 7
8@Component({ 8@Component({
9 selector: 'my-account-video-channels', 9 selector: 'my-account-video-channels',
@@ -31,6 +31,7 @@ export class AccountVideoChannelsComponent implements OnInit, OnDestroy {
31 onChannelDataSubject = new Subject<any>() 31 onChannelDataSubject = new Subject<any>()
32 32
33 userMiniature: User 33 userMiniature: User
34 nsfwPolicy: NSFWPolicyType
34 35
35 private accountSub: Subscription 36 private accountSub: Subscription
36 37
@@ -52,7 +53,11 @@ export class AccountVideoChannelsComponent implements OnInit, OnDestroy {
52 }) 53 })
53 54
54 this.userService.getAnonymousOrLoggedUser() 55 this.userService.getAnonymousOrLoggedUser()
55 .subscribe(user => this.userMiniature = user) 56 .subscribe(user => {
57 this.userMiniature = user
58
59 this.nsfwPolicy = user.nsfwPolicy
60 })
56 } 61 }
57 62
58 ngOnDestroy () { 63 ngOnDestroy () {
@@ -65,7 +70,14 @@ export class AccountVideoChannelsComponent implements OnInit, OnDestroy {
65 tap(res => this.channelPagination.totalItems = res.total), 70 tap(res => this.channelPagination.totalItems = res.total),
66 switchMap(res => from(res.data)), 71 switchMap(res => from(res.data)),
67 concatMap(videoChannel => { 72 concatMap(videoChannel => {
68 return this.videoService.getVideoChannelVideos(videoChannel, this.videosPagination, this.videosSort) 73 const options = {
74 videoChannel,
75 videoPagination: this.videosPagination,
76 sort: this.videosSort,
77 nsfwPolicy: this.nsfwPolicy
78 }
79
80 return this.videoService.getVideoChannelVideos(options)
69 .pipe(map(data => ({ videoChannel, videos: data.data }))) 81 .pipe(map(data => ({ videoChannel, videos: data.data })))
70 }) 82 })
71 ) 83 )
diff --git a/client/src/app/+accounts/account-videos/account-videos.component.ts b/client/src/app/+accounts/account-videos/account-videos.component.ts
index 3134a8ee2..58d0719fd 100644
--- a/client/src/app/+accounts/account-videos/account-videos.component.ts
+++ b/client/src/app/+accounts/account-videos/account-videos.component.ts
@@ -6,6 +6,7 @@ import { AuthService, ConfirmService, LocalStorageService, Notifier, ScreenServi
6import { immutableAssign } from '@app/helpers' 6import { immutableAssign } from '@app/helpers'
7import { Account, AccountService, VideoService } from '@app/shared/shared-main' 7import { Account, AccountService, VideoService } from '@app/shared/shared-main'
8import { AbstractVideoList } from '@app/shared/shared-video-miniature' 8import { AbstractVideoList } from '@app/shared/shared-video-miniature'
9import { VideoFilter } from '@shared/models'
9 10
10@Component({ 11@Component({
11 selector: 'my-account-videos', 12 selector: 'my-account-videos',
@@ -18,6 +19,8 @@ export class AccountVideosComponent extends AbstractVideoList implements OnInit,
18 titlePage: string 19 titlePage: string
19 loadOnInit = false 20 loadOnInit = false
20 21
22 filter: VideoFilter = null
23
21 private account: Account 24 private account: Account
22 private accountSub: Subscription 25 private accountSub: Subscription
23 26
@@ -40,6 +43,8 @@ export class AccountVideosComponent extends AbstractVideoList implements OnInit,
40 ngOnInit () { 43 ngOnInit () {
41 super.ngOnInit() 44 super.ngOnInit()
42 45
46 this.enableAllFilterIfPossible()
47
43 // Parent get the account for us 48 // Parent get the account for us
44 this.accountSub = this.accountService.accountLoaded 49 this.accountSub = this.accountService.accountLoaded
45 .pipe(first()) 50 .pipe(first())
@@ -59,9 +64,16 @@ export class AccountVideosComponent extends AbstractVideoList implements OnInit,
59 64
60 getVideosObservable (page: number) { 65 getVideosObservable (page: number) {
61 const newPagination = immutableAssign(this.pagination, { currentPage: page }) 66 const newPagination = immutableAssign(this.pagination, { currentPage: page })
67 const options = {
68 account: this.account,
69 videoPagination: newPagination,
70 sort: this.sort,
71 nsfwPolicy: this.nsfwPolicy,
72 videoFilter: this.filter
73 }
62 74
63 return this.videoService 75 return this.videoService
64 .getAccountVideos(this.account, newPagination, this.sort) 76 .getAccountVideos(options)
65 .pipe( 77 .pipe(
66 tap(({ total }) => { 78 tap(({ total }) => {
67 this.titlePage = $localize`Published ${total} videos` 79 this.titlePage = $localize`Published ${total} videos`
@@ -69,6 +81,12 @@ export class AccountVideosComponent extends AbstractVideoList implements OnInit,
69 ) 81 )
70 } 82 }
71 83
84 toggleModerationDisplay () {
85 this.filter = this.buildLocalFilter(this.filter, null)
86
87 this.reloadVideos()
88 }
89
72 generateSyndicationList () { 90 generateSyndicationList () {
73 this.syndicationItems = this.videoService.getAccountFeedUrls(this.account.id) 91 this.syndicationItems = this.videoService.getAccountFeedUrls(this.account.id)
74 } 92 }
diff --git a/client/src/app/+video-channels/video-channel-videos/video-channel-videos.component.ts b/client/src/app/+video-channels/video-channel-videos/video-channel-videos.component.ts
index e1ec6bbcb..645696f48 100644
--- a/client/src/app/+video-channels/video-channel-videos/video-channel-videos.component.ts
+++ b/client/src/app/+video-channels/video-channel-videos/video-channel-videos.component.ts
@@ -6,6 +6,7 @@ import { AuthService, ConfirmService, LocalStorageService, Notifier, ScreenServi
6import { immutableAssign } from '@app/helpers' 6import { immutableAssign } from '@app/helpers'
7import { VideoChannel, VideoChannelService, VideoService } from '@app/shared/shared-main' 7import { VideoChannel, VideoChannelService, VideoService } from '@app/shared/shared-main'
8import { AbstractVideoList } from '@app/shared/shared-video-miniature' 8import { AbstractVideoList } from '@app/shared/shared-video-miniature'
9import { VideoFilter } from '@shared/models'
9 10
10@Component({ 11@Component({
11 selector: 'my-video-channel-videos', 12 selector: 'my-video-channel-videos',
@@ -18,6 +19,8 @@ export class VideoChannelVideosComponent extends AbstractVideoList implements On
18 titlePage: string 19 titlePage: string
19 loadOnInit = false 20 loadOnInit = false
20 21
22 filter: VideoFilter = null
23
21 private videoChannel: VideoChannel 24 private videoChannel: VideoChannel
22 private videoChannelSub: Subscription 25 private videoChannelSub: Subscription
23 26
@@ -46,6 +49,8 @@ export class VideoChannelVideosComponent extends AbstractVideoList implements On
46 ngOnInit () { 49 ngOnInit () {
47 super.ngOnInit() 50 super.ngOnInit()
48 51
52 this.enableAllFilterIfPossible()
53
49 // Parent get the video channel for us 54 // Parent get the video channel for us
50 this.videoChannelSub = this.videoChannelService.videoChannelLoaded 55 this.videoChannelSub = this.videoChannelService.videoChannelLoaded
51 .pipe(first()) 56 .pipe(first())
@@ -65,9 +70,16 @@ export class VideoChannelVideosComponent extends AbstractVideoList implements On
65 70
66 getVideosObservable (page: number) { 71 getVideosObservable (page: number) {
67 const newPagination = immutableAssign(this.pagination, { currentPage: page }) 72 const newPagination = immutableAssign(this.pagination, { currentPage: page })
73 const options = {
74 videoChannel: this.videoChannel,
75 videoPagination: newPagination,
76 sort: this.sort,
77 nsfwPolicy: this.nsfwPolicy,
78 videoFilter: this.filter
79 }
68 80
69 return this.videoService 81 return this.videoService
70 .getVideoChannelVideos(this.videoChannel, newPagination, this.sort, this.nsfwPolicy) 82 .getVideoChannelVideos(options)
71 .pipe( 83 .pipe(
72 tap(({ total }) => { 84 tap(({ total }) => {
73 this.titlePage = total === 1 85 this.titlePage = total === 1
@@ -80,4 +92,10 @@ export class VideoChannelVideosComponent extends AbstractVideoList implements On
80 generateSyndicationList () { 92 generateSyndicationList () {
81 this.syndicationItems = this.videoService.getVideoChannelFeedUrls(this.videoChannel.id) 93 this.syndicationItems = this.videoService.getVideoChannelFeedUrls(this.videoChannel.id)
82 } 94 }
95
96 toggleModerationDisplay () {
97 this.filter = this.buildLocalFilter(this.filter, null)
98
99 this.reloadVideos()
100 }
83} 101}
diff --git a/client/src/app/+videos/video-list/video-local.component.ts b/client/src/app/+videos/video-list/video-local.component.ts
index 07063d4d4..20dd61db9 100644
--- a/client/src/app/+videos/video-list/video-local.component.ts
+++ b/client/src/app/+videos/video-list/video-local.component.ts
@@ -39,11 +39,7 @@ export class VideoLocalComponent extends AbstractVideoList implements OnInit, On
39 ngOnInit () { 39 ngOnInit () {
40 super.ngOnInit() 40 super.ngOnInit()
41 41
42 if (this.authService.isLoggedIn()) { 42 this.enableAllFilterIfPossible()
43 const user = this.authService.getUser()
44 this.displayModerationBlock = user.hasRight(UserRight.SEE_ALL_VIDEOS)
45 }
46
47 this.generateSyndicationList() 43 this.generateSyndicationList()
48 } 44 }
49 45
@@ -77,7 +73,7 @@ export class VideoLocalComponent extends AbstractVideoList implements OnInit, On
77 } 73 }
78 74
79 toggleModerationDisplay () { 75 toggleModerationDisplay () {
80 this.filter = this.filter === 'local' ? 'all-local' as 'all-local' : 'local' as 'local' 76 this.filter = this.buildLocalFilter(this.filter, 'local')
81 77
82 this.reloadVideos() 78 this.reloadVideos()
83 } 79 }
diff --git a/client/src/app/shared/shared-main/video/video.service.ts b/client/src/app/shared/shared-main/video/video.service.ts
index 0e2d36081..c8a3ec043 100644
--- a/client/src/app/shared/shared-main/video/video.service.ts
+++ b/client/src/app/shared/shared-main/video/video.service.ts
@@ -134,16 +134,28 @@ export class VideoService implements VideosProvider {
134 ) 134 )
135 } 135 }
136 136
137 getAccountVideos ( 137 getAccountVideos (parameters: {
138 account: Account, 138 account: Account,
139 videoPagination: ComponentPaginationLight, 139 videoPagination: ComponentPaginationLight,
140 sort: VideoSortField 140 sort: VideoSortField
141 ): Observable<ResultList<Video>> { 141 nsfwPolicy?: NSFWPolicyType
142 videoFilter?: VideoFilter
143 }): Observable<ResultList<Video>> {
144 const { account, videoPagination, sort, videoFilter, nsfwPolicy } = parameters
145
142 const pagination = this.restService.componentPaginationToRestPagination(videoPagination) 146 const pagination = this.restService.componentPaginationToRestPagination(videoPagination)
143 147
144 let params = new HttpParams() 148 let params = new HttpParams()
145 params = this.restService.addRestGetParams(params, pagination, sort) 149 params = this.restService.addRestGetParams(params, pagination, sort)
146 150
151 if (nsfwPolicy) {
152 params = params.set('nsfw', this.nsfwPolicyToParam(nsfwPolicy))
153 }
154
155 if (videoFilter) {
156 params = params.set('filter', videoFilter)
157 }
158
147 return this.authHttp 159 return this.authHttp
148 .get<ResultList<Video>>(AccountService.BASE_ACCOUNT_URL + account.nameWithHost + '/videos', { params }) 160 .get<ResultList<Video>>(AccountService.BASE_ACCOUNT_URL + account.nameWithHost + '/videos', { params })
149 .pipe( 161 .pipe(
@@ -152,12 +164,15 @@ export class VideoService implements VideosProvider {
152 ) 164 )
153 } 165 }
154 166
155 getVideoChannelVideos ( 167 getVideoChannelVideos (parameters: {
156 videoChannel: VideoChannel, 168 videoChannel: VideoChannel,
157 videoPagination: ComponentPaginationLight, 169 videoPagination: ComponentPaginationLight,
158 sort: VideoSortField, 170 sort: VideoSortField,
159 nsfwPolicy?: NSFWPolicyType 171 nsfwPolicy?: NSFWPolicyType
160 ): Observable<ResultList<Video>> { 172 videoFilter?: VideoFilter
173 }): Observable<ResultList<Video>> {
174 const { videoChannel, videoPagination, sort, nsfwPolicy, videoFilter } = parameters
175
161 const pagination = this.restService.componentPaginationToRestPagination(videoPagination) 176 const pagination = this.restService.componentPaginationToRestPagination(videoPagination)
162 177
163 let params = new HttpParams() 178 let params = new HttpParams()
@@ -167,6 +182,10 @@ export class VideoService implements VideosProvider {
167 params = params.set('nsfw', this.nsfwPolicyToParam(nsfwPolicy)) 182 params = params.set('nsfw', this.nsfwPolicyToParam(nsfwPolicy))
168 } 183 }
169 184
185 if (videoFilter) {
186 params = params.set('filter', videoFilter)
187 }
188
170 return this.authHttp 189 return this.authHttp
171 .get<ResultList<Video>>(VideoChannelService.BASE_VIDEO_CHANNEL_URL + videoChannel.nameWithHost + '/videos', { params }) 190 .get<ResultList<Video>>(VideoChannelService.BASE_VIDEO_CHANNEL_URL + videoChannel.nameWithHost + '/videos', { params })
172 .pipe( 191 .pipe(
diff --git a/client/src/app/shared/shared-video-miniature/abstract-video-list.html b/client/src/app/shared/shared-video-miniature/abstract-video-list.html
index 08962dff8..b1ac757db 100644
--- a/client/src/app/shared/shared-video-miniature/abstract-video-list.html
+++ b/client/src/app/shared/shared-video-miniature/abstract-video-list.html
@@ -21,7 +21,7 @@
21 <div class="dropdown-item"> 21 <div class="dropdown-item">
22 <my-peertube-checkbox 22 <my-peertube-checkbox
23 (change)="toggleModerationDisplay()" 23 (change)="toggleModerationDisplay()"
24 inputName="display-unlisted-private" i18n-labelText labelText="Display unlisted and private videos" 24 inputName="display-unlisted-private" i18n-labelText labelText="Display all videos (private, unlisted or not yet published)"
25 ></my-peertube-checkbox> 25 ></my-peertube-checkbox>
26 </div> 26 </div>
27 </div> 27 </div>
diff --git a/client/src/app/shared/shared-video-miniature/abstract-video-list.scss b/client/src/app/shared/shared-video-miniature/abstract-video-list.scss
index 7841b60f7..9077e2f75 100644
--- a/client/src/app/shared/shared-video-miniature/abstract-video-list.scss
+++ b/client/src/app/shared/shared-video-miniature/abstract-video-list.scss
@@ -31,13 +31,21 @@ $iconSize: 16px;
31 31
32 .moderation-block { 32 .moderation-block {
33 div { 33 div {
34 @include button-with-icon($iconSize, 3px, -1px); 34 @include button-with-icon($iconSize, 3px, -2px);
35 } 35 }
36 36
37 margin-left: .2rem; 37 margin-left: .4rem;
38 display: flex; 38 display: flex;
39 justify-content: flex-end; 39 justify-content: flex-end;
40 align-items: center; 40 align-items: center;
41
42 .dropdown-item {
43 padding: 0;
44
45 ::ng-deep my-peertube-checkbox label {
46 padding: 3px 15px;
47 }
48 }
41 } 49 }
42} 50}
43 51
diff --git a/client/src/app/shared/shared-video-miniature/abstract-video-list.ts b/client/src/app/shared/shared-video-miniature/abstract-video-list.ts
index da05e15fb..2219ced30 100644
--- a/client/src/app/shared/shared-video-miniature/abstract-video-list.ts
+++ b/client/src/app/shared/shared-video-miniature/abstract-video-list.ts
@@ -15,7 +15,7 @@ import {
15import { DisableForReuseHook } from '@app/core/routing/disable-for-reuse-hook' 15import { DisableForReuseHook } from '@app/core/routing/disable-for-reuse-hook'
16import { GlobalIconName } from '@app/shared/shared-icons' 16import { GlobalIconName } from '@app/shared/shared-icons'
17import { isLastMonth, isLastWeek, isThisMonth, isToday, isYesterday } from '@shared/core-utils/miscs/date' 17import { isLastMonth, isLastWeek, isThisMonth, isToday, isYesterday } from '@shared/core-utils/miscs/date'
18import { ServerConfig, VideoSortField } from '@shared/models' 18import { ServerConfig, UserRight, VideoFilter, VideoSortField } from '@shared/models'
19import { NSFWPolicyType } from '@shared/models/videos/nsfw-policy.type' 19import { NSFWPolicyType } from '@shared/models/videos/nsfw-policy.type'
20import { Syndication, Video } from '../shared-main' 20import { Syndication, Video } from '../shared-main'
21import { MiniatureDisplayOptions, OwnerDisplayType } from './video-miniature.component' 21import { MiniatureDisplayOptions, OwnerDisplayType } from './video-miniature.component'
@@ -205,10 +205,6 @@ export abstract class AbstractVideoList implements OnInit, OnDestroy, DisableFor
205 this.loadMoreVideos(true) 205 this.loadMoreVideos(true)
206 } 206 }
207 207
208 toggleModerationDisplay () {
209 throw new Error('toggleModerationDisplay is not implemented')
210 }
211
212 removeVideoFromArray (video: Video) { 208 removeVideoFromArray (video: Video) {
213 this.videos = this.videos.filter(v => v.id !== video.id) 209 this.videos = this.videos.filter(v => v.id !== video.id)
214 } 210 }
@@ -268,6 +264,10 @@ export abstract class AbstractVideoList implements OnInit, OnDestroy, DisableFor
268 return this.groupedDateLabels[this.groupedDates[video.id]] 264 return this.groupedDateLabels[this.groupedDates[video.id]]
269 } 265 }
270 266
267 toggleModerationDisplay () {
268 throw new Error('toggleModerationDisplay is not implemented')
269 }
270
271 // On videos hook for children that want to do something 271 // On videos hook for children that want to do something
272 protected onMoreVideos () { /* empty */ } 272 protected onMoreVideos () { /* empty */ }
273 273
@@ -277,6 +277,28 @@ export abstract class AbstractVideoList implements OnInit, OnDestroy, DisableFor
277 this.angularState = routeParams[ 'a-state' ] 277 this.angularState = routeParams[ 'a-state' ]
278 } 278 }
279 279
280 protected buildLocalFilter (existing: VideoFilter, base: VideoFilter) {
281 if (base === 'local') {
282 return existing === 'local'
283 ? 'all-local' as 'all-local'
284 : 'local' as 'local'
285 }
286
287 return existing === 'all'
288 ? null
289 : 'all'
290 }
291
292 protected enableAllFilterIfPossible () {
293 if (!this.authService.isLoggedIn()) return
294
295 this.authService.userInformationLoaded
296 .subscribe(() => {
297 const user = this.authService.getUser()
298 this.displayModerationBlock = user.hasRight(UserRight.SEE_ALL_VIDEOS)
299 })
300 }
301
280 private calcPageSizes () { 302 private calcPageSizes () {
281 if (this.screenService.isInMobileView()) { 303 if (this.screenService.isInMobileView()) {
282 this.pagination.itemsPerPage = 5 304 this.pagination.itemsPerPage = 5
diff --git a/client/src/sass/bootstrap.scss b/client/src/sass/bootstrap.scss
index 259af7a77..7cb149f5f 100644
--- a/client/src/sass/bootstrap.scss
+++ b/client/src/sass/bootstrap.scss
@@ -65,6 +65,10 @@ $icon-font-path: '~@neos21/bootstrap3-glyphicons/assets/fonts/';
65 opacity: .9; 65 opacity: .9;
66 } 66 }
67 67
68 &:active {
69 color: pvar(--mainForegroundColor) !important;
70 }
71
68 &::after { 72 &::after {
69 display: none; 73 display: none;
70 } 74 }
diff --git a/server/helpers/custom-validators/videos.ts b/server/helpers/custom-validators/videos.ts
index e99992236..8b309ae42 100644
--- a/server/helpers/custom-validators/videos.ts
+++ b/server/helpers/custom-validators/videos.ts
@@ -17,7 +17,7 @@ import * as magnetUtil from 'magnet-uri'
17const VIDEOS_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.VIDEOS 17const VIDEOS_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.VIDEOS
18 18
19function isVideoFilterValid (filter: VideoFilter) { 19function isVideoFilterValid (filter: VideoFilter) {
20 return filter === 'local' || filter === 'all-local' 20 return filter === 'local' || filter === 'all-local' || filter === 'all'
21} 21}
22 22
23function isVideoCategoryValid (value: any) { 23function isVideoCategoryValid (value: any) {
diff --git a/server/middlewares/validators/videos/videos.ts b/server/middlewares/validators/videos/videos.ts
index ff90e347a..efab67a01 100644
--- a/server/middlewares/validators/videos/videos.ts
+++ b/server/middlewares/validators/videos/videos.ts
@@ -429,7 +429,10 @@ const commonVideosFiltersValidator = [
429 if (areValidationErrors(req, res)) return 429 if (areValidationErrors(req, res)) return
430 430
431 const user = res.locals.oauth ? res.locals.oauth.token.User : undefined 431 const user = res.locals.oauth ? res.locals.oauth.token.User : undefined
432 if (req.query.filter === 'all-local' && (!user || user.hasRight(UserRight.SEE_ALL_VIDEOS) === false)) { 432 if (
433 (req.query.filter === 'all-local' || req.query.filter === 'all') &&
434 (!user || user.hasRight(UserRight.SEE_ALL_VIDEOS) === false)
435 ) {
433 res.status(401) 436 res.status(401)
434 .json({ error: 'You are not allowed to see all local videos.' }) 437 .json({ error: 'You are not allowed to see all local videos.' })
435 438
diff --git a/server/models/video/video-query-builder.ts b/server/models/video/video-query-builder.ts
index b14bb16d6..25d5042b7 100644
--- a/server/models/video/video-query-builder.ts
+++ b/server/models/video/video-query-builder.ts
@@ -89,7 +89,7 @@ function buildListQuery (model: typeof Model, options: BuildVideosQueryOptions)
89 } 89 }
90 90
91 // Only list public/published videos 91 // Only list public/published videos
92 if (!options.filter || options.filter !== 'all-local') { 92 if (!options.filter || (options.filter !== 'all-local' && options.filter !== 'all')) {
93 and.push( 93 and.push(
94 `("video"."state" = ${VideoState.PUBLISHED} OR ` + 94 `("video"."state" = ${VideoState.PUBLISHED} OR ` +
95 `("video"."state" = ${VideoState.TO_TRANSCODE} AND "video"."waitTranscoding" IS false))` 95 `("video"."state" = ${VideoState.TO_TRANSCODE} AND "video"."waitTranscoding" IS false))`
diff --git a/server/models/video/video.ts b/server/models/video/video.ts
index edf757697..f365d3d51 100644
--- a/server/models/video/video.ts
+++ b/server/models/video/video.ts
@@ -1085,7 +1085,7 @@ export class VideoModel extends Model<VideoModel> {
1085 historyOfUser?: MUserId 1085 historyOfUser?: MUserId
1086 countVideos?: boolean 1086 countVideos?: boolean
1087 }) { 1087 }) {
1088 if (options.filter && options.filter === 'all-local' && !options.user.hasRight(UserRight.SEE_ALL_VIDEOS)) { 1088 if ((options.filter === 'all-local' || options.filter === 'all') && !options.user.hasRight(UserRight.SEE_ALL_VIDEOS)) {
1089 throw new Error('Try to filter all-local but no user has not the see all videos right') 1089 throw new Error('Try to filter all-local but no user has not the see all videos right')
1090 } 1090 }
1091 1091
diff --git a/server/tests/api/check-params/videos-filter.ts b/server/tests/api/check-params/videos-filter.ts
index ec8654db2..bf8248b0e 100644
--- a/server/tests/api/check-params/videos-filter.ts
+++ b/server/tests/api/check-params/videos-filter.ts
@@ -78,28 +78,33 @@ describe('Test videos filters', function () {
78 await testEndpoints(server, server.accessToken, 'local', 200) 78 await testEndpoints(server, server.accessToken, 'local', 200)
79 }) 79 })
80 80
81 it('Should fail to list all-local with a simple user', async function () { 81 it('Should fail to list all-local/all with a simple user', async function () {
82 await testEndpoints(server, userAccessToken, 'all-local', 401) 82 await testEndpoints(server, userAccessToken, 'all-local', 401)
83 await testEndpoints(server, userAccessToken, 'all', 401)
83 }) 84 })
84 85
85 it('Should succeed to list all-local with a moderator', async function () { 86 it('Should succeed to list all-local/all with a moderator', async function () {
86 await testEndpoints(server, moderatorAccessToken, 'all-local', 200) 87 await testEndpoints(server, moderatorAccessToken, 'all-local', 200)
88 await testEndpoints(server, moderatorAccessToken, 'all', 200)
87 }) 89 })
88 90
89 it('Should succeed to list all-local with an admin', async function () { 91 it('Should succeed to list all-local/all with an admin', async function () {
90 await testEndpoints(server, server.accessToken, 'all-local', 200) 92 await testEndpoints(server, server.accessToken, 'all-local', 200)
93 await testEndpoints(server, server.accessToken, 'all', 200)
91 }) 94 })
92 95
93 // Because we cannot authenticate the user on the RSS endpoint 96 // Because we cannot authenticate the user on the RSS endpoint
94 it('Should fail on the feeds endpoint with the all-local filter', async function () { 97 it('Should fail on the feeds endpoint with the all-local/all filter', async function () {
95 await makeGetRequest({ 98 for (const filter of [ 'all', 'all-local' ]) {
96 url: server.url, 99 await makeGetRequest({
97 path: '/feeds/videos.json', 100 url: server.url,
98 statusCodeExpected: 401, 101 path: '/feeds/videos.json',
99 query: { 102 statusCodeExpected: 401,
100 filter: 'all-local' 103 query: {
101 } 104 filter
102 }) 105 }
106 })
107 }
103 }) 108 })
104 109
105 it('Should succeed on the feeds endpoint with the local filter', async function () { 110 it('Should succeed on the feeds endpoint with the local filter', async function () {
diff --git a/server/tests/api/videos/videos-filter.ts b/server/tests/api/videos/videos-filter.ts
index 95e12e43c..6b9a4b6d4 100644
--- a/server/tests/api/videos/videos-filter.ts
+++ b/server/tests/api/videos/videos-filter.ts
@@ -116,6 +116,20 @@ describe('Test videos filter validator', function () {
116 } 116 }
117 } 117 }
118 }) 118 })
119
120 it('Should display all videos by the admin or the moderator', async function () {
121 for (const server of servers) {
122 for (const token of [ server.accessToken, server['moderatorAccessToken'] ]) {
123
124 const [ channelVideos, accountVideos, videos, searchVideos ] = await getVideosNames(server, token, 'all')
125 expect(channelVideos).to.have.lengthOf(3)
126 expect(accountVideos).to.have.lengthOf(3)
127
128 expect(videos).to.have.lengthOf(5)
129 expect(searchVideos).to.have.lengthOf(5)
130 }
131 }
132 })
119 }) 133 })
120 134
121 after(async function () { 135 after(async function () {
diff --git a/shared/models/videos/video-query.type.ts b/shared/models/videos/video-query.type.ts
index f76a91aad..e641a401c 100644
--- a/shared/models/videos/video-query.type.ts
+++ b/shared/models/videos/video-query.type.ts
@@ -1 +1 @@
export type VideoFilter = 'local' | 'all-local' export type VideoFilter = 'local' | 'all-local' | 'all'
diff --git a/support/doc/api/openapi.yaml b/support/doc/api/openapi.yaml
index 4a178e4d7..c16914eb2 100644
--- a/support/doc/api/openapi.yaml
+++ b/support/doc/api/openapi.yaml
@@ -3681,9 +3681,10 @@ components:
3681 in: query 3681 in: query
3682 required: false 3682 required: false
3683 description: > 3683 description: >
3684 Special filters (local for instance) which might require special rights: 3684 Special filters which might require special rights:
3685 * `local` - only videos local to the instance 3685 * `local` - only videos local to the instance
3686 * `all-local` - only videos local to the instance, but showing private and unlisted videos (requires Admin privileges) 3686 * `all-local` - only videos local to the instance, but showing private and unlisted videos (requires Admin privileges)
3687 * `all` - all videos, showing private and unlisted videos (requires Admin privileges)
3687 schema: 3688 schema:
3688 type: string 3689 type: string
3689 enum: 3690 enum: