How can I add a namespace/path to all routes generated by Routes.some_path?

I have an umbrella app. Most of the apps are regular OTP apps. I also have an Api app that is a phoenix app (has an endpoint/router/etc.) This is the main app for incoming requests. I am adding an Admin app to my umbrella which will also have an endpoint/router/etc, but I want it totally separated from the Api. Currently my Api forwards /admin calls to the admin endpoint like this:

scope “/admin” do
forward("/", AdminWeb.Endpoint)
end

This all works great, and it renders the page, except the Routes.static_path generates /images/something.png instead of /admin/images/something.png. Since all paths generated in the Admin app, will be routed through the Api app, is there a way to append “/admin” to all path helpers in the Admin app?

2 Likes

:wave:

If AdminWeb.Endpoint includes plug Plug.Static, you should have Routes.admin_static_path available.

scope can have an option as: namespace where namespace is an atom. This will namespace the path helper.

I think he’s asking how to have it return the full proper URL, not to make two calls and not to namespace the path call itself?

Correct. I want all route helpers to do exactly what they do already within the app, except with a “/admin” appended to the path.

2 Likes

Prepended. :slight_smile:

And yep, I also want that fixed.

Does changing the config for AdminWeb.Endpoint to include url: [path: "/admin"] work? The docs mention using it when hosting behind rewrite rules, which this use of forward kinda is.

1 Like

Except I think that would change all URL’s, not just the forwarded ones? Plus how would you distinguish between different forwarded ones?

Re: the URLs, I’m assuming the forward is the only place where AdminWeb.Endpoint is attached, given the fairly-specific module name.

You don’t want to attach an endpoint there or you’ll end up running all the endpoint plugs over again, you want to attach a router.

Try applying this patch.

Author: Ryan Kelker <... no spam ...>
Date:   Thu Apr 11 13:39:50 2019 -0400

    Add the `:prefix` option to the resources builder

diff --git a/lib/phoenix/router/resource.ex b/lib/phoenix/router/resource.ex
index 043d2ddf..5bf35476 100644
--- a/lib/phoenix/router/resource.ex
+++ b/lib/phoenix/router/resource.ex
@@ -28,7 +28,8 @@ defmodule Phoenix.Router.Resource do
   Builds a resource struct.
   """
   def build(path, controller, options) when is_atom(controller) and is_list(options) do
-    path    = Phoenix.Router.Scope.validate_path(path)
+    prefix = Keyword.get(options, :prefix, "")
+    path    = Phoenix.Router.Scope.validate_path(prefix <> path)
     alias   = Keyword.get(options, :alias)
     param   = Keyword.get(options, :param, @default_param_key)
     name    = Keyword.get(options, :name, Phoenix.Naming.resource_name(controller, "Controller"))


You can now do the following if you wish.

  scope "/", DevAppWeb do
    pipe_through :browser

    # This route can be found at /example/abc
    # Routes.page_path(@conn, :index) => /example/abc
    resources "/abc", PageController, only: [:index], prefix: "example"
  end

@runexec The issue is with forward, not resource though? And that passes through to ‘another’ router with its own helpers.

Let me look into it and get back with you. I just did quick glance and needed an excuse to read the source

Edit: I’m clearly tired because this code has nothing to do with the helpers. Let me think about this again. LOL!


Try writing a plug like this and possibly adding it to an existing pipeline.

defmodule DevAppWeb.ExamplePlug do

  import Phoenix.Controller, only: [redirect: 2]

  def init(default), do: default

  @prefix "/admin"
  def call(conn, _default) do
    with %{request_path: path} = conn do
      cond do
        String.starts_with?(path, @prefix) -> conn
        true -> redirect conn, to: @prefix <> path
      end
    end
  end

end
1 Like

I think the simplest solution at the moment would be to write a wrapper for the helper and make it accessible to the view via your_app_web.ex.

in your_app_web.ex, you might do something like this.

  def view do
    quote do
      use Phoenix.View,
        root: "lib/dev_app_web/templates",
        namespace: DevAppWeb

        # ... snip ... #

        alias YourAppWeb.MyHelpers, as: AdminHelper
    end
  end

The wrapper might look something like this.

def static_path(conn, path), do: Routes.static_path(conn, "/admin#{path}")
1 Like

That is ‘essentially’ what I’m doing, the problem is remembering to do that every time I add a new forward. ^.^

I have my application broken up into dependencies, each of which just gets forwarded to among other automated setups based on what is detected automatically at compile time, but names are still made and changed manually on occasion. :slight_smile:

I just replace @conn with Endpoint.