Packages
phoenix_kit
1.7.21
1.7.211
1.7.210
1.7.209
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/phoenix_kit_web/live/users/media_detail.ex
defmodule PhoenixKitWeb.Live.Users.MediaDetail do
@moduledoc """
Single media file detail view for PhoenixKit admin panel.
Provides a shareable view for a specific uploaded media file by file_id.
"""
use PhoenixKitWeb, :live_view
use Gettext, backend: PhoenixKitWeb.Gettext
require Logger
import Ecto.Query
alias PhoenixKit.Modules.Storage
alias PhoenixKit.Modules.Storage.File
alias PhoenixKit.Modules.Storage.FileInstance
alias PhoenixKit.Modules.Storage.FileLocation
alias PhoenixKit.Modules.Storage.URLSigner
alias PhoenixKit.Modules.Storage.VariantGenerator
alias PhoenixKit.Settings
alias PhoenixKit.Utils.Date, as: UtilsDate
alias PhoenixKit.Utils.Routes
def mount(params, _session, socket) do
# Set locale for LiveView process
locale =
params["locale"] || socket.assigns[:current_locale]
# Get file_id from params
file_id = params["file_id"]
# Batch load all settings needed for this page
settings =
Settings.get_settings_cached(
["project_title"],
%{"project_title" => "PhoenixKit"}
)
socket =
socket
|> assign(:page_title, "Media Detail")
|> assign(:project_title, settings["project_title"])
|> assign(:current_locale, locale)
|> assign(:file_id, file_id)
|> load_file_data(file_id)
{:ok, socket}
end
def handle_event("toggle_edit", _params, socket) do
{:noreply, assign(socket, :edit_mode, !socket.assigns.edit_mode)}
end
def handle_event("save_metadata", params, socket) do
%{"title" => title, "description" => description, "tags" => tags_input} = params
# Parse tags from comma-separated string
tags =
tags_input
|> String.split(",")
|> Enum.map(&String.trim/1)
|> Enum.filter(&(String.length(&1) > 0))
# Update metadata
updated_metadata =
(socket.assigns.file.metadata || %{})
|> Map.put("title", title)
|> Map.put("description", description)
|> Map.put("tags", tags)
case Storage.update_file(socket.assigns.file, %{metadata: updated_metadata}) do
{:ok, updated_file} ->
# Update file_data with new metadata
updated_file_data =
socket.assigns.file_data
|> Map.put(:title, title)
|> Map.put(:description, description)
|> Map.put(:tags, tags)
|> Map.put(:metadata, updated_metadata)
socket =
socket
|> assign(:file, updated_file)
|> assign(:file_data, updated_file_data)
|> assign(:edit_mode, false)
|> put_flash(:info, "Metadata saved successfully!")
{:noreply, socket}
{:error, _changeset} ->
socket = put_flash(socket, :error, "Failed to save metadata")
{:noreply, socket}
end
end
def handle_event("cancel_edit", _params, socket) do
{:noreply, assign(socket, :edit_mode, false)}
end
def handle_event("regenerate_variants", _params, socket) do
file = socket.assigns.file
case VariantGenerator.generate_variants(file, async: false) do
{:ok, instances} ->
# instances is a list of successfully created FileInstance structs
count = length(instances)
socket =
socket
|> load_file_data(file.id)
|> put_flash(:info, "Regenerated #{count} variants")
{:noreply, socket}
{:error, reason} ->
socket = put_flash(socket, :error, "Failed to regenerate: #{inspect(reason)}")
{:noreply, socket}
end
end
defp load_file_data(socket, file_id) do
repo = PhoenixKit.Config.get_repo()
case repo.get(File, file_id) do
nil ->
socket
|> assign(:file, nil)
|> assign(:file_data, nil)
file ->
instances = load_file_instances(file_id, repo)
urls = generate_urls_from_instances(instances, file_id)
locations = load_original_locations(instances, repo)
{title, description, tags} = extract_metadata_fields(file.metadata)
user_name = get_user_name(file.user_id, repo)
file_data =
build_file_data(file, urls, locations, {title, description, tags}, user_name)
socket
|> assign(:file, file)
|> assign(:file_data, file_data)
|> assign(:edit_mode, false)
end
end
defp load_file_instances(file_id, repo) do
FileInstance
|> where([fi], fi.file_id == ^file_id)
|> repo.all()
end
defp load_original_locations(instances, repo) do
case Enum.find(instances, &(&1.variant_name == "original")) do
nil -> []
original_instance -> load_file_locations(original_instance.id, repo)
end
end
defp extract_metadata_fields(metadata) do
metadata = metadata || %{}
title = metadata["title"] || ""
description = metadata["description"] || ""
tags = metadata["tags"] || []
{title, description, tags}
end
defp get_user_name(nil, _repo), do: "Unknown"
defp get_user_name(user_id, repo) do
alias_module = PhoenixKit.Config.get_users_module()
case repo.get(alias_module, user_id) do
nil -> "Unknown"
user -> user.email
end
end
defp build_file_data(file, urls, locations, {title, description, tags}, user_name) do
%{
file_id: file.id,
filename: file.original_file_name || file.file_name || "Unknown",
original_filename: file.original_file_name,
file_type: file.file_type,
mime_type: file.mime_type,
size: file.size || 0,
status: file.status,
urls: urls,
title: title,
description: description,
tags: tags,
metadata: file.metadata || %{},
inserted_at: file.inserted_at,
updated_at: file.updated_at,
locations: locations,
user_name: user_name
}
end
# Generate URLs from pre-loaded instances (no database query needed)
defp generate_urls_from_instances(instances, file_id) do
Enum.reduce(instances, %{}, fn instance, acc ->
url = URLSigner.signed_url(file_id, instance.variant_name)
Map.put(acc, instance.variant_name, url)
end)
end
# Load file locations with bucket information
defp load_file_locations(file_instance_id, repo) do
FileLocation
|> where([fl], fl.file_instance_id == ^file_instance_id and fl.status == "active")
|> preload(:bucket)
|> repo.all()
|> Enum.map(fn location ->
%{
bucket_name: location.bucket.name,
bucket_provider: location.bucket.provider,
path: location.path
}
end)
end
# Format file size in human-readable format
defp format_file_size(bytes) when is_integer(bytes) do
cond do
bytes >= 1_000_000_000 -> "#{Float.round(bytes / 1_000_000_000, 2)} GB"
bytes >= 1_000_000 -> "#{Float.round(bytes / 1_000_000, 2)} MB"
bytes >= 1_000 -> "#{Float.round(bytes / 1_000, 2)} KB"
true -> "#{bytes} B"
end
end
# Get icon for file type
defp file_icon("image"), do: "hero-photo"
defp file_icon("video"), do: "hero-play-circle"
defp file_icon("pdf"), do: "hero-document-text"
defp file_icon("document"), do: "hero-document"
defp file_icon(_), do: "hero-document-arrow-down"
end