mirror of
https://github.com/jambonz/jambonz-api-server.git
synced 2026-01-25 02:08:24 +00:00
Compare commits
12 Commits
v0.9.4-rc1
...
v0.9.4-2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fcff3d4b32 | ||
|
|
2dd06df641 | ||
|
|
579a586a03 | ||
|
|
3e1b383284 | ||
|
|
c51b7bab82 | ||
|
|
bb5dba7c20 | ||
|
|
c7e279d0ee | ||
|
|
6700ff35be | ||
|
|
3f2a304830 | ||
|
|
f23c4fbd48 | ||
|
|
0c2f5becdc | ||
|
|
cd6772c10f |
16
app.js
16
app.js
@@ -128,11 +128,27 @@ const unless = (paths, middleware) => {
|
||||
};
|
||||
};
|
||||
|
||||
const RATE_LIMIT_BY = process.env.RATE_LIMIT_BY || 'system';
|
||||
|
||||
const limiter = rateLimit({
|
||||
windowMs: (process.env.RATE_LIMIT_WINDOWS_MINS || 5) * 60 * 1000, // 5 minutes
|
||||
max: process.env.RATE_LIMIT_MAX_PER_WINDOW || 600, // Limit each IP to 600 requests per `window`
|
||||
standardHeaders: true, // Return rate limit info in the `RateLimit-*` headers
|
||||
legacyHeaders: false, // Disable the `X-RateLimit-*` headers
|
||||
keyGenerator: (req, res) => {
|
||||
switch (RATE_LIMIT_BY) {
|
||||
case 'system':
|
||||
return '127.0.0.1';
|
||||
case 'apikey':
|
||||
// uses shared limit for requests without an authorization header
|
||||
const token = req.headers.authorization?.split(' ')[1] || '127.0.0.1';
|
||||
return token;
|
||||
case 'ip':
|
||||
return req.headers['x-real-ip'];
|
||||
default:
|
||||
return '127.0.0.1';
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Setup websocket for recording audio
|
||||
|
||||
@@ -36,25 +36,98 @@ class Application extends Model {
|
||||
super();
|
||||
}
|
||||
|
||||
static _criteriaBuilder(obj, args) {
|
||||
let sql = '';
|
||||
if (obj.account_sid) {
|
||||
sql += ' AND app.account_sid = ?';
|
||||
args.push(obj.account_sid);
|
||||
}
|
||||
if (obj.service_provider_sid) {
|
||||
sql += ' AND app.account_sid in (SELECT account_sid from accounts WHERE service_provider_sid = ?)';
|
||||
args.push(obj.service_provider_sid);
|
||||
}
|
||||
if (obj.name) {
|
||||
sql += ' AND app.name LIKE ?';
|
||||
args.push(`%${obj.name}%`);
|
||||
}
|
||||
return sql;
|
||||
}
|
||||
|
||||
static countAll(obj) {
|
||||
let sql = 'SELECT COUNT(*) AS count FROM applications app WHERE 1 = 1';
|
||||
const args = [];
|
||||
sql += Application._criteriaBuilder(obj, args);
|
||||
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[0].count);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* list all applications - for all service providers, for one service provider, or for one account,
|
||||
* or by an optional name
|
||||
*/
|
||||
static retrieveAll(service_provider_sid, account_sid, name) {
|
||||
static retrieveAll(obj) {
|
||||
const { page, page_size = 50 } = obj || {};
|
||||
|
||||
// If pagination is requested, first get the application IDs
|
||||
if (page !== null && page !== undefined) {
|
||||
let idSql = 'SELECT application_sid, name FROM applications app WHERE 1 = 1';
|
||||
const idArgs = [];
|
||||
idSql += Application._criteriaBuilder(obj, idArgs);
|
||||
idSql += ' ORDER BY app.name';
|
||||
|
||||
const limit = Number(page_size);
|
||||
const offset = Number(page > 0 ? (page - 1) : page) * limit;
|
||||
idSql += ' LIMIT ? OFFSET ?';
|
||||
idArgs.push(limit);
|
||||
idArgs.push(offset);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
getMysqlConnection((err, conn) => {
|
||||
if (err) return reject(err);
|
||||
|
||||
// Get paginated application IDs
|
||||
conn.query(idSql, idArgs, (err, idResults) => {
|
||||
if (err) {
|
||||
conn.release();
|
||||
return reject(err);
|
||||
}
|
||||
|
||||
if (idResults.length === 0) {
|
||||
conn.release();
|
||||
return resolve([]);
|
||||
}
|
||||
|
||||
// Get full data for these applications
|
||||
const appIds = idResults.map((row) => row.application_sid);
|
||||
const placeholders = appIds.map(() => '?').join(',');
|
||||
const fullSql = `${retrieveSql}
|
||||
WHERE app.application_sid IN (${placeholders}) ORDER BY app.name`;
|
||||
|
||||
conn.query({sql: fullSql, nestTables: true}, appIds, (err, results) => {
|
||||
conn.release();
|
||||
if (err) return reject(err);
|
||||
const r = transmogrifyResults(results);
|
||||
resolve(r);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// No pagination - use original query
|
||||
let sql = retrieveSql + ' WHERE 1 = 1';
|
||||
const args = [];
|
||||
if (account_sid) {
|
||||
sql = `${sql} AND app.account_sid = ?`;
|
||||
args.push(account_sid);
|
||||
}
|
||||
else if (service_provider_sid) {
|
||||
sql = `${sql} AND account_sid in (SELECT account_sid from accounts WHERE service_provider_sid = ?)`;
|
||||
args.push(service_provider_sid);
|
||||
}
|
||||
if (name) {
|
||||
sql = `${sql} AND app.name = ?`;
|
||||
args.push(name);
|
||||
}
|
||||
sql += Application._criteriaBuilder(obj, args);
|
||||
sql += ' ORDER BY app.application_sid';
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
getMysqlConnection((err, conn) => {
|
||||
if (err) return reject(err);
|
||||
|
||||
@@ -26,24 +26,45 @@ class PhoneNumber extends Model {
|
||||
return rows;
|
||||
}
|
||||
|
||||
static async retrieveAllByCriteria({
|
||||
service_provider_sid, account_sid, filter
|
||||
}) {
|
||||
static _criteriaBuilder(obj, params) {
|
||||
let sql = '';
|
||||
if (obj.service_provider_sid) {
|
||||
sql += ' AND account_sid IN (SELECT account_sid FROM accounts WHERE service_provider_sid = ?)';
|
||||
params.push(obj.service_provider_sid);
|
||||
}
|
||||
if (obj.account_sid) {
|
||||
sql += ' AND account_sid = ?';
|
||||
params.push(obj.account_sid);
|
||||
}
|
||||
if (obj.filter) {
|
||||
sql += ' AND number LIKE ?';
|
||||
params.push(`%${obj.filter}%`);
|
||||
}
|
||||
|
||||
return sql;
|
||||
}
|
||||
|
||||
static async countAll(obj) {
|
||||
let sql = 'SELECT COUNT(*) AS count FROM phone_numbers WHERE 1 = 1';
|
||||
const args = [];
|
||||
sql += PhoneNumber._criteriaBuilder(obj, args);
|
||||
const [rows] = await promisePool.query(sql, args);
|
||||
return rows[0].count;
|
||||
}
|
||||
|
||||
static async retrieveAllByCriteria(obj) {
|
||||
let sql = 'SELECT * FROM phone_numbers WHERE 1=1';
|
||||
const params = [];
|
||||
if (service_provider_sid) {
|
||||
sql += ' AND account_sid IN (SELECT account_sid FROM accounts WHERE service_provider_sid = ?)';
|
||||
params.push(service_provider_sid);
|
||||
}
|
||||
if (account_sid) {
|
||||
sql += ' AND account_sid = ?';
|
||||
params.push(account_sid);
|
||||
}
|
||||
if (filter) {
|
||||
sql += ' AND number LIKE ?';
|
||||
params.push(`%${filter}%`);
|
||||
}
|
||||
const { page, page_size = 50 } = obj || {};
|
||||
sql += PhoneNumber._criteriaBuilder(obj, params);
|
||||
sql += ' ORDER BY number';
|
||||
if (page !== null && page !== undefined) {
|
||||
const limit = Number(page_size);
|
||||
const offset = Number(page > 0 ? (page - 1) : page) * limit;
|
||||
sql += ' LIMIT ? OFFSET ?';
|
||||
params.push(limit);
|
||||
params.push(offset);
|
||||
}
|
||||
const [rows] = await promisePool.query(sql, params);
|
||||
return rows;
|
||||
}
|
||||
|
||||
@@ -8,6 +8,57 @@ class VoipCarrier extends Model {
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
static _criteriaBuilder(obj, args) {
|
||||
let sql = '';
|
||||
if (obj.account_sid) {
|
||||
// carrier belong to an account when
|
||||
// 1. account_sid is set
|
||||
// 2. account_sid is null and service_provider_sid matches the account's service_provider_sid
|
||||
sql += ` AND (vc.account_sid = ? OR
|
||||
(vc.account_sid IS NULL AND vc.service_provider_sid IN
|
||||
(SELECT service_provider_sid FROM accounts WHERE account_sid = ?))
|
||||
)`;
|
||||
args.push(obj.account_sid);
|
||||
args.push(obj.account_sid);
|
||||
}
|
||||
if (obj.service_provider_sid) {
|
||||
sql += ' AND vc.service_provider_sid = ?';
|
||||
args.push(obj.service_provider_sid);
|
||||
}
|
||||
if (obj.name) {
|
||||
sql += ' AND vc.name LIKE ?';
|
||||
args.push(`%${obj.name}%`);
|
||||
}
|
||||
|
||||
return sql;
|
||||
}
|
||||
|
||||
static async countAll(obj) {
|
||||
let sql = 'SELECT COUNT(*) AS count FROM voip_carriers vc WHERE 1 = 1';
|
||||
const args = [];
|
||||
sql += VoipCarrier._criteriaBuilder(obj, args);
|
||||
const [rows] = await promisePool.query(sql, args);
|
||||
return rows[0].count;
|
||||
}
|
||||
|
||||
static async retrieveByCriteria(obj) {
|
||||
let sql = 'SELECT * from voip_carriers vc WHERE 1 =1';
|
||||
const args = [];
|
||||
sql += VoipCarrier._criteriaBuilder(obj, args);
|
||||
if (obj.page !== null && obj.page !== undefined) {
|
||||
const limit = Number(obj.page_size || 50);
|
||||
const offset = (Number(obj.page) - 1) * limit;
|
||||
sql += ' LIMIT ? OFFSET ?';
|
||||
args.push(limit, offset);
|
||||
}
|
||||
const [rows] = await promisePool.query(sql, args);
|
||||
if (rows) {
|
||||
rows.map((r) => r.register_status = JSON.parse(r.register_status || '{}'));
|
||||
}
|
||||
return rows;
|
||||
}
|
||||
|
||||
static async retrieveAll(account_sid) {
|
||||
if (!account_sid) return super.retrieveAll();
|
||||
const [rows] = await promisePool.query(retrieveSql, account_sid);
|
||||
|
||||
@@ -26,7 +26,7 @@ const getUploader = (key, metadata, bucket_credential, logger) => {
|
||||
accessKeyId: bucket_credential.access_key_id,
|
||||
secretAccessKey: bucket_credential.secret_access_key,
|
||||
},
|
||||
region: 'us-east-1',
|
||||
region: bucket_credential.region || 'us-east-1',
|
||||
forcePathStyle: true
|
||||
};
|
||||
return new S3MultipartUploadStream(logger, uploaderOpts);
|
||||
|
||||
@@ -23,7 +23,8 @@ const {
|
||||
} = require('./utils');
|
||||
const short = require('short-uuid');
|
||||
const VoipCarrier = require('../../models/voip-carrier');
|
||||
const { encrypt, obscureBucketCredentialsSensitiveData, isObscureKey } = require('../../utils/encrypt-decrypt');
|
||||
const { encrypt, obscureBucketCredentialsSensitiveData,
|
||||
isObscureKey, decrypt } = require('../../utils/encrypt-decrypt');
|
||||
const { testS3Storage, testGoogleStorage, testAzureStorage } = require('../../utils/storage-utils');
|
||||
const translator = short();
|
||||
|
||||
@@ -92,8 +93,34 @@ router.get('/:sid/Applications', async(req, res) => {
|
||||
try {
|
||||
const account_sid = parseAccountSid(req);
|
||||
await validateRequest(req, account_sid);
|
||||
const results = await Application.retrieveAll(null, account_sid);
|
||||
res.status(200).json(results);
|
||||
const {page, page_size, name} = req.query || {};
|
||||
const isPaginationRequest = page !== null && page !== undefined;
|
||||
let results = [];
|
||||
let total = 0;
|
||||
if (isPaginationRequest) {
|
||||
total = await Application.countAll({account_sid, name});
|
||||
results = await Application.retrieveAll({
|
||||
account_sid, name, page, page_size
|
||||
});
|
||||
} else {
|
||||
results = await Application.retrieveAll({account_sid});
|
||||
}
|
||||
const ret = results.map((a) => {
|
||||
if (a.env_vars) {
|
||||
a.env_vars = JSON.parse(decrypt(a.env_vars));
|
||||
return a;
|
||||
} else {
|
||||
return a;
|
||||
}
|
||||
});
|
||||
|
||||
const body = isPaginationRequest ? {
|
||||
total,
|
||||
page: Number(page),
|
||||
page_size: Number(page_size),
|
||||
data: ret
|
||||
} : ret;
|
||||
res.status(200).json(body);
|
||||
} catch (err) {
|
||||
sysError(logger, res, err);
|
||||
}
|
||||
@@ -221,7 +248,8 @@ router.get('/:sid/RegisteredSipUsers/:client', async(req, res) => {
|
||||
allow_direct_app_calling: clientDb ? clientDb.allow_direct_app_calling : 0,
|
||||
allow_direct_queue_calling: clientDb ? clientDb.allow_direct_queue_calling : 0,
|
||||
allow_direct_user_calling: clientDb ? clientDb.allow_direct_user_calling : 0,
|
||||
registered_status: user ? 'active' : 'inactive'
|
||||
registered_status: user ? 'active' : 'inactive',
|
||||
proxy: user ? user.proxy : null
|
||||
});
|
||||
} catch (err) {
|
||||
sysError(logger, res, err);
|
||||
@@ -355,7 +383,10 @@ async function validateCreateCall(logger, sid, req) {
|
||||
const {lookupAppBySid} = req.app.locals;
|
||||
const obj = req.body;
|
||||
|
||||
if (req.user.account_sid !== sid) throw new DbErrorBadRequest(`unauthorized createCall request for account ${sid}`);
|
||||
if (req.user.hasServiceProviderAuth ||
|
||||
req.user.hasAccountAuth && req.user.account_sid !== sid) {
|
||||
throw new DbErrorBadRequest(`unauthorized createCall request for account ${sid}`);
|
||||
}
|
||||
|
||||
obj.account_sid = sid;
|
||||
if (!obj.from) throw new DbErrorBadRequest('missing from parameter');
|
||||
@@ -650,12 +681,12 @@ function encryptBucketCredential(obj, storedCredentials = {}) {
|
||||
name,
|
||||
access_key_id,
|
||||
tags,
|
||||
endpoint
|
||||
endpoint,
|
||||
} = obj.bucket_credential;
|
||||
let {
|
||||
secret_access_key,
|
||||
service_key,
|
||||
connection_string
|
||||
connection_string,
|
||||
} = obj.bucket_credential;
|
||||
|
||||
switch (vendor) {
|
||||
@@ -680,7 +711,9 @@ function encryptBucketCredential(obj, storedCredentials = {}) {
|
||||
secret_access_key = storedCredentials.secret_access_key;
|
||||
}
|
||||
const s3Data = JSON.stringify({vendor, endpoint, name, access_key_id,
|
||||
secret_access_key, tags});
|
||||
secret_access_key, tags,
|
||||
...(region && {region})
|
||||
});
|
||||
obj.bucket_credential = encrypt(s3Data);
|
||||
break;
|
||||
case 'google':
|
||||
@@ -1062,11 +1095,35 @@ const updateCall = async(req, res) => {
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
logger.error(`Error sending updateCall POST to ${url}`);
|
||||
return res.sendStatus(500);
|
||||
try {
|
||||
const text = await response.text();
|
||||
logger.error(`Error sending updateCall POST to ${url}, status: ${response.status} body: ${text}`);
|
||||
|
||||
// Try to parse as JSON if there's content
|
||||
if (text) {
|
||||
try {
|
||||
const body = JSON.parse(text);
|
||||
return res.status(response.status).json(body);
|
||||
} catch {
|
||||
// Not valid JSON
|
||||
return res.status(response.status).send(text);
|
||||
}
|
||||
}
|
||||
return res.sendStatus(response.status);
|
||||
} catch (err) {
|
||||
logger.error({err}, `updateCall: error reading response from ${url}`);
|
||||
return res.sendStatus(response.status);
|
||||
}
|
||||
}
|
||||
if (response.status === 200) {
|
||||
// feature server return json for sip_request command
|
||||
// with 200 OK
|
||||
const body = await response.json();
|
||||
return res.status(200).json(body);
|
||||
} else {
|
||||
// rest commander returns 202 Accepted for all other commands
|
||||
return res.sendStatus(response.status);
|
||||
}
|
||||
const body = await response.json();
|
||||
return res.status(200).json(body);
|
||||
}
|
||||
else {
|
||||
logger.debug(`updateCall: call not found for call sid ${callSid}`);
|
||||
|
||||
@@ -173,11 +173,19 @@ router.post('/', async(req, res) => {
|
||||
/* list */
|
||||
router.get('/', async(req, res) => {
|
||||
const logger = req.app.locals.logger;
|
||||
const {page, page_size, name} = req.query || {};
|
||||
const isPaginationRequest = page !== null && page !== undefined;
|
||||
try {
|
||||
const service_provider_sid = req.user.hasServiceProviderAuth ? req.user.service_provider_sid : null;
|
||||
const account_sid = req.user.hasAccountAuth ? req.user.account_sid : null;
|
||||
const name = req.query.name;
|
||||
const results = await Application.retrieveAll(service_provider_sid, account_sid, name);
|
||||
let results = [];
|
||||
let total = 0;
|
||||
if (isPaginationRequest) {
|
||||
total = await Application.countAll({service_provider_sid, account_sid, name});
|
||||
}
|
||||
results = await Application.retrieveAll({
|
||||
service_provider_sid, account_sid, name, page, page_size
|
||||
});
|
||||
const ret = results.map((a) => {
|
||||
if (a.env_vars) {
|
||||
a.env_vars = JSON.parse(decrypt(a.env_vars));
|
||||
@@ -186,7 +194,13 @@ router.get('/', async(req, res) => {
|
||||
return a;
|
||||
}
|
||||
});
|
||||
res.status(200).json(ret);
|
||||
const body = isPaginationRequest ? {
|
||||
total,
|
||||
page: Number(page),
|
||||
page_size: Number(page_size),
|
||||
data: ret
|
||||
} : ret;
|
||||
res.status(200).json(body);
|
||||
} catch (err) {
|
||||
sysError(logger, res, err);
|
||||
}
|
||||
|
||||
@@ -97,28 +97,35 @@ decorate(router, PhoneNumber, ['add', 'update', 'delete'], preconditions);
|
||||
/* list */
|
||||
router.get('/', async(req, res) => {
|
||||
const logger = req.app.locals.logger;
|
||||
const {account_sid, filter} = req.query;
|
||||
const {service_provider_sid: query_service_provider_sid,
|
||||
account_sid: query_account_sid, filter, page, page_size} = req.query;
|
||||
const isPaginationRequest = page !== null && page !== undefined;
|
||||
let service_provider_sid = null, account_sid = query_account_sid;
|
||||
if (req.user.hasAccountAuth) {
|
||||
account_sid = req.user.account_sid;
|
||||
} else if (req.user.hasServiceProviderAuth) {
|
||||
service_provider_sid = req.user.service_provider_sid;
|
||||
} else {
|
||||
// admin user can query all phone numbers
|
||||
service_provider_sid = query_service_provider_sid;
|
||||
account_sid = query_account_sid;
|
||||
}
|
||||
try {
|
||||
let results = [];
|
||||
if (req.user.hasAccountAuth) {
|
||||
results = await PhoneNumber.retrieveAllByCriteria({
|
||||
account_sid: req.user.account_sid,
|
||||
filter
|
||||
});
|
||||
} else if (req.user.hasServiceProviderAuth) {
|
||||
results = await PhoneNumber.retrieveAllByCriteria({
|
||||
service_provider_sid: req.user.service_provider_sid,
|
||||
account_sid,
|
||||
filter
|
||||
});
|
||||
} else if (req.user.hasAdminAuth) {
|
||||
results = await PhoneNumber.retrieveAllByCriteria({
|
||||
account_sid,
|
||||
filter
|
||||
});
|
||||
let total = 0;
|
||||
if (isPaginationRequest) {
|
||||
total = await PhoneNumber.countAll({service_provider_sid, account_sid, filter});
|
||||
}
|
||||
const results = await PhoneNumber.retrieveAllByCriteria({
|
||||
service_provider_sid, account_sid, filter, page, page_size
|
||||
});
|
||||
const body = isPaginationRequest ? {
|
||||
total,
|
||||
page: Number(page),
|
||||
page_size: Number(page_size),
|
||||
data: results,
|
||||
} : results;
|
||||
|
||||
res.status(200).json(results);
|
||||
res.status(200).json(body);
|
||||
} catch (err) {
|
||||
sysError(logger, res, err);
|
||||
}
|
||||
|
||||
@@ -149,13 +149,30 @@ router.get('/:sid/VoipCarriers', async(req, res) => {
|
||||
try {
|
||||
await validateRetrieve(req);
|
||||
const service_provider_sid = parseServiceProviderSid(req);
|
||||
const carriers = await VoipCarrier.retrieveAllForSP(service_provider_sid);
|
||||
|
||||
if (req.user.hasScope('account')) {
|
||||
return res.status(200).json(carriers.filter((c) => c.account_sid === req.user.account_sid || !c.account_sid));
|
||||
const {account_sid: query_account_sid, name, page, page_size} = req.query || {};
|
||||
const isPaginationRequest = page !== null && page !== undefined;
|
||||
const account_sid = req.user.hasAccountAuth ? req.user.account_sid : query_account_sid || null;
|
||||
let carriers = [];
|
||||
let total = 0;
|
||||
if (isPaginationRequest) {
|
||||
total = await VoipCarrier.countAll({service_provider_sid, account_sid, name});
|
||||
}
|
||||
carriers = await VoipCarrier.retrieveByCriteria({
|
||||
service_provider_sid,
|
||||
account_sid,
|
||||
name,
|
||||
page,
|
||||
page_size,
|
||||
});
|
||||
|
||||
res.status(200).json(carriers);
|
||||
const body = isPaginationRequest ? {
|
||||
total,
|
||||
page: Number(page),
|
||||
page_size: Number(page_size),
|
||||
data: carriers,
|
||||
} : carriers;
|
||||
|
||||
res.status(200).json(body);
|
||||
} catch (err) {
|
||||
sysError(logger, res, err);
|
||||
}
|
||||
|
||||
@@ -215,7 +215,8 @@ const encryptCredential = (obj) => {
|
||||
if (!deepgram_stt_uri || !deepgram_tts_uri) {
|
||||
assert(api_key, 'invalid deepgram speech credential: api_key is required');
|
||||
}
|
||||
const deepgramData = JSON.stringify({api_key, deepgram_stt_uri, deepgram_stt_use_tls, deepgram_tts_uri});
|
||||
const deepgramData = JSON.stringify({api_key, deepgram_stt_uri,
|
||||
deepgram_stt_use_tls, deepgram_tts_uri, model_id});
|
||||
return encrypt(deepgramData);
|
||||
|
||||
case 'ibm':
|
||||
|
||||
@@ -229,7 +229,7 @@ const updateQuantities = async(req, res) => {
|
||||
const obj = {
|
||||
quantity: product.quantity,
|
||||
};
|
||||
return Object.assign(obj, existingItem ? {id: existingItem.id} : {price_id: product.price_id});
|
||||
return Object.assign(obj, existingItem ? {id: existingItem.id} : {price: product.price_id});
|
||||
});
|
||||
|
||||
if (dry_run) {
|
||||
|
||||
@@ -73,22 +73,36 @@ decorate(router, VoipCarrier, ['add', 'update', 'delete'], preconditions);
|
||||
/* list */
|
||||
router.get('/', async(req, res) => {
|
||||
const logger = req.app.locals.logger;
|
||||
const {lookupAccountBySid} = req.app.locals;
|
||||
|
||||
const {account_sid: query_account_sid, name, page, page_size} = req.query || {};
|
||||
const isPaginationRequest = page !== null && page !== undefined;
|
||||
let service_provider_sid = null, account_sid = query_account_sid;
|
||||
if (req.user.hasAccountAuth) {
|
||||
account_sid = req.user.account_sid;
|
||||
} else if (req.user.hasServiceProviderAuth) {
|
||||
service_provider_sid = req.user.service_provider_sid;
|
||||
}
|
||||
try {
|
||||
let results = [];
|
||||
if (req.user.hasAdminAuth) {
|
||||
results = await VoipCarrier.retrieveAll(req.user.hasAccountAuth ? req.user.account_sid : null);
|
||||
} else {
|
||||
const account = req.user.service_provider_sid ? req.user : await lookupAccountBySid(req.user.account_sid);
|
||||
results = await VoipCarrier.retrieveAllForSP(account.service_provider_sid);
|
||||
let total = 0;
|
||||
if (isPaginationRequest) {
|
||||
total = await VoipCarrier.countAll({service_provider_sid, account_sid, name});
|
||||
}
|
||||
|
||||
if (req.user.hasScope('account')) {
|
||||
return res.status(200).json(results.filter((c) => c.account_sid === req.user.account_sid || !c.account_sid));
|
||||
}
|
||||
const carriers = await VoipCarrier.retrieveByCriteria({
|
||||
service_provider_sid,
|
||||
account_sid,
|
||||
name,
|
||||
page,
|
||||
page_size,
|
||||
});
|
||||
|
||||
res.status(200).json(results);
|
||||
const body = isPaginationRequest ? {
|
||||
total,
|
||||
page: Number(page),
|
||||
page_size: Number(page_size),
|
||||
data: carriers,
|
||||
} : carriers;
|
||||
|
||||
res.status(200).json(body);
|
||||
} catch (err) {
|
||||
sysError(logger, res, err);
|
||||
}
|
||||
|
||||
52
lib/utils/speech-data/stt-model-deepgram.js
Normal file
52
lib/utils/speech-data/stt-model-deepgram.js
Normal file
@@ -0,0 +1,52 @@
|
||||
module.exports = [
|
||||
// Nova-3
|
||||
{ name: 'Nova-3', value: 'nova-3' },
|
||||
{ name: 'Nova-3 General', value: 'nova-3-general' },
|
||||
{ name: 'Nova-3 Medical', value: 'nova-3-medical' },
|
||||
|
||||
// Nova-2
|
||||
{ name: 'Nova-2', value: 'nova-2' },
|
||||
{ name: 'Nova-2 General', value: 'nova-2-general' },
|
||||
{ name: 'Nova-2 Meeting', value: 'nova-2-meeting' },
|
||||
{ name: 'Nova-2 Phonecall', value: 'nova-2-phonecall' },
|
||||
{ name: 'Nova-2 Finance', value: 'nova-2-finance' },
|
||||
{ name: 'Nova-2 Conversational AI', value: 'nova-2-conversationalai' },
|
||||
{ name: 'Nova-2 Voicemail', value: 'nova-2-voicemail' },
|
||||
{ name: 'Nova-2 Video', value: 'nova-2-video' },
|
||||
{ name: 'Nova-2 Medical', value: 'nova-2-medical' },
|
||||
{ name: 'Nova-2 Drivethru', value: 'nova-2-drivethru' },
|
||||
{ name: 'Nova-2 Automotive', value: 'nova-2-automotive' },
|
||||
{ name: 'Nova-2 ATC', value: 'nova-2-atc' },
|
||||
|
||||
// Nova (legacy)
|
||||
{ name: 'Nova', value: 'nova' },
|
||||
{ name: 'Nova General', value: 'nova-general' },
|
||||
{ name: 'Nova Phonecall', value: 'nova-phonecall' },
|
||||
{ name: 'Nova Medical', value: 'nova-medical' },
|
||||
|
||||
// Enhanced (legacy)
|
||||
{ name: 'Enhanced', value: 'enhanced' },
|
||||
{ name: 'Enhanced General', value: 'enhanced-general' },
|
||||
{ name: 'Enhanced Meeting', value: 'enhanced-meeting' },
|
||||
{ name: 'Enhanced Phonecall', value: 'enhanced-phonecall' },
|
||||
{ name: 'Enhanced Finance', value: 'enhanced-finance' },
|
||||
|
||||
// Base (legacy)
|
||||
{ name: 'Base', value: 'base' },
|
||||
{ name: 'Base General', value: 'base-general' },
|
||||
{ name: 'Base Meeting', value: 'base-meeting' },
|
||||
{ name: 'Base Phonecall', value: 'base-phonecall' },
|
||||
{ name: 'Base Finance', value: 'base-finance' },
|
||||
{ name: 'Base Conversational AI', value: 'base-conversationalai' },
|
||||
{ name: 'Base Voicemail', value: 'base-voicemail' },
|
||||
{ name: 'Base Video', value: 'base-video' },
|
||||
|
||||
// Whisper
|
||||
{ name: 'Whisper Tiny', value: 'whisper-tiny' },
|
||||
{ name: 'Whisper Base', value: 'whisper-base' },
|
||||
{ name: 'Whisper Small', value: 'whisper-small' },
|
||||
{ name: 'Whisper Medium', value: 'whisper-medium' },
|
||||
{ name: 'Whisper Large', value: 'whisper-large' },
|
||||
{ name: 'Whisper', value: 'whisper' },
|
||||
];
|
||||
|
||||
@@ -48,6 +48,12 @@ const SttOpenaiLanguagesVoices = require('./speech-data/stt-openai');
|
||||
|
||||
|
||||
const SttModelOpenai = require('./speech-data/stt-model-openai');
|
||||
const sttModelDeepgram = require('./speech-data/stt-model-deepgram');
|
||||
|
||||
function capitalizeFirst(str) {
|
||||
if (!str) return str;
|
||||
return str.charAt(0).toUpperCase() + str.slice(1);
|
||||
}
|
||||
|
||||
|
||||
const testSonioxStt = async(logger, credentials) => {
|
||||
@@ -636,6 +642,7 @@ function decryptCredential(obj, credential, logger, isObscureKey = true) {
|
||||
obj.deepgram_stt_uri = o.deepgram_stt_uri;
|
||||
obj.deepgram_stt_use_tls = o.deepgram_stt_use_tls;
|
||||
obj.deepgram_tts_uri = o.deepgram_tts_uri;
|
||||
obj.model_id = o.model_id;
|
||||
}
|
||||
else if ('ibm' === obj.vendor) {
|
||||
const o = JSON.parse(decrypt(credential));
|
||||
@@ -815,9 +822,10 @@ async function getLanguagesVoicesForAws(credential, getTtsVoices, logger) {
|
||||
|
||||
async function getLanguagesVoicesForMicrosoft(credential, getTtsVoices, logger) {
|
||||
if (credential) {
|
||||
const response = await fetch('https://westus.tts.speech.microsoft.com/cognitiveservices/voices/list', {
|
||||
const {region, api_key} = credential;
|
||||
const response = await fetch(`https://${region}.tts.speech.microsoft.com/cognitiveservices/voices/list`, {
|
||||
headers: {
|
||||
'Ocp-Apim-Subscription-Key': credential.api_key
|
||||
'Ocp-Apim-Subscription-Key': api_key
|
||||
}
|
||||
});
|
||||
if (!response.ok) {
|
||||
@@ -851,8 +859,47 @@ async function getLanguagesVoicesForNuane(credential, getTtsVoices, logger) {
|
||||
return tranform(TtsNuanceLanguagesVoices, SttNuanceLanguagesVoices);
|
||||
}
|
||||
|
||||
async function getLanguagesVoicesForDeepgram(credential) {
|
||||
return tranform(TtsLanguagesDeepgram, SttDeepgramLanguagesVoices, TtsModelDeepgram);
|
||||
async function getLanguagesVoicesForDeepgram(credential, getTtsVoices, logger) {
|
||||
if (credential) {
|
||||
const {model_id, api_key, deepgram_stt_uri, deepgram_tts_uri} = credential;
|
||||
// currently just fetching STT and TTS models from Deepgram cloud
|
||||
if (!deepgram_stt_uri && !deepgram_tts_uri) {
|
||||
const response = await fetch('https://api.deepgram.com/v1/models', {
|
||||
headers: {
|
||||
'Authorization': `Token ${api_key}`
|
||||
}
|
||||
});
|
||||
if (!response.ok) {
|
||||
logger.error({response}, 'Error fetching Deepgram voices');
|
||||
throw new Error('failed to list voices');
|
||||
}
|
||||
const {stt, tts} = await response.json();
|
||||
let sttLangs = SttDeepgramLanguagesVoices;
|
||||
const sttModels = Array.from(
|
||||
new Map(
|
||||
stt.map((m) => [m.canonical_name, { name: capitalizeFirst(m.canonical_name), value: m.canonical_name }])
|
||||
).values()
|
||||
).sort((a, b) => a.name.localeCompare(b.name));
|
||||
const ttsModels = Array.from(
|
||||
new Map(
|
||||
tts.map((m) => [m.canonical_name, { name: capitalizeFirst(m.canonical_name), value: m.canonical_name }])
|
||||
).values()
|
||||
).sort((a, b) => a.name.localeCompare(b.name));
|
||||
// if model_id is not provided, return all models, all voices, all languages
|
||||
if (!model_id) {
|
||||
return tranform(TtsLanguagesDeepgram, sttLangs, ttsModels, sttModels);
|
||||
}
|
||||
|
||||
const selectedSttModel = stt.find((m) => m.canonical_name === model_id);
|
||||
const selectedSttLangs = selectedSttModel ? selectedSttModel.languages : [];
|
||||
sttLangs = SttDeepgramLanguagesVoices.filter((l) => {
|
||||
return selectedSttLangs.includes(l.value);
|
||||
});
|
||||
return tranform(TtsLanguagesDeepgram, sttLangs, ttsModels, sttModels);
|
||||
}
|
||||
}
|
||||
return tranform(TtsLanguagesDeepgram, SttDeepgramLanguagesVoices,
|
||||
TtsModelDeepgram, sttModelDeepgram.sort((a, b) => a.name.localeCompare(b.name)));
|
||||
}
|
||||
|
||||
async function getLanguagesVoicesForIbm(credential, getTtsVoices, logger) {
|
||||
@@ -1072,9 +1119,9 @@ async function getLanguagesVoicesForRimelabs(credential) {
|
||||
Object.keys(voices).length > 0 ? voices[Object.keys(voices)[0]] : [];
|
||||
const ttsVoices = Object.entries(modelVoices).map(([key, voices]) => ({
|
||||
value: key,
|
||||
name: key.charAt(0).toUpperCase() + key.slice(1),
|
||||
name: capitalizeFirst(key),
|
||||
voices: voices.map((v) => ({
|
||||
name: v.charAt(0).toUpperCase() + v.slice(1),
|
||||
name: capitalizeFirst(v),
|
||||
value: v
|
||||
}))
|
||||
}));
|
||||
|
||||
Reference in New Issue
Block a user