diff options
Diffstat (limited to 'server/models/utils.ts')
-rw-r--r-- | server/models/utils.ts | 88 |
1 files changed, 68 insertions, 20 deletions
diff --git a/server/models/utils.ts b/server/models/utils.ts index 5b4093aec..2b172f608 100644 --- a/server/models/utils.ts +++ b/server/models/utils.ts | |||
@@ -1,25 +1,29 @@ | |||
1 | import { Sequelize } from 'sequelize-typescript' | 1 | import { Sequelize } from 'sequelize-typescript' |
2 | import * as validator from 'validator' | ||
3 | import { OrderItem } from 'sequelize' | ||
4 | import { Col } from 'sequelize/types/lib/utils' | ||
2 | 5 | ||
3 | type SortType = { sortModel: any, sortValue: string } | 6 | type SortType = { sortModel: any, sortValue: string } |
4 | 7 | ||
5 | // Translate for example "-name" to [ [ 'name', 'DESC' ], [ 'id', 'ASC' ] ] | 8 | // Translate for example "-name" to [ [ 'name', 'DESC' ], [ 'id', 'ASC' ] ] |
6 | function getSort (value: string, lastSort: string[] = [ 'id', 'ASC' ]) { | 9 | function getSort (value: string, lastSort: OrderItem = [ 'id', 'ASC' ]): OrderItem[] { |
7 | let { direction, field } = buildDirectionAndField(value) | 10 | const { direction, field } = buildDirectionAndField(value) |
11 | |||
12 | let finalField: string | Col | ||
8 | 13 | ||
9 | if (field.toLowerCase() === 'match') { // Search | 14 | if (field.toLowerCase() === 'match') { // Search |
10 | field = Sequelize.col('similarity') | 15 | finalField = Sequelize.col('similarity') |
16 | } else { | ||
17 | finalField = field | ||
11 | } | 18 | } |
12 | 19 | ||
13 | return [ [ field, direction ], lastSort ] | 20 | return [ [ finalField, direction ], lastSort ] |
14 | } | 21 | } |
15 | 22 | ||
16 | function getVideoSort (value: string, lastSort: string[] = [ 'id', 'ASC' ]) { | 23 | function getVideoSort (value: string, lastSort: OrderItem = [ 'id', 'ASC' ]): OrderItem[] { |
17 | let { direction, field } = buildDirectionAndField(value) | 24 | const { direction, field } = buildDirectionAndField(value) |
18 | 25 | ||
19 | // Alias | 26 | if (field.toLowerCase() === 'trending') { // Sort by aggregation |
20 | if (field.toLowerCase() === 'match') { // Search | ||
21 | field = Sequelize.col('similarity') | ||
22 | } else if (field.toLowerCase() === 'trending') { // Sort by aggregation | ||
23 | return [ | 27 | return [ |
24 | [ Sequelize.fn('COALESCE', Sequelize.fn('SUM', Sequelize.col('VideoViews.views')), '0'), direction ], | 28 | [ Sequelize.fn('COALESCE', Sequelize.fn('SUM', Sequelize.col('VideoViews.views')), '0'), direction ], |
25 | 29 | ||
@@ -29,21 +33,40 @@ function getVideoSort (value: string, lastSort: string[] = [ 'id', 'ASC' ]) { | |||
29 | ] | 33 | ] |
30 | } | 34 | } |
31 | 35 | ||
32 | const firstSort = typeof field === 'string' ? | 36 | let finalField: string | Col |
33 | field.split('.').concat([ direction ]) : | 37 | |
34 | [ field, direction ] | 38 | // Alias |
39 | if (field.toLowerCase() === 'match') { // Search | ||
40 | finalField = Sequelize.col('similarity') | ||
41 | } else { | ||
42 | finalField = field | ||
43 | } | ||
44 | |||
45 | const firstSort = typeof finalField === 'string' | ||
46 | ? finalField.split('.').concat([ direction ]) as any // FIXME: sequelize typings | ||
47 | : [ finalField, direction ] | ||
35 | 48 | ||
36 | return [ firstSort, lastSort ] | 49 | return [ firstSort, lastSort ] |
37 | } | 50 | } |
38 | 51 | ||
39 | function getSortOnModel (model: any, value: string, lastSort: string[] = [ 'id', 'ASC' ]) { | 52 | function getSortOnModel (model: any, value: string, lastSort: OrderItem = [ 'id', 'ASC' ]): OrderItem[] { |
40 | let [ firstSort ] = getSort(value) | 53 | const [ firstSort ] = getSort(value) |
41 | 54 | ||
42 | if (model) return [ [ model, firstSort[0], firstSort[1] ], lastSort ] | 55 | if (model) return [ [ model, firstSort[0], firstSort[1] ], lastSort ] |
43 | return [ firstSort, lastSort ] | 56 | return [ firstSort, lastSort ] |
44 | } | 57 | } |
45 | 58 | ||
46 | function throwIfNotValid (value: any, validator: (value: any) => boolean, fieldName = 'value') { | 59 | function isOutdated (model: { createdAt: Date, updatedAt: Date }, refreshInterval: number) { |
60 | const now = Date.now() | ||
61 | const createdAtTime = model.createdAt.getTime() | ||
62 | const updatedAtTime = model.updatedAt.getTime() | ||
63 | |||
64 | return (now - createdAtTime) > refreshInterval && (now - updatedAtTime) > refreshInterval | ||
65 | } | ||
66 | |||
67 | function throwIfNotValid (value: any, validator: (value: any) => boolean, fieldName = 'value', nullable = false) { | ||
68 | if (nullable && (value === null || value === undefined)) return | ||
69 | |||
47 | if (validator(value) === false) { | 70 | if (validator(value) === false) { |
48 | throw new Error(`"${value}" is not a valid ${fieldName}.`) | 71 | throw new Error(`"${value}" is not a valid ${fieldName}.`) |
49 | } | 72 | } |
@@ -74,13 +97,34 @@ function buildBlockedAccountSQL (serverAccountId: number, userAccountId?: number | |||
74 | 97 | ||
75 | const blockerIdsString = blockerIds.join(', ') | 98 | const blockerIdsString = blockerIds.join(', ') |
76 | 99 | ||
77 | const query = 'SELECT "targetAccountId" AS "id" FROM "accountBlocklist" WHERE "accountId" IN (' + blockerIdsString + ')' + | 100 | return 'SELECT "targetAccountId" AS "id" FROM "accountBlocklist" WHERE "accountId" IN (' + blockerIdsString + ')' + |
78 | ' UNION ALL ' + | 101 | ' UNION ALL ' + |
79 | 'SELECT "account"."id" AS "id" FROM account INNER JOIN "actor" ON account."actorId" = actor.id ' + | 102 | 'SELECT "account"."id" AS "id" FROM account INNER JOIN "actor" ON account."actorId" = actor.id ' + |
80 | 'INNER JOIN "serverBlocklist" ON "actor"."serverId" = "serverBlocklist"."targetServerId" ' + | 103 | 'INNER JOIN "serverBlocklist" ON "actor"."serverId" = "serverBlocklist"."targetServerId" ' + |
81 | 'WHERE "serverBlocklist"."accountId" IN (' + blockerIdsString + ')' | 104 | 'WHERE "serverBlocklist"."accountId" IN (' + blockerIdsString + ')' |
105 | } | ||
106 | |||
107 | function buildServerIdsFollowedBy (actorId: any) { | ||
108 | const actorIdNumber = parseInt(actorId + '', 10) | ||
109 | |||
110 | return '(' + | ||
111 | 'SELECT "actor"."serverId" FROM "actorFollow" ' + | ||
112 | 'INNER JOIN "actor" ON actor.id = "actorFollow"."targetActorId" ' + | ||
113 | 'WHERE "actorFollow"."actorId" = ' + actorIdNumber + | ||
114 | ')' | ||
115 | } | ||
116 | |||
117 | function buildWhereIdOrUUID (id: number | string) { | ||
118 | return validator.isInt('' + id) ? { id } : { uuid: id } | ||
119 | } | ||
120 | |||
121 | function parseAggregateResult (result: any) { | ||
122 | if (!result) return 0 | ||
123 | |||
124 | const total = parseInt(result + '', 10) | ||
125 | if (isNaN(total)) return 0 | ||
82 | 126 | ||
83 | return query | 127 | return total |
84 | } | 128 | } |
85 | 129 | ||
86 | // --------------------------------------------------------------------------- | 130 | // --------------------------------------------------------------------------- |
@@ -93,7 +137,11 @@ export { | |||
93 | getSortOnModel, | 137 | getSortOnModel, |
94 | createSimilarityAttribute, | 138 | createSimilarityAttribute, |
95 | throwIfNotValid, | 139 | throwIfNotValid, |
96 | buildTrigramSearchIndex | 140 | buildServerIdsFollowedBy, |
141 | buildTrigramSearchIndex, | ||
142 | buildWhereIdOrUUID, | ||
143 | isOutdated, | ||
144 | parseAggregateResult | ||
97 | } | 145 | } |
98 | 146 | ||
99 | // --------------------------------------------------------------------------- | 147 | // --------------------------------------------------------------------------- |
@@ -107,7 +155,7 @@ function searchTrigramNormalizeCol (col: string) { | |||
107 | } | 155 | } |
108 | 156 | ||
109 | function buildDirectionAndField (value: string) { | 157 | function buildDirectionAndField (value: string) { |
110 | let field: any | 158 | let field: string |
111 | let direction: 'ASC' | 'DESC' | 159 | let direction: 'ASC' | 'DESC' |
112 | 160 | ||
113 | if (value.substring(0, 1) === '-') { | 161 | if (value.substring(0, 1) === '-') { |