Begin moving video channel to actor
[oweals/peertube.git] / server / models / activitypub / actor-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/actors'
6 import { FOLLOW_STATES } from '../../initializers/constants'
7 import { ServerModel } from '../server/server'
8 import { getSort } from '../utils'
9 import { ActorModel } from './actor'
10
11 @Table({
12   tableName: 'actorFollow',
13   indexes: [
14     {
15       fields: [ 'actorId' ]
16     },
17     {
18       fields: [ 'targetActorId' ]
19     },
20     {
21       fields: [ 'actorId', 'targetActorId' ],
22       unique: true
23     }
24   ]
25 })
26 export class ActorFollowModel extends Model<ActorFollowModel> {
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(() => ActorModel)
39   @Column
40   actorId: number
41
42   @BelongsTo(() => ActorModel, {
43     foreignKey: {
44       name: 'actorId',
45       allowNull: false
46     },
47     as: 'ActorFollower',
48     onDelete: 'CASCADE'
49   })
50   ActorFollower: ActorModel
51
52   @ForeignKey(() => ActorModel)
53   @Column
54   targetActorId: number
55
56   @BelongsTo(() => ActorModel, {
57     foreignKey: {
58       name: 'targetActorId',
59       allowNull: false
60     },
61     as: 'ActorFollowing',
62     onDelete: 'CASCADE'
63   })
64   ActorFollowing: ActorModel
65
66   static loadByActorAndTarget (actorId: number, targetActorId: number, t?: Sequelize.Transaction) {
67     const query = {
68       where: {
69         actorId,
70         targetActorId: targetActorId
71       },
72       include: [
73         {
74           model: ActorModel,
75           required: true,
76           as: 'ActorFollower'
77         },
78         {
79           model: ActorModel,
80           required: true,
81           as: 'ActorFollowing'
82         }
83       ],
84       transaction: t
85     }
86
87     return ActorFollowModel.findOne(query)
88   }
89
90   static loadByActorAndTargetHost (actorId: number, targetHost: string, t?: Sequelize.Transaction) {
91     const query = {
92       where: {
93         actorId
94       },
95       include: [
96         {
97           model: ActorModel,
98           required: true,
99           as: 'ActorFollower'
100         },
101         {
102           model: ActorModel,
103           required: true,
104           as: 'ActorFollowing',
105           include: [
106             {
107               model: ServerModel,
108               required: true,
109               where: {
110                 host: targetHost
111               }
112             }
113           ]
114         }
115       ],
116       transaction: t
117     }
118
119     return ActorFollowModel.findOne(query)
120   }
121
122   static listFollowingForApi (id: number, start: number, count: number, sort: string) {
123     const query = {
124       distinct: true,
125       offset: start,
126       limit: count,
127       order: [ getSort(sort) ],
128       include: [
129         {
130           model: ActorModel,
131           required: true,
132           as: 'ActorFollower',
133           where: {
134             id
135           }
136         },
137         {
138           model: ActorModel,
139           as: 'ActorFollowing',
140           required: true,
141           include: [ ServerModel ]
142         }
143       ]
144     }
145
146     return ActorFollowModel.findAndCountAll(query)
147       .then(({ rows, count }) => {
148         return {
149           data: rows,
150           total: count
151         }
152       })
153   }
154
155   static listFollowersForApi (id: number, start: number, count: number, sort: string) {
156     const query = {
157       distinct: true,
158       offset: start,
159       limit: count,
160       order: [ getSort(sort) ],
161       include: [
162         {
163           model: ActorModel,
164           required: true,
165           as: 'ActorFollower',
166           include: [ ServerModel ]
167         },
168         {
169           model: ActorModel,
170           as: 'ActorFollowing',
171           required: true,
172           where: {
173             id
174           }
175         }
176       ]
177     }
178
179     return ActorFollowModel.findAndCountAll(query)
180       .then(({ rows, count }) => {
181         return {
182           data: rows,
183           total: count
184         }
185       })
186   }
187
188   static listAcceptedFollowerUrlsForApi (actorIds: number[], t: Sequelize.Transaction, start?: number, count?: number) {
189     return ActorFollowModel.createListAcceptedFollowForApiQuery('followers', actorIds, t, start, count)
190   }
191
192   static listAcceptedFollowerSharedInboxUrls (actorIds: number[], t: Sequelize.Transaction) {
193     return ActorFollowModel.createListAcceptedFollowForApiQuery('followers', actorIds, t, undefined, undefined, 'sharedInboxUrl')
194   }
195
196   static listAcceptedFollowingUrlsForApi (actorIds: number[], t: Sequelize.Transaction, start?: number, count?: number) {
197     return ActorFollowModel.createListAcceptedFollowForApiQuery('following', actorIds, t, start, count)
198   }
199
200   private static async createListAcceptedFollowForApiQuery (type: 'followers' | 'following',
201                                        actorIds: number[],
202                                        t: Sequelize.Transaction,
203                                        start?: number,
204                                        count?: number,
205                                        columnUrl = 'url') {
206     let firstJoin: string
207     let secondJoin: string
208
209     if (type === 'followers') {
210       firstJoin = 'targetActorId'
211       secondJoin = 'actorId'
212     } else {
213       firstJoin = 'actorId'
214       secondJoin = 'targetActorId'
215     }
216
217     const selections = [ '"Follows"."' + columnUrl + '" AS "url"', 'COUNT(*) AS "total"' ]
218     const tasks: Bluebird<any>[] = []
219
220     for (const selection of selections) {
221       let query = 'SELECT ' + selection + ' FROM "actor" ' +
222         'INNER JOIN "actorFollow" ON "actorFollow"."' + firstJoin + '" = "actor"."id" ' +
223         'INNER JOIN "actor" AS "Follows" ON "actorFollow"."' + secondJoin + '" = "Follows"."id" ' +
224         'WHERE "actor"."id" = ANY ($actorIds) AND "actorFollow"."state" = \'accepted\' '
225
226       if (count !== undefined) query += 'LIMIT ' + count
227       if (start !== undefined) query += ' OFFSET ' + start
228
229       const options = {
230         bind: { actorIds },
231         type: Sequelize.QueryTypes.SELECT,
232         transaction: t
233       }
234       tasks.push(ActorFollowModel.sequelize.query(query, options))
235     }
236
237     const [ followers, [ { total } ] ] = await
238     Promise.all(tasks)
239     const urls: string[] = followers.map(f => f.url)
240
241     return {
242       data: urls,
243       total: parseInt(total, 10)
244     }
245   }
246
247   toFormattedJSON () {
248     const follower = this.ActorFollower.toFormattedJSON()
249     const following = this.ActorFollowing.toFormattedJSON()
250
251     return {
252       id: this.id,
253       follower,
254       following,
255       state: this.state,
256       createdAt: this.createdAt,
257       updatedAt: this.updatedAt
258     }
259   }
260 }