removed config files, just uses env vars now

This commit is contained in:
Dave Horton
2020-02-15 11:00:01 -05:00
parent 2e25643021
commit aad5082e76
11 changed files with 105 additions and 94 deletions

5
.gitignore vendored
View File

@@ -40,4 +40,7 @@ examples/*
create_db.sql
.vscode
.vscode
.env.*
.env

View File

@@ -4,7 +4,21 @@ Jambones REST API server.
## Configuration
The configuration needs of the application are minimal and can be found in the `config` directory (using the npmjs [config](https://www.npmjs.com/package/config) package). You simply need to configure the connection settings to the mysql database and the log level. Copy the provided [default.json.example](config/default.json.example) to default.json or local.json and edit appropriately.
This process requires the following environment variables to be set.
```
JAMBONES_MYSQL_HOST
JAMBONES_MYSQL_USER
JAMBONES_MYSQL_PASSWORD
JAMBONES_MYSQL_DATABASE
JAMBONES_MYSQL_CONNECTION_LIMIT # defaults to 10
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
```
#### Database dependency
A mysql database is used to store long-lived objects such as Accounts, Applications, etc. To create the database schema, use or review the scripts in the 'db' folder, particularly:

24
app.js
View File

@@ -1,7 +1,7 @@
const config = require('config');
const assert = require('assert');
const opts = Object.assign({
timestamp: () => {return `, "time": "${new Date().toISOString()}"`;}
}, config.get('logging'));
}, process.env.JAMBONES_LOGLEVEL || 'info');
const logger = require('pino')(opts);
const express = require('express');
const app = express();
@@ -9,16 +9,32 @@ const cors = require('cors');
const passport = require('passport');
const authStrategy = require('./lib/auth')(logger);
const routes = require('./lib/routes');
assert.ok(process.env.JAMBONES_MYSQL_HOST &&
process.env.JAMBONES_MYSQL_USER &&
process.env.JAMBONES_MYSQL_PASSWORD &&
process.env.JAMBONES_MYSQL_DATABASE, 'missing JAMBONES_MYSQL_XXX env vars');
assert.ok(process.env.JAMBONES_CREATE_CALL_URL, 'missing JAMBONES_CREATE_CALL_URL env var');
const {
retrieveCall,
deleteCall,
listCalls,
purgeCalls
} = require('jambonz-realtimedb-helpers')(config.get('redis'), logger);
} = require('jambonz-realtimedb-helpers')({
host: process.env.JAMBONES_REDIS_HOST || 'localhost',
port: process.env.JAMBONES_REDIS_PORT || 6379
}, logger);
const {
lookupApplicationBySid,
lookupAccountBySid
} = require('jambonz-db-helpers')(config.get('mysql'), logger);
} = require('jambonz-db-helpers')({
host: process.env.JAMBONES_MYSQL_HOST,
user: process.env.JAMBONES_MYSQL_USER,
password: process.env.JAMBONES_MYSQL_PASSWORD,
database: process.env.JAMBONES_MYSQL_DATABASE,
connectionLimit: process.env.JAMBONES_MYSQL_CONNECTION_LIMIT || 10
}, logger);
const PORT = process.env.HTTP_PORT || 3000;
passport.use(authStrategy);

View File

@@ -1,20 +0,0 @@
{
"logging": {
"level": "info"
},
"mysql": {
"host": "example.org",
"user": "bob",
"password": "secret",
"database": "jambones",
"connectionLimit": 10
},
"redis": {
"host": "127.0.0.1",
"port": 6379
},
"services": {
"apiVersion" : "v1",
"createCall": "http://feature.server/v1/createCall:3000"
}
}

View File

@@ -1,19 +0,0 @@
{
"logging": {
"level": "error"
},
"mysql": {
"host": "localhost",
"user": "jambones_test",
"database": "jambones_test",
"password": "jambones_test"
},
"redis": {
"host": "127.0.0.1",
"port": 6379
},
"services": {
"apiVersion" : "v1",
"createCall": "http://feature.server/v1/createCall:3000"
}
}

View File

@@ -61,26 +61,26 @@ CREATE TABLE IF NOT EXISTS `voip_carriers`
PRIMARY KEY (`voip_carrier_sid`)
) ENGINE=InnoDB COMMENT='A Carrier or customer PBX that can send or receive calls';
CREATE TABLE IF NOT EXISTS `webhooks`
(
`webhook_sid` CHAR(36) NOT NULL UNIQUE ,
`url` VARCHAR(255) 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 `phone_numbers`
(
`phone_number_sid` CHAR(36) UNIQUE ,
`number` VARCHAR(255) NOT NULL 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`
(
`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`
(
`lcr_carrier_set_entry_sid` CHAR(36),
@@ -106,7 +106,7 @@ PRIMARY KEY (`sip_gateway_sid`)
CREATE TABLE IF NOT EXISTS `applications`
(
`application_sid` CHAR(36) NOT NULL UNIQUE ,
`name` VARCHAR(255) NOT NULL,
`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',
@@ -122,7 +122,7 @@ CREATE TABLE IF NOT EXISTS `service_providers`
`service_provider_sid` CHAR(36) NOT NULL UNIQUE ,
`name` VARCHAR(64) NOT NULL UNIQUE ,
`description` VARCHAR(255),
`root_domain` VARCHAR(255) UNIQUE ,
`root_domain` VARCHAR(128) UNIQUE ,
`registration_hook_sid` CHAR(36),
PRIMARY KEY (`service_provider_sid`)
) ENGINE=InnoDB COMMENT='A partition of the platform used by one service provider';
@@ -130,7 +130,7 @@ PRIMARY KEY (`service_provider_sid`)
CREATE TABLE IF NOT EXISTS `accounts`
(
`account_sid` CHAR(36) NOT NULL UNIQUE ,
`name` VARCHAR(255) NOT NULL,
`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',
@@ -157,7 +157,6 @@ ALTER TABLE `voip_carriers` ADD FOREIGN KEY account_sid_idxfk_2 (`account_sid`)
ALTER TABLE `voip_carriers` ADD FOREIGN KEY application_sid_idxfk_1 (`application_sid`) REFERENCES `applications` (`application_sid`);
CREATE INDEX `webhooks_webhook_sid_idx` ON `webhooks` (`webhook_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`);
@@ -166,6 +165,7 @@ ALTER TABLE `phone_numbers` ADD FOREIGN KEY account_sid_idxfk_3 (`account_sid`)
ALTER TABLE `phone_numbers` ADD FOREIGN KEY application_sid_idxfk_2 (`application_sid`) REFERENCES `applications` (`application_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`);
ALTER TABLE `lcr_carrier_set_entry` ADD FOREIGN KEY voip_carrier_sid_idxfk_1 (`voip_carrier_sid`) REFERENCES `voip_carriers` (`voip_carrier_sid`);

View File

@@ -132,7 +132,7 @@
<indexed><![CDATA[1]]></indexed>
<uid><![CDATA[3F553F20-AA47-471E-A650-0B4CEC9DCB0A]]></uid>
</SQLField>
<labelWindowIndex><![CDATA[15]]></labelWindowIndex>
<labelWindowIndex><![CDATA[10]]></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>
@@ -162,14 +162,14 @@
</SQLField>
<SQLField>
<name><![CDATA[url]]></name>
<type><![CDATA[VARCHAR(255)]]></type>
<type><![CDATA[VARCHAR(1024)]]></type>
<notNull><![CDATA[1]]></notNull>
<uid><![CDATA[EB1F13D9-E99A-4F31-B5B0-78C3E0F1E344]]></uid>
</SQLField>
<SQLField>
<name><![CDATA[method]]></name>
<type><![CDATA[ENUM("get","post")]]></type>
<defaultValue><![CDATA[post]]></defaultValue>
<type><![CDATA[ENUM("GET","POST")]]></type>
<defaultValue><![CDATA[POST]]></defaultValue>
<notNull><![CDATA[1]]></notNull>
<uid><![CDATA[6DFFE441-D825-43D4-AFFB-231D5B3F565E]]></uid>
</SQLField>
@@ -254,7 +254,7 @@
<uid><![CDATA[9B4208B5-9E3B-4B76-B7F7-4E5D36B99BF2]]></uid>
<unsigned><![CDATA[0]]></unsigned>
</SQLField>
<labelWindowIndex><![CDATA[14]]></labelWindowIndex>
<labelWindowIndex><![CDATA[9]]></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>
@@ -355,7 +355,7 @@
</SQLField>
<SQLField>
<name><![CDATA[name]]></name>
<type><![CDATA[VARCHAR(255)]]></type>
<type><![CDATA[VARCHAR(64)]]></type>
<indexed><![CDATA[1]]></indexed>
<notNull><![CDATA[1]]></notNull>
<uid><![CDATA[87414407-17CB-4582-9C0B-73CA548E1016]]></uid>
@@ -421,7 +421,7 @@
<notNull><![CDATA[1]]></notNull>
<uid><![CDATA[C7130A90-DBB4-424D-A9A9-CB203C32350C]]></uid>
</SQLField>
<labelWindowIndex><![CDATA[12]]></labelWindowIndex>
<labelWindowIndex><![CDATA[8]]></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>
@@ -453,7 +453,7 @@
</SQLField>
<SQLField>
<name><![CDATA[number]]></name>
<type><![CDATA[VARCHAR(255)]]></type>
<type><![CDATA[VARCHAR(32)]]></type>
<notNull><![CDATA[1]]></notNull>
<uid><![CDATA[159B82ED-C6B0-4FC6-957B-5C354AF9E783]]></uid>
<unique><![CDATA[1]]></unique>
@@ -503,7 +503,7 @@
<uid><![CDATA[962CB80A-54CB-4C6A-9591-9BFC644CF80F]]></uid>
<unsigned><![CDATA[0]]></unsigned>
</SQLField>
<labelWindowIndex><![CDATA[9]]></labelWindowIndex>
<labelWindowIndex><![CDATA[7]]></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>
@@ -626,7 +626,7 @@
</SQLField>
<SQLField>
<name><![CDATA[name]]></name>
<type><![CDATA[VARCHAR(255)]]></type>
<type><![CDATA[VARCHAR(64)]]></type>
<indexed><![CDATA[1]]></indexed>
<notNull><![CDATA[1]]></notNull>
<uid><![CDATA[E0EDB7B1-B7F7-4F56-B94F-6B81BB87C514]]></uid>
@@ -813,7 +813,7 @@
</SQLField>
<SQLField>
<name><![CDATA[root_domain]]></name>
<type><![CDATA[VARCHAR(255)]]></type>
<type><![CDATA[VARCHAR(128)]]></type>
<indexed><![CDATA[1]]></indexed>
<uid><![CDATA[7074CA4C-D211-4926-8BD2-7EF97B49071A]]></uid>
<unique><![CDATA[1]]></unique>
@@ -850,17 +850,17 @@
<overviewPanelHidden><![CDATA[0]]></overviewPanelHidden>
<pageBoundariesVisible><![CDATA[0]]></pageBoundariesVisible>
<PageGridVisible><![CDATA[0]]></PageGridVisible>
<RightSidebarWidth><![CDATA[1477.000000]]></RightSidebarWidth>
<RightSidebarWidth><![CDATA[1403.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[1391.000000]]></windowHeight>
<windowLocationX><![CDATA[3365.000000]]></windowLocationX>
<windowLocationY><![CDATA[26.000000]]></windowLocationY>
<windowScrollOrigin><![CDATA[{55.5, 23}]]></windowScrollOrigin>
<windowWidth><![CDATA[1754.000000]]></windowWidth>
<windowHeight><![CDATA[1027.000000]]></windowHeight>
<windowLocationX><![CDATA[0.000000]]></windowLocationX>
<windowLocationY><![CDATA[0.000000]]></windowLocationY>
<windowScrollOrigin><![CDATA[{0, 17.5}]]></windowScrollOrigin>
<windowWidth><![CDATA[1680.000000]]></windowWidth>
</SQLDocumentInfo>
<AllowsIndexRenamingOnInsert><![CDATA[1]]></AllowsIndexRenamingOnInsert>
<defaultLabelExpanded><![CDATA[1]]></defaultLabelExpanded>

View File

@@ -1,6 +1,11 @@
const mysql = require('mysql2');
const config = require('config');
const pool = mysql.createPool(config.get('mysql'));
const pool = mysql.createPool({
host: process.env.JAMBONES_MYSQL_HOST,
user: process.env.JAMBONES_MYSQL_USER,
password: process.env.JAMBONES_MYSQL_PASSWORD,
database: process.env.JAMBONES_MYSQL_DATABASE,
connectionLimit: process.env.JAMBONES_MYSQL_CONNECTION_LIMIT || 10
});
pool.getConnection((err, conn) => {
if (err) return console.error(err, 'Error testing pool');

View File

@@ -1,6 +1,5 @@
const router = require('express').Router();
const request = require('request');
const config = require('config');
const {DbErrorBadRequest, DbErrorUnprocessableRequest} = require('../../utils/errors');
const Account = require('../../models/account');
const Webhook = require('../../models/webhook');
@@ -13,7 +12,6 @@ const preconditions = {
'update': validateUpdate,
'delete': validateDelete
};
const API_VERSION = config.get('services.apiVersion');
function coerceNumbers(callInfo) {
if (Array.isArray(callInfo)) {
@@ -29,15 +27,25 @@ function coerceNumbers(callInfo) {
}
function validateUpdateCall(opts) {
if (!opts.call_hook && !opts.call_status && !opts.listen_status) {
throw new DbErrorBadRequest('no valid options supplied to updateCall');
}
const count = [opts.call_hook, opts.call_status, opts.listen_status]
.reduce((acc, item) => {
if (item) return ++acc;
}, 0);
if (count > 1) {
throw new DbErrorBadRequest('multiple options supplied to updateCall');
// only one type of update can be supplied per request
const hasWhisper = opts.whisper;
const count = [
'call_hook',
'call_status',
'listen_status',
'mute_status']
.reduce((acc, prop) => (opts[prop] ? ++acc : acc), 0);
switch (count) {
case 0:
// whisper is allowed on its own, or with one of the others
if (!hasWhisper) throw new DbErrorBadRequest('no valid options supplied to updateCall');
break;
case 1:
// good
break;
default:
throw new DbErrorBadRequest('multiple options are not allowed in updateCall');
}
if (opts.call_hook && !opts.call_hook.url) throw new DbErrorBadRequest('missing call_hook.url');
@@ -47,6 +55,9 @@ function validateUpdateCall(opts) {
if (opts.listen_status && !['pause', 'silence', 'resume'].includes(opts.listen_status)) {
throw new DbErrorBadRequest('invalid listen_status');
}
if (opts.mute_status && !['mute', 'unmute'].includes(opts.mute_status)) {
throw new DbErrorBadRequest('invalid mute_status');
}
}
function validateTo(to) {
@@ -253,7 +264,7 @@ router.post('/:sid/Calls', async(req, res) => {
const logger = req.app.locals.logger;
try {
const serviceUrl = config.get('services.createCall');
const serviceUrl = process.env.JAMBONES_CREATE_CALL_URL;
await validateCreateCall(logger, sid, req);
logger.debug({payload: req.body}, `sending POST to ${serviceUrl}`);
@@ -352,7 +363,7 @@ router.post('/:sid/Calls/:callSid', async(req, res) => {
validateUpdateCall(req.body);
const call = await retrieveCall(accountSid, callSid);
if (call) {
const url = `${call.serviceUrl}/${API_VERSION}/updateCall/${callSid}`;
const url = `${call.serviceUrl}/${process.env.JAMBONES_API_VERSION || 'v1'}/updateCall/${callSid}`;
logger.debug({call, url, payload: req.body}, `updateCall: retrieved call info for call sid ${callSid}`);
request({
url: url,

View File

@@ -1212,6 +1212,8 @@ paths:
enum:
- pause
- resume
whisper:
$ref: '#/components/schemas/Webhook'
responses:
202:
description: Accepted

View File

@@ -5,7 +5,7 @@
"main": "app.js",
"scripts": {
"start": "node app.js",
"test": "NODE_ENV=test node test/ | ./node_modules/.bin/tap-spec",
"test": "NODE_ENV=test JAMBONES_MYSQL_HOST=localhost JAMBONES_MYSQL_USER=jambones_test JAMBONES_MYSQL_PASSWORD=jambones_test JAMBONES_MYSQL_DATABASE=jambones_test JAMBONES_REDIS_HOST=localhost JAMBONES_LOGLEVEL=error JAMBONES_CREATE_CALL_URL=http://localhost/v1/createCall node test/ | ./node_modules/.bin/tap-spec",
"coverage": "./node_modules/.bin/nyc --reporter html --report-dir ./coverage npm run test",
"jslint": "eslint app.js lib"
},
@@ -15,7 +15,6 @@
"url": "https://github.com/jambonz/jambonz-api-server.git"
},
"dependencies": {
"config": "^3.2.4",
"cors": "^2.8.5",
"express": "^4.17.1",
"jambonz-db-helpers": "^0.2.3",