added initial support for creating calls through REST API

This commit is contained in:
Dave Horton
2020-02-01 16:15:29 -05:00
parent 87208bc669
commit 2a974704fc
5 changed files with 182 additions and 65 deletions
+1
View File
@@ -24,6 +24,7 @@ app.use((err, req, res, next) => {
logger.error(err, 'burped error');
res.status(err.status || 500).json({msg: err.message});
});
logger.info(`listening for HTTP traffic on port ${PORT}`);
app.listen(PORT);
module.exports = app;
+3
View File
@@ -8,5 +8,8 @@
"password": "secret",
"database": "jambones",
"connectionLimit": 10
},
"services": {
"createCall": "http://feature.server/createCall:3000"
}
}
+81
View File
@@ -1,4 +1,6 @@
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');
@@ -11,6 +13,54 @@ const preconditions = {
'delete': validateDelete
};
function validateTo(to) {
if (to && typeof to === 'object') {
switch (to.type) {
case 'phone':
if (typeof to.number === 'string') return;
break;
case 'user':
if (typeof to.name === 'string') return;
break;
case 'sip':
if (typeof to.sipUri === 'string') return;
break;
}
}
throw new DbErrorBadRequest(`missing or invalid to property: ${to}`);
}
async function validateCreateCall(logger, sid, req) {
const {lookupApplicationBySid} = require('jambonz-db-helpers')(config.get('mysql'), logger);
const obj = req.body;
if (req.user.account_sid !== sid) throw new DbErrorBadRequest(`unauthorized createCall request for account ${sid}`);
if (!obj.from) throw new DbErrorBadRequest('missing from parameter');
validateTo(obj.to);
if (obj.application_sid) {
try {
logger.debug(`Accounts:validateCreateCall retrieving application ${obj.application_sid}`);
const application = await lookupApplicationBySid(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_voice: application.speech_synthesis_voice,
speech_recognizer_vendor: application.speech_recognizer_vendor,
speech_recognizer_language: application.speech_recognizer_language
});
logger.debug({obj, application}, 'Accounts:validateCreateCall augmented with application settings');
} catch (err) {
logger.error(err, `Accounts:validateCreateCall error retrieving application for sid ${obj.application_sid}`);
throw new DbErrorBadRequest(`application_sid not found ${obj.application_sid}`);
}
}
if (!obj.call_hook || (obj.call_hook && !obj.call_hook.url)) throw new DbErrorBadRequest('either url or application_sid required');
}
async function validateAdd(req) {
/* account-level token can not be used to add accounts */
if (req.user.hasAccountAuth) {
@@ -152,4 +202,35 @@ router.put('/:sid', async(req, res) => {
}
});
router.post('/:sid/Calls', async(req, res) => {
const sid = req.params.sid;
const logger = req.app.locals.logger;
try {
const serviceUrl = config.get('services.createCall');
await validateCreateCall(logger, sid, req);
logger.debug({payload: req.body}, `sending POST to ${serviceUrl}`);
request({
url: serviceUrl,
method: 'POST',
json: true,
body: req.body
}, (err, response, body) => {
if (err) {
logger.error(err, `Error sending createCall POST to ${serviceUrl}`);
return res.send(500);
}
if (response.statusCode !== 201) {
logger.error({statusCode: response.statusCode}, `Non-success response returned by createCall ${serviceUrl}`);
return res.sendStatus(500);
}
res.status(201).json(body);
});
} catch (err) {
sysError(logger, res, err);
}
});
module.exports = router;
+96 -65
View File
@@ -1,7 +1,7 @@
openapi: 3.0.0
info:
title: Jambones REST API
description: Jambones REST API
title: jambonz REST API
description: jambonz REST API
contact:
email: daveh@drachtio.org
license:
@@ -1051,7 +1051,7 @@ paths:
$ref: '#/components/schemas/GeneralError'
/Accounts/{AccountSid}/Applications/{ApplicationSid}/Calls:
/Accounts/{AccountSid}/Calls:
post:
summary: create a call
operationId: createCall
@@ -1061,11 +1061,6 @@ paths:
required: true
schema:
type: string
- name: ApplicationSid
in: path
required: true
schema:
type: string
requestBody:
content:
application/json:
@@ -1075,39 +1070,28 @@ paths:
- to
type: object
properties:
url:
application_sid:
type: string
description: |
The url of the web application to control this call.
If not provided, the url specified in the application will be used
example: https://mycompany.com/deliver-message.json
format: uuid
description: The application to use to control this call. Either applicationSid or url is required.
call_hook:
$ref: '#/components/Webhook'
call_status_hook:
$ref: '#/components/Webhook'
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.
tag:
type: object
description: initial set of customer-supplied metadata to associate with the call (see jambonz 'tag' verb)
to:
type: string
description: The telephone number or sip endpoint to call
example: 16172228000
recordingUrl:
type: string
format: url
description: A websocket url to stream the call audio to
example: wss://myserver.com
recordingMix:
type: string
description: whether to record either or both parties
enum:
- caller
- callee
- stereo
- mixed
example: stereo
statusCallback:
type: string
format: url
description: The url to send call status change events to
example: https://company.com/status
$ref: '#/components/Target'
description: destination for call
responses:
201:
description: call successfully created
@@ -1317,35 +1301,6 @@ components:
- username
- domain
- sip_contact
Call:
type: object
properties:
call_sid:
type: string
format: uuid
application:
$ref: '#/components/schemas/Application'
parent_call:
$ref: '#/components/schemas/Call'
direction:
type: string
enum:
- inbound
- outbound
phone_number:
$ref: '#/components/schemas/PhoneNumber'
inbound_user:
$ref: '#/components/schemas/RegisteredUser'
outbound_user:
$ref: '#/components/schemas/RegisteredUser'
calling_number:
type: string
called_number:
type: string
required:
- call_sid
- application
- direction
Webhook:
type: object
properties:
@@ -1363,6 +1318,82 @@ components:
type: string
required:
- url
Call:
type: object
properties:
account_sid:
type: string
format: uuid
application_sid:
type: string
format: uuid
api_version:
type: string
caller_name:
type: string
call_sid:
type: string
format: uuid
date_created:
type: string
date_updated:
type: string
direction:
type: string
enum:
- inbound
- outbound
duration:
type: integer
end_time:
type: string
forwarded_from:
type: string
from:
type: string
parent_call_sid:
type: string
format: uuid
phone_number_sid:
type: string
format: uuid
start_time:
type: string
to:
type: string
uri:
type: string
required:
- api_version
- account-sid
- call_sid
- date_created
- from
- to
- uri
Target:
properties:
type:
type: string
enum:
- phone
- sip
- user
number:
type: string
sipUri:
type: string
name:
type: string
auth:
type: object
properties:
username:
type: string
password:
type: string
required:
- type
security:
- bearerAuth: []
+1
View File
@@ -17,6 +17,7 @@
"config": "^3.2.4",
"cors": "^2.8.5",
"express": "^4.17.1",
"jambonz-db-helpers": "^0.2.0",
"mysql2": "^2.0.2",
"passport": "^0.4.0",
"passport-http-bearer": "^1.0.1",