Add account avatar
[oweals/peertube.git] / server / models / account / account-follow.ts
1 import { values } from 'lodash'
2 import * as Sequelize from 'sequelize'
3
4 import { addMethodsToModel, getSort } from '../utils'
5 import { AccountFollowAttributes, AccountFollowInstance, AccountFollowMethods } from './account-follow-interface'
6 import { FOLLOW_STATES } from '../../initializers/constants'
7
8 let AccountFollow: Sequelize.Model<AccountFollowInstance, AccountFollowAttributes>
9 let loadByAccountAndTarget: AccountFollowMethods.LoadByAccountAndTarget
10 let listFollowingForApi: AccountFollowMethods.ListFollowingForApi
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     {
20       state: {
21         type: DataTypes.ENUM(values(FOLLOW_STATES)),
22         allowNull: false
23       }
24     },
25     {
26       indexes: [
27         {
28           fields: [ 'accountId' ]
29         },
30         {
31           fields: [ 'targetAccountId' ]
32         },
33         {
34           fields: [ 'accountId', 'targetAccountId' ],
35           unique: true
36         }
37       ]
38     }
39   )
40
41   const classMethods = [
42     associate,
43     loadByAccountAndTarget,
44     listFollowingForApi,
45     listFollowersForApi,
46     listAcceptedFollowerUrlsForApi,
47     listAcceptedFollowingUrlsForApi,
48     listAcceptedFollowerSharedInboxUrls
49   ]
50   const instanceMethods = [
51     toFormattedJSON
52   ]
53   addMethodsToModel(AccountFollow, classMethods, instanceMethods)
54
55   return AccountFollow
56 }
57
58 // ------------------------------ STATICS ------------------------------
59
60 function associate (models) {
61   AccountFollow.belongsTo(models.Account, {
62     foreignKey: {
63       name: 'accountId',
64       allowNull: false
65     },
66     as: 'AccountFollower',
67     onDelete: 'CASCADE'
68   })
69
70   AccountFollow.belongsTo(models.Account, {
71     foreignKey: {
72       name: 'targetAccountId',
73       allowNull: false
74     },
75     as: 'AccountFollowing',
76     onDelete: 'CASCADE'
77   })
78 }
79
80 toFormattedJSON = function (this: AccountFollowInstance) {
81   const follower = this.AccountFollower.toFormattedJSON()
82   const following = this.AccountFollowing.toFormattedJSON()
83
84   const json = {
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       },
108       {
109         model: AccountFollow['sequelize'].models.Account,
110         required: true,
111         as: 'AccountFollowing'
112       }
113     ],
114     transaction: t
115   }
116
117   return AccountFollow.findOne(query)
118 }
119
120 listFollowingForApi = function (id: number, start: number, count: number, sort: string) {
121   const query = {
122     distinct: true,
123     offset: start,
124     limit: 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         }
134       },
135       {
136         model: AccountFollow['sequelize'].models.Account,
137         as: 'AccountFollowing',
138         required: true,
139         include: [ AccountFollow['sequelize'].models.Server ]
140       }
141     ]
142   }
143
144   return AccountFollow.findAndCountAll(query).then(({ rows, count }) => {
145     return {
146       data: rows,
147       total: count
148     }
149   })
150 }
151
152 listFollowersForApi = function (id: number, start: number, count: number, sort: string) {
153   const query = {
154     distinct: true,
155     offset: start,
156     limit: 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         }
172       }
173     ]
174   }
175
176   return AccountFollow.findAndCountAll(query).then(({ rows, count }) => {
177     return {
178       data: rows,
179       total: count
180     }
181   })
182 }
183
184 listAcceptedFollowerUrlsForApi = function (accountIds: number[], t: Sequelize.Transaction, start?: number, count?: number) {
185   return createListAcceptedFollowForApiQuery('followers', accountIds, t, start, count)
186 }
187
188 listAcceptedFollowerSharedInboxUrls = function (accountIds: number[], t: Sequelize.Transaction) {
189   return createListAcceptedFollowForApiQuery('followers', accountIds, t, undefined, undefined, 'sharedInboxUrl')
190 }
191
192 listAcceptedFollowingUrlsForApi = function (accountIds: number[], t: Sequelize.Transaction, start?: number, count?: number) {
193   return createListAcceptedFollowForApiQuery('following', accountIds, t, start, count)
194 }
195
196 // ------------------------------ UTILS ------------------------------
197
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
217   const selections = [ '"Follows"."' + columnUrl + '" AS "url"', 'COUNT(*) AS "total"' ]
218   const tasks: Promise<any>[] = []
219
220   for (const selection of selections) {
221     let query = 'SELECT ' + selection + ' FROM "Accounts" ' +
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
226     if (count !== undefined) query += 'LIMIT ' + count
227     if (start !== undefined) query += ' OFFSET ' + start
228
229     const options = {
230       bind: { accountIds },
231       type: Sequelize.QueryTypes.SELECT,
232       transaction: t
233     }
234     tasks.push(AccountFollow['sequelize'].query(query, options))
235   }
236
237   const [ followers, [ { total } ]] = await Promise.all(tasks)
238   const urls: string[] = followers.map(f => f.url)
239
240   return {
241     data: urls,
242     total: parseInt(total, 10)
243   }
244 }