Files
omnivore/packages/api/src/resolvers/integrations/index.ts
2024-03-21 14:06:56 +08:00

279 lines
6.8 KiB
TypeScript

import { DeepPartial } from 'typeorm'
import {
ImportItemState,
Integration,
IntegrationType,
} from '../../entity/integration'
import { env } from '../../env'
import {
DeleteIntegrationError,
DeleteIntegrationErrorCode,
DeleteIntegrationSuccess,
ExportToIntegrationError,
ExportToIntegrationErrorCode,
ExportToIntegrationSuccess,
ImportFromIntegrationError,
ImportFromIntegrationErrorCode,
ImportFromIntegrationSuccess,
IntegrationError,
IntegrationErrorCode,
IntegrationsError,
IntegrationsSuccess,
IntegrationSuccess,
MutationDeleteIntegrationArgs,
MutationExportToIntegrationArgs,
MutationImportFromIntegrationArgs,
MutationSetIntegrationArgs,
QueryIntegrationArgs,
SetIntegrationError,
SetIntegrationErrorCode,
SetIntegrationSuccess,
TaskState,
} from '../../generated/graphql'
import { createIntegrationToken } from '../../routers/auth/jwt_helpers'
import {
findIntegration,
findIntegrationByName,
findIntegrations,
getIntegrationClient,
removeIntegration,
saveIntegration,
updateIntegration,
} from '../../services/integrations'
import { analytics } from '../../utils/analytics'
import {
deleteTask,
enqueueExportToIntegration,
enqueueImportFromIntegration,
} from '../../utils/createTask'
import { authorized } from '../../utils/gql-utils'
export const setIntegrationResolver = authorized<
SetIntegrationSuccess,
SetIntegrationError,
MutationSetIntegrationArgs
>(async (_, { input }, { uid }) => {
const integrationToSave: DeepPartial<Integration> = {
...input,
user: { id: uid },
id: input.id || undefined,
type: input.type || IntegrationType.Export,
syncedAt: input.syncedAt ? new Date(input.syncedAt) : undefined,
importItemState:
input.type === IntegrationType.Import
? input.importItemState || ImportItemState.Unarchived // default to unarchived
: undefined,
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
settings: input.settings,
}
if (input.id) {
// Update
const existingIntegration = await findIntegration({ id: input.id }, uid)
if (!existingIntegration) {
return {
errorCodes: [SetIntegrationErrorCode.NotFound],
}
}
integrationToSave.id = existingIntegration.id
integrationToSave.taskName = existingIntegration.taskName
} else {
// Create
const integrationService = getIntegrationClient(input.name, input.token)
// authorize and get access token
const token = await integrationService.accessToken()
if (!token) {
return {
errorCodes: [SetIntegrationErrorCode.InvalidToken],
}
}
integrationToSave.token = token
}
// save integration
const integration = await saveIntegration(integrationToSave, uid)
if (integration.name.toLowerCase() === 'readwise') {
// create a task to export all the items for readwise temporarily
await enqueueExportToIntegration(integration.id, uid)
}
analytics.capture({
distinctId: uid,
event: 'integration_set',
properties: {
id: integrationToSave.id,
env: env.server.apiEnv,
},
})
return {
integration,
}
})
export const integrationsResolver = authorized<
IntegrationsSuccess,
IntegrationsError
>(async (_, __, { uid }) => {
const integrations = await findIntegrations(uid)
return {
integrations,
}
})
export const integrationResolver = authorized<
IntegrationSuccess,
IntegrationError,
QueryIntegrationArgs
>(async (_, { name }, { uid, log }) => {
const integration = await findIntegrationByName(name, uid)
if (!integration) {
log.error('integration not found', name)
return {
errorCodes: [IntegrationErrorCode.NotFound],
}
}
return {
integration,
}
})
export const deleteIntegrationResolver = authorized<
DeleteIntegrationSuccess,
DeleteIntegrationError,
MutationDeleteIntegrationArgs
>(async (_, { id }, { claims: { uid }, log }) => {
log.info('deleteIntegrationResolver')
const integration = await findIntegration({ id }, uid)
if (!integration) {
return {
errorCodes: [DeleteIntegrationErrorCode.NotFound],
}
}
if (integration.taskName) {
// delete the task if task exists
await deleteTask(integration.taskName)
log.info('task deleted', integration.taskName)
}
const deletedIntegration = await removeIntegration(integration, uid)
deletedIntegration.id = id
analytics.capture({
distinctId: uid,
event: 'integration_delete',
properties: {
integrationId: deletedIntegration.id,
env: env.server.apiEnv,
},
})
return {
integration,
}
})
export const importFromIntegrationResolver = authorized<
ImportFromIntegrationSuccess,
ImportFromIntegrationError,
MutationImportFromIntegrationArgs
>(async (_, { integrationId }, { claims: { uid }, log }) => {
const integration = await findIntegration({ id: integrationId }, uid)
if (!integration) {
return {
errorCodes: [ImportFromIntegrationErrorCode.Unauthorized],
}
}
const authToken = await createIntegrationToken({
uid: integration.user.id,
token: integration.token,
})
if (!authToken) {
return {
errorCodes: [ImportFromIntegrationErrorCode.BadRequest],
}
}
// create a task to import all the pages
const taskName = await enqueueImportFromIntegration(
integration.id,
integration.name,
integration.syncedAt?.getTime() || 0,
authToken,
integration.importItemState || ImportItemState.Unarchived
)
// update task name in integration
await updateIntegration(integration.id, { taskName }, uid)
analytics.capture({
distinctId: uid,
event: 'integration_import',
properties: {
integrationId,
},
})
return {
success: true,
}
})
export const exportToIntegrationResolver = authorized<
ExportToIntegrationSuccess,
ExportToIntegrationError,
MutationExportToIntegrationArgs
>(async (_, { integrationId }, { uid, log }) => {
const integration = await findIntegration({ id: integrationId }, uid)
if (!integration) {
log.error('integration not found', integrationId)
return {
errorCodes: [ExportToIntegrationErrorCode.Unauthorized],
}
}
// create a job to export all the items
const job = await enqueueExportToIntegration(integration.id, uid)
if (!job || !job.id) {
log.error('failed to create task', integrationId)
return {
errorCodes: [ExportToIntegrationErrorCode.FailedToCreateTask],
}
}
// update task name in integration
await updateIntegration(integration.id, { taskName: job.id }, uid)
analytics.capture({
distinctId: uid,
event: 'integration_export',
properties: {
integrationId,
},
})
return {
task: {
id: job.id,
name: job.name,
state: TaskState.Pending,
createdAt: new Date(job.timestamp),
progress: 0,
runningTime: 0,
cancellable: true,
},
}
})