mirror of
https://github.com/omnivore-app/omnivore.git
synced 2026-03-11 08:54:26 +00:00
add export to integration and get task api
This commit is contained in:
parent
79c8d8ebbb
commit
390d12eea1
10 changed files with 318 additions and 91 deletions
|
|
@ -892,6 +892,23 @@ export type EmptyTrashSuccess = {
|
|||
success?: Maybe<Scalars['Boolean']>;
|
||||
};
|
||||
|
||||
export type ExportToIntegrationError = {
|
||||
__typename?: 'ExportToIntegrationError';
|
||||
errorCodes: Array<ExportToIntegrationErrorCode>;
|
||||
};
|
||||
|
||||
export enum ExportToIntegrationErrorCode {
|
||||
FailedToCreateTask = 'FAILED_TO_CREATE_TASK',
|
||||
Unauthorized = 'UNAUTHORIZED'
|
||||
}
|
||||
|
||||
export type ExportToIntegrationResult = ExportToIntegrationError | ExportToIntegrationSuccess;
|
||||
|
||||
export type ExportToIntegrationSuccess = {
|
||||
__typename?: 'ExportToIntegrationSuccess';
|
||||
task: Task;
|
||||
};
|
||||
|
||||
export type Feature = {
|
||||
__typename?: 'Feature';
|
||||
createdAt: Scalars['Date'];
|
||||
|
|
@ -1582,6 +1599,7 @@ export type Mutation = {
|
|||
deleteWebhook: DeleteWebhookResult;
|
||||
editDiscoverFeed: EditDiscoverFeedResult;
|
||||
emptyTrash: EmptyTrashResult;
|
||||
exportToIntegration: ExportToIntegrationResult;
|
||||
fetchContent: FetchContentResult;
|
||||
generateApiKey: GenerateApiKeyResult;
|
||||
googleLogin: LoginResult;
|
||||
|
|
@ -1737,6 +1755,11 @@ export type MutationEditDiscoverFeedArgs = {
|
|||
};
|
||||
|
||||
|
||||
export type MutationExportToIntegrationArgs = {
|
||||
integrationId: Scalars['ID'];
|
||||
};
|
||||
|
||||
|
||||
export type MutationFetchContentArgs = {
|
||||
id: Scalars['ID'];
|
||||
};
|
||||
|
|
@ -3183,6 +3206,26 @@ export type SyncUpdatedItemEdge = {
|
|||
updateReason: UpdateReason;
|
||||
};
|
||||
|
||||
export type Task = {
|
||||
__typename?: 'Task';
|
||||
cancellable?: Maybe<Scalars['Boolean']>;
|
||||
createdAt: Scalars['Date'];
|
||||
failedReason?: Maybe<Scalars['String']>;
|
||||
id: Scalars['ID'];
|
||||
name: Scalars['String'];
|
||||
progress?: Maybe<Scalars['Float']>;
|
||||
runningTime?: Maybe<Scalars['Int']>;
|
||||
state: TaskState;
|
||||
};
|
||||
|
||||
export enum TaskState {
|
||||
Cancelled = 'CANCELLED',
|
||||
Failed = 'FAILED',
|
||||
Pending = 'PENDING',
|
||||
Running = 'RUNNING',
|
||||
Succeeded = 'SUCCEEDED'
|
||||
}
|
||||
|
||||
export type TypeaheadSearchError = {
|
||||
__typename?: 'TypeaheadSearchError';
|
||||
errorCodes: Array<TypeaheadSearchErrorCode>;
|
||||
|
|
@ -4014,6 +4057,10 @@ export type ResolversTypes = {
|
|||
EmptyTrashErrorCode: EmptyTrashErrorCode;
|
||||
EmptyTrashResult: ResolversTypes['EmptyTrashError'] | ResolversTypes['EmptyTrashSuccess'];
|
||||
EmptyTrashSuccess: ResolverTypeWrapper<EmptyTrashSuccess>;
|
||||
ExportToIntegrationError: ResolverTypeWrapper<ExportToIntegrationError>;
|
||||
ExportToIntegrationErrorCode: ExportToIntegrationErrorCode;
|
||||
ExportToIntegrationResult: ResolversTypes['ExportToIntegrationError'] | ResolversTypes['ExportToIntegrationSuccess'];
|
||||
ExportToIntegrationSuccess: ResolverTypeWrapper<ExportToIntegrationSuccess>;
|
||||
Feature: ResolverTypeWrapper<Feature>;
|
||||
Feed: ResolverTypeWrapper<Feed>;
|
||||
FeedArticle: ResolverTypeWrapper<FeedArticle>;
|
||||
|
|
@ -4323,6 +4370,8 @@ export type ResolversTypes = {
|
|||
SubscriptionsResult: ResolversTypes['SubscriptionsError'] | ResolversTypes['SubscriptionsSuccess'];
|
||||
SubscriptionsSuccess: ResolverTypeWrapper<SubscriptionsSuccess>;
|
||||
SyncUpdatedItemEdge: ResolverTypeWrapper<SyncUpdatedItemEdge>;
|
||||
Task: ResolverTypeWrapper<Task>;
|
||||
TaskState: TaskState;
|
||||
TypeaheadSearchError: ResolverTypeWrapper<TypeaheadSearchError>;
|
||||
TypeaheadSearchErrorCode: TypeaheadSearchErrorCode;
|
||||
TypeaheadSearchItem: ResolverTypeWrapper<TypeaheadSearchItem>;
|
||||
|
|
@ -4564,6 +4613,9 @@ export type ResolversParentTypes = {
|
|||
EmptyTrashError: EmptyTrashError;
|
||||
EmptyTrashResult: ResolversParentTypes['EmptyTrashError'] | ResolversParentTypes['EmptyTrashSuccess'];
|
||||
EmptyTrashSuccess: EmptyTrashSuccess;
|
||||
ExportToIntegrationError: ExportToIntegrationError;
|
||||
ExportToIntegrationResult: ResolversParentTypes['ExportToIntegrationError'] | ResolversParentTypes['ExportToIntegrationSuccess'];
|
||||
ExportToIntegrationSuccess: ExportToIntegrationSuccess;
|
||||
Feature: Feature;
|
||||
Feed: Feed;
|
||||
FeedArticle: FeedArticle;
|
||||
|
|
@ -4804,6 +4856,7 @@ export type ResolversParentTypes = {
|
|||
SubscriptionsResult: ResolversParentTypes['SubscriptionsError'] | ResolversParentTypes['SubscriptionsSuccess'];
|
||||
SubscriptionsSuccess: SubscriptionsSuccess;
|
||||
SyncUpdatedItemEdge: SyncUpdatedItemEdge;
|
||||
Task: Task;
|
||||
TypeaheadSearchError: TypeaheadSearchError;
|
||||
TypeaheadSearchItem: TypeaheadSearchItem;
|
||||
TypeaheadSearchResult: ResolversParentTypes['TypeaheadSearchError'] | ResolversParentTypes['TypeaheadSearchSuccess'];
|
||||
|
|
@ -5502,6 +5555,20 @@ export type EmptyTrashSuccessResolvers<ContextType = ResolverContext, ParentType
|
|||
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
|
||||
};
|
||||
|
||||
export type ExportToIntegrationErrorResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['ExportToIntegrationError'] = ResolversParentTypes['ExportToIntegrationError']> = {
|
||||
errorCodes?: Resolver<Array<ResolversTypes['ExportToIntegrationErrorCode']>, ParentType, ContextType>;
|
||||
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
|
||||
};
|
||||
|
||||
export type ExportToIntegrationResultResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['ExportToIntegrationResult'] = ResolversParentTypes['ExportToIntegrationResult']> = {
|
||||
__resolveType: TypeResolveFn<'ExportToIntegrationError' | 'ExportToIntegrationSuccess', ParentType, ContextType>;
|
||||
};
|
||||
|
||||
export type ExportToIntegrationSuccessResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['ExportToIntegrationSuccess'] = ResolversParentTypes['ExportToIntegrationSuccess']> = {
|
||||
task?: Resolver<ResolversTypes['Task'], ParentType, ContextType>;
|
||||
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
|
||||
};
|
||||
|
||||
export type FeatureResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['Feature'] = ResolversParentTypes['Feature']> = {
|
||||
createdAt?: Resolver<ResolversTypes['Date'], ParentType, ContextType>;
|
||||
expiresAt?: Resolver<Maybe<ResolversTypes['Date']>, ParentType, ContextType>;
|
||||
|
|
@ -6037,6 +6104,7 @@ export type MutationResolvers<ContextType = ResolverContext, ParentType extends
|
|||
deleteWebhook?: Resolver<ResolversTypes['DeleteWebhookResult'], ParentType, ContextType, RequireFields<MutationDeleteWebhookArgs, 'id'>>;
|
||||
editDiscoverFeed?: Resolver<ResolversTypes['EditDiscoverFeedResult'], ParentType, ContextType, RequireFields<MutationEditDiscoverFeedArgs, 'input'>>;
|
||||
emptyTrash?: Resolver<ResolversTypes['EmptyTrashResult'], ParentType, ContextType>;
|
||||
exportToIntegration?: Resolver<ResolversTypes['ExportToIntegrationResult'], ParentType, ContextType, RequireFields<MutationExportToIntegrationArgs, 'integrationId'>>;
|
||||
fetchContent?: Resolver<ResolversTypes['FetchContentResult'], ParentType, ContextType, RequireFields<MutationFetchContentArgs, 'id'>>;
|
||||
generateApiKey?: Resolver<ResolversTypes['GenerateApiKeyResult'], ParentType, ContextType, RequireFields<MutationGenerateApiKeyArgs, 'input'>>;
|
||||
googleLogin?: Resolver<ResolversTypes['LoginResult'], ParentType, ContextType, RequireFields<MutationGoogleLoginArgs, 'input'>>;
|
||||
|
|
@ -6788,6 +6856,18 @@ export type SyncUpdatedItemEdgeResolvers<ContextType = ResolverContext, ParentTy
|
|||
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
|
||||
};
|
||||
|
||||
export type TaskResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['Task'] = ResolversParentTypes['Task']> = {
|
||||
cancellable?: Resolver<Maybe<ResolversTypes['Boolean']>, ParentType, ContextType>;
|
||||
createdAt?: Resolver<ResolversTypes['Date'], ParentType, ContextType>;
|
||||
failedReason?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
|
||||
id?: Resolver<ResolversTypes['ID'], ParentType, ContextType>;
|
||||
name?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
|
||||
progress?: Resolver<Maybe<ResolversTypes['Float']>, ParentType, ContextType>;
|
||||
runningTime?: Resolver<Maybe<ResolversTypes['Int']>, ParentType, ContextType>;
|
||||
state?: Resolver<ResolversTypes['TaskState'], ParentType, ContextType>;
|
||||
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
|
||||
};
|
||||
|
||||
export type TypeaheadSearchErrorResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['TypeaheadSearchError'] = ResolversParentTypes['TypeaheadSearchError']> = {
|
||||
errorCodes?: Resolver<Array<ResolversTypes['TypeaheadSearchErrorCode']>, ParentType, ContextType>;
|
||||
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
|
||||
|
|
@ -7272,6 +7352,9 @@ export type Resolvers<ContextType = ResolverContext> = {
|
|||
EmptyTrashError?: EmptyTrashErrorResolvers<ContextType>;
|
||||
EmptyTrashResult?: EmptyTrashResultResolvers<ContextType>;
|
||||
EmptyTrashSuccess?: EmptyTrashSuccessResolvers<ContextType>;
|
||||
ExportToIntegrationError?: ExportToIntegrationErrorResolvers<ContextType>;
|
||||
ExportToIntegrationResult?: ExportToIntegrationResultResolvers<ContextType>;
|
||||
ExportToIntegrationSuccess?: ExportToIntegrationSuccessResolvers<ContextType>;
|
||||
Feature?: FeatureResolvers<ContextType>;
|
||||
Feed?: FeedResolvers<ContextType>;
|
||||
FeedArticle?: FeedArticleResolvers<ContextType>;
|
||||
|
|
@ -7473,6 +7556,7 @@ export type Resolvers<ContextType = ResolverContext> = {
|
|||
SubscriptionsResult?: SubscriptionsResultResolvers<ContextType>;
|
||||
SubscriptionsSuccess?: SubscriptionsSuccessResolvers<ContextType>;
|
||||
SyncUpdatedItemEdge?: SyncUpdatedItemEdgeResolvers<ContextType>;
|
||||
Task?: TaskResolvers<ContextType>;
|
||||
TypeaheadSearchError?: TypeaheadSearchErrorResolvers<ContextType>;
|
||||
TypeaheadSearchItem?: TypeaheadSearchItemResolvers<ContextType>;
|
||||
TypeaheadSearchResult?: TypeaheadSearchResultResolvers<ContextType>;
|
||||
|
|
|
|||
|
|
@ -794,6 +794,21 @@ type EmptyTrashSuccess {
|
|||
success: Boolean
|
||||
}
|
||||
|
||||
type ExportToIntegrationError {
|
||||
errorCodes: [ExportToIntegrationErrorCode!]!
|
||||
}
|
||||
|
||||
enum ExportToIntegrationErrorCode {
|
||||
FAILED_TO_CREATE_TASK
|
||||
UNAUTHORIZED
|
||||
}
|
||||
|
||||
union ExportToIntegrationResult = ExportToIntegrationError | ExportToIntegrationSuccess
|
||||
|
||||
type ExportToIntegrationSuccess {
|
||||
task: Task!
|
||||
}
|
||||
|
||||
type Feature {
|
||||
createdAt: Date!
|
||||
expiresAt: Date
|
||||
|
|
@ -1422,6 +1437,7 @@ type Mutation {
|
|||
deleteWebhook(id: ID!): DeleteWebhookResult!
|
||||
editDiscoverFeed(input: EditDiscoverFeedInput!): EditDiscoverFeedResult!
|
||||
emptyTrash: EmptyTrashResult!
|
||||
exportToIntegration(integrationId: ID!): ExportToIntegrationResult!
|
||||
fetchContent(id: ID!): FetchContentResult!
|
||||
generateApiKey(input: GenerateApiKeyInput!): GenerateApiKeyResult!
|
||||
googleLogin(input: GoogleLoginInput!): LoginResult!
|
||||
|
|
@ -2517,6 +2533,25 @@ type SyncUpdatedItemEdge {
|
|||
updateReason: UpdateReason!
|
||||
}
|
||||
|
||||
type Task {
|
||||
cancellable: Boolean
|
||||
createdAt: Date!
|
||||
failedReason: String
|
||||
id: ID!
|
||||
name: String!
|
||||
progress: Float
|
||||
runningTime: Int
|
||||
state: TaskState!
|
||||
}
|
||||
|
||||
enum TaskState {
|
||||
CANCELLED
|
||||
FAILED
|
||||
PENDING
|
||||
RUNNING
|
||||
SUCCEEDED
|
||||
}
|
||||
|
||||
type TypeaheadSearchError {
|
||||
errorCodes: [TypeaheadSearchErrorCode!]!
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
import {
|
||||
ConnectionOptions,
|
||||
Job,
|
||||
JobState,
|
||||
JobType,
|
||||
Queue,
|
||||
QueueEvents,
|
||||
|
|
@ -13,6 +14,7 @@ import {
|
|||
import express, { Express } from 'express'
|
||||
import { appDataSource } from './data_source'
|
||||
import { env } from './env'
|
||||
import { TaskState } from './generated/graphql'
|
||||
import { aiSummarize, AI_SUMMARIZE_JOB_NAME } from './jobs/ai-summarize'
|
||||
import { bulkAction, BULK_ACTION_JOB_NAME } from './jobs/bulk_action'
|
||||
import { callWebhook, CALL_WEBHOOK_JOB_NAME } from './jobs/call_webhook'
|
||||
|
|
@ -25,6 +27,12 @@ import {
|
|||
exportItem,
|
||||
EXPORT_ITEM_JOB_NAME,
|
||||
} from './jobs/integration/export_item'
|
||||
import {
|
||||
processYouTubeTranscript,
|
||||
processYouTubeVideo,
|
||||
PROCESS_YOUTUBE_TRANSCRIPT_JOB_NAME,
|
||||
PROCESS_YOUTUBE_VIDEO_JOB_NAME,
|
||||
} from './jobs/process-youtube-video'
|
||||
import { refreshAllFeeds } from './jobs/rss/refreshAllFeeds'
|
||||
import { refreshFeed } from './jobs/rss/refreshFeed'
|
||||
import { savePageJob } from './jobs/save_page'
|
||||
|
|
@ -44,12 +52,6 @@ import { redisDataSource } from './redis_data_source'
|
|||
import { CACHED_READING_POSITION_PREFIX } from './services/cached_reading_position'
|
||||
import { getJobPriority } from './utils/createTask'
|
||||
import { logger } from './utils/logger'
|
||||
import {
|
||||
PROCESS_YOUTUBE_TRANSCRIPT_JOB_NAME,
|
||||
PROCESS_YOUTUBE_VIDEO_JOB_NAME,
|
||||
processYouTubeTranscript,
|
||||
processYouTubeVideo,
|
||||
} from './jobs/process-youtube-video'
|
||||
|
||||
export const QUEUE_NAME = 'omnivore-backend-queue'
|
||||
export const JOB_VERSION = 'v001'
|
||||
|
|
@ -82,6 +84,33 @@ export const getBackendQueue = async (): Promise<Queue | undefined> => {
|
|||
return backendQueue
|
||||
}
|
||||
|
||||
export const getJob = async (jobId: string) => {
|
||||
const queue = await getBackendQueue()
|
||||
if (!queue) {
|
||||
return
|
||||
}
|
||||
return queue.getJob(jobId)
|
||||
}
|
||||
|
||||
export const jobStateToTaskState = (
|
||||
jobState: JobState | 'unknown'
|
||||
): TaskState => {
|
||||
switch (jobState) {
|
||||
case 'completed':
|
||||
return TaskState.Succeeded
|
||||
case 'failed':
|
||||
return TaskState.Failed
|
||||
case 'active':
|
||||
return TaskState.Running
|
||||
case 'delayed':
|
||||
return TaskState.Pending
|
||||
case 'waiting':
|
||||
return TaskState.Pending
|
||||
default:
|
||||
return TaskState.Pending
|
||||
}
|
||||
}
|
||||
|
||||
export const createWorker = (connection: ConnectionOptions) =>
|
||||
new Worker(
|
||||
QUEUE_NAME,
|
||||
|
|
|
|||
|
|
@ -41,6 +41,15 @@ import {
|
|||
generateUploadFilePathName,
|
||||
} from '../utils/uploads'
|
||||
import { emptyTrashResolver, fetchContentResolver } from './article'
|
||||
import {
|
||||
addDiscoverFeedResolver,
|
||||
deleteDiscoverArticleResolver,
|
||||
deleteDiscoverFeedsResolver,
|
||||
editDiscoverFeedsResolver,
|
||||
getDiscoverFeedArticlesResolver,
|
||||
getDiscoverFeedsResolver,
|
||||
saveDiscoverArticleResolver,
|
||||
} from './discover_feeds'
|
||||
import { optInFeatureResolver } from './features'
|
||||
import { uploadImportFileResolver } from './importers/uploadImportFileResolver'
|
||||
import {
|
||||
|
|
@ -65,6 +74,7 @@ import {
|
|||
deleteRuleResolver,
|
||||
deleteWebhookResolver,
|
||||
deviceTokensResolver,
|
||||
exportToIntegrationResolver,
|
||||
feedsResolver,
|
||||
filtersResolver,
|
||||
generateApiKeyResolver,
|
||||
|
|
@ -144,15 +154,6 @@ import { markEmailAsItemResolver, recentEmailsResolver } from './recent_emails'
|
|||
import { recentSearchesResolver } from './recent_searches'
|
||||
import { WithDataSourcesContext } from './types'
|
||||
import { updateEmailResolver } from './user'
|
||||
import {
|
||||
addDiscoverFeedResolver,
|
||||
getDiscoverFeedsResolver,
|
||||
getDiscoverFeedArticlesResolver,
|
||||
saveDiscoverArticleResolver,
|
||||
deleteDiscoverArticleResolver,
|
||||
deleteDiscoverFeedsResolver,
|
||||
editDiscoverFeedsResolver,
|
||||
} from './discover_feeds'
|
||||
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
type ResultResolveType = {
|
||||
|
|
@ -314,6 +315,7 @@ export const functionResolvers = {
|
|||
editDiscoverFeed: editDiscoverFeedsResolver,
|
||||
emptyTrash: emptyTrashResolver,
|
||||
fetchContent: fetchContentResolver,
|
||||
exportToIntegration: exportToIntegrationResolver,
|
||||
},
|
||||
Query: {
|
||||
me: getMeUserResolver,
|
||||
|
|
@ -665,4 +667,5 @@ export const functionResolvers = {
|
|||
...resultResolveTypeResolver('EmptyTrash'),
|
||||
...resultResolveTypeResolver('FetchContent'),
|
||||
...resultResolveTypeResolver('Integration'),
|
||||
...resultResolveTypeResolver('ExportToIntegration'),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,9 @@ import {
|
|||
DeleteIntegrationError,
|
||||
DeleteIntegrationErrorCode,
|
||||
DeleteIntegrationSuccess,
|
||||
ExportToIntegrationError,
|
||||
ExportToIntegrationErrorCode,
|
||||
ExportToIntegrationSuccess,
|
||||
ImportFromIntegrationError,
|
||||
ImportFromIntegrationErrorCode,
|
||||
ImportFromIntegrationSuccess,
|
||||
|
|
@ -18,12 +21,14 @@ import {
|
|||
IntegrationsSuccess,
|
||||
IntegrationSuccess,
|
||||
MutationDeleteIntegrationArgs,
|
||||
MutationExportToIntegrationArgs,
|
||||
MutationImportFromIntegrationArgs,
|
||||
MutationSetIntegrationArgs,
|
||||
QueryIntegrationArgs,
|
||||
SetIntegrationError,
|
||||
SetIntegrationErrorCode,
|
||||
SetIntegrationSuccess,
|
||||
TaskState,
|
||||
} from '../../generated/graphql'
|
||||
import { createIntegrationToken } from '../../routers/auth/jwt_helpers'
|
||||
import {
|
||||
|
|
@ -38,6 +43,7 @@ import {
|
|||
import { analytics } from '../../utils/analytics'
|
||||
import {
|
||||
deleteTask,
|
||||
enqueueExportToIntegration,
|
||||
enqueueImportFromIntegration,
|
||||
} from '../../utils/createTask'
|
||||
import { authorized } from '../../utils/gql-utils'
|
||||
|
|
@ -216,3 +222,52 @@ export const importFromIntegrationResolver = authorized<
|
|||
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,
|
||||
},
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,65 +0,0 @@
|
|||
/* eslint-disable @typescript-eslint/no-misused-promises */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
import express from 'express'
|
||||
import { Integration, IntegrationType } from '../../entity/integration'
|
||||
import { readPushSubscription } from '../../pubsub'
|
||||
import { getRepository } from '../../repository'
|
||||
import { enqueueExportAllItems } from '../../utils/createTask'
|
||||
import { logger } from '../../utils/logger'
|
||||
import { createIntegrationToken } from '../auth/jwt_helpers'
|
||||
|
||||
export function integrationsServiceRouter() {
|
||||
const router = express.Router()
|
||||
|
||||
router.post('/export', async (req, res) => {
|
||||
logger.info('start to sync with integration')
|
||||
|
||||
try {
|
||||
const { message: msgStr, expired } = readPushSubscription(req)
|
||||
if (!msgStr) {
|
||||
return res.status(200).send('Bad Request')
|
||||
}
|
||||
|
||||
if (expired) {
|
||||
logger.info('discarding expired message')
|
||||
return res.status(200).send('Expired')
|
||||
}
|
||||
|
||||
// find all active integrations
|
||||
const integrations = await getRepository(Integration).find({
|
||||
where: {
|
||||
enabled: true,
|
||||
type: IntegrationType.Export,
|
||||
},
|
||||
relations: ['user'],
|
||||
})
|
||||
|
||||
// create a task to sync with each integration
|
||||
await Promise.all(
|
||||
integrations.map(async (integration) => {
|
||||
const authToken = await createIntegrationToken({
|
||||
uid: integration.user.id,
|
||||
token: integration.token,
|
||||
})
|
||||
|
||||
if (!authToken) {
|
||||
logger.error('failed to create auth token', {
|
||||
integrationId: integration.id,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
return enqueueExportAllItems(integration.id, integration.user.id)
|
||||
})
|
||||
)
|
||||
} catch (err) {
|
||||
logger.error('sync with integrations failed', err)
|
||||
return res.status(500).send(err)
|
||||
}
|
||||
|
||||
res.status(200).send('OK')
|
||||
})
|
||||
|
||||
return router
|
||||
}
|
||||
49
packages/api/src/routers/task_router.ts
Normal file
49
packages/api/src/routers/task_router.ts
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
import cors from 'cors'
|
||||
import express from 'express'
|
||||
import { Task, TaskState } from '../generated/graphql'
|
||||
import { getJob, jobStateToTaskState } from '../queue-processor'
|
||||
import { getClaimsByToken, getTokenByRequest } from '../utils/auth'
|
||||
import { corsConfig } from '../utils/corsConfig'
|
||||
import { logger } from '../utils/logger'
|
||||
|
||||
export function taskRouter() {
|
||||
const router = express.Router()
|
||||
|
||||
router.get('/:id', cors<express.Request>(corsConfig), async (req, res) => {
|
||||
const token = getTokenByRequest(req)
|
||||
const claims = await getClaimsByToken(token)
|
||||
if (!claims) {
|
||||
return res.status(401).send('UNAUTHORIZED')
|
||||
}
|
||||
|
||||
try {
|
||||
const job = await getJob(req.params.id)
|
||||
if (!job || !job.id) {
|
||||
res.status(404).send('Not Found')
|
||||
return
|
||||
}
|
||||
|
||||
const jobState = await job.getState()
|
||||
const state = jobStateToTaskState(jobState)
|
||||
const finishedAt = job.finishedOn ? job.finishedOn : Date.now()
|
||||
const runningTime = job.processedOn ? finishedAt - job.processedOn : 0
|
||||
|
||||
const result: Task = {
|
||||
id: job.id,
|
||||
state,
|
||||
createdAt: new Date(job.timestamp),
|
||||
name: job.name,
|
||||
runningTime,
|
||||
progress: job.progress as number,
|
||||
failedReason: state === TaskState.Failed ? job.failedReason : undefined,
|
||||
}
|
||||
|
||||
res.send(result)
|
||||
} catch (e) {
|
||||
logger.error('failed to get task', e)
|
||||
res.status(500)
|
||||
}
|
||||
})
|
||||
|
||||
return router
|
||||
}
|
||||
|
|
@ -3023,6 +3023,42 @@ const schema = gql`
|
|||
NOT_FOUND
|
||||
}
|
||||
|
||||
union ExportToIntegrationResult =
|
||||
ExportToIntegrationSuccess
|
||||
| ExportToIntegrationError
|
||||
|
||||
type ExportToIntegrationSuccess {
|
||||
task: Task!
|
||||
}
|
||||
|
||||
type Task {
|
||||
id: ID!
|
||||
name: String!
|
||||
state: TaskState!
|
||||
createdAt: Date!
|
||||
runningTime: Int # in milliseconds
|
||||
cancellable: Boolean
|
||||
progress: Float
|
||||
failedReason: String
|
||||
}
|
||||
|
||||
enum TaskState {
|
||||
PENDING
|
||||
RUNNING
|
||||
SUCCEEDED
|
||||
FAILED
|
||||
CANCELLED
|
||||
}
|
||||
|
||||
type ExportToIntegrationError {
|
||||
errorCodes: [ExportToIntegrationErrorCode!]!
|
||||
}
|
||||
|
||||
enum ExportToIntegrationErrorCode {
|
||||
UNAUTHORIZED
|
||||
FAILED_TO_CREATE_TASK
|
||||
}
|
||||
|
||||
# Mutations
|
||||
type Mutation {
|
||||
googleLogin(input: GoogleLoginInput!): LoginResult!
|
||||
|
|
@ -3131,6 +3167,7 @@ const schema = gql`
|
|||
arguments: JSON # additional arguments for the action
|
||||
): BulkActionResult!
|
||||
importFromIntegration(integrationId: ID!): ImportFromIntegrationResult!
|
||||
exportToIntegration(integrationId: ID!): ExportToIntegrationResult!
|
||||
setFavoriteArticle(id: ID!): SetFavoriteArticleResult!
|
||||
updateSubscription(
|
||||
input: UpdateSubscriptionInput!
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import { makeApolloServer } from './apollo'
|
|||
import { appDataSource } from './data_source'
|
||||
import { env } from './env'
|
||||
import { redisDataSource } from './redis_data_source'
|
||||
import { aiSummariesRouter } from './routers/ai_summary_router'
|
||||
import { articleRouter } from './routers/article_router'
|
||||
import { authRouter } from './routers/auth/auth_router'
|
||||
import { mobileAuthRouter } from './routers/auth/mobile/mobile_auth_router'
|
||||
|
|
@ -29,7 +30,6 @@ import { contentServiceRouter } from './routers/svc/content'
|
|||
import { emailsServiceRouter } from './routers/svc/emails'
|
||||
import { emailAttachmentRouter } from './routers/svc/email_attachment'
|
||||
import { followingServiceRouter } from './routers/svc/following'
|
||||
import { integrationsServiceRouter } from './routers/svc/integrations'
|
||||
import { linkServiceRouter } from './routers/svc/links'
|
||||
import { newsletterServiceRouter } from './routers/svc/newsletters'
|
||||
// import { remindersServiceRouter } from './routers/svc/reminders'
|
||||
|
|
@ -37,6 +37,7 @@ import { rssFeedRouter } from './routers/svc/rss_feed'
|
|||
import { uploadServiceRouter } from './routers/svc/upload'
|
||||
import { userServiceRouter } from './routers/svc/user'
|
||||
import { webhooksServiceRouter } from './routers/svc/webhooks'
|
||||
import { taskRouter } from './routers/task_router'
|
||||
import { textToSpeechRouter } from './routers/text_to_speech'
|
||||
import { userRouter } from './routers/user_router'
|
||||
import { sentryConfig } from './sentry'
|
||||
|
|
@ -48,7 +49,6 @@ import {
|
|||
} from './utils/auth'
|
||||
import { corsConfig } from './utils/corsConfig'
|
||||
import { buildLogger, buildLoggerTransport, logger } from './utils/logger'
|
||||
import { aiSummariesRouter } from './routers/ai_summary_router'
|
||||
|
||||
const PORT = process.env.PORT || 4000
|
||||
|
||||
|
|
@ -126,13 +126,13 @@ export const createApp = (): {
|
|||
app.use('/api/text-to-speech', textToSpeechRouter())
|
||||
app.use('/api/notification', notificationRouter())
|
||||
app.use('/api/integration', integrationRouter())
|
||||
app.use('/api/tasks', taskRouter())
|
||||
app.use('/svc/pubsub/content', contentServiceRouter())
|
||||
app.use('/svc/pubsub/links', linkServiceRouter())
|
||||
app.use('/svc/pubsub/newsletters', newsletterServiceRouter())
|
||||
app.use('/svc/pubsub/emails', emailsServiceRouter())
|
||||
app.use('/svc/pubsub/upload', uploadServiceRouter())
|
||||
app.use('/svc/pubsub/webhooks', webhooksServiceRouter())
|
||||
app.use('/svc/pubsub/integrations', integrationsServiceRouter())
|
||||
app.use('/svc/pubsub/rss-feed', rssFeedRouter())
|
||||
app.use('/svc/pubsub/user', userServiceRouter())
|
||||
// app.use('/svc/reminders', remindersServiceRouter())
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import {
|
|||
ArticleSavingRequestStatus,
|
||||
CreateLabelInput,
|
||||
} from '../generated/graphql'
|
||||
import { AISummarizeJobData, AI_SUMMARIZE_JOB_NAME } from '../jobs/ai-summarize'
|
||||
import { BulkActionData, BULK_ACTION_JOB_NAME } from '../jobs/bulk_action'
|
||||
import { CallWebhookJobData, CALL_WEBHOOK_JOB_NAME } from '../jobs/call_webhook'
|
||||
import { THUMBNAIL_JOB } from '../jobs/find_thumbnail'
|
||||
|
|
@ -23,6 +24,12 @@ import {
|
|||
ExportItemJobData,
|
||||
EXPORT_ITEM_JOB_NAME,
|
||||
} from '../jobs/integration/export_item'
|
||||
import {
|
||||
ProcessYouTubeTranscriptJobData,
|
||||
ProcessYouTubeVideoJobData,
|
||||
PROCESS_YOUTUBE_TRANSCRIPT_JOB_NAME,
|
||||
PROCESS_YOUTUBE_VIDEO_JOB_NAME,
|
||||
} from '../jobs/process-youtube-video'
|
||||
import {
|
||||
queueRSSRefreshFeedJob,
|
||||
REFRESH_ALL_FEEDS_JOB_NAME,
|
||||
|
|
@ -44,13 +51,6 @@ import { CreateTaskError } from './errors'
|
|||
import { stringToHash } from './helpers'
|
||||
import { logger } from './logger'
|
||||
import View = google.cloud.tasks.v2.Task.View
|
||||
import { AISummarizeJobData, AI_SUMMARIZE_JOB_NAME } from '../jobs/ai-summarize'
|
||||
import {
|
||||
PROCESS_YOUTUBE_TRANSCRIPT_JOB_NAME,
|
||||
PROCESS_YOUTUBE_VIDEO_JOB_NAME,
|
||||
ProcessYouTubeTranscriptJobData,
|
||||
ProcessYouTubeVideoJobData,
|
||||
} from '../jobs/process-youtube-video'
|
||||
|
||||
// Instantiates a client.
|
||||
const client = new CloudTasksClient()
|
||||
|
|
@ -594,7 +594,7 @@ export const enqueueImportFromIntegration = async (
|
|||
return createdTasks[0].name
|
||||
}
|
||||
|
||||
export const enqueueExportAllItems = async (
|
||||
export const enqueueExportToIntegration = async (
|
||||
integrationId: string,
|
||||
userId: string
|
||||
) => {
|
||||
|
|
|
|||
Loading…
Reference in a new issue