# frozen_string_literal: true
class AdminActionRepo
class NotFound < StandardError; end
def initialize(redis: REDIS)
@redis = redis
end
def build(klass:, direction:, **kwargs)
dir = AdminAction::Direction.for(direction)
dir.new(AdminAction.const_get(klass).new(**kwargs))
end
# I'm using hash subset test for pred
# So if you give me any keys I'll find only things where those keys are
# present and set to that value
def find(limit, max="+", **pred)
return EMPromise.resolve([]) unless limit.positive?
xrevrange(
"admin_actions", max: max, min: "-", count: limit
).then { |new_max, results|
next [] if results.empty?
selected = results.select { |_id, values| pred < values }
.map { |id, values| build(id: id, **rename_class(values)) }
find(limit - selected.length, "(#{new_max}", **pred)
.then { |r| selected + r }
}
end
def create(action)
push_to_redis(**action.to_h).then { |id|
action.with(id: id)
}
end
protected
def rename_class(hash)
hash.transform_keys { |k| k == :class ? :klass : k }
end
# Turn value into a hash, paper over redis version issue, return earliest ID
def xrevrange(stream, min:, max:, count:)
min = next_id(min[1..-1]) if min.start_with?("(")
max = previous_id(max[1..-1]) if max.start_with?("(")
@redis.xrevrange(stream, max, min, "COUNT", count).then { |result|
next ["+", []] if result.empty?
[
result.last.first, # Reverse order, so this is the lowest ID
result.map { |id, values| [id, Hash[*values].transform_keys(&:to_sym)] }
]
}
end
# Versions of REDIS after 6.2 can just do "(#{current_id}" to make an
# exclusive version
def previous_id(current_id)
time, seq = current_id.split("-")
if seq == "0"
"#{time.to_i - 1}-18446744073709551615"
else
"#{time}-#{seq.to_i - 1}"
end
end
# Versions of REDIS after 6.2 can just do "(#{current_id}" to make an
# exclusive version
def next_id(current_id)
time, seq = current_id.split("-")
if seq == "18446744073709551615"
"#{time.to_i + 1}-0"
else
"#{time}-#{seq.to_i + 1}"
end
end
def push_to_redis(**kwargs)
@redis.xadd("admin_actions", "*", *kwargs.flatten)
end
end