1 import express from 'express'
2 import { getServerActor } from '@server/models/application/application'
3 import { ServerFollowCreate } from '@shared/models'
4 import { HttpStatusCode } from '../../../../shared/models/http/http-error-codes'
5 import { UserRight } from '../../../../shared/models/users'
6 import { logger } from '../../../helpers/logger'
7 import { getFormattedObjects } from '../../../helpers/utils'
8 import { SERVER_ACTOR_NAME } from '../../../initializers/constants'
9 import { sequelizeTypescript } from '../../../initializers/database'
10 import { autoFollowBackIfNeeded } from '../../../lib/activitypub/follow'
11 import { sendAccept, sendReject, sendUndoFollow } from '../../../lib/activitypub/send'
12 import { JobQueue } from '../../../lib/job-queue'
13 import { removeRedundanciesOfServer } from '../../../lib/redundancy'
22 } from '../../../middlewares'
24 acceptFollowerValidator,
27 instanceFollowersSortValidator,
28 instanceFollowingSortValidator,
30 rejectFollowerValidator,
31 removeFollowingValidator
32 } from '../../../middlewares/validators'
33 import { ActorFollowModel } from '../../../models/actor/actor-follow'
35 const serverFollowsRouter = express.Router()
36 serverFollowsRouter.get('/following',
39 instanceFollowingSortValidator,
42 asyncMiddleware(listFollowing)
45 serverFollowsRouter.post('/following',
47 ensureUserHasRight(UserRight.MANAGE_SERVER_FOLLOW),
50 asyncMiddleware(addFollow)
53 serverFollowsRouter.delete('/following/:hostOrHandle',
55 ensureUserHasRight(UserRight.MANAGE_SERVER_FOLLOW),
56 asyncMiddleware(removeFollowingValidator),
57 asyncMiddleware(removeFollowing)
60 serverFollowsRouter.get('/followers',
63 instanceFollowersSortValidator,
66 asyncMiddleware(listFollowers)
69 serverFollowsRouter.delete('/followers/:nameWithHost',
71 ensureUserHasRight(UserRight.MANAGE_SERVER_FOLLOW),
72 asyncMiddleware(getFollowerValidator),
73 asyncMiddleware(removeFollower)
76 serverFollowsRouter.post('/followers/:nameWithHost/reject',
78 ensureUserHasRight(UserRight.MANAGE_SERVER_FOLLOW),
79 asyncMiddleware(getFollowerValidator),
80 rejectFollowerValidator,
81 asyncMiddleware(rejectFollower)
84 serverFollowsRouter.post('/followers/:nameWithHost/accept',
86 ensureUserHasRight(UserRight.MANAGE_SERVER_FOLLOW),
87 asyncMiddleware(getFollowerValidator),
88 acceptFollowerValidator,
89 asyncMiddleware(acceptFollower)
92 // ---------------------------------------------------------------------------
98 // ---------------------------------------------------------------------------
100 async function listFollowing (req: express.Request, res: express.Response) {
101 const serverActor = await getServerActor()
102 const resultList = await ActorFollowModel.listInstanceFollowingForApi({
103 followerId: serverActor.id,
104 start: req.query.start,
105 count: req.query.count,
106 sort: req.query.sort,
107 search: req.query.search,
108 actorType: req.query.actorType,
109 state: req.query.state
112 return res.json(getFormattedObjects(resultList.data, resultList.total))
115 async function listFollowers (req: express.Request, res: express.Response) {
116 const serverActor = await getServerActor()
117 const resultList = await ActorFollowModel.listFollowersForApi({
118 actorIds: [ serverActor.id ],
119 start: req.query.start,
120 count: req.query.count,
121 sort: req.query.sort,
122 search: req.query.search,
123 actorType: req.query.actorType,
124 state: req.query.state
127 return res.json(getFormattedObjects(resultList.data, resultList.total))
130 async function addFollow (req: express.Request, res: express.Response) {
131 const { hosts, handles } = req.body as ServerFollowCreate
132 const follower = await getServerActor()
134 for (const host of hosts) {
137 name: SERVER_ACTOR_NAME,
138 followerActorId: follower.id
141 JobQueue.Instance.createJobAsync({ type: 'activitypub-follow', payload })
144 for (const handle of handles) {
145 const [ name, host ] = handle.split('@')
150 followerActorId: follower.id
153 JobQueue.Instance.createJobAsync({ type: 'activitypub-follow', payload })
156 return res.status(HttpStatusCode.NO_CONTENT_204).end()
159 async function removeFollowing (req: express.Request, res: express.Response) {
160 const follow = res.locals.follow
162 await sequelizeTypescript.transaction(async t => {
163 if (follow.state === 'accepted') sendUndoFollow(follow, t)
165 // Disable redundancy on unfollowed instances
166 const server = follow.ActorFollowing.Server
167 server.redundancyAllowed = false
168 await server.save({ transaction: t })
170 // Async, could be long
171 removeRedundanciesOfServer(server.id)
172 .catch(err => logger.error('Cannot remove redundancy of %s.', server.host, { err }))
174 await follow.destroy({ transaction: t })
177 return res.status(HttpStatusCode.NO_CONTENT_204).end()
180 async function rejectFollower (req: express.Request, res: express.Response) {
181 const follow = res.locals.follow
183 follow.state = 'rejected'
186 sendReject(follow.url, follow.ActorFollower, follow.ActorFollowing)
188 return res.status(HttpStatusCode.NO_CONTENT_204).end()
191 async function removeFollower (req: express.Request, res: express.Response) {
192 const follow = res.locals.follow
194 if (follow.state === 'accepted' || follow.state === 'pending') {
195 sendReject(follow.url, follow.ActorFollower, follow.ActorFollowing)
198 await follow.destroy()
200 return res.status(HttpStatusCode.NO_CONTENT_204).end()
203 async function acceptFollower (req: express.Request, res: express.Response) {
204 const follow = res.locals.follow
208 follow.state = 'accepted'
211 await autoFollowBackIfNeeded(follow)
213 return res.status(HttpStatusCode.NO_CONTENT_204).end()