Send follow/accept
This commit is contained in:
parent
7a7724e66e
commit
ce548a10db
|
@ -1,10 +1,16 @@
|
|||
import * as Bluebird from 'bluebird'
|
||||
import * as express from 'express'
|
||||
import { getFormattedObjects } from '../../helpers'
|
||||
import { getOrCreateAccount } from '../../helpers/activitypub'
|
||||
import { getApplicationAccount } from '../../helpers/utils'
|
||||
import { REMOTE_SCHEME } from '../../initializers/constants'
|
||||
import { database as db } from '../../initializers/database'
|
||||
import { asyncMiddleware, paginationValidator, setFollowersSort, setPagination } from '../../middlewares'
|
||||
import { setBodyHostsPort } from '../../middlewares/pods'
|
||||
import { setFollowingSort } from '../../middlewares/sort'
|
||||
import { followValidator } from '../../middlewares/validators/pods'
|
||||
import { followersSortValidator, followingSortValidator } from '../../middlewares/validators/sort'
|
||||
import { sendFollow } from '../../lib/activitypub/send-request'
|
||||
|
||||
const podsRouter = express.Router()
|
||||
|
||||
|
@ -16,6 +22,12 @@ podsRouter.get('/following',
|
|||
asyncMiddleware(listFollowing)
|
||||
)
|
||||
|
||||
podsRouter.post('/follow',
|
||||
followValidator,
|
||||
setBodyHostsPort,
|
||||
asyncMiddleware(follow)
|
||||
)
|
||||
|
||||
podsRouter.get('/followers',
|
||||
paginationValidator,
|
||||
followersSortValidator,
|
||||
|
@ -45,3 +57,32 @@ async function listFollowers (req: express.Request, res: express.Response, next:
|
|||
|
||||
return res.json(getFormattedObjects(resultList.data, resultList.total))
|
||||
}
|
||||
|
||||
async function follow (req: express.Request, res: express.Response, next: express.NextFunction) {
|
||||
const hosts = req.body.hosts as string[]
|
||||
const fromAccount = await getApplicationAccount()
|
||||
|
||||
const tasks: Bluebird<any>[] = []
|
||||
for (const host of hosts) {
|
||||
const url = REMOTE_SCHEME.HTTP + '://' + host
|
||||
const targetAccount = await getOrCreateAccount(url)
|
||||
|
||||
// We process each host in a specific transaction
|
||||
// First, we add the follow request in the database
|
||||
// Then we send the follow request to other account
|
||||
const p = db.sequelize.transaction(async t => {
|
||||
return db.AccountFollow.create({
|
||||
accountId: fromAccount.id,
|
||||
targetAccountId: targetAccount.id,
|
||||
state: 'pending'
|
||||
})
|
||||
.then(() => sendFollow(fromAccount, targetAccount, t))
|
||||
})
|
||||
|
||||
tasks.push(p)
|
||||
}
|
||||
|
||||
await Promise.all(tasks)
|
||||
|
||||
return res.status(204).end()
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ async function processAcceptActivity (activity: ActivityAccept, inboxAccount?: A
|
|||
|
||||
const targetAccount = await db.Account.loadByUrl(activity.actor)
|
||||
|
||||
return processFollow(inboxAccount, targetAccount)
|
||||
return processAccept(inboxAccount, targetAccount)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
@ -18,10 +18,10 @@ export {
|
|||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async function processFollow (account: AccountInstance, targetAccount: AccountInstance) {
|
||||
async function processAccept (account: AccountInstance, targetAccount: AccountInstance) {
|
||||
const follow = await db.AccountFollow.loadByAccountAndTarget(account.id, targetAccount.id)
|
||||
if (!follow) throw new Error('Cannot find associated follow.')
|
||||
|
||||
follow.set('state', 'accepted')
|
||||
return follow.save()
|
||||
await follow.save()
|
||||
}
|
||||
|
|
|
@ -29,7 +29,7 @@ export {
|
|||
|
||||
function processAddVideo (account: AccountInstance, videoChannelUrl: string, video: VideoTorrentObject) {
|
||||
const options = {
|
||||
arguments: [ account, videoChannelUrl ,video ],
|
||||
arguments: [ account, videoChannelUrl, video ],
|
||||
errorMessage: 'Cannot insert the remote video with many retries.'
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
import { ActivityFollow } from '../../../shared/models/activitypub/activity'
|
||||
import { getOrCreateAccount } from '../../helpers'
|
||||
import { getOrCreateAccount, retryTransactionWrapper } from '../../helpers'
|
||||
import { database as db } from '../../initializers'
|
||||
import { AccountInstance } from '../../models/account/account-interface'
|
||||
import { sendAccept } from './send-request'
|
||||
import { logger } from '../../helpers/logger'
|
||||
|
||||
async function processFollowActivity (activity: ActivityFollow) {
|
||||
const activityObject = activity.object
|
||||
|
@ -18,15 +20,34 @@ export {
|
|||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async function processFollow (account: AccountInstance, targetAccountURL: string) {
|
||||
const targetAccount = await db.Account.loadByUrl(targetAccountURL)
|
||||
function processFollow (account: AccountInstance, targetAccountURL: string) {
|
||||
const options = {
|
||||
arguments: [ account, targetAccountURL ],
|
||||
errorMessage: 'Cannot follow with many retries.'
|
||||
}
|
||||
|
||||
if (targetAccount === undefined) throw new Error('Unknown account')
|
||||
if (targetAccount.isOwned() === false) throw new Error('This is not a local account.')
|
||||
|
||||
return db.AccountFollow.create({
|
||||
accountId: account.id,
|
||||
targetAccountId: targetAccount.id,
|
||||
state: 'accepted'
|
||||
})
|
||||
return retryTransactionWrapper(follow, options)
|
||||
}
|
||||
|
||||
async function follow (account: AccountInstance, targetAccountURL: string) {
|
||||
await db.sequelize.transaction(async t => {
|
||||
const targetAccount = await db.Account.loadByUrl(targetAccountURL, t)
|
||||
|
||||
if (targetAccount === undefined) throw new Error('Unknown account')
|
||||
if (targetAccount.isOwned() === false) throw new Error('This is not a local account.')
|
||||
|
||||
const sequelizeOptions = {
|
||||
transaction: t
|
||||
}
|
||||
await db.AccountFollow.create({
|
||||
accountId: account.id,
|
||||
targetAccountId: targetAccount.id,
|
||||
state: 'accepted'
|
||||
}, sequelizeOptions)
|
||||
|
||||
// Target sends to account he accepted the follow request
|
||||
return sendAccept(targetAccount, account, t)
|
||||
})
|
||||
|
||||
logger.info('Account uuid %s is followed by account %s.', account.url, targetAccountURL)
|
||||
}
|
||||
|
|
|
@ -56,6 +56,18 @@ function sendDeleteAccount (account: AccountInstance, t: Sequelize.Transaction)
|
|||
return broadcastToFollowers(data, account, t)
|
||||
}
|
||||
|
||||
function sendAccept (fromAccount: AccountInstance, toAccount: AccountInstance, t: Sequelize.Transaction) {
|
||||
const data = acceptActivityData(fromAccount)
|
||||
|
||||
return unicastTo(data, toAccount, t)
|
||||
}
|
||||
|
||||
function sendFollow (fromAccount: AccountInstance, toAccount: AccountInstance, t: Sequelize.Transaction) {
|
||||
const data = followActivityData(toAccount.url, fromAccount)
|
||||
|
||||
return unicastTo(data, toAccount, t)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export {
|
||||
|
@ -65,7 +77,9 @@ export {
|
|||
sendAddVideo,
|
||||
sendUpdateVideo,
|
||||
sendDeleteVideo,
|
||||
sendDeleteAccount
|
||||
sendDeleteAccount,
|
||||
sendAccept,
|
||||
sendFollow
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
@ -81,6 +95,15 @@ async function broadcastToFollowers (data: any, fromAccount: AccountInstance, t:
|
|||
return httpRequestJobScheduler.createJob(t, 'httpRequestBroadcastHandler', jobPayload)
|
||||
}
|
||||
|
||||
async function unicastTo (data: any, toAccount: AccountInstance, t: Sequelize.Transaction) {
|
||||
const jobPayload = {
|
||||
uris: [ toAccount.url ],
|
||||
body: data
|
||||
}
|
||||
|
||||
return httpRequestJobScheduler.createJob(t, 'httpRequestUnicastHandler', jobPayload)
|
||||
}
|
||||
|
||||
function buildSignedActivity (byAccount: AccountInstance, data: Object) {
|
||||
const activity = activityPubContextify(data)
|
||||
|
||||
|
@ -142,3 +165,24 @@ async function addActivityData (url: string, byAccount: AccountInstance, target:
|
|||
|
||||
return buildSignedActivity(byAccount, base)
|
||||
}
|
||||
|
||||
async function followActivityData (url: string, byAccount: AccountInstance) {
|
||||
const base = {
|
||||
type: 'Follow',
|
||||
id: byAccount.url,
|
||||
actor: byAccount.url,
|
||||
object: url
|
||||
}
|
||||
|
||||
return buildSignedActivity(byAccount, base)
|
||||
}
|
||||
|
||||
async function acceptActivityData (byAccount: AccountInstance) {
|
||||
const base = {
|
||||
type: 'Accept',
|
||||
id: byAccount.url,
|
||||
actor: byAccount.url
|
||||
}
|
||||
|
||||
return buildSignedActivity(byAccount, base)
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ export * from './account'
|
|||
export * from './oembed'
|
||||
export * from './activitypub'
|
||||
export * from './pagination'
|
||||
export * from './pods'
|
||||
export * from './sort'
|
||||
export * from './users'
|
||||
export * from './videos'
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
import * as express from 'express'
|
||||
import { body } from 'express-validator/check'
|
||||
import { isEachUniqueHostValid } from '../../helpers/custom-validators/pods'
|
||||
import { isTestInstance } from '../../helpers/core-utils'
|
||||
import { CONFIG } from '../../initializers/constants'
|
||||
import { logger } from '../../helpers/logger'
|
||||
import { checkErrors } from './utils'
|
||||
|
||||
const followValidator = [
|
||||
body('hosts').custom(isEachUniqueHostValid).withMessage('Should have an array of unique hosts'),
|
||||
|
||||
(req: express.Request, res: express.Response, next: express.NextFunction) => {
|
||||
// Force https if the administrator wants to make friends
|
||||
if (isTestInstance() === false && CONFIG.WEBSERVER.SCHEME === 'http') {
|
||||
return res.status(400)
|
||||
.json({
|
||||
error: 'Cannot follow non HTTPS web server.'
|
||||
})
|
||||
.end()
|
||||
}
|
||||
|
||||
logger.debug('Checking follow parameters', { parameters: req.body })
|
||||
|
||||
checkErrors(req, res, next)
|
||||
}
|
||||
]
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export {
|
||||
followValidator
|
||||
}
|
|
@ -10,7 +10,7 @@ export namespace AccountMethods {
|
|||
|
||||
export type Load = (id: number) => Bluebird<AccountInstance>
|
||||
export type LoadByUUID = (uuid: string) => Bluebird<AccountInstance>
|
||||
export type LoadByUrl = (url: string) => Bluebird<AccountInstance>
|
||||
export type LoadByUrl = (url: string, transaction?: Sequelize.Transaction) => Bluebird<AccountInstance>
|
||||
export type LoadAccountByPodAndUUID = (uuid: string, podId: number, transaction: Sequelize.Transaction) => Bluebird<AccountInstance>
|
||||
export type LoadLocalAccountByNameAndPod = (name: string, host: string) => Bluebird<AccountInstance>
|
||||
export type ListOwned = () => Bluebird<AccountInstance[]>
|
||||
|
|
|
@ -198,6 +198,7 @@ export default function defineAccount (sequelize: Sequelize.Sequelize, DataTypes
|
|||
loadApplication,
|
||||
load,
|
||||
loadByUUID,
|
||||
loadByUrl,
|
||||
loadLocalAccountByNameAndPod,
|
||||
listOwned,
|
||||
listFollowerUrlsForApi,
|
||||
|
@ -480,11 +481,12 @@ loadLocalAccountByNameAndPod = function (name: string, host: string) {
|
|||
return Account.findOne(query)
|
||||
}
|
||||
|
||||
loadByUrl = function (url: string) {
|
||||
loadByUrl = function (url: string, transaction?: Sequelize.Transaction) {
|
||||
const query: Sequelize.FindOptions<AccountAttributes> = {
|
||||
where: {
|
||||
url
|
||||
}
|
||||
},
|
||||
transaction
|
||||
}
|
||||
|
||||
return Account.findOne(query)
|
||||
|
|
Loading…
Reference in New Issue