aboutsummaryrefslogtreecommitdiffhomepage
path: root/server/controllers/api/search
diff options
context:
space:
mode:
Diffstat (limited to 'server/controllers/api/search')
-rw-r--r--server/controllers/api/search/index.ts19
-rw-r--r--server/controllers/api/search/search-video-channels.ts152
-rw-r--r--server/controllers/api/search/search-video-playlists.ts131
-rw-r--r--server/controllers/api/search/search-videos.ts167
-rw-r--r--server/controllers/api/search/shared/index.ts1
-rw-r--r--server/controllers/api/search/shared/utils.ts16
6 files changed, 0 insertions, 486 deletions
diff --git a/server/controllers/api/search/index.ts b/server/controllers/api/search/index.ts
deleted file mode 100644
index 4d395161c..000000000
--- a/server/controllers/api/search/index.ts
+++ /dev/null
@@ -1,19 +0,0 @@
1import express from 'express'
2import { apiRateLimiter } from '@server/middlewares'
3import { searchChannelsRouter } from './search-video-channels'
4import { searchPlaylistsRouter } from './search-video-playlists'
5import { searchVideosRouter } from './search-videos'
6
7const searchRouter = express.Router()
8
9searchRouter.use(apiRateLimiter)
10
11searchRouter.use('/', searchVideosRouter)
12searchRouter.use('/', searchChannelsRouter)
13searchRouter.use('/', searchPlaylistsRouter)
14
15// ---------------------------------------------------------------------------
16
17export {
18 searchRouter
19}
diff --git a/server/controllers/api/search/search-video-channels.ts b/server/controllers/api/search/search-video-channels.ts
deleted file mode 100644
index 1d2a9d235..000000000
--- a/server/controllers/api/search/search-video-channels.ts
+++ /dev/null
@@ -1,152 +0,0 @@
1import express from 'express'
2import { sanitizeUrl } from '@server/helpers/core-utils'
3import { pickSearchChannelQuery } from '@server/helpers/query'
4import { doJSONRequest } from '@server/helpers/requests'
5import { CONFIG } from '@server/initializers/config'
6import { WEBSERVER } from '@server/initializers/constants'
7import { findLatestAPRedirection } from '@server/lib/activitypub/activity'
8import { Hooks } from '@server/lib/plugins/hooks'
9import { buildMutedForSearchIndex, isSearchIndexSearch, isURISearch } from '@server/lib/search'
10import { getServerActor } from '@server/models/application/application'
11import { HttpStatusCode, ResultList, VideoChannel } from '@shared/models'
12import { VideoChannelsSearchQueryAfterSanitize } from '../../../../shared/models/search'
13import { isUserAbleToSearchRemoteURI } from '../../../helpers/express-utils'
14import { logger } from '../../../helpers/logger'
15import { getFormattedObjects } from '../../../helpers/utils'
16import { getOrCreateAPActor, loadActorUrlOrGetFromWebfinger } from '../../../lib/activitypub/actors'
17import {
18 asyncMiddleware,
19 openapiOperationDoc,
20 optionalAuthenticate,
21 paginationValidator,
22 setDefaultPagination,
23 setDefaultSearchSort,
24 videoChannelsListSearchValidator,
25 videoChannelsSearchSortValidator
26} from '../../../middlewares'
27import { VideoChannelModel } from '../../../models/video/video-channel'
28import { MChannelAccountDefault } from '../../../types/models'
29import { searchLocalUrl } from './shared'
30
31const searchChannelsRouter = express.Router()
32
33searchChannelsRouter.get('/video-channels',
34 openapiOperationDoc({ operationId: 'searchChannels' }),
35 paginationValidator,
36 setDefaultPagination,
37 videoChannelsSearchSortValidator,
38 setDefaultSearchSort,
39 optionalAuthenticate,
40 videoChannelsListSearchValidator,
41 asyncMiddleware(searchVideoChannels)
42)
43
44// ---------------------------------------------------------------------------
45
46export { searchChannelsRouter }
47
48// ---------------------------------------------------------------------------
49
50function searchVideoChannels (req: express.Request, res: express.Response) {
51 const query = pickSearchChannelQuery(req.query)
52 const search = query.search || ''
53
54 const parts = search.split('@')
55
56 // Handle strings like @toto@example.com
57 if (parts.length === 3 && parts[0].length === 0) parts.shift()
58 const isWebfingerSearch = parts.length === 2 && parts.every(p => p && !p.includes(' '))
59
60 if (isURISearch(search) || isWebfingerSearch) return searchVideoChannelURI(search, res)
61
62 // @username -> username to search in DB
63 if (search.startsWith('@')) query.search = search.replace(/^@/, '')
64
65 if (isSearchIndexSearch(query)) {
66 return searchVideoChannelsIndex(query, res)
67 }
68
69 return searchVideoChannelsDB(query, res)
70}
71
72async function searchVideoChannelsIndex (query: VideoChannelsSearchQueryAfterSanitize, res: express.Response) {
73 const result = await buildMutedForSearchIndex(res)
74
75 const body = await Hooks.wrapObject(Object.assign(query, result), 'filter:api.search.video-channels.index.list.params')
76
77 const url = sanitizeUrl(CONFIG.SEARCH.SEARCH_INDEX.URL) + '/api/v1/search/video-channels'
78
79 try {
80 logger.debug('Doing video channels search index request on %s.', url, { body })
81
82 const { body: searchIndexResult } = await doJSONRequest<ResultList<VideoChannel>>(url, { method: 'POST', json: body })
83 const jsonResult = await Hooks.wrapObject(searchIndexResult, 'filter:api.search.video-channels.index.list.result')
84
85 return res.json(jsonResult)
86 } catch (err) {
87 logger.warn('Cannot use search index to make video channels search.', { err })
88
89 return res.fail({
90 status: HttpStatusCode.INTERNAL_SERVER_ERROR_500,
91 message: 'Cannot use search index to make video channels search'
92 })
93 }
94}
95
96async function searchVideoChannelsDB (query: VideoChannelsSearchQueryAfterSanitize, res: express.Response) {
97 const serverActor = await getServerActor()
98
99 const apiOptions = await Hooks.wrapObject({
100 ...query,
101
102 actorId: serverActor.id
103 }, 'filter:api.search.video-channels.local.list.params')
104
105 const resultList = await Hooks.wrapPromiseFun(
106 VideoChannelModel.searchForApi,
107 apiOptions,
108 'filter:api.search.video-channels.local.list.result'
109 )
110
111 return res.json(getFormattedObjects(resultList.data, resultList.total))
112}
113
114async function searchVideoChannelURI (search: string, res: express.Response) {
115 let videoChannel: MChannelAccountDefault
116 let uri = search
117
118 if (!isURISearch(search)) {
119 try {
120 uri = await loadActorUrlOrGetFromWebfinger(search)
121 } catch (err) {
122 logger.warn('Cannot load actor URL or get from webfinger.', { search, err })
123
124 return res.json({ total: 0, data: [] })
125 }
126 }
127
128 if (isUserAbleToSearchRemoteURI(res)) {
129 try {
130 const latestUri = await findLatestAPRedirection(uri)
131
132 const actor = await getOrCreateAPActor(latestUri, 'all', true, true)
133 videoChannel = actor.VideoChannel
134 } catch (err) {
135 logger.info('Cannot search remote video channel %s.', uri, { err })
136 }
137 } else {
138 videoChannel = await searchLocalUrl(sanitizeLocalUrl(uri), url => VideoChannelModel.loadByUrlAndPopulateAccount(url))
139 }
140
141 return res.json({
142 total: videoChannel ? 1 : 0,
143 data: videoChannel ? [ videoChannel.toFormattedJSON() ] : []
144 })
145}
146
147function sanitizeLocalUrl (url: string) {
148 if (!url) return ''
149
150 // Handle alternative channel URLs
151 return url.replace(new RegExp('^' + WEBSERVER.URL + '/c/'), WEBSERVER.URL + '/video-channels/')
152}
diff --git a/server/controllers/api/search/search-video-playlists.ts b/server/controllers/api/search/search-video-playlists.ts
deleted file mode 100644
index 97aeeaba9..000000000
--- a/server/controllers/api/search/search-video-playlists.ts
+++ /dev/null
@@ -1,131 +0,0 @@
1import express from 'express'
2import { sanitizeUrl } from '@server/helpers/core-utils'
3import { isUserAbleToSearchRemoteURI } from '@server/helpers/express-utils'
4import { logger } from '@server/helpers/logger'
5import { pickSearchPlaylistQuery } from '@server/helpers/query'
6import { doJSONRequest } from '@server/helpers/requests'
7import { getFormattedObjects } from '@server/helpers/utils'
8import { CONFIG } from '@server/initializers/config'
9import { WEBSERVER } from '@server/initializers/constants'
10import { findLatestAPRedirection } from '@server/lib/activitypub/activity'
11import { getOrCreateAPVideoPlaylist } from '@server/lib/activitypub/playlists/get'
12import { Hooks } from '@server/lib/plugins/hooks'
13import { buildMutedForSearchIndex, isSearchIndexSearch, isURISearch } from '@server/lib/search'
14import { getServerActor } from '@server/models/application/application'
15import { VideoPlaylistModel } from '@server/models/video/video-playlist'
16import { MVideoPlaylistFullSummary } from '@server/types/models'
17import { HttpStatusCode, ResultList, VideoPlaylist, VideoPlaylistsSearchQueryAfterSanitize } from '@shared/models'
18import {
19 asyncMiddleware,
20 openapiOperationDoc,
21 optionalAuthenticate,
22 paginationValidator,
23 setDefaultPagination,
24 setDefaultSearchSort,
25 videoPlaylistsListSearchValidator,
26 videoPlaylistsSearchSortValidator
27} from '../../../middlewares'
28import { searchLocalUrl } from './shared'
29
30const searchPlaylistsRouter = express.Router()
31
32searchPlaylistsRouter.get('/video-playlists',
33 openapiOperationDoc({ operationId: 'searchPlaylists' }),
34 paginationValidator,
35 setDefaultPagination,
36 videoPlaylistsSearchSortValidator,
37 setDefaultSearchSort,
38 optionalAuthenticate,
39 videoPlaylistsListSearchValidator,
40 asyncMiddleware(searchVideoPlaylists)
41)
42
43// ---------------------------------------------------------------------------
44
45export { searchPlaylistsRouter }
46
47// ---------------------------------------------------------------------------
48
49function searchVideoPlaylists (req: express.Request, res: express.Response) {
50 const query = pickSearchPlaylistQuery(req.query)
51 const search = query.search
52
53 if (isURISearch(search)) return searchVideoPlaylistsURI(search, res)
54
55 if (isSearchIndexSearch(query)) {
56 return searchVideoPlaylistsIndex(query, res)
57 }
58
59 return searchVideoPlaylistsDB(query, res)
60}
61
62async function searchVideoPlaylistsIndex (query: VideoPlaylistsSearchQueryAfterSanitize, res: express.Response) {
63 const result = await buildMutedForSearchIndex(res)
64
65 const body = await Hooks.wrapObject(Object.assign(query, result), 'filter:api.search.video-playlists.index.list.params')
66
67 const url = sanitizeUrl(CONFIG.SEARCH.SEARCH_INDEX.URL) + '/api/v1/search/video-playlists'
68
69 try {
70 logger.debug('Doing video playlists search index request on %s.', url, { body })
71
72 const { body: searchIndexResult } = await doJSONRequest<ResultList<VideoPlaylist>>(url, { method: 'POST', json: body })
73 const jsonResult = await Hooks.wrapObject(searchIndexResult, 'filter:api.search.video-playlists.index.list.result')
74
75 return res.json(jsonResult)
76 } catch (err) {
77 logger.warn('Cannot use search index to make video playlists search.', { err })
78
79 return res.fail({
80 status: HttpStatusCode.INTERNAL_SERVER_ERROR_500,
81 message: 'Cannot use search index to make video playlists search'
82 })
83 }
84}
85
86async function searchVideoPlaylistsDB (query: VideoPlaylistsSearchQueryAfterSanitize, res: express.Response) {
87 const serverActor = await getServerActor()
88
89 const apiOptions = await Hooks.wrapObject({
90 ...query,
91
92 followerActorId: serverActor.id
93 }, 'filter:api.search.video-playlists.local.list.params')
94
95 const resultList = await Hooks.wrapPromiseFun(
96 VideoPlaylistModel.searchForApi,
97 apiOptions,
98 'filter:api.search.video-playlists.local.list.result'
99 )
100
101 return res.json(getFormattedObjects(resultList.data, resultList.total))
102}
103
104async function searchVideoPlaylistsURI (search: string, res: express.Response) {
105 let videoPlaylist: MVideoPlaylistFullSummary
106
107 if (isUserAbleToSearchRemoteURI(res)) {
108 try {
109 const url = await findLatestAPRedirection(search)
110
111 videoPlaylist = await getOrCreateAPVideoPlaylist(url)
112 } catch (err) {
113 logger.info('Cannot search remote video playlist %s.', search, { err })
114 }
115 } else {
116 videoPlaylist = await searchLocalUrl(sanitizeLocalUrl(search), url => VideoPlaylistModel.loadByUrlWithAccountAndChannelSummary(url))
117 }
118
119 return res.json({
120 total: videoPlaylist ? 1 : 0,
121 data: videoPlaylist ? [ videoPlaylist.toFormattedJSON() ] : []
122 })
123}
124
125function sanitizeLocalUrl (url: string) {
126 if (!url) return ''
127
128 // Handle alternative channel URLs
129 return url.replace(new RegExp('^' + WEBSERVER.URL + '/videos/watch/playlist/'), WEBSERVER.URL + '/video-playlists/')
130 .replace(new RegExp('^' + WEBSERVER.URL + '/w/p/'), WEBSERVER.URL + '/video-playlists/')
131}
diff --git a/server/controllers/api/search/search-videos.ts b/server/controllers/api/search/search-videos.ts
deleted file mode 100644
index b33064335..000000000
--- a/server/controllers/api/search/search-videos.ts
+++ /dev/null
@@ -1,167 +0,0 @@
1import express from 'express'
2import { sanitizeUrl } from '@server/helpers/core-utils'
3import { pickSearchVideoQuery } from '@server/helpers/query'
4import { doJSONRequest } from '@server/helpers/requests'
5import { CONFIG } from '@server/initializers/config'
6import { WEBSERVER } from '@server/initializers/constants'
7import { findLatestAPRedirection } from '@server/lib/activitypub/activity'
8import { getOrCreateAPVideo } from '@server/lib/activitypub/videos'
9import { Hooks } from '@server/lib/plugins/hooks'
10import { buildMutedForSearchIndex, isSearchIndexSearch, isURISearch } from '@server/lib/search'
11import { getServerActor } from '@server/models/application/application'
12import { HttpStatusCode, ResultList, Video } from '@shared/models'
13import { VideosSearchQueryAfterSanitize } from '../../../../shared/models/search'
14import { buildNSFWFilter, isUserAbleToSearchRemoteURI } from '../../../helpers/express-utils'
15import { logger } from '../../../helpers/logger'
16import { getFormattedObjects } from '../../../helpers/utils'
17import {
18 asyncMiddleware,
19 commonVideosFiltersValidator,
20 openapiOperationDoc,
21 optionalAuthenticate,
22 paginationValidator,
23 setDefaultPagination,
24 setDefaultSearchSort,
25 videosSearchSortValidator,
26 videosSearchValidator
27} from '../../../middlewares'
28import { guessAdditionalAttributesFromQuery } from '../../../models/video/formatter'
29import { VideoModel } from '../../../models/video/video'
30import { MVideoAccountLightBlacklistAllFiles } from '../../../types/models'
31import { searchLocalUrl } from './shared'
32
33const searchVideosRouter = express.Router()
34
35searchVideosRouter.get('/videos',
36 openapiOperationDoc({ operationId: 'searchVideos' }),
37 paginationValidator,
38 setDefaultPagination,
39 videosSearchSortValidator,
40 setDefaultSearchSort,
41 optionalAuthenticate,
42 commonVideosFiltersValidator,
43 videosSearchValidator,
44 asyncMiddleware(searchVideos)
45)
46
47// ---------------------------------------------------------------------------
48
49export { searchVideosRouter }
50
51// ---------------------------------------------------------------------------
52
53function searchVideos (req: express.Request, res: express.Response) {
54 const query = pickSearchVideoQuery(req.query)
55 const search = query.search
56
57 if (isURISearch(search)) {
58 return searchVideoURI(search, res)
59 }
60
61 if (isSearchIndexSearch(query)) {
62 return searchVideosIndex(query, res)
63 }
64
65 return searchVideosDB(query, res)
66}
67
68async function searchVideosIndex (query: VideosSearchQueryAfterSanitize, res: express.Response) {
69 const result = await buildMutedForSearchIndex(res)
70
71 let body = { ...query, ...result }
72
73 // Use the default instance NSFW policy if not specified
74 if (!body.nsfw) {
75 const nsfwPolicy = res.locals.oauth
76 ? res.locals.oauth.token.User.nsfwPolicy
77 : CONFIG.INSTANCE.DEFAULT_NSFW_POLICY
78
79 body.nsfw = nsfwPolicy === 'do_not_list'
80 ? 'false'
81 : 'both'
82 }
83
84 body = await Hooks.wrapObject(body, 'filter:api.search.videos.index.list.params')
85
86 const url = sanitizeUrl(CONFIG.SEARCH.SEARCH_INDEX.URL) + '/api/v1/search/videos'
87
88 try {
89 logger.debug('Doing videos search index request on %s.', url, { body })
90
91 const { body: searchIndexResult } = await doJSONRequest<ResultList<Video>>(url, { method: 'POST', json: body })
92 const jsonResult = await Hooks.wrapObject(searchIndexResult, 'filter:api.search.videos.index.list.result')
93
94 return res.json(jsonResult)
95 } catch (err) {
96 logger.warn('Cannot use search index to make video search.', { err })
97
98 return res.fail({
99 status: HttpStatusCode.INTERNAL_SERVER_ERROR_500,
100 message: 'Cannot use search index to make video search'
101 })
102 }
103}
104
105async function searchVideosDB (query: VideosSearchQueryAfterSanitize, res: express.Response) {
106 const serverActor = await getServerActor()
107
108 const apiOptions = await Hooks.wrapObject({
109 ...query,
110
111 displayOnlyForFollower: {
112 actorId: serverActor.id,
113 orLocalVideos: true
114 },
115
116 nsfw: buildNSFWFilter(res, query.nsfw),
117 user: res.locals.oauth
118 ? res.locals.oauth.token.User
119 : undefined
120 }, 'filter:api.search.videos.local.list.params')
121
122 const resultList = await Hooks.wrapPromiseFun(
123 VideoModel.searchAndPopulateAccountAndServer,
124 apiOptions,
125 'filter:api.search.videos.local.list.result'
126 )
127
128 return res.json(getFormattedObjects(resultList.data, resultList.total, guessAdditionalAttributesFromQuery(query)))
129}
130
131async function searchVideoURI (url: string, res: express.Response) {
132 let video: MVideoAccountLightBlacklistAllFiles
133
134 // Check if we can fetch a remote video with the URL
135 if (isUserAbleToSearchRemoteURI(res)) {
136 try {
137 const syncParam = {
138 rates: false,
139 shares: false,
140 comments: false,
141 refreshVideo: false
142 }
143
144 const result = await getOrCreateAPVideo({
145 videoObject: await findLatestAPRedirection(url),
146 syncParam
147 })
148 video = result ? result.video : undefined
149 } catch (err) {
150 logger.info('Cannot search remote video %s.', url, { err })
151 }
152 } else {
153 video = await searchLocalUrl(sanitizeLocalUrl(url), url => VideoModel.loadByUrlAndPopulateAccount(url))
154 }
155
156 return res.json({
157 total: video ? 1 : 0,
158 data: video ? [ video.toFormattedJSON() ] : []
159 })
160}
161
162function sanitizeLocalUrl (url: string) {
163 if (!url) return ''
164
165 // Handle alternative video URLs
166 return url.replace(new RegExp('^' + WEBSERVER.URL + '/w/'), WEBSERVER.URL + '/videos/watch/')
167}
diff --git a/server/controllers/api/search/shared/index.ts b/server/controllers/api/search/shared/index.ts
deleted file mode 100644
index 9c56149ef..000000000
--- a/server/controllers/api/search/shared/index.ts
+++ /dev/null
@@ -1 +0,0 @@
1export * from './utils'
diff --git a/server/controllers/api/search/shared/utils.ts b/server/controllers/api/search/shared/utils.ts
deleted file mode 100644
index e02e84f31..000000000
--- a/server/controllers/api/search/shared/utils.ts
+++ /dev/null
@@ -1,16 +0,0 @@
1async function searchLocalUrl <T> (url: string, finder: (url: string) => Promise<T>) {
2 const data = await finder(url)
3 if (data) return data
4
5 return finder(removeQueryParams(url))
6}
7
8export {
9 searchLocalUrl
10}
11
12// ---------------------------------------------------------------------------
13
14function removeQueryParams (url: string) {
15 return url.split('?').shift()
16}