fix: issue #15 calendar index not properly created, also some smaller bugs
This commit is contained in:
@@ -1355,7 +1355,7 @@ defmodule BDS.Generation do
|
||||
["sitemap.xml"] ->
|
||||
:core
|
||||
|
||||
["feed.xml"] ->
|
||||
["rss.xml"] ->
|
||||
:core
|
||||
|
||||
["atom.xml"] ->
|
||||
|
||||
@@ -375,7 +375,7 @@ defmodule BDS.Generation.Outputs do
|
||||
main_static_outputs =
|
||||
[
|
||||
{"404.html", render_not_found_output(plan, language)},
|
||||
{"feed.xml", render_feed(plan, language, published_posts)},
|
||||
{"rss.xml", render_feed(plan, language, published_posts)},
|
||||
{"atom.xml", render_atom(plan, language, published_posts)},
|
||||
{"calendar.json", render_calendar(published_posts)}
|
||||
]
|
||||
@@ -399,7 +399,7 @@ defmodule BDS.Generation.Outputs do
|
||||
[
|
||||
{Path.join(localized_language, "404.html"),
|
||||
render_not_found_output(plan, localized_language)},
|
||||
{Path.join(localized_language, "feed.xml"),
|
||||
{Path.join(localized_language, "rss.xml"),
|
||||
render_feed(plan, localized_language, localized_source_posts)},
|
||||
{Path.join(localized_language, "atom.xml"),
|
||||
render_atom(plan, localized_language, localized_source_posts)}
|
||||
|
||||
@@ -237,7 +237,7 @@ defmodule BDS.Generation.Paths do
|
||||
|
||||
@spec sitemap_route_output?(String.t()) :: boolean()
|
||||
def sitemap_route_output?("404.html"), do: false
|
||||
def sitemap_route_output?("feed.xml"), do: false
|
||||
def sitemap_route_output?("rss.xml"), do: false
|
||||
def sitemap_route_output?("atom.xml"), do: false
|
||||
def sitemap_route_output?("calendar.json"), do: false
|
||||
def sitemap_route_output?(relative_path), do: String.ends_with?(relative_path, ".html")
|
||||
|
||||
@@ -212,13 +212,19 @@ defmodule BDS.Generation.Sitemap do
|
||||
"<feed><title>#{xml_escape(plan.project_name)} (#{xml_escape(language || "default")})</title>#{entries}</feed>"
|
||||
end
|
||||
|
||||
@doc "Render a JSON calendar of all published posts."
|
||||
@doc """
|
||||
Render the JSON calendar archive consumed by the calendar runtime:
|
||||
post counts keyed by year ("2024"), month ("2024-05"), and day ("2024-05-17").
|
||||
"""
|
||||
@spec render_calendar([map()]) :: String.t()
|
||||
def render_calendar(published_posts) do
|
||||
published_posts
|
||||
|> Enum.map(fn post ->
|
||||
%{date: Paths.local_date_iso8601!(post.created_at), slug: post.slug, title: post.title}
|
||||
end)
|
||||
day_keys = Enum.map(published_posts, &Paths.local_date_iso8601!(&1.created_at))
|
||||
|
||||
%{
|
||||
years: Enum.frequencies_by(day_keys, &binary_part(&1, 0, 4)),
|
||||
months: Enum.frequencies_by(day_keys, &binary_part(&1, 0, 7)),
|
||||
days: Enum.frequencies(day_keys)
|
||||
}
|
||||
|> Jason.encode!()
|
||||
end
|
||||
|
||||
|
||||
@@ -201,16 +201,21 @@ defmodule BDS.Preview do
|
||||
query_params: query_params
|
||||
)
|
||||
|
||||
:static ->
|
||||
serve_file(safe_join(Path.join(server.data_dir, "html"), relative_path),
|
||||
server: server,
|
||||
query_params: query_params
|
||||
)
|
||||
|
||||
:generated ->
|
||||
# Content is always rendered dynamically; generated files on
|
||||
# disk go stale and must never be served by the preview.
|
||||
case BDS.Preview.Router.render_route(server.project_id, request_path) do
|
||||
{:ok, response} ->
|
||||
{:ok, apply_response_overrides(response, query_params)}
|
||||
|
||||
:not_matched ->
|
||||
serve_file(safe_join(Path.join(server.data_dir, "html"), relative_path),
|
||||
server: server,
|
||||
query_params: query_params
|
||||
)
|
||||
render_not_found_response(server.project_id, query_params)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -319,6 +324,9 @@ defmodule BDS.Preview do
|
||||
match?(["media" | _], segments) ->
|
||||
{:ok, Path.join(tl(segments)), :media}
|
||||
|
||||
match?(["pagefind" | _], segments) ->
|
||||
{:ok, Path.join(segments), :static}
|
||||
|
||||
normalized == "/" ->
|
||||
{:ok, "index.html", :generated}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ defmodule BDS.Preview.Router do
|
||||
import Ecto.Query
|
||||
|
||||
alias BDS.Generation.Paths
|
||||
alias BDS.Generation.Sitemap
|
||||
alias BDS.MapUtils
|
||||
alias BDS.Metadata, as: ProjectMetadata
|
||||
alias BDS.Posts
|
||||
@@ -23,6 +24,9 @@ defmodule BDS.Preview.Router do
|
||||
| {:year, integer(), pos_integer()}
|
||||
| {:month, integer(), integer(), pos_integer()}
|
||||
| {:day, integer(), integer(), integer(), pos_integer()}
|
||||
| :calendar
|
||||
| :feed
|
||||
| :atom
|
||||
| :not_matched
|
||||
|
||||
@spec render_route(String.t(), String.t()) :: {:ok, map()} | :not_matched
|
||||
@@ -40,6 +44,28 @@ defmodule BDS.Preview.Router do
|
||||
:not_matched ->
|
||||
:not_matched
|
||||
|
||||
:calendar ->
|
||||
posts = load_published_list_posts(project_id, metadata)
|
||||
{:ok, %{content_type: "application/json", body: Sitemap.render_calendar(posts)}}
|
||||
|
||||
:feed ->
|
||||
posts = load_published_list_posts(project_id, metadata)
|
||||
|
||||
{:ok,
|
||||
%{
|
||||
content_type: "application/rss+xml",
|
||||
body: Sitemap.render_feed(feed_plan(metadata), effective_language, posts)
|
||||
}}
|
||||
|
||||
:atom ->
|
||||
posts = load_published_list_posts(project_id, metadata)
|
||||
|
||||
{:ok,
|
||||
%{
|
||||
content_type: "application/atom+xml",
|
||||
body: Sitemap.render_atom(feed_plan(metadata), effective_language, posts)
|
||||
}}
|
||||
|
||||
route ->
|
||||
case render(project_id, route, effective_language, main_language, metadata) do
|
||||
{:ok, body} ->
|
||||
@@ -53,6 +79,9 @@ defmodule BDS.Preview.Router do
|
||||
|
||||
@spec match_route([String.t()]) :: route()
|
||||
def match_route([]), do: {:home, 1}
|
||||
def match_route(["calendar.json"]), do: :calendar
|
||||
def match_route(["rss.xml"]), do: :feed
|
||||
def match_route(["atom.xml"]), do: :atom
|
||||
def match_route(["page", n]), do: {:home, parse_page(n)}
|
||||
|
||||
def match_route(["category", name]), do: {:category, URI.decode(name), 1}
|
||||
@@ -130,6 +159,14 @@ defmodule BDS.Preview.Router do
|
||||
|
||||
## Rendering
|
||||
|
||||
defp feed_plan(metadata) do
|
||||
%{
|
||||
base_url: metadata.public_url,
|
||||
project_name: metadata.name,
|
||||
language: metadata.main_language || "en"
|
||||
}
|
||||
end
|
||||
|
||||
defp render(project_id, {:home, page_number}, language, main_language, metadata) do
|
||||
posts = load_published_list_posts(project_id, metadata)
|
||||
posts = maybe_resolve_language(posts, language, main_language, project_id)
|
||||
|
||||
4
mix.exs
4
mix.exs
@@ -28,8 +28,8 @@ defmodule BDS.MixProject do
|
||||
|
||||
defp deps do
|
||||
[
|
||||
{:ecto_sql, "~> 3.13"},
|
||||
{:ecto_sqlite3, "~> 0.21"},
|
||||
{:ecto_sql, "~> 3.14"},
|
||||
{:ecto_sqlite3, "~> 0.24"},
|
||||
{:luerl, "~> 1.5"},
|
||||
{:jason, "~> 1.4"},
|
||||
{:mdex, "~> 0.13"},
|
||||
|
||||
14
mix.lock
14
mix.lock
@@ -9,16 +9,16 @@
|
||||
"complex": {:hex, :complex, "0.7.0", "695632ef9487517aa5d57edd1697801079d622414cb2e1a7cf538b1f9a50f205", [:mix], [], "hexpm", "0ee39c0803129f546e7f3f640da8f021c9e659402bf59da6f7f2c4848f068f8d"},
|
||||
"credo": {:hex, :credo, "1.7.19", "cc52129665fc7c15143d47838fda0f9cd6dac9ceced7bf4da6f85fcbfe64b12a", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "2d8bc95d5a7bb99dd2613621d4f08c6a3575c3fd4b62e6a2b48a100352a557b8"},
|
||||
"date_time_parser": {:hex, :date_time_parser, "1.3.0", "6ba16850b5ab83dd126576451023ab65349e29af2336ca5084aa1e37025b476e", [:mix], [{:kday, "~> 1.0", [hex: :kday, repo: "hexpm", optional: false]}], "hexpm", "93c8203a8ddc66b1f1531fc0e046329bf0b250c75ffa09567ef03d2c09218e8c"},
|
||||
"db_connection": {:hex, :db_connection, "2.9.0", "a6a97c5c958a2d7091a58a9be40caf41ab496b0701d21e1d1abff3fa27a7f371", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "17d502eacaf61829db98facf6f20808ed33da6ccf495354a41e64fe42f9c509c"},
|
||||
"db_connection": {:hex, :db_connection, "2.10.1", "d5465f6bcc125c1b8981c1dbf23c193ca16f446ec0b25832dc174f74f18be510", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "18ed94c6e627b4bf452dbd4df61b69a35a1e768525140bc1917b7a685026a6a3"},
|
||||
"dbus": {:hex, :dbus, "0.8.0", "7c800681f35d909c199265e55a8ee4aea9ebe4acccce77a0740f89f29cc57648", [:make], [], "hexpm", "a9784f2d9717ffa1f74169144a226c39633ac0d9c7fe8cb3594aeb89c827cca5"},
|
||||
"debouncer": {:hex, :debouncer, "0.1.13", "af5906b231c196943ac8386b5b5f45a2f36d54a8bcd7e1b29eef2671de33d287", [:mix], [], "hexpm", "a14f57420c7d4a287f8f08e715fc8759b5d28dcd1032f9585d57c45d22123382"},
|
||||
"decimal": {:hex, :decimal, "2.4.1", "6c0fbede12fb122ba685e9ab41c6a40c129e322b3aa192f9e072e61f3a6ffaf2", [:mix], [], "hexpm", "7e618897933a8455f19a727d7c5e50a2c071a544b700e5e724298ecb4340187f"},
|
||||
"decimal": {:hex, :decimal, "3.1.1", "430d87b04011ce6cbd4fd205be758311a81f87d552d40904abd00f015935b1d0", [:mix], [], "hexpm", "c5f25f2ced74a0587d03e6023f595db8e924c9d3922c8c8ffd9edfc4498cf1f6"},
|
||||
"desktop": {:hex, :desktop, "1.5.3", "dcf875dcff5b49a54646b4e6964acb079545c8c9c3790799aa5f1ccdcd314d15", [:mix], [{:debouncer, "~> 0.1", [hex: :debouncer, repo: "hexpm", optional: false]}, {:ex_sni, "~> 0.2", [hex: :ex_sni, repo: "hexpm", optional: false]}, {:gettext, "> 0.10.0", [hex: :gettext, repo: "hexpm", optional: false]}, {:oncrash, "~> 0.1", [hex: :oncrash, repo: "hexpm", optional: false]}, {:phoenix, "> 1.0.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_live_view, "> 0.15.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:plug, "> 1.0.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "3750aabb8ed8aaf09b33f3cad5bda20f8ce4dfa65b026c019baed99c5264e2aa"},
|
||||
"dialyxir": {:hex, :dialyxir, "1.4.7", "dda948fcee52962e4b6c5b4b16b2d8fa7d50d8645bbae8b8685c3f9ecb7f5f4d", [:mix], [{:erlex, ">= 0.2.8", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "b34527202e6eb8cee198efec110996c25c5898f43a4094df157f8d28f27d9efe"},
|
||||
"earmark_parser": {:hex, :earmark_parser, "1.4.45", "cba8369ab2a1342e419bc2760eec731b17be828941dcf494045d44766227e1d5", [:mix], [], "hexpm", "d3ec045bf122965db20c0bdb420e19ee1415843135327124918473feb4b328e8"},
|
||||
"ecto": {:hex, :ecto, "3.13.5", "9d4a69700183f33bf97208294768e561f5c7f1ecf417e0fa1006e4a91713a834", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "df9efebf70cf94142739ba357499661ef5dbb559ef902b68ea1f3c1fabce36de"},
|
||||
"ecto_sql": {:hex, :ecto_sql, "3.13.5", "2f8282b2ad97bf0f0d3217ea0a6fff320ead9e2f8770f810141189d182dc304e", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.13.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.7", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.19 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "aa36751f4e6a2b56ae79efb0e088042e010ff4935fc8684e74c23b1f49e25fdc"},
|
||||
"ecto_sqlite3": {:hex, :ecto_sqlite3, "0.22.0", "edab2d0f701b7dd05dcf7e2d97769c106aff62b5cfddc000d1dd6f46b9cbd8c3", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.13.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.13.0", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:exqlite, "~> 0.22", [hex: :exqlite, repo: "hexpm", optional: false]}], "hexpm", "5af9e031bffcc5da0b7bca90c271a7b1e7c04a93fecf7f6cd35bc1b1921a64bd"},
|
||||
"ecto": {:hex, :ecto, "3.14.0", "2fa64521eebfcb2670d907a86e4ad947290e9933706bb315e6fb5c21b172cb26", [:mix], [{:decimal, "~> 3.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "130d69ffb4285f9ce4792b65dfbb994fd13ea4cbc3cbea2524b199aa3de84af3"},
|
||||
"ecto_sql": {:hex, :ecto_sql, "3.14.0", "06446ab8410d2f85bfbb80857ee224ab3b693700cbb38f6535d507449a627b2e", [:mix], [{:db_connection, "~> 2.9", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 3.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.14.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.8", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.19 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "f4d8d36faf294c9417b5a37ec7ac8217ee2abdef5fcf197ba690f361548d3949"},
|
||||
"ecto_sqlite3": {:hex, :ecto_sqlite3, "0.24.1", "26665565f075aaf5f83a76a409ffdef4d2f0a2f3c6840bbf0995993fbea2ad32", [:mix], [{:decimal, "~> 3.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.14", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.14", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:exqlite, "~> 0.22", [hex: :exqlite, repo: "hexpm", optional: false]}], "hexpm", "681ca576c74a94944b962eeb7e0cf19aaea517decafd3213afb403ac8f4cd2e3"},
|
||||
"elixir_make": {:hex, :elixir_make, "0.9.0", "6484b3cd8c0cee58f09f05ecaf1a140a8c97670671a6a0e7ab4dc326c3109726", [:mix], [], "hexpm", "db23d4fd8b757462ad02f8aa73431a426fe6671c80b200d9710caf3d1dd0ffdb"},
|
||||
"emlx": {:hex, :emlx, "0.2.0", "f844c5456a8051032da98276f1e5c2282ff822824b139e4788af6e48375d0e1e", [:make, :mix], [{:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:nx, "~> 0.10", [hex: :nx, repo: "hexpm", optional: false]}], "hexpm", "24c674d716beca3daf422829ce7d5fc044981d3d0ca93a832a536016c543dd6f"},
|
||||
"erlex": {:hex, :erlex, "0.2.8", "cd8116f20f3c0afe376d1e8d1f0ae2452337729f68be016ea544a72f767d9c12", [:mix], [], "hexpm", "9d66ff9fedf69e49dc3fd12831e12a8a37b76f8651dd21cd45fcf5561a8a7590"},
|
||||
@@ -29,7 +29,7 @@
|
||||
"ex_stemmers": {:hex, :ex_stemmers, "0.1.0", "63a84ae3a6f0c28a1d75768411f0ae15cfe8462fb70589b60977aa1b04c9372d", [:mix], [{:rustler, "~> 0.32.1", [hex: :rustler, repo: "hexpm", optional: false]}], "hexpm", "498826e2188e502f41d1a15f3d90e7738f0d94747e197367f03a2a44c09167c0"},
|
||||
"exla": {:hex, :exla, "0.10.0", "93e7d75a774fbc06ce05b96de20c4b01bda413b315238cb3c727c09a05d2bc3a", [:make, :mix], [{:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:fine, "~> 0.1.0", [hex: :fine, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.0", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:nx, "~> 0.10.0", [hex: :nx, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:xla, "~> 0.9.0", [hex: :xla, repo: "hexpm", optional: false]}], "hexpm", "16fffdb64667d7f0a3bc683fdcd2792b143a9b345e4b1f1d5cd50330c63d8119"},
|
||||
"expo": {:hex, :expo, "1.1.1", "4202e1d2ca6e2b3b63e02f69cfe0a404f77702b041d02b58597c00992b601db5", [:mix], [], "hexpm", "5fb308b9cb359ae200b7e23d37c76978673aa1b06e2b3075d814ce12c5811640"},
|
||||
"exqlite": {:hex, :exqlite, "0.36.0", "07b4f95d61cb82b8d52946d0639497fa7d32117e09b2c8d25e24a38723c295cb", [:make, :mix], [{:cc_precompiler, "~> 0.1", [hex: :cc_precompiler, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.8", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "cbeca3ce781f9ff07cfa9a87486f3ebd512a143ad6a14ed5c9fca21fe0bf3ae7"},
|
||||
"exqlite": {:hex, :exqlite, "0.38.0", "324bca1d9b26d34b02ea64bd3724302a7d11978c682bc4a945186aeae7f61adb", [:make, :mix], [{:cc_precompiler, "~> 0.1", [hex: :cc_precompiler, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.8", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "f3da7b6e7b08bd548c33a118890d0eb8c5395fe093b31c8b329663234d0e988e"},
|
||||
"file_system": {:hex, :file_system, "1.1.1", "31864f4685b0148f25bd3fbef2b1228457c0c89024ad67f7a81a3ffbc0bbad3a", [:mix], [], "hexpm", "7a15ff97dfe526aeefb090a7a9d3d03aa907e100e262a0f8f7746b78f8f87a5d"},
|
||||
"finch": {:hex, :finch, "0.22.0", "5c48fa6f9706a78eb9036cacb67b8b996b4e66d111c543f4c29bb0f879a6806b", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.8", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.1", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b94e83c47780fc6813f746a1f1a34ee65cda42da4c5ea26a68f0acc4498e23dc"},
|
||||
"fine": {:hex, :fine, "0.1.6", "4bf7151493443c454aac9f2fa2f34f5fefd0346a83fb5586a016c4a135c63247", [:mix], [], "hexpm", "5638eb4495488e885ebec167fa57973e5c35e1a50c344eb7666c90ec1c4e3b12"},
|
||||
@@ -68,7 +68,7 @@
|
||||
"plug": {:hex, :plug, "1.19.2", "e4950525b22c6789dfb38a3f95d47171ba159da3fc5a33be9643b43d5e8adb98", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b6fce20a56af5e60fa5dfecf3f907bb98ec981be43c79a3809a499bc3d133de0"},
|
||||
"plug_crypto": {:hex, :plug_crypto, "2.1.1", "19bda8184399cb24afa10be734f84a16ea0a2bc65054e23a62bb10f06bc89491", [:mix], [], "hexpm", "6470bce6ffe41c8bd497612ffde1a7e4af67f36a15eea5f921af71cf3e11247c"},
|
||||
"polaris": {:hex, :polaris, "0.1.0", "dca61b18e3e801ecdae6ac9f0eca5f19792b44a5cb4b8d63db50fc40fc038d22", [:mix], [{:nx, "~> 0.5", [hex: :nx, repo: "hexpm", optional: false]}], "hexpm", "13ef2b166650e533cb24b10e2f3b8ab4f2f449ba4d63156e8c569527f206e2c2"},
|
||||
"progress_bar": {:hex, :progress_bar, "3.0.0", "f54ff038c2ac540cfbb4c2bfe97c75e7116ead044f3c2b10c9f212452194b5cd", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}], "hexpm", "6981c2b25ab24aecc91a2dc46623658e1399c21a2ae24db986b90d678530f2b7"},
|
||||
"progress_bar": {:hex, :progress_bar, "3.1.0", "d277d8f58f90df41ec69adb77027881f817827283b45007ce21b5c77ebe4541e", [:mix], [], "hexpm", "72e8c5f1657b610891de01fb20e8ba7b07a327ca5a10358946f0cd54db5eab34"},
|
||||
"req": {:hex, :req, "0.6.1", "7b904c8b42d0e08136a5c6aba024fd12fc79a1ed8856e7a3522b0917f7e75113", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.21.0 or ~> 0.22.0", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 2.0.6 or ~> 2.1", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "aaf11c9c80f2df2364630b3594e1857fe610d8ea7cb994e1ce3dcb55f204ff1c"},
|
||||
"rustler": {:hex, :rustler, "0.32.1", "f4cf5a39f9e85d182c0a3f75fa15b5d0add6542ab0bf9ceac6b4023109ebd3fc", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:toml, "~> 0.6", [hex: :toml, repo: "hexpm", optional: false]}], "hexpm", "b96be75526784f86f6587f051bc8d6f4eaff23d6e0f88dbcfe4d5871f52946f7"},
|
||||
"rustler_precompiled": {:hex, :rustler_precompiled, "0.9.0", "3a052eda09f3d2436364645cc1f13279cf95db310eb0c17b0d8f25484b233aa0", [:mix], [{:rustler, "~> 0.23", [hex: :rustler, repo: "hexpm", optional: true]}], "hexpm", "471d97315bd3bf7b64623418b3693eedd8e47de3d1cb79a0ac8f9da7d770d94c"},
|
||||
|
||||
@@ -136,8 +136,8 @@ rule GenerateCoreSectionPages {
|
||||
ensures: FileGenerated("index.html")
|
||||
ensures: FileGenerated("sitemap.xml")
|
||||
-- Multi-language sitemap with hreflang alternates
|
||||
ensures: FileGenerated("feed.xml")
|
||||
-- RSS 2.0 feed
|
||||
ensures: FileGenerated("rss.xml")
|
||||
-- RSS 2.0 feed (same output name as the old bDS app; templates link /rss.xml)
|
||||
ensures: FileGenerated("atom.xml")
|
||||
-- Atom feed
|
||||
ensures: FileGenerated("calendar.json")
|
||||
@@ -146,7 +146,7 @@ rule GenerateCoreSectionPages {
|
||||
-- Not-found page rendered from the not-found template
|
||||
for lang in generation.blog_languages - {generation.language}:
|
||||
ensures: FileGenerated(format("{lang}/index.html", lang: lang))
|
||||
ensures: FileGenerated(format("{lang}/feed.xml", lang: lang))
|
||||
ensures: FileGenerated(format("{lang}/rss.xml", lang: lang))
|
||||
ensures: FileGenerated(format("{lang}/atom.xml", lang: lang))
|
||||
ensures: FileGenerated(format("{lang}/404.html", lang: lang))
|
||||
}
|
||||
|
||||
53
test/bds/generation/calendar_json_test.exs
Normal file
53
test/bds/generation/calendar_json_test.exs
Normal file
@@ -0,0 +1,53 @@
|
||||
defmodule BDS.Generation.CalendarJsonTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
alias BDS.Generation.Paths
|
||||
alias BDS.Generation.Sitemap
|
||||
|
||||
defp unix_ms(date_string) do
|
||||
# Local-noon timestamp so the local-time date matches date_string regardless of TZ.
|
||||
date = Date.from_iso8601!(date_string)
|
||||
naive = NaiveDateTime.new!(date, ~T[12:00:00])
|
||||
:calendar.local_time_to_universal_time_dst(NaiveDateTime.to_erl(naive))
|
||||
|> List.first()
|
||||
|> :calendar.datetime_to_gregorian_seconds()
|
||||
|> Kernel.-(62_167_219_200)
|
||||
|> Kernel.*(1000)
|
||||
end
|
||||
|
||||
defp post(date_string, slug) do
|
||||
%{created_at: unix_ms(date_string), slug: slug, title: slug}
|
||||
end
|
||||
|
||||
test "render_calendar emits years/months/days count maps for the calendar runtime" do
|
||||
posts = [
|
||||
post("2024-05-17", "a"),
|
||||
post("2024-05-17", "b"),
|
||||
post("2024-06-01", "c"),
|
||||
post("2023-12-31", "d")
|
||||
]
|
||||
|
||||
decoded = posts |> Sitemap.render_calendar() |> Jason.decode!()
|
||||
|
||||
assert decoded == %{
|
||||
"years" => %{"2024" => 3, "2023" => 1},
|
||||
"months" => %{"2024-05" => 2, "2024-06" => 1, "2023-12" => 1},
|
||||
"days" => %{
|
||||
"2024-05-17" => 2,
|
||||
"2024-06-01" => 1,
|
||||
"2023-12-31" => 1
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
test "render_calendar of no posts emits empty count maps" do
|
||||
assert Sitemap.render_calendar([]) |> Jason.decode!() ==
|
||||
%{"years" => %{}, "months" => %{}, "days" => %{}}
|
||||
end
|
||||
|
||||
test "day keys use the local date used for post routes" do
|
||||
ts = unix_ms("2024-05-17")
|
||||
decoded = Sitemap.render_calendar([%{created_at: ts, slug: "a", title: "a"}]) |> Jason.decode!()
|
||||
assert Map.keys(decoded["days"]) == [Paths.local_date_iso8601!(ts)]
|
||||
end
|
||||
end
|
||||
@@ -103,7 +103,7 @@ defmodule BDS.GenerationTest do
|
||||
"404.html",
|
||||
"index.html",
|
||||
"sitemap.xml",
|
||||
"feed.xml",
|
||||
"rss.xml",
|
||||
"atom.xml",
|
||||
"calendar.json",
|
||||
"pagefind/index.json",
|
||||
@@ -111,7 +111,7 @@ defmodule BDS.GenerationTest do
|
||||
"pagefind/pagefind-ui.js",
|
||||
"de/404.html",
|
||||
"de/index.html",
|
||||
"de/feed.xml",
|
||||
"de/rss.xml",
|
||||
"de/atom.xml",
|
||||
"de/pagefind/index.json",
|
||||
"de/pagefind/pagefind-ui.css",
|
||||
@@ -214,7 +214,7 @@ defmodule BDS.GenerationTest do
|
||||
"https://example.com/blog" <>
|
||||
"/" <> String.trim_trailing(BDS.Generation.post_output_path(published_post), "index.html")
|
||||
|
||||
feed_xml = File.read!(Path.join([temp_dir, "html", "feed.xml"]))
|
||||
feed_xml = File.read!(Path.join([temp_dir, "html", "rss.xml"]))
|
||||
atom_xml = File.read!(Path.join([temp_dir, "html", "atom.xml"]))
|
||||
|
||||
assert feed_xml =~ "<rss>"
|
||||
@@ -854,12 +854,13 @@ defmodule BDS.GenerationTest do
|
||||
|
||||
assert expected_paths -- Enum.map(result.generated_files, & &1.relative_path) == []
|
||||
|
||||
# Archives paginate newest-first (issue #14), so page 1 holds the newest posts.
|
||||
assert File.read!(Path.join([temp_dir, "html", "category", "notes", "index.html"])) =~
|
||||
"Archive 1"
|
||||
"Archive 3"
|
||||
|
||||
assert File.read!(
|
||||
Path.join([temp_dir, "html", "category", "notes", "page", "2", "index.html"])
|
||||
) =~ "Archive 3"
|
||||
) =~ "Archive 1"
|
||||
|
||||
assert File.read!(Path.join([temp_dir, "html", "tag", "Elixir", "index.html"])) =~ "Elixir"
|
||||
assert File.read!(Path.join([temp_dir, "html", "2026", "04", "index.html"])) =~ "2026-04"
|
||||
@@ -936,7 +937,7 @@ defmodule BDS.GenerationTest do
|
||||
assert {:ok, _result} =
|
||||
BDS.Generation.generate_site(project.id, [:core, :single, :category, :tag, :date])
|
||||
|
||||
feed_path = Path.join([temp_dir, "html", "feed.xml"])
|
||||
feed_path = Path.join([temp_dir, "html", "rss.xml"])
|
||||
atom_path = Path.join([temp_dir, "html", "atom.xml"])
|
||||
not_found_path = Path.join([temp_dir, "html", "404.html"])
|
||||
pagefind_path = Path.join([temp_dir, "html", "pagefind", "index.json"])
|
||||
@@ -1146,7 +1147,7 @@ defmodule BDS.GenerationTest do
|
||||
|
||||
# Ancillary artifacts are all present.
|
||||
assert File.exists?(Path.join([temp_dir, "html", "sitemap.xml"]))
|
||||
assert File.exists?(Path.join([temp_dir, "html", "feed.xml"]))
|
||||
assert File.exists?(Path.join([temp_dir, "html", "rss.xml"]))
|
||||
assert File.exists?(Path.join([temp_dir, "html", "calendar.json"]))
|
||||
assert File.exists?(Path.join([temp_dir, "html", "pagefind", "index.json"]))
|
||||
|
||||
|
||||
@@ -170,6 +170,87 @@ defmodule BDS.PreviewTest do
|
||||
assert :ok = BDS.Preview.stop_preview(project.id)
|
||||
end
|
||||
|
||||
test "preview renders calendar.json dynamically with count maps, ignoring stale files",
|
||||
%{project: project, temp_dir: temp_dir} do
|
||||
assert {:ok, post} =
|
||||
Posts.create_post(%{
|
||||
project_id: project.id,
|
||||
title: "Calendar Post",
|
||||
content: "Calendar body",
|
||||
language: "en"
|
||||
})
|
||||
|
||||
assert {:ok, _published} = Posts.publish_post(post.id)
|
||||
|
||||
# A stale calendar.json in the old (array) format must not be served.
|
||||
File.mkdir_p!(Path.join(temp_dir, "html"))
|
||||
File.write!(Path.join([temp_dir, "html", "calendar.json"]), ~s([{"date":"2000-01-01"}]))
|
||||
|
||||
assert {:ok, _server} = BDS.Preview.start_preview(project.id)
|
||||
|
||||
assert {:ok, %{body: body, content_type: "application/json"}} =
|
||||
BDS.Preview.request(project.id, "/calendar.json")
|
||||
|
||||
decoded = Jason.decode!(body)
|
||||
day_key = BDS.Generation.Paths.local_date_iso8601!(Posts.get_post!(post.id).created_at)
|
||||
|
||||
assert decoded["days"] == %{day_key => 1}
|
||||
assert decoded["months"] == %{binary_part(day_key, 0, 7) => 1}
|
||||
assert decoded["years"] == %{binary_part(day_key, 0, 4) => 1}
|
||||
|
||||
assert :ok = BDS.Preview.stop_preview(project.id)
|
||||
end
|
||||
|
||||
test "preview renders content dynamically and never serves generated content files from disk",
|
||||
%{project: project, temp_dir: temp_dir} do
|
||||
assert {:ok, _metadata} =
|
||||
Metadata.update_project_metadata(project.id, %{
|
||||
public_url: "https://example.com/blog",
|
||||
main_language: "en"
|
||||
})
|
||||
|
||||
assert {:ok, post} =
|
||||
Posts.create_post(%{
|
||||
project_id: project.id,
|
||||
title: "Dynamic Feed Post",
|
||||
content: "Feed body",
|
||||
language: "en"
|
||||
})
|
||||
|
||||
assert {:ok, _published} = Posts.publish_post(post.id)
|
||||
|
||||
# Stale generated content files must never be served.
|
||||
html_dir = Path.join(temp_dir, "html")
|
||||
File.mkdir_p!(Path.join(html_dir, "pagefind"))
|
||||
File.write!(Path.join(html_dir, "rss.xml"), "<rss>stale feed</rss>")
|
||||
File.write!(Path.join(html_dir, "atom.xml"), "<feed>stale atom</feed>")
|
||||
File.write!(Path.join(html_dir, "sitemap.xml"), "<urlset>stale</urlset>")
|
||||
File.write!(Path.join([html_dir, "pagefind", "pagefind-ui.js"]), "console.log('pf')")
|
||||
|
||||
assert {:ok, _server} = BDS.Preview.start_preview(project.id)
|
||||
|
||||
assert {:ok, %{body: feed_xml, content_type: "application/rss+xml"}} =
|
||||
BDS.Preview.request(project.id, "/rss.xml")
|
||||
|
||||
assert feed_xml =~ "Dynamic Feed Post"
|
||||
refute feed_xml =~ "stale feed"
|
||||
|
||||
assert {:ok, %{body: atom_xml, content_type: "application/atom+xml"}} =
|
||||
BDS.Preview.request(project.id, "/atom.xml")
|
||||
|
||||
assert atom_xml =~ "Dynamic Feed Post"
|
||||
refute atom_xml =~ "stale atom"
|
||||
|
||||
# Content files without a dynamic route render the 404 page instead.
|
||||
assert {:ok, %{status: 404}} = BDS.Preview.request(project.id, "/sitemap.xml")
|
||||
|
||||
# Generated static assets (pagefind search index) still come from disk.
|
||||
assert {:ok, %{body: "console.log('pf')", content_type: "application/javascript"}} =
|
||||
BDS.Preview.request(project.id, "/pagefind/pagefind-ui.js")
|
||||
|
||||
assert :ok = BDS.Preview.stop_preview(project.id)
|
||||
end
|
||||
|
||||
test "draft preview renders through the published post template", %{project: project} do
|
||||
assert {:ok, template} =
|
||||
BDS.Templates.create_template(%{
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
Application.ensure_all_started(:ecto_sql)
|
||||
Application.ensure_all_started(:exqlite)
|
||||
{:ok, _} = BDS.Repo.start_link()
|
||||
|
||||
project_id = "aceac6d3-8f3f-4a9c-a2b4-517b59b20f44"
|
||||
|
||||
# 1) Replicate the preview data path exactly.
|
||||
import Ecto.Query
|
||||
alias BDS.Posts.Post
|
||||
|
||||
posts =
|
||||
BDS.Repo.all(
|
||||
from p in Post,
|
||||
where: p.project_id == ^project_id and p.status in [:published, :draft],
|
||||
order_by: [desc: p.created_at, desc: p.published_at, asc: p.slug]
|
||||
)
|
||||
|
||||
picture = Enum.filter(posts, fn p -> "picture" in (p.categories || []) end)
|
||||
|
||||
IO.puts("== preview data path, first 5 (expect newest) ==")
|
||||
picture |> Enum.take(5) |> Enum.each(fn p -> IO.puts(" #{p.slug} #{p.created_at} #{p.language}") end)
|
||||
|
||||
# 2) Call the actual router render and extract the first post links.
|
||||
case BDS.Preview.Router.render_route(project_id, "/category/picture") do
|
||||
{:ok, %{body: body}} ->
|
||||
body = IO.iodata_to_binary(body)
|
||||
|
||||
links =
|
||||
Regex.scan(~r{href="[^"]*/20\d\d/\d\d/[^"]*"}, body)
|
||||
|> Enum.map(&hd/1)
|
||||
|> Enum.take(6)
|
||||
|
||||
IO.puts("== render_route /category/picture, first link hrefs ==")
|
||||
Enum.each(links, &IO.puts(" #{&1}"))
|
||||
|
||||
other ->
|
||||
IO.inspect(other, label: "render_route returned")
|
||||
end
|
||||
Reference in New Issue
Block a user