diff options
Diffstat (limited to 'server/models')
-rw-r--r-- | server/models/activitypub/actor.ts | 6 | ||||
-rw-r--r-- | server/models/utils.ts | 52 | ||||
-rw-r--r-- | server/models/video/video.ts | 92 |
3 files changed, 93 insertions, 57 deletions
diff --git a/server/models/activitypub/actor.ts b/server/models/activitypub/actor.ts index 1d0e54ee3..38a689fea 100644 --- a/server/models/activitypub/actor.ts +++ b/server/models/activitypub/actor.ts | |||
@@ -88,6 +88,12 @@ enum ScopeNames { | |||
88 | }, | 88 | }, |
89 | { | 89 | { |
90 | fields: [ 'inboxUrl', 'sharedInboxUrl' ] | 90 | fields: [ 'inboxUrl', 'sharedInboxUrl' ] |
91 | }, | ||
92 | { | ||
93 | fields: [ 'serverId' ] | ||
94 | }, | ||
95 | { | ||
96 | fields: [ 'avatarId' ] | ||
91 | } | 97 | } |
92 | ] | 98 | ] |
93 | }) | 99 | }) |
diff --git a/server/models/utils.ts b/server/models/utils.ts index 59ce83c16..49d32c24f 100644 --- a/server/models/utils.ts +++ b/server/models/utils.ts | |||
@@ -1,6 +1,8 @@ | |||
1 | // Translate for example "-name" to [ [ 'name', 'DESC' ], [ 'id', 'ASC' ] ] | 1 | // Translate for example "-name" to [ [ 'name', 'DESC' ], [ 'id', 'ASC' ] ] |
2 | import { Sequelize } from 'sequelize-typescript' | ||
3 | |||
2 | function getSort (value: string, lastSort: string[] = [ 'id', 'ASC' ]) { | 4 | function getSort (value: string, lastSort: string[] = [ 'id', 'ASC' ]) { |
3 | let field: string | 5 | let field: any |
4 | let direction: 'ASC' | 'DESC' | 6 | let direction: 'ASC' | 'DESC' |
5 | 7 | ||
6 | if (value.substring(0, 1) === '-') { | 8 | if (value.substring(0, 1) === '-') { |
@@ -11,6 +13,9 @@ function getSort (value: string, lastSort: string[] = [ 'id', 'ASC' ]) { | |||
11 | field = value | 13 | field = value |
12 | } | 14 | } |
13 | 15 | ||
16 | // Alias | ||
17 | if (field.toLowerCase() === 'bestmatch') field = Sequelize.col('similarity') | ||
18 | |||
14 | return [ [ field, direction ], lastSort ] | 19 | return [ [ field, direction ], lastSort ] |
15 | } | 20 | } |
16 | 21 | ||
@@ -27,10 +32,53 @@ function throwIfNotValid (value: any, validator: (value: any) => boolean, fieldN | |||
27 | } | 32 | } |
28 | } | 33 | } |
29 | 34 | ||
35 | function buildTrigramSearchIndex (indexName: string, attribute: string) { | ||
36 | return { | ||
37 | name: indexName, | ||
38 | fields: [ Sequelize.literal('lower(immutable_unaccent(' + attribute + '))') as any ], | ||
39 | using: 'gin', | ||
40 | operator: 'gin_trgm_ops' | ||
41 | } | ||
42 | } | ||
43 | |||
44 | function createSimilarityAttribute (col: string, value: string) { | ||
45 | return Sequelize.fn( | ||
46 | 'similarity', | ||
47 | |||
48 | searchTrigramNormalizeCol(col), | ||
49 | |||
50 | searchTrigramNormalizeValue(value) | ||
51 | ) | ||
52 | } | ||
53 | |||
54 | function createSearchTrigramQuery (col: string, value: string) { | ||
55 | return { | ||
56 | [ Sequelize.Op.or ]: [ | ||
57 | // FIXME: use word_similarity instead of just similarity? | ||
58 | Sequelize.where(searchTrigramNormalizeCol(col), ' % ', searchTrigramNormalizeValue(value)), | ||
59 | |||
60 | Sequelize.where(searchTrigramNormalizeCol(col), ' LIKE ', searchTrigramNormalizeValue(`%${value}%`)) | ||
61 | ] | ||
62 | } | ||
63 | } | ||
64 | |||
30 | // --------------------------------------------------------------------------- | 65 | // --------------------------------------------------------------------------- |
31 | 66 | ||
32 | export { | 67 | export { |
33 | getSort, | 68 | getSort, |
34 | getSortOnModel, | 69 | getSortOnModel, |
35 | throwIfNotValid | 70 | createSimilarityAttribute, |
71 | throwIfNotValid, | ||
72 | buildTrigramSearchIndex, | ||
73 | createSearchTrigramQuery | ||
74 | } | ||
75 | |||
76 | // --------------------------------------------------------------------------- | ||
77 | |||
78 | function searchTrigramNormalizeValue (value: string) { | ||
79 | return Sequelize.fn('lower', Sequelize.fn('unaccent', value)) | ||
80 | } | ||
81 | |||
82 | function searchTrigramNormalizeCol (col: string) { | ||
83 | return Sequelize.fn('lower', Sequelize.fn('immutable_unaccent', Sequelize.col(col))) | ||
36 | } | 84 | } |
diff --git a/server/models/video/video.ts b/server/models/video/video.ts index 74a3a5d05..15b4dda5b 100644 --- a/server/models/video/video.ts +++ b/server/models/video/video.ts | |||
@@ -83,7 +83,7 @@ import { AccountVideoRateModel } from '../account/account-video-rate' | |||
83 | import { ActorModel } from '../activitypub/actor' | 83 | import { ActorModel } from '../activitypub/actor' |
84 | import { AvatarModel } from '../avatar/avatar' | 84 | import { AvatarModel } from '../avatar/avatar' |
85 | import { ServerModel } from '../server/server' | 85 | import { ServerModel } from '../server/server' |
86 | import { getSort, throwIfNotValid } from '../utils' | 86 | import { buildTrigramSearchIndex, createSearchTrigramQuery, createSimilarityAttribute, getSort, throwIfNotValid } from '../utils' |
87 | import { TagModel } from './tag' | 87 | import { TagModel } from './tag' |
88 | import { VideoAbuseModel } from './video-abuse' | 88 | import { VideoAbuseModel } from './video-abuse' |
89 | import { VideoChannelModel } from './video-channel' | 89 | import { VideoChannelModel } from './video-channel' |
@@ -94,6 +94,37 @@ 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 | 96 | ||
97 | // FIXME: Define indexes here because there is an issue with TS and Sequelize.literal when called directly in the annotation | ||
98 | const indexes: Sequelize.DefineIndexesOptions[] = [ | ||
99 | buildTrigramSearchIndex('video_name_trigram', 'name'), | ||
100 | |||
101 | { | ||
102 | fields: [ 'createdAt' ] | ||
103 | }, | ||
104 | { | ||
105 | fields: [ 'duration' ] | ||
106 | }, | ||
107 | { | ||
108 | fields: [ 'views' ] | ||
109 | }, | ||
110 | { | ||
111 | fields: [ 'likes' ] | ||
112 | }, | ||
113 | { | ||
114 | fields: [ 'uuid' ] | ||
115 | }, | ||
116 | { | ||
117 | fields: [ 'channelId' ] | ||
118 | }, | ||
119 | { | ||
120 | fields: [ 'id', 'privacy', 'state', 'waitTranscoding' ] | ||
121 | }, | ||
122 | { | ||
123 | fields: [ 'url'], | ||
124 | unique: true | ||
125 | } | ||
126 | ] | ||
127 | |||
97 | export enum ScopeNames { | 128 | export enum ScopeNames { |
98 | AVAILABLE_FOR_LIST = 'AVAILABLE_FOR_LIST', | 129 | AVAILABLE_FOR_LIST = 'AVAILABLE_FOR_LIST', |
99 | WITH_ACCOUNT_DETAILS = 'WITH_ACCOUNT_DETAILS', | 130 | WITH_ACCOUNT_DETAILS = 'WITH_ACCOUNT_DETAILS', |
@@ -309,36 +340,7 @@ export enum ScopeNames { | |||
309 | }) | 340 | }) |
310 | @Table({ | 341 | @Table({ |
311 | tableName: 'video', | 342 | tableName: 'video', |
312 | indexes: [ | 343 | indexes |
313 | { | ||
314 | fields: [ 'name' ] | ||
315 | }, | ||
316 | { | ||
317 | fields: [ 'createdAt' ] | ||
318 | }, | ||
319 | { | ||
320 | fields: [ 'duration' ] | ||
321 | }, | ||
322 | { | ||
323 | fields: [ 'views' ] | ||
324 | }, | ||
325 | { | ||
326 | fields: [ 'likes' ] | ||
327 | }, | ||
328 | { | ||
329 | fields: [ 'uuid' ] | ||
330 | }, | ||
331 | { | ||
332 | fields: [ 'channelId' ] | ||
333 | }, | ||
334 | { | ||
335 | fields: [ 'id', 'privacy', 'state', 'waitTranscoding' ] | ||
336 | }, | ||
337 | { | ||
338 | fields: [ 'url'], | ||
339 | unique: true | ||
340 | } | ||
341 | ] | ||
342 | }) | 344 | }) |
343 | export class VideoModel extends Model<VideoModel> { | 345 | export class VideoModel extends Model<VideoModel> { |
344 | 346 | ||
@@ -794,33 +796,13 @@ export class VideoModel extends Model<VideoModel> { | |||
794 | 796 | ||
795 | static async searchAndPopulateAccountAndServer (value: string, start: number, count: number, sort: string, hideNSFW: boolean) { | 797 | static async searchAndPopulateAccountAndServer (value: string, start: number, count: number, sort: string, hideNSFW: boolean) { |
796 | const query: IFindOptions<VideoModel> = { | 798 | const query: IFindOptions<VideoModel> = { |
799 | attributes: { | ||
800 | include: [ createSimilarityAttribute('VideoModel.name', value) ] | ||
801 | }, | ||
797 | offset: start, | 802 | offset: start, |
798 | limit: count, | 803 | limit: count, |
799 | order: getSort(sort), | 804 | order: getSort(sort), |
800 | where: { | 805 | where: createSearchTrigramQuery('VideoModel.name', value) |
801 | [Sequelize.Op.or]: [ | ||
802 | { | ||
803 | name: { | ||
804 | [ Sequelize.Op.iLike ]: '%' + value + '%' | ||
805 | } | ||
806 | }, | ||
807 | { | ||
808 | preferredUsernameChannel: Sequelize.where(Sequelize.col('VideoChannel->Actor.preferredUsername'), { | ||
809 | [ Sequelize.Op.iLike ]: '%' + value + '%' | ||
810 | }) | ||
811 | }, | ||
812 | { | ||
813 | preferredUsernameAccount: Sequelize.where(Sequelize.col('VideoChannel->Account->Actor.preferredUsername'), { | ||
814 | [ Sequelize.Op.iLike ]: '%' + value + '%' | ||
815 | }) | ||
816 | }, | ||
817 | { | ||
818 | host: Sequelize.where(Sequelize.col('VideoChannel->Account->Actor->Server.host'), { | ||
819 | [ Sequelize.Op.iLike ]: '%' + value + '%' | ||
820 | }) | ||
821 | } | ||
822 | ] | ||
823 | } | ||
824 | } | 806 | } |
825 | 807 | ||
826 | const serverActor = await getServerActor() | 808 | const serverActor = await getServerActor() |