]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blame - server/models/utils.ts
Fix lint
[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
8e286cdc
RK
57 lastSort
58 ]
59 } else if (field === 'publishedAt') {
60 return [
61 [ 'ScheduleVideoUpdate', 'updateAt', direction + ' NULLS LAST' ],
62
63 [ Sequelize.col('VideoModel.publishedAt'), direction ],
64
9a629c6e
C
65 lastSort
66 ]
67 }
68
1735c825
C
69 let finalField: string | Col
70
71 // Alias
72 if (field.toLowerCase() === 'match') { // Search
73 finalField = Sequelize.col('similarity')
74 } else {
75 finalField = field
76 }
77
78 const firstSort = typeof finalField === 'string'
79 ? finalField.split('.').concat([ direction ]) as any // FIXME: sequelize typings
80 : [ finalField, direction ]
afa4374a
C
81
82 return [ firstSort, lastSort ]
5c39adb7
C
83}
84
95153292 85function getBlacklistSort (model: any, value: string, lastSort: OrderItem = [ 'id', 'ASC' ]): OrderItem[] {
1735c825 86 const [ firstSort ] = getSort(value)
792dbaf0 87
a1587156 88 if (model) return [ [ literal(`"${model}.${firstSort[0]}" ${firstSort[1]}`) ], lastSort ] as any[] // FIXME: typings
3bb6c526 89 return [ firstSort, lastSort ]
792dbaf0
GS
90}
91
cb5ce4cb
C
92function getFollowsSort (value: string, lastSort: OrderItem = [ 'id', 'ASC' ]): OrderItem[] {
93 const { direction, field } = buildDirectionAndField(value)
94
95 if (field === 'redundancyAllowed') {
96 return [
97 [ 'ActorFollowing', 'Server', 'redundancyAllowed', direction ],
98 lastSort
99 ]
100 }
101
102 return getSort(value, lastSort)
103}
104
9f79ade6
C
105function isOutdated (model: { createdAt: Date, updatedAt: Date }, refreshInterval: number) {
106 const now = Date.now()
107 const createdAtTime = model.createdAt.getTime()
108 const updatedAtTime = model.updatedAt.getTime()
109
110 return (now - createdAtTime) > refreshInterval && (now - updatedAtTime) > refreshInterval
111}
112
1735c825
C
113function throwIfNotValid (value: any, validator: (value: any) => boolean, fieldName = 'value', nullable = false) {
114 if (nullable && (value === null || value === undefined)) return
115
3fd3ab2d
C
116 if (validator(value) === false) {
117 throw new Error(`"${value}" is not a valid ${fieldName}.`)
118 }
119}
120
57c36b27
C
121function buildTrigramSearchIndex (indexName: string, attribute: string) {
122 return {
123 name: indexName,
b49f22d8
C
124 // FIXME: gin_trgm_ops is not taken into account in Sequelize 6, so adding it ourselves in the literal function
125 fields: [ Sequelize.literal('lower(immutable_unaccent(' + attribute + ')) gin_trgm_ops') as any ],
57c36b27
C
126 using: 'gin',
127 operator: 'gin_trgm_ops'
128 }
129}
130
131function createSimilarityAttribute (col: string, value: string) {
132 return Sequelize.fn(
133 'similarity',
134
135 searchTrigramNormalizeCol(col),
136
137 searchTrigramNormalizeValue(value)
138 )
139}
140
696d83fd 141function buildBlockedAccountSQL (blockerIds: number[]) {
7ad9b984
C
142 const blockerIdsString = blockerIds.join(', ')
143
418d092a 144 return 'SELECT "targetAccountId" AS "id" FROM "accountBlocklist" WHERE "accountId" IN (' + blockerIdsString + ')' +
9d6b9d10 145 ' UNION ' +
7ad9b984
C
146 'SELECT "account"."id" AS "id" FROM account INNER JOIN "actor" ON account."actorId" = actor.id ' +
147 'INNER JOIN "serverBlocklist" ON "actor"."serverId" = "serverBlocklist"."targetServerId" ' +
148 'WHERE "serverBlocklist"."accountId" IN (' + blockerIdsString + ')'
418d092a
C
149}
150
1c58423f
C
151function buildBlockedAccountSQLOptimized (columnNameJoin: string, blockerIds: number[]) {
152 const blockerIdsString = blockerIds.join(', ')
153
154 return [
155 literal(
156 `NOT EXISTS (` +
157 ` SELECT 1 FROM "accountBlocklist" ` +
158 ` WHERE "targetAccountId" = ${columnNameJoin} ` +
159 ` AND "accountId" IN (${blockerIdsString})` +
160 `)`
161 ),
162
163 literal(
164 `NOT EXISTS (` +
165 ` SELECT 1 FROM "account" ` +
166 ` INNER JOIN "actor" ON account."actorId" = actor.id ` +
167 ` INNER JOIN "serverBlocklist" ON "actor"."serverId" = "serverBlocklist"."targetServerId" ` +
168 ` WHERE "account"."id" = ${columnNameJoin} ` +
169 ` AND "serverBlocklist"."accountId" IN (${blockerIdsString})` +
170 `)`
171 )
172 ]
173}
174
418d092a
C
175function buildServerIdsFollowedBy (actorId: any) {
176 const actorIdNumber = parseInt(actorId + '', 10)
177
178 return '(' +
179 'SELECT "actor"."serverId" FROM "actorFollow" ' +
180 'INNER JOIN "actor" ON actor.id = "actorFollow"."targetActorId" ' +
181 'WHERE "actorFollow"."actorId" = ' + actorIdNumber +
a1587156 182 ')'
418d092a 183}
7ad9b984 184
418d092a
C
185function buildWhereIdOrUUID (id: number | string) {
186 return validator.isInt('' + id) ? { id } : { uuid: id }
7ad9b984
C
187}
188
3acc5084
C
189function parseAggregateResult (result: any) {
190 if (!result) return 0
191
192 const total = parseInt(result + '', 10)
193 if (isNaN(total)) return 0
194
195 return total
196}
197
6b9c966f 198const createSafeIn = (model: typeof Model, stringArr: (string | number)[]) => {
5f3e2425
C
199 return stringArr.map(t => {
200 return t === null
201 ? null
202 : model.sequelize.escape('' + t)
203 }).join(', ')
3caf77d3
C
204}
205
6b9c966f
C
206function buildLocalAccountIdsIn () {
207 return literal(
208 '(SELECT "account"."id" FROM "account" INNER JOIN "actor" ON "actor"."id" = "account"."actorId" AND "actor"."serverId" IS NULL)'
209 )
210}
211
212function buildLocalActorIdsIn () {
213 return literal(
214 '(SELECT "actor"."id" FROM "actor" WHERE "actor"."serverId" IS NULL)'
215 )
216}
217
5f3e2425
C
218function buildDirectionAndField (value: string) {
219 let field: string
220 let direction: 'ASC' | 'DESC'
221
222 if (value.substring(0, 1) === '-') {
223 direction = 'DESC'
224 field = value.substring(1)
225 } else {
226 direction = 'ASC'
227 field = value
228 }
229
230 return { direction, field }
231}
232
feb34f6b
C
233function searchAttribute (sourceField?: string, targetField?: string) {
234 if (!sourceField) return {}
0d3a2982
RK
235
236 return {
feb34f6b
C
237 [targetField]: {
238 [Op.iLike]: `%${sourceField}%`
239 }
0d3a2982
RK
240 }
241}
242
5c39adb7
C
243// ---------------------------------------------------------------------------
244
65fcc311 245export {
7ad9b984 246 buildBlockedAccountSQL,
1c58423f 247 buildBlockedAccountSQLOptimized,
6b9c966f 248 buildLocalActorIdsIn,
49cff3a4 249 getPlaylistSort,
06215f15 250 SortType,
6b9c966f 251 buildLocalAccountIdsIn,
792dbaf0 252 getSort,
c1125bca 253 getCommentSort,
9a629c6e 254 getVideoSort,
95153292 255 getBlacklistSort,
57c36b27
C
256 createSimilarityAttribute,
257 throwIfNotValid,
418d092a
C
258 buildServerIdsFollowedBy,
259 buildTrigramSearchIndex,
9f79ade6 260 buildWhereIdOrUUID,
3acc5084 261 isOutdated,
3caf77d3 262 parseAggregateResult,
cb5ce4cb 263 getFollowsSort,
5f3e2425 264 buildDirectionAndField,
e0a92917 265 createSafeIn,
feb34f6b 266 searchAttribute
57c36b27
C
267}
268
269// ---------------------------------------------------------------------------
270
271function searchTrigramNormalizeValue (value: string) {
2cebd797 272 return Sequelize.fn('lower', Sequelize.fn('immutable_unaccent', value))
57c36b27
C
273}
274
275function searchTrigramNormalizeCol (col: string) {
276 return Sequelize.fn('lower', Sequelize.fn('immutable_unaccent', Sequelize.col(col)))
65fcc311 277}