Add ability to remove an instance follower in API
This commit is contained in:
parent
ae9bbed46d
commit
0e9c48c2ed
|
@ -3,18 +3,23 @@ import { UserRight } from '../../../../shared/models/users'
|
|||
import { logger } from '../../../helpers/logger'
|
||||
import { getFormattedObjects, getServerActor } from '../../../helpers/utils'
|
||||
import { sequelizeTypescript, SERVER_ACTOR_NAME } from '../../../initializers'
|
||||
import { sendUndoFollow } from '../../../lib/activitypub/send'
|
||||
import { sendReject, sendUndoFollow } from '../../../lib/activitypub/send'
|
||||
import {
|
||||
asyncMiddleware,
|
||||
authenticate,
|
||||
ensureUserHasRight,
|
||||
paginationValidator,
|
||||
removeFollowingValidator,
|
||||
setBodyHostsPort,
|
||||
setDefaultPagination,
|
||||
setDefaultSort
|
||||
} from '../../../middlewares'
|
||||
import { followersSortValidator, followingSortValidator, followValidator } from '../../../middlewares/validators'
|
||||
import {
|
||||
followersSortValidator,
|
||||
followingSortValidator,
|
||||
followValidator,
|
||||
removeFollowerValidator,
|
||||
removeFollowingValidator
|
||||
} from '../../../middlewares/validators'
|
||||
import { ActorFollowModel } from '../../../models/activitypub/actor-follow'
|
||||
import { JobQueue } from '../../../lib/job-queue'
|
||||
import { removeRedundancyOf } from '../../../lib/redundancy'
|
||||
|
@ -40,7 +45,7 @@ serverFollowsRouter.delete('/following/:host',
|
|||
authenticate,
|
||||
ensureUserHasRight(UserRight.MANAGE_SERVER_FOLLOW),
|
||||
asyncMiddleware(removeFollowingValidator),
|
||||
asyncMiddleware(removeFollow)
|
||||
asyncMiddleware(removeFollowing)
|
||||
)
|
||||
|
||||
serverFollowsRouter.get('/followers',
|
||||
|
@ -51,6 +56,13 @@ serverFollowsRouter.get('/followers',
|
|||
asyncMiddleware(listFollowers)
|
||||
)
|
||||
|
||||
serverFollowsRouter.delete('/followers/:nameWithHost',
|
||||
authenticate,
|
||||
ensureUserHasRight(UserRight.MANAGE_SERVER_FOLLOW),
|
||||
asyncMiddleware(removeFollowerValidator),
|
||||
asyncMiddleware(removeFollower)
|
||||
)
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export {
|
||||
|
@ -103,7 +115,7 @@ async function followInstance (req: express.Request, res: express.Response) {
|
|||
return res.status(204).end()
|
||||
}
|
||||
|
||||
async function removeFollow (req: express.Request, res: express.Response) {
|
||||
async function removeFollowing (req: express.Request, res: express.Response) {
|
||||
const follow = res.locals.follow
|
||||
|
||||
await sequelizeTypescript.transaction(async t => {
|
||||
|
@ -123,3 +135,13 @@ async function removeFollow (req: express.Request, res: express.Response) {
|
|||
|
||||
return res.status(204).end()
|
||||
}
|
||||
|
||||
async function removeFollower (req: express.Request, res: express.Response) {
|
||||
const follow = res.locals.follow
|
||||
|
||||
await sendReject(follow)
|
||||
|
||||
await follow.destroy()
|
||||
|
||||
return res.status(204).end()
|
||||
}
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
export * from './send-accept'
|
||||
export * from './send-accept'
|
||||
export * from './send-announce'
|
||||
export * from './send-create'
|
||||
export * from './send-delete'
|
||||
export * from './send-follow'
|
||||
export * from './send-like'
|
||||
export * from './send-reject'
|
||||
export * from './send-undo'
|
||||
export * from './send-update'
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
import { ActivityFollow, ActivityReject } from '../../../../shared/models/activitypub'
|
||||
import { ActorModel } from '../../../models/activitypub/actor'
|
||||
import { ActorFollowModel } from '../../../models/activitypub/actor-follow'
|
||||
import { getActorFollowAcceptActivityPubUrl, getActorFollowActivityPubUrl } from '../url'
|
||||
import { unicastTo } from './utils'
|
||||
import { buildFollowActivity } from './send-follow'
|
||||
import { logger } from '../../../helpers/logger'
|
||||
|
||||
async function sendReject (actorFollow: ActorFollowModel) {
|
||||
const follower = actorFollow.ActorFollower
|
||||
const me = actorFollow.ActorFollowing
|
||||
|
||||
if (!follower.serverId) { // This should never happen
|
||||
logger.warn('Do not sending reject to local follower.')
|
||||
return
|
||||
}
|
||||
|
||||
logger.info('Creating job to reject follower %s.', follower.url)
|
||||
|
||||
const followUrl = getActorFollowActivityPubUrl(actorFollow)
|
||||
const followData = buildFollowActivity(followUrl, follower, me)
|
||||
|
||||
const url = getActorFollowAcceptActivityPubUrl(actorFollow)
|
||||
const data = buildRejectActivity(url, me, followData)
|
||||
|
||||
return unicastTo(data, me, follower.inboxUrl)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export {
|
||||
sendReject
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function buildRejectActivity (url: string, byActor: ActorModel, followActivityData: ActivityFollow): ActivityReject {
|
||||
return {
|
||||
type: 'Reject',
|
||||
id: url,
|
||||
actor: byActor.url,
|
||||
object: followActivityData
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
import { VideoModel } from '../models/video/video'
|
||||
import { basename, dirname, join } from 'path'
|
||||
import { CONFIG, HLS_STREAMING_PLAYLIST_DIRECTORY, sequelizeTypescript } from '../initializers'
|
||||
import { CONFIG, HLS_STREAMING_PLAYLIST_DIRECTORY, P2P_MEDIA_LOADER_PEER_VERSION, sequelizeTypescript } from '../initializers'
|
||||
import { close, ensureDir, move, open, outputJSON, pathExists, read, readFile, remove, writeFile } from 'fs-extra'
|
||||
import { getVideoFileSize } from '../helpers/ffmpeg-utils'
|
||||
import { sha256 } from '../helpers/core-utils'
|
||||
|
@ -20,6 +20,7 @@ async function updateStreamingPlaylistsInfohashesIfNeeded () {
|
|||
const videoFiles = await VideoFileModel.listByStreamingPlaylist(playlist.id, t)
|
||||
|
||||
playlist.p2pMediaLoaderInfohashes = await VideoStreamingPlaylistModel.buildP2PMediaLoaderInfoHashes(playlist.playlistUrl, videoFiles)
|
||||
playlist.p2pMediaLoaderPeerVersion = P2P_MEDIA_LOADER_PEER_VERSION
|
||||
await playlist.save({ transaction: t })
|
||||
})
|
||||
}
|
||||
|
|
|
@ -7,6 +7,10 @@ import { getServerActor } from '../../helpers/utils'
|
|||
import { CONFIG, SERVER_ACTOR_NAME } from '../../initializers'
|
||||
import { ActorFollowModel } from '../../models/activitypub/actor-follow'
|
||||
import { areValidationErrors } from './utils'
|
||||
import { ActorModel } from '../../models/activitypub/actor'
|
||||
import { loadActorUrlOrGetFromWebfinger } from '../../helpers/webfinger'
|
||||
import { getOrCreateActorAndServerAndModel } from '../../lib/activitypub'
|
||||
import { isValidActorHandle } from '../../helpers/custom-validators/activitypub/actor'
|
||||
|
||||
const followValidator = [
|
||||
body('hosts').custom(isEachUniqueHostValid).withMessage('Should have an array of unique hosts'),
|
||||
|
@ -33,7 +37,7 @@ const removeFollowingValidator = [
|
|||
param('host').custom(isHostValid).withMessage('Should have a valid host'),
|
||||
|
||||
async (req: express.Request, res: express.Response, next: express.NextFunction) => {
|
||||
logger.debug('Checking unfollow parameters', { parameters: req.params })
|
||||
logger.debug('Checking unfollowing parameters', { parameters: req.params })
|
||||
|
||||
if (areValidationErrors(req, res)) return
|
||||
|
||||
|
@ -44,7 +48,36 @@ const removeFollowingValidator = [
|
|||
return res
|
||||
.status(404)
|
||||
.json({
|
||||
error: `Follower ${req.params.host} not found.`
|
||||
error: `Following ${req.params.host} not found.`
|
||||
})
|
||||
.end()
|
||||
}
|
||||
|
||||
res.locals.follow = follow
|
||||
return next()
|
||||
}
|
||||
]
|
||||
|
||||
const removeFollowerValidator = [
|
||||
param('nameWithHost').custom(isValidActorHandle).withMessage('Should have a valid nameWithHost'),
|
||||
|
||||
async (req: express.Request, res: express.Response, next: express.NextFunction) => {
|
||||
logger.debug('Checking remove follower parameters', { parameters: req.params })
|
||||
|
||||
if (areValidationErrors(req, res)) return
|
||||
|
||||
const serverActor = await getServerActor()
|
||||
|
||||
const actorUrl = await loadActorUrlOrGetFromWebfinger(req.params.nameWithHost)
|
||||
const actor = await ActorModel.loadByUrl(actorUrl)
|
||||
|
||||
const follow = await ActorFollowModel.loadByActorAndTarget(actor.id, serverActor.id)
|
||||
|
||||
if (!follow) {
|
||||
return res
|
||||
.status(404)
|
||||
.json({
|
||||
error: `Follower ${req.params.nameWithHost} not found.`
|
||||
})
|
||||
.end()
|
||||
}
|
||||
|
@ -58,5 +91,6 @@ const removeFollowingValidator = [
|
|||
|
||||
export {
|
||||
followValidator,
|
||||
removeFollowingValidator
|
||||
removeFollowingValidator,
|
||||
removeFollowerValidator
|
||||
}
|
||||
|
|
|
@ -0,0 +1,78 @@
|
|||
/* tslint:disable:no-unused-expression */
|
||||
|
||||
import * as chai from 'chai'
|
||||
import 'mocha'
|
||||
import { flushAndRunMultipleServers, killallServers, ServerInfo, setAccessTokensToServers } from '../../../../shared/utils/index'
|
||||
import {
|
||||
follow,
|
||||
getFollowersListPaginationAndSort,
|
||||
getFollowingListPaginationAndSort,
|
||||
removeFollower
|
||||
} from '../../../../shared/utils/server/follows'
|
||||
import { waitJobs } from '../../../../shared/utils/server/jobs'
|
||||
import { ActorFollow } from '../../../../shared/models/actors'
|
||||
|
||||
const expect = chai.expect
|
||||
|
||||
describe('Test follows moderation', function () {
|
||||
let servers: ServerInfo[] = []
|
||||
|
||||
before(async function () {
|
||||
this.timeout(30000)
|
||||
|
||||
servers = await flushAndRunMultipleServers(2)
|
||||
|
||||
// Get the access tokens
|
||||
await setAccessTokensToServers(servers)
|
||||
})
|
||||
|
||||
it('Should have server 1 following server 2', async function () {
|
||||
this.timeout(30000)
|
||||
|
||||
await follow(servers[0].url, [ servers[1].url ], servers[0].accessToken)
|
||||
|
||||
await waitJobs(servers)
|
||||
})
|
||||
|
||||
it('Should have correct follows', async function () {
|
||||
{
|
||||
const res = await getFollowingListPaginationAndSort(servers[0].url, 0, 5, 'createdAt')
|
||||
expect(res.body.total).to.equal(1)
|
||||
|
||||
const follow = res.body.data[0] as ActorFollow
|
||||
expect(follow.follower.url).to.equal('http://localhost:9001/accounts/peertube')
|
||||
expect(follow.following.url).to.equal('http://localhost:9002/accounts/peertube')
|
||||
}
|
||||
|
||||
{
|
||||
const res = await getFollowersListPaginationAndSort(servers[1].url, 0, 5, 'createdAt')
|
||||
expect(res.body.total).to.equal(1)
|
||||
|
||||
const follow = res.body.data[0] as ActorFollow
|
||||
expect(follow.follower.url).to.equal('http://localhost:9001/accounts/peertube')
|
||||
expect(follow.following.url).to.equal('http://localhost:9002/accounts/peertube')
|
||||
}
|
||||
})
|
||||
|
||||
it('Should remove follower on server 2', async function () {
|
||||
await removeFollower(servers[1].url, servers[1].accessToken, servers[0])
|
||||
|
||||
await waitJobs(servers)
|
||||
})
|
||||
|
||||
it('Should not not have follows anymore', async function () {
|
||||
{
|
||||
const res = await getFollowingListPaginationAndSort(servers[ 0 ].url, 0, 1, 'createdAt')
|
||||
expect(res.body.total).to.equal(0)
|
||||
}
|
||||
|
||||
{
|
||||
const res = await getFollowingListPaginationAndSort(servers[ 0 ].url, 0, 1, 'createdAt')
|
||||
expect(res.body.total).to.equal(0)
|
||||
}
|
||||
})
|
||||
|
||||
after(async function () {
|
||||
killallServers(servers)
|
||||
})
|
||||
})
|
|
@ -3,6 +3,7 @@ import './contact-form'
|
|||
import './email'
|
||||
import './follow-constraints'
|
||||
import './follows'
|
||||
import './follows-moderation'
|
||||
import './handle-down'
|
||||
import './jobs'
|
||||
import './reverse-proxy'
|
||||
|
|
|
@ -47,13 +47,21 @@ async function follow (follower: string, following: string[], accessToken: strin
|
|||
async function unfollow (url: string, accessToken: string, target: ServerInfo, expectedStatus = 204) {
|
||||
const path = '/api/v1/server/following/' + target.host
|
||||
|
||||
const res = await request(url)
|
||||
return request(url)
|
||||
.delete(path)
|
||||
.set('Accept', 'application/json')
|
||||
.set('Authorization', 'Bearer ' + accessToken)
|
||||
.expect(expectedStatus)
|
||||
}
|
||||
|
||||
return res
|
||||
function removeFollower (url: string, accessToken: string, follower: ServerInfo, expectedStatus = 204) {
|
||||
const path = '/api/v1/server/followers/peertube@' + follower.host
|
||||
|
||||
return request(url)
|
||||
.delete(path)
|
||||
.set('Accept', 'application/json')
|
||||
.set('Authorization', 'Bearer ' + accessToken)
|
||||
.expect(expectedStatus)
|
||||
}
|
||||
|
||||
async function doubleFollow (server1: ServerInfo, server2: ServerInfo) {
|
||||
|
@ -74,6 +82,7 @@ export {
|
|||
getFollowersListPaginationAndSort,
|
||||
getFollowingListPaginationAndSort,
|
||||
unfollow,
|
||||
removeFollower,
|
||||
follow,
|
||||
doubleFollow
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue