Compare commits

...

6 Commits

Author SHA1 Message Date
Quan HL
9d9cad7ccc add testcase 2023-11-24 18:09:48 +07:00
Quan HL
f33343ef7b get full registration details for all users with new querry params details = true 2023-11-24 17:59:36 +07:00
Anton Voylenko
1d69457ddc support for llc tag (#262) 2023-11-17 08:48:34 -05:00
Anton Voylenko
dcfe6cc05d swagger: update create call spec (#260) 2023-11-14 10:40:31 -05:00
Hoan Luu Huu
a474c2d4cc let realtimedb-help build redis configuraiton from env variables (#256)
* let realtimedb-help build redis configuraiton from env variables

* update speech-utils version
2023-11-14 08:57:16 -05:00
Hoan Luu Huu
0f244cf6d5 fix create update lcr generate too much request (#259)
* fix create update lcr generate too much request

* wip
2023-11-14 08:07:04 -05:00
8 changed files with 205 additions and 52 deletions

8
app.js
View File

@@ -46,18 +46,14 @@ const {
addKey,
retrieveKey,
deleteKey,
incrKey,
JAMBONES_REDIS_SENTINELS
incrKey
} = require('./lib/helpers/realtimedb-helpers');
const {
getTtsVoices,
getTtsSize,
purgeTtsCache,
synthAudio
} = require('@jambonz/speech-utils')(JAMBONES_REDIS_SENTINELS || {
host: process.env.JAMBONES_REDIS_HOST,
port: process.env.JAMBONES_REDIS_PORT || 6379
}, logger);
} = require('@jambonz/speech-utils')({redis_client: client}, logger);
const {
lookupAppBySid,
lookupAccountBySid,

View File

@@ -1,29 +1,5 @@
const logger = require('../logger');
const JAMBONES_REDIS_SENTINELS = process.env.JAMBONES_REDIS_SENTINELS ? {
sentinels: process.env.JAMBONES_REDIS_SENTINELS.split(',').map((sentinel) => {
let host, port = 26379;
if (sentinel.includes(':')) {
const arr = sentinel.split(':');
host = arr[0];
port = parseInt(arr[1], 10);
} else {
host = sentinel;
}
return {host, port};
}),
name: process.env.JAMBONES_REDIS_SENTINEL_MASTER_NAME,
...(process.env.JAMBONES_REDIS_SENTINEL_PASSWORD && {
password: process.env.JAMBONES_REDIS_SENTINEL_PASSWORD
}),
...(process.env.JAMBONES_REDIS_SENTINEL_USERNAME && {
username: process.env.JAMBONES_REDIS_SENTINEL_USERNAME
}),
...(process.env.JAMBONES_REDIS_SENTINEL_SENTINAL_PASSWORD && {
sentinelPassword: process.env.JAMBONES_REDIS_SENTINEL_SENTINAL_PASSWORD
}),
} : null;
const {
client,
retrieveCall,
@@ -37,10 +13,7 @@ const {
deleteKey,
incrKey,
client: redisClient,
} = require('@jambonz/realtimedb-helpers')(JAMBONES_REDIS_SENTINELS || {
host: process.env.JAMBONES_REDIS_HOST || 'localhost',
port: process.env.JAMBONES_REDIS_PORT || 6379
}, logger);
} = require('@jambonz/realtimedb-helpers')({}, logger);
module.exports = {
client,
@@ -54,6 +27,5 @@ module.exports = {
retrieveKey,
deleteKey,
redisClient,
incrKey,
JAMBONES_REDIS_SENTINELS
incrKey
};

View File

@@ -148,6 +148,7 @@ router.post('/:sid/VoipCarriers', async(req, res) => {
router.get('/:sid/RegisteredSipUsers', async(req, res) => {
const {logger, registrar} = req.app.locals;
const details = req.query.details;
try {
const account_sid = parseAccountSid(req);
await validateRequest(req, account_sid);
@@ -155,8 +156,14 @@ router.get('/:sid/RegisteredSipUsers', async(req, res) => {
if (!result || result.length === 0) {
throw new DbErrorBadRequest(`account not found for sid ${account_sid}`);
}
const users = await registrar.getRegisteredUsersForRealm(result[0].sip_realm);
res.status(200).json(users.map((u) => `${u}@${result[0].sip_realm}`));
let ret = [];
if (details) {
ret = await registrar.getRegisteredUsersDetailsForRealm(result[0].sip_realm);
} else {
const users = await registrar.getRegisteredUsersForRealm(result[0].sip_realm);
ret = users.map((u) => `${u}@${result[0].sip_realm}`);
}
res.status(200).json(ret);
} catch (err) {
sysError(logger, res, err);
}
@@ -224,7 +231,8 @@ function validateUpdateCall(opts) {
'conf_mute_status',
'mute_status',
'sip_request',
'record'
'record',
'tag'
]
.reduce((acc, prop) => (opts[prop] ? ++acc : acc), 0);
@@ -269,6 +277,9 @@ function validateUpdateCall(opts) {
if ('startCallRecording' === opts.record?.action && !opts.record.siprecServerURL) {
throw new DbErrorBadRequest('record requires siprecServerURL property when starting recording');
}
if (opts.tag && (typeof opts.tag !== 'object' || Array.isArray(opts.tag) || opts.tag === null)) {
throw new DbErrorBadRequest('invalid tag data');
}
}
function validateTo(to) {

View File

@@ -100,6 +100,93 @@ const preconditions = {
decorate(router, Lcr, ['add', 'update', 'delete'], preconditions);
const validateLcrBatchAdd = async(lcr_sid, body, lookupCarrierBySid) => {
for (const lcr_route of body) {
lcr_route.lcr_sid = lcr_sid;
if (!lcr_route.lcr_carrier_set_entries || lcr_route.lcr_carrier_set_entries.length === 0) {
throw new DbErrorBadRequest('Lcr Route batch process require lcr_carrier_set_entries');
}
for (const entry of lcr_route.lcr_carrier_set_entries) {
// check voip_carrier_sid is exist
if (!entry.voip_carrier_sid) {
throw new DbErrorBadRequest('One of lcr_carrier_set_entries is missing voip_carrier_sid');
}
const carrier = await lookupCarrierBySid(entry.voip_carrier_sid);
if (!carrier) {
throw new DbErrorBadRequest('unknown voip_carrier_sid');
}
}
}
};
const addNewLcrRoute = async(lcr_route) => {
const lcr_sid = lcr_route.lcr_sid;
const lcr_carrier_set_entries = lcr_route.lcr_carrier_set_entries;
delete lcr_route.lcr_carrier_set_entries;
const lcr_route_sid = await LcrRoutes.make(lcr_route);
for (const entry of lcr_carrier_set_entries) {
entry.lcr_route_sid = lcr_route_sid;
const lcr_carrier_set_entry_sid = await LcrCarrierSetEntry.make(entry);
if (lcr_route.priority === 9999) {
// this is default lcr set entry
const [lcr] = await Lcr.retrieve(lcr_sid);
if (lcr) {
lcr.default_carrier_set_entry_sid = lcr_carrier_set_entry_sid;
delete lcr.lcr_sid;
await Lcr.update(lcr_sid, lcr);
}
}
}
};
router.post('/:sid/Routes', async(req, res) => {
const results = await Lcr.retrieve(req.params.sid);
if (results.length === 0) return res.sendStatus(404);
const {logger, lookupCarrierBySid} = req.app.locals;
try {
const body = req.body;
await validateLcrBatchAdd(req.params.sid, body, lookupCarrierBySid);
for (const lcr_route of body) {
await addNewLcrRoute(lcr_route, lookupCarrierBySid);
}
res.sendStatus(204);
} catch (err) {
sysError(logger, res, err);
}
});
router.put('/:sid/Routes', async(req, res) => {
const results = await Lcr.retrieve(req.params.sid);
if (results.length === 0) return res.sendStatus(404);
const {logger, lookupCarrierBySid} = req.app.locals;
try {
const body = req.body;
await validateLcrBatchAdd(req.params.sid, body, lookupCarrierBySid);
for (const lcr_route of body) {
if (lcr_route.lcr_route_sid) {
const lcr_route_sid = lcr_route.lcr_route_sid;
delete lcr_route.lcr_route_sid;
const lcr_carrier_set_entries = lcr_route.lcr_carrier_set_entries;
delete lcr_route.lcr_carrier_set_entries;
await LcrRoutes.update(lcr_route_sid, lcr_route);
for (const entry of lcr_carrier_set_entries) {
const lcr_carrier_set_entry_sid = entry.lcr_carrier_set_entry_sid;
delete entry.lcr_carrier_set_entry_sid;
await LcrCarrierSetEntry.update(lcr_carrier_set_entry_sid, entry);
}
} else {
// Route is not available yet, let create it now
await addNewLcrRoute(lcr_route, lookupCarrierBySid);
}
}
res.sendStatus(204);
} catch (err) {
sysError(logger, res, err);
}
});
router.get('/', async(req, res) => {
const logger = req.app.locals.logger;
try {

View File

@@ -1,7 +1,7 @@
openapi: 3.0.0
info:
title: jambonz REST API
description: jambonz REST API
title: Jambonz REST API
description: Jambonz REST API specification
contact:
email: daveh@drachtio.org
license:
@@ -3799,7 +3799,7 @@ paths:
/Accounts/{AccountSid}/Calls:
post:
tags:
- Accounts
- Accounts
summary: create a call
operationId: createCall
parameters:
@@ -3858,6 +3858,10 @@ paths:
type: object
description: The customer SIP headers to associate with the call
example: {"X-Custom-Header": "Hello"}
sipRequestWithinDialogHook:
type: string
description: The sip indialog hook to receive session messages
example: '/customHook'
responses:
201:
description: call successfully created
@@ -4415,6 +4419,69 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/GeneralError'
/Lcrs/{LcrSid}/Routes:
parameters:
- name: LcrSid
in: path
required: true
style: simple
explode: false
schema:
type: string
post:
tags:
- Lcrs
summary: Create least cost routing routes and carrier set entries
operationId: createLeastCostRoutingRoutesAndCarrierEntries
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/LcrRoutes'
responses:
204:
description: least cost routing routes and carrier set entries created
400:
description: bad request
content:
application/json:
schema:
$ref: '#/components/schemas/GeneralError'
404:
description: least cost routing not found
500:
description: system error
content:
application/json:
schema:
$ref: '#/components/schemas/GeneralError'
put:
tags:
- Lcrs
summary: update least cost routing routes and carrier set entries
operationId: updateLeastCostRoutingRoutesAndCarrierEntries
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/LcrRoutes'
responses:
204:
description: least cost routing ruoutes and carrier entries updated
400:
description: bad request
content:
application/json:
schema:
$ref: '#/components/schemas/GeneralError'
404:
description: least cost routing not found
500:
description: system error
content:
application/json:
schema:
$ref: '#/components/schemas/GeneralError'
/LcrRoutes:
post:
tags:
@@ -5832,6 +5899,19 @@ components:
- lcr_route_sid
- voip_carrier_sid
- priority
LcrRouteAndCarrierEntries:
allOf:
- $ref: '#/components/schemas/LcrRoute'
- type: object
properties:
lcr_carrier_set_entries:
type: array
items:
$ref: '#/components/schemas/LcrCarrierSetEntry'
LcrRoutes:
type: array
items:
$ref: '#/components/schemas/LcrRouteAndCarrierEntries'
GoogleCustomVoice:
type: object
properties:

18
package-lock.json generated
View File

@@ -19,7 +19,7 @@
"@jambonz/lamejs": "^1.2.2",
"@jambonz/mw-registrar": "^0.2.5",
"@jambonz/realtimedb-helpers": "^0.8.7",
"@jambonz/speech-utils": "^0.0.25",
"@jambonz/speech-utils": "^0.0.26",
"@jambonz/time-series": "^0.2.8",
"@jambonz/verb-specifications": "^0.0.45",
"@soniox/soniox-node": "^1.1.1",
@@ -1968,19 +1968,19 @@
}
},
"node_modules/@jambonz/speech-utils": {
"version": "0.0.25",
"resolved": "https://registry.npmjs.org/@jambonz/speech-utils/-/speech-utils-0.0.25.tgz",
"integrity": "sha512-ry0FbGjgXxhYOQXSnkXQN5/Num057w3zdYpX8modZubK5TsH0AhHNC+6ph0IQrvk7LcnNJfCDqtNbKQZYQ/OHA==",
"version": "0.0.26",
"resolved": "https://registry.npmjs.org/@jambonz/speech-utils/-/speech-utils-0.0.26.tgz",
"integrity": "sha512-g5AdiPZGY1HnWJ8lbKwBbV5NcyPn8A1s6/IqQQ8sNCQzRyd/aveBI9FuOH1bIUNhwUDdMSXkyuxntUOt+/0TIw==",
"dependencies": {
"@aws-sdk/client-polly": "^3.359.0",
"@google-cloud/text-to-speech": "^4.2.1",
"@grpc/grpc-js": "^1.8.13",
"@jambonz/realtimedb-helpers": "^0.8.7",
"bent": "^7.3.12",
"debug": "^4.3.4",
"form-urlencoded": "^6.1.0",
"google-protobuf": "^3.21.2",
"ibm-watson": "^8.0.0",
"ioredis": "^5.3.2",
"microsoft-cognitiveservices-speech-sdk": "^1.31.0",
"openai": "^4.16.2",
"undici": "^5.21.0"
@@ -11430,19 +11430,19 @@
}
},
"@jambonz/speech-utils": {
"version": "0.0.25",
"resolved": "https://registry.npmjs.org/@jambonz/speech-utils/-/speech-utils-0.0.25.tgz",
"integrity": "sha512-ry0FbGjgXxhYOQXSnkXQN5/Num057w3zdYpX8modZubK5TsH0AhHNC+6ph0IQrvk7LcnNJfCDqtNbKQZYQ/OHA==",
"version": "0.0.26",
"resolved": "https://registry.npmjs.org/@jambonz/speech-utils/-/speech-utils-0.0.26.tgz",
"integrity": "sha512-g5AdiPZGY1HnWJ8lbKwBbV5NcyPn8A1s6/IqQQ8sNCQzRyd/aveBI9FuOH1bIUNhwUDdMSXkyuxntUOt+/0TIw==",
"requires": {
"@aws-sdk/client-polly": "^3.359.0",
"@google-cloud/text-to-speech": "^4.2.1",
"@grpc/grpc-js": "^1.8.13",
"@jambonz/realtimedb-helpers": "^0.8.7",
"bent": "^7.3.12",
"debug": "^4.3.4",
"form-urlencoded": "^6.1.0",
"google-protobuf": "^3.21.2",
"ibm-watson": "^8.0.0",
"ioredis": "^5.3.2",
"microsoft-cognitiveservices-speech-sdk": "^1.31.0",
"openai": "^4.16.2",
"undici": "^5.21.0"

View File

@@ -29,7 +29,7 @@
"@jambonz/lamejs": "^1.2.2",
"@jambonz/mw-registrar": "^0.2.5",
"@jambonz/realtimedb-helpers": "^0.8.7",
"@jambonz/speech-utils": "^0.0.25",
"@jambonz/speech-utils": "^0.0.26",
"@jambonz/time-series": "^0.2.8",
"@jambonz/verb-specifications": "^0.0.45",
"@soniox/soniox-node": "^1.1.1",

View File

@@ -85,6 +85,13 @@ test('client test', async(t) => {
t.ok(result.length === 1 && result[0] === 'dhorton@drachtio.org',
'successfully queried all registered clients');
result = await request.get(`/Accounts/${account_sid}/RegisteredSipUsers?details=true`, {
auth: authAdmin,
json: true,
});
t.ok(result.length === 1 && result[0].name === 'dhorton',
'successfully queried all registered clients');
/* query all entity */
result = await request.get('/Clients', {
auth: authAdmin,