diff --git a/.envrc b/.envrc deleted file mode 100644 index faa8210..0000000 --- a/.envrc +++ /dev/null @@ -1,3 +0,0 @@ -source_url "https://raw.githubusercontent.com/cachix/devenv/d1f7b48e35e6dee421cfd0f51481d17f77586997/direnvrc" "sha256-YBzqskFZxmNb3kYVoKD9ZixoPXJh1C9ZvTLGFRkauZ0=" - -use devenv diff --git a/.github/workflows/development.yml b/.github/workflows/development.yml index 314031f..f7b1a8d 100644 --- a/.github/workflows/development.yml +++ b/.github/workflows/development.yml @@ -13,49 +13,58 @@ permissions: contents: read jobs: - test: - runs-on: ubuntu-latest - name: Test on OTP ${{matrix.otp}} / Elixir ${{matrix.elixir}} + build: + name: OS ${{matrix.os}} / Elixir ${{matrix.elixir}} / OTP ${{matrix.otp}} strategy: matrix: - otp: ["25.0.4"] - elixir: ["1.14.1"] + elixir: ['1.14', '1.15', '1.16', '1.17'] + otp: ['24', '25', '26', '27'] + os: [ubuntu-24.04] + exclude: + # Elixir 1.14 + - elixir: '1.14' + otp: '27' + # Elixir 1.15 + - elixir: '1.15' + otp: '27' + # Elixir 1.16 + - elixir: '1.16' + otp: '27' + # Elixir 1.17 + - elixir: '1.17' + otp: '24' + runs-on: ${{matrix.os}} steps: + - name: Checkout code + uses: actions/checkout@v4 - name: Set up Elixir uses: erlef/setup-beam@v1 with: otp-version: ${{matrix.otp}} elixir-version: ${{matrix.elixir}} - - name: Checkout code - uses: actions/checkout@v3 - - name: Cache deps - id: cache-deps - uses: actions/cache@v3 - env: - cache-name: cache-elixir-deps + - name: Restore dependencies cache + uses: actions/cache@v4 with: path: deps - key: ${{ runner.os }}-mix-${{ env.cache-name }}-${{ hashFiles('**/mix.lock') }} - restore-keys: | - ${{ runner.os }}-mix-${{ env.cache-name }}- - - name: Cache compiled build - id: cache-build - uses: actions/cache@v3 - env: - cache-name: cache-compiled-build - with: - path: _build - key: ${{ runner.os }}-mix-${{ env.cache-name }}-${{ hashFiles('**/mix.lock') }} - restore-keys: | - ${{ runner.os }}-mix-${{ env.cache-name }}- - ${{ runner.os }}-mix- + key: ${{ runner.os }}-mix-${{ hashFiles('**/mix.lock') }} + restore-keys: ${{ runner.os }}-mix- + - name: Set up Postgres + run: | + sudo apt-get update + sudo apt-get install -y postgresql + sudo service postgresql start + sudo -u postgres psql -c "ALTER USER postgres WITH PASSWORD 'postgres';" - name: Install dependencies run: mix deps.get - - name: Compiles without warnings + - name: Create database + run: mix do ecto.create, ecto.migrate + - name: Compile code run: mix compile --warnings-as-errors - name: Check Formatting run: mix format --check-formatted - - name: Run credo + - name: Dialyzer + run: mix dialyzer -Wno_match --format short 2>&1 + - name: Credo run: mix credo - - name: "Run dialyzer" - run: mix dialyzer -Wno_match --format short 2>&1 + - name: Run tests + run: MIX_ENV=test mix test diff --git a/.gitignore b/.gitignore index 4f56ac3..be2d353 100644 --- a/.gitignore +++ b/.gitignore @@ -30,9 +30,4 @@ kanta-*.tar /assets/node_modules /assets/.DS_Store /.DS_Store -/.devenv.flake.nix -/.devenv/ -/.direnv/ -/.nix-mix/ -/.nix-hex/ .DS_Store diff --git a/devenv.lock b/devenv.lock deleted file mode 100644 index 12377d7..0000000 --- a/devenv.lock +++ /dev/null @@ -1,156 +0,0 @@ -{ - "nodes": { - "devenv": { - "locked": { - "dir": "src/modules", - "lastModified": 1688664806, - "narHash": "sha256-MCdKM7iZYN5d29uvbIHi/kzsLmTKGSKF+d3+9R2A+hk=", - "owner": "cachix", - "repo": "devenv", - "rev": "70d8ee2698a1378ebef1e29075995b04c056f7c7", - "type": "github" - }, - "original": { - "dir": "src/modules", - "owner": "cachix", - "repo": "devenv", - "type": "github" - } - }, - "flake-compat": { - "flake": false, - "locked": { - "lastModified": 1673956053, - "narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=", - "owner": "edolstra", - "repo": "flake-compat", - "rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9", - "type": "github" - }, - "original": { - "owner": "edolstra", - "repo": "flake-compat", - "type": "github" - } - }, - "flake-utils": { - "inputs": { - "systems": "systems" - }, - "locked": { - "lastModified": 1685518550, - "narHash": "sha256-o2d0KcvaXzTrPRIo0kOLV0/QXHhDQ5DTi+OxcjO8xqY=", - "owner": "numtide", - "repo": "flake-utils", - "rev": "a1720a10a6cfe8234c0e93907ffe81be440f4cef", - "type": "github" - }, - "original": { - "owner": "numtide", - "repo": "flake-utils", - "type": "github" - } - }, - "gitignore": { - "inputs": { - "nixpkgs": [ - "pre-commit-hooks", - "nixpkgs" - ] - }, - "locked": { - "lastModified": 1660459072, - "narHash": "sha256-8DFJjXG8zqoONA1vXtgeKXy68KdJL5UaXR8NtVMUbx8=", - "owner": "hercules-ci", - "repo": "gitignore.nix", - "rev": "a20de23b925fd8264fd7fad6454652e142fd7f73", - "type": "github" - }, - "original": { - "owner": "hercules-ci", - "repo": "gitignore.nix", - "type": "github" - } - }, - "nixpkgs": { - "locked": { - "lastModified": 1688646010, - "narHash": "sha256-kCeza5eKI2NEi8k0EoeZfv3lN1r1Vwx+L/VA6I8tmG4=", - "owner": "NixOS", - "repo": "nixpkgs", - "rev": "5daaa32204e9c46b05cd709218b7ba733d07e80c", - "type": "github" - }, - "original": { - "owner": "NixOS", - "ref": "nixpkgs-unstable", - "repo": "nixpkgs", - "type": "github" - } - }, - "nixpkgs-stable": { - "locked": { - "lastModified": 1685801374, - "narHash": "sha256-otaSUoFEMM+LjBI1XL/xGB5ao6IwnZOXc47qhIgJe8U=", - "owner": "NixOS", - "repo": "nixpkgs", - "rev": "c37ca420157f4abc31e26f436c1145f8951ff373", - "type": "github" - }, - "original": { - "owner": "NixOS", - "ref": "nixos-23.05", - "repo": "nixpkgs", - "type": "github" - } - }, - "pre-commit-hooks": { - "inputs": { - "flake-compat": "flake-compat", - "flake-utils": "flake-utils", - "gitignore": "gitignore", - "nixpkgs": [ - "nixpkgs" - ], - "nixpkgs-stable": "nixpkgs-stable" - }, - "locked": { - "lastModified": 1688596063, - "narHash": "sha256-9t7RxBiKWHygsqXtiNATTJt4lim/oSYZV3RG8OjDDng=", - "owner": "cachix", - "repo": "pre-commit-hooks.nix", - "rev": "c8d18ba345730019c3faf412c96a045ade171895", - "type": "github" - }, - "original": { - "owner": "cachix", - "repo": "pre-commit-hooks.nix", - "type": "github" - } - }, - "root": { - "inputs": { - "devenv": "devenv", - "nixpkgs": "nixpkgs", - "pre-commit-hooks": "pre-commit-hooks" - } - }, - "systems": { - "locked": { - "lastModified": 1681028828, - "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", - "owner": "nix-systems", - "repo": "default", - "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", - "type": "github" - }, - "original": { - "owner": "nix-systems", - "repo": "default", - "type": "github" - } - } - }, - "root": "root", - "version": 7 -} diff --git a/devenv.nix b/devenv.nix deleted file mode 100644 index 356fe8e..0000000 --- a/devenv.nix +++ /dev/null @@ -1,34 +0,0 @@ -{pkgs, ...}: let - erlang = pkgs.beam.packages.erlangR25; - nodejs = pkgs.nodejs_20; - elixir = erlang.elixir_1_14; - elixir-ls = erlang.elixir-ls.override {elixir = erlang.elixir_1_14;}; -in { - env.LANG = "en_US.UTF-8"; - env.ERL_AFLAGS = "-kernel shell_history enabled"; - - enterShell = '' - export MIX_HOME=$PWD/.nix-mix - export HEX_HOME=$PWD/.nix-hex - export PATH=$MIX_HOME/bin:$PATH - export PATH=$HEX_HOME/bin:$PATH - export PATH=$PATH:$(pwd)/_build/pip_packages/bin - ''; - - packages = - (with pkgs; [ - inotify-tools - alejandra - ]) - ++ [nodejs elixir-ls]; - - languages.elixir = { - enable = true; - package = elixir; - }; - - languages.javascript = { - enable = true; - package = nodejs; - }; -} diff --git a/devenv.yaml b/devenv.yaml deleted file mode 100644 index c7cb5ce..0000000 --- a/devenv.yaml +++ /dev/null @@ -1,3 +0,0 @@ -inputs: - nixpkgs: - url: github:NixOS/nixpkgs/nixpkgs-unstable diff --git a/lib/kanta/config.ex b/lib/kanta/config.ex index 8e16502..d9136cb 100644 --- a/lib/kanta/config.ex +++ b/lib/kanta/config.ex @@ -8,7 +8,8 @@ defmodule Kanta.Config do repo: module(), endpoint: module(), plugins: false | [module() | {module() | Keyword.t()}], - disable_api_authorization: boolean() + disable_api_authorization: boolean(), + id_parse_function: mfa() | (term() -> {:ok, term()} | term()) } defstruct name: Kanta, @@ -16,7 +17,8 @@ defmodule Kanta.Config do repo: nil, endpoint: nil, plugins: [], - disable_api_authorization: false + disable_api_authorization: false, + id_parse_function: {Kanta.Utils.ParamParsers, :default_id_parser, 1} alias Kanta.Validator @@ -82,6 +84,29 @@ defmodule Kanta.Config do end end + defp validate_opt(_opts, {:id_parse_function, {module, function, 1} = id_parse_function}) do + if Code.ensure_loaded?(module) and Kernel.function_exported?(module, function, 1) do + :ok + else + {:error, + "expected :id_parse_function to be a function with arity of 1, got: #{inspect(id_parse_function)}"} + end + end + + defp validate_opt(_opts, {:id_parse_function, {_module, _function, _arity} = id_parse_function}) do + {:error, + "expected :id_parse_function to be a function with arity of 1, got: #{inspect(id_parse_function)}"} + end + + defp validate_opt(_opts, {:id_parse_function, id_parse_function}) do + if is_function(id_parse_function, 1) do + :ok + else + {:error, + "expected :id_parse_function to be a function with arity of 1, got: #{inspect(id_parse_function)}"} + end + end + defp validate_opt(_opts, option) do {:unknown, option, __MODULE__} end diff --git a/lib/kanta/schema.ex b/lib/kanta/schema.ex new file mode 100644 index 0000000..b4e889b --- /dev/null +++ b/lib/kanta/schema.ex @@ -0,0 +1,13 @@ +defmodule Kanta.Schema do + @moduledoc false + + defmacro __using__(_opts) do + quote do + use Ecto.Schema + + @primary_key {:id, Application.compile_env(:kanta, :schema_id_type, :id), + autogenerate: true} + @foreign_key_type Application.compile_env(:kanta, :schema_id_type, :id) + end + end +end diff --git a/lib/kanta/translations/context.ex b/lib/kanta/translations/context.ex index a7d80cc..efc2ddc 100644 --- a/lib/kanta/translations/context.ex +++ b/lib/kanta/translations/context.ex @@ -3,7 +3,7 @@ defmodule Kanta.Translations.Context do Gettext Context DB model """ - use Ecto.Schema + use Kanta.Schema import Ecto.Changeset alias Kanta.Translations.Message diff --git a/lib/kanta/translations/domain.ex b/lib/kanta/translations/domain.ex index 39772e6..e69ff23 100644 --- a/lib/kanta/translations/domain.ex +++ b/lib/kanta/translations/domain.ex @@ -3,7 +3,7 @@ defmodule Kanta.Translations.Domain do Gettext domain DB model """ - use Ecto.Schema + use Kanta.Schema import Ecto.Changeset alias Kanta.Translations.Message diff --git a/lib/kanta/translations/locale.ex b/lib/kanta/translations/locale.ex index ffdc5d3..7c13e37 100644 --- a/lib/kanta/translations/locale.ex +++ b/lib/kanta/translations/locale.ex @@ -3,7 +3,7 @@ defmodule Kanta.Translations.Locale do Locale DB model """ - use Ecto.Schema + use Kanta.Schema import Ecto.Changeset alias Kanta.Translations.SingularTranslation diff --git a/lib/kanta/translations/message.ex b/lib/kanta/translations/message.ex index e48f02f..8b5427f 100644 --- a/lib/kanta/translations/message.ex +++ b/lib/kanta/translations/message.ex @@ -3,7 +3,7 @@ defmodule Kanta.Translations.Message do Gettext message DB model """ - use Ecto.Schema + use Kanta.Schema import Ecto.Changeset alias Kanta.Translations.{Context, Domain, PluralTranslation, SingularTranslation} diff --git a/lib/kanta/translations/plural_translation.ex b/lib/kanta/translations/plural_translation.ex index 65bc4ce..05b80e2 100644 --- a/lib/kanta/translations/plural_translation.ex +++ b/lib/kanta/translations/plural_translation.ex @@ -3,7 +3,7 @@ defmodule Kanta.Translations.PluralTranslation do Plural translation DB model """ - use Ecto.Schema + use Kanta.Schema import Ecto.Changeset alias Kanta.Translations.{Locale, Message} diff --git a/lib/kanta/translations/singular_translation.ex b/lib/kanta/translations/singular_translation.ex index 7475161..1600158 100644 --- a/lib/kanta/translations/singular_translation.ex +++ b/lib/kanta/translations/singular_translation.ex @@ -3,7 +3,7 @@ defmodule Kanta.Translations.SingularTranslation do Singular translation DB model """ - use Ecto.Schema + use Kanta.Schema import Ecto.Changeset alias Kanta.Translations.{Locale, Message} diff --git a/lib/kanta/utils/param_parsers.ex b/lib/kanta/utils/param_parsers.ex new file mode 100644 index 0000000..b1bff40 --- /dev/null +++ b/lib/kanta/utils/param_parsers.ex @@ -0,0 +1,29 @@ +defmodule Kanta.Utils.ParamParsers do + @moduledoc false + + def default_id_parser(id) do + case Integer.parse(id) do + {id, _} -> {:ok, id} + _ -> :error + end + end + + def parse_page(page) do + case Integer.parse(page) do + {page, _} -> page + _ -> 1 + end + end + + def parse_id_filter(id) do + run_parse_function(Kanta.config().id_parse_function, id) + end + + defp run_parse_function(parse_function, id) when is_function(parse_function, 1) do + parse_function.(id) + end + + defp run_parse_function({module, parse_function, 1}, id) do + apply(module, parse_function, [id]) + end +end diff --git a/lib/kanta_web/components/shared/select/select.ex b/lib/kanta_web/components/shared/select/select.ex index 43e5ac3..7959322 100644 --- a/lib/kanta_web/components/shared/select/select.ex +++ b/lib/kanta_web/components/shared/select/select.ex @@ -15,7 +15,8 @@ defmodule KantaWeb.Components.Shared.Select do if is_nil(field) do List.first(options) else - Enum.find(options, &(&1.value == value_to_integer(field.value))) || List.first(options) + Enum.find(options, &(parse_select_value(&1.value) == field.value)) || + List.first(options) end else assigns.selected_option @@ -40,12 +41,7 @@ defmodule KantaWeb.Components.Shared.Select do } end - defp value_to_integer(nil), do: nil - defp value_to_integer(""), do: nil - - defp value_to_integer(value) do - String.to_integer(value) - rescue - _ in ArgumentError -> nil - end + defp parse_select_value(nil), do: nil + defp parse_select_value(""), do: nil + defp parse_select_value(value), do: to_string(value) end diff --git a/lib/kanta_web/components/shared/select/select.html.heex b/lib/kanta_web/components/shared/select/select.html.heex index 4f7f4c7..47e2067 100644 --- a/lib/kanta_web/components/shared/select/select.html.heex +++ b/lib/kanta_web/components/shared/select/select.html.heex @@ -1,7 +1,7 @@
{ $watch('selectedIdx', val => $dispatch('selected-change', { selectedIdx: val, id: '##{@id}' }) ) }"} x-on:reset="open = false" > diff --git a/lib/kanta_web/live/translations/context_live/context_live.ex b/lib/kanta_web/live/translations/context_live/context_live.ex index d805ce9..502cdd8 100644 --- a/lib/kanta_web/live/translations/context_live/context_live.ex +++ b/lib/kanta_web/live/translations/context_live/context_live.ex @@ -1,18 +1,25 @@ defmodule KantaWeb.Translations.ContextLive do use KantaWeb, :live_view + import Kanta.Utils.ParamParsers, only: [parse_id_filter: 1] + alias Kanta.Translations alias Kanta.Translations.Context def mount(%{"id" => id}, _session, socket) do - context = - case Translations.get_context(filter: [id: id]) do - {:ok, %Context{} = context} -> context - {:error, _, _reason} -> nil + socket = + case get_context(id) do + {:ok, %Context{} = context} -> assign(socket, :context, context) + {:error, _, _reason} -> redirect(socket, to: "/kanta/contexts") end - socket = socket |> assign(:context, context) - {:ok, socket} end + + defp get_context(id) do + case parse_id_filter(id) do + {:ok, id} -> Translations.get_context(filter: [id: id]) + _ -> {:error, :id, :invalid} + end + end end diff --git a/lib/kanta_web/live/translations/domain_live/domain_live.ex b/lib/kanta_web/live/translations/domain_live/domain_live.ex index 7f87a25..a7f1a27 100644 --- a/lib/kanta_web/live/translations/domain_live/domain_live.ex +++ b/lib/kanta_web/live/translations/domain_live/domain_live.ex @@ -1,18 +1,25 @@ defmodule KantaWeb.Translations.DomainLive do use KantaWeb, :live_view + import Kanta.Utils.ParamParsers, only: [parse_id_filter: 1] + alias Kanta.Translations alias Kanta.Translations.Domain def mount(%{"id" => id}, _session, socket) do - domain = - case Translations.get_domain(filter: [id: id]) do - {:ok, %Domain{} = domain} -> domain - {:error, _, _reason} -> nil + socket = + case get_domain(id) do + {:ok, %Domain{} = domain} -> assign(socket, :domain, domain) + {:error, _, _reason} -> redirect(socket, to: "/kanta/domains") end - socket = socket |> assign(:domain, domain) - {:ok, socket} end + + defp get_domain(id) do + case parse_id_filter(id) do + {:ok, id} -> Translations.get_domain(filter: [id: id]) + _ -> {:error, :id, :invalid} + end + end end diff --git a/lib/kanta_web/live/translations/translation_form_live/translation_form_live.ex b/lib/kanta_web/live/translations/translation_form_live/translation_form_live.ex index 3bd1a99..0fbd342 100644 --- a/lib/kanta_web/live/translations/translation_form_live/translation_form_live.ex +++ b/lib/kanta_web/live/translations/translation_form_live/translation_form_live.ex @@ -1,6 +1,8 @@ defmodule KantaWeb.Translations.TranslationFormLive do use KantaWeb, :live_view + import Kanta.Utils.ParamParsers, only: [parse_id_filter: 1] + alias Kanta.Translations alias Kanta.Translations.Message @@ -41,13 +43,15 @@ defmodule KantaWeb.Translations.TranslationFormLive do def mount(%{"message_id" => message_id, "locale_id" => locale_id}, _session, socket) do socket = - with {:ok, locale} <- Translations.get_locale(filter: [id: locale_id]), - {:ok, message} <- Translations.get_message(filter: [id: message_id]), + with {:ok, locale} <- get_locale(locale_id), + {:ok, message} <- get_message(message_id), {:ok, translations} <- get_translations(message, locale) do socket |> assign(:locale, locale) |> assign(:message, message) |> assign(:translations, translations) + else + _ -> redirect(socket, to: "/kanta/locales/#{locale_id}/translations") end {:ok, socket} @@ -116,4 +120,18 @@ defmodule KantaWeb.Translations.TranslationFormLive do end end end + + defp get_locale(locale_id) do + case parse_id_filter(locale_id) do + {:ok, id} -> Translations.get_locale(filter: [id: id]) + _ -> {:error, :id, :invalid} + end + end + + defp get_message(message_id) do + case parse_id_filter(message_id) do + {:ok, id} -> Translations.get_message(filter: [id: id]) + _ -> {:error, :id, :invalid} + end + end end diff --git a/lib/kanta_web/live/translations/translations_live/components/messages_table/messages_table.ex b/lib/kanta_web/live/translations/translations_live/components/messages_table/messages_table.ex index da95517..2b32f0d 100644 --- a/lib/kanta_web/live/translations/translations_live/components/messages_table/messages_table.ex +++ b/lib/kanta_web/live/translations/translations_live/components/messages_table/messages_table.ex @@ -23,7 +23,7 @@ defmodule KantaWeb.Translations.Components.MessagesTable do )} end - def is_translated(%Message{message_type: :singular} = message, locale, source) do + def translated?(%Message{message_type: :singular} = message, locale, source) do case Enum.find(message.singular_translations, &(&1.locale_id == locale.id)) do nil -> false @@ -37,18 +37,18 @@ defmodule KantaWeb.Translations.Components.MessagesTable do end end - def is_translated(%Message{message_type: :plural} = message, locale, source) do + def translated?(%Message{message_type: :plural} = message, locale, source) do case Enum.filter(message.plural_translations, &(&1.locale_id == locale.id)) do [] -> false translations -> translations - |> Enum.map(&is_plural_form_translated(&1, source)) + |> Enum.map(&plural_form_translated?(&1, source)) end end - defp is_plural_form_translated(translation, source) do + defp plural_form_translated?(translation, source) do case get_in(translation, [Access.key!(source)]) do nil -> false diff --git a/lib/kanta_web/live/translations/translations_live/components/messages_table/messages_table.html.heex b/lib/kanta_web/live/translations/translations_live/components/messages_table/messages_table.html.heex index eea9ab8..fd7b6a9 100644 --- a/lib/kanta_web/live/translations/translations_live/components/messages_table/messages_table.html.heex +++ b/lib/kanta_web/live/translations/translations_live/components/messages_table/messages_table.html.heex @@ -36,7 +36,7 @@
<%= original_text(assigns, message) %>
@@ -44,7 +44,7 @@
<%= translated_text(assigns, message) %>
@@ -77,4 +77,4 @@
- \ No newline at end of file + diff --git a/lib/kanta_web/live/translations/translations_live/translations_live.ex b/lib/kanta_web/live/translations/translations_live/translations_live.ex index 17257a9..f72237d 100644 --- a/lib/kanta_web/live/translations/translations_live/translations_live.ex +++ b/lib/kanta_web/live/translations/translations_live/translations_live.ex @@ -1,6 +1,8 @@ defmodule KantaWeb.Translations.TranslationsLive do use KantaWeb, :live_view + import Kanta.Utils.ParamParsers + alias Kanta.Translations alias KantaWeb.Translations.Components.{FiltersBar, MessagesTable} @@ -9,10 +11,11 @@ defmodule KantaWeb.Translations.TranslationsLive do @available_filters ~w(domain_id context_id search not_translated page) @available_params ~w(page search filter) @params_in_filter ~w(domain_id context_id not_translated) + @ids_to_parse ~w(domain_id context_id locale_id) def mount(%{"locale_id" => locale_id} = params, _session, socket) do socket = - case Translations.get_locale(filter: [id: locale_id]) do + case get_locale(locale_id) do {:ok, locale} -> socket |> assign(:locale, locale) @@ -21,6 +24,7 @@ defmodule KantaWeb.Translations.TranslationsLive do _ -> socket + |> redirect(to: "/kanta/locales") end {:ok, socket} @@ -30,9 +34,11 @@ defmodule KantaWeb.Translations.TranslationsLive do %{entries: messages, metadata: messages_metadata} = Translations.list_messages( [] - |> Keyword.merge(filter: Map.put(params["filter"] || %{}, "locale_id", locale_id)) |> Keyword.merge(search: params["search"] || "") |> Keyword.merge(page: parse_page(params["page"] || "1")) + |> Keyword.merge( + filter: parse_filters(Map.put(params["filter"] || %{}, "locale_id", locale_id)) + ) |> Keyword.merge( preloads: [ :context, @@ -74,14 +80,12 @@ defmodule KantaWeb.Translations.TranslationsLive do socket |> assign( :filters, - Map.merge(socket.assigns.filters, %{"page" => String.to_integer(page_number)}) + Map.merge(socket.assigns.filters, %{"page" => parse_page(page_number)}) ) query = UriQuery.params( - format_filters( - Map.merge(socket.assigns.filters, %{"page" => String.to_integer(page_number)}) - ) + format_filters(Map.merge(socket.assigns.filters, %{"page" => parse_page(page_number)})) ) {:noreply, @@ -92,33 +96,32 @@ defmodule KantaWeb.Translations.TranslationsLive do )} end + defp get_locale(id) do + case parse_id_filter(id) do + {:ok, id} -> Translations.get_locale(filter: [id: id]) + _ -> {:error, :id, :invalid} + end + end + defp format_filters(filters) do filters |> Map.take(@available_filters) |> Enum.reject(fn {_, value} -> is_nil(value) or value == "" end) - |> Enum.reduce([filter: %{}, search: "", page: "1"], fn {key, value}, acc -> - case key do - "search" -> - Keyword.put(acc, :search, value) + |> Enum.reduce([filter: %{}, search: "", page: "1"], &update_filters_acc/2) + end - "page" -> - Keyword.put(acc, :page, value) + defp update_filters_acc({"search", value}, acc), do: Keyword.put(acc, :search, value) + defp update_filters_acc({"page", value}, acc), do: Keyword.put(acc, :page, value) - "not_translated" -> - Keyword.put( - acc, - :filter, - Map.put(acc[:filter] || %{}, "not_translated", value) - ) + defp update_filters_acc({"not_translated", value}, acc) do + Keyword.put(acc, :filter, Map.put(acc[:filter] || %{}, "not_translated", value)) + end - filter_key -> - Keyword.put( - acc, - :filter, - Map.put(acc[:filter] || %{}, filter_key, String.to_integer(value)) - ) - end - end) + defp update_filters_acc({key, value}, acc) do + case parse_id_filter(value) do + {:ok, id} -> Keyword.put(acc, :filter, Map.put(acc[:filter] || %{}, key, id)) + _ -> acc + end end defp get_assigns_from_params(params) do @@ -151,10 +154,18 @@ defmodule KantaWeb.Translations.TranslationsLive do defp get_not_translated_default_value(_), do: false - defp parse_page(page) do - case Integer.parse(page) do - {page, _} -> page - _ -> 1 + defp parse_filters(filters) do + Enum.reduce(filters, %{}, &parse_filter/2) + end + + defp parse_filter({key, value}, acc) when key in @ids_to_parse do + case parse_id_filter(value) do + {:ok, id} -> Map.put(acc, key, id) + _ -> acc end end + + defp parse_filter({key, value}, acc) do + Map.put(acc, key, value) + end end diff --git a/lib/kanta_web/plugs/api_auth_plug.ex b/lib/kanta_web/plugs/api_auth_plug.ex index b429dab..3c628bd 100644 --- a/lib/kanta_web/plugs/api_auth_plug.ex +++ b/lib/kanta_web/plugs/api_auth_plug.ex @@ -8,7 +8,7 @@ defmodule KantaWeb.APIAuthPlug do def init(_opts), do: %{} def call(conn, _opts) do - if api_authorization_disabled?() or is_bearer_token_valid?(conn) do + if api_authorization_disabled?() or bearer_token_valid?(conn) do conn else conn @@ -20,16 +20,16 @@ defmodule KantaWeb.APIAuthPlug do end end - defp is_bearer_token_valid?(conn) do + defp bearer_token_valid?(conn) do with {:ok, token} <- extract_bearer_token(conn), - true <- is_secret_token_matching?(token) do + true <- secret_token_matching?(token) do true else _ -> false end end - defp is_secret_token_matching?(token) do + defp secret_token_matching?(token) do secret_token_env = @kanta_secret_token |> System.get_env() diff --git a/mix.exs b/mix.exs index ac4f82a..91639cd 100644 --- a/mix.exs +++ b/mix.exs @@ -35,8 +35,8 @@ defmodule Kanta.MixProject do defp deps do [ {:expo, "~> 0.3"}, - {:ecto, "~> 3.10"}, - {:ecto_sql, "~> 3.10"}, + {:ecto, "~> 3.12"}, + {:ecto_sql, "~> 3.12"}, {:phoenix, "~> 1.7.0"}, {:phoenix_view, "~> 2.0"}, {:phoenix_live_view, "~> 0.20"}, @@ -48,14 +48,14 @@ defmodule Kanta.MixProject do {:shards, "~> 1.0"}, {:scrivener, "~> 2.0"}, {:scrivener_ecto, "~> 2.0"}, - {:uri_query, "~> 0.1.1"}, + {:uri_query, "~> 0.2"}, # DEV {:esbuild, "~> 0.7", only: :dev}, - {:credo, "~> 1.7", only: [:dev, :test], runtime: false}, + {:credo, "~> 1.7.7", only: [:dev, :test], runtime: false}, {:mix_audit, "~> 2.0", only: [:dev, :test], runtime: false}, {:gettext, github: "ravensiris/gettext", branch: "runtime-gettext", only: [:dev, :test]}, {:ex_doc, "~> 0.27", only: :dev, runtime: false}, - {:dialyxir, "~> 1.3", only: :dev, runtime: false} + {:dialyxir, "~> 1.4", only: :dev, runtime: false} ] end diff --git a/mix.lock b/mix.lock index 99bf2f9..7492144 100644 --- a/mix.lock +++ b/mix.lock @@ -1,20 +1,20 @@ %{ "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "castore": {:hex, :castore, "1.0.5", "9eeebb394cc9a0f3ae56b813459f990abb0a3dedee1be6b27fdb50301930502f", [:mix], [], "hexpm", "8d7c597c3e4a64c395980882d4bca3cebb8d74197c590dc272cfd3b6a6310578"}, - "credo": {:hex, :credo, "1.7.5", "643213503b1c766ec0496d828c90c424471ea54da77c8a168c725686377b9545", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "f799e9b5cd1891577d8c773d245668aa74a2fcd15eb277f51a0131690ebfb3fd"}, - "db_connection": {:hex, :db_connection, "2.6.0", "77d835c472b5b67fc4f29556dee74bf511bbafecdcaf98c27d27fa5918152086", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c2f992d15725e721ec7fbc1189d4ecdb8afef76648c746a8e1cad35e3b8a35f3"}, + "credo": {:hex, :credo, "1.7.7", "771445037228f763f9b2afd612b6aa2fd8e28432a95dbbc60d8e03ce71ba4446", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "8bc87496c9aaacdc3f90f01b7b0582467b69b4bd2441fe8aae3109d843cc2f2e"}, + "db_connection": {:hex, :db_connection, "2.7.0", "b99faa9291bb09892c7da373bb82cba59aefa9b36300f6145c5f201c7adf48ec", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "dcf08f31b2701f857dfc787fbad78223d61a32204f217f15e881dd93e4bdd3ff"}, "decimal": {:hex, :decimal, "2.1.1", "5611dca5d4b2c3dd497dec8f68751f1f1a54755e8ed2a966c2633cf885973ad6", [:mix], [], "hexpm", "53cfe5f497ed0e7771ae1a475575603d77425099ba5faef9394932b35020ffcc"}, "dialyxir": {:hex, :dialyxir, "1.4.3", "edd0124f358f0b9e95bfe53a9fcf806d615d8f838e2202a9f430d59566b6b53b", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "bf2cfb75cd5c5006bec30141b131663299c661a864ec7fbbc72dfa557487a986"}, "earmark_parser": {:hex, :earmark_parser, "1.4.39", "424642f8335b05bb9eb611aa1564c148a8ee35c9c8a8bba6e129d51a3e3c6769", [:mix], [], "hexpm", "06553a88d1f1846da9ef066b87b57c6f605552cfbe40d20bd8d59cc6bde41944"}, - "ecto": {:hex, :ecto, "3.11.1", "4b4972b717e7ca83d30121b12998f5fcdc62ba0ed4f20fd390f16f3270d85c3e", [:mix], [{:decimal, "~> 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", "ebd3d3772cd0dfcd8d772659e41ed527c28b2a8bde4b00fe03e0463da0f1983b"}, - "ecto_sql": {:hex, :ecto_sql, "3.11.1", "e9abf28ae27ef3916b43545f9578b4750956ccea444853606472089e7d169470", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.11.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", "ce14063ab3514424276e7e360108ad6c2308f6d88164a076aac8a387e1fea634"}, + "ecto": {:hex, :ecto, "3.12.3", "1a9111560731f6c3606924c81c870a68a34c819f6d4f03822f370ea31a582208", [:mix], [{:decimal, "~> 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", "9efd91506ae722f95e48dc49e70d0cb632ede3b7a23896252a60a14ac6d59165"}, + "ecto_sql": {:hex, :ecto_sql, "3.12.0", "73cea17edfa54bde76ee8561b30d29ea08f630959685006d9c6e7d1e59113b7d", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.12", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.7", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.19 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", "dc9e4d206f274f3947e96142a8fdc5f69a2a6a9abb4649ef5c882323b6d512f0"}, "erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"}, "esbuild": {:hex, :esbuild, "0.8.1", "0cbf919f0eccb136d2eeef0df49c4acf55336de864e63594adcea3814f3edf41", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "25fc876a67c13cb0a776e7b5d7974851556baeda2085296c14ab48555ea7560f"}, "ex_doc": {:hex, :ex_doc, "0.31.1", "8a2355ac42b1cc7b2379da9e40243f2670143721dd50748bf6c3b1184dae2089", [:mix], [{:earmark_parser, "~> 1.4.39", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.1", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "3178c3a407c557d8343479e1ff117a96fd31bafe52a039079593fb0524ef61b0"}, "expo": {:hex, :expo, "0.4.1", "1c61d18a5df197dfda38861673d392e642649a9cef7694d2f97a587b2cfb319b", [:mix], [], "hexpm", "2ff7ba7a798c8c543c12550fa0e2cbc81b95d4974c65855d8d15ba7b37a1ce47"}, - "file_system": {:hex, :file_system, "1.0.0", "b689cc7dcee665f774de94b5a832e578bd7963c8e637ef940cd44327db7de2cd", [:mix], [], "hexpm", "6752092d66aec5a10e662aefeed8ddb9531d79db0bc145bb8c40325ca1d8536d"}, + "file_system": {:hex, :file_system, "1.0.1", "79e8ceaddb0416f8b8cd02a0127bdbababe7bf4a23d2a395b983c1f8b3f73edd", [:mix], [], "hexpm", "4414d1f38863ddf9120720cd976fce5bdde8e91d8283353f0e31850fa89feb9e"}, "gettext": {:git, "https://github.com/ravensiris/gettext.git", "030ad843e38eaa935062997c2313709e04ecfafc", [branch: "runtime-gettext"]}, - "jason": {:hex, :jason, "1.4.1", "af1504e35f629ddcdd6addb3513c3853991f694921b1b9368b0bd32beb9f1b63", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fbb01ecdfd565b56261302f7e1fcc27c4fb8f32d56eab74db621fc154604a7a1"}, + "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, "makeup": {:hex, :makeup, "1.1.1", "fa0bc768698053b2b3869fa8a62616501ff9d11a562f3ce39580d60860c3a55e", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "5dc62fbdd0de44de194898b6710692490be74baa02d9d108bc29f007783b0b48"}, "makeup_elixir": {:hex, :makeup_elixir, "0.16.2", "627e84b8e8bf22e60a2579dad15067c755531fea049ae26ef1020cad58fe9578", [: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", "41193978704763f6bbe6cc2758b84909e62984c7752b3784bd3c218bb341706b"}, "makeup_erlang": {:hex, :makeup_erlang, "0.1.5", "e0ff5a7c708dda34311f7522a8758e23bfcd7d8d8068dc312b5eb41c6fd76eba", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "94d2e986428585a21516d7d7149781480013c56e30c6a233534bedf38867a59a"}, @@ -35,8 +35,8 @@ "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.2", "9e27288b568ede1d88517e8c61259bc214a12d7eed271e102db4c93fcca9b2cd", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}], "hexpm", "ccfb5025179ea307f7f899d1bb3905cd0ac9f687ed77feebc8f67bdca78565c4"}, - "telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"}, - "uri_query": {:hex, :uri_query, "0.1.2", "ae35b83b472f3568c2c159eee3f3ccf585375d8a94fb5382db1ea3589e75c3b4", [:mix], [], "hexpm", "e3bc81816c98502c36498b9b2f239b89c71ce5eadfff7ceb2d6c0a2e6ae2ea0c"}, + "telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"}, + "uri_query": {:hex, :uri_query, "0.2.0", "0f5e0f7ea6d9e6a7fb4929a81df9ecd756e3c71bdee5c9bc14e57d90069a82f7", [:mix], [], "hexpm", "e99f50a6af7c6643dff948db152a6a420bfe446aaec7f0924cfcdb710c175e63"}, "websock": {:hex, :websock, "0.5.3", "2f69a6ebe810328555b6fe5c831a851f485e303a7c8ce6c5f675abeb20ebdadc", [:mix], [], "hexpm", "6105453d7fac22c712ad66fab1d45abdf049868f253cf719b625151460b8b453"}, "websock_adapter": {:hex, :websock_adapter, "0.5.5", "9dfeee8269b27e958a65b3e235b7e447769f66b5b5925385f5a569269164a210", [: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", "4b977ba4a01918acbf77045ff88de7f6972c2a009213c515a445c48f224ffce9"}, "yamerl": {:hex, :yamerl, "0.10.0", "4ff81fee2f1f6a46f1700c0d880b24d193ddb74bd14ef42cb0bcf46e81ef2f8e", [:rebar3], [], "hexpm", "346adb2963f1051dc837a2364e4acf6eb7d80097c0f53cbdc3046ec8ec4b4e6e"}, diff --git a/test/kanta_test.exs b/test/kanta_test.exs deleted file mode 100644 index e77c51d..0000000 --- a/test/kanta_test.exs +++ /dev/null @@ -1,8 +0,0 @@ -defmodule KantaTest do - use ExUnit.Case - doctest Kanta - - test "greets the world" do - assert Kanta.hello() == :world - end -end