diff options
15 files changed, 207 insertions, 54 deletions
diff --git a/client/src/app/+admin/moderation/video-block-list/video-block-list.component.ts b/client/src/app/+admin/moderation/video-block-list/video-block-list.component.ts index 3edcb1c63..7baf34ca2 100644 --- a/client/src/app/+admin/moderation/video-block-list/video-block-list.component.ts +++ b/client/src/app/+admin/moderation/video-block-list/video-block-list.component.ts | |||
@@ -28,12 +28,17 @@ export class VideoBlockListComponent extends RestTable implements OnInit { | |||
28 | 28 | ||
29 | inputFilters: AdvancedInputFilter[] = [ | 29 | inputFilters: AdvancedInputFilter[] = [ |
30 | { | 30 | { |
31 | queryParams: { search: 'type:auto' }, | 31 | title: $localize`Advanced filters`, |
32 | label: $localize`Automatic blocks` | 32 | children: [ |
33 | }, | 33 | { |
34 | { | 34 | queryParams: { search: 'type:auto' }, |
35 | queryParams: { search: 'type:manual' }, | 35 | label: $localize`Automatic blocks` |
36 | label: $localize`Manual blocks` | 36 | }, |
37 | { | ||
38 | queryParams: { search: 'type:manual' }, | ||
39 | label: $localize`Manual blocks` | ||
40 | } | ||
41 | ] | ||
37 | } | 42 | } |
38 | ] | 43 | ] |
39 | 44 | ||
diff --git a/client/src/app/+admin/moderation/video-comment-list/video-comment-list.component.ts b/client/src/app/+admin/moderation/video-comment-list/video-comment-list.component.ts index c09ce7293..a60b228af 100644 --- a/client/src/app/+admin/moderation/video-comment-list/video-comment-list.component.ts +++ b/client/src/app/+admin/moderation/video-comment-list/video-comment-list.component.ts | |||
@@ -44,12 +44,17 @@ export class VideoCommentListComponent extends RestTable implements OnInit { | |||
44 | 44 | ||
45 | inputFilters: AdvancedInputFilter[] = [ | 45 | inputFilters: AdvancedInputFilter[] = [ |
46 | { | 46 | { |
47 | queryParams: { search: 'local:true' }, | 47 | title: $localize`Advanced filters`, |
48 | label: $localize`Local comments` | 48 | children: [ |
49 | }, | 49 | { |
50 | { | 50 | queryParams: { search: 'local:true' }, |
51 | queryParams: { search: 'local:false' }, | 51 | label: $localize`Local comments` |
52 | label: $localize`Remote comments` | 52 | }, |
53 | { | ||
54 | queryParams: { search: 'local:false' }, | ||
55 | label: $localize`Remote comments` | ||
56 | } | ||
57 | ] | ||
53 | } | 58 | } |
54 | ] | 59 | ] |
55 | 60 | ||
diff --git a/client/src/app/+admin/users/user-list/user-list.component.ts b/client/src/app/+admin/users/user-list/user-list.component.ts index 1030759df..548e6e80f 100644 --- a/client/src/app/+admin/users/user-list/user-list.component.ts +++ b/client/src/app/+admin/users/user-list/user-list.component.ts | |||
@@ -36,8 +36,13 @@ export class UserListComponent extends RestTable implements OnInit { | |||
36 | 36 | ||
37 | inputFilters: AdvancedInputFilter[] = [ | 37 | inputFilters: AdvancedInputFilter[] = [ |
38 | { | 38 | { |
39 | queryParams: { search: 'banned:true' }, | 39 | title: $localize`Advanced filters`, |
40 | label: $localize`Banned users` | 40 | children: [ |
41 | { | ||
42 | queryParams: { search: 'banned:true' }, | ||
43 | label: $localize`Banned users` | ||
44 | } | ||
45 | ] | ||
41 | } | 46 | } |
42 | ] | 47 | ] |
43 | 48 | ||
diff --git a/client/src/app/+my-library/my-follows/my-followers.component.ts b/client/src/app/+my-library/my-follows/my-followers.component.ts index 413d524df..4a72b983f 100644 --- a/client/src/app/+my-library/my-follows/my-followers.component.ts +++ b/client/src/app/+my-library/my-follows/my-followers.component.ts | |||
@@ -37,12 +37,19 @@ export class MyFollowersComponent implements OnInit { | |||
37 | } | 37 | } |
38 | 38 | ||
39 | this.auth.userInformationLoaded.subscribe(() => { | 39 | this.auth.userInformationLoaded.subscribe(() => { |
40 | this.inputFilters = this.auth.getUser().videoChannels.map(c => { | 40 | const channelFilters = this.auth.getUser().videoChannels.map(c => { |
41 | return { | 41 | return { |
42 | queryParams: { search: 'channel:' + c.name }, | 42 | queryParams: { search: 'channel:' + c.name }, |
43 | label: $localize`Followers of ${c.name}` | 43 | label: c.name |
44 | } | 44 | } |
45 | }) | 45 | }) |
46 | |||
47 | this.inputFilters = [ | ||
48 | { | ||
49 | title: $localize`Channel filters`, | ||
50 | children: channelFilters | ||
51 | } | ||
52 | ] | ||
46 | }) | 53 | }) |
47 | } | 54 | } |
48 | 55 | ||
diff --git a/client/src/app/+my-library/my-videos/my-videos.component.ts b/client/src/app/+my-library/my-videos/my-videos.component.ts index b1f3baf80..a117d0915 100644 --- a/client/src/app/+my-library/my-videos/my-videos.component.ts +++ b/client/src/app/+my-library/my-videos/my-videos.component.ts | |||
@@ -9,7 +9,7 @@ import { AdvancedInputFilter } from '@app/shared/shared-forms' | |||
9 | import { DropdownAction, Video, VideoService } from '@app/shared/shared-main' | 9 | import { DropdownAction, Video, VideoService } from '@app/shared/shared-main' |
10 | import { LiveStreamInformationComponent } from '@app/shared/shared-video-live' | 10 | import { LiveStreamInformationComponent } from '@app/shared/shared-video-live' |
11 | import { MiniatureDisplayOptions, SelectionType, VideosSelectionComponent } from '@app/shared/shared-video-miniature' | 11 | import { MiniatureDisplayOptions, SelectionType, VideosSelectionComponent } from '@app/shared/shared-video-miniature' |
12 | import { VideoSortField } from '@shared/models' | 12 | import { VideoChannel, VideoSortField } from '@shared/models' |
13 | import { VideoChangeOwnershipComponent } from './modals/video-change-ownership.component' | 13 | import { VideoChangeOwnershipComponent } from './modals/video-change-ownership.component' |
14 | 14 | ||
15 | @Component({ | 15 | @Component({ |
@@ -47,16 +47,12 @@ export class MyVideosComponent implements OnInit, DisableForReuseHook { | |||
47 | 47 | ||
48 | user: User | 48 | user: User |
49 | 49 | ||
50 | inputFilters: AdvancedInputFilter[] = [ | 50 | inputFilters: AdvancedInputFilter[] |
51 | { | ||
52 | queryParams: { search: 'isLive:true' }, | ||
53 | label: $localize`Only live videos` | ||
54 | } | ||
55 | ] | ||
56 | 51 | ||
57 | disabled = false | 52 | disabled = false |
58 | 53 | ||
59 | private search: string | 54 | private search: string |
55 | private userChannels: VideoChannel[] = [] | ||
60 | 56 | ||
61 | constructor ( | 57 | constructor ( |
62 | protected router: Router, | 58 | protected router: Router, |
@@ -79,6 +75,35 @@ export class MyVideosComponent implements OnInit, DisableForReuseHook { | |||
79 | if (this.route.snapshot.queryParams['search']) { | 75 | if (this.route.snapshot.queryParams['search']) { |
80 | this.search = this.route.snapshot.queryParams['search'] | 76 | this.search = this.route.snapshot.queryParams['search'] |
81 | } | 77 | } |
78 | |||
79 | this.authService.userInformationLoaded.subscribe(() => { | ||
80 | this.user = this.authService.getUser() | ||
81 | this.userChannels = this.user.videoChannels | ||
82 | |||
83 | const channelFilters = this.userChannels.map(c => { | ||
84 | return { | ||
85 | queryParams: { search: 'channel:' + c.name }, | ||
86 | label: c.name | ||
87 | } | ||
88 | }) | ||
89 | |||
90 | this.inputFilters = [ | ||
91 | { | ||
92 | title: $localize`Advanced filters`, | ||
93 | children: [ | ||
94 | { | ||
95 | queryParams: { search: 'isLive:true' }, | ||
96 | label: $localize`Only live videos` | ||
97 | } | ||
98 | ] | ||
99 | }, | ||
100 | |||
101 | { | ||
102 | title: $localize`Channel filters`, | ||
103 | children: channelFilters | ||
104 | } | ||
105 | ] | ||
106 | }) | ||
82 | } | 107 | } |
83 | 108 | ||
84 | onSearch (search: string) { | 109 | onSearch (search: string) { |
@@ -105,7 +130,12 @@ export class MyVideosComponent implements OnInit, DisableForReuseHook { | |||
105 | getVideosObservable (page: number) { | 130 | getVideosObservable (page: number) { |
106 | const newPagination = immutableAssign(this.pagination, { currentPage: page }) | 131 | const newPagination = immutableAssign(this.pagination, { currentPage: page }) |
107 | 132 | ||
108 | return this.videoService.getMyVideos(newPagination, this.sort, this.search) | 133 | return this.videoService.getMyVideos({ |
134 | videoPagination: newPagination, | ||
135 | sort: this.sort, | ||
136 | userChannels: this.userChannels, | ||
137 | search: this.search | ||
138 | }) | ||
109 | .pipe( | 139 | .pipe( |
110 | tap(res => this.pagination.totalItems = res.total) | 140 | tap(res => this.pagination.totalItems = res.total) |
111 | ) | 141 | ) |
diff --git a/client/src/app/shared/shared-abuse-list/abuse-list-table.component.ts b/client/src/app/shared/shared-abuse-list/abuse-list-table.component.ts index 33e9fd8de..297993e39 100644 --- a/client/src/app/shared/shared-abuse-list/abuse-list-table.component.ts +++ b/client/src/app/shared/shared-abuse-list/abuse-list-table.component.ts | |||
@@ -39,24 +39,29 @@ export class AbuseListTableComponent extends RestTable implements OnInit { | |||
39 | 39 | ||
40 | inputFilters: AdvancedInputFilter[] = [ | 40 | inputFilters: AdvancedInputFilter[] = [ |
41 | { | 41 | { |
42 | queryParams: { search: 'state:pending' }, | 42 | title: $localize`Advanced filters`, |
43 | label: $localize`Unsolved reports` | 43 | children: [ |
44 | }, | 44 | { |
45 | { | 45 | queryParams: { search: 'state:pending' }, |
46 | queryParams: { search: 'state:accepted' }, | 46 | label: $localize`Unsolved reports` |
47 | label: $localize`Accepted reports` | 47 | }, |
48 | }, | 48 | { |
49 | { | 49 | queryParams: { search: 'state:accepted' }, |
50 | queryParams: { search: 'state:rejected' }, | 50 | label: $localize`Accepted reports` |
51 | label: $localize`Refused reports` | 51 | }, |
52 | }, | 52 | { |
53 | { | 53 | queryParams: { search: 'state:rejected' }, |
54 | queryParams: { search: 'videoIs:blacklisted' }, | 54 | label: $localize`Refused reports` |
55 | label: $localize`Reports with blocked videos` | 55 | }, |
56 | }, | 56 | { |
57 | { | 57 | queryParams: { search: 'videoIs:blacklisted' }, |
58 | queryParams: { search: 'videoIs:deleted' }, | 58 | label: $localize`Reports with blocked videos` |
59 | label: $localize`Reports with deleted videos` | 59 | }, |
60 | { | ||
61 | queryParams: { search: 'videoIs:deleted' }, | ||
62 | label: $localize`Reports with deleted videos` | ||
63 | } | ||
64 | ] | ||
60 | } | 65 | } |
61 | ] | 66 | ] |
62 | 67 | ||
diff --git a/client/src/app/shared/shared-forms/advanced-input-filter.component.html b/client/src/app/shared/shared-forms/advanced-input-filter.component.html index 10d1296cf..c662b9bb6 100644 --- a/client/src/app/shared/shared-forms/advanced-input-filter.component.html +++ b/client/src/app/shared/shared-forms/advanced-input-filter.component.html | |||
@@ -5,11 +5,13 @@ | |||
5 | </div> | 5 | </div> |
6 | 6 | ||
7 | <div role="menu" ngbDropdownMenu> | 7 | <div role="menu" ngbDropdownMenu> |
8 | <h6 class="dropdown-header" i18n>Advanced filters</h6> | 8 | <ng-container *ngFor="let group of filters"> |
9 | <h6 class="dropdown-header">{{ group.title }}</h6> | ||
9 | 10 | ||
10 | <a *ngFor="let filter of filters" [routerLink]="[ '.' ]" [queryParams]="filter.queryParams" class="dropdown-item"> | 11 | <a *ngFor="let filter of group.children" [routerLink]="[ '.' ]" [queryParams]="filter.queryParams" class="dropdown-item"> |
11 | {{ filter.label }} | 12 | {{ filter.label }} |
12 | </a> | 13 | </a> |
14 | </ng-container> | ||
13 | </div> | 15 | </div> |
14 | </div> | 16 | </div> |
15 | 17 | ||
diff --git a/client/src/app/shared/shared-forms/advanced-input-filter.component.ts b/client/src/app/shared/shared-forms/advanced-input-filter.component.ts index 8315662b4..a12dddf7a 100644 --- a/client/src/app/shared/shared-forms/advanced-input-filter.component.ts +++ b/client/src/app/shared/shared-forms/advanced-input-filter.component.ts | |||
@@ -5,8 +5,12 @@ import { AfterViewInit, Component, EventEmitter, Input, OnInit, Output } from '@ | |||
5 | import { ActivatedRoute, Params, Router } from '@angular/router' | 5 | import { ActivatedRoute, Params, Router } from '@angular/router' |
6 | 6 | ||
7 | export type AdvancedInputFilter = { | 7 | export type AdvancedInputFilter = { |
8 | label: string | 8 | title: string |
9 | queryParams: Params | 9 | |
10 | children: { | ||
11 | label: string | ||
12 | queryParams: Params | ||
13 | }[] | ||
10 | } | 14 | } |
11 | 15 | ||
12 | const logger = debug('peertube:AdvancedInputFilterComponent') | 16 | const logger = debug('peertube:AdvancedInputFilterComponent') |
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 2f43f1b9d..7935569e7 100644 --- a/client/src/app/shared/shared-main/video/video.service.ts +++ b/client/src/app/shared/shared-main/video/video.service.ts | |||
@@ -13,6 +13,7 @@ import { | |||
13 | UserVideoRateType, | 13 | UserVideoRateType, |
14 | UserVideoRateUpdate, | 14 | UserVideoRateUpdate, |
15 | Video as VideoServerModel, | 15 | Video as VideoServerModel, |
16 | VideoChannel as VideoChannelServerModel, | ||
16 | VideoConstant, | 17 | VideoConstant, |
17 | VideoDetails as VideoDetailsServerModel, | 18 | VideoDetails as VideoDetailsServerModel, |
18 | VideoFileMetadata, | 19 | VideoFileMetadata, |
@@ -122,7 +123,14 @@ export class VideoService { | |||
122 | .pipe(catchError(err => this.restExtractor.handleError(err))) | 123 | .pipe(catchError(err => this.restExtractor.handleError(err))) |
123 | } | 124 | } |
124 | 125 | ||
125 | getMyVideos (videoPagination: ComponentPaginationLight, sort: VideoSortField, search?: string): Observable<ResultList<Video>> { | 126 | getMyVideos (options: { |
127 | videoPagination: ComponentPaginationLight | ||
128 | sort: VideoSortField | ||
129 | userChannels?: VideoChannelServerModel[] | ||
130 | search?: string | ||
131 | }): Observable<ResultList<Video>> { | ||
132 | const { videoPagination, sort, userChannels = [], search } = options | ||
133 | |||
126 | const pagination = this.restService.componentToRestPagination(videoPagination) | 134 | const pagination = this.restService.componentToRestPagination(videoPagination) |
127 | 135 | ||
128 | let params = new HttpParams() | 136 | let params = new HttpParams() |
@@ -133,6 +141,16 @@ export class VideoService { | |||
133 | isLive: { | 141 | isLive: { |
134 | prefix: 'isLive:', | 142 | prefix: 'isLive:', |
135 | isBoolean: true | 143 | isBoolean: true |
144 | }, | ||
145 | channelId: { | ||
146 | prefix: 'channel:', | ||
147 | handler: (name: string) => { | ||
148 | const channel = userChannels.find(c => c.name === name) | ||
149 | |||
150 | if (channel) return channel.id | ||
151 | |||
152 | return undefined | ||
153 | } | ||
136 | } | 154 | } |
137 | }) | 155 | }) |
138 | 156 | ||
diff --git a/server/controllers/api/users/me.ts b/server/controllers/api/users/me.ts index 83b774d3c..6bacdbbb6 100644 --- a/server/controllers/api/users/me.ts +++ b/server/controllers/api/users/me.ts | |||
@@ -25,7 +25,7 @@ import { | |||
25 | usersUpdateMeValidator, | 25 | usersUpdateMeValidator, |
26 | usersVideoRatingValidator | 26 | usersVideoRatingValidator |
27 | } from '../../../middlewares' | 27 | } from '../../../middlewares' |
28 | import { deleteMeValidator, videoImportsSortValidator, videosSortValidator } from '../../../middlewares/validators' | 28 | import { deleteMeValidator, usersVideosValidator, videoImportsSortValidator, videosSortValidator } from '../../../middlewares/validators' |
29 | import { updateAvatarValidator } from '../../../middlewares/validators/actor-image' | 29 | import { updateAvatarValidator } from '../../../middlewares/validators/actor-image' |
30 | import { AccountModel } from '../../../models/account/account' | 30 | import { AccountModel } from '../../../models/account/account' |
31 | import { AccountVideoRateModel } from '../../../models/account/account-video-rate' | 31 | import { AccountVideoRateModel } from '../../../models/account/account-video-rate' |
@@ -69,6 +69,7 @@ meRouter.get('/me/videos', | |||
69 | videosSortValidator, | 69 | videosSortValidator, |
70 | setDefaultVideosSort, | 70 | setDefaultVideosSort, |
71 | setDefaultPagination, | 71 | setDefaultPagination, |
72 | asyncMiddleware(usersVideosValidator), | ||
72 | asyncMiddleware(getUserVideos) | 73 | asyncMiddleware(getUserVideos) |
73 | ) | 74 | ) |
74 | 75 | ||
@@ -113,6 +114,7 @@ async function getUserVideos (req: express.Request, res: express.Response) { | |||
113 | count: req.query.count, | 114 | count: req.query.count, |
114 | sort: req.query.sort, | 115 | sort: req.query.sort, |
115 | search: req.query.search, | 116 | search: req.query.search, |
117 | channelId: res.locals.videoChannel?.id, | ||
116 | isLive: req.query.isLive | 118 | isLive: req.query.isLive |
117 | }, 'filter:api.user.me.videos.list.params') | 119 | }, 'filter:api.user.me.videos.list.params') |
118 | 120 | ||
diff --git a/server/middlewares/validators/users.ts b/server/middlewares/validators/users.ts index c6eeeaf18..8f1a7801f 100644 --- a/server/middlewares/validators/users.ts +++ b/server/middlewares/validators/users.ts | |||
@@ -4,7 +4,7 @@ import { omit } from 'lodash' | |||
4 | import { Hooks } from '@server/lib/plugins/hooks' | 4 | import { Hooks } from '@server/lib/plugins/hooks' |
5 | import { MUserDefault } from '@server/types/models' | 5 | import { MUserDefault } from '@server/types/models' |
6 | import { HttpStatusCode, UserRegister, UserRole } from '@shared/models' | 6 | import { HttpStatusCode, UserRegister, UserRole } from '@shared/models' |
7 | import { toBooleanOrNull, toIntOrNull } from '../../helpers/custom-validators/misc' | 7 | import { isBooleanValid, isIdValid, toBooleanOrNull, toIntOrNull } from '../../helpers/custom-validators/misc' |
8 | import { isThemeNameValid } from '../../helpers/custom-validators/plugins' | 8 | import { isThemeNameValid } from '../../helpers/custom-validators/plugins' |
9 | import { | 9 | import { |
10 | isUserAdminFlagsValid, | 10 | isUserAdminFlagsValid, |
@@ -31,7 +31,7 @@ import { Redis } from '../../lib/redis' | |||
31 | import { isSignupAllowed, isSignupAllowedForCurrentIP } from '../../lib/signup' | 31 | import { isSignupAllowed, isSignupAllowedForCurrentIP } from '../../lib/signup' |
32 | import { ActorModel } from '../../models/actor/actor' | 32 | import { ActorModel } from '../../models/actor/actor' |
33 | import { UserModel } from '../../models/user/user' | 33 | import { UserModel } from '../../models/user/user' |
34 | import { areValidationErrors, doesVideoExist, isValidVideoIdParam } from './shared' | 34 | import { areValidationErrors, doesVideoChannelIdExist, doesVideoExist, isValidVideoIdParam } from './shared' |
35 | 35 | ||
36 | const usersListValidator = [ | 36 | const usersListValidator = [ |
37 | query('blocked') | 37 | query('blocked') |
@@ -318,6 +318,28 @@ const usersVideoRatingValidator = [ | |||
318 | } | 318 | } |
319 | ] | 319 | ] |
320 | 320 | ||
321 | const usersVideosValidator = [ | ||
322 | query('isLive') | ||
323 | .optional() | ||
324 | .customSanitizer(toBooleanOrNull) | ||
325 | .custom(isBooleanValid).withMessage('Should have a valid live boolean'), | ||
326 | |||
327 | query('channelId') | ||
328 | .optional() | ||
329 | .customSanitizer(toIntOrNull) | ||
330 | .custom(isIdValid).withMessage('Should have a valid channel id'), | ||
331 | |||
332 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | ||
333 | logger.debug('Checking usersVideosValidator parameters', { parameters: req.params }) | ||
334 | |||
335 | if (areValidationErrors(req, res)) return | ||
336 | |||
337 | if (req.query.channelId && !await doesVideoChannelIdExist(req.query.channelId, res)) return | ||
338 | |||
339 | return next() | ||
340 | } | ||
341 | ] | ||
342 | |||
321 | const ensureUserRegistrationAllowed = [ | 343 | const ensureUserRegistrationAllowed = [ |
322 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | 344 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { |
323 | const allowedParams = { | 345 | const allowedParams = { |
@@ -513,6 +535,7 @@ export { | |||
513 | ensureUserRegistrationAllowed, | 535 | ensureUserRegistrationAllowed, |
514 | ensureUserRegistrationAllowedForIP, | 536 | ensureUserRegistrationAllowedForIP, |
515 | usersGetValidator, | 537 | usersGetValidator, |
538 | usersVideosValidator, | ||
516 | usersAskResetPasswordValidator, | 539 | usersAskResetPasswordValidator, |
517 | usersResetPasswordValidator, | 540 | usersResetPasswordValidator, |
518 | usersAskSendVerifyEmailValidator, | 541 | usersAskSendVerifyEmailValidator, |
diff --git a/server/models/video/video.ts b/server/models/video/video.ts index d2daf18ee..4044287ee 100644 --- a/server/models/video/video.ts +++ b/server/models/video/video.ts | |||
@@ -978,10 +978,12 @@ export class VideoModel extends Model<Partial<AttributesOnly<VideoModel>>> { | |||
978 | start: number | 978 | start: number |
979 | count: number | 979 | count: number |
980 | sort: string | 980 | sort: string |
981 | |||
982 | channelId?: number | ||
981 | isLive?: boolean | 983 | isLive?: boolean |
982 | search?: string | 984 | search?: string |
983 | }) { | 985 | }) { |
984 | const { accountId, start, count, sort, search, isLive } = options | 986 | const { accountId, channelId, start, count, sort, search, isLive } = options |
985 | 987 | ||
986 | function buildBaseQuery (): FindOptions { | 988 | function buildBaseQuery (): FindOptions { |
987 | const where: WhereOptions = {} | 989 | const where: WhereOptions = {} |
@@ -996,6 +998,10 @@ export class VideoModel extends Model<Partial<AttributesOnly<VideoModel>>> { | |||
996 | where.isLive = isLive | 998 | where.isLive = isLive |
997 | } | 999 | } |
998 | 1000 | ||
1001 | const channelWhere = channelId | ||
1002 | ? { id: channelId } | ||
1003 | : {} | ||
1004 | |||
999 | const baseQuery = { | 1005 | const baseQuery = { |
1000 | offset: start, | 1006 | offset: start, |
1001 | limit: count, | 1007 | limit: count, |
@@ -1005,6 +1011,7 @@ export class VideoModel extends Model<Partial<AttributesOnly<VideoModel>>> { | |||
1005 | { | 1011 | { |
1006 | model: VideoChannelModel, | 1012 | model: VideoChannelModel, |
1007 | required: true, | 1013 | required: true, |
1014 | where: channelWhere, | ||
1008 | include: [ | 1015 | include: [ |
1009 | { | 1016 | { |
1010 | model: AccountModel, | 1017 | model: AccountModel, |
diff --git a/server/tests/api/check-params/videos.ts b/server/tests/api/check-params/videos.ts index e11ca0c82..d02b6e156 100644 --- a/server/tests/api/check-params/videos.ts +++ b/server/tests/api/check-params/videos.ts | |||
@@ -119,6 +119,20 @@ describe('Test videos API validator', function () { | |||
119 | await checkBadSortPagination(server.url, path, server.accessToken) | 119 | await checkBadSortPagination(server.url, path, server.accessToken) |
120 | }) | 120 | }) |
121 | 121 | ||
122 | it('Should fail with an invalid channel', async function () { | ||
123 | await makeGetRequest({ url: server.url, token: server.accessToken, path, query: { channelId: 'toto' } }) | ||
124 | }) | ||
125 | |||
126 | it('Should fail with an unknown channel', async function () { | ||
127 | await makeGetRequest({ | ||
128 | url: server.url, | ||
129 | token: server.accessToken, | ||
130 | path, | ||
131 | query: { channelId: 89898 }, | ||
132 | expectedStatus: HttpStatusCode.NOT_FOUND_404 | ||
133 | }) | ||
134 | }) | ||
135 | |||
122 | it('Should success with the correct parameters', async function () { | 136 | it('Should success with the correct parameters', async function () { |
123 | await makeGetRequest({ url: server.url, token: server.accessToken, path, expectedStatus: HttpStatusCode.OK_200 }) | 137 | await makeGetRequest({ url: server.url, token: server.accessToken, path, expectedStatus: HttpStatusCode.OK_200 }) |
124 | }) | 138 | }) |
diff --git a/server/tests/api/users/users.ts b/server/tests/api/users/users.ts index 085d9d870..6c41e7d56 100644 --- a/server/tests/api/users/users.ts +++ b/server/tests/api/users/users.ts | |||
@@ -318,6 +318,8 @@ describe('Test users', function () { | |||
318 | fixture: 'video_short.webm' | 318 | fixture: 'video_short.webm' |
319 | } | 319 | } |
320 | await server.videos.upload({ token: userToken, attributes }) | 320 | await server.videos.upload({ token: userToken, attributes }) |
321 | |||
322 | await server.channels.create({ token: userToken, attributes: { name: 'other_channel' } }) | ||
321 | }) | 323 | }) |
322 | 324 | ||
323 | it('Should have video quota updated', async function () { | 325 | it('Should have video quota updated', async function () { |
@@ -340,6 +342,29 @@ describe('Test users', function () { | |||
340 | expect(video.previewPath).to.not.be.null | 342 | expect(video.previewPath).to.not.be.null |
341 | }) | 343 | }) |
342 | 344 | ||
345 | it('Should be able to filter by channel in my videos', async function () { | ||
346 | const myInfo = await server.users.getMyInfo({ token: userToken }) | ||
347 | const mainChannel = myInfo.videoChannels.find(c => c.name !== 'other_channel') | ||
348 | const otherChannel = myInfo.videoChannels.find(c => c.name === 'other_channel') | ||
349 | |||
350 | { | ||
351 | const { total, data } = await server.videos.listMyVideos({ token: userToken, channelId: mainChannel.id }) | ||
352 | expect(total).to.equal(1) | ||
353 | expect(data).to.have.lengthOf(1) | ||
354 | |||
355 | const video: Video = data[0] | ||
356 | expect(video.name).to.equal('super user video') | ||
357 | expect(video.thumbnailPath).to.not.be.null | ||
358 | expect(video.previewPath).to.not.be.null | ||
359 | } | ||
360 | |||
361 | { | ||
362 | const { total, data } = await server.videos.listMyVideos({ token: userToken, channelId: otherChannel.id }) | ||
363 | expect(total).to.equal(0) | ||
364 | expect(data).to.have.lengthOf(0) | ||
365 | } | ||
366 | }) | ||
367 | |||
343 | it('Should be able to search in my videos', async function () { | 368 | it('Should be able to search in my videos', async function () { |
344 | { | 369 | { |
345 | const { total, data } = await server.videos.listMyVideos({ token: userToken, sort: '-createdAt', search: 'user video' }) | 370 | const { total, data } = await server.videos.listMyVideos({ token: userToken, sort: '-createdAt', search: 'user video' }) |
diff --git a/shared/extra-utils/videos/videos-command.ts b/shared/extra-utils/videos/videos-command.ts index 99f56a34c..c1a9ec806 100644 --- a/shared/extra-utils/videos/videos-command.ts +++ b/shared/extra-utils/videos/videos-command.ts | |||
@@ -207,6 +207,7 @@ export class VideosCommand extends AbstractCommand { | |||
207 | sort?: string | 207 | sort?: string |
208 | search?: string | 208 | search?: string |
209 | isLive?: boolean | 209 | isLive?: boolean |
210 | channelId?: number | ||
210 | } = {}) { | 211 | } = {}) { |
211 | const path = '/api/v1/users/me/videos' | 212 | const path = '/api/v1/users/me/videos' |
212 | 213 | ||
@@ -214,7 +215,7 @@ export class VideosCommand extends AbstractCommand { | |||
214 | ...options, | 215 | ...options, |
215 | 216 | ||
216 | path, | 217 | path, |
217 | query: pick(options, [ 'start', 'count', 'sort', 'search', 'isLive' ]), | 218 | query: pick(options, [ 'start', 'count', 'sort', 'search', 'isLive', 'channelId' ]), |
218 | implicitToken: true, | 219 | implicitToken: true, |
219 | defaultExpectedStatus: HttpStatusCode.OK_200 | 220 | defaultExpectedStatus: HttpStatusCode.OK_200 |
220 | }) | 221 | }) |