1# frozen_string_literal: true
2
3require "lazy_object"
4require "ruby-bandwidth-iris"
5require_relative "geo_code_repo"
6require_relative "local_calling_guide_repo"
7
8class BandwidthTnRepo
9 class RateCenter
10 # @return [String, nil]
11 attr_reader :region
12
13 # @return [String, nil]
14 attr_reader :locality
15
16 # @return [String, nil]
17 attr_reader :lcg_region
18
19 # @return [String, nil]
20 attr_reader :lcg_locality
21
22 # @return [Array(String, String)]
23 def to_a
24 [region, locality]
25 end
26
27 # @param btn [BandwidthIris::Tn]
28 # @param lcg_repo [LocalCallingGuideRepo]
29 # @param geo_code_repo [GeoCodeRepo]
30 # @return [EMPromise<RateCenter>]
31 def self.for(
32 btn,
33 lcg_repo: LazyObject.new { LocalCallingGuideRepo.new },
34 geo_code_repo: LazyObject.new { GeoCodeRepo.new }
35 )
36 [
37 Source::Bandwidth.new(btn),
38 Source::Geocode.new(btn, lcg_repo, geo_code_repo),
39 Source::LCG.new(btn, lcg_repo)
40 ].reduce(EMPromise.resolve(new)) { |promise, source|
41 promise.then { |acc|
42 next acc if acc.settled?
43
44 source.fetch(acc).then(&acc.method(:fold))
45 }
46 }
47 end
48
49 # @return [Boolean]
50 def settled?
51 return false unless readable(@region)
52 return false unless readable(@locality)
53
54 true
55 end
56
57 protected
58
59 # @param value [String, nil]
60 # @return [String, nil]
61 def readable(value)
62 value if value&.match?(/\d/) == false
63 end
64
65 # @param kwargs [Hash]
66 # @return [self]
67 def fold(kwargs)
68 kwargs.each_pair do |k, v|
69 instance_variable_set(
70 :"@#{k}",
71 readable(instance_variable_get(:"@#{k}")) || v
72 )
73 end
74 self
75 end
76
77 class Source
78 # @param acc [RateCenter]
79 # @return [EMPromise<Hash>]
80 def fetch(acc)
81 raise NotImplementedError
82 end
83
84 class Bandwidth < self
85 # @param btn [BandwidthIris::Tn]
86 def initialize(btn)
87 @btn = btn
88 end
89
90 # @param _acc [RateCenter]
91 # @return [EMPromise<Hash>]
92 def fetch(_acc)
93 city, state = @btn.get_details.values_at(
94 :city, :state
95 )
96 EMPromise.resolve(
97 { locality: city, region: state }
98 )
99 rescue BandwidthIris::Errors::GenericError
100 EMPromise.resolve({})
101 end
102 end
103
104 class LCG < self
105 # @param btn [BandwidthIris::Tn]
106 # @param lcg_repo [LocalCallingGuideRepo]
107 def initialize(btn, lcg_repo)
108 @btn = btn
109 @lcg_repo = lcg_repo
110 end
111
112 # @param acc [RateCenter]
113 # @return [EMPromise<Hash>]
114 def fetch(acc)
115 EMPromise.resolve(
116 {
117 locality: acc.lcg_locality,
118 region: acc.lcg_region
119 }
120 )
121 end
122 end
123
124 class Geocode < self
125 # @param btn [BandwidthIris::Tn]
126 # @param lcg_repo [LocalCallingGuideRepo]
127 # @param geo_code_repo [GeoCodeRepo]
128 def initialize(btn, lcg_repo, geo_code_repo)
129 @btn = btn
130 @geo_code_repo = geo_code_repo
131 @lcg_repo = lcg_repo
132 end
133
134 # @param _acc [RateCenter]
135 # @return [EMPromise<Hash>]
136 def fetch(_acc)
137 npa, nxx, * = @btn.telephone_number.scan(/.{1,3}/).to_a
138 @lcg_repo.find(npa, nxx).then { |lcg|
139 cache = lcg.to_h.transform_keys(&"lcg_".method(:+))
140 @geo_code_repo.reverse(lcg.lat, lcg.lon).then { |gc|
141 gc.to_h.slice(:locality, :region).merge(cache)
142 }.catch { cache }
143 }
144 end
145 end
146 end
147 end
148
149 STASH_QUERY = <<~SQL
150 INSERT INTO tel_inventory (
151 tel,
152 region,
153 locality,
154 source,
155 available_after,
156 premium_price
157 ) VALUES (
158 $1,
159 $2,
160 $3,
161 $4,
162 LOCALTIMESTAMP + '1 MONTH',
163 $5
164 )
165 SQL
166
167 # @param local_calling_guide_repo [LocalCallingGuideRepo]
168 # @param geo_code_repo [GeoCodeRepo]
169 def initialize(
170 local_calling_guide_repo: LazyObject.new { LocalCallingGuideRepo.new },
171 geo_code_repo: LazyObject.new { GeoCodeRepo.new }
172 )
173 @move_client =
174 BandwidthIris::Client.new(
175 account_id: CONFIG[:keep_area_codes_in][:account],
176 client_id: CONFIG[:creds][:client_id],
177 client_secret: CONFIG[:creds][:client_secret]
178 )
179 @local_calling_guide_repo = local_calling_guide_repo
180 @geo_code_repo = geo_code_repo
181 end
182
183 def find(tel)
184 BandwidthIris::Tn.new(telephone_number: tel).get_details
185 rescue StandardError
186 {}
187 end
188
189 def put_lidb_name(tel, lidb_name)
190 BandwidthIris::Lidb.create(
191 lidb_tn_groups: { lidb_tn_group: {
192 telephone_numbers: { telephone_number: tel.sub(/\A\+1/, "") },
193 subscriber_information: lidb_name,
194 use_type: "RESIDENTIAL", visibility: "PUBLIC"
195 } }
196 )
197 rescue BandwidthIris::Errors::GenericError
198 raise "Could not set CNAM, please contact support"
199 end
200
201 # @param btn [BandwidthIris::Tn]
202 # @param premium_price [Numeric]
203 # @return [EMPromise]
204 def stash_for_later(btn, premium_price)
205 tel = "+1#{btn.telephone_number}"
206 LOG.info "stash_for_later #{tel}\n#{caller}"
207 RateCenter.for(
208 btn,
209 lcg_repo: @local_calling_guide_repo,
210 geo_code_repo: @geo_code_repo
211 ).then { |loc|
212 DB.exec(STASH_QUERY, [tel, *loc, CONFIG[:creds][:account], premium_price])
213 }
214 end
215
216 def disconnect(tel, order_name)
217 tn = tel.sub(/\A\+1/, "")
218 code_and_price = CONFIG[:keep_area_codes].find { |k|
219 tn.start_with?(k[:area_code])
220 }
221 unless code_and_price
222 return BandwidthIris::Disconnect.create(order_name, tn)
223 end
224
225 btn = BandwidthIris::Tn.new({ telephone_number: tn }, @move_client)
226 stash_for_later(btn, code_and_price[:premium_price] || 0)
227 end
228
229 def move(tel, order_name, source_account_id)
230 tn = tel.sub(/\A\+1/, "")
231 BandwidthIris::Tn.new({ telephone_number: tn }, @move_client).move(
232 customer_order_id: order_name,
233 source_account_id: source_account_id,
234 site_id: CONFIG[:bandwidth_site],
235 sip_peer_id: CONFIG[:bandwidth_peer]
236 )
237 end
238end