diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl index aec5ab7..70fdefa 100644 --- a/.beads/issues.jsonl +++ b/.beads/issues.jsonl @@ -1,4 +1,4 @@ -{"id":"firehose-1h8","title":"Verify feeds exclude future-dated posts","description":"## Context\nRSS/Atom feeds call blog.all_posts() which should now filter by date (from Step 1).\nAdd explicit tests confirming feeds exclude future-dated published posts.\n\n## Scope\n- blogex/test/blogex/feed_test.exs\n\n## TDD\nRED: Test RSS and Atom feeds exclude future-dated published posts\nGREEN: Should already pass from Step 1 changes\nREFACTOR: None","status":"in_progress","priority":2,"issue_type":"task","owner":"willem+gitea@livingsoftware.co.uk","created_at":"2026-04-01T20:07:16.213785081Z","created_by":"Willem van den Ende","updated_at":"2026-04-01T20:32:40.516091483Z","dependencies":[{"issue_id":"firehose-1h8","depends_on_id":"firehose-2wc","type":"blocks","created_at":"2026-04-01T20:07:52.701493058Z","created_by":"Willem van den Ende"}]} +{"id":"firehose-1h8","title":"Verify feeds exclude future-dated posts","description":"## Context\nRSS/Atom feeds call blog.all_posts() which should now filter by date (from Step 1).\nAdd explicit tests confirming feeds exclude future-dated published posts.\n\n## Scope\n- blogex/test/blogex/feed_test.exs\n\n## TDD\nRED: Test RSS and Atom feeds exclude future-dated published posts\nGREEN: Should already pass from Step 1 changes\nREFACTOR: None","status":"closed","priority":2,"issue_type":"task","owner":"willem+gitea@livingsoftware.co.uk","created_at":"2026-04-01T20:07:16.213785081Z","created_by":"Willem van den Ende","updated_at":"2026-04-01T20:38:37.480901856Z","closed_at":"2026-04-01T20:38:37.480901856Z","close_reason":"Closed","dependencies":[{"issue_id":"firehose-1h8","depends_on_id":"firehose-2wc","type":"blocks","created_at":"2026-04-01T20:07:52.701493058Z","created_by":"Willem van den Ende"}]} {"id":"firehose-1x3","title":"Make get_post/get_post! search all compiled posts (unfiltered)","description":"## Context\nget_post/1 and get_post!/1 currently search all_posts() (filtered). Change to search @posts (unfiltered)\nso direct URL access works for draft and scheduled posts. Enables preview links for reviewers.\n\n## Scope\n- blogex/lib/blogex/blog.ex: get_post/1, get_post!/1\n- blogex/test/support/fake_blog.ex: get_post/1, get_post!/1\n- blogex/test/blogex/blog_test.exs: update existing tests, add new ones\n\n## TDD\nRED: Test get_post! returns future-dated post, get_post returns draft post\nGREEN: Search @posts instead of all_posts()\nREFACTOR: Update existing test that expects get_post!(\"draft-post\") to raise","status":"in_progress","priority":2,"issue_type":"task","owner":"willem+gitea@livingsoftware.co.uk","created_at":"2026-04-01T20:07:04.676875214Z","created_by":"Willem van den Ende","updated_at":"2026-04-01T20:32:40.476647693Z","dependencies":[{"issue_id":"firehose-1x3","depends_on_id":"firehose-2wc","type":"blocks","created_at":"2026-04-01T20:07:52.666577397Z","created_by":"Willem van den Ende"}]} {"id":"firehose-2wc","title":"Add date filtering to Blogex all_posts/0","description":"## Context\nall_posts() in blogex/lib/blogex/blog.ex (line 77-83) currently filters by `published` boolean only.\nAdd `date \u003c= Date.utc_today()` filter so future-dated posts are hidden from public views.\n\n## Scope\n- blogex/lib/blogex/blog.ex: all_posts/0\n- blogex/test/support/fake_blog.ex: all_posts/0\n- blogex/test/blogex/blog_test.exs: new tests\n- blogex/test/support/setup.ex: add future-dated post to default_posts\n\n## TDD\nRED: Test that future-dated published post is excluded from all_posts, posts_by_tag, recent_posts, all_tags\nGREEN: Add date filter after published filter\nREFACTOR: Extract filtering predicate if duplicated","status":"closed","priority":2,"issue_type":"task","owner":"willem+gitea@livingsoftware.co.uk","created_at":"2026-04-01T20:06:54.303723951Z","created_by":"Willem van den Ende","updated_at":"2026-04-01T20:31:20.372076738Z","closed_at":"2026-04-01T20:31:20.372076738Z","close_reason":"Closed"} {"id":"firehose-4nq","title":"Add post visibility and days_until_live helpers","description":"## Context\nDashboard and status banners need to compute post visibility (draft/scheduled/live)\nand days until a scheduled post goes live.\n\n## Scope\n- blogex/lib/blogex/post.ex: add visibility/1 and days_until_live/1\n- blogex/test/blogex/post_test.exs: new tests\n\n## TDD\nRED: Test visibility returns :draft/:scheduled/:live correctly, days_until_live returns integer or nil\nGREEN: Implement functions\nREFACTOR: None","status":"closed","priority":2,"issue_type":"task","owner":"willem+gitea@livingsoftware.co.uk","created_at":"2026-04-01T20:07:44.5973142Z","created_by":"Willem van den Ende","updated_at":"2026-04-01T20:24:39.851993851Z","closed_at":"2026-04-01T20:24:39.851993851Z","close_reason":"Closed"} diff --git a/blogex/lib/blogex/blog.ex b/blogex/lib/blogex/blog.ex index 79cd6ac..01669b0 100644 --- a/blogex/lib/blogex/blog.ex +++ b/blogex/lib/blogex/blog.ex @@ -105,13 +105,13 @@ defmodule Blogex.Blog do @doc "Returns a single post by slug/id, or raises." def get_post!(id) do - Enum.find(all_posts(), &(&1.id == id)) || + Enum.find(unfiltered_posts(), &(&1.id == id)) || raise Blogex.NotFoundError, "post #{inspect(id)} not found in #{@blog_id}" end @doc "Returns a single post by slug/id, or nil." def get_post(id) do - Enum.find(all_posts(), &(&1.id == id)) + Enum.find(unfiltered_posts(), &(&1.id == id)) end @doc "Returns paginated posts. Page is 1-indexed." diff --git a/blogex/test/blogex/blog_test.exs b/blogex/test/blogex/blog_test.exs index 8faa9ce..6b5afd2 100644 --- a/blogex/test/blogex/blog_test.exs +++ b/blogex/test/blogex/blog_test.exs @@ -125,10 +125,14 @@ defmodule Blogex.BlogTest do end end - test "raises for draft post id", %{blog: blog} do - assert_raise Blogex.NotFoundError, fn -> - blog.get_post!("draft-post") - end + test "returns a future-dated published post by slug", %{blog: blog} do + post = blog.get_post!("future-post") + assert post.id == "future-post" + end + + test "returns a draft post by slug", %{blog: blog} do + post = blog.get_post!("draft-post") + assert post.id == "draft-post" end end @@ -136,6 +140,14 @@ defmodule Blogex.BlogTest do test "returns nil for unknown id", %{blog: blog} do assert blog.get_post("nope") == nil end + + test "returns a future-dated post", %{blog: blog} do + assert %{id: "future-post"} = blog.get_post("future-post") + end + + test "returns a draft post", %{blog: blog} do + assert %{id: "draft-post"} = blog.get_post("draft-post") + end end describe "paginate/2" do diff --git a/blogex/test/blogex/router_test.exs b/blogex/test/blogex/router_test.exs index 221eaa2..0e5f985 100644 --- a/blogex/test/blogex/router_test.exs +++ b/blogex/test/blogex/router_test.exs @@ -70,10 +70,10 @@ defmodule Blogex.RouterTest do assert conn.status == 404 end - test "returns 404 for draft post" do + test "returns 200 for draft post accessed by slug" do conn = call(:get, "/draft") - assert conn.status == 404 + assert conn.status == 200 end end diff --git a/blogex/test/support/fake_blog.ex b/blogex/test/support/fake_blog.ex index 104a8c3..a62d96b 100644 --- a/blogex/test/support/fake_blog.ex +++ b/blogex/test/support/fake_blog.ex @@ -78,12 +78,12 @@ defmodule Blogex.Test.FakeBlog do end def get_post!(id) do - Enum.find(all_posts(), &(&1.id == id)) || + Enum.find(unfiltered_posts(), &(&1.id == id)) || raise Blogex.NotFoundError, "post #{inspect(id)} not found" end def get_post(id) do - Enum.find(all_posts(), &(&1.id == id)) + Enum.find(unfiltered_posts(), &(&1.id == id)) end def paginate(page \\ 1, per_page \\ 10) do