Refresh remote actors on GET enpoints
This commit is contained in:
parent
bb8f7872f5
commit
744d0eca19
|
@ -14,6 +14,8 @@ import { AccountModel } from '../../models/account/account'
|
||||||
import { VideoModel } from '../../models/video/video'
|
import { VideoModel } from '../../models/video/video'
|
||||||
import { buildNSFWFilter, isUserAbleToSearchRemoteURI } from '../../helpers/express-utils'
|
import { buildNSFWFilter, isUserAbleToSearchRemoteURI } from '../../helpers/express-utils'
|
||||||
import { VideoChannelModel } from '../../models/video/video-channel'
|
import { VideoChannelModel } from '../../models/video/video-channel'
|
||||||
|
import { JobQueue } from '../../lib/job-queue'
|
||||||
|
import { logger } from '../../helpers/logger'
|
||||||
|
|
||||||
const accountsRouter = express.Router()
|
const accountsRouter = express.Router()
|
||||||
|
|
||||||
|
@ -57,6 +59,11 @@ export {
|
||||||
function getAccount (req: express.Request, res: express.Response, next: express.NextFunction) {
|
function getAccount (req: express.Request, res: express.Response, next: express.NextFunction) {
|
||||||
const account: AccountModel = res.locals.account
|
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())
|
return res.json(account.toFormattedJSON())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -30,6 +30,7 @@ import { updateActorAvatarFile } from '../../lib/avatar'
|
||||||
import { auditLoggerFactory, getAuditIdFromRes, VideoChannelAuditView } from '../../helpers/audit-logger'
|
import { auditLoggerFactory, getAuditIdFromRes, VideoChannelAuditView } from '../../helpers/audit-logger'
|
||||||
import { resetSequelizeInstance } from '../../helpers/database-utils'
|
import { resetSequelizeInstance } from '../../helpers/database-utils'
|
||||||
import { UserModel } from '../../models/account/user'
|
import { UserModel } from '../../models/account/user'
|
||||||
|
import { JobQueue } from '../../lib/job-queue'
|
||||||
|
|
||||||
const auditLogger = auditLoggerFactory('channels')
|
const auditLogger = auditLoggerFactory('channels')
|
||||||
const reqAvatarFile = createReqFiles([ 'avatarfile' ], MIMETYPES.IMAGE.MIMETYPE_EXT, { avatarfile: CONFIG.STORAGE.TMP_DIR })
|
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) {
|
async function getVideoChannel (req: express.Request, res: express.Response, next: express.NextFunction) {
|
||||||
const videoChannelWithVideos = await VideoChannelModel.loadAndPopulateAccountAndVideos(res.locals.videoChannel.id)
|
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())
|
return res.json(videoChannelWithVideos.toFormattedJSON())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -399,7 +399,7 @@ function getVideo (req: express.Request, res: express.Response) {
|
||||||
const videoInstance = res.locals.video
|
const videoInstance = res.locals.video
|
||||||
|
|
||||||
if (videoInstance.isOutdated()) {
|
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 }))
|
.catch(err => logger.error('Cannot create AP refresher job for video %s.', videoInstance.url, { err }))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -201,6 +201,62 @@ async function addFetchOutboxJob (actor: ActorModel) {
|
||||||
return JobQueue.Instance.createJob({ type: 'activitypub-http-fetcher', payload })
|
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 {
|
export {
|
||||||
getOrCreateActorAndServerAndModel,
|
getOrCreateActorAndServerAndModel,
|
||||||
buildActorInstance,
|
buildActorInstance,
|
||||||
|
@ -208,6 +264,7 @@ export {
|
||||||
fetchActorTotalItems,
|
fetchActorTotalItems,
|
||||||
fetchAvatarIfExists,
|
fetchAvatarIfExists,
|
||||||
updateActorInstance,
|
updateActorInstance,
|
||||||
|
refreshActorIfNeeded,
|
||||||
updateActorAvatarInstance,
|
updateActorAvatarInstance,
|
||||||
addFetchOutboxJob
|
addFetchOutboxJob
|
||||||
}
|
}
|
||||||
|
@ -373,58 +430,4 @@ async function saveVideoChannel (actor: ActorModel, result: FetchRemoteActorResu
|
||||||
return videoChannelCreated
|
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 }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -179,7 +179,7 @@ async function getOrCreateVideoAndAccountAndChannel (options: {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (syncParam.refreshVideo === true) videoFromDatabase = await refreshVideoIfNeeded(refreshOptions)
|
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 }
|
return { video: videoFromDatabase, created: false }
|
||||||
|
|
|
@ -1,30 +1,33 @@
|
||||||
import * as Bull from 'bull'
|
import * as Bull from 'bull'
|
||||||
import { logger } from '../../../helpers/logger'
|
import { logger } from '../../../helpers/logger'
|
||||||
import { fetchVideoByUrl } from '../../../helpers/video'
|
import { fetchVideoByUrl } from '../../../helpers/video'
|
||||||
import { refreshVideoIfNeeded } from '../../activitypub'
|
import { refreshVideoIfNeeded, refreshActorIfNeeded } from '../../activitypub'
|
||||||
|
import { ActorModel } from '../../../models/activitypub/actor'
|
||||||
|
|
||||||
export type RefreshPayload = {
|
export type RefreshPayload = {
|
||||||
videoUrl: string
|
type: 'video' | 'actor'
|
||||||
type: 'video'
|
url: string
|
||||||
}
|
}
|
||||||
|
|
||||||
async function refreshAPObject (job: Bull.Job) {
|
async function refreshAPObject (job: Bull.Job) {
|
||||||
const payload = job.data as RefreshPayload
|
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 {
|
export {
|
||||||
|
refreshActor,
|
||||||
refreshAPObject
|
refreshAPObject
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
async function refreshAPVideo (videoUrl: string) {
|
async function refreshVideo (videoUrl: string) {
|
||||||
const fetchType = 'all' as 'all'
|
const fetchType = 'all' as 'all'
|
||||||
const syncParam = { likes: true, dislikes: true, shares: true, comments: true, thumbnail: true }
|
const syncParam = { likes: true, dislikes: true, shares: true, comments: true, thumbnail: true }
|
||||||
|
|
||||||
|
@ -39,3 +42,13 @@ async function refreshAPVideo (videoUrl: string) {
|
||||||
await refreshVideoIfNeeded(refreshOptions)
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -288,6 +288,10 @@ export class AccountModel extends Model<AccountModel> {
|
||||||
return this.Actor.isOwned()
|
return this.Actor.isOwned()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isOutdated () {
|
||||||
|
return this.Actor.isOutdated()
|
||||||
|
}
|
||||||
|
|
||||||
getDisplayName () {
|
getDisplayName () {
|
||||||
return this.name
|
return this.name
|
||||||
}
|
}
|
||||||
|
|
|
@ -470,4 +470,8 @@ export class VideoChannelModel extends Model<VideoChannelModel> {
|
||||||
getDisplayName () {
|
getDisplayName () {
|
||||||
return this.name
|
return this.name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isOutdated () {
|
||||||
|
return this.Actor.isOutdated()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue