Packages
phoenix_kit
1.7.21
1.7.208
1.7.207
1.7.206
1.7.205
1.7.204
1.7.203
1.7.202
1.7.201
1.7.200
1.7.199
1.7.198
1.7.197
1.7.196
1.7.194
1.7.193
1.7.192
1.7.191
1.7.190
1.7.189
1.7.187
1.7.186
1.7.185
1.7.184
1.7.183
1.7.182
1.7.181
1.7.180
1.7.179
1.7.178
1.7.177
1.7.176
1.7.175
1.7.174
1.7.173
1.7.172
1.7.171
1.7.170
1.7.169
1.7.168
1.7.167
1.7.166
1.7.165
1.7.164
1.7.162
1.7.161
1.7.160
1.7.159
1.7.157
1.7.156
1.7.155
1.7.154
1.7.153
1.7.152
1.7.151
1.7.150
1.7.149
1.7.146
1.7.145
1.7.144
1.7.143
1.7.138
1.7.133
1.7.132
1.7.131
1.7.130
1.7.128
1.7.126
1.7.125
1.7.121
1.7.120
1.7.119
1.7.118
1.7.117
1.7.116
1.7.115
1.7.114
1.7.113
1.7.112
1.7.111
1.7.110
1.7.109
1.7.108
1.7.107
1.7.106
1.7.105
1.7.104
1.7.103
1.7.102
1.7.101
1.7.100
1.7.99
1.7.98
1.7.97
1.7.96
1.7.95
1.7.94
1.7.93
1.7.92
1.7.91
1.7.90
1.7.89
1.7.88
1.7.87
1.7.86
1.7.85
1.7.84
1.7.83
1.7.82
1.7.81
1.7.80
1.7.79
1.7.78
1.7.77
1.7.76
1.7.75
1.7.74
1.7.71
1.7.70
1.7.69
1.7.66
1.7.65
1.7.64
1.7.63
1.7.62
1.7.61
1.7.59
1.7.58
1.7.57
1.7.56
1.7.55
1.7.54
1.7.53
1.7.52
1.7.51
1.7.49
1.7.44
1.7.43
1.7.42
1.7.41
1.7.39
1.7.38
1.7.37
1.7.36
1.7.34
1.7.33
1.7.31
1.7.30
1.7.29
1.7.28
1.7.27
1.7.26
1.7.25
1.7.24
1.7.23
1.7.22
1.7.21
1.7.20
1.7.19
1.7.18
1.7.17
1.7.16
1.7.15
1.7.14
1.7.13
1.7.12
1.7.11
1.7.10
1.7.9
1.7.8
1.7.7
1.7.6
1.7.5
1.7.4
1.7.3
1.7.2
1.7.1
1.7.0
1.6.20
1.6.19
1.6.18
1.6.17
1.6.16
1.6.15
1.6.14
1.6.13
1.6.12
1.6.11
1.6.10
1.6.9
1.6.8
1.6.7
1.6.6
1.6.5
1.6.4
1.6.3
1.5.2
1.5.1
1.5.0
1.4.9
1.4.8
1.4.7
1.4.6
1.4.5
1.4.4
1.4.3
1.4.2
1.4.1
1.4.0
1.3.2
1.3.1
1.3.0
1.2.10
1.2.9
1.2.8
1.2.7
1.2.5
1.2.4
1.2.2
1.2.1
1.2.0
1.1.0
1.0.0
A foundation for building Elixir Phoenix apps — SaaS, social networks, ERP systems, marketplaces, and more
Current section
Files
Jump to
Current section
Files
lib/modules/sitemap/scheduler_worker.ex
defmodule PhoenixKit.Modules.Sitemap.SchedulerWorker do
@moduledoc """
Oban worker for scheduled sitemap regeneration.
This worker is responsible for:
- Periodic sitemap regeneration based on configured interval
- Automatic re-scheduling after each run
- Checking if scheduling is enabled before execution
## Configuration
Scheduling is controlled via Settings:
- `sitemap_schedule_enabled` - Enable/disable automatic regeneration
- `sitemap_schedule_interval_hours` - Interval between regenerations (default: 24)
## Usage
# Schedule initial job (called when schedule is enabled)
PhoenixKit.Modules.Sitemap.SchedulerWorker.schedule()
# Manual trigger
PhoenixKit.Modules.Sitemap.SchedulerWorker.regenerate_now()
# Cancel scheduled jobs
PhoenixKit.Modules.Sitemap.SchedulerWorker.cancel_scheduled()
## Oban Queue
Jobs are placed in the `:sitemap` queue with max 3 attempts.
"""
use Oban.Worker, queue: :sitemap, max_attempts: 3
import Ecto.Query
require Logger
alias PhoenixKit.Modules.Sitemap
alias PhoenixKit.Modules.Sitemap.FileStorage
alias PhoenixKit.Modules.Sitemap.Generator
alias PhoenixKit.PubSub.Manager, as: PubSubManager
alias PhoenixKit.Settings
@doc """
Performs sitemap regeneration.
This callback is invoked by Oban when the scheduled job runs.
It checks if scheduling is still enabled before regenerating.
"""
@impl Oban.Worker
def perform(%Oban.Job{args: args}) do
is_manual = args["manual"] == true
if is_manual do
Logger.info("SitemapSchedulerWorker: Starting manual regeneration")
else
Logger.info("SitemapSchedulerWorker: Starting scheduled regeneration")
end
cond do
# Sitemap module must be enabled for any regeneration
not Sitemap.enabled?() ->
Logger.info("SitemapSchedulerWorker: Sitemap module disabled, skipping regeneration")
:ok
# Schedule check only applies to scheduled jobs, not manual ones
not is_manual and not schedule_enabled?() ->
Logger.info("SitemapSchedulerWorker: Scheduling disabled, skipping regeneration")
:ok
not valid_base_url?() ->
Logger.warning("SitemapSchedulerWorker: Base URL not configured, skipping regeneration")
:ok
true ->
do_perform_regeneration(args)
end
end
defp valid_base_url? do
base_url = Sitemap.get_base_url()
base_url != ""
end
defp do_perform_regeneration(args) do
base_url = Sitemap.get_base_url()
case regenerate_sitemap(base_url) do
:ok ->
Logger.info("SitemapSchedulerWorker: Regeneration completed successfully")
if args["scheduled"], do: schedule_next()
:ok
{:error, reason} ->
Logger.error("SitemapSchedulerWorker: Regeneration failed: #{inspect(reason)}")
{:error, reason}
end
end
@impl Oban.Worker
def timeout(_job), do: :timer.minutes(10)
@doc """
Schedules the next sitemap regeneration.
The job is scheduled based on `sitemap_schedule_interval_hours` setting.
If scheduling is disabled, no job is created.
## Options
- `:delay_hours` - Override the configured interval (optional)
## Examples
# Schedule with configured interval
PhoenixKit.Modules.Sitemap.SchedulerWorker.schedule()
# Schedule with custom delay
PhoenixKit.Modules.Sitemap.SchedulerWorker.schedule(delay_hours: 1)
"""
@spec schedule(keyword()) :: {:ok, Oban.Job.t()} | {:error, term()} | :disabled
def schedule(opts \\ []) do
if schedule_enabled?() do
delay_hours = Keyword.get(opts, :delay_hours) || get_interval_hours()
scheduled_at = DateTime.add(DateTime.utc_now(), delay_hours * 3600, :second)
%{scheduled: true}
|> new(scheduled_at: scheduled_at)
|> insert_job()
else
Logger.info("SitemapSchedulerWorker: Scheduling disabled, job not created")
:disabled
end
end
@doc """
Schedules the next regeneration after current job completes.
"""
@spec schedule_next() :: {:ok, Oban.Job.t()} | {:error, term()} | :disabled
def schedule_next do
schedule()
end
@doc """
Triggers immediate sitemap regeneration.
This creates a job that runs immediately, bypassing the schedule.
"""
@spec regenerate_now() :: {:ok, Oban.Job.t()} | {:error, term()}
def regenerate_now do
Logger.info("SitemapSchedulerWorker: Manual regeneration triggered")
%{scheduled: false, manual: true}
|> new()
|> insert_job()
end
@doc """
Ensures sitemap file exists on application startup.
With file-only architecture, this is optional - sitemap will be generated
on first request if file doesn't exist. Use this for explicit pre-warming.
## Returns
- `{:ok, job}` - Regeneration job scheduled (file doesn't exist)
- `:file_exists` - Sitemap file already exists
- `:disabled` - Sitemap module is disabled
## Examples
# In Application.start/2 or supervisor child
PhoenixKit.Modules.Sitemap.SchedulerWorker.ensure_cache_warm()
"""
@spec ensure_cache_warm() ::
{:ok, Oban.Job.t()} | :file_exists | :disabled | {:error, term()}
def ensure_cache_warm do
cond do
not Sitemap.enabled?() ->
Logger.debug("SitemapSchedulerWorker: Sitemap disabled, skipping warm-up")
:disabled
FileStorage.exists?() ->
Logger.debug("SitemapSchedulerWorker: Sitemap file already exists")
:file_exists
true ->
Logger.info("SitemapSchedulerWorker: No sitemap file, triggering generation")
regenerate_now()
end
end
@doc """
Cancels all scheduled sitemap jobs.
This is called when scheduling is disabled.
"""
@spec cancel_scheduled() :: {:ok, non_neg_integer()}
def cancel_scheduled do
# Cancel all pending/scheduled jobs for this worker
worker_name = inspect(__MODULE__)
{count, _} =
Oban.Job
|> where([j], j.worker == ^worker_name)
|> where([j], j.state in ["available", "scheduled"])
|> get_repo().delete_all()
Logger.info("SitemapSchedulerWorker: Cancelled #{count} scheduled jobs")
{:ok, count}
end
@doc """
Returns the current scheduling status and next run time.
"""
@spec status() :: map()
def status do
worker_name = inspect(__MODULE__)
next_job =
Oban.Job
|> where([j], j.worker == ^worker_name)
|> where([j], j.state in ["available", "scheduled"])
|> order_by([j], asc: j.scheduled_at)
|> limit(1)
|> get_repo().one()
%{
enabled: schedule_enabled?(),
interval_hours: get_interval_hours(),
next_run: if(next_job, do: next_job.scheduled_at, else: nil),
pending_jobs: count_pending_jobs()
}
end
# Private functions
defp regenerate_sitemap(base_url) do
# Collect entries first to get URL count
entries = Generator.collect_all_entries(base_url: base_url)
url_count = length(entries)
# Generate XML sitemap and save to file
# File-only architecture: single file at priv/static/sitemap.xml
case Generator.generate_xml(base_url: base_url, cache: true, xsl_style: "table") do
{:ok, _xml} ->
# Update generation stats with actual URL count
Sitemap.update_generation_stats(%{url_count: url_count})
# Broadcast completion for real-time UI updates
broadcast_sitemap_generated(url_count)
:ok
{:ok, _xml, _parts} ->
# Sitemap index was generated
Sitemap.update_generation_stats(%{url_count: url_count})
broadcast_sitemap_generated(url_count)
:ok
{:error, reason} ->
{:error, reason}
end
end
# Broadcast sitemap generation completion for real-time UI updates
defp broadcast_sitemap_generated(url_count) do
PubSubManager.broadcast(
"sitemap:updates",
{:sitemap_generated, %{url_count: url_count, timestamp: DateTime.utc_now()}}
)
rescue
# PubSub may not be available in all environments
_ -> :ok
end
defp schedule_enabled? do
Settings.get_boolean_setting("sitemap_schedule_enabled", false)
end
defp get_interval_hours do
case Settings.get_setting("sitemap_schedule_interval_hours", "24") do
value when is_binary(value) ->
case Integer.parse(value) do
{hours, _} when hours > 0 -> hours
_ -> 24
end
value when is_integer(value) and value > 0 ->
value
_ ->
24
end
end
defp insert_job(changeset) do
case get_repo().insert(changeset) do
{:ok, job} ->
Logger.info("SitemapSchedulerWorker: Job scheduled for #{job.scheduled_at}")
{:ok, job}
{:error, reason} ->
Logger.error("SitemapSchedulerWorker: Failed to schedule job: #{inspect(reason)}")
{:error, reason}
end
end
defp count_pending_jobs do
worker_name = inspect(__MODULE__)
Oban.Job
|> where([j], j.worker == ^worker_name)
|> where([j], j.state in ["available", "scheduled"])
|> get_repo().aggregate(:count)
end
defp get_repo do
PhoenixKit.RepoHelper.repo()
end
end