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 @@ import * as express from 'express' +import { pickCommonVideoQuery } from '@server/helpers/query' import { getServerActor } from '@server/models/application/application' -import { VideosWithSearchCommonQuery } from '@shared/models' import { buildNSFWFilter, getCountVideos, isUserAbleToSearchRemoteURI } from '../../helpers/express-utils' import { getFormattedObjects } from '../../helpers/utils' import { JobQueue } from '../../lib/job-queue' @@ -159,27 +159,19 @@ async function listAccountVideos (req: express.Request, res: express.Response) { const account = res.locals.account const followerActorId = isUserAbleToSearchRemoteURI(res) ? null : undefined const countVideos = getCountVideos(req) - const query = req.query as VideosWithSearchCommonQuery + const query = pickCommonVideoQuery(req.query) const apiOptions = await Hooks.wrapObject({ + ...query, + followerActorId, - start: query.start, - count: query.count, - sort: query.sort, + search: req.query.search, includeLocalVideos: true, - categoryOneOf: query.categoryOneOf, - licenceOneOf: query.licenceOneOf, - languageOneOf: query.languageOneOf, - tagsOneOf: query.tagsOneOf, - tagsAllOf: query.tagsAllOf, - filter: query.filter, - isLive: query.isLive, nsfw: buildNSFWFilter(res, query.nsfw), withFiles: false, accountId: account.id, user: res.locals.oauth ? res.locals.oauth.token.User : undefined, - countVideos, - search: query.search + countVideos }, 'filter:api.accounts.videos.list.params') 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 @@ import * as express from 'express' import { sanitizeUrl } from '@server/helpers/core-utils' +import { pickSearchChannelQuery } from '@server/helpers/query' import { doJSONRequest } from '@server/helpers/requests' import { CONFIG } from '@server/initializers/config' import { WEBSERVER } from '@server/initializers/constants' @@ -7,7 +8,7 @@ import { Hooks } from '@server/lib/plugins/hooks' import { buildMutedForSearchIndex, isSearchIndexSearch, isURISearch } from '@server/lib/search' import { getServerActor } from '@server/models/application/application' import { HttpStatusCode, ResultList, VideoChannel } from '@shared/models' -import { VideoChannelsSearchQuery } from '../../../../shared/models/search' +import { VideoChannelsSearchQueryAfterSanitize } from '../../../../shared/models/search' import { isUserAbleToSearchRemoteURI } from '../../../helpers/express-utils' import { logger } from '../../../helpers/logger' import { getFormattedObjects } from '../../../helpers/utils' @@ -45,7 +46,7 @@ export { searchChannelsRouter } // --------------------------------------------------------------------------- function searchVideoChannels (req: express.Request, res: express.Response) { - const query: VideoChannelsSearchQuery = req.query + const query = pickSearchChannelQuery(req.query) let search = query.search || '' const parts = search.split('@') @@ -66,7 +67,7 @@ function searchVideoChannels (req: express.Request, res: express.Response) { return searchVideoChannelsDB(query, res) } -async function searchVideoChannelsIndex (query: VideoChannelsSearchQuery, res: express.Response) { +async function searchVideoChannelsIndex (query: VideoChannelsSearchQueryAfterSanitize, res: express.Response) { const result = await buildMutedForSearchIndex(res) 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 } } -async function searchVideoChannelsDB (query: VideoChannelsSearchQuery, res: express.Response) { +async function searchVideoChannelsDB (query: VideoChannelsSearchQueryAfterSanitize, res: express.Response) { const serverActor = await getServerActor() const apiOptions = await Hooks.wrapObject({ - actorId: serverActor.id, - search: query.search, - start: query.start, - count: query.count, - sort: query.sort, - host: query.host, - handles: query.handles + ...query, + + actorId: serverActor.id }, 'filter:api.search.video-channels.local.list.params') 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' import { sanitizeUrl } from '@server/helpers/core-utils' import { isUserAbleToSearchRemoteURI } from '@server/helpers/express-utils' import { logger } from '@server/helpers/logger' +import { pickSearchPlaylistQuery } from '@server/helpers/query' import { doJSONRequest } from '@server/helpers/requests' import { getFormattedObjects } from '@server/helpers/utils' import { CONFIG } from '@server/initializers/config' @@ -12,7 +13,7 @@ import { buildMutedForSearchIndex, isSearchIndexSearch, isURISearch } from '@ser import { getServerActor } from '@server/models/application/application' import { VideoPlaylistModel } from '@server/models/video/video-playlist' import { MVideoPlaylistFullSummary } from '@server/types/models' -import { HttpStatusCode, ResultList, VideoPlaylist, VideoPlaylistsSearchQuery } from '@shared/models' +import { HttpStatusCode, ResultList, VideoPlaylist, VideoPlaylistsSearchQueryAfterSanitize } from '@shared/models' import { asyncMiddleware, openapiOperationDoc, @@ -44,7 +45,7 @@ export { searchPlaylistsRouter } // --------------------------------------------------------------------------- function searchVideoPlaylists (req: express.Request, res: express.Response) { - const query: VideoPlaylistsSearchQuery = req.query + const query = pickSearchPlaylistQuery(req.query) const search = query.search if (isURISearch(search)) return searchVideoPlaylistsURI(search, res) @@ -56,7 +57,7 @@ function searchVideoPlaylists (req: express.Request, res: express.Response) { return searchVideoPlaylistsDB(query, res) } -async function searchVideoPlaylistsIndex (query: VideoPlaylistsSearchQuery, res: express.Response) { +async function searchVideoPlaylistsIndex (query: VideoPlaylistsSearchQueryAfterSanitize, res: express.Response) { const result = await buildMutedForSearchIndex(res) 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: } } -async function searchVideoPlaylistsDB (query: VideoPlaylistsSearchQuery, res: express.Response) { +async function searchVideoPlaylistsDB (query: VideoPlaylistsSearchQueryAfterSanitize, res: express.Response) { const serverActor = await getServerActor() const apiOptions = await Hooks.wrapObject({ - followerActorId: serverActor.id, - search: query.search, - start: query.start, - count: query.count, - sort: query.sort, - host: query.host, - uuids: query.uuids + ...query, + + followerActorId: serverActor.id }, 'filter:api.search.video-playlists.local.list.params') 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 @@ import * as express from 'express' import { sanitizeUrl } from '@server/helpers/core-utils' +import { pickSearchVideoQuery } from '@server/helpers/query' import { doJSONRequest } from '@server/helpers/requests' import { CONFIG } from '@server/initializers/config' import { WEBSERVER } from '@server/initializers/constants' @@ -7,7 +8,7 @@ import { getOrCreateAPVideo } from '@server/lib/activitypub/videos' import { Hooks } from '@server/lib/plugins/hooks' import { buildMutedForSearchIndex, isSearchIndexSearch, isURISearch } from '@server/lib/search' import { HttpStatusCode, ResultList, Video } from '@shared/models' -import { VideosSearchQuery } from '../../../../shared/models/search' +import { VideosSearchQueryAfterSanitize } from '../../../../shared/models/search' import { buildNSFWFilter, isUserAbleToSearchRemoteURI } from '../../../helpers/express-utils' import { logger } from '../../../helpers/logger' import { getFormattedObjects } from '../../../helpers/utils' @@ -46,7 +47,7 @@ export { searchVideosRouter } // --------------------------------------------------------------------------- function searchVideos (req: express.Request, res: express.Response) { - const query: VideosSearchQuery = req.query + const query = pickSearchVideoQuery(req.query) const search = query.search if (isURISearch(search)) { @@ -60,10 +61,10 @@ function searchVideos (req: express.Request, res: express.Response) { return searchVideosDB(query, res) } -async function searchVideosIndex (query: VideosSearchQuery, res: express.Response) { +async function searchVideosIndex (query: VideosSearchQueryAfterSanitize, res: express.Response) { const result = await buildMutedForSearchIndex(res) - let body: VideosSearchQuery = Object.assign(query, result) + let body = { ...query, ...result } // Use the default instance NSFW policy if not specified if (!body.nsfw) { @@ -97,13 +98,18 @@ async function searchVideosIndex (query: VideosSearchQuery, res: express.Respons } } -async function searchVideosDB (query: VideosSearchQuery, res: express.Response) { - const apiOptions = await Hooks.wrapObject(Object.assign(query, { +async function searchVideosDB (query: VideosSearchQueryAfterSanitize, res: express.Response) { + const apiOptions = await Hooks.wrapObject({ + ...query, + includeLocalVideos: true, - nsfw: buildNSFWFilter(res, query.nsfw), filter: query.filter, - user: res.locals.oauth ? res.locals.oauth.token.User : undefined - }), 'filter:api.search.videos.local.list.params') + + nsfw: buildNSFWFilter(res, query.nsfw), + user: res.locals.oauth + ? res.locals.oauth.token.User + : undefined + }, 'filter:api.search.videos.local.list.params') const resultList = await Hooks.wrapPromiseFun( 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 @@ import 'multer' import * as express from 'express' +import { pickCommonVideoQuery } from '@server/helpers/query' import { sendUndoFollow } from '@server/lib/activitypub/send' import { VideoChannelModel } from '@server/models/video/video-channel' -import { VideosCommonQuery } from '@shared/models' import { HttpStatusCode } from '../../../../shared/models/http/http-error-codes' import { buildNSFWFilter, getCountVideos } from '../../../helpers/express-utils' import { getFormattedObjects } from '../../../helpers/utils' @@ -170,20 +170,13 @@ async function getUserSubscriptions (req: express.Request, res: express.Response async function getUserSubscriptionVideos (req: express.Request, res: express.Response) { const user = res.locals.oauth.token.User const countVideos = getCountVideos(req) - const query = req.query as VideosCommonQuery + const query = pickCommonVideoQuery(req.query) const resultList = await VideoModel.listForApi({ - start: query.start, - count: query.count, - sort: query.sort, + ...query, + includeLocalVideos: false, - categoryOneOf: query.categoryOneOf, - licenceOneOf: query.licenceOneOf, - languageOneOf: query.languageOneOf, - tagsOneOf: query.tagsOneOf, - tagsAllOf: query.tagsAllOf, nsfw: buildNSFWFilter(res, query.nsfw), - filter: query.filter, withFiles: false, followerActorId: user.Account.Actor.id, 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 @@ import * as express from 'express' +import { pickCommonVideoQuery } from '@server/helpers/query' import { Hooks } from '@server/lib/plugins/hooks' import { getServerActor } from '@server/models/application/application' import { MChannelBannerAccountDefault } from '@server/types/models' -import { ActorImageType, VideoChannelCreate, VideoChannelUpdate, VideosCommonQuery } from '../../../shared' +import { ActorImageType, VideoChannelCreate, VideoChannelUpdate } from '../../../shared' import { HttpStatusCode } from '../../../shared/models/http/http-error-codes' import { auditLoggerFactory, getAuditIdFromRes, VideoChannelAuditView } from '../../helpers/audit-logger' import { resetSequelizeInstance } from '../../helpers/database-utils' @@ -309,20 +310,13 @@ async function listVideoChannelVideos (req: express.Request, res: express.Respon const videoChannelInstance = res.locals.videoChannel const followerActorId = isUserAbleToSearchRemoteURI(res) ? null : undefined const countVideos = getCountVideos(req) - const query = req.query as VideosCommonQuery + const query = pickCommonVideoQuery(req.query) const apiOptions = await Hooks.wrapObject({ + ...query, + followerActorId, - start: query.start, - count: query.count, - sort: query.sort, includeLocalVideos: true, - categoryOneOf: query.categoryOneOf, - licenceOneOf: query.licenceOneOf, - languageOneOf: query.languageOneOf, - tagsOneOf: query.tagsOneOf, - tagsAllOf: query.tagsAllOf, - filter: query.filter, nsfw: buildNSFWFilter(res, query.nsfw), withFiles: false, 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 @@ import * as express from 'express' import toInt from 'validator/lib/toInt' +import { pickCommonVideoQuery } from '@server/helpers/query' import { doJSONRequest } from '@server/helpers/requests' import { LiveManager } from '@server/lib/live' import { openapiOperationDoc } from '@server/middlewares/doc' import { getServerActor } from '@server/models/application/application' import { MVideoAccountLight } from '@server/types/models' -import { VideosCommonQuery } from '../../../../shared' import { HttpStatusCode } from '../../../../shared/models' import { auditLoggerFactory, getAuditIdFromRes, VideoAuditView } from '../../../helpers/audit-logger' import { buildNSFWFilter, getCountVideos } from '../../../helpers/express-utils' @@ -211,22 +211,14 @@ async function getVideoFileMetadata (req: express.Request, res: express.Response } async function listVideos (req: express.Request, res: express.Response) { - const query = req.query as VideosCommonQuery + const query = pickCommonVideoQuery(req.query) const countVideos = getCountVideos(req) const apiOptions = await Hooks.wrapObject({ - start: query.start, - count: query.count, - sort: query.sort, + ...query, + includeLocalVideos: true, - categoryOneOf: query.categoryOneOf, - licenceOneOf: query.licenceOneOf, - languageOneOf: query.languageOneOf, - tagsOneOf: query.tagsOneOf, - tagsAllOf: query.tagsAllOf, nsfw: buildNSFWFilter(res, query.nsfw), - isLive: query.isLive, - filter: query.filter, withFiles: false, user: res.locals.oauth ? res.locals.oauth.token.User : undefined, 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 @@ +import { pick } from '@shared/core-utils' +import { + VideoChannelsSearchQueryAfterSanitize, + VideoPlaylistsSearchQueryAfterSanitize, + VideosCommonQueryAfterSanitize, + VideosSearchQueryAfterSanitize +} from '@shared/models' + +function pickCommonVideoQuery (query: VideosCommonQueryAfterSanitize) { + return pick(query, [ + 'start', + 'count', + 'sort', + 'nsfw', + 'isLive', + 'categoryOneOf', + 'licenceOneOf', + 'languageOneOf', + 'tagsOneOf', + 'tagsAllOf', + 'filter', + 'skipCount' + ]) +} + +function pickSearchVideoQuery (query: VideosSearchQueryAfterSanitize) { + return { + ...pickCommonVideoQuery(query), + + ...pick(query, [ + 'searchTarget', + 'search', + 'host', + 'startDate', + 'endDate', + 'originallyPublishedStartDate', + 'originallyPublishedEndDate', + 'durationMin', + 'durationMax', + 'uuids' + ]) + } +} + +function pickSearchChannelQuery (query: VideoChannelsSearchQueryAfterSanitize) { + return pick(query, [ + 'searchTarget', + 'search', + 'start', + 'count', + 'sort', + 'host', + 'handles' + ]) +} + +function pickSearchPlaylistQuery (query: VideoPlaylistsSearchQueryAfterSanitize) { + return pick(query, [ + 'searchTarget', + 'search', + 'start', + 'count', + 'sort', + 'host', + 'uuids' + ]) +} + +export { + pickCommonVideoQuery, + pickSearchVideoQuery, + pickSearchPlaylistQuery, + pickSearchChannelQuery +} 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>> { const trendingDays = options.sort.endsWith('trending') ? CONFIG.TRENDING.VIDEOS.INTERVAL_DAYS : undefined - let trendingAlgorithm + + let trendingAlgorithm: string if (options.sort.endsWith('hot')) trendingAlgorithm = 'hot' if (options.sort.endsWith('best')) trendingAlgorithm = 'best' 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 () { const search = { ...baseSearch, host: 'framatube.org' } await check(search, true) } + + { + const goodUUID = '9c9de5e8-0a1e-484a-b099-e80766180a6d' + const goodShortUUID = 'kkGMgK9ZtnKfYAgnEtQxbv' + const badUUID = 'c29c5b77-4a04-493d-96a9-2e9267e308f0' + const badShortUUID = 'rP5RgUeX9XwTSrspCdkDej' + + { + const uuidsMatrix = [ + [ goodUUID ], + [ goodUUID, badShortUUID ], + [ badShortUUID, goodShortUUID ], + [ goodUUID, goodShortUUID ] + ] + + for (const uuids of uuidsMatrix) { + const search = { ...baseSearch, uuids } + await check(search, true) + } + } + + { + const uuidsMatrix = [ + [ badUUID ], + [ badShortUUID ] + ] + + for (const uuids of uuidsMatrix) { + const search = { ...baseSearch, uuids } + await check(search, false) + } + } + } }) it('Should have a correct pagination', async function () { @@ -315,6 +348,45 @@ describe('Test videos search', function () { describe('Playlists search', async function () { + async function check (search: VideoPlaylistsSearchQuery, exists = true) { + const body = await command.advancedPlaylistSearch({ search }) + + if (exists === false) { + expect(body.total).to.equal(0) + expect(body.data).to.have.lengthOf(0) + return + } + + expect(body.total).to.be.greaterThan(0) + expect(body.data).to.have.length.greaterThan(0) + + const videoPlaylist = body.data[0] + + expect(videoPlaylist.url).to.equal('https://peertube2.cpy.re/videos/watch/playlist/73804a40-da9a-40c2-b1eb-2c6d9eec8f0a') + expect(videoPlaylist.thumbnailUrl).to.exist + expect(videoPlaylist.embedUrl).to.equal('https://peertube2.cpy.re/video-playlists/embed/73804a40-da9a-40c2-b1eb-2c6d9eec8f0a') + + expect(videoPlaylist.type.id).to.equal(VideoPlaylistType.REGULAR) + expect(videoPlaylist.privacy.id).to.equal(VideoPlaylistPrivacy.PUBLIC) + expect(videoPlaylist.videosLength).to.exist + + expect(videoPlaylist.createdAt).to.exist + expect(videoPlaylist.updatedAt).to.exist + + expect(videoPlaylist.uuid).to.equal('73804a40-da9a-40c2-b1eb-2c6d9eec8f0a') + expect(videoPlaylist.displayName).to.exist + + expect(videoPlaylist.ownerAccount.url).to.equal('https://peertube2.cpy.re/accounts/chocobozzz') + expect(videoPlaylist.ownerAccount.name).to.equal('chocobozzz') + expect(videoPlaylist.ownerAccount.host).to.equal('peertube2.cpy.re') + expect(videoPlaylist.ownerAccount.avatar).to.exist + + expect(videoPlaylist.videoChannel.url).to.equal('https://peertube2.cpy.re/video-channels/chocobozzz_channel') + expect(videoPlaylist.videoChannel.name).to.equal('chocobozzz_channel') + expect(videoPlaylist.videoChannel.host).to.equal('peertube2.cpy.re') + expect(videoPlaylist.videoChannel.avatar).to.exist + } + it('Should make a simple search and not have results', async function () { const body = await command.searchPlaylists({ search: 'a'.repeat(500) }) @@ -323,49 +395,45 @@ describe('Test videos search', function () { }) it('Should make a search and have results', async function () { + await check({ search: 'E2E playlist', sort: '-match' }, true) + }) - async function check (search: VideoPlaylistsSearchQuery, exists = true) { - const body = await command.advancedPlaylistSearch({ search }) + it('Should make host search and have appropriate results', async function () { + await check({ search: 'E2E playlist', host: 'example.com' }, false) + await check({ search: 'E2E playlist', host: 'peertube2.cpy.re', sort: '-match' }, true) + }) - if (exists === false) { - expect(body.total).to.equal(0) - expect(body.data).to.have.lengthOf(0) - return + it('Should make a search by uuids and have appropriate results', async function () { + const goodUUID = '73804a40-da9a-40c2-b1eb-2c6d9eec8f0a' + const goodShortUUID = 'fgei1ws1oa6FCaJ2qZPG29' + const badUUID = 'c29c5b77-4a04-493d-96a9-2e9267e308f0' + const badShortUUID = 'rP5RgUeX9XwTSrspCdkDej' + + { + const uuidsMatrix = [ + [ goodUUID ], + [ goodUUID, badShortUUID ], + [ badShortUUID, goodShortUUID ], + [ goodUUID, goodShortUUID ] + ] + + for (const uuids of uuidsMatrix) { + const search = { search: 'E2E playlist', sort: '-match', uuids } + await check(search, true) } - - expect(body.total).to.be.greaterThan(0) - expect(body.data).to.have.length.greaterThan(0) - - const videoPlaylist = body.data[0] - - expect(videoPlaylist.url).to.equal('https://peertube2.cpy.re/videos/watch/playlist/73804a40-da9a-40c2-b1eb-2c6d9eec8f0a') - expect(videoPlaylist.thumbnailUrl).to.exist - expect(videoPlaylist.embedUrl).to.equal('https://peertube2.cpy.re/video-playlists/embed/73804a40-da9a-40c2-b1eb-2c6d9eec8f0a') - - expect(videoPlaylist.type.id).to.equal(VideoPlaylistType.REGULAR) - expect(videoPlaylist.privacy.id).to.equal(VideoPlaylistPrivacy.PUBLIC) - expect(videoPlaylist.videosLength).to.exist - - expect(videoPlaylist.createdAt).to.exist - expect(videoPlaylist.updatedAt).to.exist - - expect(videoPlaylist.uuid).to.equal('73804a40-da9a-40c2-b1eb-2c6d9eec8f0a') - expect(videoPlaylist.displayName).to.exist - - expect(videoPlaylist.ownerAccount.url).to.equal('https://peertube2.cpy.re/accounts/chocobozzz') - expect(videoPlaylist.ownerAccount.name).to.equal('chocobozzz') - expect(videoPlaylist.ownerAccount.host).to.equal('peertube2.cpy.re') - expect(videoPlaylist.ownerAccount.avatar).to.exist - - expect(videoPlaylist.videoChannel.url).to.equal('https://peertube2.cpy.re/video-channels/chocobozzz_channel') - expect(videoPlaylist.videoChannel.name).to.equal('chocobozzz_channel') - expect(videoPlaylist.videoChannel.host).to.equal('peertube2.cpy.re') - expect(videoPlaylist.videoChannel.avatar).to.exist } - await check({ search: 'E2E playlist', sort: '-match' }, true) - await check({ search: 'E2E playlist', host: 'example.com' }, false) - await check({ search: 'E2E playlist', host: 'peertube2.cpy.re' }, true) + { + const uuidsMatrix = [ + [ badUUID ], + [ badShortUUID ] + ] + + for (const uuids of uuidsMatrix) { + const search = { search: 'E2E playlist', sort: '-match', uuids } + await check(search, false) + } + } }) 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 @@ -function pick (object: T, keys: (keyof T)[]) { - const result: Partial = {} +function pick (object: O, keys: K[]): Pick { + const result: any = {} for (const key of keys) { 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 { host?: string handles?: string[] } + +export interface VideoChannelsSearchQueryAfterSanitize extends VideoChannelsSearchQuery { + start: number + count: number + sort: string +} 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 { sort?: string host?: string + + // UUIDs or short UUIDs uuids?: string[] } + +export interface VideoPlaylistsSearchQueryAfterSanitize extends VideoPlaylistsSearchQuery { + start: number + count: number + sort: string +} 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 { skipCount?: boolean } +export interface VideosCommonQueryAfterSanitize extends VideosCommonQuery { + start: number + count: number + sort: string +} + export interface VideosWithSearchCommonQuery extends VideosCommonQuery { search?: string } 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 durationMin?: number // seconds durationMax?: number // seconds - // UUIDs or short + // UUIDs or short UUIDs uuids?: string[] } + +export interface VideosSearchQueryAfterSanitize extends VideosSearchQuery { + start: number + count: number + sort: string +}