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