]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/commitdiff
Search channels against handles and not names
authorChocobozzz <me@florianbigard.com>
Thu, 29 Jul 2021 08:27:24 +0000 (10:27 +0200)
committerChocobozzz <me@florianbigard.com>
Thu, 29 Jul 2021 08:27:24 +0000 (10:27 +0200)
17 files changed:
server/controllers/api/search/search-video-channels.ts
server/helpers/custom-validators/misc.ts
server/middlewares/validators/search.ts
server/models/video/video-channel.ts
server/tests/api/check-params/search.ts
server/tests/api/search/search-channels.ts
shared/core-utils/index.ts
shared/core-utils/utils/index.ts [new file with mode: 0644]
shared/core-utils/utils/object.ts [new file with mode: 0644]
shared/extra-utils/moderation/abuses-command.ts
shared/extra-utils/server/follows-command.ts
shared/extra-utils/server/jobs-command.ts
shared/extra-utils/users/users-command.ts
shared/extra-utils/videos/channels-command.ts
shared/extra-utils/videos/playlists-command.ts
shared/extra-utils/videos/videos-command.ts
shared/models/search/video-channels-search-query.model.ts

index 9fc2d53a55bad24e7c0720f7744a39e46ab555a3..ae32a6726fd67295abf3b05985b00a3a9ab6ceee 100644 (file)
@@ -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(
index f8f168149e9cfeb7078e40d43eb725896ee74bc4..c19a3e5eba2692e7f1e87075d20509ede9bba365 100644 (file)
@@ -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,
index cde300968949a6d333e58b4f2601d62345f58973..27d0e541d61dd7bbc9a4848915e3c5dcbce614ae 100644 (file)
@@ -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 })
index 327f493048f742fc070eafc6621be70d0cb069f0..e4b12c517ccc71eeb61f0c70402a625cdcee87c9 100644 (file)
@@ -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 }) => {
index 789ea7754f975088b66a482205ed05733e19a03a..cc15d25930865bf8a3a765266a2cbed508c7068d 100644 (file)
@@ -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 })
     })
index ef78c0f67e80f49d6a478b3e1d6f94b71e64f558..4485c424eb3ad62ea725594c38d0520685f442b9 100644 (file)
@@ -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' ] } })
-      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 () {
index 66d50ef938756d90fbea2c5bf52edd4a488037bd..2a7d4d9828d5757d9d079b7cc4246dd609770feb 100644 (file)
@@ -4,3 +4,4 @@ export * from './i18n'
 export * from './plugins'
 export * from './renderer'
 export * from './users'
+export * from './utils'
diff --git a/shared/core-utils/utils/index.ts b/shared/core-utils/utils/index.ts
new file mode 100644 (file)
index 0000000..a71977d
--- /dev/null
@@ -0,0 +1 @@
+export * from './object'
diff --git a/shared/core-utils/utils/object.ts b/shared/core-utils/utils/object.ts
new file mode 100644 (file)
index 0000000..7b2bb81
--- /dev/null
@@ -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
+}
index 7b3abb05678bbe86578938bff86966376c939c69..0db32ba46794a2ae5cd991e464d87b3b7ad583bb 100644 (file)
@@ -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',
index 2b889cf66481fde76852e59599b853386d8532f9..01ef6f179f4186ab0afb5bd3fd4960e78ba4e682 100644 (file)
@@ -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,
index 09a299e5bfdac90ba47c625db34687223e3497a9..c4eb12dc25237cb2c51d985027137a96eaa36973 100644 (file)
@@ -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'
index d66ad15f26e92f136c983a4e531394e6545da2cd..ddd20d041805d18d4ab8c27fb644ec601ddf8cb9 100644 (file)
@@ -1,4 +1,5 @@
-import { omit, pick } from 'lodash'
+import { omit } from 'lodash'
+import { pick } from '@shared/core-utils'
 import {
   HttpStatusCode,
   MyUser,
index f8eb3f8853e722df295ae67708092cafacac2769..255e1d62d15104c52d7019cc8c8576ef4cb3db7b 100644 (file)
@@ -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'
index 6f329800e5041d2764dcc6282ac62d7680ad222c..ce23900d39bd0590e3977fdf5e6e3e4abce6b1b3 100644 (file)
@@ -1,4 +1,5 @@
-import { omit, pick } from 'lodash'
+import { omit } from 'lodash'
+import { pick } from '@shared/core-utils'
 import {
   BooleanBothQuery,
   HttpStatusCode,
index 98465e8f69f9d7741709ab16c5c7f04938ff2bdb..33725bfdcce58a41ded0ce6c277c7bef85243af8 100644 (file)
@@ -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,
index 50c59d41dec28f7552ace13cf5c9a2dd96672c6b..77cea4a59cf8dc3272a84a205295d9b21f53194b 100644 (file)
@@ -8,5 +8,5 @@ export interface VideoChannelsSearchQuery extends SearchTargetQuery {
   sort?: string
 
   host?: string
-  names?: string[]
+  handles?: string[]
 }