1# frozen_string_literal: true
2
3require "value_semantics/monkey_patched"
4require_relative "../admin_action"
5require_relative "../form_to_h"
6require_relative "../trust_level_repo"
7
8class AdminAction
9 class SetTrustLevel < AdminAction
10 include Isomorphic
11
12 class Command
13 using FormToH
14
15 def self.for(target_customer, reply:)
16 TrustLevelRepo.new.find_manual(target_customer.customer_id).then { |man|
17 new(
18 man,
19 customer_id: target_customer.customer_id
20 )
21 }.then { |x|
22 reply.call(x.form).then(&x.method(:create))
23 }
24 end
25
26 def initialize(manual, **bag)
27 @manual = manual
28 @bag = bag.compact
29 end
30
31 def form
32 FormTemplate.render(
33 "admin_set_trust_level",
34 manual: @manual,
35 levels: TrustLevel.constants.map(&:to_s).reject { |x| x == "Manual" }
36 )
37 end
38
39 def create(result)
40 AdminAction::SetTrustLevel.for(
41 previous_trust_level: @manual,
42 **@bag,
43 **result.form.to_h
44 .reject { |_k, v| v == "automatic" }.transform_keys(&:to_sym)
45 )
46 end
47 end
48
49 InvalidLevel = Struct.new(:level, :levels) {
50 def to_s
51 "Trust level invalid: expected #{levels.join(', ')}, got #{level}"
52 end
53 }
54
55 NoMatch = Struct.new(:expected, :actual) {
56 def to_s
57 "Trust level doesn't match: expected #{expected}, got #{actual}"
58 end
59 }
60
61 def initialize(previous_trust_level: nil, new_trust_level: nil, **kwargs)
62 super(
63 previous_trust_level: previous_trust_level.presence,
64 new_trust_level: new_trust_level.presence,
65 **kwargs
66 )
67 end
68
69 def customer_id
70 @attributes[:customer_id]
71 end
72
73 def previous_trust_level
74 @attributes[:previous_trust_level]
75 end
76
77 def new_trust_level
78 @attributes[:new_trust_level]
79 end
80
81 # If I don't check previous_trust_level here I could get into this
82 # situation:
83 # 1. Set from automatic to Customer
84 # 2. Undo
85 # 3. Set from automatic to Paragon
86 # 4. Undo the undo (redo set from automatic to customer)
87 # Now if I don't check previous_trust_level we'll enqueue a thing that says
88 # we've set from manual to customer, but that's not actually what we did! We
89 # set from Paragon to customer. If I undo that now I won't end up back a
90 # paragon, I'll end up at automatic again, which isn't the state I was in a
91 # second ago
92 def check_forward
93 EMPromise.all([
94 check_noop,
95 check_valid,
96 check_consistent
97 ])
98 end
99
100 def forward
101 TrustLevelRepo.new.put(customer_id, new_trust_level).then { self }
102 end
103
104 def to_reverse
105 with(
106 previous_trust_level: new_trust_level,
107 new_trust_level: previous_trust_level
108 )
109 end
110
111 def to_s
112 "set_trust_level(#{customer_id}): "\
113 "#{pretty(previous_trust_level)} -> #{pretty(new_trust_level)}"
114 end
115
116 protected
117
118 def check_noop
119 EMPromise.reject(NoOp.new) if new_trust_level == previous_trust_level
120 end
121
122 def check_valid
123 options = TrustLevel.constants.map(&:to_s)
124 return unless new_trust_level && !options.include?(new_trust_level)
125
126 EMPromise.reject(InvalidLevel.new(new_trust_level, options))
127 end
128
129 def check_consistent
130 TrustLevelRepo.new.find_manual(customer_id).then { |trust|
131 unless previous_trust_level == trust
132 EMPromise.reject(
133 NoMatch.new(pretty(previous_trust_level), pretty(trust))
134 )
135 end
136 }
137 end
138
139 def pretty(level)
140 level || "automatic"
141 end
142 end
143end