133 lines
3.8 KiB
Ruby
133 lines
3.8 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
module ::Hledger
|
|
class ReportsController < ::ApplicationController
|
|
requires_plugin PLUGIN_NAME
|
|
|
|
RATE_LIMIT_MAX = 30
|
|
RATE_LIMIT_SECONDS = 60
|
|
CACHE_TTL = 5.minutes
|
|
|
|
before_action :ensure_enabled
|
|
before_action :find_topic
|
|
before_action :enforce_rate_limit
|
|
before_action :enforce_global_rate_limit
|
|
|
|
def show
|
|
report_type = params[:report_type].to_s
|
|
|
|
if !Hledger::ReportBuilder.supported?(report_type)
|
|
return render_json_error(I18n.t("hledger.errors.unsupported_report"), status: 422)
|
|
end
|
|
|
|
begin_date = parse_date(params[:begin])
|
|
end_date = parse_date(params[:end])
|
|
|
|
if begin_date == :invalid || end_date == :invalid
|
|
return render_json_error(I18n.t("hledger.errors.invalid_date"), status: 422)
|
|
end
|
|
|
|
first_post = @topic.first_post
|
|
cache_key = cache_key_for(first_post, report_type, begin_date, end_date)
|
|
|
|
if (cached = Discourse.cache.read(cache_key))
|
|
return render(json: cached)
|
|
end
|
|
|
|
Hledger::BuildReport.call(
|
|
params: {
|
|
raw: first_post&.raw,
|
|
report_type: report_type,
|
|
begin_date: begin_date,
|
|
end_date: end_date,
|
|
},
|
|
runner: Hledger::Runner.new,
|
|
) do
|
|
on_success do |report:|
|
|
Discourse.cache.write(cache_key, report, expires_in: CACHE_TTL)
|
|
render json: report
|
|
end
|
|
on_failed_step(:extract_journal) { |step| render_journal_error(step.error) }
|
|
on_failed_contract do |contract|
|
|
render_json_error(contract.errors.full_messages, status: 422)
|
|
end
|
|
on_exceptions(Hledger::UnavailableError) do |error|
|
|
log_failure(error)
|
|
render_json_error(I18n.t("hledger.errors.unavailable"), status: 503)
|
|
end
|
|
on_exceptions(Hledger::OutputLimitError) do |error|
|
|
log_failure(error)
|
|
render_json_error(I18n.t("hledger.errors.output_too_large"), status: 422)
|
|
end
|
|
on_exceptions(Hledger::Error) do |error|
|
|
log_failure(error)
|
|
render_json_error(I18n.t("hledger.errors.execution"), status: 422)
|
|
end
|
|
on_failure { render_json_error(I18n.t("hledger.errors.generic"), status: 422) }
|
|
end
|
|
end
|
|
|
|
private
|
|
|
|
def ensure_enabled
|
|
raise Discourse::NotFound if !SiteSetting.hledger_enabled
|
|
end
|
|
|
|
def find_topic
|
|
@topic = Topic.find_by(id: params[:topic_id])
|
|
raise Discourse::NotFound if @topic.blank?
|
|
raise Discourse::NotFound if !guardian.can_see?(@topic)
|
|
end
|
|
|
|
def enforce_rate_limit
|
|
RateLimiter.new(
|
|
current_user,
|
|
"hledger-reports-#{request.remote_ip}",
|
|
RATE_LIMIT_MAX,
|
|
RATE_LIMIT_SECONDS,
|
|
).performed!
|
|
end
|
|
|
|
def enforce_global_rate_limit
|
|
max = SiteSetting.hledger_global_rate_limit_per_minute.to_i
|
|
return if max <= 0
|
|
|
|
RateLimiter.new(nil, "hledger-reports-global", max, 60, global: true).performed!
|
|
end
|
|
|
|
def parse_date(value)
|
|
return nil if value.blank?
|
|
|
|
Date.iso8601(value.to_s)
|
|
rescue Date::Error
|
|
:invalid
|
|
end
|
|
|
|
def cache_key_for(first_post, report_type, begin_date, end_date)
|
|
[
|
|
"hledger",
|
|
@topic.id,
|
|
first_post&.version,
|
|
report_type,
|
|
begin_date,
|
|
end_date,
|
|
SiteSetting.hledger_path,
|
|
].join(":")
|
|
end
|
|
|
|
def render_journal_error(error)
|
|
key = "hledger.errors.journal.#{error}"
|
|
key = "hledger.errors.journal.invalid" if !I18n.exists?(key)
|
|
|
|
render_json_error(I18n.t(key), status: error == :none ? 404 : 422)
|
|
end
|
|
|
|
def log_failure(error)
|
|
Rails.logger.warn(
|
|
"hledger report failed: report=#{params[:report_type]} topic=#{@topic&.id} " \
|
|
"user=#{current_user&.id} error=#{error.class} detail=#{error.message.to_s.truncate(500)}",
|
|
)
|
|
end
|
|
end
|
|
end
|