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