Search channels against handles and not names
This commit is contained in:
parent
fbd67e7f38
commit
b033851fb5
|
@ -100,7 +100,7 @@ async function searchVideoChannelsDB (query: VideoChannelsSearchQuery, res: expr
|
|||
count: query.count,
|
||||
sort: query.sort,
|
||||
host: query.host,
|
||||
names: query.names
|
||||
handles: query.handles
|
||||
}, 'filter:api.search.video-channels.local.list.params')
|
||||
|
||||
const resultList = await Hooks.wrapPromiseFun(
|
||||
|
|
|
@ -23,6 +23,10 @@ function isNotEmptyIntArray (value: any) {
|
|||
return Array.isArray(value) && value.every(v => validator.isInt('' + v)) && value.length !== 0
|
||||
}
|
||||
|
||||
function isNotEmptyStringArray (value: any) {
|
||||
return Array.isArray(value) && value.every(v => typeof v === 'string' && v.length !== 0) && value.length !== 0
|
||||
}
|
||||
|
||||
function isArrayOf (value: any, validator: (value: any) => boolean) {
|
||||
return isArray(value) && value.every(v => validator(v))
|
||||
}
|
||||
|
@ -187,6 +191,7 @@ export {
|
|||
isIntOrNull,
|
||||
isIdValid,
|
||||
isSafePath,
|
||||
isNotEmptyStringArray,
|
||||
isUUIDValid,
|
||||
toCompleteUUIDs,
|
||||
toCompleteUUID,
|
||||
|
|
|
@ -2,7 +2,7 @@ import * as express from 'express'
|
|||
import { query } from 'express-validator'
|
||||
import { isSearchTargetValid } from '@server/helpers/custom-validators/search'
|
||||
import { isHostValid } from '@server/helpers/custom-validators/servers'
|
||||
import { areUUIDsValid, isDateValid, toCompleteUUIDs } from '../../helpers/custom-validators/misc'
|
||||
import { areUUIDsValid, isDateValid, isNotEmptyStringArray, toCompleteUUIDs } from '../../helpers/custom-validators/misc'
|
||||
import { logger } from '../../helpers/logger'
|
||||
import { areValidationErrors } from './shared'
|
||||
|
||||
|
@ -64,9 +64,10 @@ const videoChannelsListSearchValidator = [
|
|||
.optional()
|
||||
.custom(isSearchTargetValid).withMessage('Should have a valid search target'),
|
||||
|
||||
query('names')
|
||||
query('handles')
|
||||
.optional()
|
||||
.toArray(),
|
||||
.toArray()
|
||||
.custom(isNotEmptyStringArray).withMessage('Should have valid handles'),
|
||||
|
||||
(req: express.Request, res: express.Response, next: express.NextFunction) => {
|
||||
logger.debug('Checking video channels search query', { parameters: req.query })
|
||||
|
|
|
@ -18,7 +18,7 @@ import {
|
|||
UpdatedAt
|
||||
} from 'sequelize-typescript'
|
||||
import { MAccountActor } from '@server/types/models'
|
||||
import { AttributesOnly } from '@shared/core-utils'
|
||||
import { AttributesOnly, pick } from '@shared/core-utils'
|
||||
import { ActivityPubActor } from '../../../shared/models/activitypub'
|
||||
import { VideoChannel, VideoChannelSummary } from '../../../shared/models/videos'
|
||||
import {
|
||||
|
@ -59,7 +59,7 @@ type AvailableForListOptions = {
|
|||
actorId: number
|
||||
search?: string
|
||||
host?: string
|
||||
names?: string[]
|
||||
handles?: string[]
|
||||
}
|
||||
|
||||
type AvailableWithStatsOptions = {
|
||||
|
@ -114,15 +114,33 @@ export type SummaryOptions = {
|
|||
})
|
||||
}
|
||||
|
||||
if (options.names) {
|
||||
whereActorAnd.push({
|
||||
preferredUsername: {
|
||||
[Op.in]: options.names
|
||||
let rootWhere: WhereOptions
|
||||
if (options.handles) {
|
||||
const or: WhereOptions[] = []
|
||||
|
||||
for (const handle of options.handles || []) {
|
||||
const [ preferredUsername, host ] = handle.split('@')
|
||||
|
||||
if (!host) {
|
||||
or.push({
|
||||
'$Actor.preferredUsername$': preferredUsername,
|
||||
'$Actor.serverId$': null
|
||||
})
|
||||
} else {
|
||||
or.push({
|
||||
'$Actor.preferredUsername$': preferredUsername,
|
||||
'$Actor.Server.host$': host
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
rootWhere = {
|
||||
[Op.or]: or
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
where: rootWhere,
|
||||
include: [
|
||||
{
|
||||
attributes: {
|
||||
|
@ -473,7 +491,7 @@ ON "Account->Actor"."serverId" = "Account->Actor->Server"."id"`
|
|||
sort: string
|
||||
|
||||
host?: string
|
||||
names?: string[]
|
||||
handles?: string[]
|
||||
}) {
|
||||
let attributesInclude: any[] = [ literal('0 as similarity') ]
|
||||
let where: WhereOptions
|
||||
|
@ -507,7 +525,7 @@ ON "Account->Actor"."serverId" = "Account->Actor->Server"."id"`
|
|||
|
||||
return VideoChannelModel
|
||||
.scope({
|
||||
method: [ ScopeNames.FOR_API, { actorId: options.actorId, host: options.host, names: options.names } as AvailableForListOptions ]
|
||||
method: [ ScopeNames.FOR_API, pick(options, [ 'actorId', 'host', 'handles' ]) as AvailableForListOptions ]
|
||||
})
|
||||
.findAndCountAll(query)
|
||||
.then(({ rows, count }) => {
|
||||
|
|
|
@ -216,6 +216,10 @@ describe('Test videos API validator', function () {
|
|||
await makeGetRequest({ url: server.url, path, query: { ...query, host: '6565' }, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
|
||||
})
|
||||
|
||||
it('Should fail with invalid handles', async function () {
|
||||
await makeGetRequest({ url: server.url, path, query: { ...query, handles: [ '' ] }, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
|
||||
})
|
||||
|
||||
it('Should succeed with the correct parameters', async function () {
|
||||
await makeGetRequest({ url: server.url, path, query, expectedStatus: HttpStatusCode.OK_200 })
|
||||
})
|
||||
|
|
|
@ -122,18 +122,25 @@ describe('Test channels search', function () {
|
|||
|
||||
it('Should filter by names', async function () {
|
||||
{
|
||||
const body = await command.advancedChannelSearch({ search: { names: [ 'squall_channel', 'zell_channel' ] } })
|
||||
const body = await command.advancedChannelSearch({ search: { handles: [ 'squall_channel', 'zell_channel' ] } })
|
||||
expect(body.total).to.equal(1)
|
||||
expect(body.data).to.have.lengthOf(1)
|
||||
expect(body.data[0].displayName).to.equal('Squall channel')
|
||||
}
|
||||
|
||||
{
|
||||
const body = await command.advancedChannelSearch({ search: { handles: [ 'chocobozzz_channel' ] } })
|
||||
expect(body.total).to.equal(0)
|
||||
expect(body.data).to.have.lengthOf(0)
|
||||
}
|
||||
|
||||
{
|
||||
const body = await command.advancedChannelSearch({ search: { handles: [ 'squall_channel', 'zell_channel@' + remoteServer.host ] } })
|
||||
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 () {
|
||||
|
|
|
@ -4,3 +4,4 @@ export * from './i18n'
|
|||
export * from './plugins'
|
||||
export * from './renderer'
|
||||
export * from './users'
|
||||
export * from './utils'
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
export * from './object'
|
|
@ -0,0 +1,15 @@
|
|||
function pick <T extends object> (object: T, keys: (keyof T)[]) {
|
||||
const result: Partial<T> = {}
|
||||
|
||||
for (const key of keys) {
|
||||
if (Object.prototype.hasOwnProperty.call(object, key)) {
|
||||
result[key] = object[key]
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
export {
|
||||
pick
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
import { pick } from 'lodash'
|
||||
import { pick } from '@shared/core-utils'
|
||||
import {
|
||||
AbuseFilter,
|
||||
AbuseMessage,
|
||||
|
@ -81,7 +81,7 @@ export class AbusesCommand extends AbstractCommand {
|
|||
searchVideo?: string
|
||||
searchVideoChannel?: string
|
||||
} = {}) {
|
||||
const toPick = [
|
||||
const toPick: (keyof typeof options)[] = [
|
||||
'count',
|
||||
'filter',
|
||||
'id',
|
||||
|
@ -121,7 +121,7 @@ export class AbusesCommand extends AbstractCommand {
|
|||
search?: string
|
||||
state?: AbuseState
|
||||
}) {
|
||||
const toPick = [
|
||||
const toPick: (keyof typeof options)[] = [
|
||||
'id',
|
||||
'search',
|
||||
'state',
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { pick } from 'lodash'
|
||||
import { pick } from '@shared/core-utils'
|
||||
import { ActivityPubActorType, ActorFollow, FollowState, HttpStatusCode, ResultList, ServerFollowCreate } from '@shared/models'
|
||||
import { AbstractCommand, OverrideCommandOptions } from '../shared'
|
||||
import { PeerTubeServer } from './server'
|
||||
|
@ -15,8 +15,7 @@ export class FollowsCommand extends AbstractCommand {
|
|||
}) {
|
||||
const path = '/api/v1/server/followers'
|
||||
|
||||
const toPick = [ 'start', 'count', 'sort', 'search', 'state', 'actorType' ]
|
||||
const query = pick(options, toPick)
|
||||
const query = pick(options, [ 'start', 'count', 'sort', 'search', 'state', 'actorType' ])
|
||||
|
||||
return this.getRequestBody<ResultList<ActorFollow>>({
|
||||
...options,
|
||||
|
@ -38,8 +37,7 @@ export class FollowsCommand extends AbstractCommand {
|
|||
} = {}) {
|
||||
const path = '/api/v1/server/following'
|
||||
|
||||
const toPick = [ 'start', 'count', 'sort', 'search', 'state', 'actorType' ]
|
||||
const query = pick(options, toPick)
|
||||
const query = pick(options, [ 'start', 'count', 'sort', 'search', 'state', 'actorType' ])
|
||||
|
||||
return this.getRequestBody<ResultList<ActorFollow>>({
|
||||
...options,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { pick } from 'lodash'
|
||||
import { pick } from '@shared/core-utils'
|
||||
import { HttpStatusCode } from '@shared/models'
|
||||
import { Job, JobState, JobType, ResultList } from '../../models'
|
||||
import { AbstractCommand, OverrideCommandOptions } from '../shared'
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { omit, pick } from 'lodash'
|
||||
import { omit } from 'lodash'
|
||||
import { pick } from '@shared/core-utils'
|
||||
import {
|
||||
HttpStatusCode,
|
||||
MyUser,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { pick } from 'lodash'
|
||||
import { pick } from '@shared/core-utils'
|
||||
import { HttpStatusCode, ResultList, VideoChannel, VideoChannelCreateResult } from '@shared/models'
|
||||
import { VideoChannelCreate } from '../../models/videos/channel/video-channel-create.model'
|
||||
import { VideoChannelUpdate } from '../../models/videos/channel/video-channel-update.model'
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { omit, pick } from 'lodash'
|
||||
import { omit } from 'lodash'
|
||||
import { pick } from '@shared/core-utils'
|
||||
import {
|
||||
BooleanBothQuery,
|
||||
HttpStatusCode,
|
||||
|
|
|
@ -3,10 +3,11 @@
|
|||
import { expect } from 'chai'
|
||||
import { createReadStream, stat } from 'fs-extra'
|
||||
import got, { Response as GotResponse } from 'got'
|
||||
import { omit, pick } from 'lodash'
|
||||
import { omit } from 'lodash'
|
||||
import validator from 'validator'
|
||||
import { buildUUID } from '@server/helpers/uuid'
|
||||
import { loadLanguages } from '@server/initializers/constants'
|
||||
import { pick } from '@shared/core-utils'
|
||||
import {
|
||||
HttpStatusCode,
|
||||
ResultList,
|
||||
|
|
|
@ -8,5 +8,5 @@ export interface VideoChannelsSearchQuery extends SearchTargetQuery {
|
|||
sort?: string
|
||||
|
||||
host?: string
|
||||
names?: string[]
|
||||
handles?: string[]
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue