Packages
phoenix_kit
1.7.93
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/llm_text/publishing_subscriber.ex
defmodule PhoenixKit.Modules.LLMText.PublishingSubscriber do
@moduledoc """
GenServer that subscribes to Publishing PubSub events and triggers
incremental LLM text file regeneration.
## Events handled
- `{:post_status_changed, post}` — if published: enqueue file job; otherwise delete file
- `{:post_updated, post}` — if published: enqueue file job; otherwise skip
- `{:post_deleted, post_identifier}` — delete file + enqueue source rebuild
- `{:group_created, group}` — subscribe to new group's posts topic
- `{:group_deleted, group_slug}` — enqueue source rebuild
## Topics
Subscribes to:
- `"publishing:groups"` — group lifecycle events
- `"publishing:{group_slug}:posts"` — per-group post events for each existing group
All Publishing calls are guarded with `Code.ensure_loaded?`.
"""
use GenServer
require Logger
alias PhoenixKit.Modules.LLMText.FileStorage
alias PhoenixKit.Modules.LLMText.Sources.Publishing, as: PublishingSource
alias PhoenixKit.Modules.LLMText.Workers.GenerateLLMTextJob
alias PhoenixKit.PubSub.Manager, as: PubSubManager
@compile {:no_warn_undefined,
[
{PhoenixKit.Modules.Publishing, :enabled?, 0},
{PhoenixKit.Modules.Publishing, :list_groups, 0},
{PhoenixKit.Modules.Publishing.PubSub, :groups_topic, 0},
{PhoenixKit.Modules.Publishing.PubSub, :posts_topic, 1}
]}
@publishing_mod PhoenixKit.Modules.Publishing
@publishing_pubsub PhoenixKit.Modules.Publishing.PubSub
def start_link(opts \\ []) do
GenServer.start_link(__MODULE__, opts, name: __MODULE__)
end
@impl GenServer
def init(_opts) do
subscribe_to_groups()
subscribe_to_existing_groups()
{:ok, %{}}
end
@impl GenServer
def handle_info({:post_status_changed, post}, state) do
handle_post_event(post)
{:noreply, state}
end
def handle_info({:post_updated, post}, state) do
if published?(post) do
handle_post_event(post)
end
{:noreply, state}
end
def handle_info({:post_deleted, post_identifier}, state) do
handle_post_removed(post_identifier)
{:noreply, state}
end
def handle_info({:group_created, group}, state) do
subscribe_to_group(group["slug"])
{:noreply, state}
end
def handle_info({:group_deleted, group_slug}, state) do
enqueue_source_rebuild(group_slug)
{:noreply, state}
end
def handle_info(_msg, state) do
{:noreply, state}
end
# Private helpers
defp handle_post_event(post) do
group_slug = Map.get(post, :group, "")
post_slug = extract_post_slug(post)
if published?(post) do
changeset =
GenerateLLMTextJob.enqueue_for_file(:publishing, "#{group_slug}/#{post_slug}.txt")
insert_job(changeset)
else
path = PublishingSource.build_file_path(group_slug, post_slug)
FileStorage.delete(path)
enqueue_source_rebuild(group_slug)
end
rescue
error ->
Logger.warning("LLMText PublishingSubscriber: handle_post_event failed: #{inspect(error)}")
end
defp handle_post_removed(post_identifier) do
# post_identifier may be a string slug or map
{group_slug, post_slug} =
case post_identifier do
%{group: g, slug: s} -> {to_string(g), to_string(s)}
%{"group" => g, "slug" => s} -> {to_string(g), to_string(s)}
_ -> {"unknown", to_string(post_identifier)}
end
path = PublishingSource.build_file_path(group_slug, post_slug)
FileStorage.delete(path)
enqueue_source_rebuild(group_slug)
rescue
error ->
Logger.warning(
"LLMText PublishingSubscriber: handle_post_removed failed: #{inspect(error)}"
)
end
defp enqueue_source_rebuild(_group_slug) do
changeset = GenerateLLMTextJob.enqueue_for_source(:publishing)
insert_job(changeset)
end
defp insert_job(changeset) do
Oban.insert(changeset)
rescue
error ->
Logger.warning("LLMText PublishingSubscriber: failed to insert job: #{inspect(error)}")
{:error, error}
end
defp subscribe_to_groups do
if publishing_pubsub_available?() do
topic = @publishing_pubsub.groups_topic()
PubSubManager.subscribe(topic)
end
rescue
_ -> :ok
end
defp subscribe_to_existing_groups do
if publishing_available?() do
groups = @publishing_mod.list_groups()
Enum.each(groups, fn group ->
subscribe_to_group(group["slug"])
end)
end
rescue
_ -> :ok
end
defp subscribe_to_group(group_slug) when is_binary(group_slug) do
if publishing_pubsub_available?() do
topic = @publishing_pubsub.posts_topic(group_slug)
PubSubManager.subscribe(topic)
end
rescue
_ -> :ok
end
defp publishing_available? do
Code.ensure_loaded?(@publishing_mod) and
function_exported?(@publishing_mod, :list_groups, 0)
end
defp publishing_pubsub_available? do
Code.ensure_loaded?(@publishing_pubsub) and
function_exported?(@publishing_pubsub, :groups_topic, 0)
end
defp published?(post) do
case post do
%{metadata: %{status: "published"}} -> true
%{metadata: %{"status" => "published"}} -> true
_ -> false
end
end
defp extract_post_slug(post) do
case Map.get(post, :mode) do
:timestamp ->
date = Map.get(post, :date)
time = Map.get(post, :time)
if date && time do
time_str = time |> Time.to_string() |> String.slice(0..4) |> String.replace(":", "-")
"#{Date.to_iso8601(date)}-#{time_str}"
else
Map.get(post, :slug, "post") || "post"
end
_ ->
Map.get(post, :url_slug) || Map.get(post, :slug, "post") || "post"
end
end
end