Static blog with front page summary

Goal: have a personal blog, and try out another point in the 'modular
app design with elixir' space.

Designing OTP systems with elixir had some interesting ideas.
This commit is contained in:
Your Name
2026-03-17 11:17:21 +00:00
commit bc14696f57
94 changed files with 6846 additions and 0 deletions
@@ -0,0 +1,50 @@
defmodule FirehoseWeb.BlogController do
use FirehoseWeb, :controller
def index(conn, %{"blog_id" => blog_id} = params) do
blog = resolve_blog!(blog_id)
page = String.to_integer(params["page"] || "1")
result = blog.paginate(page)
render(conn, :index,
page_title: blog.title(),
blog_title: blog.title(),
blog_description: blog.description(),
posts: result.entries,
base_path: blog.base_path(),
page: result.page,
total_pages: result.total_pages
)
end
def show(conn, %{"blog_id" => blog_id, "slug" => slug}) do
blog = resolve_blog!(blog_id)
post = blog.get_post!(slug)
render(conn, :show,
page_title: post.title,
post: post,
base_path: blog.base_path()
)
end
def tag(conn, %{"blog_id" => blog_id, "tag" => tag}) do
blog = resolve_blog!(blog_id)
posts = blog.posts_by_tag(tag)
render(conn, :tag,
page_title: "#{blog.title()}#{tag}",
blog_title: blog.title(),
tag: tag,
posts: posts,
base_path: blog.base_path()
)
end
defp resolve_blog!("engineering"), do: Firehose.EngineeringBlog
defp resolve_blog!("releases"), do: Firehose.ReleaseNotes
defp resolve_blog!(id) do
raise Blogex.NotFoundError, "unknown blog: #{inspect(id)}"
end
end
@@ -0,0 +1,7 @@
defmodule FirehoseWeb.BlogHTML do
use FirehoseWeb, :html
import Blogex.Components
embed_templates "blog_html/*"
end
@@ -0,0 +1,9 @@
<div class="space-y-8">
<header>
<h1 class="text-3xl font-bold font-display">{@blog_title}</h1>
<p :if={@blog_description} class="mt-2 text-base-content/70">{@blog_description}</p>
</header>
<.post_index posts={@posts} base_path={@base_path} />
<.pagination page={@page} total_pages={@total_pages} base_path={@base_path} />
</div>
@@ -0,0 +1,4 @@
<div class="space-y-8">
<a href={@base_path} class="text-sm text-primary hover:underline">&larr; Back to posts</a>
<.post_show post={@post} />
</div>
@@ -0,0 +1,10 @@
<div class="space-y-8">
<header>
<h1 class="text-3xl font-bold font-display">{@blog_title}</h1>
<p class="mt-2 text-base-content/70">Posts tagged "{@tag}"</p>
</header>
<.post_index posts={@posts} base_path={@base_path} />
<a href={@base_path} class="text-sm text-primary hover:underline">&larr; All posts</a>
</div>
@@ -0,0 +1,24 @@
defmodule FirehoseWeb.ErrorHTML do
@moduledoc """
This module is invoked by your endpoint in case of errors on HTML requests.
See config/config.exs.
"""
use FirehoseWeb, :html
# If you want to customize your error pages,
# uncomment the embed_templates/1 call below
# and add pages to the error directory:
#
# * lib/firehose_web/controllers/error_html/404.html.heex
# * lib/firehose_web/controllers/error_html/500.html.heex
#
# embed_templates "error_html/*"
# The default is to render a plain text page based on
# the template name. For example, "404.html" becomes
# "Not Found".
def render(template, _assigns) do
Phoenix.Controller.status_message_from_template(template)
end
end
@@ -0,0 +1,21 @@
defmodule FirehoseWeb.ErrorJSON do
@moduledoc """
This module is invoked by your endpoint in case of errors on JSON requests.
See config/config.exs.
"""
# If you want to customize a particular status code,
# you may add your own clauses, such as:
#
# def render("500.json", _assigns) do
# %{errors: %{detail: "Internal Server Error"}}
# end
# By default, Phoenix returns the status message from
# the template name. For example, "404.json" becomes
# "Not Found".
def render(template, _assigns) do
%{errors: %{detail: Phoenix.Controller.status_message_from_template(template)}}
end
end
@@ -0,0 +1,13 @@
defmodule FirehoseWeb.PageController do
use FirehoseWeb, :controller
def home(conn, _params) do
recent_posts =
(Firehose.EngineeringBlog.recent_posts(3) ++
Firehose.ReleaseNotes.recent_posts(3))
|> Enum.sort_by(& &1.date, {:desc, Date})
|> Enum.take(6)
render(conn, :home, recent_posts: recent_posts)
end
end
@@ -0,0 +1,18 @@
defmodule FirehoseWeb.PageHTML do
@moduledoc """
This module contains pages rendered by PageController.
See the `page_html` directory for all templates available.
"""
use FirehoseWeb, :html
embed_templates "page_html/*"
defp blog_label(%{blog: :engineering}), do: "Engineering"
defp blog_label(%{blog: :release_notes}), do: "Releases"
defp blog_label(_), do: "Blog"
defp post_base_path(%{blog: :engineering}), do: "/blog/engineering"
defp post_base_path(%{blog: :release_notes}), do: "/blog/releases"
defp post_base_path(_), do: "/blog"
end
@@ -0,0 +1,36 @@
<div class="space-y-12">
<section class="space-y-6">
<h1 class="text-4xl sm:text-5xl font-display font-semibold leading-tight tracking-tight text-balance">
Drinking from the firehose
</h1>
<div class="space-y-4 text-lg leading-relaxed text-base-content/80">
<p>
I'm <strong class="text-base-content">Willem van den Ende</strong>,
partner at <a href="https://qwan.eu" class="text-primary hover:underline" target="_blank" rel="noopener">QWAN</a>.
This is where I write about AI-native consulting, shitty evals,
and whatever prototype I'm building this week.
</p>
</div>
</section>
<section class="space-y-6">
<h2 class="text-2xl font-display font-semibold">Recent posts</h2>
<div class="grid gap-4 sm:grid-cols-2">
<article
:for={post <- @recent_posts}
class="rounded-box border border-base-200 p-5 space-y-2 hover:border-primary/30 transition"
>
<a href={"#{post_base_path(post)}/#{post.id}"} class="block space-y-2">
<h3 class="font-semibold text-base-content hover:text-primary transition">{post.title}</h3>
<p class="text-sm text-base-content/60">{post.description}</p>
<div class="flex items-center gap-2 text-xs text-base-content/50">
<time datetime={Date.to_iso8601(post.date)}>
{Calendar.strftime(post.date, "%B %d, %Y")}
</time>
<span class="badge badge-ghost badge-xs">{blog_label(post)}</span>
</div>
</a>
</article>
</div>
</section>
</div>