Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions lib/modules/storage/schemas/folder.ex
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,13 @@ defmodule PhoenixKit.Modules.Storage.Folder do
# Folder hero-header customization. The cover (background) and logo (icon)
# are media files living in the folder, excluded from its visible listing.
# `header_size` is small/medium/large; the `header_show_*` flags toggle each
# header element. All defaulted so existing folders render as before.
# header element. New folders default to a small header (less vertical
# space up front). Existing rows keep their stored size — the DB column
# default (v134, "medium") only applies to raw inserts, which never happen
# here; folders are always created via changeset, so this default wins.
field :cover_file_uuid, UUIDv7
field :logo_file_uuid, UUIDv7
field :header_size, :string, default: "medium"
field :header_size, :string, default: "small"
field :header_show_title, :boolean, default: true
field :header_show_icon, :boolean, default: true
field :header_show_creator, :boolean, default: true
Expand Down
69 changes: 24 additions & 45 deletions lib/modules/storage/storage.ex
Original file line number Diff line number Diff line change
Expand Up @@ -1346,36 +1346,36 @@ defmodule PhoenixKit.Modules.Storage do
limit = Keyword.get(opts, :limit, 50)
offset = Keyword.get(opts, :offset, 0)

query =
from(f in Folder,
where: not is_nil(f.trashed_at),
order_by: [desc: f.trashed_at],
limit: ^limit,
offset: ^offset
)

query =
if scope_folder_id do
from(f in query, where: f.uuid != ^scope_folder_id)
else
query
end

repo().all(query)
from(f in Folder,
where: not is_nil(f.trashed_at),
order_by: [desc: f.trashed_at],
limit: ^limit,
offset: ^offset
)
|> scope_trashed_folders(scope_folder_id)
|> repo().all()
end

@doc "Counts trashed folders (with optional scope)."
def count_trashed_folders(scope_folder_id \\ nil) do
query = from(f in Folder, where: not is_nil(f.trashed_at), select: count(f.uuid))
from(f in Folder, where: not is_nil(f.trashed_at), select: count(f.uuid))
|> scope_trashed_folders(scope_folder_id)
|> repo().one()
|> Kernel.||(0)
end

query =
if scope_folder_id do
from(f in query, where: f.uuid != ^scope_folder_id)
else
query
end
# Restrict trashed folders to the scope folder's own subtree — the folders
# trashed *under* it — so a folder's Trash never shows folders trashed in a
# sibling root. nil scope = all trashed folders (the top-level view).
# `folder_subtree_uuids/1` walks children by parent_uuid without filtering
# trashed_at, so a trashed subfolder's trashed children are still reachable.
# The subtree includes the scope folder itself; drop it — the scope folder
# is where you're standing, not a trashed row.
defp scope_trashed_folders(query, nil), do: query

repo().one(query) || 0
defp scope_trashed_folders(query, scope_folder_id) do
descendants = folder_subtree_uuids(scope_folder_id) -- [scope_folder_id]
from(f in query, where: f.uuid in ^descendants)
end

@doc """
Expand Down Expand Up @@ -1503,7 +1503,6 @@ defmodule PhoenixKit.Modules.Storage do
build_scope_file_query(scope_folder_id, folder_uuid, search, include_orphaned)
|> where([f], f.status != "trashed")
|> exclude_system_managed()
|> exclude_folder_header_assets(folder_uuid)
|> maybe_filter_file_type(file_type)

total = repo().aggregate(query, :count, :uuid)
Expand All @@ -1524,26 +1523,6 @@ defmodule PhoenixKit.Modules.Storage do
defp maybe_filter_file_type(query, type) when type in [nil, "all", ""], do: query
defp maybe_filter_file_type(query, type), do: where(query, [f], f.file_type == ^type)

# A folder's own cover/logo are folder assets, not part of its visible file
# listing — drop them from the per-folder grid. They remain real files
# (re-selectable via the header's media picker); we just don't show them as
# loose files in the folder they decorate. Only applies when listing a
# specific folder; flat views (all/orphaned/search) pass folder_uuid = nil.
defp exclude_folder_header_assets(query, nil), do: query

defp exclude_folder_header_assets(query, folder_uuid) do
case get_folder(folder_uuid) do
%{} = folder ->
case Enum.reject([folder.cover_file_uuid, folder.logo_file_uuid], &is_nil/1) do
[] -> query
excluded -> where(query, [f], f.uuid not in ^excluded)
end

_ ->
query
end
end

# Sort whitelist for the media browser toolbar — defaults to newest first.
# Every order carries `f.uuid` as a stable tiebreaker so equal values
# (same size, same name, same insert time) can't shuffle across pages.
Expand Down
10 changes: 8 additions & 2 deletions lib/phoenix_kit/migrations/postgres.ex
Original file line number Diff line number Diff line change
Expand Up @@ -529,7 +529,13 @@ defmodule PhoenixKit.Migrations.Postgres do
- Replaces unique index with partial index (slug-mode only, WHERE slug IS NOT NULL)
- Adds unique index on `(group_uuid, post_date, post_time)` for timestamp-mode posts

### V152 - Newsletters/CRM/Core restructuring (accumulator) ⚡ LATEST
### V153 - Folder header size defaults to small ⚡ LATEST
- Flips `phoenix_kit_media_folders.header_size` column default from
'medium' (V134) to 'small', and backfills existing 'medium' rows to
'small' ('medium' was the old default, so it reads as untouched;
'large' is a deliberate choice and is left alone)

### V152 - Newsletters/CRM/Core restructuring (accumulator)
- Unreleased — per the "one open migration" rule, every DDL step of the
restructuring plan lands in V152 as its own section until it ships;
later stages append here rather than opening V153.
Expand Down Expand Up @@ -1323,7 +1329,7 @@ defmodule PhoenixKit.Migrations.Postgres do
alias PhoenixKit.Migrations.Postgres.Helpers

@initial_version 1
@current_version 152
@current_version 153
@default_prefix "public"

# First version whose SQL references uuid_generate_v7(). Chains that
Expand Down
68 changes: 68 additions & 0 deletions lib/phoenix_kit/migrations/postgres/v153.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
defmodule PhoenixKit.Migrations.Postgres.V153 do
@moduledoc """
V153: folder header size defaults to small.

New folders now open with a small hero header (see the schema default on
`PhoenixKit.Modules.Storage.Folder`). This migration brings existing rows
and the DB column default in line:

* **Column default** `phoenix_kit_media_folders.header_size` flips from
`'medium'` (set in V134) to `'small'`, so raw inserts match the
changeset default.

* **Backfill** every folder currently on `'medium'` → `'small'`.
`'medium'` was the *old default*, so a stored `'medium'` is
indistinguishable from "never touched" — those reset to small.
`'large'` was never a default, so any `'large'` is a deliberate
choice and is left alone; existing `'small'` rows are unaffected.

There is no stored "user customised this" signal, so a folder someone
deliberately set to `'medium'` also resets — an accepted trade-off, since
medium and default-medium can't be told apart. Users can re-pick medium
from the header-size control any time.

Idempotent: the backfill's `WHERE header_size = 'medium'` and the default
swap are safe to re-run.
"""

use Ecto.Migration

def up(opts) do
p = prefix_str(Map.get(opts, :prefix, "public"))

execute("""
ALTER TABLE #{p}phoenix_kit_media_folders
ALTER COLUMN header_size SET DEFAULT 'small'
""")

execute("""
UPDATE #{p}phoenix_kit_media_folders
SET header_size = 'small'
WHERE header_size = 'medium'
""")

execute("COMMENT ON TABLE #{p}phoenix_kit IS '153'")
end

@doc """
Rolls V152 back.

Restores the column default to `'medium'` (its V134 value). **Lossy:** the
`medium → small` backfill is not reversed — the folders that were reset
can't be told apart from folders genuinely on small, so their sizes stay
as they are.
"""
def down(opts) do
p = prefix_str(Map.get(opts, :prefix, "public"))

execute("""
ALTER TABLE #{p}phoenix_kit_media_folders
ALTER COLUMN header_size SET DEFAULT 'medium'
""")

execute("COMMENT ON TABLE #{p}phoenix_kit IS '152'")
end

defp prefix_str("public"), do: "public."
defp prefix_str(prefix), do: "#{prefix}."
end
Loading