From aaa74c8ba3d2b055da702535d92ed98051fc370f Mon Sep 17 00:00:00 2001 From: Hongbo Wu Date: Wed, 23 Nov 2022 11:55:01 +0800 Subject: [PATCH 1/4] Add mark page as read and archived action in rule engine --- packages/rule-handler/src/page.ts | 82 +++++++++++++++++++++++++++++++ packages/rule-handler/src/rule.ts | 14 +++++- 2 files changed, 95 insertions(+), 1 deletion(-) create mode 100644 packages/rule-handler/src/page.ts diff --git a/packages/rule-handler/src/page.ts b/packages/rule-handler/src/page.ts new file mode 100644 index 000000000..0650b775a --- /dev/null +++ b/packages/rule-handler/src/page.ts @@ -0,0 +1,82 @@ +import { getAuthToken } from './index' +import axios from 'axios' + +export const archivePage = async ( + pageId: string, + userId: string, + apiEndpoint: string, + jwtSecret: string +) => { + const auth = await getAuthToken(userId, jwtSecret) + + const data = JSON.stringify({ + query: `mutation ArchivePage($input: ArchivePageInput!) { + archivePage(input: $input) { + ... on ArchivePageSuccess { + page { + id + } + } + ... on ArchivePageError { + errorCodes + } + } + }`, + variables: { + input: { + pageId, + }, + }, + }) + + try { + await axios.post(`${apiEndpoint}/graphql`, data, { + headers: { + Cookie: `auth=${auth};`, + 'Content-Type': 'application/json', + }, + }) + } catch (e) { + console.error(e) + } +} + +export const markPageAsRead = async ( + pageId: string, + userId: string, + apiEndpoint: string, + jwtSecret: string +) => { + const auth = await getAuthToken(userId, jwtSecret) + + const data = JSON.stringify({ + query: `mutation MarkPageAsRead($input: MarkPageAsReadInput!) { + markPageAsRead(input: $input) { + ... on MarkPageAsReadSuccess { + page { + id + } + } + ... on MarkPageAsReadError { + errorCodes + } + } + }`, + variables: { + input: { + pageId, + }, + }, + }) + + try { + await axios.post(`${apiEndpoint}/graphql`, data, { + headers: { + Cookie: `auth=${auth};`, + 'Content-Type': 'application/json', + }, + }) + } catch (e) { + console.error(e) + } +} diff --git a/packages/rule-handler/src/rule.ts b/packages/rule-handler/src/rule.ts index 7dc01c70b..50d2838e6 100644 --- a/packages/rule-handler/src/rule.ts +++ b/packages/rule-handler/src/rule.ts @@ -3,6 +3,7 @@ import { getAuthToken, PubSubData } from './index' import axios from 'axios' import { parse, SearchParserKeyWordOffset } from 'search-query-parser' import { addLabels } from './label' +import { archivePage, markPageAsRead } from './page' export enum RuleActionType { AddLabel = 'ADD_LABEL', @@ -148,8 +149,19 @@ export const triggerActions = async ( ) break case RuleActionType.Archive: + if (!data.id) { + console.log('invalid data for archive action') + continue + } + await archivePage(userId, apiEndpoint, jwtSecret, data.id) + break case RuleActionType.MarkAsRead: - continue + if (!data.id) { + console.log('invalid data for mark as read action') + continue + } + await markPageAsRead(userId, apiEndpoint, jwtSecret, data.id) + break case RuleActionType.SendNotification: for (const message of action.params) { await sendNotification(userId, apiEndpoint, jwtSecret, message) From 3c67900916d3832d2ddfcf6cbb28a3efa64dc943 Mon Sep 17 00:00:00 2001 From: Hongbo Wu Date: Wed, 23 Nov 2022 13:25:03 +0800 Subject: [PATCH 2/4] Update api client --- packages/rule-handler/src/label.ts | 6 +--- packages/rule-handler/src/notification.ts | 6 +--- packages/rule-handler/src/page.ts | 42 ++++++++++------------- packages/rule-handler/src/rule.ts | 16 ++++----- 4 files changed, 27 insertions(+), 43 deletions(-) diff --git a/packages/rule-handler/src/label.ts b/packages/rule-handler/src/label.ts index 948c193be..18f1549ec 100644 --- a/packages/rule-handler/src/label.ts +++ b/packages/rule-handler/src/label.ts @@ -1,15 +1,11 @@ import axios from 'axios' -import { getAuthToken } from './index' export const addLabels = async ( - userId: string, apiEndpoint: string, - jwtSecret: string, + auth: string, pageId: string, labelIds: string[] ) => { - const auth = await getAuthToken(userId, jwtSecret) - const data = JSON.stringify({ query: `mutation SetLabels($input: SetLabelsInput!) { setLabels(input: $input) { diff --git a/packages/rule-handler/src/notification.ts b/packages/rule-handler/src/notification.ts index 6a12a94a2..ed715209d 100644 --- a/packages/rule-handler/src/notification.ts +++ b/packages/rule-handler/src/notification.ts @@ -1,5 +1,4 @@ import axios from 'axios' -import { getAuthToken } from './index' interface NotificationData { body: string @@ -10,15 +9,12 @@ interface NotificationData { } export const sendNotification = async ( - userId: string, apiEndpoint: string, - jwtSecret: string, + auth: string, message: string, title?: string, image?: string ) => { - const auth = await getAuthToken(userId, jwtSecret) - const data: NotificationData = { body: message, title: title || message, diff --git a/packages/rule-handler/src/page.ts b/packages/rule-handler/src/page.ts index 0650b775a..96fa06984 100644 --- a/packages/rule-handler/src/page.ts +++ b/packages/rule-handler/src/page.ts @@ -1,30 +1,27 @@ -import { getAuthToken } from './index' import axios from 'axios' export const archivePage = async ( pageId: string, - userId: string, apiEndpoint: string, - jwtSecret: string + auth: string ) => { - const auth = await getAuthToken(userId, jwtSecret) - const data = JSON.stringify({ - query: `mutation ArchivePage($input: ArchivePageInput!) { - archivePage(input: $input) { - ... on ArchivePageSuccess { - page { - id - } + query: `mutation SetLinkArchived($input: ArchiveLinkInput!) { + setLinkArchived(input: $input) { + ... on ArchiveLinkSuccess { + linkId + message } - ... on ArchivePageError { + ... on ArchiveLinkError { + message errorCodes } } }`, variables: { input: { - pageId, + linkId: pageId, + archived: true, }, }, }) @@ -43,28 +40,27 @@ export const archivePage = async ( export const markPageAsRead = async ( pageId: string, - userId: string, apiEndpoint: string, - jwtSecret: string + auth: string ) => { - const auth = await getAuthToken(userId, jwtSecret) - const data = JSON.stringify({ - query: `mutation MarkPageAsRead($input: MarkPageAsReadInput!) { - markPageAsRead(input: $input) { - ... on MarkPageAsReadSuccess { - page { + query: `mutation SaveArticleReadingProgress($input: SaveArticleReadingProgressInput!) { + saveArticleReadingProgress(input: $input) { + ... on SaveArticleReadingProgressSuccess { + updatedArticle { id } } - ... on MarkPageAsReadError { + ... on SaveArticleReadingProgressError { errorCodes } } }`, variables: { input: { - pageId, + id: pageId, + readingProgressPercent: 100, + readingProgressAnchorIndex: 0, }, }, }) diff --git a/packages/rule-handler/src/rule.ts b/packages/rule-handler/src/rule.ts index 50d2838e6..d040051fd 100644 --- a/packages/rule-handler/src/rule.ts +++ b/packages/rule-handler/src/rule.ts @@ -133,6 +133,8 @@ export const triggerActions = async ( continue } + const authToken = await getAuthToken(userId, jwtSecret) + for (const action of rule.actions) { switch (action.type) { case RuleActionType.AddLabel: @@ -140,31 +142,25 @@ export const triggerActions = async ( console.log('invalid data for add label action') continue } - await addLabels( - userId, - apiEndpoint, - jwtSecret, - data.id, - action.params - ) + await addLabels(apiEndpoint, authToken, data.id, action.params) break case RuleActionType.Archive: if (!data.id) { console.log('invalid data for archive action') continue } - await archivePage(userId, apiEndpoint, jwtSecret, data.id) + await archivePage(apiEndpoint, authToken, data.id) break case RuleActionType.MarkAsRead: if (!data.id) { console.log('invalid data for mark as read action') continue } - await markPageAsRead(userId, apiEndpoint, jwtSecret, data.id) + await markPageAsRead(apiEndpoint, authToken, data.id) break case RuleActionType.SendNotification: for (const message of action.params) { - await sendNotification(userId, apiEndpoint, jwtSecret, message) + await sendNotification(apiEndpoint, authToken, message) } break } From 64ed6fee684f55208359557c3bf287619f38ed9a Mon Sep 17 00:00:00 2001 From: Hongbo Wu Date: Wed, 23 Nov 2022 13:51:02 +0800 Subject: [PATCH 3/4] Fix api error --- packages/rule-handler/src/page.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/rule-handler/src/page.ts b/packages/rule-handler/src/page.ts index 96fa06984..27b99b230 100644 --- a/packages/rule-handler/src/page.ts +++ b/packages/rule-handler/src/page.ts @@ -1,9 +1,9 @@ import axios from 'axios' export const archivePage = async ( - pageId: string, apiEndpoint: string, - auth: string + auth: string, + pageId: string ) => { const data = JSON.stringify({ query: `mutation SetLinkArchived($input: ArchiveLinkInput!) { @@ -39,9 +39,9 @@ export const archivePage = async ( } export const markPageAsRead = async ( - pageId: string, apiEndpoint: string, - auth: string + auth: string, + pageId: string ) => { const data = JSON.stringify({ query: `mutation SaveArticleReadingProgress($input: SaveArticleReadingProgressInput!) { From 60d23ca3874f12e918895433d730e15780cda254 Mon Sep 17 00:00:00 2001 From: Hongbo Wu Date: Wed, 23 Nov 2022 14:24:47 +0800 Subject: [PATCH 4/4] Add content filter --- packages/rule-handler/src/index.ts | 17 +++++-- packages/rule-handler/src/rule.ts | 50 +++++++++---------- .../src/search_filter/content_filter.ts | 15 ++++++ .../rule-handler/src/search_filter/index.ts | 9 ++++ .../src/search_filter/subscription_filter.ts | 18 +++++++ 5 files changed, 81 insertions(+), 28 deletions(-) create mode 100644 packages/rule-handler/src/search_filter/content_filter.ts create mode 100644 packages/rule-handler/src/search_filter/index.ts create mode 100644 packages/rule-handler/src/search_filter/subscription_filter.ts diff --git a/packages/rule-handler/src/index.ts b/packages/rule-handler/src/index.ts index ab2a3d92b..f2274cccf 100644 --- a/packages/rule-handler/src/index.ts +++ b/packages/rule-handler/src/index.ts @@ -22,8 +22,9 @@ export interface PubSubData { id: string userId: string type: EntityType - subscription?: string - image?: string + subscription: string + image: string + content: string } enum EntityType { @@ -114,7 +115,17 @@ export const ruleHandler = Sentry.GCPFunction.wrapHttpFunction( return } - await triggerActions(userId, rules, data, apiEndpoint, jwtSecret) + const triggeredActions = await triggerActions( + userId, + rules, + data, + apiEndpoint, + jwtSecret + ) + if (triggeredActions.length === 0) { + res.status(200).send('No Actions') + return + } res.status(200).send('OK') } catch (error) { diff --git a/packages/rule-handler/src/rule.ts b/packages/rule-handler/src/rule.ts index d040051fd..df89c9039 100644 --- a/packages/rule-handler/src/rule.ts +++ b/packages/rule-handler/src/rule.ts @@ -4,6 +4,9 @@ import axios from 'axios' import { parse, SearchParserKeyWordOffset } from 'search-query-parser' import { addLabels } from './label' import { archivePage, markPageAsRead } from './page' +import { SearchFilter } from './search_filter' +import { SubscriptionFilter } from './search_filter/subscription_filter' +import { ContentFilter } from './search_filter/content_filter' export enum RuleActionType { AddLabel = 'ADD_LABEL', @@ -29,20 +32,16 @@ export interface Rule { updatedAt: Date } -interface SearchFilter { - subscriptionFilter?: string -} - -const parseSearchFilter = (filter: string): SearchFilter => { +const parseSearchFilter = (filter: string): SearchFilter[] => { const searchFilter = filter ? filter.replace(/\W\s":/g, '') : undefined - const result: SearchFilter = {} + const result: SearchFilter[] = [] if (!searchFilter || searchFilter === '*') { return result } const parsed = parse(searchFilter, { - keywords: ['subscription'], + keywords: ['subscription', 'content'], tokenize: true, }) if (parsed.offsets) { @@ -53,7 +52,11 @@ const parseSearchFilter = (filter: string): SearchFilter => { for (const keyword of keywords) { switch (keyword.keyword) { case 'subscription': - result.subscriptionFilter = keyword.value + keyword.value && result.push(new SubscriptionFilter(keyword.value)) + break + case 'content': + keyword.value && result.push(new ContentFilter(keyword.value)) + break } } } @@ -62,24 +65,14 @@ const parseSearchFilter = (filter: string): SearchFilter => { } const isValidData = (filter: string, data: PubSubData): boolean => { - const searchFilter = parseSearchFilter(filter) + const searchFilters = parseSearchFilter(filter) - if (searchFilter.subscriptionFilter) { - return isValidSubscription(searchFilter.subscriptionFilter, data) + if (searchFilters.length === 0) { + console.debug('no search filters found') + return true } - return true -} - -const isValidSubscription = ( - subscriptionFilter: string, - data: PubSubData -): boolean => { - if (!data.subscription) { - return false - } - - return subscriptionFilter === '*' || data.subscription === subscriptionFilter + return searchFilters.every((searchFilter) => searchFilter.isValid(data)) } export const getEnabledRules = async ( @@ -128,13 +121,14 @@ export const triggerActions = async ( apiEndpoint: string, jwtSecret: string ) => { + const triggeredActions: RuleAction[] = [] + const authToken = await getAuthToken(userId, jwtSecret) + for (const rule of rules) { if (!isValidData(rule.filter, data)) { continue } - const authToken = await getAuthToken(userId, jwtSecret) - for (const action of rule.actions) { switch (action.type) { case RuleActionType.AddLabel: @@ -143,6 +137,7 @@ export const triggerActions = async ( continue } await addLabels(apiEndpoint, authToken, data.id, action.params) + triggeredActions.push(action) break case RuleActionType.Archive: if (!data.id) { @@ -150,6 +145,7 @@ export const triggerActions = async ( continue } await archivePage(apiEndpoint, authToken, data.id) + triggeredActions.push(action) break case RuleActionType.MarkAsRead: if (!data.id) { @@ -157,13 +153,17 @@ export const triggerActions = async ( continue } await markPageAsRead(apiEndpoint, authToken, data.id) + triggeredActions.push(action) break case RuleActionType.SendNotification: for (const message of action.params) { await sendNotification(apiEndpoint, authToken, message) } + triggeredActions.push(action) break } } } + + return triggeredActions } diff --git a/packages/rule-handler/src/search_filter/content_filter.ts b/packages/rule-handler/src/search_filter/content_filter.ts new file mode 100644 index 000000000..822c27b0d --- /dev/null +++ b/packages/rule-handler/src/search_filter/content_filter.ts @@ -0,0 +1,15 @@ +import { SearchFilter } from './index' +import { PubSubData } from '../index' + +export class ContentFilter extends SearchFilter { + public isValid(data: PubSubData): boolean { + console.debug('ContentFilter.isValid', this.filter, data.content) + + if (!data.content) { + return false + } + + // TODO: implement content filter with semantic search + return this.filter === '*' || data.content.includes(this.filter) + } +} diff --git a/packages/rule-handler/src/search_filter/index.ts b/packages/rule-handler/src/search_filter/index.ts new file mode 100644 index 000000000..37461efbc --- /dev/null +++ b/packages/rule-handler/src/search_filter/index.ts @@ -0,0 +1,9 @@ +import { PubSubData } from '../index' + +export abstract class SearchFilter { + constructor(protected filter: string) { + this.filter = filter + } + + public abstract isValid(data: PubSubData): boolean +} diff --git a/packages/rule-handler/src/search_filter/subscription_filter.ts b/packages/rule-handler/src/search_filter/subscription_filter.ts new file mode 100644 index 000000000..43893c318 --- /dev/null +++ b/packages/rule-handler/src/search_filter/subscription_filter.ts @@ -0,0 +1,18 @@ +import { SearchFilter } from './index' +import { PubSubData } from '../index' + +export class SubscriptionFilter extends SearchFilter { + public isValid(data: PubSubData): boolean { + console.debug('SubscriptionFilter.isValid', this.filter, data.subscription) + + if (!data.subscription) { + return false + } + + // compare subscription name case insensitive + return ( + this.filter === '*' || + data.subscription.toLowerCase() === this.filter.toLowerCase() + ) + } +}