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(
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))
}
isIntOrNull,
isIdValid,
isSafePath,
+ isNotEmptyStringArray,
isUUIDValid,
toCompleteUUIDs,
toCompleteUUID,
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'
.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 })
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 {
actorId: number
search?: string
host?: string
- names?: string[]
+ handles?: string[]
}
type AvailableWithStatsOptions = {
})
}
- 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: {
sort: string
host?: string
- names?: string[]
+ handles?: string[]
}) {
let attributesInclude: any[] = [ literal('0 as similarity') ]
let where: WhereOptions
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 }) => {
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 })
})
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)
+ 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')
- expect(body.data[1].displayName).to.equal('Zell channel')
}
{
- const body = await command.advancedChannelSearch({ search: { names: [ 'chocobozzz_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')
+ }
})
after(async function () {
export * from './plugins'
export * from './renderer'
export * from './users'
+export * from './utils'
--- /dev/null
+export * from './object'
--- /dev/null
+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
+}
-import { pick } from 'lodash'
+import { pick } from '@shared/core-utils'
import {
AbuseFilter,
AbuseMessage,
searchVideo?: string
searchVideoChannel?: string
} = {}) {
- const toPick = [
+ const toPick: (keyof typeof options)[] = [
'count',
'filter',
'id',
search?: string
state?: AbuseState
}) {
- const toPick = [
+ const toPick: (keyof typeof options)[] = [
'id',
'search',
'state',
-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'
}) {
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,
} = {}) {
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,
-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'
-import { omit, pick } from 'lodash'
+import { omit } from 'lodash'
+import { pick } from '@shared/core-utils'
import {
HttpStatusCode,
MyUser,
-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'
-import { omit, pick } from 'lodash'
+import { omit } from 'lodash'
+import { pick } from '@shared/core-utils'
import {
BooleanBothQuery,
HttpStatusCode,
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,
sort?: string
host?: string
- names?: string[]
+ handles?: string[]
}