Packages
phoenix_kit
1.7.79
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/publishing/shared.ex
defmodule PhoenixKit.Modules.Publishing.Shared do
@moduledoc """
Shared helper functions used across publishing submodules.
These functions were extracted to eliminate duplication between
Posts, Versions, TranslationManager, and Groups.
"""
require Logger
alias PhoenixKit.Modules.Publishing.DBStorage
alias PhoenixKit.Users.Auth.Scope
# ============================================================================
# UUID Validation
# ============================================================================
@uuid_regex ~r/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i
@doc false
def uuid_format?(str) when is_binary(str), do: Regex.match?(@uuid_regex, str)
def uuid_format?(_), do: false
# ============================================================================
# Option / Audit Helpers
# ============================================================================
@doc false
def fetch_option(opts, key) when is_map(opts) do
Map.get(opts, key) || Map.get(opts, Atom.to_string(key))
end
def fetch_option(opts, key) when is_list(opts) do
Keyword.get(opts, key) ||
(is_atom(key) && Keyword.get(opts, key)) ||
nil
end
def fetch_option(_, _), do: nil
@doc false
def audit_metadata(nil, _action), do: %{}
def audit_metadata(scope, action) do
user_uuid =
scope
|> Scope.user_uuid()
|> normalize_audit_value()
user_email =
scope
|> Scope.user_email()
|> normalize_audit_value()
base =
case action do
:create ->
%{
created_by_uuid: user_uuid,
created_by_email: user_email
}
_ ->
%{}
end
base
|> maybe_put_audit(:updated_by_uuid, user_uuid)
|> maybe_put_audit(:updated_by_email, user_email)
end
@dialyzer {:nowarn_function, normalize_audit_value: 1}
defp normalize_audit_value(nil), do: nil
defp normalize_audit_value(value) when is_binary(value), do: String.trim(value)
defp normalize_audit_value(value), do: to_string(value)
defp maybe_put_audit(map, _key, nil), do: map
defp maybe_put_audit(map, key, value), do: Map.put(map, key, value)
# ============================================================================
# Scope Resolution
# ============================================================================
@doc false
def resolve_scope_user_uuids(nil), do: nil
def resolve_scope_user_uuids(scope) do
scope
|> Scope.user_uuid()
|> normalize_audit_value()
end
# ============================================================================
# Post Reading (shared by Posts, Versions, TranslationManager)
# ============================================================================
@doc """
Reads a post back from DB using the appropriate method for the post's mode.
Used after create/update operations to return a consistent post map.
"""
def read_back_post(group_slug, identifier, db_post, language, version_number) do
case resolve_read_strategy(db_post, identifier) do
{:timestamp, date, time} ->
DBStorage.read_post_by_datetime(group_slug, date, time, language, version_number)
{:slug, slug} ->
DBStorage.read_post(group_slug, slug, language, version_number)
end
end
defp resolve_read_strategy(db_post, _identifier)
when not is_nil(db_post) and db_post.mode == "timestamp" and
not is_nil(db_post.post_date) and not is_nil(db_post.post_time) do
{:timestamp, db_post.post_date, db_post.post_time}
end
defp resolve_read_strategy(db_post, _identifier)
when not is_nil(db_post) and not is_nil(db_post.slug) do
{:slug, db_post.slug}
end
defp resolve_read_strategy(db_post, identifier) when is_binary(identifier) do
case parse_timestamp_path(identifier) do
{:ok, date, time, _version, _lang} ->
{:timestamp, date, time}
_ ->
{:slug, (db_post && db_post.slug) || identifier}
end
end
defp resolve_read_strategy(db_post, identifier) do
{:slug, (db_post && db_post.slug) || identifier}
end
# ============================================================================
# Timestamp Path Parsing
# ============================================================================
@doc false
def parse_timestamp_path(identifier) do
parts =
identifier
|> to_string()
|> String.trim_leading("/")
|> String.split("/", trim: true)
case parts do
[date_str] ->
case Date.from_iso8601(date_str) do
{:ok, date} -> {:ok, date, nil, nil, nil}
_ -> nil
end
[date_str, time_str | rest] ->
with {:ok, date} <- Date.from_iso8601(date_str),
{:ok, time} <- parse_time(time_str) do
{version, rest_after_version} = extract_version_from_parts(rest)
lang =
rest_after_version
|> List.first()
|> case do
nil -> nil
<<>> -> nil
lang_code -> lang_code
end
{:ok, date, time, version, lang}
else
_ -> nil
end
_ ->
nil
end
end
@doc false
def parse_time(time_str) when is_binary(time_str) do
case String.split(time_str, ":") do
[h, m] ->
with {hour, ""} <- Integer.parse(h),
{minute, ""} <- Integer.parse(m) do
Time.new(hour, minute, 0)
else
_ -> :error
end
_ ->
:error
end
end
def parse_time(_), do: :error
@doc false
def extract_version_from_parts([]), do: {nil, []}
def extract_version_from_parts([first | rest] = parts) do
case parse_version_segment(first) do
{:ok, version} -> {version, rest}
:error -> {nil, parts}
end
end
@doc false
def parse_version_segment(segment) when is_binary(segment) do
case Regex.run(~r/^v(\d+)$/, segment) do
[_, num_str] -> {:ok, String.to_integer(num_str)}
nil -> :error
end
end
def parse_version_segment(_), do: :error
# ============================================================================
# Version Resolution
# ============================================================================
@doc false
def resolve_db_version(db_post, nil), do: DBStorage.get_latest_version(db_post.uuid)
def resolve_db_version(db_post, version_number),
do: DBStorage.get_version(db_post.uuid, version_number)
end