towerops/lib/towerops/workers/report_worker.ex

75 lines
2 KiB
Elixir

defmodule Towerops.Workers.ReportWorker do
@moduledoc """
Oban worker for scheduled report generation and delivery.
The cron dispatcher (no args) runs hourly and checks which reports are due.
Individual jobs receive `%{"report_id" => id}` and generate + email the report.
"""
use Oban.Worker, queue: :maintenance
alias Towerops.Reports
alias Towerops.Reports.Notifier
require Logger
# Cron dispatcher: find due reports and enqueue individual jobs
@impl Oban.Worker
def perform(%Oban.Job{args: args}) when args == %{} do
reports =
Reports.Report
|> Towerops.Repo.all()
|> Enum.filter(&(&1.enabled and Reports.due?(&1)))
enqueued =
Enum.count(reports, fn report ->
case %{"report_id" => report.id}
|> new(unique: [period: 3600])
|> Oban.insert() do
{:ok, _} ->
true
{:error, changeset} ->
Logger.error("Failed to enqueue report job for report_id=#{report.id}: #{inspect(changeset)}")
false
end
end)
if enqueued > 0 do
Logger.info("Report worker: dispatched #{enqueued} report jobs")
end
:ok
end
# Individual report: generate and deliver
def perform(%Oban.Job{args: %{"report_id" => id}}) do
case Reports.get_report(id) do
{:ok, report} ->
run_report(report)
{:error, :not_found} ->
Logger.warning("Report #{id} not found, skipping")
:ok
end
end
defp run_report(report) do
{:ok, csv_data} = Reports.generate_csv(report)
case Notifier.deliver_report(report, csv_data) do
:ok ->
Reports.mark_run(report, "success")
Logger.info("Report #{report.id} (#{report.name}) delivered successfully")
:ok
{:error, _reason} ->
Reports.mark_run(report, "partial_failure")
:ok
end
rescue
exception ->
Reports.mark_run(report, "failed")
Logger.error("Report #{report.id} generation failed: #{Exception.message(exception)}")
:ok
end
end