aboutsummaryrefslogtreecommitdiffhomepage
path: root/server
diff options
context:
space:
mode:
authorChocobozzz <me@florianbigard.com>2021-07-28 16:40:21 +0200
committerChocobozzz <me@florianbigard.com>2021-07-28 16:40:21 +0200
commitfbd67e7f386504e50f2504cb6386700a58906f16 (patch)
tree1a7143aaea76ce4e195fb9d6214a0cd769c556ea /server
parent164c8d46cf5c948a28b4ac0e596fad9b83b2c229 (diff)
downloadPeerTube-fbd67e7f386504e50f2504cb6386700a58906f16.tar.gz
PeerTube-fbd67e7f386504e50f2504cb6386700a58906f16.tar.zst
PeerTube-fbd67e7f386504e50f2504cb6386700a58906f16.zip
Add ability to search by uuids/actor names
Diffstat (limited to 'server')
-rw-r--r--server/controllers/api/search/search-video-channels.ts7
-rw-r--r--server/controllers/api/search/search-video-playlists.ts3
-rw-r--r--server/helpers/custom-validators/misc.ts10
-rw-r--r--server/middlewares/validators/search.ts34
-rw-r--r--server/models/video/sql/videos-id-list-query-builder.ts10
-rw-r--r--server/models/video/video-channel.ts76
-rw-r--r--server/models/video/video-playlist.ts16
-rw-r--r--server/models/video/video.ts3
-rw-r--r--server/tests/api/check-params/search.ts15
-rw-r--r--server/tests/api/search/search-channels.ts24
-rw-r--r--server/tests/api/search/search-playlists.ts28
-rw-r--r--server/tests/api/search/search-videos.ts28
12 files changed, 208 insertions, 46 deletions
diff --git a/server/controllers/api/search/search-video-channels.ts b/server/controllers/api/search/search-video-channels.ts
index be0b6b9a2..9fc2d53a5 100644
--- a/server/controllers/api/search/search-video-channels.ts
+++ b/server/controllers/api/search/search-video-channels.ts
@@ -46,7 +46,7 @@ export { searchChannelsRouter }
46 46
47function searchVideoChannels (req: express.Request, res: express.Response) { 47function searchVideoChannels (req: express.Request, res: express.Response) {
48 const query: VideoChannelsSearchQuery = req.query 48 const query: VideoChannelsSearchQuery = req.query
49 const search = query.search 49 let search = query.search || ''
50 50
51 const parts = search.split('@') 51 const parts = search.split('@')
52 52
@@ -57,7 +57,7 @@ function searchVideoChannels (req: express.Request, res: express.Response) {
57 if (isURISearch(search) || isWebfingerSearch) return searchVideoChannelURI(search, isWebfingerSearch, res) 57 if (isURISearch(search) || isWebfingerSearch) return searchVideoChannelURI(search, isWebfingerSearch, res)
58 58
59 // @username -> username to search in DB 59 // @username -> username to search in DB
60 if (query.search.startsWith('@')) query.search = query.search.replace(/^@/, '') 60 if (search.startsWith('@')) search = search.replace(/^@/, '')
61 61
62 if (isSearchIndexSearch(query)) { 62 if (isSearchIndexSearch(query)) {
63 return searchVideoChannelsIndex(query, res) 63 return searchVideoChannelsIndex(query, res)
@@ -99,7 +99,8 @@ async function searchVideoChannelsDB (query: VideoChannelsSearchQuery, res: expr
99 start: query.start, 99 start: query.start,
100 count: query.count, 100 count: query.count,
101 sort: query.sort, 101 sort: query.sort,
102 host: query.host 102 host: query.host,
103 names: query.names
103 }, 'filter:api.search.video-channels.local.list.params') 104 }, 'filter:api.search.video-channels.local.list.params')
104 105
105 const resultList = await Hooks.wrapPromiseFun( 106 const resultList = await Hooks.wrapPromiseFun(
diff --git a/server/controllers/api/search/search-video-playlists.ts b/server/controllers/api/search/search-video-playlists.ts
index 60d1a44f7..bd6a2a564 100644
--- a/server/controllers/api/search/search-video-playlists.ts
+++ b/server/controllers/api/search/search-video-playlists.ts
@@ -89,7 +89,8 @@ async function searchVideoPlaylistsDB (query: VideoPlaylistsSearchQuery, res: ex
89 start: query.start, 89 start: query.start,
90 count: query.count, 90 count: query.count,
91 sort: query.sort, 91 sort: query.sort,
92 host: query.host 92 host: query.host,
93 uuids: query.uuids
93 }, 'filter:api.search.video-playlists.local.list.params') 94 }, 'filter:api.search.video-playlists.local.list.params')
94 95
95 const resultList = await Hooks.wrapPromiseFun( 96 const resultList = await Hooks.wrapPromiseFun(
diff --git a/server/helpers/custom-validators/misc.ts b/server/helpers/custom-validators/misc.ts
index 528bfcfb8..f8f168149 100644
--- a/server/helpers/custom-validators/misc.ts
+++ b/server/helpers/custom-validators/misc.ts
@@ -39,6 +39,10 @@ function isUUIDValid (value: string) {
39 return exists(value) && validator.isUUID('' + value, 4) 39 return exists(value) && validator.isUUID('' + value, 4)
40} 40}
41 41
42function areUUIDsValid (values: string[]) {
43 return isArray(values) && values.every(v => isUUIDValid(v))
44}
45
42function isIdOrUUIDValid (value: string) { 46function isIdOrUUIDValid (value: string) {
43 return isIdValid(value) || isUUIDValid(value) 47 return isIdValid(value) || isUUIDValid(value)
44} 48}
@@ -132,6 +136,10 @@ function toCompleteUUID (value: string) {
132 return value 136 return value
133} 137}
134 138
139function toCompleteUUIDs (values: string[]) {
140 return values.map(v => toCompleteUUID(v))
141}
142
135function toIntOrNull (value: string) { 143function toIntOrNull (value: string) {
136 const v = toValueOrNull(value) 144 const v = toValueOrNull(value)
137 145
@@ -180,6 +188,7 @@ export {
180 isIdValid, 188 isIdValid,
181 isSafePath, 189 isSafePath,
182 isUUIDValid, 190 isUUIDValid,
191 toCompleteUUIDs,
183 toCompleteUUID, 192 toCompleteUUID,
184 isIdOrUUIDValid, 193 isIdOrUUIDValid,
185 isDateValid, 194 isDateValid,
@@ -187,6 +196,7 @@ export {
187 toBooleanOrNull, 196 toBooleanOrNull,
188 isBooleanValid, 197 isBooleanValid,
189 toIntOrNull, 198 toIntOrNull,
199 areUUIDsValid,
190 toArray, 200 toArray,
191 toIntArray, 201 toIntArray,
192 isFileFieldValid, 202 isFileFieldValid,
diff --git a/server/middlewares/validators/search.ts b/server/middlewares/validators/search.ts
index ea6a490b2..cde300968 100644
--- a/server/middlewares/validators/search.ts
+++ b/server/middlewares/validators/search.ts
@@ -2,7 +2,7 @@ import * as express from 'express'
2import { query } from 'express-validator' 2import { query } from 'express-validator'
3import { isSearchTargetValid } from '@server/helpers/custom-validators/search' 3import { isSearchTargetValid } from '@server/helpers/custom-validators/search'
4import { isHostValid } from '@server/helpers/custom-validators/servers' 4import { isHostValid } from '@server/helpers/custom-validators/servers'
5import { isDateValid } from '../../helpers/custom-validators/misc' 5import { areUUIDsValid, isDateValid, toCompleteUUIDs } from '../../helpers/custom-validators/misc'
6import { logger } from '../../helpers/logger' 6import { logger } from '../../helpers/logger'
7import { areValidationErrors } from './shared' 7import { areValidationErrors } from './shared'
8 8
@@ -27,8 +27,18 @@ const videosSearchValidator = [
27 .optional() 27 .optional()
28 .custom(isDateValid).withMessage('Should have a published end date that conforms to ISO 8601'), 28 .custom(isDateValid).withMessage('Should have a published end date that conforms to ISO 8601'),
29 29
30 query('durationMin').optional().isInt().withMessage('Should have a valid min duration'), 30 query('durationMin')
31 query('durationMax').optional().isInt().withMessage('Should have a valid max duration'), 31 .optional()
32 .isInt().withMessage('Should have a valid min duration'),
33 query('durationMax')
34 .optional()
35 .isInt().withMessage('Should have a valid max duration'),
36
37 query('uuids')
38 .optional()
39 .toArray()
40 .customSanitizer(toCompleteUUIDs)
41 .custom(areUUIDsValid).withMessage('Should have valid uuids'),
32 42
33 query('searchTarget').optional().custom(isSearchTargetValid).withMessage('Should have a valid search target'), 43 query('searchTarget').optional().custom(isSearchTargetValid).withMessage('Should have a valid search target'),
34 44
@@ -42,7 +52,9 @@ const videosSearchValidator = [
42] 52]
43 53
44const videoChannelsListSearchValidator = [ 54const videoChannelsListSearchValidator = [
45 query('search').not().isEmpty().withMessage('Should have a valid search'), 55 query('search')
56 .optional()
57 .not().isEmpty().withMessage('Should have a valid search'),
46 58
47 query('host') 59 query('host')
48 .optional() 60 .optional()
@@ -52,6 +64,10 @@ const videoChannelsListSearchValidator = [
52 .optional() 64 .optional()
53 .custom(isSearchTargetValid).withMessage('Should have a valid search target'), 65 .custom(isSearchTargetValid).withMessage('Should have a valid search target'),
54 66
67 query('names')
68 .optional()
69 .toArray(),
70
55 (req: express.Request, res: express.Response, next: express.NextFunction) => { 71 (req: express.Request, res: express.Response, next: express.NextFunction) => {
56 logger.debug('Checking video channels search query', { parameters: req.query }) 72 logger.debug('Checking video channels search query', { parameters: req.query })
57 73
@@ -62,7 +78,9 @@ const videoChannelsListSearchValidator = [
62] 78]
63 79
64const videoPlaylistsListSearchValidator = [ 80const videoPlaylistsListSearchValidator = [
65 query('search').not().isEmpty().withMessage('Should have a valid search'), 81 query('search')
82 .optional()
83 .not().isEmpty().withMessage('Should have a valid search'),
66 84
67 query('host') 85 query('host')
68 .optional() 86 .optional()
@@ -72,6 +90,12 @@ const videoPlaylistsListSearchValidator = [
72 .optional() 90 .optional()
73 .custom(isSearchTargetValid).withMessage('Should have a valid search target'), 91 .custom(isSearchTargetValid).withMessage('Should have a valid search target'),
74 92
93 query('uuids')
94 .optional()
95 .toArray()
96 .customSanitizer(toCompleteUUIDs)
97 .custom(areUUIDsValid).withMessage('Should have valid uuids'),
98
75 (req: express.Request, res: express.Response, next: express.NextFunction) => { 99 (req: express.Request, res: express.Response, next: express.NextFunction) => {
76 logger.debug('Checking video playlists search query', { parameters: req.query }) 100 logger.debug('Checking video playlists search query', { parameters: req.query })
77 101
diff --git a/server/models/video/sql/videos-id-list-query-builder.ts b/server/models/video/sql/videos-id-list-query-builder.ts
index d4260c69c..7625c003d 100644
--- a/server/models/video/sql/videos-id-list-query-builder.ts
+++ b/server/models/video/sql/videos-id-list-query-builder.ts
@@ -35,6 +35,8 @@ export type BuildVideosListQueryOptions = {
35 tagsOneOf?: string[] 35 tagsOneOf?: string[]
36 tagsAllOf?: string[] 36 tagsAllOf?: string[]
37 37
38 uuids?: string[]
39
38 withFiles?: boolean 40 withFiles?: boolean
39 41
40 accountId?: number 42 accountId?: number
@@ -161,6 +163,10 @@ export class VideosIdListQueryBuilder extends AbstractVideosQueryBuilder {
161 this.whereTagsAllOf(options.tagsAllOf) 163 this.whereTagsAllOf(options.tagsAllOf)
162 } 164 }
163 165
166 if (options.uuids) {
167 this.whereUUIDs(options.uuids)
168 }
169
164 if (options.nsfw === true) { 170 if (options.nsfw === true) {
165 this.whereNSFW() 171 this.whereNSFW()
166 } else if (options.nsfw === false) { 172 } else if (options.nsfw === false) {
@@ -386,6 +392,10 @@ export class VideosIdListQueryBuilder extends AbstractVideosQueryBuilder {
386 ) 392 )
387 } 393 }
388 394
395 private whereUUIDs (uuids: string[]) {
396 this.and.push('"video"."uuid" IN (' + createSafeIn(this.sequelize, uuids) + ')')
397 }
398
389 private whereCategoryOneOf (categoryOneOf: number[]) { 399 private whereCategoryOneOf (categoryOneOf: number[]) {
390 this.and.push('"video"."category" IN (:categoryOneOf)') 400 this.and.push('"video"."category" IN (:categoryOneOf)')
391 this.replacements.categoryOneOf = categoryOneOf 401 this.replacements.categoryOneOf = categoryOneOf
diff --git a/server/models/video/video-channel.ts b/server/models/video/video-channel.ts
index 9aa271711..327f49304 100644
--- a/server/models/video/video-channel.ts
+++ b/server/models/video/video-channel.ts
@@ -59,6 +59,7 @@ type AvailableForListOptions = {
59 actorId: number 59 actorId: number
60 search?: string 60 search?: string
61 host?: string 61 host?: string
62 names?: string[]
62} 63}
63 64
64type AvailableWithStatsOptions = { 65type AvailableWithStatsOptions = {
@@ -84,18 +85,20 @@ export type SummaryOptions = {
84 // Only list local channels OR channels that are on an instance followed by actorId 85 // Only list local channels OR channels that are on an instance followed by actorId
85 const inQueryInstanceFollow = buildServerIdsFollowedBy(options.actorId) 86 const inQueryInstanceFollow = buildServerIdsFollowedBy(options.actorId)
86 87
87 const whereActor = { 88 const whereActorAnd: WhereOptions[] = [
88 [Op.or]: [ 89 {
89 { 90 [Op.or]: [
90 serverId: null 91 {
91 }, 92 serverId: null
92 { 93 },
93 serverId: { 94 {
94 [Op.in]: Sequelize.literal(inQueryInstanceFollow) 95 serverId: {
96 [Op.in]: Sequelize.literal(inQueryInstanceFollow)
97 }
95 } 98 }
96 } 99 ]
97 ] 100 }
98 } 101 ]
99 102
100 let serverRequired = false 103 let serverRequired = false
101 let whereServer: WhereOptions 104 let whereServer: WhereOptions
@@ -106,8 +109,16 @@ export type SummaryOptions = {
106 } 109 }
107 110
108 if (options.host === WEBSERVER.HOST) { 111 if (options.host === WEBSERVER.HOST) {
109 Object.assign(whereActor, { 112 whereActorAnd.push({
110 [Op.and]: [ { serverId: null } ] 113 serverId: null
114 })
115 }
116
117 if (options.names) {
118 whereActorAnd.push({
119 preferredUsername: {
120 [Op.in]: options.names
121 }
111 }) 122 })
112 } 123 }
113 124
@@ -118,7 +129,9 @@ export type SummaryOptions = {
118 exclude: unusedActorAttributesForAPI 129 exclude: unusedActorAttributesForAPI
119 }, 130 },
120 model: ActorModel, 131 model: ActorModel,
121 where: whereActor, 132 where: {
133 [Op.and]: whereActorAnd
134 },
122 include: [ 135 include: [
123 { 136 {
124 model: ServerModel, 137 model: ServerModel,
@@ -454,26 +467,23 @@ ON "Account->Actor"."serverId" = "Account->Actor->Server"."id"`
454 467
455 static searchForApi (options: { 468 static searchForApi (options: {
456 actorId: number 469 actorId: number
457 search: string 470 search?: string
458 start: number 471 start: number
459 count: number 472 count: number
460 sort: string 473 sort: string
461 474
462 host?: string 475 host?: string
476 names?: string[]
463 }) { 477 }) {
464 const attributesInclude = [] 478 let attributesInclude: any[] = [ literal('0 as similarity') ]
465 const escapedSearch = VideoChannelModel.sequelize.escape(options.search) 479 let where: WhereOptions
466 const escapedLikeSearch = VideoChannelModel.sequelize.escape('%' + options.search + '%')
467 attributesInclude.push(createSimilarityAttribute('VideoChannelModel.name', options.search))
468 480
469 const query = { 481 if (options.search) {
470 attributes: { 482 const escapedSearch = VideoChannelModel.sequelize.escape(options.search)
471 include: attributesInclude 483 const escapedLikeSearch = VideoChannelModel.sequelize.escape('%' + options.search + '%')
472 }, 484 attributesInclude = [ createSimilarityAttribute('VideoChannelModel.name', options.search) ]
473 offset: options.start, 485
474 limit: options.count, 486 where = {
475 order: getSort(options.sort),
476 where: {
477 [Op.or]: [ 487 [Op.or]: [
478 Sequelize.literal( 488 Sequelize.literal(
479 'lower(immutable_unaccent("VideoChannelModel"."name")) % lower(immutable_unaccent(' + escapedSearch + '))' 489 'lower(immutable_unaccent("VideoChannelModel"."name")) % lower(immutable_unaccent(' + escapedSearch + '))'
@@ -485,9 +495,19 @@ ON "Account->Actor"."serverId" = "Account->Actor->Server"."id"`
485 } 495 }
486 } 496 }
487 497
498 const query = {
499 attributes: {
500 include: attributesInclude
501 },
502 offset: options.start,
503 limit: options.count,
504 order: getSort(options.sort),
505 where
506 }
507
488 return VideoChannelModel 508 return VideoChannelModel
489 .scope({ 509 .scope({
490 method: [ ScopeNames.FOR_API, { actorId: options.actorId, host: options.host } as AvailableForListOptions ] 510 method: [ ScopeNames.FOR_API, { actorId: options.actorId, host: options.host, names: options.names } as AvailableForListOptions ]
491 }) 511 })
492 .findAndCountAll(query) 512 .findAndCountAll(query)
493 .then(({ rows, count }) => { 513 .then(({ rows, count }) => {
diff --git a/server/models/video/video-playlist.ts b/server/models/video/video-playlist.ts
index a2dc7075d..caa79952d 100644
--- a/server/models/video/video-playlist.ts
+++ b/server/models/video/video-playlist.ts
@@ -83,6 +83,7 @@ type AvailableForListOptions = {
83 listMyPlaylists?: boolean 83 listMyPlaylists?: boolean
84 search?: string 84 search?: string
85 host?: string 85 host?: string
86 uuids?: string[]
86 withVideos?: boolean 87 withVideos?: boolean
87} 88}
88 89
@@ -200,18 +201,26 @@ function getVideoLengthSelect () {
200 }) 201 })
201 } 202 }
202 203
204 if (options.uuids) {
205 whereAnd.push({
206 uuid: {
207 [Op.in]: options.uuids
208 }
209 })
210 }
211
203 if (options.withVideos === true) { 212 if (options.withVideos === true) {
204 whereAnd.push( 213 whereAnd.push(
205 literal(`(${getVideoLengthSelect()}) != 0`) 214 literal(`(${getVideoLengthSelect()}) != 0`)
206 ) 215 )
207 } 216 }
208 217
209 const attributesInclude = [] 218 let attributesInclude: any[] = [ literal('0 as similarity') ]
210 219
211 if (options.search) { 220 if (options.search) {
212 const escapedSearch = VideoPlaylistModel.sequelize.escape(options.search) 221 const escapedSearch = VideoPlaylistModel.sequelize.escape(options.search)
213 const escapedLikeSearch = VideoPlaylistModel.sequelize.escape('%' + options.search + '%') 222 const escapedLikeSearch = VideoPlaylistModel.sequelize.escape('%' + options.search + '%')
214 attributesInclude.push(createSimilarityAttribute('VideoPlaylistModel.name', options.search)) 223 attributesInclude = [ createSimilarityAttribute('VideoPlaylistModel.name', options.search) ]
215 224
216 whereAnd.push({ 225 whereAnd.push({
217 [Op.or]: [ 226 [Op.or]: [
@@ -359,6 +368,7 @@ export class VideoPlaylistModel extends Model<Partial<AttributesOnly<VideoPlayli
359 listMyPlaylists?: boolean 368 listMyPlaylists?: boolean
360 search?: string 369 search?: string
361 host?: string 370 host?: string
371 uuids?: string[]
362 withVideos?: boolean // false by default 372 withVideos?: boolean // false by default
363 }) { 373 }) {
364 const query = { 374 const query = {
@@ -379,6 +389,7 @@ export class VideoPlaylistModel extends Model<Partial<AttributesOnly<VideoPlayli
379 listMyPlaylists: options.listMyPlaylists, 389 listMyPlaylists: options.listMyPlaylists,
380 search: options.search, 390 search: options.search,
381 host: options.host, 391 host: options.host,
392 uuids: options.uuids,
382 withVideos: options.withVideos || false 393 withVideos: options.withVideos || false
383 } as AvailableForListOptions 394 } as AvailableForListOptions
384 ] 395 ]
@@ -402,6 +413,7 @@ export class VideoPlaylistModel extends Model<Partial<AttributesOnly<VideoPlayli
402 sort: string 413 sort: string
403 search?: string 414 search?: string
404 host?: string 415 host?: string
416 uuids?: string[]
405 }) { 417 }) {
406 return VideoPlaylistModel.listForApi({ 418 return VideoPlaylistModel.listForApi({
407 ...options, 419 ...options,
diff --git a/server/models/video/video.ts b/server/models/video/video.ts
index c444f381e..fe92ead04 100644
--- a/server/models/video/video.ts
+++ b/server/models/video/video.ts
@@ -1132,6 +1132,7 @@ export class VideoModel extends Model<Partial<AttributesOnly<VideoModel>>> {
1132 durationMax?: number // seconds 1132 durationMax?: number // seconds
1133 user?: MUserAccountId 1133 user?: MUserAccountId
1134 filter?: VideoFilter 1134 filter?: VideoFilter
1135 uuids?: string[]
1135 }) { 1136 }) {
1136 const serverActor = await getServerActor() 1137 const serverActor = await getServerActor()
1137 1138
@@ -1167,6 +1168,8 @@ export class VideoModel extends Model<Partial<AttributesOnly<VideoModel>>> {
1167 durationMin: options.durationMin, 1168 durationMin: options.durationMin,
1168 durationMax: options.durationMax, 1169 durationMax: options.durationMax,
1169 1170
1171 uuids: options.uuids,
1172
1170 search: options.search 1173 search: options.search
1171 } 1174 }
1172 1175
diff --git a/server/tests/api/check-params/search.ts b/server/tests/api/check-params/search.ts
index 72ad6c842..789ea7754 100644
--- a/server/tests/api/check-params/search.ts
+++ b/server/tests/api/check-params/search.ts
@@ -146,6 +146,16 @@ describe('Test videos API validator', function () {
146 const customQuery = { ...query, host: 'example.com' } 146 const customQuery = { ...query, host: 'example.com' }
147 await makeGetRequest({ url: server.url, path, query: customQuery, expectedStatus: HttpStatusCode.OK_200 }) 147 await makeGetRequest({ url: server.url, path, query: customQuery, expectedStatus: HttpStatusCode.OK_200 })
148 }) 148 })
149
150 it('Should fail with invalid uuids', async function () {
151 const customQuery = { ...query, uuids: [ '6565', 'dfd70b83-639f-4980-94af-304a56ab4b35' ] }
152 await makeGetRequest({ url: server.url, path, query: customQuery, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
153 })
154
155 it('Should succeed with valid uuids', async function () {
156 const customQuery = { ...query, uuids: [ 'dfd70b83-639f-4980-94af-304a56ab4b35' ] }
157 await makeGetRequest({ url: server.url, path, query: customQuery, expectedStatus: HttpStatusCode.OK_200 })
158 })
149 }) 159 })
150 160
151 describe('When searching video playlists', function () { 161 describe('When searching video playlists', function () {
@@ -172,6 +182,11 @@ describe('Test videos API validator', function () {
172 await makeGetRequest({ url: server.url, path, query: { ...query, host: '6565' }, expectedStatus: HttpStatusCode.BAD_REQUEST_400 }) 182 await makeGetRequest({ url: server.url, path, query: { ...query, host: '6565' }, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
173 }) 183 })
174 184
185 it('Should fail with invalid uuids', async function () {
186 const customQuery = { ...query, uuids: [ '6565', 'dfd70b83-639f-4980-94af-304a56ab4b35' ] }
187 await makeGetRequest({ url: server.url, path, query: customQuery, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
188 })
189
175 it('Should succeed with the correct parameters', async function () { 190 it('Should succeed with the correct parameters', async function () {
176 await makeGetRequest({ url: server.url, path, query, expectedStatus: HttpStatusCode.OK_200 }) 191 await makeGetRequest({ url: server.url, path, query, expectedStatus: HttpStatusCode.OK_200 })
177 }) 192 })
diff --git a/server/tests/api/search/search-channels.ts b/server/tests/api/search/search-channels.ts
index aab03bfd1..ef78c0f67 100644
--- a/server/tests/api/search/search-channels.ts
+++ b/server/tests/api/search/search-channels.ts
@@ -22,8 +22,12 @@ describe('Test channels search', function () {
22 before(async function () { 22 before(async function () {
23 this.timeout(120000) 23 this.timeout(120000)
24 24
25 server = await createSingleServer(1) 25 const servers = await Promise.all([
26 remoteServer = await createSingleServer(2, { transcoding: { enabled: false } }) 26 createSingleServer(1),
27 createSingleServer(2, { transcoding: { enabled: false } })
28 ])
29 server = servers[0]
30 remoteServer = servers[1]
27 31
28 await setAccessTokensToServers([ server, remoteServer ]) 32 await setAccessTokensToServers([ server, remoteServer ])
29 33
@@ -116,6 +120,22 @@ describe('Test channels search', function () {
116 } 120 }
117 }) 121 })
118 122
123 it('Should filter by names', async function () {
124 {
125 const body = await command.advancedChannelSearch({ search: { names: [ 'squall_channel', 'zell_channel' ] } })
126 expect(body.total).to.equal(2)
127 expect(body.data).to.have.lengthOf(2)
128 expect(body.data[0].displayName).to.equal('Squall channel')
129 expect(body.data[1].displayName).to.equal('Zell channel')
130 }
131
132 {
133 const body = await command.advancedChannelSearch({ search: { names: [ 'chocobozzz_channel' ] } })
134 expect(body.total).to.equal(0)
135 expect(body.data).to.have.lengthOf(0)
136 }
137 })
138
119 after(async function () { 139 after(async function () {
120 await cleanupTests([ server ]) 140 await cleanupTests([ server ])
121 }) 141 })
diff --git a/server/tests/api/search/search-playlists.ts b/server/tests/api/search/search-playlists.ts
index e7e53ff41..85be1eb59 100644
--- a/server/tests/api/search/search-playlists.ts
+++ b/server/tests/api/search/search-playlists.ts
@@ -19,12 +19,18 @@ describe('Test playlists search', function () {
19 let server: PeerTubeServer 19 let server: PeerTubeServer
20 let remoteServer: PeerTubeServer 20 let remoteServer: PeerTubeServer
21 let command: SearchCommand 21 let command: SearchCommand
22 let playlistUUID: string
23 let playlistShortUUID: string
22 24
23 before(async function () { 25 before(async function () {
24 this.timeout(120000) 26 this.timeout(120000)
25 27
26 server = await createSingleServer(1) 28 const servers = await Promise.all([
27 remoteServer = await createSingleServer(2, { transcoding: { enabled: false } }) 29 createSingleServer(1),
30 createSingleServer(2, { transcoding: { enabled: false } })
31 ])
32 server = servers[0]
33 remoteServer = servers[1]
28 34
29 await setAccessTokensToServers([ remoteServer, server ]) 35 await setAccessTokensToServers([ remoteServer, server ])
30 await setDefaultVideoChannel([ remoteServer, server ]) 36 await setDefaultVideoChannel([ remoteServer, server ])
@@ -38,6 +44,8 @@ describe('Test playlists search', function () {
38 videoChannelId: server.store.channel.id 44 videoChannelId: server.store.channel.id
39 } 45 }
40 const created = await server.playlists.create({ attributes }) 46 const created = await server.playlists.create({ attributes })
47 playlistUUID = created.uuid
48 playlistShortUUID = created.shortUUID
41 49
42 await server.playlists.addElement({ playlistId: created.id, attributes: { videoId } }) 50 await server.playlists.addElement({ playlistId: created.id, attributes: { videoId } })
43 } 51 }
@@ -136,6 +144,22 @@ describe('Test playlists search', function () {
136 } 144 }
137 }) 145 })
138 146
147 it('Should filter by UUIDs', async function () {
148 for (const uuid of [ playlistUUID, playlistShortUUID ]) {
149 const body = await command.advancedPlaylistSearch({ search: { uuids: [ uuid ] } })
150
151 expect(body.total).to.equal(1)
152 expect(body.data[0].displayName).to.equal('Dr. Kenzo Tenma hospital videos')
153 }
154
155 {
156 const body = await command.advancedPlaylistSearch({ search: { uuids: [ 'dfd70b83-639f-4980-94af-304a56ab4b35' ] } })
157
158 expect(body.total).to.equal(0)
159 expect(body.data).to.have.lengthOf(0)
160 }
161 })
162
139 it('Should not display playlists without videos', async function () { 163 it('Should not display playlists without videos', async function () {
140 const search = { 164 const search = {
141 search: 'Lunge', 165 search: 'Lunge',
diff --git a/server/tests/api/search/search-videos.ts b/server/tests/api/search/search-videos.ts
index a56dc1d87..bd1e4d266 100644
--- a/server/tests/api/search/search-videos.ts
+++ b/server/tests/api/search/search-videos.ts
@@ -22,14 +22,19 @@ describe('Test videos search', function () {
22 let remoteServer: PeerTubeServer 22 let remoteServer: PeerTubeServer
23 let startDate: string 23 let startDate: string
24 let videoUUID: string 24 let videoUUID: string
25 let videoShortUUID: string
25 26
26 let command: SearchCommand 27 let command: SearchCommand
27 28
28 before(async function () { 29 before(async function () {
29 this.timeout(120000) 30 this.timeout(120000)
30 31
31 server = await createSingleServer(1) 32 const servers = await Promise.all([
32 remoteServer = await createSingleServer(2) 33 createSingleServer(1),
34 createSingleServer(2)
35 ])
36 server = servers[0]
37 remoteServer = servers[1]
33 38
34 await setAccessTokensToServers([ server, remoteServer ]) 39 await setAccessTokensToServers([ server, remoteServer ])
35 await setDefaultVideoChannel([ server, remoteServer ]) 40 await setDefaultVideoChannel([ server, remoteServer ])
@@ -50,8 +55,9 @@ describe('Test videos search', function () {
50 55
51 { 56 {
52 const attributes3 = { ...attributes1, name: attributes1.name + ' - 3', language: undefined } 57 const attributes3 = { ...attributes1, name: attributes1.name + ' - 3', language: undefined }
53 const { id, uuid } = await server.videos.upload({ attributes: attributes3 }) 58 const { id, uuid, shortUUID } = await server.videos.upload({ attributes: attributes3 })
54 videoUUID = uuid 59 videoUUID = uuid
60 videoShortUUID = shortUUID
55 61
56 await server.captions.add({ 62 await server.captions.add({
57 language: 'en', 63 language: 'en',
@@ -479,6 +485,22 @@ describe('Test videos search', function () {
479 expect(body.data[0].name).to.equal('1111 2222 3333 - 3') 485 expect(body.data[0].name).to.equal('1111 2222 3333 - 3')
480 }) 486 })
481 487
488 it('Should filter by UUIDs', async function () {
489 for (const uuid of [ videoUUID, videoShortUUID ]) {
490 const body = await command.advancedVideoSearch({ search: { uuids: [ uuid ] } })
491
492 expect(body.total).to.equal(1)
493 expect(body.data[0].name).to.equal('1111 2222 3333 - 3')
494 }
495
496 {
497 const body = await command.advancedVideoSearch({ search: { uuids: [ 'dfd70b83-639f-4980-94af-304a56ab4b35' ] } })
498
499 expect(body.total).to.equal(0)
500 expect(body.data).to.have.lengthOf(0)
501 }
502 })
503
482 it('Should search by host', async function () { 504 it('Should search by host', async function () {
483 { 505 {
484 const body = await command.advancedVideoSearch({ search: { search: '6666 7777 8888', host: server.host } }) 506 const body = await command.advancedVideoSearch({ search: { search: '6666 7777 8888', host: server.host } })