From 54b1b71aa6457156c4c000c65511ec42f05bf97d Mon Sep 17 00:00:00 2001 From: Chili Palmer Date: Thu, 23 Apr 2026 14:44:56 +0200 Subject: [PATCH] feat: more on posts --- lib/bds/posts.ex | 43 +++++++++++++++++++++++++++++++ test/bds/posts_test.exs | 56 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 99 insertions(+) diff --git a/lib/bds/posts.ex b/lib/bds/posts.ex index 54f1c44..7cbfd98 100644 --- a/lib/bds/posts.ex +++ b/lib/bds/posts.ex @@ -94,6 +94,36 @@ defmodule BDS.Posts do end end + def delete_post(post_id) do + case Repo.get(Post, post_id) do + nil -> + {:error, :not_found} + + %Post{} = post -> + delete_post_file(post) + Repo.delete!(post) + {:ok, :deleted} + end + end + + def archive_post(post_id) do + case Repo.get(Post, post_id) do + nil -> + {:error, :not_found} + + %Post{status: status} = post when status in [:draft, :published] -> + post + |> Post.changeset(%{status: :archived, updated_at: System.system_time(:second)}) + |> Repo.update() + + %Post{} = post -> + {:error, + post + |> Post.changeset(%{}) + |> Ecto.Changeset.add_error(:status, "cannot archive archived post")} + end + end + def get_post!(post_id), do: Repo.get!(Post, post_id) def rewrite_published_post(post_id) do @@ -251,6 +281,19 @@ defmodule BDS.Posts do end end + defp delete_post_file(%Post{project_id: _project_id, file_path: file_path}) when file_path in [nil, ""], do: :ok + + defp delete_post_file(%Post{} = post) do + project = Projects.get_project!(post.project_id) + full_path = Path.join(Projects.project_data_dir(project), post.file_path) + + case File.rm(full_path) do + :ok -> :ok + {:error, :enoent} -> :ok + {:error, reason} -> {:error, reason} + end + end + defp has_attr?(attrs, key) do Map.has_key?(attrs, key) or Map.has_key?(attrs, Atom.to_string(key)) end diff --git a/test/bds/posts_test.exs b/test/bds/posts_test.exs index 6e8d792..6a223d3 100644 --- a/test/bds/posts_test.exs +++ b/test/bds/posts_test.exs @@ -134,6 +134,62 @@ defmodule BDS.PostsTest do assert file_contents =~ "\n---\nHello from markdown\n" end + test "delete_post removes the database row and published markdown file when present" do + temp_dir = Path.join(System.tmp_dir!(), "bds-post-delete-#{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: "Delete", data_path: temp_dir}) + + assert {:ok, post} = + BDS.Posts.create_post(%{ + project_id: project.id, + title: "Delete Me", + content: "Body" + }) + + assert {:ok, published} = BDS.Posts.publish_post(post.id) + full_path = Path.join(temp_dir, published.file_path) + assert File.exists?(full_path) + + assert {:ok, :deleted} = BDS.Posts.delete_post(published.id) + + assert BDS.Repo.get(BDS.Posts.Post, published.id) == nil + refute File.exists?(full_path) + end + + test "archive_post transitions draft and published posts to archived", %{project: project} do + assert {:ok, draft_post} = + BDS.Posts.create_post(%{ + project_id: project.id, + title: "Draft Archive", + content: "Body" + }) + + assert {:ok, archived_draft} = BDS.Posts.archive_post(draft_post.id) + assert archived_draft.status == :archived + + temp_dir = Path.join(System.tmp_dir!(), "bds-post-archive-#{System.unique_integer([:positive])}") + File.mkdir_p!(temp_dir) + on_exit(fn -> File.rm_rf(temp_dir) end) + + assert {:ok, publish_project} = BDS.Projects.create_project(%{name: "Archive Published", data_path: temp_dir}) + + assert {:ok, published_post} = + BDS.Posts.create_post(%{ + project_id: publish_project.id, + title: "Published Archive", + content: "Body" + }) + + assert {:ok, published_post} = BDS.Posts.publish_post(published_post.id) + assert {:ok, archived_published} = BDS.Posts.archive_post(published_post.id) + + assert archived_published.status == :archived + assert archived_published.file_path == published_post.file_path + assert archived_published.published_at == published_post.published_at + end + defp errors_on(changeset) do Ecto.Changeset.traverse_errors(changeset, fn {message, opts} -> Regex.replace(~r"%{(\w+)}", message, fn _, key ->