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