defmodule BerrypodWeb.FaviconController do use BerrypodWeb, :controller alias Berrypod.Media alias Berrypod.Settings @one_day 86_400 def favicon_svg(conn, _params) do case Media.get_favicon_variants() do %{svg: svg} when is_binary(svg) -> conn |> maybe_not_modified("svg") |> serve("image/svg+xml", svg) _ -> send_resp(conn, 404, "") end end def favicon_32(conn, _params), do: serve_variant(conn, :png_32) def apple_touch_icon(conn, _params), do: serve_variant(conn, :png_180) def icon_192(conn, _params), do: serve_variant(conn, :png_192) def icon_512(conn, _params), do: serve_variant(conn, :png_512) def webmanifest(conn, _params) do settings = Settings.get_theme_settings() short_name = case settings.favicon_short_name do name when is_binary(name) and name != "" -> name _ -> String.slice(settings.site_name, 0, 12) end manifest = %{ name: settings.site_name, short_name: short_name, theme_color: settings.accent_color || "#000000", background_color: settings.icon_background_color || "#ffffff", display: "minimal-ui", start_url: "/", icons: [ %{src: "/icon-192.png", sizes: "192x192", type: "image/png"}, %{src: "/icon-512.png", sizes: "512x512", type: "image/png", purpose: "maskable"} ] } conn |> put_resp_content_type("application/manifest+json") |> put_resp_header("cache-control", "public, max-age=#{@one_day}") |> json(manifest) end defp serve_variant(conn, field) do case Media.get_favicon_variants() do nil -> send_resp(conn, 404, "") variants -> case Map.get(variants, field) do data when is_binary(data) -> conn |> maybe_not_modified(variants) |> serve("image/png", data) _ -> send_resp(conn, 404, "") end end end defp maybe_not_modified(%{halted: true} = conn, _), do: conn defp maybe_not_modified(conn, variants) do etag = build_etag(variants) case get_req_header(conn, "if-none-match") do [^etag] -> conn |> put_resp_header("cache-control", "public, max-age=#{@one_day}") |> put_resp_header("etag", etag) |> send_resp(304, "") |> halt() _ -> conn |> put_resp_header("cache-control", "public, max-age=#{@one_day}") |> put_resp_header("etag", etag) end end defp serve(%{halted: true} = conn, _content_type, _data), do: conn defp serve(conn, content_type, data) do conn |> put_resp_content_type(content_type) |> send_resp(200, data) end defp build_etag(%{generated_at: %DateTime{} = dt}), do: ~s("fav-#{DateTime.to_unix(dt)}") defp build_etag(_), do: ~s("fav-0") end