From 0920dd28c453a7de46fa9f176c4943bb61524411 Mon Sep 17 00:00:00 2001 From: Antoine Augusti Date: Tue, 25 Jul 2023 19:33:07 +0200 Subject: [PATCH] =?UTF-8?q?Am=C3=A9lioration=20de=20la=20recherche=20de=20?= =?UTF-8?q?contacts=20dans=20le=20backoffice=20(#3348)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Amélioration de la recherche de contacts dans le backoffice * Add test * Improve unaccent * refactor * Ignore Credo * PR comments --------- Co-authored-by: Thibaut Barrère --- apps/transport/lib/db/contact.ex | 16 ++++++++++++- .../backoffice/contact_controller.ex | 23 +++++++++---------- .../backoffice/contact/index.html.heex | 10 ++++---- .../backoffice/page/form_dataset.html.heex | 7 +++++- .../views/backoffice/contact_view.ex | 1 + .../views/backoffice/page_view.ex | 14 +++++++++++ apps/transport/test/db/contact_test.exs | 7 ++++-- .../backoffice/contact_controller_test.exs | 10 ++++---- .../views/backoffice/page_view_test.exs | 4 ++++ 9 files changed, 68 insertions(+), 24 deletions(-) create mode 100644 apps/transport/test/transport_web/views/backoffice/page_view_test.exs diff --git a/apps/transport/lib/db/contact.ex b/apps/transport/lib/db/contact.ex index 4ecebb9a84..83aafeec2e 100644 --- a/apps/transport/lib/db/contact.ex +++ b/apps/transport/lib/db/contact.ex @@ -36,8 +36,11 @@ defmodule DB.Contact do def base_query, do: from(c in __MODULE__, as: :contact) def search(%{"q" => q}) do + ilike_search = "%#{safe_like_pattern(q)}%" + base_query() - |> where([contact: c], c.organization == ^q) + |> where([contact: c], ilike(c.organization, ^ilike_search)) + |> or_where([contact: c], fragment("unaccent(?) ilike unaccent(?)", c.last_name, ^ilike_search)) |> or_where( [contact: c], fragment( @@ -52,6 +55,17 @@ defmodule DB.Contact do def search(%{}), do: base_query() + @doc """ + Make sure a string that will be passed to `like` or `ilike` is safe. + + See https://elixirforum.com/t/secure-ecto-like-queries/31265 + iex> safe_like_pattern("I love %like_injections%\\!") + "I love likeinjections!" + """ + def safe_like_pattern(value) do + String.replace(value, ["\\", "%", "_"], "") + end + def insert!(%{} = fields), do: %__MODULE__{} |> changeset(fields) |> DB.Repo.insert!() @doc """ diff --git a/apps/transport/lib/transport_web/controllers/backoffice/contact_controller.ex b/apps/transport/lib/transport_web/controllers/backoffice/contact_controller.ex index 5a87dcea9a..a3e301237e 100644 --- a/apps/transport/lib/transport_web/controllers/backoffice/contact_controller.ex +++ b/apps/transport/lib/transport_web/controllers/backoffice/contact_controller.ex @@ -57,8 +57,8 @@ defmodule TransportWeb.Backoffice.ContactController do defp render_form(%Plug.Conn{assigns: assigns} = conn) do conn - |> assign(:existing_organizations, existing_organizations()) - |> assign(:existing_job_titles, existing_job_titles()) + |> assign(:existing_organizations, contact_values_for_field(:organization)) + |> assign(:existing_job_titles, contact_values_for_field(:job_title)) |> assign(:datasets_datalist, datasets_datalist()) |> assign(:notification_subscriptions, notification_subscriptions_for_contact(Map.get(assigns, :contact_id))) |> render("form.html") @@ -73,22 +73,21 @@ defmodule TransportWeb.Backoffice.ContactController do conn |> assign(:contacts, paginated_contacts) - |> assign(:existing_organizations, existing_organizations()) + |> assign(:search_datalist, search_datalist()) |> render("index.html") end - defp existing_organizations do - DB.Contact.base_query() - |> select([contact: c], c.organization) - |> order_by([contact: c], asc: c.organization) - |> distinct(true) - |> DB.Repo.all() + defp search_datalist do + :organization + |> contact_values_for_field() + |> Enum.concat(contact_values_for_field(:last_name)) + |> Enum.sort() end - defp existing_job_titles do + defp contact_values_for_field(field) when is_atom(field) do DB.Contact.base_query() - |> select([contact: c], c.job_title) - |> order_by([contact: c], asc: c.job_title) + |> select([contact: c], field(c, ^field)) + |> order_by([contact: c], asc: ^field) |> distinct(true) |> DB.Repo.all() end diff --git a/apps/transport/lib/transport_web/templates/backoffice/contact/index.html.heex b/apps/transport/lib/transport_web/templates/backoffice/contact/index.html.heex index d49c417bc7..abd2e33369 100644 --- a/apps/transport/lib/transport_web/templates/backoffice/contact/index.html.heex +++ b/apps/transport/lib/transport_web/templates/backoffice/contact/index.html.heex @@ -5,12 +5,14 @@ <%= search_input(f, :q, id: "backoffice_search", value: assigns[:q] || "", - list: "existing_organizations", + list: "search_datalist", placeholder: dgettext("contact", "Find contacts") ) %> - - <%= for org <- @existing_organizations do %> -