]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blame_incremental - server/models/utils.ts
Update translations
[github/Chocobozzz/PeerTube.git] / server / models / utils.ts
... / ...
CommitLineData
1import { Model, Sequelize } from 'sequelize-typescript'
2import validator from 'validator'
3import { Col } from 'sequelize/types/lib/utils'
4import { literal, OrderItem, Op } from 'sequelize'
5
6type SortType = { sortModel: string, sortValue: string }
7
8// Translate for example "-name" to [ [ 'name', 'DESC' ], [ 'id', 'ASC' ] ]
9function getSort (value: string, lastSort: OrderItem = [ 'id', 'ASC' ]): OrderItem[] {
10 const { direction, field } = buildDirectionAndField(value)
11
12 let finalField: string | Col
13
14 if (field.toLowerCase() === 'match') { // Search
15 finalField = Sequelize.col('similarity')
16 } else if (field === 'videoQuotaUsed') { // Users list
17 finalField = Sequelize.col('videoQuotaUsed')
18 } else {
19 finalField = field
20 }
21
22 return [ [ finalField, direction ], lastSort ]
23}
24
25function getCommentSort (value: string, lastSort: OrderItem = [ 'id', 'ASC' ]): OrderItem[] {
26 const { direction, field } = buildDirectionAndField(value)
27
28 if (field === 'totalReplies') {
29 return [
30 [ Sequelize.literal('"totalReplies"'), direction ],
31 lastSort
32 ]
33 }
34
35 return getSort(value, lastSort)
36}
37
38function getVideoSort (value: string, lastSort: OrderItem = [ 'id', 'ASC' ]): OrderItem[] {
39 const { direction, field } = buildDirectionAndField(value)
40
41 if (field.toLowerCase() === 'trending') { // Sort by aggregation
42 return [
43 [ Sequelize.fn('COALESCE', Sequelize.fn('SUM', Sequelize.col('VideoViews.views')), '0'), direction ],
44
45 [ Sequelize.col('VideoModel.views'), direction ],
46
47 lastSort
48 ]
49 }
50
51 let finalField: string | Col
52
53 // Alias
54 if (field.toLowerCase() === 'match') { // Search
55 finalField = Sequelize.col('similarity')
56 } else {
57 finalField = field
58 }
59
60 const firstSort = typeof finalField === 'string'
61 ? finalField.split('.').concat([ direction ]) as any // FIXME: sequelize typings
62 : [ finalField, direction ]
63
64 return [ firstSort, lastSort ]
65}
66
67function getBlacklistSort (model: any, value: string, lastSort: OrderItem = [ 'id', 'ASC' ]): OrderItem[] {
68 const [ firstSort ] = getSort(value)
69
70 if (model) return [ [ literal(`"${model}.${firstSort[0]}" ${firstSort[1]}`) ], lastSort ] as any[] // FIXME: typings
71 return [ firstSort, lastSort ]
72}
73
74function getFollowsSort (value: string, lastSort: OrderItem = [ 'id', 'ASC' ]): OrderItem[] {
75 const { direction, field } = buildDirectionAndField(value)
76
77 if (field === 'redundancyAllowed') {
78 return [
79 [ 'ActorFollowing', 'Server', 'redundancyAllowed', direction ],
80 lastSort
81 ]
82 }
83
84 return getSort(value, lastSort)
85}
86
87function isOutdated (model: { createdAt: Date, updatedAt: Date }, refreshInterval: number) {
88 const now = Date.now()
89 const createdAtTime = model.createdAt.getTime()
90 const updatedAtTime = model.updatedAt.getTime()
91
92 return (now - createdAtTime) > refreshInterval && (now - updatedAtTime) > refreshInterval
93}
94
95function throwIfNotValid (value: any, validator: (value: any) => boolean, fieldName = 'value', nullable = false) {
96 if (nullable && (value === null || value === undefined)) return
97
98 if (validator(value) === false) {
99 throw new Error(`"${value}" is not a valid ${fieldName}.`)
100 }
101}
102
103function buildTrigramSearchIndex (indexName: string, attribute: string) {
104 return {
105 name: indexName,
106 fields: [ Sequelize.literal('lower(immutable_unaccent(' + attribute + '))') as any ],
107 using: 'gin',
108 operator: 'gin_trgm_ops'
109 }
110}
111
112function createSimilarityAttribute (col: string, value: string) {
113 return Sequelize.fn(
114 'similarity',
115
116 searchTrigramNormalizeCol(col),
117
118 searchTrigramNormalizeValue(value)
119 )
120}
121
122function buildBlockedAccountSQL (blockerIds: number[]) {
123 const blockerIdsString = blockerIds.join(', ')
124
125 return 'SELECT "targetAccountId" AS "id" FROM "accountBlocklist" WHERE "accountId" IN (' + blockerIdsString + ')' +
126 ' UNION ALL ' +
127 'SELECT "account"."id" AS "id" FROM account INNER JOIN "actor" ON account."actorId" = actor.id ' +
128 'INNER JOIN "serverBlocklist" ON "actor"."serverId" = "serverBlocklist"."targetServerId" ' +
129 'WHERE "serverBlocklist"."accountId" IN (' + blockerIdsString + ')'
130}
131
132function buildServerIdsFollowedBy (actorId: any) {
133 const actorIdNumber = parseInt(actorId + '', 10)
134
135 return '(' +
136 'SELECT "actor"."serverId" FROM "actorFollow" ' +
137 'INNER JOIN "actor" ON actor.id = "actorFollow"."targetActorId" ' +
138 'WHERE "actorFollow"."actorId" = ' + actorIdNumber +
139 ')'
140}
141
142function buildWhereIdOrUUID (id: number | string) {
143 return validator.isInt('' + id) ? { id } : { uuid: id }
144}
145
146function parseAggregateResult (result: any) {
147 if (!result) return 0
148
149 const total = parseInt(result + '', 10)
150 if (isNaN(total)) return 0
151
152 return total
153}
154
155const createSafeIn = (model: typeof Model, stringArr: (string | number)[]) => {
156 return stringArr.map(t => {
157 return t === null
158 ? null
159 : model.sequelize.escape('' + t)
160 }).join(', ')
161}
162
163function buildLocalAccountIdsIn () {
164 return literal(
165 '(SELECT "account"."id" FROM "account" INNER JOIN "actor" ON "actor"."id" = "account"."actorId" AND "actor"."serverId" IS NULL)'
166 )
167}
168
169function buildLocalActorIdsIn () {
170 return literal(
171 '(SELECT "actor"."id" FROM "actor" WHERE "actor"."serverId" IS NULL)'
172 )
173}
174
175function buildDirectionAndField (value: string) {
176 let field: string
177 let direction: 'ASC' | 'DESC'
178
179 if (value.substring(0, 1) === '-') {
180 direction = 'DESC'
181 field = value.substring(1)
182 } else {
183 direction = 'ASC'
184 field = value
185 }
186
187 return { direction, field }
188}
189
190function searchAttribute (sourceField?: string, targetField?: string) {
191 if (!sourceField) return {}
192
193 return {
194 [targetField]: {
195 [Op.iLike]: `%${sourceField}%`
196 }
197 }
198}
199
200// ---------------------------------------------------------------------------
201
202export {
203 buildBlockedAccountSQL,
204 buildLocalActorIdsIn,
205 SortType,
206 buildLocalAccountIdsIn,
207 getSort,
208 getCommentSort,
209 getVideoSort,
210 getBlacklistSort,
211 createSimilarityAttribute,
212 throwIfNotValid,
213 buildServerIdsFollowedBy,
214 buildTrigramSearchIndex,
215 buildWhereIdOrUUID,
216 isOutdated,
217 parseAggregateResult,
218 getFollowsSort,
219 buildDirectionAndField,
220 createSafeIn,
221 searchAttribute
222}
223
224// ---------------------------------------------------------------------------
225
226function searchTrigramNormalizeValue (value: string) {
227 return Sequelize.fn('lower', Sequelize.fn('immutable_unaccent', value))
228}
229
230function searchTrigramNormalizeCol (col: string) {
231 return Sequelize.fn('lower', Sequelize.fn('immutable_unaccent', Sequelize.col(col)))
232}