diff options
-rw-r--r-- | server/controllers/api/accounts.ts | 20 | ||||
-rw-r--r-- | server/controllers/api/search/search-video-channels.ts | 19 | ||||
-rw-r--r-- | server/controllers/api/search/search-video-playlists.ts | 19 | ||||
-rw-r--r-- | server/controllers/api/search/search-videos.ts | 24 | ||||
-rw-r--r-- | server/controllers/api/users/my-subscriptions.ts | 15 | ||||
-rw-r--r-- | server/controllers/api/video-channel.ts | 16 | ||||
-rw-r--r-- | server/controllers/api/videos/index.ts | 16 | ||||
-rw-r--r-- | server/helpers/query.ts | 74 | ||||
-rw-r--r-- | server/models/video/video.ts | 3 | ||||
-rw-r--r-- | server/tests/api/search/search-index.ts | 142 | ||||
-rw-r--r-- | shared/core-utils/utils/object.ts | 4 | ||||
-rw-r--r-- | shared/models/search/video-channels-search-query.model.ts | 6 | ||||
-rw-r--r-- | shared/models/search/video-playlists-search-query.model.ts | 8 | ||||
-rw-r--r-- | shared/models/search/videos-common-query.model.ts | 6 | ||||
-rw-r--r-- | shared/models/search/videos-search-query.model.ts | 8 |
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 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { pickCommonVideoQuery } from '@server/helpers/query' | ||
2 | import { getServerActor } from '@server/models/application/application' | 3 | import { getServerActor } from '@server/models/application/application' |
3 | import { VideosWithSearchCommonQuery } from '@shared/models' | ||
4 | import { buildNSFWFilter, getCountVideos, isUserAbleToSearchRemoteURI } from '../../helpers/express-utils' | 4 | import { buildNSFWFilter, getCountVideos, isUserAbleToSearchRemoteURI } from '../../helpers/express-utils' |
5 | import { getFormattedObjects } from '../../helpers/utils' | 5 | import { getFormattedObjects } from '../../helpers/utils' |
6 | import { JobQueue } from '../../lib/job-queue' | 6 | import { 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 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { sanitizeUrl } from '@server/helpers/core-utils' | 2 | import { sanitizeUrl } from '@server/helpers/core-utils' |
3 | import { pickSearchChannelQuery } from '@server/helpers/query' | ||
3 | import { doJSONRequest } from '@server/helpers/requests' | 4 | import { doJSONRequest } from '@server/helpers/requests' |
4 | import { CONFIG } from '@server/initializers/config' | 5 | import { CONFIG } from '@server/initializers/config' |
5 | import { WEBSERVER } from '@server/initializers/constants' | 6 | import { WEBSERVER } from '@server/initializers/constants' |
@@ -7,7 +8,7 @@ import { Hooks } from '@server/lib/plugins/hooks' | |||
7 | import { buildMutedForSearchIndex, isSearchIndexSearch, isURISearch } from '@server/lib/search' | 8 | import { buildMutedForSearchIndex, isSearchIndexSearch, isURISearch } from '@server/lib/search' |
8 | import { getServerActor } from '@server/models/application/application' | 9 | import { getServerActor } from '@server/models/application/application' |
9 | import { HttpStatusCode, ResultList, VideoChannel } from '@shared/models' | 10 | import { HttpStatusCode, ResultList, VideoChannel } from '@shared/models' |
10 | import { VideoChannelsSearchQuery } from '../../../../shared/models/search' | 11 | import { VideoChannelsSearchQueryAfterSanitize } from '../../../../shared/models/search' |
11 | import { isUserAbleToSearchRemoteURI } from '../../../helpers/express-utils' | 12 | import { isUserAbleToSearchRemoteURI } from '../../../helpers/express-utils' |
12 | import { logger } from '../../../helpers/logger' | 13 | import { logger } from '../../../helpers/logger' |
13 | import { getFormattedObjects } from '../../../helpers/utils' | 14 | import { getFormattedObjects } from '../../../helpers/utils' |
@@ -45,7 +46,7 @@ export { searchChannelsRouter } | |||
45 | // --------------------------------------------------------------------------- | 46 | // --------------------------------------------------------------------------- |
46 | 47 | ||
47 | function searchVideoChannels (req: express.Request, res: express.Response) { | 48 | function 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 | ||
69 | async function searchVideoChannelsIndex (query: VideoChannelsSearchQuery, res: express.Response) { | 70 | async 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 | ||
93 | async function searchVideoChannelsDB (query: VideoChannelsSearchQuery, res: express.Response) { | 94 | async 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' | |||
2 | import { sanitizeUrl } from '@server/helpers/core-utils' | 2 | import { sanitizeUrl } from '@server/helpers/core-utils' |
3 | import { isUserAbleToSearchRemoteURI } from '@server/helpers/express-utils' | 3 | import { isUserAbleToSearchRemoteURI } from '@server/helpers/express-utils' |
4 | import { logger } from '@server/helpers/logger' | 4 | import { logger } from '@server/helpers/logger' |
5 | import { pickSearchPlaylistQuery } from '@server/helpers/query' | ||
5 | import { doJSONRequest } from '@server/helpers/requests' | 6 | import { doJSONRequest } from '@server/helpers/requests' |
6 | import { getFormattedObjects } from '@server/helpers/utils' | 7 | import { getFormattedObjects } from '@server/helpers/utils' |
7 | import { CONFIG } from '@server/initializers/config' | 8 | import { CONFIG } from '@server/initializers/config' |
@@ -12,7 +13,7 @@ import { buildMutedForSearchIndex, isSearchIndexSearch, isURISearch } from '@ser | |||
12 | import { getServerActor } from '@server/models/application/application' | 13 | import { getServerActor } from '@server/models/application/application' |
13 | import { VideoPlaylistModel } from '@server/models/video/video-playlist' | 14 | import { VideoPlaylistModel } from '@server/models/video/video-playlist' |
14 | import { MVideoPlaylistFullSummary } from '@server/types/models' | 15 | import { MVideoPlaylistFullSummary } from '@server/types/models' |
15 | import { HttpStatusCode, ResultList, VideoPlaylist, VideoPlaylistsSearchQuery } from '@shared/models' | 16 | import { HttpStatusCode, ResultList, VideoPlaylist, VideoPlaylistsSearchQueryAfterSanitize } from '@shared/models' |
16 | import { | 17 | import { |
17 | asyncMiddleware, | 18 | asyncMiddleware, |
18 | openapiOperationDoc, | 19 | openapiOperationDoc, |
@@ -44,7 +45,7 @@ export { searchPlaylistsRouter } | |||
44 | // --------------------------------------------------------------------------- | 45 | // --------------------------------------------------------------------------- |
45 | 46 | ||
46 | function searchVideoPlaylists (req: express.Request, res: express.Response) { | 47 | function 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 | ||
59 | async function searchVideoPlaylistsIndex (query: VideoPlaylistsSearchQuery, res: express.Response) { | 60 | async 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 | ||
83 | async function searchVideoPlaylistsDB (query: VideoPlaylistsSearchQuery, res: express.Response) { | 84 | async 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 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { sanitizeUrl } from '@server/helpers/core-utils' | 2 | import { sanitizeUrl } from '@server/helpers/core-utils' |
3 | import { pickSearchVideoQuery } from '@server/helpers/query' | ||
3 | import { doJSONRequest } from '@server/helpers/requests' | 4 | import { doJSONRequest } from '@server/helpers/requests' |
4 | import { CONFIG } from '@server/initializers/config' | 5 | import { CONFIG } from '@server/initializers/config' |
5 | import { WEBSERVER } from '@server/initializers/constants' | 6 | import { WEBSERVER } from '@server/initializers/constants' |
@@ -7,7 +8,7 @@ import { getOrCreateAPVideo } from '@server/lib/activitypub/videos' | |||
7 | import { Hooks } from '@server/lib/plugins/hooks' | 8 | import { Hooks } from '@server/lib/plugins/hooks' |
8 | import { buildMutedForSearchIndex, isSearchIndexSearch, isURISearch } from '@server/lib/search' | 9 | import { buildMutedForSearchIndex, isSearchIndexSearch, isURISearch } from '@server/lib/search' |
9 | import { HttpStatusCode, ResultList, Video } from '@shared/models' | 10 | import { HttpStatusCode, ResultList, Video } from '@shared/models' |
10 | import { VideosSearchQuery } from '../../../../shared/models/search' | 11 | import { VideosSearchQueryAfterSanitize } from '../../../../shared/models/search' |
11 | import { buildNSFWFilter, isUserAbleToSearchRemoteURI } from '../../../helpers/express-utils' | 12 | import { buildNSFWFilter, isUserAbleToSearchRemoteURI } from '../../../helpers/express-utils' |
12 | import { logger } from '../../../helpers/logger' | 13 | import { logger } from '../../../helpers/logger' |
13 | import { getFormattedObjects } from '../../../helpers/utils' | 14 | import { getFormattedObjects } from '../../../helpers/utils' |
@@ -46,7 +47,7 @@ export { searchVideosRouter } | |||
46 | // --------------------------------------------------------------------------- | 47 | // --------------------------------------------------------------------------- |
47 | 48 | ||
48 | function searchVideos (req: express.Request, res: express.Response) { | 49 | function 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 | ||
63 | async function searchVideosIndex (query: VideosSearchQuery, res: express.Response) { | 64 | async 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 | ||
100 | async function searchVideosDB (query: VideosSearchQuery, res: express.Response) { | 101 | async 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 @@ | |||
1 | import 'multer' | 1 | import 'multer' |
2 | import * as express from 'express' | 2 | import * as express from 'express' |
3 | import { pickCommonVideoQuery } from '@server/helpers/query' | ||
3 | import { sendUndoFollow } from '@server/lib/activitypub/send' | 4 | import { sendUndoFollow } from '@server/lib/activitypub/send' |
4 | import { VideoChannelModel } from '@server/models/video/video-channel' | 5 | import { VideoChannelModel } from '@server/models/video/video-channel' |
5 | import { VideosCommonQuery } from '@shared/models' | ||
6 | import { HttpStatusCode } from '../../../../shared/models/http/http-error-codes' | 6 | import { HttpStatusCode } from '../../../../shared/models/http/http-error-codes' |
7 | import { buildNSFWFilter, getCountVideos } from '../../../helpers/express-utils' | 7 | import { buildNSFWFilter, getCountVideos } from '../../../helpers/express-utils' |
8 | import { getFormattedObjects } from '../../../helpers/utils' | 8 | import { getFormattedObjects } from '../../../helpers/utils' |
@@ -170,20 +170,13 @@ async function getUserSubscriptions (req: express.Request, res: express.Response | |||
170 | async function getUserSubscriptionVideos (req: express.Request, res: express.Response) { | 170 | async 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 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { pickCommonVideoQuery } from '@server/helpers/query' | ||
2 | import { Hooks } from '@server/lib/plugins/hooks' | 3 | import { Hooks } from '@server/lib/plugins/hooks' |
3 | import { getServerActor } from '@server/models/application/application' | 4 | import { getServerActor } from '@server/models/application/application' |
4 | import { MChannelBannerAccountDefault } from '@server/types/models' | 5 | import { MChannelBannerAccountDefault } from '@server/types/models' |
5 | import { ActorImageType, VideoChannelCreate, VideoChannelUpdate, VideosCommonQuery } from '../../../shared' | 6 | import { ActorImageType, VideoChannelCreate, VideoChannelUpdate } from '../../../shared' |
6 | import { HttpStatusCode } from '../../../shared/models/http/http-error-codes' | 7 | import { HttpStatusCode } from '../../../shared/models/http/http-error-codes' |
7 | import { auditLoggerFactory, getAuditIdFromRes, VideoChannelAuditView } from '../../helpers/audit-logger' | 8 | import { auditLoggerFactory, getAuditIdFromRes, VideoChannelAuditView } from '../../helpers/audit-logger' |
8 | import { resetSequelizeInstance } from '../../helpers/database-utils' | 9 | import { 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 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import toInt from 'validator/lib/toInt' | 2 | import toInt from 'validator/lib/toInt' |
3 | import { pickCommonVideoQuery } from '@server/helpers/query' | ||
3 | import { doJSONRequest } from '@server/helpers/requests' | 4 | import { doJSONRequest } from '@server/helpers/requests' |
4 | import { LiveManager } from '@server/lib/live' | 5 | import { LiveManager } from '@server/lib/live' |
5 | import { openapiOperationDoc } from '@server/middlewares/doc' | 6 | import { openapiOperationDoc } from '@server/middlewares/doc' |
6 | import { getServerActor } from '@server/models/application/application' | 7 | import { getServerActor } from '@server/models/application/application' |
7 | import { MVideoAccountLight } from '@server/types/models' | 8 | import { MVideoAccountLight } from '@server/types/models' |
8 | import { VideosCommonQuery } from '../../../../shared' | ||
9 | import { HttpStatusCode } from '../../../../shared/models' | 9 | import { HttpStatusCode } from '../../../../shared/models' |
10 | import { auditLoggerFactory, getAuditIdFromRes, VideoAuditView } from '../../../helpers/audit-logger' | 10 | import { auditLoggerFactory, getAuditIdFromRes, VideoAuditView } from '../../../helpers/audit-logger' |
11 | import { buildNSFWFilter, getCountVideos } from '../../../helpers/express-utils' | 11 | import { buildNSFWFilter, getCountVideos } from '../../../helpers/express-utils' |
@@ -211,22 +211,14 @@ async function getVideoFileMetadata (req: express.Request, res: express.Response | |||
211 | } | 211 | } |
212 | 212 | ||
213 | async function listVideos (req: express.Request, res: express.Response) { | 213 | async 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 @@ | |||
1 | import { pick } from '@shared/core-utils' | ||
2 | import { | ||
3 | VideoChannelsSearchQueryAfterSanitize, | ||
4 | VideoPlaylistsSearchQueryAfterSanitize, | ||
5 | VideosCommonQueryAfterSanitize, | ||
6 | VideosSearchQueryAfterSanitize | ||
7 | } from '@shared/models' | ||
8 | |||
9 | function 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 | |||
26 | function 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 | |||
45 | function pickSearchChannelQuery (query: VideoChannelsSearchQueryAfterSanitize) { | ||
46 | return pick(query, [ | ||
47 | 'searchTarget', | ||
48 | 'search', | ||
49 | 'start', | ||
50 | 'count', | ||
51 | 'sort', | ||
52 | 'host', | ||
53 | 'handles' | ||
54 | ]) | ||
55 | } | ||
56 | |||
57 | function pickSearchPlaylistQuery (query: VideoPlaylistsSearchQueryAfterSanitize) { | ||
58 | return pick(query, [ | ||
59 | 'searchTarget', | ||
60 | 'search', | ||
61 | 'start', | ||
62 | 'count', | ||
63 | 'sort', | ||
64 | 'host', | ||
65 | 'uuids' | ||
66 | ]) | ||
67 | } | ||
68 | |||
69 | export { | ||
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 @@ | |||
1 | function pick <T extends object> (object: T, keys: (keyof T)[]) { | 1 | function 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 | |||
14 | export 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 | |||
16 | export 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 | ||
28 | export interface VideosCommonQueryAfterSanitize extends VideosCommonQuery { | ||
29 | start: number | ||
30 | count: number | ||
31 | sort: string | ||
32 | } | ||
33 | |||
28 | export interface VideosWithSearchCommonQuery extends VideosCommonQuery { | 34 | export 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 | |||
22 | export interface VideosSearchQueryAfterSanitize extends VideosSearchQuery { | ||
23 | start: number | ||
24 | count: number | ||
25 | sort: string | ||
26 | } | ||