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 repo = TrivialBackendSgxRepo.new(redis: REDIS)
92 EMPromise.all([
93 new_tel != old_tel && change_catapult_fwd!,
94 repo.put(customer_id, new_backend).then { |sgx|
95 sgx.register!(new_tel)
96 },
97 should_disconnect_old_number? && disconnect_bandwidth_number
98 ]).then { self }
99 end
100
101 def change_catapult_fwd!
102 REDIS.rename("catapult_fwd-#{old_tel}", "catapult_fwd-#{new_tel}")
103 end
104
105 def to_reverse
106 with(
107 old_tel: new_tel,
108 new_tel: old_tel,
109 old_backend: new_backend,
110 new_backend: old_backend
111 )
112 end
113
114 def to_s
115 base = "Change Number\n"
116 base += " [move backend?]: #{old_backend} -> #{new_backend}\n"
117 base += " [change number?]: #{old_tel} -> #{new_tel}"
118 base + delete_warning
119 end
120
121 protected
122
123 def disconnect_bandwidth_number
124 # Order name is limited to 40 characters
125 # Assuming 12 chars for new_tel and 12 for customer_id, this is tight
126 # but ok
127 BandwidthTnRepo.new.disconnect(
128 old_tel,
129 "cust #{customer_id} swap to #{new_tel}"
130 )
131 end
132
133 def delete_warning
134 return "" unless should_delete? && !first_time?
135
136 " * NOT DELETING"
137 end
138
139 def check_noop
140 if new_tel == old_tel && new_backend == old_backend
141 EMPromise.reject(NoOp.new)
142 else
143 EMPromise.resolve(nil)
144 end
145 end
146
147 def check_cat_jid
148 cat_jid = "catapult_jid-#{old_tel}"
149 REDIS.exists(cat_jid).then { |v|
150 EMPromise.reject(NilKey.new(cat_jid)) unless v == 1
151 }
152 end
153
154 def check_backend
155 unless (expected = CONFIG[:sgx_creds].keys.map(
156 &:to_s
157 ).push(CONFIG[:sgx])).include?(new_backend)
158 EMPromise.reject(UnknownBackend.new(new_backend, expected))
159 end
160 end
161
162 def should_disconnect_old_number?
163 should_delete? &&
164 first_time? &&
165 old_tel != new_tel &&
166 old_backend == CONFIG[:sgx]
167 end
168 end
169end