# 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