From 690157f130df039dacb5cc32fa833e50728f9409 Mon Sep 17 00:00:00 2001 From: Bodo Tasche Date: Mon, 4 Mar 2019 09:17:45 +0100 Subject: [PATCH] Add deleges sign writing images (#336) * design done for deleges * added notify button * next steps * upsert * removed unneeded series * working deleges integration * fixed spelling error * testss * finalized first version of sign_writings --- assets/css/objects/_video.css | 40 +++++ assets/gulpfile.js | 1 - assets/static/images/deleges/signimages-1.png | Bin 0 -> 2591 bytes assets/static/images/deleges/signimages-2.png | Bin 0 -> 2389 bytes assets/static/images/deleges/signimages-3.png | Bin 0 -> 2801 bytes config/config.exs | 3 +- lib/sign_dict/models/entry.ex | 9 + lib/sign_dict/models/list.ex | 55 +++--- lib/sign_dict/models/sign_writing.ex | 75 +++++++++ .../services/fetch_deleges_data_for_entry.ex | 116 +++++++++++++ lib/sign_dict/worker/refresh_sign_writings.ex | 22 +++ .../controllers/contact_controller.ex | 25 ++- .../controllers/entry_controller.ex | 88 ++++++---- .../templates/entry/show.html.eex | 64 +++++-- .../uploaders/sign_writing_image.ex | 19 +++ mix.exs | 4 +- mix.lock | 1 + priv/gettext/de/LC_MESSAGES/default.po | 63 ++++--- priv/gettext/default.pot | 61 ++++--- priv/gettext/en/LC_MESSAGES/default.po | 61 ++++--- .../20190301153628_create_sign_writings.exs | 19 +++ ...4924_add_deleges_updated_at_to_entries.exs | 9 + .../fetch_deleges_data_for_entry_test.exs | 156 ++++++++++++++++++ .../worker/refresh_sign_writings_test.exs | 22 +++ .../controllers/entry_controller_test.exs | 30 ++++ 25 files changed, 794 insertions(+), 149 deletions(-) create mode 100644 assets/static/images/deleges/signimages-1.png create mode 100644 assets/static/images/deleges/signimages-2.png create mode 100644 assets/static/images/deleges/signimages-3.png create mode 100644 lib/sign_dict/models/sign_writing.ex create mode 100644 lib/sign_dict/services/fetch_deleges_data_for_entry.ex create mode 100644 lib/sign_dict/worker/refresh_sign_writings.ex create mode 100644 lib/sign_dict_web/uploaders/sign_writing_image.ex create mode 100644 priv/repo/migrations/20190301153628_create_sign_writings.exs create mode 100644 priv/repo/migrations/20190301154924_add_deleges_updated_at_to_entries.exs create mode 100644 test/sign_dict/services/fetch_deleges_data_for_entry_test.exs create mode 100644 test/sign_dict/worker/refresh_sign_writings_test.exs diff --git a/assets/css/objects/_video.css b/assets/css/objects/_video.css index a774676c..ecae02ee 100644 --- a/assets/css/objects/_video.css +++ b/assets/css/objects/_video.css @@ -43,6 +43,46 @@ text-overflow: ellipsis; } +.so-video-details--si { + color: $deactive; + font-size: 0.7em; + margin-bottom: 0.5em; +} + +.so-video-details--voting { + margin-top: 1em; +} + +.so-video-details--signimage { + position: relative; + border: 1px solid $deactive; + padding: 1em; + margin-right: 1em; + display: inline-block; +} + +.so-video-details--signimage--report { + position: absolute; + top: 0em; + right: 0em; + width: 100%; + height: 100%; + text-align: right; + padding: 0.5em; + opacity: 0; +} + +.so-video-details--signimage--report:hover { + opacity: 1; +} + +.so-video-details--signimage-deleges { + color: $deactive; + font-size: 0.7em; + margin-top: 1em; + margin-bottom: 0.5em; +} + @media all and (max-width: 48em) { .so-video--player { padding: 0; diff --git a/assets/gulpfile.js b/assets/gulpfile.js index 9e266e15..670c231f 100644 --- a/assets/gulpfile.js +++ b/assets/gulpfile.js @@ -100,7 +100,6 @@ gulp.task("js", function() { }); gulp.task("watch", function() { - gulp.series(["static", "css", "js"]); gulp.watch("static/**/*", gulp.series("static")); gulp.watch("css/**/*", gulp.series("css")); gulp.watch("js/**/*", gulp.series("js")); diff --git a/assets/static/images/deleges/signimages-1.png b/assets/static/images/deleges/signimages-1.png new file mode 100644 index 0000000000000000000000000000000000000000..be0c609d88e4fb16a77df8ad0d3abc32f3abe11f GIT binary patch literal 2591 zcmb7GX*e6`7LGJ3_ShLx2~|ri)fqcY#1dMSBGw|++KF1LmRd5Qc8Y1$615eTph4_g zOG-=aH5F=UZSBy|8d22b{=3h;fA0P9ec$u`Iq!MSdCzy=)N9sgex6@=0002LrG=?2 z`}JU-Ch%$Y{g@eg9{}K9votlv-ksYjaZC}tBAWcFSaUst`xMU_xuzgYya_Bv<{=18 z{jRsJy2d;!cS(uxq~LbeAdxzxp-=Z z!}y>cke&chLcu_35OAU#2=xS<_&>p)Xh_gvkJ>|ZH-zZ-69#PUc za&PgspJ?*IJP%$*g&hCbBa+Caw0rlN$A^*6gBJ9omJ^Z4AhGifq6-}nB7S~;H`yU> z(lexPjudE?qqEPHhpxVsGfCzP?fpDcmu_F@klKLjrH2#Z=4hM<6{1nh-zgWpUJs=| zyWU#gRa1>AH8*0WOK1!fY6Y0G=~hp~HQZeT-&4(wejYPepN^XGB5|hR+lxJaoDLb+ zkNBKYfzD1t#Qz+?=4C+*#8q>&@pCDUG;kf;U#szmB?DAj*m{gUvwqTF;62qvA%O&IOVe6);!V0E|K+b`| zbO}2;t(0j3GmQV~y6sAK(hT{wHd>i^dV8T$@$y4e<=c)CMTdS23=0m4JN}_isEt>l z`i!Z+Ek%@m_mEOIbgXwR5BW%6d6g~O^FD6(Q|fItp;LQkRba1n(7Z1`wkztJx1UG_ zZ*L$w`41ATn(uJPhwd1~SDk9tX&LSEY4)QCAwDx=<$y1 zyvy6{M$_`2R`QD(ND*?_DqmS0&O^_UwDE=Z42nxjE)CZmy{7@17x%3TmiY4_HB+F@ zccOe{eXR6dU|rg5qld{C+JxT6ZAa!Qc7~4>o9-Rn`2xwuN9f4tg$}61V!KKVB3)W2 z)Aeqhu%o`|TDvMiP-`__nUQ_QNcizZFJX~$R*gKeVqcFB_Z{%(H<_pK44T-?Gy89Y zF*S^On~G#MUy?oR*~t1}Cok(tXCE05n62tAR~4lJl~>!j(1Z{(=*4&>~_?|q)hYjN4nh%QvD zQ8w&)97GtC)dD1CLtroBw-~LDMme6Iq;M_mzb!F%^L2?t@2OE{;dJ(me;ysU9qVlP zh6J`O3rHg3VmpqP()%NS^jJUl=w4Z_zpw+PxOcn!<~xpzJ^Wj3A<}}UZ%@OeS&1gq z+W!8fyd)l9-_)orVBIj=Ul{vouhzP9gvvQ0Ca+ULWMvutU6CB;UPKhOtri zYP}4Y4Qu}PGGTe=<0~7ye4EIMnLSPw*E6rx@dqE+f-7;9J%%x9FPpx(_}07jcG`&9#6Jr z=lgd_D0QdCC%O@zvHA=a4x0}(xMNC5ZOj~h${jl+Qwz(PN*=1T469Ue?-(kymeHD3 z-k?N|)U=<2{%Q`hhBn!aGe&8-DxOd*%ah+vL6^QcRSnH9W0+I4lCra;4!zzM>p=k- zp}}bLF9mM5MmlmLYbOy{{M7xLwi|qih2-i#gUhT7Qu{0WWJe zgQ<8Rj^F0KpsYHtLFD#)zc(Z)ZLxyiCX=cbzL|Q>f_N1xH=QPSo{)NNw%$!7wGqG4 zm+s8~^$6RZ-5dTTnIg&Au_qBErXdF!QdYR&09Sx0rr(O#oHS$qJ@klpMBt(GP6m~F z@}tWuRCSKdJO`gl0Ya&-Uky-F1g746|3Jg{s*oOtK$}cm^GWC2Yi$*xURdxu`mHdn z%1!ERt1gdI62aoaZ*JZ*?-OLumSeI!=Q|><2sA|RZ$tMIQ!QF?WU3$IT#Pb`xivsvDH+t;w-jT`ER(phF>4?)39p=zuyI2nq!M28771);=TNLRpmOjB|pm z{f<>0OJ0{lLQs{2gX=><0AWr7#1I(_%mG7)H^BY;$SxSTA3)$vxQm1kUR_G8s1Qx; zn-D^yWuPJ90}~S`k@S&?2N+w-on(!zy!_6r!7Z|NiM}P+XN<&uAT!pysU?e{3ZrtH zZ}bD5horRw!3Q$&Um#s%A7k*Dq!cN+fKyxod^*!A zga~IbJ=%%Y(NpB%T?1kca%-^{r`~!R;ID~NT~8H_Mm54z*U3C_pN1ZUW;)&LH&Y!n z0b7bX9NWgpcYOua-St>S8$C@xSF7WCtiZ$LY%LKuDGk+70S@Uib!pAw=iviWR!3?} z?0uRXyA6Vqt|`JLk>G3+yYUbpys~ZH-OYsEcrdZ=QX<>9K)@UqhcR-*v#364a-;Cb zU+lJdAMq8Hi54`69#ZCa1ILHGyY)vvw) literal 0 HcmV?d00001 diff --git a/assets/static/images/deleges/signimages-2.png b/assets/static/images/deleges/signimages-2.png new file mode 100644 index 0000000000000000000000000000000000000000..7d06bd018c044b888cd76b2a6b5d8fc4dc97dca6 GIT binary patch literal 2389 zcmb7Gdo~0C%eIORDjr*Tc>L#3O9|{67mPD2?unD zt@TrHi#5Vs`QLMf$!;qg4mDD*Qn zA44kGcdpqoIE7xCNd<0WPGdbUT;Hfx?N;3UYXOwEnd1`xgOH4LVFysX=WD znb3_l33!vE|MlfP=_}#B0kP*P#3M6`*J7!&Pp%mqu#rJeaCG){yXla{OZ_D^j^*alc6YDxbBr;%r0Z zJ$-lA{hsY~zaSf^r5Gy(@%#H1*?Mj{?oGiLNSM&gjT<2Ff~a%LzPm~V+I%gnXn8L8 z>g;>FWJR6bh~GVWf7Qc`J=!9}W(FR_uZfoS81Wp758r`NWC4%{xAl<~0k5zxJz`<3 zBFKllJzHQFK5H$m=1>gbgTMpZc?|ca`fVjwhfD1d=mz$!mc}~)8rbS{ErX`fspe34 z&OSm3`TpW%hR*3SZC;j^BRLR}=Jxcw-G%Ef;a+a2t{r7v9%YAv}-gAIl50itW)?D9;n zlw}+5K8KVL+iu@+%k$|Z_hWzk!DzH*D$>;Gsi75b7dp_zt0Q`5Py@|45byO09z5Pw z5qDv_zmQ3h4+`=-qTF7fic(M6E) z9FSUF6?5{u6LI+Y^`F&z_EL~rukFAIy2+`t_5P@yG}Y6jSftTLZTQ@W*HwKS|Ht8R z9;`hDGg>d#_+tLik06;FbDy3y&B{JmvpEdu__;XYDL4|T+`~F8?4a=DjV`M;kD;Qk*P>t+CDaGITR_D>;J+vfr8GV_C2_DlX@fx#3CfdJ_CYC_PHj{Vg4Z0i7 zQPA4St>JU7;V=sa8_UdA`-R+74`ObVp(2BHPZpY?4U!820OiB_WjXEV6*s-tg$a*SpCs}bC=q*y&1a>FP@mR!}djC^sTNcy6 zRn_$J%UPthZ)_ikQW6tfhVqin%y2YNd)HrRUVdVEW&y}YxdVCVN3&^nuk=zHPd>M%qBld5?G>J* z#qK-+uZ`vE%?SM5?Tw#8b+6SWJe8+Uf(21DZ9rmQBJq8_y2G|gEA@&&m`ICEt1Zls zG_K<8{_xer70i|7WR>n8Lm^SgGRCv$qg{Pvx|>2KV;L;5&L>-ODjvkdT#||?D%mVx zaPRP_#_@Ye5%K;mYr75}ZK!NCxS)i40LPrZ!UNvS2fq%~3o66Mrf90>7PfChj1*d~2fMU5Ye3WXM@?>b~AE4I32uMmo|Kihh> zBJdB;P_?4dT6LL~5wG66KSplLJX9X8;FKI3QpA2*~_@4^S7@ zrej1_|1p3njF4+<%=qil-$U;rPMz8@EH*0fZw3D*=>MIr5r1uaEnj!gQX**%Y?mN* MM;)wda9(l$1W?plm;e9( literal 0 HcmV?d00001 diff --git a/assets/static/images/deleges/signimages-3.png b/assets/static/images/deleges/signimages-3.png new file mode 100644 index 0000000000000000000000000000000000000000..9f8338970e74644aa7fb39f3d0d70f7d4cd2ecb4 GIT binary patch literal 2801 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 SlCpTEEWpkRi>X3;B>V?=i5*}7 literal 0 HcmV?d00001 diff --git a/config/config.exs b/config/config.exs index af258035..3ec635e3 100644 --- a/config/config.exs +++ b/config/config.exs @@ -62,7 +62,8 @@ config :exq, queues: [ # the transcoder queue is rate limited by jw_player => only 1 worker {"transcoder", 1}, - {"default", 3} + {"default", 3}, + {"sign_writings", 1} ], json_library: Jason diff --git a/lib/sign_dict/models/entry.ex b/lib/sign_dict/models/entry.ex index 5732cf5c..718d56ff 100644 --- a/lib/sign_dict/models/entry.ex +++ b/lib/sign_dict/models/entry.ex @@ -4,6 +4,7 @@ defmodule SignDict.Entry do alias SignDict.Video alias SignDict.Repo alias SignDict.Entry + alias SignDict.SignWriting alias Ecto.Adapters.SQL alias SignDict.PostgresQueryHelper @@ -15,11 +16,13 @@ defmodule SignDict.Entry do field(:description, :string) field(:type, :string) field(:url, :string, virtual: true) + field(:deleges_updated_at, :utc_datetime) belongs_to(:language, SignDict.Language) has_many(:videos, Video) has_many(:list_entries, SignDict.ListEntry) + has_many(:sign_writings, SignWriting) has_many(:lists, through: [:list_entries, :list]) belongs_to(:current_video, Video) @@ -37,6 +40,12 @@ defmodule SignDict.Entry do |> foreign_key_constraint(:language_id) end + def deleges_updated_at_changeset(struct, params \\ %{}) do + struct + |> cast(params, [:deleges_updated_at]) + |> validate_required(:deleges_updated_at) + end + def find_by_changeset(changeset) do if changeset.valid? do Repo.get_by( diff --git a/lib/sign_dict/models/list.ex b/lib/sign_dict/models/list.ex index 3c82984d..b7f39339 100644 --- a/lib/sign_dict/models/list.ex +++ b/lib/sign_dict/models/list.ex @@ -8,15 +8,15 @@ defmodule SignDict.List do @types ["categorie-list"] @sort_orders ["manual", "alphabetical_desc", "alphabetical_asc"] - @primary_key {:id, SignDict.Permalink, autogenerate: true} + @primary_key {:id, SignDict.Permalink, autogenerate: true} schema "lists" do - field :name, :string - field :description, :string - field :type, :string - field :sort_order, :string - belongs_to :created_by, SignDict.User - has_many :list_entries, SignDict.ListEntry - has_many :entries, through: [:list_entries, :entry] + field(:name, :string) + field(:description, :string) + field(:type, :string) + field(:sort_order, :string) + belongs_to(:created_by, SignDict.User) + has_many(:list_entries, SignDict.ListEntry) + has_many(:entries, through: [:list_entries, :entry]) timestamps() end @@ -36,33 +36,37 @@ defmodule SignDict.List do |> validate_inclusion(:sort_order, @sort_orders) end - # TODO: - # * Paginate list entries + # TODO: Paginate list entries def entries(%SignDict.List{id: id, sort_order: "manual"}) do from( list_entry in ListEntry, join: entry in assoc(list_entry, :entry), - where: list_entry.list_id == ^id and not is_nil(entry.current_video_id), order_by: :sort_order + where: list_entry.list_id == ^id and not is_nil(entry.current_video_id), + order_by: :sort_order ) - |> Repo.all + |> Repo.all() |> Repo.preload(entry: [:current_video]) end + def entries(%SignDict.List{id: id, sort_order: "alphabetical_asc"}) do from( list_entry in ListEntry, join: entry in assoc(list_entry, :entry), - where: list_entry.list_id == ^id and not is_nil(entry.current_video_id), order_by: entry.text + where: list_entry.list_id == ^id and not is_nil(entry.current_video_id), + order_by: entry.text ) - |> Repo.all + |> Repo.all() |> Repo.preload(entry: [:current_video]) end + def entries(%SignDict.List{id: id, sort_order: "alphabetical_desc"}) do from( list_entry in ListEntry, join: entry in assoc(list_entry, :entry), - where: list_entry.list_id == ^id and not is_nil(entry.current_video_id), order_by: [desc: entry.text] + where: list_entry.list_id == ^id and not is_nil(entry.current_video_id), + order_by: [desc: entry.text] ) - |> Repo.all + |> Repo.all() |> Repo.preload(entry: [:current_video]) end @@ -79,19 +83,24 @@ defmodule SignDict.List do def move_entry(list_entry, direction) do target_sort_order = list_entry.sort_order + direction - swap_target = Repo.get_by(ListEntry, - list_id: list_entry.list_id, - sort_order: target_sort_order) + + swap_target = + Repo.get_by(ListEntry, + list_id: list_entry.list_id, + sort_order: target_sort_order + ) + swap_list_entry_position(list_entry, swap_target) end def swap_list_entry_position(list_entry, swap_target) when is_nil(swap_target) do list_entry end + def swap_list_entry_position(list_entry, swap_target) do - ListEntry.update_sort_order(list_entry, nil) + ListEntry.update_sort_order(list_entry, nil) ListEntry.update_sort_order(swap_target, list_entry.sort_order) - ListEntry.update_sort_order(list_entry, swap_target.sort_order) + ListEntry.update_sort_order(list_entry, swap_target.sort_order) Repo.get(ListEntry, list_entry.id) end @@ -101,9 +110,8 @@ defmodule SignDict.List do join: e in ListEntry, where: l.id == e.list_id and e.entry_id == ^entry.id and l.type == ^type ) - |> Repo.all + |> Repo.all() end - end defimpl Phoenix.Param, for: SignDict.List do @@ -111,4 +119,3 @@ defimpl Phoenix.Param, for: SignDict.List do SignDict.Permalink.to_permalink(id, name) end end - diff --git a/lib/sign_dict/models/sign_writing.ex b/lib/sign_dict/models/sign_writing.ex new file mode 100644 index 00000000..dc24e068 --- /dev/null +++ b/lib/sign_dict/models/sign_writing.ex @@ -0,0 +1,75 @@ +defmodule SignDict.SignWriting do + use SignDictWeb, :model + use Arc.Ecto.Schema + import StateMc.EctoSm + + alias SignDict.Repo + + @states [:active, :marked_as_wrong, :wrong] + + schema "sign_writings" do + field(:word, :string) + field(:width, :integer) + field(:deleges_id, :integer) + field(:state, :string, default: "active") + field(:image, SignDictWeb.SignWritingImage.Type) + + belongs_to(:entry, SignDict.Entry) + timestamps() + end + + statemc :state do + defstate(@states) + + defevent(:mark_as_wrong, %{from: [:active], to: :marked_as_wrong}, fn changeset -> + changeset |> Repo.update() + end) + + defevent(:wrong, %{from: [:mark_as_wrong], to: :wrong}, fn changeset -> + changeset |> Repo.update() + end) + + defevent(:correct, %{from: [:mark_as_wrong], to: :active}, fn changeset -> + changeset |> Repo.update() + end) + end + + def changeset(struct, params \\ %{}) do + struct + |> cast(params, [ + :word, + :width, + :deleges_id, + :entry_id, + :state + ]) + |> cast_attachments(params, [:image]) + |> validate_required([ + :word, + :width, + :deleges_id, + :entry_id + ]) + |> foreign_key_constraint(:entry_id) + |> validate_state() + end + + def valid_state?(state) when is_atom(state), do: @states |> Enum.member?(state) + def valid_state?(state), do: valid_state?(String.to_atom(state)) + + # Makes sure that the video-state is in the list of possible states. + defp validate_state(changeset) do + if changeset && changeset.valid? do + state = get_field(changeset, :state) + + if valid_state?(state) do + changeset + else + error_msg = "must be in the list of " <> Enum.join(@states, ", ") + add_error(changeset, :state, error_msg) + end + else + changeset + end + end +end diff --git a/lib/sign_dict/services/fetch_deleges_data_for_entry.ex b/lib/sign_dict/services/fetch_deleges_data_for_entry.ex new file mode 100644 index 00000000..118f7036 --- /dev/null +++ b/lib/sign_dict/services/fetch_deleges_data_for_entry.ex @@ -0,0 +1,116 @@ +defmodule SignDict.Services.FetchDelegesDataForEntry do + alias SignDict.SignWriting + alias SignDict.Entry + alias SignDict.Repo + + import Ecto.Query + + def fetch_for(entry, http_poison \\ HTTPoison) do + result = + http_poison.get!( + "https://server.delegs.de/delegseditor/signwritingeditor/signitems?word=#{ + URI.encode(entry.text) + }" + ) + + if result.status_code == 200 do + entry + |> parse_data(result.body) + |> delete_outdated_items(entry) + |> fetch_images(http_poison) + + entry + |> Entry.deleges_updated_at_changeset(%{deleges_updated_at: DateTime.utc_now()}) + |> Repo.update!() + end + end + + defp parse_data(entry, body) do + body + |> String.slice(1, String.length(body) - 2) + |> Jason.decode!() + |> Enum.map(fn data -> + %{ + deleges_id: String.to_integer(data["id"]), + width: String.to_integer(data["width"]), + word: data["word"], + entry_id: entry.id + } + end) + |> Enum.map(fn entry -> + entry.deleges_id + |> find_sign_writing() + |> create_or_update_sign_writing(entry) + end) + end + + defp create_or_update_sign_writing(nil, entry) do + %SignWriting{} + |> SignWriting.changeset(entry) + |> Repo.insert!() + end + + defp create_or_update_sign_writing(sign_writing, entry) do + sign_writing + |> SignWriting.changeset(Map.delete(entry, :entry_id)) + |> Repo.update!() + end + + defp find_sign_writing(deleges_id) do + SignDict.Repo.get_by(SignWriting, deleges_id: deleges_id) + |> SignDict.Repo.preload(:entry) + end + + defp fetch_images(sign_writings, http_poison) do + sign_writings + |> Enum.filter(fn writing -> writing.state == "active" end) + |> Enum.sort_by(& &1.deleges_id) + |> Enum.take(5) + |> Enum.each(fn writing -> fetch_image(writing, http_poison) end) + end + + defp fetch_image(sign_writing, http_poison) do + result = + http_poison.get!( + "https://server.delegs.de/delegseditor/signwritingeditor/signimages?upperId=#{ + sign_writing.deleges_id + }&lowerId=#{URI.encode(sign_writing.word)}&signlocale=DGS", + %{ + "If-Modified-Since" => last_modified_of(sign_writing) + } + ) + + if result.status_code == 200 do + {:ok, filename} = Briefly.create() + File.write!(filename, result.body) + + SignWriting.changeset(sign_writing, %{ + image: %Plug.Upload{ + content_type: "image/png", + filename: "file.png", + path: filename + } + }) + |> Repo.update!() + end + end + + defp delete_outdated_items(sign_writings, entry) do + deleges_ids = Enum.map(sign_writings, & &1.deleges_id) + entry_id = entry.id + + SignWriting + |> where([p], p.entry_id == ^entry_id and p.deleges_id not in ^deleges_ids) + |> Repo.delete_all() + + sign_writings + end + + defp last_modified_of(%SignWriting{image: nil}) do + nil + end + + defp last_modified_of(sign_writing) do + Timex.format!(Timex.to_datetime(sign_writing.updated_at, "GMT"), "{RFC1123}") + end +end diff --git a/lib/sign_dict/worker/refresh_sign_writings.ex b/lib/sign_dict/worker/refresh_sign_writings.ex new file mode 100644 index 00000000..47caada5 --- /dev/null +++ b/lib/sign_dict/worker/refresh_sign_writings.ex @@ -0,0 +1,22 @@ +defmodule SignDict.Worker.RefreshSignWritings do + require Bugsnex + + alias SignDict.Repo + alias SignDict.Entry + + @process_sleep_time 100 + + def perform( + entry_id, + fetch_service \\ SignDict.Services.FetchDelegesDataForEntry, + sleep_ms \\ @process_sleep_time + ) do + Bugsnex.handle_errors %{entry_id: entry_id} do + # Rate limit the workers, sadly i didn't find a better way :( + Process.sleep(sleep_ms) + + entry = Repo.get(Entry, entry_id) + fetch_service.fetch_for(entry) + end + end +end diff --git a/lib/sign_dict_web/controllers/contact_controller.ex b/lib/sign_dict_web/controllers/contact_controller.ex index 2d79e075..083fcd53 100644 --- a/lib/sign_dict_web/controllers/contact_controller.ex +++ b/lib/sign_dict_web/controllers/contact_controller.ex @@ -6,29 +6,37 @@ defmodule SignDictWeb.ContactController do @recaptcha Application.get_env(:sign_dict, :recaptcha)[:library] def new(conn, _params) do - IO.inspect Application.get_env(:sign_dict, :recaptcha) email = user_email(conn.assigns) - render conn, "new.html", layout: {SignDictWeb.LayoutView, "app.html"}, - email: email, content: "" + + render(conn, "new.html", + layout: {SignDictWeb.LayoutView, "app.html"}, + email: email, + content: "" + ) end - def create(conn = %{assigns: %{current_user: current_user}}, params) when not is_nil(current_user) do + def create(conn = %{assigns: %{current_user: current_user}}, params) + when not is_nil(current_user) do %{"contact" => %{"email" => email, "content" => content}} = params send_email(conn, email, content) end def create(conn, params) do %{"contact" => %{"email" => email, "content" => content}} = params + case @recaptcha.verify(params["g-recaptcha-response"]) do {:ok, _response} -> send_email(conn, email, content) - {:error, _errors} -> show_error(conn, email, content) + + {:error, _errors} -> + show_error(conn, email, content) end end defp send_email(conn, email, content) do mail = Email.contact_form(email, content) Mailer.deliver_later(mail) + conn |> put_flash(:info, gettext("The email was sent.")) |> redirect(to: "/") @@ -37,8 +45,11 @@ defmodule SignDictWeb.ContactController do defp show_error(conn, email, content) do conn |> put_flash(:error, gettext("The recaptcha was wrong, email not sent.")) - |> render("new.html", layout: {SignDictWeb.LayoutView, "app.html"}, - email: email, content: content) + |> render("new.html", + layout: {SignDictWeb.LayoutView, "app.html"}, + email: email, + content: content + ) end defp user_email(%{assigns: %{current_user: %{email: email}}}) when not is_nil(email) do diff --git a/lib/sign_dict_web/controllers/entry_controller.ex b/lib/sign_dict_web/controllers/entry_controller.ex index 5a22e3de..92ac5e51 100644 --- a/lib/sign_dict_web/controllers/entry_controller.ex +++ b/lib/sign_dict_web/controllers/entry_controller.ex @@ -12,18 +12,20 @@ defmodule SignDictWeb.EntryController do def index(conn, params) do letter = params["letter"] || "A" - entries = Entry.active_entries - |> Entry.with_current_video - |> Entry.for_letter(letter) - |> Repo.paginate(params) + + entries = + Entry.active_entries() + |> Entry.with_current_video() + |> Entry.for_letter(letter) + |> Repo.paginate(params) render(conn, "index.html", - layout: {SignDictWeb.LayoutView, "app.html"}, - entries: entries, - searchbar: true, - letter: letter, - title: gettext("All entries") - ) + layout: {SignDictWeb.LayoutView, "app.html"}, + entries: entries, + searchbar: true, + letter: letter, + title: gettext("All entries") + ) end def show(conn, %{"id" => id}) do @@ -31,6 +33,7 @@ defmodule SignDictWeb.EntryController do conn |> EntryVideoLoader.load_videos_for_entry(id: id) |> add_lists_for_entry + |> refresh_sign_writings |> render_entry else redirect_to_search(conn, conn.params["id"]) @@ -41,6 +44,7 @@ defmodule SignDictWeb.EntryController do conn |> EntryVideoLoader.load_videos_for_entry(id: id, video_id: video_id) |> add_lists_for_entry + |> refresh_sign_writings |> render_entry end @@ -53,6 +57,7 @@ defmodule SignDictWeb.EntryController do def create(conn, %{"entry" => entry_params}) do changeset = Entry.changeset(%Entry{}, entry_params) entry = Entry.find_by_changeset(changeset) + if entry do redirect(conn, to: recorder_path(conn, :index, entry.id)) else @@ -65,36 +70,49 @@ defmodule SignDictWeb.EntryController do {:ok, entry} -> conn |> redirect(to: recorder_path(conn, :index, entry.id)) + {:error, changeset} -> languages = Repo.all(Language) render(conn, "new.html", changeset: changeset, languages: languages) end end - defp render_entry(%{conn: conn, videos: videos, entry: entry}) when length(videos) == 0 and not is_nil(entry) do + defp render_entry(%{conn: conn, videos: videos, entry: entry}) + when length(videos) == 0 and not is_nil(entry) do redirect_no_videos(conn) end + defp render_entry(%{conn: conn, entry: entry, video: video}) - when is_nil(video) and not is_nil(entry) do + when is_nil(video) and not is_nil(entry) do conn |> redirect(to: entry_path(conn, :show, entry)) end - defp render_entry(%{conn: conn, entry: entry, videos: videos, - video: video, voted: voted, lists: lists}) do + + defp render_entry(%{ + conn: conn, + entry: entry, + videos: videos, + video: video, + voted: voted, + lists: lists + }) do + entry = SignDict.Repo.preload(entry, :sign_writings) + render(conn, "show.html", - layout: {SignDictWeb.LayoutView, "empty.html"}, - entry: entry, - video: video, - videos: videos, - lists: lists, - share_url: Helpers.url(conn) <> conn.request_path, - share_text: gettext("Watch this sign for \"%{sign}\" on @signdict", sign: entry.text), - voted_video: voted, - searchbar: true, - ogtags: OpenGraph.to_metadata(entry, video), - title: gettext("Sign for %{sign}", sign: entry.text) - ) + layout: {SignDictWeb.LayoutView, "empty.html"}, + entry: entry, + video: video, + videos: videos, + lists: lists, + share_url: Helpers.url(conn) <> conn.request_path, + share_text: gettext("Watch this sign for \"%{sign}\" on @signdict", sign: entry.text), + voted_video: voted, + searchbar: true, + ogtags: OpenGraph.to_metadata(entry, video), + title: gettext("Sign for %{sign}", sign: entry.text) + ) end + defp render_entry(%{conn: conn}) do if conn.params["id"] && conn.params["id"] =~ ~r/\d*-\D*/ do redirect_to_search(conn, conn.params["id"]) @@ -110,8 +128,10 @@ defmodule SignDictWeb.EntryController do end defp redirect_to_search(conn, id) do - query = Regex.named_captures(~r/^(\d*-)?(?.*)$/, id)["query"] - |> String.replace("-", " ") + query = + Regex.named_captures(~r/^(\d*-)?(?.*)$/, id)["query"] + |> String.replace("-", " ") + conn |> redirect(to: search_path(conn, :index, q: query)) end @@ -119,9 +139,21 @@ defmodule SignDictWeb.EntryController do defp add_lists_for_entry(%{entry: entry} = params) when is_nil(entry) do params end + defp add_lists_for_entry(%{entry: entry} = params) do lists = List.lists_with_entry(entry) Map.merge(params, %{lists: lists}) end + defp refresh_sign_writings(%{entry: entry} = params) do + if entry do + if entry.deleges_updated_at == nil || + Timex.before?(entry.deleges_updated_at, Timex.shift(Timex.now(), days: -3)) do + queue = Application.get_env(:sign_dict, :queue)[:library] + queue.enqueue(Exq, "sign_writings", SignDict.Worker.RefreshSignWritings, [entry.id]) + end + end + + params + end end diff --git a/lib/sign_dict_web/templates/entry/show.html.eex b/lib/sign_dict_web/templates/entry/show.html.eex index 7de8120a..19fdbc0f 100644 --- a/lib/sign_dict_web/templates/entry/show.html.eex +++ b/lib/sign_dict_web/templates/entry/show.html.eex @@ -45,7 +45,7 @@ -
+
@@ -94,26 +94,25 @@
-
- <%= link @video.user.name, to: user_path(@conn, :show, @video.user) %>
-
- <%= gettext("User") %> -
+
+ <%= gettext("User") %> <%= link @video.user.name, to: user_path(@conn, :show, @video.user) %>
-
- <%= Date.to_string @video.inserted_at %> -
+
+ <%= gettext("Date of upload") %> -
+ + <%= Date.to_string @video.inserted_at %>
+ <%= if @video.original_href do %> +
+ + <%= gettext("Source") %> + + <%= link @video.copyright, to: @video.original_href %> +
+ <% end %>
- <%= if @video.original_href do %> -

- <%= gettext("Source:") %> - <%= link @video.copyright, to: @video.original_href %> -

- <% end %> Creative Commons License @@ -167,6 +166,39 @@
+ + <%= if Enum.count(@entry.sign_writings) > 0 do %> +
+

<%= gettext("SignWriting") %>

+ +

<%= raw gettext("Some variations of this entry using SignWriting:") %>

+ +
+ <%= for sign_writing <- Enum.sort_by(@entry.sign_writings, &(&1.deleges_id)) do %> +
+ <%= img_tag(SignDictWeb.SignWritingImage.url({sign_writing.image, sign_writing})) %> + +
+ <% end %> +
+ <% end %> + +
+ <%= raw gettext("Thanks to deleges for offering this service.") %> +
+

+ + Creative Commons License + +

+ +
diff --git a/lib/sign_dict_web/uploaders/sign_writing_image.ex b/lib/sign_dict_web/uploaders/sign_writing_image.ex new file mode 100644 index 00000000..8582d4d2 --- /dev/null +++ b/lib/sign_dict_web/uploaders/sign_writing_image.ex @@ -0,0 +1,19 @@ +defmodule SignDictWeb.SignWritingImage do + use Arc.Definition + use Arc.Ecto.Definition + + @versions [:original] + + # Whitelist file extensions: + def validate({file, _}) do + ~w(.jpg .jpeg .gif .png) |> Enum.member?(Path.extname(file.file_name)) + end + + def filename(version, _) do + version + end + + def storage_dir(_version, {_file, scope}) do + "uploads/sign_writing/#{scope.id}" + end +end diff --git a/mix.exs b/mix.exs index 47824b81..bf1cd87d 100644 --- a/mix.exs +++ b/mix.exs @@ -42,7 +42,6 @@ defmodule SignDict.Mixfile do defp deps do [ {:absinthe_plug, "~> 1.4.0"}, - # I got compile errors on higher versions of absinthe {:absinthe, "1.4.16"}, {:arc_ecto, "~> 0.11.1"}, {:arc, "~> 0.11.0"}, @@ -80,7 +79,8 @@ defmodule SignDict.Mixfile do {:state_mc, "~> 0.1.0"}, {:timex, "~> 3.2"}, {:wallaby, "~> 0.22.0", only: :test}, - {:jason, "~> 1.1"} + {:jason, "~> 1.1"}, + {:briefly, "~> 0.3"} ] end diff --git a/mix.lock b/mix.lock index ff28f55c..ffc86e94 100644 --- a/mix.lock +++ b/mix.lock @@ -9,6 +9,7 @@ "base64url": {:hex, :base64url, "0.0.1", "36a90125f5948e3afd7be97662a1504b934dd5dac78451ca6e9abf85a10286be", [:rebar], [], "hexpm"}, "bcrypt_elixir": {:hex, :bcrypt_elixir, "2.0.1", "1061e2114aaac554c12e5c1e4608bf4aadaca839f30d1b85224272facd5e6427", [:make, :mix], [{:comeonin, "~> 5.1", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm"}, "bootleg": {:hex, :bootleg, "0.10.0", "1deacdfa8e82aa52a9a45e52d8249a72fe6983b7bcef5593a903bc7d78ae84b5", [:mix], [{:ssh_client_key_api, "~> 0.2.1", [hex: :ssh_client_key_api, repo: "hexpm", optional: false]}, {:sshkit, "0.1.0", [hex: :sshkit, repo: "hexpm", optional: false]}], "hexpm"}, + "briefly": {:hex, :briefly, "0.3.0", "16e6b76d2070ebc9cbd025fa85cf5dbaf52368c4bd896fb482b5a6b95a540c2f", [:mix], [], "hexpm"}, "bugsnex": {:hex, :bugsnex, "0.4.1", "aba69ab5b6a8504553805b3e2f9491038e4edb22274a4a948f1f4c47f72f3200", [:mix], [{:httpoison, "~> 1.5.0", [hex: :httpoison, repo: "hexpm", optional: false]}, {:plug, "~> 1.6", [hex: :plug, repo: "hexpm", optional: false]}, {:poison, ">= 1.5.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"}, "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm"}, "canada": {:hex, :canada, "1.0.2", "040e4c47609b0a67d5773ac1fbe5e99f840cef173d69b739beda7c98453e0770", [:mix], [], "hexpm"}, diff --git a/priv/gettext/de/LC_MESSAGES/default.po b/priv/gettext/de/LC_MESSAGES/default.po index 8f32e403..c0ff484e 100644 --- a/priv/gettext/de/LC_MESSAGES/default.po +++ b/priv/gettext/de/LC_MESSAGES/default.po @@ -18,8 +18,8 @@ msgstr "Anmeldung benötigt" msgid "Internal server error" msgstr "Interner Serverfehler" -#: lib/sign_dict/services/credential_verifier.ex:16 -#: lib/sign_dict/services/credential_verifier.ex:27 +#: lib/sign_dict/services/credential_verifier.ex:15 +#: lib/sign_dict/services/credential_verifier.ex:26 #: lib/sign_dict_web/controllers/session_controller.ex:28 msgid "Invalid email address or password" msgstr "Ungültige Email-Adresse oder Passwort" @@ -154,20 +154,20 @@ msgstr "Leider darfst du dies nicht." msgid "Send password reset instructions" msgstr "Passwort-Link verschicken" -#: lib/sign_dict_web/controllers/reset_password_controller.ex:28 -#: lib/sign_dict_web/controllers/reset_password_controller.ex:49 +#: lib/sign_dict_web/controllers/reset_password_controller.ex:39 +#: lib/sign_dict_web/controllers/reset_password_controller.ex:70 msgid "Invalid password reset link. Please try again." msgstr "Ungültiger Passwort-Link. Bitte versuche es erneut." -#: lib/sign_dict_web/controllers/reset_password_controller.ex:38 +#: lib/sign_dict_web/controllers/reset_password_controller.ex:55 msgid "Password successfully changed" msgstr "Passwort erfolgreich geändert" -#: lib/sign_dict_web/controllers/reset_password_controller.ex:42 +#: lib/sign_dict_web/controllers/reset_password_controller.ex:60 msgid "Unable to change your password" msgstr "Leider konnte das Passwort nicht geändert werden" -#: lib/sign_dict_web/controllers/reset_password_controller.ex:17 +#: lib/sign_dict_web/controllers/reset_password_controller.ex:20 msgid "You'll receive an email with instructions about how to reset your password in a few minutes." msgstr "Du bekommst innerhalb der nächsten Minuten eine Email mit einem Link zum zurücksetzen des Passwortes." @@ -389,7 +389,7 @@ msgstr "Neues Video" #: lib/sign_dict_web/templates/backend/shared/videos_table.html.eex:9 #: lib/sign_dict_web/templates/backend/user/show.html.eex:1 #: lib/sign_dict_web/templates/backend/video/show.html.eex:46 -#: lib/sign_dict_web/templates/entry/show.html.eex:100 +#: lib/sign_dict_web/templates/entry/show.html.eex:98 msgid "User" msgstr "Benutzer" @@ -676,11 +676,11 @@ msgstr "Vorschaubild" msgid "video.video_url" msgstr "Video" -#: lib/sign_dict_web/templates/entry/show.html.eex:157 +#: lib/sign_dict_web/templates/entry/show.html.eex:156 msgid "No other variations" msgstr "Keine weiteren Variationen" -#: lib/sign_dict_web/templates/entry/show.html.eex:125 +#: lib/sign_dict_web/templates/entry/show.html.eex:124 msgid "Variations »%{entry}«" msgstr "Varianten »%{entry}«" @@ -688,28 +688,28 @@ msgstr "Varianten »%{entry}«" msgid "today" msgstr "heute" -#: lib/sign_dict_web/templates/entry/show.html.eex:143 +#: lib/sign_dict_web/templates/entry/show.html.eex:142 msgid "${time} ago" msgstr "Vor ${time}" -#: lib/sign_dict_web/templates/entry/show.html.eex:106 +#: lib/sign_dict_web/templates/entry/show.html.eex:102 msgid "Date of upload" msgstr "Uploaddatum" -#: lib/sign_dict_web/templates/entry/show.html.eex:140 +#: lib/sign_dict_web/templates/entry/show.html.eex:139 msgid "by" msgstr "von" -#: lib/sign_dict_web/templates/entry/show.html.eex:113 -msgid "Source:" -msgstr "Quelle:" +#: lib/sign_dict_web/templates/entry/show.html.eex:109 +msgid "Source" +msgstr "Quelle" -#: lib/sign_dict_web/templates/entry/show.html.eex:132 +#: lib/sign_dict_web/templates/entry/show.html.eex:131 msgid "Choose this variant" msgstr "Diese Variante wählen" #: lib/sign_dict_web/templates/entry/index.html.eex:57 -#: lib/sign_dict_web/templates/entry/show.html.eex:133 +#: lib/sign_dict_web/templates/entry/show.html.eex:132 #: lib/sign_dict_web/templates/list/show.html.eex:13 #: lib/sign_dict_web/templates/search/index.html.eex:26 #: lib/sign_dict_web/templates/user/show.html.eex:42 @@ -790,7 +790,7 @@ msgid "New message via contact form" msgstr "Neue Nachricht via Kontakt-Formular" #: lib/sign_dict_web/controllers/reset_password_controller.ex:10 -#: lib/sign_dict_web/controllers/reset_password_controller.ex:24 +#: lib/sign_dict_web/controllers/reset_password_controller.ex:33 msgid "Reset password" msgstr "Passwort zurücksetzen" @@ -798,7 +798,7 @@ msgstr "Passwort zurücksetzen" msgid "Search results for %{query}" msgstr "Suchergebnisse für %{query}" -#: lib/sign_dict_web/controllers/embed_controller.ex:37 +#: lib/sign_dict_web/controllers/embed_controller.ex:38 #: lib/sign_dict_web/controllers/entry_controller.ex:95 msgid "Sign for %{sign}" msgstr "Gebärde für %{sign}" @@ -880,7 +880,7 @@ msgid "Sorry, I could not find an entry for this." msgstr "Entschuldigung, leider konnte ich diesen Eintrag nicht finden." #: lib/sign_dict_web/controllers/embed_controller.ex:22 -#: lib/sign_dict_web/controllers/embed_controller.ex:43 +#: lib/sign_dict_web/controllers/embed_controller.ex:44 msgid "Sorry, no sign found" msgstr "Entschuldigung, keine Gebärde gefunden" @@ -931,7 +931,7 @@ msgstr "Bitte bestätige deine Email-Adresse" msgid "Unable to confirm your email address." msgstr "Leider kann deine Email-Adresse nicht bestätigt werden." -#: lib/sign_dict/models/user.ex:102 +#: lib/sign_dict/models/user.ex:109 msgid "already used" msgstr "wird schon benutzt" @@ -956,7 +956,7 @@ msgstr "Ja, ich will den Newsletter abonnieren." msgid "user.flags" msgstr "Feature flags" -#: lib/sign_dict_web/templates/entry/show.html.eex:165 +#: lib/sign_dict_web/templates/entry/show.html.eex:164 msgid "Record new video" msgstr "Neues Video aufnehmen" @@ -968,7 +968,7 @@ msgstr "Neue Videos" msgid "Video approved" msgstr "Video freigegeben" -#: lib/sign_dict_web/templates/entry/show.html.eex:163 +#: lib/sign_dict_web/templates/entry/show.html.eex:162 msgid "Know another variation for this word? You do? Please help us and record it with your webcam." msgstr "Du kennst eine andere Variante für dieses Wort? Bitte hilf uns und nehme diese mit deiner Webcam auf." @@ -1227,3 +1227,18 @@ msgstr "Token nicht gültig" #: lib/sign_dict_web/controllers/contact_controller.ex:39 msgid "The recaptcha was wrong, email not sent." msgstr "Der Recaptcha war falsch, Email nicht versendet." + +#, elixir-format +#: lib/sign_dict_web/templates/entry/show.html.eex:173 +msgid "Some variations of this entry using SignWriting:" +msgstr "Einige Beispiele dieses Eintrages in GebärdenSchrift:" + +#, elixir-format, fuzzy +#: lib/sign_dict_web/templates/entry/show.html.eex:171 +msgid "SignWriting" +msgstr "GebärdenSchrift" + +#, elixir-format +#: lib/sign_dict_web/templates/entry/show.html.eex:182 +msgid "Thanks to deleges for offering this service." +msgstr "Danke an deleges für diesen Service." diff --git a/priv/gettext/default.pot b/priv/gettext/default.pot index 32298300..7a336750 100644 --- a/priv/gettext/default.pot +++ b/priv/gettext/default.pot @@ -18,8 +18,8 @@ msgstr "" msgid "Internal server error" msgstr "" -#: lib/sign_dict/services/credential_verifier.ex:16 -#: lib/sign_dict/services/credential_verifier.ex:27 +#: lib/sign_dict/services/credential_verifier.ex:15 +#: lib/sign_dict/services/credential_verifier.ex:26 #: lib/sign_dict_web/controllers/session_controller.ex:28 msgid "Invalid email address or password" msgstr "" @@ -154,20 +154,20 @@ msgstr "" msgid "Send password reset instructions" msgstr "" -#: lib/sign_dict_web/controllers/reset_password_controller.ex:28 -#: lib/sign_dict_web/controllers/reset_password_controller.ex:49 +#: lib/sign_dict_web/controllers/reset_password_controller.ex:39 +#: lib/sign_dict_web/controllers/reset_password_controller.ex:70 msgid "Invalid password reset link. Please try again." msgstr "" -#: lib/sign_dict_web/controllers/reset_password_controller.ex:38 +#: lib/sign_dict_web/controllers/reset_password_controller.ex:55 msgid "Password successfully changed" msgstr "" -#: lib/sign_dict_web/controllers/reset_password_controller.ex:42 +#: lib/sign_dict_web/controllers/reset_password_controller.ex:60 msgid "Unable to change your password" msgstr "" -#: lib/sign_dict_web/controllers/reset_password_controller.ex:17 +#: lib/sign_dict_web/controllers/reset_password_controller.ex:20 msgid "You'll receive an email with instructions about how to reset your password in a few minutes." msgstr "" @@ -389,7 +389,7 @@ msgstr "" #: lib/sign_dict_web/templates/backend/shared/videos_table.html.eex:9 #: lib/sign_dict_web/templates/backend/user/show.html.eex:1 #: lib/sign_dict_web/templates/backend/video/show.html.eex:46 -#: lib/sign_dict_web/templates/entry/show.html.eex:100 +#: lib/sign_dict_web/templates/entry/show.html.eex:98 msgid "User" msgstr "" @@ -676,11 +676,11 @@ msgstr "" msgid "video.video_url" msgstr "" -#: lib/sign_dict_web/templates/entry/show.html.eex:157 +#: lib/sign_dict_web/templates/entry/show.html.eex:156 msgid "No other variations" msgstr "" -#: lib/sign_dict_web/templates/entry/show.html.eex:125 +#: lib/sign_dict_web/templates/entry/show.html.eex:124 msgid "Variations »%{entry}«" msgstr "" @@ -688,28 +688,28 @@ msgstr "" msgid "today" msgstr "" -#: lib/sign_dict_web/templates/entry/show.html.eex:143 +#: lib/sign_dict_web/templates/entry/show.html.eex:142 msgid "${time} ago" msgstr "" -#: lib/sign_dict_web/templates/entry/show.html.eex:106 +#: lib/sign_dict_web/templates/entry/show.html.eex:102 msgid "Date of upload" msgstr "" -#: lib/sign_dict_web/templates/entry/show.html.eex:140 +#: lib/sign_dict_web/templates/entry/show.html.eex:139 msgid "by" msgstr "" -#: lib/sign_dict_web/templates/entry/show.html.eex:113 -msgid "Source:" +#: lib/sign_dict_web/templates/entry/show.html.eex:109 +msgid "Source" msgstr "" -#: lib/sign_dict_web/templates/entry/show.html.eex:132 +#: lib/sign_dict_web/templates/entry/show.html.eex:131 msgid "Choose this variant" msgstr "" #: lib/sign_dict_web/templates/entry/index.html.eex:57 -#: lib/sign_dict_web/templates/entry/show.html.eex:133 +#: lib/sign_dict_web/templates/entry/show.html.eex:132 #: lib/sign_dict_web/templates/list/show.html.eex:13 #: lib/sign_dict_web/templates/search/index.html.eex:26 #: lib/sign_dict_web/templates/user/show.html.eex:42 @@ -790,7 +790,7 @@ msgid "New message via contact form" msgstr "" #: lib/sign_dict_web/controllers/reset_password_controller.ex:10 -#: lib/sign_dict_web/controllers/reset_password_controller.ex:24 +#: lib/sign_dict_web/controllers/reset_password_controller.ex:33 msgid "Reset password" msgstr "" @@ -798,7 +798,7 @@ msgstr "" msgid "Search results for %{query}" msgstr "" -#: lib/sign_dict_web/controllers/embed_controller.ex:37 +#: lib/sign_dict_web/controllers/embed_controller.ex:38 #: lib/sign_dict_web/controllers/entry_controller.ex:95 msgid "Sign for %{sign}" msgstr "" @@ -880,7 +880,7 @@ msgid "Sorry, I could not find an entry for this." msgstr "" #: lib/sign_dict_web/controllers/embed_controller.ex:22 -#: lib/sign_dict_web/controllers/embed_controller.ex:43 +#: lib/sign_dict_web/controllers/embed_controller.ex:44 msgid "Sorry, no sign found" msgstr "" @@ -931,7 +931,7 @@ msgstr "" msgid "Unable to confirm your email address." msgstr "" -#: lib/sign_dict/models/user.ex:102 +#: lib/sign_dict/models/user.ex:109 msgid "already used" msgstr "" @@ -956,7 +956,7 @@ msgstr "" msgid "user.flags" msgstr "" -#: lib/sign_dict_web/templates/entry/show.html.eex:165 +#: lib/sign_dict_web/templates/entry/show.html.eex:164 msgid "Record new video" msgstr "" @@ -968,7 +968,7 @@ msgstr "" msgid "Video approved" msgstr "" -#: lib/sign_dict_web/templates/entry/show.html.eex:163 +#: lib/sign_dict_web/templates/entry/show.html.eex:162 msgid "Know another variation for this word? You do? Please help us and record it with your webcam." msgstr "" @@ -1227,3 +1227,18 @@ msgstr "" #: lib/sign_dict_web/controllers/contact_controller.ex:39 msgid "The recaptcha was wrong, email not sent." msgstr "" + +#, elixir-format +#: lib/sign_dict_web/templates/entry/show.html.eex:173 +msgid "Some variations of this entry using SignWriting:" +msgstr "" + +#, elixir-format +#: lib/sign_dict_web/templates/entry/show.html.eex:171 +msgid "SignWriting" +msgstr "" + +#, elixir-format +#: lib/sign_dict_web/templates/entry/show.html.eex:182 +msgid "Thanks to deleges for offering this service." +msgstr "" diff --git a/priv/gettext/en/LC_MESSAGES/default.po b/priv/gettext/en/LC_MESSAGES/default.po index 25471c65..b2f7e209 100644 --- a/priv/gettext/en/LC_MESSAGES/default.po +++ b/priv/gettext/en/LC_MESSAGES/default.po @@ -18,8 +18,8 @@ msgstr "" msgid "Internal server error" msgstr "" -#: lib/sign_dict/services/credential_verifier.ex:16 -#: lib/sign_dict/services/credential_verifier.ex:27 +#: lib/sign_dict/services/credential_verifier.ex:15 +#: lib/sign_dict/services/credential_verifier.ex:26 #: lib/sign_dict_web/controllers/session_controller.ex:28 msgid "Invalid email address or password" msgstr "" @@ -154,20 +154,20 @@ msgstr "" msgid "Send password reset instructions" msgstr "" -#: lib/sign_dict_web/controllers/reset_password_controller.ex:28 -#: lib/sign_dict_web/controllers/reset_password_controller.ex:49 +#: lib/sign_dict_web/controllers/reset_password_controller.ex:39 +#: lib/sign_dict_web/controllers/reset_password_controller.ex:70 msgid "Invalid password reset link. Please try again." msgstr "" -#: lib/sign_dict_web/controllers/reset_password_controller.ex:38 +#: lib/sign_dict_web/controllers/reset_password_controller.ex:55 msgid "Password successfully changed" msgstr "" -#: lib/sign_dict_web/controllers/reset_password_controller.ex:42 +#: lib/sign_dict_web/controllers/reset_password_controller.ex:60 msgid "Unable to change your password" msgstr "" -#: lib/sign_dict_web/controllers/reset_password_controller.ex:17 +#: lib/sign_dict_web/controllers/reset_password_controller.ex:20 msgid "You'll receive an email with instructions about how to reset your password in a few minutes." msgstr "" @@ -389,7 +389,7 @@ msgstr "" #: lib/sign_dict_web/templates/backend/shared/videos_table.html.eex:9 #: lib/sign_dict_web/templates/backend/user/show.html.eex:1 #: lib/sign_dict_web/templates/backend/video/show.html.eex:46 -#: lib/sign_dict_web/templates/entry/show.html.eex:100 +#: lib/sign_dict_web/templates/entry/show.html.eex:98 msgid "User" msgstr "" @@ -676,11 +676,11 @@ msgstr "Thumbnail" msgid "video.video_url" msgstr "Video" -#: lib/sign_dict_web/templates/entry/show.html.eex:157 +#: lib/sign_dict_web/templates/entry/show.html.eex:156 msgid "No other variations" msgstr "" -#: lib/sign_dict_web/templates/entry/show.html.eex:125 +#: lib/sign_dict_web/templates/entry/show.html.eex:124 msgid "Variations »%{entry}«" msgstr "" @@ -688,28 +688,28 @@ msgstr "" msgid "today" msgstr "" -#: lib/sign_dict_web/templates/entry/show.html.eex:143 +#: lib/sign_dict_web/templates/entry/show.html.eex:142 msgid "${time} ago" msgstr "" -#: lib/sign_dict_web/templates/entry/show.html.eex:106 +#: lib/sign_dict_web/templates/entry/show.html.eex:102 msgid "Date of upload" msgstr "" -#: lib/sign_dict_web/templates/entry/show.html.eex:140 +#: lib/sign_dict_web/templates/entry/show.html.eex:139 msgid "by" msgstr "" -#: lib/sign_dict_web/templates/entry/show.html.eex:113 -msgid "Source:" +#: lib/sign_dict_web/templates/entry/show.html.eex:109 +msgid "Source" msgstr "" -#: lib/sign_dict_web/templates/entry/show.html.eex:132 +#: lib/sign_dict_web/templates/entry/show.html.eex:131 msgid "Choose this variant" msgstr "" #: lib/sign_dict_web/templates/entry/index.html.eex:57 -#: lib/sign_dict_web/templates/entry/show.html.eex:133 +#: lib/sign_dict_web/templates/entry/show.html.eex:132 #: lib/sign_dict_web/templates/list/show.html.eex:13 #: lib/sign_dict_web/templates/search/index.html.eex:26 #: lib/sign_dict_web/templates/user/show.html.eex:42 @@ -790,7 +790,7 @@ msgid "New message via contact form" msgstr "" #: lib/sign_dict_web/controllers/reset_password_controller.ex:10 -#: lib/sign_dict_web/controllers/reset_password_controller.ex:24 +#: lib/sign_dict_web/controllers/reset_password_controller.ex:33 msgid "Reset password" msgstr "" @@ -798,7 +798,7 @@ msgstr "" msgid "Search results for %{query}" msgstr "" -#: lib/sign_dict_web/controllers/embed_controller.ex:37 +#: lib/sign_dict_web/controllers/embed_controller.ex:38 #: lib/sign_dict_web/controllers/entry_controller.ex:95 msgid "Sign for %{sign}" msgstr "" @@ -880,7 +880,7 @@ msgid "Sorry, I could not find an entry for this." msgstr "" #: lib/sign_dict_web/controllers/embed_controller.ex:22 -#: lib/sign_dict_web/controllers/embed_controller.ex:43 +#: lib/sign_dict_web/controllers/embed_controller.ex:44 msgid "Sorry, no sign found" msgstr "" @@ -931,7 +931,7 @@ msgstr "" msgid "Unable to confirm your email address." msgstr "" -#: lib/sign_dict/models/user.ex:102 +#: lib/sign_dict/models/user.ex:109 msgid "already used" msgstr "" @@ -956,7 +956,7 @@ msgstr "Yes, I want to subscribe to the newsletter" msgid "user.flags" msgstr "Feature flags" -#: lib/sign_dict_web/templates/entry/show.html.eex:165 +#: lib/sign_dict_web/templates/entry/show.html.eex:164 msgid "Record new video" msgstr "" @@ -968,7 +968,7 @@ msgstr "" msgid "Video approved" msgstr "" -#: lib/sign_dict_web/templates/entry/show.html.eex:163 +#: lib/sign_dict_web/templates/entry/show.html.eex:162 msgid "Know another variation for this word? You do? Please help us and record it with your webcam." msgstr "" @@ -1227,3 +1227,18 @@ msgstr "" #: lib/sign_dict_web/controllers/contact_controller.ex:39 msgid "The recaptcha was wrong, email not sent." msgstr "" + +#, elixir-format +#: lib/sign_dict_web/templates/entry/show.html.eex:173 +msgid "Some variations of this entry using SignWriting:" +msgstr "" + +#, elixir-format, fuzzy +#: lib/sign_dict_web/templates/entry/show.html.eex:171 +msgid "SignWriting" +msgstr "" + +#, elixir-format +#: lib/sign_dict_web/templates/entry/show.html.eex:182 +msgid "Thanks to deleges for offering this service." +msgstr "" diff --git a/priv/repo/migrations/20190301153628_create_sign_writings.exs b/priv/repo/migrations/20190301153628_create_sign_writings.exs new file mode 100644 index 00000000..3183a016 --- /dev/null +++ b/priv/repo/migrations/20190301153628_create_sign_writings.exs @@ -0,0 +1,19 @@ +defmodule SignDict.Repo.Migrations.CreateSignWritings do + use Ecto.Migration + + def change do + create table(:sign_writings) do + add(:word, :string) + add(:width, :integer) + add(:deleges_id, :integer) + add(:state, :string) + add(:image, :string) + + add(:entry_id, references(:entries, on_delete: :nothing)) + + timestamps() + end + + create(index(:sign_writings, [:entry_id])) + end +end diff --git a/priv/repo/migrations/20190301154924_add_deleges_updated_at_to_entries.exs b/priv/repo/migrations/20190301154924_add_deleges_updated_at_to_entries.exs new file mode 100644 index 00000000..c97c483a --- /dev/null +++ b/priv/repo/migrations/20190301154924_add_deleges_updated_at_to_entries.exs @@ -0,0 +1,9 @@ +defmodule SignDict.Repo.Migrations.AddDelegesUpdatedAtToEntries do + use Ecto.Migration + + def change do + alter table(:entries) do + add(:deleges_updated_at, :utc_datetime) + end + end +end diff --git a/test/sign_dict/services/fetch_deleges_data_for_entry_test.exs b/test/sign_dict/services/fetch_deleges_data_for_entry_test.exs new file mode 100644 index 00000000..ad7c1bf7 --- /dev/null +++ b/test/sign_dict/services/fetch_deleges_data_for_entry_test.exs @@ -0,0 +1,156 @@ +defmodule SignDict.Services.FetchDelegesDataForEntryTest do + use SignDict.ModelCase, async: true + import SignDict.Factory + alias SignDict.Entry + alias SignDict.SignWriting + alias SignDict.Services.FetchDelegesDataForEntry + + defmodule MockPoison do + def get!("https://server.delegs.de/delegseditor/signwritingeditor/signitems?word=Hallo") do + %{ + status_code: 200, + body: + "([{\"id\":\"388\",\"word\":\"Hallo\",\"width\":\"78\"},{\"id\":\"921\",\"word\":\"hallo\",\"width\":\"77\"}])" + } + end + + def get!("https://server.delegs.de/delegseditor/signwritingeditor/signitems?word=NotActive") do + %{ + status_code: 200, + body: + "([{\"id\":\"388\",\"word\":\"Hallo\",\"width\":\"78\"},{\"id\":\"331\",\"word\":\"NotActive\",\"width\":\"77\"}])" + } + end + + def get!("https://server.delegs.de/delegseditor/signwritingeditor/signitems?word=NotModified") do + %{ + status_code: 200, + body: "([{\"id\":\"999\",\"word\":\"NotModified\",\"width\":\"78\"}])" + } + end + + def get!( + "https://server.delegs.de/delegseditor/signwritingeditor/signimages?upperId=388&lowerId=Hallo&signlocale=DGS", + _opts + ) do + %{ + status_code: 200, + body: "IMAGE" + } + end + + def get!( + "https://server.delegs.de/delegseditor/signwritingeditor/signimages?upperId=921&lowerId=hallo&signlocale=DGS", + _opts + ) do + %{ + status_code: 200, + body: "IMAGE" + } + end + + def get!( + "https://server.delegs.de/delegseditor/signwritingeditor/signimages?upperId=999&lowerId=NotModified&signlocale=DGS", + _opts + ) do + %{ + status_code: 302 + } + end + end + + describe "fetch_for/1" do + test "it will load all datasets into the database and updates the images" do + entry = insert(:entry, text: "Hallo") + FetchDelegesDataForEntry.fetch_for(entry, MockPoison) + + entry = Repo.get(Entry, entry.id) |> Repo.preload(:sign_writings) + + assert entry.deleges_updated_at != nil + assert Enum.count(entry.sign_writings) == 2 + + [first, second] = Enum.sort_by(entry.sign_writings, & &1.deleges_id) + + assert first.word == "Hallo" + assert first.deleges_id == 388 + assert first.state == "active" + assert second.word == "hallo" + assert second.deleges_id == 921 + assert second.state == "active" + end + + test "does not update image if image is not modified" do + entry = insert(:entry, text: "NotModified") + FetchDelegesDataForEntry.fetch_for(entry, MockPoison) + + entry = Repo.get(Entry, entry.id) |> Repo.preload(:sign_writings) + + assert entry.deleges_updated_at != nil + assert Enum.count(entry.sign_writings) == 1 + + first = List.first(entry.sign_writings) + + assert first.word == "NotModified" + assert first.deleges_id == 999 + assert first.image == nil + assert first.state == "active" + end + + test "only update images that are active" do + entry = insert(:entry, text: "NotActive") + + %SignWriting{} + |> SignWriting.changeset(%{ + deleges_id: 331, + width: 100, + word: "NotActive", + entry_id: entry.id, + state: "wrong" + }) + |> Repo.insert!() + + FetchDelegesDataForEntry.fetch_for(entry, MockPoison) + + entry = Repo.get(Entry, entry.id) |> Repo.preload(:sign_writings) + + assert entry.deleges_updated_at != nil + assert Enum.count(entry.sign_writings) == 2 + + [first, second] = Enum.sort_by(entry.sign_writings, & &1.deleges_id) + + assert first.word == "NotActive" + assert first.deleges_id == 331 + assert first.state == "wrong" + assert first.image == nil + assert second.word == "Hallo" + assert second.deleges_id == 388 + assert second.state == "active" + assert second.image != nil + end + + test "delete items that are no longer returend by the deleges api" do + entry = insert(:entry, text: "Hallo") + + %SignWriting{} + |> SignWriting.changeset(%{ + deleges_id: 0, + width: 100, + word: "Hallo", + entry_id: entry.id + }) + |> Repo.insert!() + + FetchDelegesDataForEntry.fetch_for(entry, MockPoison) + + entry = Repo.get(Entry, entry.id) |> Repo.preload(:sign_writings) + + assert entry.deleges_updated_at != nil + assert Enum.count(entry.sign_writings) == 2 + + [first, second] = Enum.sort_by(entry.sign_writings, & &1.deleges_id) + + assert first.deleges_id == 388 + assert second.deleges_id == 921 + end + end +end diff --git a/test/sign_dict/worker/refresh_sign_writings_test.exs b/test/sign_dict/worker/refresh_sign_writings_test.exs new file mode 100644 index 00000000..c9ce4e80 --- /dev/null +++ b/test/sign_dict/worker/refresh_sign_writings_test.exs @@ -0,0 +1,22 @@ +defmodule SignDict.Worker.RefreshSignWritingsTest do + use SignDict.ModelCase + + import SignDict.Factory + + alias SignDict.Worker.RefreshSignWritings + + defmodule FetchDelegesDataForEntryMock do + def fetch_for(entry) do + send(self(), {:for_entry, entry.id}) + :done + end + end + + describe "perform/2" do + test "it fetches the images" do + entry_id = insert(:entry).id + RefreshSignWritings.perform(entry_id, FetchDelegesDataForEntryMock) + assert_received {:for_entry, ^entry_id} + end + end +end diff --git a/test/sign_dict_web/controllers/entry_controller_test.exs b/test/sign_dict_web/controllers/entry_controller_test.exs index 19f0a1d5..2ad28f26 100644 --- a/test/sign_dict_web/controllers/entry_controller_test.exs +++ b/test/sign_dict_web/controllers/entry_controller_test.exs @@ -98,6 +98,36 @@ defmodule SignDict.EntryControllerTest do assert html_response(conn, 200) =~ entry.type assert html_response(conn, 200) =~ "User 2" end + + test "enqueus sign_writing refresh if never run ", %{conn: conn, entry: entry} do + get(conn, entry_path(conn, :show, entry)) + entry_id = entry.id + + assert_received {:mock_exq, "sign_writings", SignDict.Worker.RefreshSignWritings, + [^entry_id]} + end + + test "enqueus sign_writing refresh if never run with video in the url ", %{ + conn: conn, + entry: entry, + video_2: video + } do + get(conn, entry_video_path(conn, :show, entry, video)) + entry_id = entry.id + + assert_received {:mock_exq, "sign_writings", SignDict.Worker.RefreshSignWritings, + [^entry_id]} + end + + test "does not enque if younger than three days", %{conn: conn} do + entry = insert(:entry, deleges_updated_at: Timex.shift(Timex.now(), days: -1)) + + get(conn, entry_path(conn, :show, entry)) + entry_id = entry.id + + refute_received {:mock_exq, "sign_writings", SignDict.Worker.RefreshSignWritings, + [^entry_id]} + end end describe "new/2" do