975e7ee7dafc1d385059249f17d53437545fa59b
[oweals/peertube.git] / server / models / account / account-follow.ts
1 import * as Bluebird from 'bluebird'
2 import { values } from 'lodash'
3 import * as Sequelize from 'sequelize'
4 import { AllowNull, BelongsTo, Column, CreatedAt, DataType, ForeignKey, Model, Table, UpdatedAt } from 'sequelize-typescript'
5 import { FollowState } from '../../../shared/models/accounts'
6 import { FOLLOW_STATES } from '../../initializers/constants'
7 import { ServerModel } from '../server/server'
8 import { getSort } from '../utils'
9 import { AccountModel } from './account'
10
11 @Table({
12   tableName: 'accountFollow',
13   indexes: [
14     {
15       fields: [ 'accountId' ]
16     },
17     {
18       fields: [ 'targetAccountId' ]
19     },
20     {
21       fields: [ 'accountId', 'targetAccountId' ],
22       unique: true
23     }
24   ]
25 })
26 export class AccountFollowModel extends Model<AccountFollowModel> {
27
28   @AllowNull(false)
29   @Column(DataType.ENUM(values(FOLLOW_STATES)))
30   state: FollowState
31
32   @CreatedAt
33   createdAt: Date
34
35   @UpdatedAt
36   updatedAt: Date
37
38   @ForeignKey(() => AccountModel)
39   @Column
40   accountId: number
41
42   @BelongsTo(() => AccountModel, {
43     foreignKey: {
44       name: 'accountId',
45       allowNull: false
46     },
47     as: 'AccountFollower',
48     onDelete: 'CASCADE'
49   })
50   AccountFollower: AccountModel
51
52   @ForeignKey(() => AccountModel)
53   @Column
54   targetAccountId: number
55
56   @BelongsTo(() => AccountModel, {
57     foreignKey: {
58       name: 'targetAccountId',
59       allowNull: false
60     },
61     as: 'AccountFollowing',
62     onDelete: 'CASCADE'
63   })
64   AccountFollowing: AccountModel
65
66   static loadByAccountAndTarget (accountId: number, targetAccountId: number, t?: Sequelize.Transaction) {
67     const query = {
68       where: {
69         accountId,
70         targetAccountId
71       },
72       include: [
73         {
74           model: AccountModel,
75           required: true,
76           as: 'AccountFollower'
77         },
78         {
79           model: AccountModel,
80           required: true,
81           as: 'AccountFollowing'
82         }
83       ],
84       transaction: t
85     }
86
87     return AccountFollowModel.findOne(query)
88   }
89
90   static listFollowingForApi (id: number, start: number, count: number, sort: string) {
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     }
113
114     return AccountFollowModel.findAndCountAll(query)
115       .then(({ rows, count }) => {
116         return {
117           data: rows,
118           total: count
119         }
120       })
121   }
122
123   static listFollowersForApi (id: number, start: number, count: number, sort: string) {
124     const query = {
125       distinct: true,
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       ]
145     }
146
147     return AccountFollowModel.findAndCountAll(query)
148       .then(({ rows, count }) => {
149         return {
150           data: rows,
151           total: count
152         }
153       })
154   }
155
156   static listAcceptedFollowerUrlsForApi (accountIds: number[], t: Sequelize.Transaction, start?: number, count?: number) {
157     return AccountFollowModel.createListAcceptedFollowForApiQuery('followers', accountIds, t, start, count)
158   }
159
160   static listAcceptedFollowerSharedInboxUrls (accountIds: number[], t: Sequelize.Transaction) {
161     return AccountFollowModel.createListAcceptedFollowForApiQuery('followers', accountIds, t, undefined, undefined, 'sharedInboxUrl')
162   }
163
164   static listAcceptedFollowingUrlsForApi (accountIds: number[], t: Sequelize.Transaction, start?: number, count?: number) {
165     return AccountFollowModel.createListAcceptedFollowForApiQuery('following', accountIds, t, start, count)
166   }
167
168   private static async createListAcceptedFollowForApiQuery (type: 'followers' | 'following',
169                                        accountIds: number[],
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     }
184
185     const selections = [ '"Follows"."' + columnUrl + '" AS "url"', 'COUNT(*) AS "total"' ]
186     const tasks: Bluebird<any>[] = []
187
188     for (const selection of selections) {
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\' '
193
194       if (count !== undefined) query += 'LIMIT ' + count
195       if (start !== undefined) query += ' OFFSET ' + start
196
197       const options = {
198         bind: { accountIds },
199         type: Sequelize.QueryTypes.SELECT,
200         transaction: t
201       }
202       tasks.push(AccountFollowModel.sequelize.query(query, options))
203     }
204
205     const [ followers, [ { total } ] ] = await
206     Promise.all(tasks)
207     const urls: string[] = followers.map(f => f.url)
208
209     return {
210       data: urls,
211       total: parseInt(total, 10)
212     }
213   }
214
215   toFormattedJSON () {
216     const follower = this.AccountFollower.toFormattedJSON()
217     const following = this.AccountFollowing.toFormattedJSON()
218
219     return {
220       id: this.id,
221       follower,
222       following,
223       state: this.state,
224       createdAt: this.createdAt,
225       updatedAt: this.updatedAt
226     }
227   }
228 }