mirror of
https://github.com/jambonz/jambonz-api-server.git
synced 2026-01-25 02:08:24 +00:00
Compare commits
75 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6db73ea5a3 | ||
|
|
a805eaed32 | ||
|
|
4eab0e412c | ||
|
|
f4f1827019 | ||
|
|
8263b46fff | ||
|
|
b6699a37c3 | ||
|
|
0fc3c95cc5 | ||
|
|
aeebf6aee0 | ||
|
|
8026f45467 | ||
|
|
505884e68e | ||
|
|
9e4d6eb88b | ||
|
|
e1cfbe5010 | ||
|
|
b8dc0b2f05 | ||
|
|
110c4ed0d8 | ||
|
|
7890de8c8f | ||
|
|
b65dc7080c | ||
|
|
9d0be0f8e1 | ||
|
|
81cae89387 | ||
|
|
0811002c05 | ||
|
|
b465e0b8cf | ||
|
|
08edae376e | ||
|
|
b7a38c843a | ||
|
|
36a1d4bef1 | ||
|
|
e067fc2cf4 | ||
|
|
613b801ba9 | ||
|
|
67aff2e2a9 | ||
|
|
fe7f0900ce | ||
|
|
b6e6f6dd94 | ||
|
|
05c46c5f39 | ||
|
|
052a19cfdc | ||
|
|
0a01755a21 | ||
|
|
ace9e6a4fc | ||
|
|
2db8cb4d3a | ||
|
|
42b29af0a1 | ||
|
|
6ac2751b56 | ||
|
|
c5b1b36f28 | ||
|
|
08163a31d0 | ||
|
|
1898ba501d | ||
|
|
6f52202deb | ||
|
|
0ceb79b568 | ||
|
|
1e396266a0 | ||
|
|
69f36f5a51 | ||
|
|
f83cc25098 | ||
|
|
1473c30d7b | ||
|
|
6088b99acf | ||
|
|
9aab716dc9 | ||
|
|
56b646b6db | ||
|
|
eeb23109dc | ||
|
|
17d1be4605 | ||
|
|
2324890b72 | ||
|
|
4097ca2125 | ||
|
|
9a126f396e | ||
|
|
d32a042c5d | ||
|
|
a129c3c927 | ||
|
|
a3403de45a | ||
|
|
e2408b2511 | ||
|
|
c432b71a64 | ||
|
|
77f945bc6b | ||
|
|
8c54e80d46 | ||
|
|
815aea5c75 | ||
|
|
8097f0afda | ||
|
|
31a98d5c81 | ||
|
|
9205cd76a7 | ||
|
|
090bfbce92 | ||
|
|
038e1d3917 | ||
|
|
252de64d10 | ||
|
|
c65e50e79f | ||
|
|
cd935999a6 | ||
|
|
863d7a02c8 | ||
|
|
45c023e374 | ||
|
|
f634ca4076 | ||
|
|
654f93b30c | ||
|
|
280aaef120 | ||
|
|
e6931574e3 | ||
|
|
1bd21cb39d |
@@ -8,7 +8,7 @@
|
||||
"jsx": false,
|
||||
"modules": false
|
||||
},
|
||||
"ecmaVersion": 2018
|
||||
"ecmaVersion": 2020
|
||||
},
|
||||
"plugins": ["promise"],
|
||||
"rules": {
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,6 +1,7 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
run-tests.sh
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
|
||||
4
.husky/pre-commit
Executable file
4
.husky/pre-commit
Executable file
@@ -0,0 +1,4 @@
|
||||
#!/bin/sh
|
||||
. "$(dirname "$0")/_/husky.sh"
|
||||
|
||||
npm run jslint
|
||||
25
Dockerfile
25
Dockerfile
@@ -1,10 +1,23 @@
|
||||
FROM node:17
|
||||
FROM --platform=linux/amd64 node:18.12.1-alpine3.16 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" ]
|
||||
@@ -1,10 +1,23 @@
|
||||
FROM node:17
|
||||
FROM --platform=linux/amd64 node:18.9.0-alpine3.16 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" ]
|
||||
37
app.js
37
app.js
@@ -9,7 +9,8 @@ const opts = Object.assign({
|
||||
const logger = require('pino')(opts);
|
||||
const express = require('express');
|
||||
const app = express();
|
||||
app.disable('x-powered-by');
|
||||
const helmet = require('helmet');
|
||||
const rateLimit = require('express-rate-limit');
|
||||
const cors = require('cors');
|
||||
const passport = require('passport');
|
||||
const routes = require('./lib/routes');
|
||||
@@ -20,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 {
|
||||
@@ -77,7 +86,9 @@ app.locals = {
|
||||
lookupSipGatewayBySid,
|
||||
lookupSmppGatewayBySid,
|
||||
queryCdrs,
|
||||
queryCdrsSP,
|
||||
queryAlerts,
|
||||
queryAlertsSP,
|
||||
writeCdrs,
|
||||
writeAlerts,
|
||||
AlertType
|
||||
@@ -89,6 +100,28 @@ const unless = (paths, middleware) => {
|
||||
return middleware(req, res, next);
|
||||
};
|
||||
};
|
||||
|
||||
const limiter = rateLimit({
|
||||
windowMs: (process.env.RATE_LIMIT_WINDOWS_MINS || 5) * 60 * 1000, // 5 minutes
|
||||
max: process.env.RATE_LIMIT_MAX_PER_WINDOW || 600, // Limit each IP to 600 requests per `window`
|
||||
standardHeaders: true, // Return rate limit info in the `RateLimit-*` headers
|
||||
legacyHeaders: false, // Disable the `X-RateLimit-*` headers
|
||||
});
|
||||
|
||||
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());
|
||||
app.use(passport.initialize());
|
||||
app.use(cors());
|
||||
app.use(express.urlencoded({extended: true}));
|
||||
|
||||
@@ -4,6 +4,8 @@ SET FOREIGN_KEY_CHECKS=0;
|
||||
|
||||
DROP TABLE IF EXISTS account_static_ips;
|
||||
|
||||
DROP TABLE IF EXISTS account_limits;
|
||||
|
||||
DROP TABLE IF EXISTS account_products;
|
||||
|
||||
DROP TABLE IF EXISTS account_subscriptions;
|
||||
@@ -18,6 +20,8 @@ DROP TABLE IF EXISTS lcr_carrier_set_entry;
|
||||
|
||||
DROP TABLE IF EXISTS lcr_routes;
|
||||
|
||||
DROP TABLE IF EXISTS password_settings;
|
||||
|
||||
DROP TABLE IF EXISTS predefined_sip_gateways;
|
||||
|
||||
DROP TABLE IF EXISTS predefined_smpp_gateways;
|
||||
@@ -36,6 +40,8 @@ DROP TABLE IF EXISTS sbc_addresses;
|
||||
|
||||
DROP TABLE IF EXISTS ms_teams_tenants;
|
||||
|
||||
DROP TABLE IF EXISTS service_provider_limits;
|
||||
|
||||
DROP TABLE IF EXISTS signup_history;
|
||||
|
||||
DROP TABLE IF EXISTS smpp_addresses;
|
||||
@@ -69,6 +75,15 @@ private_ipv4 VARBINARY(16) NOT NULL UNIQUE ,
|
||||
PRIMARY KEY (account_static_ip_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','voice_call_minutes','voice_call_session_license', 'voice_call_minutes_license') NOT NULL,
|
||||
quantity INTEGER NOT NULL,
|
||||
PRIMARY KEY (account_limits_sid)
|
||||
);
|
||||
|
||||
CREATE TABLE account_subscriptions
|
||||
(
|
||||
account_subscription_sid CHAR(36) NOT NULL UNIQUE ,
|
||||
@@ -123,6 +138,13 @@ priority INTEGER NOT NULL UNIQUE COMMENT 'lower priority routes are attempted f
|
||||
PRIMARY KEY (lcr_route_sid)
|
||||
) COMMENT='Least cost routing table';
|
||||
|
||||
CREATE TABLE password_settings
|
||||
(
|
||||
min_password_length INTEGER NOT NULL DEFAULT 8,
|
||||
require_digit BOOLEAN NOT NULL DEFAULT false,
|
||||
require_special_character BOOLEAN NOT NULL DEFAULT false
|
||||
);
|
||||
|
||||
CREATE TABLE predefined_carriers
|
||||
(
|
||||
predefined_carrier_sid CHAR(36) NOT NULL UNIQUE ,
|
||||
@@ -228,6 +250,15 @@ tenant_fqdn VARCHAR(255) NOT NULL UNIQUE ,
|
||||
PRIMARY KEY (ms_teams_tenant_sid)
|
||||
) COMMENT='A Microsoft Teams customer tenant';
|
||||
|
||||
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','voice_call_minutes','voice_call_session_license', 'voice_call_minutes_license') NOT NULL,
|
||||
quantity INTEGER NOT NULL,
|
||||
PRIMARY KEY (service_provider_limits_sid)
|
||||
);
|
||||
|
||||
CREATE TABLE signup_history
|
||||
(
|
||||
email VARCHAR(255) NOT NULL,
|
||||
@@ -283,6 +314,7 @@ email_activation_code VARCHAR(16),
|
||||
email_validated BOOLEAN NOT NULL DEFAULT false,
|
||||
phone_validated BOOLEAN NOT NULL DEFAULT false,
|
||||
email_content_opt_out BOOLEAN NOT NULL DEFAULT false,
|
||||
is_active BOOLEAN NOT NULL DEFAULT true,
|
||||
PRIMARY KEY (user_sid)
|
||||
);
|
||||
|
||||
@@ -310,6 +342,9 @@ smpp_password VARCHAR(64),
|
||||
smpp_enquire_link_interval INTEGER DEFAULT 0,
|
||||
smpp_inbound_system_id VARCHAR(255),
|
||||
smpp_inbound_password VARCHAR(64),
|
||||
register_from_user VARCHAR(128),
|
||||
register_from_domain VARCHAR(255),
|
||||
register_public_ip_in_contact BOOLEAN NOT NULL DEFAULT false,
|
||||
PRIMARY KEY (voip_carrier_sid)
|
||||
) COMMENT='A Carrier or customer PBX that can send or receive calls';
|
||||
|
||||
@@ -422,6 +457,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';
|
||||
|
||||
@@ -429,18 +465,21 @@ CREATE INDEX account_static_ip_sid_idx ON account_static_ips (account_static_ip_
|
||||
CREATE INDEX account_sid_idx ON account_static_ips (account_sid);
|
||||
ALTER TABLE account_static_ips ADD FOREIGN KEY account_sid_idxfk (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_1 (account_sid) REFERENCES accounts (account_sid) ON DELETE CASCADE;
|
||||
|
||||
CREATE INDEX account_subscription_sid_idx ON account_subscriptions (account_subscription_sid);
|
||||
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);
|
||||
ALTER TABLE account_subscriptions ADD FOREIGN KEY account_sid_idxfk_2 (account_sid) REFERENCES accounts (account_sid);
|
||||
|
||||
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 +499,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 +520,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 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 email_idx ON signup_history (email);
|
||||
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 +570,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 +591,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 +610,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 +618,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;
|
||||
|
||||
296
db/jambones.sqs
296
db/jambones.sqs
@@ -87,7 +87,7 @@
|
||||
<notNull><![CDATA[1]]></notNull>
|
||||
<uid><![CDATA[49E56AF4-4E40-49B6-BA88-4E378F1E6C18]]></uid>
|
||||
</SQLField>
|
||||
<labelWindowIndex><![CDATA[4]]></labelWindowIndex>
|
||||
<labelWindowIndex><![CDATA[7]]></labelWindowIndex>
|
||||
<ui.treeExpanded><![CDATA[1]]></ui.treeExpanded>
|
||||
<uid><![CDATA[0507BD46-ACAC-48A3-841E-4DEC2FEDCB72]]></uid>
|
||||
</SQLTable>
|
||||
@@ -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>
|
||||
@@ -148,7 +148,7 @@
|
||||
<notNull><![CDATA[1]]></notNull>
|
||||
<uid><![CDATA[6E651E52-F91E-4086-9A1E-FB3425476B2F]]></uid>
|
||||
</SQLField>
|
||||
<labelWindowIndex><![CDATA[14]]></labelWindowIndex>
|
||||
<labelWindowIndex><![CDATA[17]]></labelWindowIndex>
|
||||
<ui.treeExpanded><![CDATA[1]]></ui.treeExpanded>
|
||||
<uid><![CDATA[0A95311F-47FA-429F-BAF9-1442C6EE0C0E]]></uid>
|
||||
</SQLTable>
|
||||
@@ -225,7 +225,7 @@
|
||||
<notNull><![CDATA[1]]></notNull>
|
||||
<uid><![CDATA[D1019218-F1FC-4BC5-A890-F8DBB7153375]]></uid>
|
||||
</SQLField>
|
||||
<labelWindowIndex><![CDATA[7]]></labelWindowIndex>
|
||||
<labelWindowIndex><![CDATA[10]]></labelWindowIndex>
|
||||
<ui.treeExpanded><![CDATA[1]]></ui.treeExpanded>
|
||||
<uid><![CDATA[0AC2FD20-B22F-48DB-8611-801CEF6BFA12]]></uid>
|
||||
</SQLTable>
|
||||
@@ -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>
|
||||
@@ -279,20 +279,57 @@
|
||||
<uid><![CDATA[755D10B0-F60D-4250-8971-C8E4FDB0E0CD]]></uid>
|
||||
<unique><![CDATA[1]]></unique>
|
||||
</SQLField>
|
||||
<labelWindowIndex><![CDATA[11]]></labelWindowIndex>
|
||||
<labelWindowIndex><![CDATA[14]]></labelWindowIndex>
|
||||
<ui.treeExpanded><![CDATA[1]]></ui.treeExpanded>
|
||||
<uid><![CDATA[16B9E383-E044-4D71-AB46-FEB86A46A298]]></uid>
|
||||
</SQLTable>
|
||||
<SQLTable>
|
||||
<name><![CDATA[password_settings]]></name>
|
||||
<schema><![CDATA[]]></schema>
|
||||
<location>
|
||||
<x>2170.00</x>
|
||||
<y>216.00</y>
|
||||
</location>
|
||||
<size>
|
||||
<width>276.00</width>
|
||||
<height>80.00</height>
|
||||
</size>
|
||||
<zorder>31</zorder>
|
||||
<SQLField>
|
||||
<name><![CDATA[min_password_length]]></name>
|
||||
<type><![CDATA[INTEGER]]></type>
|
||||
<defaultValue><![CDATA[8]]></defaultValue>
|
||||
<notNull><![CDATA[1]]></notNull>
|
||||
<uid><![CDATA[CA1990AC-489E-47DE-B935-F9C0723519E3]]></uid>
|
||||
</SQLField>
|
||||
<SQLField>
|
||||
<name><![CDATA[require_digit]]></name>
|
||||
<type><![CDATA[BOOLEAN]]></type>
|
||||
<defaultValue><![CDATA[false]]></defaultValue>
|
||||
<notNull><![CDATA[1]]></notNull>
|
||||
<uid><![CDATA[6A8C92C0-A8A3-40EA-A073-F846CB0E6182]]></uid>
|
||||
</SQLField>
|
||||
<SQLField>
|
||||
<name><![CDATA[require_special_character]]></name>
|
||||
<type><![CDATA[BOOLEAN]]></type>
|
||||
<defaultValue><![CDATA[false]]></defaultValue>
|
||||
<notNull><![CDATA[1]]></notNull>
|
||||
<uid><![CDATA[C4EEBFF0-C3CB-4897-8720-12D14DBA93A5]]></uid>
|
||||
</SQLField>
|
||||
<labelWindowIndex><![CDATA[1]]></labelWindowIndex>
|
||||
<ui.treeExpanded><![CDATA[1]]></ui.treeExpanded>
|
||||
<uid><![CDATA[1A80FB9B-419E-483A-86FF-B44A00A44D7F]]></uid>
|
||||
</SQLTable>
|
||||
<SQLTable>
|
||||
<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>
|
||||
<height>360.00</height>
|
||||
<height>380.00</height>
|
||||
</size>
|
||||
<zorder>11</zorder>
|
||||
<SQLField>
|
||||
@@ -426,7 +463,14 @@
|
||||
<notNull><![CDATA[1]]></notNull>
|
||||
<uid><![CDATA[0FFD2D75-3BB4-4B4A-AD93-28ADADB69045]]></uid>
|
||||
</SQLField>
|
||||
<labelWindowIndex><![CDATA[18]]></labelWindowIndex>
|
||||
<SQLField>
|
||||
<name><![CDATA[is_active]]></name>
|
||||
<type><![CDATA[BOOLEAN]]></type>
|
||||
<defaultValue><![CDATA[true]]></defaultValue>
|
||||
<notNull><![CDATA[1]]></notNull>
|
||||
<uid><![CDATA[601FA05B-78A5-4E7E-9983-39BB0E6D18EB]]></uid>
|
||||
</SQLField>
|
||||
<labelWindowIndex><![CDATA[21]]></labelWindowIndex>
|
||||
<ui.treeExpanded><![CDATA[1]]></ui.treeExpanded>
|
||||
<uid><![CDATA[2A735FAB-592C-42E5-9C8B-06B109314799]]></uid>
|
||||
</SQLTable>
|
||||
@@ -492,7 +536,7 @@
|
||||
<indexed><![CDATA[1]]></indexed>
|
||||
<uid><![CDATA[365FB018-429D-4DA4-AC33-D9D106EA97E5]]></uid>
|
||||
</SQLField>
|
||||
<labelWindowIndex><![CDATA[3]]></labelWindowIndex>
|
||||
<labelWindowIndex><![CDATA[6]]></labelWindowIndex>
|
||||
<ui.treeExpanded><![CDATA[1]]></ui.treeExpanded>
|
||||
<uid><![CDATA[311D99B2-DC8B-4C4A-A1FC-4AFAA1F450F3]]></uid>
|
||||
</SQLTable>
|
||||
@@ -501,12 +545,12 @@
|
||||
<schema><![CDATA[]]></schema>
|
||||
<comment><![CDATA[A Carrier or customer PBX that can send or receive calls]]></comment>
|
||||
<location>
|
||||
<x>24.00</x>
|
||||
<y>317.00</y>
|
||||
<x>20.00</x>
|
||||
<y>287.00</y>
|
||||
</location>
|
||||
<size>
|
||||
<width>267.00</width>
|
||||
<height>460.00</height>
|
||||
<width>293.00</width>
|
||||
<height>520.00</height>
|
||||
</size>
|
||||
<zorder>6</zorder>
|
||||
<SQLField>
|
||||
@@ -670,7 +714,24 @@
|
||||
<type><![CDATA[VARCHAR(64)]]></type>
|
||||
<uid><![CDATA[630DAD4B-E60E-43AE-9822-41C1EED748AA]]></uid>
|
||||
</SQLField>
|
||||
<labelWindowIndex><![CDATA[19]]></labelWindowIndex>
|
||||
<SQLField>
|
||||
<name><![CDATA[register_from_user]]></name>
|
||||
<type><![CDATA[VARCHAR(128)]]></type>
|
||||
<uid><![CDATA[8DC203AE-E70B-4431-9204-CA55F17B9E1B]]></uid>
|
||||
</SQLField>
|
||||
<SQLField>
|
||||
<name><![CDATA[register_from_domain]]></name>
|
||||
<type><![CDATA[VARCHAR(255)]]></type>
|
||||
<uid><![CDATA[82EA1953-92A3-4BD2-A340-B469466BCB5A]]></uid>
|
||||
</SQLField>
|
||||
<SQLField>
|
||||
<name><![CDATA[register_public_ip_in_contact]]></name>
|
||||
<type><![CDATA[BOOLEAN]]></type>
|
||||
<defaultValue><![CDATA[false]]></defaultValue>
|
||||
<notNull><![CDATA[1]]></notNull>
|
||||
<uid><![CDATA[E93A67DB-9A49-4980-9B25-CF1E7DC42F9B]]></uid>
|
||||
</SQLField>
|
||||
<labelWindowIndex><![CDATA[22]]></labelWindowIndex>
|
||||
<objectComment><![CDATA[A Carrier or customer PBX that can send or receive calls]]></objectComment>
|
||||
<ui.treeExpanded><![CDATA[1]]></ui.treeExpanded>
|
||||
<uid><![CDATA[3D3136A7-AFC0-4A70-AEC3-68577955CA2E]]></uid>
|
||||
@@ -752,17 +813,69 @@
|
||||
<defaultValue><![CDATA[CURRENT_TIMESTAMP]]></defaultValue>
|
||||
<uid><![CDATA[C84C9B6A-80B5-4B0B-8C14-EB02F7421BBE]]></uid>
|
||||
</SQLField>
|
||||
<labelWindowIndex><![CDATA[28]]></labelWindowIndex>
|
||||
<labelWindowIndex><![CDATA[31]]></labelWindowIndex>
|
||||
<objectComment><![CDATA[An authorization token that is used to access the REST api]]></objectComment>
|
||||
<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>770.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','voice_call_minutes','voice_call_session_license', 'voice_call_minutes_license')]]></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>
|
||||
<labelWindowIndex><![CDATA[3]]></labelWindowIndex>
|
||||
<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>
|
||||
@@ -880,7 +993,7 @@
|
||||
<indexType><![CDATA[UNIQUE]]></indexType>
|
||||
<uid><![CDATA[554ABEC2-3E1B-41B1-BF07-25F403D5E3B4]]></uid>
|
||||
</SQLIndex>
|
||||
<labelWindowIndex><![CDATA[13]]></labelWindowIndex>
|
||||
<labelWindowIndex><![CDATA[16]]></labelWindowIndex>
|
||||
<ui.treeExpanded><![CDATA[1]]></ui.treeExpanded>
|
||||
<uid><![CDATA[49A68E1C-DEE2-446C-A4EB-9850E16155CC]]></uid>
|
||||
</SQLTable>
|
||||
@@ -901,7 +1014,7 @@
|
||||
<type><![CDATA[VARCHAR(16)]]></type>
|
||||
<uid><![CDATA[1EA572BD-FF6B-43CC-9EBB-33A735781429]]></uid>
|
||||
</SQLField>
|
||||
<labelWindowIndex><![CDATA[1]]></labelWindowIndex>
|
||||
<labelWindowIndex><![CDATA[4]]></labelWindowIndex>
|
||||
<ui.treeExpanded><![CDATA[1]]></ui.treeExpanded>
|
||||
<uid><![CDATA[518AC592-D3E6-4032-8A33-15A3DB72B060]]></uid>
|
||||
</SQLTable>
|
||||
@@ -909,8 +1022,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>
|
||||
@@ -962,10 +1075,62 @@
|
||||
<notNull><![CDATA[1]]></notNull>
|
||||
<uid><![CDATA[6B2F726C-48A6-49D9-B7B1-8850DD6FB3EC]]></uid>
|
||||
</SQLField>
|
||||
<labelWindowIndex><![CDATA[6]]></labelWindowIndex>
|
||||
<labelWindowIndex><![CDATA[9]]></labelWindowIndex>
|
||||
<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>770.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','voice_call_minutes','voice_call_session_license', 'voice_call_minutes_license')]]></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>
|
||||
<labelWindowIndex><![CDATA[2]]></labelWindowIndex>
|
||||
<ui.treeExpanded><![CDATA[1]]></ui.treeExpanded>
|
||||
<uid><![CDATA[5784AC2F-BEBC-466F-9818-F9A7D227A5B5]]></uid>
|
||||
</SQLTable>
|
||||
<SQLTable>
|
||||
<name><![CDATA[webhooks]]></name>
|
||||
<schema><![CDATA[]]></schema>
|
||||
@@ -1012,7 +1177,7 @@
|
||||
<type><![CDATA[VARCHAR(255)]]></type>
|
||||
<uid><![CDATA[04BB457A-D532-4780-8A58-5900094171EC]]></uid>
|
||||
</SQLField>
|
||||
<labelWindowIndex><![CDATA[20]]></labelWindowIndex>
|
||||
<labelWindowIndex><![CDATA[23]]></labelWindowIndex>
|
||||
<objectComment><![CDATA[An HTTP callback]]></objectComment>
|
||||
<ui.treeExpanded><![CDATA[1]]></ui.treeExpanded>
|
||||
<uid><![CDATA[64D64CB9-0990-4C68-BE71-F9FD43C2BE19]]></uid>
|
||||
@@ -1112,7 +1277,7 @@
|
||||
<notNull><![CDATA[1]]></notNull>
|
||||
<uid><![CDATA[9A75A20B-1EFD-4E16-994A-5376C650EAB5]]></uid>
|
||||
</SQLField>
|
||||
<labelWindowIndex><![CDATA[2]]></labelWindowIndex>
|
||||
<labelWindowIndex><![CDATA[5]]></labelWindowIndex>
|
||||
<ui.treeExpanded><![CDATA[1]]></ui.treeExpanded>
|
||||
<uid><![CDATA[6511AF7D-91FD-40C7-9F73-B8E9E66DC249]]></uid>
|
||||
</SQLTable>
|
||||
@@ -1121,8 +1286,8 @@
|
||||
<schema><![CDATA[]]></schema>
|
||||
<comment><![CDATA[a regex-based pattern match for call routing]]></comment>
|
||||
<location>
|
||||
<x>36.00</x>
|
||||
<y>791.00</y>
|
||||
<x>44.00</x>
|
||||
<y>821.00</y>
|
||||
</location>
|
||||
<size>
|
||||
<width>254.00</width>
|
||||
@@ -1181,7 +1346,7 @@
|
||||
<uid><![CDATA[9B4208B5-9E3B-4B76-B7F7-4E5D36B99BF2]]></uid>
|
||||
<unsigned><![CDATA[0]]></unsigned>
|
||||
</SQLField>
|
||||
<labelWindowIndex><![CDATA[27]]></labelWindowIndex>
|
||||
<labelWindowIndex><![CDATA[30]]></labelWindowIndex>
|
||||
<objectComment><![CDATA[a regex-based pattern match for call routing]]></objectComment>
|
||||
<ui.treeExpanded><![CDATA[1]]></ui.treeExpanded>
|
||||
<uid><![CDATA[78584D93-2CD7-4495-9C5E-893C7B869133]]></uid>
|
||||
@@ -1218,7 +1383,7 @@
|
||||
<noQuoteDefault><![CDATA[1]]></noQuoteDefault>
|
||||
<uid><![CDATA[4D2F7B02-F183-4239-8CE8-3E98206708AE]]></uid>
|
||||
</SQLField>
|
||||
<labelWindowIndex><![CDATA[5]]></labelWindowIndex>
|
||||
<labelWindowIndex><![CDATA[8]]></labelWindowIndex>
|
||||
<ui.treeExpanded><![CDATA[1]]></ui.treeExpanded>
|
||||
<uid><![CDATA[86FAB0AB-DC68-4ADF-8A08-BBAF61BA1840]]></uid>
|
||||
</SQLTable>
|
||||
@@ -1226,8 +1391,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>
|
||||
@@ -1327,7 +1492,7 @@
|
||||
<uid><![CDATA[1DDAD1A1-942D-4487-89C8-D496B7F82274]]></uid>
|
||||
<unique><![CDATA[1]]></unique>
|
||||
</SQLField>
|
||||
<labelWindowIndex><![CDATA[17]]></labelWindowIndex>
|
||||
<labelWindowIndex><![CDATA[20]]></labelWindowIndex>
|
||||
<objectComment><![CDATA[A Microsoft Teams customer tenant]]></objectComment>
|
||||
<ui.treeExpanded><![CDATA[1]]></ui.treeExpanded>
|
||||
<uid><![CDATA[92FD042A-5AEC-4D8F-AB94-C73C0F566F75]]></uid>
|
||||
@@ -1397,7 +1562,7 @@
|
||||
<objectComment><![CDATA[lower priority carriers are attempted first]]></objectComment>
|
||||
<uid><![CDATA[01F61C68-799B-49B0-9E6A-0E2162EE5A54]]></uid>
|
||||
</SQLField>
|
||||
<labelWindowIndex><![CDATA[23]]></labelWindowIndex>
|
||||
<labelWindowIndex><![CDATA[26]]></labelWindowIndex>
|
||||
<objectComment><![CDATA[An entry in the LCR routing list]]></objectComment>
|
||||
<ui.treeExpanded><![CDATA[1]]></ui.treeExpanded>
|
||||
<uid><![CDATA[956025F5-0798-47F7-B76C-457814C7B52E]]></uid>
|
||||
@@ -1412,7 +1577,7 @@
|
||||
</location>
|
||||
<size>
|
||||
<width>318.00</width>
|
||||
<height>420.00</height>
|
||||
<height>440.00</height>
|
||||
</size>
|
||||
<zorder>4</zorder>
|
||||
<SQLField>
|
||||
@@ -1577,7 +1742,20 @@
|
||||
<type><![CDATA[VARCHAR(255)]]></type>
|
||||
<uid><![CDATA[2A70CC94-123D-4AF1-9881-F876051D8CB5]]></uid>
|
||||
</SQLField>
|
||||
<labelWindowIndex><![CDATA[25]]></labelWindowIndex>
|
||||
<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[28]]></labelWindowIndex>
|
||||
<objectComment><![CDATA[An enterprise that uses the platform for comm services]]></objectComment>
|
||||
<ui.treeExpanded><![CDATA[1]]></ui.treeExpanded>
|
||||
<uid><![CDATA[985D6997-B1A7-4AB3-80F4-4D59B45480C8]]></uid>
|
||||
@@ -1586,8 +1764,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>
|
||||
@@ -1616,7 +1794,7 @@
|
||||
<notNull><![CDATA[1]]></notNull>
|
||||
<uid><![CDATA[D0BF7D36-E40C-4385-9BA5-2099B49A1042]]></uid>
|
||||
</SQLField>
|
||||
<labelWindowIndex><![CDATA[12]]></labelWindowIndex>
|
||||
<labelWindowIndex><![CDATA[15]]></labelWindowIndex>
|
||||
<ui.treeExpanded><![CDATA[1]]></ui.treeExpanded>
|
||||
<uid><![CDATA[A8ED2178-3CC4-4174-A8FC-C2B58FD28214]]></uid>
|
||||
</SQLTable>
|
||||
@@ -1708,7 +1886,7 @@
|
||||
<type><![CDATA[VARCHAR(32)]]></type>
|
||||
<uid><![CDATA[CE2015BC-8538-4FB0-B4D9-454436FAB1D9]]></uid>
|
||||
</SQLField>
|
||||
<labelWindowIndex><![CDATA[8]]></labelWindowIndex>
|
||||
<labelWindowIndex><![CDATA[11]]></labelWindowIndex>
|
||||
<ui.treeExpanded><![CDATA[1]]></ui.treeExpanded>
|
||||
<uid><![CDATA[AF34726D-EDFD-414E-9B44-5243DA9D9497]]></uid>
|
||||
</SQLTable>
|
||||
@@ -1759,7 +1937,7 @@
|
||||
<notNull><![CDATA[1]]></notNull>
|
||||
<uid><![CDATA[73092A7A-9F3F-4C49-8478-39CE5DAF5ADD]]></uid>
|
||||
</SQLField>
|
||||
<labelWindowIndex><![CDATA[10]]></labelWindowIndex>
|
||||
<labelWindowIndex><![CDATA[13]]></labelWindowIndex>
|
||||
<ui.treeExpanded><![CDATA[1]]></ui.treeExpanded>
|
||||
<uid><![CDATA[B10C0DE3-03CD-4C5A-B1FB-F9F81ED14A40]]></uid>
|
||||
</SQLTable>
|
||||
@@ -1768,8 +1946,8 @@
|
||||
<schema><![CDATA[]]></schema>
|
||||
<comment><![CDATA[A phone number that has been assigned to an account]]></comment>
|
||||
<location>
|
||||
<x>33.00</x>
|
||||
<y>924.00</y>
|
||||
<x>29.00</x>
|
||||
<y>954.00</y>
|
||||
</location>
|
||||
<size>
|
||||
<width>331.00</width>
|
||||
@@ -1855,7 +2033,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[29]]></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>
|
||||
@@ -1955,7 +2133,7 @@
|
||||
<indexNamePrefix><![CDATA[sip_gateway]]></indexNamePrefix>
|
||||
<uid><![CDATA[1C744DE3-39BD-4EC6-B427-7EB2DD258771]]></uid>
|
||||
</SQLIndex>
|
||||
<labelWindowIndex><![CDATA[22]]></labelWindowIndex>
|
||||
<labelWindowIndex><![CDATA[25]]></labelWindowIndex>
|
||||
<objectComment><![CDATA[A whitelisted sip gateway used for origination/termination]]></objectComment>
|
||||
<ui.treeExpanded><![CDATA[1]]></ui.treeExpanded>
|
||||
<uid><![CDATA[D8A564E2-DA41-4217-8ACE-06CF77E9BEC1]]></uid>
|
||||
@@ -2124,7 +2302,7 @@
|
||||
<indexType><![CDATA[UNIQUE]]></indexType>
|
||||
<uid><![CDATA[3FDDDF3B-375D-4DE4-B759-514438845F7D]]></uid>
|
||||
</SQLIndex>
|
||||
<labelWindowIndex><![CDATA[24]]></labelWindowIndex>
|
||||
<labelWindowIndex><![CDATA[27]]></labelWindowIndex>
|
||||
<objectComment><![CDATA[A defined set of behaviors to be applied to phone calls ]]></objectComment>
|
||||
<ui.treeExpanded><![CDATA[1]]></ui.treeExpanded>
|
||||
<uid><![CDATA[E97EE4F0-7ED7-4E8C-862E-D98192D6EAE0]]></uid>
|
||||
@@ -2133,8 +2311,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>
|
||||
@@ -2232,7 +2410,7 @@
|
||||
<type><![CDATA[VARBINARY(52)]]></type>
|
||||
<uid><![CDATA[B4793720-635C-4E25-A306-62E7416541C4]]></uid>
|
||||
</SQLField>
|
||||
<labelWindowIndex><![CDATA[9]]></labelWindowIndex>
|
||||
<labelWindowIndex><![CDATA[12]]></labelWindowIndex>
|
||||
<ui.treeExpanded><![CDATA[1]]></ui.treeExpanded>
|
||||
<uid><![CDATA[EB4BC5F9-CC10-4C8C-AB31-6D942256AEFB]]></uid>
|
||||
</SQLTable>
|
||||
@@ -2302,7 +2480,7 @@
|
||||
<indexed><![CDATA[1]]></indexed>
|
||||
<uid><![CDATA[6F249D1F-111F-45B4-B76C-8B5E6B9CB43F]]></uid>
|
||||
</SQLField>
|
||||
<labelWindowIndex><![CDATA[16]]></labelWindowIndex>
|
||||
<labelWindowIndex><![CDATA[19]]></labelWindowIndex>
|
||||
<ui.treeExpanded><![CDATA[1]]></ui.treeExpanded>
|
||||
<uid><![CDATA[F0EE651E-DBF6-4CAC-A517-AC85BCC2D3AF]]></uid>
|
||||
</SQLTable>
|
||||
@@ -2347,7 +2525,7 @@
|
||||
<uid><![CDATA[B73773BA-AB1B-47AA-B995-2D2FE006198F]]></uid>
|
||||
<unique><![CDATA[1]]></unique>
|
||||
</SQLField>
|
||||
<labelWindowIndex><![CDATA[21]]></labelWindowIndex>
|
||||
<labelWindowIndex><![CDATA[24]]></labelWindowIndex>
|
||||
<objectComment><![CDATA[Least cost routing table]]></objectComment>
|
||||
<ui.treeExpanded><![CDATA[1]]></ui.treeExpanded>
|
||||
<uid><![CDATA[F283D572-F670-4571-91FD-A665A9D3E15D]]></uid>
|
||||
@@ -2414,7 +2592,7 @@
|
||||
<type><![CDATA[VARCHAR(255)]]></type>
|
||||
<uid><![CDATA[FA39B463-61C7-4654-BE9C-D1AC39AB1B97]]></uid>
|
||||
</SQLField>
|
||||
<labelWindowIndex><![CDATA[15]]></labelWindowIndex>
|
||||
<labelWindowIndex><![CDATA[18]]></labelWindowIndex>
|
||||
<objectComment><![CDATA[A partition of the platform used by one service provider]]></objectComment>
|
||||
<ui.treeExpanded><![CDATA[1]]></ui.treeExpanded>
|
||||
<uid><![CDATA[F294B51E-F867-47CA-BC1F-F70BDF8170FF]]></uid>
|
||||
@@ -2433,17 +2611,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>
|
||||
<windowHeight><![CDATA[871.000000]]></windowHeight>
|
||||
<windowLocationX><![CDATA[0.000000]]></windowLocationX>
|
||||
<windowLocationY><![CDATA[73.000000]]></windowLocationY>
|
||||
<windowScrollOrigin><![CDATA[{0, 0}]]></windowScrollOrigin>
|
||||
<windowWidth><![CDATA[2431.000000]]></windowWidth>
|
||||
<windowWidth><![CDATA[1512.000000]]></windowWidth>
|
||||
</SQLDocumentInfo>
|
||||
<AllowsIndexRenamingOnInsert><![CDATA[1]]></AllowsIndexRenamingOnInsert>
|
||||
<defaultLabelExpanded><![CDATA[1]]></defaultLabelExpanded>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env node
|
||||
const {promisePool} = require('../lib/db');
|
||||
const uuidv4 = require('uuid/v4');
|
||||
const { v4: uuidv4 } = require('uuid');
|
||||
const {generateHashedPassword} = require('../lib/utils/password-utils');
|
||||
const sqlInsert = `INSERT into users
|
||||
(user_sid, name, email, hashed_password, force_change, provider, email_validated)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,43 @@ 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`,
|
||||
'ALTER TABLE `voip_carriers` ADD COLUMN `register_from_user` VARCHAR(128)',
|
||||
'ALTER TABLE `voip_carriers` ADD COLUMN `register_from_domain` VARCHAR(256)',
|
||||
'ALTER TABLE `voip_carriers` ADD COLUMN `register_public_ip_in_contact` BOOLEAN NOT NULL DEFAULT false'
|
||||
]
|
||||
};
|
||||
|
||||
const doIt = async() => {
|
||||
let connection;
|
||||
try {
|
||||
@@ -35,10 +73,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 +131,6 @@ const seedDatabase = async(connection) => {
|
||||
await connection.query(sql);
|
||||
};
|
||||
|
||||
|
||||
doIt();
|
||||
|
||||
|
||||
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;
|
||||
@@ -2,7 +2,8 @@ const debug = require('debug')('jambonz:api-server');
|
||||
const Model = require('./model');
|
||||
const {getMysqlConnection} = require('../db');
|
||||
const {promisePool} = require('../db');
|
||||
const uuid = require('uuid').v4;
|
||||
const { v4: uuid } = require('uuid');
|
||||
|
||||
const {encrypt} = require('../utils/encrypt-decrypt');
|
||||
|
||||
const retrieveSql = `SELECT * from accounts acc
|
||||
@@ -313,6 +314,10 @@ Account.fields = [
|
||||
name: 'subspace_sip_teleport_destinations',
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
name: 'siprec_hook_sid',
|
||||
type: 'string',
|
||||
},
|
||||
];
|
||||
|
||||
module.exports = Account;
|
||||
|
||||
@@ -27,6 +27,25 @@ class ApiKey extends Model {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* list all api keys for a service provider
|
||||
*/
|
||||
static retrieveAllForSP(service_provider_sid) {
|
||||
const sql = 'SELECT * from api_keys WHERE service_provider_sid = ?';
|
||||
const args = [service_provider_sid];
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
getMysqlConnection((err, conn) => {
|
||||
if (err) return reject(err);
|
||||
conn.query(sql, args, (err, results) => {
|
||||
conn.release();
|
||||
if (err) return reject(err);
|
||||
resolve(results);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* update last_used api key for an account
|
||||
*/
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
const Emitter = require('events');
|
||||
const uuidv4 = require('uuid/v4');
|
||||
const { v4: uuidv4 } = require('uuid');
|
||||
const assert = require('assert');
|
||||
const {getMysqlConnection} = require('../db');
|
||||
const {DbErrorBadRequest} = require('../utils/errors');
|
||||
|
||||
70
lib/models/password-settings.js
Normal file
70
lib/models/password-settings.js
Normal file
@@ -0,0 +1,70 @@
|
||||
const {promisePool} = require('../db');
|
||||
|
||||
class PasswordSettings {
|
||||
|
||||
/**
|
||||
* Retrieve object from database
|
||||
*/
|
||||
static async retrieve() {
|
||||
const [r] = await promisePool.execute(`SELECT * FROM ${this.table}`);
|
||||
return r;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update object into the database
|
||||
*/
|
||||
static async update(obj) {
|
||||
let sql = `UPDATE ${this.table} SET `;
|
||||
const values = [];
|
||||
const keys = Object.keys(obj);
|
||||
this.fields.forEach(({name}) => {
|
||||
if (keys.includes(name)) {
|
||||
sql = sql + `${name} = ?,`;
|
||||
values.push(obj[name]);
|
||||
}
|
||||
});
|
||||
if (values.length) {
|
||||
sql = sql.slice(0, -1);
|
||||
await promisePool.execute(sql, values);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* insert object into the database
|
||||
*/
|
||||
static async make(obj) {
|
||||
let params = '', marks = '';
|
||||
const values = [];
|
||||
const keys = Object.keys(obj);
|
||||
this.fields.forEach(({name}) => {
|
||||
if (keys.includes(name)) {
|
||||
params = params + `${name},`;
|
||||
marks = marks + '?,';
|
||||
values.push(obj[name]);
|
||||
}
|
||||
});
|
||||
if (values.length) {
|
||||
params = `(${params.slice(0, -1)})`;
|
||||
marks = `values(${marks.slice(0, -1)})`;
|
||||
return await promisePool.execute(`INSERT into ${this.table} ${params} ${marks}`, values);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
PasswordSettings.table = 'password_settings';
|
||||
PasswordSettings.fields = [
|
||||
{
|
||||
name: 'min_password_length',
|
||||
type: 'number'
|
||||
},
|
||||
{
|
||||
name: 'require_digit',
|
||||
type: 'number'
|
||||
},
|
||||
{
|
||||
name: 'require_special_character',
|
||||
type: 'number'
|
||||
}
|
||||
];
|
||||
module.exports = PasswordSettings;
|
||||
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;
|
||||
@@ -111,6 +111,18 @@ VoipCarrier.fields = [
|
||||
name: 'smpp_system_id',
|
||||
type: 'string'
|
||||
},
|
||||
{
|
||||
name: 'register_from_user',
|
||||
type: 'string'
|
||||
},
|
||||
{
|
||||
name: 'register_from_domain',
|
||||
type: 'string'
|
||||
},
|
||||
{
|
||||
name: 'register_public_ip_in_contact',
|
||||
type: 'number'
|
||||
}
|
||||
];
|
||||
|
||||
module.exports = VoipCarrier;
|
||||
|
||||
@@ -8,7 +8,7 @@ const ApiKey = require('../../models/api-key');
|
||||
const ServiceProvider = require('../../models/service-provider');
|
||||
const {deleteDnsRecords} = require('../../utils/dns-utils');
|
||||
const {deleteCustomer} = require('../../utils/stripe-utils');
|
||||
const uuidv4 = require('uuid/v4');
|
||||
const { v4: uuidv4 } = require('uuid');
|
||||
const snakeCase = require('../../utils/snake-case');
|
||||
const sysError = require('../error');
|
||||
const {promisePool} = require('../../db');
|
||||
@@ -19,6 +19,26 @@ const translator = short();
|
||||
|
||||
let idx = 0;
|
||||
|
||||
const getFsUrl = async(logger, retrieveSet, setName) => {
|
||||
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);
|
||||
if (0 === fs.length) {
|
||||
logger.info('No available feature servers to handle createCall API request');
|
||||
return ;
|
||||
}
|
||||
const ip = stripPort(fs[idx++ % fs.length]);
|
||||
logger.info({fs}, `feature servers available for createCall API request, selecting ${ip}`);
|
||||
return `http://${ip}:3000/v1/createCall`;
|
||||
} catch (err) {
|
||||
logger.error({err}, 'getFsUrl: error retreving feature servers from redis');
|
||||
}
|
||||
};
|
||||
|
||||
const stripPort = (hostport) => {
|
||||
const arr = /^(.*):(.*)$/.exec(hostport);
|
||||
if (arr) return arr[1];
|
||||
@@ -31,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 {
|
||||
@@ -99,7 +120,10 @@ function validateUpdateCall(opts) {
|
||||
'listen_status',
|
||||
'conf_hold_status',
|
||||
'conf_mute_status',
|
||||
'mute_status']
|
||||
'mute_status',
|
||||
'sip_request',
|
||||
'record'
|
||||
]
|
||||
.reduce((acc, prop) => (opts[prop] ? ++acc : acc), 0);
|
||||
|
||||
switch (count) {
|
||||
@@ -133,6 +157,16 @@ function validateUpdateCall(opts) {
|
||||
if (opts.conf_mute_status && !['mute', 'unmute'].includes(opts.conf_mute_status)) {
|
||||
throw new DbErrorBadRequest('invalid conf_mute_status');
|
||||
}
|
||||
if (opts.sip_request &&
|
||||
(!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) {
|
||||
@@ -183,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) {
|
||||
@@ -217,10 +250,10 @@ async function validateCreateCall(logger, sid, req) {
|
||||
if (typeof obj.call_status_hook === 'object' && typeof obj.call_status_hook.url != 'string') {
|
||||
throw new DbErrorBadRequest('call_status_hook must be string or an object containing a url property');
|
||||
}
|
||||
if (obj.call_hook && !/^https?:/.test(obj.call_hook.url)) {
|
||||
if (obj.call_hook && !/^https?:/.test(obj.call_hook.url) && !/^wss?:/.test(obj.call_hook.url)) {
|
||||
throw new DbErrorBadRequest('call_hook url be an absolute url');
|
||||
}
|
||||
if (obj.call_status_hook && !/^https?:/.test(obj.call_status_hook.url)) {
|
||||
if (obj.call_status_hook && !/^https?:/.test(obj.call_status_hook.url) && !/^wss?:/.test(obj.call_status_hook.url)) {
|
||||
throw new DbErrorBadRequest('call_status_hook url be an absolute url');
|
||||
}
|
||||
}
|
||||
@@ -496,7 +529,7 @@ router.delete('/:sid', async(req, res) => {
|
||||
await validateDelete(req, sid);
|
||||
|
||||
const [account] = await promisePool.query('SELECT * FROM accounts WHERE account_sid = ?', sid);
|
||||
const {sip_realm, stripe_customer_id} = account[0];
|
||||
const {sip_realm, stripe_customer_id, registration_hook_sid} = account[0];
|
||||
/* remove dns records */
|
||||
if (process.env.NODE_ENV !== 'test' || process.env.DME_API_KEY) {
|
||||
|
||||
@@ -537,6 +570,15 @@ account_subscriptions WHERE account_sid = ?)
|
||||
await promisePool.execute('DELETE from applications where account_sid = ?', [sid]);
|
||||
await promisePool.execute('DELETE from accounts where account_sid = ?', [sid]);
|
||||
|
||||
if (registration_hook_sid) {
|
||||
/* remove registration hook if only used by this account */
|
||||
const sql = 'SELECT COUNT(*) as count FROM accounts WHERE registration_hook_sid = ?';
|
||||
const [r] = await promisePool.query(sql, registration_hook_sid);
|
||||
if (r[0]?.count === 0) {
|
||||
await promisePool.execute('DELETE from webhooks where webhook_sid = ?', [registration_hook_sid]);
|
||||
}
|
||||
}
|
||||
|
||||
if (stripe_customer_id) {
|
||||
const response = await deleteCustomer(logger, stripe_customer_id);
|
||||
logger.info({response}, `deleted stripe customer_id ${stripe_customer_id} for account_si ${sid}`);
|
||||
@@ -567,37 +609,32 @@ router.post('/:sid/Calls', async(req, res) => {
|
||||
const setName = `${(process.env.JAMBONES_CLUSTER_ID || 'default')}:active-fs`;
|
||||
const {retrieveSet, logger} = req.app.locals;
|
||||
|
||||
try {
|
||||
const fs = await retrieveSet(setName);
|
||||
if (0 === fs.length) {
|
||||
logger.info('No available feature servers to handle createCall API request');
|
||||
return res.json({msg: 'no available feature servers at this time'}).status(500);
|
||||
const serviceUrl = await getFsUrl(logger, retrieveSet, setName);
|
||||
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);
|
||||
}
|
||||
const ip = stripPort(fs[idx++ % fs.length]);
|
||||
logger.info({fs}, `feature servers available for createCall API request, selecting ${ip}`);
|
||||
const serviceUrl = `http://${ip}:3000/v1/createCall`;
|
||||
await validateCreateCall(logger, sid, req);
|
||||
|
||||
logger.debug({payload: req.body}, `sending createCall API request to to ${ip}`);
|
||||
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 ${ip}`);
|
||||
return res.sendStatus(500);
|
||||
}
|
||||
if (response.statusCode !== 201) {
|
||||
logger.error({statusCode: response.statusCode}, `Non-success response returned by createCall ${ip}`);
|
||||
return res.sendStatus(500);
|
||||
}
|
||||
res.status(201).json(body);
|
||||
});
|
||||
} catch (err) {
|
||||
sysError(logger, res, err);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -714,14 +751,8 @@ router.post('/:sid/Messages', async(req, res) => {
|
||||
const {retrieveSet, logger} = req.app.locals;
|
||||
|
||||
try {
|
||||
const fs = await retrieveSet(setName);
|
||||
if (0 === fs.length) {
|
||||
logger.info('No available feature servers to handle createMessage API request');
|
||||
return res.json({msg: 'no available feature servers at this time'}).status(500);
|
||||
}
|
||||
const ip = stripPort(fs[idx++ % fs.length]);
|
||||
logger.info({fs}, `feature servers available for createMessage API request, selecting ${ip}`);
|
||||
const serviceUrl = `http://${ip}:3000/v1/createMessage/${account_sid}`;
|
||||
const serviceUrl = await getFsUrl(logger, retrieveSet, setName);
|
||||
if (!serviceUrl) res.json({msg: 'no available feature servers at this time'}).status(480);
|
||||
await validateCreateMessage(logger, account_sid, req);
|
||||
|
||||
const payload = {
|
||||
@@ -729,7 +760,7 @@ router.post('/:sid/Messages', async(req, res) => {
|
||||
account_sid,
|
||||
...req.body
|
||||
};
|
||||
logger.debug({payload}, `sending createMessage API request to to ${ip}`);
|
||||
logger.debug({payload}, `sending createMessage API request to to ${serviceUrl}`);
|
||||
updateLastUsed(logger, account_sid, req).catch(() => {});
|
||||
request({
|
||||
url: serviceUrl,
|
||||
@@ -738,7 +769,7 @@ router.post('/:sid/Messages', async(req, res) => {
|
||||
body: payload
|
||||
}, (err, response, body) => {
|
||||
if (err) {
|
||||
logger.error(err, `Error sending createMessage POST to ${ip}`);
|
||||
logger.error(err, `Error sending createMessage POST to ${serviceUrl}`);
|
||||
return res.sendStatus(500);
|
||||
}
|
||||
if (response.statusCode !== 200) {
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
const router = require('express').Router();
|
||||
const {DbErrorBadRequest} = require('../../utils/errors');
|
||||
const PredefinedCarrier = require('../../models/predefined-carrier');
|
||||
const VoipCarrier = require('../../models/voip-carrier');
|
||||
const SipGateway = require('../../models/sip-gateway');
|
||||
const SmppGateway = require('../../models/smpp-gateway');
|
||||
const {parseServiceProviderSid} = require('./utils');
|
||||
const short = require('short-uuid');
|
||||
const {promisePool} = require('../../db');
|
||||
const sysError = require('../error');
|
||||
|
||||
@@ -43,8 +43,7 @@ router.post('/:sid', async(req, res) => {
|
||||
await promisePool.query(sqlSelectCarrierByNameForSP, [service_provider_sid, template.name]);
|
||||
|
||||
if (r2.length > 0) {
|
||||
logger.info({account_sid}, `Failed to add carrier with name ${template.name}, carrier of that name exists`);
|
||||
throw new DbErrorBadRequest(`A carrier with name ${template.name} already exists`);
|
||||
template.name = `${template.name}-${short.generate()}`;
|
||||
}
|
||||
|
||||
/* retrieve all the sip gateways */
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ const {DbErrorBadRequest} = require('../../utils/errors');
|
||||
const ApiKey = require('../../models/api-key');
|
||||
const Account = require('../../models/account');
|
||||
const decorate = require('./decorate');
|
||||
const uuidv4 = require('uuid/v4');
|
||||
const { v4: uuidv4 } = require('uuid');
|
||||
const sysError = require('../error');
|
||||
const preconditions = {
|
||||
'add': validateAddToken,
|
||||
|
||||
@@ -3,12 +3,12 @@ const {DbErrorBadRequest, DbErrorUnprocessableRequest} = require('../../utils/er
|
||||
const Application = require('../../models/application');
|
||||
const Account = require('../../models/account');
|
||||
const Webhook = require('../../models/webhook');
|
||||
const {promisePool} = require('../../db');
|
||||
const decorate = require('./decorate');
|
||||
const sysError = require('../error');
|
||||
const preconditions = {
|
||||
'add': validateAdd,
|
||||
'update': validateUpdate,
|
||||
'delete': validateDelete
|
||||
'update': validateUpdate
|
||||
};
|
||||
|
||||
/* only user-level tokens can add applications */
|
||||
@@ -59,7 +59,7 @@ async function validateDelete(req, sid) {
|
||||
if (assignedPhoneNumbers > 0) throw new DbErrorUnprocessableRequest('cannot delete application with phone numbers');
|
||||
}
|
||||
|
||||
decorate(router, Application, ['delete'], preconditions);
|
||||
decorate(router, Application, [], preconditions);
|
||||
|
||||
/* add */
|
||||
router.post('/', async(req, res) => {
|
||||
@@ -104,13 +104,54 @@ 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);
|
||||
}
|
||||
});
|
||||
|
||||
/* delete */
|
||||
router.delete('/:sid', async(req, res) => {
|
||||
const sid = req.params.sid;
|
||||
const logger = req.app.locals.logger;
|
||||
try {
|
||||
await validateDelete(req, sid);
|
||||
|
||||
const [application] = await promisePool.query('SELECT * FROM applications WHERE application_sid = ?', sid);
|
||||
const {call_hook_sid, call_status_hook_sid, messaging_hook_sid} = application[0];
|
||||
logger.info({call_hook_sid, call_status_hook_sid, messaging_hook_sid, sid}, 'deleting application');
|
||||
await promisePool.execute('DELETE from applications where application_sid = ?', [sid]);
|
||||
|
||||
if (call_hook_sid) {
|
||||
/* remove call hook if only used by this app */
|
||||
const sql = 'SELECT COUNT(*) as count FROM applications WHERE call_hook_sid = ?';
|
||||
const [r] = await promisePool.query(sql, call_hook_sid);
|
||||
if (r[0]?.count === 0) {
|
||||
await promisePool.execute('DELETE from webhooks where webhook_sid = ?', [call_hook_sid]);
|
||||
}
|
||||
}
|
||||
if (call_status_hook_sid) {
|
||||
const sql = 'SELECT COUNT(*) as count FROM applications WHERE call_status_hook_sid = ?';
|
||||
const [r] = await promisePool.query(sql, call_status_hook_sid);
|
||||
if (r[0]?.count === 0) {
|
||||
await promisePool.execute('DELETE from webhooks where webhook_sid = ?', [call_status_hook_sid]);
|
||||
}
|
||||
}
|
||||
if (messaging_hook_sid) {
|
||||
const sql = 'SELECT COUNT(*) as count FROM applications WHERE messaging_hook_sid = ?';
|
||||
const [r] = await promisePool.query(sql, messaging_hook_sid);
|
||||
if (r[0]?.count === 0) {
|
||||
await promisePool.execute('DELETE from webhooks where webhook_sid = ?', [messaging_hook_sid]);
|
||||
}
|
||||
}
|
||||
|
||||
res.status(204).end();
|
||||
} catch (err) {
|
||||
sysError(logger, res, err);
|
||||
}
|
||||
});
|
||||
|
||||
/* update */
|
||||
router.put('/:sid', async(req, res) => {
|
||||
const sid = req.params.sid;
|
||||
|
||||
@@ -7,9 +7,16 @@ const isAdminScope = (req, res, next) => {
|
||||
message: 'insufficient privileges'
|
||||
});
|
||||
};
|
||||
const isAdminOrSPScope = (req, res, next) => {
|
||||
if (req.user.hasScope('admin') || req.user.hasScope('service_provider')) return next();
|
||||
res.status(403).json({
|
||||
status: 'fail',
|
||||
message: 'insufficient privileges'
|
||||
});
|
||||
};
|
||||
|
||||
api.use('/BetaInviteCodes', isAdminScope, require('./beta-invite-codes'));
|
||||
api.use('/ServiceProviders', isAdminScope, require('./service-providers'));
|
||||
api.use('/ServiceProviders', isAdminOrSPScope, require('./service-providers'));
|
||||
api.use('/VoipCarriers', require('./voip-carriers'));
|
||||
api.use('/Webhooks', require('./webhooks'));
|
||||
api.use('/SipGateways', require('./sip-gateways'));
|
||||
@@ -37,6 +44,7 @@ api.use('/Subscriptions', require('./subscriptions'));
|
||||
api.use('/Invoices', require('./invoices'));
|
||||
api.use('/InviteCodes', require('./invite-codes'));
|
||||
api.use('/PredefinedCarriers', require('./predefined-carriers'));
|
||||
api.use('/PasswordSettings', isAdminScope, require('./password-settings'));
|
||||
|
||||
// messaging
|
||||
api.use('/Smpps', require('./smpps')); // our smpp server info
|
||||
|
||||
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;
|
||||
42
lib/routes/api/password-settings.js
Normal file
42
lib/routes/api/password-settings.js
Normal file
@@ -0,0 +1,42 @@
|
||||
const router = require('express').Router();
|
||||
const sysError = require('../error');
|
||||
const PasswordSettings = require('../../models/password-settings');
|
||||
const { DbErrorBadRequest } = require('../../utils/errors');
|
||||
|
||||
const validate = (obj) => {
|
||||
if (obj.min_password_length && (
|
||||
obj.min_password_length < 8 ||
|
||||
obj.min_password_length > 20
|
||||
)) {
|
||||
throw new DbErrorBadRequest('invalid min_password_length property: should be between 8-20');
|
||||
}
|
||||
};
|
||||
|
||||
router.post('/', async(req, res) => {
|
||||
const logger = req.app.locals.logger;
|
||||
try {
|
||||
validate(req.body);
|
||||
const [existing] = (await PasswordSettings.retrieve() || []);
|
||||
if (existing) {
|
||||
await PasswordSettings.update(req.body);
|
||||
} else {
|
||||
await PasswordSettings.make(req.body);
|
||||
}
|
||||
res.status(201).json({});
|
||||
}
|
||||
catch (err) {
|
||||
sysError(logger, res, err);
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/', async(req, res) => {
|
||||
const logger = req.app.locals.logger;
|
||||
try {
|
||||
const [results] = (await PasswordSettings.retrieve() || []);
|
||||
return res.status(200).json(results || {min_password_length: 8});
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ const {DbErrorBadRequest, DbErrorUnprocessableRequest} = require('../../utils/er
|
||||
const {promisePool} = require('../../db');
|
||||
const {doGithubAuth, doGoogleAuth, doLocalAuth} = require('../../utils/oauth-utils');
|
||||
const {validateEmail} = require('../../utils/email-utils');
|
||||
const uuid = require('uuid').v4;
|
||||
const { v4: uuid } = require('uuid');
|
||||
const short = require('short-uuid');
|
||||
const translator = short();
|
||||
const jwt = require('jsonwebtoken');
|
||||
|
||||
@@ -7,6 +7,7 @@ const Account = require('../../models/account');
|
||||
const VoipCarrier = require('../../models/voip-carrier');
|
||||
const Application = require('../../models/application');
|
||||
const PhoneNumber = require('../../models/phone-number');
|
||||
const ApiKey = require('../../models/api-key');
|
||||
const {hasServiceProviderPermissions, parseServiceProviderSid} = require('./utils');
|
||||
const sysError = require('../error');
|
||||
const decorate = require('./decorate');
|
||||
@@ -40,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;
|
||||
@@ -114,6 +118,17 @@ router.get(':sid/Acccounts', async(req, res) => {
|
||||
sysError(logger, res, err);
|
||||
}
|
||||
});
|
||||
router.get('/:sid/ApiKeys', async(req, res) => {
|
||||
const logger = req.app.locals.logger;
|
||||
const {sid} = req.params;
|
||||
try {
|
||||
const results = await ApiKey.retrieveAllForSP(sid);
|
||||
res.status(200).json(results);
|
||||
await ApiKey.updateLastUsed(sid);
|
||||
} catch (err) {
|
||||
sysError(logger, res, err);
|
||||
}
|
||||
});
|
||||
|
||||
/* add */
|
||||
router.post('/', async(req, res) => {
|
||||
|
||||
@@ -2,7 +2,7 @@ const router = require('express').Router();
|
||||
const {promisePool} = require('../../db');
|
||||
const {DbErrorBadRequest} = require('../../utils/errors');
|
||||
const {createDnsRecords, deleteDnsRecords} = require('../../utils/dns-utils');
|
||||
const uuid = require('uuid').v4;
|
||||
const { v4: uuid } = require('uuid');
|
||||
const sysError = require('../error');
|
||||
const insertDnsRecords = `INSERT INTO dns_records
|
||||
(dns_record_sid, account_sid, record_type, record_id)
|
||||
|
||||
@@ -1,19 +1,41 @@
|
||||
const router = require('express').Router();
|
||||
const request = require('request');
|
||||
const getProvider = require('../../utils/sms-provider');
|
||||
const uuidv4 = require('uuid/v4');
|
||||
const { v4: uuidv4 } = require('uuid');
|
||||
const sysError = require('../error');
|
||||
let idx = 0;
|
||||
|
||||
const getFsUrl = async(logger, retrieveSet, setName, provider) => {
|
||||
if (process.env.K8S) return `http://${process.env.K8S_FEATURE_SERVER_SERVICE_NAME}:3000/v1/messaging/${provider}`;
|
||||
|
||||
async function doSendResponse(res, respondFn, body) {
|
||||
try {
|
||||
const fs = await retrieveSet(setName);
|
||||
if (0 === fs.length) {
|
||||
logger.info('No available feature servers to handle createCall API request');
|
||||
return ;
|
||||
}
|
||||
const ip = stripPort(fs[idx++ % fs.length]);
|
||||
logger.info({fs}, `feature servers available for createCall API request, selecting ${ip}`);
|
||||
return `http://${ip}:3000/v1/messaging/${provider}`;
|
||||
} catch (err) {
|
||||
logger.error({err}, 'getFsUrl: error retreving feature servers from redis');
|
||||
}
|
||||
};
|
||||
|
||||
const stripPort = (hostport) => {
|
||||
const arr = /^(.*):(.*)$/.exec(hostport);
|
||||
if (arr) return arr[1];
|
||||
return hostport;
|
||||
};
|
||||
|
||||
const doSendResponse = async(res, respondFn, body) => {
|
||||
if (typeof respondFn === 'number') res.sendStatus(respondFn);
|
||||
else if (typeof respondFn !== 'function') res.sendStatus(200);
|
||||
else {
|
||||
const payload = await respondFn(body);
|
||||
res.status(200).json(payload);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
router.post('/:provider', async(req, res) => {
|
||||
const provider = req.params.provider;
|
||||
@@ -67,17 +89,8 @@ router.post('/:provider', async(req, res) => {
|
||||
}
|
||||
|
||||
try {
|
||||
const fs = await retrieveSet(setName);
|
||||
if (0 === fs.length) {
|
||||
logger.info('No available feature servers to handle createCall API request');
|
||||
return res
|
||||
.json({
|
||||
msg: 'no available feature servers at this time'
|
||||
})
|
||||
.status(480);
|
||||
}
|
||||
const ip = fs[idx++ % fs.length];
|
||||
const serviceUrl = `http://${ip}:3000/v1/messaging/${provider}`;
|
||||
const serviceUrl = await getFsUrl(logger, retrieveSet, setName, provider);
|
||||
if (!serviceUrl) res.json({msg: 'no available feature servers at this time'}).status(480);
|
||||
const messageSid = uuidv4();
|
||||
const payload = await Promise.resolve(filterFn({messageSid}, req.body));
|
||||
|
||||
@@ -113,7 +126,7 @@ router.post('/:provider', async(req, res) => {
|
||||
|
||||
logger.debug({body: req.body, payload}, 'filtered incoming SMS');
|
||||
|
||||
logger.info({payload, url: serviceUrl}, `sending incomingSms API request to FS at ${ip}`);
|
||||
logger.info({payload, url: serviceUrl}, `sending incomingSms API request to FS at ${serviceUrl}`);
|
||||
|
||||
request({
|
||||
url: serviceUrl,
|
||||
@@ -123,7 +136,7 @@ router.post('/:provider', async(req, res) => {
|
||||
},
|
||||
async(err, response, body) => {
|
||||
if (err) {
|
||||
logger.error(err, `Error sending incomingSms POST to ${ip}`);
|
||||
logger.error(err, `Error sending incomingSms POST to ${serviceUrl}`);
|
||||
return res.sendStatus(500);
|
||||
}
|
||||
if (200 === response.statusCode) {
|
||||
@@ -131,7 +144,7 @@ router.post('/:provider', async(req, res) => {
|
||||
logger.info({body}, 'sending response to provider for incomingSMS');
|
||||
return doSendResponse(res, respondFn, body);
|
||||
}
|
||||
logger.error({statusCode: response.statusCode}, `Non-success response returned by incomingSms ${ip}`);
|
||||
logger.error({statusCode: response.statusCode}, `Non-success response returned by incomingSms ${serviceUrl}`);
|
||||
return res.sendStatus(500);
|
||||
});
|
||||
} catch (err) {
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
const router = require('express').Router();
|
||||
const assert = require('assert');
|
||||
const SpeechCredential = require('../../models/speech-credential');
|
||||
const sysError = require('../error');
|
||||
const {decrypt, encrypt} = require('../../utils/encrypt-decrypt');
|
||||
const {parseAccountSid, parseServiceProviderSid} = require('./utils');
|
||||
const {DbErrorUnprocessableRequest, DbErrorBadRequest} = require('../../utils/errors');
|
||||
const {DbErrorUnprocessableRequest} = require('../../utils/errors');
|
||||
const {
|
||||
testGoogleTts,
|
||||
testGoogleStt,
|
||||
@@ -14,18 +15,80 @@ const {
|
||||
testWellSaidTts
|
||||
} = require('../../utils/speech-utils');
|
||||
|
||||
router.post('/', async(req, res) => {
|
||||
const logger = req.app.locals.logger;
|
||||
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 {
|
||||
use_for_stt,
|
||||
use_for_tts,
|
||||
vendor,
|
||||
service_key,
|
||||
access_key_id,
|
||||
secret_access_key,
|
||||
aws_region,
|
||||
api_key,
|
||||
region
|
||||
region,
|
||||
use_custom_tts,
|
||||
custom_tts_endpoint,
|
||||
use_custom_stt,
|
||||
custom_stt_endpoint
|
||||
} = obj;
|
||||
|
||||
switch (vendor) {
|
||||
case 'google':
|
||||
assert(service_key, 'invalid json key: service_key is required');
|
||||
try {
|
||||
const o = JSON.parse(service_key);
|
||||
assert(o.client_email && o.private_key, 'invalid google service account key');
|
||||
}
|
||||
catch (err) {
|
||||
assert(false, 'invalid google service account key - not JSON');
|
||||
}
|
||||
return encrypt(service_key);
|
||||
|
||||
case 'aws':
|
||||
assert(access_key_id, 'invalid aws speech credential: access_key_id is required');
|
||||
assert(secret_access_key, 'invalid aws speech credential: secret_access_key is required');
|
||||
assert(aws_region, 'invalid aws speech credential: aws_region is required');
|
||||
const awsData = JSON.stringify({aws_region, access_key_id, secret_access_key});
|
||||
return encrypt(awsData);
|
||||
|
||||
case 'microsoft':
|
||||
assert(region, 'invalid azure speech credential: region is required');
|
||||
assert(api_key, 'invalid azure speech credential: api_key is required');
|
||||
const azureData = JSON.stringify({
|
||||
region,
|
||||
api_key,
|
||||
use_custom_tts,
|
||||
custom_tts_endpoint,
|
||||
use_custom_stt,
|
||||
custom_stt_endpoint
|
||||
});
|
||||
return encrypt(azureData);
|
||||
|
||||
case 'wellsaid':
|
||||
assert(api_key, 'invalid wellsaid speech credential: api_key is required');
|
||||
const wsData = JSON.stringify({api_key});
|
||||
return encrypt(wsData);
|
||||
|
||||
default:
|
||||
assert(false, `invalid or missing vendor: ${vendor}`);
|
||||
}
|
||||
};
|
||||
|
||||
router.post('/', async(req, res) => {
|
||||
const logger = req.app.locals.logger;
|
||||
const {
|
||||
use_for_stt,
|
||||
use_for_tts,
|
||||
vendor,
|
||||
} = req.body;
|
||||
const account_sid = req.user.account_sid || req.body.account_sid;
|
||||
let service_provider_sid;
|
||||
@@ -37,43 +100,7 @@ router.post('/', async(req, res) => {
|
||||
service_provider_sid = parseServiceProviderSid(req);
|
||||
}
|
||||
try {
|
||||
let encrypted_credential;
|
||||
if (vendor === 'google') {
|
||||
let obj;
|
||||
if (!service_key) throw new DbErrorBadRequest('invalid json key: service_key is required');
|
||||
try {
|
||||
obj = JSON.parse(service_key);
|
||||
if (!obj.client_email || !obj.private_key) {
|
||||
throw new DbErrorBadRequest('invalid google service account key');
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
throw new DbErrorBadRequest('invalid google service account key - not JSON');
|
||||
}
|
||||
encrypted_credential = encrypt(service_key);
|
||||
}
|
||||
else if (vendor === 'aws') {
|
||||
const data = JSON.stringify({
|
||||
aws_region: aws_region || 'us-east-1',
|
||||
access_key_id,
|
||||
secret_access_key
|
||||
});
|
||||
encrypted_credential = encrypt(data);
|
||||
}
|
||||
else if (vendor === 'microsoft') {
|
||||
const data = JSON.stringify({
|
||||
region,
|
||||
api_key
|
||||
});
|
||||
encrypted_credential = encrypt(data);
|
||||
}
|
||||
else if (vendor === 'wellsaid') {
|
||||
const data = JSON.stringify({
|
||||
api_key
|
||||
});
|
||||
encrypted_credential = encrypt(data);
|
||||
}
|
||||
else throw new DbErrorBadRequest(`invalid speech vendor ${vendor}`);
|
||||
const encrypted_credential = encryptCredential(req.body);
|
||||
const uuid = await SpeechCredential.make({
|
||||
account_sid,
|
||||
service_provider_sid,
|
||||
@@ -104,21 +131,34 @@ router.get('/', async(req, res) => {
|
||||
res.status(200).json(creds.map((c) => {
|
||||
const {credential, ...obj} = c;
|
||||
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 = obscured;
|
||||
}
|
||||
else if ('aws' === obj.vendor) {
|
||||
const o = decrypt(credential);
|
||||
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 = decrypt(credential);
|
||||
obj.api_key = o.api_key;
|
||||
const o = JSON.parse(decrypt(credential));
|
||||
obj.api_key = obscureKey(o.api_key);
|
||||
obj.region = o.region;
|
||||
obj.use_custom_tts = o.use_custom_tts;
|
||||
obj.custom_tts_endpoint = o.custom_tts_endpoint;
|
||||
obj.use_custom_stt = o.use_custom_stt;
|
||||
obj.custom_stt_endpoint = o.custom_stt_endpoint;
|
||||
logger.info({obj, o}, 'retrieving azure speech credential');
|
||||
}
|
||||
else if ('wellsaid' === obj.vendor) {
|
||||
const o = decrypt(credential);
|
||||
obj.api_key = o.api_key;
|
||||
const o = JSON.parse(decrypt(credential));
|
||||
obj.api_key = obscureKey(o.api_key);
|
||||
}
|
||||
return obj;
|
||||
}));
|
||||
@@ -138,17 +178,32 @@ 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;
|
||||
obj.use_custom_tts = o.use_custom_tts;
|
||||
obj.custom_tts_endpoint = o.custom_tts_endpoint;
|
||||
obj.use_custom_stt = o.use_custom_stt;
|
||||
obj.custom_stt_endpoint = o.custom_stt_endpoint;
|
||||
}
|
||||
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) {
|
||||
@@ -179,7 +234,7 @@ router.put('/:sid', async(req, res) => {
|
||||
const sid = req.params.sid;
|
||||
const logger = req.app.locals.logger;
|
||||
try {
|
||||
const {use_for_tts, use_for_stt} = req.body;
|
||||
const {use_for_tts, use_for_stt, region, aws_region} = req.body;
|
||||
if (typeof use_for_tts === 'undefined' && typeof use_for_stt === 'undefined') {
|
||||
throw new DbErrorUnprocessableRequest('use_for_tts and use_for_stt are the only updateable fields');
|
||||
}
|
||||
@@ -191,6 +246,41 @@ router.put('/:sid', async(req, res) => {
|
||||
obj.use_for_stt = use_for_stt;
|
||||
}
|
||||
|
||||
/* update the credential if provided */
|
||||
try {
|
||||
const cred = await SpeechCredential.retrieve(sid);
|
||||
if (1 === cred.length) {
|
||||
const {credential, vendor} = cred[0];
|
||||
const o = JSON.parse(decrypt(credential));
|
||||
const {
|
||||
use_custom_tts,
|
||||
custom_tts_endpoint,
|
||||
use_custom_stt,
|
||||
custom_stt_endpoint
|
||||
} = req.body;
|
||||
|
||||
const newCred = {
|
||||
...o,
|
||||
region,
|
||||
vendor,
|
||||
aws_region,
|
||||
use_custom_tts,
|
||||
custom_tts_endpoint,
|
||||
use_custom_stt,
|
||||
custom_stt_endpoint
|
||||
};
|
||||
logger.info({o, newCred}, 'updating speech credential with this new credential');
|
||||
obj.credential = encryptCredential(newCred);
|
||||
obj.vendor = vendor;
|
||||
}
|
||||
else {
|
||||
logger.info({sid}, 'speech credential not found!!');
|
||||
}
|
||||
} catch (err) {
|
||||
logger.error({err}, 'error updating speech credential');
|
||||
}
|
||||
|
||||
logger.info({obj}, 'updating speech credential with changes');
|
||||
const rowsAffected = await SpeechCredential.update(sid, obj);
|
||||
if (rowsAffected === 0) {
|
||||
return res.sendStatus(404);
|
||||
@@ -280,10 +370,24 @@ router.get('/:sid/test', async(req, res) => {
|
||||
}
|
||||
}
|
||||
else if (cred.vendor === 'microsoft') {
|
||||
const {api_key, region} = credential;
|
||||
const {
|
||||
api_key,
|
||||
region,
|
||||
use_custom_tts,
|
||||
custom_tts_endpoint,
|
||||
use_custom_stt,
|
||||
custom_stt_endpoint
|
||||
} = credential;
|
||||
if (cred.use_for_tts) {
|
||||
try {
|
||||
await testMicrosoftTts(logger, {api_key, region});
|
||||
await testMicrosoftTts(logger, {
|
||||
api_key,
|
||||
region,
|
||||
use_custom_tts,
|
||||
custom_tts_endpoint,
|
||||
use_custom_stt,
|
||||
custom_stt_endpoint
|
||||
});
|
||||
results.tts.status = 'ok';
|
||||
SpeechCredential.ttsTestResult(sid, true);
|
||||
} catch (err) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const uuid = require('uuid').v4;
|
||||
const { v4: uuid } = require('uuid');
|
||||
const bent = require('bent');
|
||||
const Account = require('../../models/account');
|
||||
const {promisePool} = require('../../db');
|
||||
@@ -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;
|
||||
|
||||
@@ -909,6 +909,15 @@ paths:
|
||||
type: string
|
||||
description: sip password to authenticate with, if registration is required
|
||||
example: bar
|
||||
register_from_user:
|
||||
type: string
|
||||
description: optional username to apply in From header
|
||||
register_from_domain:
|
||||
type: string
|
||||
description: optional domain to apply in From header
|
||||
register_public_ip_in_contact:
|
||||
type: boolean
|
||||
description: if true, use our public ip in Contact header; otherwise, use sip realm
|
||||
tech_prefix:
|
||||
type: string
|
||||
description: prefix to be applied to the called number for outbound call attempts
|
||||
@@ -1346,6 +1355,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 +1815,7 @@ paths:
|
||||
summary: test a speech credential
|
||||
operationId: testSpeechCredential
|
||||
parameters:
|
||||
- name: AccountSid
|
||||
- name: ServiceProviderSid
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
@@ -1849,6 +1864,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 +2794,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 +3015,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
|
||||
@@ -2982,6 +3419,10 @@ paths:
|
||||
type: string
|
||||
description: The calling party number
|
||||
example: "16172375089"
|
||||
fromHost:
|
||||
type: string
|
||||
description: The hostname to put in the SIP From header of the INVITE
|
||||
example: "blf.finotel.com"
|
||||
timeout:
|
||||
type: integer
|
||||
description: the number of seconds to wait for call to be answered. Defaults to 60.
|
||||
@@ -3102,9 +3543,12 @@ paths:
|
||||
enum:
|
||||
- completed
|
||||
- no-answer
|
||||
conf_mute:
|
||||
type: boolean
|
||||
conf_status:
|
||||
conf_mute_status:
|
||||
type: string
|
||||
enum:
|
||||
- mute
|
||||
- unmute
|
||||
conf_hold_status:
|
||||
type: string
|
||||
enum:
|
||||
- hold
|
||||
@@ -3120,8 +3564,35 @@ paths:
|
||||
- mute
|
||||
- unmute
|
||||
whisper:
|
||||
$ref: '#/components/schemas/Webhook'
|
||||
$ref: '#/components/schemas/Webhook'
|
||||
sip_request:
|
||||
type: object
|
||||
properties:
|
||||
method:
|
||||
type: string
|
||||
content_type:
|
||||
type: string
|
||||
content:
|
||||
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
|
||||
202:
|
||||
description: Accepted
|
||||
400:
|
||||
@@ -4053,6 +4524,12 @@ components:
|
||||
type: string
|
||||
register_password:
|
||||
type: string
|
||||
register_from_user:
|
||||
type: string
|
||||
register_from_domain:
|
||||
type: string
|
||||
register_public_ip_in_contact:
|
||||
type: boolean
|
||||
tech_prefix:
|
||||
type: string
|
||||
inbound_auth_username:
|
||||
@@ -4067,6 +4544,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: []
|
||||
@@ -2,6 +2,8 @@ const ttsGoogle = require('@google-cloud/text-to-speech');
|
||||
const sttGoogle = require('@google-cloud/speech').v1p1beta1;
|
||||
const Polly = require('aws-sdk/clients/polly');
|
||||
const AWS = require('aws-sdk');
|
||||
const { Deepgram } = require('@deepgram/sdk');
|
||||
const sdk = require('microsoft-cognitiveservices-speech-sdk');
|
||||
const bent = require('bent');
|
||||
const fs = require('fs');
|
||||
|
||||
@@ -32,6 +34,65 @@ const testGoogleStt = async(logger, credentials) => {
|
||||
}
|
||||
};
|
||||
|
||||
const testDeepgramStt = async(logger, credentials) => {
|
||||
const {api_key} = credentials;
|
||||
const deepgram = new Deepgram(api_key);
|
||||
|
||||
const mimetype = 'audio/wav';
|
||||
const source = {
|
||||
buffer: fs.readFileSync(`${__dirname}/../../data/test_audio.wav`),
|
||||
mimetype: mimetype
|
||||
};
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
// Send the audio to Deepgram and get the response
|
||||
deepgram.transcription
|
||||
.preRecorded(source, {punctuate: true})
|
||||
.then((response) => {
|
||||
//logger.debug({response}, 'got transcript');
|
||||
if (response?.results?.channels[0]?.alternatives?.length > 0) resolve(response);
|
||||
else reject(new Error('no transcript returned'));
|
||||
return;
|
||||
})
|
||||
.catch((err) => {
|
||||
logger.info({err}, 'failed to get deepgram transcript');
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const testMicrosoftStt = async(logger, credentials) => {
|
||||
const {api_key, region} = credentials;
|
||||
|
||||
const speechConfig = sdk.SpeechConfig.fromSubscription(api_key, region);
|
||||
const audioConfig = sdk.AudioConfig.fromWavFileInput(fs.readFileSync(`${__dirname}/../../data/test_audio.wav`));
|
||||
speechConfig.speechRecognitionLanguage = 'en-US';
|
||||
const speechRecognizer = new sdk.SpeechRecognizer(speechConfig, audioConfig);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
speechRecognizer.recognizeOnceAsync((result) => {
|
||||
switch (result.reason) {
|
||||
case sdk.ResultReason.RecognizedSpeech:
|
||||
resolve();
|
||||
break;
|
||||
case sdk.ResultReason.NoMatch:
|
||||
reject('Speech could not be recognized.');
|
||||
break;
|
||||
case sdk.ResultReason.Canceled:
|
||||
const cancellation = sdk.CancellationDetails.fromResult(result);
|
||||
logger.info(`CANCELED: Reason=${cancellation.reason}`);
|
||||
if (cancellation.reason == sdk.CancellationReason.Error) {
|
||||
logger.info(`CANCELED: ErrorCode=${cancellation.ErrorCode}`);
|
||||
logger.info(`CANCELED: ErrorDetails=${cancellation.errorDetails}`);
|
||||
}
|
||||
reject(cancellation.reason);
|
||||
break;
|
||||
}
|
||||
speechRecognizer.close();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const testAwsTts = (logger, credentials) => {
|
||||
const polly = new Polly(credentials);
|
||||
return new Promise((resolve, reject) => {
|
||||
@@ -54,8 +115,27 @@ const testAwsStt = (logger, credentials) => {
|
||||
};
|
||||
|
||||
const testMicrosoftTts = async(logger, credentials) => {
|
||||
const {api_key, region} = credentials;
|
||||
const {
|
||||
api_key,
|
||||
region,
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
use_custom_tts,
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
custom_tts_endpoint,
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
use_custom_stt,
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
custom_stt_endpoint
|
||||
} = credentials;
|
||||
|
||||
logger.info({
|
||||
api_key,
|
||||
region,
|
||||
use_custom_tts,
|
||||
custom_tts_endpoint,
|
||||
use_custom_stt,
|
||||
custom_stt_endpoint
|
||||
}, 'testing microsoft tts');
|
||||
if (!api_key) throw new Error('testMicrosoftTts: credentials are missing api_key');
|
||||
if (!region) throw new Error('testMicrosoftTts: credentials are missing region');
|
||||
try {
|
||||
@@ -70,11 +150,6 @@ const testMicrosoftTts = async(logger, credentials) => {
|
||||
}
|
||||
};
|
||||
|
||||
const testMicrosoftStt = async(logger, credentials) => {
|
||||
//TODO
|
||||
return true;
|
||||
};
|
||||
|
||||
const testWellSaidTts = async(logger, credentials) => {
|
||||
const {api_key} = credentials;
|
||||
try {
|
||||
@@ -108,4 +183,5 @@ module.exports = {
|
||||
testMicrosoftTts,
|
||||
testMicrosoftStt,
|
||||
testWellSaidStt,
|
||||
testDeepgramStt
|
||||
};
|
||||
|
||||
5630
package-lock.json
generated
5630
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
46
package.json
46
package.json
@@ -1,15 +1,16 @@
|
||||
{
|
||||
"name": "jambonz-api-server",
|
||||
"version": "v0.7.2",
|
||||
"version": "v0.7.9",
|
||||
"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=info 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",
|
||||
"jslint": "eslint app.js lib"
|
||||
"jslint": "eslint app.js lib",
|
||||
"prepare": "husky install"
|
||||
},
|
||||
"author": "Dave Horton",
|
||||
"license": "MIT",
|
||||
@@ -17,39 +18,42 @@
|
||||
"url": "https://github.com/jambonz/jambonz-api-server.git"
|
||||
},
|
||||
"dependencies": {
|
||||
"@google-cloud/speech": "^4.9.0",
|
||||
"@google-cloud/text-to-speech": "^3.4.0",
|
||||
"@jambonz/db-helpers": "^0.6.16",
|
||||
"@jambonz/realtimedb-helpers": "^0.4.17",
|
||||
"@jambonz/time-series": "^0.1.6",
|
||||
"@deepgram/sdk": "^1.10.2",
|
||||
"@google-cloud/speech": "^5.1.0",
|
||||
"@google-cloud/text-to-speech": "^4.0.3",
|
||||
"@jambonz/db-helpers": "^0.7.3",
|
||||
"@jambonz/realtimedb-helpers": "^0.5.9",
|
||||
"@jambonz/time-series": "^0.2.5",
|
||||
"argon2-ffi": "^2.0.0",
|
||||
"aws-sdk": "^2.1049.0",
|
||||
"aws-sdk": "^2.1152.0",
|
||||
"bent": "^7.3.12",
|
||||
"cors": "^2.8.5",
|
||||
"debug": "^4.3.3",
|
||||
"express": "^4.17.1",
|
||||
"debug": "^4.3.4",
|
||||
"express": "^4.18.1",
|
||||
"express-rate-limit": "^6.4.0",
|
||||
"form-data": "^2.5.1",
|
||||
"form-urlencoded": "^6.0.5",
|
||||
"jsonwebtoken": "^8.5.1",
|
||||
"form-urlencoded": "^6.1.0",
|
||||
"helmet": "^5.1.0",
|
||||
"jsonwebtoken": "^9.0.0",
|
||||
"mailgun.js": "^3.7.3",
|
||||
"microsoft-cognitiveservices-speech-sdk": "^1.19.0",
|
||||
"microsoft-cognitiveservices-speech-sdk": "^1.24.1",
|
||||
"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.195.0",
|
||||
"swagger-ui-express": "^4.3.0",
|
||||
"uuid": "^3.4.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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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}`, {
|
||||
|
||||
@@ -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
82
test/call-test.js
Normal 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') });
|
||||
});
|
||||
@@ -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
|
||||
23
test/feature-server-test-scaffold/Dockerfile
Normal file
23
test/feature-server-test-scaffold/Dockerfile
Normal 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" ]
|
||||
72
test/feature-server-test-scaffold/app.js
Normal file
72
test/feature-server-test-scaffold/app.js
Normal 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]);
|
||||
}
|
||||
}
|
||||
873
test/feature-server-test-scaffold/package-lock.json
generated
Normal file
873
test/feature-server-test-scaffold/package-lock.json
generated
Normal 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="
|
||||
}
|
||||
}
|
||||
}
|
||||
15
test/feature-server-test-scaffold/package.json
Normal file
15
test/feature-server-test-scaffold/package.json
Normal 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"
|
||||
}
|
||||
}
|
||||
@@ -13,5 +13,7 @@ require('./ms-teams');
|
||||
require('./speech-credentials');
|
||||
require('./recent-calls');
|
||||
require('./webapp_tests');
|
||||
//require('./homer');
|
||||
// require('./homer');
|
||||
require('./call-test');
|
||||
require('./password-settings');
|
||||
require('./docker_stop');
|
||||
|
||||
71
test/password-settings.js
Normal file
71
test/password-settings.js
Normal file
@@ -0,0 +1,71 @@
|
||||
const test = require('tape') ;
|
||||
const ADMIN_TOKEN = '38700987-c7a4-4685-a5bb-af378f9734de';
|
||||
const authAdmin = {bearer: ADMIN_TOKEN};
|
||||
const request = require('request-promise-native').defaults({
|
||||
baseUrl: 'http://127.0.0.1:3000/v1'
|
||||
});
|
||||
|
||||
|
||||
process.on('unhandledRejection', (reason, p) => {
|
||||
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
|
||||
});
|
||||
|
||||
test('password settings tests', async(t) => {
|
||||
|
||||
/* Check Default Password Settings */
|
||||
result = await request.get('/PasswordSettings', {
|
||||
auth: authAdmin,
|
||||
json: true,
|
||||
});
|
||||
t.ok(result.min_password_length == 8 &&
|
||||
!result.require_digit &&
|
||||
!result.require_special_character, "default password settings is correct!")
|
||||
|
||||
/* Post New Password settings*/
|
||||
|
||||
result = await request.post('/PasswordSettings', {
|
||||
auth: authAdmin,
|
||||
json: true,
|
||||
resolveWithFullResponse: true,
|
||||
body: {
|
||||
min_password_length: 15,
|
||||
require_digit: 1,
|
||||
require_special_character: 1
|
||||
}
|
||||
});
|
||||
|
||||
t.ok(result.statusCode === 201, 'successfully added a password settings');
|
||||
|
||||
/* Check Password Settings*/
|
||||
result = await request.get('/PasswordSettings', {
|
||||
auth: authAdmin,
|
||||
json: true,
|
||||
});
|
||||
|
||||
t.ok(result.min_password_length === 15 &&
|
||||
result.require_digit === 1 &&
|
||||
result.require_special_character === 1, 'successfully queried password settings');
|
||||
|
||||
/* Update Password settings*/
|
||||
result = await request.post('/PasswordSettings', {
|
||||
auth: authAdmin,
|
||||
json: true,
|
||||
resolveWithFullResponse: true,
|
||||
body: {
|
||||
min_password_length: 10,
|
||||
require_special_character: 0
|
||||
}
|
||||
});
|
||||
|
||||
t.ok(result.statusCode === 201, 'successfully updated a password settings');
|
||||
|
||||
/* Check Password Settings After update*/
|
||||
result = await request.get('/PasswordSettings', {
|
||||
auth: authAdmin,
|
||||
json: true,
|
||||
});
|
||||
|
||||
t.ok(result.min_password_length === 10 &&
|
||||
result.require_digit === 1 &&
|
||||
result.require_special_character === 0, 'successfully queried password settings after updated');
|
||||
});
|
||||
@@ -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 */
|
||||
/*
|
||||
|
||||
@@ -106,6 +106,33 @@ test('service provider tests', async(t) => {
|
||||
});
|
||||
t.ok(result.statusCode === 204, 'successfully updated service provider');
|
||||
|
||||
/* add an api key for a service provider */
|
||||
result = await request.post(`/ApiKeys`, {
|
||||
auth: authAdmin,
|
||||
json: true,
|
||||
resolveWithFullResponse: true,
|
||||
body: {
|
||||
service_provider_sid: sid
|
||||
}
|
||||
});
|
||||
t.ok(result.statusCode === 201, 'successfully added an api_key for a service provider');
|
||||
const apiKeySid = result.body.sid;
|
||||
|
||||
/* query all api keys for a service provider */
|
||||
result = await request.get(`/ServiceProviders/${sid}/ApiKeys`, {
|
||||
auth: authAdmin,
|
||||
json: true,
|
||||
});
|
||||
t.ok(result.length === 1 , 'successfully queried all service provider keys');
|
||||
|
||||
/* delete an api key */
|
||||
result = await request.delete(`/ApiKeys/${apiKeySid}`, {
|
||||
auth: authAdmin,
|
||||
json: true,
|
||||
resolveWithFullResponse: true,
|
||||
});
|
||||
t.ok(result.statusCode === 204, 'successfully deleted an api_key for a service provider');
|
||||
|
||||
/* add a predefined carrier for a service provider */
|
||||
result = await request.post(`/ServiceProviders/${sid}/PredefinedCarriers/7d509a18-bbff-4c5d-b21e-b99bf8f8c49a`, {
|
||||
auth: authAdmin,
|
||||
@@ -113,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,
|
||||
|
||||
@@ -30,7 +30,9 @@ test('speech credentials tests', async(t) => {
|
||||
json: true,
|
||||
body: {
|
||||
vendor: 'google',
|
||||
service_key: jsonKey
|
||||
service_key: jsonKey,
|
||||
use_for_tts: true,
|
||||
use_for_stt: true
|
||||
}
|
||||
});
|
||||
t.ok(result.statusCode === 201, 'successfully added a speech credential to service provider');
|
||||
@@ -61,7 +63,9 @@ test('speech credentials tests', async(t) => {
|
||||
json: true,
|
||||
body: {
|
||||
vendor: 'google',
|
||||
service_key: jsonKey
|
||||
service_key: jsonKey,
|
||||
use_for_tts: true,
|
||||
use_for_stt: true
|
||||
}
|
||||
});
|
||||
t.ok(result.statusCode === 201, 'successfully added speech credential');
|
||||
@@ -110,20 +114,20 @@ test('speech credentials tests', async(t) => {
|
||||
});
|
||||
t.ok(result.statusCode === 204, 'successfully deleted speech credential');
|
||||
|
||||
/* add a credential for microsoft */
|
||||
if (process.env.MICROSOFT_API_KEY && process.env.MICROSOFT_REGION) {
|
||||
/* add / test a credential for google */
|
||||
if (process.env.GCP_JSON_KEY) {
|
||||
result = await request.post(`/Accounts/${account_sid}/SpeechCredentials`, {
|
||||
resolveWithFullResponse: true,
|
||||
auth: authUser,
|
||||
json: true,
|
||||
body: {
|
||||
vendor: 'microsoft',
|
||||
vendor: 'google',
|
||||
use_for_tts: true,
|
||||
api_key: process.env.MICROSOFT_API_KEY,
|
||||
region: process.env.MICROSOFT_REGION
|
||||
use_for_stt: true,
|
||||
service_key: process.env.GCP_JSON_KEY
|
||||
}
|
||||
});
|
||||
t.ok(result.statusCode === 201, 'successfully added speech credential');
|
||||
t.ok(result.statusCode === 201, 'successfully added speech credential for google');
|
||||
const ms_sid = result.body.sid;
|
||||
|
||||
/* test the speech credential */
|
||||
@@ -132,7 +136,37 @@ test('speech credentials tests', async(t) => {
|
||||
auth: authUser,
|
||||
json: true,
|
||||
});
|
||||
console.log(JSON.stringify(result));
|
||||
//console.log(JSON.stringify(result));
|
||||
t.ok(result.statusCode === 200 && result.body.tts.status === 'ok', 'successfully tested speech credential for google tts');
|
||||
t.ok(result.statusCode === 200 && result.body.stt.status === 'ok', 'successfully tested speech credential for google stt');
|
||||
}
|
||||
|
||||
/* add / test a credential for microsoft */
|
||||
if (process.env.MICROSOFT_API_KEY && process.env.MICROSOFT_REGION) {
|
||||
result = await request.post(`/Accounts/${account_sid}/SpeechCredentials`, {
|
||||
resolveWithFullResponse: true,
|
||||
auth: authUser,
|
||||
json: true,
|
||||
body: {
|
||||
vendor: 'microsoft',
|
||||
use_for_tts: true,
|
||||
use_for_stt: true,
|
||||
api_key: process.env.MICROSOFT_API_KEY,
|
||||
region: process.env.MICROSOFT_REGION
|
||||
}
|
||||
});
|
||||
t.ok(result.statusCode === 201, 'successfully added speech credential for microsoft');
|
||||
const ms_sid = result.body.sid;
|
||||
|
||||
/* test the speech credential */
|
||||
result = await request.get(`/Accounts/${account_sid}/SpeechCredentials/${ms_sid}/test`, {
|
||||
resolveWithFullResponse: true,
|
||||
auth: authUser,
|
||||
json: true,
|
||||
});
|
||||
//console.log(JSON.stringify(result));
|
||||
t.ok(result.statusCode === 200 && result.body.tts.status === 'ok', 'successfully tested speech credential for microsoft tts');
|
||||
t.ok(result.statusCode === 200 && result.body.stt.status === 'ok', 'successfully tested speech credential for microsoft stt');
|
||||
}
|
||||
|
||||
/* add a credential for wellsaid */
|
||||
@@ -156,7 +190,8 @@ test('speech credentials tests', async(t) => {
|
||||
auth: authUser,
|
||||
json: true,
|
||||
});
|
||||
console.log(JSON.stringify(result));
|
||||
//console.log(JSON.stringify(result));
|
||||
t.ok(result.statusCode === 200 && result.body.tts.status === 'ok', 'successfully tested speech credential for wellsaid');
|
||||
|
||||
/* delete the credential */
|
||||
result = await request.delete(`/Accounts/${account_sid}/SpeechCredentials/${ms_sid}`, {
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
const bytesToUuid = require("uuid/lib/bytesToUuid");
|
||||
const uuid = require('uuid').v4;
|
||||
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};
|
||||
@@ -88,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,
|
||||
@@ -95,5 +134,7 @@ module.exports = {
|
||||
createAccount,
|
||||
createApplication,
|
||||
createApiKey,
|
||||
deleteObjectBySid
|
||||
deleteObjectBySid,
|
||||
createGoogleSpeechCredentials,
|
||||
getLastRequestFromFeatureServer
|
||||
};
|
||||
|
||||
@@ -54,7 +54,9 @@ test('voip carrier tests', async(t) => {
|
||||
requires_register: true,
|
||||
register_username: 'foo',
|
||||
register_sip_realm: 'bar',
|
||||
register_password: 'baz'
|
||||
register_password: 'baz',
|
||||
register_from_user: 'fromme',
|
||||
register_from_domain: 'fromdomain'
|
||||
}
|
||||
});
|
||||
t.ok(result.statusCode === 204, 'successfully updated voip carrier');
|
||||
|
||||
Reference in New Issue
Block a user