Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow to host Azimutt on nested folder #259

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

export PHX_SERVER=true
export PHX_HOST=localhost
# export PHX_PATH=
export PORT=4000
export SECRET_KEY_BASE=CHANGE_ME

Expand Down
1 change: 1 addition & 0 deletions INSTALL.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ These are the basic variables you will **need** to set up Azimutt:

- `PHX_SERVER` (optional): if `true`, start the Phoenix server in server mode
- `PHX_HOST` (required): host of the deployed website (ex: `localhost` or `azimutt.app`), it's used to build absolute urls
- `PHX_PATH` (optional, default: `""`): if you want to host Azimutt on a sub-folder (ex: `/azimutt` for `https://example.com/azimutt` as home page), should not end with `/`
- `PORT` (required): the port the server will listen to (ex: `4000`)
- `SECRET_KEY_BASE` (required): the secret used for server encryption (cookies and others), should be at least 64 bytes and you probably want a random value for it
- `DATABASE_URL` (required): the whole url to connect to your PostgreSQL database (ex: `postgresql://<user>:<pass>@<host>:<port>/<database>`)
Expand Down
5 changes: 4 additions & 1 deletion backend/config/config.exs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
# General application configuration
import Config

# need to read it here as backend/config/runtime.exs is executed after :/
path = System.get_env("PHX_PATH") || ""

config :azimutt,
business_name: "Azimutt",
seo_title: "Azimutt · Database explorer and analyzer",
Expand Down Expand Up @@ -52,7 +55,7 @@ config :azimutt, Azimutt.Repo,

# Configures the endpoint
config :azimutt, AzimuttWeb.Endpoint,
url: [host: "localhost"],
url: [host: "localhost", path: path],
render_errors: [view: AzimuttWeb.ErrorView, accepts: ~w(html json), layout: false],
pubsub_server: Azimutt.PubSub,
live_view: [signing_salt: "eIPt31PL"]
Expand Down
6 changes: 5 additions & 1 deletion backend/config/dev.exs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import Config

# need to read it here as backend/config/runtime.exs is executed after :/
path = System.get_env("PHX_PATH") || ""

config :cors_plug,
origin: ["http://localhost:4001"],
max_age: 86400
Expand Down Expand Up @@ -56,7 +59,8 @@ config :azimutt, AzimuttWeb.Endpoint,
~r"priv/gettext/.*(po)$",
~r"lib/azimutt_web/(live|views)/.*(ex)$",
~r"lib/azimutt_web/*/*/.*(eex)$"
]
],
url: "#{path}/phoenix/live_reload/frame"
]

# Do not include metadata nor timestamps in development logs
Expand Down
4 changes: 3 additions & 1 deletion backend/config/runtime.exs
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,15 @@ import Config
# Alternatively, you can use `mix phx.gen.release` to generate a `bin/server`
# script that automatically sets the env var above.
host = System.fetch_env!("PHX_HOST")
path = System.get_env("PHX_PATH") || ""
port = String.to_integer(System.fetch_env!("PORT"))
global_organization = System.get_env("GLOBAL_ORGANIZATION")
# TODO: REQUIRE_GITHUB_ORGANIZATION: allow users only from this github orga

config :azimutt,
host: host,
gateway_url: System.get_env("GATEWAY_URL") || "/api/v1/analyzer",
path: path,
gateway_url: System.get_env("GATEWAY_URL") || "#{path}/api/v1/analyzer",
skip_public_site: !(System.get_env("PUBLIC_SITE") == "true"),
skip_onboarding_funnel: System.get_env("SKIP_ONBOARDING_FUNNEL") == "true",
skip_email_confirmation: System.get_env("SKIP_EMAIL_CONFIRMATION") == "true",
Expand Down
9 changes: 5 additions & 4 deletions backend/lib/azimutt_web/components/brand.ex
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
defmodule AzimuttWeb.Components.Brand do
@moduledoc "Brand component"
use Phoenix.Component
alias AzimuttWeb.Router.Helpers, as: Routes

@doc "Displays full logo. "
def logo(assigns) do
Expand All @@ -11,9 +12,9 @@ defmodule AzimuttWeb.Components.Brand do

~H"""
<%= if @variant do %>
<img class={@class} src={"/images/logo_#{@variant}.svg"} alt="Azimutt Logo" />
<img class={@class} src={Routes.static_path(@conn, "/images/logo_#{@variant}.svg")} alt="Azimutt Logo" />
<% else %>
<img class={@class} src={if assigns[:dark], do: "/images/logo_light.svg", else: "/images/logo_dark.svg"} alt="Azimutt Logo" />
<img class={@class} src={Routes.static_path(@conn, if(assigns[:dark], do: "/images/logo_light.svg", else: "/images/logo_dark.svg"))} alt="Azimutt Logo" />
<% end %>
"""
end
Expand All @@ -27,9 +28,9 @@ defmodule AzimuttWeb.Components.Brand do

~H"""
<%= if @variant do %>
<img class={@class} src={"/images/logo_icon_#{@variant}.svg"} alt="Azimutt Icon"/>
<img class={@class} src={Routes.static_path(@conn, "/images/logo_icon_#{@variant}.svg")} alt="Azimutt Icon"/>
<% else %>
<img class={@class <> " block"} src="/images/logo_icon_dark.svg" alt="Azimutt Icon"/>
<img class={@class <> " block"} src={Routes.static_path(@conn, "/images/logo_icon_dark.svg")} alt="Azimutt Icon"/>
<% end %>
"""
end
Expand Down
8 changes: 4 additions & 4 deletions backend/lib/azimutt_web/components/header.ex
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ defmodule AzimuttWeb.Components.Header do
"""
use Phoenix.Component
import AzimuttWeb.Components.Brand
alias AzimuttWeb.Router.Helpers, as: Routes

# FIXME: use `Routes.website_path(@conn, :index)` instead of "/", don't know how to pass `@conn` :(
@doc "Displays full logo. "
def header(assigns) do
if Azimutt.config(:skip_public_site) do
Expand All @@ -15,7 +15,7 @@ defmodule AzimuttWeb.Components.Header do
<nav class="relative z-50 flex justify-between">
<div class="flex items-center md:gap-x-12">
<a aria-label="Home" href="https://azimutt.app" target="_blank">
<.logo dark={assigns[:dark]} class="h-12 transition-transform duration-300 ease-out transform hover:scale-105" />
<.logo conn={@conn} dark={assigns[:dark]} class="h-12 transition-transform duration-300 ease-out transform hover:scale-105" />
</a>
</div>
<div class="flex items-center gap-x-5 md:gap-x-8">
Expand All @@ -31,8 +31,8 @@ defmodule AzimuttWeb.Components.Header do
<div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
<nav class="relative z-50 flex justify-between">
<div class="flex items-center md:gap-x-12">
<a aria-label="Home" href="/">
<.logo dark={assigns[:dark]} class="h-12 transition-transform duration-300 ease-out transform hover:scale-105" />
<a aria-label="Home" href={Routes.website_path(@conn, :index)}>
<.logo conn={@conn} dark={assigns[:dark]} class="h-12 transition-transform duration-300 ease-out transform hover:scale-105" />
</a>
<div class="hidden md:flex md:gap-x-6">
<%= render_slot(@menu) %>
Expand Down
8 changes: 6 additions & 2 deletions backend/lib/azimutt_web/controllers/user_auth.ex
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ defmodule AzimuttWeb.UserAuth do

!user.confirmed_at && Azimutt.config(:require_email_confirmation) && !Azimutt.config(:skip_email_confirmation) &&
!is_email_confirm_path(conn, path) && Date.compare(user.created_at, ~D[2023-04-19]) == :gt ->
# TODO: remove the date check when we are sure all users have confirmed their email
conn |> redirect(to: Routes.user_confirmation_path(conn, :new)) |> halt()

user.onboarding && !Azimutt.config(:skip_onboarding_funnel) &&
Expand All @@ -192,8 +193,11 @@ defmodule AzimuttWeb.UserAuth do
end
end

defp is_email_confirm_path(conn, path), do: path |> String.starts_with?(Routes.user_confirmation_path(conn, :new))
defp is_onboarding_path(conn, path), do: path |> String.starts_with?(Routes.user_onboarding_path(conn, :index))
defp is_email_confirm_path(conn, path),
do: path |> String.starts_with?(Azimutt.config(:path) <> Routes.user_confirmation_path(conn, :new))

defp is_onboarding_path(conn, path),
do: path |> String.starts_with?(Azimutt.config(:path) <> Routes.user_onboarding_path(conn, :index))

def require_authed_user_api(conn, _opts) do
if conn.assigns[:current_user] do
Expand Down
17 changes: 11 additions & 6 deletions backend/lib/azimutt_web/endpoint.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2,35 +2,40 @@ defmodule AzimuttWeb.Endpoint do
use Sentry.PlugCapture
use Phoenix.Endpoint, otp_app: :azimutt

# need to read them here as backend/config/runtime.exs is executed after :/
host = System.fetch_env!("PHX_HOST")
path = System.get_env("PHX_PATH") || ""

# The session will be stored in the cookie and signed,
# this means its contents can be read but not tampered with.
# Set :encryption_salt if you would also like to encrypt it.
@session_options [
store: :cookie,
key: "_azimutt_key",
signing_salt: "9EWvUx5K",
domain: Azimutt.config(:host)
domain: host
]

socket "/live", Phoenix.LiveView.Socket, websocket: [connect_info: [session: @session_options]]
socket "#{path}/live", Phoenix.LiveView.Socket, websocket: [connect_info: [session: @session_options]]

# Serve at "/" the static files from "priv/static" directory.
#
# You should set gzip to true if you are running phx.digest
# when deploying your static files in production.
plug Plug.Static,
at: "/",
at: if(path == "", do: "/", else: path),
from: :azimutt,
gzip: false,
only:
~w(assets blog gallery elm fonts images android-chrome-192x192.png android-chrome-512x512.png apple-touch-icon.png browserconfig.xml favicon.ico favicon-16x16.png favicon-32x32.png mstile-150x150.png robots.txt safari-pinned-tab.svg screenshot.png screenshot-complex.png service-worker.js site.webmanifest)
|> Enum.map(fn p -> if(path == "", do: p, else: "#{path}/#{p}") end)

plug Plug.Static, at: "/uploads", from: Path.expand('./uploads'), gzip: false
plug Plug.Static, at: "#{path}/uploads", from: Path.expand('./uploads'), gzip: false

# Code reloading can be explicitly enabled under the
# :code_reloader configuration of your endpoint.
if code_reloading? do
socket "/phoenix/live_reload/socket", Phoenix.LiveReloader.Socket
socket "#{path}/phoenix/live_reload/socket", Phoenix.LiveReloader.Socket
plug Phoenix.LiveReloader
plug Phoenix.CodeReloader
plug Phoenix.Ecto.CheckRepoStatus, otp_app: :azimutt
Expand All @@ -46,7 +51,7 @@ defmodule AzimuttWeb.Endpoint do
# Since Plug.Parsers removes the raw request_body in body_parsers
# we need to parse out the Stripe webhooks before this
plug Stripe.WebhookPlug,
at: "/webhook/stripe",
at: "#{path}/webhook/stripe",
handler: Azimutt.StripeHandler,
secret: {Application, :get_env, [:stripity_stripe, :signing_secret]}

Expand Down
43 changes: 24 additions & 19 deletions backend/lib/azimutt_web/router.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@ defmodule AzimuttWeb.Router do
import AzimuttWeb.UserAuth
alias AzimuttWeb.Plugs.AllowCrossOriginIframe

# need to read it here as backend/config/runtime.exs is executed after :/
# path = System.get_env("PHX_PATH") || ""
path = ""
# FIXME: `path` should probably not be added to the `scope`s, it generate the path twice when backend/config/config.exs:58 path is set :/

pipeline :browser_no_csrf_protection do
plug(Ueberauth)
plug(:accepts, ["html"])
Expand Down Expand Up @@ -55,7 +60,7 @@ defmodule AzimuttWeb.Router do
pipeline(:empty_layout, do: plug(:put_layout, {AzimuttWeb.LayoutView, "empty.html"}))

# public routes
scope "/", AzimuttWeb do
scope "#{path}/", AzimuttWeb do
pipe_through([:browser, :website_root_layout])
get("/", WebsiteController, :index)
get("/last", WebsiteController, :last)
Expand All @@ -77,7 +82,7 @@ defmodule AzimuttWeb.Router do
end

# auth routes
scope "/", AzimuttWeb do
scope "#{path}/", AzimuttWeb do
pipe_through([:browser, :redirect_if_user_is_authed, :hfull_root_layout])
get("/auth/:provider", UserOauthController, :request)
get("/auth/:provider/callback", UserOauthController, :callback)
Expand All @@ -92,7 +97,7 @@ defmodule AzimuttWeb.Router do
end

# authed dashboard routes
scope "/", AzimuttWeb do
scope "#{path}/", AzimuttWeb do
pipe_through([:browser, :require_authed_user, :organization_root_layout])
get("/home", UserDashboardController, :index)
get("/login/redirect", UserSessionController, :redirect_to)
Expand Down Expand Up @@ -162,43 +167,43 @@ defmodule AzimuttWeb.Router do
patch("/invitations/:invitation_id/refuse", OrganizationInvitationController, :refuse, as: :invitation)
end

scope "/heroku", AzimuttWeb do
scope "#{path}/heroku", AzimuttWeb do
pipe_through([:api, :require_heroku_basic_auth])
post("/resources", Api.HerokuController, :create)
put("/resources/:resource_id", Api.HerokuController, :update)
delete("/resources/:resource_id", Api.HerokuController, :delete)
end

scope "/heroku", AzimuttWeb do
scope "#{path}/heroku", AzimuttWeb do
pipe_through([:browser_no_csrf_protection])
if Azimutt.Application.env() == :dev, do: get("/", HerokuController, :index)
post("/login", HerokuController, :login)
end

scope "/heroku", AzimuttWeb do
scope "#{path}/heroku", AzimuttWeb do
pipe_through([:browser, :require_heroku_resource, :require_authed_user])
get("/resources/:resource_id", HerokuController, :show)
end

scope "/clevercloud", AzimuttWeb do
scope "#{path}/clevercloud", AzimuttWeb do
pipe_through([:api, :require_clever_cloud_basic_auth])
post("/resources", Api.CleverCloudController, :create)
put("/resources/:resource_id", Api.CleverCloudController, :update)
delete("/resources/:resource_id", Api.CleverCloudController, :delete)
end

scope "/clevercloud", AzimuttWeb do
scope "#{path}/clevercloud", AzimuttWeb do
pipe_through([:browser_no_csrf_protection])
if Azimutt.Application.env() == :dev, do: get("/", CleverCloudController, :index)
post("/login", CleverCloudController, :login)
end

scope "/clevercloud", AzimuttWeb do
scope "#{path}/clevercloud", AzimuttWeb do
pipe_through([:browser, :require_clever_cloud_resource, :require_authed_user, AllowCrossOriginIframe])
get("/resources/:resource_id", CleverCloudController, :show)
end

scope "/admin", AzimuttWeb, as: :admin do
scope "#{path}/admin", AzimuttWeb, as: :admin do
pipe_through([:browser, :require_authed_user, :require_admin_user, :admin_root_layout])
get("/", Admin.DashboardController, :index)
resources("/users", Admin.UserController, param: "user_id", only: [:index, :show])
Expand All @@ -207,12 +212,12 @@ defmodule AzimuttWeb.Router do
resources("/events", Admin.EventController, param: "event_id", only: [:index, :show])
end

scope "/api/v1/swagger" do
scope "#{path}/api/v1/swagger" do
forward("/", PhoenixSwagger.Plug.SwaggerUI, otp_app: :azimutt, swagger_file: "swagger.json")
end

# public APIs
scope "/api/v1", AzimuttWeb do
scope "#{path}/api/v1", AzimuttWeb do
pipe_through([:api])
# GET is practical for development and POST allows to not have params in possible http logs
get("/analyzer/schema", Api.AnalyzerController, :schema)
Expand All @@ -231,7 +236,7 @@ defmodule AzimuttWeb.Router do
end

# authed APIs
scope "/api/v1", AzimuttWeb do
scope "#{path}/api/v1", AzimuttWeb do
pipe_through([:api, :require_authed_user_api])
get("/users/current", Api.UserController, :current)

Expand All @@ -254,7 +259,7 @@ defmodule AzimuttWeb.Router do
if Azimutt.Application.env() in [:dev, :test, :staging] do
import Phoenix.LiveDashboard.Router

scope "/" do
scope "#{path}/" do
pipe_through(:browser)
live_dashboard("/dashboard", metrics: AzimuttWeb.Telemetry)
end
Expand All @@ -265,7 +270,7 @@ defmodule AzimuttWeb.Router do
# Note that preview only shows emails that were sent by the same
# node running the Phoenix server.
if Azimutt.Application.env() == :dev do
scope "/dev" do
scope "#{path}/dev" do
pipe_through(:browser)

forward("/mailbox", Plug.Swoosh.MailboxPreview)
Expand All @@ -288,20 +293,20 @@ defmodule AzimuttWeb.Router do
}
end

scope "/", AzimuttWeb do
scope "#{path}/", AzimuttWeb do
pipe_through([:browser, :elm_root_layout, AllowCrossOriginIframe])
get("/embed", ElmController, :embed)
end

scope "/", AzimuttWeb do
scope "#{path}/", AzimuttWeb do
pipe_through([:api])
get("/ping", Api.HealthController, :ping)
get("/health", Api.HealthController, :health)
end

# elm routes, must be at the end (because of `/:organization_id/:project_id` "catch all")
# routes listed in the same order than in `elm/src/Pages`
scope "/", AzimuttWeb do
scope "#{path}/", AzimuttWeb do
pipe_through([:browser, :enforce_user_requirements, :elm_root_layout])
get("/create", ElmController, :create)
get("/new", ElmController, :new)
Expand All @@ -310,7 +315,7 @@ defmodule AzimuttWeb.Router do
end

# allow cross origin iframe for Clever Cloud
scope "/", AzimuttWeb do
scope "#{path}/", AzimuttWeb do
pipe_through([:browser, :enforce_user_requirements, :elm_root_layout, AllowCrossOriginIframe])
get("/:organization_id/create", ElmController, :orga_create)
get("/:organization_id/:project_id", ElmController, :project_show)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<div class="pt-12">
<div class="flex flex-col space-y-4 md:space-y-0 md:space-x-6 md:flex-row">
<.logo_icon class="self-center flex-shrink-0 w-24 h-24 rounded-full md:justify-self-start" />
<.logo_icon conn={@conn} class="self-center flex-shrink-0 w-24 h-24 rounded-full md:justify-self-start" />
<div class="flex flex-col">
<h4 class="text-lg font-semibold"><%= Azimutt.config(:seo_title) %></h4>
<p><%= render "_blog_description.html" %></p>
Expand Down
2 changes: 1 addition & 1 deletion backend/lib/azimutt_web/templates/blog/cards.html.heex
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,5 @@
</div>

<div class="relative max-w-3xl mt-6 px-6 pb-24 mx-auto space-y-12">
<%= render "_blog_footer.html" %>
<%= render "_blog_footer.html", conn: @conn %>
</div>
2 changes: 1 addition & 1 deletion backend/lib/azimutt_web/templates/blog/index.html.heex
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,5 @@
</article>
<hr class="w-full bg-gray-100" style="height: 1px;">
<% end %>
<%= render "_blog_footer.html" %>
<%= render "_blog_footer.html", conn: @conn %>
</main>
Loading
Loading