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:
@@ -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">← 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">← 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>
|
||||
Reference in New Issue
Block a user