]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blame - server/models/utils.ts
Safely remove webtorrent files
[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,
b49f22d8
C
116 // FIXME: gin_trgm_ops is not taken into account in Sequelize 6, so adding it ourselves in the literal function
117 fields: [ Sequelize.literal('lower(immutable_unaccent(' + attribute + ')) gin_trgm_ops') as any ],
57c36b27
C
118 using: 'gin',
119 operator: 'gin_trgm_ops'
120 }
121}
122
123function createSimilarityAttribute (col: string, value: string) {
124 return Sequelize.fn(
125 'similarity',
126
127 searchTrigramNormalizeCol(col),
128
129 searchTrigramNormalizeValue(value)
130 )
131}
132
696d83fd 133function buildBlockedAccountSQL (blockerIds: number[]) {
7ad9b984
C
134 const blockerIdsString = blockerIds.join(', ')
135
418d092a 136 return 'SELECT "targetAccountId" AS "id" FROM "accountBlocklist" WHERE "accountId" IN (' + blockerIdsString + ')' +
7ad9b984 137 ' UNION ALL ' +
7ad9b984
C
138 'SELECT "account"."id" AS "id" FROM account INNER JOIN "actor" ON account."actorId" = actor.id ' +
139 'INNER JOIN "serverBlocklist" ON "actor"."serverId" = "serverBlocklist"."targetServerId" ' +
140 'WHERE "serverBlocklist"."accountId" IN (' + blockerIdsString + ')'
418d092a
C
141}
142
1c58423f
C
143function buildBlockedAccountSQLOptimized (columnNameJoin: string, blockerIds: number[]) {
144 const blockerIdsString = blockerIds.join(', ')
145
146 return [
147 literal(
148 `NOT EXISTS (` +
149 ` SELECT 1 FROM "accountBlocklist" ` +
150 ` WHERE "targetAccountId" = ${columnNameJoin} ` +
151 ` AND "accountId" IN (${blockerIdsString})` +
152 `)`
153 ),
154
155 literal(
156 `NOT EXISTS (` +
157 ` SELECT 1 FROM "account" ` +
158 ` INNER JOIN "actor" ON account."actorId" = actor.id ` +
159 ` INNER JOIN "serverBlocklist" ON "actor"."serverId" = "serverBlocklist"."targetServerId" ` +
160 ` WHERE "account"."id" = ${columnNameJoin} ` +
161 ` AND "serverBlocklist"."accountId" IN (${blockerIdsString})` +
162 `)`
163 )
164 ]
165}
166
418d092a
C
167function buildServerIdsFollowedBy (actorId: any) {
168 const actorIdNumber = parseInt(actorId + '', 10)
169
170 return '(' +
171 'SELECT "actor"."serverId" FROM "actorFollow" ' +
172 'INNER JOIN "actor" ON actor.id = "actorFollow"."targetActorId" ' +
173 'WHERE "actorFollow"."actorId" = ' + actorIdNumber +
a1587156 174 ')'
418d092a 175}
7ad9b984 176
418d092a
C
177function buildWhereIdOrUUID (id: number | string) {
178 return validator.isInt('' + id) ? { id } : { uuid: id }
7ad9b984
C
179}
180
3acc5084
C
181function parseAggregateResult (result: any) {
182 if (!result) return 0
183
184 const total = parseInt(result + '', 10)
185 if (isNaN(total)) return 0
186
187 return total
188}
189
6b9c966f 190const createSafeIn = (model: typeof Model, stringArr: (string | number)[]) => {
5f3e2425
C
191 return stringArr.map(t => {
192 return t === null
193 ? null
194 : model.sequelize.escape('' + t)
195 }).join(', ')
3caf77d3
C
196}
197
6b9c966f
C
198function buildLocalAccountIdsIn () {
199 return literal(
200 '(SELECT "account"."id" FROM "account" INNER JOIN "actor" ON "actor"."id" = "account"."actorId" AND "actor"."serverId" IS NULL)'
201 )
202}
203
204function buildLocalActorIdsIn () {
205 return literal(
206 '(SELECT "actor"."id" FROM "actor" WHERE "actor"."serverId" IS NULL)'
207 )
208}
209
5f3e2425
C
210function buildDirectionAndField (value: string) {
211 let field: string
212 let direction: 'ASC' | 'DESC'
213
214 if (value.substring(0, 1) === '-') {
215 direction = 'DESC'
216 field = value.substring(1)
217 } else {
218 direction = 'ASC'
219 field = value
220 }
221
222 return { direction, field }
223}
224
feb34f6b
C
225function searchAttribute (sourceField?: string, targetField?: string) {
226 if (!sourceField) return {}
0d3a2982
RK
227
228 return {
feb34f6b
C
229 [targetField]: {
230 [Op.iLike]: `%${sourceField}%`
231 }
0d3a2982
RK
232 }
233}
234
5c39adb7
C
235// ---------------------------------------------------------------------------
236
65fcc311 237export {
7ad9b984 238 buildBlockedAccountSQL,
1c58423f 239 buildBlockedAccountSQLOptimized,
6b9c966f 240 buildLocalActorIdsIn,
49cff3a4 241 getPlaylistSort,
06215f15 242 SortType,
6b9c966f 243 buildLocalAccountIdsIn,
792dbaf0 244 getSort,
c1125bca 245 getCommentSort,
9a629c6e 246 getVideoSort,
95153292 247 getBlacklistSort,
57c36b27
C
248 createSimilarityAttribute,
249 throwIfNotValid,
418d092a
C
250 buildServerIdsFollowedBy,
251 buildTrigramSearchIndex,
9f79ade6 252 buildWhereIdOrUUID,
3acc5084 253 isOutdated,
3caf77d3 254 parseAggregateResult,
cb5ce4cb 255 getFollowsSort,
5f3e2425 256 buildDirectionAndField,
e0a92917 257 createSafeIn,
feb34f6b 258 searchAttribute
57c36b27
C
259}
260
261// ---------------------------------------------------------------------------
262
263function searchTrigramNormalizeValue (value: string) {
2cebd797 264 return Sequelize.fn('lower', Sequelize.fn('immutable_unaccent', value))
57c36b27
C
265}
266
267function searchTrigramNormalizeCol (col: string) {
268 return Sequelize.fn('lower', Sequelize.fn('immutable_unaccent', Sequelize.col(col)))
65fcc311 269}