Fix lint
[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
16 export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) {
17   AccountFollow = sequelize.define<AccountFollowInstance, AccountFollowAttributes>('AccountFollow',
18     {
19       state: {
20         type: DataTypes.ENUM(values(FOLLOW_STATES)),
21         allowNull: false
22       }
23     },
24     {
25       indexes: [
26         {
27           fields: [ 'accountId' ]
28         },
29         {
30           fields: [ 'targetAccountId' ]
31         },
32         {
33           fields: [ 'accountId', 'targetAccountId' ],
34           unique: true
35         }
36       ]
37     }
38   )
39
40   const classMethods = [
41     associate,
42     loadByAccountAndTarget,
43     listFollowingForApi,
44     listFollowersForApi,
45     listAcceptedFollowerUrlsForApi,
46     listAcceptedFollowingUrlsForApi,
47     listAcceptedFollowerSharedInboxUrls
48   ]
49   addMethodsToModel(AccountFollow, classMethods)
50
51   return AccountFollow
52 }
53
54 // ------------------------------ STATICS ------------------------------
55
56 function associate (models) {
57   AccountFollow.belongsTo(models.Account, {
58     foreignKey: {
59       name: 'accountId',
60       allowNull: false
61     },
62     as: 'AccountFollower',
63     onDelete: 'CASCADE'
64   })
65
66   AccountFollow.belongsTo(models.Account, {
67     foreignKey: {
68       name: 'targetAccountId',
69       allowNull: false
70     },
71     as: 'AccountFollowing',
72     onDelete: 'CASCADE'
73   })
74 }
75
76 loadByAccountAndTarget = function (accountId: number, targetAccountId: number) {
77   const query = {
78     where: {
79       accountId,
80       targetAccountId
81     }
82   }
83
84   return AccountFollow.findOne(query)
85 }
86
87 listFollowingForApi = function (id: number, start: number, count: number, sort: string) {
88   const query = {
89     distinct: true,
90     offset: start,
91     limit: count,
92     order: [ getSort(sort) ],
93     include: [
94       {
95         model: AccountFollow[ 'sequelize' ].models.Account,
96         required: true,
97         as: 'AccountFollower',
98         where: {
99           id
100         }
101       },
102       {
103         model: AccountFollow['sequelize'].models.Account,
104         as: 'AccountFollowing',
105         required: true,
106         include: [ AccountFollow['sequelize'].models.Server ]
107       }
108     ]
109   }
110
111   return AccountFollow.findAndCountAll(query).then(({ rows, count }) => {
112     return {
113       data: rows.map(r => r.AccountFollowing),
114       total: count
115     }
116   })
117 }
118
119 listFollowersForApi = function (id: number, start: number, count: number, sort: string) {
120   const query = {
121     distinct: true,
122     offset: start,
123     limit: count,
124     order: [ getSort(sort) ],
125     include: [
126       {
127         model: AccountFollow[ 'sequelize' ].models.Account,
128         required: true,
129         as: 'AccountFollower',
130         include: [ AccountFollow['sequelize'].models.Server ]
131       },
132       {
133         model: AccountFollow['sequelize'].models.Account,
134         as: 'AccountFollowing',
135         required: true,
136         where: {
137           id
138         }
139       }
140     ]
141   }
142
143   return AccountFollow.findAndCountAll(query).then(({ rows, count }) => {
144     return {
145       data: rows.map(r => r.AccountFollower),
146       total: count
147     }
148   })
149 }
150
151 listAcceptedFollowerUrlsForApi = function (accountIds: number[], start?: number, count?: number) {
152   return createListAcceptedFollowForApiQuery('followers', accountIds, start, count)
153 }
154
155 listAcceptedFollowerSharedInboxUrls = function (accountIds: number[]) {
156   return createListAcceptedFollowForApiQuery('followers', accountIds, undefined, undefined, 'sharedInboxUrl')
157 }
158
159 listAcceptedFollowingUrlsForApi = function (accountIds: number[], start?: number, count?: number) {
160   return createListAcceptedFollowForApiQuery('following', accountIds, start, count)
161 }
162
163 // ------------------------------ UTILS ------------------------------
164
165 async function createListAcceptedFollowForApiQuery (
166   type: 'followers' | 'following',
167   accountIds: number[],
168   start?: number,
169   count?: number,
170   columnUrl = 'url'
171 ) {
172   let firstJoin: string
173   let secondJoin: string
174
175   if (type === 'followers') {
176     firstJoin = 'targetAccountId'
177     secondJoin = 'accountId'
178   } else {
179     firstJoin = 'accountId'
180     secondJoin = 'targetAccountId'
181   }
182
183   const selections = [ '"Follows"."' + columnUrl + '" AS "url"', 'COUNT(*) AS "total"' ]
184   const tasks: Promise<any>[] = []
185
186   for (const selection of selections) {
187     let query = 'SELECT ' + selection + ' FROM "Accounts" ' +
188       'INNER JOIN "AccountFollows" ON "AccountFollows"."' + firstJoin + '" = "Accounts"."id" ' +
189       'INNER JOIN "Accounts" AS "Follows" ON "AccountFollows"."' + secondJoin + '" = "Follows"."id" ' +
190       'WHERE "Accounts"."id" = ANY ($accountIds) AND "AccountFollows"."state" = \'accepted\' '
191
192     if (start !== undefined) query += 'LIMIT ' + start
193     if (count !== undefined) query += ', ' + count
194
195     const options = {
196       bind: { accountIds },
197       type: Sequelize.QueryTypes.SELECT
198     }
199     tasks.push(AccountFollow['sequelize'].query(query, options))
200   }
201
202   const [ followers, [ { total } ]] = await Promise.all(tasks)
203   const urls: string[] = followers.map(f => f.url)
204
205   return {
206     data: urls,
207     total: parseInt(total, 10)
208   }
209 }