1
0
Fork 0

Send follow/accept

This commit is contained in:
Chocobozzz 2017-11-13 18:48:28 +01:00
parent 7a7724e66e
commit ce548a10db
No known key found for this signature in database
GPG Key ID: 583A612D890159BE
9 changed files with 160 additions and 19 deletions

View File

@ -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()
}

View File

@ -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()
}

View File

@ -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.'
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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'

View File

@ -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
}

View File

@ -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[]>

View File

@ -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)