diff options
Diffstat (limited to 'server')
-rw-r--r-- | server/middlewares/validators/search.ts | 2 | ||||
-rw-r--r-- | server/models/video/video.ts | 37 | ||||
-rw-r--r-- | server/tests/api/search/search-videos.ts | 55 |
3 files changed, 89 insertions, 5 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 | |||
6 | import { isBooleanValid, isDateValid, toArray } from '../../helpers/custom-validators/misc' | 6 | import { isBooleanValid, isDateValid, toArray } from '../../helpers/custom-validators/misc' |
7 | 7 | ||
8 | const searchValidator = [ | 8 | const 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' | |||
93 | import { VideoTagModel } from './video-tag' | 93 | import { VideoTagModel } from './video-tag' |
94 | import { ScheduleVideoUpdateModel } from './schedule-video-update' | 94 | import { ScheduleVideoUpdateModel } from './schedule-video-update' |
95 | import { VideoCaptionModel } from './video-caption' | 95 | import { VideoCaptionModel } from './video-caption' |
96 | import { 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 |
99 | const indexes: Sequelize.DefineIndexesOptions[] = [ | 98 | const 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', |