From 7dfcd78abd7a2b8a6f5ffacc6998058013a3ec9d Mon Sep 17 00:00:00 2001 From: Chili Palmer Date: Sun, 5 Jul 2026 14:44:47 +0200 Subject: [PATCH] fix: issue #7 rebuild media links to get gallery working again --- lib/bds/desktop/shell_commands.ex | 8 +++ .../settings_editor.html.heex | 3 +- lib/bds/media.ex | 6 ++ lib/bds/media/linking.ex | 46 ++++++++++++++ lib/bds/media/sidecars.ex | 23 +++++++ priv/gettext/de/LC_MESSAGES/ui.po | 21 ++++--- priv/gettext/en/LC_MESSAGES/ui.po | 21 ++++--- priv/gettext/es/LC_MESSAGES/ui.po | 21 ++++--- priv/gettext/fr/LC_MESSAGES/ui.po | 21 ++++--- priv/gettext/it/LC_MESSAGES/ui.po | 21 ++++--- priv/gettext/ui.pot | 21 ++++--- test/bds/media/link_restore_fresh_test.exs | 62 +++++++++++++++++++ test/bds/media/link_restore_test.exs | 47 ++++++++++++++ 13 files changed, 272 insertions(+), 49 deletions(-) create mode 100644 test/bds/media/link_restore_fresh_test.exs diff --git a/lib/bds/desktop/shell_commands.ex b/lib/bds/desktop/shell_commands.ex index 04a9de8..5edc195 100644 --- a/lib/bds/desktop/shell_commands.ex +++ b/lib/bds/desktop/shell_commands.ex @@ -196,6 +196,14 @@ defmodule BDS.Desktop.ShellCommands do end) end + defp dispatch("rebuild_media_links", project, _params) do + queue_task(project, "rebuild_media_links", "Rebuild Media Links", "Maintenance", fn report -> + {:ok, result} = BDS.Media.rebuild_media_links(project.id, on_progress: report) + report.(1.0, "Media links rebuilt") + %{project_id: project.id, counts: %{media_links: result.links}} + end) + end + defp dispatch("regenerate_missing_thumbnails", project, _params) do queue_task( project, diff --git a/lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex b/lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex index 4674b8f..1ca5204 100644 --- a/lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex +++ b/lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex @@ -375,7 +375,8 @@ - + + diff --git a/lib/bds/media.ex b/lib/bds/media.ex index 687d9e4..182fb7d 100644 --- a/lib/bds/media.ex +++ b/lib/bds/media.ex @@ -66,6 +66,12 @@ defmodule BDS.Media do defdelegate link_media_to_post(media_id, post_id), to: BDS.Media.Linking defdelegate unlink_media_from_post(media_id, post_id), to: BDS.Media.Linking + defdelegate rebuild_media_links(project_id), to: BDS.Media.Linking, + as: :rebuild_links_from_sidecars + + defdelegate rebuild_media_links(project_id, opts), to: BDS.Media.Linking, + as: :rebuild_links_from_sidecars + defdelegate rebuild_media_from_files(project_id), to: BDS.Media.Rebuilder defdelegate rebuild_media_from_files(project_id, opts), to: BDS.Media.Rebuilder diff --git a/lib/bds/media/linking.ex b/lib/bds/media/linking.ex index f7170d1..6c65ea1 100644 --- a/lib/bds/media/linking.ex +++ b/lib/bds/media/linking.ex @@ -5,6 +5,13 @@ defmodule BDS.Media.Linking do import Ecto.Query + import BDS.Media.FileOps, + only: [ + progress_callback: 1, + report_rebuild_progress: 4, + report_rebuild_started: 3 + ] + alias BDS.Media.Media alias BDS.Media.Sidecars alias BDS.Persistence @@ -134,6 +141,45 @@ defmodule BDS.Media.Linking do def restore_post_links(_project_id, _media_id, _post_ids), do: :ok + @doc """ + Rebuild the `post_media` junction for a project from every media sidecar's + `linkedPostIds`. + + Media sidecars are the source of truth for gallery associations (matching the + old bDS `rebuildFromSidecars`). Existing project links are cleared and then + re-created from the on-disk sidecars, so galleries reflect the project's + metadata even if the junction table drifted or was never populated. Links to + posts that are not present in the database are skipped. + """ + @spec rebuild_links_from_sidecars(String.t(), keyword()) :: + {:ok, %{media: non_neg_integer(), links: non_neg_integer()}} + def rebuild_links_from_sidecars(project_id, opts \\ []) when is_binary(project_id) do + project = Projects.get_project!(project_id) + on_progress = progress_callback(opts) + + media_list = + Repo.all(from m in Media, where: m.project_id == ^project_id, order_by: [asc: m.id]) + + total = length(media_list) + :ok = report_rebuild_started(on_progress, total, "media links") + + {_cleared, _} = + Repo.delete_all(from pm in PostMedia, where: pm.project_id == ^project_id) + + media_list + |> Enum.with_index(1) + |> Enum.each(fn {media, index} -> + post_ids = Sidecars.read_linked_post_ids(project, media) + restore_post_links(project_id, media.id, post_ids) + :ok = report_rebuild_progress(on_progress, index, total, "media links") + end) + + links = + Repo.aggregate(from(pm in PostMedia, where: pm.project_id == ^project_id), :count) + + {:ok, %{media: total, links: links}} + end + defp ensure_link(project_id, media_id, post_id) do already_linked? = Repo.exists?( diff --git a/lib/bds/media/sidecars.ex b/lib/bds/media/sidecars.ex index 209cd31..229a0f4 100644 --- a/lib/bds/media/sidecars.ex +++ b/lib/bds/media/sidecars.ex @@ -349,6 +349,29 @@ defmodule BDS.Media.Sidecars do |> File.exists?() end + @doc """ + Read the `linkedPostIds` list persisted in a media item's canonical sidecar. + + The media sidecar is the source of truth for gallery associations (matching + the old bDS `linkedPostIds` field). Returns `[]` when the sidecar is missing + or has no linked posts. + """ + @spec read_linked_post_ids(BDS.Projects.Project.t(), Media.t()) :: [String.t()] + def read_linked_post_ids(project, %Media{} = media) do + sidecar_path = Path.join(Projects.project_data_dir(project), media.sidecar_path) + + case parse_existing_canonical_sidecar(project, sidecar_path) do + {:ok, sidecar} -> + sidecar.fields + |> DocumentFields.get("linkedPostIds", []) + |> List.wrap() + |> Enum.filter(&is_binary/1) + + {:error, _reason} -> + [] + end + end + defp parse_existing_canonical_sidecar(project, sidecar_path) do if File.exists?(sidecar_path) do parse_canonical_sidecar(project, sidecar_path) diff --git a/priv/gettext/de/LC_MESSAGES/ui.po b/priv/gettext/de/LC_MESSAGES/ui.po index 59ab5f2..f89f7a1 100644 --- a/priv/gettext/de/LC_MESSAGES/ui.po +++ b/priv/gettext/de/LC_MESSAGES/ui.po @@ -1757,7 +1757,7 @@ msgid "Open" msgstr "Öffnen" #: lib/bds/desktop/menu_bar.ex:154 -#: lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex:381 +#: lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex:382 #, elixir-autogen, elixir-format msgid "Open Data Folder" msgstr "Datenordner öffnen" @@ -2049,16 +2049,11 @@ msgid "Rebuild Database" msgstr "Datenbank neu aufbauen" #: lib/bds/desktop/menu_bar.ex:184 -#: lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex:380 +#: lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex:381 #, elixir-autogen, elixir-format msgid "Rebuild Embedding Index" msgstr "Embedding-Index neu aufbauen" -#: lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex:378 -#, elixir-autogen, elixir-format -msgid "Rebuild Links" -msgstr "Links neu aufbauen" - #: lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex:375 #, elixir-autogen, elixir-format msgid "Rebuild Media From Files" @@ -2116,7 +2111,7 @@ msgstr "Übersetzung aktualisieren" msgid "Regenerate Calendar" msgstr "Kalender neu erzeugen" -#: lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex:379 +#: lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex:380 #, elixir-autogen, elixir-format msgid "Regenerate Missing Thumbnails" msgstr "Fehlende Vorschaubilder neu erzeugen" @@ -3533,3 +3528,13 @@ msgstr "Vorgeschlagene Tags" #, elixir-autogen, elixir-format msgid "Force Render Site" msgstr "Website vollständig neu generieren" + +#: lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex:379 +#, elixir-autogen, elixir-format +msgid "Rebuild Media Links" +msgstr "Medien-Links neu aufbauen" + +#: lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex:378 +#, elixir-autogen, elixir-format +msgid "Rebuild Post Links" +msgstr "Beitrags-Links neu aufbauen" diff --git a/priv/gettext/en/LC_MESSAGES/ui.po b/priv/gettext/en/LC_MESSAGES/ui.po index a96b062..d1bb83e 100644 --- a/priv/gettext/en/LC_MESSAGES/ui.po +++ b/priv/gettext/en/LC_MESSAGES/ui.po @@ -1757,7 +1757,7 @@ msgid "Open" msgstr "" #: lib/bds/desktop/menu_bar.ex:154 -#: lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex:381 +#: lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex:382 #, elixir-autogen, elixir-format msgid "Open Data Folder" msgstr "" @@ -2049,16 +2049,11 @@ msgid "Rebuild Database" msgstr "" #: lib/bds/desktop/menu_bar.ex:184 -#: lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex:380 +#: lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex:381 #, elixir-autogen, elixir-format msgid "Rebuild Embedding Index" msgstr "" -#: lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex:378 -#, elixir-autogen, elixir-format -msgid "Rebuild Links" -msgstr "" - #: lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex:375 #, elixir-autogen, elixir-format msgid "Rebuild Media From Files" @@ -2116,7 +2111,7 @@ msgstr "" msgid "Regenerate Calendar" msgstr "" -#: lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex:379 +#: lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex:380 #, elixir-autogen, elixir-format msgid "Regenerate Missing Thumbnails" msgstr "" @@ -3533,3 +3528,13 @@ msgstr "" #, elixir-autogen, elixir-format msgid "Force Render Site" msgstr "" + +#: lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex:379 +#, elixir-autogen, elixir-format, fuzzy +msgid "Rebuild Media Links" +msgstr "" + +#: lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex:378 +#, elixir-autogen, elixir-format, fuzzy +msgid "Rebuild Post Links" +msgstr "" diff --git a/priv/gettext/es/LC_MESSAGES/ui.po b/priv/gettext/es/LC_MESSAGES/ui.po index 6592061..9ec3c2f 100644 --- a/priv/gettext/es/LC_MESSAGES/ui.po +++ b/priv/gettext/es/LC_MESSAGES/ui.po @@ -1757,7 +1757,7 @@ msgid "Open" msgstr "Abrir" #: lib/bds/desktop/menu_bar.ex:154 -#: lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex:381 +#: lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex:382 #, elixir-autogen, elixir-format msgid "Open Data Folder" msgstr "Abrir carpeta de datos" @@ -2049,16 +2049,11 @@ msgid "Rebuild Database" msgstr "Reconstruir base de datos" #: lib/bds/desktop/menu_bar.ex:184 -#: lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex:380 +#: lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex:381 #, elixir-autogen, elixir-format msgid "Rebuild Embedding Index" msgstr "Reconstruir índice de embeddings" -#: lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex:378 -#, elixir-autogen, elixir-format -msgid "Rebuild Links" -msgstr "Reconstruir enlaces" - #: lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex:375 #, elixir-autogen, elixir-format msgid "Rebuild Media From Files" @@ -2116,7 +2111,7 @@ msgstr "Actualizar traducción" msgid "Regenerate Calendar" msgstr "Regenerar calendario" -#: lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex:379 +#: lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex:380 #, elixir-autogen, elixir-format msgid "Regenerate Missing Thumbnails" msgstr "Regenerar miniaturas faltantes" @@ -3533,3 +3528,13 @@ msgstr "Etiquetas sugeridas" #, elixir-autogen, elixir-format msgid "Force Render Site" msgstr "Forzar la regeneración del sitio" + +#: lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex:379 +#, elixir-autogen, elixir-format +msgid "Rebuild Media Links" +msgstr "Reconstruir enlaces de medios" + +#: lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex:378 +#, elixir-autogen, elixir-format +msgid "Rebuild Post Links" +msgstr "Reconstruir enlaces de entradas" diff --git a/priv/gettext/fr/LC_MESSAGES/ui.po b/priv/gettext/fr/LC_MESSAGES/ui.po index f5371e6..8d47583 100644 --- a/priv/gettext/fr/LC_MESSAGES/ui.po +++ b/priv/gettext/fr/LC_MESSAGES/ui.po @@ -1757,7 +1757,7 @@ msgid "Open" msgstr "Ouvrir" #: lib/bds/desktop/menu_bar.ex:154 -#: lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex:381 +#: lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex:382 #, elixir-autogen, elixir-format msgid "Open Data Folder" msgstr "Ouvrir le dossier de données" @@ -2049,16 +2049,11 @@ msgid "Rebuild Database" msgstr "Reconstruire la base de données" #: lib/bds/desktop/menu_bar.ex:184 -#: lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex:380 +#: lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex:381 #, elixir-autogen, elixir-format msgid "Rebuild Embedding Index" msgstr "Reconstruire l’index d’embeddings" -#: lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex:378 -#, elixir-autogen, elixir-format -msgid "Rebuild Links" -msgstr "Reconstruire les liens" - #: lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex:375 #, elixir-autogen, elixir-format msgid "Rebuild Media From Files" @@ -2116,7 +2111,7 @@ msgstr "Actualiser la traduction" msgid "Regenerate Calendar" msgstr "Régénérer le calendrier" -#: lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex:379 +#: lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex:380 #, elixir-autogen, elixir-format msgid "Regenerate Missing Thumbnails" msgstr "Régénérer les vignettes manquantes" @@ -3533,3 +3528,13 @@ msgstr "Tags suggérés" #, elixir-autogen, elixir-format msgid "Force Render Site" msgstr "Forcer la régénération du site" + +#: lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex:379 +#, elixir-autogen, elixir-format +msgid "Rebuild Media Links" +msgstr "Reconstruire les liens des médias" + +#: lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex:378 +#, elixir-autogen, elixir-format +msgid "Rebuild Post Links" +msgstr "Reconstruire les liens des articles" diff --git a/priv/gettext/it/LC_MESSAGES/ui.po b/priv/gettext/it/LC_MESSAGES/ui.po index 8194f41..f212b35 100644 --- a/priv/gettext/it/LC_MESSAGES/ui.po +++ b/priv/gettext/it/LC_MESSAGES/ui.po @@ -1757,7 +1757,7 @@ msgid "Open" msgstr "Apri" #: lib/bds/desktop/menu_bar.ex:154 -#: lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex:381 +#: lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex:382 #, elixir-autogen, elixir-format msgid "Open Data Folder" msgstr "Apri cartella dati" @@ -2049,16 +2049,11 @@ msgid "Rebuild Database" msgstr "Ricostruisci database" #: lib/bds/desktop/menu_bar.ex:184 -#: lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex:380 +#: lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex:381 #, elixir-autogen, elixir-format msgid "Rebuild Embedding Index" msgstr "Ricostruisci indice embeddings" -#: lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex:378 -#, elixir-autogen, elixir-format -msgid "Rebuild Links" -msgstr "Ricostruisci i link" - #: lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex:375 #, elixir-autogen, elixir-format msgid "Rebuild Media From Files" @@ -2116,7 +2111,7 @@ msgstr "Aggiorna traduzione" msgid "Regenerate Calendar" msgstr "Rigenera calendario" -#: lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex:379 +#: lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex:380 #, elixir-autogen, elixir-format msgid "Regenerate Missing Thumbnails" msgstr "Rigenera miniature mancanti" @@ -3533,3 +3528,13 @@ msgstr "Tag suggeriti" #, elixir-autogen, elixir-format msgid "Force Render Site" msgstr "Forza la rigenerazione del sito" + +#: lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex:379 +#, elixir-autogen, elixir-format +msgid "Rebuild Media Links" +msgstr "Ricostruisci i link dei media" + +#: lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex:378 +#, elixir-autogen, elixir-format +msgid "Rebuild Post Links" +msgstr "Ricostruisci i link degli articoli" diff --git a/priv/gettext/ui.pot b/priv/gettext/ui.pot index cec9442..b599f1b 100644 --- a/priv/gettext/ui.pot +++ b/priv/gettext/ui.pot @@ -1770,7 +1770,7 @@ msgid "Open" msgstr "" #: lib/bds/desktop/menu_bar.ex:154 -#: lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex:381 +#: lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex:382 #, elixir-autogen, elixir-format msgid "Open Data Folder" msgstr "" @@ -2062,16 +2062,11 @@ msgid "Rebuild Database" msgstr "" #: lib/bds/desktop/menu_bar.ex:184 -#: lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex:380 +#: lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex:381 #, elixir-autogen, elixir-format msgid "Rebuild Embedding Index" msgstr "" -#: lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex:378 -#, elixir-autogen, elixir-format -msgid "Rebuild Links" -msgstr "" - #: lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex:375 #, elixir-autogen, elixir-format msgid "Rebuild Media From Files" @@ -2129,7 +2124,7 @@ msgstr "" msgid "Regenerate Calendar" msgstr "" -#: lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex:379 +#: lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex:380 #, elixir-autogen, elixir-format msgid "Regenerate Missing Thumbnails" msgstr "" @@ -3546,3 +3541,13 @@ msgstr "" #, elixir-autogen, elixir-format msgid "Force Render Site" msgstr "" + +#: lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex:379 +#, elixir-autogen, elixir-format +msgid "Rebuild Media Links" +msgstr "" + +#: lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex:378 +#, elixir-autogen, elixir-format +msgid "Rebuild Post Links" +msgstr "" diff --git a/test/bds/media/link_restore_fresh_test.exs b/test/bds/media/link_restore_fresh_test.exs new file mode 100644 index 0000000..fd1c4f0 --- /dev/null +++ b/test/bds/media/link_restore_fresh_test.exs @@ -0,0 +1,62 @@ +defmodule BDS.Media.LinkRestoreFreshTest do + use ExUnit.Case, async: false + + import Ecto.Query + + alias BDS.Media + alias BDS.Media.Media, as: MediaRecord + alias BDS.Posts.PostMedia + alias BDS.Repo + + setup do + :ok = Ecto.Adapters.SQL.Sandbox.checkout(Repo) + Ecto.Adapters.SQL.Sandbox.mode(Repo, {:shared, self()}) + temp_dir = Path.join(System.tmp_dir!(), "bds-freshlink-#{System.unique_integer([:positive])}") + File.mkdir_p!(temp_dir) + on_exit(fn -> File.rm_rf(temp_dir) end) + + {:ok, project} = BDS.Projects.create_project(%{name: "Fresh", data_path: temp_dir}) + {:ok, _} = BDS.Metadata.update_project_metadata(project.id, %{main_language: "en"}) + + {:ok, post} = + BDS.Posts.create_post(%{ + project_id: project.id, + title: "Gallery Post", + content: "[[gallery]]", + language: "en" + }) + + source = Path.join(temp_dir, "fresh.jpg") + File.write!(source, "fake jpeg") + {:ok, media} = Media.import_media(%{project_id: project.id, source_path: source}) + {:ok, _} = Media.link_media_to_post(media.id, post.id) + + %{project: project, post: post, media: media} + end + + test "fresh media rebuild (media record gone, only files on disk) restores links", %{ + project: project, + post: post, + media: media + } do + # Sidecar on disk should carry the link. + sidecar_path = Path.join(project.data_path, media.sidecar_path) + assert File.exists?(sidecar_path) + contents = File.read!(sidecar_path) + assert contents =~ "linkedPostIds" + assert contents =~ post.id + + # Simulate a fresh DB: drop the media record AND its links entirely. + Repo.delete_all(from pm in PostMedia, where: pm.media_id == ^media.id) + Repo.delete_all(from m in MediaRecord, where: m.id == ^media.id) + assert Repo.aggregate(from(m in MediaRecord, where: m.id == ^media.id), :count) == 0 + + assert {:ok, _} = BDS.Media.rebuild_media_from_files(project.id) + + # Media re-inserted from sidecar. + assert Repo.aggregate(from(m in MediaRecord, where: m.project_id == ^project.id), :count) == 1 + + # Link restored so the post's media list is populated again. + assert Repo.aggregate(from(pm in PostMedia, where: pm.post_id == ^post.id), :count) == 1 + end +end diff --git a/test/bds/media/link_restore_test.exs b/test/bds/media/link_restore_test.exs index 6b4e84b..44635a5 100644 --- a/test/bds/media/link_restore_test.exs +++ b/test/bds/media/link_restore_test.exs @@ -69,4 +69,51 @@ defmodule BDS.Media.LinkRestoreTest do assert {:ok, _} = BDS.Media.rebuild_media_from_files(project.id) assert link_count(post.id, media.id) == 1 end + + test "rebuild_media_links rebuilds gallery links from sidecars", %{ + project: project, + post: post, + media: media + } do + # Simulate a project whose junction table was never populated even though + # the media sidecar references the post (the reported gallery regression). + Repo.delete_all(from pm in PostMedia, where: pm.media_id == ^media.id) + assert link_count(post.id, media.id) == 0 + + assert {:ok, %{media: 1, links: 1}} = BDS.Media.rebuild_media_links(project.id) + + assert link_count(post.id, media.id) == 1 + end + + test "rebuild_media_links drops stale junction links not present in sidecars", %{ + project: project, + post: post, + media: media + } do + {:ok, other_post} = + BDS.Posts.create_post(%{ + project_id: project.id, + title: "Unrelated Post", + content: "no gallery here", + language: "en" + }) + + # A junction row that drifted from the sidecar: the media is linked to a + # post its sidecar does not reference. The sidecar is the source of truth, + # so rebuild must drop this link. + Repo.insert!(%PostMedia{ + id: Ecto.UUID.generate(), + project_id: project.id, + post_id: other_post.id, + media_id: media.id, + sort_order: 5, + created_at: 0 + }) + + assert {:ok, %{links: 1}} = BDS.Media.rebuild_media_links(project.id) + + # The real (sidecar-backed) link survives, the drifted link is gone. + assert link_count(post.id, media.id) == 1 + assert link_count(other_post.id, media.id) == 0 + end end