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