From dfad4d70859673d5f84ffc32c612c4e66320702f Mon Sep 17 00:00:00 2001 From: JoeBew42 Date: Mon, 22 Jan 2018 21:00:11 +0100 Subject: [PATCH] fixes #191: support the avatar upload by an image URL This commit will add the support to upload the avatar of a person by providing the image URL. That feature is available from the admin website. --- lib/changelog/arc_ecto/schema.ex | 75 ++++++++++++++++++ lib/changelog/data/data.ex | 2 +- lib/changelog/data/person.ex | 2 +- .../templates/admin/person/_form.html.eex | 7 +- test/changelog/data/person_test.exs | 11 +++ test/fixtures/avatar600x600.png | Bin 0 -> 1754 bytes 6 files changed, 94 insertions(+), 3 deletions(-) create mode 100644 lib/changelog/arc_ecto/schema.ex create mode 100644 test/fixtures/avatar600x600.png diff --git a/lib/changelog/arc_ecto/schema.ex b/lib/changelog/arc_ecto/schema.ex new file mode 100644 index 00000000000..bbee0f33cce --- /dev/null +++ b/lib/changelog/arc_ecto/schema.ex @@ -0,0 +1,75 @@ +defmodule Changelog.Arc.Ecto.Schema do + defmacro __using__(_) do + quote do + import Changelog.Arc.Ecto.Schema + end + end + + defmacro cast_attachments(changeset_or_data, params, allowed, options \\ []) do + quote bind_quoted: [changeset_or_data: changeset_or_data, + params: params, + allowed: allowed, + options: options] do + + # If given a changeset, apply the changes to obtain the underlying data + scope = case changeset_or_data do + %Ecto.Changeset{} -> Ecto.Changeset.apply_changes(changeset_or_data) + %{__meta__: _} -> changeset_or_data + end + + # Cast supports both atom and string keys, ensure we're matching on both. + allowed = Enum.map(allowed, fn key -> + case key do + key when is_binary(key) -> key + key when is_atom(key) -> Atom.to_string(key) + end + end) + + arc_params = case params do + :invalid -> + :invalid + %{} -> + params + |> Changelog.Arc.Ecto.Schema.convert_params_to_binary + |> Map.take(allowed) + |> Enum.reduce([], fn + # Don't wrap nil casts in the scope object + {field, nil}, fields -> [{field, nil} | fields] + + # Allow casting Plug.Uploads + {field, upload = %{__struct__: Plug.Upload}}, fields -> [{field, {upload, scope}} | fields] + + # If casting a binary (path), ensure we've explicitly allowed paths or urls + {field, path}, fields when is_binary(path) -> + if Keyword.get(options, :allow_urls, false) and Regex.match?( ~r/^https?:\/\// , path) do + [{field, {path, scope}} | fields] + else + if Keyword.get(options, :allow_paths, false) do + [{field, {path, scope}} | fields] + else + fields + end + end + end) + |> Enum.into(%{}) + end + + cast(changeset_or_data, arc_params, allowed) + end + end + + def convert_params_to_binary(params) do + Enum.reduce(params, nil, fn + {key, _value}, nil when is_binary(key) -> + nil + + {key, _value}, _ when is_binary(key) -> + raise ArgumentError, "expected params to be a map with atoms or string keys, " <> + "got a map with mixed keys: #{inspect params}" + + {key, value}, acc when is_atom(key) -> + Map.put(acc || %{}, Atom.to_string(key), value) + + end) || params + end +end diff --git a/lib/changelog/data/data.ex b/lib/changelog/data/data.ex index 1226080c7f6..85b738bf360 100644 --- a/lib/changelog/data/data.ex +++ b/lib/changelog/data/data.ex @@ -4,7 +4,7 @@ defmodule Changelog.Data do quote do use Ecto.Schema - use Arc.Ecto.Schema + use Changelog.Arc.Ecto.Schema use Timex.Ecto.Timestamps import Ecto diff --git a/lib/changelog/data/person.ex b/lib/changelog/data/person.ex index 048223213ea..3b162ff34d6 100644 --- a/lib/changelog/data/person.ex +++ b/lib/changelog/data/person.ex @@ -66,7 +66,7 @@ defmodule Changelog.Person do defp changeset_with_allowed_params(struct, params, allowed) do struct - |> cast_attachments(params, ~w(avatar)) + |> cast_attachments(params, ~w(avatar), allow_urls: true) |> cast(params, allowed) |> validate_required([:name, :email, :handle]) |> validate_format(:website, Regexp.http, message: Regexp.http_message) diff --git a/lib/changelog_web/templates/admin/person/_form.html.eex b/lib/changelog_web/templates/admin/person/_form.html.eex index 6596340dd86..bfe953a7777 100644 --- a/lib/changelog_web/templates/admin/person/_form.html.eex +++ b/lib/changelog_web/templates/admin/person/_form.html.eex @@ -51,7 +51,12 @@
<% end %> <%= error_message(f, :avatar) %> - <%= file_input f, :avatar %> +
+ <%= text_input f, :avatar, placeholder: "Avatar image URL", value: "" %> +
+
+ <%= file_input f, :avatar %> +
diff --git a/test/changelog/data/person_test.exs b/test/changelog/data/person_test.exs index 6ee42d5b1e5..03a8b5529ef 100644 --- a/test/changelog/data/person_test.exs +++ b/test/changelog/data/person_test.exs @@ -16,6 +16,17 @@ defmodule Changelog.PersonTest do refute changeset.valid? end + test "admin_changeset with a local file path image URL attribute" do + person = insert(:person) + local_file_path = File.cwd! <> "/test/fixtures/avatar600x600.png" + attrs = Map.put(@valid_attrs, :avatar, local_file_path) + + changeset = Person.admin_changeset(person, attrs) + + assert changeset.valid? + refute Map.has_key?(changeset.changes, :avatar) + end + test "encoded_auth and decoded_auth" do user = %Person{email: "jenny@hits.com", auth_token: "8675309"} {:ok, encoded} = Person.encoded_auth(user) diff --git a/test/fixtures/avatar600x600.png b/test/fixtures/avatar600x600.png new file mode 100644 index 0000000000000000000000000000000000000000..d5821c6f2677121ac1ca65987edb2833d3cb3017 GIT binary patch literal 1754 zcmb_c{Z|r(8pgHTElX`%$&!hiYb@`K?i#r=ll6g0g;=6yrlpo2Ad1R|-)uW7OS^h@ zO>-)G??^q7P6&&n0UJov08J4g@Z*4~OHlBOAVfz0!`^eA^PclQ?|Gj0`Q^P46CLU0 z>E{Um0KAY|EiG!SpJ^F0pMH>WB%QNIj#T865Yd~^~H`iWc>F=`8dJK>egyyWm zzpKo2VGT1_Hqri>bF>W(Y?V7e+xfE-yD|7hvYk5wM>-DKRT0Q+xu+&v>z z_{?A}e9!Y)nN+8`bzztrDN=UDv4!w5A+qWonY=DG8!`ENt)qdYoA^mE*a+sH@#@`% z)~Txw8bzx-Lq0YOp+PHQ;N#;*b!w_&^taf`Zv29qdV*jUCDd2D*~hTr0}DeOB*#E5 zK3?M24#Q=Pm7B7an0(dqrS@v)E#8$Z9}4^Sn6np~#YKyhP@k$(840wSE8$+H*;7zf zhz!UsSJmE;X_DNLrCkKo4-x+_@2ugncK}llDzu-CQNzn`AP4Ht)4Kx0@M_=9wO60) z`z-Eq_uXx!cQ1i{{j(CNxO})?U2C!AU6|33yC0HrVtUE%l;2d>vTk=3HzYpTVuwsz zeh;#m@g^X%;UxrjXomc8NA;-t{CfMfE-r}6Y3l_Z(ILuj;v)<;jZqk(b{BiUhiu1oU? z7FAqIKx&>SzCHNKL_b&P`65U({;w%Zzzcjrkc+Sv$J@Te)~^^kvil2Bg%;Wep`)6` zcYwF`2o3Kh9^0Y7sUC2RVu(C-w03x_J6h{(97p&%l}NtN1PQ#UL`o8qxY3N|?e#jG z*w^ahCotCJA9P;3u0NrwT>9J|p3+!HrG%X7Uet1s@n4&|u_u$DaCjwZz@iH9T>$Er zYApY|)INE9?$}^lDD(O&wY{}O`C!%O!Cs=-gq(F{Ceb_vvyhYAEoD5!}w#x#&X z;XP^QBVQ*nG!zcMsdi3uWM|eXR6JkA1fu5a64!BcIok;%JuFdh@v*00;$AFm9b77! z%lQ!KDcQ1&DAflNrKv;J3b8aa_}!uC_z!1R)G(!0v~TR_^J|mpj+*&a)sHM(vcU4w zL){OORfR3eo!knl$Ir_%#UsntI(M)acP_sm=#b+p6Unsme2?ZU115-3@-fBVyY%<` z0(c%AN6h)woD)MspPCrfQvOS#=lw@e@;*5%IW(B}j3?tpm}RUEvb|X~Nu=8MnTmM( z>9gta4_{RzRXd4ArGE2|Ia5nF(=t+^t6#b-*8X<$RvUT#Xa5t1v#+h!v5qMg2#it8 zEDHWaFYc&)v+oMP8lH=XLqqk9NGTv`^^bF{;m`Ec9hdqylO^c>qO^g-KbK>(c+F6-Q zhS|RdY_Ru~+=GJaQJ=dmbZ19zlEe}GrH;7-Dd<%DX!4YU$MDdi_xXcy^2fs zpC;*dQ6I;;ZW+(|wr|8~(shn;312#0J{fjR&#KttTXEjDDWnmsxptY8qtDI@PjP8~ o^4w%ADz09v{99kRTddLLvC|OR@(;Ad`BoZ