From 5511c05192567d786909d67c943ec1fad9dac94b Mon Sep 17 00:00:00 2001 From: Chili Palmer Date: Sat, 25 Apr 2026 14:41:47 +0200 Subject: [PATCH] fix: rebuilding posts and media fixed, also removed unnecessary thumbnail rebuild Co-authored-by: Copilot --- lib/bds/frontmatter.ex | 35 +++++++- lib/bds/media.ex | 1 - test/bds/media_test.exs | 40 +++++++++- test/bds/posts_test.exs | 79 +++++++++++++++++++ .../bds/real_blog_rebuild_diagnostic_test.exs | 46 +++++++++++ 5 files changed, 198 insertions(+), 3 deletions(-) create mode 100644 test/bds/real_blog_rebuild_diagnostic_test.exs diff --git a/lib/bds/frontmatter.ex b/lib/bds/frontmatter.ex index 72c7c33..eae9572 100644 --- a/lib/bds/frontmatter.ex +++ b/lib/bds/frontmatter.ex @@ -4,6 +4,7 @@ defmodule BDS.Frontmatter do alias BDS.Persistence @list_item_prefix " - " + @block_scalar_indent " " def serialize_document(fields, body) when is_list(fields) do frontmatter = @@ -82,7 +83,14 @@ defmodule BDS.Frontmatter do String.contains?(line, ": ") -> [key, raw_value] = String.split(line, ": ", parts: 2) - parse_lines(rest, Map.put(acc, key, parse_scalar(key, raw_value))) + + if block_scalar_indicator?(raw_value) do + {value_lines, remaining} = take_block_scalar_lines(rest, []) + value = parse_scalar(key, parse_block_scalar(raw_value, value_lines)) + parse_lines(remaining, Map.put(acc, key, value)) + else + parse_lines(rest, Map.put(acc, key, parse_scalar(key, raw_value))) + end true -> parse_lines(rest, acc) @@ -100,6 +108,16 @@ defmodule BDS.Frontmatter do defp take_list_items([], items), do: {items, []} + defp take_block_scalar_lines([line | rest], lines) do + if String.starts_with?(line, @block_scalar_indent) do + take_block_scalar_lines(rest, [String.replace_prefix(line, @block_scalar_indent, "") | lines]) + else + {Enum.reverse(lines), [line | rest]} + end + end + + defp take_block_scalar_lines([], lines), do: {Enum.reverse(lines), []} + defp parse_scalar(key, value) when is_binary(key) and is_binary(value) do trimmed = String.trim(value) parsed = parse_generic_scalar(trimmed) @@ -153,6 +171,21 @@ defmodule BDS.Frontmatter do defp parse_string(value), do: value + defp block_scalar_indicator?(value) do + trimmed = String.trim(value) + String.starts_with?(trimmed, ">") or String.starts_with?(trimmed, "|") + end + + defp parse_block_scalar(indicator, lines) do + trimmed = String.trim(indicator) + + cond do + String.starts_with?(trimmed, ">") -> Enum.join(lines, " ") + String.starts_with?(trimmed, "|") -> Enum.join(lines, "\n") + true -> Enum.join(lines, "\n") + end + end + defp serialize_scalar(_key, value) when is_boolean(value) do if(value, do: "true", else: "false") end diff --git a/lib/bds/media.ex b/lib/bds/media.ex index 4fa6c92..1c9c566 100644 --- a/lib/bds/media.ex +++ b/lib/bds/media.ex @@ -393,7 +393,6 @@ defmodule BDS.Media do media |> Media.changeset(attrs) |> Repo.insert_or_update!() - |> tap(fn reloaded_media -> ensure_thumbnails(project, reloaded_media) end) |> tap(&Search.sync_media/1) end diff --git a/test/bds/media_test.exs b/test/bds/media_test.exs index 580ba9b..b6da1d7 100644 --- a/test/bds/media_test.exs +++ b/test/bds/media_test.exs @@ -196,7 +196,7 @@ defmodule BDS.MediaTest do thumbnail_paths = BDS.Media.thumbnail_paths(media) Enum.each(Map.values(thumbnail_paths), fn path -> - assert File.exists?(Path.join(temp_dir, path)) + refute File.exists?(Path.join(temp_dir, path)) end) end @@ -240,6 +240,44 @@ defmodule BDS.MediaTest do assert media.updated_at == 1_773_847_870_146 end + test "rebuild_media_from_files does not regenerate thumbnails", %{ + project: project, + temp_dir: temp_dir + } do + media_dir = Path.join([temp_dir, "media", "2026", "04"]) + File.mkdir_p!(media_dir) + + binary_path = Path.join(media_dir, "asset.jpg") + sidecar_path = binary_path <> ".meta" + + File.write!(binary_path, tiny_jpeg_binary()) + + File.write!( + sidecar_path, + [ + "id: media-without-thumbnails", + "originalName: original.jpg", + "mimeType: image/jpeg", + "size: #{byte_size(tiny_jpeg_binary())}", + "width: 3", + "height: 2", + "title: Recovered", + "createdAt: 2024-03-30T21:20:00.000Z", + "updatedAt: 2024-03-31T21:20:00.000Z", + "tags:", + " - alpha", + "" + ] + |> Enum.join("\n") + ) + + assert {:ok, [media]} = BDS.Media.rebuild_media_from_files(project.id) + + Enum.each(Map.values(BDS.Media.thumbnail_paths(media)), fn path -> + refute File.exists?(Path.join(temp_dir, path)) + end) + end + test "import_media generates the four thumbnail files in bucketed thumbnail paths", %{ project: project, temp_dir: temp_dir diff --git a/test/bds/posts_test.exs b/test/bds/posts_test.exs index 3ecd008..dd732a1 100644 --- a/test/bds/posts_test.exs +++ b/test/bds/posts_test.exs @@ -429,6 +429,85 @@ defmodule BDS.PostsTest do assert post.published_at == 1_036_497_600_000 end + test "rebuild_posts_from_files parses folded multiline title and slug scalars alongside translations" do + temp_dir = + Path.join(System.tmp_dir!(), "bds-post-rebuild-folded-#{System.unique_integer([:positive])}") + + File.mkdir_p!(temp_dir) + on_exit(fn -> File.rm_rf(temp_dir) end) + + assert {:ok, project} = + BDS.Projects.create_project(%{name: "Folded Rebuild", data_path: temp_dir}) + + posts_dir = Path.join([BDS.Projects.project_data_dir(project), "posts", "2010", "11"]) + File.mkdir_p!(posts_dir) + + File.write!( + Path.join( + posts_dir, + "introducing-thirty-ten-my-guide-to-creating-a-twenty-ten-child-theme-aaron-jorb-inaaron-jorb-in.md" + ), + [ + "---", + "id: 66865ffe-c6d9-4379-a031-0581f33b68b4", + "title: >-", + " Introducing Thirty Ten, my guide to creating a Twenty Ten Child Theme |", + " aaron.jorb.inaaron.jorb.in", + "slug: >-", + " introducing-thirty-ten-my-guide-to-creating-a-twenty-ten-child-theme-aaron-jorb-inaaron-jorb-in", + "status: published", + "createdAt: '2010-11-13T12:37:55.000Z'", + "updatedAt: '2026-03-03T12:31:37.661Z'", + "tags:", + " - wordpress", + "categories:", + " - aside", + "author: hugo", + "language: de", + "publishedAt: '2010-11-13T11:37:55.000Z'", + "---", + "Quelle", + "" + ] + |> Enum.join("\n") + ) + + File.write!( + Path.join( + posts_dir, + "introducing-thirty-ten-my-guide-to-creating-a-twenty-ten-child-theme-aaron-jorb-inaaron-jorb-in.en.md" + ), + [ + "---", + "translationFor: 66865ffe-c6d9-4379-a031-0581f33b68b4", + "language: en", + "title: >-", + " Introducing Thirty Ten, my guide to creating a Twenty Ten Child Theme |", + " aaron.jorb.inaaron.jorb.in", + "---", + "Translated body", + "" + ] + |> Enum.join("\n") + ) + + assert {:ok, [post]} = BDS.Posts.rebuild_posts_from_files(project.id) + assert post.id == "66865ffe-c6d9-4379-a031-0581f33b68b4" + + assert post.slug == + "introducing-thirty-ten-my-guide-to-creating-a-twenty-ten-child-theme-aaron-jorb-inaaron-jorb-in" + + assert post.title == + "Introducing Thirty Ten, my guide to creating a Twenty Ten Child Theme | aaron.jorb.inaaron.jorb.in" + + assert {:ok, [translation]} = BDS.Posts.list_post_translations(post.id) + + assert translation.translation_for == post.id + + assert translation.title == + "Introducing Thirty Ten, my guide to creating a Twenty Ten Child Theme | aaron.jorb.inaaron.jorb.in" + end + defp errors_on(changeset) do Ecto.Changeset.traverse_errors(changeset, fn {message, opts} -> Regex.replace(~r"%{(\w+)}", message, fn _, key -> diff --git a/test/bds/real_blog_rebuild_diagnostic_test.exs b/test/bds/real_blog_rebuild_diagnostic_test.exs new file mode 100644 index 0000000..fb8664b --- /dev/null +++ b/test/bds/real_blog_rebuild_diagnostic_test.exs @@ -0,0 +1,46 @@ +defmodule BDS.RealBlogRebuildDiagnosticTest do + use ExUnit.Case, async: false + + @real_blog_path System.get_env("BDS_REAL_BLOG_PATH") + @moduletag timeout: :infinity + + if is_binary(@real_blog_path) and @real_blog_path != "" do + setup do + :ok = Ecto.Adapters.SQL.Sandbox.checkout(BDS.Repo, ownership_timeout: 600_000) + + now = BDS.Persistence.now_ms() + unique = Integer.to_string(System.unique_integer([:positive])) + + project = + %BDS.Projects.Project{} + |> BDS.Projects.Project.changeset(%{ + id: "real-blog-#{unique}", + name: "Real Blog Diagnostic", + slug: "real-blog-diagnostic-#{unique}", + data_path: Path.expand(@real_blog_path), + created_at: now, + updated_at: now, + is_active: false + }) + |> BDS.Repo.insert!() + + %{project: project} + end + + test "rebuilds posts from the external real blog path", %{project: project} do + assert {:ok, _posts} = BDS.Maintenance.rebuild_from_filesystem(project.id, "post") + end + + test "rebuilds media from the external real blog path", %{project: project} do + assert {:ok, _media} = BDS.Maintenance.rebuild_from_filesystem(project.id, "media") + end + else + @tag skip: "BDS_REAL_BLOG_PATH not set" + test "rebuilds posts from the external real blog path" do + end + + @tag skip: "BDS_REAL_BLOG_PATH not set" + test "rebuilds media from the external real blog path" do + end + end +end \ No newline at end of file