From 9af670a5bc2b7f2318331e5311c733da5ede2c94 Mon Sep 17 00:00:00 2001 From: Hongbo Wu Date: Mon, 21 Nov 2022 17:03:46 +0800 Subject: [PATCH] Call api to get rules and device tokens in rules handler --- docker-compose.yml | 5 -- packages/rule-handler/package.json | 7 +- packages/rule-handler/src/index.ts | 34 +++++--- packages/rule-handler/src/rule.ts | 85 ++++++++++++++++--- packages/rule-handler/src/sendNotification.ts | 46 ++++++++++ 5 files changed, 144 insertions(+), 33 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index a1fefab93..feada6e8b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -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 diff --git a/packages/rule-handler/package.json b/packages/rule-handler/package.json index 487b0e8a7..6c17ca61b 100644 --- a/packages/rule-handler/package.json +++ b/packages/rule-handler/package.json @@ -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" } } diff --git a/packages/rule-handler/src/index.ts b/packages/rule-handler/src/index.ts index 1f45f7134..0087bada9 100644 --- a/packages/rule-handler/src/index.ts +++ b/packages/rule-handler/src/index.ts @@ -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 => { + 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) { diff --git a/packages/rule-handler/src/rule.ts b/packages/rule-handler/src/rule.ts index f7c60acb6..3a1522e0a 100644 --- a/packages/rule-handler/src/rule.ts +++ b/packages/rule-handler/src/rule.ts @@ -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 => { + 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, diff --git a/packages/rule-handler/src/sendNotification.ts b/packages/rule-handler/src/sendNotification.ts index cd3676a00..775da6d71 100644 --- a/packages/rule-handler/src/sendNotification.ts +++ b/packages/rule-handler/src/sendNotification.ts @@ -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 => { + 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[]