From a7c48d43dfc17c8ea2a097da855fef664f88f384 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Artur=20Zi=C4=99tkiewicz?= Date: Thu, 31 Aug 2023 12:02:00 +0200 Subject: [PATCH 1/8] Improve translations search --- README.md | 6 +- lib/kanta/migrations/postgresql.ex | 2 +- lib/kanta/migrations/postgresql/v01.ex | 4 +- lib/kanta/migrations/postgresql/v02.ex | 46 +++++++++ lib/kanta/query.ex | 93 ++++++++++++++++++- .../context/finders/get_context.ex | 1 - .../context/finders/list_contexts.ex | 1 - .../translations/domain/finders/get_domain.ex | 1 - .../domain/finders/list_domains.ex | 1 - .../translations/locale/finders/get_locale.ex | 1 - .../locale/finders/list_locales.ex | 1 - .../messages/finders/list_messages.ex | 69 ++++++++++---- .../finders/list_singular_translations.ex | 17 ++++ mix.exs | 2 +- 14 files changed, 211 insertions(+), 34 deletions(-) create mode 100644 lib/kanta/migrations/postgresql/v02.ex create mode 100644 lib/kanta/translations/singular_translation/finders/list_singular_translations.ex diff --git a/README.md b/README.md index 25ab724..9a0b21a 100644 --- a/README.md +++ b/README.md @@ -78,7 +78,7 @@ by adding `kanta` to your list of dependencies in `mix.exs`: ```elixir def deps do [ - {:kanta, "~> 0.1.1"}, + {:kanta, "~> 0.1.4"}, {:gettext, git: "git@github.com:bamorim/gettext.git", branch: "runtime-gettext"} ] end @@ -116,11 +116,11 @@ defmodule MyApp.Repo.Migrations.AddKantaTranslationsTable do use Ecto.Migration def up do - Kanta.Migration.up(version: 1, prefix: prefix()) # Prefix is needed if you are using multitenancy with i.e. triplex + Kanta.Migration.up(version: 2, prefix: prefix()) # Prefix is needed if you are using multitenancy with i.e. triplex end def down do - Kanta.Migration.down(version: 1, prefix: prefix()) # Prefix is needed if you are using multitenancy with i.e. triplex + Kanta.Migration.down(version: 2, prefix: prefix()) # Prefix is needed if you are using multitenancy with i.e. triplex end end ``` diff --git a/lib/kanta/migrations/postgresql.ex b/lib/kanta/migrations/postgresql.ex index 8eb7e6f..5860d15 100644 --- a/lib/kanta/migrations/postgresql.ex +++ b/lib/kanta/migrations/postgresql.ex @@ -6,7 +6,7 @@ defmodule Kanta.Migrations.Postgresql do use Ecto.Migration @initial_version 1 - @current_version 1 + @current_version 2 @default_prefix "public" @doc false diff --git a/lib/kanta/migrations/postgresql/v01.ex b/lib/kanta/migrations/postgresql/v01.ex index 6c4a850..abac950 100644 --- a/lib/kanta/migrations/postgresql/v01.ex +++ b/lib/kanta/migrations/postgresql/v01.ex @@ -98,14 +98,14 @@ defmodule Kanta.Migrations.Postgresql.V01 do execute """ ALTER TABLE #{prefix}.#{@kanta_messages} - ADD COLUMN searchable tsvector + ADD COLUMN IF NOT EXISTS searchable tsvector GENERATED ALWAYS AS ( setweight(to_tsvector('english', coalesce(msgid, '')), 'A') ) STORED; """ execute """ - CREATE INDEX #{@kanta_messages}_searchable_idx ON #{prefix}.#{@kanta_messages} USING gin(searchable); + CREATE INDEX IF NOT EXISTS #{@kanta_messages}_searchable_idx ON #{prefix}.#{@kanta_messages} USING gin(searchable); """ create_if_not_exists unique_index(@kanta_messages, [:context_id, :domain_id, :msgid]) diff --git a/lib/kanta/migrations/postgresql/v02.ex b/lib/kanta/migrations/postgresql/v02.ex new file mode 100644 index 0000000..a545584 --- /dev/null +++ b/lib/kanta/migrations/postgresql/v02.ex @@ -0,0 +1,46 @@ +defmodule Kanta.Migrations.Postgresql.V02 do + @moduledoc """ + Kanta V2 Migrations + """ + + use Ecto.Migration + + @default_prefix "public" + @kanta_singular_translations "kanta_singular_translations" + @kanta_plural_translations "kanta_plural_translations" + + def up(opts) do + Kanta.Migration.up(version: 1) + up_fuzzy_search(opts) + end + + def down(opts) do + down_fuzzy_search(opts) + Kanta.Migration.down(version: 1) + end + + def up_fuzzy_search(opts) do + prefix = Map.get(opts, :prefix, @default_prefix) + + [@kanta_plural_translations, @kanta_singular_translations] + |> Enum.each(fn table_name -> + execute """ + ALTER TABLE #{prefix}.#{table_name} + ADD COLUMN IF NOT EXISTS searchable tsvector + GENERATED ALWAYS AS ( + setweight(to_tsvector('simple', coalesce(translated_text, '')), 'A') + ) STORED; + """ + + execute """ + CREATE INDEX IF NOT EXISTS #{table_name}_searchable_idx ON #{prefix}.#{table_name} USING gin(searchable); + """ + end) + + execute("CREATE EXTENSION IF NOT EXISTS unaccent;") + end + + def down_fuzzy_search(_opts) do + execute("DROP EXTENSION IF EXISTS unaccent;") + end +end diff --git a/lib/kanta/query.ex b/lib/kanta/query.ex index 0705a21..d150322 100644 --- a/lib/kanta/query.ex +++ b/lib/kanta/query.ex @@ -15,6 +15,7 @@ defmodule Kanta.Query do quote do import Ecto.Query + alias Kanta.Migrations.Postgresql alias Kanta.Repo # Returns the base for resource query with binding. @@ -134,6 +135,39 @@ defmodule Kanta.Query do ) end + @doc """ + Joins resource with another resource. + + *Important!* The joining has to be defined by join_resource/2 function. Example: + ``` + defp join_resource(query, :articles) do + query + |> join(:left, [user: u], _ in assoc(u, :articles), as: :article) + end + ``` + + First argument is a query and second argument is pattern-matched atom. + + ## Examples + + iex> Kanta.Accounts.UserQueries.with_join(:articles) + #Ecto.Query + + """ + @spec with_join(Ecto.Query.t(), atom()) :: Ecto.Query.t() + def with_join(query \\ base(), resource_name, opts \\ nil) when is_atom(resource_name) do + if has_named_binding?(query, resource_name) do + query + else + if is_nil(opts) do + join_resource(query, resource_name) + else + join_resource(query, resource_name, opts) + end + end + end + @doc """ Filters given resource by specific criterias. Filters should be pass to the function as map `%{"field_name" => filter_value}`. It supports associations: `%{"association" => %{"field_name" => filter_value}}` @@ -292,18 +326,59 @@ defmodule Kanta.Query do def search_query(query, nil), do: query def search_query(query, ""), do: query - def search_query(query, search) do + def search_query(query, search_term) do + repo = Repo.get_repo() + + if Postgresql.migrated_version(%{repo: repo}) >= 2 do + search_query_fuzzy(query, search_term) + else + search_query_legacy(query, search_term) + end + end + + defmacrop form_search_query(search_term) do + quote do + fragment( + "SELECT to_tsquery(string_agg(unaccent(lexeme) || ':*', ' & ' order by positions)) FROM unnest(to_tsvector(?))", + unquote(search_term) + ) + end + end + + defmacrop ts_rank(left, right) do + quote do + fragment("ts_rank(?, ?)", unquote(left), unquote(right)) + end + end + + defp search_query_fuzzy(query, search_term) do + query + |> or_where( + [{unquote(opts[:binding]), resource}], + fragment("? @@ ?", resource.searchable, form_search_query(^search_term)) + ) + |> order_by( + [{unquote(opts[:binding]), resource}], + desc: + ts_rank( + resource.searchable, + form_search_query(^search_term) + ) + ) + end + + defp search_query_legacy(query, search_term) do from(s in unquote(opts[:module]), where: fragment( "searchable @@ websearch_to_tsquery(?)", - ^search + ^search_term ), order_by: { :desc, fragment( "ts_rank_cd(searchable, websearch_to_tsquery(?), 4)", - ^search + ^search_term ) } ) @@ -321,6 +396,18 @@ defmodule Kanta.Query do where: is_nil(q.deleted_by) ) end + + @spec join_resource(Ecto.Query.t(), atom()) :: no_return() + @spec join_resource(Ecto.Query.t(), atom(), any()) :: no_return() + + defp join_resource(_query, _), do: join_resource_raise() + defp join_resource(_query, _, _opts), do: join_resource_raise() + + defp join_resource_raise do + raise(ArgumentError, message: "wrong join criteria") + end + + defoverridable join_resource: 2, join_resource: 3 end end end diff --git a/lib/kanta/translations/context/finders/get_context.ex b/lib/kanta/translations/context/finders/get_context.ex index 0cc7a00..e8c31f6 100644 --- a/lib/kanta/translations/context/finders/get_context.ex +++ b/lib/kanta/translations/context/finders/get_context.ex @@ -37,7 +37,6 @@ defmodule Kanta.Translations.Contexts.Finders.GetContext do defp find_in_database(params) do base() |> filter_query(params[:filter]) - |> search_query(params[:search]) |> preload_resources(params[:preloads] || []) |> one() |> case do diff --git a/lib/kanta/translations/context/finders/list_contexts.ex b/lib/kanta/translations/context/finders/list_contexts.ex index d81dd00..cdd8302 100644 --- a/lib/kanta/translations/context/finders/list_contexts.ex +++ b/lib/kanta/translations/context/finders/list_contexts.ex @@ -10,7 +10,6 @@ defmodule Kanta.Translations.Contexts.Finders.ListContexts do def find(params \\ []) do base() |> filter_query(params[:filter]) - |> search_query(params[:search]) |> preload_resources(params[:preloads] || []) |> paginate(params[:page], params[:per_page]) end diff --git a/lib/kanta/translations/domain/finders/get_domain.ex b/lib/kanta/translations/domain/finders/get_domain.ex index c94502f..c2fc963 100644 --- a/lib/kanta/translations/domain/finders/get_domain.ex +++ b/lib/kanta/translations/domain/finders/get_domain.ex @@ -37,7 +37,6 @@ defmodule Kanta.Translations.Domains.Finders.GetDomain do defp find_in_database(params) do base() |> filter_query(params[:filter]) - |> search_query(params[:search]) |> preload_resources(params[:preloads] || []) |> one() |> case do diff --git a/lib/kanta/translations/domain/finders/list_domains.ex b/lib/kanta/translations/domain/finders/list_domains.ex index b589e99..26c0e48 100644 --- a/lib/kanta/translations/domain/finders/list_domains.ex +++ b/lib/kanta/translations/domain/finders/list_domains.ex @@ -10,7 +10,6 @@ defmodule Kanta.Translations.Domains.Finders.ListDomains do def find(params \\ []) do base() |> filter_query(params[:filter]) - |> search_query(params[:search]) |> preload_resources(params[:preloads] || []) |> paginate(params[:page], params[:per_page]) end diff --git a/lib/kanta/translations/locale/finders/get_locale.ex b/lib/kanta/translations/locale/finders/get_locale.ex index f34de3c..2ae906a 100644 --- a/lib/kanta/translations/locale/finders/get_locale.ex +++ b/lib/kanta/translations/locale/finders/get_locale.ex @@ -37,7 +37,6 @@ defmodule Kanta.Translations.Locale.Finders.GetLocale do defp find_in_database(params) do base() |> filter_query(params[:filter]) - |> search_query(params[:search]) |> preload_resources(params[:preloads] || []) |> one() |> case do diff --git a/lib/kanta/translations/locale/finders/list_locales.ex b/lib/kanta/translations/locale/finders/list_locales.ex index 3d9e2e9..f659670 100644 --- a/lib/kanta/translations/locale/finders/list_locales.ex +++ b/lib/kanta/translations/locale/finders/list_locales.ex @@ -10,7 +10,6 @@ defmodule Kanta.Translations.Locale.Finders.ListLocales do def find(params \\ []) do base() |> filter_query(params[:filter]) - |> search_query(params[:search]) |> preload_resources(params[:preloads] || []) |> paginate(params[:page], params[:per_page]) end diff --git a/lib/kanta/translations/messages/finders/list_messages.ex b/lib/kanta/translations/messages/finders/list_messages.ex index 535df1d..ae02691 100644 --- a/lib/kanta/translations/messages/finders/list_messages.ex +++ b/lib/kanta/translations/messages/finders/list_messages.ex @@ -7,44 +7,77 @@ defmodule Kanta.Translations.Messages.Finders.ListMessages do module: Kanta.Translations.Message, binding: :message - alias Kanta.Translations.{PluralTranslation, SingularTranslation} + alias Kanta.Translations.PluralTranslations.Finders.ListPluralTranslations + alias Kanta.Translations.SingularTranslations.Finders.ListSingularTranslations @available_filters ~w(domain_id context_id) def find(params \\ []) do base() - |> not_translated_query(params[:filter]) |> filter_query(Map.take(params[:filter] || %{}, @available_filters)) - |> search_query(params[:search]) + |> not_translated_query(params[:filter]) + |> search_subquery(params[:filter], params[:search]) |> preload_resources(params[:preloads] || []) |> paginate(String.to_integer(params[:page] || "1"), params[:per_page]) end defp not_translated_query(query, %{"locale_id" => locale_id, "not_translated" => "true"}) do singular_messages_query = - from(m in query, - join: st in SingularTranslation, - on: st.message_id == m.id, - where: st.locale_id == ^locale_id, - where: - (is_nil(st.translated_text) or st.translated_text == "") and - (is_nil(st.original_text) or st.original_text == "") + query + |> with_join(:singular_translations, %{"locale_id" => locale_id}) + |> where( + [singular_translation: st], + (is_nil(st.translated_text) or + st.translated_text == "") and + (is_nil(st.original_text) or + st.original_text == "") ) plural_messages_query = - from(m in query, - join: pt in PluralTranslation, - on: pt.message_id == m.id, - where: pt.locale_id == ^locale_id, - where: - (is_nil(pt.translated_text) or pt.translated_text == "") and - (is_nil(pt.original_text) or pt.original_text == "") + query + |> with_join(:plural_translations, %{"locale_id" => locale_id}) + |> where( + [plural_translation: pt], + (is_nil(pt.translated_text) or + pt.translated_text == "") and + (is_nil(pt.original_text) or + pt.original_text == "") ) union_query = union_all(singular_messages_query, ^plural_messages_query) - from(m in subquery(union_query)) + from(_ in subquery(union_query), as: :message) end defp not_translated_query(query, _), do: query + + defp search_subquery(query, _, nil), do: query + defp search_subquery(query, _, ""), do: query + + defp search_subquery(query, filter, search) do + sub = + base() + |> search_query(search) + |> with_join(:singular_translations, filter) + |> with_join(:plural_translations, filter) + |> ListPluralTranslations.search_query(search) + |> ListSingularTranslations.search_query(search) + |> subquery() + + join(query, :inner, [message: m], sq in ^sub, on: sq.id == m.id) + end + + def join_resource(query, :singular_translations, %{"locale_id" => locale_id}) do + join(query, :left, [message: m], st in assoc(m, :singular_translations), + as: :singular_translation, + on: st.message_id == m.id and st.locale_id == ^locale_id + ) + end + + def join_resource(query, :plural_translations, %{"locale_id" => locale_id}) do + join(query, :left, [message: m], pt in assoc(m, :plural_translations), + as: :plural_translation, + on: pt.message_id == m.id and pt.locale_id == ^locale_id + ) + end end diff --git a/lib/kanta/translations/singular_translation/finders/list_singular_translations.ex b/lib/kanta/translations/singular_translation/finders/list_singular_translations.ex new file mode 100644 index 0000000..c5212e4 --- /dev/null +++ b/lib/kanta/translations/singular_translation/finders/list_singular_translations.ex @@ -0,0 +1,17 @@ +defmodule Kanta.Translations.SingularTranslations.Finders.ListSingularTranslations do + @moduledoc """ + Query module aka Finder responsible for listing singular translations + """ + + use Kanta.Query, + module: Kanta.Translations.SingularTranslation, + binding: :singular_translation + + def find(params \\ []) do + base() + |> filter_query(params[:filter]) + |> search_query(params[:search]) + |> preload_resources(params[:preloads] || []) + |> paginate(params[:page], params[:per_page]) + end +end diff --git a/mix.exs b/mix.exs index f507d0c..64d56e3 100644 --- a/mix.exs +++ b/mix.exs @@ -6,7 +6,7 @@ defmodule Kanta.MixProject do app: :kanta, description: "User-friendly translations manager for Elixir/Phoenix projects.", package: package(), - version: "0.1.3", + version: "0.1.4", elixir: "~> 1.14", elixirc_options: [ warnings_as_errors: true From 8d30d46f7b019add46c248c407a06c635a3ca25e Mon Sep 17 00:00:00 2001 From: Francisco Lira Date: Fri, 15 Sep 2023 06:09:40 +0100 Subject: [PATCH 2/8] fix:README missing import in router --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 866bacc..b79fffb 100644 --- a/README.md +++ b/README.md @@ -183,6 +183,8 @@ In the `application.ex` file of our project, we add Kanta and its configuration Inside your `router.ex` file we need to connect the Kanta panel using the kanta_dashboard macro. ```elixir +import KantaWeb.Router + scope "/" do   pipe_through :browser From 48b2a904d2c65085321a18d93ce377abd838c0d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Artur=20Zi=C4=99tkiewicz?= Date: Tue, 19 Sep 2023 14:14:49 +0200 Subject: [PATCH 3/8] Improve efficiency of messages list query --- lib/kanta/gettext/repo.ex | 5 --- .../messages/finders/list_messages.ex | 43 ++++++++----------- 2 files changed, 18 insertions(+), 30 deletions(-) diff --git a/lib/kanta/gettext/repo.ex b/lib/kanta/gettext/repo.ex index 23e37f0..052c9b3 100644 --- a/lib/kanta/gettext/repo.ex +++ b/lib/kanta/gettext/repo.ex @@ -1,6 +1,4 @@ defmodule Kanta.Gettext.Repo do - @behaviour Gettext.Repo - alias Kanta.Translations.{ Context, Domain, @@ -12,12 +10,10 @@ defmodule Kanta.Gettext.Repo do alias Kanta.Translations - @impl Gettext.Repo def init(_) do __MODULE__ end - @impl Gettext.Repo def get_translation(locale, domain, msgctxt, msgid, opts) do default_locale = Application.get_env(:kanta, :default_locale) || "en" @@ -56,7 +52,6 @@ defmodule Kanta.Gettext.Repo do end end - @impl Gettext.Repo def get_plural_translation( locale, domain, diff --git a/lib/kanta/translations/messages/finders/list_messages.ex b/lib/kanta/translations/messages/finders/list_messages.ex index ae02691..ec83d70 100644 --- a/lib/kanta/translations/messages/finders/list_messages.ex +++ b/lib/kanta/translations/messages/finders/list_messages.ex @@ -17,36 +17,29 @@ defmodule Kanta.Translations.Messages.Finders.ListMessages do |> filter_query(Map.take(params[:filter] || %{}, @available_filters)) |> not_translated_query(params[:filter]) |> search_subquery(params[:filter], params[:search]) + |> distinct(true) |> preload_resources(params[:preloads] || []) |> paginate(String.to_integer(params[:page] || "1"), params[:per_page]) end defp not_translated_query(query, %{"locale_id" => locale_id, "not_translated" => "true"}) do - singular_messages_query = - query - |> with_join(:singular_translations, %{"locale_id" => locale_id}) - |> where( - [singular_translation: st], - (is_nil(st.translated_text) or - st.translated_text == "") and - (is_nil(st.original_text) or - st.original_text == "") - ) - - plural_messages_query = - query - |> with_join(:plural_translations, %{"locale_id" => locale_id}) - |> where( - [plural_translation: pt], - (is_nil(pt.translated_text) or - pt.translated_text == "") and - (is_nil(pt.original_text) or - pt.original_text == "") - ) - - union_query = union_all(singular_messages_query, ^plural_messages_query) - - from(_ in subquery(union_query), as: :message) + query + |> with_join(:singular_translations, %{"locale_id" => locale_id}) + |> where( + [singular_translation: st], + (is_nil(st.translated_text) or + st.translated_text == "") and + (is_nil(st.original_text) or + st.original_text == "") + ) + |> with_join(:plural_translations, %{"locale_id" => locale_id}) + |> where( + [plural_translation: pt], + (is_nil(pt.translated_text) or + pt.translated_text == "") and + (is_nil(pt.original_text) or + pt.original_text == "") + ) end defp not_translated_query(query, _), do: query From 37d444ea90aa646a67d8f68525d1b87a6ff6ca28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Artur=20Zi=C4=99tkiewicz?= Date: Tue, 19 Sep 2023 14:55:31 +0200 Subject: [PATCH 4/8] Require opts keyword list in join_resource/3 --- lib/kanta/query.ex | 28 +++++++++---------- .../messages/finders/list_messages.ex | 26 ++++++++--------- 2 files changed, 25 insertions(+), 29 deletions(-) diff --git a/lib/kanta/query.ex b/lib/kanta/query.ex index d150322..020cd9b 100644 --- a/lib/kanta/query.ex +++ b/lib/kanta/query.ex @@ -138,9 +138,9 @@ defmodule Kanta.Query do @doc """ Joins resource with another resource. - *Important!* The joining has to be defined by join_resource/2 function. Example: + *Important!* The joining has to be defined by join_resource/3 function. Example: ``` - defp join_resource(query, :articles) do + defp join_resource(query, :articles, opts) do query |> join(:left, [user: u], _ in assoc(u, :articles), as: :article) end @@ -156,15 +156,12 @@ defmodule Kanta.Query do """ @spec with_join(Ecto.Query.t(), atom()) :: Ecto.Query.t() - def with_join(query \\ base(), resource_name, opts \\ nil) when is_atom(resource_name) do + @spec with_join(Ecto.Query.t(), atom(), keyword()) :: Ecto.Query.t() + def with_join(query \\ base(), resource_name, opts \\ []) when is_atom(resource_name) do if has_named_binding?(query, resource_name) do query else - if is_nil(opts) do - join_resource(query, resource_name) - else - join_resource(query, resource_name, opts) - end + join_resource(query, resource_name, opts) end end @@ -397,17 +394,18 @@ defmodule Kanta.Query do ) end - @spec join_resource(Ecto.Query.t(), atom()) :: no_return() - @spec join_resource(Ecto.Query.t(), atom(), any()) :: no_return() - - defp join_resource(_query, _), do: join_resource_raise() - defp join_resource(_query, _, _opts), do: join_resource_raise() + defmacro null_or_empty(field) do + quote do + fragment("(? = '') IS NOT FALSE", unquote(field)) + end + end - defp join_resource_raise do + @spec join_resource(Ecto.Query.t(), atom(), keyword()) :: no_return() + defp join_resource(_query, _, _opts) do raise(ArgumentError, message: "wrong join criteria") end - defoverridable join_resource: 2, join_resource: 3 + defoverridable join_resource: 3 end end end diff --git a/lib/kanta/translations/messages/finders/list_messages.ex b/lib/kanta/translations/messages/finders/list_messages.ex index ec83d70..1b3e354 100644 --- a/lib/kanta/translations/messages/finders/list_messages.ex +++ b/lib/kanta/translations/messages/finders/list_messages.ex @@ -14,9 +14,9 @@ defmodule Kanta.Translations.Messages.Finders.ListMessages do def find(params \\ []) do base() - |> filter_query(Map.take(params[:filter] || %{}, @available_filters)) + |> filter_query(Map.take(params[:filter], @available_filters)) |> not_translated_query(params[:filter]) - |> search_subquery(params[:filter], params[:search]) + |> search_subquery([locale_id: params[:filter]["locale_id"]], params[:search]) |> distinct(true) |> preload_resources(params[:preloads] || []) |> paginate(String.to_integer(params[:page] || "1"), params[:per_page]) @@ -24,21 +24,15 @@ defmodule Kanta.Translations.Messages.Finders.ListMessages do defp not_translated_query(query, %{"locale_id" => locale_id, "not_translated" => "true"}) do query - |> with_join(:singular_translations, %{"locale_id" => locale_id}) + |> with_join(:singular_translations, locale_id: locale_id) |> where( [singular_translation: st], - (is_nil(st.translated_text) or - st.translated_text == "") and - (is_nil(st.original_text) or - st.original_text == "") + null_or_empty(st.translated_text) and null_or_empty(st.original_text) ) - |> with_join(:plural_translations, %{"locale_id" => locale_id}) + |> with_join(:plural_translations, locale_id: locale_id) |> where( [plural_translation: pt], - (is_nil(pt.translated_text) or - pt.translated_text == "") and - (is_nil(pt.original_text) or - pt.original_text == "") + null_or_empty(pt.translated_text) and null_or_empty(pt.original_text) ) end @@ -60,14 +54,18 @@ defmodule Kanta.Translations.Messages.Finders.ListMessages do join(query, :inner, [message: m], sq in ^sub, on: sq.id == m.id) end - def join_resource(query, :singular_translations, %{"locale_id" => locale_id}) do + def join_resource(query, :singular_translations, opts) do + locale_id = opts[:locale_id] + join(query, :left, [message: m], st in assoc(m, :singular_translations), as: :singular_translation, on: st.message_id == m.id and st.locale_id == ^locale_id ) end - def join_resource(query, :plural_translations, %{"locale_id" => locale_id}) do + def join_resource(query, :plural_translations, opts) do + locale_id = opts[:locale_id] + join(query, :left, [message: m], pt in assoc(m, :plural_translations), as: :plural_translation, on: pt.message_id == m.id and pt.locale_id == ^locale_id From a749f3fcaecc4428d5533f0a202813da71c1f1a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Artur=20Zi=C4=99tkiewicz?= Date: Tue, 26 Sep 2023 10:02:34 +0200 Subject: [PATCH 5/8] Fix JS error on LiveView page change --- mix.exs | 2 +- mix.lock | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mix.exs b/mix.exs index aeea752..981b012 100644 --- a/mix.exs +++ b/mix.exs @@ -39,7 +39,7 @@ defmodule Kanta.MixProject do {:ecto_sql, "~> 3.10"}, {:phoenix, "~> 1.7.0"}, {:phoenix_view, "~> 2.0"}, - {:phoenix_live_view, "~> 0.18"}, + {:phoenix_live_view, "~> 0.2"}, {:tailwind, "~> 0.2", runtime: Mix.env() == :dev}, {:jason, "~> 1.0"}, {:nebulex, "~> 2.5"}, diff --git a/mix.lock b/mix.lock index 444d908..4e11ca7 100644 --- a/mix.lock +++ b/mix.lock @@ -36,7 +36,7 @@ "paginator": {:hex, :paginator, "1.2.0", "f59c5da6238950b902b2fc074ffbf138d8766c058d0bd96069790dca5e3d82c9", [:mix], [{:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.0", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.13", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm", "df462f015aa91021430ba5f0ed2ee100de696a925d42f6926e276dbee35fbe1d"}, "phoenix": {:hex, :phoenix, "1.7.2", "c375ffb482beb4e3d20894f84dd7920442884f5f5b70b9f4528cbe0cedefec63", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.4", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "1ebca94b32b4d0e097ab2444a9742ed8ff3361acad17365e4e6b2e79b4792159"}, "phoenix_html": {:hex, :phoenix_html, "3.3.1", "4788757e804a30baac6b3fc9695bf5562465dd3f1da8eb8460ad5b404d9a2178", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "bed1906edd4906a15fd7b412b85b05e521e1f67c9a85418c55999277e553d0d3"}, - "phoenix_live_view": {:hex, :phoenix_live_view, "0.18.18", "1f38fbd7c363723f19aad1a04b5490ff3a178e37daaf6999594d5f34796c47fc", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.15 or ~> 1.7.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.3", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a5810d0472f3189ede6d2a95bda7f31c6113156b91784a3426cb0ab6a6d85214"}, + "phoenix_live_view": {:hex, :phoenix_live_view, "0.20.0", "3f3531c835e46a3b45b4c3ca4a09cef7ba1d0f0d0035eef751c7084b8adb1299", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.15 or ~> 1.7.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.3", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "29875f8a58fb031f2dc8f3be025c92ed78d342b46f9bbf6dfe579549d7c81050"}, "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.1", "ba04e489ef03763bf28a17eb2eaddc2c20c6d217e2150a61e3298b0f4c2012b5", [:mix], [], "hexpm", "81367c6d1eea5878ad726be80808eb5a787a23dee699f96e72b1109c57cdd8d9"}, "phoenix_template": {:hex, :phoenix_template, "1.0.1", "85f79e3ad1b0180abb43f9725973e3b8c2c3354a87245f91431eec60553ed3ef", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "157dc078f6226334c91cb32c1865bf3911686f8bcd6bcff86736f6253e6993ee"}, "phoenix_view": {:hex, :phoenix_view, "2.0.2", "6bd4d2fd595ef80d33b439ede6a19326b78f0f1d8d62b9a318e3d9c1af351098", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}], "hexpm", "a929e7230ea5c7ee0e149ffcf44ce7cf7f4b6d2bfe1752dd7c084cdff152d36f"}, From d758caa00e7b030a4f0c0871b5d93aabd40d37ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Artur=20Zi=C4=99tkiewicz?= Date: Tue, 26 Sep 2023 10:29:20 +0200 Subject: [PATCH 6/8] Get rid of deprecated live_component/2 --- .../live/dashboard/dashboard_live/dashboard_live.html.heex | 2 +- .../plural_translation_form/plural_translation_form.html.heex | 2 +- .../singular_translation_form.html.heex | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/kanta_web/live/dashboard/dashboard_live/dashboard_live.html.heex b/lib/kanta_web/live/dashboard/dashboard_live/dashboard_live.html.heex index b2903f1..696816a 100644 --- a/lib/kanta_web/live/dashboard/dashboard_live/dashboard_live.html.heex +++ b/lib/kanta_web/live/dashboard/dashboard_live/dashboard_live.html.heex @@ -45,7 +45,7 @@
<%= for {plugin_name, _} <- Kanta.config().plugins do %> <%= if plugin_name |> Module.concat(DashboardComponent) |> module_exists?() do %> - <%= live_component(Module.concat(plugin_name, DashboardComponent), id: plugin_name) %> + <.live_component module={Module.concat(plugin_name, DashboardComponent)} id={plugin_name} /> <% end %> <% end %>
diff --git a/lib/kanta_web/live/translations/translation_form_live/components/plural_translation_form/plural_translation_form.html.heex b/lib/kanta_web/live/translations/translation_form_live/components/plural_translation_form/plural_translation_form.html.heex index 2cd0c28..33b6534 100644 --- a/lib/kanta_web/live/translations/translation_form_live/components/plural_translation_form/plural_translation_form.html.heex +++ b/lib/kanta_web/live/translations/translation_form_live/components/plural_translation_form/plural_translation_form.html.heex @@ -85,7 +85,7 @@
<%= for {plugin_name, _} <- Kanta.config().plugins do %> <%= if plugin_name |> Module.concat(FormComponent) |> module_exists?() do %> - <%= live_component(Module.concat(plugin_name, FormComponent), id: plugin_name, message: @message, locale: @locale, translation: @translation) %> + <.live_component module={Module.concat(plugin_name, FormComponent)} id={plugin_name} message={@message} locale={@locale} translation={@translation} /> <% end %> <% end %>
diff --git a/lib/kanta_web/live/translations/translation_form_live/components/singular_translation_form/singular_translation_form.html.heex b/lib/kanta_web/live/translations/translation_form_live/components/singular_translation_form/singular_translation_form.html.heex index eda8041..3cd2f0e 100644 --- a/lib/kanta_web/live/translations/translation_form_live/components/singular_translation_form/singular_translation_form.html.heex +++ b/lib/kanta_web/live/translations/translation_form_live/components/singular_translation_form/singular_translation_form.html.heex @@ -79,7 +79,7 @@
<%= for {plugin_name, _} <- Kanta.config().plugins do %> <%= if plugin_name |> Module.concat(FormComponent) |> module_exists?() do %> - <%= live_component(Module.concat(plugin_name, FormComponent), id: plugin_name, message: @message, locale: @locale, translation: @translation) %> + <.live_component module={Module.concat(plugin_name, FormComponent)} id={plugin_name} message={@message} locale={@locale} translation={@translation} /> <% end %> <% end %>
From 6a1fa2441288a14f1d0f95676f950da78c0be6e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Artur=20Zi=C4=99tkiewicz?= Date: Tue, 26 Sep 2023 10:44:11 +0200 Subject: [PATCH 7/8] Update Kanta version to 0.3.0 --- README.md | 2 +- mix.exs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 182ded2..bfa4305 100644 --- a/README.md +++ b/README.md @@ -102,7 +102,7 @@ by adding `kanta` to your list of dependencies in `mix.exs`: ```elixir def deps do [ - {:kanta, "~> 0.2.2"}, + {:kanta, "~> 0.3.0"}, {:gettext, git: "git@github.com:ravensiris/gettext.git", branch: "runtime-gettext"} ] end diff --git a/mix.exs b/mix.exs index 981b012..8a486d4 100644 --- a/mix.exs +++ b/mix.exs @@ -6,7 +6,7 @@ defmodule Kanta.MixProject do app: :kanta, description: "User-friendly translations manager for Elixir/Phoenix projects.", package: package(), - version: "0.2.2", + version: "0.3.0", elixir: "~> 1.14", elixirc_options: [ warnings_as_errors: true From 7633fef812ad5e20bcded3ad8d489cd19293ba9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Artur=20Zi=C4=99tkiewicz?= Date: Tue, 26 Sep 2023 12:52:49 +0200 Subject: [PATCH 8/8] Update phoenix_live_view to 0.20 --- mix.exs | 2 +- mix.lock | 49 ++++++++++++++++++------------------------------- 2 files changed, 19 insertions(+), 32 deletions(-) diff --git a/mix.exs b/mix.exs index 8a486d4..d8b9da0 100644 --- a/mix.exs +++ b/mix.exs @@ -39,7 +39,7 @@ defmodule Kanta.MixProject do {:ecto_sql, "~> 3.10"}, {:phoenix, "~> 1.7.0"}, {:phoenix_view, "~> 2.0"}, - {:phoenix_live_view, "~> 0.2"}, + {:phoenix_live_view, "~> 0.20"}, {:tailwind, "~> 0.2", runtime: Mix.env() == :dev}, {:jason, "~> 1.0"}, {:nebulex, "~> 2.5"}, diff --git a/mix.lock b/mix.lock index 4e11ca7..6a0c809 100644 --- a/mix.lock +++ b/mix.lock @@ -1,56 +1,43 @@ %{ "bunt": {:hex, :bunt, "0.2.1", "e2d4792f7bc0ced7583ab54922808919518d0e57ee162901a16a1b6664ef3b14", [:mix], [], "hexpm", "a330bfb4245239787b15005e66ae6845c9cd524a288f0d141c148b02603777a5"}, - "castore": {:hex, :castore, "1.0.2", "0c6292ecf3e3f20b7c88408f00096337c4bfd99bd46cc2fe63413ddbe45b3573", [:mix], [], "hexpm", "40b2dd2836199203df8500e4a270f10fc006cc95adc8a319e148dc3077391d96"}, - "connection": {:hex, :connection, "1.1.0", "ff2a49c4b75b6fb3e674bfc5536451607270aac754ffd1bdfe175abe4a6d7a68", [:mix], [], "hexpm", "722c1eb0a418fbe91ba7bd59a47e28008a189d47e37e0e7bb85585a016b2869c"}, + "castore": {:hex, :castore, "1.0.3", "7130ba6d24c8424014194676d608cb989f62ef8039efd50ff4b3f33286d06db8", [:mix], [], "hexpm", "680ab01ef5d15b161ed6a95449fac5c6b8f60055677a8e79acf01b27baa4390b"}, "credo": {:hex, :credo, "1.7.0", "6119bee47272e85995598ee04f2ebbed3e947678dee048d10b5feca139435f75", [:mix], [{:bunt, "~> 0.2.1", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2.8", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "6839fcf63d1f0d1c0f450abc8564a57c43d644077ab96f2934563e68b8a769d7"}, "db_connection": {:hex, :db_connection, "2.5.0", "bb6d4f30d35ded97b29fe80d8bd6f928a1912ca1ff110831edcd238a1973652c", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c92d5ba26cd69ead1ff7582dbb860adeedfff39774105a4f1c92cbb654b55aa2"}, "decimal": {:hex, :decimal, "2.1.1", "5611dca5d4b2c3dd497dec8f68751f1f1a54755e8ed2a966c2633cf885973ad6", [:mix], [], "hexpm", "53cfe5f497ed0e7771ae1a475575603d77425099ba5faef9394932b35020ffcc"}, - "decorator": {:hex, :decorator, "1.4.0", "a57ac32c823ea7e4e67f5af56412d12b33274661bb7640ec7fc882f8d23ac419", [:mix], [], "hexpm", "0a07cedd9083da875c7418dea95b78361197cf2bf3211d743f6f7ce39656597f"}, - "dialyxir": {:hex, :dialyxir, "1.3.0", "fd1672f0922b7648ff9ce7b1b26fcf0ef56dda964a459892ad15f6b4410b5284", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "00b2a4bcd6aa8db9dcb0b38c1225b7277dca9bc370b6438715667071a304696f"}, - "earmark_parser": {:hex, :earmark_parser, "1.4.33", "3c3fd9673bb5dcc9edc28dd90f50c87ce506d1f71b70e3de69aa8154bc695d44", [:mix], [], "hexpm", "2d526833729b59b9fdb85785078697c72ac5e5066350663e5be6a1182da61b8f"}, - "ecto": {:hex, :ecto, "3.10.1", "c6757101880e90acc6125b095853176a02da8f1afe056f91f1f90b80c9389822", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "d2ac4255f1601bdf7ac74c0ed971102c6829dc158719b94bd30041bbad77f87a"}, - "ecto_sql": {:hex, :ecto_sql, "3.10.1", "6ea6b3036a0b0ca94c2a02613fd9f742614b5cfe494c41af2e6571bb034dd94c", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.10.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16.0 or ~> 0.17.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "f6a25bdbbd695f12c8171eaff0851fa4c8e72eec1e98c7364402dda9ce11c56b"}, + "dialyxir": {:hex, :dialyxir, "1.4.1", "a22ed1e7bd3a3e3f197b68d806ef66acb61ee8f57b3ac85fc5d57354c5482a93", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "84b795d6d7796297cca5a3118444b80c7d94f7ce247d49886e7c291e1ae49801"}, + "earmark_parser": {:hex, :earmark_parser, "1.4.36", "487ea8ef9bdc659f085e6e654f3c3feea1d36ac3943edf9d2ef6c98de9174c13", [:mix], [], "hexpm", "a524e395634bdcf60a616efe77fd79561bec2e930d8b82745df06ab4e844400a"}, + "ecto": {:hex, :ecto, "3.10.3", "eb2ae2eecd210b4eb8bece1217b297ad4ff824b4384c0e3fdd28aaf96edd6135", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "44bec74e2364d491d70f7e42cd0d690922659d329f6465e89feb8a34e8cd3433"}, + "ecto_sql": {:hex, :ecto_sql, "3.10.2", "6b98b46534b5c2f8b8b5f03f126e75e2a73c64f3c071149d32987a5378b0fdbd", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.10.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16.0 or ~> 0.17.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "68c018debca57cb9235e3889affdaec7a10616a4e3a80c99fa1d01fdafaa9007"}, "erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"}, - "esbuild": {:hex, :esbuild, "0.7.0", "ce3afb13cd2c5fd63e13c0e2d0e0831487a97a7696cfa563707342bb825d122a", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}], "hexpm", "4ae9f4f237c5ebcb001390b8ada65a12fb2bb04f3fe3d1f1692b7a06fbfe8752"}, - "ex2ms": {:hex, :ex2ms, "1.6.1", "66d472eb14da43087c156e0396bac3cc7176b4f24590a251db53f84e9a0f5f72", [:mix], [], "hexpm", "a7192899d84af03823a8ec2f306fa858cbcce2c2e7fd0f1c49e05168fb9c740e"}, - "ex_doc": {:hex, :ex_doc, "0.30.3", "bfca4d340e3b95f2eb26e72e4890da83e2b3a5c5b0e52607333bf5017284b063", [:mix], [{:earmark_parser, "~> 1.4.31", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "fbc8702046c1d25edf79de376297e608ac78cdc3a29f075484773ad1718918b6"}, + "esbuild": {:hex, :esbuild, "0.7.1", "fa0947e8c3c3c2f86c9bf7e791a0a385007ccd42b86885e8e893bdb6631f5169", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}], "hexpm", "66661cdf70b1378ee4dc16573fcee67750b59761b2605a0207c267ab9d19f13c"}, + "ex_doc": {:hex, :ex_doc, "0.30.6", "5f8b54854b240a2b55c9734c4b1d0dd7bdd41f71a095d42a70445c03cf05a281", [:mix], [{:earmark_parser, "~> 1.4.31", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "bd48f2ddacf4e482c727f9293d9498e0881597eae6ddc3d9562bd7923375109f"}, "expo": {:hex, :expo, "0.4.1", "1c61d18a5df197dfda38861673d392e642649a9cef7694d2f97a587b2cfb319b", [:mix], [], "hexpm", "2ff7ba7a798c8c543c12550fa0e2cbc81b95d4974c65855d8d15ba7b37a1ce47"}, "file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"}, - "finch": {:hex, :finch, "0.16.0", "40733f02c89f94a112518071c0a91fe86069560f5dbdb39f9150042f44dcfb1a", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.3", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2.6 or ~> 1.0", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "f660174c4d519e5fec629016054d60edd822cdfe2b7270836739ac2f97735ec5"}, - "flagpack": {:hex, :flagpack, "0.1.0", "02bff53e97c6c05b1b11f71c4a03f207c96cbdff7943e1a917131b5a843efd09", [:mix], [{:phoenix_live_view, "~> 0.18.3", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}], "hexpm", "181ffc10deb8db3757ffcb3c01919b92b789441fd0b73149ffb864d16dc21561"}, "gettext": {:git, "https://github.com/ravensiris/gettext.git", "030ad843e38eaa935062997c2313709e04ecfafc", [branch: "runtime-gettext"]}, - "hpax": {:hex, :hpax, "0.1.2", "09a75600d9d8bbd064cdd741f21fc06fc1f4cf3d0fcc335e5aa19be1a7235c84", [:mix], [], "hexpm", "2c87843d5a23f5f16748ebe77969880e29809580efdaccd615cd3bed628a8c13"}, - "jason": {:hex, :jason, "1.4.0", "e855647bc964a44e2f67df589ccf49105ae039d4179db7f6271dfd3843dc27e6", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "79a3791085b2a0f743ca04cec0f7be26443738779d09302e01318f97bdb82121"}, - "lucide_live_view": {:hex, :lucide_live_view, "0.1.0", "31077690f397cf306b039ddef0e4db9d5054a121844b01a834313bd3f824161a", [:mix], [{:phoenix_live_view, ">= 0.16.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}], "hexpm", "cf42761ed3e2b5629fe87c37b22ffd1012d222368cf8ba42f26b1069b2611661"}, + "jason": {:hex, :jason, "1.4.1", "af1504e35f629ddcdd6addb3513c3853991f694921b1b9368b0bd32beb9f1b63", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fbb01ecdfd565b56261302f7e1fcc27c4fb8f32d56eab74db621fc154604a7a1"}, "makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"}, "makeup_elixir": {:hex, :makeup_elixir, "0.16.1", "cc9e3ca312f1cfeccc572b37a09980287e243648108384b97ff2b76e505c3555", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "e127a341ad1b209bd80f7bd1620a15693a9908ed780c3b763bccf7d200c767c6"}, "makeup_erlang": {:hex, :makeup_erlang, "0.1.2", "ad87296a092a46e03b7e9b0be7631ddcf64c790fa68a9ef5323b6cbb36affc72", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "f3f5a1ca93ce6e092d92b6d9c049bcda58a3b617a8d888f8e7231c85630e8108"}, - "mime": {:hex, :mime, "2.0.3", "3676436d3d1f7b81b5a2d2bd8405f412c677558c81b1c92be58c00562bb59095", [:mix], [], "hexpm", "27a30bf0db44d25eecba73755acf4068cbfe26a4372f9eb3e4ea3a45956bff6b"}, - "mint": {:hex, :mint, "1.5.1", "8db5239e56738552d85af398798c80648db0e90f343c8469f6c6d8898944fb6f", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "4a63e1e76a7c3956abd2c72f370a0d0aecddc3976dea5c27eccbecfa5e7d5b1e"}, - "mix_audit": {:hex, :mix_audit, "2.1.0", "3c0dafb29114dffcdb508164a3d35311a9ac2c5baeba6495c9cd5315c25902b9", [:make, :mix], [{:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:yaml_elixir, "~> 2.9", [hex: :yaml_elixir, repo: "hexpm", optional: false]}], "hexpm", "14c57a23e0a5f652c1e7f6e8dab93f166f66d63bd0c85f97278f5972b14e2be0"}, - "mix_unused": {:hex, :mix_unused, "0.3.0", "fc1dc0d0a677161b80a44fe6cea0e1169a8ed94553a082d916e97c36425a47d9", [:mix], [], "hexpm", "1ab9ea24ac8f150445f42a0b591c9b5835e92e284ab4a6dba58fe31b89ebbd65"}, - "nebulex": {:hex, :nebulex, "2.5.0", "9ce48cf335253bffd0344cf94f16cfa30c184eb41b618b05f4806eb618d3fa09", [:mix], [{:decorator, "~> 1.4", [hex: :decorator, repo: "hexpm", optional: true]}, {:shards, "~> 1.1", [hex: :shards, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "ab940d039f9fe24b3cf497d16a2711caef3f241833211f10c8e29fe17b8f7fbe"}, - "nimble_options": {:hex, :nimble_options, "1.0.2", "92098a74df0072ff37d0c12ace58574d26880e522c22801437151a159392270e", [:mix], [], "hexpm", "fd12a8db2021036ce12a309f26f564ec367373265b53e25403f0ee697380f1b8"}, + "mime": {:hex, :mime, "2.0.5", "dc34c8efd439abe6ae0343edbb8556f4d63f178594894720607772a041b04b02", [:mix], [], "hexpm", "da0d64a365c45bc9935cc5c8a7fc5e49a0e0f9932a761c55d6c52b142780a05c"}, + "mix_audit": {:hex, :mix_audit, "2.1.1", "653aa6d8f291fc4b017aa82bdb79a4017903902ebba57960ef199cbbc8c008a1", [:make, :mix], [{:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:yaml_elixir, "~> 2.9", [hex: :yaml_elixir, repo: "hexpm", optional: false]}], "hexpm", "541990c3ab3a7bb8c4aaa2ce2732a4ae160ad6237e5dcd5ad1564f4f85354db1"}, + "nebulex": {:hex, :nebulex, "2.5.2", "2d358813ccb2eeea525e3a29c270ad123d3337e97ed9159d9113cf128108bd4c", [:mix], [{:decorator, "~> 1.4", [hex: :decorator, repo: "hexpm", optional: true]}, {:shards, "~> 1.1", [hex: :shards, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "61a122302cf42fa61eca22515b1df21aaaa1b98cf462f6dd0998de9797aaf1c7"}, "nimble_parsec": {:hex, :nimble_parsec, "1.3.1", "2c54013ecf170e249e9291ed0a62e5832f70a476c61da16f6aac6dca0189f2af", [:mix], [], "hexpm", "2682e3c0b2eb58d90c6375fc0cc30bc7be06f365bf72608804fb9cffa5e1b167"}, - "nimble_pool": {:hex, :nimble_pool, "1.0.0", "5eb82705d138f4dd4423f69ceb19ac667b3b492ae570c9f5c900bb3d2f50a847", [:mix], [], "hexpm", "80be3b882d2d351882256087078e1b1952a28bf98d0a287be87e4a24a710b67a"}, - "paginator": {:hex, :paginator, "1.2.0", "f59c5da6238950b902b2fc074ffbf138d8766c058d0bd96069790dca5e3d82c9", [:mix], [{:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.0", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.13", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm", "df462f015aa91021430ba5f0ed2ee100de696a925d42f6926e276dbee35fbe1d"}, - "phoenix": {:hex, :phoenix, "1.7.2", "c375ffb482beb4e3d20894f84dd7920442884f5f5b70b9f4528cbe0cedefec63", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.4", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "1ebca94b32b4d0e097ab2444a9742ed8ff3361acad17365e4e6b2e79b4792159"}, - "phoenix_html": {:hex, :phoenix_html, "3.3.1", "4788757e804a30baac6b3fc9695bf5562465dd3f1da8eb8460ad5b404d9a2178", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "bed1906edd4906a15fd7b412b85b05e521e1f67c9a85418c55999277e553d0d3"}, + "phoenix": {:hex, :phoenix, "1.7.7", "4cc501d4d823015007ba3cdd9c41ecaaf2ffb619d6fb283199fa8ddba89191e0", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5.3", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "8966e15c395e5e37591b6ed0bd2ae7f48e961f0f60ac4c733f9566b519453085"}, + "phoenix_html": {:hex, :phoenix_html, "3.3.2", "d6ce982c6d8247d2fc0defe625255c721fb8d5f1942c5ac051f6177bffa5973f", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "44adaf8e667c1c20fb9d284b6b0fa8dc7946ce29e81ce621860aa7e96de9a11d"}, "phoenix_live_view": {:hex, :phoenix_live_view, "0.20.0", "3f3531c835e46a3b45b4c3ca4a09cef7ba1d0f0d0035eef751c7084b8adb1299", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.15 or ~> 1.7.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.3", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "29875f8a58fb031f2dc8f3be025c92ed78d342b46f9bbf6dfe579549d7c81050"}, - "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.1", "ba04e489ef03763bf28a17eb2eaddc2c20c6d217e2150a61e3298b0f4c2012b5", [:mix], [], "hexpm", "81367c6d1eea5878ad726be80808eb5a787a23dee699f96e72b1109c57cdd8d9"}, - "phoenix_template": {:hex, :phoenix_template, "1.0.1", "85f79e3ad1b0180abb43f9725973e3b8c2c3354a87245f91431eec60553ed3ef", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "157dc078f6226334c91cb32c1865bf3911686f8bcd6bcff86736f6253e6993ee"}, + "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.3", "3168d78ba41835aecad272d5e8cd51aa87a7ac9eb836eabc42f6e57538e3731d", [:mix], [], "hexpm", "bba06bc1dcfd8cb086759f0edc94a8ba2bc8896d5331a1e2c2902bf8e36ee502"}, + "phoenix_template": {:hex, :phoenix_template, "1.0.3", "32de561eefcefa951aead30a1f94f1b5f0379bc9e340bb5c667f65f1edfa4326", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "16f4b6588a4152f3cc057b9d0c0ba7e82ee23afa65543da535313ad8d25d8e2c"}, "phoenix_view": {:hex, :phoenix_view, "2.0.2", "6bd4d2fd595ef80d33b439ede6a19326b78f0f1d8d62b9a318e3d9c1af351098", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}], "hexpm", "a929e7230ea5c7ee0e149ffcf44ce7cf7f4b6d2bfe1752dd7c084cdff152d36f"}, "plug": {:hex, :plug, "1.14.2", "cff7d4ec45b4ae176a227acd94a7ab536d9b37b942c8e8fa6dfc0fff98ff4d80", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "842fc50187e13cf4ac3b253d47d9474ed6c296a8732752835ce4a86acdf68d13"}, "plug_crypto": {:hex, :plug_crypto, "1.2.5", "918772575e48e81e455818229bf719d4ab4181fcbf7f85b68a35620f78d89ced", [:mix], [], "hexpm", "26549a1d6345e2172eb1c233866756ae44a9609bd33ee6f99147ab3fd87fd842"}, "scrivener": {:hex, :scrivener, "2.7.2", "1d913c965ec352650a7f864ad7fd8d80462f76a32f33d57d1e48bc5e9d40aba2", [:mix], [], "hexpm", "7866a0ec4d40274efbee1db8bead13a995ea4926ecd8203345af8f90d2b620d9"}, "scrivener_ecto": {:hex, :scrivener_ecto, "2.7.0", "cf64b8cb8a96cd131cdbcecf64e7fd395e21aaa1cb0236c42a7c2e34b0dca580", [:mix], [{:ecto, "~> 3.3", [hex: :ecto, repo: "hexpm", optional: false]}, {:scrivener, "~> 2.4", [hex: :scrivener, repo: "hexpm", optional: false]}], "hexpm", "e809f171687806b0031129034352f5ae44849720c48dd839200adeaf0ac3e260"}, "shards": {:hex, :shards, "1.1.0", "ed3032e63ae99f0eaa6d012b8b9f9cead48b9a810b3f91aeac266cfc4118eff6", [:make, :rebar3], [], "hexpm", "1d188e565a54a458a7a601c2fd1e74f5cfeba755c5a534239266d28b7ff124c7"}, - "tailwind": {:hex, :tailwind, "0.2.0", "95f9e4a32020c5bec480f1d6a43a49ac8030b13183127b577605f506d6e13a66", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}], "hexpm", "385e939fcd7fe4654be5130b187e358aaabade385513f9d200ffecdbb9552a9e"}, + "tailwind": {:hex, :tailwind, "0.2.1", "83d8eadbe71a8e8f67861fe7f8d51658ecfb258387123afe4d9dc194eddc36b0", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}], "hexpm", "e8a13f6107c95f73e58ed1b4221744e1eb5a093cd1da244432067e19c8c9a277"}, "telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"}, - "tesla": {:hex, :tesla, "1.7.0", "a62dda2f80d4f8a925eb7b8c5b78c461e0eb996672719fe1a63b26321a5f8b4e", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:finch, "~> 0.13", [hex: :finch, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:gun, "~> 1.3", [hex: :gun, repo: "hexpm", optional: true]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "4.4.0", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.0", [hex: :mint, repo: "hexpm", optional: true]}, {:msgpax, "~> 2.3", [hex: :msgpax, repo: "hexpm", optional: true]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "2e64f01ebfdb026209b47bc651a0e65203fcff4ae79c11efb73c4852b00dc313"}, "uri_query": {:hex, :uri_query, "0.1.2", "ae35b83b472f3568c2c159eee3f3ccf585375d8a94fb5382db1ea3589e75c3b4", [:mix], [], "hexpm", "e3bc81816c98502c36498b9b2f239b89c71ce5eadfff7ceb2d6c0a2e6ae2ea0c"}, - "websock": {:hex, :websock, "0.5.1", "c496036ce95bc26d08ba086b2a827b212c67e7cabaa1c06473cd26b40ed8cf10", [:mix], [], "hexpm", "b9f785108b81cd457b06e5f5dabe5f65453d86a99118b2c0a515e1e296dc2d2c"}, - "websock_adapter": {:hex, :websock_adapter, "0.5.1", "292e6c56724e3457e808e525af0e9bcfa088cc7b9c798218e78658c7f9b85066", [:mix], [{:bandit, ">= 0.6.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "8e2e1544bfde5f9d0442f9cec2f5235398b224f75c9e06b60557debf64248ec1"}, + "websock": {:hex, :websock, "0.5.3", "2f69a6ebe810328555b6fe5c831a851f485e303a7c8ce6c5f675abeb20ebdadc", [:mix], [], "hexpm", "6105453d7fac22c712ad66fab1d45abdf049868f253cf719b625151460b8b453"}, + "websock_adapter": {:hex, :websock_adapter, "0.5.4", "7af8408e7ed9d56578539594d1ee7d8461e2dd5c3f57b0f2a5352d610ddde757", [:mix], [{:bandit, ">= 0.6.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "d2c238c79c52cbe223fcdae22ca0bb5007a735b9e933870e241fce66afb4f4ab"}, "yamerl": {:hex, :yamerl, "0.10.0", "4ff81fee2f1f6a46f1700c0d880b24d193ddb74bd14ef42cb0bcf46e81ef2f8e", [:rebar3], [], "hexpm", "346adb2963f1051dc837a2364e4acf6eb7d80097c0f53cbdc3046ec8ec4b4e6e"}, "yaml_elixir": {:hex, :yaml_elixir, "2.9.0", "9a256da867b37b8d2c1ffd5d9de373a4fda77a32a45b452f1708508ba7bbcb53", [:mix], [{:yamerl, "~> 0.10", [hex: :yamerl, repo: "hexpm", optional: false]}], "hexpm", "0cb0e7d4c56f5e99a6253ed1a670ed0e39c13fc45a6da054033928607ac08dfc"}, }