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