revamp db - refactor webhooks

This commit is contained in:
Dave Horton
2020-01-28 10:30:07 -05:00
parent 10074504c5
commit 87208bc669
13 changed files with 675 additions and 252 deletions

4
.gitignore vendored
View File

@@ -38,4 +38,6 @@ node_modules
examples/*
create_db.sql
create_db.sql
.vscode

View File

@@ -5,48 +5,37 @@ DROP TABLE IF EXISTS `call_routes`;
DROP TABLE IF EXISTS `conference_participants`;
DROP TABLE IF EXISTS `queue_members`;
DROP TABLE IF EXISTS `calls`;
DROP TABLE IF EXISTS `phone_numbers`;
DROP TABLE IF EXISTS `applications`;
DROP TABLE IF EXISTS `conferences`;
DROP TABLE IF EXISTS `lcr_carrier_set_entry`;
DROP TABLE IF EXISTS `lcr_routes`;
DROP TABLE IF EXISTS `queue_members`;
DROP TABLE IF EXISTS `queues`;
DROP TABLE IF EXISTS `calls`;
DROP TABLE IF EXISTS `subscriptions`;
DROP TABLE IF EXISTS `registrations`;
DROP TABLE IF EXISTS `api_keys`;
DROP TABLE IF EXISTS `accounts`;
DROP TABLE IF EXISTS `service_providers`;
DROP TABLE IF EXISTS `phone_numbers`;
DROP TABLE IF EXISTS `sip_gateways`;
DROP TABLE IF EXISTS `voip_carriers`;
CREATE TABLE IF NOT EXISTS `applications`
(
`application_sid` CHAR(36) NOT NULL UNIQUE ,
`name` VARCHAR(255) NOT NULL,
`account_sid` CHAR(36) NOT NULL,
`call_hook` VARCHAR(255) NOT NULL,
`call_status_hook` VARCHAR(255) NOT NULL,
`hook_basic_auth_user` VARCHAR(255),
`hook_basic_auth_password` VARCHAR(255),
`hook_http_method` ENUM('get','post') NOT NULL DEFAULT 'get',
PRIMARY KEY (`application_sid`)
) ENGINE=InnoDB COMMENT='A defined set of behaviors to be applied to phone calls with';
DROP TABLE IF EXISTS `applications`;
DROP TABLE IF EXISTS `accounts`;
DROP TABLE IF EXISTS `service_providers`;
DROP TABLE IF EXISTS `webhooks`;
CREATE TABLE IF NOT EXISTS `call_routes`
(
@@ -145,18 +134,6 @@ CREATE TABLE IF NOT EXISTS `calls`
PRIMARY KEY (`call_sid`)
) ENGINE=InnoDB COMMENT='A phone call';
CREATE TABLE IF NOT EXISTS `service_providers`
(
`service_provider_sid` CHAR(36) NOT NULL UNIQUE ,
`name` VARCHAR(255) NOT NULL UNIQUE ,
`description` VARCHAR(255),
`root_domain` VARCHAR(255) UNIQUE ,
`registration_hook` VARCHAR(255),
`hook_basic_auth_user` VARCHAR(255),
`hook_basic_auth_password` VARCHAR(255),
PRIMARY KEY (`service_provider_sid`)
) ENGINE=InnoDB COMMENT='An organization that provides communication services to its ';
CREATE TABLE IF NOT EXISTS `api_keys`
(
`api_key_sid` CHAR(36) NOT NULL UNIQUE ,
@@ -166,19 +143,6 @@ CREATE TABLE IF NOT EXISTS `api_keys`
PRIMARY KEY (`api_key_sid`)
) ENGINE=InnoDB COMMENT='An authorization token that is used to access the REST api';
CREATE TABLE IF NOT EXISTS `accounts`
(
`account_sid` CHAR(36) NOT NULL UNIQUE ,
`name` VARCHAR(255) NOT NULL,
`sip_realm` VARCHAR(255) UNIQUE ,
`service_provider_sid` CHAR(36) NOT NULL,
`registration_hook` VARCHAR(255),
`hook_basic_auth_user` VARCHAR(255),
`hook_basic_auth_password` VARCHAR(255),
`is_active` BOOLEAN NOT NULL DEFAULT true,
PRIMARY KEY (`account_sid`)
) ENGINE=InnoDB COMMENT='A single end-user of the platform';
CREATE TABLE IF NOT EXISTS `subscriptions`
(
`id` INTEGER(10) UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE ,
@@ -196,6 +160,16 @@ CREATE TABLE IF NOT EXISTS `voip_carriers`
PRIMARY KEY (`voip_carrier_sid`)
) ENGINE=InnoDB COMMENT='An external organization that can provide sip trunking and D';
CREATE TABLE IF NOT EXISTS `webhooks`
(
`webhook_sid` CHAR(36) NOT NULL UNIQUE ,
`url` VARCHAR(255) NOT NULL,
`method` ENUM("get","post") NOT NULL DEFAULT 'post',
`username` VARCHAR(255),
`password` VARCHAR(255),
PRIMARY KEY (`webhook_sid`)
);
CREATE TABLE IF NOT EXISTS `phone_numbers`
(
`phone_number_sid` CHAR(36) UNIQUE ,
@@ -228,15 +202,45 @@ CREATE TABLE IF NOT EXISTS `sip_gateways`
PRIMARY KEY (`sip_gateway_sid`)
);
CREATE UNIQUE INDEX `applications_idx_name` ON `applications` (`account_sid`,`name`);
CREATE TABLE IF NOT EXISTS `applications`
(
`application_sid` CHAR(36) NOT NULL UNIQUE ,
`name` VARCHAR(255) NOT NULL,
`account_sid` CHAR(36) NOT NULL,
`call_hook_sid` CHAR(36),
`call_status_hook_sid` CHAR(36),
`speech_synthesis_vendor` VARCHAR(64) NOT NULL DEFAULT 'google',
`speech_synthesis_voice` VARCHAR(64) NOT NULL DEFAULT 'en-US-Wavenet-C',
`speech_recognizer_vendor` VARCHAR(64) NOT NULL DEFAULT 'google',
`speech_recognizer_language` VARCHAR(64) NOT NULL DEFAULT 'en-US',
PRIMARY KEY (`application_sid`)
) ENGINE=InnoDB COMMENT='A defined set of behaviors to be applied to phone calls with';
CREATE INDEX `applications_application_sid_idx` ON `applications` (`application_sid`);
CREATE INDEX `applications_name_idx` ON `applications` (`name`);
CREATE INDEX `applications_account_sid_idx` ON `applications` (`account_sid`);
ALTER TABLE `applications` ADD FOREIGN KEY account_sid_idxfk (`account_sid`) REFERENCES `accounts` (`account_sid`);
CREATE TABLE IF NOT EXISTS `service_providers`
(
`service_provider_sid` CHAR(36) NOT NULL UNIQUE ,
`name` VARCHAR(255) NOT NULL UNIQUE ,
`description` VARCHAR(255),
`root_domain` VARCHAR(255) UNIQUE ,
`registration_hook_sid` CHAR(36),
PRIMARY KEY (`service_provider_sid`)
) ENGINE=InnoDB COMMENT='An organization that provides communication services to its ';
CREATE TABLE IF NOT EXISTS `accounts`
(
`account_sid` CHAR(36) NOT NULL UNIQUE ,
`name` VARCHAR(255) NOT NULL,
`sip_realm` VARCHAR(255) UNIQUE ,
`service_provider_sid` CHAR(36) NOT NULL,
`registration_hook_sid` CHAR(36),
`device_calling_hook_sid` CHAR(36),
`error_hook_sid` CHAR(36),
`is_active` BOOLEAN NOT NULL DEFAULT true,
PRIMARY KEY (`account_sid`)
) ENGINE=InnoDB COMMENT='A single end-user of the platform';
CREATE INDEX `call_routes_call_route_sid_idx` ON `call_routes` (`call_route_sid`);
ALTER TABLE `call_routes` ADD FOREIGN KEY account_sid_idxfk_1 (`account_sid`) REFERENCES `accounts` (`account_sid`);
ALTER TABLE `call_routes` ADD FOREIGN KEY account_sid_idxfk (`account_sid`) REFERENCES `accounts` (`account_sid`);
ALTER TABLE `call_routes` ADD FOREIGN KEY application_sid_idxfk (`application_sid`) REFERENCES `applications` (`application_sid`);
@@ -265,31 +269,23 @@ ALTER TABLE `calls` ADD FOREIGN KEY inbound_user_sid_idxfk (`inbound_user_sid`)
ALTER TABLE `calls` ADD FOREIGN KEY outbound_user_sid_idxfk (`outbound_user_sid`) REFERENCES `registrations` (`registration_sid`);
CREATE INDEX `service_providers_service_provider_sid_idx` ON `service_providers` (`service_provider_sid`);
CREATE INDEX `service_providers_name_idx` ON `service_providers` (`name`);
CREATE INDEX `service_providers_root_domain_idx` ON `service_providers` (`root_domain`);
CREATE INDEX `api_keys_api_key_sid_idx` ON `api_keys` (`api_key_sid`);
CREATE INDEX `api_keys_account_sid_idx` ON `api_keys` (`account_sid`);
ALTER TABLE `api_keys` ADD FOREIGN KEY account_sid_idxfk_2 (`account_sid`) REFERENCES `accounts` (`account_sid`);
ALTER TABLE `api_keys` ADD FOREIGN KEY account_sid_idxfk_1 (`account_sid`) REFERENCES `accounts` (`account_sid`);
CREATE INDEX `api_keys_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`);
CREATE INDEX `accounts_account_sid_idx` ON `accounts` (`account_sid`);
CREATE INDEX `accounts_name_idx` ON `accounts` (`name`);
CREATE INDEX `accounts_sip_realm_idx` ON `accounts` (`sip_realm`);
CREATE INDEX `accounts_service_provider_sid_idx` ON `accounts` (`service_provider_sid`);
ALTER TABLE `accounts` ADD FOREIGN KEY service_provider_sid_idxfk_1 (`service_provider_sid`) REFERENCES `service_providers` (`service_provider_sid`);
ALTER TABLE `subscriptions` ADD FOREIGN KEY registration_sid_idxfk (`registration_sid`) REFERENCES `registrations` (`registration_sid`);
CREATE INDEX `voip_carriers_voip_carrier_sid_idx` ON `voip_carriers` (`voip_carrier_sid`);
CREATE INDEX `voip_carriers_name_idx` ON `voip_carriers` (`name`);
CREATE INDEX `webhooks_webhook_sid_idx` ON `webhooks` (`webhook_sid`);
CREATE INDEX `phone_numbers_phone_number_sid_idx` ON `phone_numbers` (`phone_number_sid`);
CREATE INDEX `phone_numbers_voip_carrier_sid_idx` ON `phone_numbers` (`voip_carrier_sid`);
ALTER TABLE `phone_numbers` ADD FOREIGN KEY voip_carrier_sid_idxfk (`voip_carrier_sid`) REFERENCES `voip_carriers` (`voip_carrier_sid`);
ALTER TABLE `phone_numbers` ADD FOREIGN KEY account_sid_idxfk_3 (`account_sid`) REFERENCES `accounts` (`account_sid`);
ALTER TABLE `phone_numbers` ADD FOREIGN KEY account_sid_idxfk_2 (`account_sid`) REFERENCES `accounts` (`account_sid`);
ALTER TABLE `phone_numbers` ADD FOREIGN KEY application_sid_idxfk_2 (`application_sid`) REFERENCES `applications` (`application_sid`);
@@ -300,3 +296,31 @@ ALTER TABLE `lcr_carrier_set_entry` ADD FOREIGN KEY voip_carrier_sid_idxfk_1 (`v
CREATE UNIQUE INDEX `sip_gateways_sip_gateway_idx_hostport` ON `sip_gateways` (`ipv4`,`port`);
ALTER TABLE `sip_gateways` ADD FOREIGN KEY voip_carrier_sid_idxfk_2 (`voip_carrier_sid`) REFERENCES `voip_carriers` (`voip_carrier_sid`);
CREATE UNIQUE INDEX `applications_idx_name` ON `applications` (`account_sid`,`name`);
CREATE INDEX `applications_application_sid_idx` ON `applications` (`application_sid`);
CREATE INDEX `applications_name_idx` ON `applications` (`name`);
CREATE INDEX `applications_account_sid_idx` ON `applications` (`account_sid`);
ALTER TABLE `applications` ADD FOREIGN KEY account_sid_idxfk_3 (`account_sid`) REFERENCES `accounts` (`account_sid`);
ALTER TABLE `applications` ADD FOREIGN KEY call_hook_sid_idxfk (`call_hook_sid`) REFERENCES `webhooks` (`webhook_sid`);
ALTER TABLE `applications` ADD FOREIGN KEY call_status_hook_sid_idxfk (`call_status_hook_sid`) REFERENCES `webhooks` (`webhook_sid`);
CREATE INDEX `service_providers_service_provider_sid_idx` ON `service_providers` (`service_provider_sid`);
CREATE INDEX `service_providers_name_idx` ON `service_providers` (`name`);
CREATE INDEX `service_providers_root_domain_idx` ON `service_providers` (`root_domain`);
ALTER TABLE `service_providers` ADD FOREIGN KEY registration_hook_sid_idxfk (`registration_hook_sid`) REFERENCES `webhooks` (`webhook_sid`);
CREATE INDEX `accounts_account_sid_idx` ON `accounts` (`account_sid`);
CREATE INDEX `accounts_name_idx` ON `accounts` (`name`);
CREATE INDEX `accounts_sip_realm_idx` ON `accounts` (`sip_realm`);
CREATE INDEX `accounts_service_provider_sid_idx` ON `accounts` (`service_provider_sid`);
ALTER TABLE `accounts` ADD FOREIGN KEY service_provider_sid_idxfk_1 (`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`);
ALTER TABLE `accounts` ADD FOREIGN KEY device_calling_hook_sid_idxfk (`device_calling_hook_sid`) REFERENCES `webhooks` (`webhook_sid`);
ALTER TABLE `accounts` ADD FOREIGN KEY error_hook_sid_idxfk (`error_hook_sid`) REFERENCES `webhooks` (`webhook_sid`);

View File

@@ -6,8 +6,8 @@
<comment><![CDATA[An active sip registration]]></comment>
<tableType><![CDATA[InnoDB]]></tableType>
<location>
<x>843.00</x>
<y>989.00</y>
<x>854.00</x>
<y>1190.00</y>
</location>
<size>
<width>282.00</width>
@@ -48,7 +48,7 @@
<type><![CDATA[VARCHAR(255)]]></type>
<uid><![CDATA[7B5CFAB8-1489-4FCC-AA0F-8F5159C2A4E6]]></uid>
</SQLField>
<labelWindowIndex><![CDATA[16]]></labelWindowIndex>
<labelWindowIndex><![CDATA[17]]></labelWindowIndex>
<objectComment><![CDATA[An active sip registration]]></objectComment>
<ui.treeExpanded><![CDATA[1]]></ui.treeExpanded>
<uid><![CDATA[09D20234-503B-42B4-B34D-BDE7FC2FFA6E]]></uid>
@@ -93,7 +93,7 @@
<type><![CDATA[VARCHAR(255)]]></type>
<uid><![CDATA[4D191E4E-BAC3-4131-A06F-66CF45127D92]]></uid>
</SQLField>
<labelWindowIndex><![CDATA[15]]></labelWindowIndex>
<labelWindowIndex><![CDATA[16]]></labelWindowIndex>
<objectComment><![CDATA[A set of behaviors to be applied to parked calls]]></objectComment>
<ui.treeExpanded><![CDATA[1]]></ui.treeExpanded>
<uid><![CDATA[2FBA947F-0976-491E-B9DE-7812B44A663A]]></uid>
@@ -137,7 +137,7 @@
<type><![CDATA[VARCHAR(255)]]></type>
<uid><![CDATA[27E702F3-73ED-43F4-8B40-3C170AA17445]]></uid>
</SQLField>
<labelWindowIndex><![CDATA[4]]></labelWindowIndex>
<labelWindowIndex><![CDATA[5]]></labelWindowIndex>
<objectComment><![CDATA[An external organization that can provide sip trunking and DIDs]]></objectComment>
<ui.treeExpanded><![CDATA[1]]></ui.treeExpanded>
<uid><![CDATA[3D3136A7-AFC0-4A70-AEC3-68577955CA2E]]></uid>
@@ -202,18 +202,67 @@
<indexed><![CDATA[1]]></indexed>
<uid><![CDATA[3F553F20-AA47-471E-A650-0B4CEC9DCB0A]]></uid>
</SQLField>
<labelWindowIndex><![CDATA[14]]></labelWindowIndex>
<labelWindowIndex><![CDATA[15]]></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[webhooks]]></name>
<schema><![CDATA[]]></schema>
<location>
<x>1383.00</x>
<y>365.00</y>
</location>
<size>
<width>254.00</width>
<height>120.00</height>
</size>
<zorder>17</zorder>
<SQLField>
<name><![CDATA[webhook_sid]]></name>
<type><![CDATA[CHAR(36)]]></type>
<primaryKey>1</primaryKey>
<forcedUnique><![CDATA[1]]></forcedUnique>
<indexed><![CDATA[1]]></indexed>
<notNull><![CDATA[1]]></notNull>
<uid><![CDATA[E046BA30-BC18-483C-A5C8-766E7160F574]]></uid>
<unique><![CDATA[1]]></unique>
</SQLField>
<SQLField>
<name><![CDATA[url]]></name>
<type><![CDATA[VARCHAR(255)]]></type>
<notNull><![CDATA[1]]></notNull>
<uid><![CDATA[EB1F13D9-E99A-4F31-B5B0-78C3E0F1E344]]></uid>
</SQLField>
<SQLField>
<name><![CDATA[method]]></name>
<type><![CDATA[ENUM("get","post")]]></type>
<defaultValue><![CDATA[post]]></defaultValue>
<notNull><![CDATA[1]]></notNull>
<uid><![CDATA[6DFFE441-D825-43D4-AFFB-231D5B3F565E]]></uid>
</SQLField>
<SQLField>
<name><![CDATA[username]]></name>
<type><![CDATA[VARCHAR(255)]]></type>
<uid><![CDATA[D1EE3F3C-8C60-4F67-A7B1-77DC2A1AFBA5]]></uid>
</SQLField>
<SQLField>
<name><![CDATA[password]]></name>
<type><![CDATA[VARCHAR(255)]]></type>
<uid><![CDATA[04BB457A-D532-4780-8A58-5900094171EC]]></uid>
</SQLField>
<labelWindowIndex><![CDATA[1]]></labelWindowIndex>
<ui.treeExpanded><![CDATA[1]]></ui.treeExpanded>
<uid><![CDATA[64D64CB9-0990-4C68-BE71-F9FD43C2BE19]]></uid>
</SQLTable>
<SQLTable>
<name><![CDATA[call_routes]]></name>
<schema><![CDATA[]]></schema>
<tableType><![CDATA[InnoDB]]></tableType>
<location>
<x>1284.00</x>
<y>254.00</y>
<x>406.00</x>
<y>425.00</y>
</location>
<size>
<width>254.00</width>
@@ -272,7 +321,7 @@
<uid><![CDATA[9B4208B5-9E3B-4B76-B7F7-4E5D36B99BF2]]></uid>
<unsigned><![CDATA[0]]></unsigned>
</SQLField>
<labelWindowIndex><![CDATA[13]]></labelWindowIndex>
<labelWindowIndex><![CDATA[14]]></labelWindowIndex>
<ui.treeExpanded><![CDATA[1]]></ui.treeExpanded>
<uid><![CDATA[78584D93-2CD7-4495-9C5E-893C7B869133]]></uid>
</SQLTable>
@@ -282,8 +331,8 @@
<comment><![CDATA[A relationship between a call and a queue that it is waiting in]]></comment>
<tableType><![CDATA[InnoDB]]></tableType>
<location>
<x>837.00</x>
<y>809.00</y>
<x>871.00</x>
<y>1024.00</y>
</location>
<size>
<width>286.00</width>
@@ -337,7 +386,7 @@
<type><![CDATA[INTEGER]]></type>
<uid><![CDATA[96823D87-D5BE-4D15-BC31-BE44485DC303]]></uid>
</SQLField>
<labelWindowIndex><![CDATA[12]]></labelWindowIndex>
<labelWindowIndex><![CDATA[13]]></labelWindowIndex>
<objectComment><![CDATA[A relationship between a call and a queue that it is waiting in]]></objectComment>
<ui.treeExpanded><![CDATA[1]]></ui.treeExpanded>
<uid><![CDATA[8FA89482-40F1-46E5-8652-03CE22395A7B]]></uid>
@@ -403,7 +452,7 @@
<notNull><![CDATA[1]]></notNull>
<uid><![CDATA[01F61C68-799B-49B0-9E6A-0E2162EE5A54]]></uid>
</SQLField>
<labelWindowIndex><![CDATA[3]]></labelWindowIndex>
<labelWindowIndex><![CDATA[4]]></labelWindowIndex>
<ui.treeExpanded><![CDATA[1]]></ui.treeExpanded>
<uid><![CDATA[956025F5-0798-47F7-B76C-457814C7B52E]]></uid>
</SQLTable>
@@ -413,11 +462,11 @@
<comment><![CDATA[A single end-user of the platform]]></comment>
<tableType><![CDATA[InnoDB]]></tableType>
<location>
<x>820.00</x>
<y>205.00</y>
<x>825.00</x>
<y>321.00</y>
</location>
<size>
<width>289.00</width>
<width>380.00</width>
<height>180.00</height>
</size>
<zorder>10</zorder>
@@ -464,19 +513,43 @@
<unsigned><![CDATA[0]]></unsigned>
</SQLField>
<SQLField>
<name><![CDATA[registration_hook]]></name>
<type><![CDATA[VARCHAR(255)]]></type>
<uid><![CDATA[B81FD130-FAE7-4758-A6B7-B9B1F52F68B7]]></uid>
<name><![CDATA[registration_hook_sid]]></name>
<type><![CDATA[CHAR(36)]]></type>
<referencesField>webhook_sid</referencesField>
<referencesTable>webhooks</referencesTable>
<referencesField><![CDATA[webhook_sid]]></referencesField>
<referencesTable><![CDATA[webhooks]]></referencesTable>
<sourceCardinality>4</sourceCardinality>
<destinationCardinality>2</destinationCardinality>
<referencesFieldUID><![CDATA[E046BA30-BC18-483C-A5C8-766E7160F574]]></referencesFieldUID>
<referencesTableUID><![CDATA[64D64CB9-0990-4C68-BE71-F9FD43C2BE19]]></referencesTableUID>
<uid><![CDATA[A75FAB8E-C2A1-4A05-A09E-6FF454109B6F]]></uid>
</SQLField>
<SQLField>
<name><![CDATA[hook_basic_auth_user]]></name>
<type><![CDATA[VARCHAR(255)]]></type>
<uid><![CDATA[D0D88B7A-ED2D-4730-AD82-55418947EC66]]></uid>
<name><![CDATA[device_calling_hook_sid]]></name>
<type><![CDATA[CHAR(36)]]></type>
<referencesField>webhook_sid</referencesField>
<referencesTable>webhooks</referencesTable>
<referencesField><![CDATA[webhook_sid]]></referencesField>
<referencesTable><![CDATA[webhooks]]></referencesTable>
<sourceCardinality>4</sourceCardinality>
<destinationCardinality>2</destinationCardinality>
<referencesFieldUID><![CDATA[E046BA30-BC18-483C-A5C8-766E7160F574]]></referencesFieldUID>
<referencesTableUID><![CDATA[64D64CB9-0990-4C68-BE71-F9FD43C2BE19]]></referencesTableUID>
<uid><![CDATA[36C13087-E026-4F6F-A409-A082F7ADE1B6]]></uid>
</SQLField>
<SQLField>
<name><![CDATA[hook_basic_auth_password]]></name>
<type><![CDATA[VARCHAR(255)]]></type>
<uid><![CDATA[2FF5162F-782B-43A5-BFF0-BD06A82387EB]]></uid>
<name><![CDATA[error_hook_sid]]></name>
<type><![CDATA[CHAR(36)]]></type>
<referencesField>webhook_sid</referencesField>
<referencesTable>webhooks</referencesTable>
<referencesField><![CDATA[webhook_sid]]></referencesField>
<referencesTable><![CDATA[webhooks]]></referencesTable>
<sourceCardinality>4</sourceCardinality>
<destinationCardinality>2</destinationCardinality>
<referencesFieldUID><![CDATA[E046BA30-BC18-483C-A5C8-766E7160F574]]></referencesFieldUID>
<referencesTableUID><![CDATA[64D64CB9-0990-4C68-BE71-F9FD43C2BE19]]></referencesTableUID>
<uid><![CDATA[F563DEB1-071D-4BBE-A858-A0A33D64A8B6]]></uid>
</SQLField>
<SQLField>
<name><![CDATA[is_active]]></name>
@@ -485,7 +558,7 @@
<notNull><![CDATA[1]]></notNull>
<uid><![CDATA[C7130A90-DBB4-424D-A9A9-CB203C32350C]]></uid>
</SQLField>
<labelWindowIndex><![CDATA[11]]></labelWindowIndex>
<labelWindowIndex><![CDATA[12]]></labelWindowIndex>
<objectComment><![CDATA[A single end-user of the platform]]></objectComment>
<ui.treeExpanded><![CDATA[1]]></ui.treeExpanded>
<uid><![CDATA[985D6997-B1A7-4AB3-80F4-4D59B45480C8]]></uid>
@@ -496,8 +569,8 @@
<comment><![CDATA[An active sip subscription]]></comment>
<tableType><![CDATA[InnoDB]]></tableType>
<location>
<x>845.00</x>
<y>1190.00</y>
<x>846.00</x>
<y>1359.00</y>
</location>
<size>
<width>283.00</width>
@@ -542,7 +615,7 @@
<type><![CDATA[VARCHAR(255)]]></type>
<uid><![CDATA[318884AC-2CC4-4975-9973-606D2E9206BD]]></uid>
</SQLField>
<labelWindowIndex><![CDATA[10]]></labelWindowIndex>
<labelWindowIndex><![CDATA[11]]></labelWindowIndex>
<objectComment><![CDATA[An active sip subscription]]></objectComment>
<ui.treeExpanded><![CDATA[1]]></ui.treeExpanded>
<uid><![CDATA[997ED97A-3E4E-4CDC-8F48-9C0FE9F913B2]]></uid>
@@ -777,7 +850,7 @@
<type><![CDATA[VARCHAR(255)]]></type>
<uid><![CDATA[47EBED41-CCBE-42F6-9615-78D44720ADED]]></uid>
</SQLField>
<labelWindowIndex><![CDATA[9]]></labelWindowIndex>
<labelWindowIndex><![CDATA[10]]></labelWindowIndex>
<objectComment><![CDATA[A phone call]]></objectComment>
<ui.treeExpanded><![CDATA[1]]></ui.treeExpanded>
<uid><![CDATA[A58DEB6C-A5C6-441A-8659-5BC27A53FB00]]></uid>
@@ -859,7 +932,7 @@
<uid><![CDATA[962CB80A-54CB-4C6A-9591-9BFC644CF80F]]></uid>
<unsigned><![CDATA[0]]></unsigned>
</SQLField>
<labelWindowIndex><![CDATA[8]]></labelWindowIndex>
<labelWindowIndex><![CDATA[9]]></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>
@@ -870,8 +943,8 @@
<comment><![CDATA[A relationship between a call and a conference that it is connected to]]></comment>
<tableType><![CDATA[InnoDB]]></tableType>
<location>
<x>839.00</x>
<y>684.00</y>
<x>854.00</x>
<y>900.00</y>
</location>
<size>
<width>329.00</width>
@@ -917,7 +990,7 @@
<uid><![CDATA[E07F5855-50C6-4D6E-92EC-724E299B2CF5]]></uid>
<unsigned><![CDATA[0]]></unsigned>
</SQLField>
<labelWindowIndex><![CDATA[7]]></labelWindowIndex>
<labelWindowIndex><![CDATA[8]]></labelWindowIndex>
<objectComment><![CDATA[A relationship between a call and a conference that it is connected to]]></objectComment>
<ui.treeExpanded><![CDATA[1]]></ui.treeExpanded>
<uid><![CDATA[C415EB08-CA27-42B5-AE94-2681B6B6DC93]]></uid>
@@ -1004,7 +1077,7 @@
<indexType><![CDATA[UNIQUE]]></indexType>
<uid><![CDATA[1C744DE3-39BD-4EC6-B427-7EB2DD258771]]></uid>
</SQLIndex>
<labelWindowIndex><![CDATA[2]]></labelWindowIndex>
<labelWindowIndex><![CDATA[3]]></labelWindowIndex>
<ui.treeExpanded><![CDATA[1]]></ui.treeExpanded>
<uid><![CDATA[D8A564E2-DA41-4217-8ACE-06CF77E9BEC1]]></uid>
</SQLTable>
@@ -1048,7 +1121,7 @@
<type><![CDATA[VARCHAR(255)]]></type>
<uid><![CDATA[3FB439EC-7146-4A77-8204-AC4B38BE4825]]></uid>
</SQLField>
<labelWindowIndex><![CDATA[6]]></labelWindowIndex>
<labelWindowIndex><![CDATA[7]]></labelWindowIndex>
<objectComment><![CDATA[An audio conference]]></objectComment>
<ui.treeExpanded><![CDATA[1]]></ui.treeExpanded>
<uid><![CDATA[DAED2AAE-A146-4F09-809A-CC453E2CDFE9]]></uid>
@@ -1059,12 +1132,12 @@
<comment><![CDATA[A defined set of behaviors to be applied to phone calls within an account]]></comment>
<tableType><![CDATA[InnoDB]]></tableType>
<location>
<x>830.00</x>
<y>431.00</y>
<x>829.00</x>
<y>568.00</y>
</location>
<size>
<width>345.00</width>
<height>200.00</height>
<height>220.00</height>
</size>
<zorder>0</zorder>
<SQLField>
@@ -1101,33 +1174,58 @@
<unsigned><![CDATA[0]]></unsigned>
</SQLField>
<SQLField>
<name><![CDATA[call_hook]]></name>
<type><![CDATA[VARCHAR(255)]]></type>
<name><![CDATA[call_hook_sid]]></name>
<type><![CDATA[CHAR(36)]]></type>
<referencesField>webhook_sid</referencesField>
<referencesTable>webhooks</referencesTable>
<referencesField><![CDATA[webhook_sid]]></referencesField>
<referencesTable><![CDATA[webhooks]]></referencesTable>
<sourceCardinality>4</sourceCardinality>
<destinationCardinality>2</destinationCardinality>
<referencesFieldUID><![CDATA[E046BA30-BC18-483C-A5C8-766E7160F574]]></referencesFieldUID>
<referencesTableUID><![CDATA[64D64CB9-0990-4C68-BE71-F9FD43C2BE19]]></referencesTableUID>
<uid><![CDATA[55AE0F31-209A-49F9-A6CB-1BB31BE4178A]]></uid>
</SQLField>
<SQLField>
<name><![CDATA[call_status_hook_sid]]></name>
<type><![CDATA[CHAR(36)]]></type>
<referencesField>webhook_sid</referencesField>
<referencesTable>webhooks</referencesTable>
<referencesField><![CDATA[webhook_sid]]></referencesField>
<referencesTable><![CDATA[webhooks]]></referencesTable>
<sourceCardinality>4</sourceCardinality>
<destinationCardinality>2</destinationCardinality>
<referencesFieldUID><![CDATA[E046BA30-BC18-483C-A5C8-766E7160F574]]></referencesFieldUID>
<referencesTableUID><![CDATA[64D64CB9-0990-4C68-BE71-F9FD43C2BE19]]></referencesTableUID>
<uid><![CDATA[A3B4621B-DF13-4920-A718-068B20CB4E8A]]></uid>
</SQLField>
<SQLField>
<name><![CDATA[speech_synthesis_vendor]]></name>
<type><![CDATA[VARCHAR(64)]]></type>
<defaultValue><![CDATA[google]]></defaultValue>
<notNull><![CDATA[1]]></notNull>
<uid><![CDATA[9DB599B2-0EEA-4B91-93F9-A9725A732987]]></uid>
<uid><![CDATA[05D3C937-255D-44A5-A102-40303FA37CF1]]></uid>
</SQLField>
<SQLField>
<name><![CDATA[call_status_hook]]></name>
<type><![CDATA[VARCHAR(255)]]></type>
<name><![CDATA[speech_synthesis_voice]]></name>
<type><![CDATA[VARCHAR(64)]]></type>
<defaultValue><![CDATA[en-US-Wavenet-C]]></defaultValue>
<notNull><![CDATA[1]]></notNull>
<uid><![CDATA[B22DE754-1A59-45BB-9650-F0B12F07393D]]></uid>
<uid><![CDATA[929D66F0-64B9-4D7C-AB4B-24F131E1178F]]></uid>
</SQLField>
<SQLField>
<name><![CDATA[hook_basic_auth_user]]></name>
<type><![CDATA[VARCHAR(255)]]></type>
<uid><![CDATA[B1F4D817-C8BF-47AA-81C3-257095D37335]]></uid>
</SQLField>
<SQLField>
<name><![CDATA[hook_basic_auth_password]]></name>
<type><![CDATA[VARCHAR(255)]]></type>
<uid><![CDATA[1E8D916B-AFB6-4AE2-A1EA-70DFA4FF459D]]></uid>
</SQLField>
<SQLField>
<name><![CDATA[hook_http_method]]></name>
<type><![CDATA[ENUM('get','post')]]></type>
<defaultValue><![CDATA[get]]></defaultValue>
<name><![CDATA[speech_recognizer_vendor]]></name>
<type><![CDATA[VARCHAR(64)]]></type>
<defaultValue><![CDATA[google]]></defaultValue>
<notNull><![CDATA[1]]></notNull>
<uid><![CDATA[19DB8C91-9309-4DA9-9D3D-279EF7036A7B]]></uid>
<uid><![CDATA[0A601195-287E-4F35-BE69-9A80CAF363D2]]></uid>
</SQLField>
<SQLField>
<name><![CDATA[speech_recognizer_language]]></name>
<type><![CDATA[VARCHAR(64)]]></type>
<defaultValue><![CDATA[en-US]]></defaultValue>
<notNull><![CDATA[1]]></notNull>
<uid><![CDATA[A03AFB7B-492F-48E3-AE3C-B1416D5B6B12]]></uid>
</SQLField>
<SQLIndex>
<name><![CDATA[applications_idx_name]]></name>
@@ -1147,7 +1245,7 @@
<indexType><![CDATA[UNIQUE]]></indexType>
<uid><![CDATA[3FDDDF3B-375D-4DE4-B759-514438845F7D]]></uid>
</SQLIndex>
<labelWindowIndex><![CDATA[5]]></labelWindowIndex>
<labelWindowIndex><![CDATA[6]]></labelWindowIndex>
<objectComment><![CDATA[A defined set of behaviors to be applied to phone calls within an account]]></objectComment>
<ui.treeExpanded><![CDATA[1]]></ui.treeExpanded>
<uid><![CDATA[E97EE4F0-7ED7-4E8C-862E-D98192D6EAE0]]></uid>
@@ -1190,7 +1288,7 @@
<uid><![CDATA[B73773BA-AB1B-47AA-B995-2D2FE006198F]]></uid>
<unique><![CDATA[1]]></unique>
</SQLField>
<labelWindowIndex><![CDATA[1]]></labelWindowIndex>
<labelWindowIndex><![CDATA[2]]></labelWindowIndex>
<ui.treeExpanded><![CDATA[1]]></ui.treeExpanded>
<uid><![CDATA[F283D572-F670-4571-91FD-A665A9D3E15D]]></uid>
</SQLTable>
@@ -1200,12 +1298,12 @@
<comment><![CDATA[An organization that provides communication services to its customers using the platform]]></comment>
<tableType><![CDATA[InnoDB]]></tableType>
<location>
<x>821.00</x>
<y>6.00</y>
<x>813.00</x>
<y>99.00</y>
</location>
<size>
<width>293.00</width>
<height>160.00</height>
<height>120.00</height>
</size>
<zorder>9</zorder>
<SQLField>
@@ -1240,19 +1338,17 @@
<unique><![CDATA[1]]></unique>
</SQLField>
<SQLField>
<name><![CDATA[registration_hook]]></name>
<type><![CDATA[VARCHAR(255)]]></type>
<uid><![CDATA[7E44BC47-CB8A-4754-96BB-32B589B408B0]]></uid>
</SQLField>
<SQLField>
<name><![CDATA[hook_basic_auth_user]]></name>
<type><![CDATA[VARCHAR(255)]]></type>
<uid><![CDATA[0395A96B-8229-4C65-A153-DCA83E145B7C]]></uid>
</SQLField>
<SQLField>
<name><![CDATA[hook_basic_auth_password]]></name>
<type><![CDATA[VARCHAR(255)]]></type>
<uid><![CDATA[1A765999-2BB9-45ED-B840-1688EA8E8B84]]></uid>
<name><![CDATA[registration_hook_sid]]></name>
<type><![CDATA[CHAR(36)]]></type>
<referencesField>webhook_sid</referencesField>
<referencesTable>webhooks</referencesTable>
<referencesField><![CDATA[webhook_sid]]></referencesField>
<referencesTable><![CDATA[webhooks]]></referencesTable>
<sourceCardinality>4</sourceCardinality>
<destinationCardinality>2</destinationCardinality>
<referencesFieldUID><![CDATA[E046BA30-BC18-483C-A5C8-766E7160F574]]></referencesFieldUID>
<referencesTableUID><![CDATA[64D64CB9-0990-4C68-BE71-F9FD43C2BE19]]></referencesTableUID>
<uid><![CDATA[506BBE72-1A97-4776-B2C3-D94169652FFE]]></uid>
</SQLField>
<labelWindowIndex><![CDATA[0]]></labelWindowIndex>
<objectComment><![CDATA[An organization that provides communication services to its customers using the platform]]></objectComment>
@@ -1273,7 +1369,7 @@
<overviewPanelHidden><![CDATA[0]]></overviewPanelHidden>
<pageBoundariesVisible><![CDATA[0]]></pageBoundariesVisible>
<PageGridVisible><![CDATA[0]]></PageGridVisible>
<RightSidebarWidth><![CDATA[1276.000000]]></RightSidebarWidth>
<RightSidebarWidth><![CDATA[1552.000000]]></RightSidebarWidth>
<sidebarIndex><![CDATA[2]]></sidebarIndex>
<snapToGrid><![CDATA[0]]></snapToGrid>
<SourceSidebarWidth><![CDATA[0.000000]]></SourceSidebarWidth>
@@ -1282,7 +1378,7 @@
<windowHeight><![CDATA[1027.000000]]></windowHeight>
<windowLocationX><![CDATA[115.000000]]></windowLocationX>
<windowLocationY><![CDATA[0.000000]]></windowLocationY>
<windowScrollOrigin><![CDATA[{136.5, 0}]]></windowScrollOrigin>
<windowScrollOrigin><![CDATA[{295, 0}]]></windowScrollOrigin>
<windowWidth><![CDATA[1553.000000]]></windowWidth>
</SQLDocumentInfo>
<AllowsIndexRenamingOnInsert><![CDATA[1]]></AllowsIndexRenamingOnInsert>

View File

@@ -1,8 +1,35 @@
const Model = require('./model');
const {getMysqlConnection} = require('../db');
const listSqlSp = 'SELECT * from accounts WHERE service_provider_sid = ?';
const listSqlAccount = 'SELECT * from accounts WHERE account_sid = ?';
const retrieveSql = 'SELECT * from accounts WHERE service_provider_sid = ? AND account_sid = ?';
const retrieveSql = `SELECT * from accounts acc
LEFT JOIN webhooks AS rh
ON acc.registration_hook_sid = rh.webhook_sid
LEFT JOIN webhooks AS dh
ON acc.device_calling_hook_sid = dh.webhook_sid
LEFT JOIN webhooks AS eh
ON acc.error_hook_sid = eh.webhook_sid`;
function transmogrifyResults(results) {
return results.map((row) => {
const obj = row.acc;
if (row.rh && Object.keys(row.rh).length && row.rh.url !== null) {
Object.assign(obj, {registration_hook: row.rh});
}
else obj.registration_hook = null;
if (row.dh && Object.keys(row.dh).length && row.dh.url !== null) {
Object.assign(obj, {device_calling_hook: row.dh});
}
else obj.device_calling_hook = null;
if (row.eh && Object.keys(row.eh).length && row.eh.url !== null) {
Object.assign(obj, {error_hook: row.eh});
}
else obj.error_hook = null;
delete obj.registration_hook_sid;
delete obj.device_calling_hook_sid;
delete obj.error_hook_sid;
return obj;
});
}
class Account extends Model {
constructor() {
@@ -13,16 +40,24 @@ class Account extends Model {
* list all accounts
*/
static retrieveAll(service_provider_sid, account_sid) {
if (!service_provider_sid && !account_sid) return super.retrieveAll();
let sql = retrieveSql;
const args = [];
if (account_sid) {
sql = `${sql} WHERE acc.account_sid = ?`;
args.push(account_sid);
}
else if (service_provider_sid) {
sql = `${sql} WHERE acc.service_provider_sid = ?`;
args.push(service_provider_sid);
}
return new Promise((resolve, reject) => {
getMysqlConnection((err, conn) => {
if (err) return reject(err);
const sql = account_sid ? listSqlAccount : listSqlSp;
const args = account_sid ? [account_sid] : [service_provider_sid];
conn.query(sql, args, (err, results, fields) => {
conn.query({sql, nestTables: true}, args, (err, results, fields) => {
conn.release();
if (err) return reject(err);
resolve(results);
const r = transmogrifyResults(results);
resolve(r);
});
});
});
@@ -32,14 +67,20 @@ class Account extends Model {
* retrieve an account
*/
static retrieve(sid, service_provider_sid) {
if (!service_provider_sid) return super.retrieve(sid);
const args = [sid];
let sql = `${retrieveSql} WHERE acc.account_sid = ?`;
if (service_provider_sid) {
sql = `${retrieveSql} WHERE acc.account_sid = ? AND acc.service_provider_sid = ?`;
args.push(service_provider_sid);
}
return new Promise((resolve, reject) => {
getMysqlConnection((err, conn) => {
if (err) return reject(err);
conn.query(retrieveSql, [service_provider_sid, sid], (err, results, fields) => {
conn.query({sql, nestTables: true}, args, (err, results, fields) => {
conn.release();
if (err) return reject(err);
resolve(results);
const r = transmogrifyResults(results);
resolve(r);
});
});
});
@@ -69,15 +110,15 @@ Account.fields = [
type: 'string',
},
{
name: 'registration_hook',
name: 'registration_hook_sid',
type: 'string',
},
{
name: 'hook_basic_auth_user',
name: 'device_calling_hook_sid',
type: 'string',
},
{
name: 'hook_basic_auth_password',
name: 'error_hook_sid',
type: 'string',
}
];

View File

@@ -19,6 +19,29 @@ SELECT * from applications
WHERE account_sid = ?
AND application_sid = ?`;
const retrieveSql = `SELECT * from applications app
LEFT JOIN webhooks AS ch
ON app.call_hook_sid = ch.webhook_sid
LEFT JOIN webhooks AS sh
ON app.call_status_hook_sid = sh.webhook_sid`;
function transmogrifyResults(results) {
return results.map((row) => {
const obj = row.app;
if (row.ch && Object.keys(row.ch).length && row.ch.url !== null) {
Object.assign(obj, {call_hook: row.ch});
}
else obj.call_hook = null;
if (row.sh && Object.keys(row.sh).length && row.sh.url !== null) {
Object.assign(obj, {call_status_hook: row.sh});
}
else obj.call_status_hook = null;
delete obj.call_hook_sid;
delete obj.call_status_hook_sid;
return obj;
});
}
class Application extends Model {
constructor() {
super();
@@ -28,16 +51,24 @@ class Application extends Model {
* list all applications - for all service providers, for one service provider, or for one account
*/
static retrieveAll(service_provider_sid, account_sid) {
if (!service_provider_sid && !account_sid) return super.retrieveAll();
let sql = retrieveSql;
const args = [];
if (account_sid) {
sql = `${sql} WHERE app.account_sid = ?`;
args.push(account_sid);
}
else if (service_provider_sid) {
sql = `${sql} WHERE account_sid in (SELECT account_sid from accounts WHERE service_provider_sid = ?)`;
args.push(service_provider_sid);
}
return new Promise((resolve, reject) => {
getMysqlConnection((err, conn) => {
if (err) return reject(err);
const sql = account_sid ? listSqlAccount : listSqlSp;
const args = account_sid ? [account_sid] : [service_provider_sid];
conn.query(sql, args, (err, results, fields) => {
conn.query({sql, nestTables: true}, args, (err, results, fields) => {
conn.release();
if (err) return reject(err);
resolve(results);
const r = transmogrifyResults(results);
resolve(r);
});
});
});
@@ -47,16 +78,24 @@ class Application extends Model {
* retrieve an application
*/
static retrieve(sid, service_provider_sid, account_sid) {
if (!service_provider_sid && !account_sid) return super.retrieve(sid);
const args = [sid];
let sql = `${retrieveSql} WHERE app.application_sid = ?`;
if (account_sid) {
sql = `${sql} AND app.account_sid = ?`;
args.push(account_sid);
}
if (service_provider_sid) {
sql = `${sql} AND account_sid in (SELECT account_sid from accounts WHERE service_provider_sid = ?)`;
args.push(service_provider_sid);
}
return new Promise((resolve, reject) => {
getMysqlConnection((err, conn) => {
if (err) return reject(err);
const sql = account_sid ? retrieveSqlAccount : retrieveSqlSp;
const args = account_sid ? [account_sid, sid] : [service_provider_sid, sid];
conn.query(sql, args, (err, results, fields) => {
conn.query({sql, nestTables: true}, args, (err, results, fields) => {
conn.release();
if (err) return reject(err);
resolve(results);
const r = transmogrifyResults(results);
resolve(r);
});
});
});
@@ -82,26 +121,14 @@ Application.fields = [
required: true
},
{
name: 'call_hook',
name: 'call_hook_sid',
type: 'string',
required: true
},
{
name: 'call_status_hook',
name: 'call_status_hook_sid',
type: 'string',
required: true
},
{
name: 'hook_basic_auth_user',
type: 'string',
},
{
name: 'hook_basic_auth_password',
type: 'string',
},
{
name: 'hook_http_method',
type: 'string',
}
];

35
lib/models/webhook.js Normal file
View File

@@ -0,0 +1,35 @@
const Model = require('./model');
class Webhook extends Model {
constructor() {
super();
}
}
Webhook.table = 'webhooks';
Webhook.fields = [
{
name: 'webhook_sid',
type: 'string',
primaryKey: true
},
{
name: 'url',
type: 'string',
required: true
},
{
name: 'method',
type: 'string'
},
{
name: 'username',
type: 'string'
},
{
name: 'password',
type: 'string'
}
];
module.exports = Webhook;

View File

@@ -1,6 +1,7 @@
const router = require('express').Router();
const {DbErrorBadRequest, DbErrorUnprocessableRequest} = require('../../utils/errors');
const Account = require('../../models/account');
const Webhook = require('../../models/webhook');
const ServiceProvider = require('../../models/service-provider');
const decorate = require('./decorate');
const sysError = require('./error');
@@ -25,6 +26,16 @@ async function validateAdd(req) {
throw new DbErrorBadRequest(`service_provider not found for sid ${req.body.service_provider_sid}`);
}
}
if (req.body.registration_hook && typeof req.body.registration_hook !== 'object') {
throw new DbErrorBadRequest('\'registration_hook\' must be an object when adding an account');
}
if (req.body.device_calling_hook && typeof req.body.device_calling_hook !== 'object') {
throw new DbErrorBadRequest('\'device_calling_hook\' must be an object when adding an account');
}
if (req.body.error_hook && typeof req.body.error_hook !== 'object') {
throw new DbErrorBadRequest('\'error_hook\' must be an object when adding an account');
}
}
async function validateUpdate(req, sid) {
if (req.user.hasAccountAuth && req.user.account_sid !== sid) {
@@ -53,7 +64,30 @@ async function validateDelete(req, sid) {
}
}
decorate(router, Account, ['add', 'update', 'delete'], preconditions);
decorate(router, Account, ['delete'], preconditions);
/* add */
router.post('/', async(req, res) => {
const logger = req.app.locals.logger;
try {
await validateAdd(req);
// create webhooks if provided
const obj = Object.assign({}, req.body);
for (const prop of ['registration_hook', 'device_calling_hook', 'error_hook']) {
if (obj[prop]) {
obj[`${prop}_sid`] = await Webhook.make(obj[prop]);
delete obj[prop];
}
}
//logger.debug(`Attempting to add account ${JSON.stringify(obj)}`);
const uuid = await Account.make(obj);
res.status(201).json({sid: uuid});
} catch (err) {
sysError(logger, res, err);
}
});
/* list */
router.get('/', async(req, res) => {
@@ -82,4 +116,40 @@ router.get('/:sid', async(req, res) => {
}
});
/* update */
router.put('/:sid', async(req, res) => {
const sid = req.params.sid;
const logger = req.app.locals.logger;
try {
// create webhooks if provided
const obj = Object.assign({}, req.body);
for (const prop of ['registration_hook', 'device_calling_hook', 'error_hook']) {
if (prop in obj && Object.keys(obj[prop]).length) {
if ('webhook_sid' in obj[prop]) {
const sid = obj[prop]['webhook_sid'];
delete obj[prop]['webhook_sid'];
await Webhook.update(sid, obj[prop]);
}
else {
const sid = await Webhook.make(obj[prop]);
obj[`${prop}_sid`] = sid;
}
}
else {
obj[`${prop}_sid`] = null;
}
delete obj[prop];
}
await validateUpdate(req, sid);
const rowsAffected = await Account.update(sid, obj);
if (rowsAffected === 0) {
return res.status(404).end();
}
res.status(204).end();
} catch (err) {
sysError(logger, res, err);
}
});
module.exports = router;

View File

@@ -2,6 +2,7 @@ const router = require('express').Router();
const {DbErrorBadRequest, DbErrorUnprocessableRequest} = require('../../utils/errors');
const Application = require('../../models/application');
const Account = require('../../models/account');
const Webhook = require('../../models/webhook');
const decorate = require('./decorate');
const sysError = require('./error');
const preconditions = {
@@ -23,12 +24,24 @@ async function validateAdd(req) {
throw new DbErrorBadRequest('insufficient privileges to create an application under the specified account');
}
}
if (req.body.call_hook && typeof req.body.call_hook !== 'object') {
throw new DbErrorBadRequest('\'call_hook\' must be an object when adding an application');
}
if (req.body.call_status_hook && typeof req.body.call_hook !== 'object') {
throw new DbErrorBadRequest('\'call_status_hook\' must be an object when adding an application');
}
}
async function validateUpdate(req, sid) {
if (req.user.account_sid && sid !== req.user.account_sid) {
throw new DbErrorBadRequest('you may not update or delete an application associated with a different account');
}
if (req.body.call_hook && typeof req.body.call_hook !== 'object') {
throw new DbErrorBadRequest('\'call_hook\' must be an object when updating an application');
}
if (req.body.call_status_hook && typeof req.body.call_hook !== 'object') {
throw new DbErrorBadRequest('\'call_status_hook\' must be an object when updating an application');
}
}
async function validateDelete(req, sid) {
@@ -39,7 +52,29 @@ async function validateDelete(req, sid) {
if (assignedPhoneNumbers > 0) throw new DbErrorUnprocessableRequest('cannot delete application with phone numbers');
}
decorate(router, Application, ['add', 'update', 'delete'], preconditions);
decorate(router, Application, ['delete'], preconditions);
/* add */
router.post('/', async(req, res) => {
const logger = req.app.locals.logger;
try {
await validateAdd(req);
// create webhooks if provided
const obj = Object.assign({}, req.body);
for (const prop of ['call_hook', 'call_status_hook']) {
if (obj[prop]) {
obj[`${prop}_sid`] = await Webhook.make(obj[prop]);
delete obj[prop];
}
}
const uuid = await Application.make(obj);
res.status(201).json({sid: uuid});
} catch (err) {
sysError(logger, res, err);
}
});
/* list */
router.get('/', async(req, res) => {
@@ -69,4 +104,41 @@ router.get('/:sid', async(req, res) => {
}
});
/* update */
router.put('/:sid', async(req, res) => {
const sid = req.params.sid;
const logger = req.app.locals.logger;
try {
await validateUpdate(req, sid);
// create webhooks if provided
const obj = Object.assign({}, req.body);
for (const prop of ['call_hook', 'call_status_hook']) {
if (prop in obj && Object.keys(obj[prop]).length) {
if ('webhook_sid' in obj[prop]) {
const sid = obj[prop]['webhook_sid'];
delete obj[prop]['webhook_sid'];
await Webhook.update(sid, obj[prop]);
}
else {
const sid = await Webhook.make(obj[prop]);
obj[`${prop}_sid`] = sid;
}
}
else {
obj[`${prop}_sid`] = null;
}
delete obj[prop];
}
const rowsAffected = await Application.update(sid, obj);
if (rowsAffected === 0) {
return res.status(404).end();
}
res.status(204).end();
} catch (err) {
sysError(logger, res, err);
}
});
module.exports = router;

View File

@@ -655,20 +655,18 @@ paths:
description: sip realm for registration
example: sip.mycompany.com
registration_hook:
type: string
format: url
$ref: '#/components/Webhook'
description: authentication webhook for registration
example: https://mycompany.com
device_calling_hook:
$ref: '#/components/Webhook'
description: webhook for inbound call from registered devices
error_hook:
$ref: '#/components/Webhook'
description: webhook for reporting errors from malformed applications
service_provider_sid:
type: string
format: uuid
example: 85f9c036-ba61-4f28-b2f5-617c23fa68ff
hook_basic_auth_user:
type: string
description: username to use for http basic auth when calling hook
hook_basic_auth_password:
type: string
description: password to use for http basic auth when calling hook
required:
- name
- service_provider_sid
@@ -797,10 +795,6 @@ paths:
type: string
format: url
description: url of application to invoke when this device places a call
call_status_hook:
type: string
format: url
description: url of application to invoke for call status events on calls placed from this device
expires:
type: number
description: |
@@ -922,25 +916,19 @@ paths:
type: string
format: uuid
call_hook:
type: string
format: url
description: webhook to invoke when call is received
$ref: '#/components/Webhook'
description: authentication webhook for inbound calls from PSTN
call_status_hook:
$ref: '#/components/Webhook'
description: webhook for call status events
speech_synthesis_vendor:
type: string
format: url
description: webhook to pass call status updates to
hook_basic_auth_user:
speech_synthesis_voice:
type: string
description: username to use for http basic auth when calling hook
hook_basic_auth_password:
speech_recognizer_vendor:
type: string
description: password to use for http basic auth when calling hook
hook_http_method:
speech_recognizer_language:
type: string
description: whether to use GET or POST
enum:
- get
- post
required:
- name
- account_sid
@@ -1243,17 +1231,17 @@ components:
sip_realm:
type: string
registration_hook:
type: string
format: url
$ref: '#/components/Webhook'
description: authentication webhook for registration
device_calling_hook:
$ref: '#/components/Webhook'
description: webhook for inbound call from registered devices
error_hook:
$ref: '#/components/Webhook'
description: webhook for reporting errors from malformed applications
service_provider_sid:
type: string
format: uuid
hook_basic_auth_user:
type: string
format: url
hook_basic_auth_password:
type: string
format: url
required:
- account_sid
- name
@@ -1270,29 +1258,25 @@ components:
type: string
format: uuid
call_hook:
type: string
format: url
$ref: '#/components/Webhook'
description: authentication webhook for registration
call_status_hook:
$ref: '#/components/Webhook'
description: authentication webhook for registration
speech_synthesis_vendor:
type: string
format: url
hook_http_method:
speech_synthesis_voice:
type: string
enum:
- get
- post
default: get
hook_basic_auth_user:
speech_recognizer_vendor:
type: string
format: url
hook_basic_auth_password:
speech_recognizer_language:
type: string
format: url
required:
- application_sid
- name
- account_sid
- call_hook
- call_status_hook
- inbound_hook
- inbound_status_hook
PhoneNumber:
type: object
properties:
@@ -1362,6 +1346,23 @@ components:
- call_sid
- application
- direction
Webhook:
type: object
properties:
url:
type: string
format: url
method:
type: string
enum:
- get
- post
username:
type: string
password:
type: string
required:
- url
security:
- bearerAuth: []

View File

@@ -1,6 +1,6 @@
{
"name": "jambones-api-server",
"version": "1.0.1",
"name": "jambonz-api-server",
"version": "1.1.0",
"description": "",
"main": "app.js",
"scripts": {
@@ -11,7 +11,7 @@
"author": "Dave Horton",
"license": "MIT",
"repository": {
"url": "https://github.com/jambonz/jambones-api-server.git"
"url": "https://github.com/jambonz/jambonz-api-server.git"
},
"dependencies": {
"config": "^3.2.4",

View File

@@ -28,7 +28,20 @@ test('account tests', async(t) => {
json: true,
body: {
name: 'daveh',
service_provider_sid
service_provider_sid,
registration_hook: {
url: 'http://example.com/reg',
method: 'get'
},
device_calling_hook: {
url: 'http://example.com/device',
method: 'get',
username: 'foo',
password: 'abr'
},
error_hook: {
url: 'http://example.com/error'
}
}
});
t.ok(result.statusCode === 201, 'successfully created account');
@@ -39,7 +52,13 @@ test('account tests', async(t) => {
auth: authAdmin,
json: true,
});
t.ok(result.length === 1 , 'successfully queried all accounts');
let regHook = result[0].registration_hook;
let devHook = result[0].device_calling_hook;
let errHook = result[0].error_hook;
t.ok(result.length === 1 &&
Object.keys(regHook).length == 5 &&
Object.keys(devHook).length === 5 &&
Object.keys(errHook).length === 5, 'successfully queried all accounts');
/* query one accounts */
result = await request.get(`/Accounts/${sid}`, {
@@ -47,6 +66,7 @@ test('account tests', async(t) => {
json: true,
});
t.ok(result.name === 'daveh' , 'successfully retrieved account by sid');
const error_hook_sid = result.error_hook.webhook_sid;
/* update accounts */
result = await request.put(`/Accounts/${sid}`, {
@@ -54,11 +74,28 @@ test('account tests', async(t) => {
json: true,
resolveWithFullResponse: true,
body: {
name: 'robb'
name: 'robb',
registration_hook: {
url: 'http://example.com/reg2',
method: 'get'
},
error_hook: {
webhook_sid: error_hook_sid,
method: 'post'
}
}
});
t.ok(result.statusCode === 204, 'successfully updated account');
result = await request.get(`/Accounts/${sid}`, {
auth: authAdmin,
json: true,
});
//console.log(`retrieved account after update: ${JSON.stringify(result)}`);
t.ok(result.device_calling_hook === null &&
Object.keys(result.registration_hook).length === 5 &&
Object.keys(result.error_hook).length === 5, 'successfully removed a hook from account');
/* assign phone number to account */
result = await request.put(`/PhoneNumbers/${phone_number_sid}`, {
auth: authAdmin,

View File

@@ -32,8 +32,13 @@ test('application tests', async(t) => {
body: {
name: 'daveh',
account_sid,
call_hook: 'http://example.com',
call_status_hook: 'http://example.com'
call_hook: {
url: 'http://example.com'
},
call_status_hook: {
url: 'http://example.com/status',
method: 'POST'
}
}
});
t.ok(result.statusCode === 201, 'successfully created application');
@@ -60,7 +65,9 @@ test('application tests', async(t) => {
json: true,
resolveWithFullResponse: true,
body: {
call_hook: 'http://example2.com'
call_hook: {
url: 'http://example2.com'
}
}
});
t.ok(result.statusCode === 204, 'successfully updated application');

View File

@@ -55,7 +55,10 @@ test('authentication tests', async(t) => {
simple: false,
json: true,
body: {
name: 'accountA1'
name: 'accountA1',
registration_hook: {
url: 'http://example.com'
}
}
});
t.ok(result.statusCode === 201, 'successfully created account A1 using service provider token A');
@@ -216,8 +219,12 @@ test('authentication tests', async(t) => {
json: true,
body: {
name: 'A1-app',
call_hook: 'http://example.com',
call_status_hook: 'http://example.com'
call_hook: {
url: 'http://example.com'
},
call_status_hook: {
url: 'http://example.com'
}
}
});
t.ok(result.statusCode === 201, 'successfully created application for account A1 (using account level token)');
@@ -231,8 +238,12 @@ test('authentication tests', async(t) => {
body: {
name: 'A2-app',
account_sid: accA2,
call_hook: 'http://example.com',
call_status_hook: 'http://example.com'
call_hook: {
url: 'http://example.com',
},
call_status_hook: {
url: 'http://example.com'
}
}
});
t.ok(result.statusCode === 400 && result.body.msg === 'insufficient privileges to create an application under the specified account',
@@ -246,8 +257,8 @@ test('authentication tests', async(t) => {
body: {
name: 'A2-app',
account_sid: accA2,
call_hook: 'http://example.com',
call_status_hook: 'http://example.com'
call_hook: {url: 'http://example.com'},
call_status_hook: {url: 'http://example.com'}
}
});
t.ok(result.statusCode === 201, 'successfully created application for account A2 (using service provider token A)');
@@ -261,8 +272,8 @@ test('authentication tests', async(t) => {
body: {
name: 'B1-app',
account_sid: accB1,
call_hook: 'http://example.com',
call_status_hook: 'http://example.com'
call_hook: {url: 'http://example.com'},
call_status_hook: {url: 'http://example.com'}
}
});
t.ok(result.statusCode === 201, 'successfully created application for account B1 (using service provider token B)');