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			return unless old_tel
118
119			REDIS.rename("catapult_fwd-#{old_tel}", "catapult_fwd-#{new_tel}")
120		end
121
122		def to_reverse
123			with(
124				old_tel: new_tel, new_tel: old_tel,
125				old_backend: new_backend, new_backend: old_backend
126			)
127		end
128
129		def to_s
130			base = "Change Number\n"
131			base += "	[move backend?]: #{old_backend} -> #{new_backend}\n"
132			base += "	[change number?]: #{old_tel || 'nil'} -> #{new_tel}"
133			base + delete_warning
134		end
135
136	protected
137
138		def disconnect_bandwidth_number
139			# Order name is limited to 40 characters
140			# Assuming 12 chars for new_tel and 12 for customer_id, this is tight
141			# but ok
142			BandwidthTnRepo.new.disconnect(
143				old_tel,
144				"cust #{customer_id} swap to #{new_tel}"
145			)
146		end
147
148		def delete_warning
149			return "" unless should_delete? && !first_time?
150
151			" * NOT DELETING"
152		end
153
154		def check_noop
155			if new_tel == old_tel && new_backend == old_backend
156				EMPromise.reject(NoOp.new)
157			else
158				EMPromise.resolve(nil)
159			end
160		end
161
162		def check_orphan
163			EMPromise.reject(Orphan.new) unless new_tel
164		end
165
166		def check_backend
167			unless (expected = CONFIG[:sgx_creds].keys.map(
168				&:to_s
169			).push(CONFIG[:sgx])).include?(new_backend)
170				EMPromise.reject(UnknownBackend.new(new_backend, expected))
171			end
172		end
173
174		def should_disconnect_old_number?
175			should_delete? &&
176				first_time? &&
177				old_tel != new_tel &&
178				old_backend == CONFIG[:sgx]
179		end
180	end
181end