mirror of
https://github.com/jambonz/jambonz-api-server.git
synced 2026-07-04 19:21:53 +00:00
initial support for live call control
This commit is contained in:
@@ -9,11 +9,18 @@ const cors = require('cors');
|
||||
const passport = require('passport');
|
||||
const authStrategy = require('./lib/auth')(logger);
|
||||
const routes = require('./lib/routes');
|
||||
const {retrieveCall, deleteCall, listCalls} = require('jambonz-realtimedb-helpers')(config.get('redis'), logger);
|
||||
const PORT = process.env.HTTP_PORT || 3000;
|
||||
|
||||
passport.use(authStrategy);
|
||||
|
||||
app.locals.logger = logger;
|
||||
app.locals = app.locals || {};
|
||||
Object.assign(app.locals, {
|
||||
logger,
|
||||
retrieveCall,
|
||||
deleteCall,
|
||||
listCalls
|
||||
});
|
||||
|
||||
app.use(cors());
|
||||
app.use(express.urlencoded({ extended: true }));
|
||||
|
||||
@@ -9,7 +9,12 @@
|
||||
"database": "jambones",
|
||||
"connectionLimit": 10
|
||||
},
|
||||
"redis": {
|
||||
"host": "127.0.0.1",
|
||||
"port": 6379
|
||||
},
|
||||
"services": {
|
||||
"createCall": "http://feature.server/createCall:3000"
|
||||
"apiVersion" : "v1",
|
||||
"createCall": "http://feature.server/v1/createCall:3000"
|
||||
}
|
||||
}
|
||||
@@ -7,5 +7,9 @@
|
||||
"user": "jambones_test",
|
||||
"database": "jambones_test",
|
||||
"password": "jambones_test"
|
||||
},
|
||||
"redis": {
|
||||
"host": "127.0.0.1",
|
||||
"port": 6379
|
||||
}
|
||||
}
|
||||
+104
-2
@@ -6,12 +6,14 @@ const Account = require('../../models/account');
|
||||
const Webhook = require('../../models/webhook');
|
||||
const ServiceProvider = require('../../models/service-provider');
|
||||
const decorate = require('./decorate');
|
||||
const snakeCase = require('../../utils/snake-case');
|
||||
const sysError = require('./error');
|
||||
const preconditions = {
|
||||
'add': validateAdd,
|
||||
'update': validateUpdate,
|
||||
'delete': validateDelete
|
||||
};
|
||||
const API_VERSION = config.get('services.apiVersion');
|
||||
|
||||
function validateTo(to) {
|
||||
if (to && typeof to === 'object') {
|
||||
@@ -213,6 +215,9 @@ router.put('/:sid', async(req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* create a new Call
|
||||
*/
|
||||
router.post('/:sid/Calls', async(req, res) => {
|
||||
const sid = req.params.sid;
|
||||
const logger = req.app.locals.logger;
|
||||
@@ -230,7 +235,7 @@ router.post('/:sid/Calls', async(req, res) => {
|
||||
}, (err, response, body) => {
|
||||
if (err) {
|
||||
logger.error(err, `Error sending createCall POST to ${serviceUrl}`);
|
||||
return res.send(500);
|
||||
return res.sendStatus(500);
|
||||
}
|
||||
if (response.statusCode !== 201) {
|
||||
logger.error({statusCode: response.statusCode}, `Non-success response returned by createCall ${serviceUrl}`);
|
||||
@@ -241,7 +246,104 @@ router.post('/:sid/Calls', async(req, res) => {
|
||||
} catch (err) {
|
||||
sysError(logger, res, err);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
/**
|
||||
* retrieve info for a group of calls under an account
|
||||
*/
|
||||
router.get('/:sid/Calls', async(req, res) => {
|
||||
const accountSid = req.params.sid;
|
||||
const {logger, listCalls} = req.app.locals;
|
||||
|
||||
try {
|
||||
const calls = await listCalls(accountSid);
|
||||
logger.debug(`retrieved ${calls.length} calls for account sid ${accountSid}`);
|
||||
res.status(200).json(snakeCase(calls));
|
||||
} catch (err) {
|
||||
sysError(logger, res, err);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* retrieve single call
|
||||
*/
|
||||
router.get('/:sid/Calls/:callSid', async(req, res) => {
|
||||
const accountSid = req.params.sid;
|
||||
const callSid = req.params.callSid;
|
||||
const {logger, retrieveCall} = req.app.locals;
|
||||
|
||||
try {
|
||||
const callInfo = await retrieveCall(accountSid, callSid);
|
||||
if (callInfo) {
|
||||
logger.debug(callInfo, `retrieved call info for call sid ${callSid}`);
|
||||
res.status(200).json(snakeCase(callInfo));
|
||||
}
|
||||
else {
|
||||
logger.debug(`call not found for call sid ${callSid}`);
|
||||
res.sendStatus(404);
|
||||
}
|
||||
} catch (err) {
|
||||
sysError(logger, res, err);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* delete call
|
||||
*/
|
||||
router.delete('/:sid/Calls/:callSid', async(req, res) => {
|
||||
const accountSid = req.params.sid;
|
||||
const callSid = req.params.callSid;
|
||||
const {logger, deleteCall} = req.app.locals;
|
||||
|
||||
try {
|
||||
const result = await deleteCall(accountSid, callSid);
|
||||
if (result) {
|
||||
logger.debug(`successfully deleted call ${callSid}`);
|
||||
res.sendStatus(204);
|
||||
}
|
||||
else {
|
||||
logger.debug(`call not found for call sid ${callSid}`);
|
||||
res.sendStatus(404);
|
||||
}
|
||||
} catch (err) {
|
||||
sysError(logger, res, err);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* update a call
|
||||
*/
|
||||
router.post('/:sid/Calls/:callSid', async(req, res) => {
|
||||
const accountSid = req.params.sid;
|
||||
const callSid = req.params.callSid;
|
||||
const {logger, retrieveCall} = req.app.locals;
|
||||
|
||||
try {
|
||||
const call = await retrieveCall(accountSid, callSid);
|
||||
if (call) {
|
||||
const url = `${call.serviceUrl}/${API_VERSION}/updateCall/${callSid}`;
|
||||
logger.debug({call, url, payload: req.body}, `updateCall: retrieved call info for call sid ${callSid}`);
|
||||
request({
|
||||
url: url,
|
||||
method: 'POST',
|
||||
json: true,
|
||||
body: req.body
|
||||
}, (err, response, body) => {
|
||||
if (err) {
|
||||
logger.error(err, `updateCall: Error sending update call POST to ${url}`);
|
||||
return res.sendStatus(500);
|
||||
}
|
||||
res.sendStatus(response.statusCode);
|
||||
});
|
||||
}
|
||||
else {
|
||||
logger.debug(`updateCall: call not found for call sid ${callSid}`);
|
||||
res.sendStatus(404);
|
||||
}
|
||||
} catch (err) {
|
||||
sysError(logger, res, err);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
module.exports = router;
|
||||
|
||||
+134
-20
@@ -1094,7 +1094,6 @@ paths:
|
||||
to:
|
||||
$ref: '#/components/schemas/Target'
|
||||
description: destination for call
|
||||
|
||||
responses:
|
||||
201:
|
||||
description: call successfully created
|
||||
@@ -1110,7 +1109,117 @@ paths:
|
||||
example: 2531329f-fb09-4ef7-887e-84e648214436
|
||||
400:
|
||||
description: bad request
|
||||
get:
|
||||
summary: list calls
|
||||
operationId: listCalls
|
||||
parameters:
|
||||
- name: AccountSid
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
200:
|
||||
description: list of calls for a specified account
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Call'
|
||||
500:
|
||||
description: system error
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/GeneralError'
|
||||
|
||||
|
||||
/Accounts/{AccountSid}/Calls/{CallSid}:
|
||||
parameters:
|
||||
- name: AccountSid
|
||||
in: path
|
||||
required: true
|
||||
style: simple
|
||||
explode: false
|
||||
schema:
|
||||
type: string
|
||||
- name: CallSid
|
||||
in: path
|
||||
required: true
|
||||
style: simple
|
||||
explode: false
|
||||
schema:
|
||||
type: string
|
||||
delete:
|
||||
summary: delete a call
|
||||
operationId: deleteCall
|
||||
responses:
|
||||
204:
|
||||
description: call successfully deleted
|
||||
404:
|
||||
description: call 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'
|
||||
get:
|
||||
summary: retrieve a call
|
||||
responses:
|
||||
200:
|
||||
description: call found
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Call'
|
||||
404:
|
||||
description: call not found
|
||||
500:
|
||||
description: system error
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/GeneralError'
|
||||
post:
|
||||
summary: update a call
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
call_hook:
|
||||
$ref: '#/components/schemas/Webhook'
|
||||
call_status_hook:
|
||||
$ref: '#/components/schemas/Webhook'
|
||||
call_status:
|
||||
type: string
|
||||
enum:
|
||||
- completed
|
||||
- no-answer
|
||||
responses:
|
||||
200:
|
||||
description: call updated
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Call'
|
||||
404:
|
||||
description: call not found
|
||||
500:
|
||||
description: system error
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/GeneralError'
|
||||
components:
|
||||
securitySchemes:
|
||||
bearerAuth:
|
||||
@@ -1331,16 +1440,24 @@ components:
|
||||
application_sid:
|
||||
type: string
|
||||
format: uuid
|
||||
api_version:
|
||||
type: string
|
||||
caller_name:
|
||||
call_id:
|
||||
type: string
|
||||
call_sid:
|
||||
type: string
|
||||
format: uuid
|
||||
date_created:
|
||||
call_status:
|
||||
type: string
|
||||
date_updated:
|
||||
enum:
|
||||
- trying
|
||||
- ringing
|
||||
- alerting
|
||||
- in-progress
|
||||
- completed
|
||||
- busy
|
||||
- no-answer
|
||||
- failed
|
||||
- queued
|
||||
caller_name:
|
||||
type: string
|
||||
direction:
|
||||
type: string
|
||||
@@ -1349,32 +1466,29 @@ components:
|
||||
- outbound
|
||||
duration:
|
||||
type: integer
|
||||
end_time:
|
||||
type: string
|
||||
forwarded_from:
|
||||
type: string
|
||||
from:
|
||||
type: string
|
||||
originating_sip_trunk_name:
|
||||
type: string
|
||||
parent_call_sid:
|
||||
type: string
|
||||
format: uuid
|
||||
phone_number_sid:
|
||||
type: string
|
||||
format: uuid
|
||||
start_time:
|
||||
service_url:
|
||||
type: string
|
||||
sip_status:
|
||||
type: integer
|
||||
to:
|
||||
type: string
|
||||
uri:
|
||||
type: string
|
||||
required:
|
||||
- api_version
|
||||
- account-sid
|
||||
- account_sid
|
||||
- call_id
|
||||
- call_sid
|
||||
- date_created
|
||||
- call_status
|
||||
- direction
|
||||
- from
|
||||
- service_url
|
||||
- sip_status
|
||||
- to
|
||||
- uri
|
||||
Target:
|
||||
properties:
|
||||
type:
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
|
||||
function snake(input) {
|
||||
return input.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
|
||||
}
|
||||
|
||||
function snakeCase(obj) {
|
||||
if (Array.isArray(obj)) {
|
||||
obj.forEach((r) => snakeCase(r));
|
||||
}
|
||||
else if (typeof obj === 'object' && obj !== null) {
|
||||
Object.keys(obj).forEach((key) => {
|
||||
obj[snake(key)] = obj[key];
|
||||
delete obj[key];
|
||||
});
|
||||
}
|
||||
else if (typeof obj === 'string') {
|
||||
obj = snake(obj);
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
module.exports = snakeCase;
|
||||
+2
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "jambonz-api-server",
|
||||
"version": "1.1.0",
|
||||
"version": "1.1.1",
|
||||
"description": "",
|
||||
"main": "app.js",
|
||||
"scripts": {
|
||||
@@ -19,6 +19,7 @@
|
||||
"cors": "^2.8.5",
|
||||
"express": "^4.17.1",
|
||||
"jambonz-db-helpers": "^0.2.0",
|
||||
"jambonz-realtimedb-helpers": "0.1.3",
|
||||
"mysql2": "^2.0.2",
|
||||
"passport": "^0.4.0",
|
||||
"passport-http-bearer": "^1.0.1",
|
||||
|
||||
Reference in New Issue
Block a user