aboutsummaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/mix/tasks/pleroma/frontend.ex218
-rw-r--r--lib/mix/tasks/pleroma/instance.ex93
-rw-r--r--lib/mix/tasks/pleroma/user.ex2
-rw-r--r--lib/pleroma/application.ex2
-rw-r--r--lib/pleroma/emails/user_email.ex2
-rw-r--r--lib/pleroma/frontend.ex32
-rw-r--r--lib/pleroma/plugs/frontend_plug.ex51
-rw-r--r--lib/pleroma/plugs/instance_static.ex72
-rw-r--r--lib/pleroma/plugs/static_fe_plug.ex18
-rw-r--r--lib/pleroma/utils.ex15
-rw-r--r--lib/pleroma/web/controllers/frontend/admin_controller.ex8
-rw-r--r--lib/pleroma/web/controllers/frontend/default_controller.ex40
-rw-r--r--lib/pleroma/web/controllers/frontend/headless_controller.ex13
-rw-r--r--lib/pleroma/web/controllers/frontend/kenoma_controller.ex8
-rw-r--r--lib/pleroma/web/controllers/frontend/mastodon_controller.ex56
-rw-r--r--lib/pleroma/web/controllers/frontend/pleroma_controller.ex75
-rw-r--r--lib/pleroma/web/controllers/frontend/static_controller.ex (renamed from lib/pleroma/web/static_fe/static_fe_controller.ex)146
-rw-r--r--lib/pleroma/web/endpoint.ex10
-rw-r--r--lib/pleroma/web/fallback_redirect_controller.ex108
-rw-r--r--lib/pleroma/web/feed/user_controller.ex7
-rw-r--r--lib/pleroma/web/frontend_controller.ex76
-rw-r--r--lib/pleroma/web/mastodon_api/controllers/auth_controller.ex6
-rw-r--r--lib/pleroma/web/ostatus/ostatus_controller.ex40
-rw-r--r--lib/pleroma/web/router.ex25
-rw-r--r--lib/pleroma/web/templates/frontend/mastodon/index.html.eex (renamed from lib/pleroma/web/templates/masto_fe/index.html.eex)4
-rw-r--r--lib/pleroma/web/templates/frontend/static/_attachment.html.eex (renamed from lib/pleroma/web/templates/static_fe/static_fe/_attachment.html.eex)0
-rw-r--r--lib/pleroma/web/templates/frontend/static/_notice.html.eex (renamed from lib/pleroma/web/templates/static_fe/static_fe/_notice.html.eex)8
-rw-r--r--lib/pleroma/web/templates/frontend/static/_user_card.html.eex (renamed from lib/pleroma/web/templates/static_fe/static_fe/_user_card.html.eex)4
-rw-r--r--lib/pleroma/web/templates/frontend/static/conversation.html.eex (renamed from lib/pleroma/web/templates/static_fe/static_fe/conversation.html.eex)0
-rw-r--r--lib/pleroma/web/templates/frontend/static/error.html.eex (renamed from lib/pleroma/web/templates/static_fe/static_fe/error.html.eex)0
-rw-r--r--lib/pleroma/web/templates/frontend/static/profile.html.eex (renamed from lib/pleroma/web/templates/static_fe/static_fe/profile.html.eex)4
-rw-r--r--lib/pleroma/web/templates/layout/static_fe.html.eex2
-rw-r--r--lib/pleroma/web/views/frontend/mastodon_view.ex121
-rw-r--r--lib/pleroma/web/views/frontend/static_view.ex (renamed from lib/pleroma/web/static_fe/static_fe_view.ex)7
-rw-r--r--lib/pleroma/web/views/masto_fe_view.ex2
-rw-r--r--lib/pleroma/web/web.ex4
36 files changed, 990 insertions, 289 deletions
diff --git a/lib/mix/tasks/pleroma/frontend.ex b/lib/mix/tasks/pleroma/frontend.ex
new file mode 100644
index 000000000..03169c8b8
--- /dev/null
+++ b/lib/mix/tasks/pleroma/frontend.ex
@@ -0,0 +1,218 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Mix.Tasks.Pleroma.Frontend do
+ use Mix.Task
+
+ import Mix.Pleroma
+
+ # alias Pleroma.Config
+
+ @shortdoc "Manages bundled Pleroma frontends"
+ @moduledoc File.read!("docs/administration/CLI_tasks/frontend.md")
+
+ @pleroma_gitlab_host "git.pleroma.social"
+ @frontends %{
+ # TODO stable
+ "admin" => %{"project" => "pleroma/admin-fe"},
+ # TODO
+ "kenoma" => %{"project" => "lambadalambda/kenoma"},
+ # TODO
+ "mastodon" => %{"project" => "pleroma/mastofe"},
+ # OK
+ "pleroma" => %{"project" => "pleroma/pleroma-fe"}
+ }
+ @known_frontends Map.keys(@frontends)
+
+ def run(["install", "none" | _args]) do
+ shell_info("Skipping frontend installation because none was requested")
+ end
+
+ def run(["install", unknown_fe | _args]) when unknown_fe not in @known_frontends do
+ shell_error(
+ "Frontend \"#{unknown_fe}\" is not known. Known frontends are: #{
+ Enum.join(@known_frontends, ", ")
+ }"
+ )
+ end
+
+ def run(["install", frontend | args]) do
+ log_level = Logger.level()
+ Logger.configure(level: :warn)
+ {:ok, _} = Application.ensure_all_started(:pleroma)
+
+ {options, [], []} =
+ OptionParser.parse(
+ args,
+ strict: [
+ ref: :string
+ ]
+ )
+
+ ref = suggest_ref(options, frontend)
+
+ %{"name" => bundle_name, "url" => bundle_url} =
+ get_bundle_meta(ref, @pleroma_gitlab_host, @frontends[frontend]["project"])
+
+ shell_info("Installing frontend #{frontend}, version: #{bundle_name}")
+
+ dest = Path.join([Pleroma.Config.get!([:instance, :static_dir]), "frontends", frontend, ref])
+
+ with :ok <- install_bundle(bundle_url, dest),
+ :ok <- post_install_bundle(frontend, dest) do
+ shell_info("Installed!")
+ else
+ {:error, error} ->
+ shell_error("Error: #{inspect(error)}")
+ end
+
+ Logger.configure(level: log_level)
+ end
+
+ defp post_install_bundle("mastodon", path) do
+ with :ok <- File.rename("#{path}/public/assets/sw.js", "#{path}/sw.js"),
+ :ok <- File.rename("#{path}/public/packs", "#{path}/packs"),
+ {:ok, _deleted_files} <- File.rm_rf("#{path}/public") do
+ :ok
+ else
+ error ->
+ {:error, error}
+ end
+ end
+
+ defp post_install_bundle(_fe_name, _path), do: :ok
+
+ defp suggest_ref(options, frontend) do
+ case Pleroma.Config.get([:frontends, String.to_atom(frontend)]) do
+ nil ->
+ primary_fe_config = Pleroma.Config.get([:frontends, :primary])
+
+ if primary_fe_config["name"] == frontend do
+ primary_fe_config["ref"]
+ else
+ nil
+ end
+
+ val ->
+ val
+ end
+ |> case do
+ nil ->
+ stable_pleroma? = Pleroma.Application.stable?()
+
+ current_stable_out =
+ if stable_pleroma? do
+ "stable"
+ else
+ "develop"
+ end
+
+ get_option(
+ options,
+ :ref,
+ "You are currently running #{current_stable_out} version of Pleroma backend. What version of \"#{
+ frontend
+ }\" frontend you want to install? (\"stable\", \"develop\" or specific ref)",
+ current_stable_out
+ )
+
+ config_value ->
+ current_ref =
+ case config_value do
+ %{"ref" => ref} -> ref
+ ref -> ref
+ end
+
+ get_option(
+ options,
+ :ref,
+ "You are currently running \"#{current_ref}\" version of \"#{frontend}\" frontend. What version do you want to install? (\"stable\", \"develop\" or specific ref)",
+ current_ref
+ )
+ end
+ end
+
+ defp get_bundle_meta("develop", gitlab_base_url, project) do
+ url = "#{gitlab_api_url(gitlab_base_url, project)}/repository/branches"
+
+ %{status: 200, body: json} = Tesla.get!(http_client(), url)
+
+ %{"name" => name, "commit" => %{"short_id" => last_commit_ref}} =
+ Enum.find(json, & &1["default"])
+
+ %{
+ "name" => name,
+ "url" => build_url(gitlab_base_url, project, last_commit_ref)
+ }
+ end
+
+ defp get_bundle_meta("stable", gitlab_base_url, project) do
+ url = "#{gitlab_api_url(gitlab_base_url, project)}/releases"
+ %{status: 200, body: json} = Tesla.get!(http_client(), url)
+
+ [%{"commit" => %{"short_id" => commit_id}, "name" => name} | _] =
+ Enum.sort(json, fn r1, r2 ->
+ {:ok, date1, _offset} = DateTime.from_iso8601(r1["created_at"])
+ {:ok, date2, _offset} = DateTime.from_iso8601(r2["created_at"])
+ DateTime.compare(date1, date2) != :lt
+ end)
+
+ %{
+ "name" => name,
+ "url" => build_url(gitlab_base_url, project, commit_id)
+ }
+ end
+
+ defp get_bundle_meta(ref, gitlab_base_url, project) do
+ %{
+ "name" => ref,
+ "url" => build_url(gitlab_base_url, project, ref)
+ }
+ end
+
+ defp install_bundle(bundle_url, dir) do
+ http_client = http_client()
+
+ with {:ok, %{status: 200, body: zip_body}} <- Tesla.get(http_client, bundle_url),
+ {:ok, unzipped} <- :zip.unzip(zip_body, [:memory]) do
+ File.rm_rf!(dir)
+
+ Enum.each(unzipped, fn {path, data} ->
+ path =
+ path
+ |> to_string()
+ |> String.replace(~r/^dist\//, "")
+
+ file_path = Path.join(dir, path)
+
+ file_path
+ |> Path.dirname()
+ |> File.mkdir_p!()
+
+ File.write!(file_path, data)
+ end)
+ else
+ {:ok, %{status: 404}} ->
+ {:error, "Bundle not found"}
+
+ error ->
+ {:error, error}
+ end
+ end
+
+ defp gitlab_api_url(gitlab_base_url, project),
+ do: "https://#{gitlab_base_url}/api/v4/projects/#{URI.encode_www_form(project)}"
+
+ defp build_url(gitlab_base_url, project, ref),
+ do: "https://#{gitlab_base_url}/#{project}/-/jobs/artifacts/#{ref}/download?job=build"
+
+ defp http_client do
+ middleware = [
+ Tesla.Middleware.FollowRedirects,
+ Tesla.Middleware.JSON
+ ]
+
+ Tesla.client(middleware)
+ end
+end
diff --git a/lib/mix/tasks/pleroma/instance.ex b/lib/mix/tasks/pleroma/instance.ex
index 91440b453..226ed399f 100644
--- a/lib/mix/tasks/pleroma/instance.ex
+++ b/lib/mix/tasks/pleroma/instance.ex
@@ -33,7 +33,14 @@ defmodule Mix.Tasks.Pleroma.Instance do
uploads_dir: :string,
static_dir: :string,
listen_ip: :string,
- listen_port: :string
+ listen_port: :string,
+ fe_primary: :string,
+ fe_primary_ref: :string,
+ fe_mastodon: :string,
+ fe_mastodon_ref: :string,
+ fe_admin: :string,
+ fe_admin_ref: :string,
+ fe_static: :string
],
aliases: [
o: :output,
@@ -160,6 +167,62 @@ defmodule Mix.Tasks.Pleroma.Instance do
Config.put([:instance, :static_dir], static_dir)
+ install_fe =
+ case Mix.env() do
+ :test -> fn _, _ -> :ok end
+ _ -> &Mix.Tasks.Pleroma.Frontend.run(["install", &1, "--ref", &2])
+ end
+
+ fe_primary =
+ get_option(
+ options,
+ :fe_primary,
+ "Choose primary frontend for your instance (available: pleroma/kenoma/none)",
+ "pleroma"
+ )
+
+ fe_primary_ref =
+ get_frontend_ref(fe_primary !== "none", fe_primary, :fe_primary_ref, options)
+
+ install_fe.(fe_primary, fe_primary_ref)
+
+ enable_static_fe? =
+ get_option(
+ options,
+ :fe_static,
+ "Would you like to enable Static frontend (render profiles and posts using server-generated HTML that is viewable without using JavaScript)?",
+ "y"
+ ) === "y"
+
+ install_mastodon_fe? =
+ get_option(
+ options,
+ :fe_mastodon,
+ "Would you like to install Mastodon frontend?",
+ "y"
+ ) === "y"
+
+ fe_mastodon_ref =
+ get_frontend_ref(install_mastodon_fe?, "mastodon", :fe_mastodon_ref, options)
+
+ if install_mastodon_fe? do
+ install_fe.("mastodon", fe_mastodon_ref)
+ end
+
+ install_admin_fe? =
+ get_option(
+ options,
+ :fe_admin,
+ "Would you like to install Admin frontend?",
+ "y"
+ ) === "y"
+
+ fe_admin_ref = get_frontend_ref(install_admin_fe?, "admin", :fe_admin_ref, options)
+
+ if install_admin_fe? do
+ install_fe.("admin", fe_admin_ref)
+ end
+
secret = :crypto.strong_rand_bytes(64) |> Base.encode64() |> binary_part(0, 64)
jwt_secret = :crypto.strong_rand_bytes(64) |> Base.encode64() |> binary_part(0, 64)
signing_salt = :crypto.strong_rand_bytes(8) |> Base.encode64() |> binary_part(0, 8)
@@ -188,7 +251,11 @@ defmodule Mix.Tasks.Pleroma.Instance do
uploads_dir: uploads_dir,
rum_enabled: rum_enabled,
listen_ip: listen_ip,
- listen_port: listen_port
+ listen_port: listen_port,
+ fe_primary: %{"name" => fe_primary, "ref" => fe_primary_ref},
+ fe_mastodon: %{"name" => "mastodon", "ref" => fe_mastodon_ref},
+ fe_admin: %{"name" => "admin", "ref" => fe_admin_ref},
+ enable_static_fe?: enable_static_fe?
)
result_psql =
@@ -247,4 +314,26 @@ defmodule Mix.Tasks.Pleroma.Instance do
File.write(robots_txt_path, robots_txt)
shell_info("Writing #{robots_txt_path}.")
end
+
+ defp get_frontend_ref(false, _frontend, _option_key, _options), do: ""
+
+ defp get_frontend_ref(true, frontend, option_key, options) do
+ stable_pleroma? = Pleroma.Application.stable?()
+
+ current_stable_out =
+ if stable_pleroma? do
+ "stable"
+ else
+ "develop"
+ end
+
+ get_option(
+ options,
+ option_key,
+ "You are currently running #{current_stable_out} version of Pleroma. What version of #{
+ frontend
+ } you want to install? (\"stable\", \"develop\" or specific ref)",
+ current_stable_out
+ )
+ end
end
diff --git a/lib/mix/tasks/pleroma/user.ex b/lib/mix/tasks/pleroma/user.ex
index 01824aa18..ce7ba9b80 100644
--- a/lib/mix/tasks/pleroma/user.ex
+++ b/lib/mix/tasks/pleroma/user.ex
@@ -277,7 +277,7 @@ defmodule Mix.Tasks.Pleroma.User do
shell_info("Generated user invite token " <> String.replace(invite.invite_type, "_", " "))
url =
- Pleroma.Web.Router.Helpers.redirect_url(
+ Pleroma.Web.Router.Helpers.frontend_url(
Pleroma.Web.Endpoint,
:registration_page,
invite.token
diff --git a/lib/pleroma/application.ex b/lib/pleroma/application.ex
index 84f3aa82d..fa593abb9 100644
--- a/lib/pleroma/application.ex
+++ b/lib/pleroma/application.ex
@@ -13,11 +13,13 @@ defmodule Pleroma.Application do
@name Mix.Project.config()[:name]
@version Mix.Project.config()[:version]
+ @stable? Mix.Project.config()[:stable?]
@repository Mix.Project.config()[:source_url]
@env Mix.env()
def name, do: @name
def version, do: @version
+ def stable?, do: @stable?
def named_version, do: @name <> " " <> @version
def repository, do: @repository
diff --git a/lib/pleroma/emails/user_email.ex b/lib/pleroma/emails/user_email.ex
index dfadc10b3..d9cf2a6d7 100644
--- a/lib/pleroma/emails/user_email.ex
+++ b/lib/pleroma/emails/user_email.ex
@@ -47,7 +47,7 @@ defmodule Pleroma.Emails.UserEmail do
to_name \\ nil
) do
registration_url =
- Router.Helpers.redirect_url(
+ Router.Helpers.frontend_url(
Endpoint,
:registration_page,
user_invite_token.token
diff --git a/lib/pleroma/frontend.ex b/lib/pleroma/frontend.ex
new file mode 100644
index 000000000..b344edd9e
--- /dev/null
+++ b/lib/pleroma/frontend.ex
@@ -0,0 +1,32 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Frontend do
+ @type primary_fe_opts :: %{config: Map.t(), controller: Module.t(), static: boolean()}
+
+ @spec get_primary_fe_opts() :: primary_fe_opts()
+ def get_primary_fe_opts,
+ do: [:frontends] |> Pleroma.Config.get(%{}) |> Enum.into(%{}) |> get_primary_fe_opts()
+
+ @spec get_primary_fe_opts(Map.t()) :: primary_fe_opts()
+ def get_primary_fe_opts(%{primary: %{"name" => "none"}} = fe_config) do
+ %{
+ config: %{},
+ controller: Pleroma.Web.Frontend.HeadlessController,
+ static: fe_config[:static] || false
+ }
+ end
+
+ def get_primary_fe_opts(fe_config) do
+ %{
+ config: fe_config[:primary],
+ controller:
+ Module.concat([
+ Pleroma.Web.Frontend,
+ String.capitalize(fe_config[:primary]["name"]) <> "Controller"
+ ]),
+ static: fe_config[:static] || false
+ }
+ end
+end
diff --git a/lib/pleroma/plugs/frontend_plug.ex b/lib/pleroma/plugs/frontend_plug.ex
new file mode 100644
index 000000000..4db194dc3
--- /dev/null
+++ b/lib/pleroma/plugs/frontend_plug.ex
@@ -0,0 +1,51 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Plugs.FrontendPlug do
+ @moduledoc """
+ Sets private key `:frontend` for the given connection.
+ It is set to one of admin|mastodon|primary frontends config values based
+ on `conn.request_path`
+ """
+
+ import Plug.Conn
+
+ @behaviour Plug
+
+ @mastodon_paths ~w(web packs sw.js api/web)
+ @admin_paths ~w(pleroma)
+
+ def init(opts) do
+ opts
+ end
+
+ for path <- @mastodon_paths do
+ def call(%{request_path: "/" <> unquote(path) <> _rest} = conn, _opts) do
+ fe_config =
+ Pleroma.Config.get([:frontends], %{mastodon: %{"name" => "mastodon", "ref" => ""}})
+
+ put_private(conn, :frontend, %{
+ config: fe_config[:mastodon],
+ controller: Pleroma.Web.Frontend.MastodonController,
+ static: false
+ })
+ end
+ end
+
+ for path <- @admin_paths do
+ def call(%{request_path: "/" <> unquote(path) <> _rest} = conn, _opts) do
+ fe_config = Pleroma.Config.get([:frontends], %{admin: %{"name" => "admin", "ref" => ""}})
+
+ put_private(conn, :frontend, %{
+ config: fe_config[:admin],
+ controller: Pleroma.Web.Frontend.AdminController,
+ static: false
+ })
+ end
+ end
+
+ def call(conn, _opts) do
+ put_private(conn, :frontend, Pleroma.Frontend.get_primary_fe_opts())
+ end
+end
diff --git a/lib/pleroma/plugs/instance_static.ex b/lib/pleroma/plugs/instance_static.ex
index 7516f75c3..93e6528ea 100644
--- a/lib/pleroma/plugs/instance_static.ex
+++ b/lib/pleroma/plugs/instance_static.ex
@@ -9,19 +9,28 @@ defmodule Pleroma.Plugs.InstanceStatic do
This is a shim to call `Plug.Static` but with runtime `from` configuration.
Mountpoints are defined directly in the module to avoid calling the configuration for every request including non-static ones.
+
+ Files in FE bundles can override files in priv/static, and files in
+ instance/static can override files in FE bundles:
+ instance/static > FE bundles > priv/static
"""
@behaviour Plug
- def file_path(path) do
- instance_path =
- Path.join(Pleroma.Config.get([:instance, :static_dir], "instance/static/"), path)
+ # list of paths to be looked up in intance/static
+ @instance_overridable_paths ~w(robots.txt emoji sounds images instance favicon.png)
- if File.exists?(instance_path) do
- instance_path
- else
- Path.join(Application.app_dir(:pleroma, "priv/static/"), path)
- end
- end
+ # both pleroma/{STATIC_PATH} and pleroma/admin/{STATIC_PATH} can be requested
+ @fe_prefixed_paths ~w(pleroma/admin pleroma)
+ @fe_paths [
+ # mastodon
+ "packs",
+ "sw.js",
+ # primary frontend
+ "static",
+ "index.html",
+ "sw-pleroma.js",
+ "static-fe.css"
+ ]
def init(opts) do
opts
@@ -30,29 +39,42 @@ defmodule Pleroma.Plugs.InstanceStatic do
|> Plug.Static.init()
end
- for only <- Pleroma.Constants.static_only_files() do
- at = Plug.Router.Utils.split("/")
+ # for only <- Pleroma.Constants.static_only_files() do
+ for path <- @fe_prefixed_paths do
+ def call(%{request_path: "/" <> unquote(path) <> _} = conn, opts) do
+ fe_path = get_fe_path(conn)
+ opts = %{opts | at: Plug.Router.Utils.split(unquote(path)), from: fe_path}
+ Plug.Static.call(conn, opts)
+ end
+ end
- def call(%{request_path: "/" <> unquote(only) <> _} = conn, opts) do
- call_static(
- conn,
- opts,
- unquote(at),
- Pleroma.Config.get([:instance, :static_dir], "instance/static")
- )
+ for path <- @fe_paths do
+ def call(%{request_path: "/" <> unquote(path) <> _} = conn, opts) do
+ fe_path = get_fe_path(conn)
+ opts = %{opts | at: [], from: fe_path}
+
+ with ^conn <- call_instance_static(conn, opts) do
+ Plug.Static.call(conn, opts)
+ end
end
end
- def call(conn, _) do
- conn
+ for path <- @instance_overridable_paths do
+ def call(%{request_path: "/" <> unquote(path) <> _} = conn, opts) do
+ call_instance_static(conn, opts)
+ end
end
- defp call_static(conn, opts, at, from) do
- opts =
- opts
- |> Map.put(:from, from)
- |> Map.put(:at, at)
+ def call(conn, _), do: conn
+ defp call_instance_static(conn, opts) do
+ instance_static_path = Pleroma.Config.get([:instance, :static_dir], "instance/static")
+ opts = %{opts | at: [], from: instance_static_path}
Plug.Static.call(conn, opts)
end
+
+ defp get_fe_path(%{private: %{frontend: %{config: conf}}}) do
+ instance_static_path = Pleroma.Config.get([:instance, :static_dir], "instance/static")
+ Path.join([instance_static_path, "frontends", conf["name"], conf["ref"]])
+ end
end
diff --git a/lib/pleroma/plugs/static_fe_plug.ex b/lib/pleroma/plugs/static_fe_plug.ex
index 143665c71..d3ae08835 100644
--- a/lib/pleroma/plugs/static_fe_plug.ex
+++ b/lib/pleroma/plugs/static_fe_plug.ex
@@ -4,23 +4,25 @@
defmodule Pleroma.Plugs.StaticFEPlug do
import Plug.Conn
- alias Pleroma.Web.StaticFE.StaticFEController
def init(options), do: options
- def call(conn, _) do
- if enabled?() and requires_html?(conn) do
+ def call(%{private: %{frontend: %{static: true}}} = conn, _) do
+ action = Phoenix.Controller.action_name(conn)
+
+ if requires_html?(conn) and has_action?(action) do
conn
- |> StaticFEController.call(:show)
+ |> Pleroma.Web.FrontendController.call(action)
|> halt()
else
conn
end
end
- defp enabled?, do: Pleroma.Config.get([:static_fe, :enabled], false)
+ def call(conn, _), do: conn
- defp requires_html?(conn) do
- Phoenix.Controller.get_format(conn) == "html"
- end
+ defp requires_html?(conn), do: Phoenix.Controller.get_format(conn) == "html"
+
+ defp has_action?(action),
+ do: function_exported?(Pleroma.Web.Frontend.StaticController, action, 2)
end
diff --git a/lib/pleroma/utils.ex b/lib/pleroma/utils.ex
index 6b8e3accf..21d1159be 100644
--- a/lib/pleroma/utils.ex
+++ b/lib/pleroma/utils.ex
@@ -9,4 +9,19 @@ defmodule Pleroma.Utils do
|> Enum.map(&Path.join(dir, &1))
|> Kernel.ParallelCompiler.compile()
end
+
+ @doc """
+ POSIX-compliant check if command is available in the system
+
+ ## Examples
+ iex> command_available?("git")
+ true
+ iex> command_available?("wrongcmd")
+ false
+
+ """
+ @spec command_available?(String.t()) :: boolean()
+ def command_available?(command) do
+ match?({_output, 0}, System.cmd("sh", ["-c", "command -v #{command}"]))
+ end
end
diff --git a/lib/pleroma/web/controllers/frontend/admin_controller.ex b/lib/pleroma/web/controllers/frontend/admin_controller.ex
new file mode 100644
index 000000000..c6af4811a
--- /dev/null
+++ b/lib/pleroma/web/controllers/frontend/admin_controller.ex
@@ -0,0 +1,8 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Frontend.AdminController do
+ use Pleroma.Web, :controller
+ use Pleroma.Web.Frontend.DefaultController
+end
diff --git a/lib/pleroma/web/controllers/frontend/default_controller.ex b/lib/pleroma/web/controllers/frontend/default_controller.ex
new file mode 100644
index 000000000..e56288f06
--- /dev/null
+++ b/lib/pleroma/web/controllers/frontend/default_controller.ex
@@ -0,0 +1,40 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Frontend.DefaultController do
+ defmacro __using__(_opts) do
+ quote do
+ import Pleroma.Web.FrontendController, only: [index_file_path: 0, index_file_path: 1]
+
+ def index(conn, _params) do
+ status = conn.status || 200
+ {:ok, index_file_path} = index_file_path(conn.private[:frontend][:config])
+
+ conn
+ |> put_resp_content_type("text/html")
+ |> send_file(status, index_file_path)
+ end
+
+ def api_not_implemented(conn, _params) do
+ conn
+ |> put_status(404)
+ |> json(%{error: "Not implemented"})
+ end
+
+ def empty(conn, _params) do
+ conn
+ |> put_status(204)
+ |> text("")
+ end
+
+ def fallback(conn, _params) do
+ conn
+ |> put_status(404)
+ |> text("Not found")
+ end
+
+ defoverridable index: 2, api_not_implemented: 2, empty: 2, fallback: 2
+ end
+ end
+end
diff --git a/lib/pleroma/web/controllers/frontend/headless_controller.ex b/lib/pleroma/web/controllers/frontend/headless_controller.ex
new file mode 100644
index 000000000..7d9bbd443
--- /dev/null
+++ b/lib/pleroma/web/controllers/frontend/headless_controller.ex
@@ -0,0 +1,13 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Frontend.HeadlessController do
+ use Pleroma.Web, :controller
+
+ def index(conn, _params) do
+ conn
+ |> put_status(404)
+ |> text("")
+ end
+end
diff --git a/lib/pleroma/web/controllers/frontend/kenoma_controller.ex b/lib/pleroma/web/controllers/frontend/kenoma_controller.ex
new file mode 100644
index 000000000..bb4f7f550
--- /dev/null
+++ b/lib/pleroma/web/controllers/frontend/kenoma_controller.ex
@@ -0,0 +1,8 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Frontend.KenomaController do
+ use Pleroma.Web, :controller
+ use Pleroma.Web.Frontend.DefaultController
+end
diff --git a/lib/pleroma/web/controllers/frontend/mastodon_controller.ex b/lib/pleroma/web/controllers/frontend/mastodon_controller.ex
new file mode 100644
index 000000000..996651128
--- /dev/null
+++ b/lib/pleroma/web/controllers/frontend/mastodon_controller.ex
@@ -0,0 +1,56 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Frontend.MastodonController do
+ use Pleroma.Web, :controller
+
+ alias Pleroma.Plugs.OAuthScopesPlug
+ alias Pleroma.User
+
+ plug(OAuthScopesPlug, %{scopes: ["write:accounts"]} when action == :put_settings)
+
+ # Note: :index action handles attempt of unauthenticated access to private instance with redirect
+ plug(
+ OAuthScopesPlug,
+ %{scopes: ["read"], fallback: :proceed_unauthenticated, skip_instance_privacy_check: true}
+ when action == :index
+ )
+
+ plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug when action != :index)
+
+ def index(%{assigns: %{user: user, token: token}} = conn, _params)
+ when not is_nil(user) and not is_nil(token) do
+ conn
+ |> put_layout(false)
+ |> render("index.html",
+ token: token.token,
+ user: user,
+ custom_emojis: Pleroma.Emoji.get_all()
+ )
+ end
+
+ def index(conn, _params) do
+ conn
+ |> put_session(:return_to, conn.request_path)
+ |> redirect(to: auth_path(conn, :login))
+ end
+
+ @doc "GET /web/manifest.json"
+ def manifest(conn, _params) do
+ render(conn, "manifest.json")
+ end
+
+ @doc "PUT /api/web/settings"
+ def put_settings(%{assigns: %{user: user}} = conn, %{"data" => settings} = _params) do
+ case User.mastodon_settings_update(user, settings) do
+ {:ok, _} ->
+ json(conn, %{})
+
+ e ->
+ conn
+ |> put_status(:internal_server_error)
+ |> json(%{error: inspect(e)})
+ end
+ end
+end
diff --git a/lib/pleroma/web/controllers/frontend/pleroma_controller.ex b/lib/pleroma/web/controllers/frontend/pleroma_controller.ex
new file mode 100644
index 000000000..b1286be54
--- /dev/null
+++ b/lib/pleroma/web/controllers/frontend/pleroma_controller.ex
@@ -0,0 +1,75 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Frontend.PleromaController do
+ use Pleroma.Web, :controller
+ use Pleroma.Web.Frontend.DefaultController
+
+ require Logger
+
+ alias Pleroma.User
+ alias Pleroma.Web.Metadata
+ alias Pleroma.Web.Preload
+
+ def index_with_meta(conn, %{"maybe_nickname_or_id" => maybe_nickname_or_id} = params) do
+ case User.get_cached_by_nickname_or_id(maybe_nickname_or_id) do
+ %User{} = user ->
+ index_with_meta(conn, %{user: user})
+
+ _ ->
+ index(conn, params)
+ end
+ end
+
+ # not intended to be matched from router, but can be called from the app internally
+ def index_with_meta(conn, params) do
+ {:ok, path} = index_file_path()
+ {:ok, index_content} = File.read(path)
+
+ tags =
+ try do
+ Metadata.build_tags(params)
+ rescue
+ e ->
+ Logger.error(
+ "Metadata rendering for #{conn.request_path} failed.\n" <>
+ Exception.format(:error, e, __STACKTRACE__)
+ )
+
+ ""
+ end
+
+ preloads = preload_data(conn, params)
+
+ response = String.replace(index_content, "<!--server-generated-meta-->", tags <> preloads)
+
+ html(conn, response)
+ end
+
+ def index_with_preload(conn, params) do
+ {:ok, path} = index_file_path()
+ {:ok, index_content} = File.read(path)
+ preloads = preload_data(conn, params)
+
+ response = String.replace(index_content, "<!--server-generated-meta-->", preloads)
+
+ html(conn, response)
+ end
+
+ defdelegate registration_page(conn, params), to: __MODULE__, as: :index
+
+ defp preload_data(conn, params) do
+ try do
+ Preload.build_tags(conn, params)
+ rescue
+ e ->
+ Logger.error(
+ "Preloading for #{conn.request_path} failed.\n" <>
+ Exception.format(:error, e, __STACKTRACE__)
+ )
+
+ ""
+ end
+ end
+end
diff --git a/lib/pleroma/web/static_fe/static_fe_controller.ex b/lib/pleroma/web/controllers/frontend/static_controller.ex
index a7a891b13..27d70d3da 100644
--- a/lib/pleroma/web/static_fe/static_fe_controller.ex
+++ b/lib/pleroma/web/controllers/frontend/static_controller.ex
@@ -2,7 +2,7 @@
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
-defmodule Pleroma.Web.StaticFE.StaticFEController do
+defmodule Pleroma.Web.Frontend.StaticController do
use Pleroma.Web, :controller
alias Pleroma.Activity
@@ -11,11 +11,8 @@ defmodule Pleroma.Web.StaticFE.StaticFEController do
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Visibility
alias Pleroma.Web.Metadata
- alias Pleroma.Web.Router.Helpers
plug(:put_layout, :static_fe)
- plug(:put_view, Pleroma.Web.StaticFE.StaticFEView)
- plug(:assign_id)
plug(Pleroma.Plugs.EnsureAuthenticatedPlug,
unless_func: &Pleroma.Web.FederatingPlug.federating?/1
@@ -23,65 +20,20 @@ defmodule Pleroma.Web.StaticFE.StaticFEController do
@page_keys ["max_id", "min_id", "limit", "since_id", "order"]
- defp get_title(%Object{data: %{"name" => name}}) when is_binary(name),
- do: name
-
- defp get_title(%Object{data: %{"summary" => summary}}) when is_binary(summary),
- do: summary
-
- defp get_title(_), do: nil
-
- defp not_found(conn, message) do
- conn
- |> put_status(404)
- |> render("error.html", %{message: message, meta: ""})
- end
-
- defp get_counts(%Activity{} = activity) do
- %Object{data: data} = Object.normalize(activity)
-
- %{
- likes: data["like_count"] || 0,
- replies: data["repliesCount"] || 0,
- announces: data["announcement_count"] || 0
- }
- end
-
- defp represent(%Activity{} = activity), do: represent(activity, false)
-
- defp represent(%Activity{object: %Object{data: data}} = activity, selected) do
- {:ok, user} = User.get_or_fetch(activity.object.data["actor"])
+ def object(conn, %{"uuid" => _uuid}) do
+ url = url(conn) <> conn.request_path
- link =
- case user.local do
- true -> Helpers.o_status_url(Pleroma.Web.Endpoint, :notice, activity)
- _ -> data["url"] || data["external_url"] || data["id"]
- end
-
- content =
- if data["content"] do
- data["content"]
- |> Pleroma.HTML.filter_tags()
- |> Pleroma.Emoji.Formatter.emojify(Map.get(data, "emoji", %{}))
- else
- nil
- end
+ case Activity.get_create_by_object_ap_id_with_object(url) do
+ %Activity{} = activity ->
+ to = o_status_path(Pleroma.Web.Endpoint, :notice, activity)
+ redirect(conn, to: to)
- %{
- user: User.sanitize_html(user),
- title: get_title(activity.object),
- content: content,
- attachment: data["attachment"],
- link: link,
- published: data["published"],
- sensitive: data["sensitive"],
- selected: selected,
- counts: get_counts(activity),
- id: activity.id
- }
+ _ ->
+ not_found(conn, "Post not found.")
+ end
end
- def show(%{assigns: %{notice_id: notice_id}} = conn, _params) do
+ def notice(conn, %{"id" => notice_id}) do
with %Activity{local: true} = activity <-
Activity.get_by_id_with_object(notice_id),
true <- Visibility.is_public?(activity.object),
@@ -106,7 +58,7 @@ defmodule Pleroma.Web.StaticFE.StaticFEController do
end
end
- def show(%{assigns: %{username_or_id: username_or_id}} = conn, params) do
+ def feed_redirect(conn, %{"nickname" => username_or_id} = params) do
case User.get_cached_by_nickname_or_id(username_or_id) do
%User{} = user ->
meta = Metadata.build_tags(%{user: user})
@@ -140,12 +92,12 @@ defmodule Pleroma.Web.StaticFE.StaticFEController do
end
end
- def show(%{assigns: %{object_id: _}} = conn, _params) do
- url = Helpers.url(conn) <> conn.request_path
+ def activity(conn, %{"uuid" => _uuid}) do
+ url = url(conn) <> conn.request_path
- case Activity.get_create_by_object_ap_id_with_object(url) do
+ case Activity.get_by_ap_id(url) do
%Activity{} = activity ->
- to = Helpers.o_status_path(Pleroma.Web.Endpoint, :notice, activity)
+ to = o_status_path(Pleroma.Web.Endpoint, :notice, activity)
redirect(conn, to: to)
_ ->
@@ -153,30 +105,60 @@ defmodule Pleroma.Web.StaticFE.StaticFEController do
end
end
- def show(%{assigns: %{activity_id: _}} = conn, _params) do
- url = Helpers.url(conn) <> conn.request_path
+ defp get_title(%Object{data: %{"name" => name}}) when is_binary(name),
+ do: name
- case Activity.get_by_ap_id(url) do
- %Activity{} = activity ->
- to = Helpers.o_status_path(Pleroma.Web.Endpoint, :notice, activity)
- redirect(conn, to: to)
+ defp get_title(%Object{data: %{"summary" => summary}}) when is_binary(summary),
+ do: summary
- _ ->
- not_found(conn, "Post not found.")
- end
+ defp get_title(_), do: nil
+
+ defp not_found(conn, message) do
+ conn
+ |> put_status(404)
+ |> render("error.html", %{message: message, meta: ""})
+ end
+
+ defp get_counts(%Activity{} = activity) do
+ %Object{data: data} = Object.normalize(activity)
+
+ %{
+ likes: data["like_count"] || 0,
+ replies: data["repliesCount"] || 0,
+ announces: data["announcement_count"] || 0
+ }
end
- defp assign_id(%{path_info: ["notice", notice_id]} = conn, _opts),
- do: assign(conn, :notice_id, notice_id)
+ defp represent(%Activity{} = activity), do: represent(activity, false)
- defp assign_id(%{path_info: ["users", user_id]} = conn, _opts),
- do: assign(conn, :username_or_id, user_id)
+ defp represent(%Activity{object: %Object{data: data}} = activity, selected) do
+ {:ok, user} = User.get_or_fetch(activity.object.data["actor"])
- defp assign_id(%{path_info: ["objects", object_id]} = conn, _opts),
- do: assign(conn, :object_id, object_id)
+ link =
+ if user.local do
+ o_status_url(Pleroma.Web.Endpoint, :notice, activity)
+ else
+ data["url"] || data["external_url"] || data["id"]
+ end
- defp assign_id(%{path_info: ["activities", activity_id]} = conn, _opts),
- do: assign(conn, :activity_id, activity_id)
+ content =
+ if data["content"] do
+ data["content"]
+ |> Pleroma.HTML.filter_tags()
+ |> Pleroma.Emoji.Formatter.emojify(Map.get(data, "emoji", %{}))
+ end
- defp assign_id(conn, _opts), do: conn
+ %{
+ user: User.sanitize_html(user),
+ title: get_title(activity.object),
+ content: content,
+ attachment: data["attachment"],
+ link: link,
+ published: data["published"],
+ sensitive: data["sensitive"],
+ selected: selected,
+ counts: get_counts(activity),
+ id: activity.id
+ }
+ end
end
diff --git a/lib/pleroma/web/endpoint.ex b/lib/pleroma/web/endpoint.ex
index 226d42c2c..a4476ffb7 100644
--- a/lib/pleroma/web/endpoint.ex
+++ b/lib/pleroma/web/endpoint.ex
@@ -13,6 +13,7 @@ defmodule Pleroma.Web.Endpoint do
plug(CORSPlug)
plug(Pleroma.Plugs.HTTPSecurityPlug)
plug(Pleroma.Plugs.UploadedMedia)
+ plug(Pleroma.Plugs.FrontendPlug)
@static_cache_control "public, no-cache"
@@ -36,6 +37,8 @@ defmodule Pleroma.Web.Endpoint do
Plug.Static,
at: "/",
from: :pleroma,
+ # only:
+ # ~w(robots.txt static-fe.css finmoji emoji sounds images instance favicon.png schemas doc),
only: Pleroma.Constants.static_only_files(),
# credo:disable-for-previous-line Credo.Check.Readability.MaxLineLength
gzip: true,
@@ -45,13 +48,6 @@ defmodule Pleroma.Web.Endpoint do
}
)
- plug(Plug.Static.IndexHtml, at: "/pleroma/admin/")
-
- plug(Plug.Static,
- at: "/pleroma/admin/",
- from: {:pleroma, "priv/static/adminfe/"}
- )
-
# Code reloading can be explicitly enabled under the
# :code_reloader configuration of your endpoint.
if code_reloading? do
diff --git a/lib/pleroma/web/fallback_redirect_controller.ex b/lib/pleroma/web/fallback_redirect_controller.ex
deleted file mode 100644
index 431ad5485..000000000
--- a/lib/pleroma/web/fallback_redirect_controller.ex
+++ /dev/null
@@ -1,108 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Fallback.RedirectController do
- use Pleroma.Web, :controller
-
- require Logger
-
- alias Pleroma.User
- alias Pleroma.Web.Metadata
- alias Pleroma.Web.Preload
-
- def api_not_implemented(conn, _params) do
- conn
- |> put_status(404)
- |> json(%{error: "Not implemented"})
- end
-
- def redirector(conn, _params, code \\ 200) do
- conn
- |> put_resp_content_type("text/html")
- |> send_file(code, index_file_path())
- end
-
- def redirector_with_meta(conn, %{"maybe_nickname_or_id" => maybe_nickname_or_id} = params) do
- with %User{} = user <- User.get_cached_by_nickname_or_id(maybe_nickname_or_id) do
- redirector_with_meta(conn, %{user: user})
- else
- nil ->
- redirector(conn, params)
- end
- end
-
- def redirector_with_meta(conn, params) do
- {:ok, index_content} = File.read(index_file_path())
-
- tags = build_tags(conn, params)
- preloads = preload_data(conn, params)
-
- response =
- index_content
- |> String.replace("<!--server-generated-meta-->", tags <> preloads)
-
- conn
- |> put_resp_content_type("text/html")
- |> send_resp(200, response)
- end
-
- def redirector_with_preload(conn, %{"path" => ["pleroma", "admin"]}) do
- redirect(conn, to: "/pleroma/admin/")
- end
-
- def redirector_with_preload(conn, params) do
- {:ok, index_content} = File.read(index_file_path())
- preloads = preload_data(conn, params)
-
- response =
- index_content
- |> String.replace("<!--server-generated-meta-->", preloads)
-
- conn
- |> put_resp_content_type("text/html")
- |> send_resp(200, response)
- end
-
- def registration_page(conn, params) do
- redirector(conn, params)
- end
-
- def empty(conn, _params) do
- conn
- |> put_status(204)
- |> text("")
- end
-
- defp index_file_path do
- Pleroma.Plugs.InstanceStatic.file_path("index.html")
- end
-
- defp build_tags(conn, params) do
- try do
- Metadata.build_tags(params)
- rescue
- e ->
- Logger.error(
- "Metadata rendering for #{conn.request_path} failed.\n" <>
- Exception.format(:error, e, __STACKTRACE__)
- )
-
- ""
- end
- end
-
- defp preload_data(conn, params) do
- try do
- Preload.build_tags(conn, params)
- rescue
- e ->
- Logger.error(
- "Preloading for #{conn.request_path} failed.\n" <>
- Exception.format(:error, e, __STACKTRACE__)
- )
-
- ""
- end
- end
-end
diff --git a/lib/pleroma/web/feed/user_controller.ex b/lib/pleroma/web/feed/user_controller.ex
index d56f43818..8a7e94b94 100644
--- a/lib/pleroma/web/feed/user_controller.ex
+++ b/lib/pleroma/web/feed/user_controller.ex
@@ -5,7 +5,6 @@
defmodule Pleroma.Web.Feed.UserController do
use Pleroma.Web, :controller
- alias Fallback.RedirectController
alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.ActivityPubController
@@ -13,11 +12,15 @@ defmodule Pleroma.Web.Feed.UserController do
plug(Pleroma.Plugs.SetFormatPlug when action in [:feed_redirect])
+ plug(Pleroma.Plugs.StaticFEPlug)
+
action_fallback(:errors)
def feed_redirect(%{assigns: %{format: "html"}} = conn, %{"nickname" => nickname}) do
with {_, %User{} = user} <- {:fetch_user, User.get_cached_by_nickname_or_id(nickname)} do
- RedirectController.redirector_with_meta(conn, %{user: user})
+ conn
+ |> Map.put(:params, %{user: user})
+ |> Pleroma.Web.FrontendController.call(:index_with_meta)
end
end
diff --git a/lib/pleroma/web/frontend_controller.ex b/lib/pleroma/web/frontend_controller.ex
new file mode 100644
index 000000000..2ba445df9
--- /dev/null
+++ b/lib/pleroma/web/frontend_controller.ex
@@ -0,0 +1,76 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.FrontendController do
+ use Pleroma.Web, :controller
+ import Pleroma.Frontend, only: [get_primary_fe_opts: 0]
+
+ def action(conn, _opts) do
+ # `conn.private[:frontend]` can be unset if the function is called outside
+ # of the standard controller pipeline
+ fe_config = conn.private[:frontend] || get_primary_fe_opts()
+
+ # can only be true for :primary frontend
+ static_enabled? = Map.get(fe_config, :static, false)
+
+ action_name = action_name(conn)
+
+ {controller, action} =
+ cond do
+ static_enabled? and
+ function_exported?(Pleroma.Web.Frontend.StaticController, action_name, 2) ->
+ {Pleroma.Web.Frontend.StaticController, action_name}
+
+ function_exported?(fe_config[:controller], action_name, 2) ->
+ {fe_config[:controller], action_name}
+
+ true ->
+ {fe_config[:controller], :fallback}
+ end
+
+ conn
+ # in case we are serving internal call
+ |> put_private(:frontend, fe_config)
+ |> put_view(Phoenix.Controller.__view__(controller))
+ |> controller.call(controller.init(action))
+ end
+
+ @doc """
+ Returns path to index.html file for the frontend from the given config.
+ If config is not provided, config for the `:primary` frontend is fetched and used.
+ If index.html file is not found for the requested frontend, the function fallback
+ to looking the file at instance static directory and then, in case of failure,
+ in priv/static directory.
+ Path returned in case of success is guaranteed to be existing file.
+ """
+ @spec index_file_path(Map.t()) :: {:ok, String.t()} | {:error, String.t()}
+ def index_file_path(fe_config \\ nil) do
+ filename = "index.html"
+ instance_base_path = Pleroma.Config.get([:instance, :static_dir], "instance/static/")
+
+ %{"name" => name, "ref" => ref} =
+ with nil <- fe_config do
+ Pleroma.Frontend.get_primary_fe_opts()[:config]
+ end
+
+ frontend_path = Path.join([instance_base_path, "frontends", name, ref, filename])
+ instance_path = Path.join([instance_base_path, filename])
+ priv_path = Application.app_dir(:pleroma, ["priv", "static", filename])
+
+ cond do
+ File.exists?(instance_path) ->
+ {:ok, instance_path}
+
+ File.exists?(frontend_path) ->
+ {:ok, frontend_path}
+
+ File.exists?(priv_path) ->
+ {:ok, priv_path}
+
+ true ->
+ {:error,
+ "index.html file was not found in #{inspect([instance_path, frontend_path, priv_path])}"}
+ end
+ end
+end
diff --git a/lib/pleroma/web/mastodon_api/controllers/auth_controller.ex b/lib/pleroma/web/mastodon_api/controllers/auth_controller.ex
index 753b3db3e..2aa68c2cf 100644
--- a/lib/pleroma/web/mastodon_api/controllers/auth_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/auth_controller.ex
@@ -51,8 +51,8 @@ defmodule Pleroma.Web.MastodonAPI.AuthController do
@doc "DELETE /auth/sign_out"
def logout(conn, _) do
conn
- |> clear_session
- |> redirect(to: "/")
+ |> clear_session()
+ |> redirect(to: frontend_path(conn, :index, []))
end
@doc "POST /auth/password"
@@ -75,7 +75,7 @@ defmodule Pleroma.Web.MastodonAPI.AuthController do
defp local_mastodon_root_path(conn) do
case get_session(conn, :return_to) do
nil ->
- masto_fe_path(conn, :index, ["getting-started"])
+ frontend_mastodon_path(conn, :index, ["getting-started"])
return_to ->
delete_session(conn, :return_to)
diff --git a/lib/pleroma/web/ostatus/ostatus_controller.ex b/lib/pleroma/web/ostatus/ostatus_controller.ex
index de1b0b3f0..08bb47aa8 100644
--- a/lib/pleroma/web/ostatus/ostatus_controller.ex
+++ b/lib/pleroma/web/ostatus/ostatus_controller.ex
@@ -5,7 +5,6 @@
defmodule Pleroma.Web.OStatus.OStatusController do
use Pleroma.Web, :controller
- alias Fallback.RedirectController
alias Pleroma.Activity
alias Pleroma.Object
alias Pleroma.Plugs.RateLimiter
@@ -30,6 +29,8 @@ defmodule Pleroma.Web.OStatus.OStatusController do
when action in [:object, :activity, :notice]
)
+ plug(Pleroma.Plugs.StaticFEPlug)
+
action_fallback(:errors)
def object(%{assigns: %{format: format}} = conn, _params)
@@ -37,14 +38,12 @@ defmodule Pleroma.Web.OStatus.OStatusController do
ActivityPubController.call(conn, :object)
end
- def object(%{assigns: %{format: format}} = conn, _params) do
+ def object(%{assigns: %{format: _format}} = conn, _params) do
with id <- Endpoint.url() <> conn.request_path,
{_, %Activity{} = activity} <-
{:activity, Activity.get_create_by_object_ap_id_with_object(id)},
{_, true} <- {:public?, Visibility.is_public?(activity)} do
- case format do
- _ -> redirect(conn, to: "/notice/#{activity.id}")
- end
+ redirect(conn, to: o_status_path(conn, :notice, activity.id))
else
reason when reason in [{:public?, false}, {:activity, nil}] ->
{:error, :not_found}
@@ -59,13 +58,11 @@ defmodule Pleroma.Web.OStatus.OStatusController do
ActivityPubController.call(conn, :activity)
end
- def activity(%{assigns: %{format: format}} = conn, _params) do
+ def activity(%{assigns: %{format: _format}} = conn, _params) do
with id <- Endpoint.url() <> conn.request_path,
{_, %Activity{} = activity} <- {:activity, Activity.normalize(id)},
{_, true} <- {:public?, Visibility.is_public?(activity)} do
- case format do
- _ -> redirect(conn, to: "/notice/#{activity.id}")
- end
+ redirect(conn, to: o_status_path(conn, :notice, activity.id))
else
reason when reason in [{:public?, false}, {:activity, nil}] ->
{:error, :not_found}
@@ -91,24 +88,25 @@ defmodule Pleroma.Web.OStatus.OStatusController do
activity.data["type"] == "Create" ->
%Object{} = object = Object.normalize(activity)
- RedirectController.redirector_with_meta(
- conn,
- %{
- activity_id: activity.id,
- object: object,
- url: Router.Helpers.o_status_url(Endpoint, :notice, activity.id),
- user: user
- }
- )
+ params = %{
+ activity_id: activity.id,
+ object: object,
+ url: Router.Helpers.o_status_url(Endpoint, :notice, activity.id),
+ user: user
+ }
+
+ conn
+ |> Map.put(:params, params)
+ |> Pleroma.Web.FrontendController.call(:index_with_meta)
true ->
- RedirectController.redirector(conn, nil)
+ Pleroma.Web.FrontendController.call(conn, :index)
end
else
reason when reason in [{:public?, false}, {:activity, nil}] ->
conn
|> put_status(404)
- |> RedirectController.redirector(nil, 404)
+ |> Pleroma.Web.FrontendController.call(:index)
e ->
e
@@ -135,7 +133,7 @@ defmodule Pleroma.Web.OStatus.OStatusController do
_error ->
conn
|> put_status(404)
- |> RedirectController.redirector(nil, 404)
+ |> Pleroma.Web.FrontendController.call(:index)
end
end
diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex
index 386308362..e25ffd317 100644
--- a/lib/pleroma/web/router.ex
+++ b/lib/pleroma/web/router.ex
@@ -464,7 +464,8 @@ defmodule Pleroma.Web.Router do
pipe_through(:authenticated_api)
# Backend-obscure settings blob for MastoFE, don't parse/reuse elsewhere
- put("/settings", MastoFEController, :put_settings)
+ # put("/settings", MastoFEController, :put_settings)
+ put("/settings", FrontendController, :put_settings, as: :frontend_mastodon)
end
scope "/api/v1", Pleroma.Web.MastodonAPI do
@@ -547,7 +548,6 @@ defmodule Pleroma.Web.Router do
pipeline :ostatus do
plug(:accepts, ["html", "xml", "rss", "atom", "activity+json", "json"])
- plug(Pleroma.Plugs.StaticFEPlug)
end
pipeline :oembed do
@@ -652,7 +652,7 @@ defmodule Pleroma.Web.Router do
scope "/", Pleroma.Web do
pipe_through(:api)
- get("/web/manifest.json", MastoFEController, :manifest)
+ get("/web/manifest.json", FrontendController, :manifest, as: :frontend_mastodon)
end
scope "/", Pleroma.Web do
@@ -663,8 +663,7 @@ defmodule Pleroma.Web.Router do
post("/auth/password", MastodonAPI.AuthController, :password_reset)
- get("/web/*path", MastoFEController, :index)
-
+ get("/web/*path", FrontendController, :index, as: :frontend_mastodon)
get("/embed/:id", EmbedController, :show)
end
@@ -714,12 +713,16 @@ defmodule Pleroma.Web.Router do
get("/check_password", MongooseIMController, :check_password)
end
- scope "/", Fallback do
- get("/registration/:token", RedirectController, :registration_page)
- get("/:maybe_nickname_or_id", RedirectController, :redirector_with_meta)
- get("/api*path", RedirectController, :api_not_implemented)
- get("/*path", RedirectController, :redirector_with_preload)
+ scope "/pleroma/admin", Pleroma.Web do
+ get("/*path", FrontendController, :index, as: :frontend_admin)
+ end
+
+ scope "/", Pleroma.Web do
+ get("/registration/:token", FrontendController, :registration_page)
+ get("/:maybe_nickname_or_id", FrontendController, :index_with_meta)
+ get("/api*path", FrontendController, :api_not_implemented)
+ get("/*path", FrontendController, :index)
- options("/*path", RedirectController, :empty)
+ options("/*path", FrontendController, :empty)
end
end
diff --git a/lib/pleroma/web/templates/masto_fe/index.html.eex b/lib/pleroma/web/templates/frontend/mastodon/index.html.eex
index c330960fa..22f3f3b5a 100644
--- a/lib/pleroma/web/templates/masto_fe/index.html.eex
+++ b/lib/pleroma/web/templates/frontend/mastodon/index.html.eex
@@ -6,8 +6,8 @@
<title>
<%= Config.get([:instance, :name]) %>
</title>
-<link rel="icon" type="image/png" href="/favicon.png"/>
-<link rel="manifest" type="applicaton/manifest+json" href="<%= masto_fe_path(Pleroma.Web.Endpoint, :manifest) %>" />
+<link rel="icon" type="image/png" href="favicon.png"/>
+<link rel="manifest" type="applicaton/manifest+json" href="<%= frontend_mastodon_path(Pleroma.Web.Endpoint, :manifest) %>" />
<meta name="theme-color" content="<%= Config.get([:manifest, :theme_color]) %>" />
diff --git a/lib/pleroma/web/templates/static_fe/static_fe/_attachment.html.eex b/lib/pleroma/web/templates/frontend/static/_attachment.html.eex
index 4853e7f4b..4853e7f4b 100644
--- a/lib/pleroma/web/templates/static_fe/static_fe/_attachment.html.eex
+++ b/lib/pleroma/web/templates/frontend/static/_attachment.html.eex
diff --git a/lib/pleroma/web/templates/static_fe/static_fe/_notice.html.eex b/lib/pleroma/web/templates/frontend/static/_notice.html.eex
index df0244795..07c80d72b 100644
--- a/lib/pleroma/web/templates/static_fe/static_fe/_notice.html.eex
+++ b/lib/pleroma/web/templates/frontend/static/_notice.html.eex
@@ -19,7 +19,7 @@
<%= for %{"name" => name, "url" => [url | _]} <- @attachment do %>
<%= if @sensitive do %>
<details class="nsfw">
- <summary><%= Gettext.gettext("sensitive media") %></summary>
+ <summary><%= gettext("sensitive media") %></summary>
<div>
<%= render("_attachment.html", %{name: name, url: url["href"],
mediaType: fetch_media_type(url)}) %>
@@ -33,9 +33,9 @@
</div>
<%= if @selected do %>
<dl class="counts">
- <dt><%= Gettext.gettext("replies") %></dt><dd><%= @counts.replies %></dd>
- <dt><%= Gettext.gettext("announces") %></dt><dd><%= @counts.announces %></dd>
- <dt><%= Gettext.gettext("likes") %></dt><dd><%= @counts.likes %></dd>
+ <dt><%= gettext("replies") %></dt><dd><%= @counts.replies %></dd>
+ <dt><%= gettext("announces") %></dt><dd><%= @counts.announces %></dd>
+ <dt><%= gettext("likes") %></dt><dd><%= @counts.likes %></dd>
</dl>
<% end %>
</div>
diff --git a/lib/pleroma/web/templates/static_fe/static_fe/_user_card.html.eex b/lib/pleroma/web/templates/frontend/static/_user_card.html.eex
index 977b894d3..c29170ff5 100644
--- a/lib/pleroma/web/templates/static_fe/static_fe/_user_card.html.eex
+++ b/lib/pleroma/web/templates/frontend/static/_user_card.html.eex
@@ -1,10 +1,10 @@
<div class="p-author h-card">
<a class="u-url" rel="author noopener" href="<%= (@user.uri || @user.ap_id) %>">
<div class="avatar">
- <img class="u-photo" src="<%= User.avatar_url(@user) |> MediaProxy.url %>" width="48" height="48" alt="">
+ <img class="u-photo" src="<%= User.avatar_url(@user) |> Pleroma.Web.MediaProxy.url %>" width="48" height="48" alt="">
</div>
<span class="display-name">
- <bdi class="p-name"><%= raw Formatter.emojify(@user.name, @user.emoji) %></bdi>
+ <bdi class="p-name"><%= raw Pleroma.Emoji.Formatter.emojify(@user.name, @user.emoji) %></bdi>
<span class="nickname"><%= @user.nickname %></span>
</span>
</a>
diff --git a/lib/pleroma/web/templates/static_fe/static_fe/conversation.html.eex b/lib/pleroma/web/templates/frontend/static/conversation.html.eex
index 2acd84828..2acd84828 100644
--- a/lib/pleroma/web/templates/static_fe/static_fe/conversation.html.eex
+++ b/lib/pleroma/web/templates/frontend/static/conversation.html.eex
diff --git a/lib/pleroma/web/templates/static_fe/static_fe/error.html.eex b/lib/pleroma/web/templates/frontend/static/error.html.eex
index d98a1eba7..d98a1eba7 100644
--- a/lib/pleroma/web/templates/static_fe/static_fe/error.html.eex
+++ b/lib/pleroma/web/templates/frontend/static/error.html.eex
diff --git a/lib/pleroma/web/templates/static_fe/static_fe/profile.html.eex b/lib/pleroma/web/templates/frontend/static/profile.html.eex
index 3191bf450..1690c5edf 100644
--- a/lib/pleroma/web/templates/static_fe/static_fe/profile.html.eex
+++ b/lib/pleroma/web/templates/frontend/static/profile.html.eex
@@ -2,12 +2,12 @@
<h1><%= link instance_name(), to: "/" %></h1>
<h3>
- <form class="pull-right collapse" method="POST" action="<%= Helpers.util_path(@conn, :remote_subscribe) %>">
+ <form class="pull-right collapse" method="POST" action="<%= util_path(@conn, :remote_subscribe) %>">
<input type="hidden" name="nickname" value="<%= @user.nickname %>">
<input type="hidden" name="profile" value="">
<button type="submit" class="collapse">Remote follow</button>
</form>
- <%= raw Formatter.emojify(@user.name, @user.emoji) %> |
+ <%= raw Pleroma.Emoji.Formatter.emojify(@user.name, @user.emoji) %> |
<%= link "@#{@user.nickname}@#{Endpoint.host()}", to: (@user.uri || @user.ap_id) %>
</h3>
<p><%= raw @user.bio %></p>
diff --git a/lib/pleroma/web/templates/layout/static_fe.html.eex b/lib/pleroma/web/templates/layout/static_fe.html.eex
index dc0ee2a5c..b7ec4d0c9 100644
--- a/lib/pleroma/web/templates/layout/static_fe.html.eex
+++ b/lib/pleroma/web/templates/layout/static_fe.html.eex
@@ -5,7 +5,7 @@
<meta name="viewport" content="width=device-width,initial-scale=1,minimal-ui" />
<title><%= Pleroma.Config.get([:instance, :name]) %></title>
<%= Phoenix.HTML.raw(assigns[:meta] || "") %>
- <link rel="stylesheet" href="/static-fe/static-fe.css">
+ <link rel="stylesheet" href="<%= static_path(@conn, "/static-fe.css") %>"/>
</head>
<body>
<div class="container">
diff --git a/lib/pleroma/web/views/frontend/mastodon_view.ex b/lib/pleroma/web/views/frontend/mastodon_view.ex
new file mode 100644
index 000000000..6428df601
--- /dev/null
+++ b/lib/pleroma/web/views/frontend/mastodon_view.ex
@@ -0,0 +1,121 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Frontend.MastodonView do
+ use Pleroma.Web, :view
+ alias Pleroma.Config
+ alias Pleroma.User
+ alias Pleroma.Web.MastodonAPI.AccountView
+ alias Pleroma.Web.MastodonAPI.CustomEmojiView
+
+ @default_settings %{
+ onboarded: true,
+ home: %{
+ shows: %{
+ reblog: true,
+ reply: true
+ }
+ },
+ notifications: %{
+ alerts: %{
+ follow: true,
+ favourite: true,
+ reblog: true,
+ mention: true
+ },
+ shows: %{
+ follow: true,
+ favourite: true,
+ reblog: true,
+ mention: true
+ },
+ sounds: %{
+ follow: true,
+ favourite: true,
+ reblog: true,
+ mention: true
+ }
+ }
+ }
+
+ def initial_state(token, user, custom_emojis) do
+ limit = Config.get([:instance, :limit])
+
+ %{
+ meta: %{
+ streaming_api_base_url: Pleroma.Web.Endpoint.websocket_url(),
+ access_token: token,
+ locale: "en",
+ domain: Pleroma.Web.Endpoint.host(),
+ admin: "1",
+ me: "#{user.id}",
+ unfollow_modal: false,
+ boost_modal: false,
+ delete_modal: true,
+ auto_play_gif: false,
+ display_sensitive_media: false,
+ reduce_motion: false,
+ max_toot_chars: limit,
+ mascot: User.get_mascot(user)["url"]
+ },
+ poll_limits: Config.get([:instance, :poll_limits]),
+ rights: %{
+ delete_others_notice: present?(user.is_moderator),
+ admin: present?(user.is_admin)
+ },
+ compose: %{
+ me: "#{user.id}",
+ default_privacy: user.default_scope,
+ default_sensitive: false,
+ allow_content_types: Config.get([:instance, :allowed_post_formats])
+ },
+ media_attachments: %{
+ accept_content_types: [
+ ".jpg",
+ ".jpeg",
+ ".png",
+ ".gif",
+ ".webm",
+ ".mp4",
+ ".m4v",
+ "image\/jpeg",
+ "image\/png",
+ "image\/gif",
+ "video\/webm",
+ "video\/mp4"
+ ]
+ },
+ settings: user.settings || @default_settings,
+ push_subscription: nil,
+ accounts: %{user.id => render(AccountView, "show.json", user: user, for: user)},
+ custom_emojis: render(CustomEmojiView, "index.json", custom_emojis: custom_emojis),
+ char_limit: limit
+ }
+ |> Jason.encode!()
+ |> Phoenix.HTML.raw()
+ end
+
+ defp present?(nil), do: false
+ defp present?(false), do: false
+ defp present?(_), do: true
+
+ def render("manifest.json", _params) do
+ %{
+ name: Config.get([:instance, :name]),
+ description: Config.get([:instance, :description]),
+ icons: Config.get([:manifest, :icons]),
+ theme_color: Config.get([:manifest, :theme_color]),
+ background_color: Config.get([:manifest, :background_color]),
+ display: "standalone",
+ scope: Pleroma.Web.base_url(),
+ start_url: frontend_mastodon_path(Pleroma.Web.Endpoint, :index, ["getting-started"]),
+ categories: [
+ "social"
+ ],
+ serviceworker: %{
+ src: "/sw.js"
+ }
+ }
+ end
+end
diff --git a/lib/pleroma/web/static_fe/static_fe_view.ex b/lib/pleroma/web/views/frontend/static_view.ex
index b3d1d1ec8..4cdb1128a 100644
--- a/lib/pleroma/web/static_fe/static_fe_view.ex
+++ b/lib/pleroma/web/views/frontend/static_view.ex
@@ -2,17 +2,12 @@
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
-defmodule Pleroma.Web.StaticFE.StaticFEView do
+defmodule Pleroma.Web.Frontend.StaticView do
use Pleroma.Web, :view
alias Calendar.Strftime
- alias Pleroma.Emoji.Formatter
alias Pleroma.User
- alias Pleroma.Web.Endpoint
- alias Pleroma.Web.Gettext
- alias Pleroma.Web.MediaProxy
alias Pleroma.Web.Metadata.Utils
- alias Pleroma.Web.Router.Helpers
use Phoenix.HTML
diff --git a/lib/pleroma/web/views/masto_fe_view.ex b/lib/pleroma/web/views/masto_fe_view.ex
index f739dacb6..d11ecd4f4 100644
--- a/lib/pleroma/web/views/masto_fe_view.ex
+++ b/lib/pleroma/web/views/masto_fe_view.ex
@@ -109,7 +109,7 @@ defmodule Pleroma.Web.MastoFEView do
background_color: Config.get([:manifest, :background_color]),
display: "standalone",
scope: Pleroma.Web.base_url(),
- start_url: masto_fe_path(Pleroma.Web.Endpoint, :index, ["getting-started"]),
+ start_url: frontend_mastodon_path(Pleroma.Web.Endpoint, :index, ["getting-started"]),
categories: [
"social"
],
diff --git a/lib/pleroma/web/web.ex b/lib/pleroma/web/web.ex
index 4f9281851..69aebd877 100644
--- a/lib/pleroma/web/web.ex
+++ b/lib/pleroma/web/web.ex
@@ -120,6 +120,8 @@ defmodule Pleroma.Web do
conn
end
end
+
+ defoverridable(action: 2)
end
end
@@ -138,6 +140,8 @@ defmodule Pleroma.Web do
require Logger
+ alias Pleroma.Web.Endpoint
+
@doc "Same as `render/3` but wrapped in a rescue block"
def safe_render(view, template, assigns \\ %{}) do
Phoenix.View.render(view, template, assigns)