Packages

phoenix_kit

1.5.2
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
phoenix_kit lib phoenix_kit_web live users media.ex
Raw

lib/phoenix_kit_web/live/users/media.ex

defmodule PhoenixKitWeb.Live.Users.Media do
@moduledoc """
Media management LiveView for PhoenixKit admin panel.
Provides interface for viewing and managing user media.
"""
use PhoenixKitWeb, :live_view
use Gettext, backend: PhoenixKitWeb.Gettext
require Logger
alias PhoenixKit.Settings
alias PhoenixKit.Storage.FileInstance
alias PhoenixKit.Storage.URLSigner
alias PhoenixKit.Storage.Workers.ProcessFileJob
alias PhoenixKit.Utils.Routes
def mount(params, _session, socket) do
# Set locale for LiveView process
locale = params["locale"] || socket.assigns[:current_locale] || "en"
Gettext.put_locale(PhoenixKitWeb.Gettext, locale)
Process.put(:phoenix_kit_current_locale, locale)
# Batch load all settings needed for this page (uses cached settings for performance)
settings =
Settings.get_settings_cached(
["project_title"],
%{"project_title" => "PhoenixKit"}
)
socket =
socket
|> allow_upload(:media_files,
accept: ["image/*", "video/*", "application/pdf"],
max_entries: 10,
max_file_size: 100_000_000,
auto_upload: true
)
|> assign(:page_title, "Media")
|> assign(:project_title, settings["project_title"])
|> assign(:current_locale, locale)
|> assign(:url_path, Routes.path("/admin/users/media"))
{:ok, socket}
end
def handle_params(params, _uri, socket) do
# Pagination setup
per_page = 50
page = String.to_integer(params["page"] || "1")
# Load existing files from database with pagination
{existing_files, total_count} = load_existing_files(page, per_page)
total_pages = ceil(total_count / per_page)
socket =
socket
|> assign(:uploaded_files, existing_files)
|> assign(:current_page, page)
|> assign(:per_page, per_page)
|> assign(:total_pages, total_pages)
|> assign(:total_count, total_count)
{:noreply, socket}
end
def handle_event("validate", _params, socket) do
# File selection event - files will auto-upload
entries = socket.assigns.uploads.media_files.entries
Logger.info("validate event: entries=#{length(entries)}")
if entries != [] do
Logger.info("validate: scheduling check_uploads_complete")
Process.send_after(self(), :check_uploads_complete, 500)
end
{:noreply, socket}
end
def handle_event("cancel_upload", %{"ref" => ref}, socket) do
{:noreply, cancel_upload(socket, :media_files, ref)}
end
def handle_info(:check_uploads_complete, socket) do
entries = socket.assigns.uploads.media_files.entries
Logger.info(
"check_uploads_complete: entries=#{length(entries)}, done?=#{inspect(Enum.map(entries, & &1.done?))}"
)
# Check if all entries are done uploading
if entries != [] && Enum.all?(entries, & &1.done?) do
Logger.info("All uploads done! Processing...")
# All done - process them
process_uploads(socket)
else
# Still uploading - check again later
Logger.info("Still uploading, checking again...")
Process.send_after(self(), :check_uploads_complete, 500)
{:noreply, socket}
end
end
defp process_uploads(socket) do
# Process uploaded files
uploaded_files =
consume_uploaded_entries(socket, :media_files, fn %{path: path}, entry ->
# Get file info
ext = Path.extname(entry.client_name) |> String.replace_leading(".", "")
mime_type = entry.client_type || MIME.from_path(entry.client_name)
file_type = determine_file_type(mime_type)
# Get current user
current_user = socket.assigns.phoenix_kit_current_user
user_id = if current_user, do: current_user.id, else: 1
# Get file size
{:ok, stat} = Elixir.File.stat(path)
file_size = stat.size
# Calculate hash
file_hash = calculate_file_hash(path)
# Store file in storage
case PhoenixKit.Storage.store_file_in_buckets(
path,
file_type,
user_id,
file_hash,
ext,
entry.client_name
) do
{:ok, file} ->
# Queue background job for processing
_job =
%{file_id: file.id, user_id: user_id, filename: entry.client_name}
|> ProcessFileJob.new()
|> Oban.insert()
# Generate URLs for available variants (start with original)
urls = generate_file_urls(file.id)
{:ok,
%{
file_id: file.id,
filename: entry.client_name,
file_type: file_type,
mime_type: mime_type,
size: file_size,
status: file.status,
urls: urls
}}
{:error, reason} ->
Logger.error("Storage Error: #{inspect(reason)}")
{:error, reason}
end
end)
# Reload paginated data from database to show newly uploaded files
per_page = socket.assigns.per_page || 50
page = socket.assigns.current_page || 1
{refreshed_files, total_count} = load_existing_files(page, per_page)
total_pages = ceil(total_count / per_page)
socket =
socket
|> assign(:uploaded_files, refreshed_files)
|> assign(:total_count, total_count)
|> assign(:total_pages, total_pages)
|> put_flash(:info, "Upload successful! #{length(uploaded_files)} file(s) processed")
{:noreply, socket}
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
# Legacy function for cases where we need to query a single file's instances
defp generate_file_urls(file_id) do
import Ecto.Query
repo = Application.get_env(:phoenix_kit, :repo)
instances =
FileInstance
|> where([fi], fi.file_id == ^file_id)
|> repo.all()
generate_urls_from_instances(instances, file_id)
end
defp calculate_file_hash(file_path) do
file_path
|> Elixir.File.read!()
|> then(fn data -> :crypto.hash(:sha256, data) end)
|> Base.encode16(case: :lower)
end
defp determine_file_type(mime_type) do
cond do
String.starts_with?(mime_type, "image/") -> "image"
String.starts_with?(mime_type, "video/") -> "video"
mime_type == "application/pdf" -> "pdf"
true -> "document"
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"
# Load existing files from database with pagination
defp load_existing_files(page, per_page) do
import Ecto.Query
repo = Application.get_env(:phoenix_kit, :repo)
# Get total count
total_count = repo.aggregate(PhoenixKit.Storage.File, :count, :id)
# Calculate offset
offset = (page - 1) * per_page
# Query files ordered by most recent first with pagination
files =
from(f in PhoenixKit.Storage.File,
order_by: [desc: f.inserted_at],
limit: ^per_page,
offset: ^offset
)
|> repo.all()
# Batch load ALL file instances in ONE query instead of N queries
file_ids = Enum.map(files, & &1.id)
instances_by_file =
if file_ids != [] do
from(fi in FileInstance,
where: fi.file_id in ^file_ids
)
|> repo.all()
|> Enum.group_by(& &1.file_id)
else
%{}
end
# Convert to same format as uploaded files
existing_files =
Enum.map(files, fn file ->
# Get pre-loaded instances for this file (no DB query!)
instances = Map.get(instances_by_file, file.id, [])
urls = generate_urls_from_instances(instances, file.id)
%{
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
}
end)
{existing_files, total_count}
end
end