Compare commits

...

26 Commits

Author SHA1 Message Date
kitajchuk
ec73bcef53 handle edge case for short key strings 2022-09-20 15:05:20 -07:00
khang
28d9bbf98e Apikey obscure unobscure Aws Apikey 2022-09-20 15:05:20 -07:00
khang
e5793a85f2 hope this works? 2022-09-20 15:05:20 -07:00
khang
31db9131b8 changes suggested and fix wellsaid apikey return 2022-09-20 15:05:20 -07:00
khang
c1a2c6c591 obscuring api key when called from webapp 2022-09-20 15:05:20 -07:00
Dave Horton
05c46c5f39 Feature/sp call limits (#63)
* add api for setting/querying call limits by account and sp

* update an account or sp limit if one exists rather than creating a new one
2022-09-20 22:55:28 +02:00
Dave Horton
052a19cfdc Feature/sp call limits (#63)
* add api for setting/querying call limits by account and sp

* update an account or sp limit if one exists rather than creating a new one
2022-09-20 13:12:28 +02:00
Dave Horton
0a01755a21 update time-series and add trust proxy setting for rate limiting 2022-09-16 13:21:10 +02:00
Dave Horton
ace9e6a4fc add api to retrieve RecentCalls and Alerts by SP (#62) 2022-09-07 14:02:08 +02:00
xquanluu
2db8cb4d3a feat: auto choose speech synthezier from account/service level for createCall (#60)
* feat: auto choose speech synthezier from account/service level

* wip: add integration test

* fix: review comment

* fix: review comment
2022-08-24 10:06:38 +02:00
xquanluu
42b29af0a1 feat: update time-series version 0.1.12 (#56)
* feat: update time-series version 0.1.12

* update generated package-lock.json
2022-08-19 13:24:47 +02:00
Dave Horton
6ac2751b56 update time-series 2022-08-19 09:59:56 +02:00
Lê Hàn Minh Khang
c5b1b36f28 Fix application /:sid return (#54)
* Fix application /:sid return

* fix tests
2022-08-17 18:50:04 -04:00
Hans Krutzer
08163a31d0 Add missing fields to PhoneNumbers POST request Swagger spec (#53) 2022-08-17 15:55:25 +02:00
Dave Horton
1898ba501d upgrade should a FK to new column 2022-08-09 15:27:26 +02:00
Dave Horton
6f52202deb bugfix: FK was wrong 2022-08-03 17:22:30 +01:00
Dave Horton
0ceb79b568 add siprec_hook_sid to accounts model 2022-08-03 14:56:07 +01:00
Dave Horton
1e396266a0 upgrade-jambonz-db.js now handles schema upgrades 2022-07-29 09:48:28 +01:00
Dave Horton
69f36f5a51 Feature/siprec server (#49)
* upgrade schema to 0.7.6 if necessary

* db upgrade

* db upgrade
2022-07-28 16:17:39 +01:00
Dave Horton
f83cc25098 Dockerfile: update base image 2022-07-28 12:59:05 +01:00
Dave Horton
1473c30d7b update passport 2022-07-07 16:15:54 +02:00
Dave Horton
6088b99acf fix Dockerfile for db-create image 2022-07-07 16:10:18 +02:00
Guilherme Rauen
9aab716dc9 Snyk Security Issues (#46)
* improve dockerfile and fix snyk security issues

* enable build in arm64 machine

Co-authored-by: Guilherme Rauen <g.rauen@cognigy.com>
2022-06-29 08:33:08 -04:00
Dave Horton
56b646b6db Feature/siprec client (#45)
* updateCall now supports record action to start, stop, pause, or resume siprec

* update Dockerfile
2022-06-23 16:25:11 -04:00
Dave Horton
eeb23109dc update to azure 1.22.0 2022-06-11 16:18:26 -04:00
Dave Horton
17d1be4605 update deps 2022-06-11 12:23:54 -04:00
34 changed files with 4060 additions and 2717 deletions

View File

@@ -1,10 +1,23 @@
FROM node:17
FROM --platform=linux/amd64 node:18.8.0-alpine as base
RUN apk --update --no-cache add --virtual .builds-deps build-base python3
WORKDIR /opt/app/
COPY package.json ./
RUN npm install
RUN npm prune
COPY . /opt/app
FROM base as build
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
FROM base
COPY --from=build /opt/app /opt/app/
ARG NODE_ENV
ENV NODE_ENV $NODE_ENV
CMD [ "npm", "start" ]
CMD [ "node", "app.js" ]

View File

@@ -1,10 +1,23 @@
FROM node:17
FROM --platform=linux/amd64 node:18.8.0-alpine as base
RUN apk --update --no-cache add --virtual .builds-deps build-base python3
WORKDIR /opt/app/
COPY package.json ./
RUN npm install
RUN npm prune
COPY . /opt/app
FROM base as build
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
FROM base
COPY --from=build /opt/app /opt/app/
ARG NODE_ENV
ENV NODE_ENV $NODE_ENV
CMD [ "npm", "run", "upgrade-db" ]

23
app.js
View File

@@ -21,7 +21,15 @@ assert.ok(process.env.JAMBONES_MYSQL_HOST &&
process.env.JAMBONES_MYSQL_DATABASE, 'missing JAMBONES_MYSQL_XXX env vars');
assert.ok(process.env.JAMBONES_REDIS_HOST, 'missing JAMBONES_REDIS_HOST env var');
assert.ok(process.env.JAMBONES_TIME_SERIES_HOST, 'missing JAMBONES_TIME_SERIES_HOST env var');
const {queryCdrs, queryAlerts, writeCdrs, writeAlerts, AlertType} = require('@jambonz/time-series')(
const {
queryCdrs,
queryCdrsSP,
queryAlerts,
queryAlertsSP,
writeCdrs,
writeAlerts,
AlertType
} = require('@jambonz/time-series')(
logger, process.env.JAMBONES_TIME_SERIES_HOST
);
const {
@@ -78,7 +86,9 @@ app.locals = {
lookupSipGatewayBySid,
lookupSmppGatewayBySid,
queryCdrs,
queryCdrsSP,
queryAlerts,
queryAlertsSP,
writeCdrs,
writeAlerts,
AlertType
@@ -98,6 +108,17 @@ const limiter = rateLimit({
legacyHeaders: false, // Disable the `X-RateLimit-*` headers
});
if (process.env.JAMBONES_TRUST_PROXY) {
const proxyCount = parseInt(process.env.JAMBONES_TRUST_PROXY);
if (!isNaN(proxyCount) && proxyCount > 0) {
logger.info(`setting trust proxy to ${proxyCount} and mounting endpoint /ip`);
app.set('trust proxy', proxyCount);
app.get('/ip', (req, res) => {
logger.info({headers: req.headers}, 'received GET /ip');
res.send(req.ip);
});
}
}
app.use(limiter);
app.use(helmet());
app.use(helmet.hidePoweredBy());

View File

@@ -8,6 +8,8 @@ DROP TABLE IF EXISTS account_products;
DROP TABLE IF EXISTS account_subscriptions;
DROP TABLE IF EXISTS account_limits;
DROP TABLE IF EXISTS beta_invite_codes;
DROP TABLE IF EXISTS call_routes;
@@ -38,6 +40,8 @@ DROP TABLE IF EXISTS ms_teams_tenants;
DROP TABLE IF EXISTS signup_history;
DROP TABLE IF EXISTS service_provider_limits;
DROP TABLE IF EXISTS smpp_addresses;
DROP TABLE IF EXISTS speech_credentials;
@@ -88,6 +92,15 @@ pending_reason VARBINARY(52),
PRIMARY KEY (account_subscription_sid)
);
CREATE TABLE account_limits
(
account_limits_sid CHAR(36) NOT NULL UNIQUE ,
account_sid CHAR(36) NOT NULL,
category ENUM('api_rate','voice_call_session', 'device') NOT NULL,
quantity INTEGER NOT NULL,
PRIMARY KEY (account_limits_sid)
);
CREATE TABLE beta_invite_codes
(
invite_code CHAR(6) NOT NULL UNIQUE ,
@@ -236,6 +249,15 @@ signed_up_at DATETIME DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (email)
);
CREATE TABLE service_provider_limits
(
service_provider_limits_sid CHAR(36) NOT NULL UNIQUE ,
service_provider_sid CHAR(36) NOT NULL,
category ENUM('api_rate','voice_call_session', 'device') NOT NULL,
quantity INTEGER NOT NULL,
PRIMARY KEY (service_provider_limits_sid)
);
CREATE TABLE smpp_addresses
(
smpp_address_sid CHAR(36) NOT NULL UNIQUE ,
@@ -365,7 +387,7 @@ CREATE TABLE webhooks
(
webhook_sid CHAR(36) NOT NULL UNIQUE ,
url VARCHAR(1024) NOT NULL,
method ENUM("GET","POST","WS") NOT NULL DEFAULT 'POST',
method ENUM("GET","POST") NOT NULL DEFAULT 'POST',
username VARCHAR(255),
password VARCHAR(255),
PRIMARY KEY (webhook_sid)
@@ -422,6 +444,7 @@ subspace_client_id VARCHAR(255),
subspace_client_secret VARCHAR(255),
subspace_sip_teleport_id VARCHAR(255),
subspace_sip_teleport_destinations VARCHAR(255),
siprec_hook_sid CHAR(36),
PRIMARY KEY (account_sid)
) COMMENT='An enterprise that uses the platform for comm services';
@@ -433,14 +456,17 @@ CREATE INDEX account_subscription_sid_idx ON account_subscriptions (account_subs
CREATE INDEX account_sid_idx ON account_subscriptions (account_sid);
ALTER TABLE account_subscriptions ADD FOREIGN KEY account_sid_idxfk_1 (account_sid) REFERENCES accounts (account_sid);
CREATE INDEX account_sid_idx ON account_limits (account_sid);
ALTER TABLE account_limits ADD FOREIGN KEY account_sid_idxfk_2 (account_sid) REFERENCES accounts (account_sid) ON DELETE CASCADE;
CREATE INDEX invite_code_idx ON beta_invite_codes (invite_code);
CREATE INDEX call_route_sid_idx ON call_routes (call_route_sid);
ALTER TABLE call_routes ADD FOREIGN KEY account_sid_idxfk_2 (account_sid) REFERENCES accounts (account_sid);
ALTER TABLE call_routes ADD FOREIGN KEY account_sid_idxfk_3 (account_sid) REFERENCES accounts (account_sid);
ALTER TABLE call_routes ADD FOREIGN KEY application_sid_idxfk (application_sid) REFERENCES applications (application_sid);
CREATE INDEX dns_record_sid_idx ON dns_records (dns_record_sid);
ALTER TABLE dns_records ADD FOREIGN KEY account_sid_idxfk_3 (account_sid) REFERENCES accounts (account_sid);
ALTER TABLE dns_records ADD FOREIGN KEY account_sid_idxfk_4 (account_sid) REFERENCES accounts (account_sid);
CREATE INDEX predefined_carrier_sid_idx ON predefined_carriers (predefined_carrier_sid);
CREATE INDEX predefined_sip_gateway_sid_idx ON predefined_sip_gateways (predefined_sip_gateway_sid);
@@ -460,14 +486,14 @@ ALTER TABLE account_products ADD FOREIGN KEY product_sid_idxfk (product_sid) REF
CREATE INDEX account_offer_sid_idx ON account_offers (account_offer_sid);
CREATE INDEX account_sid_idx ON account_offers (account_sid);
ALTER TABLE account_offers ADD FOREIGN KEY account_sid_idxfk_4 (account_sid) REFERENCES accounts (account_sid);
ALTER TABLE account_offers ADD FOREIGN KEY account_sid_idxfk_5 (account_sid) REFERENCES accounts (account_sid);
CREATE INDEX product_sid_idx ON account_offers (product_sid);
ALTER TABLE account_offers ADD FOREIGN KEY product_sid_idxfk_1 (product_sid) REFERENCES products (product_sid);
CREATE INDEX api_key_sid_idx ON api_keys (api_key_sid);
CREATE INDEX account_sid_idx ON api_keys (account_sid);
ALTER TABLE api_keys ADD FOREIGN KEY account_sid_idxfk_5 (account_sid) REFERENCES accounts (account_sid);
ALTER TABLE api_keys ADD FOREIGN KEY account_sid_idxfk_6 (account_sid) REFERENCES accounts (account_sid);
CREATE INDEX service_provider_sid_idx ON api_keys (service_provider_sid);
ALTER TABLE api_keys ADD FOREIGN KEY service_provider_sid_idxfk (service_provider_sid) REFERENCES service_providers (service_provider_sid);
@@ -481,41 +507,44 @@ ALTER TABLE sbc_addresses ADD FOREIGN KEY service_provider_sid_idxfk_1 (service_
CREATE INDEX ms_teams_tenant_sid_idx ON ms_teams_tenants (ms_teams_tenant_sid);
ALTER TABLE ms_teams_tenants ADD FOREIGN KEY service_provider_sid_idxfk_2 (service_provider_sid) REFERENCES service_providers (service_provider_sid);
ALTER TABLE ms_teams_tenants ADD FOREIGN KEY account_sid_idxfk_6 (account_sid) REFERENCES accounts (account_sid);
ALTER TABLE ms_teams_tenants ADD FOREIGN KEY account_sid_idxfk_7 (account_sid) REFERENCES accounts (account_sid);
ALTER TABLE ms_teams_tenants ADD FOREIGN KEY application_sid_idxfk_1 (application_sid) REFERENCES applications (application_sid);
CREATE INDEX tenant_fqdn_idx ON ms_teams_tenants (tenant_fqdn);
CREATE INDEX email_idx ON signup_history (email);
CREATE INDEX service_provider_sid_idx ON service_provider_limits (service_provider_sid);
ALTER TABLE service_provider_limits ADD FOREIGN KEY service_provider_sid_idxfk_3 (service_provider_sid) REFERENCES service_providers (service_provider_sid) ON DELETE CASCADE;
CREATE INDEX smpp_address_sid_idx ON smpp_addresses (smpp_address_sid);
CREATE INDEX service_provider_sid_idx ON smpp_addresses (service_provider_sid);
ALTER TABLE smpp_addresses ADD FOREIGN KEY service_provider_sid_idxfk_3 (service_provider_sid) REFERENCES service_providers (service_provider_sid);
ALTER TABLE smpp_addresses ADD FOREIGN KEY service_provider_sid_idxfk_4 (service_provider_sid) REFERENCES service_providers (service_provider_sid);
CREATE UNIQUE INDEX speech_credentials_idx_1 ON speech_credentials (vendor,account_sid);
CREATE INDEX speech_credential_sid_idx ON speech_credentials (speech_credential_sid);
CREATE INDEX service_provider_sid_idx ON speech_credentials (service_provider_sid);
ALTER TABLE speech_credentials ADD FOREIGN KEY service_provider_sid_idxfk_4 (service_provider_sid) REFERENCES service_providers (service_provider_sid);
ALTER TABLE speech_credentials ADD FOREIGN KEY service_provider_sid_idxfk_5 (service_provider_sid) REFERENCES service_providers (service_provider_sid);
CREATE INDEX account_sid_idx ON speech_credentials (account_sid);
ALTER TABLE speech_credentials ADD FOREIGN KEY account_sid_idxfk_7 (account_sid) REFERENCES accounts (account_sid);
ALTER TABLE speech_credentials ADD FOREIGN KEY account_sid_idxfk_8 (account_sid) REFERENCES accounts (account_sid);
CREATE INDEX user_sid_idx ON users (user_sid);
CREATE INDEX email_idx ON users (email);
CREATE INDEX phone_idx ON users (phone);
CREATE INDEX account_sid_idx ON users (account_sid);
ALTER TABLE users ADD FOREIGN KEY account_sid_idxfk_8 (account_sid) REFERENCES accounts (account_sid);
ALTER TABLE users ADD FOREIGN KEY account_sid_idxfk_9 (account_sid) REFERENCES accounts (account_sid);
CREATE INDEX service_provider_sid_idx ON users (service_provider_sid);
ALTER TABLE users ADD FOREIGN KEY service_provider_sid_idxfk_5 (service_provider_sid) REFERENCES service_providers (service_provider_sid);
ALTER TABLE users ADD FOREIGN KEY service_provider_sid_idxfk_6 (service_provider_sid) REFERENCES service_providers (service_provider_sid);
CREATE INDEX email_activation_code_idx ON users (email_activation_code);
CREATE INDEX voip_carrier_sid_idx ON voip_carriers (voip_carrier_sid);
CREATE INDEX account_sid_idx ON voip_carriers (account_sid);
ALTER TABLE voip_carriers ADD FOREIGN KEY account_sid_idxfk_9 (account_sid) REFERENCES accounts (account_sid);
ALTER TABLE voip_carriers ADD FOREIGN KEY account_sid_idxfk_10 (account_sid) REFERENCES accounts (account_sid);
CREATE INDEX service_provider_sid_idx ON voip_carriers (service_provider_sid);
ALTER TABLE voip_carriers ADD FOREIGN KEY service_provider_sid_idxfk_6 (service_provider_sid) REFERENCES service_providers (service_provider_sid);
ALTER TABLE voip_carriers ADD FOREIGN KEY service_provider_sid_idxfk_7 (service_provider_sid) REFERENCES service_providers (service_provider_sid);
ALTER TABLE voip_carriers ADD FOREIGN KEY application_sid_idxfk_2 (application_sid) REFERENCES applications (application_sid);
@@ -528,12 +557,12 @@ CREATE INDEX number_idx ON phone_numbers (number);
CREATE INDEX voip_carrier_sid_idx ON phone_numbers (voip_carrier_sid);
ALTER TABLE phone_numbers ADD FOREIGN KEY voip_carrier_sid_idxfk_1 (voip_carrier_sid) REFERENCES voip_carriers (voip_carrier_sid);
ALTER TABLE phone_numbers ADD FOREIGN KEY account_sid_idxfk_10 (account_sid) REFERENCES accounts (account_sid);
ALTER TABLE phone_numbers ADD FOREIGN KEY account_sid_idxfk_11 (account_sid) REFERENCES accounts (account_sid);
ALTER TABLE phone_numbers ADD FOREIGN KEY application_sid_idxfk_3 (application_sid) REFERENCES applications (application_sid);
CREATE INDEX service_provider_sid_idx ON phone_numbers (service_provider_sid);
ALTER TABLE phone_numbers ADD FOREIGN KEY service_provider_sid_idxfk_7 (service_provider_sid) REFERENCES service_providers (service_provider_sid);
ALTER TABLE phone_numbers ADD FOREIGN KEY service_provider_sid_idxfk_8 (service_provider_sid) REFERENCES service_providers (service_provider_sid);
CREATE INDEX sip_gateway_idx_hostport ON sip_gateways (ipv4,port);
@@ -549,10 +578,10 @@ CREATE UNIQUE INDEX applications_idx_name ON applications (account_sid,name);
CREATE INDEX application_sid_idx ON applications (application_sid);
CREATE INDEX service_provider_sid_idx ON applications (service_provider_sid);
ALTER TABLE applications ADD FOREIGN KEY service_provider_sid_idxfk_8 (service_provider_sid) REFERENCES service_providers (service_provider_sid);
ALTER TABLE applications ADD FOREIGN KEY service_provider_sid_idxfk_9 (service_provider_sid) REFERENCES service_providers (service_provider_sid);
CREATE INDEX account_sid_idx ON applications (account_sid);
ALTER TABLE applications ADD FOREIGN KEY account_sid_idxfk_11 (account_sid) REFERENCES accounts (account_sid);
ALTER TABLE applications ADD FOREIGN KEY account_sid_idxfk_12 (account_sid) REFERENCES accounts (account_sid);
ALTER TABLE applications ADD FOREIGN KEY call_hook_sid_idxfk (call_hook_sid) REFERENCES webhooks (webhook_sid);
@@ -568,7 +597,7 @@ ALTER TABLE service_providers ADD FOREIGN KEY registration_hook_sid_idxfk (regis
CREATE INDEX account_sid_idx ON accounts (account_sid);
CREATE INDEX sip_realm_idx ON accounts (sip_realm);
CREATE INDEX service_provider_sid_idx ON accounts (service_provider_sid);
ALTER TABLE accounts ADD FOREIGN KEY service_provider_sid_idxfk_9 (service_provider_sid) REFERENCES service_providers (service_provider_sid);
ALTER TABLE accounts ADD FOREIGN KEY service_provider_sid_idxfk_10 (service_provider_sid) REFERENCES service_providers (service_provider_sid);
ALTER TABLE accounts ADD FOREIGN KEY registration_hook_sid_idxfk_1 (registration_hook_sid) REFERENCES webhooks (webhook_sid);
@@ -576,4 +605,6 @@ ALTER TABLE accounts ADD FOREIGN KEY queue_event_hook_sid_idxfk (queue_event_hoo
ALTER TABLE accounts ADD FOREIGN KEY device_calling_application_sid_idxfk (device_calling_application_sid) REFERENCES applications (application_sid);
SET FOREIGN_KEY_CHECKS=0;
ALTER TABLE accounts ADD FOREIGN KEY siprec_hook_sid_idxfk (siprec_hook_sid) REFERENCES applications (application_sid);
SET FOREIGN_KEY_CHECKS=1;

View File

@@ -95,8 +95,8 @@
<name><![CDATA[account_products]]></name>
<schema><![CDATA[]]></schema>
<location>
<x>1791.00</x>
<y>608.00</y>
<x>1823.00</x>
<y>738.00</y>
</location>
<size>
<width>294.00</width>
@@ -233,8 +233,8 @@
<name><![CDATA[account_static_ips]]></name>
<schema><![CDATA[]]></schema>
<location>
<x>1334.00</x>
<y>1169.00</y>
<x>1333.00</x>
<y>1267.00</y>
</location>
<size>
<width>298.00</width>
@@ -287,8 +287,8 @@
<name><![CDATA[users]]></name>
<schema><![CDATA[]]></schema>
<location>
<x>1745.00</x>
<y>19.00</y>
<x>1769.00</x>
<y>16.00</y>
</location>
<size>
<width>316.00</width>
@@ -757,12 +757,63 @@
<ui.treeExpanded><![CDATA[1]]></ui.treeExpanded>
<uid><![CDATA[3EDF89A0-FD38-4DF9-BB65-E0FCD0A678BE]]></uid>
</SQLTable>
<SQLTable>
<name><![CDATA[account_limits]]></name>
<schema><![CDATA[]]></schema>
<location>
<x>1334.00</x>
<y>325.00</y>
</location>
<size>
<width>352.00</width>
<height>100.00</height>
</size>
<zorder>30</zorder>
<SQLField>
<name><![CDATA[account_limits_sid]]></name>
<type><![CDATA[CHAR(36)]]></type>
<primaryKey>1</primaryKey>
<notNull><![CDATA[1]]></notNull>
<uid><![CDATA[F717BEE1-C530-4C3B-A35F-F4AB0A797B7A]]></uid>
<unique><![CDATA[1]]></unique>
</SQLField>
<SQLField>
<name><![CDATA[account_sid]]></name>
<type><![CDATA[CHAR(36)]]></type>
<referencesField>account_sid</referencesField>
<referencesTable>accounts</referencesTable>
<deleteAction>1</deleteAction>
<referencesField><![CDATA[account_sid]]></referencesField>
<referencesTable><![CDATA[accounts]]></referencesTable>
<sourceCardinality>4</sourceCardinality>
<destinationCardinality>1</destinationCardinality>
<referencesFieldUID><![CDATA[1342FAFA-C15C-429B-809B-C6C55F9FA5B6]]></referencesFieldUID>
<referencesTableUID><![CDATA[985D6997-B1A7-4AB3-80F4-4D59B45480C8]]></referencesTableUID>
<indexed><![CDATA[1]]></indexed>
<notNull><![CDATA[1]]></notNull>
<uid><![CDATA[F350FCA7-C536-47EB-A9A3-CEE9CDA00DAD]]></uid>
</SQLField>
<SQLField>
<name><![CDATA[category]]></name>
<type><![CDATA[ENUM('api_rate','voice_call_session', 'device')]]></type>
<notNull><![CDATA[1]]></notNull>
<uid><![CDATA[9724C27F-3B51-453A-99B8-313480D4A63A]]></uid>
</SQLField>
<SQLField>
<name><![CDATA[quantity]]></name>
<type><![CDATA[INTEGER]]></type>
<notNull><![CDATA[1]]></notNull>
<uid><![CDATA[EA4C1A7E-68ED-41D5-9EE9-345DD61F00C7]]></uid>
</SQLField>
<ui.treeExpanded><![CDATA[1]]></ui.treeExpanded>
<uid><![CDATA[4893A0F0-BE1B-4322-9034-644528E802DE]]></uid>
</SQLTable>
<SQLTable>
<name><![CDATA[speech_credentials]]></name>
<schema><![CDATA[]]></schema>
<location>
<x>1354.00</x>
<y>821.00</y>
<x>1328.00</x>
<y>958.00</y>
</location>
<size>
<width>368.00</width>
@@ -909,8 +960,8 @@
<name><![CDATA[account_offers]]></name>
<schema><![CDATA[]]></schema>
<location>
<x>1386.00</x>
<y>333.00</y>
<x>1330.00</x>
<y>514.00</y>
</location>
<size>
<width>276.00</width>
@@ -966,6 +1017,57 @@
<ui.treeExpanded><![CDATA[1]]></ui.treeExpanded>
<uid><![CDATA[51A02EFE-AA51-46EF-8671-E8B2F1FC5F8D]]></uid>
</SQLTable>
<SQLTable>
<name><![CDATA[service_provider_limits]]></name>
<schema><![CDATA[]]></schema>
<location>
<x>1327.00</x>
<y>205.00</y>
</location>
<size>
<width>352.00</width>
<height>100.00</height>
</size>
<zorder>29</zorder>
<SQLField>
<name><![CDATA[service_provider_limits_sid]]></name>
<type><![CDATA[CHAR(36)]]></type>
<primaryKey>1</primaryKey>
<notNull><![CDATA[1]]></notNull>
<uid><![CDATA[DE7EA932-0467-4D5F-8311-CADB7E7D6D17]]></uid>
<unique><![CDATA[1]]></unique>
</SQLField>
<SQLField>
<name><![CDATA[service_provider_sid]]></name>
<type><![CDATA[CHAR(36)]]></type>
<referencesField>service_provider_sid</referencesField>
<referencesTable>service_providers</referencesTable>
<deleteAction>1</deleteAction>
<referencesField><![CDATA[service_provider_sid]]></referencesField>
<referencesTable><![CDATA[service_providers]]></referencesTable>
<sourceCardinality>4</sourceCardinality>
<destinationCardinality>1</destinationCardinality>
<referencesFieldUID><![CDATA[58E1702C-6A95-4B17-8C08-8A3810EA16A1]]></referencesFieldUID>
<referencesTableUID><![CDATA[F294B51E-F867-47CA-BC1F-F70BDF8170FF]]></referencesTableUID>
<indexed><![CDATA[1]]></indexed>
<notNull><![CDATA[1]]></notNull>
<uid><![CDATA[E1C82BD4-FA34-40C7-8236-EC924C9CF7D5]]></uid>
</SQLField>
<SQLField>
<name><![CDATA[category]]></name>
<type><![CDATA[ENUM('api_rate','voice_call_session', 'device')]]></type>
<notNull><![CDATA[1]]></notNull>
<uid><![CDATA[72BFA408-371D-4B21-8DA0-A56644FCD92C]]></uid>
</SQLField>
<SQLField>
<name><![CDATA[quantity]]></name>
<type><![CDATA[INTEGER]]></type>
<notNull><![CDATA[1]]></notNull>
<uid><![CDATA[2EA3A57F-7EF7-4958-B06B-62B0279BB87E]]></uid>
</SQLField>
<ui.treeExpanded><![CDATA[1]]></ui.treeExpanded>
<uid><![CDATA[5784AC2F-BEBC-466F-9818-F9A7D227A5B5]]></uid>
</SQLTable>
<SQLTable>
<name><![CDATA[webhooks]]></name>
<schema><![CDATA[]]></schema>
@@ -1226,8 +1328,8 @@
<name><![CDATA[beta_invite_codes]]></name>
<schema><![CDATA[]]></schema>
<location>
<x>1796.00</x>
<y>780.00</y>
<x>1860.00</x>
<y>897.00</y>
</location>
<size>
<width>232.00</width>
@@ -1412,7 +1514,7 @@
</location>
<size>
<width>318.00</width>
<height>420.00</height>
<height>440.00</height>
</size>
<zorder>4</zorder>
<SQLField>
@@ -1577,6 +1679,19 @@
<type><![CDATA[VARCHAR(255)]]></type>
<uid><![CDATA[2A70CC94-123D-4AF1-9881-F876051D8CB5]]></uid>
</SQLField>
<SQLField>
<name><![CDATA[siprec_hook_sid]]></name>
<type><![CDATA[CHAR(36)]]></type>
<referencesField>application_sid</referencesField>
<referencesTable>applications</referencesTable>
<referencesField><![CDATA[application_sid]]></referencesField>
<referencesTable><![CDATA[applications]]></referencesTable>
<sourceCardinality>4</sourceCardinality>
<destinationCardinality>1</destinationCardinality>
<referencesFieldUID><![CDATA[EF943D13-DCB0-43C1-B03F-550612E20F9D]]></referencesFieldUID>
<referencesTableUID><![CDATA[E97EE4F0-7ED7-4E8C-862E-D98192D6EAE0]]></referencesTableUID>
<uid><![CDATA[4B8283B4-5E16-4846-A79D-12C6B2E73C86]]></uid>
</SQLField>
<labelWindowIndex><![CDATA[25]]></labelWindowIndex>
<objectComment><![CDATA[An enterprise that uses the platform for comm services]]></objectComment>
<ui.treeExpanded><![CDATA[1]]></ui.treeExpanded>
@@ -1586,8 +1701,8 @@
<name><![CDATA[products]]></name>
<schema><![CDATA[]]></schema>
<location>
<x>1779.00</x>
<y>427.00</y>
<x>1842.00</x>
<y>550.00</y>
</location>
<size>
<width>352.00</width>
@@ -1855,7 +1970,7 @@
<objectComment><![CDATA[if not null, this number is a test number for the associated service provider]]></objectComment>
<uid><![CDATA[D2D46B75-F9C7-42A5-8D8C-4A8412C75ECA]]></uid>
</SQLField>
<labelWindowIndex><![CDATA[25]]></labelWindowIndex>
<labelWindowIndex><![CDATA[26]]></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>
@@ -2133,8 +2248,8 @@
<name><![CDATA[account_subscriptions]]></name>
<schema><![CDATA[]]></schema>
<location>
<x>1367.00</x>
<y>477.00</y>
<x>1324.00</x>
<y>637.00</y>
</location>
<size>
<width>322.00</width>
@@ -2433,17 +2548,17 @@
<overviewPanelHidden><![CDATA[0]]></overviewPanelHidden>
<pageBoundariesVisible><![CDATA[0]]></pageBoundariesVisible>
<PageGridVisible><![CDATA[0]]></PageGridVisible>
<RightSidebarWidth><![CDATA[2154.000000]]></RightSidebarWidth>
<RightSidebarWidth><![CDATA[1235.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[977.000000]]></windowHeight>
<windowLocationX><![CDATA[1230.000000]]></windowLocationX>
<windowLocationY><![CDATA[80.000000]]></windowLocationY>
<windowScrollOrigin><![CDATA[{0, 0}]]></windowScrollOrigin>
<windowWidth><![CDATA[2431.000000]]></windowWidth>
<windowHeight><![CDATA[833.000000]]></windowHeight>
<windowLocationX><![CDATA[0.000000]]></windowLocationX>
<windowLocationY><![CDATA[111.000000]]></windowLocationY>
<windowScrollOrigin><![CDATA[{462, 81}]]></windowScrollOrigin>
<windowWidth><![CDATA[1512.000000]]></windowWidth>
</SQLDocumentInfo>
<AllowsIndexRenamingOnInsert><![CDATA[1]]></AllowsIndexRenamingOnInsert>
<defaultLabelExpanded><![CDATA[1]]></defaultLabelExpanded>

View File

@@ -1,6 +1,8 @@
SET FOREIGN_KEY_CHECKS=0;
-- create one service provider
insert into service_providers (service_provider_sid, name, description, root_domain)
values ('2708b1b3-2736-40ea-b502-c53d8396247f', 'sip.jambonz.us', 'jambonz.us service provider', 'sip.jambonz.us');
values ('2708b1b3-2736-40ea-b502-c53d8396247f', 'sip.jambonz.xyz', 'jambonz.xyz service provider', 'sip.jambonz.xyz');
insert into api_keys (api_key_sid, token)
values ('3f35518f-5a0d-4c2e-90a5-2407bb3b36f0', '38700987-c7a4-4685-a5bb-af378f9734de');
@@ -16,9 +18,9 @@ insert into predefined_carriers (predefined_carrier_sid, name, requires_static_i
requires_register, register_username, register_password,
register_sip_realm, tech_prefix, inbound_auth_username, inbound_auth_password, diversion)
VALUES
('17479288-bb9f-421a-89d1-f4ac57af1dca', 'TelecomsXChange', 0, 0, 0, NULL, NULL, NULL, 'your-tech-prefix', NULL, NULL, NULL),
('7d509a18-bbff-4c5d-b21e-b99bf8f8c49a', 'Twilio', 0, 1, 0, '<your-twilio-credential-username>', '<your-twilio-credential-password>', NULL, NULL, NULL, NULL, NULL),
('032d90d5-39e8-41c0-b807-9c88cffba65c', 'Voxbone', 0, 1, 0, '<your-voxbone-outbound-username>', '<your-voxbone-outbound-password>', NULL, NULL, NULL, NULL, '<your-voxbone-DID>'),
('17479288-bb9f-421a-89d1-f4ac57af1dca', 'TelecomsXChange', 0, 0, 0, NULL, NULL, NULL, 'your-tech-prefix', NULL, NULL, NULL),
('e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', 'Simwood', 0, 1, 0, '<your-simwood-auth-trunk-username>', '<your-simwood-auth-trunk-password>', NULL, NULL, NULL, NULL, NULL);
-- TelecomXchange gateways
@@ -69,3 +71,4 @@ VALUES
('e7447e7e-2c7d-4738-ab53-097c187236ff', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '178.22.143.66', 32, 5060, 1, 0),
('5f431d42-48e4-44ce-a311-d946f0b475b6', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', 'out.simwood.com', 32, 5060, 0, 1);
SET FOREIGN_KEY_CHECKS=1;

View File

@@ -1,4 +1,5 @@
#!/usr/bin/env node
/* eslint-disable max-len */
const assert = require('assert');
const mysql = require('mysql2/promise');
const {readFile} = require('fs/promises');
@@ -22,6 +23,40 @@ const opts = {
multipleStatements: true
};
const sql = {
'7006': [
'ALTER TABLE `accounts` ADD COLUMN `siprec_hook_sid` CHAR(36)',
'ALTER TABLE accounts ADD FOREIGN KEY siprec_hook_sid_idxfk (siprec_hook_sid) REFERENCES applications (application_sid)'
],
'7007': [
`CREATE TABLE service_provider_limits
(service_provider_limits_sid CHAR(36) NOT NULL UNIQUE,
service_provider_sid CHAR(36) NOT NULL,
category ENUM('api_rate','voice_call_session', 'device') NOT NULL,
quantity INTEGER NOT NULL,
PRIMARY KEY (service_provider_limits_sid)
)`,
`CREATE TABLE account_limits
(
account_limits_sid CHAR(36) NOT NULL UNIQUE ,
account_sid CHAR(36) NOT NULL,
category ENUM('api_rate','voice_call_session', 'device') NOT NULL,
quantity INTEGER NOT NULL,
PRIMARY KEY (account_limits_sid)
)`,
'CREATE INDEX service_provider_sid_idx ON service_provider_limits (service_provider_sid)',
`ALTER TABLE service_provider_limits
ADD FOREIGN KEY service_provider_sid_idxfk_3 (service_provider_sid)
REFERENCES service_providers (service_provider_sid)
ON DELETE CASCADE`,
'CREATE INDEX account_sid_idx ON account_limits (account_sid)',
`ALTER TABLE account_limits
ADD FOREIGN KEY account_sid_idxfk_2 (account_sid)
REFERENCES accounts (account_sid)
ON DELETE CASCADE`
]
};
const doIt = async() => {
let connection;
try {
@@ -35,10 +70,33 @@ const doIt = async() => {
try {
/* does the schema exist at all ? */
const [r] = await connection.execute('SELECT version from schema_version');
let errors = 0;
if (r.length) {
//TODO: check against desired version and perform upgrades
logger.info(`current version is ${r[0].version}, no upgrade will be performed`);
const {version} = r[0];
const arr = /v?(\d+)\.(\d+)\.(\d+)/.exec(version);
if (arr) {
const upgrades = [];
logger.info(`performing schema migration: ${version} => ${desiredVersion}`);
const val = (1000 * arr[1]) + (100 * arr[2]) + arr[3];
logger.info(`current schema value: ${val}`);
if (val < 7006) upgrades.push(...sql['7006']);
if (val < 7007) upgrades.push(...sql['7007']);
// perform all upgrades
logger.info({upgrades}, 'applying schema upgrades..');
for (const upgrade of upgrades) {
try {
await connection.execute(upgrade);
} catch (err) {
errors++;
logger.info({statement:upgrade, err}, 'Error applying statement');
}
}
}
if (errors === 0) await connection.execute(`UPDATE schema_version SET version = '${desiredVersion}'`);
await connection.end();
logger.info(`schema migration to ${desiredVersion} completed with ${errors} errors`);
return;
}
} catch (err) {
@@ -70,5 +128,6 @@ const seedDatabase = async(connection) => {
await connection.query(sql);
};
doIt();

View File

@@ -0,0 +1,41 @@
const Model = require('./model');
const {promisePool} = require('../db');
const sql = 'SELECT * FROM account_limits WHERE account_sid = ?';
class AccountLimits extends Model {
constructor() {
super();
}
static async retrieve(account_sid) {
const [rows] = await promisePool.query(sql, [account_sid]);
return rows;
}
}
AccountLimits.table = 'account_limits';
AccountLimits.fields = [
{
name: 'account_limits_sid',
type: 'string',
primaryKey: true
},
{
name: 'account_sid',
type: 'string',
required: true
},
{
name: 'category',
type: 'string',
required: true
},
{
name: 'quantity',
type: 'number',
required: true
}
];
module.exports = AccountLimits;

View File

@@ -314,6 +314,10 @@ Account.fields = [
name: 'subspace_sip_teleport_destinations',
type: 'string',
},
{
name: 'siprec_hook_sid',
type: 'string',
},
];
module.exports = Account;

View File

@@ -0,0 +1,39 @@
const Model = require('./model');
const {promisePool} = require('../db');
const sql = 'SELECT * FROM service_provider_limits WHERE service_provider_sid = ?';
class ServiceProviderLimits extends Model {
constructor() {
super();
}
static async retrieve(service_provider_sid) {
const [rows] = await promisePool.query(sql, [service_provider_sid]);
return rows;
}
}
ServiceProviderLimits.table = 'service_provider_limits';
ServiceProviderLimits.fields = [
{
name: 'service_provider_limits_sid',
type: 'string',
primaryKey: true
},
{
name: 'service_provider_sid',
type: 'string',
required: true
},
{
name: 'category',
type: 'string',
required: true
},
{
name: 'quantity',
type: 'number',
required: true
}
];
module.exports = ServiceProviderLimits;

View File

@@ -20,7 +20,10 @@ const translator = short();
let idx = 0;
const getFsUrl = async(logger, retrieveSet, setName) => {
if (process.env.K8S) return `http://${process.env.K8S_FEATURE_SERVER_SERVICE_NAME}:3000/v1/createCall`;
if (process.env.K8S) {
const port = process.env.K8S_FEATURE_SERVER_SERVICE_PORT || 3000;
return `http://${process.env.K8S_FEATURE_SERVER_SERVICE_NAME}:${port}/v1/createCall`;
}
try {
const fs = await retrieveSet(setName);
@@ -48,6 +51,7 @@ router.use('/:sid/Alerts', hasAccountPermissions, require('./alerts'));
router.use('/:sid/Charges', hasAccountPermissions, require('./charges'));
router.use('/:sid/SipRealms', hasAccountPermissions, require('./sip-realm'));
router.use('/:sid/PredefinedCarriers', hasAccountPermissions, require('./add-from-predefined-carrier'));
router.use('/:sid/Limits', hasAccountPermissions, require('./limits'));
router.get('/:sid/Applications', async(req, res) => {
const logger = req.app.locals.logger;
try {
@@ -117,7 +121,8 @@ function validateUpdateCall(opts) {
'conf_hold_status',
'conf_mute_status',
'mute_status',
'sip_request'
'sip_request',
'record'
]
.reduce((acc, prop) => (opts[prop] ? ++acc : acc), 0);
@@ -156,6 +161,12 @@ function validateUpdateCall(opts) {
(!opts.sip_request.method && !opts.sip_request.content_type || !opts.sip_request.content_type)) {
throw new DbErrorBadRequest('sip_request requires content_type and content properties');
}
if (opts.record && !opts.record.action) {
throw new DbErrorBadRequest('record requires action property');
}
if ('startCallRecording' === opts.record?.action && !opts.record.siprecServerURL) {
throw new DbErrorBadRequest('record requires siprecServerURL property when starting recording');
}
}
function validateTo(to) {
@@ -206,15 +217,14 @@ async function validateCreateCall(logger, sid, req) {
}
else {
delete obj.application_sid;
// TODO: these should be retrieved from account, using account_sid if provided
Object.assign(obj, {
speech_synthesis_vendor: 'google',
speech_synthesis_voice: 'en-US-Wavenet-C',
speech_synthesis_language: 'en-US',
speech_recognizer_vendor: 'google',
speech_recognizer_language: 'en-US'
});
if (!obj.speech_synthesis_vendor ||
!obj.speech_synthesis_language ||
!obj.speech_synthesis_voice ||
!obj.speech_recognizer_vendor ||
!obj.speech_recognizer_language)
throw new DbErrorBadRequest('either application_sid or set of' +
' speech_synthesis_vendor, speech_synthesis_language, speech_synthesis_voice,' +
' speech_recognizer_vendor, speech_recognizer_language required');
}
if (!obj.call_hook && !obj.application_sid) {
@@ -600,29 +610,31 @@ router.post('/:sid/Calls', async(req, res) => {
const {retrieveSet, logger} = req.app.locals;
const serviceUrl = await getFsUrl(logger, retrieveSet, setName);
if (!serviceUrl) res.json({msg: 'no available feature servers at this time'}).status(480);
try {
await validateCreateCall(logger, sid, req);
updateLastUsed(logger, sid, req).catch((err) => {});
request({
url: serviceUrl,
method: 'POST',
json: true,
body: Object.assign(req.body, {account_sid: sid})
}, (err, response, body) => {
if (err) {
logger.error(err, `Error sending createCall POST to ${serviceUrl}`);
return res.sendStatus(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);
if (!serviceUrl) {
res.status(480).json({msg: 'no available feature servers at this time'});
} else {
try {
await validateCreateCall(logger, sid, req);
updateLastUsed(logger, sid, req).catch((err) => {});
request({
url: serviceUrl,
method: 'POST',
json: true,
body: Object.assign(req.body, {account_sid: sid})
}, (err, response, body) => {
if (err) {
logger.error(err, `Error sending createCall POST to ${serviceUrl}`);
return res.sendStatus(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);
}
}
});

View File

@@ -7,26 +7,47 @@ const parseAccountSid = (url) => {
if (arr) return arr[1];
};
const parseServiceProviderSid = (url) => {
const arr = /ServiceProviders\/([^\/]*)/.exec(url);
if (arr) return arr[1];
};
router.get('/', async(req, res) => {
const {logger, queryAlerts} = req.app.locals;
const {logger, queryAlerts, queryAlertsSP} = req.app.locals;
try {
logger.debug({opts: req.query}, 'GET /Alerts');
const account_sid = parseAccountSid(req.originalUrl);
const service_provider_sid = account_sid ? null : parseServiceProviderSid(req.originalUrl);
const {page, count, alert_type, days, start, end} = req.query || {};
if (!page || page < 1) throw new DbErrorBadRequest('missing or invalid "page" query arg');
if (!count || count < 25 || count > 500) throw new DbErrorBadRequest('missing or invalid "count" query arg');
const data = await queryAlerts({
account_sid,
page,
page_size: count,
alert_type,
days,
start: days ? undefined : start,
end: days ? undefined : end,
});
if (account_sid) {
const data = await queryAlerts({
account_sid,
page,
page_size: count,
alert_type,
days,
start: days ? undefined : start,
end: days ? undefined : end,
});
res.status(200).json(data);
res.status(200).json(data);
}
else {
const data = await queryAlertsSP({
service_provider_sid,
page,
page_size: count,
alert_type,
days,
start: days ? undefined : start,
end: days ? undefined : end,
});
res.status(200).json(data);
}
} catch (err) {
sysError(logger, res, err);
}

View File

@@ -104,7 +104,7 @@ router.get('/:sid', async(req, res) => {
const account_sid = req.user.hasAccountAuth ? req.user.account_sid : null;
const results = await Application.retrieve(req.params.sid, service_provider_sid, account_sid);
if (results.length === 0) return res.status(404).end();
return res.status(200).json(results);
return res.status(200).json(results[0]);
}
catch (err) {
sysError(logger, res, err);

128
lib/routes/api/limits.js Normal file
View File

@@ -0,0 +1,128 @@
const router = require('express').Router();
const sysError = require('../error');
const AccountLimits = require('../../models/account-limits');
const ServiceProviderLimits = require('../../models/service-provider-limits');
const {parseAccountSid, parseServiceProviderSid} = require('./utils');
const {promisePool} = require('../../db');
const sqlDeleteSPLimits = `
DELETE FROM service_provider_limits
WHERE service_provider_sid = ?
`;
const sqlDeleteSPLimitsByCategory = `
DELETE FROM service_provider_limits
WHERE service_provider_sid = ?
AND category = ?
`;
const sqlDeleteAccountLimits = `
DELETE FROM account_limits
WHERE account_sid = ?
`;
const sqlDeleteAccountLimitsByCategory = `
DELETE FROM account_limits
WHERE account_sid = ?
AND category = ?
`;
router.post('/', async(req, res) => {
const logger = req.app.locals.logger;
const {
category,
quantity
} = req.body;
const account_sid = parseAccountSid(req);
let service_provider_sid;
if (!account_sid) {
if (!req.user.hasServiceProviderAuth) {
logger.error('POST /SpeechCredentials invalid credentials');
return res.send(403);
}
service_provider_sid = parseServiceProviderSid(req);
}
try {
let uuid;
if (account_sid) {
const existing = (await AccountLimits.retrieve(account_sid) || [])
.find((el) => el.category === category);
if (existing) {
uuid = existing.account_limits_sid;
await AccountLimits.update(uuid, {category, quantity});
}
else {
uuid = await AccountLimits.make({
account_sid,
category,
quantity
});
}
}
else {
const existing = (await ServiceProviderLimits.retrieve(service_provider_sid) || [])
.find((el) => el.category === category);
if (existing) {
uuid = existing.service_provider_limits_sid;
await ServiceProviderLimits.update(uuid, {category, quantity});
}
else {
uuid = await ServiceProviderLimits.make({
service_provider_sid,
category,
quantity
});
}
}
res.status(201).json({sid: uuid});
} catch (err) {
sysError(logger, res, err);
}
});
/**
* retrieve all limits for an account or service provider
*/
router.get('/', async(req, res) => {
let service_provider_sid;
const account_sid = parseAccountSid(req);
if (!account_sid) service_provider_sid = parseServiceProviderSid(req);
const logger = req.app.locals.logger;
try {
const limits = account_sid ?
await AccountLimits.retrieve(account_sid) :
await ServiceProviderLimits.retrieve(service_provider_sid);
if (req.query?.category) {
return res.status(200).json(limits.filter((el) => el.category === req.query.category));
}
res.status(200).json(limits);
} catch (err) {
sysError(logger, res, err);
}
});
router.delete('/', async(req, res) => {
const logger = req.app.locals.logger;
const account_sid = parseAccountSid(req);
const {category} = req.query;
const service_provider_sid = parseServiceProviderSid(req);
try {
if (account_sid) {
if (category) {
await promisePool.execute(sqlDeleteAccountLimitsByCategory, [account_sid, category]);
}
else {
await promisePool.execute(sqlDeleteAccountLimits, [account_sid]);
}
}
else {
if (category) {
await promisePool.execute(sqlDeleteSPLimitsByCategory, [service_provider_sid, category]);
}
else {
await promisePool.execute(sqlDeleteSPLimits, [service_provider_sid]);
}
}
res.status(204).end();
} catch (err) {
sysError(logger, res, err);
}
});
module.exports = router;

View File

@@ -7,28 +7,49 @@ const parseAccountSid = (url) => {
if (arr) return arr[1];
};
const parseServiceProviderSid = (url) => {
const arr = /ServiceProviders\/([^\/]*)/.exec(url);
if (arr) return arr[1];
};
router.get('/', async(req, res) => {
const {logger, queryCdrs} = req.app.locals;
const {logger, queryCdrs, queryCdrsSP} = req.app.locals;
try {
logger.debug({opts: req.query}, 'GET /RecentCalls');
const account_sid = parseAccountSid(req.originalUrl);
const service_provider_sid = account_sid ? null : parseServiceProviderSid(req.originalUrl);
const {page, count, trunk, direction, days, answered, start, end} = req.query || {};
if (!page || page < 1) throw new DbErrorBadRequest('missing or invalid "page" query arg');
if (!count || count < 25 || count > 500) throw new DbErrorBadRequest('missing or invalid "count" query arg');
const data = await queryCdrs({
account_sid,
page,
page_size: count,
trunk,
direction,
days,
answered,
start: days ? undefined : start,
end: days ? undefined : end,
});
res.status(200).json(data);
if (account_sid) {
const data = await queryCdrs({
account_sid,
page,
page_size: count,
trunk,
direction,
days,
answered,
start: days ? undefined : start,
end: days ? undefined : end,
});
res.status(200).json(data);
}
else {
const data = await queryCdrsSP({
service_provider_sid,
page,
page_size: count,
trunk,
direction,
days,
answered,
start: days ? undefined : start,
end: days ? undefined : end,
});
res.status(200).json(data);
}
} catch (err) {
sysError(logger, res, err);
}

View File

@@ -41,7 +41,10 @@ async function noActiveAccounts(req, sid) {
decorate(router, ServiceProvider, ['delete'], preconditions);
router.use('/:sid/RecentCalls', hasServiceProviderPermissions, require('./recent-calls'));
router.use('/:sid/Alerts', hasServiceProviderPermissions, require('./alerts'));
router.use('/:sid/SpeechCredentials', hasServiceProviderPermissions, require('./speech-credentials'));
router.use('/:sid/Limits', hasServiceProviderPermissions, require('./limits'));
router.use('/:sid/PredefinedCarriers', hasServiceProviderPermissions, require('./add-from-predefined-carrier'));
router.get('/:sid/Accounts', async(req, res) => {
const logger = req.app.locals.logger;
@@ -127,7 +130,6 @@ router.get('/:sid/ApiKeys', async(req, res) => {
}
});
/* add */
router.post('/', async(req, res) => {
const logger = req.app.locals.logger;

View File

@@ -15,6 +15,16 @@ const {
testWellSaidTts
} = require('../../utils/speech-utils');
const obscureKey = (key) => {
const key_spoiler_length = 6;
const key_spoiler_char = 'X';
if (key.length <= key_spoiler_length) {
return key;
}
return `${key.slice(0, key_spoiler_length)}${key_spoiler_char.repeat(key.length - key_spoiler_length)}`;
};
const encryptCredential = (obj) => {
const {
@@ -110,24 +120,30 @@ router.get('/', async(req, res) => {
res.status(200).json(creds.map((c) => {
const {credential, ...obj} = c;
if ('google' === obj.vendor) {
obj.service_key = JSON.parse(decrypt(credential));
const o = JSON.parse(decrypt(credential));
const key_header = '-----BEGIN PRIVATE KEY-----\n';
const obscured = {
...o,
private_key: `${key_header}${obscureKey(o.private_key.slice(key_header.length, o.private_key.length))}`
};
obj.service_key = obscured;
}
else if ('aws' === obj.vendor) {
const o = JSON.parse(decrypt(credential));
obj.access_key_id = o.access_key_id;
obj.secret_access_key = o.secret_access_key;
obj.secret_access_key = obscureKey(o.secret_access_key);
obj.aws_region = o.aws_region;
logger.info({obj, o}, 'retrieving aws speech credential');
}
else if ('microsoft' === obj.vendor) {
const o = JSON.parse(decrypt(credential));
obj.api_key = o.api_key;
obj.api_key = obscureKey(o.api_key);
obj.region = o.region;
logger.info({obj, o}, 'retrieving azure speech credential');
}
else if ('wellsaid' === obj.vendor) {
const o = JSON.parse(decrypt(credential));
obj.api_key = o.api_key;
obj.api_key = obscureKey(o.api_key);
}
return obj;
}));
@@ -147,19 +163,29 @@ router.get('/:sid', async(req, res) => {
if (0 === cred.length) return res.sendStatus(404);
const {credential, ...obj} = cred[0];
if ('google' === obj.vendor) {
obj.service_key = decrypt(credential);
const o = JSON.parse(decrypt(credential));
const key_header = '-----BEGIN PRIVATE KEY-----\n';
const obscured = {
...o,
private_key: `${key_header}${obscureKey(o.private_key.slice(key_header.length, o.private_key.length))}`
};
obj.service_key = JSON.stringify(obscured);
}
else if ('aws' === obj.vendor) {
const o = JSON.parse(decrypt(credential));
obj.access_key_id = o.access_key_id;
obj.secret_access_key = o.secret_access_key;
obj.secret_access_key = obscureKey(o.secret_access_key);
obj.aws_region = o.aws_region;
}
else if ('microsoft' === obj.vendor) {
const o = JSON.parse(decrypt(credential));
obj.api_key = o.api_key;
obj.api_key = obscureKey(o.api_key);
obj.region = o.region;
}
else if ('wellsaid' === obj.vendor) {
const o = JSON.parse(decrypt(credential));
obj.api_key = obscureKey(o.api_key);
}
res.status(200).json(obj);
} catch (err) {
sysError(logger, res, err);

View File

@@ -126,7 +126,7 @@ const createTestAlerts = async(writeAlerts, AlertType, account_sid) => {
data.push({timestamp, account_sid, alert_type: AlertType.CARRIER_NOT_PROVISIONED});
break;
case 4:
data.push({timestamp, account_sid, alert_type: AlertType.CALL_LIMIT, count: 50});
data.push({timestamp, account_sid, alert_type: AlertType.ACCOUNT_CALL_LIMIT, count: 50});
break;
default:
break;

View File

@@ -1346,6 +1346,12 @@ paths:
schema:
type: object
properties:
account_sid:
type: string
format: uuid
application_sid:
type: string
format: uuid
number:
type: string
description: telephone number
@@ -1800,7 +1806,7 @@ paths:
summary: test a speech credential
operationId: testSpeechCredential
parameters:
- name: AccountSid
- name: ServiceProviderSid
in: path
required: true
schema:
@@ -1849,6 +1855,122 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/GeneralError'
/ServiceProviders/{ServiceProviderSid}/Limits:
post:
summary: create a limit for a service provider
operationId: addLimitForServiceProvider
parameters:
- name: ServiceProviderSid
in: path
required: true
schema:
type: string
format: uuid
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/Limits'
responses:
201:
description: limit successfully created or updated
content:
application/json:
schema:
$ref: '#/components/schemas/SuccessfulAdd'
404:
description: service provider not found
500:
description: system error
content:
application/json:
schema:
$ref: '#/components/schemas/GeneralError'
get:
summary: retrieve call capacity and other limits from the service provider
operationId: getServiceProviderLimits
parameters:
- name: ServiceProviderSid
in: path
required: true
schema:
type: string
format: uuid
responses:
200:
description: service provider limits
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/Limits'
404:
description: service provider not found
500:
description: system error
content:
application/json:
schema:
$ref: '#/components/schemas/GeneralError'
/Accounts/{AccountSid}/Limits:
post:
summary: create a limit for an account
operationId: addLimitForAccount
parameters:
- name: AccountSid
in: path
required: true
schema:
type: string
format: uuid
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/Limits'
responses:
201:
description: limit successfully created or updated
content:
application/json:
schema:
$ref: '#/components/schemas/SuccessfulAdd'
404:
description: account not found
500:
description: system error
content:
application/json:
schema:
$ref: '#/components/schemas/GeneralError'
get:
summary: retrieve call capacity and other limits from the account
operationId: getAccountLimits
parameters:
- name: AccountSid
in: path
required: true
schema:
type: string
format: uuid
responses:
200:
description: account limits
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/Limits'
404:
description: account not found
500:
description: system error
content:
application/json:
schema:
$ref: '#/components/schemas/GeneralError'
/MicrosoftTeamsTenants:
post:
summary: provision a customer tenant for MS Teams
@@ -2663,6 +2785,202 @@ paths:
type: object
404:
description: account or call not found
/ServiceProviders/{ServiceProviderSid}/RecentCalls/{CallId}/pcap:
parameters:
- name: ServiceProviderSid
in: path
required: true
schema:
type: string
format: uuid
- name: CallId
in: path
required: true
schema:
type: string
get:
summary: retrieve pcap for a call
operationId: getRecentCallTrace
responses:
200:
description: retrieve sip trace data
content:
application/octet-stream:
schema:
type: object
404:
description: account or call not found
/ServiceProviders/{ServiceProviderSid}/RecentCalls:
parameters:
- name: ServiceProviderSid
in: path
required: true
schema:
type: string
format: uuid
- in: query
name: page
required: true
schema:
type: number
format: integer
description: page number of data to retrieve
- in: query
name: count
required: true
schema:
type: number
format: integer
description: number of rows to retrieve in each page set
- in: query
name: days
required: false
schema:
type: number
format: integer
description: number of days back to retrieve, must be ge 1 and le 30
- in: query
name: start
required: false
schema:
type: string
format: date-time
description: start date to retrieve
- in: query
name: end
required: false
schema:
type: string
format: date-time
description: end date to retrieve
- in: query
name: answered
required: false
schema:
type: string
enum:
- true
- false
description: retrieve only answered calls
- in: query
name: direction
required: false
schema:
type: string
enum:
- inbound
- outbound
get:
summary: retrieve recent calls for an account
operationId: listRecentCalls
responses:
200:
description: retrieve recent call records for a specified account
content:
application/json:
schema:
type: object
properties:
total:
type: number
format: integer
description: total number of records in that database that match the filter criteria
batch:
type: number
format: integer
description: total number of records returned in this page set
page:
type: number
format: integer
description: page number that was requested, and is being returned
data:
type: array
items:
type: object
properties:
service_provider_sid:
type: string
format: uuid
account_sid:
type: string
format: uuid
call_sid:
type: string
format: uuid
from:
type: string
to:
type: string
answered:
type: boolean
sip_call_id:
type: string
sip_status:
type: number
format: integer
duration:
type: number
format: integer
attempted_at:
type: number
format: integer
answered_at:
type: number
format: integer
terminated_at:
type: number
format: integer
termination_reason:
type: string
host:
type: string
remote_host:
type: string
direction:
type: string
enum:
- inbound
- outbound
trunk:
type: string
required:
- account_sid
- call_sid
- attempted_at
- terminated_at
- answered
- direction
- from
- to
- sip_status
- duration
404:
description: account not found
/ServiceProviders/{ServiceProviderSid}/RecentCalls/{CallId}:
parameters:
- name: ServiceProviderSid
in: path
required: true
schema:
type: string
format: uuid
- name: CallId
in: path
required: true
schema:
type: string
get:
summary: retrieve sip trace detail for a call
operationId: getRecentCallTrace
responses:
200:
description: retrieve sip trace data
content:
application/json:
schema:
type: object
404:
description: service provider or call not found
/Accounts/{AccountSid}/RecentCalls/{CallId}/pcap:
parameters:
- name: AccountSid
@@ -2688,6 +3006,116 @@ paths:
type: object
404:
description: account or call not found
/ServiceProviders/{ServiceProviderSid}/Alerts:
parameters:
- name: ServiceProviderSid
in: path
required: true
schema:
type: string
format: uuid
- in: query
name: page
required: true
schema:
type: number
format: integer
description: page number of data to retrieve
- in: query
name: count
required: true
schema:
type: number
format: integer
description: number of rows to retrieve in each page set
- in: query
name: days
required: false
schema:
type: number
format: integer
description: number of days back to retrieve, must be ge 1 and le 30
- in: query
name: start
required: false
schema:
type: string
format: date-time
description: start date to retrieve
- in: query
name: end
required: false
schema:
type: string
format: date-time
description: end date to retrieve
- in: query
name: alert_type
required: false
schema:
type: string
enum:
- webhook-failure
- webhook-connection-failure
- webhook-auth-failure
- no-tts
- no-stt
- tts-failure
- stt-failure
- no-carrier
- call-limit
- device-limit
- api-limit
get:
summary: retrieve alerts for a service provider
operationId: listAlerts
responses:
200:
description: retrieve alerts for a specified account
content:
application/json:
schema:
type: object
properties:
total:
type: number
format: integer
description: total number of records in that database that match the filter criteria
batch:
type: number
format: integer
description: total number of records returned in this page set
page:
type: number
format: integer
description: page number that was requested, and is being returned
data:
type: array
items:
type: object
properties:
time:
type: string
format: date-time
service_provider_sid:
type: string
format: uuid
account_sid:
type: string
format: uuid
alert_type:
type: string
message:
type: string
detail:
type: string
required:
- time
- account_sid
- alert_type
- message
404:
description: service provider not found
/Accounts/{AccountSid}/Alerts:
parameters:
- name: AccountSid
@@ -3135,6 +3563,20 @@ paths:
type: string
headers:
type: object
record:
type: object
properties:
action:
type: string
enum:
- startCallRecording
- stopCallRecording
- pauseCallRecording
- resumeCallRecording
recordingID:
type: string
siprecServerURL:
type: string
responses:
200:
description: Accepted
@@ -4083,6 +4525,15 @@ components:
- requires_static_ip
- e164_leading_plus
- requires_register
Limits:
type: object
properties:
category:
type: string
enum:
- voice_call_session
- api_limit
- devices
security:
- bearerAuth: []

4247
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,11 +1,11 @@
{
"name": "jambonz-api-server",
"version": "v0.7.5",
"version": "v0.7.6",
"description": "",
"main": "app.js",
"scripts": {
"start": "node app.js",
"test": "NODE_ENV=test APPLY_JAMBONZ_DB_LIMITS=1 JWT_SECRET=foobarbazzle JAMBONES_MYSQL_HOST=127.0.0.1 JAMBONES_MYSQL_PORT=3360 JAMBONES_MYSQL_USER=jambones_test JAMBONES_MYSQL_PASSWORD=jambones_test JAMBONES_MYSQL_DATABASE=jambones_test JAMBONES_REDIS_HOST=localhost JAMBONES_REDIS_PORT=16379 JAMBONES_TIME_SERIES_HOST=127.0.0.1 JAMBONES_LOGLEVEL=error JAMBONES_CREATE_CALL_URL=http://localhost/v1/createCall node test/ ",
"test": "NODE_ENV=test APPLY_JAMBONZ_DB_LIMITS=1 JWT_SECRET=foobarbazzle JAMBONES_MYSQL_HOST=127.0.0.1 JAMBONES_MYSQL_PORT=3360 JAMBONES_MYSQL_USER=jambones_test JAMBONES_MYSQL_PASSWORD=jambones_test JAMBONES_MYSQL_DATABASE=jambones_test JAMBONES_REDIS_HOST=localhost JAMBONES_REDIS_PORT=16379 JAMBONES_TIME_SERIES_HOST=127.0.0.1 JAMBONES_LOGLEVEL=error JAMBONES_CREATE_CALL_URL=http://localhost/v1/createCall K8S=true K8S_FEATURE_SERVER_SERVICE_NAME=127.0.0.1 K8S_FEATURE_SERVER_SERVICE_PORT=3100 node test/ ",
"integration-test": "NODE_ENV=test JAMBONES_TIME_SERIES_HOST=127.0.0.1 AWS_REGION='us-east-1' JAMBONES_CURRENCY=USD JWT_SECRET=foobarbazzle JAMBONES_MYSQL_HOST=127.0.0.1 JAMBONES_MYSQL_PORT=3360 JAMBONES_MYSQL_USER=jambones_test JAMBONES_MYSQL_PASSWORD=jambones_test JAMBONES_MYSQL_DATABASE=jambones_test JAMBONES_REDIS_HOST=localhost JAMBONES_REDIS_PORT=16379 JAMBONES_LOGLEVEL=debug JAMBONES_CREATE_CALL_URL=http://localhost/v1/createCall node test/serve-integration.js",
"upgrade-db": "node ./db/upgrade-jambonz-db.js",
"coverage": "./node_modules/.bin/nyc --reporter html --report-dir ./coverage npm run test",
@@ -18,42 +18,40 @@
"url": "https://github.com/jambonz/jambonz-api-server.git"
},
"dependencies": {
"@google-cloud/speech": "^4.10.0",
"@google-cloud/speech": "^4.10.2",
"@google-cloud/text-to-speech": "^3.4.0",
"@jambonz/db-helpers": "^0.6.16",
"@jambonz/realtimedb-helpers": "^0.4.24",
"@jambonz/time-series": "^0.1.6",
"@jambonz/db-helpers": "^0.6.18",
"@jambonz/realtimedb-helpers": "^0.4.29",
"@jambonz/time-series": "^0.2.1",
"argon2-ffi": "^2.0.0",
"aws-sdk": "^2.1073.0",
"aws-sdk": "^2.1152.0",
"bent": "^7.3.12",
"cors": "^2.8.5",
"debug": "^4.3.3",
"express": "^4.17.2",
"express-rate-limit": "^6.3.0",
"debug": "^4.3.4",
"express": "^4.18.1",
"express-rate-limit": "^6.4.0",
"form-data": "^2.5.1",
"form-urlencoded": "^6.0.5",
"helmet": "^5.0.2",
"form-urlencoded": "^6.1.0",
"helmet": "^5.1.0",
"jsonwebtoken": "^8.5.1",
"mailgun.js": "^3.7.3",
"microsoft-cognitiveservices-speech-sdk": "^1.19.0",
"mysql2": "^2.3.3",
"passport": "^0.5.2",
"passport": "^0.6.0",
"passport-http-bearer": "^1.0.1",
"pino": "^5.17.0",
"request-debug": "^0.2.0",
"short-uuid": "^4.1.0",
"stripe": "^8.196.0",
"swagger-ui-express": "^4.3.0",
"stripe": "^8.222.0",
"swagger-ui-express": "^4.4.0",
"uuid": "^8.3.2",
"yamljs": "^0.3.0"
},
"devDependencies": {
"eslint": "^7.17.0",
"eslint": "^7.32.0",
"eslint-plugin-promise": "^4.2.1",
"husky": "7.0.4",
"nyc": "^15.1.0",
"request": "^2.88.2",
"request-promise-native": "^1.0.9",
"tape": "^5.2.2"
"tape": "^5.5.3"
}
}

View File

@@ -188,6 +188,60 @@ test('account tests', async(t) => {
});
t.ok(result.statusCode === 204, 'successfully assigned phone number to account');
/* query all limits for an account */
result = await request.get(`/Accounts/${sid}/Limits`, {
auth: authAdmin,
json: true,
});
t.ok(result.length === 0, 'successfully queried account limits when there is none configured');
/* add a new limit for a account */
result = await request.post(`/Accounts/${sid}/Limits`, {
auth: authAdmin,
json: true,
resolveWithFullResponse: true,
body: {
category: 'voice_call_session',
quantity: 200
}
});
t.ok(result.statusCode === 201, 'successfully added a call session limit to an account');
/* update an existing limit for a account */
result = await request.post(`/Accounts/${sid}/Limits`, {
auth: authAdmin,
json: true,
resolveWithFullResponse: true,
body: {
category: 'voice_call_session',
quantity: 205
}
});
t.ok(result.statusCode === 201, 'successfully updated a call session limit to an account');
/* query all limits for an account */
result = await request.get(`/Accounts/${sid}/Limits`, {
auth: authAdmin,
json: true,
});
//console.log(result);
t.ok(result.length === 1 && result[0].quantity === 205, 'successfully queried account limits');
/* query all limits for an account by category*/
result = await request.get(`/Accounts/${sid}/Limits?category=voice_call_session`, {
auth: authAdmin,
json: true,
});
//console.log(result);
t.ok(result.length === 1 && result[0].quantity === 205, 'successfully queried account limits by category');
/* delete call session limits for a service provider */
result = await request.delete(`/Accounts/${sid}/Limits?category=voice_call_session`, {
auth: authAdmin,
resolveWithFullResponse: true
});
t.ok(result.statusCode === 204, 'successfully deleted a call session limit for an account');
/* delete account */
result = await request.delete(`/Accounts/${sid}`, {
auth: authAdmin,

View File

@@ -60,8 +60,8 @@ test('application tests', async(t) => {
auth: authAdmin,
json: true,
});
t.ok(result[0].name === 'daveh' , 'successfully retrieved application by sid');
t.ok(result[0].messaging_hook.url === 'http://example.com/sms' , 'successfully retrieved messaging_hook from application');
t.ok(result.name === 'daveh' , 'successfully retrieved application by sid');
t.ok(result.messaging_hook.url === 'http://example.com/sms' , 'successfully retrieved messaging_hook from application');
/* update applications */
result = await request.put(`/Applications/${sid}`, {
@@ -84,7 +84,7 @@ test('application tests', async(t) => {
auth: authAdmin,
json: true,
});
t.ok(result[0].messaging_hook.url === 'http://example2.com/mms' , 'successfully updated messaging_hook');
t.ok(result.messaging_hook.url === 'http://example2.com/mms' , 'successfully updated messaging_hook');
/* assign phone number to application */
result = await request.put(`/PhoneNumbers/${phone_number_sid}`, {

View File

@@ -318,7 +318,7 @@ test('authentication tests', async(t) => {
json: true
});
//console.log(`result: ${JSON.stringify(result)}`);
t.ok(result.length === 1, 'using account token A1 we are able to retrieve application A1');
t.ok(result.name === "A1-app", 'using account token A1 we are able to retrieve application A1');
/* cannot see app under another account using account token */
result = await request.get(`/Applications/${appA2}`, {

82
test/call-test.js Normal file
View File

@@ -0,0 +1,82 @@
const test = require('tape');
const jwt = require('jsonwebtoken');
const request = require('request-promise-native').defaults({
baseUrl: 'http://127.0.0.1:3000/v1'
});
const consoleLogger = { debug: console.log, info: console.log, error: console.error }
const {
createServiceProvider,
createAccount,
createGoogleSpeechCredentials,
getLastRequestFromFeatureServer
} = require('./utils');
test('Create Call Success With Synthesizer in Payload', async (t) => {
// GIVEN
const app = require('../app');
let result;
const service_provider_sid = await createServiceProvider(request, 'account_has_synthesizer');
const account_sid = await createAccount(request, service_provider_sid, 'account_has_synthesizer');
const token = jwt.sign({
account_sid
}, process.env.JWT_SECRET, { expiresIn: '1h' });
const authUser = { bearer: token };
const speech_sid = await createGoogleSpeechCredentials(request, account_sid, null, authUser, true, true)
// WHEN
result = await request.post(`/Accounts/${account_sid}/Calls`, {
resolveWithFullResponse: true,
auth: authUser,
json: true,
body: {
call_hook: "https://public-apps.jambonz.us/hello-world",
call_status_hook: "https://public-apps.jambonz.us/call-status",
from: "15083778299",
to: {
type: "phone",
number: "15089084809"
},
speech_synthesis_vendor: "google",
speech_synthesis_language: "en-US",
speech_synthesis_voice: "en-US-Standard-C",
speech_recognizer_vendor: "google",
speech_recognizer_language: "en-US"
}
});
// THEN
t.ok(result.statusCode === 201, 'successfully created Call without Synthesizer && application_sid');
const fs_request = await getLastRequestFromFeatureServer('15083778299_createCall');
const obj = JSON.parse(fs_request);
t.ok(obj.body.speech_synthesis_vendor == 'google', 'speech synthesizer successfully added')
t.ok(obj.body.speech_recognizer_vendor == 'google', 'speech recognizer successfully added')
});
test('Create Call Success Without Synthesizer in Payload', async (t) => {
// GIVEN
const app = require('../app');
let result;
const service_provider_sid = await createServiceProvider(request, 'account2_has_synthesizer');
const account_sid = await createAccount(request, service_provider_sid, 'account2_has_synthesizer');
const token = jwt.sign({
account_sid
}, process.env.JWT_SECRET, { expiresIn: '1h' });
const authUser = { bearer: token };
const speech_sid = await createGoogleSpeechCredentials(request, account_sid, null, authUser, true, true)
// WHEN
result = await request.post(`/Accounts/${account_sid}/Calls`, {
resolveWithFullResponse: true,
auth: authUser,
json: true,
body: {
call_hook: "https://public-apps.jambonz.us/hello-world",
call_status_hook: "https://public-apps.jambonz.us/call-status",
from: "15083778299",
to: {
type: "phone",
number: "15089084809"
}
}
}).then(data => { t.ok(false, 'Create Call should not be success') })
.catch(error => { t.ok(error.response.statusCode === 400, 'Call failed for no synthesizer') });
});

View File

@@ -46,7 +46,6 @@ services:
db:
image: postgres:11-alpine
restart: always
environment:
POSTGRES_PASSWORD: homerSeven
POSTGRES_USER: root
@@ -127,6 +126,11 @@ services:
depends_on:
db:
condition: service_healthy
feature-server-test-scaffold:
image: jambonz/feature-server-test-scaffold:latest
ports:
- "3100:3000/tcp"
networks:
jambonz-api:
ipv4_address: 172.58.0.9

View File

@@ -0,0 +1,23 @@
FROM --platform=linux/amd64 node:18.6.0-alpine as base
RUN apk --update --no-cache add --virtual .builds-deps build-base python3
WORKDIR /opt/app/
FROM base as build
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
FROM base
COPY --from=build /opt/app /opt/app/
ARG NODE_ENV
ENV NODE_ENV $NODE_ENV
CMD [ "node", "app.js" ]

View File

@@ -0,0 +1,72 @@
const assert = require('assert');
const fs = require('fs');
const express = require('express');
const app = express();
const listenPort = process.env.HTTP_PORT || 3000;
const { v4: uuidv4 } = require('uuid');
let hook_mapping = new Map();
app.listen(listenPort, () => {
console.log(`sample jambones app server listening on ${listenPort}`);
});
app.use(express.urlencoded({ extended: true }));
app.use(express.json());
/*
* CreateCall
*/
app.all('/v1/createCall', (req, res) => {
console.log(req.body, 'POST /v1/createCall');
const key = req.body.from + '_createCall'
addRequestToMap(key, req, hook_mapping);
res.status(201).json({
sid: uuidv4(),
callId: uuidv4()
});
});
// Fetch Requests
app.get('/requests/:key', (req, res) => {
let key = req.params.key;
if (hook_mapping.has(key)) {
return res.json(hook_mapping.get(key));
} else {
return res.sendStatus(404);
}
})
app.get('/lastRequest/:key', (req, res) => {
let key = req.params.key;
if (hook_mapping.has(key)) {
let requests = hook_mapping.get(key);
return res.json(requests[requests.length - 1]);
} else {
return res.sendStatus(404);
}
})
/*
* private function
*/
function addRequestToMap(key, req, map) {
let headers = new Map()
for(let i = 0; i < req.rawHeaders.length; i++) {
if (i % 2 === 0) {
headers.set(req.rawHeaders[i], req.rawHeaders[i + 1])
}
}
let request = {
'url': req.url,
'headers': Object.fromEntries(headers),
'body': req.body
}
if (map.has(key)) {
map.get(key).push(request);
} else {
map.set(key, [request]);
}
}

View File

@@ -0,0 +1,873 @@
{
"name": "webhook",
"version": "1.0.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "webhook",
"version": "1.0.0",
"license": "MIT",
"dependencies": {
"express": "^4.17.1",
"uuid": "^8.3.2"
}
},
"node_modules/accepts": {
"version": "1.3.7",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz",
"integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==",
"dependencies": {
"mime-types": "~2.1.24",
"negotiator": "0.6.2"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/array-flatten": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
"integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI="
},
"node_modules/body-parser": {
"version": "1.19.0",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz",
"integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==",
"dependencies": {
"bytes": "3.1.0",
"content-type": "~1.0.4",
"debug": "2.6.9",
"depd": "~1.1.2",
"http-errors": "1.7.2",
"iconv-lite": "0.4.24",
"on-finished": "~2.3.0",
"qs": "6.7.0",
"raw-body": "2.4.0",
"type-is": "~1.6.17"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/bytes": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz",
"integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/content-disposition": {
"version": "0.5.3",
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz",
"integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==",
"dependencies": {
"safe-buffer": "5.1.2"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/content-type": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz",
"integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/cookie": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz",
"integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/cookie-signature": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
"integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw="
},
"node_modules/debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"dependencies": {
"ms": "2.0.0"
}
},
"node_modules/depd": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
"integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/destroy": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz",
"integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA="
},
"node_modules/ee-first": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
"integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0="
},
"node_modules/encodeurl": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
"integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/escape-html": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
"integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg="
},
"node_modules/etag": {
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
"integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/express": {
"version": "4.17.1",
"resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz",
"integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==",
"dependencies": {
"accepts": "~1.3.7",
"array-flatten": "1.1.1",
"body-parser": "1.19.0",
"content-disposition": "0.5.3",
"content-type": "~1.0.4",
"cookie": "0.4.0",
"cookie-signature": "1.0.6",
"debug": "2.6.9",
"depd": "~1.1.2",
"encodeurl": "~1.0.2",
"escape-html": "~1.0.3",
"etag": "~1.8.1",
"finalhandler": "~1.1.2",
"fresh": "0.5.2",
"merge-descriptors": "1.0.1",
"methods": "~1.1.2",
"on-finished": "~2.3.0",
"parseurl": "~1.3.3",
"path-to-regexp": "0.1.7",
"proxy-addr": "~2.0.5",
"qs": "6.7.0",
"range-parser": "~1.2.1",
"safe-buffer": "5.1.2",
"send": "0.17.1",
"serve-static": "1.14.1",
"setprototypeof": "1.1.1",
"statuses": "~1.5.0",
"type-is": "~1.6.18",
"utils-merge": "1.0.1",
"vary": "~1.1.2"
},
"engines": {
"node": ">= 0.10.0"
}
},
"node_modules/finalhandler": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz",
"integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==",
"dependencies": {
"debug": "2.6.9",
"encodeurl": "~1.0.2",
"escape-html": "~1.0.3",
"on-finished": "~2.3.0",
"parseurl": "~1.3.3",
"statuses": "~1.5.0",
"unpipe": "~1.0.0"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/forwarded": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz",
"integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/fresh": {
"version": "0.5.2",
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
"integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/http-errors": {
"version": "1.7.2",
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz",
"integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==",
"dependencies": {
"depd": "~1.1.2",
"inherits": "2.0.3",
"setprototypeof": "1.1.1",
"statuses": ">= 1.5.0 < 2",
"toidentifier": "1.0.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/iconv-lite": {
"version": "0.4.24",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
"dependencies": {
"safer-buffer": ">= 2.1.2 < 3"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/inherits": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
},
"node_modules/ipaddr.js": {
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
"integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
"engines": {
"node": ">= 0.10"
}
},
"node_modules/media-typer": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
"integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/merge-descriptors": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
"integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E="
},
"node_modules/methods": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
"integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mime": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
"integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
"bin": {
"mime": "cli.js"
},
"engines": {
"node": ">=4"
}
},
"node_modules/mime-db": {
"version": "1.45.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.45.0.tgz",
"integrity": "sha512-CkqLUxUk15hofLoLyljJSrukZi8mAtgd+yE5uO4tqRZsdsAJKv0O+rFMhVDRJgozy+yG6md5KwuXhD4ocIoP+w==",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mime-types": {
"version": "2.1.28",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.28.tgz",
"integrity": "sha512-0TO2yJ5YHYr7M2zzT7gDU1tbwHxEUWBCLt0lscSNpcdAfFyJOVEpRYNS7EXVcTLNj/25QO8gulHC5JtTzSE2UQ==",
"dependencies": {
"mime-db": "1.45.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
},
"node_modules/negotiator": {
"version": "0.6.2",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz",
"integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/on-finished": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
"integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=",
"dependencies": {
"ee-first": "1.1.1"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/parseurl": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
"integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/path-to-regexp": {
"version": "0.1.7",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
"integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w="
},
"node_modules/proxy-addr": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz",
"integrity": "sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw==",
"dependencies": {
"forwarded": "~0.1.2",
"ipaddr.js": "1.9.1"
},
"engines": {
"node": ">= 0.10"
}
},
"node_modules/qs": {
"version": "6.7.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz",
"integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==",
"engines": {
"node": ">=0.6"
}
},
"node_modules/range-parser": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
"integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/raw-body": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz",
"integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==",
"dependencies": {
"bytes": "3.1.0",
"http-errors": "1.7.2",
"iconv-lite": "0.4.24",
"unpipe": "1.0.0"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/safe-buffer": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
},
"node_modules/safer-buffer": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
},
"node_modules/send": {
"version": "0.17.1",
"resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz",
"integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==",
"dependencies": {
"debug": "2.6.9",
"depd": "~1.1.2",
"destroy": "~1.0.4",
"encodeurl": "~1.0.2",
"escape-html": "~1.0.3",
"etag": "~1.8.1",
"fresh": "0.5.2",
"http-errors": "~1.7.2",
"mime": "1.6.0",
"ms": "2.1.1",
"on-finished": "~2.3.0",
"range-parser": "~1.2.1",
"statuses": "~1.5.0"
},
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/send/node_modules/ms": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
"integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg=="
},
"node_modules/serve-static": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz",
"integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==",
"dependencies": {
"encodeurl": "~1.0.2",
"escape-html": "~1.0.3",
"parseurl": "~1.3.3",
"send": "0.17.1"
},
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/setprototypeof": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz",
"integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw=="
},
"node_modules/statuses": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
"integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/toidentifier": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz",
"integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==",
"engines": {
"node": ">=0.6"
}
},
"node_modules/type-is": {
"version": "1.6.18",
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
"integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
"dependencies": {
"media-typer": "0.3.0",
"mime-types": "~2.1.24"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/unpipe": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
"integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/utils-merge": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
"integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=",
"engines": {
"node": ">= 0.4.0"
}
},
"node_modules/uuid": {
"version": "8.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
"bin": {
"uuid": "dist/bin/uuid"
}
},
"node_modules/vary": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
"integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=",
"engines": {
"node": ">= 0.8"
}
}
},
"dependencies": {
"accepts": {
"version": "1.3.7",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz",
"integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==",
"requires": {
"mime-types": "~2.1.24",
"negotiator": "0.6.2"
}
},
"array-flatten": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
"integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI="
},
"body-parser": {
"version": "1.19.0",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz",
"integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==",
"requires": {
"bytes": "3.1.0",
"content-type": "~1.0.4",
"debug": "2.6.9",
"depd": "~1.1.2",
"http-errors": "1.7.2",
"iconv-lite": "0.4.24",
"on-finished": "~2.3.0",
"qs": "6.7.0",
"raw-body": "2.4.0",
"type-is": "~1.6.17"
}
},
"bytes": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz",
"integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg=="
},
"content-disposition": {
"version": "0.5.3",
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz",
"integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==",
"requires": {
"safe-buffer": "5.1.2"
}
},
"content-type": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz",
"integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA=="
},
"cookie": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz",
"integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg=="
},
"cookie-signature": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
"integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw="
},
"debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"requires": {
"ms": "2.0.0"
}
},
"depd": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
"integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak="
},
"destroy": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz",
"integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA="
},
"ee-first": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
"integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0="
},
"encodeurl": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
"integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k="
},
"escape-html": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
"integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg="
},
"etag": {
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
"integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc="
},
"express": {
"version": "4.17.1",
"resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz",
"integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==",
"requires": {
"accepts": "~1.3.7",
"array-flatten": "1.1.1",
"body-parser": "1.19.0",
"content-disposition": "0.5.3",
"content-type": "~1.0.4",
"cookie": "0.4.0",
"cookie-signature": "1.0.6",
"debug": "2.6.9",
"depd": "~1.1.2",
"encodeurl": "~1.0.2",
"escape-html": "~1.0.3",
"etag": "~1.8.1",
"finalhandler": "~1.1.2",
"fresh": "0.5.2",
"merge-descriptors": "1.0.1",
"methods": "~1.1.2",
"on-finished": "~2.3.0",
"parseurl": "~1.3.3",
"path-to-regexp": "0.1.7",
"proxy-addr": "~2.0.5",
"qs": "6.7.0",
"range-parser": "~1.2.1",
"safe-buffer": "5.1.2",
"send": "0.17.1",
"serve-static": "1.14.1",
"setprototypeof": "1.1.1",
"statuses": "~1.5.0",
"type-is": "~1.6.18",
"utils-merge": "1.0.1",
"vary": "~1.1.2"
}
},
"finalhandler": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz",
"integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==",
"requires": {
"debug": "2.6.9",
"encodeurl": "~1.0.2",
"escape-html": "~1.0.3",
"on-finished": "~2.3.0",
"parseurl": "~1.3.3",
"statuses": "~1.5.0",
"unpipe": "~1.0.0"
}
},
"forwarded": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz",
"integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ="
},
"fresh": {
"version": "0.5.2",
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
"integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac="
},
"http-errors": {
"version": "1.7.2",
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz",
"integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==",
"requires": {
"depd": "~1.1.2",
"inherits": "2.0.3",
"setprototypeof": "1.1.1",
"statuses": ">= 1.5.0 < 2",
"toidentifier": "1.0.0"
}
},
"iconv-lite": {
"version": "0.4.24",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
"requires": {
"safer-buffer": ">= 2.1.2 < 3"
}
},
"inherits": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
},
"ipaddr.js": {
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
"integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="
},
"media-typer": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
"integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g="
},
"merge-descriptors": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
"integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E="
},
"methods": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
"integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4="
},
"mime": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
"integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg=="
},
"mime-db": {
"version": "1.45.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.45.0.tgz",
"integrity": "sha512-CkqLUxUk15hofLoLyljJSrukZi8mAtgd+yE5uO4tqRZsdsAJKv0O+rFMhVDRJgozy+yG6md5KwuXhD4ocIoP+w=="
},
"mime-types": {
"version": "2.1.28",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.28.tgz",
"integrity": "sha512-0TO2yJ5YHYr7M2zzT7gDU1tbwHxEUWBCLt0lscSNpcdAfFyJOVEpRYNS7EXVcTLNj/25QO8gulHC5JtTzSE2UQ==",
"requires": {
"mime-db": "1.45.0"
}
},
"ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
},
"negotiator": {
"version": "0.6.2",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz",
"integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw=="
},
"on-finished": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
"integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=",
"requires": {
"ee-first": "1.1.1"
}
},
"parseurl": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
"integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="
},
"path-to-regexp": {
"version": "0.1.7",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
"integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w="
},
"proxy-addr": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz",
"integrity": "sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw==",
"requires": {
"forwarded": "~0.1.2",
"ipaddr.js": "1.9.1"
}
},
"qs": {
"version": "6.7.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz",
"integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ=="
},
"range-parser": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
"integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="
},
"raw-body": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz",
"integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==",
"requires": {
"bytes": "3.1.0",
"http-errors": "1.7.2",
"iconv-lite": "0.4.24",
"unpipe": "1.0.0"
}
},
"safe-buffer": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
},
"safer-buffer": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
},
"send": {
"version": "0.17.1",
"resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz",
"integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==",
"requires": {
"debug": "2.6.9",
"depd": "~1.1.2",
"destroy": "~1.0.4",
"encodeurl": "~1.0.2",
"escape-html": "~1.0.3",
"etag": "~1.8.1",
"fresh": "0.5.2",
"http-errors": "~1.7.2",
"mime": "1.6.0",
"ms": "2.1.1",
"on-finished": "~2.3.0",
"range-parser": "~1.2.1",
"statuses": "~1.5.0"
},
"dependencies": {
"ms": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
"integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg=="
}
}
},
"serve-static": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz",
"integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==",
"requires": {
"encodeurl": "~1.0.2",
"escape-html": "~1.0.3",
"parseurl": "~1.3.3",
"send": "0.17.1"
}
},
"setprototypeof": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz",
"integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw=="
},
"statuses": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
"integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow="
},
"toidentifier": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz",
"integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw=="
},
"type-is": {
"version": "1.6.18",
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
"integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
"requires": {
"media-typer": "0.3.0",
"mime-types": "~2.1.24"
}
},
"unpipe": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
"integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw="
},
"utils-merge": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
"integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM="
},
"uuid": {
"version": "8.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="
},
"vary": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
"integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw="
}
}
}

View File

@@ -0,0 +1,15 @@
{
"name": "webhook",
"version": "1.0.0",
"description": "simple webhook app for test purposes",
"main": "app.js",
"scripts": {
"start": "node app"
},
"author": "Dave Horton",
"license": "MIT",
"dependencies": {
"express": "^4.17.1",
"uuid": "^8.3.2"
}
}

View File

@@ -13,5 +13,6 @@ require('./ms-teams');
require('./speech-credentials');
require('./recent-calls');
require('./webapp_tests');
//require('./homer');
// require('./homer');
require('./call-test');
require('./docker_stop');

View File

@@ -28,13 +28,18 @@ test('recent calls tests', async(t) => {
}, process.env.JWT_SECRET, { expiresIn: '1h' });
const authUser = {bearer: token};
const tokenSP = jwt.sign({
service_provider_sid
}, process.env.JWT_SECRET, { expiresIn: '1h' });
const authUserSP = {bearer: token};
/* write sample cdr data */
const points = 500;
const data = [];
const start = new Date(Date.now() - (30 * 24 * 60 * 60 * 1000));
const now = new Date();
const increment = (now.getTime() - start.getTime()) / points;
for (let i =0 ; i < 500; i++) {
for (let i =0 ; i < 5; i++) {
const attempted_at = new Date(start.getTime() + (i * increment));
const failed = 0 === i % 5;
data.push({
@@ -51,7 +56,8 @@ test('recent calls tests', async(t) => {
termination_reason: 'caller hungup',
host: "192.168.1.100",
remote_host: '3.55.24.34',
account_sid: account_sid,
account_sid,
service_provider_sid,
direction: 0 === i % 2 ? 'inbound' : 'outbound',
trunk: 0 === i % 2 ? 'twilio' : 'user'
});
@@ -60,11 +66,21 @@ test('recent calls tests', async(t) => {
await writeCdrs(data);
t.pass('seeded cdr data');
/* query last 7 days */
/* query last 7 days by account */
result = await request.get(`/Accounts/${account_sid}/RecentCalls?page=1&count=25`, {
auth: authUser,
json: true,
});
t.ok(result.data.length === 5, 'retrieved 5 recent calls by account');
//console.log({data: result.data}, 'Account recent calls');
/* query last 7 days by service provider */
result = await request.get(`/ServiceProviders/${service_provider_sid}/RecentCalls?page=1&count=25`, {
auth: authAdmin,
json: true,
});
t.ok(result.data.length === 5, 'retrieved 5 recent calls by service provider');
//console.log({data: result.data}, 'SP recent calls');
/* pull sip traces and pcap from homer */
/*

View File

@@ -140,9 +140,35 @@ test('service provider tests', async(t) => {
resolveWithFullResponse: true,
});
t.ok(result.statusCode === 201, 'successfully added predefined carrier to service provider');
await deleteObjectBySid(request, '/VoipCarriers', result.body.sid);
/* add a limit for a service provider */
result = await request.post(`/ServiceProviders/${sid}/Limits`, {
auth: authAdmin,
json: true,
resolveWithFullResponse: true,
body: {
category: 'voice_call_session',
quantity: 1000
}
});
t.ok(result.statusCode === 201, 'successfully added a call session limit to service provider');
/* query all limits for a service provider */
result = await request.get(`/ServiceProviders/${sid}/Limits`, {
auth: authAdmin,
json: true,
});
//console.log(result);
t.ok(result.length === 1 , 'successfully queried all limits');
/* delete call session limits for a service provider */
result = await request.delete(`/ServiceProviders/${sid}/Limits?category=voice_call_session`, {
auth: authAdmin,
resolveWithFullResponse: true
});
t.ok(result.statusCode === 204, 'successfully deleted a call session limit for a service provider');
/* delete service providers */
result = await request.delete(`/ServiceProviders/${sid}`, {
auth: authAdmin,

View File

@@ -1,4 +1,8 @@
const { v4: uuid } = require('uuid');
const fs = require('fs');
const request_fs_mock = require('request-promise-native').defaults({
baseUrl: 'http://127.0.0.1:3100'
});
const ADMIN_TOKEN = '38700987-c7a4-4685-a5bb-af378f9734de';
const authAdmin = {bearer: ADMIN_TOKEN};
@@ -87,6 +91,42 @@ async function deleteObjectBySid(request, path, sid) {
return result;
}
async function createGoogleSpeechCredentials(request, account_sid, service_provider_sid,token, use_tts, use_stt) {
const jsonKey = fs.readFileSync(`${__dirname}/data/test.json`, {encoding: 'utf8'});
if(account_sid) {
const result = await request.post(`/Accounts/${account_sid}/SpeechCredentials`, {
auth: token ? token : authAdmin,
json: true,
body: {
vendor: 'google',
service_key: jsonKey,
use_for_tts: use_tts,
use_for_stt: use_stt
}
});
return result.sid;
} else if(service_provider_sid) {
const result = await request.post(`/ServiceProviders/${service_provider_sid}/SpeechCredentials`, {
auth: token ? token : authAdmin,
json: true,
body: {
vendor: 'google',
service_key: jsonKey,
use_for_tts: use_tts,
use_for_stt: use_stt
}
});
return result.sid;
}
}
async function getLastRequestFromFeatureServer(key) {
const result = await request_fs_mock.get(`/lastRequest/${key}`);
return result;
}
module.exports = {
createServiceProvider,
createVoipCarrier,
@@ -94,5 +134,7 @@ module.exports = {
createAccount,
createApplication,
createApiKey,
deleteObjectBySid
deleteObjectBySid,
createGoogleSpeechCredentials,
getLastRequestFromFeatureServer
};