diff options
author | Chocobozzz <me@florianbigard.com> | 2018-07-20 14:35:18 +0200 |
---|---|---|
committer | Chocobozzz <me@florianbigard.com> | 2018-07-24 14:04:05 +0200 |
commit | d525fc399a14a8b16eaad6d4c0bc0a9c4093c3c9 (patch) | |
tree | 4305044c4a97bdf1275b241c63cb0e85151cfb6a | |
parent | 57c36b277e68b764dd34cb2e449f6e2ca3d1e9b6 (diff) | |
download | PeerTube-d525fc399a14a8b16eaad6d4c0bc0a9c4093c3c9.tar.gz PeerTube-d525fc399a14a8b16eaad6d4c0bc0a9c4093c3c9.tar.zst PeerTube-d525fc399a14a8b16eaad6d4c0bc0a9c4093c3c9.zip |
Add videos list filters
26 files changed, 831 insertions, 267 deletions
diff --git a/server/controllers/api/accounts.ts b/server/controllers/api/accounts.ts index 8e937276c..0117fc8c6 100644 --- a/server/controllers/api/accounts.ts +++ b/server/controllers/api/accounts.ts | |||
@@ -1,7 +1,7 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { getFormattedObjects } from '../../helpers/utils' | 2 | import { getFormattedObjects } from '../../helpers/utils' |
3 | import { | 3 | import { |
4 | asyncMiddleware, | 4 | asyncMiddleware, commonVideosFiltersValidator, |
5 | listVideoAccountChannelsValidator, | 5 | listVideoAccountChannelsValidator, |
6 | optionalAuthenticate, | 6 | optionalAuthenticate, |
7 | paginationValidator, | 7 | paginationValidator, |
@@ -11,7 +11,7 @@ import { | |||
11 | import { accountsNameWithHostGetValidator, accountsSortValidator, videosSortValidator } from '../../middlewares/validators' | 11 | import { accountsNameWithHostGetValidator, accountsSortValidator, videosSortValidator } from '../../middlewares/validators' |
12 | import { AccountModel } from '../../models/account/account' | 12 | import { AccountModel } from '../../models/account/account' |
13 | import { VideoModel } from '../../models/video/video' | 13 | import { VideoModel } from '../../models/video/video' |
14 | import { isNSFWHidden } from '../../helpers/express-utils' | 14 | import { buildNSFWFilter } from '../../helpers/express-utils' |
15 | import { VideoChannelModel } from '../../models/video/video-channel' | 15 | import { VideoChannelModel } from '../../models/video/video-channel' |
16 | 16 | ||
17 | const accountsRouter = express.Router() | 17 | const accountsRouter = express.Router() |
@@ -36,6 +36,7 @@ accountsRouter.get('/:accountName/videos', | |||
36 | setDefaultSort, | 36 | setDefaultSort, |
37 | setDefaultPagination, | 37 | setDefaultPagination, |
38 | optionalAuthenticate, | 38 | optionalAuthenticate, |
39 | commonVideosFiltersValidator, | ||
39 | asyncMiddleware(listAccountVideos) | 40 | asyncMiddleware(listAccountVideos) |
40 | ) | 41 | ) |
41 | 42 | ||
@@ -77,7 +78,12 @@ async function listAccountVideos (req: express.Request, res: express.Response, n | |||
77 | start: req.query.start, | 78 | start: req.query.start, |
78 | count: req.query.count, | 79 | count: req.query.count, |
79 | sort: req.query.sort, | 80 | sort: req.query.sort, |
80 | hideNSFW: isNSFWHidden(res), | 81 | categoryOneOf: req.query.categoryOneOf, |
82 | licenceOneOf: req.query.licenceOneOf, | ||
83 | languageOneOf: req.query.languageOneOf, | ||
84 | tagsOneOf: req.query.tagsOneOf, | ||
85 | tagsAllOf: req.query.tagsAllOf, | ||
86 | nsfw: buildNSFWFilter(res, req.query.nsfw), | ||
81 | withFiles: false, | 87 | withFiles: false, |
82 | accountId: account.id | 88 | accountId: account.id |
83 | }) | 89 | }) |
diff --git a/server/controllers/api/search.ts b/server/controllers/api/search.ts index 2ff340b59..f810c7452 100644 --- a/server/controllers/api/search.ts +++ b/server/controllers/api/search.ts | |||
@@ -1,9 +1,10 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { isNSFWHidden } from '../../helpers/express-utils' | 2 | import { buildNSFWFilter } from '../../helpers/express-utils' |
3 | import { getFormattedObjects } from '../../helpers/utils' | 3 | import { getFormattedObjects } from '../../helpers/utils' |
4 | import { VideoModel } from '../../models/video/video' | 4 | import { VideoModel } from '../../models/video/video' |
5 | import { | 5 | import { |
6 | asyncMiddleware, | 6 | asyncMiddleware, |
7 | commonVideosFiltersValidator, | ||
7 | optionalAuthenticate, | 8 | optionalAuthenticate, |
8 | paginationValidator, | 9 | paginationValidator, |
9 | searchValidator, | 10 | searchValidator, |
@@ -11,6 +12,7 @@ import { | |||
11 | setDefaultSearchSort, | 12 | setDefaultSearchSort, |
12 | videosSearchSortValidator | 13 | videosSearchSortValidator |
13 | } from '../../middlewares' | 14 | } from '../../middlewares' |
15 | import { VideosSearchQuery } from '../../../shared/models/search' | ||
14 | 16 | ||
15 | const searchRouter = express.Router() | 17 | const searchRouter = express.Router() |
16 | 18 | ||
@@ -20,6 +22,7 @@ searchRouter.get('/videos', | |||
20 | videosSearchSortValidator, | 22 | videosSearchSortValidator, |
21 | setDefaultSearchSort, | 23 | setDefaultSearchSort, |
22 | optionalAuthenticate, | 24 | optionalAuthenticate, |
25 | commonVideosFiltersValidator, | ||
23 | searchValidator, | 26 | searchValidator, |
24 | asyncMiddleware(searchVideos) | 27 | asyncMiddleware(searchVideos) |
25 | ) | 28 | ) |
@@ -31,13 +34,10 @@ export { searchRouter } | |||
31 | // --------------------------------------------------------------------------- | 34 | // --------------------------------------------------------------------------- |
32 | 35 | ||
33 | async function searchVideos (req: express.Request, res: express.Response) { | 36 | async function searchVideos (req: express.Request, res: express.Response) { |
34 | const resultList = await VideoModel.searchAndPopulateAccountAndServer( | 37 | const query: VideosSearchQuery = req.query |
35 | req.query.search as string, | 38 | |
36 | req.query.start as number, | 39 | const options = Object.assign(query, { nsfw: buildNSFWFilter(res, query.nsfw) }) |
37 | req.query.count as number, | 40 | const resultList = await VideoModel.searchAndPopulateAccountAndServer(options) |
38 | req.query.sort as string, | ||
39 | isNSFWHidden(res) | ||
40 | ) | ||
41 | 41 | ||
42 | return res.json(getFormattedObjects(resultList.data, resultList.total)) | 42 | return res.json(getFormattedObjects(resultList.data, resultList.total)) |
43 | } | 43 | } |
diff --git a/server/controllers/api/video-channel.ts b/server/controllers/api/video-channel.ts index 1707732ee..0488ba8f5 100644 --- a/server/controllers/api/video-channel.ts +++ b/server/controllers/api/video-channel.ts | |||
@@ -3,7 +3,7 @@ import { getFormattedObjects, resetSequelizeInstance } from '../../helpers/utils | |||
3 | import { | 3 | import { |
4 | asyncMiddleware, | 4 | asyncMiddleware, |
5 | asyncRetryTransactionMiddleware, | 5 | asyncRetryTransactionMiddleware, |
6 | authenticate, | 6 | authenticate, commonVideosFiltersValidator, |
7 | optionalAuthenticate, | 7 | optionalAuthenticate, |
8 | paginationValidator, | 8 | paginationValidator, |
9 | setDefaultPagination, | 9 | setDefaultPagination, |
@@ -19,7 +19,7 @@ import { videosSortValidator } from '../../middlewares/validators' | |||
19 | import { sendUpdateActor } from '../../lib/activitypub/send' | 19 | import { sendUpdateActor } from '../../lib/activitypub/send' |
20 | import { VideoChannelCreate, VideoChannelUpdate } from '../../../shared' | 20 | import { VideoChannelCreate, VideoChannelUpdate } from '../../../shared' |
21 | import { createVideoChannel } from '../../lib/video-channel' | 21 | import { createVideoChannel } from '../../lib/video-channel' |
22 | import { createReqFiles, isNSFWHidden } from '../../helpers/express-utils' | 22 | import { createReqFiles, buildNSFWFilter } from '../../helpers/express-utils' |
23 | import { setAsyncActorKeys } from '../../lib/activitypub' | 23 | import { setAsyncActorKeys } from '../../lib/activitypub' |
24 | import { AccountModel } from '../../models/account/account' | 24 | import { AccountModel } from '../../models/account/account' |
25 | import { CONFIG, IMAGE_MIMETYPE_EXT, sequelizeTypescript } from '../../initializers' | 25 | import { CONFIG, IMAGE_MIMETYPE_EXT, sequelizeTypescript } from '../../initializers' |
@@ -79,6 +79,7 @@ videoChannelRouter.get('/:id/videos', | |||
79 | setDefaultSort, | 79 | setDefaultSort, |
80 | setDefaultPagination, | 80 | setDefaultPagination, |
81 | optionalAuthenticate, | 81 | optionalAuthenticate, |
82 | commonVideosFiltersValidator, | ||
82 | asyncMiddleware(listVideoChannelVideos) | 83 | asyncMiddleware(listVideoChannelVideos) |
83 | ) | 84 | ) |
84 | 85 | ||
@@ -189,7 +190,12 @@ async function listVideoChannelVideos (req: express.Request, res: express.Respon | |||
189 | start: req.query.start, | 190 | start: req.query.start, |
190 | count: req.query.count, | 191 | count: req.query.count, |
191 | sort: req.query.sort, | 192 | sort: req.query.sort, |
192 | hideNSFW: isNSFWHidden(res), | 193 | categoryOneOf: req.query.categoryOneOf, |
194 | licenceOneOf: req.query.licenceOneOf, | ||
195 | languageOneOf: req.query.languageOneOf, | ||
196 | tagsOneOf: req.query.tagsOneOf, | ||
197 | tagsAllOf: req.query.tagsAllOf, | ||
198 | nsfw: buildNSFWFilter(res, req.query.nsfw), | ||
193 | withFiles: false, | 199 | withFiles: false, |
194 | videoChannelId: videoChannelInstance.id | 200 | videoChannelId: videoChannelInstance.id |
195 | }) | 201 | }) |
diff --git a/server/controllers/api/videos/index.ts b/server/controllers/api/videos/index.ts index 547522123..101183eab 100644 --- a/server/controllers/api/videos/index.ts +++ b/server/controllers/api/videos/index.ts | |||
@@ -31,6 +31,7 @@ import { | |||
31 | asyncMiddleware, | 31 | asyncMiddleware, |
32 | asyncRetryTransactionMiddleware, | 32 | asyncRetryTransactionMiddleware, |
33 | authenticate, | 33 | authenticate, |
34 | commonVideosFiltersValidator, | ||
34 | optionalAuthenticate, | 35 | optionalAuthenticate, |
35 | paginationValidator, | 36 | paginationValidator, |
36 | setDefaultPagination, | 37 | setDefaultPagination, |
@@ -49,7 +50,7 @@ import { blacklistRouter } from './blacklist' | |||
49 | import { videoCommentRouter } from './comment' | 50 | import { videoCommentRouter } from './comment' |
50 | import { rateVideoRouter } from './rate' | 51 | import { rateVideoRouter } from './rate' |
51 | import { VideoFilter } from '../../../../shared/models/videos/video-query.type' | 52 | import { VideoFilter } from '../../../../shared/models/videos/video-query.type' |
52 | import { createReqFiles, isNSFWHidden } from '../../../helpers/express-utils' | 53 | import { createReqFiles, buildNSFWFilter } from '../../../helpers/express-utils' |
53 | import { ScheduleVideoUpdateModel } from '../../../models/video/schedule-video-update' | 54 | import { ScheduleVideoUpdateModel } from '../../../models/video/schedule-video-update' |
54 | import { videoCaptionsRouter } from './captions' | 55 | import { videoCaptionsRouter } from './captions' |
55 | 56 | ||
@@ -90,6 +91,7 @@ videosRouter.get('/', | |||
90 | setDefaultSort, | 91 | setDefaultSort, |
91 | setDefaultPagination, | 92 | setDefaultPagination, |
92 | optionalAuthenticate, | 93 | optionalAuthenticate, |
94 | commonVideosFiltersValidator, | ||
93 | asyncMiddleware(listVideos) | 95 | asyncMiddleware(listVideos) |
94 | ) | 96 | ) |
95 | videosRouter.put('/:id', | 97 | videosRouter.put('/:id', |
@@ -401,8 +403,12 @@ async function listVideos (req: express.Request, res: express.Response, next: ex | |||
401 | start: req.query.start, | 403 | start: req.query.start, |
402 | count: req.query.count, | 404 | count: req.query.count, |
403 | sort: req.query.sort, | 405 | sort: req.query.sort, |
404 | category: req.query.category, | 406 | categoryOneOf: req.query.categoryOneOf, |
405 | hideNSFW: isNSFWHidden(res), | 407 | licenceOneOf: req.query.licenceOneOf, |
408 | languageOneOf: req.query.languageOneOf, | ||
409 | tagsOneOf: req.query.tagsOneOf, | ||
410 | tagsAllOf: req.query.tagsAllOf, | ||
411 | nsfw: buildNSFWFilter(res, req.query.nsfw), | ||
406 | filter: req.query.filter as VideoFilter, | 412 | filter: req.query.filter as VideoFilter, |
407 | withFiles: false | 413 | withFiles: false |
408 | }) | 414 | }) |
diff --git a/server/controllers/feeds.ts b/server/controllers/feeds.ts index ff6b423d9..682f4abda 100644 --- a/server/controllers/feeds.ts +++ b/server/controllers/feeds.ts | |||
@@ -8,6 +8,7 @@ import { AccountModel } from '../models/account/account' | |||
8 | import { cacheRoute } from '../middlewares/cache' | 8 | import { cacheRoute } from '../middlewares/cache' |
9 | import { VideoChannelModel } from '../models/video/video-channel' | 9 | import { VideoChannelModel } from '../models/video/video-channel' |
10 | import { VideoCommentModel } from '../models/video/video-comment' | 10 | import { VideoCommentModel } from '../models/video/video-comment' |
11 | import { buildNSFWFilter } from '../helpers/express-utils' | ||
11 | 12 | ||
12 | const feedsRouter = express.Router() | 13 | const feedsRouter = express.Router() |
13 | 14 | ||
@@ -73,7 +74,7 @@ async function generateVideoFeed (req: express.Request, res: express.Response, n | |||
73 | 74 | ||
74 | const account: AccountModel = res.locals.account | 75 | const account: AccountModel = res.locals.account |
75 | const videoChannel: VideoChannelModel = res.locals.videoChannel | 76 | const videoChannel: VideoChannelModel = res.locals.videoChannel |
76 | const hideNSFW = CONFIG.INSTANCE.DEFAULT_NSFW_POLICY === 'do_not_list' | 77 | const nsfw = buildNSFWFilter(res, req.query.nsfw) |
77 | 78 | ||
78 | let name: string | 79 | let name: string |
79 | let description: string | 80 | let description: string |
@@ -95,7 +96,7 @@ async function generateVideoFeed (req: express.Request, res: express.Response, n | |||
95 | start, | 96 | start, |
96 | count: FEEDS.COUNT, | 97 | count: FEEDS.COUNT, |
97 | sort: req.query.sort, | 98 | sort: req.query.sort, |
98 | hideNSFW, | 99 | nsfw, |
99 | filter: req.query.filter, | 100 | filter: req.query.filter, |
100 | withFiles: true, | 101 | withFiles: true, |
101 | accountId: account ? account.id : null, | 102 | accountId: account ? account.id : null, |
diff --git a/server/helpers/custom-validators/misc.ts b/server/helpers/custom-validators/misc.ts index 455aae367..151fc852b 100644 --- a/server/helpers/custom-validators/misc.ts +++ b/server/helpers/custom-validators/misc.ts | |||
@@ -41,6 +41,12 @@ function toValueOrNull (value: string) { | |||
41 | return value | 41 | return value |
42 | } | 42 | } |
43 | 43 | ||
44 | function toArray (value: string) { | ||
45 | if (value && isArray(value) === false) return [ value ] | ||
46 | |||
47 | return value | ||
48 | } | ||
49 | |||
44 | function isFileValid ( | 50 | function isFileValid ( |
45 | files: { [ fieldname: string ]: Express.Multer.File[] } | Express.Multer.File[], | 51 | files: { [ fieldname: string ]: Express.Multer.File[] } | Express.Multer.File[], |
46 | mimeTypeRegex: string, | 52 | mimeTypeRegex: string, |
@@ -80,5 +86,6 @@ export { | |||
80 | toValueOrNull, | 86 | toValueOrNull, |
81 | isBooleanValid, | 87 | isBooleanValid, |
82 | toIntOrNull, | 88 | toIntOrNull, |
89 | toArray, | ||
83 | isFileValid | 90 | isFileValid |
84 | } | 91 | } |
diff --git a/server/helpers/custom-validators/search.ts b/server/helpers/custom-validators/search.ts new file mode 100644 index 000000000..2fde39160 --- /dev/null +++ b/server/helpers/custom-validators/search.ts | |||
@@ -0,0 +1,19 @@ | |||
1 | import * as validator from 'validator' | ||
2 | import 'express-validator' | ||
3 | |||
4 | import { isArray } from './misc' | ||
5 | |||
6 | function isNumberArray (value: any) { | ||
7 | return isArray(value) && value.every(v => validator.isInt('' + v)) | ||
8 | } | ||
9 | |||
10 | function isStringArray (value: any) { | ||
11 | return isArray(value) && value.every(v => typeof v === 'string') | ||
12 | } | ||
13 | |||
14 | // --------------------------------------------------------------------------- | ||
15 | |||
16 | export { | ||
17 | isNumberArray, | ||
18 | isStringArray | ||
19 | } | ||
diff --git a/server/helpers/express-utils.ts b/server/helpers/express-utils.ts index d023117a8..5bf1e1a5f 100644 --- a/server/helpers/express-utils.ts +++ b/server/helpers/express-utils.ts | |||
@@ -5,13 +5,19 @@ import { logger } from './logger' | |||
5 | import { User } from '../../shared/models/users' | 5 | import { User } from '../../shared/models/users' |
6 | import { generateRandomString } from './utils' | 6 | import { generateRandomString } from './utils' |
7 | 7 | ||
8 | function isNSFWHidden (res: express.Response) { | 8 | function buildNSFWFilter (res: express.Response, paramNSFW?: boolean) { |
9 | if (paramNSFW === true || paramNSFW === false) return paramNSFW | ||
10 | |||
9 | if (res.locals.oauth) { | 11 | if (res.locals.oauth) { |
10 | const user: User = res.locals.oauth.token.User | 12 | const user: User = res.locals.oauth.token.User |
11 | if (user) return user.nsfwPolicy === 'do_not_list' | 13 | // User does not want NSFW videos |
14 | if (user && user.nsfwPolicy === 'do_not_list') return false | ||
12 | } | 15 | } |
13 | 16 | ||
14 | return CONFIG.INSTANCE.DEFAULT_NSFW_POLICY === 'do_not_list' | 17 | if (CONFIG.INSTANCE.DEFAULT_NSFW_POLICY === 'do_not_list') return false |
18 | |||
19 | // Display all | ||
20 | return null | ||
15 | } | 21 | } |
16 | 22 | ||
17 | function getHostWithPort (host: string) { | 23 | function getHostWithPort (host: string) { |
@@ -70,7 +76,7 @@ function createReqFiles ( | |||
70 | // --------------------------------------------------------------------------- | 76 | // --------------------------------------------------------------------------- |
71 | 77 | ||
72 | export { | 78 | export { |
73 | isNSFWHidden, | 79 | buildNSFWFilter, |
74 | getHostWithPort, | 80 | getHostWithPort, |
75 | badRequest, | 81 | badRequest, |
76 | createReqFiles | 82 | createReqFiles |
diff --git a/server/initializers/constants.ts b/server/initializers/constants.ts index b966c0acb..9f220aea5 100644 --- a/server/initializers/constants.ts +++ b/server/initializers/constants.ts | |||
@@ -37,7 +37,7 @@ const SORTABLE_COLUMNS = { | |||
37 | FOLLOWERS: [ 'createdAt' ], | 37 | FOLLOWERS: [ 'createdAt' ], |
38 | FOLLOWING: [ 'createdAt' ], | 38 | FOLLOWING: [ 'createdAt' ], |
39 | 39 | ||
40 | VIDEOS_SEARCH: [ 'bestmatch', 'name', 'duration', 'createdAt', 'publishedAt', 'views', 'likes' ] | 40 | VIDEOS_SEARCH: [ 'match', 'name', 'duration', 'createdAt', 'publishedAt', 'views', 'likes' ] |
41 | } | 41 | } |
42 | 42 | ||
43 | const OAUTH_LIFETIME = { | 43 | const OAUTH_LIFETIME = { |
diff --git a/server/middlewares/sort.ts b/server/middlewares/sort.ts index 6307ee154..8a62c8be6 100644 --- a/server/middlewares/sort.ts +++ b/server/middlewares/sort.ts | |||
@@ -9,7 +9,7 @@ function setDefaultSort (req: express.Request, res: express.Response, next: expr | |||
9 | } | 9 | } |
10 | 10 | ||
11 | function setDefaultSearchSort (req: express.Request, res: express.Response, next: express.NextFunction) { | 11 | function setDefaultSearchSort (req: express.Request, res: express.Response, next: express.NextFunction) { |
12 | if (!req.query.sort) req.query.sort = '-bestmatch' | 12 | if (!req.query.sort) req.query.sort = '-match' |
13 | 13 | ||
14 | return next() | 14 | return next() |
15 | } | 15 | } |
diff --git a/server/middlewares/validators/search.ts b/server/middlewares/validators/search.ts index 774845e8a..fb2148eb3 100644 --- a/server/middlewares/validators/search.ts +++ b/server/middlewares/validators/search.ts | |||
@@ -2,12 +2,55 @@ 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 } from '../../helpers/custom-validators/search' | ||
6 | import { isBooleanValid, isDateValid, toArray } from '../../helpers/custom-validators/misc' | ||
5 | 7 | ||
6 | const searchValidator = [ | 8 | const searchValidator = [ |
7 | query('search').not().isEmpty().withMessage('Should have a valid search'), | 9 | query('search').not().isEmpty().withMessage('Should have a valid search'), |
8 | 10 | ||
11 | query('startDate').optional().custom(isDateValid).withMessage('Should have a valid start date'), | ||
12 | query('endDate').optional().custom(isDateValid).withMessage('Should have a valid end date'), | ||
13 | |||
14 | query('durationMin').optional().isInt().withMessage('Should have a valid min duration'), | ||
15 | query('durationMax').optional().isInt().withMessage('Should have a valid max duration'), | ||
16 | |||
17 | (req: express.Request, res: express.Response, next: express.NextFunction) => { | ||
18 | logger.debug('Checking search query', { parameters: req.query }) | ||
19 | |||
20 | if (areValidationErrors(req, res)) return | ||
21 | |||
22 | return next() | ||
23 | } | ||
24 | ] | ||
25 | |||
26 | const commonVideosFiltersValidator = [ | ||
27 | query('categoryOneOf') | ||
28 | .optional() | ||
29 | .customSanitizer(toArray) | ||
30 | .custom(isNumberArray).withMessage('Should have a valid one of category array'), | ||
31 | query('licenceOneOf') | ||
32 | .optional() | ||
33 | .customSanitizer(toArray) | ||
34 | .custom(isNumberArray).withMessage('Should have a valid one of licence array'), | ||
35 | query('languageOneOf') | ||
36 | .optional() | ||
37 | .customSanitizer(toArray) | ||
38 | .custom(isStringArray).withMessage('Should have a valid one of language array'), | ||
39 | query('tagsOneOf') | ||
40 | .optional() | ||
41 | .customSanitizer(toArray) | ||
42 | .custom(isStringArray).withMessage('Should have a valid one of tags array'), | ||
43 | query('tagsAllOf') | ||
44 | .optional() | ||
45 | .customSanitizer(toArray) | ||
46 | .custom(isStringArray).withMessage('Should have a valid all of tags array'), | ||
47 | query('nsfw') | ||
48 | .optional() | ||
49 | .toBoolean() | ||
50 | .custom(isBooleanValid).withMessage('Should have a valid NSFW attribute'), | ||
51 | |||
9 | (req: express.Request, res: express.Response, next: express.NextFunction) => { | 52 | (req: express.Request, res: express.Response, next: express.NextFunction) => { |
10 | logger.debug('Checking search parameters', { parameters: req.params }) | 53 | logger.debug('Checking commons video filters query', { parameters: req.query }) |
11 | 54 | ||
12 | if (areValidationErrors(req, res)) return | 55 | if (areValidationErrors(req, res)) return |
13 | 56 | ||
@@ -18,5 +61,6 @@ const searchValidator = [ | |||
18 | // --------------------------------------------------------------------------- | 61 | // --------------------------------------------------------------------------- |
19 | 62 | ||
20 | export { | 63 | export { |
64 | commonVideosFiltersValidator, | ||
21 | searchValidator | 65 | searchValidator |
22 | } | 66 | } |
diff --git a/server/models/utils.ts b/server/models/utils.ts index 49d32c24f..393f8f036 100644 --- a/server/models/utils.ts +++ b/server/models/utils.ts | |||
@@ -14,7 +14,7 @@ function getSort (value: string, lastSort: string[] = [ 'id', 'ASC' ]) { | |||
14 | } | 14 | } |
15 | 15 | ||
16 | // Alias | 16 | // Alias |
17 | if (field.toLowerCase() === 'bestmatch') field = Sequelize.col('similarity') | 17 | if (field.toLowerCase() === 'match') field = Sequelize.col('similarity') |
18 | 18 | ||
19 | return [ [ field, direction ], lastSort ] | 19 | return [ [ field, direction ], lastSort ] |
20 | } | 20 | } |
diff --git a/server/models/video/video.ts b/server/models/video/video.ts index 15b4dda5b..68116e309 100644 --- a/server/models/video/video.ts +++ b/server/models/video/video.ts | |||
@@ -93,6 +93,7 @@ import { VideoShareModel } from './video-share' | |||
93 | import { VideoTagModel } from './video-tag' | 93 | import { VideoTagModel } from './video-tag' |
94 | import { ScheduleVideoUpdateModel } from './schedule-video-update' | 94 | import { ScheduleVideoUpdateModel } from './schedule-video-update' |
95 | import { VideoCaptionModel } from './video-caption' | 95 | import { VideoCaptionModel } from './video-caption' |
96 | import { VideosSearchQuery } from '../../../shared/models/search' | ||
96 | 97 | ||
97 | // FIXME: Define indexes here because there is an issue with TS and Sequelize.literal when called directly in the annotation | 98 | // FIXME: Define indexes here because there is an issue with TS and Sequelize.literal when called directly in the annotation |
98 | const indexes: Sequelize.DefineIndexesOptions[] = [ | 99 | const indexes: Sequelize.DefineIndexesOptions[] = [ |
@@ -133,16 +134,22 @@ export enum ScopeNames { | |||
133 | WITH_SCHEDULED_UPDATE = 'WITH_SCHEDULED_UPDATE' | 134 | WITH_SCHEDULED_UPDATE = 'WITH_SCHEDULED_UPDATE' |
134 | } | 135 | } |
135 | 136 | ||
137 | type AvailableForListOptions = { | ||
138 | actorId: number, | ||
139 | filter?: VideoFilter, | ||
140 | categoryOneOf?: number[], | ||
141 | nsfw?: boolean, | ||
142 | licenceOneOf?: number[], | ||
143 | languageOneOf?: string[], | ||
144 | tagsOneOf?: string[], | ||
145 | tagsAllOf?: string[], | ||
146 | withFiles?: boolean, | ||
147 | accountId?: number, | ||
148 | videoChannelId?: number | ||
149 | } | ||
150 | |||
136 | @Scopes({ | 151 | @Scopes({ |
137 | [ScopeNames.AVAILABLE_FOR_LIST]: (options: { | 152 | [ScopeNames.AVAILABLE_FOR_LIST]: (options: AvailableForListOptions) => { |
138 | actorId: number, | ||
139 | hideNSFW: boolean, | ||
140 | filter?: VideoFilter, | ||
141 | category?: number, | ||
142 | withFiles?: boolean, | ||
143 | accountId?: number, | ||
144 | videoChannelId?: number | ||
145 | }) => { | ||
146 | const accountInclude = { | 153 | const accountInclude = { |
147 | attributes: [ 'id', 'name' ], | 154 | attributes: [ 'id', 'name' ], |
148 | model: AccountModel.unscoped(), | 155 | model: AccountModel.unscoped(), |
@@ -243,13 +250,55 @@ export enum ScopeNames { | |||
243 | }) | 250 | }) |
244 | } | 251 | } |
245 | 252 | ||
246 | // Hide nsfw videos? | 253 | // FIXME: issues with sequelize count when making a join on n:m relation, so we just make a IN() |
247 | if (options.hideNSFW === true) { | 254 | if (options.tagsAllOf || options.tagsOneOf) { |
248 | query.where['nsfw'] = false | 255 | const createTagsIn = (tags: string[]) => { |
256 | return tags.map(t => VideoModel.sequelize.escape(t)) | ||
257 | .join(', ') | ||
258 | } | ||
259 | |||
260 | if (options.tagsOneOf) { | ||
261 | query.where['id'][Sequelize.Op.in] = Sequelize.literal( | ||
262 | '(' + | ||
263 | 'SELECT "videoId" FROM "videoTag" ' + | ||
264 | 'INNER JOIN "tag" ON "tag"."id" = "videoTag"."tagId" ' + | ||
265 | 'WHERE "tag"."name" IN (' + createTagsIn(options.tagsOneOf) + ')' + | ||
266 | ')' | ||
267 | ) | ||
268 | } | ||
269 | |||
270 | if (options.tagsAllOf) { | ||
271 | query.where['id'][Sequelize.Op.in] = Sequelize.literal( | ||
272 | '(' + | ||
273 | 'SELECT "videoId" FROM "videoTag" ' + | ||
274 | 'INNER JOIN "tag" ON "tag"."id" = "videoTag"."tagId" ' + | ||
275 | 'WHERE "tag"."name" IN (' + createTagsIn(options.tagsAllOf) + ')' + | ||
276 | 'GROUP BY "videoTag"."videoId" HAVING COUNT(*) = ' + options.tagsAllOf.length + | ||
277 | ')' | ||
278 | ) | ||
279 | } | ||
280 | } | ||
281 | |||
282 | if (options.nsfw === true || options.nsfw === false) { | ||
283 | query.where['nsfw'] = options.nsfw | ||
284 | } | ||
285 | |||
286 | if (options.categoryOneOf) { | ||
287 | query.where['category'] = { | ||
288 | [Sequelize.Op.or]: options.categoryOneOf | ||
289 | } | ||
290 | } | ||
291 | |||
292 | if (options.licenceOneOf) { | ||
293 | query.where['licence'] = { | ||
294 | [Sequelize.Op.or]: options.licenceOneOf | ||
295 | } | ||
249 | } | 296 | } |
250 | 297 | ||
251 | if (options.category) { | 298 | if (options.languageOneOf) { |
252 | query.where['category'] = options.category | 299 | query.where['language'] = { |
300 | [Sequelize.Op.or]: options.languageOneOf | ||
301 | } | ||
253 | } | 302 | } |
254 | 303 | ||
255 | if (options.accountId) { | 304 | if (options.accountId) { |
@@ -756,9 +805,13 @@ export class VideoModel extends Model<VideoModel> { | |||
756 | start: number, | 805 | start: number, |
757 | count: number, | 806 | count: number, |
758 | sort: string, | 807 | sort: string, |
759 | hideNSFW: boolean, | 808 | nsfw: boolean, |
760 | withFiles: boolean, | 809 | withFiles: boolean, |
761 | category?: number, | 810 | categoryOneOf?: number[], |
811 | licenceOneOf?: number[], | ||
812 | languageOneOf?: string[], | ||
813 | tagsOneOf?: string[], | ||
814 | tagsAllOf?: string[], | ||
762 | filter?: VideoFilter, | 815 | filter?: VideoFilter, |
763 | accountId?: number, | 816 | accountId?: number, |
764 | videoChannelId?: number | 817 | videoChannelId?: number |
@@ -774,13 +827,17 @@ export class VideoModel extends Model<VideoModel> { | |||
774 | method: [ | 827 | method: [ |
775 | ScopeNames.AVAILABLE_FOR_LIST, { | 828 | ScopeNames.AVAILABLE_FOR_LIST, { |
776 | actorId: serverActor.id, | 829 | actorId: serverActor.id, |
777 | hideNSFW: options.hideNSFW, | 830 | nsfw: options.nsfw, |
778 | category: options.category, | 831 | categoryOneOf: options.categoryOneOf, |
832 | licenceOneOf: options.licenceOneOf, | ||
833 | languageOneOf: options.languageOneOf, | ||
834 | tagsOneOf: options.tagsOneOf, | ||
835 | tagsAllOf: options.tagsAllOf, | ||
779 | filter: options.filter, | 836 | filter: options.filter, |
780 | withFiles: options.withFiles, | 837 | withFiles: options.withFiles, |
781 | accountId: options.accountId, | 838 | accountId: options.accountId, |
782 | videoChannelId: options.videoChannelId | 839 | videoChannelId: options.videoChannelId |
783 | } | 840 | } as AvailableForListOptions |
784 | ] | 841 | ] |
785 | } | 842 | } |
786 | 843 | ||
@@ -794,15 +851,39 @@ export class VideoModel extends Model<VideoModel> { | |||
794 | }) | 851 | }) |
795 | } | 852 | } |
796 | 853 | ||
797 | static async searchAndPopulateAccountAndServer (value: string, start: number, count: number, sort: string, hideNSFW: boolean) { | 854 | static async searchAndPopulateAccountAndServer (options: VideosSearchQuery) { |
855 | const whereAnd = [ ] | ||
856 | |||
857 | if (options.startDate || options.endDate) { | ||
858 | const publishedAtRange = { } | ||
859 | |||
860 | if (options.startDate) publishedAtRange[Sequelize.Op.gte] = options.startDate | ||
861 | if (options.endDate) publishedAtRange[Sequelize.Op.lte] = options.endDate | ||
862 | |||
863 | whereAnd.push({ publishedAt: publishedAtRange }) | ||
864 | } | ||
865 | |||
866 | if (options.durationMin || options.durationMax) { | ||
867 | const durationRange = { } | ||
868 | |||
869 | if (options.durationMin) durationRange[Sequelize.Op.gte] = options.durationMin | ||
870 | if (options.durationMax) durationRange[Sequelize.Op.lte] = options.durationMax | ||
871 | |||
872 | whereAnd.push({ duration: durationRange }) | ||
873 | } | ||
874 | |||
875 | whereAnd.push(createSearchTrigramQuery('VideoModel.name', options.search)) | ||
876 | |||
798 | const query: IFindOptions<VideoModel> = { | 877 | const query: IFindOptions<VideoModel> = { |
799 | attributes: { | 878 | attributes: { |
800 | include: [ createSimilarityAttribute('VideoModel.name', value) ] | 879 | include: [ createSimilarityAttribute('VideoModel.name', options.search) ] |
801 | }, | 880 | }, |
802 | offset: start, | 881 | offset: options.start, |
803 | limit: count, | 882 | limit: options.count, |
804 | order: getSort(sort), | 883 | order: getSort(options.sort), |
805 | where: createSearchTrigramQuery('VideoModel.name', value) | 884 | where: { |
885 | [ Sequelize.Op.and ]: whereAnd | ||
886 | } | ||
806 | } | 887 | } |
807 | 888 | ||
808 | const serverActor = await getServerActor() | 889 | const serverActor = await getServerActor() |
@@ -810,8 +891,13 @@ export class VideoModel extends Model<VideoModel> { | |||
810 | method: [ | 891 | method: [ |
811 | ScopeNames.AVAILABLE_FOR_LIST, { | 892 | ScopeNames.AVAILABLE_FOR_LIST, { |
812 | actorId: serverActor.id, | 893 | actorId: serverActor.id, |
813 | hideNSFW | 894 | nsfw: options.nsfw, |
814 | } | 895 | categoryOneOf: options.categoryOneOf, |
896 | licenceOneOf: options.licenceOneOf, | ||
897 | languageOneOf: options.languageOneOf, | ||
898 | tagsOneOf: options.tagsOneOf, | ||
899 | tagsAllOf: options.tagsAllOf | ||
900 | } as AvailableForListOptions | ||
815 | ] | 901 | ] |
816 | } | 902 | } |
817 | 903 | ||
diff --git a/server/tests/api/check-params/index.ts b/server/tests/api/check-params/index.ts index c0e0302df..820dde889 100644 --- a/server/tests/api/check-params/index.ts +++ b/server/tests/api/check-params/index.ts | |||
@@ -10,3 +10,4 @@ import './video-captions' | |||
10 | import './video-channels' | 10 | import './video-channels' |
11 | import './video-comments' | 11 | import './video-comments' |
12 | import './videos' | 12 | import './videos' |
13 | import './search' | ||
diff --git a/server/tests/api/check-params/search.ts b/server/tests/api/check-params/search.ts new file mode 100644 index 000000000..d35eac7fe --- /dev/null +++ b/server/tests/api/check-params/search.ts | |||
@@ -0,0 +1,122 @@ | |||
1 | /* tslint:disable:no-unused-expression */ | ||
2 | |||
3 | import 'mocha' | ||
4 | |||
5 | import { flushTests, immutableAssign, killallServers, makeGetRequest, runServer, ServerInfo } from '../../utils' | ||
6 | import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '../../utils/requests/check-api-params' | ||
7 | |||
8 | describe('Test videos API validator', function () { | ||
9 | const path = '/api/v1/search/videos/' | ||
10 | let server: ServerInfo | ||
11 | |||
12 | // --------------------------------------------------------------- | ||
13 | |||
14 | before(async function () { | ||
15 | this.timeout(30000) | ||
16 | |||
17 | await flushTests() | ||
18 | |||
19 | server = await runServer(1) | ||
20 | }) | ||
21 | |||
22 | describe('When searching videos', function () { | ||
23 | const query = { | ||
24 | search: 'coucou' | ||
25 | } | ||
26 | |||
27 | it('Should fail with a bad start pagination', async function () { | ||
28 | await checkBadStartPagination(server.url, path, null, query) | ||
29 | }) | ||
30 | |||
31 | it('Should fail with a bad count pagination', async function () { | ||
32 | await checkBadCountPagination(server.url, path, null, query) | ||
33 | }) | ||
34 | |||
35 | it('Should fail with an incorrect sort', async function () { | ||
36 | await checkBadSortPagination(server.url, path, null, query) | ||
37 | }) | ||
38 | |||
39 | it('Should success with the correct parameters', async function () { | ||
40 | await makeGetRequest({ url: server.url, path, query, statusCodeExpected: 200 }) | ||
41 | }) | ||
42 | |||
43 | it('Should fail with an invalid category', async function () { | ||
44 | const customQuery1 = immutableAssign(query, { categoryOneOf: [ 'aa', 'b' ] }) | ||
45 | await makeGetRequest({ url: server.url, path, query: customQuery1, statusCodeExpected: 400 }) | ||
46 | |||
47 | const customQuery2 = immutableAssign(query, { categoryOneOf: 'a' }) | ||
48 | await makeGetRequest({ url: server.url, path, query: customQuery2, statusCodeExpected: 400 }) | ||
49 | }) | ||
50 | |||
51 | it('Should succeed with a valid category', async function () { | ||
52 | const customQuery1 = immutableAssign(query, { categoryOneOf: [ 1, 7 ] }) | ||
53 | await makeGetRequest({ url: server.url, path, query: customQuery1, statusCodeExpected: 200 }) | ||
54 | |||
55 | const customQuery2 = immutableAssign(query, { categoryOneOf: 1 }) | ||
56 | await makeGetRequest({ url: server.url, path, query: customQuery2, statusCodeExpected: 200 }) | ||
57 | }) | ||
58 | |||
59 | it('Should fail with an invalid licence', async function () { | ||
60 | const customQuery1 = immutableAssign(query, { licenceOneOf: [ 'aa', 'b' ] }) | ||
61 | await makeGetRequest({ url: server.url, path, query: customQuery1, statusCodeExpected: 400 }) | ||
62 | |||
63 | const customQuery2 = immutableAssign(query, { licenceOneOf: 'a' }) | ||
64 | await makeGetRequest({ url: server.url, path, query: customQuery2, statusCodeExpected: 400 }) | ||
65 | }) | ||
66 | |||
67 | it('Should succeed with a valid licence', async function () { | ||
68 | const customQuery1 = immutableAssign(query, { licenceOneOf: [ 1, 2 ] }) | ||
69 | await makeGetRequest({ url: server.url, path, query: customQuery1, statusCodeExpected: 200 }) | ||
70 | |||
71 | const customQuery2 = immutableAssign(query, { licenceOneOf: 1 }) | ||
72 | await makeGetRequest({ url: server.url, path, query: customQuery2, statusCodeExpected: 200 }) | ||
73 | }) | ||
74 | |||
75 | it('Should succeed with a valid language', async function () { | ||
76 | const customQuery1 = immutableAssign(query, { languageOneOf: [ 'fr', 'en' ] }) | ||
77 | await makeGetRequest({ url: server.url, path, query: customQuery1, statusCodeExpected: 200 }) | ||
78 | |||
79 | const customQuery2 = immutableAssign(query, { languageOneOf: 'fr' }) | ||
80 | await makeGetRequest({ url: server.url, path, query: customQuery2, statusCodeExpected: 200 }) | ||
81 | }) | ||
82 | |||
83 | it('Should succeed with valid tags', async function () { | ||
84 | const customQuery1 = immutableAssign(query, { tagsOneOf: [ 'tag1', 'tag2' ] }) | ||
85 | await makeGetRequest({ url: server.url, path, query: customQuery1, statusCodeExpected: 200 }) | ||
86 | |||
87 | const customQuery2 = immutableAssign(query, { tagsOneOf: 'tag1' }) | ||
88 | await makeGetRequest({ url: server.url, path, query: customQuery2, statusCodeExpected: 200 }) | ||
89 | |||
90 | const customQuery3 = immutableAssign(query, { tagsAllOf: [ 'tag1', 'tag2' ] }) | ||
91 | await makeGetRequest({ url: server.url, path, query: customQuery3, statusCodeExpected: 200 }) | ||
92 | |||
93 | const customQuery4 = immutableAssign(query, { tagsAllOf: 'tag1' }) | ||
94 | await makeGetRequest({ url: server.url, path, query: customQuery4, statusCodeExpected: 200 }) | ||
95 | }) | ||
96 | |||
97 | it('Should fail with invalid durations', async function () { | ||
98 | const customQuery1 = immutableAssign(query, { durationMin: 'hello' }) | ||
99 | await makeGetRequest({ url: server.url, path, query: customQuery1, statusCodeExpected: 400 }) | ||
100 | |||
101 | const customQuery2 = immutableAssign(query, { durationMax: 'hello' }) | ||
102 | await makeGetRequest({ url: server.url, path, query: customQuery2, statusCodeExpected: 400 }) | ||
103 | }) | ||
104 | |||
105 | it('Should fail with invalid dates', async function () { | ||
106 | const customQuery1 = immutableAssign(query, { startDate: 'hello' }) | ||
107 | await makeGetRequest({ url: server.url, path, query: customQuery1, statusCodeExpected: 400 }) | ||
108 | |||
109 | const customQuery2 = immutableAssign(query, { endDate: 'hello' }) | ||
110 | await makeGetRequest({ url: server.url, path, query: customQuery2, statusCodeExpected: 400 }) | ||
111 | }) | ||
112 | }) | ||
113 | |||
114 | after(async function () { | ||
115 | killallServers([ server ]) | ||
116 | |||
117 | // Keep the logs if the test failed | ||
118 | if (this['ok']) { | ||
119 | await flushTests() | ||
120 | } | ||
121 | }) | ||
122 | }) | ||
diff --git a/server/tests/api/index-fast.ts b/server/tests/api/index-fast.ts index d530dfc06..531a09b82 100644 --- a/server/tests/api/index-fast.ts +++ b/server/tests/api/index-fast.ts | |||
@@ -14,3 +14,4 @@ import './videos/services' | |||
14 | import './server/email' | 14 | import './server/email' |
15 | import './server/config' | 15 | import './server/config' |
16 | import './server/reverse-proxy' | 16 | import './server/reverse-proxy' |
17 | import './search/search-videos' | ||
diff --git a/server/tests/api/search/search-videos.ts b/server/tests/api/search/search-videos.ts new file mode 100644 index 000000000..7fc133b46 --- /dev/null +++ b/server/tests/api/search/search-videos.ts | |||
@@ -0,0 +1,299 @@ | |||
1 | /* tslint:disable:no-unused-expression */ | ||
2 | |||
3 | import * as chai from 'chai' | ||
4 | import 'mocha' | ||
5 | import { | ||
6 | advancedVideosSearch, | ||
7 | flushTests, | ||
8 | killallServers, | ||
9 | runServer, | ||
10 | searchVideo, | ||
11 | ServerInfo, | ||
12 | setAccessTokensToServers, | ||
13 | uploadVideo, | ||
14 | wait, | ||
15 | immutableAssign | ||
16 | } from '../../utils' | ||
17 | |||
18 | const expect = chai.expect | ||
19 | |||
20 | describe('Test a videos search', function () { | ||
21 | let server: ServerInfo = null | ||
22 | let startDate: string | ||
23 | |||
24 | before(async function () { | ||
25 | this.timeout(30000) | ||
26 | |||
27 | await flushTests() | ||
28 | |||
29 | server = await runServer(1) | ||
30 | |||
31 | await setAccessTokensToServers([ server ]) | ||
32 | |||
33 | { | ||
34 | const attributes1 = { | ||
35 | name: '1111 2222 3333', | ||
36 | fixture: '60fps_720p_small.mp4', // 2 seconds | ||
37 | category: 1, | ||
38 | licence: 1, | ||
39 | nsfw: false, | ||
40 | language: 'fr' | ||
41 | } | ||
42 | await uploadVideo(server.url, server.accessToken, attributes1) | ||
43 | |||
44 | const attributes2 = immutableAssign(attributes1, { name: attributes1.name + ' - 2', fixture: 'video_short.mp4' }) | ||
45 | await uploadVideo(server.url, server.accessToken, attributes2) | ||
46 | |||
47 | const attributes3 = immutableAssign(attributes1, { name: attributes1.name + ' - 3', language: 'en' }) | ||
48 | await uploadVideo(server.url, server.accessToken, attributes3) | ||
49 | |||
50 | const attributes4 = immutableAssign(attributes1, { name: attributes1.name + ' - 4', language: 'pl', nsfw: true }) | ||
51 | await uploadVideo(server.url, server.accessToken, attributes4) | ||
52 | |||
53 | await wait(1000) | ||
54 | |||
55 | startDate = new Date().toISOString() | ||
56 | |||
57 | const attributes5 = immutableAssign(attributes1, { name: attributes1.name + ' - 5', licence: 2 }) | ||
58 | await uploadVideo(server.url, server.accessToken, attributes5) | ||
59 | |||
60 | const attributes6 = immutableAssign(attributes1, { name: attributes1.name + ' - 6', tags: [ 't1', 't2 '] }) | ||
61 | await uploadVideo(server.url, server.accessToken, attributes6) | ||
62 | |||
63 | const attributes7 = immutableAssign(attributes1, { name: attributes1.name + ' - 7' }) | ||
64 | await uploadVideo(server.url, server.accessToken, attributes7) | ||
65 | |||
66 | const attributes8 = immutableAssign(attributes1, { name: attributes1.name + ' - 8', licence: 4 }) | ||
67 | await uploadVideo(server.url, server.accessToken, attributes8) | ||
68 | } | ||
69 | |||
70 | { | ||
71 | const attributes = { | ||
72 | name: '3333 4444 5555', | ||
73 | fixture: 'video_short.mp4', | ||
74 | category: 2, | ||
75 | licence: 2, | ||
76 | language: 'en' | ||
77 | } | ||
78 | await uploadVideo(server.url, server.accessToken, attributes) | ||
79 | |||
80 | await uploadVideo(server.url, server.accessToken, immutableAssign(attributes, { name: attributes.name + ' duplicate' })) | ||
81 | } | ||
82 | |||
83 | { | ||
84 | const attributes = { | ||
85 | name: '6666 7777 8888', | ||
86 | fixture: 'video_short.mp4', | ||
87 | category: 3, | ||
88 | licence: 3, | ||
89 | language: 'pl' | ||
90 | } | ||
91 | await uploadVideo(server.url, server.accessToken, attributes) | ||
92 | } | ||
93 | |||
94 | { | ||
95 | const attributes1 = { | ||
96 | name: '9999', | ||
97 | tags: [ 'aaaa', 'bbbb', 'cccc' ], | ||
98 | category: 1 | ||
99 | } | ||
100 | await uploadVideo(server.url, server.accessToken, attributes1) | ||
101 | await uploadVideo(server.url, server.accessToken, immutableAssign(attributes1, { category: 2 })) | ||
102 | |||
103 | await uploadVideo(server.url, server.accessToken, immutableAssign(attributes1, { tags: [ 'cccc', 'dddd' ] })) | ||
104 | await uploadVideo(server.url, server.accessToken, immutableAssign(attributes1, { tags: [ 'eeee', 'ffff' ] })) | ||
105 | } | ||
106 | }) | ||
107 | |||
108 | it('Should make a simple search and not have results', async function () { | ||
109 | const res = await searchVideo(server.url, 'abc') | ||
110 | |||
111 | expect(res.body.total).to.equal(0) | ||
112 | expect(res.body.data).to.have.lengthOf(0) | ||
113 | }) | ||
114 | |||
115 | it('Should make a simple search and have results', async function () { | ||
116 | const res = await searchVideo(server.url, '4444 5555 duplicate') | ||
117 | |||
118 | expect(res.body.total).to.equal(2) | ||
119 | |||
120 | const videos = res.body.data | ||
121 | expect(videos).to.have.lengthOf(2) | ||
122 | |||
123 | // bestmatch | ||
124 | expect(videos[0].name).to.equal('3333 4444 5555 duplicate') | ||
125 | expect(videos[1].name).to.equal('3333 4444 5555') | ||
126 | }) | ||
127 | |||
128 | it('Should search by tags (one of)', async function () { | ||
129 | const query = { | ||
130 | search: '9999', | ||
131 | categoryOneOf: [ 1 ], | ||
132 | tagsOneOf: [ 'aaaa', 'ffff' ] | ||
133 | } | ||
134 | const res1 = await advancedVideosSearch(server.url, query) | ||
135 | expect(res1.body.total).to.equal(2) | ||
136 | |||
137 | const res2 = await advancedVideosSearch(server.url, immutableAssign(query, { tagsOneOf: [ 'blabla' ] })) | ||
138 | expect(res2.body.total).to.equal(0) | ||
139 | }) | ||
140 | |||
141 | it('Should search by tags (all of)', async function () { | ||
142 | const query = { | ||
143 | search: '9999', | ||
144 | categoryOneOf: [ 1 ], | ||
145 | tagsAllOf: [ 'cccc' ] | ||
146 | } | ||
147 | const res1 = await advancedVideosSearch(server.url, query) | ||
148 | expect(res1.body.total).to.equal(2) | ||
149 | |||
150 | const res2 = await advancedVideosSearch(server.url, immutableAssign(query, { tagsAllOf: [ 'blabla' ] })) | ||
151 | expect(res2.body.total).to.equal(0) | ||
152 | |||
153 | const res3 = await advancedVideosSearch(server.url, immutableAssign(query, { tagsAllOf: [ 'bbbb', 'cccc' ] })) | ||
154 | expect(res3.body.total).to.equal(1) | ||
155 | }) | ||
156 | |||
157 | it('Should search by category', async function () { | ||
158 | const query = { | ||
159 | search: '6666', | ||
160 | categoryOneOf: [ 3 ] | ||
161 | } | ||
162 | const res1 = await advancedVideosSearch(server.url, query) | ||
163 | expect(res1.body.total).to.equal(1) | ||
164 | expect(res1.body.data[0].name).to.equal('6666 7777 8888') | ||
165 | |||
166 | const res2 = await advancedVideosSearch(server.url, immutableAssign(query, { categoryOneOf: [ 2 ] })) | ||
167 | expect(res2.body.total).to.equal(0) | ||
168 | }) | ||
169 | |||
170 | it('Should search by licence', async function () { | ||
171 | const query = { | ||
172 | search: '4444 5555', | ||
173 | licenceOneOf: [ 2 ] | ||
174 | } | ||
175 | const res1 = await advancedVideosSearch(server.url, query) | ||
176 | expect(res1.body.total).to.equal(2) | ||
177 | expect(res1.body.data[0].name).to.equal('3333 4444 5555') | ||
178 | expect(res1.body.data[1].name).to.equal('3333 4444 5555 duplicate') | ||
179 | |||
180 | const res2 = await advancedVideosSearch(server.url, immutableAssign(query, { licenceOneOf: [ 3 ] })) | ||
181 | expect(res2.body.total).to.equal(0) | ||
182 | }) | ||
183 | |||
184 | it('Should search by languages', async function () { | ||
185 | const query = { | ||
186 | search: '1111 2222 3333', | ||
187 | languageOneOf: [ 'pl', 'en' ] | ||
188 | } | ||
189 | const res1 = await advancedVideosSearch(server.url, query) | ||
190 | expect(res1.body.total).to.equal(2) | ||
191 | expect(res1.body.data[0].name).to.equal('1111 2222 3333 - 3') | ||
192 | expect(res1.body.data[1].name).to.equal('1111 2222 3333 - 4') | ||
193 | |||
194 | const res2 = await advancedVideosSearch(server.url, immutableAssign(query, { languageOneOf: [ 'eo' ] })) | ||
195 | expect(res2.body.total).to.equal(0) | ||
196 | }) | ||
197 | |||
198 | it('Should search by start date', async function () { | ||
199 | const query = { | ||
200 | search: '1111 2222 3333', | ||
201 | startDate | ||
202 | } | ||
203 | |||
204 | const res = await advancedVideosSearch(server.url, query) | ||
205 | expect(res.body.total).to.equal(4) | ||
206 | |||
207 | const videos = res.body.data | ||
208 | expect(videos[0].name).to.equal('1111 2222 3333 - 5') | ||
209 | expect(videos[1].name).to.equal('1111 2222 3333 - 6') | ||
210 | expect(videos[2].name).to.equal('1111 2222 3333 - 7') | ||
211 | expect(videos[3].name).to.equal('1111 2222 3333 - 8') | ||
212 | }) | ||
213 | |||
214 | it('Should make an advanced search', async function () { | ||
215 | const query = { | ||
216 | search: '1111 2222 3333', | ||
217 | languageOneOf: [ 'pl', 'fr' ], | ||
218 | durationMax: 4, | ||
219 | nsfw: false, | ||
220 | licenceOneOf: [ 1, 4 ] | ||
221 | } | ||
222 | |||
223 | const res = await advancedVideosSearch(server.url, query) | ||
224 | expect(res.body.total).to.equal(4) | ||
225 | |||
226 | const videos = res.body.data | ||
227 | expect(videos[0].name).to.equal('1111 2222 3333') | ||
228 | expect(videos[1].name).to.equal('1111 2222 3333 - 6') | ||
229 | expect(videos[2].name).to.equal('1111 2222 3333 - 7') | ||
230 | expect(videos[3].name).to.equal('1111 2222 3333 - 8') | ||
231 | }) | ||
232 | |||
233 | it('Should make an advanced search and sort results', async function () { | ||
234 | const query = { | ||
235 | search: '1111 2222 3333', | ||
236 | languageOneOf: [ 'pl', 'fr' ], | ||
237 | durationMax: 4, | ||
238 | nsfw: false, | ||
239 | licenceOneOf: [ 1, 4 ], | ||
240 | sort: '-name' | ||
241 | } | ||
242 | |||
243 | const res = await advancedVideosSearch(server.url, query) | ||
244 | expect(res.body.total).to.equal(4) | ||
245 | |||
246 | const videos = res.body.data | ||
247 | expect(videos[0].name).to.equal('1111 2222 3333 - 8') | ||
248 | expect(videos[1].name).to.equal('1111 2222 3333 - 7') | ||
249 | expect(videos[2].name).to.equal('1111 2222 3333 - 6') | ||
250 | expect(videos[3].name).to.equal('1111 2222 3333') | ||
251 | }) | ||
252 | |||
253 | it('Should make an advanced search and only show the first result', async function () { | ||
254 | const query = { | ||
255 | search: '1111 2222 3333', | ||
256 | languageOneOf: [ 'pl', 'fr' ], | ||
257 | durationMax: 4, | ||
258 | nsfw: false, | ||
259 | licenceOneOf: [ 1, 4 ], | ||
260 | sort: '-name', | ||
261 | start: 0, | ||
262 | count: 1 | ||
263 | } | ||
264 | |||
265 | const res = await advancedVideosSearch(server.url, query) | ||
266 | expect(res.body.total).to.equal(4) | ||
267 | |||
268 | const videos = res.body.data | ||
269 | expect(videos[0].name).to.equal('1111 2222 3333 - 8') | ||
270 | }) | ||
271 | |||
272 | it('Should make an advanced search and only show the last result', async function () { | ||
273 | const query = { | ||
274 | search: '1111 2222 3333', | ||
275 | languageOneOf: [ 'pl', 'fr' ], | ||
276 | durationMax: 4, | ||
277 | nsfw: false, | ||
278 | licenceOneOf: [ 1, 4 ], | ||
279 | sort: '-name', | ||
280 | start: 3, | ||
281 | count: 1 | ||
282 | } | ||
283 | |||
284 | const res = await advancedVideosSearch(server.url, query) | ||
285 | expect(res.body.total).to.equal(4) | ||
286 | |||
287 | const videos = res.body.data | ||
288 | expect(videos[0].name).to.equal('1111 2222 3333') | ||
289 | }) | ||
290 | |||
291 | after(async function () { | ||
292 | killallServers([ server ]) | ||
293 | |||
294 | // Keep the logs if the test failed | ||
295 | if (this['ok']) { | ||
296 | await flushTests() | ||
297 | } | ||
298 | }) | ||
299 | }) | ||
diff --git a/server/tests/api/videos/single-server.ts b/server/tests/api/videos/single-server.ts index d8af94e8f..ba4920d1b 100644 --- a/server/tests/api/videos/single-server.ts +++ b/server/tests/api/videos/single-server.ts | |||
@@ -16,13 +16,11 @@ import { | |||
16 | getVideosList, | 16 | getVideosList, |
17 | getVideosListPagination, | 17 | getVideosListPagination, |
18 | getVideosListSort, | 18 | getVideosListSort, |
19 | getVideosWithFilters, | ||
19 | killallServers, | 20 | killallServers, |
20 | rateVideo, | 21 | rateVideo, |
21 | removeVideo, | 22 | removeVideo, |
22 | runServer, | 23 | runServer, |
23 | searchVideo, | ||
24 | searchVideoWithPagination, | ||
25 | searchVideoWithSort, | ||
26 | ServerInfo, | 24 | ServerInfo, |
27 | setAccessTokensToServers, | 25 | setAccessTokensToServers, |
28 | testImage, | 26 | testImage, |
@@ -218,72 +216,6 @@ describe('Test a single server', function () { | |||
218 | expect(video.views).to.equal(3) | 216 | expect(video.views).to.equal(3) |
219 | }) | 217 | }) |
220 | 218 | ||
221 | it('Should search the video by name', async function () { | ||
222 | const res = await searchVideo(server.url, 'my') | ||
223 | |||
224 | expect(res.body.total).to.equal(1) | ||
225 | expect(res.body.data).to.be.an('array') | ||
226 | expect(res.body.data.length).to.equal(1) | ||
227 | |||
228 | const video = res.body.data[0] | ||
229 | await completeVideoCheck(server.url, video, getCheckAttributes) | ||
230 | }) | ||
231 | |||
232 | // Not implemented yet | ||
233 | // it('Should search the video by tag', async function () { | ||
234 | // const res = await searchVideo(server.url, 'tag1') | ||
235 | // | ||
236 | // expect(res.body.total).to.equal(1) | ||
237 | // expect(res.body.data).to.be.an('array') | ||
238 | // expect(res.body.data.length).to.equal(1) | ||
239 | // | ||
240 | // const video = res.body.data[0] | ||
241 | // expect(video.name).to.equal('my super name') | ||
242 | // expect(video.category).to.equal(2) | ||
243 | // expect(video.categoryLabel).to.equal('Films') | ||
244 | // expect(video.licence).to.equal(6) | ||
245 | // expect(video.licenceLabel).to.equal('Attribution - Non Commercial - No Derivatives') | ||
246 | // expect(video.language).to.equal('zh') | ||
247 | // expect(video.languageLabel).to.equal('Chinese') | ||
248 | // expect(video.nsfw).to.be.ok | ||
249 | // expect(video.description).to.equal('my super description') | ||
250 | // expect(video.account.name).to.equal('root') | ||
251 | // expect(video.account.host).to.equal('localhost:9001') | ||
252 | // expect(video.isLocal).to.be.true | ||
253 | // expect(video.tags).to.deep.equal([ 'tag1', 'tag2', 'tag3' ]) | ||
254 | // expect(dateIsValid(video.createdAt)).to.be.true | ||
255 | // expect(dateIsValid(video.updatedAt)).to.be.true | ||
256 | // | ||
257 | // const test = await testVideoImage(server.url, 'video_short.webm', video.thumbnailPath) | ||
258 | // expect(test).to.equal(true) | ||
259 | // }) | ||
260 | |||
261 | it('Should not find a search by name', async function () { | ||
262 | const res = await searchVideo(server.url, 'hello') | ||
263 | |||
264 | expect(res.body.total).to.equal(0) | ||
265 | expect(res.body.data).to.be.an('array') | ||
266 | expect(res.body.data.length).to.equal(0) | ||
267 | }) | ||
268 | |||
269 | // Not implemented yet | ||
270 | // it('Should not find a search by author', async function () { | ||
271 | // const res = await searchVideo(server.url, 'hello') | ||
272 | // | ||
273 | // expect(res.body.total).to.equal(0) | ||
274 | // expect(res.body.data).to.be.an('array') | ||
275 | // expect(res.body.data.length).to.equal(0) | ||
276 | // }) | ||
277 | // | ||
278 | // Not implemented yet | ||
279 | // it('Should not find a search by tag', async function () { | ||
280 | // const res = await searchVideo(server.url, 'hello') | ||
281 | // | ||
282 | // expect(res.body.total).to.equal(0) | ||
283 | // expect(res.body.data).to.be.an('array') | ||
284 | // expect(res.body.data.length).to.equal(0) | ||
285 | // }) | ||
286 | |||
287 | it('Should remove the video', async function () { | 219 | it('Should remove the video', async function () { |
288 | await removeVideo(server.url, server.accessToken, videoId) | 220 | await removeVideo(server.url, server.accessToken, videoId) |
289 | 221 | ||
@@ -386,65 +318,6 @@ describe('Test a single server', function () { | |||
386 | expect(videos[0].name).to.equal(videosListBase[5].name) | 318 | expect(videos[0].name).to.equal(videosListBase[5].name) |
387 | }) | 319 | }) |
388 | 320 | ||
389 | it('Should search the first video', async function () { | ||
390 | const res = await searchVideoWithPagination(server.url, 'webm', 0, 1, 'name') | ||
391 | |||
392 | const videos = res.body.data | ||
393 | expect(res.body.total).to.equal(4) | ||
394 | expect(videos.length).to.equal(1) | ||
395 | expect(videos[0].name).to.equal('video_short1.webm name') | ||
396 | }) | ||
397 | |||
398 | it('Should search the last two videos', async function () { | ||
399 | const res = await searchVideoWithPagination(server.url, 'webm', 2, 2, 'name') | ||
400 | |||
401 | const videos = res.body.data | ||
402 | expect(res.body.total).to.equal(4) | ||
403 | expect(videos.length).to.equal(2) | ||
404 | expect(videos[0].name).to.equal('video_short3.webm name') | ||
405 | expect(videos[1].name).to.equal('video_short.webm name') | ||
406 | }) | ||
407 | |||
408 | it('Should search all the webm videos', async function () { | ||
409 | const res = await searchVideoWithPagination(server.url, 'webm', 0, 15) | ||
410 | |||
411 | const videos = res.body.data | ||
412 | expect(res.body.total).to.equal(4) | ||
413 | expect(videos.length).to.equal(4) | ||
414 | }) | ||
415 | |||
416 | // Not implemented yet | ||
417 | // it('Should search all the root author videos', async function () { | ||
418 | // const res = await searchVideoWithPagination(server.url, 'root', 0, 15) | ||
419 | // | ||
420 | // const videos = res.body.data | ||
421 | // expect(res.body.total).to.equal(6) | ||
422 | // expect(videos.length).to.equal(6) | ||
423 | // }) | ||
424 | |||
425 | // Not implemented yet | ||
426 | // it('Should search all the 9001 port videos', async function () { | ||
427 | // const res = await videosUtils.searchVideoWithPagination(server.url, '9001', 'host', 0, 15) | ||
428 | |||
429 | // const videos = res.body.data | ||
430 | // expect(res.body.total).to.equal(6) | ||
431 | // expect(videos.length).to.equal(6) | ||
432 | |||
433 | // done() | ||
434 | // }) | ||
435 | // }) | ||
436 | |||
437 | // it('Should search all the localhost videos', async function () { | ||
438 | // const res = await videosUtils.searchVideoWithPagination(server.url, 'localhost', 'host', 0, 15) | ||
439 | |||
440 | // const videos = res.body.data | ||
441 | // expect(res.body.total).to.equal(6) | ||
442 | // expect(videos.length).to.equal(6) | ||
443 | |||
444 | // done() | ||
445 | // }) | ||
446 | // }) | ||
447 | |||
448 | it('Should list and sort by name in descending order', async function () { | 321 | it('Should list and sort by name in descending order', async function () { |
449 | const res = await getVideosListSort(server.url, '-name') | 322 | const res = await getVideosListSort(server.url, '-name') |
450 | 323 | ||
@@ -457,21 +330,8 @@ describe('Test a single server', function () { | |||
457 | expect(videos[3].name).to.equal('video_short3.webm name') | 330 | expect(videos[3].name).to.equal('video_short3.webm name') |
458 | expect(videos[4].name).to.equal('video_short2.webm name') | 331 | expect(videos[4].name).to.equal('video_short2.webm name') |
459 | expect(videos[5].name).to.equal('video_short1.webm name') | 332 | expect(videos[5].name).to.equal('video_short1.webm name') |
460 | }) | ||
461 | |||
462 | it('Should search and sort by name in ascending order', async function () { | ||
463 | const res = await searchVideoWithSort(server.url, 'webm', 'name') | ||
464 | 333 | ||
465 | const videos = res.body.data | 334 | videoId = videos[3].uuid |
466 | expect(res.body.total).to.equal(4) | ||
467 | expect(videos.length).to.equal(4) | ||
468 | |||
469 | expect(videos[0].name).to.equal('video_short1.webm name') | ||
470 | expect(videos[1].name).to.equal('video_short2.webm name') | ||
471 | expect(videos[2].name).to.equal('video_short3.webm name') | ||
472 | expect(videos[3].name).to.equal('video_short.webm name') | ||
473 | |||
474 | videoId = videos[2].id | ||
475 | }) | 335 | }) |
476 | 336 | ||
477 | it('Should update a video', async function () { | 337 | it('Should update a video', async function () { |
@@ -488,6 +348,15 @@ describe('Test a single server', function () { | |||
488 | await updateVideo(server.url, server.accessToken, videoId, attributes) | 348 | await updateVideo(server.url, server.accessToken, videoId, attributes) |
489 | }) | 349 | }) |
490 | 350 | ||
351 | it('Should filter by tags and category', async function () { | ||
352 | const res1 = await getVideosWithFilters(server.url, { tagsAllOf: [ 'tagup1', 'tagup2' ], categoryOneOf: 4 }) | ||
353 | expect(res1.body.total).to.equal(1) | ||
354 | expect(res1.body.data[0].name).to.equal('my super video updated') | ||
355 | |||
356 | const res2 = await getVideosWithFilters(server.url, { tagsAllOf: [ 'tagup1', 'tagup2' ], categoryOneOf: 3 }) | ||
357 | expect(res2.body.total).to.equal(0) | ||
358 | }) | ||
359 | |||
491 | it('Should have the video updated', async function () { | 360 | it('Should have the video updated', async function () { |
492 | this.timeout(60000) | 361 | this.timeout(60000) |
493 | 362 | ||
diff --git a/server/tests/api/videos/video-nsfw.ts b/server/tests/api/videos/video-nsfw.ts index 6af0ca8af..38bdaa54e 100644 --- a/server/tests/api/videos/video-nsfw.ts +++ b/server/tests/api/videos/video-nsfw.ts | |||
@@ -30,7 +30,7 @@ describe('Test video NSFW policy', function () { | |||
30 | let userAccessToken: string | 30 | let userAccessToken: string |
31 | let customConfig: CustomConfig | 31 | let customConfig: CustomConfig |
32 | 32 | ||
33 | function getVideosFunctions (token?: string) { | 33 | function getVideosFunctions (token?: string, query = {}) { |
34 | return getMyUserInformation(server.url, server.accessToken) | 34 | return getMyUserInformation(server.url, server.accessToken) |
35 | .then(res => { | 35 | .then(res => { |
36 | const user: User = res.body | 36 | const user: User = res.body |
@@ -39,10 +39,10 @@ describe('Test video NSFW policy', function () { | |||
39 | 39 | ||
40 | if (token) { | 40 | if (token) { |
41 | return Promise.all([ | 41 | return Promise.all([ |
42 | getVideosListWithToken(server.url, token), | 42 | getVideosListWithToken(server.url, token, query), |
43 | searchVideoWithToken(server.url, 'n', token), | 43 | searchVideoWithToken(server.url, 'n', token, query), |
44 | getAccountVideos(server.url, token, accountName, 0, 5), | 44 | getAccountVideos(server.url, token, accountName, 0, 5, undefined, query), |
45 | getVideoChannelVideos(server.url, token, videoChannelUUID, 0, 5) | 45 | getVideoChannelVideos(server.url, token, videoChannelUUID, 0, 5, undefined, query) |
46 | ]) | 46 | ]) |
47 | } | 47 | } |
48 | 48 | ||
@@ -200,6 +200,26 @@ describe('Test video NSFW policy', function () { | |||
200 | expect(videos[ 0 ].name).to.equal('normal') | 200 | expect(videos[ 0 ].name).to.equal('normal') |
201 | expect(videos[ 1 ].name).to.equal('nsfw') | 201 | expect(videos[ 1 ].name).to.equal('nsfw') |
202 | }) | 202 | }) |
203 | |||
204 | it('Should display NSFW videos when the nsfw param === true', async function () { | ||
205 | for (const res of await getVideosFunctions(server.accessToken, { nsfw: true })) { | ||
206 | expect(res.body.total).to.equal(1) | ||
207 | |||
208 | const videos = res.body.data | ||
209 | expect(videos).to.have.lengthOf(1) | ||
210 | expect(videos[ 0 ].name).to.equal('nsfw') | ||
211 | } | ||
212 | }) | ||
213 | |||
214 | it('Should hide NSFW videos when the nsfw param === true', async function () { | ||
215 | for (const res of await getVideosFunctions(server.accessToken, { nsfw: false })) { | ||
216 | expect(res.body.total).to.equal(1) | ||
217 | |||
218 | const videos = res.body.data | ||
219 | expect(videos).to.have.lengthOf(1) | ||
220 | expect(videos[ 0 ].name).to.equal('normal') | ||
221 | } | ||
222 | }) | ||
203 | }) | 223 | }) |
204 | 224 | ||
205 | after(async function () { | 225 | after(async function () { |
diff --git a/server/tests/utils/index.ts b/server/tests/utils/index.ts index 5b560ca39..391db18cf 100644 --- a/server/tests/utils/index.ts +++ b/server/tests/utils/index.ts | |||
@@ -14,3 +14,4 @@ export * from './videos/video-blacklist' | |||
14 | export * from './videos/video-channels' | 14 | export * from './videos/video-channels' |
15 | export * from './videos/videos' | 15 | export * from './videos/videos' |
16 | export * from './feeds/feeds' | 16 | export * from './feeds/feeds' |
17 | export * from './search/videos' | ||
diff --git a/server/tests/utils/requests/check-api-params.ts b/server/tests/utils/requests/check-api-params.ts index 7550eb3d8..edb47e0e9 100644 --- a/server/tests/utils/requests/check-api-params.ts +++ b/server/tests/utils/requests/check-api-params.ts | |||
@@ -1,31 +1,32 @@ | |||
1 | import { makeGetRequest } from './requests' | 1 | import { makeGetRequest } from './requests' |
2 | import { immutableAssign } from '..' | ||
2 | 3 | ||
3 | function checkBadStartPagination (url: string, path: string, token?: string) { | 4 | function checkBadStartPagination (url: string, path: string, token?: string, query = {}) { |
4 | return makeGetRequest({ | 5 | return makeGetRequest({ |
5 | url, | 6 | url, |
6 | path, | 7 | path, |
7 | token, | 8 | token, |
8 | query: { start: 'hello' }, | 9 | query: immutableAssign(query, { start: 'hello' }), |
9 | statusCodeExpected: 400 | 10 | statusCodeExpected: 400 |
10 | }) | 11 | }) |
11 | } | 12 | } |
12 | 13 | ||
13 | function checkBadCountPagination (url: string, path: string, token?: string) { | 14 | function checkBadCountPagination (url: string, path: string, token?: string, query = {}) { |
14 | return makeGetRequest({ | 15 | return makeGetRequest({ |
15 | url, | 16 | url, |
16 | path, | 17 | path, |
17 | token, | 18 | token, |
18 | query: { count: 'hello' }, | 19 | query: immutableAssign(query, { count: 'hello' }), |
19 | statusCodeExpected: 400 | 20 | statusCodeExpected: 400 |
20 | }) | 21 | }) |
21 | } | 22 | } |
22 | 23 | ||
23 | function checkBadSortPagination (url: string, path: string, token?: string) { | 24 | function checkBadSortPagination (url: string, path: string, token?: string, query = {}) { |
24 | return makeGetRequest({ | 25 | return makeGetRequest({ |
25 | url, | 26 | url, |
26 | path, | 27 | path, |
27 | token, | 28 | token, |
28 | query: { sort: 'hello' }, | 29 | query: immutableAssign(query, { sort: 'hello' }), |
29 | statusCodeExpected: 400 | 30 | statusCodeExpected: 400 |
30 | }) | 31 | }) |
31 | } | 32 | } |
diff --git a/server/tests/utils/search/videos.ts b/server/tests/utils/search/videos.ts new file mode 100644 index 000000000..3a0c10e42 --- /dev/null +++ b/server/tests/utils/search/videos.ts | |||
@@ -0,0 +1,77 @@ | |||
1 | /* tslint:disable:no-unused-expression */ | ||
2 | |||
3 | import * as request from 'supertest' | ||
4 | import { VideosSearchQuery } from '../../../../shared/models/search' | ||
5 | import { immutableAssign } from '..' | ||
6 | |||
7 | function searchVideo (url: string, search: string) { | ||
8 | const path = '/api/v1/search/videos' | ||
9 | const req = request(url) | ||
10 | .get(path) | ||
11 | .query({ sort: '-publishedAt', search }) | ||
12 | .set('Accept', 'application/json') | ||
13 | |||
14 | return req.expect(200) | ||
15 | .expect('Content-Type', /json/) | ||
16 | } | ||
17 | |||
18 | function searchVideoWithToken (url: string, search: string, token: string, query: { nsfw?: boolean } = {}) { | ||
19 | const path = '/api/v1/search/videos' | ||
20 | const req = request(url) | ||
21 | .get(path) | ||
22 | .set('Authorization', 'Bearer ' + token) | ||
23 | .query(immutableAssign(query, { sort: '-publishedAt', search })) | ||
24 | .set('Accept', 'application/json') | ||
25 | |||
26 | return req.expect(200) | ||
27 | .expect('Content-Type', /json/) | ||
28 | } | ||
29 | |||
30 | function searchVideoWithPagination (url: string, search: string, start: number, count: number, sort?: string) { | ||
31 | const path = '/api/v1/search/videos' | ||
32 | |||
33 | const req = request(url) | ||
34 | .get(path) | ||
35 | .query({ start }) | ||
36 | .query({ search }) | ||
37 | .query({ count }) | ||
38 | |||
39 | if (sort) req.query({ sort }) | ||
40 | |||
41 | return req.set('Accept', 'application/json') | ||
42 | .expect(200) | ||
43 | .expect('Content-Type', /json/) | ||
44 | } | ||
45 | |||
46 | function searchVideoWithSort (url: string, search: string, sort: string) { | ||
47 | const path = '/api/v1/search/videos' | ||
48 | |||
49 | return request(url) | ||
50 | .get(path) | ||
51 | .query({ search }) | ||
52 | .query({ sort }) | ||
53 | .set('Accept', 'application/json') | ||
54 | .expect(200) | ||
55 | .expect('Content-Type', /json/) | ||
56 | } | ||
57 | |||
58 | function advancedVideosSearch (url: string, options: VideosSearchQuery) { | ||
59 | const path = '/api/v1/search/videos' | ||
60 | |||
61 | return request(url) | ||
62 | .get(path) | ||
63 | .query(options) | ||
64 | .set('Accept', 'application/json') | ||
65 | .expect(200) | ||
66 | .expect('Content-Type', /json/) | ||
67 | } | ||
68 | |||
69 | // --------------------------------------------------------------------------- | ||
70 | |||
71 | export { | ||
72 | searchVideo, | ||
73 | advancedVideosSearch, | ||
74 | searchVideoWithToken, | ||
75 | searchVideoWithPagination, | ||
76 | searchVideoWithSort | ||
77 | } | ||
diff --git a/server/tests/utils/videos/videos.ts b/server/tests/utils/videos/videos.ts index a42d0f043..8c49eb02b 100644 --- a/server/tests/utils/videos/videos.ts +++ b/server/tests/utils/videos/videos.ts | |||
@@ -7,7 +7,7 @@ import { extname, join } from 'path' | |||
7 | import * as request from 'supertest' | 7 | import * as request from 'supertest' |
8 | import { | 8 | import { |
9 | buildAbsoluteFixturePath, | 9 | buildAbsoluteFixturePath, |
10 | getMyUserInformation, | 10 | getMyUserInformation, immutableAssign, |
11 | makeGetRequest, | 11 | makeGetRequest, |
12 | makePutBodyRequest, | 12 | makePutBodyRequest, |
13 | makeUploadRequest, | 13 | makeUploadRequest, |
@@ -133,13 +133,13 @@ function getVideosList (url: string) { | |||
133 | .expect('Content-Type', /json/) | 133 | .expect('Content-Type', /json/) |
134 | } | 134 | } |
135 | 135 | ||
136 | function getVideosListWithToken (url: string, token: string) { | 136 | function getVideosListWithToken (url: string, token: string, query: { nsfw?: boolean } = {}) { |
137 | const path = '/api/v1/videos' | 137 | const path = '/api/v1/videos' |
138 | 138 | ||
139 | return request(url) | 139 | return request(url) |
140 | .get(path) | 140 | .get(path) |
141 | .set('Authorization', 'Bearer ' + token) | 141 | .set('Authorization', 'Bearer ' + token) |
142 | .query({ sort: 'name' }) | 142 | .query(immutableAssign(query, { sort: 'name' })) |
143 | .set('Accept', 'application/json') | 143 | .set('Accept', 'application/json') |
144 | .expect(200) | 144 | .expect(200) |
145 | .expect('Content-Type', /json/) | 145 | .expect('Content-Type', /json/) |
@@ -172,17 +172,25 @@ function getMyVideos (url: string, accessToken: string, start: number, count: nu | |||
172 | .expect('Content-Type', /json/) | 172 | .expect('Content-Type', /json/) |
173 | } | 173 | } |
174 | 174 | ||
175 | function getAccountVideos (url: string, accessToken: string, accountName: string, start: number, count: number, sort?: string) { | 175 | function getAccountVideos ( |
176 | url: string, | ||
177 | accessToken: string, | ||
178 | accountName: string, | ||
179 | start: number, | ||
180 | count: number, | ||
181 | sort?: string, | ||
182 | query: { nsfw?: boolean } = {} | ||
183 | ) { | ||
176 | const path = '/api/v1/accounts/' + accountName + '/videos' | 184 | const path = '/api/v1/accounts/' + accountName + '/videos' |
177 | 185 | ||
178 | return makeGetRequest({ | 186 | return makeGetRequest({ |
179 | url, | 187 | url, |
180 | path, | 188 | path, |
181 | query: { | 189 | query: immutableAssign(query, { |
182 | start, | 190 | start, |
183 | count, | 191 | count, |
184 | sort | 192 | sort |
185 | }, | 193 | }), |
186 | token: accessToken, | 194 | token: accessToken, |
187 | statusCodeExpected: 200 | 195 | statusCodeExpected: 200 |
188 | }) | 196 | }) |
@@ -194,18 +202,19 @@ function getVideoChannelVideos ( | |||
194 | videoChannelId: number | string, | 202 | videoChannelId: number | string, |
195 | start: number, | 203 | start: number, |
196 | count: number, | 204 | count: number, |
197 | sort?: string | 205 | sort?: string, |
206 | query: { nsfw?: boolean } = {} | ||
198 | ) { | 207 | ) { |
199 | const path = '/api/v1/video-channels/' + videoChannelId + '/videos' | 208 | const path = '/api/v1/video-channels/' + videoChannelId + '/videos' |
200 | 209 | ||
201 | return makeGetRequest({ | 210 | return makeGetRequest({ |
202 | url, | 211 | url, |
203 | path, | 212 | path, |
204 | query: { | 213 | query: immutableAssign(query, { |
205 | start, | 214 | start, |
206 | count, | 215 | count, |
207 | sort | 216 | sort |
208 | }, | 217 | }), |
209 | token: accessToken, | 218 | token: accessToken, |
210 | statusCodeExpected: 200 | 219 | statusCodeExpected: 200 |
211 | }) | 220 | }) |
@@ -237,65 +246,25 @@ function getVideosListSort (url: string, sort: string) { | |||
237 | .expect('Content-Type', /json/) | 246 | .expect('Content-Type', /json/) |
238 | } | 247 | } |
239 | 248 | ||
240 | function removeVideo (url: string, token: string, id: number | string, expectedStatus = 204) { | 249 | function getVideosWithFilters (url: string, query: { tagsAllOf: string[], categoryOneOf: number[] | number }) { |
241 | const path = '/api/v1/videos' | 250 | const path = '/api/v1/videos' |
242 | 251 | ||
243 | return request(url) | 252 | return request(url) |
244 | .delete(path + '/' + id) | ||
245 | .set('Accept', 'application/json') | ||
246 | .set('Authorization', 'Bearer ' + token) | ||
247 | .expect(expectedStatus) | ||
248 | } | ||
249 | |||
250 | function searchVideo (url: string, search: string) { | ||
251 | const path = '/api/v1/search/videos' | ||
252 | const req = request(url) | ||
253 | .get(path) | 253 | .get(path) |
254 | .query({ search }) | 254 | .query(query) |
255 | .set('Accept', 'application/json') | 255 | .set('Accept', 'application/json') |
256 | 256 | .expect(200) | |
257 | return req.expect(200) | ||
258 | .expect('Content-Type', /json/) | 257 | .expect('Content-Type', /json/) |
259 | } | 258 | } |
260 | 259 | ||
261 | function searchVideoWithToken (url: string, search: string, token: string) { | 260 | function removeVideo (url: string, token: string, id: number | string, expectedStatus = 204) { |
262 | const path = '/api/v1/videos' | 261 | const path = '/api/v1/videos' |
263 | const req = request(url) | ||
264 | .get(path + '/search') | ||
265 | .set('Authorization', 'Bearer ' + token) | ||
266 | .query({ search }) | ||
267 | .set('Accept', 'application/json') | ||
268 | |||
269 | return req.expect(200) | ||
270 | .expect('Content-Type', /json/) | ||
271 | } | ||
272 | |||
273 | function searchVideoWithPagination (url: string, search: string, start: number, count: number, sort?: string) { | ||
274 | const path = '/api/v1/search/videos' | ||
275 | |||
276 | const req = request(url) | ||
277 | .get(path) | ||
278 | .query({ start }) | ||
279 | .query({ search }) | ||
280 | .query({ count }) | ||
281 | |||
282 | if (sort) req.query({ sort }) | ||
283 | |||
284 | return req.set('Accept', 'application/json') | ||
285 | .expect(200) | ||
286 | .expect('Content-Type', /json/) | ||
287 | } | ||
288 | |||
289 | function searchVideoWithSort (url: string, search: string, sort: string) { | ||
290 | const path = '/api/v1/search/videos' | ||
291 | 262 | ||
292 | return request(url) | 263 | return request(url) |
293 | .get(path) | 264 | .delete(path + '/' + id) |
294 | .query({ search }) | ||
295 | .query({ sort }) | ||
296 | .set('Accept', 'application/json') | 265 | .set('Accept', 'application/json') |
297 | .expect(200) | 266 | .set('Authorization', 'Bearer ' + token) |
298 | .expect('Content-Type', /json/) | 267 | .expect(expectedStatus) |
299 | } | 268 | } |
300 | 269 | ||
301 | async function checkVideoFilesWereRemoved (videoUUID: string, serverNumber: number) { | 270 | async function checkVideoFilesWereRemoved (videoUUID: string, serverNumber: number) { |
@@ -581,18 +550,15 @@ export { | |||
581 | getMyVideos, | 550 | getMyVideos, |
582 | getAccountVideos, | 551 | getAccountVideos, |
583 | getVideoChannelVideos, | 552 | getVideoChannelVideos, |
584 | searchVideoWithToken, | ||
585 | getVideo, | 553 | getVideo, |
586 | getVideoWithToken, | 554 | getVideoWithToken, |
587 | getVideosList, | 555 | getVideosList, |
588 | getVideosListPagination, | 556 | getVideosListPagination, |
589 | getVideosListSort, | 557 | getVideosListSort, |
590 | removeVideo, | 558 | removeVideo, |
591 | searchVideo, | ||
592 | searchVideoWithPagination, | ||
593 | searchVideoWithSort, | ||
594 | getVideosListWithToken, | 559 | getVideosListWithToken, |
595 | uploadVideo, | 560 | uploadVideo, |
561 | getVideosWithFilters, | ||
596 | updateVideo, | 562 | updateVideo, |
597 | rateVideo, | 563 | rateVideo, |
598 | viewVideo, | 564 | viewVideo, |
diff --git a/shared/models/index.ts b/shared/models/index.ts index c8ce71f17..1db00c295 100644 --- a/shared/models/index.ts +++ b/shared/models/index.ts | |||
@@ -4,6 +4,7 @@ export * from './users' | |||
4 | export * from './videos' | 4 | export * from './videos' |
5 | export * from './feeds' | 5 | export * from './feeds' |
6 | export * from './i18n' | 6 | export * from './i18n' |
7 | export * from './search' | ||
7 | export * from './server/job.model' | 8 | export * from './server/job.model' |
8 | export * from './oauth-client-local.model' | 9 | export * from './oauth-client-local.model' |
9 | export * from './result-list.model' | 10 | export * from './result-list.model' |
diff --git a/shared/models/search/index.ts b/shared/models/search/index.ts new file mode 100644 index 000000000..288ee41ef --- /dev/null +++ b/shared/models/search/index.ts | |||
@@ -0,0 +1 @@ | |||
export * from './videos-search-query.model' | |||
diff --git a/shared/models/search/videos-search-query.model.ts b/shared/models/search/videos-search-query.model.ts new file mode 100644 index 000000000..bb23bd636 --- /dev/null +++ b/shared/models/search/videos-search-query.model.ts | |||
@@ -0,0 +1,24 @@ | |||
1 | export interface VideosSearchQuery { | ||
2 | search: string | ||
3 | |||
4 | start?: number | ||
5 | count?: number | ||
6 | sort?: string | ||
7 | |||
8 | startDate?: string // ISO 8601 | ||
9 | endDate?: string // ISO 8601 | ||
10 | |||
11 | nsfw?: boolean | ||
12 | |||
13 | categoryOneOf?: number[] | ||
14 | |||
15 | licenceOneOf?: number[] | ||
16 | |||
17 | languageOneOf?: string[] | ||
18 | |||
19 | tagsOneOf?: string[] | ||
20 | tagsAllOf?: string[] | ||
21 | |||
22 | durationMin?: number // seconds | ||
23 | durationMax?: number // seconds | ||
24 | } | ||