mirror of
https://github.com/jambonz/jambonz-api-server.git
synced 2026-01-25 02:08:24 +00:00
Compare commits
10 Commits
v0.8.0-rc4
...
v0.8.0-rc1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d4f2be3dc1 | ||
|
|
a46c24b3aa | ||
|
|
f5c833720a | ||
|
|
4d2cc15de4 | ||
|
|
019599741a | ||
|
|
f2c2623b28 | ||
|
|
6c494786c8 | ||
|
|
80ee1d06d7 | ||
|
|
274377960e | ||
|
|
02bba9d981 |
6
.github/workflows/ci.yml
vendored
6
.github/workflows/ci.yml
vendored
@@ -6,10 +6,10 @@ jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v1
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 14
|
||||
node-version: lts/*
|
||||
- run: npm install
|
||||
- run: npm run jslint
|
||||
- run: npm test
|
||||
|
||||
@@ -20,7 +20,7 @@ jobs:
|
||||
if: github.event_name == 'push'
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Build image
|
||||
run: docker build . --file Dockerfile.db-create --tag $IMAGE_NAME
|
||||
|
||||
2
.github/workflows/docker-publish.yml
vendored
2
.github/workflows/docker-publish.yml
vendored
@@ -20,7 +20,7 @@ jobs:
|
||||
if: github.event_name == 'push'
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Build image
|
||||
run: docker build . --file Dockerfile --tag $IMAGE_NAME
|
||||
|
||||
@@ -45,8 +45,6 @@ VALUES
|
||||
('91cb050f-9826-4ac9-b736-84a10372a9fe', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '178.22.139.77', 32, 5060, 1, 0),
|
||||
('58700fad-98bf-4d31-b61e-888c54911b35', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '185.63.140.77', 32, 5060, 1, 0),
|
||||
('d020fd9e-7fdb-4bca-ae0d-e61b38142873', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '185.63.142.77', 32, 5060, 1, 0),
|
||||
('441fd2e7-c845-459c-963d-6e917063ed9a', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '185.63.141.77', 32, 5060, 1, 0),
|
||||
('1e0d3e80-9973-4184-9bec-07ae564f983f', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '185.63.143.77', 32, 5060, 1, 0),
|
||||
('e56ec745-5f37-443f-afb4-7bbda31ae7ac', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '178.22.140.34', 32, 5060, 1, 0),
|
||||
('e7447e7e-2c7d-4738-ab53-097c187236ff', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '178.22.143.66', 32, 5060, 1, 0),
|
||||
('38d8520a-527f-4f8e-8456-f9dfca742561', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '172.86.225.77', 32, 5060, 1, 0),
|
||||
('834f8b0c-d4c2-4f3e-93d9-cf307995eedd', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '172.86.225.88', 32, 5060, 1, 0),
|
||||
('5f431d42-48e4-44ce-a311-d946f0b475b6', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', 'out.simwood.com', 32, 5060, 0, 1);
|
||||
|
||||
@@ -435,6 +435,7 @@ account_sid CHAR(36) COMMENT 'account that this application belongs to (if null,
|
||||
call_hook_sid CHAR(36) COMMENT 'webhook to call for inbound calls ',
|
||||
call_status_hook_sid CHAR(36) COMMENT 'webhook to call for call status events',
|
||||
messaging_hook_sid CHAR(36) COMMENT 'webhook to call for inbound SMS/MMS ',
|
||||
app_json TEXT,
|
||||
speech_synthesis_vendor VARCHAR(64) NOT NULL DEFAULT 'google',
|
||||
speech_synthesis_language VARCHAR(12) NOT NULL DEFAULT 'en-US',
|
||||
speech_synthesis_voice VARCHAR(64),
|
||||
@@ -647,4 +648,4 @@ ALTER TABLE accounts ADD FOREIGN KEY device_calling_application_sid_idxfk (devic
|
||||
|
||||
ALTER TABLE accounts ADD FOREIGN KEY siprec_hook_sid_idxfk (siprec_hook_sid) REFERENCES applications (application_sid);
|
||||
|
||||
SET FOREIGN_KEY_CHECKS=1;
|
||||
SET FOREIGN_KEY_CHECKS=1;
|
||||
@@ -1841,8 +1841,8 @@
|
||||
<name><![CDATA[predefined_carriers]]></name>
|
||||
<schema><![CDATA[]]></schema>
|
||||
<location>
|
||||
<x>47.00</x>
|
||||
<y>1103.00</y>
|
||||
<x>35.00</x>
|
||||
<y>1194.00</y>
|
||||
</location>
|
||||
<size>
|
||||
<width>302.00</width>
|
||||
@@ -2187,7 +2187,7 @@
|
||||
</location>
|
||||
<size>
|
||||
<width>345.00</width>
|
||||
<height>300.00</height>
|
||||
<height>320.00</height>
|
||||
</size>
|
||||
<zorder>0</zorder>
|
||||
<SQLField>
|
||||
@@ -2281,6 +2281,11 @@
|
||||
<objectComment><![CDATA[webhook to call for inbound SMS/MMS ]]></objectComment>
|
||||
<uid><![CDATA[4690A98A-2F67-4205-A5C1-D9523F6B3282]]></uid>
|
||||
</SQLField>
|
||||
<SQLField>
|
||||
<name><![CDATA[app_json]]></name>
|
||||
<type><![CDATA[TEXT]]></type>
|
||||
<uid><![CDATA[B3BB24D3-DDA7-418F-A8CE-FD0E3E8B25C7]]></uid>
|
||||
</SQLField>
|
||||
<SQLField>
|
||||
<name><![CDATA[speech_synthesis_vendor]]></name>
|
||||
<type><![CDATA[VARCHAR(64)]]></type>
|
||||
@@ -2711,10 +2716,10 @@
|
||||
<SourceSidebarWidth><![CDATA[0.000000]]></SourceSidebarWidth>
|
||||
<SQLEditorFileFormatVersion><![CDATA[4]]></SQLEditorFileFormatVersion>
|
||||
<uid><![CDATA[58C99A00-06C9-478C-A667-C63842E088F3]]></uid>
|
||||
<windowHeight><![CDATA[870.000000]]></windowHeight>
|
||||
<windowHeight><![CDATA[917.000000]]></windowHeight>
|
||||
<windowLocationX><![CDATA[0.000000]]></windowLocationX>
|
||||
<windowLocationY><![CDATA[74.000000]]></windowLocationY>
|
||||
<windowScrollOrigin><![CDATA[{432, 18.5}]]></windowScrollOrigin>
|
||||
<windowLocationY><![CDATA[27.000000]]></windowLocationY>
|
||||
<windowScrollOrigin><![CDATA[{383, 243.5}]]></windowScrollOrigin>
|
||||
<windowWidth><![CDATA[1512.000000]]></windowWidth>
|
||||
</SQLDocumentInfo>
|
||||
<AllowsIndexRenamingOnInsert><![CDATA[1]]></AllowsIndexRenamingOnInsert>
|
||||
|
||||
@@ -98,10 +98,8 @@ VALUES
|
||||
('91cb050f-9826-4ac9-b736-84a10372a9fe', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '178.22.139.77', 32, 5060, 1, 0),
|
||||
('58700fad-98bf-4d31-b61e-888c54911b35', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '185.63.140.77', 32, 5060, 1, 0),
|
||||
('d020fd9e-7fdb-4bca-ae0d-e61b38142873', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '185.63.142.77', 32, 5060, 1, 0),
|
||||
('441fd2e7-c845-459c-963d-6e917063ed9a', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '185.63.141.77', 32, 5060, 1, 0),
|
||||
('1e0d3e80-9973-4184-9bec-07ae564f983f', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '185.63.143.77', 32, 5060, 1, 0),
|
||||
('e56ec745-5f37-443f-afb4-7bbda31ae7ac', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '178.22.140.34', 32, 5060, 1, 0),
|
||||
('e7447e7e-2c7d-4738-ab53-097c187236ff', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '178.22.143.66', 32, 5060, 1, 0),
|
||||
('38d8520a-527f-4f8e-8456-f9dfca742561', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '172.86.225.77', 32, 5060, 1, 0),
|
||||
('834f8b0c-d4c2-4f3e-93d9-cf307995eedd', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '172.86.225.88', 32, 5060, 1, 0),
|
||||
('5f431d42-48e4-44ce-a311-d946f0b475b6', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', 'out.simwood.com', 32, 5060, 0, 1);
|
||||
|
||||
|
||||
|
||||
@@ -68,6 +68,9 @@ VALUES
|
||||
('d2ccfcb1-9198-4fe9-a0ca-6e49395837c4', '7d509a18-bbff-4c5d-b21e-b99bf8f8c49a', '54.172.60.0', 30, 5060, 1, 0),
|
||||
('6b1d0032-4430-41f1-87c6-f22233d394ef', '7d509a18-bbff-4c5d-b21e-b99bf8f8c49a', '54.244.51.0', 30, 5060, 1, 0),
|
||||
('0de40217-8bd5-4aa8-a9fd-1994282953c6', '7d509a18-bbff-4c5d-b21e-b99bf8f8c49a', '54.171.127.192', 30, 5060, 1, 0),
|
||||
('48b108e3-1ce7-4f18-a4cb-e41e63688bdf', '7d509a18-bbff-4c5d-b21e-b99bf8f8c49a', '54.171.127.193', 30, 5060, 1, 0),
|
||||
('d9131a69-fe44-4c2a-ba82-4adc81f628dd', '7d509a18-bbff-4c5d-b21e-b99bf8f8c49a', '54.171.127.194', 30, 5060, 1, 0),
|
||||
('34a6a311-4bd6-49ca-aa77-edd3cb92c6e1', '7d509a18-bbff-4c5d-b21e-b99bf8f8c49a', '54.171.127.195', 30, 5060, 1, 0),
|
||||
('37bc0b20-b53c-4c31-95a6-f82b1c3713e3', '7d509a18-bbff-4c5d-b21e-b99bf8f8c49a', '35.156.191.128', 30, 5060, 1, 0),
|
||||
('39791f4e-b612-4882-a37e-e92711a39f3f', '7d509a18-bbff-4c5d-b21e-b99bf8f8c49a', '54.65.63.192', 30, 5060, 1, 0),
|
||||
('81a0c8cb-a33e-42da-8f20-99083da6f02f', '7d509a18-bbff-4c5d-b21e-b99bf8f8c49a', '54.252.254.64', 30, 5060, 1, 0),
|
||||
@@ -92,10 +95,8 @@ VALUES
|
||||
('91cb050f-9826-4ac9-b736-84a10372a9fe', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '178.22.139.77', 32, 5060, 1, 0),
|
||||
('58700fad-98bf-4d31-b61e-888c54911b35', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '185.63.140.77', 32, 5060, 1, 0),
|
||||
('d020fd9e-7fdb-4bca-ae0d-e61b38142873', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '185.63.142.77', 32, 5060, 1, 0),
|
||||
('441fd2e7-c845-459c-963d-6e917063ed9a', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '185.63.141.77', 32, 5060, 1, 0),
|
||||
('1e0d3e80-9973-4184-9bec-07ae564f983f', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '185.63.143.77', 32, 5060, 1, 0),
|
||||
('e56ec745-5f37-443f-afb4-7bbda31ae7ac', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '178.22.140.34', 32, 5060, 1, 0),
|
||||
('e7447e7e-2c7d-4738-ab53-097c187236ff', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '178.22.143.66', 32, 5060, 1, 0),
|
||||
('38d8520a-527f-4f8e-8456-f9dfca742561', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '172.86.225.77', 32, 5060, 1, 0),
|
||||
('834f8b0c-d4c2-4f3e-93d9-cf307995eedd', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '172.86.225.88', 32, 5060, 1, 0),
|
||||
('5f431d42-48e4-44ce-a311-d946f0b475b6', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', 'out.simwood.com', 32, 5060, 0, 1);
|
||||
|
||||
SET FOREIGN_KEY_CHECKS=1;
|
||||
|
||||
@@ -72,10 +72,8 @@ VALUES
|
||||
('91cb050f-9826-4ac9-b736-84a10372a9fe', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '178.22.139.77', 32, 5060, 1, 0),
|
||||
('58700fad-98bf-4d31-b61e-888c54911b35', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '185.63.140.77', 32, 5060, 1, 0),
|
||||
('d020fd9e-7fdb-4bca-ae0d-e61b38142873', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '185.63.142.77', 32, 5060, 1, 0),
|
||||
('441fd2e7-c845-459c-963d-6e917063ed9a', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '185.63.141.77', 32, 5060, 1, 0),
|
||||
('1e0d3e80-9973-4184-9bec-07ae564f983f', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '185.63.143.77', 32, 5060, 1, 0),
|
||||
('e56ec745-5f37-443f-afb4-7bbda31ae7ac', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '178.22.140.34', 32, 5060, 1, 0),
|
||||
('e7447e7e-2c7d-4738-ab53-097c187236ff', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '178.22.143.66', 32, 5060, 1, 0),
|
||||
('38d8520a-527f-4f8e-8456-f9dfca742561', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '172.86.225.77', 32, 5060, 1, 0),
|
||||
('834f8b0c-d4c2-4f3e-93d9-cf307995eedd', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '172.86.225.88', 32, 5060, 1, 0),
|
||||
('5f431d42-48e4-44ce-a311-d946f0b475b6', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', 'out.simwood.com', 32, 5060, 0, 1);
|
||||
|
||||
SET FOREIGN_KEY_CHECKS=1;
|
||||
|
||||
@@ -59,6 +59,7 @@ const sql = {
|
||||
'ALTER TABLE `voip_carriers` ADD COLUMN `register_public_ip_in_contact` BOOLEAN NOT NULL DEFAULT false'
|
||||
],
|
||||
'8000': [
|
||||
'ALTER TABLE `applications` ADD COLUMN `app_json` TEXT',
|
||||
'ALTER TABLE voip_carriers CHANGE register_public_domain_in_contact register_public_ip_in_contact BOOLEAN',
|
||||
'alter table phone_numbers modify number varchar(132) NOT NULL UNIQUE',
|
||||
`CREATE TABLE permissions
|
||||
|
||||
@@ -85,9 +85,6 @@ VALUES
|
||||
-- simwood gateways
|
||||
insert into predefined_sip_gateways (predefined_sip_gateway_sid, predefined_carrier_sid, ipv4, netmask, port, inbound, outbound)
|
||||
VALUES
|
||||
('91cb050f-9826-4ac9-b736-84a10372a9fe', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '149.91.14.0', 24, 5060, 1, 0),
|
||||
('58700fad-98bf-4d31-b61e-888c54911b35', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '154.51.137.96', 27, 5060, 1, 0),
|
||||
('d020fd9e-7fdb-4bca-ae0d-e61b38142873', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '78.40.245.160', 27, 5060, 1, 0),
|
||||
('441fd2e7-c845-459c-963d-6e917063ed9a', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '178.22.136.24', 29, 5060, 1, 0),
|
||||
('1e0d3e80-9973-4184-9bec-07ae564f983f', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '178.22.136.28', 28, 5060, 1, 0),
|
||||
('e56ec745-5f37-443f-afb4-7bbda31ae7ac', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '178.22.140.48', 28, 5060, 1, 0),
|
||||
@@ -98,7 +95,7 @@ VALUES
|
||||
('b6ae6240-55ac-4c11-892f-a71b2155ea60', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '185.63.142.0', 26, 5060, 1, 0),
|
||||
('5a976337-164b-408e-8748-d8bfb4bd5d76', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '185.63.143.0', 26, 5060, 1, 0),
|
||||
('ed0434ca-7f26-4624-9523-0419d0d2924d', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '178.22.139.0', 26, 5060, 1, 0),
|
||||
('d1a594c2-c14f-4ead-b621-96129bc87886', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '172.86.224.0', 24, 5060, 1, 0),
|
||||
('6bfb55e5-e248-48dc-a104-4f3eedd7d7de', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '172.86.225.0', 24, 5060, 1, 0),
|
||||
('5f431d42-48e4-44ce-a311-d946f0b475b6', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', 'out.simwood.com', 32, 5060, 0, 1);
|
||||
|
||||
|
||||
|
||||
@@ -8,9 +8,7 @@ const short = require('short-uuid');
|
||||
const {promisePool} = require('../../db');
|
||||
const sysError = require('../error');
|
||||
|
||||
const sqlSelectCarrierByName = `SELECT * FROM voip_carriers
|
||||
WHERE account_sid = ?
|
||||
AND name = ?`;
|
||||
|
||||
const sqlSelectCarrierByNameForSP = `SELECT * FROM voip_carriers
|
||||
WHERE service_provider_sid = ?
|
||||
AND name = ?`;
|
||||
@@ -25,18 +23,20 @@ router.post('/:sid', async(req, res) => {
|
||||
const {sid } = req.params;
|
||||
let service_provider_sid;
|
||||
const {account_sid} = req.user;
|
||||
|
||||
if (!account_sid) {
|
||||
service_provider_sid = parseServiceProviderSid(req);
|
||||
} else {
|
||||
service_provider_sid = req.user.service_provider_sid;
|
||||
}
|
||||
|
||||
try {
|
||||
const [template] = await PredefinedCarrier.retrieve(sid);
|
||||
logger.debug({template}, `Retrieved template carrier for sid ${sid}`);
|
||||
if (!template) return res.sendStatus(404);
|
||||
|
||||
/* make sure not to add the same carrier twice */
|
||||
const [r2] = account_sid ?
|
||||
await promisePool.query(sqlSelectCarrierByName, [account_sid, template.name]) :
|
||||
await promisePool.query(sqlSelectCarrierByNameForSP, [service_provider_sid, template.name]);
|
||||
const [r2] = await promisePool.query(sqlSelectCarrierByNameForSP, [service_provider_sid, template.name]);
|
||||
|
||||
if (r2.length > 0) {
|
||||
template.name = `${template.name}-${short.generate()}`;
|
||||
|
||||
@@ -6,6 +6,7 @@ const Webhook = require('../../models/webhook');
|
||||
const {promisePool} = require('../../db');
|
||||
const decorate = require('./decorate');
|
||||
const sysError = require('../error');
|
||||
const jambonzAppJsonValidator = require('../../utils/jambonz-app-json-validation');
|
||||
const preconditions = {
|
||||
'add': validateAdd,
|
||||
'update': validateUpdate
|
||||
@@ -76,6 +77,12 @@ router.post('/', async(req, res) => {
|
||||
}
|
||||
}
|
||||
|
||||
// validate app json if required
|
||||
if (obj['app_json']) {
|
||||
const app_json = JSON.parse(obj['app_json']);
|
||||
jambonzAppJsonValidator(logger, app_json);
|
||||
}
|
||||
|
||||
const uuid = await Application.make(obj);
|
||||
res.status(201).json({sid: uuid});
|
||||
} catch (err) {
|
||||
@@ -179,6 +186,12 @@ router.put('/:sid', async(req, res) => {
|
||||
delete obj[prop];
|
||||
}
|
||||
|
||||
// validate app json if required
|
||||
if (obj['app_json']) {
|
||||
const app_json = JSON.parse(obj['app_json']);
|
||||
jambonzAppJsonValidator(logger, app_json);
|
||||
}
|
||||
|
||||
const rowsAffected = await Application.update(sid, obj);
|
||||
if (rowsAffected === 0) {
|
||||
return res.status(404).end();
|
||||
|
||||
@@ -12,7 +12,7 @@ const {hasServiceProviderPermissions, parseServiceProviderSid} = require('./util
|
||||
const sysError = require('../error');
|
||||
const decorate = require('./decorate');
|
||||
const preconditions = {
|
||||
'delete': noActiveAccounts
|
||||
'delete': noActiveAccountsOrUsers
|
||||
};
|
||||
const sqlDeleteSipGateways = `DELETE from sip_gateways
|
||||
WHERE voip_carrier_sid IN (
|
||||
@@ -59,12 +59,20 @@ function validateUpdate(req) {
|
||||
}
|
||||
|
||||
/* can not delete a service provider if it has any active accounts */
|
||||
async function noActiveAccounts(req, sid) {
|
||||
async function noActiveAccountsOrUsers(req, sid) {
|
||||
if (!req.user.hasAdminAuth) {
|
||||
throw new DbErrorForbidden('only admin users can delete a service provider');
|
||||
}
|
||||
const activeAccounts = await ServiceProvider.getForeignKeyReferences('accounts.service_provider_sid', sid);
|
||||
const activeUsers = await ServiceProvider.getForeignKeyReferences('users.service_provider_sid', sid);
|
||||
if (activeAccounts > 0 && activeUsers > 0) throw new DbErrorUnprocessableRequest(
|
||||
'cannot delete service provider with active accounts or users'
|
||||
);
|
||||
|
||||
if (activeAccounts > 0) throw new DbErrorUnprocessableRequest('cannot delete service provider with active accounts');
|
||||
if (activeUsers > 0) throw new DbErrorUnprocessableRequest(
|
||||
'cannot delete service provider with active service provider level users'
|
||||
);
|
||||
|
||||
/* ok we can delete -- no active accounts. remove carriers and speech credentials */
|
||||
await promisePool.execute('DELETE from speech_credentials WHERE service_provider_sid = ?', [sid]);
|
||||
@@ -127,8 +135,13 @@ router.get('/:sid/VoipCarriers', async(req, res) => {
|
||||
try {
|
||||
await validateRetrieve(req);
|
||||
const service_provider_sid = parseServiceProviderSid(req);
|
||||
const results = await VoipCarrier.retrieveAllForSP(service_provider_sid);
|
||||
res.status(200).json(results);
|
||||
const carriers = await VoipCarrier.retrieveAllForSP(service_provider_sid);
|
||||
|
||||
if (req.user.hasScope('account')) {
|
||||
return res.status(200).json(carriers.filter((c) => c.account_sid === req.user.account_sid || !c.account_sid));
|
||||
}
|
||||
|
||||
res.status(200).json(carriers);
|
||||
} catch (err) {
|
||||
sysError(logger, res, err);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
const router = require('express').Router();
|
||||
const assert = require('assert');
|
||||
const Account = require('../../models/account');
|
||||
const SpeechCredential = require('../../models/speech-credential');
|
||||
const sysError = require('../error');
|
||||
const {decrypt, encrypt} = require('../../utils/encrypt-decrypt');
|
||||
@@ -118,13 +119,13 @@ router.post('/', async(req, res) => {
|
||||
vendor,
|
||||
} = req.body;
|
||||
const account_sid = req.user.account_sid || req.body.account_sid;
|
||||
let service_provider_sid;
|
||||
const service_provider_sid = req.user.service_provider_sid ||
|
||||
req.body.service_provider_sid || parseServiceProviderSid(req);
|
||||
if (!account_sid) {
|
||||
if (!req.user.hasServiceProviderAuth && !req.user.hasAdminAuth) {
|
||||
logger.error('POST /SpeechCredentials invalid credentials');
|
||||
return res.sendStatus(403);
|
||||
}
|
||||
service_provider_sid = parseServiceProviderSid(req);
|
||||
}
|
||||
try {
|
||||
const encrypted_credential = encryptCredential(req.body);
|
||||
@@ -146,14 +147,20 @@ router.post('/', async(req, res) => {
|
||||
* retrieve all speech credentials for an account
|
||||
*/
|
||||
router.get('/', async(req, res) => {
|
||||
let service_provider_sid;
|
||||
const account_sid = parseAccountSid(req);
|
||||
if (!account_sid) service_provider_sid = parseServiceProviderSid(req);
|
||||
const account_sid = parseAccountSid(req) || req.user.account_sid;
|
||||
const service_provider_sid = parseServiceProviderSid(req) || req.user.service_provider_sid;
|
||||
const logger = req.app.locals.logger;
|
||||
try {
|
||||
const creds = account_sid ?
|
||||
await SpeechCredential.retrieveAll(account_sid) :
|
||||
await SpeechCredential.retrieveAllForSP(service_provider_sid);
|
||||
const credsAccount = account_sid ? await SpeechCredential.retrieveAll(account_sid) : [];
|
||||
const credsSP = service_provider_sid ?
|
||||
await SpeechCredential.retrieveAllForSP(service_provider_sid) :
|
||||
await SpeechCredential.retrieveAllForSP((await Account.retrieve(account_sid))[0].service_provider_sid);
|
||||
|
||||
// filter out duplicates and discard those from other non-matching accounts
|
||||
let creds = [...new Set([...credsAccount, ...credsSP].map((c) => JSON.stringify(c)))].map((c) => JSON.parse(c));
|
||||
if (req.user.hasScope('account')) {
|
||||
creds = creds.filter((c) => c.account_sid === req.user.account_sid || !c.account_sid);
|
||||
}
|
||||
|
||||
res.status(200).json(creds.map((c) => {
|
||||
const {credential, ...obj} = c;
|
||||
|
||||
@@ -285,6 +285,11 @@ router.put('/:user_sid', async(req, res) => {
|
||||
//debug(`PUT /Users/:sid pwd ${old_password} does not match hash ${old_hashed_password}`);
|
||||
return res.sendStatus(403);
|
||||
}
|
||||
|
||||
if (old_password === new_password) {
|
||||
throw new Error('new password cannot be your old password');
|
||||
}
|
||||
|
||||
const passwordHash = await generateHashedPassword(new_password);
|
||||
//debug(`updating hashed_password to ${passwordHash}`);
|
||||
const r = await promisePool.execute(updateSql, [passwordHash, user_sid]);
|
||||
@@ -305,12 +310,12 @@ router.put('/:user_sid', async(req, res) => {
|
||||
if (0 === r.changedRows) throw new Error('database update failed');
|
||||
}
|
||||
|
||||
if (is_active) {
|
||||
if (typeof is_active !== 'undefined') {
|
||||
const r = await promisePool.execute('UPDATE users SET is_active = ? WHERE user_sid = ?', [is_active, user_sid]);
|
||||
if (0 === r.changedRows) throw new Error('database update failed');
|
||||
}
|
||||
|
||||
if (force_change) {
|
||||
if (typeof force_change !== 'undefined') {
|
||||
const r = await promisePool.execute(
|
||||
'UPDATE users SET force_change = ? WHERE user_sid = ?',
|
||||
[force_change, user_sid]);
|
||||
@@ -368,9 +373,16 @@ router.post('/', async(req, res) => {
|
||||
|
||||
try {
|
||||
const email = allUsers.find((e) => e.email === payload.email);
|
||||
const name = allUsers.find((e) => e.name === payload.name);
|
||||
|
||||
if (name) {
|
||||
logger.debug({payload}, 'user with this username already exists');
|
||||
return res.status(422).json({msg: 'user with this username already exists'});
|
||||
}
|
||||
|
||||
if (email) {
|
||||
logger.debug({payload}, 'user with this email already exists');
|
||||
res.status(422).json({msg: 'user with this email already exists'});
|
||||
return res.status(422).json({msg: 'user with this email already exists'});
|
||||
}
|
||||
|
||||
if (decodedJwt.scope === 'admin') {
|
||||
@@ -412,7 +424,8 @@ router.delete('/:user_sid', async(req, res) => {
|
||||
const user = await User.retrieve(user_sid);
|
||||
|
||||
try {
|
||||
if (decodedJwt.scope === 'admin' && activeAdminUsers.length === 1) {
|
||||
if (decodedJwt.scope === 'admin' && !user.account_sid && !user.service_provider_sid &&
|
||||
activeAdminUsers.length === 1) {
|
||||
throw new Error('cannot delete this admin user - there are no other active admin users');
|
||||
}
|
||||
|
||||
|
||||
@@ -3382,6 +3382,9 @@ paths:
|
||||
messaging_hook:
|
||||
$ref: '#/components/schemas/Webhook'
|
||||
description: application webhook to handle inbound SMS/MMS messages
|
||||
app_json:
|
||||
type: string
|
||||
description: Voice Application Json, call_hook will not be invoked if app_json is provided
|
||||
speech_synthesis_vendor:
|
||||
type: string
|
||||
speech_synthesis_voice:
|
||||
|
||||
112
lib/utils/jambonz-app-json-validation.js
Normal file
112
lib/utils/jambonz-app-json-validation.js
Normal file
@@ -0,0 +1,112 @@
|
||||
const {DbErrorBadRequest} = require('../utils/errors');
|
||||
const assert = require('assert');
|
||||
const _specData = require('./specs');
|
||||
const specs = new Map();
|
||||
for (const key in _specData) { specs.set(key, _specData[key]); }
|
||||
|
||||
function normalizeJambones(logger, obj) {
|
||||
if (!Array.isArray(obj)) throw new DbErrorBadRequest('malformed jambonz payload: must be array');
|
||||
const document = [];
|
||||
for (const tdata of obj) {
|
||||
if (typeof tdata !== 'object') throw new DbErrorBadRequest('malformed jambonz payload: must be array of objects');
|
||||
if ('verb' in tdata) {
|
||||
// {verb: 'say', text: 'foo..bar'..}
|
||||
const name = tdata.verb;
|
||||
const o = {};
|
||||
Object.keys(tdata)
|
||||
.filter((k) => k !== 'verb')
|
||||
.forEach((k) => o[k] = tdata[k]);
|
||||
const o2 = {};
|
||||
o2[name] = o;
|
||||
document.push(o2);
|
||||
}
|
||||
else if (Object.keys(tdata).length === 1) {
|
||||
// {'say': {..}}
|
||||
document.push(tdata);
|
||||
}
|
||||
else {
|
||||
logger.info(tdata, 'malformed jambonz payload: missing verb property');
|
||||
throw new DbErrorBadRequest('malformed jambonz payload: missing verb property');
|
||||
}
|
||||
}
|
||||
logger.debug({ document }, `normalizeJambones: returning document with ${document.length} tasks`);
|
||||
return document;
|
||||
}
|
||||
|
||||
function validate(logger, obj) {
|
||||
normalizeJambones(logger, obj).map((tdata) => {
|
||||
const keys = Object.keys(tdata);
|
||||
const name = keys[0];
|
||||
const data = tdata[name];
|
||||
validateVerb(name, data, logger);
|
||||
});
|
||||
}
|
||||
|
||||
function validateVerb(name, data, logger) {
|
||||
logger.debug(`validating ${name} with data ${JSON.stringify(data)}`);
|
||||
// validate the instruction is supported
|
||||
if (!specs.has(name)) throw new DbErrorBadRequest(`invalid instruction: ${name}`);
|
||||
|
||||
// check type of each element and make sure required elements are present
|
||||
const specData = specs.get(name);
|
||||
let required = specData.required || [];
|
||||
for (const dKey in data) {
|
||||
if (dKey in specData.properties) {
|
||||
const dVal = data[dKey];
|
||||
const dSpec = specData.properties[dKey];
|
||||
logger.debug(`Task:validate validating property ${dKey} with value ${JSON.stringify(dVal)}`);
|
||||
|
||||
if (typeof dSpec === 'string' && dSpec === 'array') {
|
||||
if (!Array.isArray(dVal)) throw new DbErrorBadRequest(`${name}: property ${dKey} is not an array`);
|
||||
}
|
||||
else if (typeof dSpec === 'string' && dSpec.includes('|')) {
|
||||
const types = dSpec.split('|').map((t) => t.trim());
|
||||
if (!types.includes(typeof dVal) && !(types.includes('array') && Array.isArray(dVal))) {
|
||||
throw new DbErrorBadRequest(`${name}: property ${dKey} has invalid data type, must be one of ${types}`);
|
||||
}
|
||||
}
|
||||
else if (typeof dSpec === 'string' && ['number', 'string', 'object', 'boolean'].includes(dSpec)) {
|
||||
// simple types
|
||||
if (typeof dVal !== specData.properties[dKey]) {
|
||||
throw new DbErrorBadRequest(`${name}: property ${dKey} has invalid data type`);
|
||||
}
|
||||
}
|
||||
else if (Array.isArray(dSpec) && dSpec[0].startsWith('#')) {
|
||||
const name = dSpec[0].slice(1);
|
||||
for (const item of dVal) {
|
||||
validate(name, item);
|
||||
}
|
||||
}
|
||||
else if (typeof dSpec === 'object') {
|
||||
// complex types
|
||||
const type = dSpec.type;
|
||||
assert.ok(['number', 'string', 'object', 'boolean'].includes(type),
|
||||
`invalid or missing type in spec ${JSON.stringify(dSpec)}`);
|
||||
if (type === 'string' && dSpec.enum) {
|
||||
assert.ok(Array.isArray(dSpec.enum), `enum must be an array ${JSON.stringify(dSpec.enum)}`);
|
||||
if (!dSpec.enum.includes(dVal)) {
|
||||
throw new DbErrorBadRequest(`invalid value ${dVal} must be one of ${dSpec.enum}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (typeof dSpec === 'string' && dSpec.startsWith('#')) {
|
||||
// reference to another datatype (i.e. nested type)
|
||||
const name = dSpec.slice(1);
|
||||
//const obj = {};
|
||||
//obj[name] = dVal;
|
||||
validate(name, dVal);
|
||||
}
|
||||
else {
|
||||
assert.ok(0, `invalid spec ${JSON.stringify(dSpec)}`);
|
||||
}
|
||||
required = required.filter((item) => item !== dKey);
|
||||
}
|
||||
else if (dKey === '_') {
|
||||
/* no op: allow arbitrary info to be carried here, used by conference e.g in transfer */
|
||||
}
|
||||
else throw new DbErrorBadRequest(`${name}: unknown property ${dKey}`);
|
||||
}
|
||||
if (required.length > 0) throw new DbErrorBadRequest(`${name}: missing value for ${required}`);
|
||||
}
|
||||
|
||||
module.exports = validate;
|
||||
759
lib/utils/specs.json
Normal file
759
lib/utils/specs.json
Normal file
@@ -0,0 +1,759 @@
|
||||
{
|
||||
"sip:decline": {
|
||||
"properties": {
|
||||
"id": "string",
|
||||
"status": "number",
|
||||
"reason": "string",
|
||||
"headers": "object"
|
||||
},
|
||||
"required": [
|
||||
"status"
|
||||
]
|
||||
},
|
||||
"sip:request": {
|
||||
"properties": {
|
||||
"id": "string",
|
||||
"method": "string",
|
||||
"body": "string",
|
||||
"headers": "object",
|
||||
"actionHook": "object|string"
|
||||
},
|
||||
"required": [
|
||||
"method"
|
||||
]
|
||||
},
|
||||
"sip:refer": {
|
||||
"properties": {
|
||||
"id": "string",
|
||||
"referTo": "string",
|
||||
"referredBy": "string",
|
||||
"headers": "object",
|
||||
"actionHook": "object|string",
|
||||
"eventHook": "object|string"
|
||||
},
|
||||
"required": [
|
||||
"referTo"
|
||||
]
|
||||
},
|
||||
"config": {
|
||||
"properties": {
|
||||
"id": "string",
|
||||
"synthesizer": "#synthesizer",
|
||||
"recognizer": "#recognizer",
|
||||
"bargeIn": "#bargeIn",
|
||||
"record": "#recordOptions",
|
||||
"amd": "#amd",
|
||||
"notifyEvents": "boolean"
|
||||
},
|
||||
"required": []
|
||||
},
|
||||
"bargeIn": {
|
||||
"properties": {
|
||||
"enable": "boolean",
|
||||
"sticky": "boolean",
|
||||
"actionHook": "object|string",
|
||||
"input": "array",
|
||||
"finishOnKey": "string",
|
||||
"numDigits": "number",
|
||||
"minDigits": "number",
|
||||
"maxDigits": "number",
|
||||
"interDigitTimeout": "number",
|
||||
"dtmfBargein": "boolean",
|
||||
"minBargeinWordCount": "number"
|
||||
},
|
||||
"required": [
|
||||
"enable"
|
||||
]
|
||||
},
|
||||
"dequeue": {
|
||||
"properties": {
|
||||
"id": "string",
|
||||
"name": "string",
|
||||
"actionHook": "object|string",
|
||||
"timeout": "number",
|
||||
"beep": "boolean"
|
||||
},
|
||||
"required": [
|
||||
"name"
|
||||
]
|
||||
},
|
||||
"enqueue": {
|
||||
"properties": {
|
||||
"id": "string",
|
||||
"name": "string",
|
||||
"actionHook": "object|string",
|
||||
"waitHook": "object|string",
|
||||
"_": "object"
|
||||
},
|
||||
"required": [
|
||||
"name"
|
||||
]
|
||||
},
|
||||
"leave": {
|
||||
"properties": {
|
||||
"id": "string"
|
||||
}
|
||||
},
|
||||
"hangup": {
|
||||
"properties": {
|
||||
"id": "string",
|
||||
"headers": "object"
|
||||
},
|
||||
"required": [
|
||||
]
|
||||
},
|
||||
"play": {
|
||||
"properties": {
|
||||
"id": "string",
|
||||
"url": "string|array",
|
||||
"loop": "number|string",
|
||||
"earlyMedia": "boolean",
|
||||
"seekOffset": "number|string",
|
||||
"timeoutSecs": "number|string",
|
||||
"actionHook": "object|string"
|
||||
},
|
||||
"required": [
|
||||
"url"
|
||||
]
|
||||
},
|
||||
"say": {
|
||||
"properties": {
|
||||
"id": "string",
|
||||
"text": "string|array",
|
||||
"loop": "number|string",
|
||||
"synthesizer": "#synthesizer",
|
||||
"earlyMedia": "boolean",
|
||||
"disableTtsCache": "boolean"
|
||||
},
|
||||
"required": [
|
||||
"text"
|
||||
]
|
||||
},
|
||||
"gather": {
|
||||
"properties": {
|
||||
"id": "string",
|
||||
"actionHook": "object|string",
|
||||
"finishOnKey": "string",
|
||||
"input": "array",
|
||||
"numDigits": "number",
|
||||
"minDigits": "number",
|
||||
"maxDigits": "number",
|
||||
"interDigitTimeout": "number",
|
||||
"partialResultHook": "object|string",
|
||||
"speechTimeout": "number",
|
||||
"listenDuringPrompt": "boolean",
|
||||
"dtmfBargein": "boolean",
|
||||
"bargein": "boolean",
|
||||
"minBargeinWordCount": "number",
|
||||
"timeout": "number",
|
||||
"recognizer": "#recognizer",
|
||||
"play": "#play",
|
||||
"say": "#say"
|
||||
},
|
||||
"required": [
|
||||
]
|
||||
},
|
||||
"conference": {
|
||||
"properties": {
|
||||
"id": "string",
|
||||
"name": "string",
|
||||
"beep": "boolean",
|
||||
"startConferenceOnEnter": "boolean",
|
||||
"endConferenceOnExit": "boolean",
|
||||
"maxParticipants": "number",
|
||||
"joinMuted": "boolean",
|
||||
"actionHook": "object|string",
|
||||
"waitHook": "object|string",
|
||||
"statusEvents": "array",
|
||||
"statusHook": "object|string",
|
||||
"enterHook": "object|string",
|
||||
"record": "#record"
|
||||
},
|
||||
"required": [
|
||||
"name"
|
||||
]
|
||||
},
|
||||
"dial": {
|
||||
"properties": {
|
||||
"id": "string",
|
||||
"actionHook": "object|string",
|
||||
"answerOnBridge": "boolean",
|
||||
"callerId": "string",
|
||||
"confirmHook": "object|string",
|
||||
"referHook": "object|string",
|
||||
"dialMusic": "string",
|
||||
"dtmfCapture": "object",
|
||||
"dtmfHook": "object|string",
|
||||
"headers": "object",
|
||||
"listen": "#listen",
|
||||
"target": ["#target"],
|
||||
"timeLimit": "number",
|
||||
"timeout": "number",
|
||||
"proxy": "string",
|
||||
"transcribe": "#transcribe",
|
||||
"amd": "#amd"
|
||||
},
|
||||
"required": [
|
||||
"target"
|
||||
]
|
||||
},
|
||||
"dialogflow": {
|
||||
"properties": {
|
||||
"id": "string",
|
||||
"credentials": "object|string",
|
||||
"project": "string",
|
||||
"environment": "string",
|
||||
"region": {
|
||||
"type": "string",
|
||||
"enum": ["europe-west1", "europe-west2", "australia-southeast1", "asia-northeast1"]
|
||||
},
|
||||
"lang": "string",
|
||||
"actionHook": "object|string",
|
||||
"eventHook": "object|string",
|
||||
"events": "[string]",
|
||||
"welcomeEvent": "string",
|
||||
"welcomeEventParams": "object",
|
||||
"noInputTimeout": "number",
|
||||
"noInputEvent": "string",
|
||||
"passDtmfAsTextInput": "boolean",
|
||||
"thinkingMusic": "string",
|
||||
"tts": "#synthesizer",
|
||||
"bargein": "boolean"
|
||||
},
|
||||
"required": [
|
||||
"project",
|
||||
"credentials",
|
||||
"lang"
|
||||
]
|
||||
},
|
||||
"dtmf": {
|
||||
"properties": {
|
||||
"id": "string",
|
||||
"dtmf": "string",
|
||||
"duration": "number"
|
||||
},
|
||||
"required": [
|
||||
"dtmf"
|
||||
]
|
||||
},
|
||||
"lex": {
|
||||
"properties": {
|
||||
"id": "string",
|
||||
"botId": "string",
|
||||
"botAlias": "string",
|
||||
"credentials": "object",
|
||||
"region": "string",
|
||||
"locale": "string",
|
||||
"intent": "#lexIntent",
|
||||
"welcomeMessage": "string",
|
||||
"metadata": "object",
|
||||
"bargein": "boolean",
|
||||
"passDtmf": "boolean",
|
||||
"actionHook": "object|string",
|
||||
"eventHook": "object|string",
|
||||
"noInputTimeout": "number",
|
||||
"tts": "#synthesizer"
|
||||
},
|
||||
"required": [
|
||||
"botId",
|
||||
"botAlias",
|
||||
"region",
|
||||
"credentials"
|
||||
]
|
||||
},
|
||||
"listen": {
|
||||
"properties": {
|
||||
"id": "string",
|
||||
"actionHook": "object|string",
|
||||
"auth": "#auth",
|
||||
"finishOnKey": "string",
|
||||
"maxLength": "number",
|
||||
"metadata": "object",
|
||||
"mixType": {
|
||||
"type": "string",
|
||||
"enum": ["mono", "stereo", "mixed"]
|
||||
},
|
||||
"passDtmf": "boolean",
|
||||
"playBeep": "boolean",
|
||||
"sampleRate": "number",
|
||||
"timeout": "number",
|
||||
"transcribe": "#transcribe",
|
||||
"url": "string",
|
||||
"wsAuth": "#auth",
|
||||
"earlyMedia": "boolean"
|
||||
},
|
||||
"required": [
|
||||
"url"
|
||||
]
|
||||
},
|
||||
"message": {
|
||||
"properties": {
|
||||
"id": "string",
|
||||
"carrier": "string",
|
||||
"account_sid": "string",
|
||||
"message_sid": "string",
|
||||
"to": "string",
|
||||
"from": "string",
|
||||
"text": "string",
|
||||
"media": "string|array",
|
||||
"actionHook": "object|string"
|
||||
},
|
||||
"required": [
|
||||
"to",
|
||||
"from"
|
||||
]
|
||||
},
|
||||
"pause": {
|
||||
"properties": {
|
||||
"id": "string",
|
||||
"length": "number"
|
||||
},
|
||||
"required": [
|
||||
"length"
|
||||
]
|
||||
},
|
||||
"rasa": {
|
||||
"properties": {
|
||||
"id": "string",
|
||||
"url": "string",
|
||||
"recognizer": "#recognizer",
|
||||
"tts": "#synthesizer",
|
||||
"prompt": "string",
|
||||
"actionHook": "object|string",
|
||||
"eventHook": "object|string"
|
||||
},
|
||||
"required": [
|
||||
"url"
|
||||
]
|
||||
},
|
||||
"record": {
|
||||
"properties": {
|
||||
"path": "string"
|
||||
},
|
||||
"required": [
|
||||
"path"
|
||||
]
|
||||
},
|
||||
"recordOptions": {
|
||||
"properties": {
|
||||
"action": {
|
||||
"type": "string",
|
||||
"enum": ["startCallRecording", "stopCallRecording", "pauseCallRecording", "resumeCallRecording"]
|
||||
},
|
||||
"recordingID": "string",
|
||||
"siprecServerURL": "string"
|
||||
},
|
||||
"required": [
|
||||
"action"
|
||||
]
|
||||
},
|
||||
"redirect": {
|
||||
"properties": {
|
||||
"id": "string",
|
||||
"actionHook": "object|string"
|
||||
},
|
||||
"required": [
|
||||
"actionHook"
|
||||
]
|
||||
},
|
||||
"rest:dial": {
|
||||
"properties": {
|
||||
"id": "string",
|
||||
"account_sid": "string",
|
||||
"application_sid": "string",
|
||||
"call_hook": "object|string",
|
||||
"call_status_hook": "object|string",
|
||||
"from": "string",
|
||||
"fromHost": "string",
|
||||
"speech_synthesis_vendor": "string",
|
||||
"speech_synthesis_voice": "string",
|
||||
"speech_synthesis_language": "string",
|
||||
"speech_recognizer_vendor": "string",
|
||||
"speech_recognizer_language": "string",
|
||||
"tag": "object",
|
||||
"to": "#target",
|
||||
"headers": "object",
|
||||
"timeout": "number"
|
||||
},
|
||||
"required": [
|
||||
"call_hook",
|
||||
"from",
|
||||
"to"
|
||||
]
|
||||
},
|
||||
"tag": {
|
||||
"properties": {
|
||||
"id": "string",
|
||||
"data": "object"
|
||||
},
|
||||
"required": [
|
||||
"data"
|
||||
]
|
||||
},
|
||||
"transcribe": {
|
||||
"properties": {
|
||||
"id": "string",
|
||||
"transcriptionHook": "string",
|
||||
"recognizer": "#recognizer",
|
||||
"earlyMedia": "boolean"
|
||||
},
|
||||
"required": [
|
||||
"recognizer"
|
||||
]
|
||||
},
|
||||
"target": {
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": ["phone", "sip", "user", "teams"]
|
||||
},
|
||||
"confirmHook": "object|string",
|
||||
"method": {
|
||||
"type": "string",
|
||||
"enum": ["GET", "POST"]
|
||||
},
|
||||
"headers": "object",
|
||||
"from": "#dialFrom",
|
||||
"name": "string",
|
||||
"number": "string",
|
||||
"sipUri": "string",
|
||||
"auth": "#auth",
|
||||
"vmail": "boolean",
|
||||
"tenant": "string",
|
||||
"trunk": "string",
|
||||
"overrideTo": "string"
|
||||
},
|
||||
"required": [
|
||||
"type"
|
||||
]
|
||||
},
|
||||
"dialFrom": {
|
||||
"properties": {
|
||||
"user": "string",
|
||||
"host": "string"
|
||||
},
|
||||
"required": [
|
||||
]
|
||||
},
|
||||
"auth": {
|
||||
"properties": {
|
||||
"username": "string",
|
||||
"password": "string"
|
||||
},
|
||||
"required": [
|
||||
"username",
|
||||
"password"
|
||||
]
|
||||
},
|
||||
"synthesizer": {
|
||||
"properties": {
|
||||
"vendor": {
|
||||
"type": "string",
|
||||
"enum": ["google", "aws", "polly", "microsoft", "nuance", "ibm", "default"]
|
||||
},
|
||||
"language": "string",
|
||||
"voice": "string",
|
||||
"engine": {
|
||||
"type": "string",
|
||||
"enum": ["standard", "neural"]
|
||||
},
|
||||
"gender": {
|
||||
"type": "string",
|
||||
"enum": ["MALE", "FEMALE", "NEUTRAL"]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"vendor"
|
||||
]
|
||||
},
|
||||
"recognizer": {
|
||||
"properties": {
|
||||
"vendor": {
|
||||
"type": "string",
|
||||
"enum": ["google", "aws", "microsoft", "nuance", "deepgram", "ibm", "default"]
|
||||
},
|
||||
"language": "string",
|
||||
"vad": "#vad",
|
||||
"hints": "array",
|
||||
"hintsBoost": "number",
|
||||
"altLanguages": "array",
|
||||
"profanityFilter": "boolean",
|
||||
"interim": "boolean",
|
||||
"singleUtterance": "boolean",
|
||||
"dualChannel": "boolean",
|
||||
"separateRecognitionPerChannel": "boolean",
|
||||
"punctuation": "boolean",
|
||||
"enhancedModel": "boolean",
|
||||
"words": "boolean",
|
||||
"diarization": "boolean",
|
||||
"diarizationMinSpeakers": "number",
|
||||
"diarizationMaxSpeakers": "number",
|
||||
"interactionType": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"unspecified",
|
||||
"discussion",
|
||||
"presentation",
|
||||
"phone_call",
|
||||
"voicemail",
|
||||
"voice_search",
|
||||
"voice_command",
|
||||
"dictation"
|
||||
]
|
||||
},
|
||||
"naicsCode": "number",
|
||||
"identifyChannels": "boolean",
|
||||
"vocabularyName": "string",
|
||||
"vocabularyFilterName": "string",
|
||||
"filterMethod": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"remove",
|
||||
"mask",
|
||||
"tag"
|
||||
]
|
||||
},
|
||||
"model": "string",
|
||||
"outputFormat": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"simple",
|
||||
"detailed"
|
||||
]
|
||||
},
|
||||
"profanityOption": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"masked",
|
||||
"removed",
|
||||
"raw"
|
||||
]
|
||||
},
|
||||
"requestSnr": "boolean",
|
||||
"initialSpeechTimeoutMs": "number",
|
||||
"azureServiceEndpoint": "string",
|
||||
"azureSttEndpointId": "string",
|
||||
"asrDtmfTerminationDigit": "string",
|
||||
"asrTimeout": "number",
|
||||
"nuanceOptions": "#nuanceOptions",
|
||||
"deepgramOptions": "#deepgramOptions",
|
||||
"ibmOptions": "#ibmOptions"
|
||||
},
|
||||
"required": [
|
||||
"vendor"
|
||||
]
|
||||
},
|
||||
"ibmOptions": {
|
||||
"properties": {
|
||||
"sttApiKey": "string",
|
||||
"sttRegion": "string",
|
||||
"ttsApiKey": "string",
|
||||
"ttsRegion": "string",
|
||||
"instanceId": "string",
|
||||
"model": "string",
|
||||
"languageCustomizationId": "string",
|
||||
"acousticCustomizationId": "string",
|
||||
"baseModelVersion": "string",
|
||||
"watsonMetadata": "string",
|
||||
"watsonLearningOptOut": "boolean"
|
||||
},
|
||||
"required": [
|
||||
]
|
||||
},
|
||||
"deepgramOptions": {
|
||||
"properties": {
|
||||
"apiKey": "string",
|
||||
"tier": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"enhanced",
|
||||
"base"
|
||||
]
|
||||
},
|
||||
"model": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"general",
|
||||
"meeting",
|
||||
"phonecall",
|
||||
"voicemail",
|
||||
"finance",
|
||||
"conversationalai",
|
||||
"video",
|
||||
"custom"
|
||||
]
|
||||
},
|
||||
"customModel": "string",
|
||||
"version": "string",
|
||||
"punctuate": "boolean",
|
||||
"profanityFilter": "boolean",
|
||||
"redact": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"pci",
|
||||
"numbers",
|
||||
"true",
|
||||
"ssn"
|
||||
]
|
||||
},
|
||||
"diarize": "boolean",
|
||||
"diarizeVersion": "string",
|
||||
"ner": "boolean",
|
||||
"multichannel": "boolean",
|
||||
"alternatives": "number",
|
||||
"numerals": "boolean",
|
||||
"search": "array",
|
||||
"replace": "array",
|
||||
"keywords": "array",
|
||||
"endpointing": "boolean",
|
||||
"vadTurnoff": "number",
|
||||
"tag": "string"
|
||||
}
|
||||
},
|
||||
"nuanceOptions": {
|
||||
"properties": {
|
||||
"clientId": "string",
|
||||
"secret": "string",
|
||||
"kryptonEndpoint": "string",
|
||||
"topic": "string",
|
||||
"utteranceDetectionMode": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"single",
|
||||
"multiple",
|
||||
"disabled"
|
||||
]
|
||||
},
|
||||
"punctuation": "boolean",
|
||||
"profanityFilter": "boolean",
|
||||
"includeTokenization": "boolean",
|
||||
"discardSpeakerAdaptation": "boolean",
|
||||
"suppressCallRecording": "boolean",
|
||||
"maskLoadFailures": "boolean",
|
||||
"suppressInitialCapitalization": "boolean",
|
||||
"allowZeroBaseLmWeight": "boolean",
|
||||
"filterWakeupWord": "boolean",
|
||||
"resultType": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"final",
|
||||
"partial",
|
||||
"immutable_partial"
|
||||
]
|
||||
},
|
||||
"noInputTimeoutMs": "number",
|
||||
"recognitionTimeoutMs": "number",
|
||||
"utteranceEndSilenceMs": "number",
|
||||
"maxHypotheses": "number",
|
||||
"speechDomain": "string",
|
||||
"formatting": "#formatting",
|
||||
"clientData": "object",
|
||||
"userId": "string",
|
||||
"speechDetectionSensitivity": "number",
|
||||
"resources": ["#resource"]
|
||||
},
|
||||
"required": [
|
||||
]
|
||||
},
|
||||
"resource": {
|
||||
"properties": {
|
||||
"externalReference": "#resourceReference",
|
||||
"inlineWordset": "string",
|
||||
"builtin": "string",
|
||||
"inlineGrammar": "string",
|
||||
"wakeupWord": "[string]",
|
||||
"weightName": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"defaultWeight",
|
||||
"lowest",
|
||||
"low",
|
||||
"medium",
|
||||
"high",
|
||||
"highest"
|
||||
]
|
||||
},
|
||||
"weightValue": "number",
|
||||
"reuse": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"undefined_reuse",
|
||||
"low_reuse",
|
||||
"high_reuse"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
]
|
||||
},
|
||||
"resourceReference": {
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"undefined_resource_type",
|
||||
"wordset",
|
||||
"compiled_wordset",
|
||||
"domain_lm",
|
||||
"speaker_profile",
|
||||
"grammar",
|
||||
"settings"
|
||||
]
|
||||
},
|
||||
"uri": "string",
|
||||
"maxLoadFailures": "boolean",
|
||||
"requestTimeoutMs": "number",
|
||||
"headers": "object"
|
||||
},
|
||||
"required": [
|
||||
]
|
||||
},
|
||||
"formatting": {
|
||||
"properties": {
|
||||
"scheme": "string",
|
||||
"options": "object"
|
||||
},
|
||||
"required": [
|
||||
"scheme",
|
||||
"options"
|
||||
]
|
||||
},
|
||||
"lexIntent": {
|
||||
"properties": {
|
||||
"name": "string",
|
||||
"slots": "object"
|
||||
},
|
||||
"required": [
|
||||
"name"
|
||||
]
|
||||
},
|
||||
"vad": {
|
||||
"properties": {
|
||||
"enable": "boolean",
|
||||
"voiceMs": "number",
|
||||
"mode": "number"
|
||||
},
|
||||
"required": [
|
||||
"enable"
|
||||
]
|
||||
},
|
||||
"amd": {
|
||||
"properties": {
|
||||
"actionHook": "object|string",
|
||||
"thresholdWordCount": "number",
|
||||
"timers": "#amdTimers",
|
||||
"recognizer": "#recognizer"
|
||||
},
|
||||
"required": [
|
||||
"actionHook"
|
||||
]
|
||||
},
|
||||
"amdTimers": {
|
||||
"properties": {
|
||||
"noSpeechTimeoutMs": "number",
|
||||
"decisionTimeoutMs": "number",
|
||||
"toneTimeoutMs": "number",
|
||||
"greetingCompletionTimeoutMs": "number"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,37 @@ test('application tests', async(t) => {
|
||||
const service_provider_sid = await createServiceProvider(request);
|
||||
const phone_number_sid = await createPhoneNumber(request, voip_carrier_sid);
|
||||
const account_sid = await createAccount(request, service_provider_sid);
|
||||
|
||||
/* add an invalid application app_json */
|
||||
result = await request.post('/Applications', {
|
||||
resolveWithFullResponse: true,
|
||||
simple: false,
|
||||
auth: authAdmin,
|
||||
json: true,
|
||||
body: {
|
||||
name: 'daveh',
|
||||
account_sid,
|
||||
call_hook: {
|
||||
url: 'http://example.com'
|
||||
},
|
||||
call_status_hook: {
|
||||
url: 'http://example.com/status',
|
||||
method: 'POST'
|
||||
},
|
||||
messaging_hook: {
|
||||
url: 'http://example.com/sms'
|
||||
},
|
||||
app_json : '[\
|
||||
{\
|
||||
"verb": "play",\
|
||||
"timeoutSecs": 10,\
|
||||
"seekOffset": 8000,\
|
||||
"actionHook": "/play/action"\
|
||||
}\
|
||||
]'
|
||||
}
|
||||
});
|
||||
t.ok(result.statusCode === 400, 'Cant create application with invalid app_josn');
|
||||
|
||||
/* add an application */
|
||||
result = await request.post('/Applications', {
|
||||
@@ -41,7 +72,16 @@ test('application tests', async(t) => {
|
||||
},
|
||||
messaging_hook: {
|
||||
url: 'http://example.com/sms'
|
||||
}
|
||||
},
|
||||
app_json : '[\
|
||||
{\
|
||||
"verb": "play",\
|
||||
"url": "https://example.com/example.mp3",\
|
||||
"timeoutSecs": 10,\
|
||||
"seekOffset": 8000,\
|
||||
"actionHook": "/play/action"\
|
||||
}\
|
||||
]'
|
||||
}
|
||||
});
|
||||
t.ok(result.statusCode === 201, 'successfully created application');
|
||||
@@ -62,6 +102,9 @@ test('application tests', async(t) => {
|
||||
});
|
||||
t.ok(result.name === 'daveh' , 'successfully retrieved application by sid');
|
||||
t.ok(result.messaging_hook.url === 'http://example.com/sms' , 'successfully retrieved messaging_hook from application');
|
||||
let app_json = JSON.parse(result.app_json);
|
||||
t.ok(app_json[0].verb === 'play', 'successfully retrieved app_json from application')
|
||||
|
||||
|
||||
/* update applications */
|
||||
result = await request.put(`/Applications/${sid}`, {
|
||||
@@ -74,7 +117,15 @@ test('application tests', async(t) => {
|
||||
},
|
||||
messaging_hook: {
|
||||
url: 'http://example2.com/mms'
|
||||
}
|
||||
},
|
||||
app_json : '[\
|
||||
{\
|
||||
"verb": "hangup",\
|
||||
"headers": {\
|
||||
"X-Reason" : "maximum call duration exceeded"\
|
||||
}\
|
||||
}\
|
||||
]'
|
||||
}
|
||||
});
|
||||
t.ok(result.statusCode === 204, 'successfully updated application');
|
||||
@@ -85,6 +136,57 @@ test('application tests', async(t) => {
|
||||
json: true,
|
||||
});
|
||||
t.ok(result.messaging_hook.url === 'http://example2.com/mms' , 'successfully updated messaging_hook');
|
||||
app_json = JSON.parse(result.app_json);
|
||||
t.ok(app_json[0].verb === 'hangup', 'successfully updated app_json from application')
|
||||
|
||||
/* remove applications app_json*/
|
||||
result = await request.put(`/Applications/${sid}`, {
|
||||
auth: authAdmin,
|
||||
json: true,
|
||||
resolveWithFullResponse: true,
|
||||
body: {
|
||||
call_hook: {
|
||||
url: 'http://example2.com'
|
||||
},
|
||||
messaging_hook: {
|
||||
url: 'http://example2.com/mms'
|
||||
},
|
||||
app_json : null
|
||||
}
|
||||
});
|
||||
t.ok(result.statusCode === 204, 'successfully updated application');
|
||||
|
||||
/* validate messaging hook was updated */
|
||||
result = await request.get(`/Applications/${sid}`, {
|
||||
auth: authAdmin,
|
||||
json: true,
|
||||
});
|
||||
t.ok(result.app_json == undefined, 'successfully removed app_json from application')
|
||||
|
||||
/* Update invalid applications app_json*/
|
||||
result = await request.put(`/Applications/${sid}`, {
|
||||
auth: authAdmin,
|
||||
json: true,
|
||||
resolveWithFullResponse: true,
|
||||
simple: false,
|
||||
body: {
|
||||
call_hook: {
|
||||
url: 'http://example2.com'
|
||||
},
|
||||
messaging_hook: {
|
||||
url: 'http://example2.com/mms'
|
||||
},
|
||||
app_json : '[\
|
||||
{\
|
||||
"verb": "play",\
|
||||
"timeoutSecs": 10,\
|
||||
"seekOffset": 8000,\
|
||||
"actionHook": "/play/action"\
|
||||
}\
|
||||
]'
|
||||
}
|
||||
});
|
||||
t.ok(result.statusCode === 400, 'Cant update invalid application app_json');
|
||||
|
||||
/* assign phone number to application */
|
||||
result = await request.put(`/PhoneNumbers/${phone_number_sid}`, {
|
||||
|
||||
@@ -86,7 +86,7 @@ test('service provider tests', async(t) => {
|
||||
auth: authAdmin,
|
||||
json: true,
|
||||
});
|
||||
console.log(JSON.stringify(result));
|
||||
//console.log(JSON.stringify(result));
|
||||
t.ok(result.length === 2 , 'successfully queried all service providers');
|
||||
|
||||
/* query one service providers */
|
||||
|
||||
Reference in New Issue
Block a user