279 lines
6.8 KiB
TypeScript
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,
|
|
},
|
|
}
|
|
})
|