]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blob - server/models/account/account-follow.ts
Add ability to unfollow a server
[github/Chocobozzz/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) {
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 }
115
116 return AccountFollow.findOne(query)
117 }
118
119 listFollowingForApi = 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 where: {
131 id
132 }
133 },
134 {
135 model: AccountFollow['sequelize'].models.Account,
136 as: 'AccountFollowing',
137 required: true,
138 include: [ AccountFollow['sequelize'].models.Server ]
139 }
140 ]
141 }
142
143 return AccountFollow.findAndCountAll(query).then(({ rows, count }) => {
144 return {
145 data: rows,
146 total: count
147 }
148 })
149 }
150
151 listFollowersForApi = function (id: number, start: number, count: number, sort: string) {
152 const query = {
153 distinct: true,
154 offset: start,
155 limit: count,
156 order: [ getSort(sort) ],
157 include: [
158 {
159 model: AccountFollow[ 'sequelize' ].models.Account,
160 required: true,
161 as: 'AccountFollower',
162 include: [ AccountFollow['sequelize'].models.Server ]
163 },
164 {
165 model: AccountFollow['sequelize'].models.Account,
166 as: 'AccountFollowing',
167 required: true,
168 where: {
169 id
170 }
171 }
172 ]
173 }
174
175 return AccountFollow.findAndCountAll(query).then(({ rows, count }) => {
176 return {
177 data: rows,
178 total: count
179 }
180 })
181 }
182
183 listAcceptedFollowerUrlsForApi = function (accountIds: number[], start?: number, count?: number) {
184 return createListAcceptedFollowForApiQuery('followers', accountIds, start, count)
185 }
186
187 listAcceptedFollowerSharedInboxUrls = function (accountIds: number[]) {
188 return createListAcceptedFollowForApiQuery('followers', accountIds, undefined, undefined, 'sharedInboxUrl')
189 }
190
191 listAcceptedFollowingUrlsForApi = function (accountIds: number[], start?: number, count?: number) {
192 return createListAcceptedFollowForApiQuery('following', accountIds, start, count)
193 }
194
195 // ------------------------------ UTILS ------------------------------
196
197 async function createListAcceptedFollowForApiQuery (
198 type: 'followers' | 'following',
199 accountIds: number[],
200 start?: number,
201 count?: number,
202 columnUrl = 'url'
203 ) {
204 let firstJoin: string
205 let secondJoin: string
206
207 if (type === 'followers') {
208 firstJoin = 'targetAccountId'
209 secondJoin = 'accountId'
210 } else {
211 firstJoin = 'accountId'
212 secondJoin = 'targetAccountId'
213 }
214
215 const selections = [ '"Follows"."' + columnUrl + '" AS "url"', 'COUNT(*) AS "total"' ]
216 const tasks: Promise<any>[] = []
217
218 for (const selection of selections) {
219 let query = 'SELECT ' + selection + ' FROM "Accounts" ' +
220 'INNER JOIN "AccountFollows" ON "AccountFollows"."' + firstJoin + '" = "Accounts"."id" ' +
221 'INNER JOIN "Accounts" AS "Follows" ON "AccountFollows"."' + secondJoin + '" = "Follows"."id" ' +
222 'WHERE "Accounts"."id" = ANY ($accountIds) AND "AccountFollows"."state" = \'accepted\' '
223
224 if (start !== undefined) query += 'LIMIT ' + start
225 if (count !== undefined) query += ', ' + count
226
227 const options = {
228 bind: { accountIds },
229 type: Sequelize.QueryTypes.SELECT
230 }
231 tasks.push(AccountFollow['sequelize'].query(query, options))
232 }
233
234 const [ followers, [ { total } ]] = await Promise.all(tasks)
235 const urls: string[] = followers.map(f => f.url)
236
237 return {
238 data: urls,
239 total: parseInt(total, 10)
240 }
241 }