Files
speckle-server/packages/server/modules/previews/services/retryErrors.ts
T

121 lines
4.0 KiB
TypeScript

import type { Logger } from '@/observability/logging'
import {
GetPaginatedObjectPreviewsInErrorState,
GetPaginatedObjectPreviewsPage,
GetPaginatedObjectPreviewsTotalCount,
RequestObjectPreview,
UpdateObjectPreview
} from '@/modules/previews/domain/operations'
import { PreviewStatus } from '@/modules/previews/domain/consts'
import { Roles, Scopes, TIME_MS } from '@speckle/shared'
import { DefaultAppIds } from '@/modules/auth/defaultApps'
import { TokenResourceIdentifierType } from '@/modules/core/domain/tokens/types'
import { GetStreamCollaborators } from '@/modules/core/domain/streams/operations'
import { CreateAndStoreAppToken } from '@/modules/core/domain/tokens/operations'
export const getPaginatedObjectPreviewInErrorStateFactory =
(deps: {
getPaginatedObjectPreviewsPage: GetPaginatedObjectPreviewsPage
getPaginatedObjectPreviewsTotalCount: GetPaginatedObjectPreviewsTotalCount
maximumNumberOfAttempts?: number
}): GetPaginatedObjectPreviewsInErrorState =>
async (params) => {
const filter = {
status: PreviewStatus.ERROR,
maxNumberOfAttempts: deps.maximumNumberOfAttempts ?? 3 // only retry items that have errored less than 3 times
}
const [result, totalCount] = await Promise.all([
deps.getPaginatedObjectPreviewsPage({
...params,
filter
}),
deps.getPaginatedObjectPreviewsTotalCount({
...params,
filter
})
])
return {
...result,
totalCount
}
}
export const retryFailedPreviewsFactory = (deps: {
getPaginatedObjectPreviewsInErrorState: GetPaginatedObjectPreviewsInErrorState
updateObjectPreview: UpdateObjectPreview
getStreamCollaborators: GetStreamCollaborators
serverOrigin: string
createAppToken: CreateAndStoreAppToken
requestObjectPreview: RequestObjectPreview
}) => {
const {
getPaginatedObjectPreviewsInErrorState,
updateObjectPreview,
getStreamCollaborators,
serverOrigin,
createAppToken,
requestObjectPreview
} = deps
return async (params: { logger: Logger }): Promise<boolean> => {
const { logger } = params
const { items, totalCount } = await getPaginatedObjectPreviewsInErrorState({
limit: 1, //get the least recent item that has errored
cursor: null // always get the first item
})
if (items.length === 0) {
//NOTE we rely on the items returned, as this accounts for the cursor position. More errored items might have been added since the last time we checked and changed the totalCount.
logger.info('No object previews in error state found.')
return false
}
const objPreview = items[0]
const { streamId, objectId } = objPreview
logger.info(
{
totalErroredPreviewCount: totalCount,
streamId, //legacy
projectId: streamId,
objectId,
attempts: objPreview.attempts
},
'Found {totalErroredPreviewCount} object previews in error state. Attempting to retry one: {projectId}.{objectId}. Previous attempts: {attempts}'
)
await updateObjectPreview({
objectPreview: {
...objPreview,
previewStatus: PreviewStatus.PENDING, // move it to pending so it doesn't get picked up again
incrementAttempts: true // increment the number of attempts
}
})
const owners = await getStreamCollaborators(streamId, Roles.Stream.Owner)
// there is always an owner, this is safe
const userId = owners[0].id
// we're running the preview generation in the name of a project owner
const token = await createAppToken({
appId: DefaultAppIds.Web,
name: `preview-${streamId}@${objectId}`,
userId,
scopes: [Scopes.Streams.Read],
lifespan: 2 * TIME_MS.hour,
limitResources: [
{
id: streamId,
type: TokenResourceIdentifierType.Project
}
]
})
const url = new URL(
`/projects/${streamId}/models/${objectId}`,
serverOrigin
).toString()
await requestObjectPreview({ jobId: `${streamId}.${objectId}`, token, url })
return true
}
}