Add content filter

This commit is contained in:
Hongbo Wu 2022-11-23 14:24:47 +08:00
parent 64ed6fee68
commit 60d23ca387
5 changed files with 81 additions and 28 deletions

View file

@ -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) {

View file

@ -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
}

View file

@ -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)
}
}

View file

@ -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
}

View file

@ -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()
)
}
}