Refresh remote actors on GET enpoints

pull/1567/head
Chocobozzz 2019-01-14 11:30:15 +01:00
parent bb8f7872f5
commit 744d0eca19
No known key found for this signature in database
GPG Key ID: 583A612D890159BE
8 changed files with 99 additions and 62 deletions

View File

@ -14,6 +14,8 @@ import { AccountModel } from '../../models/account/account'
import { VideoModel } from '../../models/video/video'
import { buildNSFWFilter, isUserAbleToSearchRemoteURI } from '../../helpers/express-utils'
import { VideoChannelModel } from '../../models/video/video-channel'
import { JobQueue } from '../../lib/job-queue'
import { logger } from '../../helpers/logger'
const accountsRouter = express.Router()
@ -57,6 +59,11 @@ export {
function getAccount (req: express.Request, res: express.Response, next: express.NextFunction) {
const account: AccountModel = res.locals.account
if (account.isOutdated()) {
JobQueue.Instance.createJob({ type: 'activitypub-refresher', payload: { type: 'actor', url: account.Actor.url } })
.catch(err => logger.error('Cannot create AP refresher job for actor %s.', account.Actor.url, { err }))
}
return res.json(account.toFormattedJSON())
}

View File

@ -30,6 +30,7 @@ import { updateActorAvatarFile } from '../../lib/avatar'
import { auditLoggerFactory, getAuditIdFromRes, VideoChannelAuditView } from '../../helpers/audit-logger'
import { resetSequelizeInstance } from '../../helpers/database-utils'
import { UserModel } from '../../models/account/user'
import { JobQueue } from '../../lib/job-queue'
const auditLogger = auditLoggerFactory('channels')
const reqAvatarFile = createReqFiles([ 'avatarfile' ], MIMETYPES.IMAGE.MIMETYPE_EXT, { avatarfile: CONFIG.STORAGE.TMP_DIR })
@ -197,6 +198,11 @@ async function removeVideoChannel (req: express.Request, res: express.Response)
async function getVideoChannel (req: express.Request, res: express.Response, next: express.NextFunction) {
const videoChannelWithVideos = await VideoChannelModel.loadAndPopulateAccountAndVideos(res.locals.videoChannel.id)
if (videoChannelWithVideos.isOutdated()) {
JobQueue.Instance.createJob({ type: 'activitypub-refresher', payload: { type: 'actor', url: videoChannelWithVideos.Actor.url } })
.catch(err => logger.error('Cannot create AP refresher job for actor %s.', videoChannelWithVideos.Actor.url, { err }))
}
return res.json(videoChannelWithVideos.toFormattedJSON())
}

View File

@ -399,7 +399,7 @@ function getVideo (req: express.Request, res: express.Response) {
const videoInstance = res.locals.video
if (videoInstance.isOutdated()) {
JobQueue.Instance.createJob({ type: 'activitypub-refresher', payload: { type: 'video', videoUrl: videoInstance.url } })
JobQueue.Instance.createJob({ type: 'activitypub-refresher', payload: { type: 'video', url: videoInstance.url } })
.catch(err => logger.error('Cannot create AP refresher job for video %s.', videoInstance.url, { err }))
}

View File

@ -201,6 +201,62 @@ async function addFetchOutboxJob (actor: ActorModel) {
return JobQueue.Instance.createJob({ type: 'activitypub-http-fetcher', payload })
}
async function refreshActorIfNeeded (
actorArg: ActorModel,
fetchedType: ActorFetchByUrlType
): Promise<{ actor: ActorModel, refreshed: boolean }> {
if (!actorArg.isOutdated()) return { actor: actorArg, refreshed: false }
// We need more attributes
const actor = fetchedType === 'all' ? actorArg : await ActorModel.loadByUrlAndPopulateAccountAndChannel(actorArg.url)
try {
const actorUrl = await getUrlFromWebfinger(actor.preferredUsername + '@' + actor.getHost())
const { result, statusCode } = await fetchRemoteActor(actorUrl)
if (statusCode === 404) {
logger.info('Deleting actor %s because there is a 404 in refresh actor.', actor.url)
actor.Account ? actor.Account.destroy() : actor.VideoChannel.destroy()
return { actor: undefined, refreshed: false }
}
if (result === undefined) {
logger.warn('Cannot fetch remote actor in refresh actor.')
return { actor, refreshed: false }
}
return sequelizeTypescript.transaction(async t => {
updateInstanceWithAnother(actor, result.actor)
if (result.avatarName !== undefined) {
await updateActorAvatarInstance(actor, result.avatarName, t)
}
// Force update
actor.setDataValue('updatedAt', new Date())
await actor.save({ transaction: t })
if (actor.Account) {
actor.Account.set('name', result.name)
actor.Account.set('description', result.summary)
await actor.Account.save({ transaction: t })
} else if (actor.VideoChannel) {
actor.VideoChannel.set('name', result.name)
actor.VideoChannel.set('description', result.summary)
actor.VideoChannel.set('support', result.support)
await actor.VideoChannel.save({ transaction: t })
}
return { refreshed: true, actor }
})
} catch (err) {
logger.warn('Cannot refresh actor.', { err })
return { actor, refreshed: false }
}
}
export {
getOrCreateActorAndServerAndModel,
buildActorInstance,
@ -208,6 +264,7 @@ export {
fetchActorTotalItems,
fetchAvatarIfExists,
updateActorInstance,
refreshActorIfNeeded,
updateActorAvatarInstance,
addFetchOutboxJob
}
@ -373,58 +430,4 @@ async function saveVideoChannel (actor: ActorModel, result: FetchRemoteActorResu
return videoChannelCreated
}
async function refreshActorIfNeeded (
actorArg: ActorModel,
fetchedType: ActorFetchByUrlType
): Promise<{ actor: ActorModel, refreshed: boolean }> {
if (!actorArg.isOutdated()) return { actor: actorArg, refreshed: false }
// We need more attributes
const actor = fetchedType === 'all' ? actorArg : await ActorModel.loadByUrlAndPopulateAccountAndChannel(actorArg.url)
try {
const actorUrl = await getUrlFromWebfinger(actor.preferredUsername + '@' + actor.getHost())
const { result, statusCode } = await fetchRemoteActor(actorUrl)
if (statusCode === 404) {
logger.info('Deleting actor %s because there is a 404 in refresh actor.', actor.url)
actor.Account ? actor.Account.destroy() : actor.VideoChannel.destroy()
return { actor: undefined, refreshed: false }
}
if (result === undefined) {
logger.warn('Cannot fetch remote actor in refresh actor.')
return { actor, refreshed: false }
}
return sequelizeTypescript.transaction(async t => {
updateInstanceWithAnother(actor, result.actor)
if (result.avatarName !== undefined) {
await updateActorAvatarInstance(actor, result.avatarName, t)
}
// Force update
actor.setDataValue('updatedAt', new Date())
await actor.save({ transaction: t })
if (actor.Account) {
actor.Account.set('name', result.name)
actor.Account.set('description', result.summary)
await actor.Account.save({ transaction: t })
} else if (actor.VideoChannel) {
actor.VideoChannel.set('name', result.name)
actor.VideoChannel.set('description', result.summary)
actor.VideoChannel.set('support', result.support)
await actor.VideoChannel.save({ transaction: t })
}
return { refreshed: true, actor }
})
} catch (err) {
logger.warn('Cannot refresh actor.', { err })
return { actor, refreshed: false }
}
}

View File

@ -179,7 +179,7 @@ async function getOrCreateVideoAndAccountAndChannel (options: {
}
if (syncParam.refreshVideo === true) videoFromDatabase = await refreshVideoIfNeeded(refreshOptions)
else await JobQueue.Instance.createJob({ type: 'activitypub-refresher', payload: { type: 'video', videoUrl: videoFromDatabase.url } })
else await JobQueue.Instance.createJob({ type: 'activitypub-refresher', payload: { type: 'video', url: videoFromDatabase.url } })
}
return { video: videoFromDatabase, created: false }

View File

@ -1,30 +1,33 @@
import * as Bull from 'bull'
import { logger } from '../../../helpers/logger'
import { fetchVideoByUrl } from '../../../helpers/video'
import { refreshVideoIfNeeded } from '../../activitypub'
import { refreshVideoIfNeeded, refreshActorIfNeeded } from '../../activitypub'
import { ActorModel } from '../../../models/activitypub/actor'
export type RefreshPayload = {
videoUrl: string
type: 'video'
type: 'video' | 'actor'
url: string
}
async function refreshAPObject (job: Bull.Job) {
const payload = job.data as RefreshPayload
logger.info('Processing AP refresher in job %d for video %s.', job.id, payload.videoUrl)
logger.info('Processing AP refresher in job %d for %s.', job.id, payload.url)
if (payload.type === 'video') return refreshAPVideo(payload.videoUrl)
if (payload.type === 'video') return refreshVideo(payload.url)
if (payload.type === 'actor') return refreshActor(payload.url)
}
// ---------------------------------------------------------------------------
export {
refreshActor,
refreshAPObject
}
// ---------------------------------------------------------------------------
async function refreshAPVideo (videoUrl: string) {
async function refreshVideo (videoUrl: string) {
const fetchType = 'all' as 'all'
const syncParam = { likes: true, dislikes: true, shares: true, comments: true, thumbnail: true }
@ -39,3 +42,13 @@ async function refreshAPVideo (videoUrl: string) {
await refreshVideoIfNeeded(refreshOptions)
}
}
async function refreshActor (actorUrl: string) {
const fetchType = 'all' as 'all'
const actor = await ActorModel.loadByUrlAndPopulateAccountAndChannel(actorUrl)
if (actor) {
await refreshActorIfNeeded(actor, fetchType)
}
}

View File

@ -288,6 +288,10 @@ export class AccountModel extends Model<AccountModel> {
return this.Actor.isOwned()
}
isOutdated () {
return this.Actor.isOutdated()
}
getDisplayName () {
return this.name
}

View File

@ -470,4 +470,8 @@ export class VideoChannelModel extends Model<VideoChannelModel> {
getDisplayName () {
return this.name
}
isOutdated () {
return this.Actor.isOutdated()
}
}