diff --git a/assets/css/_recording_card.scss b/assets/css/_recording_card.scss index 9344dc114..f62d1a6aa 100644 --- a/assets/css/_recording_card.scss +++ b/assets/css/_recording_card.scss @@ -79,7 +79,7 @@ div.asciicast-card { width: 20px; height: 20px; - img { + img, svg { width: 100%; height: 100%; border-radius: 2px; diff --git a/config/config.exs b/config/config.exs index eedacbf84..d8afdc08e 100644 --- a/config/config.exs +++ b/config/config.exs @@ -64,6 +64,8 @@ config :asciinema, Asciinema.FileCache, path: "cache/" config :asciinema, Asciinema.Emails.Mailer, adapter: Bamboo.LocalAdapter +config :asciinema, :avatar_provider, :identicon + config :asciinema, :png_generator, Asciinema.PngGenerator.Rsvg config :asciinema, Asciinema.PngGenerator.Rsvg, diff --git a/config/runtime.exs b/config/runtime.exs index 894dac8c8..647a7bed6 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -212,6 +212,12 @@ if config_env() in [:prod, :dev] do config :asciinema, :sign_up_enabled?, false end + provider = env.("AVATAR_PROVIDER") + + if provider in ["gravatar", "identicon"] do + config :asciinema, :avatar_provider, String.to_existing_atom(provider) + end + if dsn = env.("SENTRY_DSN") do config :sentry, dsn: dsn else diff --git a/lib/asciinema_web/controllers/avatar_controller.ex b/lib/asciinema_web/controllers/avatar_controller.ex new file mode 100644 index 000000000..0eef6aaeb --- /dev/null +++ b/lib/asciinema_web/controllers/avatar_controller.ex @@ -0,0 +1,14 @@ +defmodule AsciinemaWeb.AvatarController do + use AsciinemaWeb, :new_controller + alias Asciinema.Accounts + + @one_day 24 * 60 * 60 + + def show(conn, %{"id" => id}) do + with {:ok, user} <- Accounts.fetch_user(id) do + conn + |> put_resp_header("cache-control", "public, max-age=#{@one_day}") + |> render("show.svg", user: user) + end + end +end diff --git a/lib/asciinema_web/controllers/avatar_svg.ex b/lib/asciinema_web/controllers/avatar_svg.ex new file mode 100644 index 000000000..78f9ea54e --- /dev/null +++ b/lib/asciinema_web/controllers/avatar_svg.ex @@ -0,0 +1,7 @@ +defmodule AsciinemaWeb.AvatarSVG do + def show(%{user: user}) do + email = user.email || "#{user.id}@asciinema" + + {:safe, IdenticonSvg.generate(email, 6, :basic)} + end +end diff --git a/lib/asciinema_web/controllers/recording/card.html.heex b/lib/asciinema_web/controllers/recording/card.html.heex index c8068ff3e..247f3d118 100644 --- a/lib/asciinema_web/controllers/recording/card.html.heex +++ b/lib/asciinema_web/controllers/recording/card.html.heex @@ -21,7 +21,7 @@ <.link href={author_profile_path(@asciicast)} title={author_username(@asciicast)}> - + diff --git a/lib/asciinema_web/controllers/user_html.ex b/lib/asciinema_web/controllers/user_html.ex index d56cd3255..0d5b15321 100644 --- a/lib/asciinema_web/controllers/user_html.ex +++ b/lib/asciinema_web/controllers/user_html.ex @@ -12,8 +12,14 @@ defmodule AsciinemaWeb.UserHTML do defdelegate default_font_display_name, to: Fonts def avatar_url(user) do - username = username(user) - email = user.email || "#{username}+#{user.id}@asciinema.org" + avatar_url(user, Application.fetch_env!(:asciinema, :avatar_provider)) + end + + def avatar_url(user, :identicon), do: ~p"/u/#{user}/avatar" + + def avatar_url(user, :gravatar) do + email = user.email || "#{user.id}@asciinema" + Gravatar.gravatar_url(email) end diff --git a/lib/asciinema_web/router.ex b/lib/asciinema_web/router.ex index 73e0e9e88..af4d7e30e 100644 --- a/lib/asciinema_web/router.ex +++ b/lib/asciinema_web/router.ex @@ -85,6 +85,7 @@ defmodule AsciinemaWeb.Router do resources "/users", UserController, as: :users, only: [:new, :create] get "/u/:id", UserController, :show get "/~:username", UserController, :show + get "/u/:id/avatar", AvatarController, :show resources "/username", UsernameController, only: [:new, :create], singleton: true get "/username/skip", UsernameController, :skip, as: :username diff --git a/mix.exs b/mix.exs index 246e1a3c5..c2701eca8 100644 --- a/mix.exs +++ b/mix.exs @@ -55,6 +55,7 @@ defmodule Asciinema.MixProject do {:hackney, "~> 1.18"}, {:horde, "~> 0.8.7"}, {:html_sanitize_ex, "~> 1.4"}, + {:identicon_svg, "~> 0.8.0"}, {:inflex, "~> 2.0"}, {:jason, "~> 1.2"}, {:libcluster, "~> 3.3"}, diff --git a/mix.lock b/mix.lock index a0be464d4..af503fd0b 100644 --- a/mix.lock +++ b/mix.lock @@ -32,6 +32,7 @@ "horde": {:hex, :horde, "0.8.7", "e51ab8e0e5bc7dcd0caa85d84b144cccfde97994bd865d822c7e489746b87e7f", [:mix], [{:delta_crdt, "~> 0.6.2", [hex: :delta_crdt, repo: "hexpm", optional: false]}, {:libring, "~> 1.4", [hex: :libring, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:telemetry_poller, "~> 0.5.0 or ~> 1.0", [hex: :telemetry_poller, repo: "hexpm", optional: false]}], "hexpm", "835aede887d777542f85e0a88293c18113abcc1356006050ec216da16aa5e0e3"}, "html_entities": {:hex, :html_entities, "0.5.2", "9e47e70598da7de2a9ff6af8758399251db6dbb7eebe2b013f2bbd2515895c3c", [:mix], [], "hexpm", "c53ba390403485615623b9531e97696f076ed415e8d8058b1dbaa28181f4fdcc"}, "html_sanitize_ex": {:hex, :html_sanitize_ex, "1.4.2", "c479398b6de798c03eb5d04a0a9a9159d73508f83f6590a00b8eacba3619cf4c", [:mix], [{:mochiweb, "~> 2.15", [hex: :mochiweb, repo: "hexpm", optional: false]}], "hexpm", "aef6c28585d06a9109ad591507e508854c5559561f950bbaea773900dd369b0e"}, + "identicon_svg": {:hex, :identicon_svg, "0.8.0", "a3368175bffff7c18a28bc7f2aec065787f16caee8a86560c7219141413cf15c", [:mix], [], "hexpm", "26ff8ce55c0674b0c9031c7f51038e31d0e939cb95b6dd038a287bc89d8711d2"}, "idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"}, "inflex": {:hex, :inflex, "2.1.0", "a365cf0821a9dacb65067abd95008ca1b0bb7dcdd85ae59965deef2aa062924c", [:mix], [], "hexpm", "14c17d05db4ee9b6d319b0bff1bdf22aa389a25398d1952c7a0b5f3d93162dd8"}, "jason": {:hex, :jason, "1.4.1", "af1504e35f629ddcdd6addb3513c3853991f694921b1b9368b0bd32beb9f1b63", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fbb01ecdfd565b56261302f7e1fcc27c4fb8f32d56eab74db621fc154604a7a1"}, diff --git a/test/controllers/avatar_controller_test.exs b/test/controllers/avatar_controller_test.exs new file mode 100644 index 000000000..00f0a5667 --- /dev/null +++ b/test/controllers/avatar_controller_test.exs @@ -0,0 +1,13 @@ +defmodule Asciinema.AvatarControllerTest do + use AsciinemaWeb.ConnCase + import Asciinema.Factory + + test "image response", %{conn: conn} do + user = insert(:user) + + conn = get(conn, ~p"/u/#{user}/avatar") + + assert response(conn, 200) + assert List.first(get_resp_header(conn, "content-type")) =~ ~r|image/.+| + end +end