Compare commits

..

7 Commits

Author SHA1 Message Date
Dave Horton
6d3fa96e04 add inbound and outbound messaging support 2020-10-09 08:02:33 -04:00
Dave Horton
6efbc49d4f sms working for 3 providers 2020-10-07 14:40:56 -04:00
Dave Horton
cff276402f update deps to clear warnings 2020-10-02 14:01:34 -04:00
Dave Horton
d9f372000e outbound SMS now working 2020-10-02 13:49:46 -04:00
Dave Horton
4da1f4f89d bugfix: createCall REST API to Teams endpoint was being blocked 2020-09-30 15:38:19 -04:00
Dave Horton
8a91333725 inbound SMS 2020-09-30 15:28:12 -04:00
Dave Horton
ee27cf41cb initial changes to support messaging 2020-09-29 15:45:08 -04:00
36 changed files with 164 additions and 715 deletions

View File

@@ -8,7 +8,7 @@
"jsx": false,
"modules": false
},
"ecmaVersion": 2018
"ecmaVersion": 2017
},
"plugins": ["promise"],
"rules": {

View File

@@ -1,19 +0,0 @@
name: CI
on:
push:
workflow_dispatch:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
with:
node-version: 12
- run: npm install
- run: npm run jslint
- run: npm test

8
.travis.yml Normal file
View File

@@ -0,0 +1,8 @@
dist: bionic
language: node_js
node_js:
- "lts/*"
services:
- mysql
script:
- npm test

View File

@@ -1,16 +1,13 @@
FROM node:alpine as builder
RUN apk update && apk add --no-cache python make g++
WORKDIR /opt/app/
COPY package.json ./
RUN npm install
RUN npm prune
FROM node:lts-alpine
FROM node:alpine as app
WORKDIR /opt/app
COPY . /opt/app
COPY --from=builder /opt/app/node_modules ./node_modules
RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app
ARG NODE_ENV
ENV NODE_ENV $NODE_ENV
COPY package.json /usr/src/app/
RUN npm install
COPY . /usr/src/app
CMD [ "npm", "start" ]

View File

@@ -1,4 +1,4 @@
# jambonz-api-server ![Build Status](https://github.com/jambonz/jambonz-api-server/workflows/CI/badge.svg)
# jambones-api-server [![Build Status](https://secure.travis-ci.org/jambonz/jambones-api-server.png)](http://travis-ci.org/jambonz/jambones-api-server)
Jambones REST API server.

5
app.js
View File

@@ -24,8 +24,6 @@ const {
retrieveCall,
deleteCall,
listCalls,
pushBack,
listQueues,
purgeCalls,
retrieveSet
} = require('@jambonz/realtimedb-helpers')({
@@ -40,7 +38,6 @@ const {
} = require('@jambonz/db-helpers')({
host: process.env.JAMBONES_MYSQL_HOST,
user: process.env.JAMBONES_MYSQL_USER,
port: process.env.JAMBONES_MYSQL_PORT || 3306,
password: process.env.JAMBONES_MYSQL_PASSWORD,
database: process.env.JAMBONES_MYSQL_DATABASE,
connectionLimit: process.env.JAMBONES_MYSQL_CONNECTION_LIMIT || 10
@@ -55,8 +52,6 @@ Object.assign(app.locals, {
retrieveCall,
deleteCall,
listCalls,
pushBack,
listQueues,
purgeCalls,
retrieveSet,
lookupAppBySid,

View File

@@ -1,3 +1,3 @@
create database jambones_test;
create user jambones_test@'%' IDENTIFIED WITH mysql_native_password by 'jambones_test';
grant all on jambones_test.* to jambones_test@'%';
create user jambones_test@localhost IDENTIFIED WITH mysql_native_password by 'jambones_test';
grant all on jambones_test.* to jambones_test@localhost;

View File

@@ -22,10 +22,10 @@ DROP TABLE IF EXISTS sip_gateways;
DROP TABLE IF EXISTS voip_carriers;
DROP TABLE IF EXISTS applications;
DROP TABLE IF EXISTS accounts;
DROP TABLE IF EXISTS applications;
DROP TABLE IF EXISTS service_providers;
DROP TABLE IF EXISTS webhooks;
@@ -38,7 +38,7 @@ account_sid CHAR(36) NOT NULL,
regex VARCHAR(255) NOT NULL,
application_sid CHAR(36) NOT NULL,
PRIMARY KEY (call_route_sid)
) COMMENT='a regex-based pattern match for call routing';
) ENGINE=InnoDB COMMENT='a regex-based pattern match for call routing';
CREATE TABLE lcr_routes
(
@@ -55,11 +55,11 @@ api_key_sid CHAR(36) NOT NULL UNIQUE ,
token CHAR(36) NOT NULL UNIQUE ,
account_sid CHAR(36),
service_provider_sid CHAR(36),
expires_at TIMESTAMP NULL DEFAULT NULL,
last_used TIMESTAMP NULL DEFAULT NULL,
expires_at TIMESTAMP NULL,
last_used TIMESTAMP NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (api_key_sid)
) COMMENT='An authorization token that is used to access the REST api';
) ENGINE=InnoDB COMMENT='An authorization token that is used to access the REST api';
CREATE TABLE ms_teams_tenants
(
@@ -98,15 +98,8 @@ description VARCHAR(255),
account_sid CHAR(36) COMMENT 'if provided, indicates this entity represents a customer PBX that is associated with a specific account',
application_sid CHAR(36) COMMENT 'If provided, all incoming calls from this source will be routed to the associated application',
e164_leading_plus BOOLEAN NOT NULL DEFAULT false,
requires_register BOOLEAN NOT NULL DEFAULT false,
register_username VARCHAR(64),
register_sip_realm VARCHAR(64),
register_password VARCHAR(64),
tech_prefix VARCHAR(16),
diversion VARCHAR(32),
is_active BOOLEAN NOT NULL DEFAULT true,
PRIMARY KEY (voip_carrier_sid)
) COMMENT='A Carrier or customer PBX that can send or receive calls';
) ENGINE=InnoDB COMMENT='A Carrier or customer PBX that can send or receive calls';
CREATE TABLE phone_numbers
(
@@ -150,21 +143,6 @@ priority INTEGER NOT NULL DEFAULT 0 COMMENT 'lower priority carriers are attempt
PRIMARY KEY (lcr_carrier_set_entry_sid)
) COMMENT='An entry in the LCR routing list';
CREATE TABLE accounts
(
account_sid CHAR(36) NOT NULL UNIQUE ,
name VARCHAR(64) NOT NULL,
sip_realm VARCHAR(132) UNIQUE COMMENT 'sip domain that will be used for devices registering under this account',
service_provider_sid CHAR(36) NOT NULL COMMENT 'service provider that owns the customer relationship with this account',
registration_hook_sid CHAR(36) COMMENT 'webhook to call when devices underr this account attempt to register',
queue_event_hook_sid CHAR(36) COMMENT 'webhook to call when members enter or leave a queue created by this account.',
device_calling_application_sid CHAR(36) COMMENT 'application to use for outbound calling from an account',
is_active BOOLEAN NOT NULL DEFAULT true,
webhook_secret VARCHAR(36),
disable_cdrs BOOLEAN NOT NULL DEFAULT 0,
PRIMARY KEY (account_sid)
) COMMENT='An enterprise that uses the platform for comm services';
CREATE TABLE applications
(
application_sid CHAR(36) NOT NULL UNIQUE ,
@@ -179,7 +157,7 @@ speech_synthesis_voice VARCHAR(64),
speech_recognizer_vendor VARCHAR(64) NOT NULL DEFAULT 'google',
speech_recognizer_language VARCHAR(64) NOT NULL DEFAULT 'en-US',
PRIMARY KEY (application_sid)
) COMMENT='A defined set of behaviors to be applied to phone calls ';
) ENGINE=InnoDB COMMENT='A defined set of behaviors to be applied to phone calls ';
CREATE TABLE service_providers
(
@@ -190,7 +168,19 @@ root_domain VARCHAR(128) UNIQUE ,
registration_hook_sid CHAR(36),
ms_teams_fqdn VARCHAR(255),
PRIMARY KEY (service_provider_sid)
) COMMENT='A partition of the platform used by one service provider';
) ENGINE=InnoDB COMMENT='A partition of the platform used by one service provider';
CREATE TABLE accounts
(
account_sid CHAR(36) NOT NULL UNIQUE ,
name VARCHAR(64) NOT NULL,
sip_realm VARCHAR(132) UNIQUE COMMENT 'sip domain that will be used for devices registering under this account',
service_provider_sid CHAR(36) NOT NULL COMMENT 'service provider that owns the customer relationship with this account',
registration_hook_sid CHAR(36) COMMENT 'webhook to call when devices underr this account attempt to register',
device_calling_application_sid CHAR(36) COMMENT 'application to use for outbound calling from an account',
is_active BOOLEAN NOT NULL DEFAULT true,
PRIMARY KEY (account_sid)
) ENGINE=InnoDB COMMENT='An enterprise that uses the platform for comm services';
CREATE INDEX call_route_sid_idx ON call_routes (call_route_sid);
ALTER TABLE call_routes ADD FOREIGN KEY account_sid_idxfk (account_sid) REFERENCES accounts (account_sid);
@@ -243,17 +233,6 @@ ALTER TABLE lcr_carrier_set_entry ADD FOREIGN KEY lcr_route_sid_idxfk (lcr_route
ALTER TABLE lcr_carrier_set_entry ADD FOREIGN KEY voip_carrier_sid_idxfk_2 (voip_carrier_sid) REFERENCES voip_carriers (voip_carrier_sid);
CREATE INDEX account_sid_idx ON accounts (account_sid);
CREATE INDEX sip_realm_idx ON accounts (sip_realm);
CREATE INDEX service_provider_sid_idx ON accounts (service_provider_sid);
ALTER TABLE accounts ADD FOREIGN KEY service_provider_sid_idxfk_3 (service_provider_sid) REFERENCES service_providers (service_provider_sid);
ALTER TABLE accounts ADD FOREIGN KEY registration_hook_sid_idxfk (registration_hook_sid) REFERENCES webhooks (webhook_sid);
ALTER TABLE accounts ADD FOREIGN KEY queue_event_hook_sid_idxfk (queue_event_hook_sid) REFERENCES webhooks (webhook_sid);
ALTER TABLE accounts ADD FOREIGN KEY device_calling_application_sid_idxfk (device_calling_application_sid) REFERENCES applications (application_sid);
CREATE UNIQUE INDEX applications_idx_name ON applications (account_sid,name);
CREATE INDEX application_sid_idx ON applications (application_sid);
@@ -269,6 +248,15 @@ ALTER TABLE applications ADD FOREIGN KEY messaging_hook_sid_idxfk (messaging_hoo
CREATE INDEX service_provider_sid_idx ON service_providers (service_provider_sid);
CREATE INDEX name_idx ON service_providers (name);
CREATE INDEX root_domain_idx ON service_providers (root_domain);
ALTER TABLE service_providers ADD FOREIGN KEY registration_hook_sid_idxfk_1 (registration_hook_sid) REFERENCES webhooks (webhook_sid);
ALTER TABLE service_providers ADD FOREIGN KEY registration_hook_sid_idxfk (registration_hook_sid) REFERENCES webhooks (webhook_sid);
CREATE INDEX account_sid_idx ON accounts (account_sid);
CREATE INDEX sip_realm_idx ON accounts (sip_realm);
CREATE INDEX service_provider_sid_idx ON accounts (service_provider_sid);
ALTER TABLE accounts ADD FOREIGN KEY service_provider_sid_idxfk_3 (service_provider_sid) REFERENCES service_providers (service_provider_sid);
ALTER TABLE accounts ADD FOREIGN KEY registration_hook_sid_idxfk_1 (registration_hook_sid) REFERENCES webhooks (webhook_sid);
ALTER TABLE accounts ADD FOREIGN KEY device_calling_application_sid_idxfk (device_calling_application_sid) REFERENCES applications (application_sid);
SET FOREIGN_KEY_CHECKS=1;

View File

@@ -56,13 +56,14 @@
<name><![CDATA[voip_carriers]]></name>
<schema><![CDATA[]]></schema>
<comment><![CDATA[A Carrier or customer PBX that can send or receive calls]]></comment>
<tableType><![CDATA[InnoDB]]></tableType>
<location>
<x>417.00</x>
<y>263.00</y>
</location>
<size>
<width>266.00</width>
<height>280.00</height>
<height>140.00</height>
</size>
<zorder>6</zorder>
<SQLField>
@@ -125,45 +126,6 @@
<notNull><![CDATA[1]]></notNull>
<uid><![CDATA[123EA4AC-627B-42A1-8779-D72494E8D47F]]></uid>
</SQLField>
<SQLField>
<name><![CDATA[requires_register]]></name>
<type><![CDATA[BOOLEAN]]></type>
<defaultValue><![CDATA[false]]></defaultValue>
<notNull><![CDATA[1]]></notNull>
<uid><![CDATA[B694DA3E-F58D-44C5-980F-E0CFBE6DFA02]]></uid>
</SQLField>
<SQLField>
<name><![CDATA[register_username]]></name>
<type><![CDATA[VARCHAR(64)]]></type>
<uid><![CDATA[7EA13180-1746-44F5-8699-6099D5D29018]]></uid>
</SQLField>
<SQLField>
<name><![CDATA[register_sip_realm]]></name>
<type><![CDATA[VARCHAR(64)]]></type>
<uid><![CDATA[163F2E47-6536-4A30-BD0A-4BBAA5AB4214]]></uid>
</SQLField>
<SQLField>
<name><![CDATA[register_password]]></name>
<type><![CDATA[VARCHAR(64)]]></type>
<uid><![CDATA[3699DD5F-20F9-4650-86EB-A08A90894C59]]></uid>
</SQLField>
<SQLField>
<name><![CDATA[tech_prefix]]></name>
<type><![CDATA[VARCHAR(16)]]></type>
<uid><![CDATA[58305E16-A895-4E7B-866F-F2A7BAD8B609]]></uid>
</SQLField>
<SQLField>
<name><![CDATA[diversion]]></name>
<type><![CDATA[VARCHAR(32)]]></type>
<uid><![CDATA[33E3BA51-A9A6-40D2-BAF8-F8E67CC9DD13]]></uid>
</SQLField>
<SQLField>
<name><![CDATA[is_active]]></name>
<type><![CDATA[BOOLEAN]]></type>
<defaultValue><![CDATA[true]]></defaultValue>
<notNull><![CDATA[1]]></notNull>
<uid><![CDATA[8A6DFB34-1620-4DA6-AB6A-FFA79F71D110]]></uid>
</SQLField>
<labelWindowIndex><![CDATA[8]]></labelWindowIndex>
<objectComment><![CDATA[A Carrier or customer PBX that can send or receive calls]]></objectComment>
<ui.treeExpanded><![CDATA[1]]></ui.treeExpanded>
@@ -173,6 +135,7 @@
<name><![CDATA[api_keys]]></name>
<schema><![CDATA[]]></schema>
<comment><![CDATA[An authorization token that is used to access the REST api]]></comment>
<tableType><![CDATA[InnoDB]]></tableType>
<location>
<x>1319.00</x>
<y>38.00</y>
@@ -260,7 +223,7 @@
<y>376.00</y>
</location>
<size>
<width>268.00</width>
<width>254.00</width>
<height>120.00</height>
</size>
<zorder>10</zorder>
@@ -306,9 +269,10 @@
<name><![CDATA[call_routes]]></name>
<schema><![CDATA[]]></schema>
<comment><![CDATA[a regex-based pattern match for call routing]]></comment>
<tableType><![CDATA[InnoDB]]></tableType>
<location>
<x>407.00</x>
<y>584.00</y>
<x>424.00</x>
<y>461.00</y>
</location>
<size>
<width>254.00</width>
@@ -523,13 +487,14 @@
<name><![CDATA[accounts]]></name>
<schema><![CDATA[]]></schema>
<comment><![CDATA[An enterprise that uses the platform for comm services]]></comment>
<tableType><![CDATA[InnoDB]]></tableType>
<location>
<x>825.00</x>
<y>321.00</y>
</location>
<size>
<width>380.00</width>
<height>220.00</height>
<height>160.00</height>
</size>
<zorder>4</zorder>
<SQLField>
@@ -583,29 +548,13 @@
<referencesTable>webhooks</referencesTable>
<referencesField><![CDATA[webhook_sid]]></referencesField>
<referencesTable><![CDATA[webhooks]]></referencesTable>
<sourceCardinality>2</sourceCardinality>
<destinationCardinality>1</destinationCardinality>
<sourceCardinality>4</sourceCardinality>
<destinationCardinality>2</destinationCardinality>
<referencesFieldUID><![CDATA[E046BA30-BC18-483C-A5C8-766E7160F574]]></referencesFieldUID>
<referencesTableUID><![CDATA[64D64CB9-0990-4C68-BE71-F9FD43C2BE19]]></referencesTableUID>
<forcedUnique><![CDATA[0]]></forcedUnique>
<objectComment><![CDATA[webhook to call when devices underr this account attempt to register]]></objectComment>
<uid><![CDATA[A75FAB8E-C2A1-4A05-A09E-6FF454109B6F]]></uid>
</SQLField>
<SQLField>
<name><![CDATA[queue_event_hook_sid]]></name>
<type><![CDATA[CHAR(36)]]></type>
<referencesField>webhook_sid</referencesField>
<referencesTable>webhooks</referencesTable>
<referencesField><![CDATA[webhook_sid]]></referencesField>
<referencesTable><![CDATA[webhooks]]></referencesTable>
<sourceCardinality>2</sourceCardinality>
<destinationCardinality>1</destinationCardinality>
<referencesFieldUID><![CDATA[E046BA30-BC18-483C-A5C8-766E7160F574]]></referencesFieldUID>
<referencesTableUID><![CDATA[64D64CB9-0990-4C68-BE71-F9FD43C2BE19]]></referencesTableUID>
<forcedUnique><![CDATA[0]]></forcedUnique>
<objectComment><![CDATA[webhook to call when members enter or leave a queue created by this account.]]></objectComment>
<uid><![CDATA[3F1CD447-0ABF-4B4C-80A8-BAB0F73B64C9]]></uid>
</SQLField>
<SQLField>
<name><![CDATA[device_calling_application_sid]]></name>
<type><![CDATA[CHAR(36)]]></type>
@@ -627,19 +576,6 @@
<notNull><![CDATA[1]]></notNull>
<uid><![CDATA[C7130A90-DBB4-424D-A9A9-CB203C32350C]]></uid>
</SQLField>
<SQLField>
<name><![CDATA[webhook_secret]]></name>
<type><![CDATA[VARCHAR(36)]]></type>
<notNull><![CDATA[0]]></notNull>
<uid><![CDATA[CF25660D-AACA-4783-8A13-C7393D3B3D95]]></uid>
</SQLField>
<SQLField>
<name><![CDATA[disable_cdrs]]></name>
<type><![CDATA[BOOLEAN]]></type>
<defaultValue><![CDATA[0]]></defaultValue>
<notNull><![CDATA[1]]></notNull>
<uid><![CDATA[341B2A8D-AE85-4FCA-8EA8-D6E6149511F4]]></uid>
</SQLField>
<labelWindowIndex><![CDATA[11]]></labelWindowIndex>
<objectComment><![CDATA[An enterprise that uses the platform for comm services]]></objectComment>
<ui.treeExpanded><![CDATA[1]]></ui.treeExpanded>
@@ -651,8 +587,8 @@
<comment><![CDATA[A phone number that has been assigned to an account]]></comment>
<tableType><![CDATA[InnoDB]]></tableType>
<location>
<x>360.00</x>
<y>753.00</y>
<x>385.00</x>
<y>625.00</y>
</location>
<size>
<width>331.00</width>
@@ -823,6 +759,7 @@
<name><![CDATA[applications]]></name>
<schema><![CDATA[]]></schema>
<comment><![CDATA[A defined set of behaviors to be applied to phone calls ]]></comment>
<tableType><![CDATA[InnoDB]]></tableType>
<location>
<x>829.00</x>
<y>568.00</y>
@@ -1084,6 +1021,7 @@
<name><![CDATA[service_providers]]></name>
<schema><![CDATA[]]></schema>
<comment><![CDATA[A partition of the platform used by one service provider]]></comment>
<tableType><![CDATA[InnoDB]]></tableType>
<location>
<x>838.00</x>
<y>96.00</y>
@@ -1161,17 +1099,17 @@
<overviewPanelHidden><![CDATA[0]]></overviewPanelHidden>
<pageBoundariesVisible><![CDATA[0]]></pageBoundariesVisible>
<PageGridVisible><![CDATA[0]]></PageGridVisible>
<RightSidebarWidth><![CDATA[1415.000000]]></RightSidebarWidth>
<RightSidebarWidth><![CDATA[1924.000000]]></RightSidebarWidth>
<sidebarIndex><![CDATA[2]]></sidebarIndex>
<snapToGrid><![CDATA[0]]></snapToGrid>
<SourceSidebarWidth><![CDATA[0.000000]]></SourceSidebarWidth>
<SQLEditorFileFormatVersion><![CDATA[4]]></SQLEditorFileFormatVersion>
<uid><![CDATA[58C99A00-06C9-478C-A667-C63842E088F3]]></uid>
<windowHeight><![CDATA[1042.000000]]></windowHeight>
<windowLocationX><![CDATA[3550.000000]]></windowLocationX>
<windowLocationY><![CDATA[1884.000000]]></windowLocationY>
<windowScrollOrigin><![CDATA[{369.5, 4}]]></windowScrollOrigin>
<windowWidth><![CDATA[1692.000000]]></windowWidth>
<windowHeight><![CDATA[1013.000000]]></windowHeight>
<windowLocationX><![CDATA[2716.000000]]></windowLocationX>
<windowLocationY><![CDATA[1913.000000]]></windowLocationY>
<windowScrollOrigin><![CDATA[{0, 5}]]></windowScrollOrigin>
<windowWidth><![CDATA[2201.000000]]></windowWidth>
</SQLDocumentInfo>
<AllowsIndexRenamingOnInsert><![CDATA[1]]></AllowsIndexRenamingOnInsert>
<defaultLabelExpanded><![CDATA[1]]></defaultLabelExpanded>

View File

@@ -1,7 +1,5 @@
const getMysqlConnection = require('./mysql');
const promisePool = require('./pool');
module.exports = {
getMysqlConnection,
promisePool
getMysqlConnection
};

View File

@@ -2,7 +2,6 @@ const mysql = require('mysql2');
const pool = mysql.createPool({
host: process.env.JAMBONES_MYSQL_HOST,
user: process.env.JAMBONES_MYSQL_USER,
port: process.env.JAMBONES_MYSQL_PORT || 3306,
password: process.env.JAMBONES_MYSQL_PASSWORD,
database: process.env.JAMBONES_MYSQL_DATABASE,
connectionLimit: process.env.JAMBONES_MYSQL_CONNECTION_LIMIT || 10

View File

@@ -1,10 +0,0 @@
const mysql = require('mysql2');
const pool = mysql.createPool({
host: process.env.JAMBONES_MYSQL_HOST,
port: process.env.JAMBONES_MYSQL_PORT || 3306,
user: process.env.JAMBONES_MYSQL_USER,
password: process.env.JAMBONES_MYSQL_PASSWORD,
database: process.env.JAMBONES_MYSQL_DATABASE,
connectionLimit: process.env.JAMBONES_MYSQL_CONNECTION_LIMIT || 10
});
module.exports = pool.promise();

View File

@@ -3,28 +3,17 @@ const {getMysqlConnection} = require('../db');
const retrieveSql = `SELECT * from accounts acc
LEFT JOIN webhooks AS rh
ON acc.registration_hook_sid = rh.webhook_sid
LEFT JOIN webhooks AS qh
ON acc.queue_event_hook_sid = qh.webhook_sid`;
ON acc.registration_hook_sid = rh.webhook_sid`;
function transmogrifyResults(results) {
return results.map((row) => {
let obj = row.acc;
const obj = row.acc;
if (row.rh && Object.keys(row.rh).length && row.rh.url !== null) {
obj = {...obj, registration_hook: row.rh};
Object.assign(obj, {registration_hook: row.rh});
delete obj.registration_hook.webhook_sid;
}
else obj.registration_hook = null;
if (row.qh && Object.keys(row.qh).length && row.qh.url !== null) {
obj = {...obj, queue_event_hook: row.qh};
delete obj.queue_event_hook.webhook_sid;
}
else obj.queue_event_hook = null;
delete obj.registration_hook_sid;
delete obj.queue_event_hook_sid;
return obj;
});
}
@@ -111,10 +100,6 @@ Account.fields = [
name: 'registration_hook_sid',
type: 'string',
},
{
name: 'queue_event_hook_sid',
type: 'string',
},
{
name: 'device_calling_application_sid',
type: 'string',

View File

@@ -33,22 +33,6 @@ VoipCarrier.fields = [
{
name: 'e164_leading_plus',
type: 'number'
},
{
name: 'requires_register',
type: 'number'
},
{
name: 'register_username',
type: 'string'
},
{
name: 'register_sip_realm',
type: 'string'
},
{
name: 'register_password',
type: 'string'
}
];

View File

@@ -6,14 +6,16 @@ const Webhook = require('../../models/webhook');
const ApiKey = require('../../models/api-key');
const ServiceProvider = require('../../models/service-provider');
const uuidv4 = require('uuid/v4');
const decorate = require('./decorate');
const snakeCase = require('../../utils/snake-case');
const sysError = require('./error');
const {promisePool} = require('../../db');
const {hasAccountPermissions} = require('./utils');
const preconditions = {
'add': validateAdd,
'update': validateUpdate,
'delete': validateDelete
};
let idx = 0;
router.use('/:sid/Queues', hasAccountPermissions, require('./queues'));
function coerceNumbers(callInfo) {
if (Array.isArray(callInfo)) {
return callInfo.map((ci) => {
@@ -41,7 +43,6 @@ function validateUpdateCall(opts) {
const hasWhisper = opts.whisper;
const count = [
'call_hook',
'child_call_hook',
'call_status',
'listen_status',
'mute_status']
@@ -55,13 +56,11 @@ function validateUpdateCall(opts) {
case 1:
// good
break;
case 2:
if (opts.call_hook && opts.child_call_hook) break;
// eslint-disable-next-line no-fallthrough
default:
throw new DbErrorBadRequest('multiple options are not allowed in updateCall');
}
if (opts.call_hook && !opts.call_hook.url) throw new DbErrorBadRequest('missing call_hook.url');
if (opts.call_status && !['completed', 'no-answer'].includes(opts.call_status)) {
throw new DbErrorBadRequest('invalid call_status');
}
@@ -132,34 +131,8 @@ async function validateCreateCall(logger, sid, req) {
});
}
if (!obj.call_hook && !obj.application_sid) {
throw new DbErrorBadRequest('either call_hook or application_sid required');
}
if (typeof obj.call_hook === 'string') {
const url = obj.call_hook;
obj.call_hook = {
url,
method: 'POST'
};
}
if (typeof obj.call_status_hook === 'string') {
const url = obj.call_status_hook;
obj.call_status_hook = {
url,
method: 'POST'
};
}
if (typeof obj.call_hook === 'object' && typeof obj.call_hook.url != 'string') {
throw new DbErrorBadRequest('call_hook must be string or an object containing a url property');
}
if (typeof obj.call_status_hook === 'object' && typeof obj.call_status_hook.url != 'string') {
throw new DbErrorBadRequest('call_status_hook must be string or an object containing a url property');
}
if (obj.call_hook && !/^https?:/.test(obj.call_hook.url)) {
throw new DbErrorBadRequest('call_hook url be an absolute url');
}
if (obj.call_status_hook && !/^https?:/.test(obj.call_status_hook.url)) {
throw new DbErrorBadRequest('call_status_hook url be an absolute url');
if (!obj.call_hook || (obj.call_hook && !obj.call_hook.url)) {
throw new DbErrorBadRequest('either url or application_sid required');
}
}
@@ -205,9 +178,6 @@ async function validateAdd(req) {
if (req.body.registration_hook && typeof req.body.registration_hook !== 'object') {
throw new DbErrorBadRequest('\'registration_hook\' must be an object when adding an account');
}
if (req.body.queue_event_hook && typeof req.body.queue_event_hook !== 'object') {
throw new DbErrorBadRequest('\'queue_event_hook\' must be an object when adding an account');
}
}
async function validateUpdate(req, sid) {
if (req.user.hasAccountAuth && req.user.account_sid !== sid) {
@@ -236,32 +206,7 @@ async function validateDelete(req, sid) {
}
}
/* delete */
router.delete('/:sid', async(req, res) => {
const sid = req.params.sid;
const logger = req.app.locals.logger;
try {
await validateDelete(req, sid);
/* delete associated webhooks */
const webhooks = [];
const [results] = await promisePool.query('SELECT * FROM accounts where account_sid = ?', sid);
if (results.length) {
if (results[0].registration_hook_sid) webhooks.push(results[0].registration_hook_sid);
if (results[0].queue_event_hook_sid) webhooks.push(results[0].queue_event_hook_sid);
}
await Account.remove(sid);
for (const wh of webhooks) {
promisePool.execute('DELETE from webhooks where webhook_sid = ?', [wh])
.catch((err) => logger.info({err, webhooks}, 'DELETE /Accounts Error deleting webhooks'));
}
res.sendStatus(204);
} catch (err) {
sysError(logger, res, err);
}
});
decorate(router, Account, ['delete'], preconditions);
/* add */
router.post('/', async(req, res) => {
@@ -271,7 +216,7 @@ router.post('/', async(req, res) => {
// create webhooks if provided
const obj = Object.assign({}, req.body);
for (const prop of ['registration_hook', 'queue_event_hook']) {
for (const prop of ['registration_hook']) {
if (obj[prop]) {
obj[`${prop}_sid`] = await Webhook.make(obj[prop]);
delete obj[prop];
@@ -318,10 +263,9 @@ router.put('/:sid', async(req, res) => {
const sid = req.params.sid;
const logger = req.app.locals.logger;
try {
// create webhooks if provided
const obj = Object.assign({}, req.body);
for (const prop of ['registration_hook', 'queue_event_hook']) {
for (const prop of ['registration_hook']) {
if (prop in obj && Object.keys(obj[prop]).length) {
if ('webhook_sid' in obj[prop]) {
const sid = obj[prop]['webhook_sid'];
@@ -333,39 +277,17 @@ router.put('/:sid', async(req, res) => {
obj[`${prop}_sid`] = sid;
}
}
else {
obj[`${prop}_sid`] = null;
}
delete obj[prop];
}
await validateUpdate(req, sid);
if (Object.keys(obj).length) {
const orphanedHooks = [];
if (null === obj.registration_hook || null == obj.queue_event_hook) {
const results = await Account.retrieve(sid);
if (results.length) {
if (results[0].registration_hook_sid) orphanedHooks.push(results[0].registration_hook_sid);
if (results[0].queue_event_hook_sid) orphanedHooks.push(results[0].queue_event_hook_sid);
}
if (null === obj.registration_hook) {
obj.registration_hook_sid = null;
delete obj.registration_hook;
}
if (null === obj.queue_event_hook) {
obj.queue_event_hook_sid = null;
delete obj.queue_event_hook;
}
}
logger.info({obj}, `about to update Account ${sid}`);
const rowsAffected = await Account.update(sid, obj);
if (rowsAffected === 0) {
return res.status(404).end();
}
if (orphanedHooks.length) {
for (const sid in orphanedHooks) {
await Webhook.remove(sid);
}
}
const rowsAffected = await Account.update(sid, obj);
if (rowsAffected === 0) {
return res.status(404).end();
}
res.status(204).end();
updateLastUsed(logger, sid, req).catch((err) => {});
} catch (err) {
@@ -410,7 +332,7 @@ router.post('/:sid/Calls', async(req, res) => {
url: serviceUrl,
method: 'POST',
json: true,
body: Object.assign(req.body, {account_sid: sid})
body: req.body
}, (err, response, body) => {
if (err) {
logger.error(err, `Error sending createCall POST to ${ip}`);
@@ -495,7 +417,7 @@ router.delete('/:sid/Calls/:callSid', async(req, res) => {
/**
* update a call
*/
const updateCall = async(req, res) => {
router.post('/:sid/Calls/:callSid', async(req, res) => {
const accountSid = req.params.sid;
const callSid = req.params.callSid;
const {logger, retrieveCall} = req.app.locals;
@@ -521,14 +443,6 @@ const updateCall = async(req, res) => {
} catch (err) {
sysError(logger, res, err);
}
};
/** leaving for legacy purposes, this should have been (and now is) a PUT */
router.post('/:sid/Calls/:callSid', async(req, res) => {
await updateCall(req, res);
});
router.put('/:sid/Calls/:callSid', async(req, res) => {
await updateCall(req, res);
});
/**
@@ -550,7 +464,7 @@ router.post('/:sid/Messages', async(req, res) => {
const serviceUrl = `http://${ip}:3000/v1/createMessage/${sid}`;
await validateCreateMessage(logger, sid, req);
const payload = Object.assign({messageSid: uuidv4(), account_sid: sid}, req.body);
const payload = Object.assign({messageSid: uuidv4()}, req.body);
logger.debug({payload}, `sending createMessage API request to to ${ip}`);
updateLastUsed(logger, sid, req).catch((err) => {});
request({

View File

@@ -9,16 +9,10 @@ const preconditions = {
'delete': checkInUse,
'update': validateUpdate
};
const sysError = require('./error');
/* check for required fields when adding */
async function validateAdd(req) {
try {
/* account level user can only act on carriers associated to his/her account */
if (req.user.hasAccountAuth) {
req.body.account_sid = req.user.account_sid;
}
if (!req.body.voip_carrier_sid) throw new DbErrorBadRequest('voip_carrier_sid is required');
if (!req.body.number) throw new DbErrorBadRequest('number is required');
validateNumber(req.body.number);
@@ -36,55 +30,24 @@ async function validateAdd(req) {
/* can not delete a phone number if it in use */
async function checkInUse(req, sid) {
const phoneNumber = await PhoneNumber.retrieve(sid);
if (req.user.hasAccountAuth) {
if (phoneNumber.account_sid !== req.user.account_sid) {
throw new DbErrorUnprocessableRequest('cannot delete a phone number that belongs to another account');
}
}
if (!req.user.hasAccountAuth && phoneNumber.account_sid) {
if (phoneNumber.account_sid) {
throw new DbErrorUnprocessableRequest('cannot delete phone number that is assigned to an account');
}
}
/* can not change number or voip carrier */
async function validateUpdate(req, sid) {
//const result = await PhoneNumber.retrieve(sid);
if (req.body.voip_carrier_sid) throw new DbErrorBadRequest('voip_carrier_sid may not be modified');
if (req.body.number) throw new DbErrorBadRequest('number may not be modified');
const phoneNumber = await PhoneNumber.retrieve(sid);
if (req.user.hasAccountAuth) {
if (phoneNumber.account_sid !== req.user.account_sid) {
throw new DbErrorUnprocessableRequest('cannot delete a phone number that belongs to another account');
}
}
// TODO: if we are assigning to an account, verify it exists
// TODO: if we are assigning to an application, verify it is associated to the same account
// TODO: if we are removing from an account, verify we are also removing from application.
}
decorate(router, PhoneNumber, ['add', 'update', 'delete'], preconditions);
/* list */
router.get('/', async(req, res) => {
const logger = req.app.locals.logger;
try {
const results = await PhoneNumber.retrieveAll(req.user.hasAccountAuth ? req.user.account_sid : null);
res.status(200).json(results);
} catch (err) {
sysError(logger, res, err);
}
});
/* retrieve */
router.get('/:sid', async(req, res) => {
const logger = req.app.locals.logger;
try {
const account_sid = req.user.hasAccountAuth ? req.user.account_sid : null;
const results = await PhoneNumber.retrieve(req.params.sid, account_sid);
if (results.length === 0) return res.status(404).end();
return res.status(200).json(results[0]);
}
catch (err) {
sysError(logger, res, err);
}
});
decorate(router, PhoneNumber, ['*'], preconditions);
module.exports = router;

View File

@@ -1,20 +0,0 @@
const router = require('express').Router();
const sysError = require('./error');
const {parseAccountSid} = require('./utils');
/**
* retrieve queues for an account
*/
router.get('/', async(req, res) => {
const account_sid = parseAccountSid(req);
const {logger, listQueues} = req.app.locals;
try {
const queues = await listQueues(account_sid, req.query.name);
res.status(200).json(queues);
} catch (err) {
sysError(logger, res, err);
}
});
module.exports = router;

View File

@@ -1,40 +0,0 @@
const parseServiceProviderSid = (req) => {
const arr = /ServiceProviders\/([^\/]*)/.exec(req.originalUrl);
if (arr) return arr[1];
};
const parseAccountSid = (req) => {
const arr = /Accounts\/([^\/]*)/.exec(req.originalUrl);
if (arr) return arr[1];
};
const hasAccountPermissions = (req, res, next) => {
if (req.user.hasScope('admin')) return next();
if (req.user.hasScope('account')) {
const account_sid = parseAccountSid(req);
if (account_sid === req.user.account_sid) return next();
}
res.status(403).json({
status: 'fail',
message: 'insufficient privileges'
});
};
const hasServiceProviderPermissions = (req, res, next) => {
if (req.user.hasScope('admin')) return next();
if (req.user.hasScope('service_provider')) {
const service_provider_sid = parseServiceProviderSid(req);
if (service_provider_sid === req.user.service_provider_sid) return next();
}
res.status(403).json({
status: 'fail',
message: 'insufficient privileges'
});
};
module.exports = {
parseAccountSid,
parseServiceProviderSid,
hasAccountPermissions,
hasServiceProviderPermissions
};

View File

@@ -177,8 +177,7 @@ paths:
description: login succeeded
content:
application/json:
schema:
$ref: '#/components/schemas/Login'
schema: '#/components/schemas/Login'
403:
description: login failed
content:
@@ -250,29 +249,10 @@ paths:
name:
type: string
description: voip carrier name
example: fastco
description:
type: string
example: my US sip trunking provider
e164_leading_plus:
type: boolean
description: whether a leading + is required on INVITEs to this provider
example: true
requires_register:
type: boolean
description: wehther this provider requires us to send a REGISTER to them in order to receive calls
register_username:
type: string
description: sip username to authenticate with, if registration is required
example: foo
register_sip_realm:
type: string
description: sip realm to authenticate with, if registration is required
example: sip.fastco.com
register_password:
type: string
description: sip password to authenticate with, if registration is required
example: bar
required:
- name
responses:
@@ -981,9 +961,12 @@ paths:
registration_hook:
$ref: '#/components/schemas/Webhook'
description: authentication webhook for registration
queue_event_hook:
device_calling_hook:
$ref: '#/components/schemas/Webhook'
description: webhook for queue events
description: webhook for inbound call from registered devices
error_hook:
$ref: '#/components/schemas/Webhook'
description: webhook for reporting errors from malformed applications
service_provider_sid:
type: string
format: uuid
@@ -1402,36 +1385,7 @@ paths:
schema:
$ref: '#/components/schemas/GeneralError'
/Accounts/{AccountSid}/Queues:
get:
summary: list queues for an Account
parameters:
- name: AccountSid
in: path
required: true
schema:
type: string
- name: name
in: query
required: false
schema:
type: string
description: a queue name or glob-style pattern matching one or more queues to return
responses:
200:
description: list of queues for this account matching the name query param, if provided
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/Queue'
500:
description: system error
content:
application/json:
schema:
$ref: '#/components/schemas/GeneralError'
/Accounts/{AccountSid}/Calls:
post:
summary: create a call
@@ -1728,14 +1682,6 @@ components:
type: string
e164_leading_plus:
type: boolean
requires_register:
type: boolean
register_username:
type: string
register_sip_realm:
type: string
register_password:
type: string
required:
- voip_carrier_sid
- name
@@ -1776,19 +1722,19 @@ components:
registration_hook:
$ref: '#/components/schemas/Webhook'
description: authentication webhook for registration
queue_event_hook:
device_calling_hook:
$ref: '#/components/schemas/Webhook'
description: webhook for queue events
device_calling_application_sid:
type: string
format: uuid
description: webhook for inbound call from registered devices
error_hook:
$ref: '#/components/schemas/Webhook'
description: webhook for reporting errors from malformed applications
service_provider_sid:
type: string
format: uuid
required:
- account_sid
- name
- service_provider_sid
- service_provider
Application:
type: object
properties:
@@ -2018,18 +1964,6 @@ components:
- from
- to
example: {"from": "13394445678", "to": "16173333456", "text": "please call when you can"}
Queue:
properties:
name:
type: string
example: support
length:
type: number
format: integer
example: 5
required:
- name
- length
example: {"name": "new-orders", length: 12}
security:
- bearerAuth: []

View File

@@ -1,12 +1,11 @@
{
"name": "jambonz-api-server",
"version": "1.2.1",
"version": "1.2.0",
"description": "",
"main": "app.js",
"scripts": {
"start": "node app.js",
"test": "NODE_ENV=test JAMBONES_MYSQL_HOST=127.0.0.1 JAMBONES_MYSQL_USER=jambones_test JAMBONES_MYSQL_PASSWORD=jambones_test JAMBONES_MYSQL_DATABASE=jambones_test JAMBONES_MYSQL_PORT=3360 JAMBONES_REDIS_HOST=localhost JAMBONES_REDIS_PORT=16379 JAMBONES_LOGLEVEL=error JAMBONES_CREATE_CALL_URL=http://localhost/v1/createCall node test/ ",
"integration-test": "NODE_ENV=test JAMBONES_TIME_SERIES_HOST=127.0.0.1 AWS_REGION='us-east-1' JAMBONES_CURRENCY=USD JWT_SECRET=foobarbazzle JAMBONES_MYSQL_HOST=127.0.0.1 JAMBONES_MYSQL_PORT=3360 JAMBONES_MYSQL_USER=jambones_test JAMBONES_MYSQL_PASSWORD=jambones_test JAMBONES_MYSQL_DATABASE=jambones_test JAMBONES_REDIS_HOST=localhost JAMBONES_REDIS_PORT=16379 JAMBONES_LOGLEVEL=debug JAMBONES_CREATE_CALL_URL=http://localhost/v1/createCall node test/serve-integration.js",
"test": "NODE_ENV=test JAMBONES_MYSQL_HOST=localhost JAMBONES_MYSQL_USER=jambones_test JAMBONES_MYSQL_PASSWORD=jambones_test JAMBONES_MYSQL_DATABASE=jambones_test JAMBONES_REDIS_HOST=localhost JAMBONES_LOGLEVEL=error JAMBONES_CREATE_CALL_URL=http://localhost/v1/createCall node test/ | ./node_modules/.bin/tap-spec",
"coverage": "./node_modules/.bin/nyc --reporter html --report-dir ./coverage npm run test",
"jslint": "eslint app.js lib"
},
@@ -16,25 +15,31 @@
"url": "https://github.com/jambonz/jambonz-api-server.git"
},
"dependencies": {
"@jambonz/db-helpers": "^0.6.11",
"@jambonz/realtimedb-helpers": "^0.4.3",
"@jambonz/db-helpers": "^0.5.1",
"@jambonz/messaging-382com": "0.0.2",
"@jambonz/messaging-peerless": "0.0.9",
"@jambonz/messaging-simwood": "0.0.4",
"@jambonz/realtimedb-helpers": "0.2.17",
"cors": "^2.8.5",
"express": "^4.17.1",
"mysql2": "^2.2.5",
"mysql2": "^2.1.0",
"passport": "^0.4.1",
"passport-http-bearer": "^1.0.1",
"pino": "^5.17.0",
"request": "^2.88.2",
"request-debug": "^0.2.0",
"swagger-ui-express": "^4.1.5",
"swagger-ui-dist": "^3.35.0",
"swagger-ui-express": "^4.1.4",
"uuid": "^3.4.0",
"yamljs": "^0.3.0"
},
"devDependencies": {
"eslint": "^7.15.0",
"eslint": "^7.10.0",
"eslint-plugin-promise": "^4.2.1",
"nyc": "^15.1.0",
"request-promise-native": "^1.0.9",
"tape": "^5.2.2"
"tap-dot": "^2.0.0",
"tap-spec": "^5.0.0",
"tape": "^5.0.1"
}
}

View File

@@ -1,4 +1,4 @@
const test = require('tape') ;
const test = require('tape').test ;
const ADMIN_TOKEN = '38700987-c7a4-4685-a5bb-af378f9734de';
const authAdmin = {bearer: ADMIN_TOKEN};
const request = require('request-promise-native').defaults({
@@ -16,7 +16,6 @@ process.on('unhandledRejection', (reason, p) => {
test('account tests', async(t) => {
const app = require('../app');
const {pushBack} = app.locals;
let sid;
try {
let result;
@@ -37,10 +36,6 @@ test('account tests', async(t) => {
registration_hook: {
url: 'http://example.com/reg',
method: 'get'
},
queue_event_hook: {
url: 'http://example.com/q',
method: 'post'
}
}
});
@@ -73,10 +68,9 @@ test('account tests', async(t) => {
json: true,
});
let regHook = result[0].registration_hook;
let qHook = result[0].queue_event_hook;
t.ok(result.length === 1 &&
Object.keys(regHook).length == 4 && Object.keys(qHook).length == 4, 'successfully queried all accounts');
Object.keys(regHook).length == 4, 'successfully queried all accounts');
/* query one accounts */
result = await request.get(`/Accounts/${sid}`, {
auth: authAdmin,
@@ -110,6 +104,7 @@ test('account tests', async(t) => {
auth: authAdmin,
json: true,
});
//console.log(`retrieved account after update: ${JSON.stringify(result)}`);
t.ok(Object.keys(result.registration_hook).length === 4, 'successfully removed a hook from account');
/* assign phone number to account */
@@ -123,36 +118,6 @@ test('account tests', async(t) => {
});
t.ok(result.statusCode === 204, 'successfully assigned phone number to account');
/* retrieve queues for account */
result = await request.get(`/Accounts/${sid}/Queues`, {
auth: {bearer: accountLevelToken},
json: true,
resolveWithFullResponse: true,
});
t.ok(result.statusCode === 200 && 0 === result.body.length,
'successfully retrieved an empty array when no queues exist');
await pushBack(`queue:${sid}:customer-support`, 'https://ip:300/v1/enqueue/foobar');
await pushBack(`queue:${sid}:customer-support`, 'https://ip:300/v1/enqueue/bazzle');
await pushBack(`queue:${sid}:sales-new-orders`, 'https://ip:300/v1/enqueue/bazzle');
await pushBack(`queue:${sid}:sales-returns`, 'https://ip:300/v1/enqueue/bazzle');
result = await request.get(`/Accounts/${sid}/Queues`, {
auth: {bearer: accountLevelToken},
json: true,
resolveWithFullResponse: true,
});
console.log(`retrieved queues: ${result.statusCode}: ${JSON.stringify(result.body)}`);
//t.ok(result.statusCode === 200 && 0 === result.body.length,
// 'successfully retrieved an empty array when no queues exist');
result = await request.get(`/Accounts/${sid}/Queues?name=sales-*`, {
auth: {bearer: accountLevelToken},
json: true,
resolveWithFullResponse: true,
});
console.log(`retrieved queues: ${result.statusCode}: ${JSON.stringify(result.body)}`);
/* cannot delete account that has phone numbers assigned */
result = await request.delete(`/Accounts/${sid}`, {
auth: authAdmin,
@@ -173,7 +138,7 @@ test('account tests', async(t) => {
await deleteObjectBySid(request, '/VoipCarriers', voip_carrier_sid);
await deleteObjectBySid(request, '/ServiceProviders', service_provider_sid);
//t.end();
t.end();
}
catch (err) {
console.error(err);

View File

@@ -1,4 +1,4 @@
const test = require('tape') ;
const test = require('tape').test ;
const ADMIN_TOKEN = '38700987-c7a4-4685-a5bb-af378f9734de';
const authAdmin = {bearer: ADMIN_TOKEN};
const request = require('request-promise-native').defaults({
@@ -121,7 +121,7 @@ test('application tests', async(t) => {
await deleteObjectBySid(request, '/VoipCarriers', voip_carrier_sid);
await deleteObjectBySid(request, '/ServiceProviders', service_provider_sid);
//t.end();
t.end();
}
catch (err) {
//console.error(err);

View File

@@ -461,7 +461,7 @@ test('authentication tests', async(t) => {
await deleteObjectBySid(request, '/ServiceProviders', spA_sid);
await deleteObjectBySid(request, '/ServiceProviders', spB_sid);
//t.end();
t.end();
}
catch (err) {
console.error(err);

View File

@@ -1,8 +1,9 @@
const test = require('tape') ;
const test = require('tape').test ;
const exec = require('child_process').exec ;
const pwd = process.env.TRAVIS ? '' : '-p$MYSQL_ROOT_PASSWORD';
test('creating jambones_test database', (t) => {
exec(`mysql -h 127.0.0.1 -u root --protocol=tcp --port=3360 < ${__dirname}/../db/create_test_db.sql`, (err, stdout, stderr) => {
exec(`mysql -h localhost -u root ${pwd} < ${__dirname}/../db/create_test_db.sql`, (err, stdout, stderr) => {
if (err) return t.end(err);
t.pass('database successfully created');
t.end();
@@ -10,7 +11,7 @@ test('creating jambones_test database', (t) => {
});
test('creating schema', (t) => {
exec(`mysql -h 127.0.0.1 -u root --protocol=tcp --port=3360 -D jambones_test < ${__dirname}/../db/jambones-sql.sql`, (err, stdout, stderr) => {
exec(`mysql -h localhost -u root ${pwd} -D jambones_test < ${__dirname}/../db/jambones-sql.sql`, (err, stdout, stderr) => {
if (err) return t.end(err);
t.pass('schema successfully created');
t.end();
@@ -18,7 +19,7 @@ test('creating schema', (t) => {
});
test('creating auth token', (t) => {
exec(`mysql -h 127.0.0.1 -u root --protocol=tcp --port=3360 -D jambones_test < ${__dirname}/../db/create-admin-token.sql`, (err, stdout, stderr) => {
exec(`mysql -h localhost -u root ${pwd} -D jambones_test < ${__dirname}/../db/create-admin-token.sql`, (err, stdout, stderr) => {
if (err) return t.end(err);
t.pass('auth token successfully created');
t.end();

View File

@@ -1,25 +0,0 @@
version: '3'
services:
mysql:
image: mysql:5.7
ports:
- "3360:3306"
environment:
MYSQL_ALLOW_EMPTY_PASSWORD: "yes"
healthcheck:
test: ["CMD", "mysqladmin" ,"ping", "-h", "127.0.0.1", "--protocol", "tcp"]
timeout: 5s
retries: 10
redis:
image: redis:5-alpine
ports:
- "16379:6379/tcp"
depends_on:
mysql:
condition: service_healthy
influxdb:
image: influxdb:1.8-alpine
ports:
- "8086:8086"

View File

@@ -1,11 +0,0 @@
const test = require('tape');
const exec = require('child_process').exec ;
test('starting docker network..', (t) => {
exec(`docker-compose -f ${__dirname}/docker-compose-testbed.yaml up -d`, (err, stdout, stderr) => {
setTimeout(() => {
t.pass('docker started');
t.end(err);
}, 15000);
});
});

View File

@@ -1,12 +0,0 @@
const test = require('tape');
const exec = require('child_process').exec ;
test('stopping docker network..', (t) => {
t.timeoutAfter(10000);
exec(`docker-compose -f ${__dirname}/docker-compose-testbed.yaml down`, (err, stdout, stderr) => {
//console.log(`stderr: ${stderr}`);
process.exit(0);
});
t.end() ;
});

View File

@@ -1,4 +1,3 @@
require('./docker_start');
require('./create-test-db');
require('./sip-gateways');
require('./service-providers');
@@ -9,4 +8,4 @@ require('./applications');
require('./auth');
require('./sbcs');
require('./ms-teams');
require('./docker_stop');
require('./remove-test-db');

View File

@@ -1,4 +1,4 @@
const test = require('tape') ;
const test = require('tape').test ;
const ADMIN_TOKEN = '38700987-c7a4-4685-a5bb-af378f9734de';
const authAdmin = {bearer: ADMIN_TOKEN};
const request = require('request-promise-native').defaults({
@@ -10,7 +10,7 @@ process.on('unhandledRejection', (reason, p) => {
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
});
test('ms teams tests', async(t) => {
test('sbc_addresses tests', async(t) => {
const app = require('../app');
let sid;
try {
@@ -79,7 +79,7 @@ test('ms teams tests', async(t) => {
await deleteObjectBySid(request, '/Accounts', account_sid2);
await deleteObjectBySid(request, '/ServiceProviders', service_provider_sid);
//t.end();
t.end();
}
catch (err) {
console.error(err);

View File

@@ -1,4 +1,4 @@
const test = require('tape') ;
const test = require('tape').test ;
const ADMIN_TOKEN = '38700987-c7a4-4685-a5bb-af378f9734de';
const authAdmin = {bearer: ADMIN_TOKEN};
const request = require('request-promise-native').defaults({
@@ -111,7 +111,7 @@ test('phone number tests', async(t) => {
await deleteObjectBySid(request, '/VoipCarriers', voip_carrier_sid);
//t.end();
t.end();
}
catch (err) {
console.error(err);

View File

@@ -1,9 +1,9 @@
const test = require('tape') ;
const test = require('tape').test ;
const exec = require('child_process').exec ;
const pwd = process.env.CI ? '' : '-p$MYSQL_ROOT_PASSWORD';
const pwd = process.env.TRAVIS ? '' : '-p$MYSQL_ROOT_PASSWORD';
test('dropping jambones_test database', (t) => {
exec(`mysql -h 127.0.0.1 -u root ${pwd} --protocol=tcp < ${__dirname}/../db/remove_test_db.sql`, (err, stdout, stderr) => {
exec(`mysql -h localhost -u root ${pwd} < ${__dirname}/../db/remove_test_db.sql`, (err, stdout, stderr) => {
if (err) return t.end(err);
t.pass('database successfully dropped');
t.end();

View File

@@ -1,4 +1,4 @@
const test = require('tape') ;
const test = require('tape').test ;
const ADMIN_TOKEN = '38700987-c7a4-4685-a5bb-af378f9734de';
const authAdmin = {bearer: ADMIN_TOKEN};
const request = require('request-promise-native').defaults({
@@ -60,7 +60,7 @@ test('sbc_addresses tests', async(t) => {
await deleteObjectBySid(request, '/Sbcs', sid2);
await deleteObjectBySid(request, '/ServiceProviders', service_provider_sid);
//t.end();
t.end();
}
catch (err) {
console.error(err);

View File

@@ -1,83 +0,0 @@
const exec = require('child_process').exec ;
let stopping = false;
process.on('SIGINT', async() => {
if (stopping) return;
stopping = true;
console.log('shutting down');
await stopDocker();
process.exit(0);
});
const startDocker = () => {
return new Promise((resolve, reject) => {
console.log('starting dockerized mysql and redis..')
exec(`docker-compose -f ${__dirname}/docker-compose-testbed.yaml up -d`, (err) => {
if (err) return reject(err);
setTimeout(() => {
console.log('mysql is running');
resolve();
}, 10000);
});
});
};
const createDb = () => {
return new Promise((resolve, reject) => {
console.log('creating database..')
exec(`mysql -h 127.0.0.1 -u root --protocol=tcp --port=3360 < ${__dirname}/../db/create_test_db.sql`, (err) => {
if (err) return reject(err);
resolve();
});
});
};
const createSchema = () => {
return new Promise((resolve, reject) => {
console.log('creating schema..')
exec(`mysql -h 127.0.0.1 -u root --protocol=tcp --port=3360 -D jambones_test < ${__dirname}/../db/jambones-sql.sql`, (err, stdout, stderr) => {
if (err) return reject(err);
resolve();
});
});
};
const seedDb = () => {
return new Promise((resolve, reject) => {
console.log('seeding database..')
exec(`mysql -h 127.0.0.1 -u root --protocol=tcp --port=3360 -D jambones_test < ${__dirname}/../db/create-default-service-provider-and-account.sql`, (err) => {
if (err) return reject(err);
exec(`mysql -h 127.0.0.1 -u root --protocol=tcp --port=3360 -D jambones_test < ${__dirname}/../db/create-admin-token.sql`, (err) => {
if (err) return reject(err);
exec(`node ${__dirname}/../db/reset_admin_password.js`, (err) => {
if (err) return reject(err);
resolve();
});
});
});
});
};
const stopDocker = () => {
return new Promise((resolve, reject) => {
console.log('stopping docker network..')
exec(`docker-compose -f ${__dirname}/docker-compose-testbed.yaml down`, (err) => {
if (err) return reject(err);
resolve();
});
})
};
startDocker()
.then(createDb)
.then(createSchema)
.then(seedDb)
.then(() => {
console.log('ready for testing!');
require('..');
})
.catch(async(err) => {
console.error({err}, 'Error running integration test');
await stopDocker();
process.exit(-1);
});

View File

@@ -1,4 +1,4 @@
const test = require('tape') ;
const test = require('tape').test ;
const ADMIN_TOKEN = '38700987-c7a4-4685-a5bb-af378f9734de';
const authAdmin = {bearer: ADMIN_TOKEN};
const request = require('request-promise-native').defaults({
@@ -117,7 +117,7 @@ test('service provider tests', async(t) => {
resolveWithFullResponse: true,
});
t.ok(result.statusCode === 204, 'successfully deleted service provider 2');
//t.end();
t.end();
}
catch (err) {
console.error(err);

View File

@@ -1,4 +1,4 @@
const test = require('tape') ;
const test = require('tape').test ;
const ADMIN_TOKEN = '38700987-c7a4-4685-a5bb-af378f9734de';
const authAdmin = {bearer: ADMIN_TOKEN};
const request = require('request-promise-native').defaults({
@@ -72,7 +72,7 @@ test('sip gateway tests', async(t) => {
await deleteObjectBySid(request, '/VoipCarriers', voip_carrier_sid);
//t.end();
t.end();
}
catch (err) {
console.error(err);

View File

@@ -1,4 +1,4 @@
const test = require('tape') ;
const test = require('tape').test ;
const ADMIN_TOKEN = '38700987-c7a4-4685-a5bb-af378f9734de';
const authAdmin = {bearer: ADMIN_TOKEN};
const request = require('request-promise-native').defaults({
@@ -50,11 +50,7 @@ test('voip carrier tests', async(t) => {
json: true,
resolveWithFullResponse: true,
body: {
name: 'robb',
requires_register: true,
register_username: 'foo',
register_sip_realm: 'bar',
register_password: 'baz'
name: 'robb'
}
});
t.ok(result.statusCode === 204, 'successfully updated voip carrier');
@@ -194,7 +190,7 @@ test('voip carrier tests', async(t) => {
await deleteObjectBySid(request, '/Accounts', account_sid2);
await deleteObjectBySid(request, '/ServiceProviders', service_provider_sid);
//t.end();
t.end();
}
catch (err) {
console.error(err);