aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--server/middlewares/validators/search.ts2
-rw-r--r--server/models/video/video.ts37
-rw-r--r--server/tests/api/search/search-videos.ts55
-rw-r--r--shared/models/search/videos-search-query.model.ts2
4 files changed, 90 insertions, 6 deletions
diff --git a/server/middlewares/validators/search.ts b/server/middlewares/validators/search.ts
index a97f5b581..e516c4c41 100644
--- a/server/middlewares/validators/search.ts
+++ b/server/middlewares/validators/search.ts
@@ -6,7 +6,7 @@ import { isNumberArray, isStringArray, isNSFWQueryValid } from '../../helpers/cu
6import { isBooleanValid, isDateValid, toArray } from '../../helpers/custom-validators/misc' 6import { isBooleanValid, isDateValid, toArray } from '../../helpers/custom-validators/misc'
7 7
8const searchValidator = [ 8const searchValidator = [
9 query('search').not().isEmpty().withMessage('Should have a valid search'), 9 query('search').optional().not().isEmpty().withMessage('Should have a valid search'),
10 10
11 query('startDate').optional().custom(isDateValid).withMessage('Should have a valid start date'), 11 query('startDate').optional().custom(isDateValid).withMessage('Should have a valid start date'),
12 query('endDate').optional().custom(isDateValid).withMessage('Should have a valid end date'), 12 query('endDate').optional().custom(isDateValid).withMessage('Should have a valid end date'),
diff --git a/server/models/video/video.ts b/server/models/video/video.ts
index 27e73bbf1..3a3cfbe85 100644
--- a/server/models/video/video.ts
+++ b/server/models/video/video.ts
@@ -93,7 +93,6 @@ import { VideoShareModel } from './video-share'
93import { VideoTagModel } from './video-tag' 93import { VideoTagModel } from './video-tag'
94import { ScheduleVideoUpdateModel } from './schedule-video-update' 94import { ScheduleVideoUpdateModel } from './schedule-video-update'
95import { VideoCaptionModel } from './video-caption' 95import { VideoCaptionModel } from './video-caption'
96import { VideosSearchQuery } from '../../../shared/models/search'
97 96
98// FIXME: Define indexes here because there is an issue with TS and Sequelize.literal when called directly in the annotation 97// FIXME: Define indexes here because there is an issue with TS and Sequelize.literal when called directly in the annotation
99const indexes: Sequelize.DefineIndexesOptions[] = [ 98const indexes: Sequelize.DefineIndexesOptions[] = [
@@ -848,7 +847,7 @@ export class VideoModel extends Model<VideoModel> {
848 } 847 }
849 848
850 static async searchAndPopulateAccountAndServer (options: { 849 static async searchAndPopulateAccountAndServer (options: {
851 search: string 850 search?: string
852 start?: number 851 start?: number
853 count?: number 852 count?: number
854 sort?: string 853 sort?: string
@@ -883,11 +882,41 @@ export class VideoModel extends Model<VideoModel> {
883 whereAnd.push({ duration: durationRange }) 882 whereAnd.push({ duration: durationRange })
884 } 883 }
885 884
886 whereAnd.push(createSearchTrigramQuery('VideoModel.name', options.search)) 885 const attributesInclude = []
886 if (options.search) {
887 whereAnd.push(
888 {
889 [ Sequelize.Op.or ]: [
890 createSearchTrigramQuery('VideoModel.name', options.search),
891
892 {
893 id: {
894 [ Sequelize.Op.in ]: Sequelize.literal(
895 '(' +
896 'SELECT "video"."id" FROM "video" LEFT JOIN "videoTag" ON "videoTag"."videoId" = "video"."id" ' +
897 'INNER JOIN "tag" ON "tag"."id" = "videoTag"."tagId" ' +
898 'WHERE "tag"."name" = ' + VideoModel.sequelize.escape(options.search) +
899 ')'
900 )
901 }
902 }
903 ]
904 }
905 )
906
907 attributesInclude.push(createSimilarityAttribute('VideoModel.name', options.search))
908 }
909
910 // Cannot search on similarity if we don't have a search
911 if (!options.search) {
912 attributesInclude.push(
913 Sequelize.literal('0 as similarity')
914 )
915 }
887 916
888 const query: IFindOptions<VideoModel> = { 917 const query: IFindOptions<VideoModel> = {
889 attributes: { 918 attributes: {
890 include: [ createSimilarityAttribute('VideoModel.name', options.search) ] 919 include: attributesInclude
891 }, 920 },
892 offset: options.start, 921 offset: options.start,
893 limit: options.count, 922 limit: options.count,
diff --git a/server/tests/api/search/search-videos.ts b/server/tests/api/search/search-videos.ts
index d2b0f0312..f1392ffea 100644
--- a/server/tests/api/search/search-videos.ts
+++ b/server/tests/api/search/search-videos.ts
@@ -103,6 +103,15 @@ describe('Test a videos search', function () {
103 await uploadVideo(server.url, server.accessToken, immutableAssign(attributes1, { tags: [ 'cccc', 'dddd' ] })) 103 await uploadVideo(server.url, server.accessToken, immutableAssign(attributes1, { tags: [ 'cccc', 'dddd' ] }))
104 await uploadVideo(server.url, server.accessToken, immutableAssign(attributes1, { tags: [ 'eeee', 'ffff' ] })) 104 await uploadVideo(server.url, server.accessToken, immutableAssign(attributes1, { tags: [ 'eeee', 'ffff' ] }))
105 } 105 }
106
107 {
108 const attributes1 = {
109 name: 'aaaa 2',
110 category: 1
111 }
112 await uploadVideo(server.url, server.accessToken, attributes1)
113 await uploadVideo(server.url, server.accessToken, immutableAssign(attributes1, { category: 2 }))
114 }
106 }) 115 })
107 116
108 it('Should make a simple search and not have results', async function () { 117 it('Should make a simple search and not have results', async function () {
@@ -125,6 +134,52 @@ describe('Test a videos search', function () {
125 expect(videos[1].name).to.equal('3333 4444 5555') 134 expect(videos[1].name).to.equal('3333 4444 5555')
126 }) 135 })
127 136
137 it('Should make a search on tags too, and have results', async function () {
138 const query = {
139 search: 'aaaa',
140 categoryOneOf: [ 1 ]
141 }
142 const res = await advancedVideosSearch(server.url, query)
143
144 expect(res.body.total).to.equal(2)
145
146 const videos = res.body.data
147 expect(videos).to.have.lengthOf(2)
148
149 // bestmatch
150 expect(videos[0].name).to.equal('aaaa 2')
151 expect(videos[1].name).to.equal('9999')
152 })
153
154 it('Should filter on tags without a search', async function () {
155 const query = {
156 tagsAllOf: [ 'bbbb' ]
157 }
158 const res = await advancedVideosSearch(server.url, query)
159
160 expect(res.body.total).to.equal(2)
161
162 const videos = res.body.data
163 expect(videos).to.have.lengthOf(2)
164
165 expect(videos[0].name).to.equal('9999')
166 expect(videos[1].name).to.equal('9999')
167 })
168
169 it('Should filter on category without a search', async function () {
170 const query = {
171 categoryOneOf: [ 3 ]
172 }
173 const res = await advancedVideosSearch(server.url, query)
174
175 expect(res.body.total).to.equal(1)
176
177 const videos = res.body.data
178 expect(videos).to.have.lengthOf(1)
179
180 expect(videos[0].name).to.equal('6666 7777 8888')
181 })
182
128 it('Should search by tags (one of)', async function () { 183 it('Should search by tags (one of)', async function () {
129 const query = { 184 const query = {
130 search: '9999', 185 search: '9999',
diff --git a/shared/models/search/videos-search-query.model.ts b/shared/models/search/videos-search-query.model.ts
index dc14b1177..29aa5c100 100644
--- a/shared/models/search/videos-search-query.model.ts
+++ b/shared/models/search/videos-search-query.model.ts
@@ -1,7 +1,7 @@
1import { NSFWQuery } from './nsfw-query.model' 1import { NSFWQuery } from './nsfw-query.model'
2 2
3export interface VideosSearchQuery { 3export interface VideosSearchQuery {
4 search: string 4 search?: string
5 5
6 start?: number 6 start?: number
7 count?: number 7 count?: number