aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--server/controllers/api/accounts.ts20
-rw-r--r--server/controllers/api/search/search-video-channels.ts19
-rw-r--r--server/controllers/api/search/search-video-playlists.ts19
-rw-r--r--server/controllers/api/search/search-videos.ts24
-rw-r--r--server/controllers/api/users/my-subscriptions.ts15
-rw-r--r--server/controllers/api/video-channel.ts16
-rw-r--r--server/controllers/api/videos/index.ts16
-rw-r--r--server/helpers/query.ts74
-rw-r--r--server/models/video/video.ts3
-rw-r--r--server/tests/api/search/search-index.ts142
-rw-r--r--shared/core-utils/utils/object.ts4
-rw-r--r--shared/models/search/video-channels-search-query.model.ts6
-rw-r--r--shared/models/search/video-playlists-search-query.model.ts8
-rw-r--r--shared/models/search/videos-common-query.model.ts6
-rw-r--r--shared/models/search/videos-search-query.model.ts8
15 files changed, 260 insertions, 120 deletions
diff --git a/server/controllers/api/accounts.ts b/server/controllers/api/accounts.ts
index 49a8e3195..55e2aaf62 100644
--- a/server/controllers/api/accounts.ts
+++ b/server/controllers/api/accounts.ts
@@ -1,6 +1,6 @@
1import * as express from 'express' 1import * as express from 'express'
2import { pickCommonVideoQuery } from '@server/helpers/query'
2import { getServerActor } from '@server/models/application/application' 3import { getServerActor } from '@server/models/application/application'
3import { VideosWithSearchCommonQuery } from '@shared/models'
4import { buildNSFWFilter, getCountVideos, isUserAbleToSearchRemoteURI } from '../../helpers/express-utils' 4import { buildNSFWFilter, getCountVideos, isUserAbleToSearchRemoteURI } from '../../helpers/express-utils'
5import { getFormattedObjects } from '../../helpers/utils' 5import { getFormattedObjects } from '../../helpers/utils'
6import { JobQueue } from '../../lib/job-queue' 6import { JobQueue } from '../../lib/job-queue'
@@ -159,27 +159,19 @@ async function listAccountVideos (req: express.Request, res: express.Response) {
159 const account = res.locals.account 159 const account = res.locals.account
160 const followerActorId = isUserAbleToSearchRemoteURI(res) ? null : undefined 160 const followerActorId = isUserAbleToSearchRemoteURI(res) ? null : undefined
161 const countVideos = getCountVideos(req) 161 const countVideos = getCountVideos(req)
162 const query = req.query as VideosWithSearchCommonQuery 162 const query = pickCommonVideoQuery(req.query)
163 163
164 const apiOptions = await Hooks.wrapObject({ 164 const apiOptions = await Hooks.wrapObject({
165 ...query,
166
165 followerActorId, 167 followerActorId,
166 start: query.start, 168 search: req.query.search,
167 count: query.count,
168 sort: query.sort,
169 includeLocalVideos: true, 169 includeLocalVideos: true,
170 categoryOneOf: query.categoryOneOf,
171 licenceOneOf: query.licenceOneOf,
172 languageOneOf: query.languageOneOf,
173 tagsOneOf: query.tagsOneOf,
174 tagsAllOf: query.tagsAllOf,
175 filter: query.filter,
176 isLive: query.isLive,
177 nsfw: buildNSFWFilter(res, query.nsfw), 170 nsfw: buildNSFWFilter(res, query.nsfw),
178 withFiles: false, 171 withFiles: false,
179 accountId: account.id, 172 accountId: account.id,
180 user: res.locals.oauth ? res.locals.oauth.token.User : undefined, 173 user: res.locals.oauth ? res.locals.oauth.token.User : undefined,
181 countVideos, 174 countVideos
182 search: query.search
183 }, 'filter:api.accounts.videos.list.params') 175 }, 'filter:api.accounts.videos.list.params')
184 176
185 const resultList = await Hooks.wrapPromiseFun( 177 const resultList = await Hooks.wrapPromiseFun(
diff --git a/server/controllers/api/search/search-video-channels.ts b/server/controllers/api/search/search-video-channels.ts
index ae32a6726..eef222506 100644
--- a/server/controllers/api/search/search-video-channels.ts
+++ b/server/controllers/api/search/search-video-channels.ts
@@ -1,5 +1,6 @@
1import * as express from 'express' 1import * as express from 'express'
2import { sanitizeUrl } from '@server/helpers/core-utils' 2import { sanitizeUrl } from '@server/helpers/core-utils'
3import { pickSearchChannelQuery } from '@server/helpers/query'
3import { doJSONRequest } from '@server/helpers/requests' 4import { doJSONRequest } from '@server/helpers/requests'
4import { CONFIG } from '@server/initializers/config' 5import { CONFIG } from '@server/initializers/config'
5import { WEBSERVER } from '@server/initializers/constants' 6import { WEBSERVER } from '@server/initializers/constants'
@@ -7,7 +8,7 @@ import { Hooks } from '@server/lib/plugins/hooks'
7import { buildMutedForSearchIndex, isSearchIndexSearch, isURISearch } from '@server/lib/search' 8import { buildMutedForSearchIndex, isSearchIndexSearch, isURISearch } from '@server/lib/search'
8import { getServerActor } from '@server/models/application/application' 9import { getServerActor } from '@server/models/application/application'
9import { HttpStatusCode, ResultList, VideoChannel } from '@shared/models' 10import { HttpStatusCode, ResultList, VideoChannel } from '@shared/models'
10import { VideoChannelsSearchQuery } from '../../../../shared/models/search' 11import { VideoChannelsSearchQueryAfterSanitize } from '../../../../shared/models/search'
11import { isUserAbleToSearchRemoteURI } from '../../../helpers/express-utils' 12import { isUserAbleToSearchRemoteURI } from '../../../helpers/express-utils'
12import { logger } from '../../../helpers/logger' 13import { logger } from '../../../helpers/logger'
13import { getFormattedObjects } from '../../../helpers/utils' 14import { getFormattedObjects } from '../../../helpers/utils'
@@ -45,7 +46,7 @@ export { searchChannelsRouter }
45// --------------------------------------------------------------------------- 46// ---------------------------------------------------------------------------
46 47
47function searchVideoChannels (req: express.Request, res: express.Response) { 48function searchVideoChannels (req: express.Request, res: express.Response) {
48 const query: VideoChannelsSearchQuery = req.query 49 const query = pickSearchChannelQuery(req.query)
49 let search = query.search || '' 50 let search = query.search || ''
50 51
51 const parts = search.split('@') 52 const parts = search.split('@')
@@ -66,7 +67,7 @@ function searchVideoChannels (req: express.Request, res: express.Response) {
66 return searchVideoChannelsDB(query, res) 67 return searchVideoChannelsDB(query, res)
67} 68}
68 69
69async function searchVideoChannelsIndex (query: VideoChannelsSearchQuery, res: express.Response) { 70async function searchVideoChannelsIndex (query: VideoChannelsSearchQueryAfterSanitize, res: express.Response) {
70 const result = await buildMutedForSearchIndex(res) 71 const result = await buildMutedForSearchIndex(res)
71 72
72 const body = await Hooks.wrapObject(Object.assign(query, result), 'filter:api.search.video-channels.index.list.params') 73 const body = await Hooks.wrapObject(Object.assign(query, result), 'filter:api.search.video-channels.index.list.params')
@@ -90,17 +91,13 @@ async function searchVideoChannelsIndex (query: VideoChannelsSearchQuery, res: e
90 } 91 }
91} 92}
92 93
93async function searchVideoChannelsDB (query: VideoChannelsSearchQuery, res: express.Response) { 94async function searchVideoChannelsDB (query: VideoChannelsSearchQueryAfterSanitize, res: express.Response) {
94 const serverActor = await getServerActor() 95 const serverActor = await getServerActor()
95 96
96 const apiOptions = await Hooks.wrapObject({ 97 const apiOptions = await Hooks.wrapObject({
97 actorId: serverActor.id, 98 ...query,
98 search: query.search, 99
99 start: query.start, 100 actorId: serverActor.id
100 count: query.count,
101 sort: query.sort,
102 host: query.host,
103 handles: query.handles
104 }, 'filter:api.search.video-channels.local.list.params') 101 }, 'filter:api.search.video-channels.local.list.params')
105 102
106 const resultList = await Hooks.wrapPromiseFun( 103 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 bd6a2a564..0a56f19b7 100644
--- a/server/controllers/api/search/search-video-playlists.ts
+++ b/server/controllers/api/search/search-video-playlists.ts
@@ -2,6 +2,7 @@ import * as express from 'express'
2import { sanitizeUrl } from '@server/helpers/core-utils' 2import { sanitizeUrl } from '@server/helpers/core-utils'
3import { isUserAbleToSearchRemoteURI } from '@server/helpers/express-utils' 3import { isUserAbleToSearchRemoteURI } from '@server/helpers/express-utils'
4import { logger } from '@server/helpers/logger' 4import { logger } from '@server/helpers/logger'
5import { pickSearchPlaylistQuery } from '@server/helpers/query'
5import { doJSONRequest } from '@server/helpers/requests' 6import { doJSONRequest } from '@server/helpers/requests'
6import { getFormattedObjects } from '@server/helpers/utils' 7import { getFormattedObjects } from '@server/helpers/utils'
7import { CONFIG } from '@server/initializers/config' 8import { CONFIG } from '@server/initializers/config'
@@ -12,7 +13,7 @@ import { buildMutedForSearchIndex, isSearchIndexSearch, isURISearch } from '@ser
12import { getServerActor } from '@server/models/application/application' 13import { getServerActor } from '@server/models/application/application'
13import { VideoPlaylistModel } from '@server/models/video/video-playlist' 14import { VideoPlaylistModel } from '@server/models/video/video-playlist'
14import { MVideoPlaylistFullSummary } from '@server/types/models' 15import { MVideoPlaylistFullSummary } from '@server/types/models'
15import { HttpStatusCode, ResultList, VideoPlaylist, VideoPlaylistsSearchQuery } from '@shared/models' 16import { HttpStatusCode, ResultList, VideoPlaylist, VideoPlaylistsSearchQueryAfterSanitize } from '@shared/models'
16import { 17import {
17 asyncMiddleware, 18 asyncMiddleware,
18 openapiOperationDoc, 19 openapiOperationDoc,
@@ -44,7 +45,7 @@ export { searchPlaylistsRouter }
44// --------------------------------------------------------------------------- 45// ---------------------------------------------------------------------------
45 46
46function searchVideoPlaylists (req: express.Request, res: express.Response) { 47function searchVideoPlaylists (req: express.Request, res: express.Response) {
47 const query: VideoPlaylistsSearchQuery = req.query 48 const query = pickSearchPlaylistQuery(req.query)
48 const search = query.search 49 const search = query.search
49 50
50 if (isURISearch(search)) return searchVideoPlaylistsURI(search, res) 51 if (isURISearch(search)) return searchVideoPlaylistsURI(search, res)
@@ -56,7 +57,7 @@ function searchVideoPlaylists (req: express.Request, res: express.Response) {
56 return searchVideoPlaylistsDB(query, res) 57 return searchVideoPlaylistsDB(query, res)
57} 58}
58 59
59async function searchVideoPlaylistsIndex (query: VideoPlaylistsSearchQuery, res: express.Response) { 60async function searchVideoPlaylistsIndex (query: VideoPlaylistsSearchQueryAfterSanitize, res: express.Response) {
60 const result = await buildMutedForSearchIndex(res) 61 const result = await buildMutedForSearchIndex(res)
61 62
62 const body = await Hooks.wrapObject(Object.assign(query, result), 'filter:api.search.video-playlists.index.list.params') 63 const body = await Hooks.wrapObject(Object.assign(query, result), 'filter:api.search.video-playlists.index.list.params')
@@ -80,17 +81,13 @@ async function searchVideoPlaylistsIndex (query: VideoPlaylistsSearchQuery, res:
80 } 81 }
81} 82}
82 83
83async function searchVideoPlaylistsDB (query: VideoPlaylistsSearchQuery, res: express.Response) { 84async function searchVideoPlaylistsDB (query: VideoPlaylistsSearchQueryAfterSanitize, res: express.Response) {
84 const serverActor = await getServerActor() 85 const serverActor = await getServerActor()
85 86
86 const apiOptions = await Hooks.wrapObject({ 87 const apiOptions = await Hooks.wrapObject({
87 followerActorId: serverActor.id, 88 ...query,
88 search: query.search, 89
89 start: query.start, 90 followerActorId: serverActor.id
90 count: query.count,
91 sort: query.sort,
92 host: query.host,
93 uuids: query.uuids
94 }, 'filter:api.search.video-playlists.local.list.params') 91 }, 'filter:api.search.video-playlists.local.list.params')
95 92
96 const resultList = await Hooks.wrapPromiseFun( 93 const resultList = await Hooks.wrapPromiseFun(
diff --git a/server/controllers/api/search/search-videos.ts b/server/controllers/api/search/search-videos.ts
index a4153f3f8..4a6ce0de4 100644
--- a/server/controllers/api/search/search-videos.ts
+++ b/server/controllers/api/search/search-videos.ts
@@ -1,5 +1,6 @@
1import * as express from 'express' 1import * as express from 'express'
2import { sanitizeUrl } from '@server/helpers/core-utils' 2import { sanitizeUrl } from '@server/helpers/core-utils'
3import { pickSearchVideoQuery } from '@server/helpers/query'
3import { doJSONRequest } from '@server/helpers/requests' 4import { doJSONRequest } from '@server/helpers/requests'
4import { CONFIG } from '@server/initializers/config' 5import { CONFIG } from '@server/initializers/config'
5import { WEBSERVER } from '@server/initializers/constants' 6import { WEBSERVER } from '@server/initializers/constants'
@@ -7,7 +8,7 @@ import { getOrCreateAPVideo } from '@server/lib/activitypub/videos'
7import { Hooks } from '@server/lib/plugins/hooks' 8import { Hooks } from '@server/lib/plugins/hooks'
8import { buildMutedForSearchIndex, isSearchIndexSearch, isURISearch } from '@server/lib/search' 9import { buildMutedForSearchIndex, isSearchIndexSearch, isURISearch } from '@server/lib/search'
9import { HttpStatusCode, ResultList, Video } from '@shared/models' 10import { HttpStatusCode, ResultList, Video } from '@shared/models'
10import { VideosSearchQuery } from '../../../../shared/models/search' 11import { VideosSearchQueryAfterSanitize } from '../../../../shared/models/search'
11import { buildNSFWFilter, isUserAbleToSearchRemoteURI } from '../../../helpers/express-utils' 12import { buildNSFWFilter, isUserAbleToSearchRemoteURI } from '../../../helpers/express-utils'
12import { logger } from '../../../helpers/logger' 13import { logger } from '../../../helpers/logger'
13import { getFormattedObjects } from '../../../helpers/utils' 14import { getFormattedObjects } from '../../../helpers/utils'
@@ -46,7 +47,7 @@ export { searchVideosRouter }
46// --------------------------------------------------------------------------- 47// ---------------------------------------------------------------------------
47 48
48function searchVideos (req: express.Request, res: express.Response) { 49function searchVideos (req: express.Request, res: express.Response) {
49 const query: VideosSearchQuery = req.query 50 const query = pickSearchVideoQuery(req.query)
50 const search = query.search 51 const search = query.search
51 52
52 if (isURISearch(search)) { 53 if (isURISearch(search)) {
@@ -60,10 +61,10 @@ function searchVideos (req: express.Request, res: express.Response) {
60 return searchVideosDB(query, res) 61 return searchVideosDB(query, res)
61} 62}
62 63
63async function searchVideosIndex (query: VideosSearchQuery, res: express.Response) { 64async function searchVideosIndex (query: VideosSearchQueryAfterSanitize, res: express.Response) {
64 const result = await buildMutedForSearchIndex(res) 65 const result = await buildMutedForSearchIndex(res)
65 66
66 let body: VideosSearchQuery = Object.assign(query, result) 67 let body = { ...query, ...result }
67 68
68 // Use the default instance NSFW policy if not specified 69 // Use the default instance NSFW policy if not specified
69 if (!body.nsfw) { 70 if (!body.nsfw) {
@@ -97,13 +98,18 @@ async function searchVideosIndex (query: VideosSearchQuery, res: express.Respons
97 } 98 }
98} 99}
99 100
100async function searchVideosDB (query: VideosSearchQuery, res: express.Response) { 101async function searchVideosDB (query: VideosSearchQueryAfterSanitize, res: express.Response) {
101 const apiOptions = await Hooks.wrapObject(Object.assign(query, { 102 const apiOptions = await Hooks.wrapObject({
103 ...query,
104
102 includeLocalVideos: true, 105 includeLocalVideos: true,
103 nsfw: buildNSFWFilter(res, query.nsfw),
104 filter: query.filter, 106 filter: query.filter,
105 user: res.locals.oauth ? res.locals.oauth.token.User : undefined 107
106 }), 'filter:api.search.videos.local.list.params') 108 nsfw: buildNSFWFilter(res, query.nsfw),
109 user: res.locals.oauth
110 ? res.locals.oauth.token.User
111 : undefined
112 }, 'filter:api.search.videos.local.list.params')
107 113
108 const resultList = await Hooks.wrapPromiseFun( 114 const resultList = await Hooks.wrapPromiseFun(
109 VideoModel.searchAndPopulateAccountAndServer, 115 VideoModel.searchAndPopulateAccountAndServer,
diff --git a/server/controllers/api/users/my-subscriptions.ts b/server/controllers/api/users/my-subscriptions.ts
index 84f519926..26a715704 100644
--- a/server/controllers/api/users/my-subscriptions.ts
+++ b/server/controllers/api/users/my-subscriptions.ts
@@ -1,8 +1,8 @@
1import 'multer' 1import 'multer'
2import * as express from 'express' 2import * as express from 'express'
3import { pickCommonVideoQuery } from '@server/helpers/query'
3import { sendUndoFollow } from '@server/lib/activitypub/send' 4import { sendUndoFollow } from '@server/lib/activitypub/send'
4import { VideoChannelModel } from '@server/models/video/video-channel' 5import { VideoChannelModel } from '@server/models/video/video-channel'
5import { VideosCommonQuery } from '@shared/models'
6import { HttpStatusCode } from '../../../../shared/models/http/http-error-codes' 6import { HttpStatusCode } from '../../../../shared/models/http/http-error-codes'
7import { buildNSFWFilter, getCountVideos } from '../../../helpers/express-utils' 7import { buildNSFWFilter, getCountVideos } from '../../../helpers/express-utils'
8import { getFormattedObjects } from '../../../helpers/utils' 8import { getFormattedObjects } from '../../../helpers/utils'
@@ -170,20 +170,13 @@ async function getUserSubscriptions (req: express.Request, res: express.Response
170async function getUserSubscriptionVideos (req: express.Request, res: express.Response) { 170async function getUserSubscriptionVideos (req: express.Request, res: express.Response) {
171 const user = res.locals.oauth.token.User 171 const user = res.locals.oauth.token.User
172 const countVideos = getCountVideos(req) 172 const countVideos = getCountVideos(req)
173 const query = req.query as VideosCommonQuery 173 const query = pickCommonVideoQuery(req.query)
174 174
175 const resultList = await VideoModel.listForApi({ 175 const resultList = await VideoModel.listForApi({
176 start: query.start, 176 ...query,
177 count: query.count, 177
178 sort: query.sort,
179 includeLocalVideos: false, 178 includeLocalVideos: false,
180 categoryOneOf: query.categoryOneOf,
181 licenceOneOf: query.licenceOneOf,
182 languageOneOf: query.languageOneOf,
183 tagsOneOf: query.tagsOneOf,
184 tagsAllOf: query.tagsAllOf,
185 nsfw: buildNSFWFilter(res, query.nsfw), 179 nsfw: buildNSFWFilter(res, query.nsfw),
186 filter: query.filter,
187 withFiles: false, 180 withFiles: false,
188 followerActorId: user.Account.Actor.id, 181 followerActorId: user.Account.Actor.id,
189 user, 182 user,
diff --git a/server/controllers/api/video-channel.ts b/server/controllers/api/video-channel.ts
index 784f97b1e..7bdb33737 100644
--- a/server/controllers/api/video-channel.ts
+++ b/server/controllers/api/video-channel.ts
@@ -1,8 +1,9 @@
1import * as express from 'express' 1import * as express from 'express'
2import { pickCommonVideoQuery } from '@server/helpers/query'
2import { Hooks } from '@server/lib/plugins/hooks' 3import { Hooks } from '@server/lib/plugins/hooks'
3import { getServerActor } from '@server/models/application/application' 4import { getServerActor } from '@server/models/application/application'
4import { MChannelBannerAccountDefault } from '@server/types/models' 5import { MChannelBannerAccountDefault } from '@server/types/models'
5import { ActorImageType, VideoChannelCreate, VideoChannelUpdate, VideosCommonQuery } from '../../../shared' 6import { ActorImageType, VideoChannelCreate, VideoChannelUpdate } from '../../../shared'
6import { HttpStatusCode } from '../../../shared/models/http/http-error-codes' 7import { HttpStatusCode } from '../../../shared/models/http/http-error-codes'
7import { auditLoggerFactory, getAuditIdFromRes, VideoChannelAuditView } from '../../helpers/audit-logger' 8import { auditLoggerFactory, getAuditIdFromRes, VideoChannelAuditView } from '../../helpers/audit-logger'
8import { resetSequelizeInstance } from '../../helpers/database-utils' 9import { resetSequelizeInstance } from '../../helpers/database-utils'
@@ -309,20 +310,13 @@ async function listVideoChannelVideos (req: express.Request, res: express.Respon
309 const videoChannelInstance = res.locals.videoChannel 310 const videoChannelInstance = res.locals.videoChannel
310 const followerActorId = isUserAbleToSearchRemoteURI(res) ? null : undefined 311 const followerActorId = isUserAbleToSearchRemoteURI(res) ? null : undefined
311 const countVideos = getCountVideos(req) 312 const countVideos = getCountVideos(req)
312 const query = req.query as VideosCommonQuery 313 const query = pickCommonVideoQuery(req.query)
313 314
314 const apiOptions = await Hooks.wrapObject({ 315 const apiOptions = await Hooks.wrapObject({
316 ...query,
317
315 followerActorId, 318 followerActorId,
316 start: query.start,
317 count: query.count,
318 sort: query.sort,
319 includeLocalVideos: true, 319 includeLocalVideos: true,
320 categoryOneOf: query.categoryOneOf,
321 licenceOneOf: query.licenceOneOf,
322 languageOneOf: query.languageOneOf,
323 tagsOneOf: query.tagsOneOf,
324 tagsAllOf: query.tagsAllOf,
325 filter: query.filter,
326 nsfw: buildNSFWFilter(res, query.nsfw), 320 nsfw: buildNSFWFilter(res, query.nsfw),
327 withFiles: false, 321 withFiles: false,
328 videoChannelId: videoChannelInstance.id, 322 videoChannelId: videoChannelInstance.id,
diff --git a/server/controllers/api/videos/index.ts b/server/controllers/api/videos/index.ts
index 5a2ff81dc..49490f79b 100644
--- a/server/controllers/api/videos/index.ts
+++ b/server/controllers/api/videos/index.ts
@@ -1,11 +1,11 @@
1import * as express from 'express' 1import * as express from 'express'
2import toInt from 'validator/lib/toInt' 2import toInt from 'validator/lib/toInt'
3import { pickCommonVideoQuery } from '@server/helpers/query'
3import { doJSONRequest } from '@server/helpers/requests' 4import { doJSONRequest } from '@server/helpers/requests'
4import { LiveManager } from '@server/lib/live' 5import { LiveManager } from '@server/lib/live'
5import { openapiOperationDoc } from '@server/middlewares/doc' 6import { openapiOperationDoc } from '@server/middlewares/doc'
6import { getServerActor } from '@server/models/application/application' 7import { getServerActor } from '@server/models/application/application'
7import { MVideoAccountLight } from '@server/types/models' 8import { MVideoAccountLight } from '@server/types/models'
8import { VideosCommonQuery } from '../../../../shared'
9import { HttpStatusCode } from '../../../../shared/models' 9import { HttpStatusCode } from '../../../../shared/models'
10import { auditLoggerFactory, getAuditIdFromRes, VideoAuditView } from '../../../helpers/audit-logger' 10import { auditLoggerFactory, getAuditIdFromRes, VideoAuditView } from '../../../helpers/audit-logger'
11import { buildNSFWFilter, getCountVideos } from '../../../helpers/express-utils' 11import { buildNSFWFilter, getCountVideos } from '../../../helpers/express-utils'
@@ -211,22 +211,14 @@ async function getVideoFileMetadata (req: express.Request, res: express.Response
211} 211}
212 212
213async function listVideos (req: express.Request, res: express.Response) { 213async function listVideos (req: express.Request, res: express.Response) {
214 const query = req.query as VideosCommonQuery 214 const query = pickCommonVideoQuery(req.query)
215 const countVideos = getCountVideos(req) 215 const countVideos = getCountVideos(req)
216 216
217 const apiOptions = await Hooks.wrapObject({ 217 const apiOptions = await Hooks.wrapObject({
218 start: query.start, 218 ...query,
219 count: query.count, 219
220 sort: query.sort,
221 includeLocalVideos: true, 220 includeLocalVideos: true,
222 categoryOneOf: query.categoryOneOf,
223 licenceOneOf: query.licenceOneOf,
224 languageOneOf: query.languageOneOf,
225 tagsOneOf: query.tagsOneOf,
226 tagsAllOf: query.tagsAllOf,
227 nsfw: buildNSFWFilter(res, query.nsfw), 221 nsfw: buildNSFWFilter(res, query.nsfw),
228 isLive: query.isLive,
229 filter: query.filter,
230 withFiles: false, 222 withFiles: false,
231 user: res.locals.oauth ? res.locals.oauth.token.User : undefined, 223 user: res.locals.oauth ? res.locals.oauth.token.User : undefined,
232 countVideos 224 countVideos
diff --git a/server/helpers/query.ts b/server/helpers/query.ts
new file mode 100644
index 000000000..e711b15f2
--- /dev/null
+++ b/server/helpers/query.ts
@@ -0,0 +1,74 @@
1import { pick } from '@shared/core-utils'
2import {
3 VideoChannelsSearchQueryAfterSanitize,
4 VideoPlaylistsSearchQueryAfterSanitize,
5 VideosCommonQueryAfterSanitize,
6 VideosSearchQueryAfterSanitize
7} from '@shared/models'
8
9function pickCommonVideoQuery (query: VideosCommonQueryAfterSanitize) {
10 return pick(query, [
11 'start',
12 'count',
13 'sort',
14 'nsfw',
15 'isLive',
16 'categoryOneOf',
17 'licenceOneOf',
18 'languageOneOf',
19 'tagsOneOf',
20 'tagsAllOf',
21 'filter',
22 'skipCount'
23 ])
24}
25
26function pickSearchVideoQuery (query: VideosSearchQueryAfterSanitize) {
27 return {
28 ...pickCommonVideoQuery(query),
29
30 ...pick(query, [
31 'searchTarget',
32 'search',
33 'host',
34 'startDate',
35 'endDate',
36 'originallyPublishedStartDate',
37 'originallyPublishedEndDate',
38 'durationMin',
39 'durationMax',
40 'uuids'
41 ])
42 }
43}
44
45function pickSearchChannelQuery (query: VideoChannelsSearchQueryAfterSanitize) {
46 return pick(query, [
47 'searchTarget',
48 'search',
49 'start',
50 'count',
51 'sort',
52 'host',
53 'handles'
54 ])
55}
56
57function pickSearchPlaylistQuery (query: VideoPlaylistsSearchQueryAfterSanitize) {
58 return pick(query, [
59 'searchTarget',
60 'search',
61 'start',
62 'count',
63 'sort',
64 'host',
65 'uuids'
66 ])
67}
68
69export {
70 pickCommonVideoQuery,
71 pickSearchVideoQuery,
72 pickSearchPlaylistQuery,
73 pickSearchChannelQuery
74}
diff --git a/server/models/video/video.ts b/server/models/video/video.ts
index fe92ead04..d5efe2eac 100644
--- a/server/models/video/video.ts
+++ b/server/models/video/video.ts
@@ -1070,7 +1070,8 @@ export class VideoModel extends Model<Partial<AttributesOnly<VideoModel>>> {
1070 const trendingDays = options.sort.endsWith('trending') 1070 const trendingDays = options.sort.endsWith('trending')
1071 ? CONFIG.TRENDING.VIDEOS.INTERVAL_DAYS 1071 ? CONFIG.TRENDING.VIDEOS.INTERVAL_DAYS
1072 : undefined 1072 : undefined
1073 let trendingAlgorithm 1073
1074 let trendingAlgorithm: string
1074 if (options.sort.endsWith('hot')) trendingAlgorithm = 'hot' 1075 if (options.sort.endsWith('hot')) trendingAlgorithm = 'hot'
1075 if (options.sort.endsWith('best')) trendingAlgorithm = 'best' 1076 if (options.sort.endsWith('best')) trendingAlgorithm = 'best'
1076 1077
diff --git a/server/tests/api/search/search-index.ts b/server/tests/api/search/search-index.ts
index cc841a6ff..d1aa5ef41 100644
--- a/server/tests/api/search/search-index.ts
+++ b/server/tests/api/search/search-index.ts
@@ -211,6 +211,39 @@ describe('Test videos search', function () {
211 const search = { ...baseSearch, host: 'framatube.org' } 211 const search = { ...baseSearch, host: 'framatube.org' }
212 await check(search, true) 212 await check(search, true)
213 } 213 }
214
215 {
216 const goodUUID = '9c9de5e8-0a1e-484a-b099-e80766180a6d'
217 const goodShortUUID = 'kkGMgK9ZtnKfYAgnEtQxbv'
218 const badUUID = 'c29c5b77-4a04-493d-96a9-2e9267e308f0'
219 const badShortUUID = 'rP5RgUeX9XwTSrspCdkDej'
220
221 {
222 const uuidsMatrix = [
223 [ goodUUID ],
224 [ goodUUID, badShortUUID ],
225 [ badShortUUID, goodShortUUID ],
226 [ goodUUID, goodShortUUID ]
227 ]
228
229 for (const uuids of uuidsMatrix) {
230 const search = { ...baseSearch, uuids }
231 await check(search, true)
232 }
233 }
234
235 {
236 const uuidsMatrix = [
237 [ badUUID ],
238 [ badShortUUID ]
239 ]
240
241 for (const uuids of uuidsMatrix) {
242 const search = { ...baseSearch, uuids }
243 await check(search, false)
244 }
245 }
246 }
214 }) 247 })
215 248
216 it('Should have a correct pagination', async function () { 249 it('Should have a correct pagination', async function () {
@@ -315,57 +348,92 @@ describe('Test videos search', function () {
315 348
316 describe('Playlists search', async function () { 349 describe('Playlists search', async function () {
317 350
318 it('Should make a simple search and not have results', async function () { 351 async function check (search: VideoPlaylistsSearchQuery, exists = true) {
319 const body = await command.searchPlaylists({ search: 'a'.repeat(500) }) 352 const body = await command.advancedPlaylistSearch({ search })
320 353
321 expect(body.total).to.equal(0) 354 if (exists === false) {
322 expect(body.data).to.have.lengthOf(0) 355 expect(body.total).to.equal(0)
323 }) 356 expect(body.data).to.have.lengthOf(0)
357 return
358 }
324 359
325 it('Should make a search and have results', async function () { 360 expect(body.total).to.be.greaterThan(0)
361 expect(body.data).to.have.length.greaterThan(0)
326 362
327 async function check (search: VideoPlaylistsSearchQuery, exists = true) { 363 const videoPlaylist = body.data[0]
328 const body = await command.advancedPlaylistSearch({ search })
329 364
330 if (exists === false) { 365 expect(videoPlaylist.url).to.equal('https://peertube2.cpy.re/videos/watch/playlist/73804a40-da9a-40c2-b1eb-2c6d9eec8f0a')
331 expect(body.total).to.equal(0) 366 expect(videoPlaylist.thumbnailUrl).to.exist
332 expect(body.data).to.have.lengthOf(0) 367 expect(videoPlaylist.embedUrl).to.equal('https://peertube2.cpy.re/video-playlists/embed/73804a40-da9a-40c2-b1eb-2c6d9eec8f0a')
333 return
334 }
335 368
336 expect(body.total).to.be.greaterThan(0) 369 expect(videoPlaylist.type.id).to.equal(VideoPlaylistType.REGULAR)
337 expect(body.data).to.have.length.greaterThan(0) 370 expect(videoPlaylist.privacy.id).to.equal(VideoPlaylistPrivacy.PUBLIC)
338 371 expect(videoPlaylist.videosLength).to.exist
339 const videoPlaylist = body.data[0]
340 372
341 expect(videoPlaylist.url).to.equal('https://peertube2.cpy.re/videos/watch/playlist/73804a40-da9a-40c2-b1eb-2c6d9eec8f0a') 373 expect(videoPlaylist.createdAt).to.exist
342 expect(videoPlaylist.thumbnailUrl).to.exist 374 expect(videoPlaylist.updatedAt).to.exist
343 expect(videoPlaylist.embedUrl).to.equal('https://peertube2.cpy.re/video-playlists/embed/73804a40-da9a-40c2-b1eb-2c6d9eec8f0a')
344 375
345 expect(videoPlaylist.type.id).to.equal(VideoPlaylistType.REGULAR) 376 expect(videoPlaylist.uuid).to.equal('73804a40-da9a-40c2-b1eb-2c6d9eec8f0a')
346 expect(videoPlaylist.privacy.id).to.equal(VideoPlaylistPrivacy.PUBLIC) 377 expect(videoPlaylist.displayName).to.exist
347 expect(videoPlaylist.videosLength).to.exist
348 378
349 expect(videoPlaylist.createdAt).to.exist 379 expect(videoPlaylist.ownerAccount.url).to.equal('https://peertube2.cpy.re/accounts/chocobozzz')
350 expect(videoPlaylist.updatedAt).to.exist 380 expect(videoPlaylist.ownerAccount.name).to.equal('chocobozzz')
381 expect(videoPlaylist.ownerAccount.host).to.equal('peertube2.cpy.re')
382 expect(videoPlaylist.ownerAccount.avatar).to.exist
351 383
352 expect(videoPlaylist.uuid).to.equal('73804a40-da9a-40c2-b1eb-2c6d9eec8f0a') 384 expect(videoPlaylist.videoChannel.url).to.equal('https://peertube2.cpy.re/video-channels/chocobozzz_channel')
353 expect(videoPlaylist.displayName).to.exist 385 expect(videoPlaylist.videoChannel.name).to.equal('chocobozzz_channel')
386 expect(videoPlaylist.videoChannel.host).to.equal('peertube2.cpy.re')
387 expect(videoPlaylist.videoChannel.avatar).to.exist
388 }
354 389
355 expect(videoPlaylist.ownerAccount.url).to.equal('https://peertube2.cpy.re/accounts/chocobozzz') 390 it('Should make a simple search and not have results', async function () {
356 expect(videoPlaylist.ownerAccount.name).to.equal('chocobozzz') 391 const body = await command.searchPlaylists({ search: 'a'.repeat(500) })
357 expect(videoPlaylist.ownerAccount.host).to.equal('peertube2.cpy.re')
358 expect(videoPlaylist.ownerAccount.avatar).to.exist
359 392
360 expect(videoPlaylist.videoChannel.url).to.equal('https://peertube2.cpy.re/video-channels/chocobozzz_channel') 393 expect(body.total).to.equal(0)
361 expect(videoPlaylist.videoChannel.name).to.equal('chocobozzz_channel') 394 expect(body.data).to.have.lengthOf(0)
362 expect(videoPlaylist.videoChannel.host).to.equal('peertube2.cpy.re') 395 })
363 expect(videoPlaylist.videoChannel.avatar).to.exist
364 }
365 396
397 it('Should make a search and have results', async function () {
366 await check({ search: 'E2E playlist', sort: '-match' }, true) 398 await check({ search: 'E2E playlist', sort: '-match' }, true)
399 })
400
401 it('Should make host search and have appropriate results', async function () {
367 await check({ search: 'E2E playlist', host: 'example.com' }, false) 402 await check({ search: 'E2E playlist', host: 'example.com' }, false)
368 await check({ search: 'E2E playlist', host: 'peertube2.cpy.re' }, true) 403 await check({ search: 'E2E playlist', host: 'peertube2.cpy.re', sort: '-match' }, true)
404 })
405
406 it('Should make a search by uuids and have appropriate results', async function () {
407 const goodUUID = '73804a40-da9a-40c2-b1eb-2c6d9eec8f0a'
408 const goodShortUUID = 'fgei1ws1oa6FCaJ2qZPG29'
409 const badUUID = 'c29c5b77-4a04-493d-96a9-2e9267e308f0'
410 const badShortUUID = 'rP5RgUeX9XwTSrspCdkDej'
411
412 {
413 const uuidsMatrix = [
414 [ goodUUID ],
415 [ goodUUID, badShortUUID ],
416 [ badShortUUID, goodShortUUID ],
417 [ goodUUID, goodShortUUID ]
418 ]
419
420 for (const uuids of uuidsMatrix) {
421 const search = { search: 'E2E playlist', sort: '-match', uuids }
422 await check(search, true)
423 }
424 }
425
426 {
427 const uuidsMatrix = [
428 [ badUUID ],
429 [ badShortUUID ]
430 ]
431
432 for (const uuids of uuidsMatrix) {
433 const search = { search: 'E2E playlist', sort: '-match', uuids }
434 await check(search, false)
435 }
436 }
369 }) 437 })
370 438
371 it('Should have a correct pagination', async function () { 439 it('Should have a correct pagination', async function () {
diff --git a/shared/core-utils/utils/object.ts b/shared/core-utils/utils/object.ts
index 7b2bb81d0..9a8a98f9b 100644
--- a/shared/core-utils/utils/object.ts
+++ b/shared/core-utils/utils/object.ts
@@ -1,5 +1,5 @@
1function pick <T extends object> (object: T, keys: (keyof T)[]) { 1function pick <O extends object, K extends keyof O> (object: O, keys: K[]): Pick<O, K> {
2 const result: Partial<T> = {} 2 const result: any = {}
3 3
4 for (const key of keys) { 4 for (const key of keys) {
5 if (Object.prototype.hasOwnProperty.call(object, key)) { 5 if (Object.prototype.hasOwnProperty.call(object, key)) {
diff --git a/shared/models/search/video-channels-search-query.model.ts b/shared/models/search/video-channels-search-query.model.ts
index 77cea4a59..b68a1e80b 100644
--- a/shared/models/search/video-channels-search-query.model.ts
+++ b/shared/models/search/video-channels-search-query.model.ts
@@ -10,3 +10,9 @@ export interface VideoChannelsSearchQuery extends SearchTargetQuery {
10 host?: string 10 host?: string
11 handles?: string[] 11 handles?: string[]
12} 12}
13
14export interface VideoChannelsSearchQueryAfterSanitize extends VideoChannelsSearchQuery {
15 start: number
16 count: number
17 sort: string
18}
diff --git a/shared/models/search/video-playlists-search-query.model.ts b/shared/models/search/video-playlists-search-query.model.ts
index 55393c92a..d9027eb5b 100644
--- a/shared/models/search/video-playlists-search-query.model.ts
+++ b/shared/models/search/video-playlists-search-query.model.ts
@@ -8,5 +8,13 @@ export interface VideoPlaylistsSearchQuery extends SearchTargetQuery {
8 sort?: string 8 sort?: string
9 9
10 host?: string 10 host?: string
11
12 // UUIDs or short UUIDs
11 uuids?: string[] 13 uuids?: string[]
12} 14}
15
16export interface VideoPlaylistsSearchQueryAfterSanitize extends VideoPlaylistsSearchQuery {
17 start: number
18 count: number
19 sort: string
20}
diff --git a/shared/models/search/videos-common-query.model.ts b/shared/models/search/videos-common-query.model.ts
index 179266338..2f2e9a934 100644
--- a/shared/models/search/videos-common-query.model.ts
+++ b/shared/models/search/videos-common-query.model.ts
@@ -25,6 +25,12 @@ export interface VideosCommonQuery {
25 skipCount?: boolean 25 skipCount?: boolean
26} 26}
27 27
28export interface VideosCommonQueryAfterSanitize extends VideosCommonQuery {
29 start: number
30 count: number
31 sort: string
32}
33
28export interface VideosWithSearchCommonQuery extends VideosCommonQuery { 34export interface VideosWithSearchCommonQuery extends VideosCommonQuery {
29 search?: string 35 search?: string
30} 36}
diff --git a/shared/models/search/videos-search-query.model.ts b/shared/models/search/videos-search-query.model.ts
index 736d89577..a5436879d 100644
--- a/shared/models/search/videos-search-query.model.ts
+++ b/shared/models/search/videos-search-query.model.ts
@@ -15,6 +15,12 @@ export interface VideosSearchQuery extends SearchTargetQuery, VideosCommonQuery
15 durationMin?: number // seconds 15 durationMin?: number // seconds
16 durationMax?: number // seconds 16 durationMax?: number // seconds
17 17
18 // UUIDs or short 18 // UUIDs or short UUIDs
19 uuids?: string[] 19 uuids?: string[]
20} 20}
21
22export interface VideosSearchQueryAfterSanitize extends VideosSearchQuery {
23 start: number
24 count: number
25 sort: string
26}