Merge branch 'release/1.4.0' into develop
[oweals/peertube.git] / server / controllers / api / server / follows.ts
1 import * as express from 'express'
2 import { UserRight } from '../../../../shared/models/users'
3 import { logger } from '../../../helpers/logger'
4 import { getFormattedObjects, getServerActor } from '../../../helpers/utils'
5 import { SERVER_ACTOR_NAME } from '../../../initializers/constants'
6 import { sendAccept, sendReject, sendUndoFollow } from '../../../lib/activitypub/send'
7 import {
8   asyncMiddleware,
9   authenticate,
10   ensureUserHasRight,
11   paginationValidator,
12   setBodyHostsPort,
13   setDefaultPagination,
14   setDefaultSort
15 } from '../../../middlewares'
16 import {
17   acceptOrRejectFollowerValidator,
18   followersSortValidator,
19   followingSortValidator,
20   followValidator,
21   getFollowerValidator,
22   removeFollowingValidator
23 } from '../../../middlewares/validators'
24 import { ActorFollowModel } from '../../../models/activitypub/actor-follow'
25 import { JobQueue } from '../../../lib/job-queue'
26 import { removeRedundancyOf } from '../../../lib/redundancy'
27 import { sequelizeTypescript } from '../../../initializers/database'
28 import { autoFollowBackIfNeeded } from '../../../lib/activitypub/follow'
29
30 const serverFollowsRouter = express.Router()
31 serverFollowsRouter.get('/following',
32   paginationValidator,
33   followingSortValidator,
34   setDefaultSort,
35   setDefaultPagination,
36   asyncMiddleware(listFollowing)
37 )
38
39 serverFollowsRouter.post('/following',
40   authenticate,
41   ensureUserHasRight(UserRight.MANAGE_SERVER_FOLLOW),
42   followValidator,
43   setBodyHostsPort,
44   asyncMiddleware(followInstance)
45 )
46
47 serverFollowsRouter.delete('/following/:host',
48   authenticate,
49   ensureUserHasRight(UserRight.MANAGE_SERVER_FOLLOW),
50   asyncMiddleware(removeFollowingValidator),
51   asyncMiddleware(removeFollowing)
52 )
53
54 serverFollowsRouter.get('/followers',
55   paginationValidator,
56   followersSortValidator,
57   setDefaultSort,
58   setDefaultPagination,
59   asyncMiddleware(listFollowers)
60 )
61
62 serverFollowsRouter.delete('/followers/:nameWithHost',
63   authenticate,
64   ensureUserHasRight(UserRight.MANAGE_SERVER_FOLLOW),
65   asyncMiddleware(getFollowerValidator),
66   asyncMiddleware(removeOrRejectFollower)
67 )
68
69 serverFollowsRouter.post('/followers/:nameWithHost/reject',
70   authenticate,
71   ensureUserHasRight(UserRight.MANAGE_SERVER_FOLLOW),
72   asyncMiddleware(getFollowerValidator),
73   acceptOrRejectFollowerValidator,
74   asyncMiddleware(removeOrRejectFollower)
75 )
76
77 serverFollowsRouter.post('/followers/:nameWithHost/accept',
78   authenticate,
79   ensureUserHasRight(UserRight.MANAGE_SERVER_FOLLOW),
80   asyncMiddleware(getFollowerValidator),
81   acceptOrRejectFollowerValidator,
82   asyncMiddleware(acceptFollower)
83 )
84
85 // ---------------------------------------------------------------------------
86
87 export {
88   serverFollowsRouter
89 }
90
91 // ---------------------------------------------------------------------------
92
93 async function listFollowing (req: express.Request, res: express.Response) {
94   const serverActor = await getServerActor()
95   const resultList = await ActorFollowModel.listFollowingForApi(
96     serverActor.id,
97     req.query.start,
98     req.query.count,
99     req.query.sort,
100     req.query.search
101   )
102
103   return res.json(getFormattedObjects(resultList.data, resultList.total))
104 }
105
106 async function listFollowers (req: express.Request, res: express.Response) {
107   const serverActor = await getServerActor()
108   const resultList = await ActorFollowModel.listFollowersForApi(
109     serverActor.id,
110     req.query.start,
111     req.query.count,
112     req.query.sort,
113     req.query.search
114   )
115
116   return res.json(getFormattedObjects(resultList.data, resultList.total))
117 }
118
119 async function followInstance (req: express.Request, res: express.Response) {
120   const hosts = req.body.hosts as string[]
121   const follower = await getServerActor()
122
123   for (const host of hosts) {
124     const payload = {
125       host,
126       name: SERVER_ACTOR_NAME,
127       followerActorId: follower.id
128     }
129
130     JobQueue.Instance.createJob({ type: 'activitypub-follow', payload })
131       .catch(err => logger.error('Cannot create follow job for %s.', host, err))
132   }
133
134   return res.status(204).end()
135 }
136
137 async function removeFollowing (req: express.Request, res: express.Response) {
138   const follow = res.locals.follow
139
140   await sequelizeTypescript.transaction(async t => {
141     if (follow.state === 'accepted') await sendUndoFollow(follow, t)
142
143     // Disable redundancy on unfollowed instances
144     const server = follow.ActorFollowing.Server
145     server.redundancyAllowed = false
146     await server.save({ transaction: t })
147
148     // Async, could be long
149     removeRedundancyOf(server.id)
150       .catch(err => logger.error('Cannot remove redundancy of %s.', server.host, err))
151
152     await follow.destroy({ transaction: t })
153   })
154
155   return res.status(204).end()
156 }
157
158 async function removeOrRejectFollower (req: express.Request, res: express.Response) {
159   const follow = res.locals.follow
160
161   await sendReject(follow.ActorFollower, follow.ActorFollowing)
162
163   await follow.destroy()
164
165   return res.status(204).end()
166 }
167
168 async function acceptFollower (req: express.Request, res: express.Response) {
169   const follow = res.locals.follow
170
171   await sendAccept(follow)
172
173   follow.state = 'accepted'
174   await follow.save()
175
176   await autoFollowBackIfNeeded(follow)
177
178   return res.status(204).end()
179 }