~singpolyma/sgx-jmp

ref: 8dd92b96258d14d431e9966d0534631c7fdc0214 sgx-jmp/lib/admin_actions/set_trust_level.rb -rw-r--r-- 3.3 KiB
8dd92b96Stephen Paul Weber Merge branch 'admin-actions' 5 months ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
# frozen_string_literal: true

require "value_semantics/monkey_patched"
require_relative "../admin_action"
require_relative "../form_to_h"
require_relative "../trust_level_repo"

class AdminAction
	class SetTrustLevel < AdminAction
		include Isomorphic

		class Command
			using FormToH

			def self.for(target_customer, reply:)
				TrustLevelRepo.new.find_manual(target_customer.customer_id).then { |man|
					new(
						man,
						customer_id: target_customer.customer_id
					)
				}.then { |x|
					reply.call(x.form).then(&x.method(:create))
				}
			end

			def initialize(manual, **bag)
				@manual = manual
				@bag = bag.compact
			end

			def form
				FormTemplate.render(
					"admin_set_trust_level",
					manual: @manual,
					levels: TrustLevel.constants.map(&:to_s).reject { |x| x == "Manual" }
				)
			end

			def create(result)
				AdminAction::SetTrustLevel.for(
					previous_trust_level: @manual,
					**@bag,
					**result.form.to_h
						.reject { |_k, v| v == "automatic" }.transform_keys(&:to_sym)
				)
			end
		end

		InvalidLevel = Struct.new(:level, :levels) {
			def to_s
				"Trust level invalid: expected #{levels.join(', ')}, got #{level}"
			end
		}

		NoMatch = Struct.new(:expected, :actual) {
			def to_s
				"Trust level doesn't match: expected #{expected}, got #{actual}"
			end
		}

		def initialize(previous_trust_level: nil, new_trust_level: nil, **kwargs)
			super(
				previous_trust_level: previous_trust_level.presence,
				new_trust_level: new_trust_level.presence,
				**kwargs
			)
		end

		def customer_id
			@attributes[:customer_id]
		end

		def previous_trust_level
			@attributes[:previous_trust_level]
		end

		def new_trust_level
			@attributes[:new_trust_level]
		end

		# If I don't check previous_trust_level here I could get into this
		# situation:
		# 1. Set from automatic to Customer
		# 2. Undo
		# 3. Set from automatic to Paragon
		# 4. Undo the undo (redo set from automatic to customer)
		# Now if I don't check previous_trust_level we'll enqueue a thing that says
		# we've set from manual to customer, but that's not actually what we did! We
		# set from Paragon to customer. If I undo that now I won't end up back a
		# paragon, I'll end up at automatic again, which isn't the state I was in a
		# second ago
		def check_forward
			EMPromise.all([
				check_noop,
				check_valid,
				check_consistent
			])
		end

		def forward
			TrustLevelRepo.new.put(customer_id, new_trust_level).then { self }
		end

		def to_reverse
			with(
				previous_trust_level: new_trust_level,
				new_trust_level: previous_trust_level
			)
		end

		def to_s
			"set_trust_level(#{customer_id}): "\
			"#{pretty(previous_trust_level)} -> #{pretty(new_trust_level)}"
		end

	protected

		def check_noop
			EMPromise.reject(NoOp.new) if new_trust_level == previous_trust_level
		end

		def check_valid
			options = TrustLevel.constants.map(&:to_s)
			return unless new_trust_level && !options.include?(new_trust_level)

			EMPromise.reject(InvalidLevel.new(new_trust_level, options))
		end

		def check_consistent
			TrustLevelRepo.new.find_manual(customer_id).then { |trust|
				unless previous_trust_level == trust
					EMPromise.reject(
						NoMatch.new(pretty(previous_trust_level), pretty(trust))
					)
				end
			}
		end

		def pretty(level)
			level || "automatic"
		end
	end
end