diff --git a/lib/modules/storage/spec.md b/lib/modules/storage/spec.md index e2d661a06..61ed5226c 100644 --- a/lib/modules/storage/spec.md +++ b/lib/modules/storage/spec.md @@ -279,7 +279,7 @@ The V18 migration will seed one default local storage bucket: The V18 migration will add 3 new settings: ```elixir -%{key: "storage_redundancy_copies", value: "2"} # Store files on 2 buckets +%{key: "storage_redundancy_copies", value: "1"} # Store files on 2 buckets %{key: "storage_auto_generate_variants", value: "true"} # Auto-generate thumbnails/resizes %{key: "storage_default_bucket_id", value: nil} # No default bucket (use selection algorithm) ``` @@ -311,7 +311,7 @@ All instances stored **next to original** in same directory: ``` ### Redundancy -- Setting: `storage_redundancy_copies` (integer, 1-5, default: 2) +- Setting: `storage_redundancy_copies` (integer, 1-5, default: 1) - Each file + all variants replicated across N buckets - Example: redundancy = 2, file stored on 2 different buckets @@ -385,14 +385,14 @@ token = :crypto.hash(:md5, data <> secret) New settings added in V18 migration: ```elixir -storage_redundancy_copies: "2" # How many bucket copies (1-5) +storage_redundancy_copies: "1" # How many bucket copies (1-5) storage_auto_generate_variants: "true" # Auto-generate thumbnails/resizes storage_default_bucket_id: nil # Default bucket for uploads (optional) ``` **Access in code:** ```elixir -PhoenixKit.Settings.get_setting("storage_redundancy_copies", "2") +PhoenixKit.Settings.get_setting("storage_redundancy_copies", "1") ``` --- diff --git a/lib/phoenix_kit/install/oban_config.ex b/lib/phoenix_kit/install/oban_config.ex index 1ad5f7adb..901479c52 100644 --- a/lib/phoenix_kit/install/oban_config.ex +++ b/lib/phoenix_kit/install/oban_config.ex @@ -101,8 +101,8 @@ defmodule PhoenixKit.Install.ObanConfig do Igniter.update_file(igniter, "config/config.exs", fn source -> content = Rewrite.Source.get(source, :content) - # Check if Oban config already exists - if String.contains?(content, "config :#{app_name}, Oban") do + # Check if Oban config already exists (with more robust detection) + if oban_config_already_exists?(content, app_name) do source else # Find insertion point before import_config statements @@ -129,23 +129,52 @@ defmodule PhoenixKit.Install.ObanConfig do end end - # Get repo module from PhoenixKit config or use placeholder - defp get_repo_module(_igniter) do + # Get repo module from PhoenixKit config or detect from app + defp get_repo_module(igniter) do config_path = "config/config.exs" + app_name = IgniterHelpers.get_parent_app_name(igniter) if File.exists?(config_path) do content = File.read!(config_path) - # Look for existing PhoenixKit repo config + # First try: Look for existing PhoenixKit repo config case Regex.run(~r/config :phoenix_kit,\s+repo:\s+([A-Za-z0-9_.]+)/, content) do - [_, repo] -> repo - _ -> "MyApp.Repo" + [_, repo] -> + repo + + _ -> + # Second try: Look for ecto_repos in app config + app_module = Macro.camelize(to_string(app_name)) + + case Regex.run(~r/config :#{app_name}.*?ecto_repos:\s*\[([A-Za-z0-9_.]+)\]/s, content) do + [_, repo] -> repo + _ -> "#{app_module}.Repo" + end end else - "MyApp.Repo" + app_module = Macro.camelize(to_string(app_name)) + "#{app_module}.Repo" end rescue - _ -> "MyApp.Repo" + _ -> + app_name = IgniterHelpers.get_parent_app_name(igniter) + app_module = Macro.camelize(to_string(app_name)) + "#{app_module}.Repo" + end + + # Check if Oban config already exists in the file + defp oban_config_already_exists?(content, app_name) do + lines = String.split(content, "\n") + + Enum.any?(lines, fn line -> + trimmed = String.trim(line) + + # Not a comment and contains config for Oban + # Also check for variations with spaces + !String.starts_with?(trimmed, "#") and + (String.contains?(line, "config :#{app_name}, Oban") or + Regex.match?(~r/config\s+:#{app_name},\s+Oban/, line)) + end) end # Find the location to insert config before import_config statements diff --git a/lib/phoenix_kit/migrations/postgres/v20.ex b/lib/phoenix_kit/migrations/postgres/v20.ex index d4f8245f8..08c4ca31b 100644 --- a/lib/phoenix_kit/migrations/postgres/v20.ex +++ b/lib/phoenix_kit/migrations/postgres/v20.ex @@ -44,7 +44,7 @@ defmodule PhoenixKit.Migrations.Postgres.V20 do ## Settings - - `storage_redundancy_copies`: How many bucket copies (default: 2) + - `storage_redundancy_copies`: How many bucket copies (default: 1) - `storage_auto_generate_variants`: Auto-generate thumbnails/resizes (default: true) - `storage_default_bucket_id`: Default bucket for uploads (optional) """ @@ -217,7 +217,7 @@ defmodule PhoenixKit.Migrations.Postgres.V20 do seed_default_bucket(prefix) # Add storage settings - insert_setting(prefix, "storage_redundancy_copies", "2") + insert_setting(prefix, "storage_redundancy_copies", "1") insert_setting(prefix, "storage_auto_generate_variants", "true") insert_setting(prefix, "storage_default_bucket_id", nil) diff --git a/lib/phoenix_kit/storage.ex b/lib/phoenix_kit/storage.ex index 63f13997f..149a21523 100644 --- a/lib/phoenix_kit/storage.ex +++ b/lib/phoenix_kit/storage.ex @@ -1462,6 +1462,23 @@ defmodule PhoenixKit.Storage do end end + @doc """ + Creates file location records for a file instance across specified buckets. + + ## Parameters + + - `file_instance_id` - The file instance UUID + - `bucket_ids` - List of bucket UUIDs where the file is stored + - `file_path` - The storage path of the file + + ## Returns + + - `:ok` - Locations created successfully + """ + def create_file_locations_for_instance(file_instance_id, bucket_ids, file_path) do + create_file_locations(file_instance_id, bucket_ids, file_path) + end + defp generate_temp_path do temp_dir = System.tmp_dir!() random_name = :crypto.strong_rand_bytes(8) |> Base.encode16(case: :lower) diff --git a/lib/phoenix_kit/storage/manager.ex b/lib/phoenix_kit/storage/manager.ex index 8a571f42a..e63e26d24 100644 --- a/lib/phoenix_kit/storage/manager.ex +++ b/lib/phoenix_kit/storage/manager.ex @@ -245,7 +245,7 @@ defmodule PhoenixKit.Storage.Manager do end defp get_redundancy_copies do - PhoenixKit.Settings.get_setting("storage_redundancy_copies", "2") + PhoenixKit.Settings.get_setting("storage_redundancy_copies", "1") |> String.to_integer() |> max(1) |> min(5) diff --git a/lib/phoenix_kit/storage/variant_generator.ex b/lib/phoenix_kit/storage/variant_generator.ex index db9f90beb..2cd8b27bc 100644 --- a/lib/phoenix_kit/storage/variant_generator.ex +++ b/lib/phoenix_kit/storage/variant_generator.ex @@ -115,7 +115,7 @@ defmodule PhoenixKit.Storage.VariantGenerator do {:ok, variant_path} <- process_variant(original_path, variant_path, file.mime_type, dimension), {:ok, file_stats} <- get_variant_file_stats(variant_path), - {:ok, _storage_info} <- + {:ok, storage_info} <- store_variant_file(variant_path, variant_name, variant_storage_path, file.id), {:ok, instance} <- create_variant_instance( @@ -126,8 +126,16 @@ defmodule PhoenixKit.Storage.VariantGenerator do variant_ext, file_stats ) do + # Create file location records for this variant instance + _ = + Storage.create_file_locations_for_instance( + instance.id, + storage_info.bucket_ids, + variant_storage_path + ) + cleanup_temp_files([original_path, variant_path]) - Logger.info("Variant #{variant_name} created successfully in database") + Logger.info("Variant #{variant_name} created successfully in database with locations") {:ok, instance} else {:error, reason} = error -> diff --git a/lib/phoenix_kit_web/live/settings/storage.ex b/lib/phoenix_kit_web/live/settings/storage.ex index 33c822ec1..24614c542 100644 --- a/lib/phoenix_kit_web/live/settings/storage.ex +++ b/lib/phoenix_kit_web/live/settings/storage.ex @@ -9,6 +9,8 @@ defmodule PhoenixKitWeb.Live.Settings.Storage do require Logger + import Ecto.Query + alias PhoenixKit.Settings alias PhoenixKit.System.Dependencies alias PhoenixKit.Utils.Routes @@ -28,8 +30,11 @@ defmodule PhoenixKitWeb.Live.Settings.Storage do # Load buckets buckets = PhoenixKit.Storage.list_buckets() + # Load file counts per bucket (unique files, not instances) + bucket_file_counts = get_bucket_file_counts(buckets) + # Load storage settings from database (using basic function to avoid cache issues) - redundancy_copies = Settings.get_setting("storage_redundancy_copies", "2") + redundancy_copies = Settings.get_setting("storage_redundancy_copies", "1") auto_generate_variants = Settings.get_setting("storage_auto_generate_variants", "true") default_bucket_id = Settings.get_setting("storage_default_bucket_id", nil) @@ -54,6 +59,7 @@ defmodule PhoenixKitWeb.Live.Settings.Storage do |> assign(:page_title, "Storage Settings") |> assign(:project_title, project_title) |> assign(:buckets, buckets) + |> assign(:bucket_file_counts, bucket_file_counts) |> assign(:redundancy_copies, current_redundancy) |> assign(:auto_generate_variants, auto_generate_variants == "true") |> assign(:default_bucket_id, default_bucket_id) @@ -188,7 +194,7 @@ defmodule PhoenixKitWeb.Live.Settings.Storage do case {redundancy_result, variants_result} do {{:ok, _}, {:ok, _}} -> # Verify the settings were saved correctly by reading them back - saved_redundancy = Settings.get_setting("storage_redundancy_copies", "2") + saved_redundancy = Settings.get_setting("storage_redundancy_copies", "1") saved_variants = Settings.get_setting("storage_auto_generate_variants", "true") socket = @@ -305,4 +311,28 @@ defmodule PhoenixKitWeb.Live.Settings.Storage do "#{bucket.provider}: unknown configuration" end end + + # Get count of unique files stored on each bucket + defp get_bucket_file_counts(buckets) do + repo = PhoenixKit.Config.get_repo() + + Enum.reduce(buckets, %{}, fn bucket, acc -> + # Count distinct files that have at least one instance located on this bucket + # We count files, not instances or locations + count = + repo.one( + from f in PhoenixKit.Storage.File, + join: fi in PhoenixKit.Storage.FileInstance, + on: fi.file_id == f.id, + join: fl in PhoenixKit.Storage.FileLocation, + on: fl.file_instance_id == fi.id, + where: fl.bucket_id == ^bucket.id and fl.status == "active", + select: count(f.id, :distinct) + ) + + Map.put(acc, bucket.id, count || 0) + end) + rescue + _ -> %{} + end end diff --git a/lib/phoenix_kit_web/live/settings/storage.html.heex b/lib/phoenix_kit_web/live/settings/storage.html.heex index 1533438d7..88b2c06cb 100644 --- a/lib/phoenix_kit_web/live/settings/storage.html.heex +++ b/lib/phoenix_kit_web/live/settings/storage.html.heex @@ -131,7 +131,7 @@
Upload images, videos, or PDFs. Files are automatically processed and optimized. @@ -57,16 +65,14 @@