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				unless (registration = target_customer.registered?)
 16					return EMPromise.reject("Customer not registered")
 17				end
 18
 19				EMPromise.resolve(
 20					new(
 21						customer_id: target_customer.customer_id,
 22						old_backend: target_customer.sgx.strip!.to_s,
 23						old_tel: registration.phone
 24					)
 25				).then { |x| reply.call(x.form).then(&x.method(:change)) }
 26			end
 27
 28			def initialize(**bag)
 29				@bag = bag
 30			end
 31
 32			def form
 33				FormTemplate.render("admin_number_change")
 34			end
 35
 36			def change(result)
 37				AdminAction::NumberChange.for(
 38					**@bag,
 39					**result.form.to_h
 40						.reject { |_k, v| v == "nil" || v.to_s.empty? }
 41						.tap { |form_h|
 42							form_h["new_backend"] ||= @bag[:old_backend]
 43							form_h["new_tel"] ||= @bag[:old_tel]
 44						}
 45						.transform_keys(&:to_sym)
 46				)
 47			end
 48		end
 49
 50		NilKey = Struct.new(:key) {
 51			def to_s
 52				"Expected a key with a value, but #{key} has no value."
 53			end
 54		}
 55
 56		UnknownBackend = Struct.new(:backend, :expected) {
 57			def to_s
 58				"Got unknown backend: #{backend}, expected one of #{expected}"
 59			end
 60		}
 61
 62		def customer_id
 63			@attributes[:customer_id]
 64		end
 65
 66		def old_tel
 67			@attributes[:old_tel]
 68		end
 69
 70		def new_tel
 71			@attributes[:new_tel]
 72		end
 73
 74		def old_backend
 75			@attributes[:old_backend]
 76		end
 77
 78		def new_backend
 79			@attributes[:new_backend]
 80		end
 81
 82		def should_delete?
 83			["1", "true"].include?(@attributes[:should_delete])
 84		end
 85
 86		def check_forward
 87			EMPromise.all([check_noop, check_cat_jid, check_backend])
 88		end
 89
 90		def forward
 91			EMPromise.all([
 92				new_tel != old_tel && change_catapult_fwd!,
 93				TrivialBackendSgxRepo.new(
 94					redis: REDIS
 95				).put(customer_id, new_backend, new_tel),
 96				should_disconnect_old_number? && disconnect_number
 97			]).then { self }
 98		end
 99
100		def change_catapult_fwd!
101			REDIS.rename("catapult_fwd-#{old_tel}", "catapult_fwd-#{new_tel}")
102		end
103
104		def to_reverse
105			with(
106				old_tel: new_tel,
107				new_tel: old_tel,
108				old_backend: new_backend,
109				new_backend: old_backend
110			)
111		end
112
113		def to_s
114			base = "Change Number\n"
115			base += "	[move backend?]: #{old_backend} -> #{new_backend}\n"
116			base += "	[change number?]: #{old_tel} -> #{new_tel}"
117			base + delete_warning
118		end
119
120	protected
121
122		def disconnect_number
123			# Order name is limited to 40 characters
124			# Assuming 12 chars for new_tel and 12 for customer_id, this is tight
125			# but ok
126			BandwidthTnRepo.new.disconnect(
127				old_tel,
128				"cust #{customer_id} swap to #{new_tel}"
129			)
130		end
131
132		def delete_warning
133			return "" unless should_delete? && !first_time?
134
135			" * NOT DELETING"
136		end
137
138		def check_noop
139			if new_tel == old_tel && new_backend == old_backend
140				EMPromise.reject(NoOp.new)
141			else
142				EMPromise.resolve(nil)
143			end
144		end
145
146		def check_cat_jid
147			cat_jid = "catapult_jid-#{old_tel}"
148			REDIS.exists(cat_jid).then { |v|
149				EMPromise.reject(NilKey.new(cat_jid)) unless v == 1
150			}
151		end
152
153		def check_backend
154			unless (expected = CONFIG[:sgx_creds].keys.map(
155				&:to_s
156			).push(CONFIG[:sgx])).include?(new_backend)
157				EMPromise.reject(UnknownBackend.new(new_backend, expected))
158			end
159		end
160
161		def should_disconnect_old_number?
162			should_delete? &&
163				first_time? &&
164				old_tel != new_tel &&
165				old_backend == CONFIG[:sgx]
166		end
167	end
168end