From 194a8507dab93fd9783acb189bcdfb1878d2042e Mon Sep 17 00:00:00 2001 From: Dawid Sawczuk Date: Mon, 30 Mar 2026 11:24:19 +0200 Subject: [PATCH 1/3] feat: igniter configuration --- lib/mix/tasks/permit_ecto.install.ex | 151 ++++++++++++++++++++ mix.exs | 12 +- mix.lock | 14 ++ test/mix/tasks/permit_ecto_install_test.exs | 90 ++++++++++++ 4 files changed, 265 insertions(+), 2 deletions(-) create mode 100644 lib/mix/tasks/permit_ecto.install.ex create mode 100644 test/mix/tasks/permit_ecto_install_test.exs diff --git a/lib/mix/tasks/permit_ecto.install.ex b/lib/mix/tasks/permit_ecto.install.ex new file mode 100644 index 0000000..e06ece0 --- /dev/null +++ b/lib/mix/tasks/permit_ecto.install.ex @@ -0,0 +1,151 @@ +if Code.ensure_loaded?(Igniter) do + defmodule Mix.Tasks.PermitEcto.Install do + @shortdoc "Installs Permit.Ecto authorization into your project" + + @moduledoc """ + Installs Permit.Ecto authorization into your project, creating an authorization + module and a permissions module configured for Ecto. + + ## Usage + + mix permit_ecto.install + + ## Options + + - `--authorization-module` - Authorization module name (default: `.Authorization`) + - `--permissions-module` - Permissions module name (default: `.Authorization.Permissions`) + - `--actions-module` - Actions module to use in permissions (default: `Permit.Actions.CrudActions`) + - `--repo` - Ecto repo module name (auto-detected if not specified) + """ + + use Igniter.Mix.Task + + alias Igniter.Project.Module, as: ProjectModule + + @impl Igniter.Mix.Task + def info(_argv, _composing_task) do + %Igniter.Mix.Task.Info{ + group: :permit, + schema: [ + authorization_module: :string, + permissions_module: :string, + actions_module: :string, + repo: :string + ] + } + end + + @impl Igniter.Mix.Task + def igniter(igniter) do + options = igniter.args.options + app_module = ProjectModule.module_name_prefix(igniter) + + authorization_module = + parse_module(options[:authorization_module], Module.concat(app_module, Authorization)) + + permissions_module = + parse_module( + options[:permissions_module], + Module.concat(authorization_module, Permissions) + ) + + actions_module = parse_module(options[:actions_module], nil) + + {igniter, repo} = detect_repo(igniter, options[:repo]) + + igniter + |> create_authorization_module(authorization_module, permissions_module, repo) + |> create_permissions_module(permissions_module, actions_module) + |> Igniter.add_notice(""" + Permit.Ecto has been set up! + + Edit #{inspect(permissions_module)} to define your permission rules. + + Example: + + def can(%MyApp.User{id: user_id}) do + permit() + |> all(MyApp.Resource, owner_id: user_id) + |> read(MyApp.Resource) + end + """) + end + + defp create_authorization_module(igniter, authorization_module, permissions_module, repo) do + ProjectModule.create_module(igniter, authorization_module, """ + use Permit.Ecto, + permissions_module: #{inspect(permissions_module)}, + repo: #{inspect(repo)} + """) + end + + defp create_permissions_module(igniter, permissions_module, actions_module) do + resolved_actions = actions_module || Permit.Actions.CrudActions + + ProjectModule.create_module(igniter, permissions_module, """ + use Permit.Ecto.Permissions, actions_module: #{inspect(resolved_actions)} + + def can(_user) do + permit() + end + """) + end + + defp detect_repo(igniter, repo_string) when is_binary(repo_string) do + {igniter, parse_module(repo_string, nil)} + end + + defp detect_repo(igniter, nil) do + if Code.ensure_loaded?(Igniter.Libs.Ecto) do + {igniter, repos} = Igniter.Libs.Ecto.list_repos(igniter) + + case repos do + [repo] -> + {igniter, repo} + + [repo | _rest] -> + {Igniter.add_notice( + igniter, + "Multiple Ecto repos detected. Using #{inspect(repo)}. Pass --repo to specify a different one." + ), repo} + + [] -> + app_module = ProjectModule.module_name_prefix(igniter) + fallback = Module.concat(app_module, Repo) + + {Igniter.add_notice( + igniter, + "Could not auto-detect Ecto repo. Using #{inspect(fallback)}. Pass --repo if this is incorrect." + ), fallback} + end + else + app_module = ProjectModule.module_name_prefix(igniter) + fallback = Module.concat(app_module, Repo) + {igniter, fallback} + end + end + + defp parse_module(nil, default), do: default + + defp parse_module(string, _default) when is_binary(string) do + string + |> String.split(".") + |> Module.concat() + end + end +else + defmodule Mix.Tasks.PermitEcto.Install do + @shortdoc "Installs Permit.Ecto authorization into your project" + @moduledoc "Installs Permit.Ecto authorization into your project. Requires the `igniter` package." + + use Mix.Task + + def run(_argv) do + Mix.shell().error(""" + The `permit_ecto.install` task requires the `igniter` package. + + Please add `{:igniter, "~> 0.5"}` to your dependencies and run `mix deps.get`. + """) + end + end +end diff --git a/mix.exs b/mix.exs index 905bdf2..11de249 100644 --- a/mix.exs +++ b/mix.exs @@ -55,7 +55,7 @@ defmodule Permit.Ecto.MixProject do {:ecto, ">= 3.11.2 and < 4.0.0"}, {:ecto_sql, ">= 3.11.0"}, {:postgrex, "~> 0.16", only: :test}, - {:jason, "~> 1.3", only: [:dev, :test]}, + {:jason, "~> 1.3"}, {:ex_doc, ">= 0.0.0", only: :dev, runtime: false}, {:dialyxir, "~> 1.3", only: [:dev, :test], runtime: false}, {:credo, "~> 1.7", only: [:dev, :test], runtime: false}, @@ -63,7 +63,15 @@ defmodule Permit.Ecto.MixProject do {:git_cli, "~> 0.3.0", only: [:dev, :test], runtime: false}, {:excoveralls, "~> 0.18", only: :test, runtime: false}, {:castore, "~> 1.0", only: :test, runtime: false} - ] + ] ++ igniter_dep() + end + + defp igniter_dep do + if Version.match?(System.version(), ">= 1.15.0") do + [{:igniter, "~> 0.5", optional: true}] + else + [] + end end defp docs do diff --git a/mix.lock b/mix.lock index dc91cd0..84f1e19 100644 --- a/mix.lock +++ b/mix.lock @@ -13,14 +13,28 @@ "ex_doc": {:hex, :ex_doc, "0.40.1", "67542e4b6dde74811cfd580e2c0149b78010fd13001fda7cfeb2b2c2ffb1344d", [:mix], [{:earmark_parser, "~> 1.4.44", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "bcef0e2d360d93ac19f01a85d58f91752d930c0a30e2681145feea6bd3516e00"}, "excoveralls": {:hex, :excoveralls, "0.18.5", "e229d0a65982613332ec30f07940038fe451a2e5b29bce2a5022165f0c9b157e", [:mix], [{:castore, "~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "523fe8a15603f86d64852aab2abe8ddbd78e68579c8525ae765facc5eae01562"}, "file_system": {:hex, :file_system, "1.1.1", "31864f4685b0148f25bd3fbef2b1228457c0c89024ad67f7a81a3ffbc0bbad3a", [:mix], [], "hexpm", "7a15ff97dfe526aeefb090a7a9d3d03aa907e100e262a0f8f7746b78f8f87a5d"}, + "finch": {:hex, :finch, "0.21.0", "b1c3b2d48af02d0c66d2a9ebfb5622be5c5ecd62937cf79a88a7f98d48a8290c", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.6.2 or ~> 1.7", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.1", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "87dc6e169794cb2570f75841a19da99cfde834249568f2a5b121b809588a4377"}, "git_cli": {:hex, :git_cli, "0.3.0", "a5422f9b95c99483385b976f5d43f7e8233283a47cda13533d7c16131cb14df5", [:mix], [], "hexpm", "78cb952f4c86a41f4d3511f1d3ecb28edb268e3a7df278de2faa1bd4672eaf9b"}, + "glob_ex": {:hex, :glob_ex, "0.1.11", "cb50d3f1ef53f6ca04d6252c7fde09fd7a1cf63387714fe96f340a1349e62c93", [:mix], [], "hexpm", "342729363056e3145e61766b416769984c329e4378f1d558b63e341020525de4"}, + "hpax": {:hex, :hpax, "1.0.3", "ed67ef51ad4df91e75cc6a1494f851850c0bd98ebc0be6e81b026e765ee535aa", [:mix], [], "hexpm", "8eab6e1cfa8d5918c2ce4ba43588e894af35dbd8e91e6e55c817bca5847df34a"}, + "igniter": {:hex, :igniter, "0.7.7", "08bae07b7b610100bc7c676e6b18130fe12bb90617982023cc798346879c2c5f", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "caeb1227887362b22038ff8419a7e6ddd3888f3d7e6cffacb14c73abbce17600"}, "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.2.1", "e90ac1c65589ef354378def3ba19d401e739ee7ee06fb47f94c687016e3713d1", [:mix], [{:nimble_parsec, "~> 1.4", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "d36484867b0bae0fea568d10131197a4c2e47056a6fbe84922bf6ba71c8d17ce"}, "makeup_elixir": {:hex, :makeup_elixir, "1.0.1", "e928a4f984e795e41e3abd27bfc09f51db16ab8ba1aebdba2b3a575437efafc2", [: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", "7284900d412a3e5cfd97fdaed4f5ed389b8f2b4cb49efc0eb3bd10e2febf9507"}, "makeup_erlang": {:hex, :makeup_erlang, "1.0.3", "4252d5d4098da7415c390e847c814bad3764c94a814a0b4245176215615e1035", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "953297c02582a33411ac6208f2c6e55f0e870df7f80da724ed613f10e6706afd"}, + "mime": {:hex, :mime, "2.0.7", "b8d739037be7cd402aee1ba0306edfdef982687ee7e9859bee6198c1e7e2f128", [:mix], [], "hexpm", "6171188e399ee16023ffc5b76ce445eb6d9672e2e241d2df6050f3c771e80ccd"}, + "mint": {:hex, :mint, "1.7.1", "113fdb2b2f3b59e47c7955971854641c61f378549d73e829e1768de90fc1abf1", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0 or ~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "fceba0a4d0f24301ddee3024ae116df1c3f4bb7a563a731f45fdfeb9d39a231b"}, + "nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"}, "nimble_parsec": {:hex, :nimble_parsec, "1.4.2", "8efba0122db06df95bfaa78f791344a89352ba04baedd3849593bfce4d0dc1c6", [:mix], [], "hexpm", "4b21398942dda052b403bbe1da991ccd03a053668d147d53fb8c4e0efe09c973"}, + "nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"}, + "owl": {:hex, :owl, "0.13.0", "26010e066d5992774268f3163506972ddac0a7e77bfe57fa42a250f24d6b876e", [:mix], [{:ucwidth, "~> 0.2", [hex: :ucwidth, repo: "hexpm", optional: true]}], "hexpm", "59bf9d11ce37a4db98f57cb68fbfd61593bf419ec4ed302852b6683d3d2f7475"}, "permit": {:hex, :permit, "0.3.3", "1ed016b39983b897c92944a4ef9ca135b22adf0aabadbb29b2f30f30f770e965", [:mix], [], "hexpm", "5fdf26b5b3f455b187693ed566924eae3c939221e3dc828cf5f75d3ea2341b63"}, "postgrex": {:hex, :postgrex, "0.22.0", "fb027b58b6eab1f6de5396a2abcdaaeb168f9ed4eccbb594e6ac393b02078cbd", [:mix], [{:db_connection, "~> 2.9", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "a68c4261e299597909e03e6f8ff5a13876f5caadaddd0d23af0d0a61afcc5d84"}, + "req": {:hex, :req, "0.5.17", "0096ddd5b0ed6f576a03dde4b158a0c727215b15d2795e59e0916c6971066ede", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.17", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 2.0.6 or ~> 2.1", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "0b8bc6ffdfebbc07968e59d3ff96d52f2202d0536f10fef4dc11dc02a2a43e39"}, + "rewrite": {:hex, :rewrite, "1.3.0", "67448ba7975690b35ba7e7f35717efcce317dbd5963cb0577aa7325c1923121a", [:mix], [{:glob_ex, "~> 0.1", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}, {:text_diff, "~> 0.1", [hex: :text_diff, repo: "hexpm", optional: false]}], "hexpm", "d111ac7ff3a58a802ef4f193bbd1831e00a9c57b33276e5068e8390a212714a5"}, + "sourceror": {:hex, :sourceror, "1.12.0", "da354c5f35aad3cc1132f5d5b0d8437d865e2661c263260480bab51b5eedb437", [:mix], [], "hexpm", "755703683bd014ebcd5de9acc24b68fb874a660a568d1d63f8f98cd8a6ef9cd0"}, + "spitfire": {:hex, :spitfire, "0.3.10", "19aea9914132456515e8f7d592f63ab9f3130876b0252e834d2390bdd8becb24", [:mix], [], "hexpm", "6a6a5f77eb4165249c76199cd2d01fb595bac9207aed3de551918ac1c2bc9267"}, "telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"}, + "text_diff": {:hex, :text_diff, "0.1.0", "1caf3175e11a53a9a139bc9339bd607c47b9e376b073d4571c031913317fecaa", [:mix], [], "hexpm", "d1ffaaecab338e49357b6daa82e435f877e0649041ace7755583a0ea3362dbd7"}, "versioce": {:hex, :versioce, "2.0.0", "a31b5e7b744d0d4a3694dd6fe4c0ee403e969631789e73cbd2a3367246404948", [:mix], [{:git_cli, "~> 0.3.0", [hex: :git_cli, repo: "hexpm", optional: true]}], "hexpm", "b2112ce621cd40fe23ad957a3dd82bccfdfa33c9a7f1e710a44b75ae772186cc"}, } diff --git a/test/mix/tasks/permit_ecto_install_test.exs b/test/mix/tasks/permit_ecto_install_test.exs new file mode 100644 index 0000000..1c5ee01 --- /dev/null +++ b/test/mix/tasks/permit_ecto_install_test.exs @@ -0,0 +1,90 @@ +defmodule Mix.Tasks.PermitEcto.InstallTest do + use ExUnit.Case + + import Igniter.Test + + describe "permit_ecto.install" do + test "creates authorization and permissions modules" do + test_project() + |> Igniter.compose_task("permit_ecto.install", []) + |> assert_creates("lib/test/authorization.ex") + |> assert_creates("lib/test/authorization/permissions.ex") + end + + test "authorization module uses Permit.Ecto with correct options" do + igniter = + test_project() + |> Igniter.compose_task("permit_ecto.install", []) + |> apply_igniter!() + + source = Rewrite.source!(igniter.rewrite, "lib/test/authorization.ex") + content = Rewrite.Source.get(source, :content) + + assert content =~ "use Permit.Ecto" + assert content =~ "permissions_module: Test.Authorization.Permissions" + assert content =~ "repo: Test.Repo" + end + + test "permissions module uses Permit.Ecto.Permissions with CrudActions by default" do + igniter = + test_project() + |> Igniter.compose_task("permit_ecto.install", []) + |> apply_igniter!() + + source = Rewrite.source!(igniter.rewrite, "lib/test/authorization/permissions.ex") + content = Rewrite.Source.get(source, :content) + + assert content =~ "use Permit.Ecto.Permissions, actions_module: Permit.Actions.CrudActions" + assert content =~ "def can(_user) do" + assert content =~ "permit()" + end + + test "uses custom repo when specified" do + igniter = + test_project() + |> Igniter.compose_task("permit_ecto.install", ["--repo", "Test.CustomRepo"]) + |> apply_igniter!() + + source = Rewrite.source!(igniter.rewrite, "lib/test/authorization.ex") + content = Rewrite.Source.get(source, :content) + + assert content =~ "repo: Test.CustomRepo" + end + + test "uses provided actions module in permissions" do + igniter = + test_project() + |> Igniter.compose_task("permit_ecto.install", [ + "--actions-module", + "Test.Authorization.Actions" + ]) + |> apply_igniter!() + + source = Rewrite.source!(igniter.rewrite, "lib/test/authorization/permissions.ex") + content = Rewrite.Source.get(source, :content) + + assert content =~ "actions_module: Test.Authorization.Actions" + end + + test "uses custom authorization module name" do + test_project() + |> Igniter.compose_task("permit_ecto.install", [ + "--authorization-module", + "Test.Auth" + ]) + |> assert_creates("lib/test/auth.ex") + end + + test "uses custom permissions module name" do + test_project() + |> Igniter.compose_task("permit_ecto.install", [ + "--authorization-module", + "Test.Auth", + "--permissions-module", + "Test.Auth.Perms" + ]) + |> assert_creates("lib/test/auth.ex") + |> assert_creates("lib/test/auth/perms.ex") + end + end +end From 0cdb9565debf53f49f62f4a27e879dd47d60ecea Mon Sep 17 00:00:00 2001 From: Dawid Sawczuk Date: Mon, 30 Mar 2026 12:54:37 +0200 Subject: [PATCH 2/3] versioning and dialyzer in mix --- lib/mix/tasks/permit_ecto.install.ex | 17 +---------------- mix.exs | 2 +- test/mix/tasks/permit_ecto_install_test.exs | 8 +++++--- 3 files changed, 7 insertions(+), 20 deletions(-) diff --git a/lib/mix/tasks/permit_ecto.install.ex b/lib/mix/tasks/permit_ecto.install.ex index e06ece0..620ecc6 100644 --- a/lib/mix/tasks/permit_ecto.install.ex +++ b/lib/mix/tasks/permit_ecto.install.ex @@ -1,4 +1,4 @@ -if Code.ensure_loaded?(Igniter) do +if Version.match?(System.version(), ">= 1.15.0") and Code.ensure_loaded?(Igniter) do defmodule Mix.Tasks.PermitEcto.Install do @shortdoc "Installs Permit.Ecto authorization into your project" @@ -133,19 +133,4 @@ if Code.ensure_loaded?(Igniter) do |> Module.concat() end end -else - defmodule Mix.Tasks.PermitEcto.Install do - @shortdoc "Installs Permit.Ecto authorization into your project" - @moduledoc "Installs Permit.Ecto authorization into your project. Requires the `igniter` package." - - use Mix.Task - - def run(_argv) do - Mix.shell().error(""" - The `permit_ecto.install` task requires the `igniter` package. - - Please add `{:igniter, "~> 0.5"}` to your dependencies and run `mix deps.get`. - """) - end - end end diff --git a/mix.exs b/mix.exs index 11de249..4c2dd85 100644 --- a/mix.exs +++ b/mix.exs @@ -15,7 +15,7 @@ defmodule Permit.Ecto.MixProject do deps: deps(), description: "Ecto integration for the Permit authorization library.", package: package(), - dialyzer: [plt_add_apps: [:ex_unit]], + dialyzer: [plt_add_apps: [:ex_unit, :mix]], docs: docs(), test_coverage: [tool: ExCoveralls], preferred_cli_env: [ diff --git a/test/mix/tasks/permit_ecto_install_test.exs b/test/mix/tasks/permit_ecto_install_test.exs index 1c5ee01..6e2e607 100644 --- a/test/mix/tasks/permit_ecto_install_test.exs +++ b/test/mix/tasks/permit_ecto_install_test.exs @@ -1,7 +1,8 @@ -defmodule Mix.Tasks.PermitEcto.InstallTest do - use ExUnit.Case +if Version.match?(System.version(), ">= 1.15.0") and Code.ensure_loaded?(Igniter.Test) do + defmodule Mix.Tasks.PermitEcto.InstallTest do + use ExUnit.Case - import Igniter.Test + import Igniter.Test describe "permit_ecto.install" do test "creates authorization and permissions modules" do @@ -87,4 +88,5 @@ defmodule Mix.Tasks.PermitEcto.InstallTest do |> assert_creates("lib/test/auth/perms.ex") end end + end end From 211cc71c48fffb6aa19ff71f0268df454906e36d Mon Sep 17 00:00:00 2001 From: Dawid Sawczuk Date: Mon, 27 Apr 2026 12:07:55 +0200 Subject: [PATCH 3/3] Fixes --- README.md | 46 ++++++++++++++++++++++++++++ lib/mix/tasks/permit_ecto.install.ex | 5 +-- mix.exs | 6 ++-- 3 files changed, 52 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 1819693..eb5cc9b 100644 --- a/README.md +++ b/README.md @@ -114,6 +114,36 @@ Permit.Ecto is part of the modular Permit ecosystem: ## Installation +### Using Igniter (recommended) + +If you use [Igniter](https://hex.pm/packages/igniter) for project setup, add `permit_ecto` and `igniter` to your deps and run: + +```bash +mix permit_ecto.install +``` + +This creates your `Authorization` and `Authorization.Permissions` modules pre configured for Ecto. + +**Options:** + +| Option | Description | Default | +|---|---|---| +| `--authorization-module` | Authorization module name | `.Authorization` | +| `--permissions-module` | Permissions module name | `.Authorization.Permissions` | +| `--actions-module` | Actions module to use | `Permit.Actions.CrudActions` | +| `--repo` | Ecto repo module | auto-detected | + +Example with options: + +```bash +mix permit_ecto.install \ + --authorization-module MyApp.Auth \ + --permissions-module MyApp.Auth.Permissions \ + --repo MyApp.Repo +``` + +### Manual installation + The package can be installed by adding `permit_ecto` to your list of dependencies in `mix.exs`: ```elixir @@ -125,6 +155,22 @@ def deps do end ``` +Then create your authorization module: + +```elixir +defmodule MyApp.Authorization do + use Permit.Ecto, + permissions_module: MyApp.Authorization.Permissions, + repo: MyApp.Repo +end + +defmodule MyApp.Authorization.Permissions do + use Permit.Ecto.Permissions, actions_module: Permit.Actions.CrudActions + + def can(_user), do: permit() +end +``` + For a complete setup with Phoenix or Absinthe integration, add `:permit_phoenix` or `:permit_absinthe`. ## Documentation diff --git a/lib/mix/tasks/permit_ecto.install.ex b/lib/mix/tasks/permit_ecto.install.ex index 620ecc6..46c7ccd 100644 --- a/lib/mix/tasks/permit_ecto.install.ex +++ b/lib/mix/tasks/permit_ecto.install.ex @@ -20,6 +20,7 @@ if Version.match?(System.version(), ">= 1.15.0") and Code.ensure_loaded?(Igniter use Igniter.Mix.Task + alias Igniter.Libs.Ecto, as: IgniterEcto alias Igniter.Project.Module, as: ProjectModule @impl Igniter.Mix.Task @@ -96,8 +97,8 @@ if Version.match?(System.version(), ">= 1.15.0") and Code.ensure_loaded?(Igniter end defp detect_repo(igniter, nil) do - if Code.ensure_loaded?(Igniter.Libs.Ecto) do - {igniter, repos} = Igniter.Libs.Ecto.list_repos(igniter) + if Code.ensure_loaded?(IgniterEcto) do + {igniter, repos} = IgniterEcto.list_repos(igniter) case repos do [repo] -> diff --git a/mix.exs b/mix.exs index 4c2dd85..a803bdd 100644 --- a/mix.exs +++ b/mix.exs @@ -15,7 +15,7 @@ defmodule Permit.Ecto.MixProject do deps: deps(), description: "Ecto integration for the Permit authorization library.", package: package(), - dialyzer: [plt_add_apps: [:ex_unit, :mix]], + dialyzer: [plt_add_apps: [:ex_unit, :mix, :igniter]], docs: docs(), test_coverage: [tool: ExCoveralls], preferred_cli_env: [ @@ -55,7 +55,7 @@ defmodule Permit.Ecto.MixProject do {:ecto, ">= 3.11.2 and < 4.0.0"}, {:ecto_sql, ">= 3.11.0"}, {:postgrex, "~> 0.16", only: :test}, - {:jason, "~> 1.3"}, + {:jason, "~> 1.3", only: [:dev, :test]}, {:ex_doc, ">= 0.0.0", only: :dev, runtime: false}, {:dialyxir, "~> 1.3", only: [:dev, :test], runtime: false}, {:credo, "~> 1.7", only: [:dev, :test], runtime: false}, @@ -68,7 +68,7 @@ defmodule Permit.Ecto.MixProject do defp igniter_dep do if Version.match?(System.version(), ">= 1.15.0") do - [{:igniter, "~> 0.5", optional: true}] + [{:igniter, "~> 0.5", only: [:dev, :test], runtime: false}] else [] end