From 34f2b84fe2619d4c59397f34b5dd8104ed48919b Mon Sep 17 00:00:00 2001
From: Cesar <26280794+csrapr@users.noreply.github.com>
Date: Wed, 20 Jan 2021 12:22:02 +0000
Subject: [PATCH] Feat: add notification center row in notification settings
table
---
lib/machine-loader.js | 7 +-
lib/notifier/codes.js | 1 +
lib/notifier/index.js | 52 +++++++++---
lib/notifier/queries.js | 81 ++++++++++---------
lib/routes.js | 4 +-
...607009558538-create-notifications-table.js | 1 +
.../NotificationCenter/NotificationRow.js | 31 +++----
.../src/pages/Notifications/sections/Setup.js | 1 +
8 files changed, 109 insertions(+), 69 deletions(-)
diff --git a/lib/machine-loader.js b/lib/machine-loader.js
index 0ba4a610..3452440e 100644
--- a/lib/machine-loader.js
+++ b/lib/machine-loader.js
@@ -3,7 +3,8 @@ const axios = require('axios')
const db = require('./db')
const pairing = require('./pairing')
-const notifier = require('./notifier')
+const checkPings = require('./notifier').checkPings
+const checkStuckScreen = require('./notifier').checkStuckScreen
const dbm = require('./postgresql_interface')
const configManager = require('./new-config-manager')
const settingsLoader = require('./new-settings-loader')
@@ -41,7 +42,7 @@ function getMachineNames (config) {
return Promise.all([getMachines(), getConfig(config)])
.then(([machines, config]) => Promise.all(
- [machines, notifier.checkPings(machines), dbm.machineEvents(), config]
+ [machines, checkPings(machines), dbm.machineEvents(), config]
))
.then(([machines, pings, events, config]) => {
const getStatus = (ping, stuck) => {
@@ -60,7 +61,7 @@ function getMachineNames (config) {
const statuses = [
getStatus(
_.first(pings[r.deviceId]),
- _.first(notifier.checkStuckScreen(events, r.name))
+ _.first(checkStuckScreen(events, r.name))
)
]
diff --git a/lib/notifier/codes.js b/lib/notifier/codes.js
index 8e8a7cc4..a278ecee 100644
--- a/lib/notifier/codes.js
+++ b/lib/notifier/codes.js
@@ -22,6 +22,7 @@ const ALERT_SEND_INTERVAL = T.hour
const NOTIFICATION_TYPES = {
HIGH_VALUE_TX: 'highValueTransaction',
+ NORMAL_VALUE_TX: 'transaction',
FIAT_BALANCE: 'fiatBalance',
CRYPTO_BALANCE: 'cryptoBalance',
COMPLIANCE: 'compliance',
diff --git a/lib/notifier/index.js b/lib/notifier/index.js
index 0466b4dd..66491632 100644
--- a/lib/notifier/index.js
+++ b/lib/notifier/index.js
@@ -2,7 +2,6 @@ const _ = require('lodash/fp')
const configManager = require('../new-config-manager')
const logger = require('../logger')
-const machineLoader = require('../machine-loader')
const queries = require('./queries')
const settingsLoader = require('../new-settings-loader')
const customers = require('../customers')
@@ -10,14 +9,17 @@ const customers = require('../customers')
const utils = require('./utils')
const emailFuncs = require('./email')
const smsFuncs = require('./sms')
+const codes = require('./codes')
const { STALE, STALE_STATE, PING } = require('./codes')
+
const { NOTIFICATION_TYPES: {
HIGH_VALUE_TX,
+ NORMAL_VALUE_TX,
FIAT_BALANCE,
CRYPTO_BALANCE,
COMPLIANCE,
ERROR }
-} = require('./codes')
+} = codes
function buildMessage (alerts, notifications) {
const smsEnabled = utils.isActive(notifications.sms)
@@ -50,7 +52,7 @@ function checkNotification (plugins) {
return getAlerts(plugins)
.then(alerts => {
- errorAlertsNotify(alerts)
+ notifyIfActive('errors', alerts).catch(console.error)
const currentAlertFingerprint = utils.buildAlertFingerprint(
alerts,
notifications
@@ -81,7 +83,7 @@ function getAlerts (plugins) {
queries.machineEvents(),
plugins.getMachineNames()
]).then(([balances, events, devices]) => {
- balancesNotify(balances)
+ notifyIfActive('balance', balances).catch(console.error)
return buildAlerts(checkPings(devices), balances, events, devices)
})
}
@@ -136,17 +138,24 @@ function checkStuckScreen (deviceEvents, machineName) {
return []
}
+function notifCenterTransactionNotify (isHighValue, direction, fiat, fiatCode, deviceId, cryptoAddress) {
+ const messageSuffix = isHighValue ? 'High value' : ''
+ const message = `${messageSuffix} ${fiat} ${fiatCode} ${direction} transaction`
+ const detailB = utils.buildDetail({ deviceId: deviceId, direction, fiat, fiatCode, cryptoAddress })
+ return queries.addNotification(isHighValue ? HIGH_VALUE_TX : NORMAL_VALUE_TX, message, detailB)
+}
+
function transactionNotify (tx, rec) {
return settingsLoader.loadLatest().then(settings => {
const notifSettings = configManager.getGlobalNotifications(settings.config)
const highValueTx = tx.fiat.gt(notifSettings.highValueTransaction || Infinity)
const isCashOut = tx.direction === 'cashOut'
- // high value tx on database
- if (highValueTx && (tx.direction === 'cashIn' || (tx.direction === 'cashOut' && rec.isRedemption))) {
- const direction = tx.direction === 'cashOut' ? 'cash-out' : 'cash-in'
- const message = `${tx.fiat} ${tx.fiatCode} ${direction} transaction`
- const detailB = utils.buildDetail({ deviceId: tx.deviceId, direction, fiat: tx.fiat, fiatCode: tx.fiatCode, cryptoAddress: tx.toAddress })
- queries.addNotification(HIGH_VALUE_TX, message, detailB)
+
+ // for notification center
+ const directionDisplay = tx.direction === 'cashOut' ? 'cash-out' : 'cash-in'
+ const readyToNotify = tx.direction === 'cashIn' || (tx.direction === 'cashOut' && rec.isRedemption)
+ if (readyToNotify) {
+ notifyIfActive('transactions', highValueTx, directionDisplay, tx.fiat, tx.fiatCode, tx.deviceId, tx.toAddress).catch(console.error)
}
// alert through sms or email any transaction or high value transaction, if SMS || email alerts are enabled
@@ -161,7 +170,7 @@ function transactionNotify (tx, rec) {
if (!zeroConf && rec.isRedemption) return sendRedemptionMessage(tx.id, rec.error)
return Promise.all([
- machineLoader.getMachineName(tx.deviceId),
+ queries.getMachineName(tx.deviceId),
customerPromise
]).then(([machineName, customer]) => {
return utils.buildTransactionMessage(tx, rec, highValueTx, machineName, customer)
@@ -356,6 +365,23 @@ const customerComplianceNotify = (customer, deviceId, code, days = null) => {
.catch(console.error)
}
+const notificationCenterFunctions = {
+ 'compliance': customerComplianceNotify,
+ 'balance': balancesNotify,
+ 'errors': errorAlertsNotify,
+ 'transactions': notifCenterTransactionNotify
+}
+
+// for notification center, check if type of notification is active before calling the respective notify function
+const notifyIfActive = (type, ...args) => {
+ return settingsLoader.loadLatest().then(settings => {
+ const notificationSettings = configManager.getGlobalNotifications(settings.config).notificationCenter
+ if (!notificationCenterFunctions[type]) return Promise.reject(new Error(`Notification of type ${type} does not exist`))
+ if (!(notificationSettings.active && notificationSettings[type])) return Promise.resolve()
+ return notificationCenterFunctions[type](...args)
+ })
+}
+
module.exports = {
transactionNotify,
checkNotification,
@@ -363,6 +389,6 @@ module.exports = {
checkStuckScreen,
sendRedemptionMessage,
blacklistNotify,
- customerComplianceNotify,
- clearBlacklistNotification
+ clearBlacklistNotification,
+ notifyIfActive
}
diff --git a/lib/notifier/queries.js b/lib/notifier/queries.js
index 39f1bd1c..c6ae67fb 100644
--- a/lib/notifier/queries.js
+++ b/lib/notifier/queries.js
@@ -14,75 +14,82 @@ compliance - notifications related to warnings triggered by compliance settings
error - notifications related to errors
*/
+function getMachineName (machineId) {
+ const sql = 'SELECT * FROM devices WHERE device_id=$1'
+ return db.oneOrNone(sql, [machineId])
+ .then(it => it.name)
+}
+
const addNotification = (type, message, detail) => {
- const sql = `INSERT INTO notifications (id, type, message, detail) values ($1, $2, $3, $4)`
- return db.oneOrNone(sql, [uuidv4(), type, message, detail])
+ const sql = `INSERT INTO notifications (id, type, message, detail) values ($1, $2, $3, $4)`
+ return db.oneOrNone(sql, [uuidv4(), type, message, detail])
}
const getAllValidNotifications = (type) => {
- const sql = `SELECT * FROM notifications WHERE type = $1 AND valid = 't'`
- return db.any(sql, [type])
+ const sql = `SELECT * FROM notifications WHERE type = $1 AND valid = 't'`
+ return db.any(sql, [type])
}
const invalidateNotification = (detail, type) => {
- detail = _.omitBy(_.isEmpty, detail)
- const sql = `UPDATE notifications SET valid = 'f', read = 't' WHERE valid = 't' AND type = $1 AND detail::jsonb @> $2::jsonb`
- return db.none(sql, [type, detail])
+ detail = _.omitBy(_.isEmpty, detail)
+ const sql = `UPDATE notifications SET valid = 'f', read = 't' WHERE valid = 't' AND type = $1 AND detail::jsonb @> $2::jsonb`
+ return db.none(sql, [type, detail])
}
const batchInvalidate = (ids) => {
- const formattedIds = _.map(pgp.as.text, ids).join(',')
- const sql = `UPDATE notifications SET valid = 'f', read = 't' WHERE id IN ($1^)`
- return db.none(sql, [formattedIds])
+ const formattedIds = _.map(pgp.as.text, ids).join(',')
+ const sql = `UPDATE notifications SET valid = 'f', read = 't' WHERE id IN ($1^)`
+ return db.none(sql, [formattedIds])
}
const clearBlacklistNotification = (cryptoCode, cryptoAddress) => {
- const sql = `UPDATE notifications SET valid = 'f', read = 't' WHERE type = 'compliance' AND detail->>'cryptoCode' = $1 AND detail->>'cryptoAddress' = $2 AND (detail->>'code' = 'BLOCKED' OR detail->>'code' = 'REUSED')`
- return db.none(sql, [cryptoCode, cryptoAddress])
+ const sql = `UPDATE notifications SET valid = 'f', read = 't' WHERE type = 'compliance' AND detail->>'cryptoCode' = $1 AND detail->>'cryptoAddress' = $2 AND (detail->>'code' = 'BLOCKED' OR detail->>'code' = 'REUSED')`
+ return db.none(sql, [cryptoCode, cryptoAddress])
}
const getValidNotifications = (type, detail) => {
- const sql = `SELECT * FROM notifications WHERE type = $1 AND valid = 't' AND detail @> $2`
- return db.any(sql, [type, detail])
+ const sql = `SELECT * FROM notifications WHERE type = $1 AND valid = 't' AND detail @> $2`
+ return db.any(sql, [type, detail])
}
const getNotifications = () => {
- const sql = `SELECT * FROM notifications ORDER BY created DESC`
- return db.any(sql)
+ const sql = `SELECT * FROM notifications ORDER BY created DESC`
+ return db.any(sql)
}
const markAsRead = (id) => {
- const sql = `UPDATE notifications SET read = 't' WHERE id = $1`
- return db.none(sql, [id])
+ const sql = `UPDATE notifications SET read = 't' WHERE id = $1`
+ return db.none(sql, [id])
}
const markAllAsRead = () => {
- const sql = `UPDATE notifications SET read = 't'`
- return db.none(sql)
+ const sql = `UPDATE notifications SET read = 't'`
+ return db.none(sql)
}
const hasUnreadNotifications = () => {
- const sql = `SELECT EXISTS (SELECT 1 FROM notifications WHERE read = 'f' LIMIT 1)`
- return db.oneOrNone(sql).then(res => res.exists)
+ const sql = `SELECT EXISTS (SELECT 1 FROM notifications WHERE read = 'f' LIMIT 1)`
+ return db.oneOrNone(sql).then(res => res.exists)
}
const getAlerts = () => {
- const types = ['fiatBalance', 'cryptoBalance', 'error']
- const sql = `SELECT * FROM notifications WHERE valid = 't' AND type IN ($1:list) ORDER BY created DESC`
- return db.any(sql, [types])
+ const types = ['fiatBalance', 'cryptoBalance', 'error']
+ const sql = `SELECT * FROM notifications WHERE valid = 't' AND type IN ($1:list) ORDER BY created DESC`
+ return db.any(sql, [types])
}
module.exports = {
- machineEvents: dbm.machineEvents,
- addNotification,
- getAllValidNotifications,
- invalidateNotification,
- batchInvalidate,
- clearBlacklistNotification,
- getValidNotifications,
- getNotifications,
- markAsRead,
- markAllAsRead,
- hasUnreadNotifications,
- getAlerts
+ machineEvents: dbm.machineEvents,
+ addNotification,
+ getAllValidNotifications,
+ invalidateNotification,
+ batchInvalidate,
+ clearBlacklistNotification,
+ getValidNotifications,
+ getNotifications,
+ markAsRead,
+ markAllAsRead,
+ hasUnreadNotifications,
+ getAlerts,
+ getMachineName
}
diff --git a/lib/routes.js b/lib/routes.js
index 5a912f56..dc07002b 100644
--- a/lib/routes.js
+++ b/lib/routes.js
@@ -343,7 +343,7 @@ function triggerBlock (req, res, next) {
customers.update(id, { authorizedOverride: 'blocked' })
.then(customer => {
- notifier.customerComplianceNotify(customer, req.deviceId, 'BLOCKED')
+ notifier.notifyIfActive('compliance', customer, req.deviceId, 'BLOCKED').catch(console.error)
return respond(req, res, { customer })
})
.catch(next)
@@ -362,7 +362,7 @@ function triggerSuspend (req, res, next) {
date.setDate(date.getDate() + days);
customers.update(id, { suspendedUntil: date })
.then(customer => {
- notifier.customerComplianceNotify(customer, req.deviceId, 'SUSPENDED', days)
+ notifier.notifyIfActive('compliance', customer, req.deviceId, 'SUSPENDED', days).catch(console.error)
return respond(req, res, { customer })
})
.catch(next)
diff --git a/migrations/1607009558538-create-notifications-table.js b/migrations/1607009558538-create-notifications-table.js
index 88231246..d4351c91 100644
--- a/migrations/1607009558538-create-notifications-table.js
+++ b/migrations/1607009558538-create-notifications-table.js
@@ -4,6 +4,7 @@ const singleQuotify = (item) => `'${item}'`
var types = [
'highValueTransaction',
+ 'transaction',
'fiatBalance',
'cryptoBalance',
'compliance',
diff --git a/new-lamassu-admin/src/components/NotificationCenter/NotificationRow.js b/new-lamassu-admin/src/components/NotificationCenter/NotificationRow.js
index b01312ce..1b81b325 100644
--- a/new-lamassu-admin/src/components/NotificationCenter/NotificationRow.js
+++ b/new-lamassu-admin/src/components/NotificationCenter/NotificationRow.js
@@ -2,6 +2,7 @@ import Grid from '@material-ui/core/Grid'
import { makeStyles } from '@material-ui/core/styles'
import classnames from 'classnames'
import prettyMs from 'pretty-ms'
+import * as R from 'ramda'
import React from 'react'
import { Label1, Label2, TL2 } from 'src/components/typography'
@@ -14,6 +15,7 @@ import styles from './NotificationCenter.styles'
const useStyles = makeStyles(styles)
const types = {
+ transaction: { display: 'Transactions', icon: },
highValueTransaction: { display: 'Transactions', icon: },
fiatBalance: { display: 'Maintenance', icon: },
cryptoBalance: { display: 'Maintenance', icon: },
@@ -34,15 +36,18 @@ const NotificationRow = ({
}) => {
const classes = useStyles()
- const buildType = () => {
- return types[type].display
- }
-
- const buildAge = () => {
- const createdDate = new Date(created)
- const interval = +new Date() - createdDate
- return prettyMs(interval, { compact: true, verbose: true })
- }
+ const typeDisplay = R.path([type, 'display'])(types) ?? null
+ const icon = R.path([type, 'icon'])(types) ??
+ const age = prettyMs(new Date().getTime() - new Date(created).getTime(), {
+ compact: true,
+ verbose: true
+ })
+ const notificationTitle =
+ typeDisplay && deviceName
+ ? `${typeDisplay} - ${deviceName}`
+ : !typeDisplay && deviceName
+ ? `${deviceName}`
+ : `${typeDisplay}`
return (
- {types[type].icon}
+ {icon}
- {`${buildType()} ${deviceName ? '- ' + deviceName : ''}`}
+ {notificationTitle}
{message}
-
- {buildAge(created)}
-
+ {age}
diff --git a/new-lamassu-admin/src/pages/Notifications/sections/Setup.js b/new-lamassu-admin/src/pages/Notifications/sections/Setup.js
index 3c5991bb..be1d1c78 100644
--- a/new-lamassu-admin/src/pages/Notifications/sections/Setup.js
+++ b/new-lamassu-admin/src/pages/Notifications/sections/Setup.js
@@ -87,6 +87,7 @@ const Setup = ({ wizard, forceDisable }) => {
+
)