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