diff --git a/lib/custom-sms.js b/lib/custom-sms.js
index 3ba6d9c0..f02b0dc8 100644
--- a/lib/custom-sms.js
+++ b/lib/custom-sms.js
@@ -1,17 +1,50 @@
+const _ = require('lodash/fp')
const uuid = require('uuid')
const db = require('./db')
const getCustomMessages = () => {
- const sql = `SELECT * FROM custom_messages`
- return db.any(sql)
+ const sql = `SELECT * FROM custom_messages ORDER BY created`
+ return db.any(sql).then(res => _.map(
+ it => ({
+ id: it.id,
+ event: _.camelCase(it.event),
+ deviceId: it.device_id,
+ message: it.message
+ }), res))
}
const createCustomMessage = (event, deviceId, message) => {
- const sql = `INSERT INTO custom_message (event, device_id, message) VALUES ($2, $3, $4)`
- return db.none(sql, [uuid.v4(), event, deviceId, message])
+ const machineId = deviceId === 'ALL_MACHINES' ? null : deviceId
+ const sql = `INSERT INTO custom_messages (id, event, device_id, message) VALUES ($1, $2, $3, $4)`
+ return db.none(sql, [uuid.v4(), _.snakeCase(event), machineId, message])
+}
+
+const editCustomMessage = (id, event, deviceId, message) => {
+ const machineId = deviceId === 'ALL_MACHINES' ? null : deviceId
+ const sql = `UPDATE custom_messages SET event=$2, device_id=$3, message=$4 WHERE id=$1`
+ return db.none(sql, [id, _.snakeCase(event), machineId, message])
+}
+
+const deleteCustomMessage = id => {
+ const sql = `DELETE FROM custom_messages WHERE id=$1`
+ return db.none(sql, [id])
+}
+
+const getCommonCustomMessages = event => {
+ const sql = `SELECT * FROM custom_messages WHERE event=$1 AND device_id IS NULL LIMIT 1`
+ return db.oneOrNone(sql, [event])
+}
+
+const getMachineCustomMessages = (event, deviceId) => {
+ const sql = `SELECT * FROM custom_messages WHERE event=$1 AND device_id=$2 LIMIT 1`
+ return db.oneOrNone(sql, [event, deviceId])
}
module.exports = {
getCustomMessages,
- createCustomMessage
+ createCustomMessage,
+ editCustomMessage,
+ deleteCustomMessage,
+ getCommonCustomMessages,
+ getMachineCustomMessages
}
diff --git a/lib/new-admin/graphql/resolvers/sms.resolver.js b/lib/new-admin/graphql/resolvers/sms.resolver.js
index 6936dfe8..a9c2ba5e 100644
--- a/lib/new-admin/graphql/resolvers/sms.resolver.js
+++ b/lib/new-admin/graphql/resolvers/sms.resolver.js
@@ -5,7 +5,9 @@ const resolvers = {
customMessages: () => customSms.getCustomMessages()
},
Mutation: {
- createCustomMessage: (...[, { event, deviceId, message }]) => customSms.createCustomMessage(event, deviceId, message)
+ createCustomMessage: (...[, { event, deviceId, message }]) => customSms.createCustomMessage(event, deviceId, message),
+ editCustomMessage: (...[, { id, event, deviceId, message }]) => customSms.editCustomMessage(id, event, deviceId, message),
+ deleteCustomMessage: (...[, { id }]) => customSms.deleteCustomMessage(id)
}
}
diff --git a/lib/new-admin/graphql/types/sms.type.js b/lib/new-admin/graphql/types/sms.type.js
index c6528b54..2eb1c62e 100644
--- a/lib/new-admin/graphql/types/sms.type.js
+++ b/lib/new-admin/graphql/types/sms.type.js
@@ -19,6 +19,8 @@ const typeDef = gql`
type Mutation {
createCustomMessage(event: CustomMessageEvent!, deviceId: String, message: String!): CustomMessage @auth
+ editCustomMessage(id: ID!, event: CustomMessageEvent!, deviceId: String, message: String!): CustomMessage @auth
+ deleteCustomMessage(id: ID!): CustomMessage @auth
}
`
diff --git a/lib/plugins.js b/lib/plugins.js
index ce133e8c..1c62cf7e 100644
--- a/lib/plugins.js
+++ b/lib/plugins.js
@@ -361,19 +361,19 @@ function plugins (settings, deviceId) {
const phone = tx.phone
const timestamp = dateFormat(new Date(), 'UTC:HH:MM Z')
- const rec = {
- sms: {
- toNumber: phone,
- body: `Your cash is waiting! Go to the Cryptomat and press Redeem within 24 hours. [${timestamp}]`
- }
- }
+ return sms.getCashOutReadySms(deviceId, phone, code)
+ .then(msg => {
+ const rec = {
+ sms: msg
+ }
+
+ return sms.sendMessage(settings, rec)
+ .then(() => {
+ const sql = 'update cash_out_txs set notified=$1 where id=$2'
+ const values = [true, tx.id]
- return sms.sendMessage(settings, rec)
- .then(() => {
- const sql = 'update cash_out_txs set notified=$1 where id=$2'
- const values = [true, tx.id]
-
- return db.none(sql, values)
+ return db.none(sql, values)
+ })
})
}
@@ -723,15 +723,15 @@ function plugins (settings, deviceId) {
? '123'
: randomCode()
- const rec = {
- sms: {
- toNumber: phone,
- body: 'Your cryptomat code: ' + code
- }
- }
-
- return sms.sendMessage(settings, rec)
- .then(() => code)
+ return sms.getPhoneCodeSms(deviceId, phone, code)
+ .then(msg => {
+ const rec = {
+ sms: msg
+ }
+
+ return sms.sendMessage(settings, rec)
+ .then(() => code)
+ })
}
function sweepHdRow (row) {
diff --git a/lib/sms.js b/lib/sms.js
index 597f5bd1..c1cbd08c 100644
--- a/lib/sms.js
+++ b/lib/sms.js
@@ -1,6 +1,67 @@
const ph = require('./plugin-helper')
const argv = require('minimist')(process.argv.slice(2))
const { utils: coinUtils } = require('lamassu-coins')
+const _ = require('lodash/fp')
+
+const customSms = require('./custom-sms')
+
+function getPhoneCodeSms (deviceId, phone, code) {
+ return Promise.all([
+ customSms.getCommonCustomMessages('sms_code'),
+ customSms.getMachineCustomMessages('sms_code', deviceId)
+ ])
+ .then(([commonMsg, machineMsg]) => {
+ if (!_.isNil(machineMsg)) {
+ const messageContent = _.replace('#code', code, machineMsg.message)
+ return {
+ toNumber: phone,
+ body: messageContent
+ }
+ }
+
+ if (!_.isNil(commonMsg)) {
+ const messageContent = _.replace('#code', code, commonMsg.message)
+ return {
+ toNumber: phone,
+ body: messageContent
+ }
+ }
+
+ return {
+ toNumber: phone,
+ body: `Your cryptomat code: ${code}`
+ }
+ })
+}
+
+function getCashOutReadySms (deviceId, phone, timestamp) {
+ return Promise.all([
+ customSms.getCommonCustomMessages('cash_out_dispense_ready'),
+ customSms.getMachineCustomMessages('cash_out_dispense_ready', deviceId)
+ ])
+ .then(([commonMsg, machineMsg]) => {
+ if (!_.isNil(machineMsg)) {
+ const messageContent = _.replace('#timestamp', timestamp, machineMsg.message)
+ return {
+ toNumber: phone,
+ body: messageContent
+ }
+ }
+
+ if (!_.isNil(commonMsg)) {
+ const messageContent = _.replace('#timestamp', timestamp, commonMsg.message)
+ return {
+ toNumber: phone,
+ body: messageContent
+ }
+ }
+
+ return {
+ toNumber: phone,
+ body: `Your cash is waiting! Go to the Cryptomat and press Redeem within 24 hours. [${timestamp}]`
+ }
+ })
+}
function getPlugin (settings) {
const pluginCode = argv.mockSms ? 'mock-sms' : 'twilio'
@@ -91,4 +152,11 @@ function formatSmsReceipt (data, options) {
return request
}
-module.exports = { sendMessage, formatSmsReceipt, getLookup, toCryptoUnits }
+module.exports = {
+ getPhoneCodeSms,
+ getCashOutReadySms,
+ sendMessage,
+ getLookup,
+ formatSmsReceipt,
+ toCryptoUnits
+}
diff --git a/migrations/1627518944902-custom-sms.js b/migrations/1627518944902-custom-sms.js
index ff19c1c5..95395d31 100644
--- a/migrations/1627518944902-custom-sms.js
+++ b/migrations/1627518944902-custom-sms.js
@@ -7,9 +7,11 @@ exports.up = function (next) {
id UUID PRIMARY KEY,
event custom_message_event NOT NULL,
device_id TEXT REFERENCES devices(device_id),
- message TEXT NOT NULL
+ message TEXT NOT NULL,
+ created TIMESTAMPTZ NOT NULL DEFAULT now()
)`,
- `CREATE UNIQUE INDEX uq_custom_message_per_device ON custom_messages (event, device_id)`
+ `CREATE UNIQUE INDEX uq_custom_message_per_device ON custom_messages (event, device_id) WHERE device_id IS NOT NULL`,
+ `CREATE UNIQUE INDEX uq_custom_message_all_devices ON custom_messages (event) WHERE device_id IS NULL`
]
db.multi(sql, next)
diff --git a/new-lamassu-admin/src/pages/OperatorInfo/CustomSMS/CustomSMS.js b/new-lamassu-admin/src/pages/OperatorInfo/CustomSMS/CustomSMS.js
index 188e7081..44bcf845 100644
--- a/new-lamassu-admin/src/pages/OperatorInfo/CustomSMS/CustomSMS.js
+++ b/new-lamassu-admin/src/pages/OperatorInfo/CustomSMS/CustomSMS.js
@@ -1,20 +1,20 @@
-import { useQuery } from '@apollo/react-hooks'
+import { useQuery, useMutation } from '@apollo/react-hooks'
import { makeStyles, Box } from '@material-ui/core'
import gql from 'graphql-tag'
import * as R from 'ramda'
import React, { useState } from 'react'
+import { DeleteDialog } from 'src/components/DeleteDialog'
import { Link, IconButton } from 'src/components/buttons'
import DataTable from 'src/components/tables/DataTable'
import { H4 } from 'src/components/typography'
import { ReactComponent as DeleteIcon } from 'src/styling/icons/action/delete/enabled.svg'
import { ReactComponent as EditIcon } from 'src/styling/icons/action/edit/enabled.svg'
-import { global } from '../OperatorInfo.styles'
-
+import styles from './CustomSMS.styles'
import CustomSMSModal from './CustomSMSModal'
-const useStyles = makeStyles(global)
+const useStyles = makeStyles(styles)
const GET_CUSTOM_MESSAGES = gql`
query customMessages {
@@ -27,6 +27,44 @@ const GET_CUSTOM_MESSAGES = gql`
}
`
+const CREATE_CUSTOM_MESSAGE = gql`
+ mutation createCustomMessage(
+ $event: CustomMessageEvent!
+ $deviceId: String!
+ $message: String!
+ ) {
+ createCustomMessage(event: $event, deviceId: $deviceId, message: $message) {
+ id
+ }
+ }
+`
+
+const EDIT_CUSTOM_MESSAGE = gql`
+ mutation editCustomMessage(
+ $id: ID!
+ $event: CustomMessageEvent!
+ $deviceId: String!
+ $message: String!
+ ) {
+ editCustomMessage(
+ id: $id
+ event: $event
+ deviceId: $deviceId
+ message: $message
+ ) {
+ id
+ }
+ }
+`
+
+const DELETE_CUSTOM_MESSAGE = gql`
+ mutation deleteCustomMessage($id: ID!) {
+ deleteCustomMessage(id: $id) {
+ id
+ }
+ }
+`
+
const GET_MACHINES = gql`
{
machines {
@@ -36,43 +74,91 @@ const GET_MACHINES = gql`
}
`
+const EVENT_OPTIONS = [
+ { code: 'smsCode', display: 'On SMS confirmation code' },
+ { code: 'cashOutDispenseReady', display: 'Cash out dispense ready' }
+]
+
const CustomSMS = () => {
const classes = useStyles()
+ const [deleteDialog, setDeleteDialog] = useState(false)
const [showModal, setShowModal] = useState(false)
+ const [selectedSMS, setSelectedSMS] = useState(null)
+ const [errorMsg, setErrorMsg] = useState('')
+
const { data: messagesData, loading: messagesLoading } = useQuery(
GET_CUSTOM_MESSAGES
)
+
const { data: machinesData, loading: machinesLoading } = useQuery(
GET_MACHINES
)
+ const [createMessage] = useMutation(CREATE_CUSTOM_MESSAGE, {
+ onError: ({ msg }) => setErrorMsg(msg),
+ refetchQueries: () => ['customMessages']
+ })
+
+ const [editMessage] = useMutation(EDIT_CUSTOM_MESSAGE, {
+ onError: ({ msg }) => setErrorMsg(msg),
+ refetchQueries: () => ['customMessages']
+ })
+
+ const [deleteMessage] = useMutation(DELETE_CUSTOM_MESSAGE, {
+ onError: ({ msg }) => setErrorMsg(msg),
+ refetchQueries: () => ['customMessages']
+ })
+
const loading = messagesLoading && machinesLoading
const machineOptions =
- machinesData &&
- R.map(
- it => ({ code: it.deviceId, display: it.name }),
- R.path(['machines'])(machinesData)
- )
+ (machinesData &&
+ R.map(
+ it => ({ code: it.deviceId, display: it.name }),
+ R.path(['machines'])(machinesData)
+ )) ??
+ []
+
+ const handleClose = () => {
+ setSelectedSMS(null)
+ setShowModal(false)
+ setDeleteDialog(false)
+ }
+
+ const handleOpen = () => {
+ setErrorMsg('')
+ setShowModal(true)
+ }
const elements = [
{
- header: 'Message name',
+ header: 'Event',
width: 400,
size: 'sm',
textAlign: 'left',
- view: it => it.event
+ view: it =>
+ R.find(ite => R.propEq('event', ite.code, it), EVENT_OPTIONS).display
+ },
+ {
+ header: 'Machine',
+ width: 200,
+ size: 'sm',
+ textAlign: 'left',
+ view: it =>
+ R.find(ite => R.propEq('deviceId', ite.code, it), machineOptions)
+ ?.display ?? `All Machines`
},
{
header: 'Edit',
- width: 120,
+ width: 100,
size: 'sm',
textAlign: 'center',
view: it => (