mirror of
https://github.com/jambonz/jambonz-api-server.git
synced 2026-01-25 02:08:24 +00:00
Compare commits
24 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ea64fb1a58 | ||
|
|
53763aae14 | ||
|
|
491b44709c | ||
|
|
c89ee55389 | ||
|
|
f52cf88423 | ||
|
|
d3c347ac3f | ||
|
|
9df6925b47 | ||
|
|
5ae6cda12a | ||
|
|
3be0412de1 | ||
|
|
4efee5a8b8 | ||
|
|
326b1b673e | ||
|
|
a746bbc4c9 | ||
|
|
0e248cb393 | ||
|
|
5d5bd223cd | ||
|
|
ed74ec45c2 | ||
|
|
2fb2fc5c10 | ||
|
|
e38137ae7b | ||
|
|
8e6b0ec111 | ||
|
|
be011db109 | ||
|
|
993833212f | ||
|
|
61a5ce2672 | ||
|
|
c43d24b477 | ||
|
|
2accbcef74 | ||
|
|
7a144ffe74 |
@@ -5,4 +5,4 @@ node_js:
|
||||
services:
|
||||
- mysql
|
||||
script:
|
||||
- npm test
|
||||
- npm test
|
||||
|
||||
@@ -16,7 +16,6 @@ JAMBONES_REDIS_HOST
|
||||
JAMBONES_REDIS_PORT
|
||||
JAMBONES_LOGLEVEL # defaults to info
|
||||
JAMBONES_API_VERSION # defaults to v1
|
||||
JAMBONES_CREATE_CALL_URL
|
||||
HTTP_PORT # defaults to 3000
|
||||
```
|
||||
|
||||
|
||||
44
app.js
44
app.js
@@ -1,7 +1,11 @@
|
||||
const assert = require('assert');
|
||||
const opts = Object.assign({
|
||||
timestamp: () => {return `, "time": "${new Date().toISOString()}"`;}
|
||||
}, {level: process.env.JAMBONES_LOGLEVEL || 'info'});
|
||||
timestamp: () => {
|
||||
return `, "time": "${new Date().toISOString()}"`;
|
||||
}
|
||||
}, {
|
||||
level: process.env.JAMBONES_LOGLEVEL || 'info'
|
||||
});
|
||||
const logger = require('pino')(opts);
|
||||
const express = require('express');
|
||||
const app = express();
|
||||
@@ -15,21 +19,23 @@ assert.ok(process.env.JAMBONES_MYSQL_HOST &&
|
||||
process.env.JAMBONES_MYSQL_PASSWORD &&
|
||||
process.env.JAMBONES_MYSQL_DATABASE, 'missing JAMBONES_MYSQL_XXX env vars');
|
||||
assert.ok(process.env.JAMBONES_REDIS_HOST, 'missing JAMBONES_REDIS_HOST env var');
|
||||
assert.ok(process.env.JAMBONES_CREATE_CALL_URL, 'missing JAMBONES_CREATE_CALL_URL env var');
|
||||
|
||||
const {
|
||||
retrieveCall,
|
||||
deleteCall,
|
||||
listCalls,
|
||||
purgeCalls
|
||||
} = require('jambonz-realtimedb-helpers')({
|
||||
purgeCalls,
|
||||
retrieveSet
|
||||
} = require('@jambonz/realtimedb-helpers')({
|
||||
host: process.env.JAMBONES_REDIS_HOST || 'localhost',
|
||||
port: process.env.JAMBONES_REDIS_PORT || 6379
|
||||
}, logger);
|
||||
const {
|
||||
lookupAppBySid,
|
||||
lookupAccountBySid
|
||||
} = require('jambonz-db-helpers')({
|
||||
lookupAccountBySid,
|
||||
lookupAccountByPhoneNumber,
|
||||
lookupAppByPhoneNumber
|
||||
} = require('@jambonz/db-helpers')({
|
||||
host: process.env.JAMBONES_MYSQL_HOST,
|
||||
user: process.env.JAMBONES_MYSQL_USER,
|
||||
password: process.env.JAMBONES_MYSQL_PASSWORD,
|
||||
@@ -47,18 +53,34 @@ Object.assign(app.locals, {
|
||||
deleteCall,
|
||||
listCalls,
|
||||
purgeCalls,
|
||||
retrieveSet,
|
||||
lookupAppBySid,
|
||||
lookupAccountBySid
|
||||
lookupAccountBySid,
|
||||
lookupAccountByPhoneNumber,
|
||||
lookupAppByPhoneNumber
|
||||
});
|
||||
|
||||
const unless = (paths, middleware) => {
|
||||
return (req, res, next) => {
|
||||
if (paths.find((path) => req.path.startsWith(path))) return next();
|
||||
return middleware(req, res, next);
|
||||
};
|
||||
};
|
||||
|
||||
app.use(cors());
|
||||
app.use(express.urlencoded({ extended: true }));
|
||||
app.use(express.urlencoded({
|
||||
extended: true
|
||||
}));
|
||||
app.use(express.json());
|
||||
app.use('/v1', passport.authenticate('bearer', { session: false }));
|
||||
app.use('/v1', unless(['/login', '/Users', '/messaging', '/outboundSMS'], passport.authenticate('bearer', {
|
||||
session: false
|
||||
})));
|
||||
app.use('/', routes);
|
||||
app.use((err, req, res, next) => {
|
||||
logger.error(err, 'burped error');
|
||||
res.status(err.status || 500).json({msg: err.message});
|
||||
res.status(err.status || 500).json({
|
||||
msg: err.message
|
||||
});
|
||||
});
|
||||
logger.info(`listening for HTTP traffic on port ${PORT}`);
|
||||
app.listen(PORT);
|
||||
|
||||
4
db/create-default-account.sql
Normal file
4
db/create-default-account.sql
Normal file
@@ -0,0 +1,4 @@
|
||||
insert into service_providers (service_provider_sid, name)
|
||||
values ('2708b1b3-2736-40ea-b502-c53d8396247f', 'default service provider');
|
||||
insert into accounts (account_sid, service_provider_sid, name)
|
||||
values ('9351f46a-678c-43f5-b8a6-d4eb58d131af','2708b1b3-2736-40ea-b502-c53d8396247f', 'default account');
|
||||
4
db/create-default-service-provider-and-account.sql
Normal file
4
db/create-default-service-provider-and-account.sql
Normal file
@@ -0,0 +1,4 @@
|
||||
insert into service_providers (service_provider_sid, name)
|
||||
values ('2708b1b3-2736-40ea-b502-c53d8396247f', 'default service provider');
|
||||
insert into accounts (account_sid, service_provider_sid, name)
|
||||
values ('9351f46a-678c-43f5-b8a6-d4eb58d131af','2708b1b3-2736-40ea-b502-c53d8396247f', 'default account');
|
||||
@@ -1,204 +1,262 @@
|
||||
/* SQLEditor (MySQL (2))*/
|
||||
|
||||
SET FOREIGN_KEY_CHECKS = 0;
|
||||
SET FOREIGN_KEY_CHECKS=0;
|
||||
|
||||
DROP TABLE IF EXISTS `call_routes`;
|
||||
DROP TABLE IF EXISTS call_routes;
|
||||
|
||||
DROP TABLE IF EXISTS `lcr_carrier_set_entry`;
|
||||
DROP TABLE IF EXISTS lcr_carrier_set_entry;
|
||||
|
||||
DROP TABLE IF EXISTS `lcr_routes`;
|
||||
DROP TABLE IF EXISTS lcr_routes;
|
||||
|
||||
DROP TABLE IF EXISTS `api_keys`;
|
||||
DROP TABLE IF EXISTS api_keys;
|
||||
|
||||
DROP TABLE IF EXISTS `phone_numbers`;
|
||||
DROP TABLE IF EXISTS ms_teams_tenants;
|
||||
|
||||
DROP TABLE IF EXISTS `sip_gateways`;
|
||||
DROP TABLE IF EXISTS sbc_addresses;
|
||||
|
||||
DROP TABLE IF EXISTS `voip_carriers`;
|
||||
DROP TABLE IF EXISTS users;
|
||||
|
||||
DROP TABLE IF EXISTS `accounts`;
|
||||
DROP TABLE IF EXISTS phone_numbers;
|
||||
|
||||
DROP TABLE IF EXISTS `applications`;
|
||||
DROP TABLE IF EXISTS sip_gateways;
|
||||
|
||||
DROP TABLE IF EXISTS `service_providers`;
|
||||
DROP TABLE IF EXISTS voip_carriers;
|
||||
|
||||
DROP TABLE IF EXISTS `webhooks`;
|
||||
DROP TABLE IF EXISTS accounts;
|
||||
|
||||
SET FOREIGN_KEY_CHECKS = 1;
|
||||
DROP TABLE IF EXISTS applications;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `call_routes`
|
||||
DROP TABLE IF EXISTS service_providers;
|
||||
|
||||
DROP TABLE IF EXISTS webhooks;
|
||||
|
||||
CREATE TABLE call_routes
|
||||
(
|
||||
`call_route_sid` CHAR(36) NOT NULL UNIQUE ,
|
||||
`priority` INTEGER NOT NULL,
|
||||
`account_sid` CHAR(36) NOT NULL,
|
||||
`regex` VARCHAR(255) NOT NULL,
|
||||
`application_sid` CHAR(36) NOT NULL,
|
||||
PRIMARY KEY (`call_route_sid`)
|
||||
call_route_sid CHAR(36) NOT NULL UNIQUE ,
|
||||
priority INTEGER NOT NULL,
|
||||
account_sid CHAR(36) NOT NULL,
|
||||
regex VARCHAR(255) NOT NULL,
|
||||
application_sid CHAR(36) NOT NULL,
|
||||
PRIMARY KEY (call_route_sid)
|
||||
) ENGINE=InnoDB COMMENT='a regex-based pattern match for call routing';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `lcr_routes`
|
||||
CREATE TABLE lcr_routes
|
||||
(
|
||||
`lcr_route_sid` CHAR(36),
|
||||
`regex` VARCHAR(32) NOT NULL COMMENT 'regex-based pattern match against dialed number, used for LCR routing of PSTN calls',
|
||||
`description` VARCHAR(1024),
|
||||
`priority` INTEGER NOT NULL UNIQUE COMMENT 'lower priority routes are attempted first',
|
||||
PRIMARY KEY (`lcr_route_sid`)
|
||||
lcr_route_sid CHAR(36),
|
||||
regex VARCHAR(32) NOT NULL COMMENT 'regex-based pattern match against dialed number, used for LCR routing of PSTN calls',
|
||||
description VARCHAR(1024),
|
||||
priority INTEGER NOT NULL UNIQUE COMMENT 'lower priority routes are attempted first',
|
||||
PRIMARY KEY (lcr_route_sid)
|
||||
) COMMENT='Least cost routing table';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `api_keys`
|
||||
CREATE TABLE api_keys
|
||||
(
|
||||
`api_key_sid` CHAR(36) NOT NULL UNIQUE ,
|
||||
`token` CHAR(36) NOT NULL UNIQUE ,
|
||||
`account_sid` CHAR(36),
|
||||
`service_provider_sid` CHAR(36),
|
||||
PRIMARY KEY (`api_key_sid`)
|
||||
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,
|
||||
last_used TIMESTAMP NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (api_key_sid)
|
||||
) ENGINE=InnoDB COMMENT='An authorization token that is used to access the REST api';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `voip_carriers`
|
||||
CREATE TABLE ms_teams_tenants
|
||||
(
|
||||
`voip_carrier_sid` CHAR(36) NOT NULL UNIQUE ,
|
||||
`name` VARCHAR(64) NOT NULL UNIQUE ,
|
||||
`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',
|
||||
PRIMARY KEY (`voip_carrier_sid`)
|
||||
ms_teams_tenant_sid CHAR(36) NOT NULL UNIQUE ,
|
||||
service_provider_sid CHAR(36) NOT NULL,
|
||||
account_sid CHAR(36) NOT NULL,
|
||||
application_sid CHAR(36),
|
||||
tenant_fqdn VARCHAR(255) NOT NULL UNIQUE ,
|
||||
PRIMARY KEY (ms_teams_tenant_sid)
|
||||
) COMMENT='A Microsoft Teams customer tenant';
|
||||
|
||||
CREATE TABLE sbc_addresses
|
||||
(
|
||||
sbc_address_sid CHAR(36) NOT NULL UNIQUE ,
|
||||
ipv4 VARCHAR(255) NOT NULL,
|
||||
port INTEGER NOT NULL DEFAULT 5060,
|
||||
service_provider_sid CHAR(36),
|
||||
PRIMARY KEY (sbc_address_sid)
|
||||
);
|
||||
|
||||
CREATE TABLE users
|
||||
(
|
||||
user_sid CHAR(36) NOT NULL UNIQUE ,
|
||||
name CHAR(36) NOT NULL UNIQUE ,
|
||||
hashed_password VARCHAR(1024) NOT NULL,
|
||||
salt CHAR(16) NOT NULL,
|
||||
force_change BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
PRIMARY KEY (user_sid)
|
||||
);
|
||||
|
||||
CREATE TABLE voip_carriers
|
||||
(
|
||||
voip_carrier_sid CHAR(36) NOT NULL UNIQUE ,
|
||||
name VARCHAR(64) NOT NULL UNIQUE ,
|
||||
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,
|
||||
PRIMARY KEY (voip_carrier_sid)
|
||||
) ENGINE=InnoDB COMMENT='A Carrier or customer PBX that can send or receive calls';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `phone_numbers`
|
||||
CREATE TABLE phone_numbers
|
||||
(
|
||||
`phone_number_sid` CHAR(36) UNIQUE ,
|
||||
`number` VARCHAR(32) NOT NULL UNIQUE ,
|
||||
`voip_carrier_sid` CHAR(36) NOT NULL,
|
||||
`account_sid` CHAR(36),
|
||||
`application_sid` CHAR(36),
|
||||
PRIMARY KEY (`phone_number_sid`)
|
||||
phone_number_sid CHAR(36) UNIQUE ,
|
||||
number VARCHAR(32) NOT NULL UNIQUE ,
|
||||
voip_carrier_sid CHAR(36) NOT NULL,
|
||||
account_sid CHAR(36),
|
||||
application_sid CHAR(36),
|
||||
PRIMARY KEY (phone_number_sid)
|
||||
) ENGINE=InnoDB COMMENT='A phone number that has been assigned to an account';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `webhooks`
|
||||
CREATE TABLE webhooks
|
||||
(
|
||||
`webhook_sid` CHAR(36) NOT NULL UNIQUE ,
|
||||
`url` VARCHAR(1024) NOT NULL,
|
||||
`method` ENUM("GET","POST") NOT NULL DEFAULT 'POST',
|
||||
`username` VARCHAR(255),
|
||||
`password` VARCHAR(255),
|
||||
PRIMARY KEY (`webhook_sid`)
|
||||
webhook_sid CHAR(36) NOT NULL UNIQUE ,
|
||||
url VARCHAR(1024) NOT NULL,
|
||||
method ENUM("GET","POST") NOT NULL DEFAULT 'POST',
|
||||
username VARCHAR(255),
|
||||
password VARCHAR(255),
|
||||
PRIMARY KEY (webhook_sid)
|
||||
) COMMENT='An HTTP callback';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `lcr_carrier_set_entry`
|
||||
CREATE TABLE sip_gateways
|
||||
(
|
||||
`lcr_carrier_set_entry_sid` CHAR(36),
|
||||
`workload` INTEGER NOT NULL DEFAULT 1 COMMENT 'represents a proportion of traffic to send through the associated carrier; can be used for load balancing traffic across carriers with a common priority for a destination',
|
||||
`lcr_route_sid` CHAR(36) NOT NULL,
|
||||
`voip_carrier_sid` CHAR(36) NOT NULL,
|
||||
`priority` INTEGER NOT NULL DEFAULT 0 COMMENT 'lower priority carriers are attempted first',
|
||||
PRIMARY KEY (`lcr_carrier_set_entry_sid`)
|
||||
) COMMENT='An entry in the LCR routing list';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `sip_gateways`
|
||||
(
|
||||
`sip_gateway_sid` CHAR(36),
|
||||
`ipv4` VARCHAR(32) NOT NULL COMMENT 'ip address or DNS name of the gateway. For gateways providing inbound calling service, ip address is required.',
|
||||
`port` INTEGER NOT NULL DEFAULT 5060 COMMENT 'sip signaling port',
|
||||
`inbound` BOOLEAN NOT NULL COMMENT 'if true, whitelist this IP to allow inbound calls from the gateway',
|
||||
`outbound` BOOLEAN NOT NULL COMMENT 'if true, include in least-cost routing when placing calls to the PSTN',
|
||||
`voip_carrier_sid` CHAR(36) NOT NULL,
|
||||
`is_active` BOOLEAN NOT NULL DEFAULT 1,
|
||||
PRIMARY KEY (`sip_gateway_sid`)
|
||||
sip_gateway_sid CHAR(36),
|
||||
ipv4 VARCHAR(128) NOT NULL COMMENT 'ip address or DNS name of the gateway. For gateways providing inbound calling service, ip address is required.',
|
||||
port INTEGER NOT NULL DEFAULT 5060 COMMENT 'sip signaling port',
|
||||
inbound BOOLEAN NOT NULL COMMENT 'if true, whitelist this IP to allow inbound calls from the gateway',
|
||||
outbound BOOLEAN NOT NULL COMMENT 'if true, include in least-cost routing when placing calls to the PSTN',
|
||||
voip_carrier_sid CHAR(36) NOT NULL,
|
||||
is_active BOOLEAN NOT NULL DEFAULT 1,
|
||||
PRIMARY KEY (sip_gateway_sid)
|
||||
) COMMENT='A whitelisted sip gateway used for origination/termination';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `applications`
|
||||
CREATE TABLE lcr_carrier_set_entry
|
||||
(
|
||||
`application_sid` CHAR(36) NOT NULL UNIQUE ,
|
||||
`name` VARCHAR(64) NOT NULL,
|
||||
`account_sid` CHAR(36) NOT NULL COMMENT 'account that this application belongs to',
|
||||
`call_hook_sid` CHAR(36) COMMENT 'webhook to call for inbound calls to phone numbers owned by this account',
|
||||
`call_status_hook_sid` CHAR(36) COMMENT 'webhook to call for call status events',
|
||||
`speech_synthesis_vendor` VARCHAR(64) NOT NULL DEFAULT 'google',
|
||||
`speech_synthesis_voice` VARCHAR(64) NOT NULL DEFAULT 'en-US-Wavenet-C',
|
||||
`speech_recognizer_vendor` VARCHAR(64) NOT NULL DEFAULT 'google',
|
||||
`speech_recognizer_language` VARCHAR(64) NOT NULL DEFAULT 'en-US',
|
||||
PRIMARY KEY (`application_sid`)
|
||||
lcr_carrier_set_entry_sid CHAR(36),
|
||||
workload INTEGER NOT NULL DEFAULT 1 COMMENT 'represents a proportion of traffic to send through the associated carrier; can be used for load balancing traffic across carriers with a common priority for a destination',
|
||||
lcr_route_sid CHAR(36) NOT NULL,
|
||||
voip_carrier_sid CHAR(36) NOT NULL,
|
||||
priority INTEGER NOT NULL DEFAULT 0 COMMENT 'lower priority carriers are attempted first',
|
||||
PRIMARY KEY (lcr_carrier_set_entry_sid)
|
||||
) COMMENT='An entry in the LCR routing list';
|
||||
|
||||
CREATE TABLE applications
|
||||
(
|
||||
application_sid CHAR(36) NOT NULL UNIQUE ,
|
||||
name VARCHAR(64) NOT NULL,
|
||||
account_sid CHAR(36) NOT NULL COMMENT 'account that this application belongs to',
|
||||
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 ',
|
||||
speech_synthesis_vendor VARCHAR(64) NOT NULL DEFAULT 'google',
|
||||
speech_synthesis_language VARCHAR(12) NOT NULL DEFAULT 'en-US',
|
||||
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)
|
||||
) ENGINE=InnoDB COMMENT='A defined set of behaviors to be applied to phone calls ';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `service_providers`
|
||||
CREATE TABLE service_providers
|
||||
(
|
||||
`service_provider_sid` CHAR(36) NOT NULL UNIQUE ,
|
||||
`name` VARCHAR(64) NOT NULL UNIQUE ,
|
||||
`description` VARCHAR(255),
|
||||
`root_domain` VARCHAR(128) UNIQUE ,
|
||||
`registration_hook_sid` CHAR(36),
|
||||
PRIMARY KEY (`service_provider_sid`)
|
||||
service_provider_sid CHAR(36) NOT NULL UNIQUE ,
|
||||
name VARCHAR(64) NOT NULL UNIQUE ,
|
||||
description VARCHAR(255),
|
||||
root_domain VARCHAR(128) UNIQUE ,
|
||||
registration_hook_sid CHAR(36),
|
||||
ms_teams_fqdn VARCHAR(255),
|
||||
PRIMARY KEY (service_provider_sid)
|
||||
) ENGINE=InnoDB COMMENT='A partition of the platform used by one service provider';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `accounts`
|
||||
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`)
|
||||
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_routes_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`);
|
||||
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);
|
||||
|
||||
ALTER TABLE `call_routes` ADD FOREIGN KEY application_sid_idxfk (`application_sid`) REFERENCES `applications` (`application_sid`);
|
||||
ALTER TABLE call_routes ADD FOREIGN KEY application_sid_idxfk (application_sid) REFERENCES applications (application_sid);
|
||||
|
||||
CREATE INDEX `api_keys_api_key_sid_idx` ON `api_keys` (`api_key_sid`);
|
||||
CREATE INDEX `api_keys_account_sid_idx` ON `api_keys` (`account_sid`);
|
||||
ALTER TABLE `api_keys` ADD FOREIGN KEY account_sid_idxfk_1 (`account_sid`) REFERENCES `accounts` (`account_sid`);
|
||||
CREATE INDEX api_key_sid_idx ON api_keys (api_key_sid);
|
||||
CREATE INDEX account_sid_idx ON api_keys (account_sid);
|
||||
ALTER TABLE api_keys ADD FOREIGN KEY account_sid_idxfk_1 (account_sid) REFERENCES accounts (account_sid);
|
||||
|
||||
CREATE INDEX `api_keys_service_provider_sid_idx` ON `api_keys` (`service_provider_sid`);
|
||||
ALTER TABLE `api_keys` ADD FOREIGN KEY service_provider_sid_idxfk (`service_provider_sid`) REFERENCES `service_providers` (`service_provider_sid`);
|
||||
CREATE INDEX service_provider_sid_idx ON api_keys (service_provider_sid);
|
||||
ALTER TABLE api_keys ADD FOREIGN KEY service_provider_sid_idxfk (service_provider_sid) REFERENCES service_providers (service_provider_sid);
|
||||
|
||||
CREATE INDEX `voip_carriers_voip_carrier_sid_idx` ON `voip_carriers` (`voip_carrier_sid`);
|
||||
CREATE INDEX `voip_carriers_name_idx` ON `voip_carriers` (`name`);
|
||||
ALTER TABLE `voip_carriers` ADD FOREIGN KEY account_sid_idxfk_2 (`account_sid`) REFERENCES `accounts` (`account_sid`);
|
||||
CREATE INDEX ms_teams_tenant_sid_idx ON ms_teams_tenants (ms_teams_tenant_sid);
|
||||
ALTER TABLE ms_teams_tenants ADD FOREIGN KEY service_provider_sid_idxfk_1 (service_provider_sid) REFERENCES service_providers (service_provider_sid);
|
||||
|
||||
ALTER TABLE `voip_carriers` ADD FOREIGN KEY application_sid_idxfk_1 (`application_sid`) REFERENCES `applications` (`application_sid`);
|
||||
ALTER TABLE ms_teams_tenants ADD FOREIGN KEY account_sid_idxfk_2 (account_sid) REFERENCES accounts (account_sid);
|
||||
|
||||
CREATE INDEX `phone_numbers_phone_number_sid_idx` ON `phone_numbers` (`phone_number_sid`);
|
||||
CREATE INDEX `phone_numbers_voip_carrier_sid_idx` ON `phone_numbers` (`voip_carrier_sid`);
|
||||
ALTER TABLE `phone_numbers` ADD FOREIGN KEY voip_carrier_sid_idxfk (`voip_carrier_sid`) REFERENCES `voip_carriers` (`voip_carrier_sid`);
|
||||
ALTER TABLE ms_teams_tenants ADD FOREIGN KEY application_sid_idxfk_1 (application_sid) REFERENCES applications (application_sid);
|
||||
|
||||
ALTER TABLE `phone_numbers` ADD FOREIGN KEY account_sid_idxfk_3 (`account_sid`) REFERENCES `accounts` (`account_sid`);
|
||||
CREATE INDEX tenant_fqdn_idx ON ms_teams_tenants (tenant_fqdn);
|
||||
CREATE INDEX sbc_addresses_idx_host_port ON sbc_addresses (ipv4,port);
|
||||
|
||||
ALTER TABLE `phone_numbers` ADD FOREIGN KEY application_sid_idxfk_2 (`application_sid`) REFERENCES `applications` (`application_sid`);
|
||||
CREATE INDEX sbc_address_sid_idx ON sbc_addresses (sbc_address_sid);
|
||||
CREATE INDEX service_provider_sid_idx ON sbc_addresses (service_provider_sid);
|
||||
ALTER TABLE sbc_addresses ADD FOREIGN KEY service_provider_sid_idxfk_2 (service_provider_sid) REFERENCES service_providers (service_provider_sid);
|
||||
|
||||
CREATE INDEX `webhooks_webhook_sid_idx` ON `webhooks` (`webhook_sid`);
|
||||
ALTER TABLE `lcr_carrier_set_entry` ADD FOREIGN KEY lcr_route_sid_idxfk (`lcr_route_sid`) REFERENCES `lcr_routes` (`lcr_route_sid`);
|
||||
CREATE INDEX user_sid_idx ON users (user_sid);
|
||||
CREATE INDEX name_idx ON users (name);
|
||||
CREATE INDEX voip_carrier_sid_idx ON voip_carriers (voip_carrier_sid);
|
||||
CREATE INDEX name_idx ON voip_carriers (name);
|
||||
ALTER TABLE voip_carriers ADD FOREIGN KEY account_sid_idxfk_3 (account_sid) REFERENCES accounts (account_sid);
|
||||
|
||||
ALTER TABLE `lcr_carrier_set_entry` ADD FOREIGN KEY voip_carrier_sid_idxfk_1 (`voip_carrier_sid`) REFERENCES `voip_carriers` (`voip_carrier_sid`);
|
||||
ALTER TABLE voip_carriers ADD FOREIGN KEY application_sid_idxfk_2 (application_sid) REFERENCES applications (application_sid);
|
||||
|
||||
CREATE UNIQUE INDEX `sip_gateways_sip_gateway_idx_hostport` ON `sip_gateways` (`ipv4`,`port`);
|
||||
CREATE INDEX phone_number_sid_idx ON phone_numbers (phone_number_sid);
|
||||
CREATE INDEX voip_carrier_sid_idx ON phone_numbers (voip_carrier_sid);
|
||||
ALTER TABLE phone_numbers ADD FOREIGN KEY voip_carrier_sid_idxfk (voip_carrier_sid) REFERENCES voip_carriers (voip_carrier_sid);
|
||||
|
||||
ALTER TABLE `sip_gateways` ADD FOREIGN KEY voip_carrier_sid_idxfk_2 (`voip_carrier_sid`) REFERENCES `voip_carriers` (`voip_carrier_sid`);
|
||||
ALTER TABLE phone_numbers ADD FOREIGN KEY account_sid_idxfk_4 (account_sid) REFERENCES accounts (account_sid);
|
||||
|
||||
CREATE UNIQUE INDEX `applications_idx_name` ON `applications` (`account_sid`,`name`);
|
||||
ALTER TABLE phone_numbers ADD FOREIGN KEY application_sid_idxfk_3 (application_sid) REFERENCES applications (application_sid);
|
||||
|
||||
CREATE INDEX `applications_application_sid_idx` ON `applications` (`application_sid`);
|
||||
CREATE INDEX `applications_name_idx` ON `applications` (`name`);
|
||||
CREATE INDEX `applications_account_sid_idx` ON `applications` (`account_sid`);
|
||||
ALTER TABLE `applications` ADD FOREIGN KEY account_sid_idxfk_4 (`account_sid`) REFERENCES `accounts` (`account_sid`);
|
||||
CREATE INDEX webhook_sid_idx ON webhooks (webhook_sid);
|
||||
CREATE UNIQUE INDEX sip_gateway_idx_hostport ON sip_gateways (ipv4,port);
|
||||
|
||||
ALTER TABLE `applications` ADD FOREIGN KEY call_hook_sid_idxfk (`call_hook_sid`) REFERENCES `webhooks` (`webhook_sid`);
|
||||
ALTER TABLE sip_gateways ADD FOREIGN KEY voip_carrier_sid_idxfk_1 (voip_carrier_sid) REFERENCES voip_carriers (voip_carrier_sid);
|
||||
|
||||
ALTER TABLE `applications` ADD FOREIGN KEY call_status_hook_sid_idxfk (`call_status_hook_sid`) REFERENCES `webhooks` (`webhook_sid`);
|
||||
ALTER TABLE lcr_carrier_set_entry ADD FOREIGN KEY lcr_route_sid_idxfk (lcr_route_sid) REFERENCES lcr_routes (lcr_route_sid);
|
||||
|
||||
CREATE INDEX `service_providers_service_provider_sid_idx` ON `service_providers` (`service_provider_sid`);
|
||||
CREATE INDEX `service_providers_name_idx` ON `service_providers` (`name`);
|
||||
CREATE INDEX `service_providers_root_domain_idx` ON `service_providers` (`root_domain`);
|
||||
ALTER TABLE `service_providers` ADD FOREIGN KEY registration_hook_sid_idxfk (`registration_hook_sid`) REFERENCES `webhooks` (`webhook_sid`);
|
||||
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 `accounts_account_sid_idx` ON `accounts` (`account_sid`);
|
||||
CREATE INDEX `accounts_name_idx` ON `accounts` (`name`);
|
||||
CREATE INDEX `accounts_sip_realm_idx` ON `accounts` (`sip_realm`);
|
||||
CREATE INDEX `accounts_service_provider_sid_idx` ON `accounts` (`service_provider_sid`);
|
||||
ALTER TABLE `accounts` ADD FOREIGN KEY service_provider_sid_idxfk_1 (`service_provider_sid`) REFERENCES `service_providers` (`service_provider_sid`);
|
||||
CREATE UNIQUE INDEX applications_idx_name ON applications (account_sid,name);
|
||||
|
||||
ALTER TABLE `accounts` ADD FOREIGN KEY registration_hook_sid_idxfk_1 (`registration_hook_sid`) REFERENCES `webhooks` (`webhook_sid`);
|
||||
CREATE INDEX application_sid_idx ON applications (application_sid);
|
||||
CREATE INDEX account_sid_idx ON applications (account_sid);
|
||||
ALTER TABLE applications ADD FOREIGN KEY account_sid_idxfk_5 (account_sid) REFERENCES accounts (account_sid);
|
||||
|
||||
ALTER TABLE `accounts` ADD FOREIGN KEY device_calling_application_sid_idxfk (`device_calling_application_sid`) REFERENCES `applications` (`application_sid`);
|
||||
ALTER TABLE applications ADD FOREIGN KEY call_hook_sid_idxfk (call_hook_sid) REFERENCES webhooks (webhook_sid);
|
||||
|
||||
ALTER TABLE applications ADD FOREIGN KEY call_status_hook_sid_idxfk (call_status_hook_sid) REFERENCES webhooks (webhook_sid);
|
||||
|
||||
ALTER TABLE applications ADD FOREIGN KEY messaging_hook_sid_idxfk (messaging_hook_sid) REFERENCES webhooks (webhook_sid);
|
||||
|
||||
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 (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;
|
||||
|
||||
319
db/jambones.sqs
319
db/jambones.sqs
@@ -1,5 +1,57 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<SQLContainer>
|
||||
<SQLTable>
|
||||
<name><![CDATA[users]]></name>
|
||||
<schema><![CDATA[]]></schema>
|
||||
<location>
|
||||
<x>1599.00</x>
|
||||
<y>37.00</y>
|
||||
</location>
|
||||
<size>
|
||||
<width>250.00</width>
|
||||
<height>120.00</height>
|
||||
</size>
|
||||
<zorder>11</zorder>
|
||||
<SQLField>
|
||||
<name><![CDATA[user_sid]]></name>
|
||||
<type><![CDATA[CHAR(36)]]></type>
|
||||
<primaryKey>1</primaryKey>
|
||||
<indexed><![CDATA[1]]></indexed>
|
||||
<notNull><![CDATA[1]]></notNull>
|
||||
<uid><![CDATA[F33F9604-ADC9-46E3-AE51-A2A839A81758]]></uid>
|
||||
<unique><![CDATA[1]]></unique>
|
||||
</SQLField>
|
||||
<SQLField>
|
||||
<name><![CDATA[name]]></name>
|
||||
<type><![CDATA[CHAR(36)]]></type>
|
||||
<indexed><![CDATA[1]]></indexed>
|
||||
<notNull><![CDATA[1]]></notNull>
|
||||
<uid><![CDATA[6E8DE796-46DA-4644-AA7F-1A75D9B834FB]]></uid>
|
||||
<unique><![CDATA[1]]></unique>
|
||||
</SQLField>
|
||||
<SQLField>
|
||||
<name><![CDATA[hashed_password]]></name>
|
||||
<type><![CDATA[VARCHAR(1024)]]></type>
|
||||
<notNull><![CDATA[1]]></notNull>
|
||||
<uid><![CDATA[3E7E38A2-96FC-4E28-BF10-332823FB17F1]]></uid>
|
||||
</SQLField>
|
||||
<SQLField>
|
||||
<name><![CDATA[salt]]></name>
|
||||
<type><![CDATA[CHAR(16)]]></type>
|
||||
<notNull><![CDATA[1]]></notNull>
|
||||
<uid><![CDATA[F21A170F-5922-4749-A062-6C7516051560]]></uid>
|
||||
</SQLField>
|
||||
<SQLField>
|
||||
<name><![CDATA[force_change]]></name>
|
||||
<type><![CDATA[BOOLEAN]]></type>
|
||||
<defaultValue><![CDATA[TRUE]]></defaultValue>
|
||||
<notNull><![CDATA[1]]></notNull>
|
||||
<uid><![CDATA[57655C0C-9427-4CC7-9502-24ACF56AAECF]]></uid>
|
||||
</SQLField>
|
||||
<labelWindowIndex><![CDATA[3]]></labelWindowIndex>
|
||||
<ui.treeExpanded><![CDATA[1]]></ui.treeExpanded>
|
||||
<uid><![CDATA[2A735FAB-592C-42E5-9C8B-06B109314799]]></uid>
|
||||
</SQLTable>
|
||||
<SQLTable>
|
||||
<name><![CDATA[voip_carriers]]></name>
|
||||
<schema><![CDATA[]]></schema>
|
||||
@@ -11,7 +63,7 @@
|
||||
</location>
|
||||
<size>
|
||||
<width>266.00</width>
|
||||
<height>120.00</height>
|
||||
<height>140.00</height>
|
||||
</size>
|
||||
<zorder>6</zorder>
|
||||
<SQLField>
|
||||
@@ -67,7 +119,14 @@
|
||||
<objectComment><![CDATA[If provided, all incoming calls from this source will be routed to the associated application]]></objectComment>
|
||||
<uid><![CDATA[B6545E2E-7F55-4082-AEFA-29F50C137D64]]></uid>
|
||||
</SQLField>
|
||||
<labelWindowIndex><![CDATA[5]]></labelWindowIndex>
|
||||
<SQLField>
|
||||
<name><![CDATA[e164_leading_plus]]></name>
|
||||
<type><![CDATA[BOOLEAN]]></type>
|
||||
<defaultValue><![CDATA[false]]></defaultValue>
|
||||
<notNull><![CDATA[1]]></notNull>
|
||||
<uid><![CDATA[123EA4AC-627B-42A1-8779-D72494E8D47F]]></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>
|
||||
<uid><![CDATA[3D3136A7-AFC0-4A70-AEC3-68577955CA2E]]></uid>
|
||||
@@ -78,12 +137,12 @@
|
||||
<comment><![CDATA[An authorization token that is used to access the REST api]]></comment>
|
||||
<tableType><![CDATA[InnoDB]]></tableType>
|
||||
<location>
|
||||
<x>1279.00</x>
|
||||
<y>61.00</y>
|
||||
<x>1319.00</x>
|
||||
<y>38.00</y>
|
||||
</location>
|
||||
<size>
|
||||
<width>252.00</width>
|
||||
<height>100.00</height>
|
||||
<width>245.00</width>
|
||||
<height>160.00</height>
|
||||
</size>
|
||||
<zorder>1</zorder>
|
||||
<SQLField>
|
||||
@@ -132,7 +191,25 @@
|
||||
<indexed><![CDATA[1]]></indexed>
|
||||
<uid><![CDATA[3F553F20-AA47-471E-A650-0B4CEC9DCB0A]]></uid>
|
||||
</SQLField>
|
||||
<labelWindowIndex><![CDATA[10]]></labelWindowIndex>
|
||||
<SQLField>
|
||||
<name><![CDATA[expires_at]]></name>
|
||||
<type><![CDATA[TIMESTAMP]]></type>
|
||||
<defaultValue><![CDATA[NULL]]></defaultValue>
|
||||
<uid><![CDATA[DE86BC18-858E-4D7E-9B83-891DB2861434]]></uid>
|
||||
</SQLField>
|
||||
<SQLField>
|
||||
<name><![CDATA[last_used]]></name>
|
||||
<type><![CDATA[TIMESTAMP]]></type>
|
||||
<defaultValue><![CDATA[NULL]]></defaultValue>
|
||||
<uid><![CDATA[11A93288-B892-436B-9BB4-D5C3B70DB061]]></uid>
|
||||
</SQLField>
|
||||
<SQLField>
|
||||
<name><![CDATA[created_at]]></name>
|
||||
<type><![CDATA[TIMESTAMP]]></type>
|
||||
<defaultValue><![CDATA[CURRENT_TIMESTAMP]]></defaultValue>
|
||||
<uid><![CDATA[C84C9B6A-80B5-4B0B-8C14-EB02F7421BBE]]></uid>
|
||||
</SQLField>
|
||||
<labelWindowIndex><![CDATA[13]]></labelWindowIndex>
|
||||
<objectComment><![CDATA[An authorization token that is used to access the REST api]]></objectComment>
|
||||
<ui.treeExpanded><![CDATA[1]]></ui.treeExpanded>
|
||||
<uid><![CDATA[3EDF89A0-FD38-4DF9-BB65-E0FCD0A678BE]]></uid>
|
||||
@@ -142,8 +219,8 @@
|
||||
<schema><![CDATA[]]></schema>
|
||||
<comment><![CDATA[An HTTP callback]]></comment>
|
||||
<location>
|
||||
<x>1383.00</x>
|
||||
<y>365.00</y>
|
||||
<x>1315.00</x>
|
||||
<y>376.00</y>
|
||||
</location>
|
||||
<size>
|
||||
<width>254.00</width>
|
||||
@@ -183,7 +260,7 @@
|
||||
<type><![CDATA[VARCHAR(255)]]></type>
|
||||
<uid><![CDATA[04BB457A-D532-4780-8A58-5900094171EC]]></uid>
|
||||
</SQLField>
|
||||
<labelWindowIndex><![CDATA[1]]></labelWindowIndex>
|
||||
<labelWindowIndex><![CDATA[4]]></labelWindowIndex>
|
||||
<objectComment><![CDATA[An HTTP callback]]></objectComment>
|
||||
<ui.treeExpanded><![CDATA[1]]></ui.treeExpanded>
|
||||
<uid><![CDATA[64D64CB9-0990-4C68-BE71-F9FD43C2BE19]]></uid>
|
||||
@@ -194,8 +271,8 @@
|
||||
<comment><![CDATA[a regex-based pattern match for call routing]]></comment>
|
||||
<tableType><![CDATA[InnoDB]]></tableType>
|
||||
<location>
|
||||
<x>406.00</x>
|
||||
<y>425.00</y>
|
||||
<x>424.00</x>
|
||||
<y>461.00</y>
|
||||
</location>
|
||||
<size>
|
||||
<width>254.00</width>
|
||||
@@ -254,11 +331,89 @@
|
||||
<uid><![CDATA[9B4208B5-9E3B-4B76-B7F7-4E5D36B99BF2]]></uid>
|
||||
<unsigned><![CDATA[0]]></unsigned>
|
||||
</SQLField>
|
||||
<labelWindowIndex><![CDATA[9]]></labelWindowIndex>
|
||||
<labelWindowIndex><![CDATA[12]]></labelWindowIndex>
|
||||
<objectComment><![CDATA[a regex-based pattern match for call routing]]></objectComment>
|
||||
<ui.treeExpanded><![CDATA[1]]></ui.treeExpanded>
|
||||
<uid><![CDATA[78584D93-2CD7-4495-9C5E-893C7B869133]]></uid>
|
||||
</SQLTable>
|
||||
<SQLTable>
|
||||
<name><![CDATA[ms_teams_tenants]]></name>
|
||||
<schema><![CDATA[]]></schema>
|
||||
<comment><![CDATA[A Microsoft Teams customer tenant]]></comment>
|
||||
<location>
|
||||
<x>1309.00</x>
|
||||
<y>219.00</y>
|
||||
</location>
|
||||
<size>
|
||||
<width>298.00</width>
|
||||
<height>120.00</height>
|
||||
</size>
|
||||
<zorder>12</zorder>
|
||||
<SQLField>
|
||||
<name><![CDATA[ms_teams_tenant_sid]]></name>
|
||||
<type><![CDATA[CHAR(36)]]></type>
|
||||
<primaryKey>1</primaryKey>
|
||||
<indexed><![CDATA[1]]></indexed>
|
||||
<notNull><![CDATA[1]]></notNull>
|
||||
<uid><![CDATA[A0E48CB5-C2B2-47FC-8486-EF5F16DAF797]]></uid>
|
||||
<unique><![CDATA[1]]></unique>
|
||||
</SQLField>
|
||||
<SQLField>
|
||||
<name><![CDATA[service_provider_sid]]></name>
|
||||
<type><![CDATA[CHAR(36)]]></type>
|
||||
<referencesField>service_provider_sid</referencesField>
|
||||
<referencesTable>service_providers</referencesTable>
|
||||
<referencesField><![CDATA[service_provider_sid]]></referencesField>
|
||||
<referencesTable><![CDATA[service_providers]]></referencesTable>
|
||||
<sourceCardinality>4</sourceCardinality>
|
||||
<destinationCardinality>1</destinationCardinality>
|
||||
<referencesFieldUID><![CDATA[58E1702C-6A95-4B17-8C08-8A3810EA16A1]]></referencesFieldUID>
|
||||
<referencesTableUID><![CDATA[F294B51E-F867-47CA-BC1F-F70BDF8170FF]]></referencesTableUID>
|
||||
<indexed><![CDATA[0]]></indexed>
|
||||
<notNull><![CDATA[1]]></notNull>
|
||||
<uid><![CDATA[519A33A0-B678-4F60-892F-ADA90E656500]]></uid>
|
||||
</SQLField>
|
||||
<SQLField>
|
||||
<name><![CDATA[account_sid]]></name>
|
||||
<type><![CDATA[CHAR(36)]]></type>
|
||||
<referencesField>account_sid</referencesField>
|
||||
<referencesTable>accounts</referencesTable>
|
||||
<referencesField><![CDATA[account_sid]]></referencesField>
|
||||
<referencesTable><![CDATA[accounts]]></referencesTable>
|
||||
<sourceCardinality>4</sourceCardinality>
|
||||
<destinationCardinality>2</destinationCardinality>
|
||||
<referencesFieldUID><![CDATA[1342FAFA-C15C-429B-809B-C6C55F9FA5B6]]></referencesFieldUID>
|
||||
<referencesTableUID><![CDATA[985D6997-B1A7-4AB3-80F4-4D59B45480C8]]></referencesTableUID>
|
||||
<notNull><![CDATA[1]]></notNull>
|
||||
<uid><![CDATA[EB48F39F-9D5F-43E0-BE8A-34A5C1304A76]]></uid>
|
||||
</SQLField>
|
||||
<SQLField>
|
||||
<name><![CDATA[application_sid]]></name>
|
||||
<type><![CDATA[CHAR(36)]]></type>
|
||||
<referencesField>application_sid</referencesField>
|
||||
<referencesTable>applications</referencesTable>
|
||||
<referencesField><![CDATA[application_sid]]></referencesField>
|
||||
<referencesTable><![CDATA[applications]]></referencesTable>
|
||||
<sourceCardinality>4</sourceCardinality>
|
||||
<destinationCardinality>2</destinationCardinality>
|
||||
<referencesFieldUID><![CDATA[EF943D13-DCB0-43C1-B03F-550612E20F9D]]></referencesFieldUID>
|
||||
<referencesTableUID><![CDATA[E97EE4F0-7ED7-4E8C-862E-D98192D6EAE0]]></referencesTableUID>
|
||||
<forcedUnique><![CDATA[0]]></forcedUnique>
|
||||
<uid><![CDATA[7959F455-D49D-4185-97B5-37C7FAAB339B]]></uid>
|
||||
</SQLField>
|
||||
<SQLField>
|
||||
<name><![CDATA[tenant_fqdn]]></name>
|
||||
<type><![CDATA[VARCHAR(255)]]></type>
|
||||
<indexed><![CDATA[1]]></indexed>
|
||||
<notNull><![CDATA[1]]></notNull>
|
||||
<uid><![CDATA[1DDAD1A1-942D-4487-89C8-D496B7F82274]]></uid>
|
||||
<unique><![CDATA[1]]></unique>
|
||||
</SQLField>
|
||||
<labelWindowIndex><![CDATA[2]]></labelWindowIndex>
|
||||
<objectComment><![CDATA[A Microsoft Teams customer tenant]]></objectComment>
|
||||
<ui.treeExpanded><![CDATA[1]]></ui.treeExpanded>
|
||||
<uid><![CDATA[92FD042A-5AEC-4D8F-AB94-C73C0F566F75]]></uid>
|
||||
</SQLTable>
|
||||
<SQLTable>
|
||||
<name><![CDATA[lcr_carrier_set_entry]]></name>
|
||||
<schema><![CDATA[]]></schema>
|
||||
@@ -323,7 +478,7 @@
|
||||
<objectComment><![CDATA[lower priority carriers are attempted first]]></objectComment>
|
||||
<uid><![CDATA[01F61C68-799B-49B0-9E6A-0E2162EE5A54]]></uid>
|
||||
</SQLField>
|
||||
<labelWindowIndex><![CDATA[4]]></labelWindowIndex>
|
||||
<labelWindowIndex><![CDATA[7]]></labelWindowIndex>
|
||||
<objectComment><![CDATA[An entry in the LCR routing list]]></objectComment>
|
||||
<ui.treeExpanded><![CDATA[1]]></ui.treeExpanded>
|
||||
<uid><![CDATA[956025F5-0798-47F7-B76C-457814C7B52E]]></uid>
|
||||
@@ -356,7 +511,7 @@
|
||||
<SQLField>
|
||||
<name><![CDATA[name]]></name>
|
||||
<type><![CDATA[VARCHAR(64)]]></type>
|
||||
<indexed><![CDATA[1]]></indexed>
|
||||
<indexed><![CDATA[0]]></indexed>
|
||||
<notNull><![CDATA[1]]></notNull>
|
||||
<uid><![CDATA[87414407-17CB-4582-9C0B-73CA548E1016]]></uid>
|
||||
</SQLField>
|
||||
@@ -421,7 +576,7 @@
|
||||
<notNull><![CDATA[1]]></notNull>
|
||||
<uid><![CDATA[C7130A90-DBB4-424D-A9A9-CB203C32350C]]></uid>
|
||||
</SQLField>
|
||||
<labelWindowIndex><![CDATA[8]]></labelWindowIndex>
|
||||
<labelWindowIndex><![CDATA[11]]></labelWindowIndex>
|
||||
<objectComment><![CDATA[An enterprise that uses the platform for comm services]]></objectComment>
|
||||
<ui.treeExpanded><![CDATA[1]]></ui.treeExpanded>
|
||||
<uid><![CDATA[985D6997-B1A7-4AB3-80F4-4D59B45480C8]]></uid>
|
||||
@@ -503,7 +658,7 @@
|
||||
<uid><![CDATA[962CB80A-54CB-4C6A-9591-9BFC644CF80F]]></uid>
|
||||
<unsigned><![CDATA[0]]></unsigned>
|
||||
</SQLField>
|
||||
<labelWindowIndex><![CDATA[7]]></labelWindowIndex>
|
||||
<labelWindowIndex><![CDATA[10]]></labelWindowIndex>
|
||||
<objectComment><![CDATA[A phone number that has been assigned to an account]]></objectComment>
|
||||
<ui.treeExpanded><![CDATA[1]]></ui.treeExpanded>
|
||||
<uid><![CDATA[BA650DDC-AC7B-4DFE-A5E5-828C75607807]]></uid>
|
||||
@@ -529,7 +684,7 @@
|
||||
</SQLField>
|
||||
<SQLField>
|
||||
<name><![CDATA[ipv4]]></name>
|
||||
<type><![CDATA[VARCHAR(32)]]></type>
|
||||
<type><![CDATA[VARCHAR(128)]]></type>
|
||||
<notNull><![CDATA[1]]></notNull>
|
||||
<objectComment><![CDATA[ip address or DNS name of the gateway. For gateways providing inbound calling service, ip address is required.]]></objectComment>
|
||||
<uid><![CDATA[F18DB7D4-F902-4863-870C-CB07032AE17C]]></uid>
|
||||
@@ -595,7 +750,7 @@
|
||||
<indexType><![CDATA[UNIQUE]]></indexType>
|
||||
<uid><![CDATA[1C744DE3-39BD-4EC6-B427-7EB2DD258771]]></uid>
|
||||
</SQLIndex>
|
||||
<labelWindowIndex><![CDATA[3]]></labelWindowIndex>
|
||||
<labelWindowIndex><![CDATA[6]]></labelWindowIndex>
|
||||
<objectComment><![CDATA[A whitelisted sip gateway used for origination/termination]]></objectComment>
|
||||
<ui.treeExpanded><![CDATA[1]]></ui.treeExpanded>
|
||||
<uid><![CDATA[D8A564E2-DA41-4217-8ACE-06CF77E9BEC1]]></uid>
|
||||
@@ -611,7 +766,7 @@
|
||||
</location>
|
||||
<size>
|
||||
<width>345.00</width>
|
||||
<height>220.00</height>
|
||||
<height>260.00</height>
|
||||
</size>
|
||||
<zorder>0</zorder>
|
||||
<SQLField>
|
||||
@@ -627,7 +782,7 @@
|
||||
<SQLField>
|
||||
<name><![CDATA[name]]></name>
|
||||
<type><![CDATA[VARCHAR(64)]]></type>
|
||||
<indexed><![CDATA[1]]></indexed>
|
||||
<indexed><![CDATA[0]]></indexed>
|
||||
<notNull><![CDATA[1]]></notNull>
|
||||
<uid><![CDATA[E0EDB7B1-B7F7-4F56-B94F-6B81BB87C514]]></uid>
|
||||
</SQLField>
|
||||
@@ -659,7 +814,7 @@
|
||||
<destinationCardinality>2</destinationCardinality>
|
||||
<referencesFieldUID><![CDATA[E046BA30-BC18-483C-A5C8-766E7160F574]]></referencesFieldUID>
|
||||
<referencesTableUID><![CDATA[64D64CB9-0990-4C68-BE71-F9FD43C2BE19]]></referencesTableUID>
|
||||
<objectComment><![CDATA[webhook to call for inbound calls to phone numbers owned by this account]]></objectComment>
|
||||
<objectComment><![CDATA[webhook to call for inbound calls ]]></objectComment>
|
||||
<uid><![CDATA[55AE0F31-209A-49F9-A6CB-1BB31BE4178A]]></uid>
|
||||
</SQLField>
|
||||
<SQLField>
|
||||
@@ -676,6 +831,20 @@
|
||||
<objectComment><![CDATA[webhook to call for call status events]]></objectComment>
|
||||
<uid><![CDATA[A3B4621B-DF13-4920-A718-068B20CB4E8A]]></uid>
|
||||
</SQLField>
|
||||
<SQLField>
|
||||
<name><![CDATA[messaging_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>4</sourceCardinality>
|
||||
<destinationCardinality>1</destinationCardinality>
|
||||
<referencesFieldUID><![CDATA[E046BA30-BC18-483C-A5C8-766E7160F574]]></referencesFieldUID>
|
||||
<referencesTableUID><![CDATA[64D64CB9-0990-4C68-BE71-F9FD43C2BE19]]></referencesTableUID>
|
||||
<objectComment><![CDATA[webhook to call for inbound SMS/MMS ]]></objectComment>
|
||||
<uid><![CDATA[4690A98A-2F67-4205-A5C1-D9523F6B3282]]></uid>
|
||||
</SQLField>
|
||||
<SQLField>
|
||||
<name><![CDATA[speech_synthesis_vendor]]></name>
|
||||
<type><![CDATA[VARCHAR(64)]]></type>
|
||||
@@ -683,11 +852,17 @@
|
||||
<notNull><![CDATA[1]]></notNull>
|
||||
<uid><![CDATA[05D3C937-255D-44A5-A102-40303FA37CF1]]></uid>
|
||||
</SQLField>
|
||||
<SQLField>
|
||||
<name><![CDATA[speech_synthesis_language]]></name>
|
||||
<type><![CDATA[VARCHAR(12)]]></type>
|
||||
<defaultValue><![CDATA[en-US]]></defaultValue>
|
||||
<notNull><![CDATA[1]]></notNull>
|
||||
<uid><![CDATA[2498FC63-58A1-40AF-8502-ABC4F1F5F541]]></uid>
|
||||
</SQLField>
|
||||
<SQLField>
|
||||
<name><![CDATA[speech_synthesis_voice]]></name>
|
||||
<type><![CDATA[VARCHAR(64)]]></type>
|
||||
<defaultValue><![CDATA[en-US-Wavenet-C]]></defaultValue>
|
||||
<notNull><![CDATA[1]]></notNull>
|
||||
<notNull><![CDATA[0]]></notNull>
|
||||
<uid><![CDATA[929D66F0-64B9-4D7C-AB4B-24F131E1178F]]></uid>
|
||||
</SQLField>
|
||||
<SQLField>
|
||||
@@ -722,11 +897,80 @@
|
||||
<indexType><![CDATA[UNIQUE]]></indexType>
|
||||
<uid><![CDATA[3FDDDF3B-375D-4DE4-B759-514438845F7D]]></uid>
|
||||
</SQLIndex>
|
||||
<labelWindowIndex><![CDATA[6]]></labelWindowIndex>
|
||||
<labelWindowIndex><![CDATA[9]]></labelWindowIndex>
|
||||
<objectComment><![CDATA[A defined set of behaviors to be applied to phone calls ]]></objectComment>
|
||||
<ui.treeExpanded><![CDATA[1]]></ui.treeExpanded>
|
||||
<uid><![CDATA[E97EE4F0-7ED7-4E8C-862E-D98192D6EAE0]]></uid>
|
||||
</SQLTable>
|
||||
<SQLTable>
|
||||
<name><![CDATA[sbc_addresses]]></name>
|
||||
<schema><![CDATA[]]></schema>
|
||||
<location>
|
||||
<x>1305.00</x>
|
||||
<y>564.00</y>
|
||||
</location>
|
||||
<size>
|
||||
<width>281.00</width>
|
||||
<height>120.00</height>
|
||||
</size>
|
||||
<zorder>13</zorder>
|
||||
<SQLField>
|
||||
<name><![CDATA[sbc_address_sid]]></name>
|
||||
<type><![CDATA[CHAR(36)]]></type>
|
||||
<primaryKey>1</primaryKey>
|
||||
<indexed><![CDATA[1]]></indexed>
|
||||
<notNull><![CDATA[1]]></notNull>
|
||||
<uid><![CDATA[468F8C06-5A38-494A-8E77-3F53F111237B]]></uid>
|
||||
<unique><![CDATA[1]]></unique>
|
||||
</SQLField>
|
||||
<SQLField>
|
||||
<name><![CDATA[ipv4]]></name>
|
||||
<type><![CDATA[VARCHAR(255)]]></type>
|
||||
<notNull><![CDATA[1]]></notNull>
|
||||
<uid><![CDATA[4FE97DA7-8D8E-4BE3-982A-01B0444FB070]]></uid>
|
||||
</SQLField>
|
||||
<SQLField>
|
||||
<name><![CDATA[port]]></name>
|
||||
<type><![CDATA[INTEGER]]></type>
|
||||
<defaultValue><![CDATA[5060]]></defaultValue>
|
||||
<notNull><![CDATA[1]]></notNull>
|
||||
<uid><![CDATA[707EF28F-F4AF-4B7E-AB04-C128C894ECE9]]></uid>
|
||||
</SQLField>
|
||||
<SQLIndex>
|
||||
<name><![CDATA[sbc_addresses_idx_host_port]]></name>
|
||||
<fieldName><![CDATA[ipv4]]></fieldName>
|
||||
<fieldName><![CDATA[port]]></fieldName>
|
||||
<SQLIndexEntry>
|
||||
<name><![CDATA[ipv4]]></name>
|
||||
<prefixSize><![CDATA[]]></prefixSize>
|
||||
<fieldUid><![CDATA[4FE97DA7-8D8E-4BE3-982A-01B0444FB070]]></fieldUid>
|
||||
</SQLIndexEntry>
|
||||
<SQLIndexEntry>
|
||||
<name><![CDATA[port]]></name>
|
||||
<prefixSize><![CDATA[]]></prefixSize>
|
||||
<fieldUid><![CDATA[707EF28F-F4AF-4B7E-AB04-C128C894ECE9]]></fieldUid>
|
||||
</SQLIndexEntry>
|
||||
<indexNamePrefix><![CDATA[sbc_addresses]]></indexNamePrefix>
|
||||
<uid><![CDATA[174BD35A-B09C-4F4A-B3D2-D5464D927D9B]]></uid>
|
||||
</SQLIndex>
|
||||
<SQLField>
|
||||
<name><![CDATA[service_provider_sid]]></name>
|
||||
<type><![CDATA[CHAR(36)]]></type>
|
||||
<referencesField>service_provider_sid</referencesField>
|
||||
<referencesTable>service_providers</referencesTable>
|
||||
<referencesField><![CDATA[service_provider_sid]]></referencesField>
|
||||
<referencesTable><![CDATA[service_providers]]></referencesTable>
|
||||
<sourceCardinality>4</sourceCardinality>
|
||||
<destinationCardinality>1</destinationCardinality>
|
||||
<referencesFieldUID><![CDATA[58E1702C-6A95-4B17-8C08-8A3810EA16A1]]></referencesFieldUID>
|
||||
<referencesTableUID><![CDATA[F294B51E-F867-47CA-BC1F-F70BDF8170FF]]></referencesTableUID>
|
||||
<indexed><![CDATA[1]]></indexed>
|
||||
<uid><![CDATA[6F249D1F-111F-45B4-B76C-8B5E6B9CB43F]]></uid>
|
||||
</SQLField>
|
||||
<labelWindowIndex><![CDATA[1]]></labelWindowIndex>
|
||||
<ui.treeExpanded><![CDATA[1]]></ui.treeExpanded>
|
||||
<uid><![CDATA[F0EE651E-DBF6-4CAC-A517-AC85BCC2D3AF]]></uid>
|
||||
</SQLTable>
|
||||
<SQLTable>
|
||||
<name><![CDATA[lcr_routes]]></name>
|
||||
<schema><![CDATA[]]></schema>
|
||||
@@ -768,7 +1012,7 @@
|
||||
<uid><![CDATA[B73773BA-AB1B-47AA-B995-2D2FE006198F]]></uid>
|
||||
<unique><![CDATA[1]]></unique>
|
||||
</SQLField>
|
||||
<labelWindowIndex><![CDATA[2]]></labelWindowIndex>
|
||||
<labelWindowIndex><![CDATA[5]]></labelWindowIndex>
|
||||
<objectComment><![CDATA[Least cost routing table]]></objectComment>
|
||||
<ui.treeExpanded><![CDATA[1]]></ui.treeExpanded>
|
||||
<uid><![CDATA[F283D572-F670-4571-91FD-A665A9D3E15D]]></uid>
|
||||
@@ -779,12 +1023,12 @@
|
||||
<comment><![CDATA[A partition of the platform used by one service provider]]></comment>
|
||||
<tableType><![CDATA[InnoDB]]></tableType>
|
||||
<location>
|
||||
<x>813.00</x>
|
||||
<y>99.00</y>
|
||||
<x>838.00</x>
|
||||
<y>96.00</y>
|
||||
</location>
|
||||
<size>
|
||||
<width>293.00</width>
|
||||
<height>120.00</height>
|
||||
<height>140.00</height>
|
||||
</size>
|
||||
<zorder>3</zorder>
|
||||
<SQLField>
|
||||
@@ -831,6 +1075,11 @@
|
||||
<referencesTableUID><![CDATA[64D64CB9-0990-4C68-BE71-F9FD43C2BE19]]></referencesTableUID>
|
||||
<uid><![CDATA[506BBE72-1A97-4776-B2C3-D94169652FFE]]></uid>
|
||||
</SQLField>
|
||||
<SQLField>
|
||||
<name><![CDATA[ms_teams_fqdn]]></name>
|
||||
<type><![CDATA[VARCHAR(255)]]></type>
|
||||
<uid><![CDATA[FA39B463-61C7-4654-BE9C-D1AC39AB1B97]]></uid>
|
||||
</SQLField>
|
||||
<labelWindowIndex><![CDATA[0]]></labelWindowIndex>
|
||||
<objectComment><![CDATA[A partition of the platform used by one service provider]]></objectComment>
|
||||
<ui.treeExpanded><![CDATA[1]]></ui.treeExpanded>
|
||||
@@ -850,17 +1099,17 @@
|
||||
<overviewPanelHidden><![CDATA[0]]></overviewPanelHidden>
|
||||
<pageBoundariesVisible><![CDATA[0]]></pageBoundariesVisible>
|
||||
<PageGridVisible><![CDATA[0]]></PageGridVisible>
|
||||
<RightSidebarWidth><![CDATA[2146.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[1322.000000]]></windowHeight>
|
||||
<windowLocationX><![CDATA[2553.000000]]></windowLocationX>
|
||||
<windowLocationY><![CDATA[95.000000]]></windowLocationY>
|
||||
<windowScrollOrigin><![CDATA[{0, 0}]]></windowScrollOrigin>
|
||||
<windowWidth><![CDATA[2423.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>
|
||||
|
||||
71
db/reset_admin_password.js
Executable file
71
db/reset_admin_password.js
Executable file
@@ -0,0 +1,71 @@
|
||||
#!/usr/bin/env node
|
||||
const {getMysqlConnection} = require('../lib/db');
|
||||
const crypto = require('crypto');
|
||||
const uuidv4 = require('uuid/v4');
|
||||
const sqlInsert = `INSERT into users
|
||||
(user_sid, name, hashed_password, salt)
|
||||
values (?, ?, ?, ?)
|
||||
`;
|
||||
const sqlChangeAdminToken = `UPDATE api_keys set token = ?
|
||||
WHERE account_sid IS NULL
|
||||
AND service_provider_sid IS NULL`;
|
||||
|
||||
/**
|
||||
* generates random string of characters i.e salt
|
||||
* @function
|
||||
* @param {number} length - Length of the random string.
|
||||
*/
|
||||
const genRandomString = (len) => {
|
||||
return crypto.randomBytes(Math.ceil(len / 2))
|
||||
.toString('hex') /** convert to hexadecimal format */
|
||||
.slice(0, len); /** return required number of characters */
|
||||
};
|
||||
|
||||
/**
|
||||
* hash password with sha512.
|
||||
* @function
|
||||
* @param {string} password - List of required fields.
|
||||
* @param {string} salt - Data to be validated.
|
||||
*/
|
||||
const sha512 = function(password, salt) {
|
||||
const hash = crypto.createHmac('sha512', salt); /** Hashing algorithm sha512 */
|
||||
hash.update(password);
|
||||
var value = hash.digest('hex');
|
||||
return {
|
||||
salt:salt,
|
||||
passwordHash:value
|
||||
};
|
||||
};
|
||||
|
||||
const saltHashPassword = (userpassword) => {
|
||||
var salt = genRandomString(16); /** Gives us salt of length 16 */
|
||||
return sha512(userpassword, salt);
|
||||
};
|
||||
|
||||
/* reset admin password */
|
||||
getMysqlConnection((err, conn) => {
|
||||
if (err) return console.log(err, 'Error connecting to database');
|
||||
|
||||
/* delete admin user if it exists */
|
||||
conn.query('DELETE from users where name = "admin"', (err) => {
|
||||
if (err) return console.log(err, 'Error removing admin user');
|
||||
const {salt, passwordHash} = saltHashPassword('admin');
|
||||
const sid = uuidv4();
|
||||
conn.query(sqlInsert, [
|
||||
sid,
|
||||
'admin',
|
||||
passwordHash,
|
||||
salt
|
||||
], (err) => {
|
||||
if (err) return console.log(err, 'Error inserting admin user');
|
||||
console.log('successfully reset admin password');
|
||||
const uuid = uuidv4();
|
||||
conn.query(sqlChangeAdminToken, [uuid], (err) => {
|
||||
if (err) return console.log(err, 'Error updating admin token');
|
||||
console.log('successfully changed admin tokens');
|
||||
conn.release();
|
||||
process.exit(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -10,6 +10,7 @@ function transmogrifyResults(results) {
|
||||
const obj = row.acc;
|
||||
if (row.rh && Object.keys(row.rh).length && row.rh.url !== null) {
|
||||
Object.assign(obj, {registration_hook: row.rh});
|
||||
delete obj.registration_hook.webhook_sid;
|
||||
}
|
||||
else obj.registration_hook = null;
|
||||
delete obj.registration_hook_sid;
|
||||
|
||||
@@ -1,11 +1,53 @@
|
||||
const Model = require('./model');
|
||||
const {getMysqlConnection} = require('../db');
|
||||
|
||||
class ApiKey extends Model {
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* list all api keys for an account
|
||||
*/
|
||||
static retrieveAll(account_sid) {
|
||||
const sql = account_sid ?
|
||||
'SELECT * from api_keys WHERE account_sid = ?' :
|
||||
'SELECT * from api_keys WHERE account_sid IS NULL';
|
||||
const args = account_sid ? [account_sid] : [];
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
getMysqlConnection((err, conn) => {
|
||||
if (err) return reject(err);
|
||||
conn.query(sql, args, (err, results) => {
|
||||
conn.release();
|
||||
if (err) return reject(err);
|
||||
resolve(results);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* update last_used api key for an account
|
||||
*/
|
||||
static updateLastUsed(account_sid) {
|
||||
const sql = 'UPDATE api_keys SET last_used = NOW() WHERE account_sid = ?';
|
||||
const args = [account_sid];
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
getMysqlConnection((err, conn) => {
|
||||
if (err) return reject(err);
|
||||
conn.query(sql, args, (err, results) => {
|
||||
conn.release();
|
||||
if (err) return reject(err);
|
||||
resolve(results);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ApiKey.table = 'api_keys';
|
||||
ApiKey.fields = [
|
||||
{
|
||||
@@ -25,6 +67,18 @@ ApiKey.fields = [
|
||||
{
|
||||
name: 'service_provider_sid',
|
||||
type: 'string'
|
||||
},
|
||||
{
|
||||
name: 'expires_at',
|
||||
type: 'date'
|
||||
},
|
||||
{
|
||||
name: 'created_at',
|
||||
type: 'date'
|
||||
},
|
||||
{
|
||||
name: 'last_used',
|
||||
type: 'date'
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
@@ -1,29 +1,13 @@
|
||||
const Model = require('./model');
|
||||
const {getMysqlConnection} = require('../db');
|
||||
const listSqlSp = `
|
||||
SELECT * from applications
|
||||
WHERE account_sid in (
|
||||
SELECT account_sid from accounts
|
||||
WHERE service_provider_sid = ?
|
||||
)`;
|
||||
const listSqlAccount = 'SELECT * from applications WHERE account_sid = ?';
|
||||
const retrieveSqlSp = `
|
||||
SELECT * from applications
|
||||
WHERE account_sid in (
|
||||
SELECT account_sid from accounts
|
||||
WHERE service_provider_sid = ?
|
||||
)
|
||||
AND application_sid = ?`;
|
||||
const retrieveSqlAccount = `
|
||||
SELECT * from applications
|
||||
WHERE account_sid = ?
|
||||
AND application_sid = ?`;
|
||||
|
||||
const retrieveSql = `SELECT * from applications app
|
||||
LEFT JOIN webhooks AS ch
|
||||
ON app.call_hook_sid = ch.webhook_sid
|
||||
LEFT JOIN webhooks AS sh
|
||||
ON app.call_status_hook_sid = sh.webhook_sid`;
|
||||
ON app.call_status_hook_sid = sh.webhook_sid
|
||||
LEFT JOIN webhooks AS mh
|
||||
ON app.messaging_hook_sid = mh.webhook_sid`;
|
||||
|
||||
function transmogrifyResults(results) {
|
||||
return results.map((row) => {
|
||||
@@ -36,8 +20,13 @@ function transmogrifyResults(results) {
|
||||
Object.assign(obj, {call_status_hook: row.sh});
|
||||
}
|
||||
else obj.call_status_hook = null;
|
||||
if (row.mh && Object.keys(row.mh).length && row.mh.url !== null) {
|
||||
Object.assign(obj, {messaging_hook: row.mh});
|
||||
}
|
||||
else obj.messaging_hook = null;
|
||||
delete obj.call_hook_sid;
|
||||
delete obj.call_status_hook_sid;
|
||||
delete obj.messaging_hook_sid;
|
||||
return obj;
|
||||
});
|
||||
}
|
||||
@@ -123,12 +112,14 @@ Application.fields = [
|
||||
{
|
||||
name: 'call_hook_sid',
|
||||
type: 'string',
|
||||
required: true
|
||||
},
|
||||
{
|
||||
name: 'call_status_hook_sid',
|
||||
type: 'string',
|
||||
required: true
|
||||
},
|
||||
{
|
||||
name: 'messaging_hook_sid',
|
||||
type: 'string',
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
55
lib/models/sbc.js
Normal file
55
lib/models/sbc.js
Normal file
@@ -0,0 +1,55 @@
|
||||
const Model = require('./model');
|
||||
const {getMysqlConnection} = require('../db');
|
||||
|
||||
class Sbc extends Model {
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* list all SBCs either for a given service provider, or those not associated with a
|
||||
* service provider (i.e. community SBCs)
|
||||
*/
|
||||
static retrieveAll(service_provider_sid) {
|
||||
const sql = service_provider_sid ?
|
||||
'SELECT * from sbc_addresses WHERE service_provider_sid = ?' :
|
||||
'SELECT * from sbc_addresses WHERE service_provider_sid IS NULL';
|
||||
const args = service_provider_sid ? [service_provider_sid] : [];
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
getMysqlConnection((err, conn) => {
|
||||
if (err) return reject(err);
|
||||
conn.query(sql, args, (err, results) => {
|
||||
conn.release();
|
||||
if (err) return reject(err);
|
||||
resolve(results);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Sbc.table = 'sbc_addresses';
|
||||
Sbc.fields = [
|
||||
{
|
||||
name: 'sbc_address_sid',
|
||||
type: 'string',
|
||||
primaryKey: true
|
||||
},
|
||||
{
|
||||
name: 'ipv4',
|
||||
type: 'string',
|
||||
required: true
|
||||
},
|
||||
{
|
||||
name: 'port',
|
||||
type: 'number'
|
||||
},
|
||||
{
|
||||
name: 'service_provider_sid',
|
||||
type: 'string'
|
||||
}
|
||||
];
|
||||
|
||||
module.exports = Sbc;
|
||||
@@ -1,9 +1,65 @@
|
||||
const Model = require('./model');
|
||||
const {getMysqlConnection} = require('../db');
|
||||
|
||||
const retrieveSql = `SELECT * from service_providers sp
|
||||
LEFT JOIN webhooks AS rh
|
||||
ON sp.registration_hook_sid = rh.webhook_sid`;
|
||||
|
||||
|
||||
function transmogrifyResults(results) {
|
||||
return results.map((row) => {
|
||||
const obj = row.sp;
|
||||
if (row.rh && Object.keys(row.rh).length && row.rh.url !== null) {
|
||||
Object.assign(obj, {registration_hook: row.rh});
|
||||
delete obj.registration_hook.webhook_sid;
|
||||
}
|
||||
else obj.registration_hook = null;
|
||||
delete obj.registration_hook_sid;
|
||||
return obj;
|
||||
});
|
||||
}
|
||||
|
||||
class ServiceProvider extends Model {
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* list all service providers
|
||||
*/
|
||||
static retrieveAll() {
|
||||
const sql = retrieveSql;
|
||||
return new Promise((resolve, reject) => {
|
||||
getMysqlConnection((err, conn) => {
|
||||
if (err) return reject(err);
|
||||
conn.query({sql, nestTables: true}, [], (err, results, fields) => {
|
||||
conn.release();
|
||||
if (err) return reject(err);
|
||||
const r = transmogrifyResults(results);
|
||||
resolve(r);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* retrieve a service provider
|
||||
*/
|
||||
static retrieve(sid) {
|
||||
const args = [sid];
|
||||
const sql = `${retrieveSql} WHERE sp.service_provider_sid = ?`;
|
||||
return new Promise((resolve, reject) => {
|
||||
getMysqlConnection((err, conn) => {
|
||||
if (err) return reject(err);
|
||||
conn.query({sql, nestTables: true}, args, (err, results, fields) => {
|
||||
conn.release();
|
||||
if (err) return reject(err);
|
||||
const r = transmogrifyResults(results);
|
||||
resolve(r);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
ServiceProvider.table = 'service_providers';
|
||||
@@ -27,7 +83,11 @@ ServiceProvider.fields = [
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
name: 'registration_hook',
|
||||
name: 'registration_hook_sid',
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
name: 'ms_teams_fqdn',
|
||||
type: 'string',
|
||||
}
|
||||
|
||||
|
||||
37
lib/models/tenant.js
Normal file
37
lib/models/tenant.js
Normal file
@@ -0,0 +1,37 @@
|
||||
const Model = require('./model');
|
||||
|
||||
class MsTeamsTenant extends Model {
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
}
|
||||
|
||||
MsTeamsTenant.table = 'ms_teams_tenants';
|
||||
MsTeamsTenant.fields = [
|
||||
{
|
||||
name: 'ms_teams_tenant_sid',
|
||||
type: 'string',
|
||||
primaryKey: true
|
||||
},
|
||||
{
|
||||
name: 'service_provider_sid',
|
||||
type: 'string',
|
||||
required: true
|
||||
},
|
||||
{
|
||||
name: 'account_sid',
|
||||
type: 'string',
|
||||
required: true
|
||||
},
|
||||
{
|
||||
name: 'application_sid',
|
||||
type: 'string'
|
||||
},
|
||||
{
|
||||
name: 'tenant_fqdn',
|
||||
type: 'string',
|
||||
required: true
|
||||
}
|
||||
];
|
||||
|
||||
module.exports = MsTeamsTenant;
|
||||
@@ -21,6 +21,18 @@ VoipCarrier.fields = [
|
||||
{
|
||||
name: 'description',
|
||||
type: 'string'
|
||||
},
|
||||
{
|
||||
name: 'account_sid',
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
name: 'application_sid',
|
||||
type: 'string'
|
||||
},
|
||||
{
|
||||
name: 'e164_leading_plus',
|
||||
type: 'number'
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
@@ -3,7 +3,9 @@ const request = require('request');
|
||||
const {DbErrorBadRequest, DbErrorUnprocessableRequest} = require('../../utils/errors');
|
||||
const Account = require('../../models/account');
|
||||
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');
|
||||
@@ -12,6 +14,7 @@ const preconditions = {
|
||||
'update': validateUpdate,
|
||||
'delete': validateDelete
|
||||
};
|
||||
let idx = 0;
|
||||
|
||||
function coerceNumbers(callInfo) {
|
||||
if (Array.isArray(callInfo)) {
|
||||
@@ -26,6 +29,15 @@ function coerceNumbers(callInfo) {
|
||||
return callInfo;
|
||||
}
|
||||
|
||||
async function updateLastUsed(logger, sid, req) {
|
||||
if (req.user.hasAdminAuth || req.user.hasServiceProviderAuth) return;
|
||||
try {
|
||||
await ApiKey.updateLastUsed(sid);
|
||||
} catch (err) {
|
||||
logger.error({err}, `Error updating last used for accountSid ${sid}`);
|
||||
}
|
||||
}
|
||||
|
||||
function validateUpdateCall(opts) {
|
||||
// only one type of update can be supplied per request
|
||||
const hasWhisper = opts.whisper;
|
||||
@@ -64,6 +76,7 @@ function validateTo(to) {
|
||||
if (to && typeof to === 'object') {
|
||||
switch (to.type) {
|
||||
case 'phone':
|
||||
case 'teams':
|
||||
if (typeof to.number === 'string') return;
|
||||
break;
|
||||
case 'user':
|
||||
@@ -90,11 +103,11 @@ async function validateCreateCall(logger, sid, req) {
|
||||
try {
|
||||
logger.debug(`Accounts:validateCreateCall retrieving application ${obj.application_sid}`);
|
||||
const application = await lookupAppBySid(obj.application_sid);
|
||||
logger.debug(`Accounts:validateCreateCall retrieved application ${JSON.stringify(application)}`);
|
||||
Object.assign(obj, {
|
||||
call_hook: application.call_hook,
|
||||
call_status_hook: application.call_status_hook,
|
||||
speech_synthesis_vendor: application.speech_synthesis_vendor,
|
||||
speech_synthesis_language: application.speech_synthesis_language,
|
||||
speech_synthesis_voice: application.speech_synthesis_voice,
|
||||
speech_recognizer_vendor: application.speech_recognizer_vendor,
|
||||
speech_recognizer_language: application.speech_recognizer_language
|
||||
@@ -106,9 +119,13 @@ async function validateCreateCall(logger, sid, req) {
|
||||
}
|
||||
}
|
||||
else {
|
||||
delete obj.application_sid;
|
||||
|
||||
// TODO: these should be retrieved from account, using account_sid if provided
|
||||
Object.assign(obj, {
|
||||
speech_synthesis_vendor: 'google',
|
||||
speech_synthesis_voice: 'en-US-Wavenet-C',
|
||||
speech_synthesis_language: 'en-US',
|
||||
speech_recognizer_vendor: 'google',
|
||||
speech_recognizer_language: 'en-US'
|
||||
});
|
||||
@@ -119,6 +136,30 @@ async function validateCreateCall(logger, sid, req) {
|
||||
}
|
||||
}
|
||||
|
||||
async function validateCreateMessage(logger, sid, req) {
|
||||
const obj = req.body;
|
||||
const {lookupAccountByPhoneNumber} = req.app.locals;
|
||||
|
||||
if (req.user.account_sid !== sid) {
|
||||
throw new DbErrorBadRequest(`unauthorized createMessage request for account ${sid}`);
|
||||
}
|
||||
|
||||
if (!obj.from) throw new DbErrorBadRequest('missing from property');
|
||||
else {
|
||||
const regex = /^\+(\d+)$/;
|
||||
const arr = regex.exec(obj.from);
|
||||
const from = arr ? arr[1] : obj.from;
|
||||
const account = await lookupAccountByPhoneNumber(from);
|
||||
if (!account) throw new DbErrorBadRequest(`accountSid ${sid} does not own phone number ${from}`);
|
||||
}
|
||||
|
||||
if (!obj.to) throw new DbErrorBadRequest('missing to property');
|
||||
|
||||
if (!obj.text && !obj.media) {
|
||||
throw new DbErrorBadRequest('either text or media required in outbound message');
|
||||
}
|
||||
}
|
||||
|
||||
async function validateAdd(req) {
|
||||
/* account-level token can not be used to add accounts */
|
||||
if (req.user.hasAccountAuth) {
|
||||
@@ -248,6 +289,19 @@ router.put('/:sid', async(req, res) => {
|
||||
return res.status(404).end();
|
||||
}
|
||||
res.status(204).end();
|
||||
updateLastUsed(logger, sid, req).catch((err) => {});
|
||||
} catch (err) {
|
||||
sysError(logger, res, err);
|
||||
}
|
||||
});
|
||||
|
||||
/* retrieve account level api keys */
|
||||
router.get('/:sid/ApiKeys', async(req, res) => {
|
||||
const logger = req.app.locals.logger;
|
||||
try {
|
||||
const results = await ApiKey.retrieveAll(req.params.sid);
|
||||
res.status(200).json(results);
|
||||
updateLastUsed(logger, req.params.sid, req).catch((err) => {});
|
||||
} catch (err) {
|
||||
sysError(logger, res, err);
|
||||
}
|
||||
@@ -258,13 +312,22 @@ router.put('/:sid', async(req, res) => {
|
||||
*/
|
||||
router.post('/:sid/Calls', async(req, res) => {
|
||||
const sid = req.params.sid;
|
||||
const logger = req.app.locals.logger;
|
||||
const setName = `${(process.env.JAMBONES_CLUSTER_ID || 'default')}:active-fs`;
|
||||
const {retrieveSet, logger} = req.app.locals;
|
||||
|
||||
try {
|
||||
const serviceUrl = process.env.JAMBONES_CREATE_CALL_URL;
|
||||
const fs = await retrieveSet(setName);
|
||||
if (0 === fs.length) {
|
||||
logger.info('No available feature servers to handle createCall API request');
|
||||
return res.json({msg: 'no available feature servers at this time'}).status(500);
|
||||
}
|
||||
const ip = fs[idx++ % fs.length];
|
||||
logger.info({fs}, `feature servers available for createCall API request, selecting ${ip}`);
|
||||
const serviceUrl = `http://${ip}:3000/v1/createCall`;
|
||||
await validateCreateCall(logger, sid, req);
|
||||
|
||||
logger.debug({payload: req.body}, `sending POST to ${serviceUrl}`);
|
||||
logger.debug({payload: req.body}, `sending createCall API request to to ${ip}`);
|
||||
updateLastUsed(logger, sid, req).catch((err) => {});
|
||||
request({
|
||||
url: serviceUrl,
|
||||
method: 'POST',
|
||||
@@ -272,11 +335,11 @@ router.post('/:sid/Calls', async(req, res) => {
|
||||
body: req.body
|
||||
}, (err, response, body) => {
|
||||
if (err) {
|
||||
logger.error(err, `Error sending createCall POST to ${serviceUrl}`);
|
||||
logger.error(err, `Error sending createCall POST to ${ip}`);
|
||||
return res.sendStatus(500);
|
||||
}
|
||||
if (response.statusCode !== 201) {
|
||||
logger.error({statusCode: response.statusCode}, `Non-success response returned by createCall ${serviceUrl}`);
|
||||
logger.error({statusCode: response.statusCode}, `Non-success response returned by createCall ${ip}`);
|
||||
return res.sendStatus(500);
|
||||
}
|
||||
res.status(201).json(body);
|
||||
@@ -297,6 +360,7 @@ router.get('/:sid/Calls', async(req, res) => {
|
||||
const calls = await listCalls(accountSid);
|
||||
logger.debug(`retrieved ${calls.length} calls for account sid ${accountSid}`);
|
||||
res.status(200).json(coerceNumbers(snakeCase(calls)));
|
||||
updateLastUsed(logger, accountSid, req).catch((err) => {});
|
||||
} catch (err) {
|
||||
sysError(logger, res, err);
|
||||
}
|
||||
@@ -320,6 +384,7 @@ router.get('/:sid/Calls/:callSid', async(req, res) => {
|
||||
logger.debug(`call not found for call sid ${callSid}`);
|
||||
res.sendStatus(404);
|
||||
}
|
||||
updateLastUsed(logger, accountSid, req).catch((err) => {});
|
||||
} catch (err) {
|
||||
sysError(logger, res, err);
|
||||
}
|
||||
@@ -343,6 +408,7 @@ router.delete('/:sid/Calls/:callSid', async(req, res) => {
|
||||
logger.debug(`call not found for call sid ${callSid}`);
|
||||
res.sendStatus(404);
|
||||
}
|
||||
updateLastUsed(logger, accountSid, req).catch((err) => {});
|
||||
} catch (err) {
|
||||
sysError(logger, res, err);
|
||||
}
|
||||
@@ -373,10 +439,53 @@ router.post('/:sid/Calls/:callSid', async(req, res) => {
|
||||
logger.debug(`updateCall: call not found for call sid ${callSid}`);
|
||||
res.sendStatus(404);
|
||||
}
|
||||
updateLastUsed(logger, accountSid, req).catch((err) => {});
|
||||
} catch (err) {
|
||||
sysError(logger, res, err);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* create a new Message
|
||||
*/
|
||||
router.post('/:sid/Messages', async(req, res) => {
|
||||
const sid = req.params.sid;
|
||||
const setName = `${(process.env.JAMBONES_CLUSTER_ID || 'default')}:active-fs`;
|
||||
const {retrieveSet, logger} = req.app.locals;
|
||||
|
||||
try {
|
||||
const fs = await retrieveSet(setName);
|
||||
if (0 === fs.length) {
|
||||
logger.info('No available feature servers to handle createMessage API request');
|
||||
return res.json({msg: 'no available feature servers at this time'}).status(500);
|
||||
}
|
||||
const ip = fs[idx++ % fs.length];
|
||||
logger.info({fs}, `feature servers available for createMessage API request, selecting ${ip}`);
|
||||
const serviceUrl = `http://${ip}:3000/v1/createMessage/${sid}`;
|
||||
await validateCreateMessage(logger, sid, req);
|
||||
|
||||
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({
|
||||
url: serviceUrl,
|
||||
method: 'POST',
|
||||
json: true,
|
||||
body: payload
|
||||
}, (err, response, body) => {
|
||||
if (err) {
|
||||
logger.error(err, `Error sending createMessage POST to ${ip}`);
|
||||
return res.sendStatus(500);
|
||||
}
|
||||
if (response.statusCode !== 200) {
|
||||
logger.error({statusCode: response.statusCode}, `Non-success response returned by createMessage ${serviceUrl}`);
|
||||
return res.sendStatus(500);
|
||||
}
|
||||
res.status(201).json(body);
|
||||
});
|
||||
} catch (err) {
|
||||
sysError(logger, res, err);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
|
||||
@@ -62,7 +62,7 @@ router.post('/', async(req, res) => {
|
||||
|
||||
// create webhooks if provided
|
||||
const obj = Object.assign({}, req.body);
|
||||
for (const prop of ['call_hook', 'call_status_hook']) {
|
||||
for (const prop of ['call_hook', 'call_status_hook', 'messaging_hook']) {
|
||||
if (obj[prop]) {
|
||||
obj[`${prop}_sid`] = await Webhook.make(obj[prop]);
|
||||
delete obj[prop];
|
||||
@@ -113,7 +113,7 @@ router.put('/:sid', async(req, res) => {
|
||||
|
||||
// create webhooks if provided
|
||||
const obj = Object.assign({}, req.body);
|
||||
for (const prop of ['call_hook', 'call_status_hook']) {
|
||||
for (const prop of ['call_hook', 'call_status_hook', 'messaging_hook']) {
|
||||
if (prop in obj && Object.keys(obj[prop]).length) {
|
||||
if ('webhook_sid' in obj[prop]) {
|
||||
const sid = obj[prop]['webhook_sid'];
|
||||
|
||||
@@ -11,7 +11,7 @@ const decorators = {
|
||||
'delete': remove
|
||||
};
|
||||
|
||||
function decorate(router, klass, methods, preconditions) {
|
||||
function decorate(router, klass, methods, preconditions = {}) {
|
||||
const decs = methods && Array.isArray(methods) && methods[0] !== '*' ? methods : Object.keys(decorators);
|
||||
decs.forEach((m) => {
|
||||
assert(m in decorators);
|
||||
|
||||
@@ -15,5 +15,13 @@ api.use('/PhoneNumbers', isAdminScope, require('./phone-numbers'));
|
||||
api.use('/ApiKeys', require('./api-keys'));
|
||||
api.use('/Accounts', require('./accounts'));
|
||||
api.use('/Applications', require('./applications'));
|
||||
api.use('/MicrosoftTeamsTenants', require('./tenants'));
|
||||
api.use('/Sbcs', isAdminScope, require('./sbcs'));
|
||||
api.use('/Users', require('./users'));
|
||||
api.use('/login', require('./login'));
|
||||
|
||||
// messaging
|
||||
api.use('/messaging', require('./sms-inbound')); // inbound SMS from carrier
|
||||
api.use('/outboundSMS', require('./sms-outbound')); // outbound SMS from feature server
|
||||
|
||||
module.exports = api;
|
||||
|
||||
75
lib/routes/api/login.js
Normal file
75
lib/routes/api/login.js
Normal file
@@ -0,0 +1,75 @@
|
||||
const router = require('express').Router();
|
||||
const crypto = require('crypto');
|
||||
const {getMysqlConnection} = require('../../db');
|
||||
|
||||
const retrieveSql = 'SELECT * from users where name = ?';
|
||||
const tokenSql = 'SELECT token from api_keys where account_sid IS NULL AND service_provider_sid IS NULL';
|
||||
|
||||
const sha512 = function(password, salt) {
|
||||
const hash = crypto.createHmac('sha512', salt); /** Hashing algorithm sha512 */
|
||||
hash.update(password);
|
||||
var value = hash.digest('hex');
|
||||
return {
|
||||
salt:salt,
|
||||
passwordHash:value
|
||||
};
|
||||
};
|
||||
|
||||
router.post('/', (req, res) => {
|
||||
const logger = req.app.locals.logger;
|
||||
const {username, password} = req.body;
|
||||
if (!username || !password) {
|
||||
logger.info('Bad POST to /login is missing username or password');
|
||||
return res.sendStatus(400);
|
||||
}
|
||||
|
||||
getMysqlConnection((err, conn) => {
|
||||
if (err) {
|
||||
logger.error({err}, 'Error getting db connection');
|
||||
return res.sendStatus(500);
|
||||
}
|
||||
conn.query(retrieveSql, [username], (err, results) => {
|
||||
conn.release();
|
||||
if (err) {
|
||||
logger.error({err}, 'Error getting db connection');
|
||||
return res.sendStatus(500);
|
||||
}
|
||||
if (0 === results.length) {
|
||||
logger.info(`Failed login attempt for user ${username}`);
|
||||
return res.sendStatus(403);
|
||||
}
|
||||
|
||||
logger.info({results}, 'successfully retrieved account');
|
||||
const salt = results[0].salt;
|
||||
const trueHash = results[0].hashed_password;
|
||||
const forceChange = results[0].force_change;
|
||||
|
||||
const {passwordHash} = sha512(password, salt);
|
||||
if (trueHash !== passwordHash) return res.sendStatus(403);
|
||||
|
||||
if (forceChange) return res.json({user_sid: results[0].user_sid, force_change: true});
|
||||
|
||||
getMysqlConnection((err, conn) => {
|
||||
if (err) {
|
||||
logger.error({err}, 'Error getting db connection');
|
||||
return res.sendStatus(500);
|
||||
}
|
||||
conn.query(tokenSql, (err, tokenResults) => {
|
||||
conn.release();
|
||||
if (err) {
|
||||
logger.error({err}, 'Error getting db connection');
|
||||
return res.sendStatus(500);
|
||||
}
|
||||
if (0 === tokenResults.length) {
|
||||
logger.error('Database has no admin token provisioned...run reset_admin_password');
|
||||
return res.sendStatus(500);
|
||||
}
|
||||
res.json({user_sid: results[0].user_sid, token: tokenResults[0].token});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
module.exports = router;
|
||||
@@ -31,13 +31,13 @@ async function validateAdd(req) {
|
||||
async function checkInUse(req, sid) {
|
||||
const phoneNumber = await PhoneNumber.retrieve(sid);
|
||||
if (phoneNumber.account_sid) {
|
||||
throw new DbErrorUnprocessableRequest('cannot delete phone number that is assigned to an account');
|
||||
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);
|
||||
//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');
|
||||
|
||||
|
||||
19
lib/routes/api/sbcs.js
Normal file
19
lib/routes/api/sbcs.js
Normal file
@@ -0,0 +1,19 @@
|
||||
const router = require('express').Router();
|
||||
const Sbc = require('../../models/sbc');
|
||||
const decorate = require('./decorate');
|
||||
const sysError = require('./error');
|
||||
|
||||
decorate(router, Sbc, ['add', 'delete']);
|
||||
|
||||
/* list */
|
||||
router.get('/', async(req, res) => {
|
||||
const logger = req.app.locals.logger;
|
||||
try {
|
||||
const results = await Sbc.retrieveAll(req.query.service_provider_sid);
|
||||
res.status(200).json(results);
|
||||
} catch (err) {
|
||||
sysError(logger, res, err);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
@@ -1,6 +1,8 @@
|
||||
const router = require('express').Router();
|
||||
const {DbErrorUnprocessableRequest} = require('../../utils/errors');
|
||||
const Webhook = require('../../models/webhook');
|
||||
const ServiceProvider = require('../../models/service-provider');
|
||||
const sysError = require('./error');
|
||||
const decorate = require('./decorate');
|
||||
const preconditions = {
|
||||
'delete': noActiveAccounts
|
||||
@@ -12,6 +14,87 @@ async function noActiveAccounts(req, sid) {
|
||||
if (activeAccounts > 0) throw new DbErrorUnprocessableRequest('cannot delete service provider with active accounts');
|
||||
}
|
||||
|
||||
decorate(router, ServiceProvider, ['*'], preconditions);
|
||||
decorate(router, ServiceProvider, ['delete'], preconditions);
|
||||
|
||||
/* add */
|
||||
router.post('/', async(req, res) => {
|
||||
const logger = req.app.locals.logger;
|
||||
try {
|
||||
|
||||
// create webhooks if provided
|
||||
const obj = Object.assign({}, req.body);
|
||||
for (const prop of ['registration_hook']) {
|
||||
if (obj[prop]) {
|
||||
obj[`${prop}_sid`] = await Webhook.make(obj[prop]);
|
||||
delete obj[prop];
|
||||
}
|
||||
}
|
||||
|
||||
//logger.debug(`Attempting to add account ${JSON.stringify(obj)}`);
|
||||
const uuid = await ServiceProvider.make(obj);
|
||||
res.status(201).json({sid: uuid});
|
||||
} catch (err) {
|
||||
sysError(logger, res, err);
|
||||
}
|
||||
});
|
||||
|
||||
/* list */
|
||||
router.get('/', async(req, res) => {
|
||||
const logger = req.app.locals.logger;
|
||||
try {
|
||||
const results = await ServiceProvider.retrieveAll();
|
||||
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 results = await ServiceProvider.retrieve(req.params.sid);
|
||||
if (results.length === 0) return res.status(404).end();
|
||||
return res.status(200).json(results[0]);
|
||||
}
|
||||
catch (err) {
|
||||
sysError(logger, res, err);
|
||||
}
|
||||
});
|
||||
|
||||
/* update */
|
||||
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']) {
|
||||
if (prop in obj && Object.keys(obj[prop]).length) {
|
||||
if ('webhook_sid' in obj[prop]) {
|
||||
const sid = obj[prop]['webhook_sid'];
|
||||
delete obj[prop]['webhook_sid'];
|
||||
await Webhook.update(sid, obj[prop]);
|
||||
}
|
||||
else {
|
||||
const sid = await Webhook.make(obj[prop]);
|
||||
obj[`${prop}_sid`] = sid;
|
||||
}
|
||||
}
|
||||
else {
|
||||
obj[`${prop}_sid`] = null;
|
||||
}
|
||||
delete obj[prop];
|
||||
}
|
||||
|
||||
const rowsAffected = await ServiceProvider.update(sid, obj);
|
||||
if (rowsAffected === 0) {
|
||||
return res.status(404).end();
|
||||
}
|
||||
res.status(204).end();
|
||||
} catch (err) {
|
||||
sysError(logger, res, err);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
|
||||
142
lib/routes/api/sms-inbound.js
Normal file
142
lib/routes/api/sms-inbound.js
Normal file
@@ -0,0 +1,142 @@
|
||||
const router = require('express').Router();
|
||||
const request = require('request');
|
||||
const getProvider = require('../../utils/sms-provider');
|
||||
const uuidv4 = require('uuid/v4');
|
||||
const sysError = require('./error');
|
||||
let idx = 0;
|
||||
|
||||
|
||||
async function doSendResponse(res, respondFn, body) {
|
||||
if (typeof respondFn === 'number') res.sendStatus(respondFn);
|
||||
else if (typeof respondFn !== 'function') res.sendStatus(200);
|
||||
else {
|
||||
const payload = await respondFn(body);
|
||||
res.status(200).json(payload);
|
||||
}
|
||||
}
|
||||
|
||||
router.post('/:provider', async(req, res) => {
|
||||
const provider = req.params.provider;
|
||||
const {
|
||||
retrieveSet,
|
||||
lookupAppByPhoneNumber,
|
||||
logger
|
||||
} = req.app.locals;
|
||||
const setName = `${process.env.JAMBONES_CLUSTER_ID || 'default'}:active-fs`;
|
||||
logger.debug({path: req.path, body: req.body}, 'incomingSMS from carrier');
|
||||
|
||||
// search for provider module
|
||||
const arr = getProvider(logger, provider);
|
||||
if (!arr) {
|
||||
logger.info({body: req.body, params: req.params},
|
||||
`rejecting incomingSms request from unknown provider ${provider}`
|
||||
);
|
||||
return res.sendStatus(404);
|
||||
}
|
||||
|
||||
const providerData = arr[1];
|
||||
if (!providerData || !providerData.module) {
|
||||
logger.info({body: req.body, params: req.params},
|
||||
`rejecting incomingSms request from badly configured provider ${provider}`
|
||||
);
|
||||
return res.sendStatus(404);
|
||||
}
|
||||
|
||||
// load provider module
|
||||
let filterFn, respondFn;
|
||||
try {
|
||||
const {
|
||||
fromProviderFormat,
|
||||
formatProviderResponse
|
||||
} = require(providerData.module);
|
||||
// must at least provide a filter function
|
||||
if (!fromProviderFormat) {
|
||||
logger.info(
|
||||
`missing fromProviderFormat function in module ${providerData.module} for provider ${provider}`
|
||||
);
|
||||
return res.sendStatus(404);
|
||||
}
|
||||
filterFn = fromProviderFormat;
|
||||
respondFn = formatProviderResponse;
|
||||
} catch (err) {
|
||||
logger.info(
|
||||
err,
|
||||
`failure loading module ${providerData.module} for provider ${provider}`
|
||||
);
|
||||
return res.sendStatus(500);
|
||||
}
|
||||
|
||||
try {
|
||||
const fs = await retrieveSet(setName);
|
||||
if (0 === fs.length) {
|
||||
logger.info('No available feature servers to handle createCall API request');
|
||||
return res
|
||||
.json({
|
||||
msg: 'no available feature servers at this time'
|
||||
})
|
||||
.status(480);
|
||||
}
|
||||
const ip = fs[idx++ % fs.length];
|
||||
const serviceUrl = `http://${ip}:3000/v1/messaging/${provider}`;
|
||||
const messageSid = uuidv4();
|
||||
const payload = await Promise.resolve(filterFn({messageSid}, req.body));
|
||||
|
||||
/**
|
||||
* lookup the application associated with the number in the To field
|
||||
* since there could be multiple Tos, we have to search through (and cc also)
|
||||
*/
|
||||
let app;
|
||||
const to = Array.isArray(payload.to) ? payload.to : [payload.to];
|
||||
const cc = Array.isArray(payload.cc) ? payload.cc : (payload.cc ? [payload.cc] : []);
|
||||
const dids = to.concat(cc).filter((n) => n.length);
|
||||
for (let did of dids) {
|
||||
const regex = /^\+(\d+)$/;
|
||||
const arr = regex.exec(did);
|
||||
did = arr ? arr[1] : did;
|
||||
const obj = await lookupAppByPhoneNumber(did);
|
||||
logger.info({obj}, `lookup app for phone number ${did}`);
|
||||
if (obj) {
|
||||
logger.info({did, obj}, 'Found app for DID');
|
||||
app = obj;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!app) {
|
||||
logger.info({payload}, 'No application found for incoming SMS');
|
||||
return res.sendStatus(404);
|
||||
}
|
||||
if (!app.messaging_hook) {
|
||||
logger.info({payload}, `app "${app.name}" found for incoming SMS does not have an associated messaging hook`);
|
||||
return res.sendStatus(404);
|
||||
}
|
||||
payload.app = app;
|
||||
|
||||
logger.debug({body: req.body, payload}, 'filtered incoming SMS');
|
||||
|
||||
logger.info({payload, url: serviceUrl}, `sending incomingSms API request to FS at ${ip}`);
|
||||
|
||||
request({
|
||||
url: serviceUrl,
|
||||
method: 'POST',
|
||||
json: true,
|
||||
body: payload,
|
||||
},
|
||||
async(err, response, body) => {
|
||||
if (err) {
|
||||
logger.error(err, `Error sending incomingSms POST to ${ip}`);
|
||||
return res.sendStatus(500);
|
||||
}
|
||||
if (200 === response.statusCode) {
|
||||
// success
|
||||
logger.info({body}, 'sending response to provider for incomingSMS');
|
||||
return doSendResponse(res, respondFn, body);
|
||||
}
|
||||
logger.error({statusCode: response.statusCode}, `Non-success response returned by incomingSms ${ip}`);
|
||||
return res.sendStatus(500);
|
||||
});
|
||||
} catch (err) {
|
||||
sysError(logger, res, err);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
44
lib/routes/api/sms-outbound.js
Normal file
44
lib/routes/api/sms-outbound.js
Normal file
@@ -0,0 +1,44 @@
|
||||
const router = require('express').Router();
|
||||
const getProvider = require('../../utils/sms-provider');
|
||||
const sysError = require('./error');
|
||||
|
||||
router.post('/', async(req, res) => {
|
||||
const { logger } = req.app.locals;
|
||||
|
||||
try {
|
||||
// if provider specified use it, otherwise use first in list
|
||||
const arr = getProvider(logger, req.body.provider);
|
||||
if (!Array.isArray(arr)) {
|
||||
throw new Error('outboundSMS - unable to locate sms provider to use to send message');
|
||||
}
|
||||
|
||||
const providerData = arr[1];
|
||||
if (!providerData || !providerData.module) {
|
||||
throw new Error(`rejecting outgoingSms request for unknown or badly configured provider ${req.body.provider}`);
|
||||
}
|
||||
|
||||
const provider = arr[0];
|
||||
const opts = providerData.options;
|
||||
if (!opts || !opts.url) {
|
||||
throw new Error(`rejecting outgoingSms request -- no HTTP url for ${req.body.provider}`);
|
||||
}
|
||||
|
||||
// load provider module
|
||||
const { sendSms } = require(providerData.module);
|
||||
if (!sendSms) {
|
||||
throw new Error(`missing sendSms function in module ${providerData.module} for provider ${provider}`);
|
||||
}
|
||||
|
||||
// send the SMS
|
||||
const payload = req.body;
|
||||
delete payload.provider;
|
||||
logger.debug({opts, payload}, `outboundSMS - sending to ${opts.url}`);
|
||||
const response = await sendSms(opts, payload);
|
||||
logger.info({response, payload: req.body}, `outboundSMS - sent to ${opts.url}`);
|
||||
res.status(200).json(response);
|
||||
} catch (err) {
|
||||
sysError(logger, res, err);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
8
lib/routes/api/tenants.js
Normal file
8
lib/routes/api/tenants.js
Normal file
@@ -0,0 +1,8 @@
|
||||
const router = require('express').Router();
|
||||
const Tenant = require('../../models/tenant');
|
||||
const decorate = require('./decorate');
|
||||
const preconditions = {};
|
||||
|
||||
decorate(router, Tenant, ['*'], preconditions);
|
||||
|
||||
module.exports = router;
|
||||
96
lib/routes/api/users.js
Normal file
96
lib/routes/api/users.js
Normal file
@@ -0,0 +1,96 @@
|
||||
const router = require('express').Router();
|
||||
const crypto = require('crypto');
|
||||
const {getMysqlConnection} = require('../../db');
|
||||
|
||||
const retrieveSql = 'SELECT * from users where user_sid = ?';
|
||||
const updateSql = 'UPDATE users set hashed_password = ?, salt = ?, force_change = false WHERE user_sid = ?';
|
||||
const tokenSql = 'SELECT token from api_keys where account_sid IS NULL AND service_provider_sid IS NULL';
|
||||
|
||||
const genRandomString = (len) => {
|
||||
return crypto.randomBytes(Math.ceil(len / 2))
|
||||
.toString('hex') /** convert to hexadecimal format */
|
||||
.slice(0, len); /** return required number of characters */
|
||||
};
|
||||
|
||||
const sha512 = function(password, salt) {
|
||||
const hash = crypto.createHmac('sha512', salt); /** Hashing algorithm sha512 */
|
||||
hash.update(password);
|
||||
var value = hash.digest('hex');
|
||||
return {
|
||||
salt:salt,
|
||||
passwordHash:value
|
||||
};
|
||||
};
|
||||
|
||||
const saltHashPassword = (userpassword) => {
|
||||
var salt = genRandomString(16); /** Gives us salt of length 16 */
|
||||
return sha512(userpassword, salt);
|
||||
};
|
||||
|
||||
router.put('/:user_sid', (req, res) => {
|
||||
const logger = req.app.locals.logger;
|
||||
const {old_password, new_password} = req.body;
|
||||
if (!old_password || !new_password) {
|
||||
logger.info('Bad PUT to /Users is missing old_password or new password');
|
||||
return res.sendStatus(400);
|
||||
}
|
||||
|
||||
getMysqlConnection((err, conn) => {
|
||||
if (err) {
|
||||
logger.error({err}, 'Error getting db connection');
|
||||
return res.sendStatus(500);
|
||||
}
|
||||
conn.query(retrieveSql, [req.params.user_sid], (err, results) => {
|
||||
conn.release();
|
||||
if (err) {
|
||||
logger.error({err}, 'Error getting db connection');
|
||||
return res.sendStatus(500);
|
||||
}
|
||||
if (0 === results.length) {
|
||||
logger.info(`Failed to find user with sid ${req.params.user_sid}`);
|
||||
return res.sendStatus(404);
|
||||
}
|
||||
|
||||
logger.info({results}, 'successfully retrieved user');
|
||||
const old_salt = results[0].salt;
|
||||
const old_hashed_password = results[0].hashed_password;
|
||||
|
||||
const {passwordHash} = sha512(old_password, old_salt);
|
||||
if (old_hashed_password !== passwordHash) return res.sendStatus(403);
|
||||
|
||||
getMysqlConnection((err, conn) => {
|
||||
if (err) {
|
||||
logger.error({err}, 'Error getting db connection');
|
||||
return res.sendStatus(500);
|
||||
}
|
||||
const {salt, passwordHash} = saltHashPassword(new_password);
|
||||
conn.query(updateSql, [passwordHash, salt, req.params.user_sid], (err, r) => {
|
||||
conn.release();
|
||||
if (err) {
|
||||
logger.error({err}, 'Error getting db connection');
|
||||
return res.sendStatus(500);
|
||||
}
|
||||
if (0 === r.changedRows) {
|
||||
logger.error('Failed updating database with new password');
|
||||
return res.sendStatus(500);
|
||||
}
|
||||
conn.query(tokenSql, (err, tokenResults) => {
|
||||
conn.release();
|
||||
if (err) {
|
||||
logger.error({err}, 'Error getting db connection');
|
||||
return res.sendStatus(500);
|
||||
}
|
||||
if (0 === tokenResults.length) {
|
||||
logger.error('Database has no admin token provisioned...run reset_admin_password');
|
||||
return res.sendStatus(500);
|
||||
}
|
||||
res.json({user_sid: results[0].user_sid, token: tokenResults[0].token});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
module.exports = router;
|
||||
@@ -12,6 +12,92 @@ servers:
|
||||
- url: /v1
|
||||
description: development server
|
||||
paths:
|
||||
/Sbcs:
|
||||
post:
|
||||
summary: add an SBC address
|
||||
operationId: createSbc
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
ipv4:
|
||||
type: string
|
||||
port:
|
||||
type: number
|
||||
service_provider_sid:
|
||||
type: string
|
||||
description: service provider scope for the generated api key
|
||||
required:
|
||||
- ipv4
|
||||
responses:
|
||||
201:
|
||||
description: sbc address successfully created
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/SuccessfulAdd'
|
||||
400:
|
||||
description: bad request
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/GeneralError'
|
||||
|
||||
500:
|
||||
description: bad request
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/GeneralError'
|
||||
get:
|
||||
summary: retrieve public IP addresses of the jambonz Sbcs
|
||||
operationId: listSbcs
|
||||
parameters:
|
||||
- in: query
|
||||
name: service_provider_sid
|
||||
required: false
|
||||
schema:
|
||||
type: string
|
||||
description: return only the SBCs operated for the sole use of this service provider
|
||||
responses:
|
||||
200:
|
||||
description: list of SBC addresses
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
properties:
|
||||
ipv4:
|
||||
type: string
|
||||
description: ip address of one of our Sbcs
|
||||
required:
|
||||
- ipv4
|
||||
500:
|
||||
description: system error
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/GeneralError'
|
||||
/Sbcs/{SbcSid}:
|
||||
parameters:
|
||||
- name: SbcSid
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
delete:
|
||||
summary: delete sbc address
|
||||
operationId: deleteSbcAddress
|
||||
responses:
|
||||
200:
|
||||
description: sbc address deleted
|
||||
404:
|
||||
description: sbc address not found
|
||||
|
||||
|
||||
/ApiKeys:
|
||||
post:
|
||||
summary: create an api key
|
||||
@@ -28,6 +114,9 @@ paths:
|
||||
account_sid:
|
||||
type: string
|
||||
description: account scope for the generated api key
|
||||
expiry_secs:
|
||||
type: number
|
||||
description: duration of key validity, in seconds
|
||||
responses:
|
||||
201:
|
||||
description: api key successfully created
|
||||
@@ -64,6 +153,89 @@ paths:
|
||||
404:
|
||||
description: api key or account not found
|
||||
|
||||
/login:
|
||||
post:
|
||||
summary: login a user and receive an api token
|
||||
operationId: loginUser
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
username:
|
||||
type: string
|
||||
description: login username
|
||||
password:
|
||||
type: string
|
||||
description: login password
|
||||
required:
|
||||
- username
|
||||
- password
|
||||
responses:
|
||||
200:
|
||||
description: login succeeded
|
||||
content:
|
||||
application/json:
|
||||
schema: '#/components/schemas/Login'
|
||||
403:
|
||||
description: login failed
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/GeneralError'
|
||||
500:
|
||||
description: system error
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/GeneralError'
|
||||
/Users/{UserSid}:
|
||||
parameters:
|
||||
- name: UserSid
|
||||
in: path
|
||||
required: true
|
||||
style: simple
|
||||
explode: false
|
||||
schema:
|
||||
type: string
|
||||
put:
|
||||
summary: update a user password
|
||||
operationId: updateUser
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
old_password:
|
||||
type: string
|
||||
description: existing password, which is to be replaced
|
||||
new_password:
|
||||
type: string
|
||||
description: new password
|
||||
required:
|
||||
- old_password
|
||||
- new_password
|
||||
responses:
|
||||
200:
|
||||
description: password successfully changed
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Login'
|
||||
403:
|
||||
description: password change failed
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/GeneralError'
|
||||
500:
|
||||
description: system error
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/GeneralError'
|
||||
/VoipCarriers:
|
||||
post:
|
||||
summary: create voip carrier
|
||||
@@ -79,6 +251,8 @@ paths:
|
||||
description: voip carrier name
|
||||
description:
|
||||
type: string
|
||||
e164_leading_plus:
|
||||
type: boolean
|
||||
required:
|
||||
- name
|
||||
responses:
|
||||
@@ -516,16 +690,12 @@ paths:
|
||||
description: root domain for group of accounts that share a registration hook
|
||||
example: example.com
|
||||
registration_hook:
|
||||
type: string
|
||||
format: url
|
||||
$ref: '#/components/schemas/Webhook'
|
||||
description: authentication webhook for registration
|
||||
example: https://mycompany.com
|
||||
hook_basic_auth_user:
|
||||
ms_teams_fqdn:
|
||||
type: string
|
||||
description: username to use for http basic auth when calling hook
|
||||
hook_basic_auth_password:
|
||||
type: string
|
||||
description: password to use for http basic auth when calling hook
|
||||
description: SBC domain name for Microsoft Teams
|
||||
example: contoso.com
|
||||
required:
|
||||
- name
|
||||
responses:
|
||||
@@ -636,6 +806,140 @@ paths:
|
||||
schema:
|
||||
$ref: '#/components/schemas/GeneralError'
|
||||
|
||||
/MicrosoftTeamsTenants:
|
||||
post:
|
||||
summary: provision a customer tenant for MS Teams
|
||||
operationId: createMsTeamsTenant
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
service_provider_sid:
|
||||
type: string
|
||||
format: uuid
|
||||
example: 85f9c036-ba61-4f28-b2f5-617c23fa68ff
|
||||
account_sid:
|
||||
type: string
|
||||
format: uuid
|
||||
example: 85f9c036-ba61-4f28-b2f5-617c23fa68ff
|
||||
application_sid:
|
||||
type: string
|
||||
format: uuid
|
||||
example: 85f9c036-ba61-4f28-b2f5-617c23fa68ff
|
||||
tenant_fqdn:
|
||||
type: string
|
||||
example: customer.contoso.com
|
||||
required:
|
||||
- service_provider_sid
|
||||
- account
|
||||
- tenant_fqdn
|
||||
responses:
|
||||
201:
|
||||
description: tenant successfully created
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/SuccessfulAdd'
|
||||
400:
|
||||
description: bad request
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/GeneralError'
|
||||
500:
|
||||
description: system error
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/GeneralError'
|
||||
get:
|
||||
summary: list MS Teams tenants
|
||||
operationId: listMsTeamsTenants
|
||||
responses:
|
||||
200:
|
||||
description: list of tenants
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/MsTeamsTenant'
|
||||
500:
|
||||
description: system error
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/GeneralError'
|
||||
/MicrosoftTeamsTenants/{TenantSid}:
|
||||
parameters:
|
||||
- name: TenantSid
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
delete:
|
||||
summary: delete an MS Teams tenant
|
||||
operationId: deleteTenant
|
||||
responses:
|
||||
204:
|
||||
description: tenant successfully deleted
|
||||
404:
|
||||
description: tenant not found
|
||||
500:
|
||||
description: system error
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/GeneralError'
|
||||
get:
|
||||
summary: retrieve an MS Teams tenant
|
||||
operationId: getTenant
|
||||
responses:
|
||||
200:
|
||||
description: tenant found
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/MsTeamsTenant'
|
||||
404:
|
||||
description: account not found
|
||||
500:
|
||||
description: system error
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/GeneralError'
|
||||
|
||||
put:
|
||||
summary: update tenant
|
||||
operationId: updateAccount
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Tenant'
|
||||
responses:
|
||||
204:
|
||||
description: tenant updated
|
||||
404:
|
||||
description: tenant not found
|
||||
422:
|
||||
description: unprocessable entity
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/GeneralError'
|
||||
500:
|
||||
description: system error
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/GeneralError'
|
||||
|
||||
|
||||
/Accounts:
|
||||
post:
|
||||
summary: create an account
|
||||
@@ -898,6 +1202,34 @@ paths:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/GeneralError'
|
||||
/Accounts/{AccountSid}/ApiKeys:
|
||||
parameters:
|
||||
- name: AccountSid
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
get:
|
||||
summary: get all api keys for an account
|
||||
operationId: getAccountApiKeys
|
||||
responses:
|
||||
200:
|
||||
description: list of api keys
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/ApiKey'
|
||||
404:
|
||||
description: account not found
|
||||
500:
|
||||
description: system error
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/GeneralError'
|
||||
|
||||
/Applications:
|
||||
post:
|
||||
@@ -917,10 +1249,13 @@ paths:
|
||||
format: uuid
|
||||
call_hook:
|
||||
$ref: '#/components/schemas/Webhook'
|
||||
description: authentication webhook for inbound calls from PSTN
|
||||
description: application webhook to handle inbound voice calls
|
||||
call_status_hook:
|
||||
$ref: '#/components/schemas/Webhook'
|
||||
description: webhook for call status events
|
||||
description: webhook to report call status events
|
||||
messaging_hook:
|
||||
$ref: '#/components/schemas/Webhook'
|
||||
description: application webhook to handle inbound SMS/MMS messages
|
||||
speech_synthesis_vendor:
|
||||
type: string
|
||||
speech_synthesis_voice:
|
||||
@@ -1082,7 +1417,7 @@ paths:
|
||||
from:
|
||||
type: string
|
||||
description: The calling party number
|
||||
example: 16172375089
|
||||
example: "16172375089"
|
||||
timeout:
|
||||
type: integer
|
||||
description: the number of seconds to wait for call to be answered. Defaults to 60.
|
||||
@@ -1101,9 +1436,9 @@ paths:
|
||||
application/json:
|
||||
schema:
|
||||
required:
|
||||
- callSid
|
||||
- sid
|
||||
properties:
|
||||
callSid:
|
||||
sid:
|
||||
type: string
|
||||
format: uuid
|
||||
example: 2531329f-fb09-4ef7-887e-84e648214436
|
||||
@@ -1133,8 +1468,6 @@ paths:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/GeneralError'
|
||||
|
||||
|
||||
/Accounts/{AccountSid}/Calls/{CallSid}:
|
||||
parameters:
|
||||
- name: AccountSid
|
||||
@@ -1200,8 +1533,6 @@ paths:
|
||||
properties:
|
||||
call_hook:
|
||||
$ref: '#/components/schemas/Webhook'
|
||||
call_status_hook:
|
||||
$ref: '#/components/schemas/Webhook'
|
||||
call_status:
|
||||
type: string
|
||||
enum:
|
||||
@@ -1212,6 +1543,11 @@ paths:
|
||||
enum:
|
||||
- pause
|
||||
- resume
|
||||
mute_status:
|
||||
type: string
|
||||
enum:
|
||||
- mute
|
||||
- unmute
|
||||
whisper:
|
||||
$ref: '#/components/schemas/Webhook'
|
||||
responses:
|
||||
@@ -1232,7 +1568,39 @@ paths:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/GeneralError'
|
||||
$ref: '#/components/schemas/GeneralError'
|
||||
/Accounts/{AccountSid}/Messages:
|
||||
post:
|
||||
summary: create an outgoing SMS message
|
||||
operationId: createMessage
|
||||
parameters:
|
||||
- name: AccountSid
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Message'
|
||||
responses:
|
||||
201:
|
||||
description: call successfully created
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
required:
|
||||
- sid
|
||||
properties:
|
||||
sid:
|
||||
type: string
|
||||
format: uuid
|
||||
example: 2531329f-fb09-4ef7-887e-84e648214436
|
||||
providerResponse:
|
||||
type: string
|
||||
400:
|
||||
description: bad request
|
||||
components:
|
||||
securitySchemes:
|
||||
bearerAuth:
|
||||
@@ -1240,6 +1608,17 @@ components:
|
||||
scheme: bearer
|
||||
bearerFormat: token
|
||||
schemas:
|
||||
Login:
|
||||
type: object
|
||||
properties:
|
||||
user_sid:
|
||||
type: string
|
||||
api_token:
|
||||
type: string
|
||||
change_password:
|
||||
type: boolean
|
||||
required:
|
||||
- user_sid
|
||||
SuccessfulApiKeyAdd:
|
||||
type: object
|
||||
required:
|
||||
@@ -1283,12 +1662,11 @@ components:
|
||||
type: string
|
||||
root_domain:
|
||||
type: string
|
||||
hook_basic_auth_user:
|
||||
registration_hook:
|
||||
$ref: '#/components/schemas/Webhook'
|
||||
description: authentication webhook for registration
|
||||
ms_teams_fqdn:
|
||||
type: string
|
||||
format: url
|
||||
hook_basic_auth_password:
|
||||
type: string
|
||||
format: url
|
||||
required:
|
||||
- service_provider_sid
|
||||
- name
|
||||
@@ -1302,6 +1680,8 @@ components:
|
||||
type: string
|
||||
description:
|
||||
type: string
|
||||
e164_leading_plus:
|
||||
type: boolean
|
||||
required:
|
||||
- voip_carrier_sid
|
||||
- name
|
||||
@@ -1368,10 +1748,13 @@ components:
|
||||
format: uuid
|
||||
call_hook:
|
||||
$ref: '#/components/schemas/Webhook'
|
||||
description: authentication webhook for registration
|
||||
description: application webhook for inbound voice calls
|
||||
call_status_hook:
|
||||
$ref: '#/components/schemas/Webhook'
|
||||
description: authentication webhook for registration
|
||||
description: webhhok for reporting call status events
|
||||
messaging_hook:
|
||||
$ref: '#/components/schemas/Webhook'
|
||||
description: application webhook for inbound SMS/MMS
|
||||
speech_synthesis_vendor:
|
||||
type: string
|
||||
speech_synthesis_voice:
|
||||
@@ -1384,8 +1767,30 @@ components:
|
||||
- application_sid
|
||||
- name
|
||||
- account_sid
|
||||
- inbound_hook
|
||||
- inbound_status_hook
|
||||
ApiKey:
|
||||
type: object
|
||||
properties:
|
||||
api_key_sid:
|
||||
type: string
|
||||
format: uuid
|
||||
token:
|
||||
type: string
|
||||
format: uuid
|
||||
account_sid:
|
||||
type: string
|
||||
format: uuid
|
||||
service_provider_sid:
|
||||
type: string
|
||||
format: uuid
|
||||
expires_at:
|
||||
type: dateTime
|
||||
created_at:
|
||||
type: dateTime
|
||||
last_used:
|
||||
type: dateTime
|
||||
required:
|
||||
- api_key_sid
|
||||
- token
|
||||
PhoneNumber:
|
||||
type: object
|
||||
properties:
|
||||
@@ -1444,6 +1849,23 @@ components:
|
||||
required:
|
||||
- url
|
||||
example: {"url": "https://acme.com", "method": "POST"}
|
||||
MsTeamsTenant:
|
||||
type: object
|
||||
properties:
|
||||
service_provider_sid:
|
||||
type: string
|
||||
format: uuid
|
||||
account_sid:
|
||||
type: string
|
||||
format: uuid
|
||||
application_sid:
|
||||
type: string
|
||||
format: uuid
|
||||
tenant_fqdn:
|
||||
type: string
|
||||
required:
|
||||
- service_provider_sid
|
||||
- tenant_fqdn
|
||||
Call:
|
||||
type: object
|
||||
properties:
|
||||
@@ -1526,6 +1948,22 @@ components:
|
||||
required:
|
||||
- type
|
||||
example: {"type": "phone", "number": "+16172375080"}
|
||||
Message:
|
||||
properties:
|
||||
provider:
|
||||
type: string
|
||||
from:
|
||||
type: string
|
||||
to:
|
||||
type: string
|
||||
text:
|
||||
type: string
|
||||
media:
|
||||
type: string
|
||||
required:
|
||||
- from
|
||||
- to
|
||||
example: {"from": "13394445678", "to": "16173333456", "text": "please call when you can"}
|
||||
|
||||
security:
|
||||
- bearerAuth: []
|
||||
40
lib/utils/sms-provider.js
Normal file
40
lib/utils/sms-provider.js
Normal file
@@ -0,0 +1,40 @@
|
||||
const providers = new Map();
|
||||
let init = false;
|
||||
|
||||
function initProviders(logger) {
|
||||
if (init) return;
|
||||
if (process.env.JAMBONES_MESSAGING) {
|
||||
try {
|
||||
const obj = JSON.parse(process.env.JAMBONES_MESSAGING);
|
||||
for (const [key, value] of Object.entries(obj)) {
|
||||
logger.debug({config: value}, `Adding SMS provider ${key}`);
|
||||
providers.set(key, value);
|
||||
}
|
||||
logger.info(`Configured ${providers.size} SMS providers`);
|
||||
} catch (err) {
|
||||
logger.error(err, `expected JSON for JAMBONES_MESSAGING : ${process.env.JAMBONES_MESSAGING}`);
|
||||
}
|
||||
}
|
||||
else {
|
||||
logger.info('no JAMBONES_MESSAGING env var, messaging is disabled');
|
||||
}
|
||||
init = true;
|
||||
}
|
||||
|
||||
function getProvider(logger, partner) {
|
||||
initProviders(logger);
|
||||
if (typeof partner === 'string') {
|
||||
const config = providers.get(partner);
|
||||
const arr = [partner, config];
|
||||
logger.debug({arr}, 'getProvider by name');
|
||||
return arr;
|
||||
}
|
||||
else if (providers.size) {
|
||||
const arr = providers.entries().next().value;
|
||||
logger.debug({arr}, 'getProvider by first available');
|
||||
return arr;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = getProvider;
|
||||
|
||||
33
package.json
33
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "jambonz-api-server",
|
||||
"version": "1.1.4",
|
||||
"version": "1.2.0",
|
||||
"description": "",
|
||||
"main": "app.js",
|
||||
"scripts": {
|
||||
@@ -15,28 +15,31 @@
|
||||
"url": "https://github.com/jambonz/jambonz-api-server.git"
|
||||
},
|
||||
"dependencies": {
|
||||
"@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",
|
||||
"jambonz-db-helpers": "^0.3.2",
|
||||
"jambonz-realtimedb-helpers": "0.1.7",
|
||||
"mysql2": "^2.0.2",
|
||||
"passport": "^0.4.0",
|
||||
"mysql2": "^2.1.0",
|
||||
"passport": "^0.4.1",
|
||||
"passport-http-bearer": "^1.0.1",
|
||||
"pino": "^5.14.0",
|
||||
"request": "^2.88.0",
|
||||
"pino": "^5.17.0",
|
||||
"request": "^2.88.2",
|
||||
"request-debug": "^0.2.0",
|
||||
"swagger-ui-express": "^4.1.2",
|
||||
"tape": "^4.11.0",
|
||||
"uuid": "^3.3.3",
|
||||
"swagger-ui-dist": "^3.35.0",
|
||||
"swagger-ui-express": "^4.1.4",
|
||||
"uuid": "^3.4.0",
|
||||
"yamljs": "^0.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"eslint": "^6.7.2",
|
||||
"eslint": "^7.10.0",
|
||||
"eslint-plugin-promise": "^4.2.1",
|
||||
"nyc": "^14.1.1",
|
||||
"request-promise-native": "^1.0.8",
|
||||
"tap": "^14.10.2",
|
||||
"nyc": "^15.1.0",
|
||||
"request-promise-native": "^1.0.9",
|
||||
"tap-dot": "^2.0.0",
|
||||
"tap-spec": "^5.0.0"
|
||||
"tap-spec": "^5.0.0",
|
||||
"tape": "^5.0.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,11 @@ const authAdmin = {bearer: ADMIN_TOKEN};
|
||||
const request = require('request-promise-native').defaults({
|
||||
baseUrl: 'http://127.0.0.1:3000/v1'
|
||||
});
|
||||
const {createVoipCarrier, createServiceProvider, createPhoneNumber, deleteObjectBySid} = require('./utils');
|
||||
const {
|
||||
createVoipCarrier,
|
||||
createServiceProvider,
|
||||
createPhoneNumber,
|
||||
deleteObjectBySid} = require('./utils');
|
||||
|
||||
process.on('unhandledRejection', (reason, p) => {
|
||||
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
|
||||
@@ -38,6 +42,26 @@ test('account tests', async(t) => {
|
||||
t.ok(result.statusCode === 201, 'successfully created account');
|
||||
const sid = result.body.sid;
|
||||
|
||||
/* add an account level api key */
|
||||
result = await request.post(`/ApiKeys`, {
|
||||
auth: authAdmin,
|
||||
json: true,
|
||||
resolveWithFullResponse: true,
|
||||
body: {
|
||||
account_sid: sid
|
||||
}
|
||||
});
|
||||
t.ok(result.statusCode === 201 && result.body.token, 'successfully created account level token');
|
||||
const apiKeySid = result.body.sid;
|
||||
const accountLevelToken = result.body.token;
|
||||
|
||||
/* query all account level api keys */
|
||||
result = await request.get(`/Accounts/${sid}/ApiKeys`, {
|
||||
auth: {bearer: accountLevelToken},
|
||||
json: true,
|
||||
});
|
||||
t.ok(Array.isArray(result) && result.length === 1, 'successfully queried account level keys');
|
||||
|
||||
/* query all accounts */
|
||||
result = await request.get('/Accounts', {
|
||||
auth: authAdmin,
|
||||
@@ -45,7 +69,7 @@ test('account tests', async(t) => {
|
||||
});
|
||||
let regHook = result[0].registration_hook;
|
||||
t.ok(result.length === 1 &&
|
||||
Object.keys(regHook).length == 5, 'successfully queried all accounts');
|
||||
Object.keys(regHook).length == 4, 'successfully queried all accounts');
|
||||
|
||||
/* query one accounts */
|
||||
result = await request.get(`/Accounts/${sid}`, {
|
||||
@@ -54,9 +78,9 @@ test('account tests', async(t) => {
|
||||
});
|
||||
t.ok(result.name === 'daveh' , 'successfully retrieved account by sid');
|
||||
|
||||
/* update accounts */
|
||||
/* update account with account level token */
|
||||
result = await request.put(`/Accounts/${sid}`, {
|
||||
auth: authAdmin,
|
||||
auth: {bearer: accountLevelToken},
|
||||
json: true,
|
||||
resolveWithFullResponse: true,
|
||||
body: {
|
||||
@@ -67,14 +91,21 @@ test('account tests', async(t) => {
|
||||
}
|
||||
}
|
||||
});
|
||||
t.ok(result.statusCode === 204, 'successfully updated account');
|
||||
t.ok(result.statusCode === 204, 'successfully updated account using account level token');
|
||||
|
||||
/* verify that account level api key last_used was updated*/
|
||||
result = await request.get(`/Accounts/${sid}/ApiKeys`, {
|
||||
auth: {bearer: accountLevelToken},
|
||||
json: true,
|
||||
});
|
||||
t.ok(typeof result[0].last_used === 'string', 'api_key last_used timestamp was updated');
|
||||
|
||||
result = await request.get(`/Accounts/${sid}`, {
|
||||
auth: authAdmin,
|
||||
json: true,
|
||||
});
|
||||
//console.log(`retrieved account after update: ${JSON.stringify(result)}`);
|
||||
t.ok(Object.keys(result.registration_hook).length === 5, 'successfully removed a hook from account');
|
||||
t.ok(Object.keys(result.registration_hook).length === 4, 'successfully removed a hook from account');
|
||||
|
||||
/* assign phone number to account */
|
||||
result = await request.put(`/PhoneNumbers/${phone_number_sid}`, {
|
||||
@@ -97,6 +128,7 @@ test('account tests', async(t) => {
|
||||
t.ok(result.statusCode === 422 && result.body.msg === 'cannot delete account with phone numbers', 'cannot delete account with phone numbers');
|
||||
|
||||
/* delete account */
|
||||
await request.delete(`ApiKeys/${apiKeySid}`, {auth: {bearer: accountLevelToken}});
|
||||
await request.delete(`/PhoneNumbers/${phone_number_sid}`, {auth: authAdmin});
|
||||
result = await request.delete(`/Accounts/${sid}`, {
|
||||
auth: authAdmin,
|
||||
@@ -109,7 +141,7 @@ test('account tests', async(t) => {
|
||||
t.end();
|
||||
}
|
||||
catch (err) {
|
||||
//console.error(err);
|
||||
console.error(err);
|
||||
t.end(err);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -38,6 +38,9 @@ test('application tests', async(t) => {
|
||||
call_status_hook: {
|
||||
url: 'http://example.com/status',
|
||||
method: 'POST'
|
||||
},
|
||||
messaging_hook: {
|
||||
url: 'http://example.com/sms'
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -58,6 +61,7 @@ test('application tests', async(t) => {
|
||||
json: true,
|
||||
});
|
||||
t.ok(result[0].name === 'daveh' , 'successfully retrieved application by sid');
|
||||
t.ok(result[0].messaging_hook.url === 'http://example.com/sms' , 'successfully retrieved messaging_hook from application');
|
||||
|
||||
/* update applications */
|
||||
result = await request.put(`/Applications/${sid}`, {
|
||||
@@ -67,11 +71,21 @@ test('application tests', async(t) => {
|
||||
body: {
|
||||
call_hook: {
|
||||
url: 'http://example2.com'
|
||||
},
|
||||
messaging_hook: {
|
||||
url: 'http://example2.com/mms'
|
||||
}
|
||||
}
|
||||
});
|
||||
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[0].messaging_hook.url === 'http://example2.com/mms' , 'successfully updated messaging_hook');
|
||||
|
||||
/* assign phone number to application */
|
||||
result = await request.put(`/PhoneNumbers/${phone_number_sid}`, {
|
||||
auth: authAdmin,
|
||||
|
||||
@@ -6,4 +6,6 @@ require('./accounts');
|
||||
require('./phone-numbers');
|
||||
require('./applications');
|
||||
require('./auth');
|
||||
require('./sbcs');
|
||||
require('./ms-teams');
|
||||
require('./remove-test-db');
|
||||
|
||||
89
test/ms-teams.js
Normal file
89
test/ms-teams.js
Normal file
@@ -0,0 +1,89 @@
|
||||
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({
|
||||
baseUrl: 'http://127.0.0.1:3000/v1'
|
||||
});
|
||||
const {createServiceProvider, createAccount, deleteObjectBySid} = require('./utils');
|
||||
|
||||
process.on('unhandledRejection', (reason, p) => {
|
||||
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
|
||||
});
|
||||
|
||||
test('sbc_addresses tests', async(t) => {
|
||||
const app = require('../app');
|
||||
let sid;
|
||||
try {
|
||||
let result;
|
||||
const service_provider_sid = await createServiceProvider(request);
|
||||
const account_sid = await createAccount(request, service_provider_sid);
|
||||
const account_sid2 = await createAccount(request, service_provider_sid, 'account2');
|
||||
|
||||
/* add a tenant */
|
||||
result = await request.post('/MicrosoftTeamsTenants', {
|
||||
resolveWithFullResponse: true,
|
||||
auth: authAdmin,
|
||||
json: true,
|
||||
body: {
|
||||
service_provider_sid,
|
||||
account_sid,
|
||||
tenant_fqdn: 'foo.bar.baz'
|
||||
}
|
||||
});
|
||||
t.ok(result.statusCode === 201, 'successfully added ms teams tenant');
|
||||
const sid1 = result.body.sid;
|
||||
|
||||
/* add a second tenant */
|
||||
result = await request.post('/MicrosoftTeamsTenants', {
|
||||
resolveWithFullResponse: true,
|
||||
auth: authAdmin,
|
||||
json: true,
|
||||
body: {
|
||||
service_provider_sid,
|
||||
account_sid: account_sid2,
|
||||
tenant_fqdn: 'junk.bar.baz'
|
||||
}
|
||||
});
|
||||
t.ok(result.statusCode === 201, 'successfully added ms teams tenant');
|
||||
const sid2 = result.body.sid;
|
||||
|
||||
result = await request.get('/MicrosoftTeamsTenants', {
|
||||
resolveWithFullResponse: true,
|
||||
auth: authAdmin,
|
||||
json: true
|
||||
});
|
||||
t.ok(result.body.length === 2, 'successfully retrieved tenants');
|
||||
|
||||
/* update tenant */
|
||||
result = await request.put(`/MicrosoftTeamsTenants/${sid1}`, {
|
||||
auth: authAdmin,
|
||||
json: true,
|
||||
resolveWithFullResponse: true,
|
||||
body: {
|
||||
tenant_fqdn: 'foo.bar.bazzle'
|
||||
}
|
||||
});
|
||||
t.ok(result.statusCode === 204, 'successfully updated ms teams tenant');
|
||||
|
||||
/* get tenant */
|
||||
result = await request.get(`/MicrosoftTeamsTenants/${sid1}`, {
|
||||
auth: authAdmin,
|
||||
json: true
|
||||
});
|
||||
t.ok(result.tenant_fqdn === 'foo.bar.bazzle', 'successfully retrieved ms teams tenant');
|
||||
|
||||
|
||||
await deleteObjectBySid(request, '/MicrosoftTeamsTenants', sid1);
|
||||
await deleteObjectBySid(request, '/MicrosoftTeamsTenants', sid2);
|
||||
await deleteObjectBySid(request, '/Accounts', account_sid);
|
||||
await deleteObjectBySid(request, '/Accounts', account_sid2);
|
||||
await deleteObjectBySid(request, '/ServiceProviders', service_provider_sid);
|
||||
|
||||
t.end();
|
||||
}
|
||||
catch (err) {
|
||||
console.error(err);
|
||||
t.end(err);
|
||||
}
|
||||
});
|
||||
|
||||
70
test/sbcs.js
Normal file
70
test/sbcs.js
Normal file
@@ -0,0 +1,70 @@
|
||||
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({
|
||||
baseUrl: 'http://127.0.0.1:3000/v1'
|
||||
});
|
||||
const {createServiceProvider, deleteObjectBySid} = require('./utils');
|
||||
|
||||
process.on('unhandledRejection', (reason, p) => {
|
||||
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
|
||||
});
|
||||
|
||||
test('sbc_addresses tests', async(t) => {
|
||||
const app = require('../app');
|
||||
let sid;
|
||||
try {
|
||||
let result;
|
||||
const service_provider_sid = await createServiceProvider(request);
|
||||
|
||||
/* add a community sbc */
|
||||
result = await request.post('/Sbcs', {
|
||||
resolveWithFullResponse: true,
|
||||
auth: authAdmin,
|
||||
json: true,
|
||||
body: {
|
||||
ipv4: '192.168.1.1'
|
||||
}
|
||||
});
|
||||
t.ok(result.statusCode === 201, 'successfully created community sbc ');
|
||||
const sid1 = result.body.sid;
|
||||
|
||||
/* add a service provider sbc */
|
||||
result = await request.post('/Sbcs', {
|
||||
resolveWithFullResponse: true,
|
||||
auth: authAdmin,
|
||||
json: true,
|
||||
body: {
|
||||
ipv4: '192.168.1.4',
|
||||
service_provider_sid
|
||||
}
|
||||
});
|
||||
t.ok(result.statusCode === 201, 'successfully created service provider sbc ');
|
||||
const sid2 = result.body.sid;
|
||||
|
||||
result = await request.get('/Sbcs', {
|
||||
resolveWithFullResponse: true,
|
||||
auth: authAdmin,
|
||||
json: true
|
||||
});
|
||||
t.ok(result.body.length === 1 && result.body[0].ipv4 === '192.168.1.1', 'successfully retrieved community sbc');
|
||||
|
||||
result = await request.get(`/Sbcs?service_provider_sid=${service_provider_sid}`, {
|
||||
resolveWithFullResponse: true,
|
||||
auth: authAdmin,
|
||||
json: true
|
||||
});
|
||||
t.ok(result.body.length === 1 && result.body[0].ipv4 === '192.168.1.4', 'successfully retrieved service provider sbc');
|
||||
|
||||
await deleteObjectBySid(request, '/Sbcs', sid1);
|
||||
await deleteObjectBySid(request, '/Sbcs', sid2);
|
||||
await deleteObjectBySid(request, '/ServiceProviders', service_provider_sid);
|
||||
|
||||
t.end();
|
||||
}
|
||||
catch (err) {
|
||||
console.error(err);
|
||||
t.end(err);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -30,7 +30,8 @@ test('service provider tests', async(t) => {
|
||||
auth: authAdmin,
|
||||
json: true,
|
||||
body: {
|
||||
name: 'daveh'
|
||||
name: 'daveh',
|
||||
ms_teams_fqdn: 'contoso.com'
|
||||
}
|
||||
});
|
||||
t.ok(result.statusCode === 201, 'successfully created service provider');
|
||||
@@ -43,7 +44,10 @@ test('service provider tests', async(t) => {
|
||||
json: true,
|
||||
body: {
|
||||
name: 'johndoe',
|
||||
root_domain: 'example.com'
|
||||
root_domain: 'example.com',
|
||||
registration_hook: {
|
||||
url: 'http://a.com'
|
||||
}
|
||||
}
|
||||
});
|
||||
t.ok(result.statusCode === 201, 'successfully created service provider with a root domain');
|
||||
@@ -84,11 +88,11 @@ test('service provider tests', async(t) => {
|
||||
t.ok(result.length === 2 , 'successfully queried all service providers');
|
||||
|
||||
/* query one service providers */
|
||||
result = await request.get(`/ServiceProviders/${sid}`, {
|
||||
result = await request.get(`/ServiceProviders/${sid2}`, {
|
||||
auth: authAdmin,
|
||||
json: true,
|
||||
});
|
||||
t.ok(result.name === 'daveh' , 'successfully retrieved service provider by sid');
|
||||
t.ok(result.name === 'johndoe' && result.root_domain === 'example.com', 'successfully retrieved service provider by sid');
|
||||
|
||||
|
||||
/* update service providers */
|
||||
|
||||
@@ -22,7 +22,8 @@ test('voip carrier tests', async(t) => {
|
||||
auth: authAdmin,
|
||||
json: true,
|
||||
body: {
|
||||
name: 'daveh'
|
||||
name: 'daveh',
|
||||
e164_leading_plus: true
|
||||
}
|
||||
});
|
||||
t.ok(result.statusCode === 201, 'successfully created voip carrier');
|
||||
@@ -33,7 +34,7 @@ test('voip carrier tests', async(t) => {
|
||||
auth: authAdmin,
|
||||
json: true,
|
||||
});
|
||||
t.ok(result.length === 1 , 'successfully queried all voip carriers');
|
||||
t.ok(result.length === 1 && result[0].e164_leading_plus, 'successfully queried all voip carriers');
|
||||
|
||||
/* query one voip carriers */
|
||||
result = await request.get(`/VoipCarriers/${sid}`, {
|
||||
|
||||
Reference in New Issue
Block a user