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
|