diff --git a/lib/jol/blog.ex b/lib/jol/blog.ex
index e718e92..692c6fb 100644
--- a/lib/jol/blog.ex
+++ b/lib/jol/blog.ex
@@ -1,5 +1,7 @@
defmodule JOL.Blog do
- alias JOL.Blog.Post
+ alias JOL.Blog.{Post, Tag}
+ alias JOL.Repo
+ import Ecto.Query
defmodule NotFoundError do
defexception [:message, plug_status: 404]
@@ -10,17 +12,40 @@ defmodule JOL.Blog do
end
def unique_tag_list do
- []
+ Repo.all(Tag, order_by: :name)
end
def recent_posts(num \\ 10) do
- []
+ Repo.all(
+ from p in Post,
+ limit: ^num,
+ order_by: {:desc, :published_at}
+ )
end
def get_post_by_slug!(slug) do
+ Repo.one(
+ from p in Post,
+ where: [slug: ^slug],
+ preload: [:tags]
+ )
end
def get_posts_by_tag!(tag) do
+ Repo.all(
+ from p in Post,
+ join: t in assoc(p, :tags),
+ preload: [tags: t],
+ where: t.name == ^tag
+ )
+ end
+
+ def get_tag_by_id!(id) do
+ Repo.one(
+ from t in Tag,
+ where: t.id == ^id,
+ preload: [:posts]
+ )
end
def format_date(date) do
diff --git a/lib/jol/blog/post.ex b/lib/jol/blog/post.ex
index 0e03b1f..7d125fe 100644
--- a/lib/jol/blog/post.ex
+++ b/lib/jol/blog/post.ex
@@ -7,6 +7,7 @@ defmodule JOL.Blog.Post do
field :body, :string
field :published_at, :naive_datetime
field :slug, :string
+ many_to_many :tags, JOL.Blog.Tag, join_through: "post_tags"
timestamps(type: :utc_datetime)
end
@@ -15,6 +16,7 @@ defmodule JOL.Blog.Post do
def changeset(post, attrs) do
post
|> cast(attrs, [:title, :body, :published_at, :slug])
- |> validate_required([:title, :body, :published_at, :slug])
+ |> validate_required([:title, :body, :slug])
+ |> cast_assoc(:tags)
end
end
diff --git a/lib/jol/blog/tags.ex b/lib/jol/blog/tag.ex
similarity index 67%
rename from lib/jol/blog/tags.ex
rename to lib/jol/blog/tag.ex
index ff67e17..826dfe6 100644
--- a/lib/jol/blog/tags.ex
+++ b/lib/jol/blog/tag.ex
@@ -1,9 +1,10 @@
-defmodule JOL.Blog.Tags do
+defmodule JOL.Blog.Tag do
use Ecto.Schema
import Ecto.Changeset
schema "tags" do
field :name, :string
+ many_to_many :posts, JOL.Blog.Post, join_through: "post_tags"
timestamps(type: :utc_datetime)
end
@@ -13,5 +14,6 @@ defmodule JOL.Blog.Tags do
tags
|> cast(attrs, [:name])
|> validate_required([:name])
+ |> unique_constraint([:name])
end
end
diff --git a/lib/jol_web/controllers/blog_html/show.heex b/lib/jol_web/controllers/blog_html/show.heex
index a7c8a1f..791a160 100644
--- a/lib/jol_web/controllers/blog_html/show.heex
+++ b/lib/jol_web/controllers/blog_html/show.heex
@@ -3,11 +3,11 @@
<%= @post.title %>
- <%= JOL.Blog.format_date(@post.date) %>
+ <%= JOL.Blog.format_date(@post.published_at) %>
<%= raw @post.body %>
- Filed under: <%= Enum.join(@post.tags, ", ") %>
+ Filed under: <%= @post.tags |> Enum.map(& &1.name) |> Enum.join(", ") %>
diff --git a/lib/jol_web/controllers/page_html/home.html.heex b/lib/jol_web/controllers/page_html/home.html.heex
index 4c451e3..3a8256a 100644
--- a/lib/jol_web/controllers/page_html/home.html.heex
+++ b/lib/jol_web/controllers/page_html/home.html.heex
@@ -12,7 +12,7 @@
<%= for post <- @posts do %>
-
- <%= JOL.Blog.format_date(post.date) %>
+ <%= JOL.Blog.format_date(post.published_at) %>
<.link href={~p"/blog/#{post.slug}"}><%= post.title %>
<% end %>
diff --git a/lib/jol_web/controllers/tag_controller.ex b/lib/jol_web/controllers/tag_controller.ex
index 8462951..4f1a69f 100644
--- a/lib/jol_web/controllers/tag_controller.ex
+++ b/lib/jol_web/controllers/tag_controller.ex
@@ -8,11 +8,13 @@ defmodule JOLWeb.TagController do
tags: JOL.Blog.unique_tag_list)
end
- def tag(conn, %{"tag" => tag}) do
+ def tag(conn, %{"id" => id}) do
+ tag = JOL.Blog.get_tag_by_id!(id)
+
conn
|> render(:tag,
- tag: tag,
- page_title: "Posts Filed Under #{tag}",
- posts: JOL.Blog.get_posts_by_tag!(tag))
+ tag: tag.name,
+ page_title: "Filed Under #{tag.name}",
+ posts: tag.posts)
end
end
diff --git a/lib/jol_web/controllers/tag_html/index.html.heex b/lib/jol_web/controllers/tag_html/index.html.heex
index 7c8e557..8a985c0 100644
--- a/lib/jol_web/controllers/tag_html/index.html.heex
+++ b/lib/jol_web/controllers/tag_html/index.html.heex
@@ -3,7 +3,7 @@
diff --git a/lib/jol_web/controllers/tag_html/tag.html.heex b/lib/jol_web/controllers/tag_html/tag.html.heex
index 6a41cac..7263e13 100644
--- a/lib/jol_web/controllers/tag_html/tag.html.heex
+++ b/lib/jol_web/controllers/tag_html/tag.html.heex
@@ -2,7 +2,7 @@
<%= for post <- @posts do %>
-
<%= post.date %>
+
<%= post.published_at %>
<.link href={~p"/blog/#{post.slug}"}><%= post.title %>
<%= raw post.lede %>
diff --git a/lib/jol_web/router.ex b/lib/jol_web/router.ex
index 3e98175..8d4fd82 100644
--- a/lib/jol_web/router.ex
+++ b/lib/jol_web/router.ex
@@ -28,7 +28,7 @@ defmodule JOLWeb.Router do
get "/colophon", PageController, :colophon
get "/tags", TagController, :index
- get "/tags/:tag", TagController, :tag
+ get "/tags/:id", TagController, :tag
get "/blog", BlogController, :index
get "/blog/:slug", BlogController, :show
diff --git a/mix.exs b/mix.exs
index 76c07a5..7d0f9ed 100644
--- a/mix.exs
+++ b/mix.exs
@@ -59,7 +59,8 @@ defmodule JOL.MixProject do
{:dns_cluster, "~> 0.1.1"},
{:bandit, "~> 1.2"},
{:atomex, "~> 0.3.0"},
- {:tz, "~> 0.27"}
+ {:tz, "~> 0.27"},
+ {:toml, "~> 0.7"}
]
end
diff --git a/priv/migrate_posts.exs b/priv/migrate_posts.exs
new file mode 100644
index 0000000..16a7922
--- /dev/null
+++ b/priv/migrate_posts.exs
@@ -0,0 +1,26 @@
+alias JOL.Blog.{Parser,Post,Tag}
+alias JOL.Repo
+import Ecto.Query
+
+post_dir = Application.app_dir(:jol, "priv/posts/blog/")
+
+Path.wildcard("#{post_dir}/**/*.md")
+|> Enum.each(fn path ->
+ {attrs, body} = Parser.parse(path, File.read!(path))
+ for tag <- attrs.tags do
+ Repo.insert!(%Tag{name: tag}, on_conflict: :nothing, conflict_target: [:name])
+ end
+
+ post_attrs = %{title: attrs.title,
+ published_at: attrs.date,
+ slug: attrs.slug,
+ body: body,
+ tags: Repo.all(from t in Tag, where: t.name in ^attrs.tags)
+ }
+
+ %Post{}
+ |> Repo.preload(:tags)
+ |> Ecto.Changeset.cast(post_attrs, [:published_at, :slug, :body, :title])
+ |> Ecto.Changeset.put_assoc(:tags, post_attrs.tags)
+ |> Repo.insert!()
+ end)
diff --git a/priv/repo/migrations/20240924153630_join_posts_and_tags.exs b/priv/repo/migrations/20240924153630_join_posts_and_tags.exs
new file mode 100644
index 0000000..f613e3b
--- /dev/null
+++ b/priv/repo/migrations/20240924153630_join_posts_and_tags.exs
@@ -0,0 +1,10 @@
+defmodule JOL.Repo.Migrations.JoinPostsAndTags do
+ use Ecto.Migration
+
+ def change do
+ create table(:post_tags) do
+ add :post_id, references(:posts)
+ add :tag_id, references(:tags)
+ end
+ end
+end
diff --git a/priv/repo/migrations/20240924154328_make_tag_name_unique.exs b/priv/repo/migrations/20240924154328_make_tag_name_unique.exs
new file mode 100644
index 0000000..aa72634
--- /dev/null
+++ b/priv/repo/migrations/20240924154328_make_tag_name_unique.exs
@@ -0,0 +1,8 @@
+defmodule JOL.Repo.Migrations.MakeTagNameUnique do
+ use Ecto.Migration
+
+ def change do
+ create unique_index(:tags, ["name"])
+ create unique_index(:post_tags, ["post_id", "tag_id"])
+ end
+end