aboutsummaryrefslogtreecommitdiffhomepage
path: root/server/models/account/account-follow.ts
diff options
context:
space:
mode:
Diffstat (limited to 'server/models/account/account-follow.ts')
-rw-r--r--server/models/account/account-follow.ts364
1 files changed, 174 insertions, 190 deletions
diff --git a/server/models/account/account-follow.ts b/server/models/account/account-follow.ts
index 724f37baa..975e7ee7d 100644
--- a/server/models/account/account-follow.ts
+++ b/server/models/account/account-follow.ts
@@ -1,64 +1,45 @@
1import * as Bluebird from 'bluebird'
1import { values } from 'lodash' 2import { values } from 'lodash'
2import * as Sequelize from 'sequelize' 3import * as Sequelize from 'sequelize'
3 4import { AllowNull, BelongsTo, Column, CreatedAt, DataType, ForeignKey, Model, Table, UpdatedAt } from 'sequelize-typescript'
4import { addMethodsToModel, getSort } from '../utils' 5import { FollowState } from '../../../shared/models/accounts'
5import { AccountFollowAttributes, AccountFollowInstance, AccountFollowMethods } from './account-follow-interface'
6import { FOLLOW_STATES } from '../../initializers/constants' 6import { FOLLOW_STATES } from '../../initializers/constants'
7import { ServerModel } from '../server/server'
8import { getSort } from '../utils'
9import { AccountModel } from './account'
7 10
8let AccountFollow: Sequelize.Model<AccountFollowInstance, AccountFollowAttributes> 11@Table({
9let loadByAccountAndTarget: AccountFollowMethods.LoadByAccountAndTarget 12 tableName: 'accountFollow',
10let listFollowingForApi: AccountFollowMethods.ListFollowingForApi 13 indexes: [
11let listFollowersForApi: AccountFollowMethods.ListFollowersForApi
12let listAcceptedFollowerUrlsForApi: AccountFollowMethods.ListAcceptedFollowerUrlsForApi
13let listAcceptedFollowingUrlsForApi: AccountFollowMethods.ListAcceptedFollowingUrlsForApi
14let listAcceptedFollowerSharedInboxUrls: AccountFollowMethods.ListAcceptedFollowerSharedInboxUrls
15let toFormattedJSON: AccountFollowMethods.ToFormattedJSON
16
17export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) {
18 AccountFollow = sequelize.define<AccountFollowInstance, AccountFollowAttributes>('AccountFollow',
19 { 14 {
20 state: { 15 fields: [ 'accountId' ]
21 type: DataTypes.ENUM(values(FOLLOW_STATES)),
22 allowNull: false
23 }
24 }, 16 },
25 { 17 {
26 indexes: [ 18 fields: [ 'targetAccountId' ]
27 { 19 },
28 fields: [ 'accountId' ] 20 {
29 }, 21 fields: [ 'accountId', 'targetAccountId' ],
30 { 22 unique: true
31 fields: [ 'targetAccountId' ]
32 },
33 {
34 fields: [ 'accountId', 'targetAccountId' ],
35 unique: true
36 }
37 ]
38 } 23 }
39 )
40
41 const classMethods = [
42 associate,
43 loadByAccountAndTarget,
44 listFollowingForApi,
45 listFollowersForApi,
46 listAcceptedFollowerUrlsForApi,
47 listAcceptedFollowingUrlsForApi,
48 listAcceptedFollowerSharedInboxUrls
49 ] 24 ]
50 const instanceMethods = [ 25})
51 toFormattedJSON 26export class AccountFollowModel extends Model<AccountFollowModel> {
52 ]
53 addMethodsToModel(AccountFollow, classMethods, instanceMethods)
54 27
55 return AccountFollow 28 @AllowNull(false)
56} 29 @Column(DataType.ENUM(values(FOLLOW_STATES)))
30 state: FollowState
57 31
58// ------------------------------ STATICS ------------------------------ 32 @CreatedAt
33 createdAt: Date
59 34
60function associate (models) { 35 @UpdatedAt
61 AccountFollow.belongsTo(models.Account, { 36 updatedAt: Date
37
38 @ForeignKey(() => AccountModel)
39 @Column
40 accountId: number
41
42 @BelongsTo(() => AccountModel, {
62 foreignKey: { 43 foreignKey: {
63 name: 'accountId', 44 name: 'accountId',
64 allowNull: false 45 allowNull: false
@@ -66,8 +47,13 @@ function associate (models) {
66 as: 'AccountFollower', 47 as: 'AccountFollower',
67 onDelete: 'CASCADE' 48 onDelete: 'CASCADE'
68 }) 49 })
50 AccountFollower: AccountModel
69 51
70 AccountFollow.belongsTo(models.Account, { 52 @ForeignKey(() => AccountModel)
53 @Column
54 targetAccountId: number
55
56 @BelongsTo(() => AccountModel, {
71 foreignKey: { 57 foreignKey: {
72 name: 'targetAccountId', 58 name: 'targetAccountId',
73 allowNull: false 59 allowNull: false
@@ -75,170 +61,168 @@ function associate (models) {
75 as: 'AccountFollowing', 61 as: 'AccountFollowing',
76 onDelete: 'CASCADE' 62 onDelete: 'CASCADE'
77 }) 63 })
78} 64 AccountFollowing: AccountModel
79 65
80toFormattedJSON = function (this: AccountFollowInstance) { 66 static loadByAccountAndTarget (accountId: number, targetAccountId: number, t?: Sequelize.Transaction) {
81 const follower = this.AccountFollower.toFormattedJSON() 67 const query = {
82 const following = this.AccountFollowing.toFormattedJSON() 68 where: {
83 69 accountId,
84 const json = { 70 targetAccountId
85 id: this.id,
86 follower,
87 following,
88 state: this.state,
89 createdAt: this.createdAt,
90 updatedAt: this.updatedAt
91 }
92
93 return json
94}
95
96loadByAccountAndTarget = function (accountId: number, targetAccountId: number, t?: Sequelize.Transaction) {
97 const query = {
98 where: {
99 accountId,
100 targetAccountId
101 },
102 include: [
103 {
104 model: AccountFollow[ 'sequelize' ].models.Account,
105 required: true,
106 as: 'AccountFollower'
107 }, 71 },
108 { 72 include: [
109 model: AccountFollow['sequelize'].models.Account, 73 {
110 required: true, 74 model: AccountModel,
111 as: 'AccountFollowing' 75 required: true,
112 } 76 as: 'AccountFollower'
113 ], 77 },
114 transaction: t 78 {
79 model: AccountModel,
80 required: true,
81 as: 'AccountFollowing'
82 }
83 ],
84 transaction: t
85 }
86
87 return AccountFollowModel.findOne(query)
115 } 88 }
116 89
117 return AccountFollow.findOne(query) 90 static listFollowingForApi (id: number, start: number, count: number, sort: string) {
118} 91 const query = {
92 distinct: true,
93 offset: start,
94 limit: count,
95 order: [ getSort(sort) ],
96 include: [
97 {
98 model: AccountModel,
99 required: true,
100 as: 'AccountFollower',
101 where: {
102 id
103 }
104 },
105 {
106 model: AccountModel,
107 as: 'AccountFollowing',
108 required: true,
109 include: [ ServerModel ]
110 }
111 ]
112 }
119 113
120listFollowingForApi = function (id: number, start: number, count: number, sort: string) { 114 return AccountFollowModel.findAndCountAll(query)
121 const query = { 115 .then(({ rows, count }) => {
122 distinct: true, 116 return {
123 offset: start, 117 data: rows,
124 limit: count, 118 total: count
125 order: [ getSort(sort) ],
126 include: [
127 {
128 model: AccountFollow[ 'sequelize' ].models.Account,
129 required: true,
130 as: 'AccountFollower',
131 where: {
132 id
133 } 119 }
134 }, 120 })
135 {
136 model: AccountFollow['sequelize'].models.Account,
137 as: 'AccountFollowing',
138 required: true,
139 include: [ AccountFollow['sequelize'].models.Server ]
140 }
141 ]
142 } 121 }
143 122
144 return AccountFollow.findAndCountAll(query).then(({ rows, count }) => { 123 static listFollowersForApi (id: number, start: number, count: number, sort: string) {
145 return { 124 const query = {
146 data: rows, 125 distinct: true,
147 total: count 126 offset: start,
127 limit: count,
128 order: [ getSort(sort) ],
129 include: [
130 {
131 model: AccountModel,
132 required: true,
133 as: 'AccountFollower',
134 include: [ ServerModel ]
135 },
136 {
137 model: AccountModel,
138 as: 'AccountFollowing',
139 required: true,
140 where: {
141 id
142 }
143 }
144 ]
148 } 145 }
149 })
150}
151 146
152listFollowersForApi = function (id: number, start: number, count: number, sort: string) { 147 return AccountFollowModel.findAndCountAll(query)
153 const query = { 148 .then(({ rows, count }) => {
154 distinct: true, 149 return {
155 offset: start, 150 data: rows,
156 limit: count, 151 total: count
157 order: [ getSort(sort) ],
158 include: [
159 {
160 model: AccountFollow[ 'sequelize' ].models.Account,
161 required: true,
162 as: 'AccountFollower',
163 include: [ AccountFollow['sequelize'].models.Server ]
164 },
165 {
166 model: AccountFollow['sequelize'].models.Account,
167 as: 'AccountFollowing',
168 required: true,
169 where: {
170 id
171 } 152 }
172 } 153 })
173 ]
174 } 154 }
175 155
176 return AccountFollow.findAndCountAll(query).then(({ rows, count }) => { 156 static listAcceptedFollowerUrlsForApi (accountIds: number[], t: Sequelize.Transaction, start?: number, count?: number) {
177 return { 157 return AccountFollowModel.createListAcceptedFollowForApiQuery('followers', accountIds, t, start, count)
178 data: rows, 158 }
179 total: count
180 }
181 })
182}
183 159
184listAcceptedFollowerUrlsForApi = function (accountIds: number[], t: Sequelize.Transaction, start?: number, count?: number) { 160 static listAcceptedFollowerSharedInboxUrls (accountIds: number[], t: Sequelize.Transaction) {
185 return createListAcceptedFollowForApiQuery('followers', accountIds, t, start, count) 161 return AccountFollowModel.createListAcceptedFollowForApiQuery('followers', accountIds, t, undefined, undefined, 'sharedInboxUrl')
186} 162 }
187 163
188listAcceptedFollowerSharedInboxUrls = function (accountIds: number[], t: Sequelize.Transaction) { 164 static listAcceptedFollowingUrlsForApi (accountIds: number[], t: Sequelize.Transaction, start?: number, count?: number) {
189 return createListAcceptedFollowForApiQuery('followers', accountIds, t, undefined, undefined, 'sharedInboxUrl') 165 return AccountFollowModel.createListAcceptedFollowForApiQuery('following', accountIds, t, start, count)
190} 166 }
191 167
192listAcceptedFollowingUrlsForApi = function (accountIds: number[], t: Sequelize.Transaction, start?: number, count?: number) { 168 private static async createListAcceptedFollowForApiQuery (type: 'followers' | 'following',
193 return createListAcceptedFollowForApiQuery('following', accountIds, t, start, count) 169 accountIds: number[],
194} 170 t: Sequelize.Transaction,
171 start?: number,
172 count?: number,
173 columnUrl = 'url') {
174 let firstJoin: string
175 let secondJoin: string
176
177 if (type === 'followers') {
178 firstJoin = 'targetAccountId'
179 secondJoin = 'accountId'
180 } else {
181 firstJoin = 'accountId'
182 secondJoin = 'targetAccountId'
183 }
195 184
196// ------------------------------ UTILS ------------------------------ 185 const selections = [ '"Follows"."' + columnUrl + '" AS "url"', 'COUNT(*) AS "total"' ]
197 186 const tasks: Bluebird<any>[] = []
198async function createListAcceptedFollowForApiQuery (
199 type: 'followers' | 'following',
200 accountIds: number[],
201 t: Sequelize.Transaction,
202 start?: number,
203 count?: number,
204 columnUrl = 'url'
205) {
206 let firstJoin: string
207 let secondJoin: string
208
209 if (type === 'followers') {
210 firstJoin = 'targetAccountId'
211 secondJoin = 'accountId'
212 } else {
213 firstJoin = 'accountId'
214 secondJoin = 'targetAccountId'
215 }
216 187
217 const selections = [ '"Follows"."' + columnUrl + '" AS "url"', 'COUNT(*) AS "total"' ] 188 for (const selection of selections) {
218 const tasks: Promise<any>[] = [] 189 let query = 'SELECT ' + selection + ' FROM "account" ' +
190 'INNER JOIN "accountFollow" ON "accountFollow"."' + firstJoin + '" = "account"."id" ' +
191 'INNER JOIN "account" AS "Follows" ON "accountFollow"."' + secondJoin + '" = "Follows"."id" ' +
192 'WHERE "account"."id" = ANY ($accountIds) AND "accountFollow"."state" = \'accepted\' '
219 193
220 for (const selection of selections) { 194 if (count !== undefined) query += 'LIMIT ' + count
221 let query = 'SELECT ' + selection + ' FROM "Accounts" ' + 195 if (start !== undefined) query += ' OFFSET ' + start
222 'INNER JOIN "AccountFollows" ON "AccountFollows"."' + firstJoin + '" = "Accounts"."id" ' +
223 'INNER JOIN "Accounts" AS "Follows" ON "AccountFollows"."' + secondJoin + '" = "Follows"."id" ' +
224 'WHERE "Accounts"."id" = ANY ($accountIds) AND "AccountFollows"."state" = \'accepted\' '
225 196
226 if (count !== undefined) query += 'LIMIT ' + count 197 const options = {
227 if (start !== undefined) query += ' OFFSET ' + start 198 bind: { accountIds },
199 type: Sequelize.QueryTypes.SELECT,
200 transaction: t
201 }
202 tasks.push(AccountFollowModel.sequelize.query(query, options))
203 }
228 204
229 const options = { 205 const [ followers, [ { total } ] ] = await
230 bind: { accountIds }, 206 Promise.all(tasks)
231 type: Sequelize.QueryTypes.SELECT, 207 const urls: string[] = followers.map(f => f.url)
232 transaction: t 208
209 return {
210 data: urls,
211 total: parseInt(total, 10)
233 } 212 }
234 tasks.push(AccountFollow['sequelize'].query(query, options))
235 } 213 }
236 214
237 const [ followers, [ { total } ]] = await Promise.all(tasks) 215 toFormattedJSON () {
238 const urls: string[] = followers.map(f => f.url) 216 const follower = this.AccountFollower.toFormattedJSON()
217 const following = this.AccountFollowing.toFormattedJSON()
239 218
240 return { 219 return {
241 data: urls, 220 id: this.id,
242 total: parseInt(total, 10) 221 follower,
222 following,
223 state: this.state,
224 createdAt: this.createdAt,
225 updatedAt: this.updatedAt
226 }
243 } 227 }
244} 228}