Call api to get rules and device tokens in rules handler

This commit is contained in:
Hongbo Wu
2022-11-21 17:03:46 +08:00
parent f358094bbf
commit 9af670a5bc
5 changed files with 144 additions and 33 deletions

View File

@ -159,11 +159,6 @@ services:
- "9091:8080"
environment:
- PUBSUB_VERIFICATION_TOKEN=some_token
- PG_HOST=postgres
- PG_PORT=5432
- PG_USER=app_user
- PG_PASSWORD=app_pass
- PG_DB=omnivore
depends_on:
migrate:
condition: service_completed_successfully

View File

@ -21,11 +21,10 @@
},
"dependencies": {
"@google-cloud/functions-framework": "3.1.2",
"@sentry/serverless": "^6.16.1",
"axios": "^0.27.2",
"dotenv": "^16.0.1",
"firebase-admin": "^10.0.2",
"@sentry/serverless": "^6.16.1",
"pg": "^8.3.3",
"typeorm": "^0.3.4",
"typeorm-naming-strategies": "^4.1.0"
"jsonwebtoken": "^8.5.1"
}
}

View File

@ -1,9 +1,11 @@
import * as Sentry from '@sentry/serverless'
import express, { Request, Response } from 'express'
import * as dotenv from 'dotenv'
import { closeDBConnection, createDBConnection, getRepository } from './db'
import { Rules } from './entity/rules'
import { triggerActions } from './rule' // see https://github.com/motdotla/dotenv#how-do-i-use-dotenv-with-import
import { getEnabledRules, triggerActions } from './rule'
import { promisify } from 'util'
import * as jwt from 'jsonwebtoken'
const signToken = promisify(jwt.sign)
dotenv.config()
@ -58,8 +60,22 @@ const readPushSubscription = (
return { message: message, expired: expired(body) }
}
export const getAuthToken = async (
userId: string,
jwtSecret: string
): Promise<string> => {
const auth = await signToken({ uid: userId }, jwtSecret)
return auth as string
}
export const ruleHandler = Sentry.GCPFunction.wrapHttpFunction(
async (req: Request, res: Response) => {
const apiEndpoint = process.env.API_ENDPOINT
const jwtSecret = process.env.JWT_SECRET
if (!apiEndpoint || !jwtSecret) {
throw new Error('REST_BACKEND_ENDPOINT or JWT_SECRET not set')
}
const { message: msgStr, expired } = readPushSubscription(req)
if (!msgStr) {
@ -88,16 +104,10 @@ export const ruleHandler = Sentry.GCPFunction.wrapHttpFunction(
return
}
await createDBConnection()
// get rules by calling api
const rules = await getEnabledRules(userId, apiEndpoint, jwtSecret)
const rules = await getRepository(Rules).findBy({
user: { id: userId },
enabled: true,
})
await triggerActions(userId, rules, data)
await closeDBConnection()
await triggerActions(userId, rules, data, apiEndpoint, jwtSecret)
res.status(200).send('OK')
} catch (error) {

View File

@ -1,23 +1,80 @@
import { Rules } from './entity/rules'
import {
getBatchMessages,
getUserDeviceTokens,
sendBatchPushNotifications,
} from './sendNotification'
import { getRepository } from './db'
import { UserDeviceToken } from './entity/user_device_tokens'
import { PubSubData } from './index'
import { getAuthToken, PubSubData } from './index'
import axios from 'axios'
enum RuleActionType {
export enum RuleActionType {
AddLabel = 'ADD_LABEL',
Archive = 'ARCHIVE',
MarkAsRead = 'MARK_AS_READ',
SendNotification = 'SEND_NOTIFICATION',
}
export interface RuleAction {
type: RuleActionType
params: string[]
}
export interface Rule {
id: string
userId: string
name: string
filter: string
actions: RuleAction[]
description?: string
enabled: boolean
createdAt: Date
updatedAt: Date
}
export const getEnabledRules = async (
userId: string,
apiEndpoint: string,
jwtSecret: string
): Promise<Rule[]> => {
const auth = await getAuthToken(userId, jwtSecret)
const data = JSON.stringify({
query: `query {
rules(enabled: true) {
... on RulesError {
errorCodes
}
... on RulesSuccess {
rules {
id
name
filter
actions {
type
params
}
}
}
}
}`,
})
const response = await axios.post(`${apiEndpoint}/graphql`, data, {
headers: {
Cookie: `auth=${auth};`,
'Content-Type': 'application/json',
},
})
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
return response.data.data.rules.rules as Rule[]
}
export const triggerActions = async (
userId: string,
rules: Rules[],
data: PubSubData
rules: Rule[],
data: PubSubData,
apiEndpoint: string,
jwtSecret: string
) => {
for (const rule of rules) {
// TODO: filter out rules that don't match the trigger
@ -37,16 +94,20 @@ export const triggerActions = async (
console.log('No notification messages provided')
continue
}
await sendNotification(userId, action.params)
await sendNotification(userId, action.params, apiEndpoint, jwtSecret)
}
}
}
}
export const sendNotification = async (userId: string, messages: string[]) => {
const tokens = await getRepository(UserDeviceToken).findBy({
user: { id: userId },
})
export const sendNotification = async (
userId: string,
messages: string[],
apiEndpoint: string,
jwtSecret: string
) => {
// get device tokens by calling api
const tokens = await getUserDeviceTokens(userId, apiEndpoint, jwtSecret)
const batchMessages = getBatchMessages(
messages,

View File

@ -5,12 +5,58 @@ import {
Message,
MulticastMessage,
} from 'firebase-admin/messaging'
import axios from 'axios'
import { getAuthToken } from './index'
export interface UserDeviceToken {
id: string
token: string
userId: string
createdAt: Date
}
// getting credentials from App Engine
initializeApp({
credential: applicationDefault(),
})
export const getUserDeviceTokens = async (
userId: string,
apiEndpoint: string,
jwtSecret: string
): Promise<UserDeviceToken[]> => {
const auth = await getAuthToken(userId, jwtSecret)
const data = JSON.stringify({
query: `query {
userDeviceTokens(userId: "${userId}") {
... on UserDeviceTokensError {
errorCodes
}
... on UserDeviceTokensSuccess {
userDeviceTokens {
id
token
userId
createdAt
}
}
}
}`,
})
const response = await axios.post(`${apiEndpoint}/graphql`, data, {
headers: {
Cookie: `auth=${auth};`,
'Content-Type': 'application/json',
},
})
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
return response.data.data.userDeviceTokens
.userDeviceTokens as UserDeviceToken[]
}
export const getBatchMessages = (
messages: string[],
tokens: string[]