Skip to main content

lib/bloccs/web/assets.ex

defmodule Bloccs.Web.Assets do
  @moduledoc """
  Serves the dashboard's own precompiled `app.css` / `app.js` from
  `priv/static/assets`, so the host needs no `Plug.Static` configuration and the
  bundles can't collide with the host's own assets.

  `forward`ed by `bloccs_dashboard/2` at `<path>/assets`, so the filename arrives
  as the remaining `path_info` segment. A long `cache-control` is safe because
  the filenames are content-stable per release.
  """

  @behaviour Plug

  @types %{
    "app.css" => "text/css",
    "app.js" => "application/javascript",
    "mark.svg" => "image/svg+xml"
  }

  @impl true
  def init(opts), do: opts

  @impl true
  def call(%Plug.Conn{path_info: path_info} = conn, _opts) do
    serve(conn, List.last(path_info))
  end

  defp serve(conn, file) when is_binary(file) do
    case Map.fetch(@types, Path.basename(file)) do
      {:ok, content_type} ->
        path = Path.join(:code.priv_dir(:bloccs_web), "static/assets/#{Path.basename(file)}")

        if File.regular?(path) do
          conn
          |> Plug.Conn.put_resp_header("content-type", content_type)
          |> Plug.Conn.put_resp_header("cache-control", "public, max-age=31536000")
          |> Plug.Conn.send_file(200, path)
          |> Plug.Conn.halt()
        else
          not_found(conn)
        end

      :error ->
        not_found(conn)
    end
  end

  defp serve(conn, _), do: not_found(conn)

  defp not_found(conn) do
    conn
    |> Plug.Conn.send_resp(404, "not found")
    |> Plug.Conn.halt()
  end
end