diff options
author | Roman Chvanikov <chvanikoff@pm.me> | 2020-04-17 18:36:32 +0300 |
---|---|---|
committer | Roman Chvanikov <chvanikoff@pm.me> | 2020-04-17 18:36:32 +0300 |
commit | 863e61ad5f5bb29369dfc602509a0492d0b7ef7a (patch) | |
tree | 2168212cc999b51f5b3750e912a943218a8c63ad /lib | |
parent | e0d7847bc56c61156c8df0e7a94d728b82bf2d86 (diff) | |
download | pleroma-863e61ad5f5bb29369dfc602509a0492d0b7ef7a.tar.gz |
FE bundles
Diffstat (limited to 'lib')
35 files changed, 906 insertions, 274 deletions
diff --git a/lib/mix/tasks/pleroma/frontend.ex b/lib/mix/tasks/pleroma/frontend.ex new file mode 100644 index 000000000..d701c1404 --- /dev/null +++ b/lib/mix/tasks/pleroma/frontend.ex @@ -0,0 +1,209 @@ +# 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") + + @known_frontends ~w(pleroma kenoma mastodon admin) + @pleroma_gitlab_host "git.pleroma.social" + @projects %{ + "pleroma" => "pleroma/pleroma-fe", + "kenoma" => "lambadalambda/kenoma", + "admin" => "pleroma/admin-fe", + "mastodon" => "pleroma/mastofe" + } + + 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 + {: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, @projects[frontend]) + + shell_info("Installing #{frontend} frontend (version: #{bundle_name}, url: #{bundle_url})") + + 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 + 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 + 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]) + + case primary_fe_config["name"] == frontend do + true -> + primary_fe_config["ref"] + + false -> + nil + end + + val -> + val + end + |> case do + nil -> + stable_pleroma? = Pleroma.Application.stable?() + + current_stable_out = + case stable_pleroma? do + true -> "stable" + false -> "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" + + http_client = http_client() + %{status: 200, body: json} = Tesla.get!(http_client, url) + + %{"name" => name, "commit" => %{"short_id" => last_commit_ref}} = + Enum.find(json, &(&1["default"] == true)) + + %{ + "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" + http_client = http_client() + %{status: 200, body: json} = Tesla.get!(http_client, url) + + [%{"commit" => %{"short_id" => commit_id}, "name" => name} | _] = + Enum.sort(json, fn r1, r2 -> r1 > r2 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 bc842a59f..8bb3f71e8 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, @@ -158,6 +165,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) + + with true <- 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) + + with true <- 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) @@ -186,7 +249,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,25 @@ 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 = + case stable_pleroma? do + true -> "stable" + false -> "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 40dd9bdc0..f15112e9e 100644 --- a/lib/mix/tasks/pleroma/user.ex +++ b/lib/mix/tasks/pleroma/user.ex @@ -272,7 +272,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 a00938c04..e7a031c34 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..92ed17bd4 --- /dev/null +++ b/lib/pleroma/frontend.ex @@ -0,0 +1,28 @@ +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..d3fd90a3c --- /dev/null +++ b/lib/pleroma/plugs/frontend_plug.ex @@ -0,0 +1,47 @@ +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 927fa2663..1436d16ae 100644 --- a/lib/pleroma/plugs/instance_static.ex +++ b/lib/pleroma/plugs/instance_static.ex @@ -1,28 +1,30 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> -# SPDX-License-Identifier: AGPL-3.0-only - defmodule Pleroma.Plugs.InstanceStatic do @moduledoc """ 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) - - if File.exists?(instance_path) do - instance_path - else - Path.join(Application.app_dir(:pleroma, "priv/static/"), path) - end - end + # list of paths to be looked up in intance/static + @instance_overridable_paths ~w(robots.txt emoji sounds images instance favicon.png packs sw.js static index.html sw-pleroma.js static-fe.css) - @only ~w(index.html robots.txt static emoji packs sounds images instance favicon.png sw.js - sw-pleroma.js) + # 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 @@ -31,29 +33,42 @@ defmodule Pleroma.Plugs.InstanceStatic do |> Plug.Static.init() end - for only <- @only do - at = Plug.Router.Utils.split("/") + 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 + + 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} - def call(%{request_path: "/" <> unquote(only) <> _} = conn, opts) do - call_static( - conn, - opts, - unquote(at), - Pleroma.Config.get([:instance, :static_dir], "instance/static") - ) + 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 156e6788e..c12776f4d 100644 --- a/lib/pleroma/plugs/static_fe_plug.ex +++ b/lib/pleroma/plugs/static_fe_plug.ex @@ -4,21 +4,23 @@ 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 accepts_html?(conn) do + def call(%{private: %{frontend: %{static: true}}} = conn, _) do + action = Phoenix.Controller.action_name(conn) + + if accepts_html?(conn) and + function_exported?(Pleroma.Web.Frontend.StaticController, action, 2) 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 accepts_html?(conn) do case get_req_header(conn, "accept") do 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..f2e885e70 --- /dev/null +++ b/lib/pleroma/web/controllers/frontend/admin_controller.ex @@ -0,0 +1,4 @@ +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..9f1711389 --- /dev/null +++ b/lib/pleroma/web/controllers/frontend/default_controller.ex @@ -0,0 +1,36 @@ +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..c61581588 --- /dev/null +++ b/lib/pleroma/web/controllers/frontend/headless_controller.ex @@ -0,0 +1,9 @@ +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..8ea2635fb --- /dev/null +++ b/lib/pleroma/web/controllers/frontend/kenoma_controller.ex @@ -0,0 +1,4 @@ +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..bb2e94220 --- /dev/null +++ b/lib/pleroma/web/controllers/frontend/mastodon_controller.ex @@ -0,0 +1,52 @@ +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..1085d05bf --- /dev/null +++ b/lib/pleroma/web/controllers/frontend/pleroma_controller.ex @@ -0,0 +1,44 @@ +defmodule Pleroma.Web.Frontend.PleromaController do + use Pleroma.Web, :controller + use Pleroma.Web.Frontend.DefaultController + + require Logger + + alias Pleroma.User + alias Pleroma.Web.Metadata + + 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 + + response = String.replace(index_content, "<!--server-generated-meta-->", tags) + + html(conn, response) + end + + defdelegate registration_page(conn, params), to: __MODULE__, as: :index +end diff --git a/lib/pleroma/web/static_fe/static_fe_controller.ex b/lib/pleroma/web/controllers/frontend/static_controller.ex index 7a35238d7..0746a9a6d 100644 --- a/lib/pleroma/web/static_fe/static_fe_controller.ex +++ b/lib/pleroma/web/controllers/frontend/static_controller.ex @@ -1,8 +1,4 @@ -# Pleroma: A lightweight social networking server -# 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 +7,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?/0 @@ -23,65 +16,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"]) - - link = - case user.local do - true -> Helpers.o_status_url(Pleroma.Web.Endpoint, :notice, activity) - _ -> data["url"] || data["external_url"] || data["id"] - end + def object(conn, %{"uuid" => _uuid}) do + url = url(conn) <> conn.request_path - 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 +54,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}) @@ -134,12 +82,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) _ -> @@ -147,30 +95,61 @@ 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 = + case user.local do + true -> o_status_url(Pleroma.Web.Endpoint, :notice, activity) + _ -> 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", %{})) + else + nil + 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 72cb3ee27..2616273de 100644 --- a/lib/pleroma/web/endpoint.ex +++ b/lib/pleroma/web/endpoint.ex @@ -11,6 +11,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" @@ -35,7 +36,7 @@ defmodule Pleroma.Web.Endpoint do at: "/", from: :pleroma, only: - ~w(index.html robots.txt static finmoji emoji packs sounds images instance sw.js sw-pleroma.js favicon.png schemas doc), + ~w(robots.txt static-fe.css finmoji emoji sounds images instance favicon.png schemas doc), # credo:disable-for-previous-line Credo.Check.Readability.MaxLineLength gzip: true, cache_control_for_etags: @static_cache_control, @@ -44,13 +45,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 c13518030..000000000 --- a/lib/pleroma/web/fallback_redirect_controller.ex +++ /dev/null @@ -1,77 +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 - - def api_not_implemented(conn, _params) do - conn - |> put_status(404) - |> json(%{error: "Not implemented"}) - end - - def redirector(conn, _params, code \\ 200) - - # redirect to admin section - # /pleroma/admin -> /pleroma/admin/ - # - def redirector(conn, %{"path" => ["pleroma", "admin"]} = _, _code) do - redirect(conn, to: "/pleroma/admin/") - end - - def redirector(conn, _params, code) 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 = - try do - Metadata.build_tags(params) - rescue - e -> - Logger.error( - "Metadata rendering for #{conn.request_path} failed.\n" <> - Exception.format(:error, e, __STACKTRACE__) - ) - - "" - end - - response = String.replace(index_content, "<!--server-generated-meta-->", tags) - - conn - |> put_resp_content_type("text/html") - |> send_resp(200, response) - end - - def index_file_path do - Pleroma.Plugs.InstanceStatic.file_path("index.html") - end - - def registration_page(conn, params) do - redirector(conn, params) - end - - def empty(conn, _params) do - conn - |> put_status(204) - |> text("") - end -end diff --git a/lib/pleroma/web/feed/user_controller.ex b/lib/pleroma/web/feed/user_controller.ex index e27f85929..3bad4663d 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 @@ -15,11 +14,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..e236119a0 --- /dev/null +++ b/lib/pleroma/web/frontend_controller.ex @@ -0,0 +1,74 @@ +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 = + with nil <- conn.private[:frontend] do + get_primary_fe_opts() + end + + # 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"} + 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 37b389382..70edeae7c 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 6fd3cfce5..57682c1ea 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, %{"uuid" => _uuid}) @@ -37,14 +38,12 @@ defmodule Pleroma.Web.OStatus.OStatusController do ActivityPubController.call(conn, :object) end - def object(%{assigns: %{format: format}} = conn, %{"uuid" => uuid}) do + def object(conn, %{"uuid" => uuid}) do with id <- o_status_url(conn, :object, uuid), {_, %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, %{"uuid" => uuid}) do + def activity(conn, %{"uuid" => uuid}) do with id <- o_status_url(conn, :activity, uuid), {_, %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 7e5960949..34eff708c 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -447,7 +447,7 @@ defmodule Pleroma.Web.Router do scope "/api/web", Pleroma.Web do pipe_through(:authenticated_api) - put("/settings", MastoFEController, :put_settings) + put("/settings", FrontendController, :put_settings, as: :frontend_mastodon) end scope "/api/v1", Pleroma.Web.MastodonAPI do @@ -535,16 +535,18 @@ defmodule Pleroma.Web.Router do pipeline :ostatus do plug(:accepts, ["html", "xml", "rss", "atom", "activity+json", "json"]) - plug(Pleroma.Plugs.StaticFEPlug) end + # pipeline :static_fe do + # plug(Pleroma.Plugs.StaticFEPlug) + # end + pipeline :oembed do plug(:accepts, ["json", "xml"]) end scope "/", Pleroma.Web do - pipe_through(:ostatus) - pipe_through(:http_signature) + pipe_through([:http_signature, :ostatus]) get("/objects/:uuid", OStatus.OStatusController, :object) get("/activities/:uuid", OStatus.OStatusController, :activity) @@ -646,7 +648,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 @@ -657,15 +659,10 @@ defmodule Pleroma.Web.Router do post("/auth/password", MastodonAPI.AuthController, :password_reset) - get("/web/*path", MastoFEController, :index) - end - - pipeline :remote_media do + get("/web/*path", FrontendController, :index, as: :frontend_mastodon) end scope "/proxy/", Pleroma.Web.MediaProxy do - pipe_through(:remote_media) - get("/:sig/:url", MediaProxyController, :remote) get("/:sig/:url/:filename", MediaProxyController, :remote) end @@ -694,12 +691,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) + 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 7e04e9550..7e04e9550 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 df5e5eedd..44171c86c 100644 --- a/lib/pleroma/web/templates/static_fe/static_fe/_notice.html.eex +++ b/lib/pleroma/web/templates/frontend/static/_notice.html.eex @@ -15,7 +15,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)}) %> @@ -29,9 +29,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 56f3a1524..ba96cca41 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 src="<%= User.avatar_url(@user) |> MediaProxy.url %>" width="48" height="48" alt=""> + <img src="<%= User.avatar_url(@user) |> Pleroma.Web.MediaProxy.url() %>" width="48" height="48" alt=""> </div> <span class="display-name"> - <bdi><%= raw Formatter.emojify(@user.name, @user.emoji) %></bdi> + <bdi><%= 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 819632cec..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/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 c3096006e..eb977b1bd 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 ae7c94640..ec1ab4664 100644 --- a/lib/pleroma/web/web.ex +++ b/lib/pleroma/web/web.ex @@ -50,7 +50,7 @@ defmodule Pleroma.Web do end # Here we can apply before-action hooks (e.g. verify whether auth checks were preformed) - defp action(conn, params) do + def action(conn, params) do if Pleroma.Plugs.AuthExpectedPlug.auth_expected?(conn) && not PlugHelper.plug_called_or_skipped?(conn, Pleroma.Plugs.OAuthScopesPlug) do conn @@ -63,6 +63,8 @@ defmodule Pleroma.Web do super(conn, params) end end + + defoverridable(action: 2) end end @@ -81,6 +83,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) |