diff options
Diffstat (limited to 'server/models/account/account-follow.ts')
-rw-r--r-- | server/models/account/account-follow.ts | 364 |
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 @@ | |||
1 | import * as Bluebird from 'bluebird' | ||
1 | import { values } from 'lodash' | 2 | import { values } from 'lodash' |
2 | import * as Sequelize from 'sequelize' | 3 | import * as Sequelize from 'sequelize' |
3 | 4 | import { AllowNull, BelongsTo, Column, CreatedAt, DataType, ForeignKey, Model, Table, UpdatedAt } from 'sequelize-typescript' | |
4 | import { addMethodsToModel, getSort } from '../utils' | 5 | import { FollowState } from '../../../shared/models/accounts' |
5 | import { AccountFollowAttributes, AccountFollowInstance, AccountFollowMethods } from './account-follow-interface' | ||
6 | import { FOLLOW_STATES } from '../../initializers/constants' | 6 | import { FOLLOW_STATES } from '../../initializers/constants' |
7 | import { ServerModel } from '../server/server' | ||
8 | import { getSort } from '../utils' | ||
9 | import { AccountModel } from './account' | ||
7 | 10 | ||
8 | let AccountFollow: Sequelize.Model<AccountFollowInstance, AccountFollowAttributes> | 11 | @Table({ |
9 | let loadByAccountAndTarget: AccountFollowMethods.LoadByAccountAndTarget | 12 | tableName: 'accountFollow', |
10 | let listFollowingForApi: AccountFollowMethods.ListFollowingForApi | 13 | indexes: [ |
11 | let listFollowersForApi: AccountFollowMethods.ListFollowersForApi | ||
12 | let listAcceptedFollowerUrlsForApi: AccountFollowMethods.ListAcceptedFollowerUrlsForApi | ||
13 | let listAcceptedFollowingUrlsForApi: AccountFollowMethods.ListAcceptedFollowingUrlsForApi | ||
14 | let listAcceptedFollowerSharedInboxUrls: AccountFollowMethods.ListAcceptedFollowerSharedInboxUrls | ||
15 | let toFormattedJSON: AccountFollowMethods.ToFormattedJSON | ||
16 | |||
17 | export 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 | 26 | export 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 | ||
60 | function 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 | ||
80 | toFormattedJSON = 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 | |||
96 | loadByAccountAndTarget = 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 | ||
120 | listFollowingForApi = 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 | ||
152 | listFollowersForApi = 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 | ||
184 | listAcceptedFollowerUrlsForApi = 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 | ||
188 | listAcceptedFollowerSharedInboxUrls = 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 | ||
192 | listAcceptedFollowingUrlsForApi = 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>[] = [] | |
198 | async 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 | } |