mirror of https://github.com/Chocobozzz/PeerTube
Filter host for channels and playlists search
parent
f68d1cb6ac
commit
fa47956ecf
|
@ -98,7 +98,8 @@ async function searchVideoChannelsDB (query: VideoChannelsSearchQuery, res: expr
|
||||||
search: query.search,
|
search: query.search,
|
||||||
start: query.start,
|
start: query.start,
|
||||||
count: query.count,
|
count: query.count,
|
||||||
sort: query.sort
|
sort: query.sort,
|
||||||
|
host: query.host
|
||||||
}, '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(
|
||||||
|
|
|
@ -88,7 +88,8 @@ async function searchVideoPlaylistsDB (query: VideoPlaylistsSearchQuery, res: ex
|
||||||
search: query.search,
|
search: query.search,
|
||||||
start: query.start,
|
start: query.start,
|
||||||
count: query.count,
|
count: query.count,
|
||||||
sort: query.sort
|
sort: query.sort,
|
||||||
|
host: query.host
|
||||||
}, '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(
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import * as retry from 'async/retry'
|
import * as retry from 'async/retry'
|
||||||
import * as Bluebird from 'bluebird'
|
import * as Bluebird from 'bluebird'
|
||||||
import { BindOrReplacements, QueryTypes, Transaction } from 'sequelize'
|
import { Transaction } from 'sequelize'
|
||||||
import { Model } from 'sequelize-typescript'
|
import { Model } from 'sequelize-typescript'
|
||||||
import { sequelizeTypescript } from '@server/initializers/database'
|
import { sequelizeTypescript } from '@server/initializers/database'
|
||||||
import { logger } from './logger'
|
import { logger } from './logger'
|
||||||
|
@ -95,18 +95,6 @@ function deleteAllModels <T extends Pick<Model, 'destroy'>> (models: T[], transa
|
||||||
return Promise.all(models.map(f => f.destroy({ transaction })))
|
return Promise.all(models.map(f => f.destroy({ transaction })))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sequelize always skip the update if we only update updatedAt field
|
|
||||||
function setAsUpdated (table: string, id: number, transaction?: Transaction) {
|
|
||||||
return sequelizeTypescript.query(
|
|
||||||
`UPDATE "${table}" SET "updatedAt" = :updatedAt WHERE id = :id`,
|
|
||||||
{
|
|
||||||
replacements: { table, id, updatedAt: new Date() },
|
|
||||||
type: QueryTypes.UPDATE,
|
|
||||||
transaction
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
function runInReadCommittedTransaction <T> (fn: (t: Transaction) => Promise<T>) {
|
function runInReadCommittedTransaction <T> (fn: (t: Transaction) => Promise<T>) {
|
||||||
|
@ -123,19 +111,6 @@ function afterCommitIfTransaction (t: Transaction, fn: Function) {
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
function doesExist (query: string, bind?: BindOrReplacements) {
|
|
||||||
const options = {
|
|
||||||
type: QueryTypes.SELECT as QueryTypes.SELECT,
|
|
||||||
bind,
|
|
||||||
raw: true
|
|
||||||
}
|
|
||||||
|
|
||||||
return sequelizeTypescript.query(query, options)
|
|
||||||
.then(results => results.length === 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
export {
|
export {
|
||||||
resetSequelizeInstance,
|
resetSequelizeInstance,
|
||||||
retryTransactionWrapper,
|
retryTransactionWrapper,
|
||||||
|
@ -144,7 +119,5 @@ export {
|
||||||
afterCommitIfTransaction,
|
afterCommitIfTransaction,
|
||||||
filterNonExistingModels,
|
filterNonExistingModels,
|
||||||
deleteAllModels,
|
deleteAllModels,
|
||||||
setAsUpdated,
|
runInReadCommittedTransaction
|
||||||
runInReadCommittedTransaction,
|
|
||||||
doesExist
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,7 +43,14 @@ const videosSearchValidator = [
|
||||||
|
|
||||||
const videoChannelsListSearchValidator = [
|
const videoChannelsListSearchValidator = [
|
||||||
query('search').not().isEmpty().withMessage('Should have a valid search'),
|
query('search').not().isEmpty().withMessage('Should have a valid search'),
|
||||||
query('searchTarget').optional().custom(isSearchTargetValid).withMessage('Should have a valid search target'),
|
|
||||||
|
query('host')
|
||||||
|
.optional()
|
||||||
|
.custom(isHostValid).withMessage('Should have a valid host'),
|
||||||
|
|
||||||
|
query('searchTarget')
|
||||||
|
.optional()
|
||||||
|
.custom(isSearchTargetValid).withMessage('Should have a valid search target'),
|
||||||
|
|
||||||
(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 })
|
||||||
|
@ -56,7 +63,14 @@ const videoChannelsListSearchValidator = [
|
||||||
|
|
||||||
const videoPlaylistsListSearchValidator = [
|
const videoPlaylistsListSearchValidator = [
|
||||||
query('search').not().isEmpty().withMessage('Should have a valid search'),
|
query('search').not().isEmpty().withMessage('Should have a valid search'),
|
||||||
query('searchTarget').optional().custom(isSearchTargetValid).withMessage('Should have a valid search target'),
|
|
||||||
|
query('host')
|
||||||
|
.optional()
|
||||||
|
.custom(isHostValid).withMessage('Should have a valid host'),
|
||||||
|
|
||||||
|
query('searchTarget')
|
||||||
|
.optional()
|
||||||
|
.custom(isSearchTargetValid).withMessage('Should have a valid search target'),
|
||||||
|
|
||||||
(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 })
|
||||||
|
|
|
@ -52,6 +52,7 @@ export enum ScopeNames {
|
||||||
export type SummaryOptions = {
|
export type SummaryOptions = {
|
||||||
actorRequired?: boolean // Default: true
|
actorRequired?: boolean // Default: true
|
||||||
whereActor?: WhereOptions
|
whereActor?: WhereOptions
|
||||||
|
whereServer?: WhereOptions
|
||||||
withAccountBlockerIds?: number[]
|
withAccountBlockerIds?: number[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -65,12 +66,11 @@ export type SummaryOptions = {
|
||||||
}))
|
}))
|
||||||
@Scopes(() => ({
|
@Scopes(() => ({
|
||||||
[ScopeNames.SUMMARY]: (options: SummaryOptions = {}) => {
|
[ScopeNames.SUMMARY]: (options: SummaryOptions = {}) => {
|
||||||
const whereActor = options.whereActor || undefined
|
|
||||||
|
|
||||||
const serverInclude: IncludeOptions = {
|
const serverInclude: IncludeOptions = {
|
||||||
attributes: [ 'host' ],
|
attributes: [ 'host' ],
|
||||||
model: ServerModel.unscoped(),
|
model: ServerModel.unscoped(),
|
||||||
required: false
|
required: !!options.whereServer,
|
||||||
|
where: options.whereServer
|
||||||
}
|
}
|
||||||
|
|
||||||
const queryInclude: Includeable[] = [
|
const queryInclude: Includeable[] = [
|
||||||
|
@ -78,7 +78,7 @@ export type SummaryOptions = {
|
||||||
attributes: [ 'id', 'preferredUsername', 'url', 'serverId', 'avatarId' ],
|
attributes: [ 'id', 'preferredUsername', 'url', 'serverId', 'avatarId' ],
|
||||||
model: ActorModel.unscoped(),
|
model: ActorModel.unscoped(),
|
||||||
required: options.actorRequired ?? true,
|
required: options.actorRequired ?? true,
|
||||||
where: whereActor,
|
where: options.whereActor,
|
||||||
include: [
|
include: [
|
||||||
serverInclude,
|
serverInclude,
|
||||||
|
|
||||||
|
|
|
@ -19,7 +19,6 @@ import {
|
||||||
UpdatedAt
|
UpdatedAt
|
||||||
} from 'sequelize-typescript'
|
} from 'sequelize-typescript'
|
||||||
import { isActivityPubUrlValid } from '@server/helpers/custom-validators/activitypub/misc'
|
import { isActivityPubUrlValid } from '@server/helpers/custom-validators/activitypub/misc'
|
||||||
import { doesExist } from '@server/helpers/database-utils'
|
|
||||||
import { getServerActor } from '@server/models/application/application'
|
import { getServerActor } from '@server/models/application/application'
|
||||||
import {
|
import {
|
||||||
MActorFollowActorsDefault,
|
MActorFollowActorsDefault,
|
||||||
|
@ -36,6 +35,7 @@ import { logger } from '../../helpers/logger'
|
||||||
import { ACTOR_FOLLOW_SCORE, CONSTRAINTS_FIELDS, FOLLOW_STATES, SERVER_ACTOR_NAME } from '../../initializers/constants'
|
import { ACTOR_FOLLOW_SCORE, CONSTRAINTS_FIELDS, FOLLOW_STATES, SERVER_ACTOR_NAME } from '../../initializers/constants'
|
||||||
import { AccountModel } from '../account/account'
|
import { AccountModel } from '../account/account'
|
||||||
import { ServerModel } from '../server/server'
|
import { ServerModel } from '../server/server'
|
||||||
|
import { doesExist } from '../shared/query'
|
||||||
import { createSafeIn, getFollowsSort, getSort, searchAttribute, throwIfNotValid } from '../utils'
|
import { createSafeIn, getFollowsSort, getSort, searchAttribute, throwIfNotValid } from '../utils'
|
||||||
import { VideoChannelModel } from '../video/video-channel'
|
import { VideoChannelModel } from '../video/video-channel'
|
||||||
import { ActorModel, unusedActorAttributesForAPI } from './actor'
|
import { ActorModel, unusedActorAttributesForAPI } from './actor'
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
export * from './query'
|
||||||
|
export * from './update'
|
|
@ -0,0 +1,17 @@
|
||||||
|
import { BindOrReplacements, QueryTypes } from 'sequelize'
|
||||||
|
import { sequelizeTypescript } from '@server/initializers/database'
|
||||||
|
|
||||||
|
function doesExist (query: string, bind?: BindOrReplacements) {
|
||||||
|
const options = {
|
||||||
|
type: QueryTypes.SELECT as QueryTypes.SELECT,
|
||||||
|
bind,
|
||||||
|
raw: true
|
||||||
|
}
|
||||||
|
|
||||||
|
return sequelizeTypescript.query(query, options)
|
||||||
|
.then(results => results.length === 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
doesExist
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
import { QueryTypes, Transaction } from 'sequelize'
|
||||||
|
import { sequelizeTypescript } from '@server/initializers/database'
|
||||||
|
|
||||||
|
// Sequelize always skip the update if we only update updatedAt field
|
||||||
|
function setAsUpdated (table: string, id: number, transaction?: Transaction) {
|
||||||
|
return sequelizeTypescript.query(
|
||||||
|
`UPDATE "${table}" SET "updatedAt" = :updatedAt WHERE id = :id`,
|
||||||
|
{
|
||||||
|
replacements: { table, id, updatedAt: new Date() },
|
||||||
|
type: QueryTypes.UPDATE,
|
||||||
|
transaction
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
setAsUpdated
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
import { FindOptions, Includeable, literal, Op, QueryTypes, ScopeOptions, Transaction } from 'sequelize'
|
import { FindOptions, Includeable, literal, Op, QueryTypes, ScopeOptions, Transaction, WhereOptions } from 'sequelize'
|
||||||
import {
|
import {
|
||||||
AllowNull,
|
AllowNull,
|
||||||
BeforeDestroy,
|
BeforeDestroy,
|
||||||
|
@ -17,7 +17,6 @@ import {
|
||||||
Table,
|
Table,
|
||||||
UpdatedAt
|
UpdatedAt
|
||||||
} from 'sequelize-typescript'
|
} from 'sequelize-typescript'
|
||||||
import { setAsUpdated } from '@server/helpers/database-utils'
|
|
||||||
import { MAccountActor } from '@server/types/models'
|
import { MAccountActor } from '@server/types/models'
|
||||||
import { AttributesOnly } from '@shared/core-utils'
|
import { AttributesOnly } from '@shared/core-utils'
|
||||||
import { ActivityPubActor } from '../../../shared/models/activitypub'
|
import { ActivityPubActor } from '../../../shared/models/activitypub'
|
||||||
|
@ -41,6 +40,7 @@ import { ActorModel, unusedActorAttributesForAPI } from '../actor/actor'
|
||||||
import { ActorFollowModel } from '../actor/actor-follow'
|
import { ActorFollowModel } from '../actor/actor-follow'
|
||||||
import { ActorImageModel } from '../actor/actor-image'
|
import { ActorImageModel } from '../actor/actor-image'
|
||||||
import { ServerModel } from '../server/server'
|
import { ServerModel } from '../server/server'
|
||||||
|
import { setAsUpdated } from '../shared'
|
||||||
import { buildServerIdsFollowedBy, buildTrigramSearchIndex, createSimilarityAttribute, getSort, throwIfNotValid } from '../utils'
|
import { buildServerIdsFollowedBy, buildTrigramSearchIndex, createSimilarityAttribute, getSort, throwIfNotValid } from '../utils'
|
||||||
import { VideoModel } from './video'
|
import { VideoModel } from './video'
|
||||||
import { VideoPlaylistModel } from './video-playlist'
|
import { VideoPlaylistModel } from './video-playlist'
|
||||||
|
@ -58,6 +58,7 @@ export enum ScopeNames {
|
||||||
type AvailableForListOptions = {
|
type AvailableForListOptions = {
|
||||||
actorId: number
|
actorId: number
|
||||||
search?: string
|
search?: string
|
||||||
|
host?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
type AvailableWithStatsOptions = {
|
type AvailableWithStatsOptions = {
|
||||||
|
@ -83,14 +84,7 @@ 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)
|
||||||
|
|
||||||
return {
|
const whereActor = {
|
||||||
include: [
|
|
||||||
{
|
|
||||||
attributes: {
|
|
||||||
exclude: unusedActorAttributesForAPI
|
|
||||||
},
|
|
||||||
model: ActorModel,
|
|
||||||
where: {
|
|
||||||
[Op.or]: [
|
[Op.or]: [
|
||||||
{
|
{
|
||||||
serverId: null
|
serverId: null
|
||||||
|
@ -101,8 +95,41 @@ export type SummaryOptions = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
}
|
||||||
|
|
||||||
|
let serverRequired = false
|
||||||
|
let whereServer: WhereOptions
|
||||||
|
|
||||||
|
if (options.host && options.host !== WEBSERVER.HOST) {
|
||||||
|
serverRequired = true
|
||||||
|
whereServer = { host: options.host }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.host === WEBSERVER.HOST) {
|
||||||
|
Object.assign(whereActor, {
|
||||||
|
[Op.and]: [ { serverId: null } ]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
include: [
|
include: [
|
||||||
|
{
|
||||||
|
attributes: {
|
||||||
|
exclude: unusedActorAttributesForAPI
|
||||||
|
},
|
||||||
|
model: ActorModel,
|
||||||
|
where: whereActor,
|
||||||
|
include: [
|
||||||
|
{
|
||||||
|
model: ServerModel,
|
||||||
|
required: serverRequired,
|
||||||
|
where: whereServer
|
||||||
|
},
|
||||||
|
{
|
||||||
|
model: ActorImageModel,
|
||||||
|
as: 'Avatar',
|
||||||
|
required: false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
model: ActorImageModel,
|
model: ActorImageModel,
|
||||||
as: 'Banner',
|
as: 'Banner',
|
||||||
|
@ -431,6 +458,8 @@ ON "Account->Actor"."serverId" = "Account->Actor->Server"."id"`
|
||||||
start: number
|
start: number
|
||||||
count: number
|
count: number
|
||||||
sort: string
|
sort: string
|
||||||
|
|
||||||
|
host?: string
|
||||||
}) {
|
}) {
|
||||||
const attributesInclude = []
|
const attributesInclude = []
|
||||||
const escapedSearch = VideoChannelModel.sequelize.escape(options.search)
|
const escapedSearch = VideoChannelModel.sequelize.escape(options.search)
|
||||||
|
@ -458,7 +487,7 @@ ON "Account->Actor"."serverId" = "Account->Actor->Server"."id"`
|
||||||
|
|
||||||
return VideoChannelModel
|
return VideoChannelModel
|
||||||
.scope({
|
.scope({
|
||||||
method: [ ScopeNames.FOR_API, { actorId: options.actorId } as AvailableForListOptions ]
|
method: [ ScopeNames.FOR_API, { actorId: options.actorId, host: options.host } as AvailableForListOptions ]
|
||||||
})
|
})
|
||||||
.findAndCountAll(query)
|
.findAndCountAll(query)
|
||||||
.then(({ rows, count }) => {
|
.then(({ rows, count }) => {
|
||||||
|
|
|
@ -21,7 +21,6 @@ import {
|
||||||
import { Where } from 'sequelize/types/lib/utils'
|
import { Where } from 'sequelize/types/lib/utils'
|
||||||
import validator from 'validator'
|
import validator from 'validator'
|
||||||
import { buildRemoteVideoBaseUrl } from '@server/helpers/activitypub'
|
import { buildRemoteVideoBaseUrl } from '@server/helpers/activitypub'
|
||||||
import { doesExist } from '@server/helpers/database-utils'
|
|
||||||
import { logger } from '@server/helpers/logger'
|
import { logger } from '@server/helpers/logger'
|
||||||
import { extractVideo } from '@server/helpers/video'
|
import { extractVideo } from '@server/helpers/video'
|
||||||
import { getTorrentFilePath } from '@server/lib/video-paths'
|
import { getTorrentFilePath } from '@server/lib/video-paths'
|
||||||
|
@ -45,6 +44,7 @@ import {
|
||||||
} from '../../initializers/constants'
|
} from '../../initializers/constants'
|
||||||
import { MVideoFile, MVideoFileStreamingPlaylistVideo, MVideoFileVideo } from '../../types/models/video/video-file'
|
import { MVideoFile, MVideoFileStreamingPlaylistVideo, MVideoFileVideo } from '../../types/models/video/video-file'
|
||||||
import { VideoRedundancyModel } from '../redundancy/video-redundancy'
|
import { VideoRedundancyModel } from '../redundancy/video-redundancy'
|
||||||
|
import { doesExist } from '../shared'
|
||||||
import { parseAggregateResult, throwIfNotValid } from '../utils'
|
import { parseAggregateResult, throwIfNotValid } from '../utils'
|
||||||
import { VideoModel } from './video'
|
import { VideoModel } from './video'
|
||||||
import { VideoStreamingPlaylistModel } from './video-streaming-playlist'
|
import { VideoStreamingPlaylistModel } from './video-streaming-playlist'
|
||||||
|
|
|
@ -17,7 +17,6 @@ import {
|
||||||
Table,
|
Table,
|
||||||
UpdatedAt
|
UpdatedAt
|
||||||
} from 'sequelize-typescript'
|
} from 'sequelize-typescript'
|
||||||
import { setAsUpdated } from '@server/helpers/database-utils'
|
|
||||||
import { buildUUID, uuidToShort } from '@server/helpers/uuid'
|
import { buildUUID, uuidToShort } from '@server/helpers/uuid'
|
||||||
import { MAccountId, MChannelId } from '@server/types/models'
|
import { MAccountId, MChannelId } from '@server/types/models'
|
||||||
import { AttributesOnly, buildPlaylistEmbedPath, buildPlaylistWatchPath } from '@shared/core-utils'
|
import { AttributesOnly, buildPlaylistEmbedPath, buildPlaylistWatchPath } from '@shared/core-utils'
|
||||||
|
@ -53,6 +52,7 @@ import {
|
||||||
} from '../../types/models/video/video-playlist'
|
} from '../../types/models/video/video-playlist'
|
||||||
import { AccountModel, ScopeNames as AccountScopeNames, SummaryOptions } from '../account/account'
|
import { AccountModel, ScopeNames as AccountScopeNames, SummaryOptions } from '../account/account'
|
||||||
import { ActorModel } from '../actor/actor'
|
import { ActorModel } from '../actor/actor'
|
||||||
|
import { setAsUpdated } from '../shared'
|
||||||
import {
|
import {
|
||||||
buildServerIdsFollowedBy,
|
buildServerIdsFollowedBy,
|
||||||
buildTrigramSearchIndex,
|
buildTrigramSearchIndex,
|
||||||
|
@ -82,6 +82,7 @@ type AvailableForListOptions = {
|
||||||
videoChannelId?: number
|
videoChannelId?: number
|
||||||
listMyPlaylists?: boolean
|
listMyPlaylists?: boolean
|
||||||
search?: string
|
search?: string
|
||||||
|
host?: string
|
||||||
withVideos?: boolean
|
withVideos?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -141,9 +142,19 @@ function getVideoLengthSelect () {
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
[ScopeNames.AVAILABLE_FOR_LIST]: (options: AvailableForListOptions) => {
|
[ScopeNames.AVAILABLE_FOR_LIST]: (options: AvailableForListOptions) => {
|
||||||
|
const whereAnd: WhereOptions[] = []
|
||||||
|
|
||||||
|
const whereServer = options.host && options.host !== WEBSERVER.HOST
|
||||||
|
? { host: options.host }
|
||||||
|
: undefined
|
||||||
|
|
||||||
let whereActor: WhereOptions = {}
|
let whereActor: WhereOptions = {}
|
||||||
|
|
||||||
const whereAnd: WhereOptions[] = []
|
if (options.host === WEBSERVER.HOST) {
|
||||||
|
whereActor = {
|
||||||
|
[Op.and]: [ { serverId: null } ]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (options.listMyPlaylists !== true) {
|
if (options.listMyPlaylists !== true) {
|
||||||
whereAnd.push({
|
whereAnd.push({
|
||||||
|
@ -168,9 +179,7 @@ function getVideoLengthSelect () {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
whereActor = {
|
Object.assign(whereActor, { [Op.or]: whereActorOr })
|
||||||
[Op.or]: whereActorOr
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.accountId) {
|
if (options.accountId) {
|
||||||
|
@ -228,7 +237,7 @@ function getVideoLengthSelect () {
|
||||||
include: [
|
include: [
|
||||||
{
|
{
|
||||||
model: AccountModel.scope({
|
model: AccountModel.scope({
|
||||||
method: [ AccountScopeNames.SUMMARY, { whereActor } as SummaryOptions ]
|
method: [ AccountScopeNames.SUMMARY, { whereActor, whereServer } as SummaryOptions ]
|
||||||
}),
|
}),
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
|
@ -349,6 +358,7 @@ export class VideoPlaylistModel extends Model<Partial<AttributesOnly<VideoPlayli
|
||||||
videoChannelId?: number
|
videoChannelId?: number
|
||||||
listMyPlaylists?: boolean
|
listMyPlaylists?: boolean
|
||||||
search?: string
|
search?: string
|
||||||
|
host?: string
|
||||||
withVideos?: boolean // false by default
|
withVideos?: boolean // false by default
|
||||||
}) {
|
}) {
|
||||||
const query = {
|
const query = {
|
||||||
|
@ -368,6 +378,7 @@ export class VideoPlaylistModel extends Model<Partial<AttributesOnly<VideoPlayli
|
||||||
videoChannelId: options.videoChannelId,
|
videoChannelId: options.videoChannelId,
|
||||||
listMyPlaylists: options.listMyPlaylists,
|
listMyPlaylists: options.listMyPlaylists,
|
||||||
search: options.search,
|
search: options.search,
|
||||||
|
host: options.host,
|
||||||
withVideos: options.withVideos || false
|
withVideos: options.withVideos || false
|
||||||
} as AvailableForListOptions
|
} as AvailableForListOptions
|
||||||
]
|
]
|
||||||
|
@ -390,6 +401,7 @@ export class VideoPlaylistModel extends Model<Partial<AttributesOnly<VideoPlayli
|
||||||
count: number
|
count: number
|
||||||
sort: string
|
sort: string
|
||||||
search?: string
|
search?: string
|
||||||
|
host?: string
|
||||||
}) {
|
}) {
|
||||||
return VideoPlaylistModel.listForApi({
|
return VideoPlaylistModel.listForApi({
|
||||||
...options,
|
...options,
|
||||||
|
|
|
@ -2,7 +2,6 @@ import * as memoizee from 'memoizee'
|
||||||
import { join } from 'path'
|
import { join } from 'path'
|
||||||
import { Op } from 'sequelize'
|
import { Op } from 'sequelize'
|
||||||
import { AllowNull, BelongsTo, Column, CreatedAt, DataType, ForeignKey, HasMany, Is, Model, Table, UpdatedAt } from 'sequelize-typescript'
|
import { AllowNull, BelongsTo, Column, CreatedAt, DataType, ForeignKey, HasMany, Is, Model, Table, UpdatedAt } from 'sequelize-typescript'
|
||||||
import { doesExist } from '@server/helpers/database-utils'
|
|
||||||
import { VideoFileModel } from '@server/models/video/video-file'
|
import { VideoFileModel } from '@server/models/video/video-file'
|
||||||
import { MStreamingPlaylist, MVideo } from '@server/types/models'
|
import { MStreamingPlaylist, MVideo } from '@server/types/models'
|
||||||
import { AttributesOnly } from '@shared/core-utils'
|
import { AttributesOnly } from '@shared/core-utils'
|
||||||
|
@ -20,6 +19,7 @@ import {
|
||||||
WEBSERVER
|
WEBSERVER
|
||||||
} from '../../initializers/constants'
|
} from '../../initializers/constants'
|
||||||
import { VideoRedundancyModel } from '../redundancy/video-redundancy'
|
import { VideoRedundancyModel } from '../redundancy/video-redundancy'
|
||||||
|
import { doesExist } from '../shared'
|
||||||
import { throwIfNotValid } from '../utils'
|
import { throwIfNotValid } from '../utils'
|
||||||
import { VideoModel } from './video'
|
import { VideoModel } from './video'
|
||||||
|
|
||||||
|
|
|
@ -24,7 +24,6 @@ import {
|
||||||
Table,
|
Table,
|
||||||
UpdatedAt
|
UpdatedAt
|
||||||
} from 'sequelize-typescript'
|
} from 'sequelize-typescript'
|
||||||
import { setAsUpdated } from '@server/helpers/database-utils'
|
|
||||||
import { buildNSFWFilter } from '@server/helpers/express-utils'
|
import { buildNSFWFilter } from '@server/helpers/express-utils'
|
||||||
import { uuidToShort } from '@server/helpers/uuid'
|
import { uuidToShort } from '@server/helpers/uuid'
|
||||||
import { getPrivaciesForFederation, isPrivacyForFederation, isStateForFederation } from '@server/helpers/video'
|
import { getPrivaciesForFederation, isPrivacyForFederation, isStateForFederation } from '@server/helpers/video'
|
||||||
|
@ -92,6 +91,7 @@ import { VideoRedundancyModel } from '../redundancy/video-redundancy'
|
||||||
import { ServerModel } from '../server/server'
|
import { ServerModel } from '../server/server'
|
||||||
import { TrackerModel } from '../server/tracker'
|
import { TrackerModel } from '../server/tracker'
|
||||||
import { VideoTrackerModel } from '../server/video-tracker'
|
import { VideoTrackerModel } from '../server/video-tracker'
|
||||||
|
import { setAsUpdated } from '../shared'
|
||||||
import { UserModel } from '../user/user'
|
import { UserModel } from '../user/user'
|
||||||
import { UserVideoHistoryModel } from '../user/user-video-history'
|
import { UserVideoHistoryModel } from '../user/user-video-history'
|
||||||
import { buildTrigramSearchIndex, buildWhereIdOrUUID, getVideoSort, isOutdated, throwIfNotValid } from '../utils'
|
import { buildTrigramSearchIndex, buildWhereIdOrUUID, getVideoSort, isOutdated, throwIfNotValid } from '../utils'
|
||||||
|
|
|
@ -2,24 +2,33 @@
|
||||||
|
|
||||||
import 'mocha'
|
import 'mocha'
|
||||||
import * as chai from 'chai'
|
import * as chai from 'chai'
|
||||||
import { cleanupTests, createSingleServer, PeerTubeServer, SearchCommand, setAccessTokensToServers } from '@shared/extra-utils'
|
import {
|
||||||
|
cleanupTests,
|
||||||
|
createSingleServer,
|
||||||
|
doubleFollow,
|
||||||
|
PeerTubeServer,
|
||||||
|
SearchCommand,
|
||||||
|
setAccessTokensToServers
|
||||||
|
} from '@shared/extra-utils'
|
||||||
import { VideoChannel } from '@shared/models'
|
import { VideoChannel } from '@shared/models'
|
||||||
|
|
||||||
const expect = chai.expect
|
const expect = chai.expect
|
||||||
|
|
||||||
describe('Test channels search', function () {
|
describe('Test channels search', function () {
|
||||||
let server: PeerTubeServer = null
|
let server: PeerTubeServer
|
||||||
|
let remoteServer: PeerTubeServer
|
||||||
let command: SearchCommand
|
let command: SearchCommand
|
||||||
|
|
||||||
before(async function () {
|
before(async function () {
|
||||||
this.timeout(30000)
|
this.timeout(30000)
|
||||||
|
|
||||||
server = await createSingleServer(1)
|
server = await createSingleServer(1)
|
||||||
|
remoteServer = await createSingleServer(2, { transcoding: { enabled: false } })
|
||||||
|
|
||||||
await setAccessTokensToServers([ server ])
|
await setAccessTokensToServers([ server, remoteServer ])
|
||||||
|
|
||||||
{
|
{
|
||||||
await server.users.create({ username: 'user1', password: 'password' })
|
await server.users.create({ username: 'user1' })
|
||||||
const channel = {
|
const channel = {
|
||||||
name: 'squall_channel',
|
name: 'squall_channel',
|
||||||
displayName: 'Squall channel'
|
displayName: 'Squall channel'
|
||||||
|
@ -27,6 +36,19 @@ describe('Test channels search', function () {
|
||||||
await server.channels.create({ attributes: channel })
|
await server.channels.create({ attributes: channel })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
await remoteServer.users.create({ username: 'user1' })
|
||||||
|
const channel = {
|
||||||
|
name: 'zell_channel',
|
||||||
|
displayName: 'Zell channel'
|
||||||
|
}
|
||||||
|
const { id } = await remoteServer.channels.create({ attributes: channel })
|
||||||
|
|
||||||
|
await remoteServer.videos.upload({ attributes: { channelId: id } })
|
||||||
|
}
|
||||||
|
|
||||||
|
await doubleFollow(server, remoteServer)
|
||||||
|
|
||||||
command = server.search
|
command = server.search
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -66,6 +88,34 @@ describe('Test channels search', function () {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('Should filter by host', async function () {
|
||||||
|
{
|
||||||
|
const search = { search: 'channel', host: remoteServer.host }
|
||||||
|
|
||||||
|
const body = await command.advancedChannelSearch({ search })
|
||||||
|
expect(body.total).to.equal(1)
|
||||||
|
expect(body.data).to.have.lengthOf(1)
|
||||||
|
expect(body.data[0].displayName).to.equal('Zell channel')
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const search = { search: 'Sq', host: server.host }
|
||||||
|
|
||||||
|
const body = await command.advancedChannelSearch({ search })
|
||||||
|
expect(body.total).to.equal(1)
|
||||||
|
expect(body.data).to.have.lengthOf(1)
|
||||||
|
expect(body.data[0].displayName).to.equal('Squall channel')
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const search = { search: 'Squall', host: 'example.com' }
|
||||||
|
|
||||||
|
const body = await command.advancedChannelSearch({ search })
|
||||||
|
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 ])
|
||||||
})
|
})
|
||||||
|
|
|
@ -5,6 +5,7 @@ import * as chai from 'chai'
|
||||||
import {
|
import {
|
||||||
cleanupTests,
|
cleanupTests,
|
||||||
createSingleServer,
|
createSingleServer,
|
||||||
|
doubleFollow,
|
||||||
PeerTubeServer,
|
PeerTubeServer,
|
||||||
SearchCommand,
|
SearchCommand,
|
||||||
setAccessTokensToServers,
|
setAccessTokensToServers,
|
||||||
|
@ -15,20 +16,22 @@ import { VideoPlaylistPrivacy } from '@shared/models'
|
||||||
const expect = chai.expect
|
const expect = chai.expect
|
||||||
|
|
||||||
describe('Test playlists search', function () {
|
describe('Test playlists search', function () {
|
||||||
let server: PeerTubeServer = null
|
let server: PeerTubeServer
|
||||||
|
let remoteServer: PeerTubeServer
|
||||||
let command: SearchCommand
|
let command: SearchCommand
|
||||||
|
|
||||||
before(async function () {
|
before(async function () {
|
||||||
this.timeout(30000)
|
this.timeout(30000)
|
||||||
|
|
||||||
server = await createSingleServer(1)
|
server = await createSingleServer(1)
|
||||||
|
remoteServer = await createSingleServer(2, { transcoding: { enabled: false } })
|
||||||
|
|
||||||
await setAccessTokensToServers([ server ])
|
await setAccessTokensToServers([ remoteServer, server ])
|
||||||
await setDefaultVideoChannel([ server ])
|
await setDefaultVideoChannel([ remoteServer, server ])
|
||||||
|
|
||||||
const videoId = (await server.videos.quickUpload({ name: 'video' })).uuid
|
|
||||||
|
|
||||||
{
|
{
|
||||||
|
const videoId = (await server.videos.upload()).uuid
|
||||||
|
|
||||||
const attributes = {
|
const attributes = {
|
||||||
displayName: 'Dr. Kenzo Tenma hospital videos',
|
displayName: 'Dr. Kenzo Tenma hospital videos',
|
||||||
privacy: VideoPlaylistPrivacy.PUBLIC,
|
privacy: VideoPlaylistPrivacy.PUBLIC,
|
||||||
|
@ -40,14 +43,16 @@ describe('Test playlists search', function () {
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
const attributes = {
|
const videoId = (await remoteServer.videos.upload()).uuid
|
||||||
displayName: 'Johan & Anna Libert musics',
|
|
||||||
privacy: VideoPlaylistPrivacy.PUBLIC,
|
|
||||||
videoChannelId: server.store.channel.id
|
|
||||||
}
|
|
||||||
const created = await server.playlists.create({ attributes })
|
|
||||||
|
|
||||||
await server.playlists.addElement({ playlistId: created.id, attributes: { videoId } })
|
const attributes = {
|
||||||
|
displayName: 'Johan & Anna Libert music videos',
|
||||||
|
privacy: VideoPlaylistPrivacy.PUBLIC,
|
||||||
|
videoChannelId: remoteServer.store.channel.id
|
||||||
|
}
|
||||||
|
const created = await remoteServer.playlists.create({ attributes })
|
||||||
|
|
||||||
|
await remoteServer.playlists.addElement({ playlistId: created.id, attributes: { videoId } })
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -59,6 +64,8 @@ describe('Test playlists search', function () {
|
||||||
await server.playlists.create({ attributes })
|
await server.playlists.create({ attributes })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await doubleFollow(server, remoteServer)
|
||||||
|
|
||||||
command = server.search
|
command = server.search
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -87,7 +94,7 @@ describe('Test playlists search', function () {
|
||||||
|
|
||||||
{
|
{
|
||||||
const search = {
|
const search = {
|
||||||
search: 'Anna Livert',
|
search: 'Anna Livert music',
|
||||||
start: 0,
|
start: 0,
|
||||||
count: 1
|
count: 1
|
||||||
}
|
}
|
||||||
|
@ -96,7 +103,36 @@ describe('Test playlists search', function () {
|
||||||
expect(body.data).to.have.lengthOf(1)
|
expect(body.data).to.have.lengthOf(1)
|
||||||
|
|
||||||
const playlist = body.data[0]
|
const playlist = body.data[0]
|
||||||
expect(playlist.displayName).to.equal('Johan & Anna Libert musics')
|
expect(playlist.displayName).to.equal('Johan & Anna Libert music videos')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should filter by host', async function () {
|
||||||
|
{
|
||||||
|
const search = { search: 'tenma', host: server.host }
|
||||||
|
const body = await command.advancedPlaylistSearch({ search })
|
||||||
|
expect(body.total).to.equal(1)
|
||||||
|
expect(body.data).to.have.lengthOf(1)
|
||||||
|
|
||||||
|
const playlist = body.data[0]
|
||||||
|
expect(playlist.displayName).to.equal('Dr. Kenzo Tenma hospital videos')
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const search = { search: 'Anna', host: 'example.com' }
|
||||||
|
const body = await command.advancedPlaylistSearch({ search })
|
||||||
|
expect(body.total).to.equal(0)
|
||||||
|
expect(body.data).to.have.lengthOf(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const search = { search: 'video', host: remoteServer.host }
|
||||||
|
const body = await command.advancedPlaylistSearch({ search })
|
||||||
|
expect(body.total).to.equal(1)
|
||||||
|
expect(body.data).to.have.lengthOf(1)
|
||||||
|
|
||||||
|
const playlist = body.data[0]
|
||||||
|
expect(playlist.displayName).to.equal('Johan & Anna Libert music videos')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -6,4 +6,6 @@ export interface VideoChannelsSearchQuery extends SearchTargetQuery {
|
||||||
start?: number
|
start?: number
|
||||||
count?: number
|
count?: number
|
||||||
sort?: string
|
sort?: string
|
||||||
|
|
||||||
|
host?: string
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,4 +6,6 @@ export interface VideoPlaylistsSearchQuery extends SearchTargetQuery {
|
||||||
start?: number
|
start?: number
|
||||||
count?: number
|
count?: number
|
||||||
sort?: string
|
sort?: string
|
||||||
|
|
||||||
|
host?: string
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue