Packages
phoenix_kit
1.7.70
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/publishing/web/settings.ex
defmodule PhoenixKit.Modules.Publishing.Web.Settings do
@moduledoc """
Admin configuration for publishing groups.
"""
use PhoenixKitWeb, :live_view
use Gettext, backend: PhoenixKitWeb.Gettext
alias PhoenixKit.Modules.Languages
alias PhoenixKit.Modules.Publishing
alias PhoenixKit.Modules.Publishing.DBStorage
alias PhoenixKit.Modules.Publishing.ListingCache
alias PhoenixKit.Modules.Publishing.PubSub, as: PublishingPubSub
alias PhoenixKit.Modules.Publishing.Renderer
alias PhoenixKit.Settings
alias PhoenixKit.Utils.Routes
# Settings keys
@memory_cache_key "publishing_memory_cache_enabled"
@render_cache_key "publishing_render_cache_enabled"
# Legacy settings keys (read from these as fallback)
@legacy_memory_cache_key "blogging_memory_cache_enabled"
@legacy_render_cache_key "blogging_render_cache_enabled"
def mount(_params, _session, socket) do
# Subscribe to group changes for live updates
if connected?(socket) do
PublishingPubSub.subscribe_to_groups()
end
# Admin side reads from database only
groups = db_groups_to_maps()
cache_groups = groups
languages_enabled = Languages.enabled?()
socket =
socket
|> assign(:project_title, Settings.get_project_title())
|> assign(:page_title, gettext("Manage Publishing"))
|> assign(
:current_path,
Routes.path("/admin/settings/publishing")
)
|> assign(:module_enabled, Publishing.enabled?())
|> assign(:publishing, groups)
|> assign(:cache_groups, cache_groups)
|> assign(:languages_enabled, languages_enabled)
|> assign(:global_primary_language, Publishing.get_primary_language())
|> assign(
:memory_cache_enabled,
get_cache_setting(@memory_cache_key, @legacy_memory_cache_key)
)
|> assign(
:render_cache_enabled,
get_cache_setting(@render_cache_key, @legacy_render_cache_key)
)
|> assign(:cache_status, build_cache_status(cache_groups))
|> assign(:render_cache_stats, get_render_cache_stats())
|> assign(:render_cache_per_group, build_render_cache_per_group(cache_groups))
{:ok, socket}
end
def handle_params(_params, _uri, socket), do: {:noreply, socket}
def handle_event("remove_group", %{"slug" => slug}, socket) do
case Publishing.trash_group(slug) do
{:ok, trashed_name} ->
# The `Publishing.trash_group` call triggers `remove_group`, which handles
# the broadcast. This LiveView will catch the event and update its state.
{:noreply,
put_flash(
socket,
:info,
gettext("Group moved to trash as: %{name}", name: trashed_name)
)}
{:error, :not_found} ->
# Group not found in DB, just remove from config
case Publishing.remove_group(slug) do
{:ok, _} ->
# The `Publishing.remove_group` call handles the broadcast. This
# LiveView will catch the event and update its state.
{:noreply, put_flash(socket, :info, gettext("Group removed from configuration"))}
{:error, _reason} ->
{:noreply, put_flash(socket, :error, gettext("Failed to remove group"))}
end
{:error, _reason} ->
{:noreply, put_flash(socket, :error, gettext("Failed to move group to trash"))}
end
end
def handle_event("regenerate_cache", %{"slug" => slug}, socket) do
case ListingCache.regenerate(slug) do
:ok ->
{:noreply,
socket
|> assign(:cache_status, build_cache_status(socket.assigns.cache_groups))
|> put_flash(:info, gettext("Cache regenerated for %{group}", group: slug))}
{:error, _reason} ->
{:noreply, put_flash(socket, :error, gettext("Failed to regenerate cache"))}
end
end
def handle_event("invalidate_cache", %{"slug" => slug}, socket) do
ListingCache.invalidate(slug)
{:noreply,
socket
|> assign(:cache_status, build_cache_status(socket.assigns.cache_groups))
|> put_flash(:info, gettext("Cache cleared for %{group}", group: slug))}
end
def handle_event("migrate_primary_language", %{"slug" => slug}, socket) do
primary_lang = socket.assigns.global_primary_language
{:ok, count} = DBStorage.migrate_primary_language(slug, primary_lang)
{:noreply,
socket
|> refresh_groups()
|> put_flash(
:info,
gettext("Updated %{count} posts to use primary language: %{lang}",
count: count,
lang: primary_lang
)
)}
end
def handle_event("regenerate_all_caches", _params, socket) do
results =
Enum.map(socket.assigns.cache_groups, fn group ->
{group["slug"], ListingCache.regenerate(group["slug"])}
end)
success_count = Enum.count(results, fn {_, result} -> result == :ok end)
{:noreply,
socket
|> assign(:cache_status, build_cache_status(socket.assigns.cache_groups))
|> put_flash(:info, gettext("Regenerated %{count} caches", count: success_count))}
end
def handle_event("toggle_memory_cache", _params, socket) do
new_value = !socket.assigns.memory_cache_enabled
Settings.update_setting(@memory_cache_key, to_string(new_value))
# If disabling memory cache, clear all :persistent_term entries
if !new_value do
Enum.each(socket.assigns.cache_groups, fn group ->
try do
:persistent_term.erase(ListingCache.persistent_term_key(group["slug"]))
rescue
ArgumentError -> :ok
end
end)
end
{:noreply,
socket
|> assign(:memory_cache_enabled, new_value)
|> assign(:cache_status, build_cache_status(socket.assigns.cache_groups))
|> put_flash(:info, cache_toggle_message("Memory cache", new_value))}
end
def handle_event("clear_render_cache", _params, socket) do
Renderer.clear_all_cache()
{:noreply,
socket
|> assign(:render_cache_stats, get_render_cache_stats())
|> put_flash(:info, gettext("Render cache cleared"))}
end
def handle_event("clear_group_render_cache", %{"slug" => slug}, socket) do
case Renderer.clear_group_cache(slug) do
{:ok, count} ->
{:noreply,
socket
|> assign(:render_cache_stats, get_render_cache_stats())
|> put_flash(
:info,
gettext("Cleared %{count} cached posts for %{group}", count: count, group: slug)
)}
{:error, _} ->
{:noreply, put_flash(socket, :error, gettext("Failed to clear cache"))}
end
end
def handle_event("toggle_render_cache", _params, socket) do
new_value = !socket.assigns.render_cache_enabled
Settings.update_setting(@render_cache_key, to_string(new_value))
{:noreply,
socket
|> assign(:render_cache_enabled, new_value)
|> put_flash(:info, cache_toggle_message("Render cache", new_value))}
end
def handle_event("toggle_group_render_cache", %{"slug" => slug}, socket) do
# Use Renderer helper to get the new key for writes
per_group_key = Renderer.per_group_cache_key(slug)
current_value = Renderer.group_render_cache_enabled?(slug)
new_value = !current_value
Settings.update_setting(per_group_key, to_string(new_value))
{:noreply,
socket
|> assign(:render_cache_per_group, build_render_cache_per_group(socket.assigns.cache_groups))
|> put_flash(:info, cache_toggle_message("Render cache for #{slug}", new_value))}
end
# ============================================================================
# PubSub Handlers - Live updates when groups change elsewhere
# ============================================================================
def handle_info({:group_created, _group}, socket) do
{:noreply, refresh_groups(socket)}
end
def handle_info({:group_deleted, _slug}, socket) do
{:noreply, refresh_groups(socket)}
end
def handle_info({:group_updated, _group}, socket) do
{:noreply, refresh_groups(socket)}
end
defp refresh_groups(socket) do
groups = db_groups_to_maps()
socket
|> assign(:publishing, groups)
|> assign(:cache_groups, groups)
|> assign(:cache_status, build_cache_status(groups))
|> assign(:render_cache_per_group, build_render_cache_per_group(groups))
end
defp db_groups_to_maps do
global_primary = Publishing.get_primary_language()
DBStorage.list_groups()
|> Enum.map(fn g ->
primary_lang_status = DBStorage.count_primary_language_status(g.slug, global_primary)
needs_lang_migration =
primary_lang_status.needs_backfill + primary_lang_status.needs_migration > 0
%{
"name" => g.name,
"slug" => g.slug,
"mode" => g.mode,
"position" => g.position,
"needs_primary_lang_migration" => needs_lang_migration,
"primary_language_status" => primary_lang_status
}
end)
end
# Helper for dual-key cache setting reads
defp get_cache_setting(new_key, legacy_key) do
case Settings.get_setting(new_key, nil) do
nil -> Settings.get_setting(legacy_key, "true") == "true"
value -> value == "true"
end
end
defp cache_toggle_message(cache_type, enabled) do
if enabled do
gettext("%{type} enabled", type: cache_type)
else
gettext("%{type} disabled", type: cache_type)
end
end
# Build cache status for all groups
defp build_cache_status(groups) do
Map.new(groups, fn group ->
slug = group["slug"]
{slug, get_cache_info(slug)}
end)
end
defp get_cache_info(group_slug) do
get_cache_info_db(group_slug)
end
defp get_cache_info_db(group_slug) do
in_memory =
case :persistent_term.get(ListingCache.persistent_term_key(group_slug), :not_found) do
:not_found -> false
_ -> true
end
post_count =
case :persistent_term.get(ListingCache.persistent_term_key(group_slug), :not_found) do
:not_found -> length(DBStorage.list_posts(group_slug))
posts -> length(posts)
end
%{
exists: in_memory,
file_size: 0,
modified_at: nil,
post_count: post_count,
in_memory: in_memory
}
end
defp get_render_cache_stats do
PhoenixKit.Cache.stats(:publishing_posts)
rescue
_ -> %{hits: 0, misses: 0, puts: 0, invalidations: 0, hit_rate: 0.0}
end
defp build_render_cache_per_group(groups) do
Map.new(groups, fn group ->
slug = group["slug"]
{slug, Renderer.group_render_cache_enabled?(slug)}
end)
end
end