fix: issue #7 rebuild media links to get gallery working again

This commit is contained in:
2026-07-05 14:44:47 +02:00
parent 8e60fe31a5
commit 7dfcd78abd
13 changed files with 272 additions and 49 deletions

View File

@@ -196,6 +196,14 @@ defmodule BDS.Desktop.ShellCommands do
end) end)
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 defp dispatch("regenerate_missing_thumbnails", project, _params) do
queue_task( queue_task(
project, project,

View File

@@ -375,7 +375,8 @@
<button class="secondary ui-button ui-button-secondary" type="button" phx-click="settings_shell_command" phx-value-action="rebuild_media_from_files"><%= dgettext("ui", "Rebuild Media From Files") %></button> <button class="secondary ui-button ui-button-secondary" type="button" phx-click="settings_shell_command" phx-value-action="rebuild_media_from_files"><%= dgettext("ui", "Rebuild Media From Files") %></button>
<button class="secondary ui-button ui-button-secondary" type="button" phx-click="settings_shell_command" phx-value-action="rebuild_scripts_from_files"><%= dgettext("ui", "Rebuild Scripts From Files") %></button> <button class="secondary ui-button ui-button-secondary" type="button" phx-click="settings_shell_command" phx-value-action="rebuild_scripts_from_files"><%= dgettext("ui", "Rebuild Scripts From Files") %></button>
<button class="secondary ui-button ui-button-secondary" type="button" phx-click="settings_shell_command" phx-value-action="rebuild_templates_from_files"><%= dgettext("ui", "Rebuild Templates From Files") %></button> <button class="secondary ui-button ui-button-secondary" type="button" phx-click="settings_shell_command" phx-value-action="rebuild_templates_from_files"><%= dgettext("ui", "Rebuild Templates From Files") %></button>
<button class="secondary ui-button ui-button-secondary" type="button" phx-click="settings_shell_command" phx-value-action="rebuild_post_links"><%= dgettext("ui", "Rebuild Links") %></button> <button class="secondary ui-button ui-button-secondary" type="button" phx-click="settings_shell_command" phx-value-action="rebuild_post_links"><%= dgettext("ui", "Rebuild Post Links") %></button>
<button class="secondary ui-button ui-button-secondary" type="button" phx-click="settings_shell_command" phx-value-action="rebuild_media_links"><%= dgettext("ui", "Rebuild Media Links") %></button>
<button class="secondary ui-button ui-button-secondary" type="button" phx-click="settings_shell_command" phx-value-action="regenerate_missing_thumbnails"><%= dgettext("ui", "Regenerate Missing Thumbnails") %></button> <button class="secondary ui-button ui-button-secondary" type="button" phx-click="settings_shell_command" phx-value-action="regenerate_missing_thumbnails"><%= dgettext("ui", "Regenerate Missing Thumbnails") %></button>
<button class="secondary ui-button ui-button-secondary" type="button" phx-click="settings_shell_command" phx-value-action="rebuild_embedding_index"><%= dgettext("ui", "Rebuild Embedding Index") %></button> <button class="secondary ui-button ui-button-secondary" type="button" phx-click="settings_shell_command" phx-value-action="rebuild_embedding_index"><%= dgettext("ui", "Rebuild Embedding Index") %></button>
<button class="secondary ui-button ui-button-secondary" type="button" phx-click="settings_shell_command" phx-value-action="open_data_folder"><%= dgettext("ui", "Open Data Folder") %></button> <button class="secondary ui-button ui-button-secondary" type="button" phx-click="settings_shell_command" phx-value-action="open_data_folder"><%= dgettext("ui", "Open Data Folder") %></button>

View File

@@ -66,6 +66,12 @@ defmodule BDS.Media do
defdelegate link_media_to_post(media_id, post_id), to: BDS.Media.Linking 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 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), to: BDS.Media.Rebuilder
defdelegate rebuild_media_from_files(project_id, opts), to: BDS.Media.Rebuilder defdelegate rebuild_media_from_files(project_id, opts), to: BDS.Media.Rebuilder

View File

@@ -5,6 +5,13 @@ defmodule BDS.Media.Linking do
import Ecto.Query 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.Media
alias BDS.Media.Sidecars alias BDS.Media.Sidecars
alias BDS.Persistence alias BDS.Persistence
@@ -134,6 +141,45 @@ defmodule BDS.Media.Linking do
def restore_post_links(_project_id, _media_id, _post_ids), do: :ok 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 defp ensure_link(project_id, media_id, post_id) do
already_linked? = already_linked? =
Repo.exists?( Repo.exists?(

View File

@@ -349,6 +349,29 @@ defmodule BDS.Media.Sidecars do
|> File.exists?() |> File.exists?()
end 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 defp parse_existing_canonical_sidecar(project, sidecar_path) do
if File.exists?(sidecar_path) do if File.exists?(sidecar_path) do
parse_canonical_sidecar(project, sidecar_path) parse_canonical_sidecar(project, sidecar_path)

View File

@@ -1757,7 +1757,7 @@ msgid "Open"
msgstr "Öffnen" msgstr "Öffnen"
#: lib/bds/desktop/menu_bar.ex:154 #: 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 #, elixir-autogen, elixir-format
msgid "Open Data Folder" msgid "Open Data Folder"
msgstr "Datenordner öffnen" msgstr "Datenordner öffnen"
@@ -2049,16 +2049,11 @@ msgid "Rebuild Database"
msgstr "Datenbank neu aufbauen" msgstr "Datenbank neu aufbauen"
#: lib/bds/desktop/menu_bar.ex:184 #: 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 #, elixir-autogen, elixir-format
msgid "Rebuild Embedding Index" msgid "Rebuild Embedding Index"
msgstr "Embedding-Index neu aufbauen" 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 #: lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex:375
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Rebuild Media From Files" msgid "Rebuild Media From Files"
@@ -2116,7 +2111,7 @@ msgstr "Übersetzung aktualisieren"
msgid "Regenerate Calendar" msgid "Regenerate Calendar"
msgstr "Kalender neu erzeugen" 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 #, elixir-autogen, elixir-format
msgid "Regenerate Missing Thumbnails" msgid "Regenerate Missing Thumbnails"
msgstr "Fehlende Vorschaubilder neu erzeugen" msgstr "Fehlende Vorschaubilder neu erzeugen"
@@ -3533,3 +3528,13 @@ msgstr "Vorgeschlagene Tags"
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Force Render Site" msgid "Force Render Site"
msgstr "Website vollständig neu generieren" 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"

View File

@@ -1757,7 +1757,7 @@ msgid "Open"
msgstr "" msgstr ""
#: lib/bds/desktop/menu_bar.ex:154 #: 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 #, elixir-autogen, elixir-format
msgid "Open Data Folder" msgid "Open Data Folder"
msgstr "" msgstr ""
@@ -2049,16 +2049,11 @@ msgid "Rebuild Database"
msgstr "" msgstr ""
#: lib/bds/desktop/menu_bar.ex:184 #: 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 #, elixir-autogen, elixir-format
msgid "Rebuild Embedding Index" msgid "Rebuild Embedding Index"
msgstr "" 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 #: lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex:375
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Rebuild Media From Files" msgid "Rebuild Media From Files"
@@ -2116,7 +2111,7 @@ msgstr ""
msgid "Regenerate Calendar" msgid "Regenerate Calendar"
msgstr "" 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 #, elixir-autogen, elixir-format
msgid "Regenerate Missing Thumbnails" msgid "Regenerate Missing Thumbnails"
msgstr "" msgstr ""
@@ -3533,3 +3528,13 @@ msgstr ""
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Force Render Site" msgid "Force Render Site"
msgstr "" 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 ""

View File

@@ -1757,7 +1757,7 @@ msgid "Open"
msgstr "Abrir" msgstr "Abrir"
#: lib/bds/desktop/menu_bar.ex:154 #: 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 #, elixir-autogen, elixir-format
msgid "Open Data Folder" msgid "Open Data Folder"
msgstr "Abrir carpeta de datos" msgstr "Abrir carpeta de datos"
@@ -2049,16 +2049,11 @@ msgid "Rebuild Database"
msgstr "Reconstruir base de datos" msgstr "Reconstruir base de datos"
#: lib/bds/desktop/menu_bar.ex:184 #: 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 #, elixir-autogen, elixir-format
msgid "Rebuild Embedding Index" msgid "Rebuild Embedding Index"
msgstr "Reconstruir índice de embeddings" 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 #: lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex:375
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Rebuild Media From Files" msgid "Rebuild Media From Files"
@@ -2116,7 +2111,7 @@ msgstr "Actualizar traducción"
msgid "Regenerate Calendar" msgid "Regenerate Calendar"
msgstr "Regenerar calendario" 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 #, elixir-autogen, elixir-format
msgid "Regenerate Missing Thumbnails" msgid "Regenerate Missing Thumbnails"
msgstr "Regenerar miniaturas faltantes" msgstr "Regenerar miniaturas faltantes"
@@ -3533,3 +3528,13 @@ msgstr "Etiquetas sugeridas"
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Force Render Site" msgid "Force Render Site"
msgstr "Forzar la regeneración del sitio" 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"

View File

@@ -1757,7 +1757,7 @@ msgid "Open"
msgstr "Ouvrir" msgstr "Ouvrir"
#: lib/bds/desktop/menu_bar.ex:154 #: 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 #, elixir-autogen, elixir-format
msgid "Open Data Folder" msgid "Open Data Folder"
msgstr "Ouvrir le dossier de données" msgstr "Ouvrir le dossier de données"
@@ -2049,16 +2049,11 @@ msgid "Rebuild Database"
msgstr "Reconstruire la base de données" msgstr "Reconstruire la base de données"
#: lib/bds/desktop/menu_bar.ex:184 #: 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 #, elixir-autogen, elixir-format
msgid "Rebuild Embedding Index" msgid "Rebuild Embedding Index"
msgstr "Reconstruire lindex dembeddings" msgstr "Reconstruire lindex dembeddings"
#: 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 #: lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex:375
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Rebuild Media From Files" msgid "Rebuild Media From Files"
@@ -2116,7 +2111,7 @@ msgstr "Actualiser la traduction"
msgid "Regenerate Calendar" msgid "Regenerate Calendar"
msgstr "Régénérer le calendrier" 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 #, elixir-autogen, elixir-format
msgid "Regenerate Missing Thumbnails" msgid "Regenerate Missing Thumbnails"
msgstr "Régénérer les vignettes manquantes" msgstr "Régénérer les vignettes manquantes"
@@ -3533,3 +3528,13 @@ msgstr "Tags suggérés"
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Force Render Site" msgid "Force Render Site"
msgstr "Forcer la régénération du 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"

View File

@@ -1757,7 +1757,7 @@ msgid "Open"
msgstr "Apri" msgstr "Apri"
#: lib/bds/desktop/menu_bar.ex:154 #: 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 #, elixir-autogen, elixir-format
msgid "Open Data Folder" msgid "Open Data Folder"
msgstr "Apri cartella dati" msgstr "Apri cartella dati"
@@ -2049,16 +2049,11 @@ msgid "Rebuild Database"
msgstr "Ricostruisci database" msgstr "Ricostruisci database"
#: lib/bds/desktop/menu_bar.ex:184 #: 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 #, elixir-autogen, elixir-format
msgid "Rebuild Embedding Index" msgid "Rebuild Embedding Index"
msgstr "Ricostruisci indice embeddings" 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 #: lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex:375
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Rebuild Media From Files" msgid "Rebuild Media From Files"
@@ -2116,7 +2111,7 @@ msgstr "Aggiorna traduzione"
msgid "Regenerate Calendar" msgid "Regenerate Calendar"
msgstr "Rigenera calendario" 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 #, elixir-autogen, elixir-format
msgid "Regenerate Missing Thumbnails" msgid "Regenerate Missing Thumbnails"
msgstr "Rigenera miniature mancanti" msgstr "Rigenera miniature mancanti"
@@ -3533,3 +3528,13 @@ msgstr "Tag suggeriti"
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Force Render Site" msgid "Force Render Site"
msgstr "Forza la rigenerazione del sito" 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"

View File

@@ -1770,7 +1770,7 @@ msgid "Open"
msgstr "" msgstr ""
#: lib/bds/desktop/menu_bar.ex:154 #: 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 #, elixir-autogen, elixir-format
msgid "Open Data Folder" msgid "Open Data Folder"
msgstr "" msgstr ""
@@ -2062,16 +2062,11 @@ msgid "Rebuild Database"
msgstr "" msgstr ""
#: lib/bds/desktop/menu_bar.ex:184 #: 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 #, elixir-autogen, elixir-format
msgid "Rebuild Embedding Index" msgid "Rebuild Embedding Index"
msgstr "" 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 #: lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex:375
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Rebuild Media From Files" msgid "Rebuild Media From Files"
@@ -2129,7 +2124,7 @@ msgstr ""
msgid "Regenerate Calendar" msgid "Regenerate Calendar"
msgstr "" 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 #, elixir-autogen, elixir-format
msgid "Regenerate Missing Thumbnails" msgid "Regenerate Missing Thumbnails"
msgstr "" msgstr ""
@@ -3546,3 +3541,13 @@ msgstr ""
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Force Render Site" msgid "Force Render Site"
msgstr "" 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 ""

View File

@@ -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

View File

@@ -69,4 +69,51 @@ defmodule BDS.Media.LinkRestoreTest do
assert {:ok, _} = BDS.Media.rebuild_media_from_files(project.id) assert {:ok, _} = BDS.Media.rebuild_media_from_files(project.id)
assert link_count(post.id, media.id) == 1 assert link_count(post.id, media.id) == 1
end 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 end