Packages

Outerfaces ODD (Outerfaces Dependency Distribution) -- Extensions for Outerfaces framework

Current section

Files

Jump to
outerfaces_odd lib endpoints dynamic_loader.ex
Raw

lib/endpoints/dynamic_loader.ex

defmodule Outerfaces.Odd.Endpoints.DynamicLoader do
@moduledoc """
Custom endpoint loader for Outerfaces projects.
"""
@ports_per_project 1
@behaviour Outerfaces.Endpoints.DynamicLoader.DynamicLoaderBehavior
alias Outerfaces.Odd.Endpoints.ConfigurationBuilder
alias Outerfaces.Odd.Plugs.OddCDNConsumerContentSecurityPlug
alias Outerfaces.Odd.Plugs.OddCDNConsumerServeIndex
alias Outerfaces.Odd.Plugs.OddCDNProviderContentSecurityPlug
alias Outerfaces.Odd.Plugs.OddEnvironmentPlug
alias Outerfaces.Odd.Plugs.OddRevProxyPlug
alias Outerfaces.Odd.Plugs.OddRevCacheHeadersPlug
alias Outerfaces.Odd.Plugs.OddRevEndpointPlug
alias Outerfaces.Plugs.ServeIndex.DefaultServeIndex
require Logger
@impl true
def endpoint_config_for_project(project_name, port, app_web_module) do
Logger.debug("#{__MODULE__} Creating endpoint config for #{project_name}")
# node_scope = Application.get_env(:my_app, :my_config][:localhost_or_public]
node_scope = "localhost"
ip = ConfigurationBuilder.build_ip_for_node_scope(node_scope)
host = ConfigurationBuilder.build_host_for_node_scope(node_scope)
secret_key_base = System.get_env("SECRET_KEY_BASE")
# url_scheme = Application.get_env(:my_app, :my_config)[:http_or_https]
url_scheme = "http"
if url_scheme == "https" do
ConfigurationBuilder.dynamic_loader_https_endpoint_url_configuration!(
app_web_module,
host,
port,
ip,
secret_key_base,
System.get_env("NODE_TLS_KEY_PATH"),
System.get_env("NODE_TLS_CERT_PATH")
)
else
ConfigurationBuilder.dynamic_loader_http_endpoint_url_configuration!(
app_web_module,
host,
port,
ip,
secret_key_base
)
end
end
@impl true
def prepare_endpoint_module(
"odd_cdn" = outerfaces_project_name,
app_slug,
endpoint_module,
opts
) do
unless Code.ensure_loaded?(endpoint_module) do
# TODO: Move this path building out
project_path =
[
:code.priv_dir(app_slug),
"static",
"outerfaces",
"projects",
outerfaces_project_name
]
|> Path.join()
module_body =
quote do
# node_host_name = Application.compile_env(:my_app, :my_config)[:host_name]
node_host_name = "localhost"
# url_scheme = Application.compile_env(:my_app, :my_config)[:http_or_https]
url_scheme = "http"
# maybe_node_host_name_secondary =
# Application.compile_env(:my_app, :my_config)[:host_name_secondary]
maybe_node_host_name_secondary = nil
origin_applications = [
%{
protocol: url_scheme,
host: node_host_name,
port: Keyword.get(unquote(opts), :ui_port)
}
]
origin_applications =
(maybe_node_host_name_secondary &&
Enum.concat(origin_applications, [
%{
protocol: url_scheme,
host: maybe_node_host_name_secondary,
port: Keyword.get(unquote(opts), :ui_port)
}
])) || origin_applications
use Phoenix.Endpoint, otp_app: unquote(app_slug)
plug(Plug.Logger, log: :debug)
plug(OddCDNProviderContentSecurityPlug,
origin_applications: origin_applications,
source_host_options: origin_applications
)
plug(DefaultServeIndex,
index_path: "#{unquote(project_path)}/index.html",
static_root: unquote(project_path)
)
end
Module.create(endpoint_module, module_body, Macro.Env.location(__ENV__))
end
end
def prepare_endpoint_module(outerfaces_project_name, app_slug, endpoint_module, opts) do
unless Code.ensure_loaded?(endpoint_module) do
# TODO: Move this path building out
project_path =
[
:code.priv_dir(app_slug),
"static",
"outerfaces",
"projects",
outerfaces_project_name
]
|> Path.join()
module_body =
quote do
# node_host_name = Application.compile_env(:my_app, :my_config)[:host_name]
node_host_name = "localhost"
# node_scope = Application.compile_env(:my_app, :my_config)[:localhost_or_public]
node_scope = "localhost"
# url_scheme = Application.compile_env(:my_app, :my_config)[:http_or_https]
url_scheme = "http"
# maybe_host_name_secondary =
# Application.compile_env(:my_app, :my_config)[:secondary_host_name]
maybe_node_host_name_secondary = nil
api_port = Keyword.get(unquote(opts), :api_port)
cdn_port = Keyword.get(unquote(opts), :cdn_port)
ui_port = Keyword.get(unquote(opts), :ui_port)
host_names =
[
"localhost",
node_host_name,
maybe_node_host_name_secondary
]
|> Enum.reject(&is_nil/1)
|> Enum.uniq()
allowed_sources =
host_names
|> Enum.flat_map(fn host ->
[
%{
protocol: url_scheme,
host: host,
port: cdn_port
},
%{
protocol: url_scheme,
host: host,
port: api_port
}
]
end)
use Phoenix.Endpoint, otp_app: unquote(app_slug)
plug(Plug.Logger, log: :debug)
# NEW: Rev plugs (MUST be first for rev URL parsing)
plug(OddRevProxyPlug, mismatch_behavior: :redirect)
plug(OddRevCacheHeadersPlug)
# Rev endpoint for service worker rev checks
plug(OddRevEndpointPlug)
plug(OddCDNConsumerContentSecurityPlug,
source_host_options: allowed_sources
)
plug(OddEnvironmentPlug,
protocol: url_scheme,
host_names: host_names,
cdn_port: cdn_port,
ui_port: ui_port,
api_port: api_port
)
plug(OddCDNConsumerServeIndex,
index_path: "#{unquote(project_path)}/index.html",
static_root: unquote(project_path)
)
end
Module.create(endpoint_module, module_body, Macro.Env.location(__ENV__))
end
end
@impl true
def prepare_endpoint_modules(project_directories, app_slug, app_web_module, opts) do
Logger.debug("#{__MODULE__}: Generating endpoint modules")
Enum.each(project_directories, fn project ->
prepare_endpoint_module(
project,
app_slug,
endpoint_module_name(app_web_module, project),
opts
)
end)
end
@impl true
def hydrate_endpoint_modules(
project_directories,
app_module,
app_web_module,
base_port
) do
Logger.debug("#{__MODULE__}: Initializing generated endpoint modules")
project_directories
|> Enum.with_index()
|> Enum.map(&create_dynamic_endpoint_spec(&1, app_module, app_web_module, base_port))
end
@impl true
@spec create_dynamic_endpoint_spec(
{String.t(), non_neg_integer()},
atom(),
atom(),
pos_integer()
) :: {atom(), Keyword.t()}
def create_dynamic_endpoint_spec(
{project_name, index},
app_module,
app_web_module,
base_port
) do
Logger.debug("#{__MODULE__}: Creating dynamic endpoint spec")
port = dynamic_port(base_port, index)
endpoint_module = endpoint_module_name(app_web_module, project_name)
config = endpoint_config_for_project(project_name, port, app_web_module)
Application.put_env(app_module, endpoint_module, config)
{endpoint_module, config}
end
@impl true
def dynamic_port(base_port, 0) when is_integer(base_port) do
base_port
end
def dynamic_port(base_port, index) when is_integer(base_port) and is_integer(index) do
base_port + index * @ports_per_project
end
defp endpoint_module_name(app_web_module, project_name) do
camel_case_name = snake_to_camel(project_name)
Module.concat([app_web_module, "#{camel_case_name}Endpoint"])
end
defp snake_to_camel(snake_case) do
snake_case
|> String.split("_")
|> Enum.map(&String.capitalize/1)
|> Enum.join()
end
end