Add ability to only filter in the search endpoint
authorChocobozzz <me@florianbigard.com>
Tue, 24 Jul 2018 09:09:00 +0000 (11:09 +0200)
committerChocobozzz <me@florianbigard.com>
Tue, 24 Jul 2018 12:04:05 +0000 (14:04 +0200)
server/middlewares/validators/search.ts
server/models/video/video.ts
server/tests/api/search/search-videos.ts
shared/models/search/videos-search-query.model.ts

index a97f5b581d8b280e0bf895844b5512c55a8613b0..e516c4c41d550befd2969ddfa5c3273752bc49e9 100644 (file)
@@ -6,7 +6,7 @@ import { isNumberArray, isStringArray, isNSFWQueryValid } from '../../helpers/cu
 import { isBooleanValid, isDateValid, toArray } from '../../helpers/custom-validators/misc'
 
 const searchValidator = [
-  query('search').not().isEmpty().withMessage('Should have a valid search'),
+  query('search').optional().not().isEmpty().withMessage('Should have a valid search'),
 
   query('startDate').optional().custom(isDateValid).withMessage('Should have a valid start date'),
   query('endDate').optional().custom(isDateValid).withMessage('Should have a valid end date'),
index 27e73bbf126d18dd36987ec89991f2d219c4857c..3a3cfbe85452a22000452ae440df21ac495604e3 100644 (file)
@@ -93,7 +93,6 @@ import { VideoShareModel } from './video-share'
 import { VideoTagModel } from './video-tag'
 import { ScheduleVideoUpdateModel } from './schedule-video-update'
 import { VideoCaptionModel } from './video-caption'
-import { VideosSearchQuery } from '../../../shared/models/search'
 
 // FIXME: Define indexes here because there is an issue with TS and Sequelize.literal when called directly in the annotation
 const indexes: Sequelize.DefineIndexesOptions[] = [
@@ -848,7 +847,7 @@ export class VideoModel extends Model<VideoModel> {
   }
 
   static async searchAndPopulateAccountAndServer (options: {
-    search: string
+    search?: string
     start?: number
     count?: number
     sort?: string
@@ -883,11 +882,41 @@ export class VideoModel extends Model<VideoModel> {
       whereAnd.push({ duration: durationRange })
     }
 
-    whereAnd.push(createSearchTrigramQuery('VideoModel.name', options.search))
+    const attributesInclude = []
+    if (options.search) {
+      whereAnd.push(
+        {
+          [ Sequelize.Op.or ]: [
+            createSearchTrigramQuery('VideoModel.name', options.search),
+
+            {
+              id: {
+                [ Sequelize.Op.in ]: Sequelize.literal(
+                  '(' +
+                    'SELECT "video"."id" FROM "video" LEFT JOIN "videoTag" ON "videoTag"."videoId" = "video"."id" ' +
+                    'INNER JOIN "tag" ON "tag"."id" = "videoTag"."tagId" ' +
+                    'WHERE "tag"."name" = ' + VideoModel.sequelize.escape(options.search) +
+                  ')'
+                )
+              }
+            }
+          ]
+        }
+      )
+
+      attributesInclude.push(createSimilarityAttribute('VideoModel.name', options.search))
+    }
+
+    // Cannot search on similarity if we don't have a search
+    if (!options.search) {
+      attributesInclude.push(
+        Sequelize.literal('0 as similarity')
+      )
+    }
 
     const query: IFindOptions<VideoModel> = {
       attributes: {
-        include: [ createSimilarityAttribute('VideoModel.name', options.search) ]
+        include: attributesInclude
       },
       offset: options.start,
       limit: options.count,
index d2b0f03129e63f7ac18bdb679d668c7cece568e1..f1392ffea7a7b25b3fb316d17fe44298aef35bf5 100644 (file)
@@ -103,6 +103,15 @@ describe('Test a videos search', function () {
       await uploadVideo(server.url, server.accessToken, immutableAssign(attributes1, { tags: [ 'cccc', 'dddd' ] }))
       await uploadVideo(server.url, server.accessToken, immutableAssign(attributes1, { tags: [ 'eeee', 'ffff' ] }))
     }
+
+    {
+      const attributes1 = {
+        name: 'aaaa 2',
+        category: 1
+      }
+      await uploadVideo(server.url, server.accessToken, attributes1)
+      await uploadVideo(server.url, server.accessToken, immutableAssign(attributes1, { category: 2 }))
+    }
   })
 
   it('Should make a simple search and not have results', async function () {
@@ -125,6 +134,52 @@ describe('Test a videos search', function () {
     expect(videos[1].name).to.equal('3333 4444 5555')
   })
 
+  it('Should make a search on tags too, and have results', async function () {
+    const query = {
+      search: 'aaaa',
+      categoryOneOf: [ 1 ]
+    }
+    const res = await advancedVideosSearch(server.url, query)
+
+    expect(res.body.total).to.equal(2)
+
+    const videos = res.body.data
+    expect(videos).to.have.lengthOf(2)
+
+    // bestmatch
+    expect(videos[0].name).to.equal('aaaa 2')
+    expect(videos[1].name).to.equal('9999')
+  })
+
+  it('Should filter on tags without a search', async function () {
+    const query = {
+      tagsAllOf: [ 'bbbb' ]
+    }
+    const res = await advancedVideosSearch(server.url, query)
+
+    expect(res.body.total).to.equal(2)
+
+    const videos = res.body.data
+    expect(videos).to.have.lengthOf(2)
+
+    expect(videos[0].name).to.equal('9999')
+    expect(videos[1].name).to.equal('9999')
+  })
+
+  it('Should filter on category without a search', async function () {
+    const query = {
+      categoryOneOf: [ 3 ]
+    }
+    const res = await advancedVideosSearch(server.url, query)
+
+    expect(res.body.total).to.equal(1)
+
+    const videos = res.body.data
+    expect(videos).to.have.lengthOf(1)
+
+    expect(videos[0].name).to.equal('6666 7777 8888')
+  })
+
   it('Should search by tags (one of)', async function () {
     const query = {
       search: '9999',
index dc14b11775fe90da2e66fcb7f8b254a309c939b2..29aa5c100c716c22ebc84e8aa252c3574635c024 100644 (file)
@@ -1,7 +1,7 @@
 import { NSFWQuery } from './nsfw-query.model'
 
 export interface VideosSearchQuery {
-  search: string
+  search?: string
 
   start?: number
   count?: number