defmodule SimpleshopThemeWeb.ImageController do use SimpleshopThemeWeb, :controller alias SimpleshopTheme.Media alias SimpleshopTheme.Media.SVGRecolorer @doc """ Serves an image from the database by ID. Images are served with aggressive caching headers since they are immutable once uploaded. """ def show(conn, %{"id" => id}) do case Media.get_image(id) do nil -> send_resp(conn, 404, "Image not found") image -> conn |> put_resp_content_type(image.content_type) |> put_resp_header("cache-control", "public, max-age=31536000, immutable") |> put_resp_header("etag", ~s("#{image.id}")) |> send_resp(200, image.data) end end @doc """ Serves a thumbnail of an image if available, otherwise falls back to full image. """ def thumbnail(conn, %{"id" => id}) do case Media.get_image(id) do nil -> send_resp(conn, 404, "Image not found") %{thumbnail_data: thumbnail_data} = image when not is_nil(thumbnail_data) -> conn |> put_resp_content_type("image/jpeg") |> put_resp_header("cache-control", "public, max-age=31536000, immutable") |> put_resp_header("etag", ~s("#{image.id}-thumb")) |> send_resp(200, thumbnail_data) image -> conn |> put_resp_content_type(image.content_type) |> put_resp_header("cache-control", "public, max-age=31536000, immutable") |> put_resp_header("etag", ~s("#{image.id}")) |> send_resp(200, image.data) end end @doc """ Serves an SVG image recolored with the specified color. The color should be a hex color code (with or without the leading #). Only works with SVG images. """ def recolored_svg(conn, %{"id" => id, "color" => color}) do clean_color = normalize_color(color) with true <- SVGRecolorer.valid_hex_color?(clean_color), %{is_svg: true, svg_content: svg} when not is_nil(svg) <- Media.get_image(id) do recolored = SVGRecolorer.recolor(svg, clean_color) conn |> put_resp_content_type("image/svg+xml") |> put_resp_header("cache-control", "public, max-age=3600") |> put_resp_header("etag", ~s("#{id}-#{clean_color}")) |> send_resp(200, recolored) else false -> send_resp(conn, 400, "Invalid color format") nil -> send_resp(conn, 404, "Image not found") %{is_svg: false} -> send_resp(conn, 400, "Image is not an SVG") %{svg_content: nil} -> send_resp(conn, 400, "SVG content not available") end end defp normalize_color(color) do color = String.trim(color) if String.starts_with?(color, "#") do color else "#" <> color end end end