diff --git a/packages/api/src/entity/rule.ts b/packages/api/src/entity/rule.ts index 18538971c..9ef6d797e 100644 --- a/packages/api/src/entity/rule.ts +++ b/packages/api/src/entity/rule.ts @@ -21,7 +21,7 @@ export enum RuleActionType { export enum RuleEventType { PageCreated = 'PAGE_CREATED', PageUpdated = 'PAGE_UPDATED', - LabelCreated = 'PAGE_CREATED', + LabelCreated = 'LABEL_CREATED', HighlightCreated = 'HIGHLIGHT_CREATED', } diff --git a/packages/api/src/generated/graphql.ts b/packages/api/src/generated/graphql.ts index ae5bcdbd8..3e3119db6 100644 --- a/packages/api/src/generated/graphql.ts +++ b/packages/api/src/generated/graphql.ts @@ -2502,10 +2502,13 @@ export enum RuleActionType { Archive = 'ARCHIVE', Delete = 'DELETE', MarkAsRead = 'MARK_AS_READ', - SendNotification = 'SEND_NOTIFICATION' + SendNotification = 'SEND_NOTIFICATION', + Webhook = 'WEBHOOK' } export enum RuleEventType { + HighlightCreated = 'HIGHLIGHT_CREATED', + LabelCreated = 'LABEL_CREATED', PageCreated = 'PAGE_CREATED', PageUpdated = 'PAGE_UPDATED' } diff --git a/packages/api/src/generated/schema.graphql b/packages/api/src/generated/schema.graphql index 94695e350..1782328b8 100644 --- a/packages/api/src/generated/schema.graphql +++ b/packages/api/src/generated/schema.graphql @@ -1879,9 +1879,12 @@ enum RuleActionType { DELETE MARK_AS_READ SEND_NOTIFICATION + WEBHOOK } enum RuleEventType { + HIGHLIGHT_CREATED + LABEL_CREATED PAGE_CREATED PAGE_UPDATED } diff --git a/packages/api/src/jobs/trigger_rule.ts b/packages/api/src/jobs/trigger_rule.ts index 3a72dac11..132218ebb 100644 --- a/packages/api/src/jobs/trigger_rule.ts +++ b/packages/api/src/jobs/trigger_rule.ts @@ -1,4 +1,5 @@ import { LiqeQuery } from '@omnivore/liqe' +import axios, { Method } from 'axios' import { ReadingProgressDataSource } from '../datasources/reading_progress_data_source' import { LibraryItem, LibraryItemState } from '../entity/library_item' import { Rule, RuleAction, RuleActionType, RuleEventType } from '../entity/rule' @@ -28,6 +29,7 @@ interface RuleActionObj { userId: string action: RuleAction data: ItemEvent | LibraryItem + ruleEventType: RuleEventType } type RuleActionFunc = (obj: RuleActionObj) => Promise @@ -86,6 +88,29 @@ const sendNotification = async (obj: RuleActionObj) => { return sendPushNotifications(obj.userId, message, 'rule', data) } +const sendToWebhook = async (obj: RuleActionObj) => { + const [url, method, contentType] = obj.action.params + const [type, action] = obj.ruleEventType.split('_') + + const body = { + action, + userId: obj.userId, + [type]: obj.data, + } + + logger.info('triggering webhook', { url, method }) + + return axios.request({ + url, + method: method as Method, + headers: { + 'Content-Type': contentType, + }, + data: body, + timeout: 5000, // 5s + }) +} + const getRuleAction = ( actionType: RuleActionType ): RuleActionFunc | undefined => { @@ -100,6 +125,8 @@ const getRuleAction = ( return markPageAsRead case RuleActionType.SendNotification: return sendNotification + case RuleActionType.Webhook: + return sendToWebhook default: logger.error('Unknown rule action type', actionType) return undefined @@ -110,7 +137,8 @@ const triggerActions = async ( libraryItemId: string, userId: string, rules: Rule[], - data: ItemEvent + data: ItemEvent, + ruleEventType: RuleEventType ) => { const actionPromises: Promise[] = [] @@ -165,6 +193,7 @@ const triggerActions = async ( userId, action, data: results[0], + ruleEventType, } actionPromises.push(actionFunc(actionObj)) @@ -188,7 +217,7 @@ export const triggerRule = async (jobData: TriggerRuleJobData) => { return false } - await triggerActions(libraryItemId, userId, rules, data) + await triggerActions(libraryItemId, userId, rules, data, ruleEventType) return true } diff --git a/packages/api/src/pubsub.ts b/packages/api/src/pubsub.ts index d8c6bba4d..d340f520b 100644 --- a/packages/api/src/pubsub.ts +++ b/packages/api/src/pubsub.ts @@ -12,6 +12,8 @@ import { import { buildLogger } from './utils/logger' import { isYouTubeVideoURL } from './utils/youtube' +export type BaseEntityEvent = { id: string; userId: string } + const logger = buildLogger('pubsub') const client = new PubSub() @@ -46,7 +48,7 @@ export const createPubSubClient = (): PubsubClient => { Buffer.from(JSON.stringify({ userId, email, name, username })) ) }, - entityCreated: async >( + entityCreated: async ( type: EntityType, data: T, userId: string, @@ -54,10 +56,10 @@ export const createPubSubClient = (): PubsubClient => { ): Promise => { // queue trigger rule job await enqueueTriggerRuleJob({ - userId, ruleEventType: `${type.toUpperCase()}_CREATED` as RuleEventType, - libraryItemId, data, + userId, + libraryItemId, }) // queue export item job await enqueueExportItem({ @@ -92,21 +94,20 @@ export const createPubSubClient = (): PubsubClient => { } } }, - entityUpdated: async >( + entityUpdated: async ( type: EntityType, data: T, userId: string, libraryItemId: string ): Promise => { // queue trigger rule job - if (type === EntityType.PAGE) { - await enqueueTriggerRuleJob({ - userId, - ruleEventType: RuleEventType.PageUpdated, - libraryItemId, - data, - }) - } + await enqueueTriggerRuleJob({ + userId, + ruleEventType: RuleEventType.PageUpdated, + libraryItemId, + data, + }) + // queue export item job await enqueueExportItem({ userId, @@ -145,7 +146,7 @@ export const createPubSubClient = (): PubsubClient => { } export enum EntityType { - PAGE = 'page', + ITEM = 'page', HIGHLIGHT = 'highlight', LABEL = 'label', RSS_FEED = 'feed', @@ -158,13 +159,13 @@ export interface PubsubClient { name: string, username: string ) => Promise - entityCreated: >( + entityCreated: ( type: EntityType, data: T, userId: string, libraryItemId: string ) => Promise - entityUpdated: >( + entityUpdated: ( type: EntityType, data: T, userId: string, diff --git a/packages/api/src/schema.ts b/packages/api/src/schema.ts index e106514e4..d2279407e 100755 --- a/packages/api/src/schema.ts +++ b/packages/api/src/schema.ts @@ -2167,6 +2167,7 @@ const schema = gql` DELETE MARK_AS_READ SEND_NOTIFICATION + WEBHOOK } type RulesError { @@ -2181,6 +2182,8 @@ const schema = gql` enum RuleEventType { PAGE_CREATED PAGE_UPDATED + LABEL_CREATED + HIGHLIGHT_CREATED } input SetRuleInput { diff --git a/packages/api/src/services/highlights.ts b/packages/api/src/services/highlights.ts index 91249626b..8cc10ad0b 100644 --- a/packages/api/src/services/highlights.ts +++ b/packages/api/src/services/highlights.ts @@ -59,7 +59,7 @@ export const createHighlight = async ( await pubsub.entityCreated( EntityType.HIGHLIGHT, - { id: libraryItemId, highlights: [newHighlight] }, + { id: libraryItemId, highlights: [newHighlight], userId }, userId, libraryItemId ) @@ -107,7 +107,7 @@ export const mergeHighlights = async ( await pubsub.entityCreated( EntityType.HIGHLIGHT, - { id: libraryItemId, highlights: [newHighlight] }, + { id: libraryItemId, highlights: [newHighlight], userId }, userId, libraryItemId ) @@ -142,7 +142,7 @@ export const updateHighlight = async ( const libraryItemId = updatedHighlight.libraryItem.id await pubsub.entityUpdated( EntityType.HIGHLIGHT, - { id: libraryItemId, highlights: [highlight] }, + { id: libraryItemId, highlights: [highlight], userId }, userId, libraryItemId ) diff --git a/packages/api/src/services/labels.ts b/packages/api/src/services/labels.ts index ca0e02d64..1f05d14c1 100644 --- a/packages/api/src/services/labels.ts +++ b/packages/api/src/services/labels.ts @@ -146,7 +146,7 @@ export const saveLabelsInLibraryItem = async ( // create pubsub event await pubsub.entityCreated( EntityType.LABEL, - { id: libraryItemId, labels }, + { id: libraryItemId, labels, userId }, userId, libraryItemId ) @@ -217,7 +217,7 @@ export const saveLabelsInHighlight = async ( // create pubsub event await pubsub.entityCreated( EntityType.LABEL, - { id: libraryItemId, highlights: [{ id: highlightId, labels }] }, + { id: libraryItemId, highlights: [{ id: highlightId, labels }], userId }, userId, libraryItemId ) diff --git a/packages/api/src/services/library_item.ts b/packages/api/src/services/library_item.ts index 83473104c..4ac000900 100644 --- a/packages/api/src/services/library_item.ts +++ b/packages/api/src/services/library_item.ts @@ -15,7 +15,7 @@ import { Highlight } from '../entity/highlight' import { Label } from '../entity/label' import { LibraryItem, LibraryItemState } from '../entity/library_item' import { BulkActionType, InputMaybe, SortParams } from '../generated/graphql' -import { createPubSubClient, EntityType } from '../pubsub' +import { BaseEntityEvent, createPubSubClient, EntityType } from '../pubsub' import { redisDataSource } from '../redis_data_source' import { authTrx, @@ -37,10 +37,13 @@ type IgnoredFields = | 'links' | 'textContentHash' export type ItemEvent = CreateItemEvent | UpdateItemEvent -export type CreateItemEvent = Omit, IgnoredFields> -export type UpdateItemEvent = Omit< - QueryDeepPartialEntity, - IgnoredFields +export type CreateItemEvent = Merge< + Omit, IgnoredFields>, + BaseEntityEvent +> +export type UpdateItemEvent = Merge< + Omit, IgnoredFields>, + BaseEntityEvent > export class RequiresSearchQueryError extends Error { @@ -841,7 +844,7 @@ export const softDeleteLibraryItem = async ( userId ) - await pubsub.entityDeleted(EntityType.PAGE, id, userId) + await pubsub.entityDeleted(EntityType.ITEM, id, userId) return deletedLibraryItem } @@ -883,13 +886,8 @@ export const updateLibraryItem = async ( if (libraryItem.state === LibraryItemState.Succeeded) { // send create event if the item was created await pubsub.entityCreated( - EntityType.PAGE, - { - ...updatedLibraryItem, - originalContent: undefined, - readableContent: undefined, - feedContent: undefined, - }, + EntityType.ITEM, + { ...updatedLibraryItem, userId }, userId, id ) @@ -898,13 +896,8 @@ export const updateLibraryItem = async ( } await pubsub.entityUpdated( - EntityType.PAGE, - { - ...libraryItem, - originalContent: undefined, - readableContent: undefined, - feedContent: undefined, - }, + EntityType.ITEM, + { ...libraryItem, id, userId }, userId, id ) @@ -966,8 +959,8 @@ export const updateLibraryItemReadingProgress = async ( const updatedItem = result[0][0] await pubsub.entityUpdated( - EntityType.PAGE, - updatedItem, + EntityType.ITEM, + { ...updatedItem, id, userId }, userId, id ) @@ -1067,13 +1060,8 @@ export const createOrUpdateLibraryItem = async ( } await pubsub.entityCreated( - EntityType.PAGE, - { - ...newLibraryItem, - originalContent: undefined, - readableContent: undefined, - feedContent: undefined, - }, + EntityType.ITEM, + { ...newLibraryItem, userId }, userId, newLibraryItem.id )