1 import * as Bluebird from 'bluebird'
2 import * as url from 'url'
3 import { ActivityPubActor } from '../../../shared/models/activitypub/activitypub-actor'
4 import { isRemoteAccountValid } from '../../helpers/custom-validators/activitypub/account'
5 import { retryTransactionWrapper } from '../../helpers/database-utils'
6 import { logger } from '../../helpers/logger'
7 import { doRequest } from '../../helpers/requests'
8 import { ACTIVITY_PUB } from '../../initializers/constants'
9 import { database as db } from '../../initializers/database'
10 import { AccountInstance } from '../../models/account/account-interface'
11 import { Transaction } from 'sequelize'
13 async function getOrCreateAccountAndServer (accountUrl: string) {
14 let account = await db.Account.loadByUrl(accountUrl)
16 // We don't have this account in our database, fetch it on remote
18 account = await fetchRemoteAccount(accountUrl)
19 if (account === undefined) throw new Error('Cannot fetch remote account.')
22 arguments: [ account ],
23 errorMessage: 'Cannot save account and server with many retries.'
25 account = await retryTransactionWrapper(saveAccountAndServerIfNotExist, options)
31 function saveAccountAndServerIfNotExist (account: AccountInstance, t?: Transaction): Bluebird<AccountInstance> | Promise<AccountInstance> {
32 if (t !== undefined) {
35 return db.sequelize.transaction(t => {
40 async function save (t: Transaction) {
41 const accountHost = url.parse(account.url).host
43 const serverOptions = {
52 const [ server ] = await db.Server.findOrCreate(serverOptions)
54 // Save our new account in database
55 account.set('serverId', server.id)
56 account = await account.save({ transaction: t })
62 async function fetchRemoteAccount (accountUrl: string) {
67 'Accept': ACTIVITY_PUB.ACCEPT_HEADER
71 logger.info('Fetching remote account %s.', accountUrl)
75 requestResult = await doRequest(options)
77 logger.warn('Cannot fetch remote account %s.', accountUrl, err)
81 const accountJSON: ActivityPubActor = JSON.parse(requestResult.body)
82 if (isRemoteAccountValid(accountJSON) === false) {
83 logger.debug('Remote account JSON is not valid.', { accountJSON })
87 const followersCount = await fetchAccountCount(accountJSON.followers)
88 const followingCount = await fetchAccountCount(accountJSON.following)
90 const account = db.Account.build({
91 uuid: accountJSON.uuid,
92 name: accountJSON.preferredUsername,
94 publicKey: accountJSON.publicKey.publicKeyPem,
96 followersCount: followersCount,
97 followingCount: followingCount,
98 inboxUrl: accountJSON.inbox,
99 outboxUrl: accountJSON.outbox,
100 sharedInboxUrl: accountJSON.endpoints.sharedInbox,
101 followersUrl: accountJSON.followers,
102 followingUrl: accountJSON.following
109 getOrCreateAccountAndServer,
111 saveAccountAndServerIfNotExist
114 // ---------------------------------------------------------------------------
116 async function fetchAccountCount (url: string) {
124 requestResult = await doRequest(options)
126 logger.warn('Cannot fetch remote account count %s.', url, err)
130 return requestResult.totalItems ? requestResult.totalItems : 0