From d8cb729a32b91afb11284d141a5beaf76a279de9 Mon Sep 17 00:00:00 2001 From: Stephen Paul Weber Date: Wed, 13 Oct 2021 13:35:59 -0500 Subject: [PATCH 1/4] CustomerFwd uses ValueSemantics, translates old XMPP-SIP URI More of the original data is kept now, so this object could be used for putting to persistence as well as for loading from it. --- lib/bwmsgsv2_repo.rb | 2 +- lib/customer_fwd.rb | 65 +++++++++++++++++++++------------------ test/test_customer_fwd.rb | 51 ++++++++++++++++++++++++++++++ web.rb | 19 ++++++------ 4 files changed, 96 insertions(+), 41 deletions(-) create mode 100644 test/test_customer_fwd.rb diff --git a/lib/bwmsgsv2_repo.rb b/lib/bwmsgsv2_repo.rb index 312563122ea83d88ffa8274c5f319ba22dfea488..2aa498e6a91dab4c7f922e9d6be77f36fdfe179a 100644 --- a/lib/bwmsgsv2_repo.rb +++ b/lib/bwmsgsv2_repo.rb @@ -20,7 +20,7 @@ class Bwmsgsv2Repo fetch_raw(sgx.from_jid).then do |(((ogm_url, fwd_time, fwd), trans_d), reg)| sgx.with({ ogm_url: ogm_url, - fwd: CustomerFwd.for(fwd, fwd_time), + fwd: CustomerFwd.for(uri: fwd, timeout: fwd_time), transcription_enabled: !trans_d, registered?: reg }.compact) diff --git a/lib/customer_fwd.rb b/lib/customer_fwd.rb index b43f79383749a09411068cf3ae20849fcde6e668..a1a2e8dc3f7c92b3a60a851f0fcb43c9f2117e15 100644 --- a/lib/customer_fwd.rb +++ b/lib/customer_fwd.rb @@ -1,17 +1,25 @@ # frozen_string_literal: true +require "value_semantics/monkey_patched" require "uri" class CustomerFwd - def self.for(uri, timeout) + def self.for(uri:, timeout:) timeout = Timeout.new(timeout) - return if !uri || timeout.zero? + return None.new(uri: uri, timeout: timeout) if !uri || timeout.zero? + if uri =~ /\Asip:(.*)@sip.cheogram.com\Z/ + uri = "xmpp:#{$1.gsub(/%([0-9A-F]{2})/i) { $1.to_i(16).chr }}" + end URIS.fetch(uri.split(":", 2).first.to_sym) { raise "Unknown forward URI: #{uri}" - }.new(uri, timeout) + }.new(uri: uri, timeout: timeout) end class Timeout + def self.new(s) + s.is_a?(self) ? s : super + end + def initialize(s) @timeout = s.nil? || s.to_i.negative? ? 300 : s.to_i end @@ -25,54 +33,51 @@ class CustomerFwd end end - def create_call_request + value_semantics do + uri Either(String, NilClass) + # rubocop:disable Style/RedundantSelf + self.timeout Timeout, coerce: Timeout.method(:new) + # rubocop:enable Style/RedundantSelf + end + + def with(new_attrs) + CustomerFwd.for(to_h.merge(new_attrs)) + end + + def create_call(account) request = Bandwidth::ApiCreateCallRequest.new.tap do |cc| cc.to = to cc.call_timeout = timeout.to_i + yield cc if block_given? end - yield request if block_given? - request + BANDWIDTH_VOICE.create_call(account, body: request).data.call_id end class Tel < CustomerFwd - attr_reader :timeout - - def initialize(uri, timeout) - @tel = uri.sub(/^tel:/, "") - @timeout = timeout - end - def to - @tel + uri.sub(/^tel:/, "") end end class SIP < CustomerFwd - attr_reader :timeout - - def initialize(uri, timeout) - @uri = uri - @timeout = timeout - end - def to - @uri + uri end end class XMPP < CustomerFwd - attr_reader :timeout - - def initialize(uri, timeout) - @jid = uri.sub(/^xmpp:/, "") - @timeout = timeout - end - def to - "sip:#{ERB::Util.url_encode(@jid)}@sip.cheogram.com" + jid = uri.sub(/^xmpp:/, "") + "sip:#{ERB::Util.url_encode(jid)}@sip.cheogram.com" end end + class None < CustomerFwd + def create_call; end + + def to; end + end + URIS = { tel: Tel, sip: SIP, diff --git a/test/test_customer_fwd.rb b/test/test_customer_fwd.rb new file mode 100644 index 0000000000000000000000000000000000000000..de530302a86caf52e29656dec5852cda31f9e6d1 --- /dev/null +++ b/test/test_customer_fwd.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +require "test_helper" +require "customer_fwd" + +class Rantly + def jid + v = Blather::JID.new(Blather::JID.new(string, string).stripped.to_s) + guard !v.to_s.to_s.empty? + v + end +end + +class CustomerFwdTest < Minitest::Test + property(:for_xmpp) { jid } + def for_xmpp(jid) + sip = "sip:#{ERB::Util.url_encode(jid.to_s)}@sip.cheogram.com" + fwd = CustomerFwd.for(uri: "xmpp:#{jid}", timeout: 10) + assert_kind_of CustomerFwd::XMPP, fwd + assert_equal sip, fwd.to + end + + property(:for_xmpp_sip) { jid } + def for_xmpp_sip(jid) + sip = "sip:#{ERB::Util.url_encode(jid.to_s)}@sip.cheogram.com" + fwd = CustomerFwd.for(uri: sip, timeout: 10) + assert_kind_of CustomerFwd::XMPP, fwd + assert_equal sip, fwd.to + end + + property(:for_tel) { "+#{string(:digit)}" } + def for_tel(tel) + fwd = CustomerFwd.for(uri: "tel:#{tel}", timeout: 10) + assert_kind_of CustomerFwd::Tel, fwd + assert_equal tel, fwd.to + end + + property(:for_sip) { "#{string(:alnum)}@#{string(:alnum)}.example.com" } + def for_sip(sip) + fwd = CustomerFwd.for(uri: "sip:#{sip}", timeout: 10) + assert_kind_of CustomerFwd::SIP, fwd + assert_equal "sip:#{sip}", fwd.to + end + + property(:for_bogus) { string } + def for_bogus(bogus) + assert_raises(RuntimeError) do + CustomerFwd.for(uri: "bogus:#{bogus}", timeout: 10) + end + end +end diff --git a/web.rb b/web.rb index 75145a1590e79f3d45803ef2fb946f7b75693d69..530c6ec9eaf994387b4ef267b4d20037582d1673 100644 --- a/web.rb +++ b/web.rb @@ -257,17 +257,16 @@ class Web < Roda CustomerRepo.new( sgx_repo: Bwmsgsv2Repo.new ).find_by_tel(params["to"]).then(&:fwd).then do |fwd| - if fwd + call = fwd.create_call(CONFIG[:creds][:account]) do |cc| true_inbound_call[pseudo_call_id] = params["callId"] - outbound_transfers[pseudo_call_id] = BANDWIDTH_VOICE.create_call( - CONFIG[:creds][:account], - body: fwd.create_call_request do |cc| - cc.from = params["from"] - cc.application_id = params["applicationId"] - cc.answer_url = url inbound_calls_path(nil) - cc.disconnect_url = url inbound_calls_path(:transfer_complete) - end - ).data.call_id + cc.from = params["from"] + cc.application_id = params["applicationId"] + cc.answer_url = url inbound_calls_path(nil) + cc.disconnect_url = url inbound_calls_path(:transfer_complete) + end + + if call + outbound_transfers[pseudo_call_id] = call render :pause, locals: { duration: 300 } else render :redirect, locals: { to: inbound_calls_path(:voicemail) } From 75d1042bbb7e495bc026ef08372633f944dbde89 Mon Sep 17 00:00:00 2001 From: Stephen Paul Weber Date: Wed, 13 Oct 2021 13:41:08 -0500 Subject: [PATCH 2/4] Easy DSL for adding XEP-0122 validation to fields --- lib/form_template.rb | 26 +++++++++++++-- test/test_form_template.rb | 68 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 92 insertions(+), 2 deletions(-) diff --git a/lib/form_template.rb b/lib/form_template.rb index ffe7ccdd968bdb4143740bd00b48d62bfa8114a0..422ec719f022203c993c81c0fc958eb7fd0fb97e 100644 --- a/lib/form_template.rb +++ b/lib/form_template.rb @@ -48,8 +48,30 @@ class FormTemplate @__form.instructions = s end - def field(**kwargs) - @__form.fields = @__form.fields + [kwargs] + def validate(f, datatype: nil, **kwargs) + Nokogiri::XML::Builder.with(f) do |x| + x.validate( + xmlns: "http://jabber.org/protocol/xdata-validate", + datatype: datatype || "xs:string" + ) do + x.basic unless validation_type(x, **kwargs) + end + end + end + + def validation_type(x, open: false, regex: nil, range: nil) + x.open if open + x.range(min: range.first, max: range.last) if range + x.regex(regex.source) if regex + open || regex || range + end + + def field(datatype: nil, open: false, regex: nil, range: nil, **kwargs) + f = Blather::Stanza::X::Field.new(kwargs) + if datatype || open || regex || range + validate(f, datatype: datatype, open: open, regex: regex, range: range) + end + @__form.fields += [f] end def xml diff --git a/test/test_form_template.rb b/test/test_form_template.rb index 78c4e0e0071fd2a8addd6eabf11578b010b349e1..8015d917a2ec1a2ad5183cf21855677bd2aa95f3 100644 --- a/test/test_form_template.rb +++ b/test/test_form_template.rb @@ -46,6 +46,74 @@ class FormTemplateTest < Minitest::Test assert_equal "INSTRUCTIONS", form.instructions end + def test_form_validate_basic + template = FormTemplate.new(<<~TEMPLATE) + form! + field(var: "thevar", label: "thelabel", datatype: "xs:integer") + TEMPLATE + form = template.render + assert_equal 1, form.fields.length + assert_equal "thevar", form.fields[0].var + assert_equal "thelabel", form.fields[0].label + validate = form.fields[0].find( + "ns:validate", + ns: "http://jabber.org/protocol/xdata-validate" + ).first + assert_equal "xs:integer", validate[:datatype] + assert_equal "basic", validate.children.first.name + end + + def test_form_validate_open + template = FormTemplate.new(<<~TEMPLATE) + form! + field(var: "thevar", label: "thelabel", open: true) + TEMPLATE + form = template.render + assert_equal 1, form.fields.length + assert_equal "thevar", form.fields[0].var + assert_equal "thelabel", form.fields[0].label + validate = form.fields[0].find( + "ns:validate", + ns: "http://jabber.org/protocol/xdata-validate" + ).first + assert_equal ["open"], validate.children.map(&:name) + end + + def test_form_validate_regex + template = FormTemplate.new(<<~TEMPLATE) + form! + field(var: "thevar", label: "thelabel", regex: /[A-Z]/) + TEMPLATE + form = template.render + assert_equal 1, form.fields.length + assert_equal "thevar", form.fields[0].var + assert_equal "thelabel", form.fields[0].label + validate = form.fields[0].find( + "ns:validate", + ns: "http://jabber.org/protocol/xdata-validate" + ).first + assert_equal ["regex"], validate.children.map(&:name) + assert_equal "[A-Z]", validate.children.first.content + end + + def test_form_validate_range + template = FormTemplate.new(<<~TEMPLATE) + form! + field(var: "thevar", label: "thelabel", range: (10..22)) + TEMPLATE + form = template.render + assert_equal 1, form.fields.length + assert_equal "thevar", form.fields[0].var + assert_equal "thelabel", form.fields[0].label + validate = form.fields[0].find( + "ns:validate", + ns: "http://jabber.org/protocol/xdata-validate" + ).first + assert_equal ["range"], validate.children.map(&:name) + assert_equal "10", validate.children.first[:min] + assert_equal "22", validate.children.first[:max] + end + def test_no_type template = FormTemplate.new(<<~TEMPLATE) title "TITLE" From 43ac00160445859bfed198c781ecb1f0ee55ce4d Mon Sep 17 00:00:00 2001 From: Stephen Paul Weber Date: Wed, 13 Oct 2021 14:36:24 -0500 Subject: [PATCH 3/4] Move more persistence into the repo layer BackendSGX shouldn't touch Redis, instead get one of the repos to save the data we want saved wherever that repo saves it (in this case, those same Redis keys). --- lib/backend_sgx.rb | 8 ---- lib/bwmsgsv2_repo.rb | 28 +++++++++++++ lib/customer.rb | 2 +- lib/customer_repo.rb | 30 ++++++++++++-- lib/registration.rb | 11 ++---- sgx_jmp.rb | 10 ++--- test/test_customer_repo.rb | 81 +++++++++++++++++++++++++++++++++++++- test/test_registration.rb | 10 ++--- 8 files changed, 150 insertions(+), 30 deletions(-) diff --git a/lib/backend_sgx.rb b/lib/backend_sgx.rb index 9a0ee42940c4f9184424a94651f72955572a1e5a..8b0a15a20fb018df23aacf5ef5a4aae843d9cdc2 100644 --- a/lib/backend_sgx.rb +++ b/lib/backend_sgx.rb @@ -36,14 +36,6 @@ class BackendSgx end end - def set_fwd(uri) - REDIS.set("catapult_fwd-#{registered?.phone}", uri) - end - - def set_fwd_timeout(timeout) - REDIS.set("catapult_fwd_timeout-#{from_jid}", timeout) - end - def set_ogm_url(url) REDIS.set("catapult_ogm_url-#{from_jid}", url) end diff --git a/lib/bwmsgsv2_repo.rb b/lib/bwmsgsv2_repo.rb index 2aa498e6a91dab4c7f922e9d6be77f36fdfe179a..b295ff199a08b972648ff974d9e1b79cf4d23112 100644 --- a/lib/bwmsgsv2_repo.rb +++ b/lib/bwmsgsv2_repo.rb @@ -27,8 +27,36 @@ class Bwmsgsv2Repo end end + def put_transcription_enabled(customer_id, enabled) + sgx = @trivial_repo.get(customer_id) + REDIS.setbit( + "catapult_settings_flags-#{sgx.from_jid}", + Bwmsgsv2Repo::VOICEMAIL_TRANSCRIPTION_DISABLED, + enabled ? 0 : 1 + ) + end + + def put_fwd(customer_id, tel, customer_fwd) + sgx = @trivial_repo.get(customer_id) + EMPromise.all([ + set_or_delete("catapult_fwd-#{tel}", customer_fwd.uri), + set_or_delete( + "catapult_fwd_timeout-#{sgx.from_jid}", + customer_fwd.timeout.to_i + ) + ]) + end + protected + def set_or_delete(k, v) + if v.nil? + REDIS.del(k) + else + REDIS.set(k, v) + end + end + def fetch_raw(from_jid) registration(from_jid).then do |r| EMPromise.all([from_redis(from_jid, r ? r.phone : nil), r]) diff --git a/lib/customer.rb b/lib/customer.rb index 71aebc0b97ed12416b218d7bf15e3e110e0bf7c9..e4f50a6839f5bab5723510871ae2f003e79e8219 100644 --- a/lib/customer.rb +++ b/lib/customer.rb @@ -23,7 +23,7 @@ class Customer def_delegators :@plan, :active?, :activate_plan_starting_now, :bill_plan, :currency, :merchant_account, :plan_name, :auto_top_up_amount def_delegators :@sgx, :register!, :registered?, :set_ogm_url, - :set_fwd, :fwd, :transcription_enabled + :fwd, :transcription_enabled def_delegators :@usage, :usage_report, :message_usage, :incr_message_usage def initialize( diff --git a/lib/customer_repo.rb b/lib/customer_repo.rb index c45c8fc50f056b67d978ee224495535244561c14..109ac28cf82da9c3b5b5cfe62754b378ca9bd593 100644 --- a/lib/customer_repo.rb +++ b/lib/customer_repo.rb @@ -59,12 +59,36 @@ class CustomerRepo end end + def put_lidb_name(customer, lidb_name) + BandwidthIris::Lidb.create( + customer_order_id: customer.customer_id, + lidb_tn_groups: { lidb_tn_group: { + telephone_numbers: [customer.registered?.phone.sub(/\A\+1/, "")], + subscriber_information: lidb_name, + use_type: "RESIDENTIAL", + visibility: "PUBLIC" + } } + ) + end + + def put_transcription_enabled(customer, transcription_enabled) + @sgx_repo.put_transcription_enabled( + customer.customer_id, transcription_enabled + ) + end + + def put_fwd(customer, customer_fwd) + @sgx_repo.put_fwd( + customer.customer_id, + customer.registered?.phone, + customer_fwd + ) + end + protected def new_sgx(customer_id) - TrivialBackendSgxRepo.new.get(customer_id).with( - registered?: false - ) + TrivialBackendSgxRepo.new.get(customer_id).with(registered?: false) end def find_legacy_customer(jid) diff --git a/lib/registration.rb b/lib/registration.rb index 7b12c51a4a35c5473ff0e594bd76e3c2704b6724..9de075707d379cfb09efa1f9f0a81b67290e94e6 100644 --- a/lib/registration.rb +++ b/lib/registration.rb @@ -445,10 +445,6 @@ class Registration }.then { |tel| Finish.new(@customer, tel).write } end - def cheogram_sip_addr - "sip:#{ERB::Util.url_encode(@customer.jid)}@sip.cheogram.com" - end - def raise_setup_error(e) Command.log.error "@customer.register! failed", e Command.finish( @@ -459,11 +455,12 @@ class Registration end def customer_active_tel_purchased - @customer.register!(@tel).catch(&method(:raise_setup_error)).then { |sgx| + @customer.register!(@tel).catch(&method(:raise_setup_error)).then { EMPromise.all([ REDIS.del("pending_tel_for-#{@customer.jid}"), - sgx.set_fwd(cheogram_sip_addr), - sgx.set_fwd_timeout(25) # ~5 seconds / ring, 5 rings + Bwmsgsv2Repo.new.put_fwd(@customer.customer_id, @tel, CustomerFwd.for( + uri: "xmpp:#{@customer.jid}", timeout: 25 # ~5 seconds / ring, 5 rings + )) ]) }.then do Command.finish("Your JMP account has been activated as #{@tel}") diff --git a/sgx_jmp.rb b/sgx_jmp.rb index 15cfde0eeea3ed5d3725c6de6f4cbcc52c22f571..6c4cf34d657d88c7f9a433185f213974b3cadb46 100644 --- a/sgx_jmp.rb +++ b/sgx_jmp.rb @@ -617,12 +617,12 @@ Command.new( if ["1", "true"].include?(fwd.form.field("change_fwd")&.value.to_s) # Migrate location if needed BandwidthIris::SipPeer.new( - site_id: CONFIG[:bandwidth_site], - id: CONFIG[:bandwidth_peer] + site_id: CONFIG[:bandwidth_site], id: CONFIG[:bandwidth_peer] ).move_tns([customer.registered?.phone]) - customer.set_fwd(sip_account.uri).then do - Command.finish("Inbound calls will now forward to SIP.") - end + Command.execution.customer_repo.put_fwd( + customer, + customer.fwd.with(uri: sip_account.uri) + ).then { Command.finish("Inbound calls will now forward to SIP.") } else Command.finish end diff --git a/test/test_customer_repo.rb b/test/test_customer_repo.rb index 255352dc78f557c2964a63c0ff8223cc780c5d91..d996491e4d0aeb26d5c85812e3f65b19d3e3006c 100644 --- a/test/test_customer_repo.rb +++ b/test/test_customer_repo.rb @@ -3,6 +3,10 @@ require "test_helper" require "customer_repo" +class CustomerRepo + attr_reader :sgx_repo +end + class CustomerRepoTest < Minitest::Test FAKE_REDIS = FakeRedis.new( # sgx-jmp customer @@ -51,7 +55,13 @@ class CustomerRepoTest < Minitest::Test db: FAKE_DB, braintree: Minitest::Mock.new ) - CustomerRepo.new(redis: redis, db: db, braintree: braintree) + sgx_repo = Minitest::Mock.new(TrivialBackendSgxRepo.new) + CustomerRepo.new( + redis: redis, + db: db, + braintree: braintree, + sgx_repo: sgx_repo + ) end def setup @@ -153,4 +163,73 @@ class CustomerRepoTest < Minitest::Test assert_mock redis end em :test_create + + def test_put_lidb_name + post = stub_request( + :post, + "https://dashboard.bandwidth.com/v1.0/accounts//lidbs" + ).with(body: { + CustomerOrderId: "test", + LidbTnGroups: { + LidbTnGroup: { + TelephoneNumbers: "5556667777", + SubscriberInformation: "Hank", + UseType: "RESIDENTIAL", + Visibility: "PUBLIC" + } + } + }.to_xml(root: "LidbOrder", indent: 0)).to_return( + status: 201, + headers: { location: "/boop/123" } + ) + + stub_request( + :get, + "https://dashboard.bandwidth.com/v1.0/accounts//lidbs/123" + ) + + @repo.put_lidb_name( + Customer.new( + "test", + "test@exmple.com", + sgx: OpenStruct.new(registered?: OpenStruct.new(phone: "+15556667777")) + ), + "Hank" + ) + + assert_requested post + end + em :test_put_lidb_name + + def test_put_transcription_enabled + @repo.sgx_repo.expect( + :put_transcription_enabled, + EMPromise.resolve(nil), + ["test", true] + ) + @repo.put_transcription_enabled( + Customer.new("test", "test@exmple.com"), + true + ) + assert_mock @repo.sgx_repo + end + em :test_put_transcription_enabled + + def test_put_fwd + @repo.sgx_repo.expect( + :put_fwd, + EMPromise.resolve(nil), + ["test", "+15556667777", :fwd] + ) + @repo.put_fwd( + Customer.new( + "test", + "test@exmple.com", + sgx: OpenStruct.new(registered?: OpenStruct.new(phone: "+15556667777")) + ), + :fwd + ) + assert_mock @repo.sgx_repo + end + em :test_put_fwd end diff --git a/test/test_registration.rb b/test/test_registration.rb index 52b5e6475fe5592a543b4aa4b20da63892dfe731..24b2e7c926e89d230bc0d4802feed387a60934d7 100644 --- a/test/test_registration.rb +++ b/test/test_registration.rb @@ -517,7 +517,7 @@ class RegistrationTest < Minitest::Test Command::COMMAND_MANAGER = Minitest::Mock.new Registration::Finish::TEL_SELECTIONS = FakeTelSelections.new Registration::Finish::REDIS = Minitest::Mock.new - BackendSgx::REDIS = Minitest::Mock.new + Bwmsgsv2Repo::REDIS = Minitest::Mock.new def setup @sgx = Minitest::Mock.new(TrivialBackendSgxRepo.new.get("test")) @@ -568,15 +568,15 @@ class RegistrationTest < Minitest::Test nil, ["pending_tel_for-test@example.net"] ) - BackendSgx::REDIS.expect( + Bwmsgsv2Repo::REDIS.expect( :set, nil, [ "catapult_fwd-+15555550000", - "sip:test%40example.net@sip.cheogram.com" + "xmpp:test@example.net" ] ) - BackendSgx::REDIS.expect( + Bwmsgsv2Repo::REDIS.expect( :set, nil, ["catapult_fwd_timeout-customer_test@component", 25] @@ -608,7 +608,7 @@ class RegistrationTest < Minitest::Test assert_requested create_order assert_mock @sgx assert_mock Registration::Finish::REDIS - assert_mock BackendSgx::REDIS + assert_mock Bwmsgsv2Repo::REDIS assert_mock blather end em :test_write From 192d092f0a54962adf5ef067708eaacec273ce8a Mon Sep 17 00:00:00 2001 From: Stephen Paul Weber Date: Wed, 13 Oct 2021 14:37:52 -0500 Subject: [PATCH 4/4] New configure calls command Uses only v2 APIs and sets things up in the new way. --- forms/configure_calls.rb | 48 +++++++++++++++++++++++++++++++++++++ lib/configure_calls_form.rb | 40 +++++++++++++++++++++++++++++++ lib/customer.rb | 5 ++++ sgx_jmp.rb | 28 +++++++++++++++------- 4 files changed, 112 insertions(+), 9 deletions(-) create mode 100644 forms/configure_calls.rb create mode 100644 lib/configure_calls_form.rb diff --git a/forms/configure_calls.rb b/forms/configure_calls.rb new file mode 100644 index 0000000000000000000000000000000000000000..c69859b117482f9de788648c0fec6118780756fd --- /dev/null +++ b/forms/configure_calls.rb @@ -0,0 +1,48 @@ +form! +title "Configure Calls" + +field( + var: "fwd[timeout]", + type: "text-single", + datatype: "xs:integer", + label: "Seconds to ring before voicemail", + description: "One ring is ~5 seconds. Negative means ring forever.", + value: @customer.fwd.timeout.to_i.to_s +) + +field( + var: "voicemail_transcription", + type: "boolean", + label: "Voicemail transcription", + value: @customer.transcription_enabled.to_s +) + +field( + var: "fwd[uri]", + type: "list-single", + datatype: "xs:anyURI", + open: true, + label: "Forward calls to", + description: "List item or any custom xmpp:, sip:, or tel: URI.", + options: [ + { label: "Jabber ID", value: "xmpp:#{@customer.jid}" }, + { label: "SIP Account", value: @customer.sip_account.uri } + ], + value: @customer.fwd.uri +) + +if @customer.tndetails.dig(:features, :lidb) + field( + var: "lidb_name", + type: "fixed", + label: "CNAM", + value: "#{@lidb[:name]} (#{@lidb[:status]})" + ) +elsif @customer.tndetails[:on_net_vendor] + field( + var: "lidb_name", + type: "text-single", + label: "CNAM Name", + description: "or nothing/space to leave blank" + ) +end diff --git a/lib/configure_calls_form.rb b/lib/configure_calls_form.rb new file mode 100644 index 0000000000000000000000000000000000000000..1d0690597846e68e0a507542a717755558e8b208 --- /dev/null +++ b/lib/configure_calls_form.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +require_relative "form_to_h" + +class ConfigureCallsForm + using FormToH + + def initialize(customer) + @customer = customer + end + + def render + FormTemplate.render("configure_calls", customer: @customer) + end + + def parse(form) + params = form.to_h + {}.tap do |result| + result[:fwd] = parse_fwd(params["fwd"]) if params.key?("fwd") + if params.key?("voicemail_transcription") + result[:transcription_enabled] = + ["1", "true"].include?(params["voicemail_transcription"]) + end + result[:lidb_name] = params["lidb_name"] if lidb_guard(params["lidb_name"]) + end + end + +protected + + def lidb_guard(lidb_name) + !lidb_name.to_s.strip.empty? && + !@customer.tndetails.dig(:features, :lidb) + end + + def parse_fwd(fwd_from_form) + fwd_from_form.reduce(@customer.fwd) do |fwd, (var, val)| + fwd.with(var.to_sym => val) + end + end +end diff --git a/lib/customer.rb b/lib/customer.rb index e4f50a6839f5bab5723510871ae2f003e79e8219..68e74fa01418fb3b76763730194e08d052ba456f 100644 --- a/lib/customer.rb +++ b/lib/customer.rb @@ -83,6 +83,11 @@ class Customer stanza_to(iq, &IQ_MANAGER.method(:write)).then(&:vcard) end + def tndetails + @tndetails ||= + BandwidthIris::Tn.new(telephone_number: registered?.phone).get_details + end + def ogm(from_tel=nil) CustomerOGM.for(@sgx.ogm_url, -> { fetch_vcard_temp(from_tel) }) end diff --git a/sgx_jmp.rb b/sgx_jmp.rb index 6c4cf34d657d88c7f9a433185f213974b3cadb46..c1fd2695097c13e5d4013f0657240bf5f74e9ad3 100644 --- a/sgx_jmp.rb +++ b/sgx_jmp.rb @@ -73,6 +73,7 @@ require_relative "lib/bwmsgsv2_repo" require_relative "lib/bandwidth_tn_order" require_relative "lib/btc_sell_prices" require_relative "lib/buy_account_credit_form" +require_relative "lib/configure_calls_form" require_relative "lib/command" require_relative "lib/command_list" require_relative "lib/customer" @@ -453,16 +454,25 @@ Command.new( end }.register(self).then(&CommandList.method(:register)) -# Commands that just pass through to the SGX -{ - "configure-calls" => ["Configure Calls"] -}.each do |node, args| - Command.new(node, *args) { - Command.customer.then do |customer| - customer.stanza_from(Command.execution.iq) +Command.new( + "configure calls", + "Configure Calls", + customer_repo: CustomerRepo.new(sgx_repo: Bwmsgsv2Repo.new) +) { + Command.customer.then do |customer| + cc_form = ConfigureCallsForm.new(customer) + Command.reply { |reply| + reply.allowed_actions = [:next] + reply.command << cc_form.render + }.then { |iq| + EMPromise.all(cc_form.parse(iq.form).map { |k, v| + Command.execution.customer_repo.public_send("put_#{k}", customer, v) + }) + }.then do + Command.finish("Configuration saved!") end - }.register(self, guards: [node: node]).then(&CommandList.method(:register)) -end + end +}.register(self).then(&CommandList.method(:register)) Command.new( "ogm",