number_change.rb

  1# frozen_string_literal: true
  2
  3require "value_semantics/monkey_patched"
  4require_relative "../admin_action"
  5require_relative "../form_to_h"
  6require_relative "../utils"
  7
  8class AdminAction
  9	class NumberChange < AdminAction
 10		include Isomorphic
 11		class Command
 12			using FormToH
 13
 14			def self.for(target_customer, reply:)
 15				EMPromise.resolve(
 16					new(
 17						customer_id: target_customer.customer_id,
 18						old_backend: target_customer.sgx&.strip!&.to_s,
 19						old_tel: (reg = target_customer.registered?) ? reg.phone : nil
 20					)
 21				).then { |x| reply.call(x.form).then(&x.method(:change)) }
 22			end
 23
 24			def initialize(**bag)
 25				@bag = bag
 26			end
 27
 28			def form
 29				FormTemplate.render("admin_number_change")
 30			end
 31
 32			def change(result)
 33				AdminAction::NumberChange.for(
 34					**@bag,
 35					**result.form.to_h
 36						.reject { |_k, v| v == "nil" || v.to_s.empty? }
 37						.tap { |form_h|
 38							form_h["new_backend"] ||= @bag[:old_backend]
 39							form_h["new_tel"] ||= @bag[:old_tel]
 40						}
 41						.transform_keys(&:to_sym)
 42				)
 43			end
 44		end
 45
 46		class Orphan < StandardError
 47			def to_s
 48				"Can't register nil tel to any backend"
 49			end
 50		end
 51
 52		class UnknownBackend < StandardError
 53			def initialize(backend:, expected:)
 54				@backend = backend
 55				@expected = expected
 56			end
 57
 58			def to_s
 59				"Got unknown backend: #{@backend}, expected one of #{@expected}"
 60			end
 61		end
 62
 63		def customer_id
 64			@attributes[:customer_id]
 65		end
 66
 67		def old_tel
 68			@attributes[:old_tel]
 69		end
 70
 71		def new_tel
 72			@attributes[:new_tel]
 73		end
 74
 75		def old_backend
 76			@attributes[:old_backend]
 77		end
 78
 79		def new_backend
 80			@attributes[:new_backend]
 81		end
 82
 83		def should_delete?
 84			["1", "true"].include?(@attributes[:should_delete])
 85		end
 86
 87		def check_forward
 88			# Without this ordering, it's possible for
 89			# check_noop and check_orphan to race,
 90			# which creates ambiguity around whether
 91			# an actual error will raise or not.
 92			check_orphan.then {
 93				EMPromise.all([check_noop, check_backend])
 94			}
 95		end
 96
 97		def forward
 98			EMPromise.all([
 99				old_tel && new_tel != old_tel && change_catapult_fwd!,
100				update_tel_and_backend,
101				should_disconnect_old_number? && disconnect_bandwidth_number
102			]).then { self }
103		end
104
105		def update_tel_and_backend
106			TrivialBackendSgxRepo.new(redis: REDIS).put(
107				customer_id, new_backend,
108			).then { |sgx| sgx.register!(new_tel) }
109		end
110
111		# If old_tel exists, it's still possible no catapult_fwd has been set.
112		# However, if one exists, we should rename it.
113		# If old_tel does not exist, it's not possible for catapult_fwd
114		# to exist, and it's not possible for there to be value.
115		# to set catapult_fwd to. That's out-of-scope for this command.
116		def change_catapult_fwd!
117			REDIS.rename("catapult_fwd-#{old_tel}", "catapult_fwd-#{new_tel}")
118		end
119
120		def to_reverse
121			with(
122				old_tel: new_tel, new_tel: old_tel,
123				old_backend: new_backend, new_backend: old_backend
124			)
125		end
126
127		def to_s
128			base = "Change Number\n"
129			base += "	[move backend?]: #{old_backend} -> #{new_backend}\n"
130			base += "	[change number?]: #{old_tel || 'nil'} -> #{new_tel}"
131			base + delete_warning
132		end
133
134	protected
135
136		def disconnect_bandwidth_number
137			# Order name is limited to 40 characters
138			# Assuming 12 chars for new_tel and 12 for customer_id, this is tight
139			# but ok
140			BandwidthTnRepo.new.disconnect(
141				old_tel,
142				"cust #{customer_id} swap to #{new_tel}"
143			)
144		end
145
146		def delete_warning
147			return "" unless should_delete? && !first_time?
148
149			" * NOT DELETING"
150		end
151
152		def check_noop
153			if new_tel == old_tel && new_backend == old_backend
154				EMPromise.reject(NoOp.new)
155			else
156				EMPromise.resolve(nil)
157			end
158		end
159
160		def check_orphan
161			EMPromise.reject(Orphan.new) unless new_tel
162		end
163
164		def check_backend
165			unless (expected = CONFIG[:sgx_creds].keys.map(
166				&:to_s
167			).push(CONFIG[:sgx])).include?(new_backend)
168				EMPromise.reject(UnknownBackend.new(new_backend, expected))
169			end
170		end
171
172		def should_disconnect_old_number?
173			should_delete? &&
174				first_time? &&
175				old_tel != new_tel &&
176				old_backend == CONFIG[:sgx]
177		end
178	end
179end