Packages

phoenix_kit

1.7.71
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
phoenix_kit lib modules pages storage deletion.ex
Raw

lib/modules/pages/storage/deletion.ex

defmodule PhoenixKit.Modules.Pages.Storage.Deletion do
@moduledoc """
Deletion and trash operations for pages storage.
Handles moving posts to trash, deleting language files,
and deleting versions.
"""
alias PhoenixKit.Modules.Pages.Storage.Paths
alias PhoenixKit.Modules.Pages.Storage.Versions
alias PhoenixKit.Utils.Date, as: UtilsDate
@doc """
Moves a post to the trash folder.
For slug-mode groups, moves the entire post directory (all versions and languages).
For timestamp-mode groups, moves the time folder.
The post directory is moved to:
priv/pages/trash/<group_slug>/<post_identifier>-<timestamp>/
(or priv/blogging/trash/... for legacy groups)
Returns {:ok, trash_path} on success or {:error, reason} on failure.
"""
@spec trash_post(String.t(), String.t()) :: {:ok, String.t()} | {:error, term()}
def trash_post(group_slug, post_identifier) do
post_dir = resolve_post_directory(group_slug, post_identifier)
if File.dir?(post_dir) do
trash_dir = Path.join([Paths.root_path(), "trash", group_slug])
File.mkdir_p!(trash_dir)
timestamp =
UtilsDate.utc_now()
|> Calendar.strftime("%Y-%m-%d-%H-%M-%S")
sanitized_id = sanitize_for_trash(post_identifier)
new_name = "#{sanitized_id}-#{timestamp}"
destination = Path.join(trash_dir, new_name)
case File.rename(post_dir, destination) do
:ok ->
Paths.cleanup_empty_dirs(Path.dirname(post_dir))
{:ok, "trash/#{group_slug}/#{new_name}"}
{:error, reason} ->
{:error, reason}
end
else
{:error, :not_found}
end
end
@doc """
Moves a language file to trash (legacy operation, now just deletes).
"""
@spec trash_language(String.t(), String.t()) :: {:ok, String.t()} | {:error, term()}
def trash_language(group_slug, relative_path) do
full_path = Paths.absolute_path(relative_path)
if File.exists?(full_path) do
trash_dir = Path.join([Paths.root_path(), "trash", group_slug])
File.mkdir_p!(trash_dir)
timestamp =
UtilsDate.utc_now()
|> Calendar.strftime("%Y-%m-%d-%H-%M-%S")
new_name = "#{Path.basename(relative_path)}-#{timestamp}"
destination = Path.join(trash_dir, new_name)
case File.rename(full_path, destination) do
:ok -> {:ok, "trash/#{new_name}"}
{:error, reason} -> {:error, reason}
end
else
{:error, :not_found}
end
end
@doc """
Deletes a specific language file from a post.
For versioned posts, specify the version. For legacy posts, version is ignored.
Refuses to delete the last remaining language file.
Returns :ok on success or {:error, reason} on failure.
"""
@spec delete_language(String.t(), String.t(), String.t(), integer() | nil) ::
:ok | {:error, term()}
def delete_language(group_slug, post_identifier, language_code, version \\ nil) do
post_dir = resolve_post_directory(group_slug, post_identifier)
if File.dir?(post_dir) do
structure = Versions.detect_post_structure(post_dir)
do_delete_language(post_dir, structure, language_code, version, group_slug, post_identifier)
else
{:error, :post_not_found}
end
end
defp do_delete_language(
post_dir,
:versioned,
language_code,
version,
group_slug,
post_identifier
) do
target_version = version || get_latest_version_number(group_slug, post_identifier)
case target_version do
nil ->
{:error, :version_not_found}
v ->
version_dir = Path.join(post_dir, "v#{v}")
delete_language_from_directory(version_dir, language_code)
end
end
defp do_delete_language(post_dir, :legacy, language_code, _version, _group_slug, _post_uuid) do
delete_language_from_directory(post_dir, language_code)
end
defp do_delete_language(_post_dir, :empty, _language_code, _version, _group_slug, _post_uuid) do
{:error, :post_not_found}
end
defp delete_language_from_directory(dir, language_code) do
file_path = Path.join(dir, "#{language_code}.phk")
cond do
not File.exists?(file_path) ->
{:error, :language_not_found}
last_language_file?(dir) ->
{:error, :cannot_delete_last_language}
true ->
File.rm(file_path)
end
end
defp last_language_file?(dir) do
case File.ls(dir) do
{:ok, files} ->
phk_count = Enum.count(files, &String.ends_with?(&1, ".phk"))
phk_count <= 1
{:error, _} ->
true
end
end
defp get_latest_version_number(group_slug, post_identifier) do
case Versions.get_latest_version(group_slug, post_identifier) do
{:ok, v} -> v
_ -> nil
end
end
@doc """
Deletes an entire version of a post.
Moves the version folder to trash instead of permanent deletion.
Refuses to delete the last remaining version or the published version.
Returns :ok on success or {:error, reason} on failure.
"""
@spec delete_version(String.t(), String.t(), integer()) :: :ok | {:error, term()}
def delete_version(group_slug, post_identifier, version) do
post_dir = resolve_post_directory(group_slug, post_identifier)
if File.dir?(post_dir) do
structure = Versions.detect_post_structure(post_dir)
case structure do
:versioned ->
do_delete_version(post_dir, group_slug, post_identifier, version)
:legacy ->
{:error, :not_versioned}
:empty ->
{:error, :post_not_found}
end
else
{:error, :post_not_found}
end
end
defp do_delete_version(post_dir, group_slug, post_identifier, version) do
version_dir = Path.join(post_dir, "v#{version}")
cond do
not File.dir?(version_dir) ->
{:error, :version_not_found}
Versions.version_is_published?(group_slug, post_identifier, version) ->
{:error, :cannot_delete_published_version}
Versions.only_version?(post_dir) ->
{:error, :cannot_delete_last_version}
true ->
trash_dir = Path.join([Paths.root_path(), "trash", group_slug, post_identifier])
File.mkdir_p!(trash_dir)
timestamp =
UtilsDate.utc_now()
|> Calendar.strftime("%Y-%m-%d-%H-%M-%S")
destination = Path.join(trash_dir, "v#{version}-#{timestamp}")
case File.rename(version_dir, destination) do
:ok -> :ok
{:error, reason} -> {:error, reason}
end
end
end
# ============================================================================
# Private Helpers
# ============================================================================
# Resolve the post directory path based on the identifier format
defp resolve_post_directory(group_slug, post_identifier) do
if String.contains?(post_identifier, "/") do
parts = String.split(post_identifier, "/", trim: true)
case parts do
[date, time | _] when byte_size(date) == 10 and byte_size(time) >= 4 ->
Path.join([Paths.group_path(group_slug), date, time])
[slug | _] ->
Path.join([Paths.group_path(group_slug), slug])
end
else
Path.join([Paths.group_path(group_slug), post_identifier])
end
end
# Sanitize identifier for use in trash folder name
defp sanitize_for_trash(identifier) do
identifier
|> String.replace("/", "_")
|> String.replace(":", "-")
end
end