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