Add ability to search by uuids/actor names
This commit is contained in:
parent
164c8d46cf
commit
fbd67e7f38
15 changed files with 215 additions and 48 deletions
|
@ -46,7 +46,7 @@ export { searchChannelsRouter }
|
||||||
|
|
||||||
function searchVideoChannels (req: express.Request, res: express.Response) {
|
function searchVideoChannels (req: express.Request, res: express.Response) {
|
||||||
const query: VideoChannelsSearchQuery = req.query
|
const query: VideoChannelsSearchQuery = req.query
|
||||||
const search = query.search
|
let search = query.search || ''
|
||||||
|
|
||||||
const parts = search.split('@')
|
const parts = search.split('@')
|
||||||
|
|
||||||
|
@ -57,7 +57,7 @@ function searchVideoChannels (req: express.Request, res: express.Response) {
|
||||||
if (isURISearch(search) || isWebfingerSearch) return searchVideoChannelURI(search, isWebfingerSearch, res)
|
if (isURISearch(search) || isWebfingerSearch) return searchVideoChannelURI(search, isWebfingerSearch, res)
|
||||||
|
|
||||||
// @username -> username to search in DB
|
// @username -> username to search in DB
|
||||||
if (query.search.startsWith('@')) query.search = query.search.replace(/^@/, '')
|
if (search.startsWith('@')) search = search.replace(/^@/, '')
|
||||||
|
|
||||||
if (isSearchIndexSearch(query)) {
|
if (isSearchIndexSearch(query)) {
|
||||||
return searchVideoChannelsIndex(query, res)
|
return searchVideoChannelsIndex(query, res)
|
||||||
|
@ -99,7 +99,8 @@ async function searchVideoChannelsDB (query: VideoChannelsSearchQuery, res: expr
|
||||||
start: query.start,
|
start: query.start,
|
||||||
count: query.count,
|
count: query.count,
|
||||||
sort: query.sort,
|
sort: query.sort,
|
||||||
host: query.host
|
host: query.host,
|
||||||
|
names: query.names
|
||||||
}, 'filter:api.search.video-channels.local.list.params')
|
}, 'filter:api.search.video-channels.local.list.params')
|
||||||
|
|
||||||
const resultList = await Hooks.wrapPromiseFun(
|
const resultList = await Hooks.wrapPromiseFun(
|
||||||
|
|
|
@ -89,7 +89,8 @@ async function searchVideoPlaylistsDB (query: VideoPlaylistsSearchQuery, res: ex
|
||||||
start: query.start,
|
start: query.start,
|
||||||
count: query.count,
|
count: query.count,
|
||||||
sort: query.sort,
|
sort: query.sort,
|
||||||
host: query.host
|
host: query.host,
|
||||||
|
uuids: query.uuids
|
||||||
}, 'filter:api.search.video-playlists.local.list.params')
|
}, 'filter:api.search.video-playlists.local.list.params')
|
||||||
|
|
||||||
const resultList = await Hooks.wrapPromiseFun(
|
const resultList = await Hooks.wrapPromiseFun(
|
||||||
|
|
|
@ -39,6 +39,10 @@ function isUUIDValid (value: string) {
|
||||||
return exists(value) && validator.isUUID('' + value, 4)
|
return exists(value) && validator.isUUID('' + value, 4)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function areUUIDsValid (values: string[]) {
|
||||||
|
return isArray(values) && values.every(v => isUUIDValid(v))
|
||||||
|
}
|
||||||
|
|
||||||
function isIdOrUUIDValid (value: string) {
|
function isIdOrUUIDValid (value: string) {
|
||||||
return isIdValid(value) || isUUIDValid(value)
|
return isIdValid(value) || isUUIDValid(value)
|
||||||
}
|
}
|
||||||
|
@ -132,6 +136,10 @@ function toCompleteUUID (value: string) {
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function toCompleteUUIDs (values: string[]) {
|
||||||
|
return values.map(v => toCompleteUUID(v))
|
||||||
|
}
|
||||||
|
|
||||||
function toIntOrNull (value: string) {
|
function toIntOrNull (value: string) {
|
||||||
const v = toValueOrNull(value)
|
const v = toValueOrNull(value)
|
||||||
|
|
||||||
|
@ -180,6 +188,7 @@ export {
|
||||||
isIdValid,
|
isIdValid,
|
||||||
isSafePath,
|
isSafePath,
|
||||||
isUUIDValid,
|
isUUIDValid,
|
||||||
|
toCompleteUUIDs,
|
||||||
toCompleteUUID,
|
toCompleteUUID,
|
||||||
isIdOrUUIDValid,
|
isIdOrUUIDValid,
|
||||||
isDateValid,
|
isDateValid,
|
||||||
|
@ -187,6 +196,7 @@ export {
|
||||||
toBooleanOrNull,
|
toBooleanOrNull,
|
||||||
isBooleanValid,
|
isBooleanValid,
|
||||||
toIntOrNull,
|
toIntOrNull,
|
||||||
|
areUUIDsValid,
|
||||||
toArray,
|
toArray,
|
||||||
toIntArray,
|
toIntArray,
|
||||||
isFileFieldValid,
|
isFileFieldValid,
|
||||||
|
|
|
@ -2,7 +2,7 @@ import * as express from 'express'
|
||||||
import { query } from 'express-validator'
|
import { query } from 'express-validator'
|
||||||
import { isSearchTargetValid } from '@server/helpers/custom-validators/search'
|
import { isSearchTargetValid } from '@server/helpers/custom-validators/search'
|
||||||
import { isHostValid } from '@server/helpers/custom-validators/servers'
|
import { isHostValid } from '@server/helpers/custom-validators/servers'
|
||||||
import { isDateValid } from '../../helpers/custom-validators/misc'
|
import { areUUIDsValid, isDateValid, toCompleteUUIDs } from '../../helpers/custom-validators/misc'
|
||||||
import { logger } from '../../helpers/logger'
|
import { logger } from '../../helpers/logger'
|
||||||
import { areValidationErrors } from './shared'
|
import { areValidationErrors } from './shared'
|
||||||
|
|
||||||
|
@ -27,8 +27,18 @@ const videosSearchValidator = [
|
||||||
.optional()
|
.optional()
|
||||||
.custom(isDateValid).withMessage('Should have a published end date that conforms to ISO 8601'),
|
.custom(isDateValid).withMessage('Should have a published end date that conforms to ISO 8601'),
|
||||||
|
|
||||||
query('durationMin').optional().isInt().withMessage('Should have a valid min duration'),
|
query('durationMin')
|
||||||
query('durationMax').optional().isInt().withMessage('Should have a valid max duration'),
|
.optional()
|
||||||
|
.isInt().withMessage('Should have a valid min duration'),
|
||||||
|
query('durationMax')
|
||||||
|
.optional()
|
||||||
|
.isInt().withMessage('Should have a valid max duration'),
|
||||||
|
|
||||||
|
query('uuids')
|
||||||
|
.optional()
|
||||||
|
.toArray()
|
||||||
|
.customSanitizer(toCompleteUUIDs)
|
||||||
|
.custom(areUUIDsValid).withMessage('Should have valid uuids'),
|
||||||
|
|
||||||
query('searchTarget').optional().custom(isSearchTargetValid).withMessage('Should have a valid search target'),
|
query('searchTarget').optional().custom(isSearchTargetValid).withMessage('Should have a valid search target'),
|
||||||
|
|
||||||
|
@ -42,7 +52,9 @@ const videosSearchValidator = [
|
||||||
]
|
]
|
||||||
|
|
||||||
const videoChannelsListSearchValidator = [
|
const videoChannelsListSearchValidator = [
|
||||||
query('search').not().isEmpty().withMessage('Should have a valid search'),
|
query('search')
|
||||||
|
.optional()
|
||||||
|
.not().isEmpty().withMessage('Should have a valid search'),
|
||||||
|
|
||||||
query('host')
|
query('host')
|
||||||
.optional()
|
.optional()
|
||||||
|
@ -52,6 +64,10 @@ const videoChannelsListSearchValidator = [
|
||||||
.optional()
|
.optional()
|
||||||
.custom(isSearchTargetValid).withMessage('Should have a valid search target'),
|
.custom(isSearchTargetValid).withMessage('Should have a valid search target'),
|
||||||
|
|
||||||
|
query('names')
|
||||||
|
.optional()
|
||||||
|
.toArray(),
|
||||||
|
|
||||||
(req: express.Request, res: express.Response, next: express.NextFunction) => {
|
(req: express.Request, res: express.Response, next: express.NextFunction) => {
|
||||||
logger.debug('Checking video channels search query', { parameters: req.query })
|
logger.debug('Checking video channels search query', { parameters: req.query })
|
||||||
|
|
||||||
|
@ -62,7 +78,9 @@ const videoChannelsListSearchValidator = [
|
||||||
]
|
]
|
||||||
|
|
||||||
const videoPlaylistsListSearchValidator = [
|
const videoPlaylistsListSearchValidator = [
|
||||||
query('search').not().isEmpty().withMessage('Should have a valid search'),
|
query('search')
|
||||||
|
.optional()
|
||||||
|
.not().isEmpty().withMessage('Should have a valid search'),
|
||||||
|
|
||||||
query('host')
|
query('host')
|
||||||
.optional()
|
.optional()
|
||||||
|
@ -72,6 +90,12 @@ const videoPlaylistsListSearchValidator = [
|
||||||
.optional()
|
.optional()
|
||||||
.custom(isSearchTargetValid).withMessage('Should have a valid search target'),
|
.custom(isSearchTargetValid).withMessage('Should have a valid search target'),
|
||||||
|
|
||||||
|
query('uuids')
|
||||||
|
.optional()
|
||||||
|
.toArray()
|
||||||
|
.customSanitizer(toCompleteUUIDs)
|
||||||
|
.custom(areUUIDsValid).withMessage('Should have valid uuids'),
|
||||||
|
|
||||||
(req: express.Request, res: express.Response, next: express.NextFunction) => {
|
(req: express.Request, res: express.Response, next: express.NextFunction) => {
|
||||||
logger.debug('Checking video playlists search query', { parameters: req.query })
|
logger.debug('Checking video playlists search query', { parameters: req.query })
|
||||||
|
|
||||||
|
|
|
@ -35,6 +35,8 @@ export type BuildVideosListQueryOptions = {
|
||||||
tagsOneOf?: string[]
|
tagsOneOf?: string[]
|
||||||
tagsAllOf?: string[]
|
tagsAllOf?: string[]
|
||||||
|
|
||||||
|
uuids?: string[]
|
||||||
|
|
||||||
withFiles?: boolean
|
withFiles?: boolean
|
||||||
|
|
||||||
accountId?: number
|
accountId?: number
|
||||||
|
@ -161,6 +163,10 @@ export class VideosIdListQueryBuilder extends AbstractVideosQueryBuilder {
|
||||||
this.whereTagsAllOf(options.tagsAllOf)
|
this.whereTagsAllOf(options.tagsAllOf)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (options.uuids) {
|
||||||
|
this.whereUUIDs(options.uuids)
|
||||||
|
}
|
||||||
|
|
||||||
if (options.nsfw === true) {
|
if (options.nsfw === true) {
|
||||||
this.whereNSFW()
|
this.whereNSFW()
|
||||||
} else if (options.nsfw === false) {
|
} else if (options.nsfw === false) {
|
||||||
|
@ -386,6 +392,10 @@ export class VideosIdListQueryBuilder extends AbstractVideosQueryBuilder {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private whereUUIDs (uuids: string[]) {
|
||||||
|
this.and.push('"video"."uuid" IN (' + createSafeIn(this.sequelize, uuids) + ')')
|
||||||
|
}
|
||||||
|
|
||||||
private whereCategoryOneOf (categoryOneOf: number[]) {
|
private whereCategoryOneOf (categoryOneOf: number[]) {
|
||||||
this.and.push('"video"."category" IN (:categoryOneOf)')
|
this.and.push('"video"."category" IN (:categoryOneOf)')
|
||||||
this.replacements.categoryOneOf = categoryOneOf
|
this.replacements.categoryOneOf = categoryOneOf
|
||||||
|
|
|
@ -59,6 +59,7 @@ type AvailableForListOptions = {
|
||||||
actorId: number
|
actorId: number
|
||||||
search?: string
|
search?: string
|
||||||
host?: string
|
host?: string
|
||||||
|
names?: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
type AvailableWithStatsOptions = {
|
type AvailableWithStatsOptions = {
|
||||||
|
@ -84,18 +85,20 @@ export type SummaryOptions = {
|
||||||
// Only list local channels OR channels that are on an instance followed by actorId
|
// Only list local channels OR channels that are on an instance followed by actorId
|
||||||
const inQueryInstanceFollow = buildServerIdsFollowedBy(options.actorId)
|
const inQueryInstanceFollow = buildServerIdsFollowedBy(options.actorId)
|
||||||
|
|
||||||
const whereActor = {
|
const whereActorAnd: WhereOptions[] = [
|
||||||
[Op.or]: [
|
{
|
||||||
{
|
[Op.or]: [
|
||||||
serverId: null
|
{
|
||||||
},
|
serverId: null
|
||||||
{
|
},
|
||||||
serverId: {
|
{
|
||||||
[Op.in]: Sequelize.literal(inQueryInstanceFollow)
|
serverId: {
|
||||||
|
[Op.in]: Sequelize.literal(inQueryInstanceFollow)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
]
|
||||||
]
|
}
|
||||||
}
|
]
|
||||||
|
|
||||||
let serverRequired = false
|
let serverRequired = false
|
||||||
let whereServer: WhereOptions
|
let whereServer: WhereOptions
|
||||||
|
@ -106,8 +109,16 @@ export type SummaryOptions = {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.host === WEBSERVER.HOST) {
|
if (options.host === WEBSERVER.HOST) {
|
||||||
Object.assign(whereActor, {
|
whereActorAnd.push({
|
||||||
[Op.and]: [ { serverId: null } ]
|
serverId: null
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.names) {
|
||||||
|
whereActorAnd.push({
|
||||||
|
preferredUsername: {
|
||||||
|
[Op.in]: options.names
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -118,7 +129,9 @@ export type SummaryOptions = {
|
||||||
exclude: unusedActorAttributesForAPI
|
exclude: unusedActorAttributesForAPI
|
||||||
},
|
},
|
||||||
model: ActorModel,
|
model: ActorModel,
|
||||||
where: whereActor,
|
where: {
|
||||||
|
[Op.and]: whereActorAnd
|
||||||
|
},
|
||||||
include: [
|
include: [
|
||||||
{
|
{
|
||||||
model: ServerModel,
|
model: ServerModel,
|
||||||
|
@ -454,26 +467,23 @@ ON "Account->Actor"."serverId" = "Account->Actor->Server"."id"`
|
||||||
|
|
||||||
static searchForApi (options: {
|
static searchForApi (options: {
|
||||||
actorId: number
|
actorId: number
|
||||||
search: string
|
search?: string
|
||||||
start: number
|
start: number
|
||||||
count: number
|
count: number
|
||||||
sort: string
|
sort: string
|
||||||
|
|
||||||
host?: string
|
host?: string
|
||||||
|
names?: string[]
|
||||||
}) {
|
}) {
|
||||||
const attributesInclude = []
|
let attributesInclude: any[] = [ literal('0 as similarity') ]
|
||||||
const escapedSearch = VideoChannelModel.sequelize.escape(options.search)
|
let where: WhereOptions
|
||||||
const escapedLikeSearch = VideoChannelModel.sequelize.escape('%' + options.search + '%')
|
|
||||||
attributesInclude.push(createSimilarityAttribute('VideoChannelModel.name', options.search))
|
|
||||||
|
|
||||||
const query = {
|
if (options.search) {
|
||||||
attributes: {
|
const escapedSearch = VideoChannelModel.sequelize.escape(options.search)
|
||||||
include: attributesInclude
|
const escapedLikeSearch = VideoChannelModel.sequelize.escape('%' + options.search + '%')
|
||||||
},
|
attributesInclude = [ createSimilarityAttribute('VideoChannelModel.name', options.search) ]
|
||||||
offset: options.start,
|
|
||||||
limit: options.count,
|
where = {
|
||||||
order: getSort(options.sort),
|
|
||||||
where: {
|
|
||||||
[Op.or]: [
|
[Op.or]: [
|
||||||
Sequelize.literal(
|
Sequelize.literal(
|
||||||
'lower(immutable_unaccent("VideoChannelModel"."name")) % lower(immutable_unaccent(' + escapedSearch + '))'
|
'lower(immutable_unaccent("VideoChannelModel"."name")) % lower(immutable_unaccent(' + escapedSearch + '))'
|
||||||
|
@ -485,9 +495,19 @@ ON "Account->Actor"."serverId" = "Account->Actor->Server"."id"`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const query = {
|
||||||
|
attributes: {
|
||||||
|
include: attributesInclude
|
||||||
|
},
|
||||||
|
offset: options.start,
|
||||||
|
limit: options.count,
|
||||||
|
order: getSort(options.sort),
|
||||||
|
where
|
||||||
|
}
|
||||||
|
|
||||||
return VideoChannelModel
|
return VideoChannelModel
|
||||||
.scope({
|
.scope({
|
||||||
method: [ ScopeNames.FOR_API, { actorId: options.actorId, host: options.host } as AvailableForListOptions ]
|
method: [ ScopeNames.FOR_API, { actorId: options.actorId, host: options.host, names: options.names } as AvailableForListOptions ]
|
||||||
})
|
})
|
||||||
.findAndCountAll(query)
|
.findAndCountAll(query)
|
||||||
.then(({ rows, count }) => {
|
.then(({ rows, count }) => {
|
||||||
|
|
|
@ -83,6 +83,7 @@ type AvailableForListOptions = {
|
||||||
listMyPlaylists?: boolean
|
listMyPlaylists?: boolean
|
||||||
search?: string
|
search?: string
|
||||||
host?: string
|
host?: string
|
||||||
|
uuids?: string[]
|
||||||
withVideos?: boolean
|
withVideos?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -200,18 +201,26 @@ function getVideoLengthSelect () {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (options.uuids) {
|
||||||
|
whereAnd.push({
|
||||||
|
uuid: {
|
||||||
|
[Op.in]: options.uuids
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
if (options.withVideos === true) {
|
if (options.withVideos === true) {
|
||||||
whereAnd.push(
|
whereAnd.push(
|
||||||
literal(`(${getVideoLengthSelect()}) != 0`)
|
literal(`(${getVideoLengthSelect()}) != 0`)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const attributesInclude = []
|
let attributesInclude: any[] = [ literal('0 as similarity') ]
|
||||||
|
|
||||||
if (options.search) {
|
if (options.search) {
|
||||||
const escapedSearch = VideoPlaylistModel.sequelize.escape(options.search)
|
const escapedSearch = VideoPlaylistModel.sequelize.escape(options.search)
|
||||||
const escapedLikeSearch = VideoPlaylistModel.sequelize.escape('%' + options.search + '%')
|
const escapedLikeSearch = VideoPlaylistModel.sequelize.escape('%' + options.search + '%')
|
||||||
attributesInclude.push(createSimilarityAttribute('VideoPlaylistModel.name', options.search))
|
attributesInclude = [ createSimilarityAttribute('VideoPlaylistModel.name', options.search) ]
|
||||||
|
|
||||||
whereAnd.push({
|
whereAnd.push({
|
||||||
[Op.or]: [
|
[Op.or]: [
|
||||||
|
@ -359,6 +368,7 @@ export class VideoPlaylistModel extends Model<Partial<AttributesOnly<VideoPlayli
|
||||||
listMyPlaylists?: boolean
|
listMyPlaylists?: boolean
|
||||||
search?: string
|
search?: string
|
||||||
host?: string
|
host?: string
|
||||||
|
uuids?: string[]
|
||||||
withVideos?: boolean // false by default
|
withVideos?: boolean // false by default
|
||||||
}) {
|
}) {
|
||||||
const query = {
|
const query = {
|
||||||
|
@ -379,6 +389,7 @@ export class VideoPlaylistModel extends Model<Partial<AttributesOnly<VideoPlayli
|
||||||
listMyPlaylists: options.listMyPlaylists,
|
listMyPlaylists: options.listMyPlaylists,
|
||||||
search: options.search,
|
search: options.search,
|
||||||
host: options.host,
|
host: options.host,
|
||||||
|
uuids: options.uuids,
|
||||||
withVideos: options.withVideos || false
|
withVideos: options.withVideos || false
|
||||||
} as AvailableForListOptions
|
} as AvailableForListOptions
|
||||||
]
|
]
|
||||||
|
@ -402,6 +413,7 @@ export class VideoPlaylistModel extends Model<Partial<AttributesOnly<VideoPlayli
|
||||||
sort: string
|
sort: string
|
||||||
search?: string
|
search?: string
|
||||||
host?: string
|
host?: string
|
||||||
|
uuids?: string[]
|
||||||
}) {
|
}) {
|
||||||
return VideoPlaylistModel.listForApi({
|
return VideoPlaylistModel.listForApi({
|
||||||
...options,
|
...options,
|
||||||
|
|
|
@ -1132,6 +1132,7 @@ export class VideoModel extends Model<Partial<AttributesOnly<VideoModel>>> {
|
||||||
durationMax?: number // seconds
|
durationMax?: number // seconds
|
||||||
user?: MUserAccountId
|
user?: MUserAccountId
|
||||||
filter?: VideoFilter
|
filter?: VideoFilter
|
||||||
|
uuids?: string[]
|
||||||
}) {
|
}) {
|
||||||
const serverActor = await getServerActor()
|
const serverActor = await getServerActor()
|
||||||
|
|
||||||
|
@ -1167,6 +1168,8 @@ export class VideoModel extends Model<Partial<AttributesOnly<VideoModel>>> {
|
||||||
durationMin: options.durationMin,
|
durationMin: options.durationMin,
|
||||||
durationMax: options.durationMax,
|
durationMax: options.durationMax,
|
||||||
|
|
||||||
|
uuids: options.uuids,
|
||||||
|
|
||||||
search: options.search
|
search: options.search
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -146,6 +146,16 @@ describe('Test videos API validator', function () {
|
||||||
const customQuery = { ...query, host: 'example.com' }
|
const customQuery = { ...query, host: 'example.com' }
|
||||||
await makeGetRequest({ url: server.url, path, query: customQuery, expectedStatus: HttpStatusCode.OK_200 })
|
await makeGetRequest({ url: server.url, path, query: customQuery, expectedStatus: HttpStatusCode.OK_200 })
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('Should fail with invalid uuids', async function () {
|
||||||
|
const customQuery = { ...query, uuids: [ '6565', 'dfd70b83-639f-4980-94af-304a56ab4b35' ] }
|
||||||
|
await makeGetRequest({ url: server.url, path, query: customQuery, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should succeed with valid uuids', async function () {
|
||||||
|
const customQuery = { ...query, uuids: [ 'dfd70b83-639f-4980-94af-304a56ab4b35' ] }
|
||||||
|
await makeGetRequest({ url: server.url, path, query: customQuery, expectedStatus: HttpStatusCode.OK_200 })
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('When searching video playlists', function () {
|
describe('When searching video playlists', function () {
|
||||||
|
@ -172,6 +182,11 @@ describe('Test videos API validator', function () {
|
||||||
await makeGetRequest({ url: server.url, path, query: { ...query, host: '6565' }, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
|
await makeGetRequest({ url: server.url, path, query: { ...query, host: '6565' }, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('Should fail with invalid uuids', async function () {
|
||||||
|
const customQuery = { ...query, uuids: [ '6565', 'dfd70b83-639f-4980-94af-304a56ab4b35' ] }
|
||||||
|
await makeGetRequest({ url: server.url, path, query: customQuery, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
|
||||||
|
})
|
||||||
|
|
||||||
it('Should succeed with the correct parameters', async function () {
|
it('Should succeed with the correct parameters', async function () {
|
||||||
await makeGetRequest({ url: server.url, path, query, expectedStatus: HttpStatusCode.OK_200 })
|
await makeGetRequest({ url: server.url, path, query, expectedStatus: HttpStatusCode.OK_200 })
|
||||||
})
|
})
|
||||||
|
|
|
@ -22,8 +22,12 @@ describe('Test channels search', function () {
|
||||||
before(async function () {
|
before(async function () {
|
||||||
this.timeout(120000)
|
this.timeout(120000)
|
||||||
|
|
||||||
server = await createSingleServer(1)
|
const servers = await Promise.all([
|
||||||
remoteServer = await createSingleServer(2, { transcoding: { enabled: false } })
|
createSingleServer(1),
|
||||||
|
createSingleServer(2, { transcoding: { enabled: false } })
|
||||||
|
])
|
||||||
|
server = servers[0]
|
||||||
|
remoteServer = servers[1]
|
||||||
|
|
||||||
await setAccessTokensToServers([ server, remoteServer ])
|
await setAccessTokensToServers([ server, remoteServer ])
|
||||||
|
|
||||||
|
@ -116,6 +120,22 @@ describe('Test channels search', function () {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('Should filter by names', async function () {
|
||||||
|
{
|
||||||
|
const body = await command.advancedChannelSearch({ search: { names: [ 'squall_channel', 'zell_channel' ] } })
|
||||||
|
expect(body.total).to.equal(2)
|
||||||
|
expect(body.data).to.have.lengthOf(2)
|
||||||
|
expect(body.data[0].displayName).to.equal('Squall channel')
|
||||||
|
expect(body.data[1].displayName).to.equal('Zell channel')
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const body = await command.advancedChannelSearch({ search: { names: [ 'chocobozzz_channel' ] } })
|
||||||
|
expect(body.total).to.equal(0)
|
||||||
|
expect(body.data).to.have.lengthOf(0)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
after(async function () {
|
after(async function () {
|
||||||
await cleanupTests([ server ])
|
await cleanupTests([ server ])
|
||||||
})
|
})
|
||||||
|
|
|
@ -19,12 +19,18 @@ describe('Test playlists search', function () {
|
||||||
let server: PeerTubeServer
|
let server: PeerTubeServer
|
||||||
let remoteServer: PeerTubeServer
|
let remoteServer: PeerTubeServer
|
||||||
let command: SearchCommand
|
let command: SearchCommand
|
||||||
|
let playlistUUID: string
|
||||||
|
let playlistShortUUID: string
|
||||||
|
|
||||||
before(async function () {
|
before(async function () {
|
||||||
this.timeout(120000)
|
this.timeout(120000)
|
||||||
|
|
||||||
server = await createSingleServer(1)
|
const servers = await Promise.all([
|
||||||
remoteServer = await createSingleServer(2, { transcoding: { enabled: false } })
|
createSingleServer(1),
|
||||||
|
createSingleServer(2, { transcoding: { enabled: false } })
|
||||||
|
])
|
||||||
|
server = servers[0]
|
||||||
|
remoteServer = servers[1]
|
||||||
|
|
||||||
await setAccessTokensToServers([ remoteServer, server ])
|
await setAccessTokensToServers([ remoteServer, server ])
|
||||||
await setDefaultVideoChannel([ remoteServer, server ])
|
await setDefaultVideoChannel([ remoteServer, server ])
|
||||||
|
@ -38,6 +44,8 @@ describe('Test playlists search', function () {
|
||||||
videoChannelId: server.store.channel.id
|
videoChannelId: server.store.channel.id
|
||||||
}
|
}
|
||||||
const created = await server.playlists.create({ attributes })
|
const created = await server.playlists.create({ attributes })
|
||||||
|
playlistUUID = created.uuid
|
||||||
|
playlistShortUUID = created.shortUUID
|
||||||
|
|
||||||
await server.playlists.addElement({ playlistId: created.id, attributes: { videoId } })
|
await server.playlists.addElement({ playlistId: created.id, attributes: { videoId } })
|
||||||
}
|
}
|
||||||
|
@ -136,6 +144,22 @@ describe('Test playlists search', function () {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('Should filter by UUIDs', async function () {
|
||||||
|
for (const uuid of [ playlistUUID, playlistShortUUID ]) {
|
||||||
|
const body = await command.advancedPlaylistSearch({ search: { uuids: [ uuid ] } })
|
||||||
|
|
||||||
|
expect(body.total).to.equal(1)
|
||||||
|
expect(body.data[0].displayName).to.equal('Dr. Kenzo Tenma hospital videos')
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const body = await command.advancedPlaylistSearch({ search: { uuids: [ 'dfd70b83-639f-4980-94af-304a56ab4b35' ] } })
|
||||||
|
|
||||||
|
expect(body.total).to.equal(0)
|
||||||
|
expect(body.data).to.have.lengthOf(0)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
it('Should not display playlists without videos', async function () {
|
it('Should not display playlists without videos', async function () {
|
||||||
const search = {
|
const search = {
|
||||||
search: 'Lunge',
|
search: 'Lunge',
|
||||||
|
|
|
@ -22,14 +22,19 @@ describe('Test videos search', function () {
|
||||||
let remoteServer: PeerTubeServer
|
let remoteServer: PeerTubeServer
|
||||||
let startDate: string
|
let startDate: string
|
||||||
let videoUUID: string
|
let videoUUID: string
|
||||||
|
let videoShortUUID: string
|
||||||
|
|
||||||
let command: SearchCommand
|
let command: SearchCommand
|
||||||
|
|
||||||
before(async function () {
|
before(async function () {
|
||||||
this.timeout(120000)
|
this.timeout(120000)
|
||||||
|
|
||||||
server = await createSingleServer(1)
|
const servers = await Promise.all([
|
||||||
remoteServer = await createSingleServer(2)
|
createSingleServer(1),
|
||||||
|
createSingleServer(2)
|
||||||
|
])
|
||||||
|
server = servers[0]
|
||||||
|
remoteServer = servers[1]
|
||||||
|
|
||||||
await setAccessTokensToServers([ server, remoteServer ])
|
await setAccessTokensToServers([ server, remoteServer ])
|
||||||
await setDefaultVideoChannel([ server, remoteServer ])
|
await setDefaultVideoChannel([ server, remoteServer ])
|
||||||
|
@ -50,8 +55,9 @@ describe('Test videos search', function () {
|
||||||
|
|
||||||
{
|
{
|
||||||
const attributes3 = { ...attributes1, name: attributes1.name + ' - 3', language: undefined }
|
const attributes3 = { ...attributes1, name: attributes1.name + ' - 3', language: undefined }
|
||||||
const { id, uuid } = await server.videos.upload({ attributes: attributes3 })
|
const { id, uuid, shortUUID } = await server.videos.upload({ attributes: attributes3 })
|
||||||
videoUUID = uuid
|
videoUUID = uuid
|
||||||
|
videoShortUUID = shortUUID
|
||||||
|
|
||||||
await server.captions.add({
|
await server.captions.add({
|
||||||
language: 'en',
|
language: 'en',
|
||||||
|
@ -479,6 +485,22 @@ describe('Test videos search', function () {
|
||||||
expect(body.data[0].name).to.equal('1111 2222 3333 - 3')
|
expect(body.data[0].name).to.equal('1111 2222 3333 - 3')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('Should filter by UUIDs', async function () {
|
||||||
|
for (const uuid of [ videoUUID, videoShortUUID ]) {
|
||||||
|
const body = await command.advancedVideoSearch({ search: { uuids: [ uuid ] } })
|
||||||
|
|
||||||
|
expect(body.total).to.equal(1)
|
||||||
|
expect(body.data[0].name).to.equal('1111 2222 3333 - 3')
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const body = await command.advancedVideoSearch({ search: { uuids: [ 'dfd70b83-639f-4980-94af-304a56ab4b35' ] } })
|
||||||
|
|
||||||
|
expect(body.total).to.equal(0)
|
||||||
|
expect(body.data).to.have.lengthOf(0)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
it('Should search by host', async function () {
|
it('Should search by host', async function () {
|
||||||
{
|
{
|
||||||
const body = await command.advancedVideoSearch({ search: { search: '6666 7777 8888', host: server.host } })
|
const body = await command.advancedVideoSearch({ search: { search: '6666 7777 8888', host: server.host } })
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
import { SearchTargetQuery } from './search-target-query.model'
|
import { SearchTargetQuery } from './search-target-query.model'
|
||||||
|
|
||||||
export interface VideoChannelsSearchQuery extends SearchTargetQuery {
|
export interface VideoChannelsSearchQuery extends SearchTargetQuery {
|
||||||
search: string
|
search?: string
|
||||||
|
|
||||||
start?: number
|
start?: number
|
||||||
count?: number
|
count?: number
|
||||||
sort?: string
|
sort?: string
|
||||||
|
|
||||||
host?: string
|
host?: string
|
||||||
|
names?: string[]
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
import { SearchTargetQuery } from './search-target-query.model'
|
import { SearchTargetQuery } from './search-target-query.model'
|
||||||
|
|
||||||
export interface VideoPlaylistsSearchQuery extends SearchTargetQuery {
|
export interface VideoPlaylistsSearchQuery extends SearchTargetQuery {
|
||||||
search: string
|
search?: string
|
||||||
|
|
||||||
start?: number
|
start?: number
|
||||||
count?: number
|
count?: number
|
||||||
sort?: string
|
sort?: string
|
||||||
|
|
||||||
host?: string
|
host?: string
|
||||||
|
uuids?: string[]
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,4 +14,7 @@ export interface VideosSearchQuery extends SearchTargetQuery, VideosCommonQuery
|
||||||
|
|
||||||
durationMin?: number // seconds
|
durationMin?: number // seconds
|
||||||
durationMax?: number // seconds
|
durationMax?: number // seconds
|
||||||
|
|
||||||
|
// UUIDs or short
|
||||||
|
uuids?: string[]
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue