mirror of
https://github.com/jambonz/jambonz-api-server.git
synced 2026-01-25 02:08:24 +00:00
Compare commits
9 Commits
v0.7.6
...
implement-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ec73bcef53 | ||
|
|
28d9bbf98e | ||
|
|
e5793a85f2 | ||
|
|
31db9131b8 | ||
|
|
c1a2c6c591 | ||
|
|
05c46c5f39 | ||
|
|
052a19cfdc | ||
|
|
0a01755a21 | ||
|
|
ace9e6a4fc |
@@ -1,4 +1,4 @@
|
||||
FROM --platform=linux/amd64 node:18.6.0-alpine as base
|
||||
FROM --platform=linux/amd64 node:18.8.0-alpine as base
|
||||
|
||||
RUN apk --update --no-cache add --virtual .builds-deps build-base python3
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM --platform=linux/amd64 node:18.6.0-alpine as base
|
||||
FROM --platform=linux/amd64 node:18.8.0-alpine as base
|
||||
|
||||
RUN apk --update --no-cache add --virtual .builds-deps build-base python3
|
||||
|
||||
|
||||
23
app.js
23
app.js
@@ -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());
|
||||
|
||||
@@ -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 ,
|
||||
@@ -434,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);
|
||||
@@ -461,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);
|
||||
@@ -482,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);
|
||||
|
||||
@@ -529,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);
|
||||
|
||||
@@ -550,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);
|
||||
|
||||
@@ -569,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);
|
||||
|
||||
|
||||
136
db/jambones.sqs
136
db/jambones.sqs
@@ -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>
|
||||
@@ -1599,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>
|
||||
@@ -2146,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>
|
||||
@@ -2455,7 +2557,7 @@
|
||||
<windowHeight><![CDATA[833.000000]]></windowHeight>
|
||||
<windowLocationX><![CDATA[0.000000]]></windowLocationX>
|
||||
<windowLocationY><![CDATA[111.000000]]></windowLocationY>
|
||||
<windowScrollOrigin><![CDATA[{172, 109}]]></windowScrollOrigin>
|
||||
<windowScrollOrigin><![CDATA[{462, 81}]]></windowScrollOrigin>
|
||||
<windowWidth><![CDATA[1512.000000]]></windowWidth>
|
||||
</SQLDocumentInfo>
|
||||
<AllowsIndexRenamingOnInsert><![CDATA[1]]></AllowsIndexRenamingOnInsert>
|
||||
|
||||
@@ -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');
|
||||
@@ -25,8 +26,34 @@ const opts = {
|
||||
const sql = {
|
||||
'7006': [
|
||||
'ALTER TABLE `accounts` ADD COLUMN `siprec_hook_sid` CHAR(36)',
|
||||
// eslint-disable-next-line max-len
|
||||
'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`
|
||||
]
|
||||
};
|
||||
|
||||
@@ -54,6 +81,7 @@ const doIt = async() => {
|
||||
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..');
|
||||
|
||||
41
lib/models/account-limits.js
Normal file
41
lib/models/account-limits.js
Normal 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;
|
||||
39
lib/models/service-provider-limits.js
Normal file
39
lib/models/service-provider-limits.js
Normal 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;
|
||||
@@ -51,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 {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
128
lib/routes/api/limits.js
Normal file
128
lib/routes/api/limits.js
Normal 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;
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -1806,7 +1806,7 @@ paths:
|
||||
summary: test a speech credential
|
||||
operationId: testSpeechCredential
|
||||
parameters:
|
||||
- name: AccountSid
|
||||
- name: ServiceProviderSid
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
@@ -1855,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
|
||||
@@ -2669,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
|
||||
@@ -2694,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
|
||||
@@ -4103,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: []
|
||||
14
package-lock.json
generated
14
package-lock.json
generated
@@ -13,7 +13,7 @@
|
||||
"@google-cloud/text-to-speech": "^3.4.0",
|
||||
"@jambonz/db-helpers": "^0.6.18",
|
||||
"@jambonz/realtimedb-helpers": "^0.4.29",
|
||||
"@jambonz/time-series": "^0.1.12",
|
||||
"@jambonz/time-series": "^0.2.1",
|
||||
"argon2-ffi": "^2.0.0",
|
||||
"aws-sdk": "^2.1152.0",
|
||||
"bent": "^7.3.12",
|
||||
@@ -865,9 +865,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@jambonz/time-series": {
|
||||
"version": "0.1.12",
|
||||
"resolved": "https://registry.npmjs.org/@jambonz/time-series/-/time-series-0.1.12.tgz",
|
||||
"integrity": "sha512-TmCG4jcI8oK3NXOc4/PbdRhhMVLEr5FOyG4IIWpNlwB0vbjAGLY3K+O5PF4fXK+UcNYnIrUcrd2C0J9z3+YBxw==",
|
||||
"version": "0.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@jambonz/time-series/-/time-series-0.2.1.tgz",
|
||||
"integrity": "sha512-uAoeZ3ibS7kEOGdT+vaY8BB8hOV4q38eEaF+d5OvLQaHCrPonNiwB8tWhhXDwtYdDompfqVRUy/plNA9fyS7Vw==",
|
||||
"dependencies": {
|
||||
"debug": "^4.3.1",
|
||||
"influx": "^5.9.3"
|
||||
@@ -7120,9 +7120,9 @@
|
||||
}
|
||||
},
|
||||
"@jambonz/time-series": {
|
||||
"version": "0.1.12",
|
||||
"resolved": "https://registry.npmjs.org/@jambonz/time-series/-/time-series-0.1.12.tgz",
|
||||
"integrity": "sha512-TmCG4jcI8oK3NXOc4/PbdRhhMVLEr5FOyG4IIWpNlwB0vbjAGLY3K+O5PF4fXK+UcNYnIrUcrd2C0J9z3+YBxw==",
|
||||
"version": "0.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@jambonz/time-series/-/time-series-0.2.1.tgz",
|
||||
"integrity": "sha512-uAoeZ3ibS7kEOGdT+vaY8BB8hOV4q38eEaF+d5OvLQaHCrPonNiwB8tWhhXDwtYdDompfqVRUy/plNA9fyS7Vw==",
|
||||
"requires": {
|
||||
"debug": "^4.3.1",
|
||||
"influx": "^5.9.3"
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
"@google-cloud/text-to-speech": "^3.4.0",
|
||||
"@jambonz/db-helpers": "^0.6.18",
|
||||
"@jambonz/realtimedb-helpers": "^0.4.29",
|
||||
"@jambonz/time-series": "^0.1.12",
|
||||
"@jambonz/time-series": "^0.2.1",
|
||||
"argon2-ffi": "^2.0.0",
|
||||
"aws-sdk": "^2.1152.0",
|
||||
"bent": "^7.3.12",
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 */
|
||||
/*
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user