From bbed1a5cd83bc3cb583aab4f266bc189b13dd146 Mon Sep 17 00:00:00 2001 From: Eduardo Borsa Date: Tue, 9 Jan 2024 15:09:36 -0300 Subject: [PATCH] Feat: Route and Resource Loading Add --path to beacon.install generator (#387) --- lib/beacon/types/site.ex | 9 +++ lib/mix/tasks/install.ex | 35 ++++++++-- priv/templates/install/beacon_router_scope.ex | 2 +- test/beacon/types/site_test.exs | 10 +++ test/mix/tasks/install_test.exs | 67 +++++++++++++++++++ 5 files changed, 116 insertions(+), 7 deletions(-) diff --git a/lib/beacon/types/site.ex b/lib/beacon/types/site.ex index 30bd0d13a..8b1615f45 100644 --- a/lib/beacon/types/site.ex +++ b/lib/beacon/types/site.ex @@ -27,6 +27,7 @@ defmodule Beacon.Types.Site do def valid?(site) when site in ["", nil, true, false], do: false def valid?(site) when is_binary(site) do + Regex.match?(~r/^[a-zA-Z0-9_]+$/, site) end def valid?(site) when is_atom(site) do @@ -44,6 +45,14 @@ defmodule Beacon.Types.Site do not String.starts_with?(site, "beacon_") end + def valid_path?(path) when is_atom(path) do + path |> Atom.to_string() |> valid_path?() + end + + def valid_path?(path) when is_binary(path) do + String.starts_with?(path, "/") + end + @doc false def safe_to_atom(site) when is_atom(site), do: site def safe_to_atom(site) when is_binary(site), do: String.to_existing_atom(site) diff --git a/lib/mix/tasks/install.ex b/lib/mix/tasks/install.ex index 8081f2206..fce02cd9d 100644 --- a/lib/mix/tasks/install.ex +++ b/lib/mix/tasks/install.ex @@ -20,7 +20,8 @@ defmodule Mix.Tasks.Beacon.Install do use Mix.Task @switches [ - site: :string + site: :string, + path: :string ] def run(argv) do @@ -274,7 +275,10 @@ defmodule Mix.Tasks.Beacon.Install do end defp build_context_bindings(options) do - options = validate_options!(options) + options = + options + |> add_default_options_if_missing() + |> validate_options!() base_module = Mix.Phoenix.base() web_module = Mix.Phoenix.web_module(base_module) @@ -285,6 +289,7 @@ defmodule Mix.Tasks.Beacon.Install do templates_path = Path.join([Application.app_dir(:beacon), "priv", "templates"]) root = root_path() site = Keyword.get(options, :site) + path = Keyword.get(options, :path) [ base_module: base_module, @@ -293,6 +298,7 @@ defmodule Mix.Tasks.Beacon.Install do ctx_app: ctx_app, templates_path: templates_path, site: site, + path: path, beacon_data_source: %{ dest_path: Path.join([root, lib_path, "beacon_data_source.ex"]), template_path: Path.join([templates_path, "install", "beacon_data_source.ex"]), @@ -326,6 +332,8 @@ defmodule Mix.Tasks.Beacon.Install do mix beacon.install expect a site name, for example: mix beacon.install --site blog + or + mix beacon.install --site blog --path "/blog_path" """) end @@ -334,12 +342,27 @@ defmodule Mix.Tasks.Beacon.Install do end defp validate_options!(options) do - site = String.to_atom(options[:site]) - cond do - !Beacon.Types.Site.valid?(site) -> raise_with_help!("Invalid site name. It should not contain special characters.") - !Beacon.Types.Site.valid_name?(site) -> raise_with_help!("Invalid site name. The site name can't start with \"beacon_\".") + !Beacon.Types.Site.valid?(options[:site]) -> raise_with_help!("Invalid site name. It should not contain special characters.") + !Beacon.Types.Site.valid_name?(options[:site]) -> raise_with_help!("Invalid site name. The site name can't start with \"beacon_\".") + !Beacon.Types.Site.valid_path?(options[:path]) -> raise_with_help!("Invalid path value. The path value have to start with /.") :default -> options end end + + defp add_default_options_if_missing(options) do + defaults = + @switches + |> Keyword.keys() + |> Enum.reduce([], fn + :path, acc -> + site = Keyword.get(options, :site) + [{:path, "/#{site}"} | acc] + + _key, acc -> + acc + end) + + Keyword.merge(defaults, options) + end end diff --git a/priv/templates/install/beacon_router_scope.ex b/priv/templates/install/beacon_router_scope.ex index a59aeb72c..4ef32a054 100644 --- a/priv/templates/install/beacon_router_scope.ex +++ b/priv/templates/install/beacon_router_scope.ex @@ -3,5 +3,5 @@ scope "/" do pipe_through :browser - beacon_site "/<%= site %>", site: :<%= site %> + beacon_site "<%= path %>", site: :<%= site %> end diff --git a/test/beacon/types/site_test.exs b/test/beacon/types/site_test.exs index f30296ee3..070b2e0d7 100644 --- a/test/beacon/types/site_test.exs +++ b/test/beacon/types/site_test.exs @@ -34,4 +34,14 @@ defmodule Beacon.Types.SiteTest do refute Site.valid_name?("beacon_some_name") end end + + describe "valid_path?/1" do + test "SUCCESS: Return TRUE if it is a valid path" do + assert Site.valid_path?("/some_name") + end + + test "SUCCESS: Return FALSE if it is an invalid name" do + refute Site.valid_path?("forgot_slash_at_beginning") + end + end end diff --git a/test/mix/tasks/install_test.exs b/test/mix/tasks/install_test.exs index c253e5f67..23d39b6ea 100644 --- a/test/mix/tasks/install_test.exs +++ b/test/mix/tasks/install_test.exs @@ -448,10 +448,77 @@ defmodule Mix.Tasks.Beacon.InstallTest do Install.run(["--site", "beacon_"]) end + # Invalid path value + assert_raise Mix.Error, fn -> + Install.run(["--site", "blog", "--path", "forgot_slash_at_beginning"]) + end + # Invalid option assert_raise OptionParser.ParseError, ~r/1 error found!\n--invalid-argument : Unknown option/, fn -> Install.run(["--invalid-argument", "invalid"]) end end) end + + test "SUCCESS: New flag --path value updates beacon_path" do + Mix.Project.in_project(:my_app, ".", fn _module -> + Install.run(["--site", "my_site", "--path", "/some_other_path"]) + + # Injects beacon scope into router file + assert File.read!(@router_path) == + """ + defmodule MyAppWeb.Router do + use MyAppWeb, :router + + pipeline :browser do + plug :accepts, ["html"] + plug :fetch_session + plug :fetch_live_flash + plug :put_root_layout, html: {MyAppWeb.Layouts, :root} + plug :protect_from_forgery + plug :put_secure_browser_headers + end + + pipeline :api do + plug :accepts, ["json"] + end + + scope "/", MyAppWeb do + pipe_through :browser + + get "/", PageController, :home + end + + # Other scopes may use custom stacks. + # scope "/api", MyAppWeb do + # pipe_through :api + # end + + # Enable LiveDashboard and Swoosh mailbox preview in development + if Application.compile_env(:test_app, :dev_routes) do + # If you want to use the LiveDashboard in production, you should put + # it behind authentication and allow only admins to access it. + # If your application does not have an admins-only section yet, + # you can use Plug.BasicAuth to set up some basic authentication + # as long as you are also using SSL (which you should anyway). + import Phoenix.LiveDashboard.Router + + scope "/dev" do + pipe_through :browser + + live_dashboard "/dashboard", metrics: MyAppWeb.Telemetry + forward "/mailbox", Plug.Swoosh.MailboxPreview + end + end + + use Beacon.Router + + scope "/" do + pipe_through :browser + beacon_site "/some_other_path", site: :my_site + end + end + """ + end) + end end