Files
omnivore/packages/api/src/jobs/trigger_rule.ts
2024-03-08 17:15:59 +08:00

155 lines
3.8 KiB
TypeScript

import { ReadingProgressDataSource } from '../datasources/reading_progress_data_source'
import { LibraryItem, LibraryItemState } from '../entity/library_item'
import { Rule, RuleAction, RuleActionType, RuleEventType } from '../entity/rule'
import { addLabelsToLibraryItem } from '../services/labels'
import {
SearchArgs,
searchLibraryItems,
softDeleteLibraryItem,
updateLibraryItem,
} from '../services/library_item'
import { findEnabledRules } from '../services/rules'
import { sendPushNotifications } from '../services/user'
import { logger } from '../utils/logger'
export interface TriggerRuleJobData {
libraryItemId: string
userId: string
ruleEventType: RuleEventType
}
interface RuleActionObj {
userId: string
action: RuleAction
libraryItem: LibraryItem
}
type RuleActionFunc = (obj: RuleActionObj) => Promise<unknown>
export const TRIGGER_RULE_JOB_NAME = 'trigger-rule'
const readingProgressDataSource = new ReadingProgressDataSource()
const addLabels = async (obj: RuleActionObj) => {
const labelIds = obj.action.params
return addLabelsToLibraryItem(
labelIds,
obj.libraryItem.id,
obj.userId,
'system'
)
}
const deleteLibraryItem = async (obj: RuleActionObj) => {
return softDeleteLibraryItem(obj.libraryItem.id, obj.userId)
}
const archivePage = async (obj: RuleActionObj) => {
return updateLibraryItem(
obj.libraryItem.id,
{ archivedAt: new Date(), state: LibraryItemState.Archived },
obj.userId,
undefined,
true
)
}
const markPageAsRead = async (obj: RuleActionObj) => {
return readingProgressDataSource.updateReadingProgress(
obj.userId,
obj.libraryItem.id,
{
readingProgressPercent: 100,
readingProgressTopPercent: 100,
readingProgressAnchorIndex: undefined,
}
)
}
const sendNotification = async (obj: RuleActionObj) => {
const item = obj.libraryItem
const message = {
title: item.author || item.siteName || 'Omnivore',
body: item.title,
image: item.thumbnail,
}
const data = {
libraryItemId: item.id,
}
return sendPushNotifications(obj.userId, message, 'rule')
}
const getRuleAction = (actionType: RuleActionType): RuleActionFunc => {
switch (actionType) {
case RuleActionType.AddLabel:
return addLabels
case RuleActionType.Archive:
return archivePage
case RuleActionType.Delete:
return deleteLibraryItem
case RuleActionType.MarkAsRead:
return markPageAsRead
case RuleActionType.SendNotification:
return sendNotification
}
}
const triggerActions = async (
userId: string,
rules: Rule[],
data: TriggerRuleJobData
) => {
const actionPromises: Promise<unknown>[] = []
for (const rule of rules) {
const itemId = data.libraryItemId
const searchArgs: SearchArgs = {
includeContent: false,
includeDeleted: false,
includePending: false,
size: 1,
query: `(${rule.filter}) AND includes:${itemId}`,
}
const libraryItems = await searchLibraryItems(searchArgs, userId)
if (libraryItems.count === 0) {
logger.info(`No pages found for rule ${rule.id}`)
continue
}
const libraryItem = libraryItems.libraryItems[0]
for (const action of rule.actions) {
const actionFunc = getRuleAction(action.type)
const actionObj: RuleActionObj = {
userId,
action,
libraryItem,
}
actionPromises.push(actionFunc(actionObj))
}
}
try {
await Promise.all(actionPromises)
} catch (error) {
logger.error('Error triggering rule actions', error)
}
}
export const triggerRule = async (data: TriggerRuleJobData) => {
const { userId, ruleEventType } = data
// get rules by calling api
const rules = await findEnabledRules(userId, ruleEventType)
if (rules.length === 0) {
console.log('No rules found')
return false
}
await triggerActions(userId, rules, data)
return true
}