diff options
-rw-r--r-- | server/controllers/api/accounts.ts | 4 | ||||
-rw-r--r-- | server/controllers/api/search.ts | 1 | ||||
-rw-r--r-- | server/controllers/api/video-channel.ts | 4 | ||||
-rw-r--r-- | server/controllers/feeds.ts | 10 | ||||
-rw-r--r-- | server/helpers/custom-validators/videos.ts | 9 | ||||
-rw-r--r-- | server/helpers/express-utils.ts | 3 | ||||
-rw-r--r-- | server/middlewares/validators/search.ts | 38 | ||||
-rw-r--r-- | server/middlewares/validators/videos/videos.ts | 54 | ||||
-rw-r--r-- | server/models/video/video.ts | 26 | ||||
-rw-r--r-- | server/tests/api/check-params/index.ts | 1 | ||||
-rw-r--r-- | server/tests/api/check-params/videos-filter.ts | 127 | ||||
-rw-r--r-- | server/tests/api/videos/index.ts | 1 | ||||
-rw-r--r-- | server/tests/api/videos/videos-filter.ts | 130 | ||||
-rw-r--r-- | shared/models/search/videos-search-query.model.ts | 3 | ||||
-rw-r--r-- | shared/models/users/user-right.enum.ts | 1 | ||||
-rw-r--r-- | shared/models/users/user-role.ts | 3 | ||||
-rw-r--r-- | shared/models/videos/video-query.type.ts | 2 |
17 files changed, 363 insertions, 54 deletions
diff --git a/server/controllers/api/accounts.ts b/server/controllers/api/accounts.ts index b7691ccba..8e3f60010 100644 --- a/server/controllers/api/accounts.ts +++ b/server/controllers/api/accounts.ts | |||
@@ -86,9 +86,11 @@ async function listAccountVideos (req: express.Request, res: express.Response, n | |||
86 | languageOneOf: req.query.languageOneOf, | 86 | languageOneOf: req.query.languageOneOf, |
87 | tagsOneOf: req.query.tagsOneOf, | 87 | tagsOneOf: req.query.tagsOneOf, |
88 | tagsAllOf: req.query.tagsAllOf, | 88 | tagsAllOf: req.query.tagsAllOf, |
89 | filter: req.query.filter, | ||
89 | nsfw: buildNSFWFilter(res, req.query.nsfw), | 90 | nsfw: buildNSFWFilter(res, req.query.nsfw), |
90 | withFiles: false, | 91 | withFiles: false, |
91 | accountId: account.id | 92 | accountId: account.id, |
93 | userId: res.locals.oauth ? res.locals.oauth.token.User.id : undefined | ||
92 | }) | 94 | }) |
93 | 95 | ||
94 | return res.json(getFormattedObjects(resultList.data, resultList.total)) | 96 | return res.json(getFormattedObjects(resultList.data, resultList.total)) |
diff --git a/server/controllers/api/search.ts b/server/controllers/api/search.ts index 4be2b5ef7..a8a6cfb08 100644 --- a/server/controllers/api/search.ts +++ b/server/controllers/api/search.ts | |||
@@ -118,6 +118,7 @@ async function searchVideosDB (query: VideosSearchQuery, res: express.Response) | |||
118 | const options = Object.assign(query, { | 118 | const options = Object.assign(query, { |
119 | includeLocalVideos: true, | 119 | includeLocalVideos: true, |
120 | nsfw: buildNSFWFilter(res, query.nsfw), | 120 | nsfw: buildNSFWFilter(res, query.nsfw), |
121 | filter: query.filter, | ||
121 | userId: res.locals.oauth ? res.locals.oauth.token.User.id : undefined | 122 | userId: res.locals.oauth ? res.locals.oauth.token.User.id : undefined |
122 | }) | 123 | }) |
123 | const resultList = await VideoModel.searchAndPopulateAccountAndServer(options) | 124 | const resultList = await VideoModel.searchAndPopulateAccountAndServer(options) |
diff --git a/server/controllers/api/video-channel.ts b/server/controllers/api/video-channel.ts index 1fa842d9c..c84d1be58 100644 --- a/server/controllers/api/video-channel.ts +++ b/server/controllers/api/video-channel.ts | |||
@@ -215,9 +215,11 @@ async function listVideoChannelVideos (req: express.Request, res: express.Respon | |||
215 | languageOneOf: req.query.languageOneOf, | 215 | languageOneOf: req.query.languageOneOf, |
216 | tagsOneOf: req.query.tagsOneOf, | 216 | tagsOneOf: req.query.tagsOneOf, |
217 | tagsAllOf: req.query.tagsAllOf, | 217 | tagsAllOf: req.query.tagsAllOf, |
218 | filter: req.query.filter, | ||
218 | nsfw: buildNSFWFilter(res, req.query.nsfw), | 219 | nsfw: buildNSFWFilter(res, req.query.nsfw), |
219 | withFiles: false, | 220 | withFiles: false, |
220 | videoChannelId: videoChannelInstance.id | 221 | videoChannelId: videoChannelInstance.id, |
222 | userId: res.locals.oauth ? res.locals.oauth.token.User.id : undefined | ||
221 | }) | 223 | }) |
222 | 224 | ||
223 | return res.json(getFormattedObjects(resultList.data, resultList.total)) | 225 | return res.json(getFormattedObjects(resultList.data, resultList.total)) |
diff --git a/server/controllers/feeds.ts b/server/controllers/feeds.ts index b30ad8e8d..ccb9b6029 100644 --- a/server/controllers/feeds.ts +++ b/server/controllers/feeds.ts | |||
@@ -1,7 +1,14 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { CONFIG, FEEDS, ROUTE_CACHE_LIFETIME } from '../initializers/constants' | 2 | import { CONFIG, FEEDS, ROUTE_CACHE_LIFETIME } from '../initializers/constants' |
3 | import { THUMBNAILS_SIZE } from '../initializers' | 3 | import { THUMBNAILS_SIZE } from '../initializers' |
4 | import { asyncMiddleware, setDefaultSort, videoCommentsFeedsValidator, videoFeedsValidator, videosSortValidator } from '../middlewares' | 4 | import { |
5 | asyncMiddleware, | ||
6 | commonVideosFiltersValidator, | ||
7 | setDefaultSort, | ||
8 | videoCommentsFeedsValidator, | ||
9 | videoFeedsValidator, | ||
10 | videosSortValidator | ||
11 | } from '../middlewares' | ||
5 | import { VideoModel } from '../models/video/video' | 12 | import { VideoModel } from '../models/video/video' |
6 | import * as Feed from 'pfeed' | 13 | import * as Feed from 'pfeed' |
7 | import { AccountModel } from '../models/account/account' | 14 | import { AccountModel } from '../models/account/account' |
@@ -22,6 +29,7 @@ feedsRouter.get('/feeds/videos.:format', | |||
22 | videosSortValidator, | 29 | videosSortValidator, |
23 | setDefaultSort, | 30 | setDefaultSort, |
24 | asyncMiddleware(cacheRoute(ROUTE_CACHE_LIFETIME.FEEDS)), | 31 | asyncMiddleware(cacheRoute(ROUTE_CACHE_LIFETIME.FEEDS)), |
32 | commonVideosFiltersValidator, | ||
25 | asyncMiddleware(videoFeedsValidator), | 33 | asyncMiddleware(videoFeedsValidator), |
26 | asyncMiddleware(generateVideoFeed) | 34 | asyncMiddleware(generateVideoFeed) |
27 | ) | 35 | ) |
diff --git a/server/helpers/custom-validators/videos.ts b/server/helpers/custom-validators/videos.ts index 714f7ac95..a13b09ac8 100644 --- a/server/helpers/custom-validators/videos.ts +++ b/server/helpers/custom-validators/videos.ts | |||
@@ -3,7 +3,7 @@ import 'express-validator' | |||
3 | import { values } from 'lodash' | 3 | import { values } from 'lodash' |
4 | import 'multer' | 4 | import 'multer' |
5 | import * as validator from 'validator' | 5 | import * as validator from 'validator' |
6 | import { UserRight, VideoPrivacy, VideoRateType } from '../../../shared' | 6 | import { UserRight, VideoFilter, VideoPrivacy, VideoRateType } from '../../../shared' |
7 | import { | 7 | import { |
8 | CONSTRAINTS_FIELDS, | 8 | CONSTRAINTS_FIELDS, |
9 | VIDEO_CATEGORIES, | 9 | VIDEO_CATEGORIES, |
@@ -22,6 +22,10 @@ import { fetchVideo, VideoFetchType } from '../video' | |||
22 | 22 | ||
23 | const VIDEOS_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.VIDEOS | 23 | const VIDEOS_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.VIDEOS |
24 | 24 | ||
25 | function isVideoFilterValid (filter: VideoFilter) { | ||
26 | return filter === 'local' || filter === 'all-local' | ||
27 | } | ||
28 | |||
25 | function isVideoCategoryValid (value: any) { | 29 | function isVideoCategoryValid (value: any) { |
26 | return value === null || VIDEO_CATEGORIES[ value ] !== undefined | 30 | return value === null || VIDEO_CATEGORIES[ value ] !== undefined |
27 | } | 31 | } |
@@ -225,5 +229,6 @@ export { | |||
225 | isVideoExist, | 229 | isVideoExist, |
226 | isVideoImage, | 230 | isVideoImage, |
227 | isVideoChannelOfAccountExist, | 231 | isVideoChannelOfAccountExist, |
228 | isVideoSupportValid | 232 | isVideoSupportValid, |
233 | isVideoFilterValid | ||
229 | } | 234 | } |
diff --git a/server/helpers/express-utils.ts b/server/helpers/express-utils.ts index 8a9cee8c5..162fe2244 100644 --- a/server/helpers/express-utils.ts +++ b/server/helpers/express-utils.ts | |||
@@ -2,7 +2,6 @@ import * as express from 'express' | |||
2 | import * as multer from 'multer' | 2 | import * as multer from 'multer' |
3 | import { CONFIG, REMOTE_SCHEME } from '../initializers' | 3 | import { CONFIG, REMOTE_SCHEME } from '../initializers' |
4 | import { logger } from './logger' | 4 | import { logger } from './logger' |
5 | import { User } from '../../shared/models/users' | ||
6 | import { deleteFileAsync, generateRandomString } from './utils' | 5 | import { deleteFileAsync, generateRandomString } from './utils' |
7 | import { extname } from 'path' | 6 | import { extname } from 'path' |
8 | import { isArray } from './custom-validators/misc' | 7 | import { isArray } from './custom-validators/misc' |
@@ -101,7 +100,7 @@ function createReqFiles ( | |||
101 | } | 100 | } |
102 | 101 | ||
103 | function isUserAbleToSearchRemoteURI (res: express.Response) { | 102 | function isUserAbleToSearchRemoteURI (res: express.Response) { |
104 | const user: User = res.locals.oauth ? res.locals.oauth.token.User : undefined | 103 | const user: UserModel = res.locals.oauth ? res.locals.oauth.token.User : undefined |
105 | 104 | ||
106 | return CONFIG.SEARCH.REMOTE_URI.ANONYMOUS === true || | 105 | return CONFIG.SEARCH.REMOTE_URI.ANONYMOUS === true || |
107 | (CONFIG.SEARCH.REMOTE_URI.USERS === true && user !== undefined) | 106 | (CONFIG.SEARCH.REMOTE_URI.USERS === true && user !== undefined) |
diff --git a/server/middlewares/validators/search.ts b/server/middlewares/validators/search.ts index 8baf643a5..6a95d6095 100644 --- a/server/middlewares/validators/search.ts +++ b/server/middlewares/validators/search.ts | |||
@@ -2,8 +2,7 @@ import * as express from 'express' | |||
2 | import { areValidationErrors } from './utils' | 2 | import { areValidationErrors } from './utils' |
3 | import { logger } from '../../helpers/logger' | 3 | import { logger } from '../../helpers/logger' |
4 | import { query } from 'express-validator/check' | 4 | import { query } from 'express-validator/check' |
5 | import { isNumberArray, isStringArray, isNSFWQueryValid } from '../../helpers/custom-validators/search' | 5 | import { isDateValid } from '../../helpers/custom-validators/misc' |
6 | import { isBooleanValid, isDateValid, toArray } from '../../helpers/custom-validators/misc' | ||
7 | 6 | ||
8 | const videosSearchValidator = [ | 7 | const videosSearchValidator = [ |
9 | query('search').optional().not().isEmpty().withMessage('Should have a valid search'), | 8 | query('search').optional().not().isEmpty().withMessage('Should have a valid search'), |
@@ -35,44 +34,9 @@ const videoChannelsSearchValidator = [ | |||
35 | } | 34 | } |
36 | ] | 35 | ] |
37 | 36 | ||
38 | const commonVideosFiltersValidator = [ | ||
39 | query('categoryOneOf') | ||
40 | .optional() | ||
41 | .customSanitizer(toArray) | ||
42 | .custom(isNumberArray).withMessage('Should have a valid one of category array'), | ||
43 | query('licenceOneOf') | ||
44 | .optional() | ||
45 | .customSanitizer(toArray) | ||
46 | .custom(isNumberArray).withMessage('Should have a valid one of licence array'), | ||
47 | query('languageOneOf') | ||
48 | .optional() | ||
49 | .customSanitizer(toArray) | ||
50 | .custom(isStringArray).withMessage('Should have a valid one of language array'), | ||
51 | query('tagsOneOf') | ||
52 | .optional() | ||
53 | .customSanitizer(toArray) | ||
54 | .custom(isStringArray).withMessage('Should have a valid one of tags array'), | ||
55 | query('tagsAllOf') | ||
56 | .optional() | ||
57 | .customSanitizer(toArray) | ||
58 | .custom(isStringArray).withMessage('Should have a valid all of tags array'), | ||
59 | query('nsfw') | ||
60 | .optional() | ||
61 | .custom(isNSFWQueryValid).withMessage('Should have a valid NSFW attribute'), | ||
62 | |||
63 | (req: express.Request, res: express.Response, next: express.NextFunction) => { | ||
64 | logger.debug('Checking commons video filters query', { parameters: req.query }) | ||
65 | |||
66 | if (areValidationErrors(req, res)) return | ||
67 | |||
68 | return next() | ||
69 | } | ||
70 | ] | ||
71 | |||
72 | // --------------------------------------------------------------------------- | 37 | // --------------------------------------------------------------------------- |
73 | 38 | ||
74 | export { | 39 | export { |
75 | commonVideosFiltersValidator, | ||
76 | videoChannelsSearchValidator, | 40 | videoChannelsSearchValidator, |
77 | videosSearchValidator | 41 | videosSearchValidator |
78 | } | 42 | } |
diff --git a/server/middlewares/validators/videos/videos.ts b/server/middlewares/validators/videos/videos.ts index 1d0a64bb1..9dc52a134 100644 --- a/server/middlewares/validators/videos/videos.ts +++ b/server/middlewares/validators/videos/videos.ts | |||
@@ -1,6 +1,6 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import 'express-validator' | 2 | import 'express-validator' |
3 | import { body, param, ValidationChain } from 'express-validator/check' | 3 | import { body, param, query, ValidationChain } from 'express-validator/check' |
4 | import { UserRight, VideoChangeOwnershipStatus, VideoPrivacy } from '../../../../shared' | 4 | import { UserRight, VideoChangeOwnershipStatus, VideoPrivacy } from '../../../../shared' |
5 | import { | 5 | import { |
6 | isBooleanValid, | 6 | isBooleanValid, |
@@ -8,6 +8,7 @@ import { | |||
8 | isIdOrUUIDValid, | 8 | isIdOrUUIDValid, |
9 | isIdValid, | 9 | isIdValid, |
10 | isUUIDValid, | 10 | isUUIDValid, |
11 | toArray, | ||
11 | toIntOrNull, | 12 | toIntOrNull, |
12 | toValueOrNull | 13 | toValueOrNull |
13 | } from '../../../helpers/custom-validators/misc' | 14 | } from '../../../helpers/custom-validators/misc' |
@@ -19,6 +20,7 @@ import { | |||
19 | isVideoDescriptionValid, | 20 | isVideoDescriptionValid, |
20 | isVideoExist, | 21 | isVideoExist, |
21 | isVideoFile, | 22 | isVideoFile, |
23 | isVideoFilterValid, | ||
22 | isVideoImage, | 24 | isVideoImage, |
23 | isVideoLanguageValid, | 25 | isVideoLanguageValid, |
24 | isVideoLicenceValid, | 26 | isVideoLicenceValid, |
@@ -42,6 +44,7 @@ import { VideoChangeOwnershipAccept } from '../../../../shared/models/videos/vid | |||
42 | import { VideoChangeOwnershipModel } from '../../../models/video/video-change-ownership' | 44 | import { VideoChangeOwnershipModel } from '../../../models/video/video-change-ownership' |
43 | import { AccountModel } from '../../../models/account/account' | 45 | import { AccountModel } from '../../../models/account/account' |
44 | import { VideoFetchType } from '../../../helpers/video' | 46 | import { VideoFetchType } from '../../../helpers/video' |
47 | import { isNSFWQueryValid, isNumberArray, isStringArray } from '../../../helpers/custom-validators/search' | ||
45 | 48 | ||
46 | const videosAddValidator = getCommonVideoAttributes().concat([ | 49 | const videosAddValidator = getCommonVideoAttributes().concat([ |
47 | body('videofile') | 50 | body('videofile') |
@@ -359,6 +362,51 @@ function getCommonVideoAttributes () { | |||
359 | ] as (ValidationChain | express.Handler)[] | 362 | ] as (ValidationChain | express.Handler)[] |
360 | } | 363 | } |
361 | 364 | ||
365 | const commonVideosFiltersValidator = [ | ||
366 | query('categoryOneOf') | ||
367 | .optional() | ||
368 | .customSanitizer(toArray) | ||
369 | .custom(isNumberArray).withMessage('Should have a valid one of category array'), | ||
370 | query('licenceOneOf') | ||
371 | .optional() | ||
372 | .customSanitizer(toArray) | ||
373 | .custom(isNumberArray).withMessage('Should have a valid one of licence array'), | ||
374 | query('languageOneOf') | ||
375 | .optional() | ||
376 | .customSanitizer(toArray) | ||
377 | .custom(isStringArray).withMessage('Should have a valid one of language array'), | ||
378 | query('tagsOneOf') | ||
379 | .optional() | ||
380 | .customSanitizer(toArray) | ||
381 | .custom(isStringArray).withMessage('Should have a valid one of tags array'), | ||
382 | query('tagsAllOf') | ||
383 | .optional() | ||
384 | .customSanitizer(toArray) | ||
385 | .custom(isStringArray).withMessage('Should have a valid all of tags array'), | ||
386 | query('nsfw') | ||
387 | .optional() | ||
388 | .custom(isNSFWQueryValid).withMessage('Should have a valid NSFW attribute'), | ||
389 | query('filter') | ||
390 | .optional() | ||
391 | .custom(isVideoFilterValid).withMessage('Should have a valid filter attribute'), | ||
392 | |||
393 | (req: express.Request, res: express.Response, next: express.NextFunction) => { | ||
394 | logger.debug('Checking commons video filters query', { parameters: req.query }) | ||
395 | |||
396 | if (areValidationErrors(req, res)) return | ||
397 | |||
398 | const user: UserModel = res.locals.oauth ? res.locals.oauth.token.User : undefined | ||
399 | if (req.query.filter === 'all-local' && (!user || user.hasRight(UserRight.SEE_ALL_VIDEOS) === false)) { | ||
400 | res.status(401) | ||
401 | .json({ error: 'You are not allowed to see all local videos.' }) | ||
402 | |||
403 | return | ||
404 | } | ||
405 | |||
406 | return next() | ||
407 | } | ||
408 | ] | ||
409 | |||
362 | // --------------------------------------------------------------------------- | 410 | // --------------------------------------------------------------------------- |
363 | 411 | ||
364 | export { | 412 | export { |
@@ -375,7 +423,9 @@ export { | |||
375 | videosTerminateChangeOwnershipValidator, | 423 | videosTerminateChangeOwnershipValidator, |
376 | videosAcceptChangeOwnershipValidator, | 424 | videosAcceptChangeOwnershipValidator, |
377 | 425 | ||
378 | getCommonVideoAttributes | 426 | getCommonVideoAttributes, |
427 | |||
428 | commonVideosFiltersValidator | ||
379 | } | 429 | } |
380 | 430 | ||
381 | // --------------------------------------------------------------------------- | 431 | // --------------------------------------------------------------------------- |
diff --git a/server/models/video/video.ts b/server/models/video/video.ts index 070ac7623..4f3f75613 100644 --- a/server/models/video/video.ts +++ b/server/models/video/video.ts | |||
@@ -235,7 +235,14 @@ type AvailableForListIDsOptions = { | |||
235 | ) | 235 | ) |
236 | } | 236 | } |
237 | ] | 237 | ] |
238 | }, | 238 | } |
239 | }, | ||
240 | include: [] | ||
241 | } | ||
242 | |||
243 | // Only list public/published videos | ||
244 | if (!options.filter || options.filter !== 'all-local') { | ||
245 | const privacyWhere = { | ||
239 | // Always list public videos | 246 | // Always list public videos |
240 | privacy: VideoPrivacy.PUBLIC, | 247 | privacy: VideoPrivacy.PUBLIC, |
241 | // Always list published videos, or videos that are being transcoded but on which we don't want to wait for transcoding | 248 | // Always list published videos, or videos that are being transcoded but on which we don't want to wait for transcoding |
@@ -250,8 +257,9 @@ type AvailableForListIDsOptions = { | |||
250 | } | 257 | } |
251 | } | 258 | } |
252 | ] | 259 | ] |
253 | }, | 260 | } |
254 | include: [] | 261 | |
262 | Object.assign(query.where, privacyWhere) | ||
255 | } | 263 | } |
256 | 264 | ||
257 | if (options.filter || options.accountId || options.videoChannelId) { | 265 | if (options.filter || options.accountId || options.videoChannelId) { |
@@ -969,6 +977,10 @@ export class VideoModel extends Model<VideoModel> { | |||
969 | trendingDays?: number, | 977 | trendingDays?: number, |
970 | userId?: number | 978 | userId?: number |
971 | }, countVideos = true) { | 979 | }, countVideos = true) { |
980 | if (options.filter && options.filter === 'all-local' && !options.userId) { | ||
981 | throw new Error('Try to filter all-local but no userId is provided') | ||
982 | } | ||
983 | |||
972 | const query: IFindOptions<VideoModel> = { | 984 | const query: IFindOptions<VideoModel> = { |
973 | offset: options.start, | 985 | offset: options.start, |
974 | limit: options.count, | 986 | limit: options.count, |
@@ -1021,7 +1033,8 @@ export class VideoModel extends Model<VideoModel> { | |||
1021 | tagsAllOf?: string[] | 1033 | tagsAllOf?: string[] |
1022 | durationMin?: number // seconds | 1034 | durationMin?: number // seconds |
1023 | durationMax?: number // seconds | 1035 | durationMax?: number // seconds |
1024 | userId?: number | 1036 | userId?: number, |
1037 | filter?: VideoFilter | ||
1025 | }) { | 1038 | }) { |
1026 | const whereAnd = [] | 1039 | const whereAnd = [] |
1027 | 1040 | ||
@@ -1098,7 +1111,8 @@ export class VideoModel extends Model<VideoModel> { | |||
1098 | languageOneOf: options.languageOneOf, | 1111 | languageOneOf: options.languageOneOf, |
1099 | tagsOneOf: options.tagsOneOf, | 1112 | tagsOneOf: options.tagsOneOf, |
1100 | tagsAllOf: options.tagsAllOf, | 1113 | tagsAllOf: options.tagsAllOf, |
1101 | userId: options.userId | 1114 | userId: options.userId, |
1115 | filter: options.filter | ||
1102 | } | 1116 | } |
1103 | 1117 | ||
1104 | return VideoModel.getAvailableForApi(query, queryOptions) | 1118 | return VideoModel.getAvailableForApi(query, queryOptions) |
@@ -1262,7 +1276,7 @@ export class VideoModel extends Model<VideoModel> { | |||
1262 | } | 1276 | } |
1263 | 1277 | ||
1264 | private static buildActorWhereWithFilter (filter?: VideoFilter) { | 1278 | private static buildActorWhereWithFilter (filter?: VideoFilter) { |
1265 | if (filter && filter === 'local') { | 1279 | if (filter && (filter === 'local' || filter === 'all-local')) { |
1266 | return { | 1280 | return { |
1267 | serverId: null | 1281 | serverId: null |
1268 | } | 1282 | } |
diff --git a/server/tests/api/check-params/index.ts b/server/tests/api/check-params/index.ts index 71a217649..bfc550ae5 100644 --- a/server/tests/api/check-params/index.ts +++ b/server/tests/api/check-params/index.ts | |||
@@ -15,4 +15,5 @@ import './video-channels' | |||
15 | import './video-comments' | 15 | import './video-comments' |
16 | import './video-imports' | 16 | import './video-imports' |
17 | import './videos' | 17 | import './videos' |
18 | import './videos-filter' | ||
18 | import './videos-history' | 19 | import './videos-history' |
diff --git a/server/tests/api/check-params/videos-filter.ts b/server/tests/api/check-params/videos-filter.ts new file mode 100644 index 000000000..784cd8ba1 --- /dev/null +++ b/server/tests/api/check-params/videos-filter.ts | |||
@@ -0,0 +1,127 @@ | |||
1 | /* tslint:disable:no-unused-expression */ | ||
2 | |||
3 | import * as chai from 'chai' | ||
4 | import 'mocha' | ||
5 | import { | ||
6 | createUser, | ||
7 | flushTests, | ||
8 | killallServers, | ||
9 | makeGetRequest, | ||
10 | runServer, | ||
11 | ServerInfo, | ||
12 | setAccessTokensToServers, | ||
13 | userLogin | ||
14 | } from '../../utils' | ||
15 | import { UserRole } from '../../../../shared/models/users' | ||
16 | |||
17 | const expect = chai.expect | ||
18 | |||
19 | async function testEndpoints (server: ServerInfo, token: string, filter: string, statusCodeExpected: number) { | ||
20 | const paths = [ | ||
21 | '/api/v1/video-channels/root_channel/videos', | ||
22 | '/api/v1/accounts/root/videos', | ||
23 | '/api/v1/videos', | ||
24 | '/api/v1/search/videos' | ||
25 | ] | ||
26 | |||
27 | for (const path of paths) { | ||
28 | await makeGetRequest({ | ||
29 | url: server.url, | ||
30 | path, | ||
31 | token, | ||
32 | query: { | ||
33 | filter | ||
34 | }, | ||
35 | statusCodeExpected | ||
36 | }) | ||
37 | } | ||
38 | } | ||
39 | |||
40 | describe('Test videos filters', function () { | ||
41 | let server: ServerInfo | ||
42 | let userAccessToken: string | ||
43 | let moderatorAccessToken: string | ||
44 | |||
45 | // --------------------------------------------------------------- | ||
46 | |||
47 | before(async function () { | ||
48 | this.timeout(30000) | ||
49 | |||
50 | await flushTests() | ||
51 | |||
52 | server = await runServer(1) | ||
53 | |||
54 | await setAccessTokensToServers([ server ]) | ||
55 | |||
56 | const user = { username: 'user1', password: 'my super password' } | ||
57 | await createUser(server.url, server.accessToken, user.username, user.password) | ||
58 | userAccessToken = await userLogin(server, user) | ||
59 | |||
60 | const moderator = { username: 'moderator', password: 'my super password' } | ||
61 | await createUser( | ||
62 | server.url, | ||
63 | server.accessToken, | ||
64 | moderator.username, | ||
65 | moderator.password, | ||
66 | undefined, | ||
67 | undefined, | ||
68 | UserRole.MODERATOR | ||
69 | ) | ||
70 | moderatorAccessToken = await userLogin(server, moderator) | ||
71 | }) | ||
72 | |||
73 | describe('When setting a video filter', function () { | ||
74 | |||
75 | it('Should fail with a bad filter', async function () { | ||
76 | await testEndpoints(server, server.accessToken, 'bad-filter', 400) | ||
77 | }) | ||
78 | |||
79 | it('Should succeed with a good filter', async function () { | ||
80 | await testEndpoints(server, server.accessToken,'local', 200) | ||
81 | }) | ||
82 | |||
83 | it('Should fail to list all-local with a simple user', async function () { | ||
84 | await testEndpoints(server, userAccessToken, 'all-local', 401) | ||
85 | }) | ||
86 | |||
87 | it('Should succeed to list all-local with a moderator', async function () { | ||
88 | await testEndpoints(server, moderatorAccessToken, 'all-local', 200) | ||
89 | }) | ||
90 | |||
91 | it('Should succeed to list all-local with an admin', async function () { | ||
92 | await testEndpoints(server, server.accessToken, 'all-local', 200) | ||
93 | }) | ||
94 | |||
95 | // Because we cannot authenticate the user on the RSS endpoint | ||
96 | it('Should fail on the feeds endpoint with the all-local filter', async function () { | ||
97 | await makeGetRequest({ | ||
98 | url: server.url, | ||
99 | path: '/feeds/videos.json', | ||
100 | statusCodeExpected: 401, | ||
101 | query: { | ||
102 | filter: 'all-local' | ||
103 | } | ||
104 | }) | ||
105 | }) | ||
106 | |||
107 | it('Should succed on the feeds endpoint with the local filter', async function () { | ||
108 | await makeGetRequest({ | ||
109 | url: server.url, | ||
110 | path: '/feeds/videos.json', | ||
111 | statusCodeExpected: 200, | ||
112 | query: { | ||
113 | filter: 'local' | ||
114 | } | ||
115 | }) | ||
116 | }) | ||
117 | }) | ||
118 | |||
119 | after(async function () { | ||
120 | killallServers([ server ]) | ||
121 | |||
122 | // Keep the logs if the test failed | ||
123 | if (this['ok']) { | ||
124 | await flushTests() | ||
125 | } | ||
126 | }) | ||
127 | }) | ||
diff --git a/server/tests/api/videos/index.ts b/server/tests/api/videos/index.ts index 09bb62a8d..9bdb78491 100644 --- a/server/tests/api/videos/index.ts +++ b/server/tests/api/videos/index.ts | |||
@@ -14,5 +14,6 @@ import './video-nsfw' | |||
14 | import './video-privacy' | 14 | import './video-privacy' |
15 | import './video-schedule-update' | 15 | import './video-schedule-update' |
16 | import './video-transcoder' | 16 | import './video-transcoder' |
17 | import './videos-filter' | ||
17 | import './videos-history' | 18 | import './videos-history' |
18 | import './videos-overview' | 19 | import './videos-overview' |
diff --git a/server/tests/api/videos/videos-filter.ts b/server/tests/api/videos/videos-filter.ts new file mode 100644 index 000000000..a7588129f --- /dev/null +++ b/server/tests/api/videos/videos-filter.ts | |||
@@ -0,0 +1,130 @@ | |||
1 | /* tslint:disable:no-unused-expression */ | ||
2 | |||
3 | import * as chai from 'chai' | ||
4 | import 'mocha' | ||
5 | import { | ||
6 | createUser, | ||
7 | doubleFollow, | ||
8 | flushAndRunMultipleServers, | ||
9 | flushTests, | ||
10 | killallServers, | ||
11 | makeGetRequest, | ||
12 | ServerInfo, | ||
13 | setAccessTokensToServers, | ||
14 | uploadVideo, | ||
15 | userLogin | ||
16 | } from '../../utils' | ||
17 | import { Video, VideoPrivacy } from '../../../../shared/models/videos' | ||
18 | import { UserRole } from '../../../../shared/models/users' | ||
19 | |||
20 | const expect = chai.expect | ||
21 | |||
22 | async function getVideosNames (server: ServerInfo, token: string, filter: string, statusCodeExpected = 200) { | ||
23 | const paths = [ | ||
24 | '/api/v1/video-channels/root_channel/videos', | ||
25 | '/api/v1/accounts/root/videos', | ||
26 | '/api/v1/videos', | ||
27 | '/api/v1/search/videos' | ||
28 | ] | ||
29 | |||
30 | const videosResults: Video[][] = [] | ||
31 | |||
32 | for (const path of paths) { | ||
33 | const res = await makeGetRequest({ | ||
34 | url: server.url, | ||
35 | path, | ||
36 | token, | ||
37 | query: { | ||
38 | sort: 'createdAt', | ||
39 | filter | ||
40 | }, | ||
41 | statusCodeExpected | ||
42 | }) | ||
43 | |||
44 | videosResults.push(res.body.data.map(v => v.name)) | ||
45 | } | ||
46 | |||
47 | return videosResults | ||
48 | } | ||
49 | |||
50 | describe('Test videos filter validator', function () { | ||
51 | let servers: ServerInfo[] | ||
52 | |||
53 | // --------------------------------------------------------------- | ||
54 | |||
55 | before(async function () { | ||
56 | this.timeout(120000) | ||
57 | |||
58 | await flushTests() | ||
59 | |||
60 | servers = await flushAndRunMultipleServers(2) | ||
61 | |||
62 | await setAccessTokensToServers(servers) | ||
63 | |||
64 | for (const server of servers) { | ||
65 | const moderator = { username: 'moderator', password: 'my super password' } | ||
66 | await createUser( | ||
67 | server.url, | ||
68 | server.accessToken, | ||
69 | moderator.username, | ||
70 | moderator.password, | ||
71 | undefined, | ||
72 | undefined, | ||
73 | UserRole.MODERATOR | ||
74 | ) | ||
75 | server['moderatorAccessToken'] = await userLogin(server, moderator) | ||
76 | |||
77 | await uploadVideo(server.url, server.accessToken, { name: 'public ' + server.serverNumber }) | ||
78 | |||
79 | { | ||
80 | const attributes = { name: 'unlisted ' + server.serverNumber, privacy: VideoPrivacy.UNLISTED } | ||
81 | await uploadVideo(server.url, server.accessToken, attributes) | ||
82 | } | ||
83 | |||
84 | { | ||
85 | const attributes = { name: 'private ' + server.serverNumber, privacy: VideoPrivacy.PRIVATE } | ||
86 | await uploadVideo(server.url, server.accessToken, attributes) | ||
87 | } | ||
88 | } | ||
89 | |||
90 | await doubleFollow(servers[0], servers[1]) | ||
91 | }) | ||
92 | |||
93 | describe('Check videos filter', function () { | ||
94 | |||
95 | it('Should display local videos', async function () { | ||
96 | for (const server of servers) { | ||
97 | const namesResults = await getVideosNames(server, server.accessToken, 'local') | ||
98 | for (const names of namesResults) { | ||
99 | expect(names).to.have.lengthOf(1) | ||
100 | expect(names[ 0 ]).to.equal('public ' + server.serverNumber) | ||
101 | } | ||
102 | } | ||
103 | }) | ||
104 | |||
105 | it('Should display all local videos by the admin or the moderator', async function () { | ||
106 | for (const server of servers) { | ||
107 | for (const token of [ server.accessToken, server['moderatorAccessToken'] ]) { | ||
108 | |||
109 | const namesResults = await getVideosNames(server, token, 'all-local') | ||
110 | for (const names of namesResults) { | ||
111 | expect(names).to.have.lengthOf(3) | ||
112 | |||
113 | expect(names[ 0 ]).to.equal('public ' + server.serverNumber) | ||
114 | expect(names[ 1 ]).to.equal('unlisted ' + server.serverNumber) | ||
115 | expect(names[ 2 ]).to.equal('private ' + server.serverNumber) | ||
116 | } | ||
117 | } | ||
118 | } | ||
119 | }) | ||
120 | }) | ||
121 | |||
122 | after(async function () { | ||
123 | killallServers(servers) | ||
124 | |||
125 | // Keep the logs if the test failed | ||
126 | if (this['ok']) { | ||
127 | await flushTests() | ||
128 | } | ||
129 | }) | ||
130 | }) | ||
diff --git a/shared/models/search/videos-search-query.model.ts b/shared/models/search/videos-search-query.model.ts index 29aa5c100..0db220758 100644 --- a/shared/models/search/videos-search-query.model.ts +++ b/shared/models/search/videos-search-query.model.ts | |||
@@ -1,4 +1,5 @@ | |||
1 | import { NSFWQuery } from './nsfw-query.model' | 1 | import { NSFWQuery } from './nsfw-query.model' |
2 | import { VideoFilter } from '../videos' | ||
2 | 3 | ||
3 | export interface VideosSearchQuery { | 4 | export interface VideosSearchQuery { |
4 | search?: string | 5 | search?: string |
@@ -23,4 +24,6 @@ export interface VideosSearchQuery { | |||
23 | 24 | ||
24 | durationMin?: number // seconds | 25 | durationMin?: number // seconds |
25 | durationMax?: number // seconds | 26 | durationMax?: number // seconds |
27 | |||
28 | filter?: VideoFilter | ||
26 | } | 29 | } |
diff --git a/shared/models/users/user-right.enum.ts b/shared/models/users/user-right.enum.ts index c4ccd632f..ed2c536ce 100644 --- a/shared/models/users/user-right.enum.ts +++ b/shared/models/users/user-right.enum.ts | |||
@@ -14,5 +14,6 @@ export enum UserRight { | |||
14 | REMOVE_ANY_VIDEO_CHANNEL, | 14 | REMOVE_ANY_VIDEO_CHANNEL, |
15 | REMOVE_ANY_VIDEO_COMMENT, | 15 | REMOVE_ANY_VIDEO_COMMENT, |
16 | UPDATE_ANY_VIDEO, | 16 | UPDATE_ANY_VIDEO, |
17 | SEE_ALL_VIDEOS, | ||
17 | CHANGE_VIDEO_OWNERSHIP | 18 | CHANGE_VIDEO_OWNERSHIP |
18 | } | 19 | } |
diff --git a/shared/models/users/user-role.ts b/shared/models/users/user-role.ts index 552aad999..d7020c0f2 100644 --- a/shared/models/users/user-role.ts +++ b/shared/models/users/user-role.ts | |||
@@ -26,7 +26,8 @@ const userRoleRights: { [ id: number ]: UserRight[] } = { | |||
26 | UserRight.REMOVE_ANY_VIDEO, | 26 | UserRight.REMOVE_ANY_VIDEO, |
27 | UserRight.REMOVE_ANY_VIDEO_CHANNEL, | 27 | UserRight.REMOVE_ANY_VIDEO_CHANNEL, |
28 | UserRight.REMOVE_ANY_VIDEO_COMMENT, | 28 | UserRight.REMOVE_ANY_VIDEO_COMMENT, |
29 | UserRight.UPDATE_ANY_VIDEO | 29 | UserRight.UPDATE_ANY_VIDEO, |
30 | UserRight.SEE_ALL_VIDEOS | ||
30 | ], | 31 | ], |
31 | 32 | ||
32 | [UserRole.USER]: [] | 33 | [UserRole.USER]: [] |
diff --git a/shared/models/videos/video-query.type.ts b/shared/models/videos/video-query.type.ts index ff0f527f3..f76a91aad 100644 --- a/shared/models/videos/video-query.type.ts +++ b/shared/models/videos/video-query.type.ts | |||
@@ -1 +1 @@ | |||
export type VideoFilter = 'local' | export type VideoFilter = 'local' | 'all-local' | ||