about summary refs log tree commit diff
path: root/pkgs/akkoma/0001-Migrate-to-phoenix-1.7.patch
diff options
context:
space:
mode:
Diffstat (limited to 'pkgs/akkoma/0001-Migrate-to-phoenix-1.7.patch')
-rw-r--r--pkgs/akkoma/0001-Migrate-to-phoenix-1.7.patch2390
1 files changed, 2390 insertions, 0 deletions
diff --git a/pkgs/akkoma/0001-Migrate-to-phoenix-1.7.patch b/pkgs/akkoma/0001-Migrate-to-phoenix-1.7.patch
new file mode 100644
index 0000000..d4cc790
--- /dev/null
+++ b/pkgs/akkoma/0001-Migrate-to-phoenix-1.7.patch
@@ -0,0 +1,2390 @@
+From ed0cdb8b2be55b9f33f93b3ef2c7e2fa46ba4eb3 Mon Sep 17 00:00:00 2001
+From: sefidel <contact@sefidel.net>
+Date: Mon, 19 Feb 2024 21:06:43 +0900
+Subject: [PATCH 1/3] Migrate to phoenix 1.7
+
+Original commit: https://akkoma.dev/AkkomaGang/akkoma/commit/6cb40bee26
+Backported to v3.10.4
+
+Signed-off-by: sefidel <contact@sefidel.net>
+---
+ .formatter.exs                                | 13 ++++-
+ .woodpecker/lint.yml                          | 55 ++++++++++++++++++
+ .woodpecker/test.yml                          | 40 ++++---------
+ CHANGELOG.md                                  | 10 +++-
+ config/config.exs                             | 16 +----
+ lib/mix/tasks/pleroma/emoji.ex                |  2 +
+ lib/mix/tasks/pleroma/user.ex                 | 15 +----
+ lib/phoenix/transports/web_socket/raw.ex      |  1 -
+ lib/pleroma/emails/admin_email.ex             |  9 ++-
+ lib/pleroma/emails/user_email.ex              | 22 ++-----
+ lib/pleroma/user.ex                           |  9 +--
+ lib/pleroma/web.ex                            | 29 ++++++++--
+ lib/pleroma/web/activity_pub/activity_pub.ex  |  5 ++
+ lib/pleroma/web/activity_pub/builder.ex       |  4 +-
+ lib/pleroma/web/activity_pub/utils.ex         | 15 ++---
+ .../web/activity_pub/views/user_view.ex       | 14 ++---
+ .../controllers/admin_api_controller.ex       |  4 +-
+ lib/pleroma/web/endpoint.ex                   |  2 +-
+ lib/pleroma/web/feed/user_controller.ex       |  2 +-
+ lib/pleroma/web/masto_fe_controller.ex        |  4 +-
+ .../controllers/auth_controller.ex            |  9 +--
+ .../web/mastodon_api/views/status_view.ex     |  2 +-
+ .../web/mastodon_api/views/tag_view.ex        |  3 +-
+ lib/pleroma/web/metadata/providers/feed.ex    |  6 +-
+ .../web/metadata/providers/twitter_card.ex    |  4 +-
+ lib/pleroma/web/o_auth/o_auth_controller.ex   |  4 +-
+ .../web/o_status/o_status_controller.ex       |  3 +-
+ lib/pleroma/web/plugs/http_signature_plug.ex  |  7 ++-
+ .../web/static_fe/static_fe_controller.ex     | 11 ++--
+ lib/pleroma/web/static_fe/static_fe_view.ex   |  1 -
+ .../frontend_switcher/switch.html.eex         |  2 +-
+ .../web/templates/feed/feed/tag.atom.eex      |  4 +-
+ .../web/templates/feed/feed/tag.rss.eex       |  2 +-
+ .../web/templates/feed/feed/user.atom.eex     |  6 +-
+ .../web/templates/feed/feed/user.rss.eex      |  6 +-
+ .../web/templates/masto_fe/fedibird.html.heex | 58 +++++++++++++++++++
+ .../masto_fe/fedibird.index.html.eex          | 35 -----------
+ .../templates/masto_fe/glitchsoc.html.heex    | 57 ++++++++++++++++++
+ .../masto_fe/glitchsoc.index.html.eex         | 35 -----------
+ .../templates/o_auth/mfa/recovery.html.eex    | 12 ++--
+ .../web/templates/o_auth/mfa/totp.html.eex    | 12 ++--
+ .../templates/o_auth/o_auth/consumer.html.eex |  2 +-
+ .../templates/o_auth/o_auth/register.html.eex | 10 ++--
+ .../web/templates/o_auth/o_auth/show.html.eex | 10 ++--
+ .../static_fe/static_fe/profile.html.eex      |  2 +-
+ .../twitter_api/password/reset.html.eex       |  2 +-
+ .../twitter_api/remote_follow/follow.html.eex |  2 +-
+ .../remote_follow/follow_login.html.eex       |  2 +-
+ .../remote_follow/follow_mfa.html.eex         |  2 +-
+ .../twitter_api/util/status_interact.html.eex |  2 +-
+ .../twitter_api/util/subscribe.html.eex       |  2 +-
+ .../controllers/remote_follow_controller.ex   |  2 +-
+ lib/pleroma/web/views/embed_view.ex           |  3 +-
+ lib/pleroma/web/views/masto_fe_view.ex        |  2 +-
+ mix.exs                                       |  7 ++-
+ mix.lock                                      | 12 ++--
+ test/pleroma/emails/admin_email_test.exs      |  3 +-
+ test/pleroma/emails/user_email_test.exs       | 10 +---
+ test/pleroma/integration/federation_test.exs  |  5 +-
+ test/pleroma/user_test.exs                    | 14 +----
+ .../activity_pub_controller_test.exs          | 10 ++--
+ .../web/activity_pub/activity_pub_test.exs    |  8 +++
+ .../controllers/admin_api_controller_test.exs |  3 +-
+ .../controllers/report_controller_test.exs    | 10 ++--
+ .../controllers/user_controller_test.exs      |  6 +-
+ test/pleroma/web/feed/tag_controller_test.exs |  8 +--
+ .../pleroma/web/feed/user_controller_test.exs | 12 ++--
+ .../mastodon_api/update_credentials_test.exs  |  2 +-
+ .../mastodon_api/views/status_view_test.exs   |  2 +-
+ .../metadata/providers/twitter_card_test.exs  |  3 +-
+ .../web/mongoose_im_controller_test.exs       | 16 ++---
+ .../web/o_auth/o_auth_controller_test.exs     | 12 ++--
+ .../remote_follow_controller_test.exs         | 42 ++++++--------
+ test/pleroma/web/uploader_controller_test.exs |  4 +-
+ test/support/conn_case.ex                     |  2 +-
+ test/support/data_case.ex                     |  2 +
+ 76 files changed, 431 insertions(+), 355 deletions(-)
+ create mode 100644 .woodpecker/lint.yml
+ create mode 100644 lib/pleroma/web/templates/masto_fe/fedibird.html.heex
+ delete mode 100644 lib/pleroma/web/templates/masto_fe/fedibird.index.html.eex
+ create mode 100644 lib/pleroma/web/templates/masto_fe/glitchsoc.html.heex
+ delete mode 100644 lib/pleroma/web/templates/masto_fe/glitchsoc.index.html.eex
+
+diff --git a/.formatter.exs b/.formatter.exs
+index abd91dbbe..a96afe758 100644
+--- a/.formatter.exs
++++ b/.formatter.exs
+@@ -1,3 +1,14 @@
+ [
+-  inputs: ["mix.exs", "{config,lib,test}/**/*.{ex,exs}", "priv/repo/migrations/*.exs", "priv/repo/optional_migrations/**/*.exs", "priv/scrubbers/*.ex"]
++  import_deps: [:ecto, :ecto_sql, :phoenix],
++  subdirectories: ["priv/*/migrations"],
++  plugins: [Phoenix.LiveView.HTMLFormatter],
++  inputs: [
++    "mix.exs",
++    "*.{heex,ex,exs}",
++    "{config,lib,test}/**/*.{heex,ex,exs}",
++    "priv/*/seeds.exs",
++    "priv/repo/migrations/*.exs",
++    "priv/repo/optional_migrations/**/*.exs",
++    "priv/scrubbers/*.ex"
++  ]
+ ]
+diff --git a/.woodpecker/lint.yml b/.woodpecker/lint.yml
+new file mode 100644
+index 000000000..8308e57d7
+--- /dev/null
++++ b/.woodpecker/lint.yml
+@@ -0,0 +1,55 @@
++platform: linux/amd64
++
++variables:
++  - &scw-secrets
++    - SCW_ACCESS_KEY
++    - SCW_SECRET_KEY
++    - SCW_DEFAULT_ORGANIZATION_ID
++  - &setup-hex "mix local.hex --force && mix local.rebar --force"
++  - &on-release
++    when:
++      event:
++        - push
++        - tag
++      branch:
++        - develop
++        - stable
++        - refs/tags/v*
++        - refs/tags/stable-*
++  - &on-stable
++    when:
++      event:
++        - push
++        - tag
++      branch:
++        - stable
++        - refs/tags/stable-*
++  - &on-point-release
++    when:
++      event:
++        - push
++      branch:
++        - develop
++        - stable
++  - &on-pr-open
++    when:
++      event:
++        - pull_request
++
++  - &tag-build "export BUILD_TAG=$${CI_COMMIT_TAG:-\"$CI_COMMIT_BRANCH\"} && export PLEROMA_BUILD_BRANCH=$BUILD_TAG"
++
++  - &clean "(rm -rf release || true) && (rm -rf _build || true) && (rm -rf /root/.mix)"
++  - &mix-clean "mix deps.clean --all && mix clean"
++
++pipeline:
++  lint:
++    image: akkoma/ci-base:1.15-otp26
++    <<: *on-pr-open
++    environment:
++      MIX_ENV: test
++    commands:
++      - mix local.hex --force
++      - mix local.rebar --force
++      - mix deps.get
++      - mix compile
++      - mix format --check-formatted
+diff --git a/.woodpecker/test.yml b/.woodpecker/test.yml
+index be8ea0dfa..3a8a1652e 100644
+--- a/.woodpecker/test.yml
++++ b/.woodpecker/test.yml
+@@ -1,5 +1,8 @@
+ platform: linux/amd64
+ 
++depends_on:
++  - lint
++
+ matrix:
+   ELIXIR_VERSION:
+     - 1.14
+@@ -69,15 +72,7 @@ services:
+       POSTGRES_PASSWORD: postgres
+ 
+ pipeline:
+-  lint:
+-    <<: *on-pr-open
+-    image: akkoma/ci-base:1.15
+-    commands:
+-    - mix local.hex --force
+-    - mix local.rebar --force
+-    - mix format --check-formatted
+-
+-  build:
++  test:
+     image: akkoma/ci-base:${ELIXIR_VERSION}-otp${OTP_VERSION}
+     <<: *on-pr-open
+     environment:
+@@ -91,24 +86,9 @@ pipeline:
+       - mix local.rebar --force
+       - mix deps.get
+       - mix compile
+-
+-  test:
+-    image: akkoma/ci-base:${ELIXIR_VERSION}-otp${OTP_VERSION}
+-    <<: *on-pr-open
+-    environment:
+-      MIX_ENV: test
+-      POSTGRES_DB: pleroma_test_${ELIXIR_VERSION}_${OTP_VERSION}
+-      POSTGRES_USER: postgres
+-      POSTGRES_PASSWORD: postgres
+-      DB_HOST: postgres
+-    commands:
+-    - mix local.hex --force
+-    - mix local.rebar --force
+-    - mix deps.get
+-    - mix compile
+-    - mix ecto.drop -f -q
+-    - mix ecto.create
+-    - mix ecto.migrate
+-    - mkdir -p test/tmp
+-    - mix test --preload-modules --exclude erratic --exclude federated --exclude mocked
+-    - mix test --preload-modules --only mocked
++      - mix ecto.drop -f -q
++      - mix ecto.create
++      - mix ecto.migrate
++      - mkdir -p test/tmp
++      - mix test --preload-modules --exclude erratic --exclude federated --exclude mocked
++      - mix test --preload-modules --only mocked
+diff --git a/CHANGELOG.md b/CHANGELOG.md
+index 92b3d1a71..a6c356bab 100644
+--- a/CHANGELOG.md
++++ b/CHANGELOG.md
+@@ -6,8 +6,16 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
+ 
+ ## Unreleased
+ 
++## Added
++- Full compatibility with Erlang OTP26
++- handling of GET /api/v1/preferences
++
++## Changed
++- OTP builds are now built on erlang OTP26
++- The base Phoenix framework is now updated to 1.7
++
+ ## Fixed
+-- Issue where a bad inbox URL could break federation
++- Documentation issue in which a non-existing nginx file was referenced
+ 
+ ## 2023.08
+ 
+diff --git a/config/config.exs b/config/config.exs
+index 3430ee4d7..0a3cbde3f 100644
+--- a/config/config.exs
++++ b/config/config.exs
+@@ -110,17 +110,6 @@
+     "xmpp"
+   ]
+ 
+-websocket_config = [
+-  path: "/websocket",
+-  serializer: [
+-    {Phoenix.Socket.V1.JSONSerializer, "~> 1.0.0"},
+-    {Phoenix.Socket.V2.JSONSerializer, "~> 2.0.0"}
+-  ],
+-  timeout: 60_000,
+-  transport_log: false,
+-  compress: false
+-]
+-
+ # Configures the endpoint
+ config :pleroma, Pleroma.Web.Endpoint,
+   url: [host: "localhost"],
+@@ -130,10 +119,7 @@
+       {:_,
+        [
+          {"/api/v1/streaming", Pleroma.Web.MastodonAPI.WebsocketHandler, []},
+-         {"/websocket", Phoenix.Endpoint.CowboyWebSocket,
+-          {Phoenix.Transports.WebSocket,
+-           {Pleroma.Web.Endpoint, Pleroma.Web.UserSocket, websocket_config}}},
+-         {:_, Phoenix.Endpoint.Cowboy2Handler, {Pleroma.Web.Endpoint, []}}
++         {:_, Plug.Cowboy.Handler, {Pleroma.Web.Endpoint, []}}
+        ]}
+     ]
+   ],
+diff --git a/lib/mix/tasks/pleroma/emoji.ex b/lib/mix/tasks/pleroma/emoji.ex
+index 5dedf276a..89df511a7 100644
+--- a/lib/mix/tasks/pleroma/emoji.ex
++++ b/lib/mix/tasks/pleroma/emoji.ex
+@@ -235,6 +235,8 @@ def run(["gen-pack" | args]) do
+ 
+       IO.puts("#{pack_file} has been created with the #{name} pack")
+     end
++
++    Pleroma.Emoji.reload()
+   end
+ 
+   def run(["reload"]) do
+diff --git a/lib/mix/tasks/pleroma/user.ex b/lib/mix/tasks/pleroma/user.ex
+index 4ca1c28eb..1a8e866ef 100644
+--- a/lib/mix/tasks/pleroma/user.ex
++++ b/lib/mix/tasks/pleroma/user.ex
+@@ -11,6 +11,7 @@ defmodule Mix.Tasks.Pleroma.User do
+   alias Pleroma.UserInviteToken
+   alias Pleroma.Web.ActivityPub.Builder
+   alias Pleroma.Web.ActivityPub.Pipeline
++  use Pleroma.Web, :verified_routes
+ 
+   @shortdoc "Manages Pleroma users"
+   @moduledoc File.read!("docs/docs/administration/CLI_tasks/user.md")
+@@ -113,11 +114,7 @@ def run(["reset_password", nickname]) do
+          {:ok, token} <- Pleroma.PasswordResetToken.create_token(user) do
+       shell_info("Generated password reset token for #{user.nickname}")
+ 
+-      IO.puts(
+-        "URL: #{Pleroma.Web.Router.Helpers.reset_password_url(Pleroma.Web.Endpoint,
+-        :reset,
+-        token.token)}"
+-      )
++      IO.puts("URL: #{~p[/api/v1/pleroma/password_reset/#{token.token}]}")
+     else
+       _ ->
+         shell_error("No local user #{nickname}")
+@@ -303,13 +300,7 @@ def run(["invite" | rest]) do
+          {:ok, invite} <- UserInviteToken.create_invite(options) do
+       shell_info("Generated user invite token " <> String.replace(invite.invite_type, "_", " "))
+ 
+-      url =
+-        Pleroma.Web.Router.Helpers.redirect_url(
+-          Pleroma.Web.Endpoint,
+-          :registration_page,
+-          invite.token
+-        )
+-
++      url = url(~p[/registration/#{invite.token}])
+       IO.puts(url)
+     else
+       error ->
+diff --git a/lib/phoenix/transports/web_socket/raw.ex b/lib/phoenix/transports/web_socket/raw.ex
+index 8ed64eb16..72def9dff 100644
+--- a/lib/phoenix/transports/web_socket/raw.ex
++++ b/lib/phoenix/transports/web_socket/raw.ex
+@@ -26,7 +26,6 @@ def init(%Plug.Conn{method: "GET"} = conn, {endpoint, handler, transport}) do
+       conn
+       |> fetch_query_params
+       |> Transport.transport_log(opts[:transport_log])
+-      |> Transport.force_ssl(handler, endpoint, opts)
+       |> Transport.check_origin(handler, endpoint, opts)
+ 
+     case conn do
+diff --git a/lib/pleroma/emails/admin_email.ex b/lib/pleroma/emails/admin_email.ex
+index 88bc78aec..683de8e3b 100644
+--- a/lib/pleroma/emails/admin_email.ex
++++ b/lib/pleroma/emails/admin_email.ex
+@@ -6,10 +6,13 @@ defmodule Pleroma.Emails.AdminEmail do
+   @moduledoc "Admin emails"
+ 
+   import Swoosh.Email
+-
++  use Pleroma.Web, :mailer
+   alias Pleroma.Config
+   alias Pleroma.HTML
+-  alias Pleroma.Web.Router.Helpers
++
++  use Phoenix.VerifiedRoutes,
++    endpoint: Pleroma.Web.Endpoint,
++    router: Pleroma.Web.Router
+ 
+   defp instance_config, do: Config.get(:instance)
+   defp instance_name, do: instance_config()[:name]
+@@ -45,7 +48,7 @@ def report(to, reporter, account, statuses, comment) do
+           statuses
+           |> Enum.map(fn
+             %{id: id} ->
+-              status_url = Helpers.o_status_url(Pleroma.Web.Endpoint, :notice, id)
++              status_url = url(~p[/notice/#{id}])
+               "<li><a href=\"#{status_url}\">#{status_url}</li>"
+ 
+             %{"id" => id} when is_binary(id) ->
+diff --git a/lib/pleroma/emails/user_email.ex b/lib/pleroma/emails/user_email.ex
+index 1588c099c..fe319c775 100644
+--- a/lib/pleroma/emails/user_email.ex
++++ b/lib/pleroma/emails/user_email.ex
+@@ -6,12 +6,11 @@ defmodule Pleroma.Emails.UserEmail do
+   @moduledoc "User emails"
+ 
+   require Pleroma.Web.Gettext
++  use Pleroma.Web, :mailer
+ 
+   alias Pleroma.Config
+   alias Pleroma.User
+-  alias Pleroma.Web.Endpoint
+   alias Pleroma.Web.Gettext
+-  alias Pleroma.Web.Router
+ 
+   import Swoosh.Email
+   import Phoenix.Swoosh, except: [render_body: 3]
+@@ -75,7 +74,7 @@ def welcome(user, opts \\ %{}) do
+ 
+   def password_reset_email(user, token) when is_binary(token) do
+     Gettext.with_locale_or_default user.language do
+-      password_reset_url = Router.Helpers.reset_password_url(Endpoint, :reset, token)
++      password_reset_url = ~p[/api/v1/pleroma/password_reset/#{token}]
+ 
+       html_body =
+         Gettext.dpgettext(
+@@ -108,12 +107,7 @@ def user_invitation_email(
+         to_name \\ nil
+       ) do
+     Gettext.with_locale_or_default user.language do
+-      registration_url =
+-        Router.Helpers.redirect_url(
+-          Endpoint,
+-          :registration_page,
+-          user_invite_token.token
+-        )
++      registration_url = ~p[/registration/#{user_invite_token.token}]
+ 
+       html_body =
+         Gettext.dpgettext(
+@@ -146,13 +140,7 @@ def user_invitation_email(
+ 
+   def account_confirmation_email(user) do
+     Gettext.with_locale_or_default user.language do
+-      confirmation_url =
+-        Router.Helpers.confirm_email_url(
+-          Endpoint,
+-          :confirm_email,
+-          user.id,
+-          to_string(user.confirmation_token)
+-        )
++      confirmation_url = ~p[/api/account/confirm_email/#{user.id}/#{user.confirmation_token}]
+ 
+       html_body =
+         Gettext.dpgettext(
+@@ -342,7 +330,7 @@ def unsubscribe_url(user, notifications_type) do
+       |> Pleroma.JWT.generate_and_sign!()
+       |> Base.encode64()
+ 
+-    Router.Helpers.subscription_url(Endpoint, :unsubscribe, token)
++    ~p[/mailer/unsubscribe/#{token}]
+   end
+ 
+   def backup_is_ready_email(backup, admin_user_id \\ nil) do
+diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex
+index 83b45e3b4..ad87837fa 100644
+--- a/lib/pleroma/user.ex
++++ b/lib/pleroma/user.ex
+@@ -44,6 +44,8 @@ defmodule Pleroma.User do
+   alias Pleroma.Web.RelMe
+   alias Pleroma.Workers.BackgroundWorker
+ 
++  use Pleroma.Web, :verified_routes
++
+   require Logger
+ 
+   @type t :: %__MODULE__{}
+@@ -2447,12 +2449,7 @@ defp validate_rel_me_field(changeset, fields, raw_fields, %User{
+           end
+ 
+         if is_url(raw_value) do
+-          frontend_url =
+-            Pleroma.Web.Router.Helpers.redirect_url(
+-              Pleroma.Web.Endpoint,
+-              :redirector_with_meta,
+-              nickname
+-            )
++          frontend_url = url(~p[/#{nickname}])
+ 
+           possible_urls = [ap_id, frontend_url]
+ 
+diff --git a/lib/pleroma/web.ex b/lib/pleroma/web.ex
+index ecd98b6ca..5422e7896 100644
+--- a/lib/pleroma/web.ex
++++ b/lib/pleroma/web.ex
+@@ -27,6 +27,7 @@ defmodule Pleroma.Web do
+   alias Pleroma.Web.Plugs.ExpectPublicOrAuthenticatedCheckPlug
+   alias Pleroma.Web.Plugs.OAuthScopesPlug
+   alias Pleroma.Web.Plugs.PlugHelper
++  require Pleroma.Constants
+ 
+   def controller do
+     quote do
+@@ -37,7 +38,7 @@ def controller do
+       import Pleroma.Web.Gettext
+       import Pleroma.Web.TranslationHelpers
+ 
+-      alias Pleroma.Web.Router.Helpers, as: Routes
++      unquote(verified_routes())
+ 
+       plug(:set_put_layout)
+ 
+@@ -184,7 +185,10 @@ def view do
+ 
+       # Import convenience functions from controllers
+       import Phoenix.Controller,
+-        only: [get_flash: 1, get_flash: 2, view_module: 1, view_template: 1]
++        only: [view_module: 1, view_template: 1]
++
++      import Phoenix.Flash
++      alias Phoenix.Flash
+ 
+       # Include shared imports and aliases for views
+       unquote(view_helpers())
+@@ -218,7 +222,7 @@ def component do
+ 
+   def router do
+     quote do
+-      use Phoenix.Router
++      use Phoenix.Router, helpers: false
+ 
+       import Plug.Conn
+       import Phoenix.Controller
+@@ -246,7 +250,24 @@ defp view_helpers do
+ 
+       import Pleroma.Web.ErrorHelpers
+       import Pleroma.Web.Gettext
+-      alias Pleroma.Web.Router.Helpers, as: Routes
++      unquote(verified_routes())
++    end
++  end
++
++  def static_paths, do: Pleroma.Constants.static_only_files()
++
++  def verified_routes do
++    quote do
++      use Phoenix.VerifiedRoutes,
++        endpoint: Pleroma.Web.Endpoint,
++        router: Pleroma.Web.Router,
++        statics: Pleroma.Web.static_paths()
++    end
++  end
++
++  def mailer do
++    quote do
++      unquote(verified_routes())
+     end
+   end
+ 
+diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex
+index 649bf9095..e4c626d36 100644
+--- a/lib/pleroma/web/activity_pub/activity_pub.ex
++++ b/lib/pleroma/web/activity_pub/activity_pub.ex
+@@ -1792,6 +1792,11 @@ def pin_data_from_featured_collection(
+     end)
+   end
+ 
++  def pin_data_from_featured_collection(obj) do
++    Logger.error("Could not parse featured collection #{inspect(obj)}")
++    %{}
++  end
++
+   def fetch_and_prepare_featured_from_ap_id(nil) do
+     {:ok, %{}}
+   end
+diff --git a/lib/pleroma/web/activity_pub/builder.ex b/lib/pleroma/web/activity_pub/builder.ex
+index 6d39ad3a8..e67a14b58 100644
+--- a/lib/pleroma/web/activity_pub/builder.ex
++++ b/lib/pleroma/web/activity_pub/builder.ex
+@@ -18,6 +18,8 @@ defmodule Pleroma.Web.ActivityPub.Builder do
+   alias Pleroma.Web.CommonAPI.ActivityDraft
+   alias Pleroma.Web.Endpoint
+ 
++  use Pleroma.Web, :verified_routes
++
+   require Pleroma.Constants
+ 
+   def accept_or_reject(actor, activity, type) do
+@@ -402,6 +404,6 @@ def unpin(%User{} = user, object) do
+   end
+ 
+   defp pinned_url(nickname) when is_binary(nickname) do
+-    Pleroma.Web.Router.Helpers.activity_pub_url(Pleroma.Web.Endpoint, :pinned, nickname)
++    url(~p[/users/#{nickname}/collections/featured])
+   end
+ end
+diff --git a/lib/pleroma/web/activity_pub/utils.ex b/lib/pleroma/web/activity_pub/utils.ex
+index 008aec475..f731b5286 100644
+--- a/lib/pleroma/web/activity_pub/utils.ex
++++ b/lib/pleroma/web/activity_pub/utils.ex
+@@ -16,10 +16,11 @@ defmodule Pleroma.Web.ActivityPub.Utils do
+   alias Pleroma.Web.ActivityPub.Visibility
+   alias Pleroma.Web.AdminAPI.AccountView
+   alias Pleroma.Web.Endpoint
+-  alias Pleroma.Web.Router.Helpers
+ 
+   import Ecto.Query
+ 
++  use Pleroma.Web, :verified_routes
++
+   require Logger
+   require Pleroma.Constants
+ 
+@@ -124,19 +125,15 @@ def make_date do
+   end
+ 
+   def generate_activity_id do
+-    generate_id("activities")
++    url(~p[/activities/#{UUID.generate()}])
+   end
+ 
+   def generate_context_id do
+-    generate_id("contexts")
++    url(~p[/contexts/#{UUID.generate()}])
+   end
+ 
+   def generate_object_id do
+-    Helpers.o_status_url(Endpoint, :object, UUID.generate())
+-  end
+-
+-  def generate_id(type) do
+-    "#{Endpoint.url()}/#{type}/#{UUID.generate()}"
++    url(~p[/objects/#{UUID.generate()}])
+   end
+ 
+   def get_notified_from_object(%{"type" => type} = object) when type in @supported_object_types do
+@@ -154,7 +151,7 @@ def get_notified_from_object(object) do
+     Notification.get_notified_from_activity(%Activity{data: object}, false)
+   end
+ 
+-  def maybe_create_context(context), do: context || generate_id("contexts")
++  def maybe_create_context(context), do: context || generate_context_id()
+ 
+   @doc """
+   Enqueues an activity for federation if it's local
+diff --git a/lib/pleroma/web/activity_pub/views/user_view.ex b/lib/pleroma/web/activity_pub/views/user_view.ex
+index 7333fb2c1..82b59c47f 100644
+--- a/lib/pleroma/web/activity_pub/views/user_view.ex
++++ b/lib/pleroma/web/activity_pub/views/user_view.ex
+@@ -12,24 +12,22 @@ defmodule Pleroma.Web.ActivityPub.UserView do
+   alias Pleroma.Web.ActivityPub.ObjectView
+   alias Pleroma.Web.ActivityPub.Transmogrifier
+   alias Pleroma.Web.ActivityPub.Utils
+-  alias Pleroma.Web.Endpoint
+-  alias Pleroma.Web.Router.Helpers
+ 
+   require Pleroma.Web.ActivityPub.Transmogrifier
+ 
+   import Ecto.Query
+ 
+   def render("endpoints.json", %{user: %User{nickname: nil, local: true} = _user}) do
+-    %{"sharedInbox" => Helpers.activity_pub_url(Endpoint, :inbox)}
++    %{"sharedInbox" => ~p"/inbox"}
+   end
+ 
+   def render("endpoints.json", %{user: %User{local: true} = _user}) do
+     %{
+-      "oauthAuthorizationEndpoint" => Helpers.o_auth_url(Endpoint, :authorize),
+-      "oauthRegistrationEndpoint" => Helpers.app_url(Endpoint, :create),
+-      "oauthTokenEndpoint" => Helpers.o_auth_url(Endpoint, :token_exchange),
+-      "sharedInbox" => Helpers.activity_pub_url(Endpoint, :inbox),
+-      "uploadMedia" => Helpers.activity_pub_url(Endpoint, :upload_media)
++      "oauthAuthorizationEndpoint" => ~p"/oauth/authorize",
++      "oauthRegistrationEndpoint" => ~p"/api/v1/apps",
++      "oauthTokenEndpoint" => ~p"/oauth/token",
++      "sharedInbox" => ~p"/inbox",
++      "uploadMedia" => ~p"/api/ap/upload_media"
+     }
+   end
+ 
+diff --git a/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex b/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex
+index 1d7ac78a0..7344e1f77 100644
+--- a/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex
++++ b/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex
+@@ -17,9 +17,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
+   alias Pleroma.Web.AdminAPI
+   alias Pleroma.Web.AdminAPI.AccountView
+   alias Pleroma.Web.AdminAPI.ModerationLogView
+-  alias Pleroma.Web.Endpoint
+   alias Pleroma.Web.Plugs.OAuthScopesPlug
+-  alias Pleroma.Web.Router
+ 
+   @users_page_size 50
+ 
+@@ -256,7 +254,7 @@ def get_password_reset(conn, %{"nickname" => nickname}) do
+     conn
+     |> json(%{
+       token: token.token,
+-      link: Router.Helpers.reset_password_url(Endpoint, :reset, token.token)
++      link: url(~p[/api/v1/pleroma/password_reset/#{token.token}])
+     })
+   end
+ 
+diff --git a/lib/pleroma/web/endpoint.ex b/lib/pleroma/web/endpoint.ex
+index e3a251ca1..64593767d 100644
+--- a/lib/pleroma/web/endpoint.ex
++++ b/lib/pleroma/web/endpoint.ex
+@@ -97,7 +97,7 @@ defmodule Pleroma.Web.Endpoint do
+     Plug.Static,
+     at: "/",
+     from: :pleroma,
+-    only: Pleroma.Constants.static_only_files(),
++    only: Pleroma.Web.static_paths(),
+     # credo:disable-for-previous-line Credo.Check.Readability.MaxLineLength
+     gzip: true,
+     cache_control_for_etags: @static_cache_control,
+diff --git a/lib/pleroma/web/feed/user_controller.ex b/lib/pleroma/web/feed/user_controller.ex
+index dc3b1f94b..b320c9224 100644
+--- a/lib/pleroma/web/feed/user_controller.ex
++++ b/lib/pleroma/web/feed/user_controller.ex
+@@ -30,7 +30,7 @@ def feed_redirect(%{assigns: %{format: format}} = conn, _params)
+ 
+   def feed_redirect(conn, %{"nickname" => nickname}) do
+     with {_, %User{} = user} <- {:fetch_user, User.get_cached_by_nickname(nickname)} do
+-      redirect(conn, external: "#{Routes.user_feed_url(conn, :feed, user.nickname)}.atom")
++      redirect(conn, external: "#{url(~p"/users/#{user.nickname}/feed")}.atom")
+     end
+   end
+ 
+diff --git a/lib/pleroma/web/masto_fe_controller.ex b/lib/pleroma/web/masto_fe_controller.ex
+index 7b6e01aad..b24f00620 100644
+--- a/lib/pleroma/web/masto_fe_controller.ex
++++ b/lib/pleroma/web/masto_fe_controller.ex
+@@ -34,9 +34,9 @@ def index(conn, _params) do
+ 
+       index =
+         if flavour == "fedibird-fe" do
+-          "fedibird.index.html"
++          "fedibird.html"
+         else
+-          "glitchsoc.index.html"
++          "glitchsoc.html"
+         end
+ 
+       conn
+diff --git a/lib/pleroma/web/mastodon_api/controllers/auth_controller.ex b/lib/pleroma/web/mastodon_api/controllers/auth_controller.ex
+index a9ccaa982..30e40ac42 100644
+--- a/lib/pleroma/web/mastodon_api/controllers/auth_controller.ex
++++ b/lib/pleroma/web/mastodon_api/controllers/auth_controller.ex
+@@ -54,12 +54,7 @@ def login(conn, params) do
+   defp redirect_to_oauth_form(conn, _params) do
+     with {:ok, app} <- local_mastofe_app() do
+       path =
+-        Routes.o_auth_path(conn, :authorize,
+-          response_type: "code",
+-          client_id: app.client_id,
+-          redirect_uri: ".",
+-          scope: Enum.join(app.scopes, " ")
+-        )
++        ~p[/oauth/authorize?#{[response_type: "code", client_id: app.client_id, redirect_uri: ".", scope: Enum.join(app.scopes, " ")]}]
+ 
+       redirect(conn, to: path)
+     end
+@@ -91,7 +86,7 @@ def password_reset(conn, params) do
+   defp local_mastodon_post_login_path(conn) do
+     case get_session(conn, :return_to) do
+       nil ->
+-        Routes.masto_fe_path(conn, :index, ["getting-started"])
++        ~p"/web/getting-started"
+ 
+       return_to ->
+         delete_session(conn, :return_to)
+diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex
+index 47d1616c4..ac0955534 100644
+--- a/lib/pleroma/web/mastodon_api/views/status_view.ex
++++ b/lib/pleroma/web/mastodon_api/views/status_view.ex
+@@ -322,7 +322,7 @@ def render("show.json", %{activity: %{id: id, data: %{"object" => _object}} = ac
+ 
+       url =
+         if user.local do
+-          Pleroma.Web.Router.Helpers.o_status_url(Pleroma.Web.Endpoint, :notice, activity)
++          url(~p[/notice/#{activity}])
+         else
+           object.data["url"] || object.data["external_url"] || object.data["id"]
+         end
+diff --git a/lib/pleroma/web/mastodon_api/views/tag_view.ex b/lib/pleroma/web/mastodon_api/views/tag_view.ex
+index 02108c736..6d3ea3c1a 100644
+--- a/lib/pleroma/web/mastodon_api/views/tag_view.ex
++++ b/lib/pleroma/web/mastodon_api/views/tag_view.ex
+@@ -1,7 +1,6 @@
+ defmodule Pleroma.Web.MastodonAPI.TagView do
+   use Pleroma.Web, :view
+   alias Pleroma.User
+-  alias Pleroma.Web.Router.Helpers
+ 
+   def render("index.json", %{tags: tags, for_user: user}) do
+     render_many(tags, __MODULE__, "show.json", %{for_user: user})
+@@ -17,7 +16,7 @@ def render("show.json", %{tag: tag, for_user: user}) do
+ 
+     %{
+       name: tag.name,
+-      url: Helpers.tag_feed_url(Pleroma.Web.Endpoint, :feed, tag.name),
++      url: url(~p[/tags/#{tag.name}]),
+       history: [],
+       following: following
+     }
+diff --git a/lib/pleroma/web/metadata/providers/feed.ex b/lib/pleroma/web/metadata/providers/feed.ex
+index d0ab5c19e..15f47b843 100644
+--- a/lib/pleroma/web/metadata/providers/feed.ex
++++ b/lib/pleroma/web/metadata/providers/feed.ex
+@@ -3,9 +3,9 @@
+ # SPDX-License-Identifier: AGPL-3.0-only
+ 
+ defmodule Pleroma.Web.Metadata.Providers.Feed do
+-  alias Pleroma.Web.Endpoint
+   alias Pleroma.Web.Metadata.Providers.Provider
+-  alias Pleroma.Web.Router.Helpers
++
++  use Pleroma.Web, :verified_routes
+ 
+   @behaviour Provider
+ 
+@@ -16,7 +16,7 @@ def build_tags(%{user: user}) do
+        [
+          rel: "alternate",
+          type: "application/atom+xml",
+-         href: Helpers.user_feed_path(Endpoint, :feed, user.nickname) <> ".atom"
++         href: ~p[/users/#{user.nickname}/feed.atom]
+        ], []}
+     ]
+   end
+diff --git a/lib/pleroma/web/metadata/providers/twitter_card.ex b/lib/pleroma/web/metadata/providers/twitter_card.ex
+index b2497d14e..ab48ea272 100644
+--- a/lib/pleroma/web/metadata/providers/twitter_card.ex
++++ b/lib/pleroma/web/metadata/providers/twitter_card.ex
+@@ -10,6 +10,8 @@ defmodule Pleroma.Web.Metadata.Providers.TwitterCard do
+   alias Pleroma.Web.Metadata.Providers.Provider
+   alias Pleroma.Web.Metadata.Utils
+ 
++  use Pleroma.Web, :verified_routes
++
+   @behaviour Provider
+   @media_types ["image", "audio", "video"]
+ 
+@@ -112,7 +114,7 @@ defp build_attachments(id, %{data: %{"attachment" => attachments}}) do
+   defp build_attachments(_id, _object), do: []
+ 
+   defp player_url(id) do
+-    Pleroma.Web.Router.Helpers.o_status_url(Pleroma.Web.Endpoint, :notice_player, id)
++    url(~p[/notice/#{id}/embed_player])
+   end
+ 
+   # Videos have problems without dimensions, but we used to not provide WxH for images.
+diff --git a/lib/pleroma/web/o_auth/o_auth_controller.ex b/lib/pleroma/web/o_auth/o_auth_controller.ex
+index 277df1c46..ba33dc9e7 100644
+--- a/lib/pleroma/web/o_auth/o_auth_controller.ex
++++ b/lib/pleroma/web/o_auth/o_auth_controller.ex
+@@ -449,7 +449,7 @@ def prepare_request(%Plug.Conn{} = conn, %{
+       |> Map.put("state", state)
+ 
+     # Handing the request to Ueberauth
+-    redirect(conn, to: Routes.o_auth_path(conn, :request, provider, params))
++    redirect(conn, to: ~p"/oauth/#{provider}?#{params}")
+   end
+ 
+   def request(%Plug.Conn{} = conn, params) do
+@@ -623,7 +623,7 @@ def login(%User{} = user, %App{} = app, requested_scopes) when is_list(requested
+   end
+ 
+   # Special case: Local MastodonFE
+-  defp redirect_uri(%Plug.Conn{} = conn, "."), do: Routes.auth_url(conn, :login)
++  defp redirect_uri(_, "."), do: url(~p"/web/login")
+ 
+   defp redirect_uri(%Plug.Conn{}, redirect_uri), do: redirect_uri
+ 
+diff --git a/lib/pleroma/web/o_status/o_status_controller.ex b/lib/pleroma/web/o_status/o_status_controller.ex
+index 95a22895e..2b2872c9a 100644
+--- a/lib/pleroma/web/o_status/o_status_controller.ex
++++ b/lib/pleroma/web/o_status/o_status_controller.ex
+@@ -14,7 +14,6 @@ defmodule Pleroma.Web.OStatus.OStatusController do
+   alias Pleroma.Web.Fallback.RedirectController
+   alias Pleroma.Web.Metadata.PlayerView
+   alias Pleroma.Web.Plugs.RateLimiter
+-  alias Pleroma.Web.Router
+ 
+   plug(
+     RateLimiter,
+@@ -87,7 +86,7 @@ def notice(%{assigns: %{format: format}} = conn, %{"id" => id}) do
+             %{
+               activity_id: activity.id,
+               object: object,
+-              url: Router.Helpers.o_status_url(Endpoint, :notice, activity.id),
++              url: url(~p[/notice/#{activity.id}]),
+               user: user
+             }
+           )
+diff --git a/lib/pleroma/web/plugs/http_signature_plug.ex b/lib/pleroma/web/plugs/http_signature_plug.ex
+index 488108b08..eb6a46736 100644
+--- a/lib/pleroma/web/plugs/http_signature_plug.ex
++++ b/lib/pleroma/web/plugs/http_signature_plug.ex
+@@ -5,8 +5,9 @@
+ defmodule Pleroma.Web.Plugs.HTTPSignaturePlug do
+   import Plug.Conn
+   import Phoenix.Controller, only: [get_format: 1]
++
++  use Pleroma.Web, :verified_routes
+   alias Pleroma.Activity
+-  alias Pleroma.Web.Router
+   alias Pleroma.Signature
+   alias Pleroma.Instances
+   require Logger
+@@ -32,10 +33,10 @@ def call(conn, _opts) do
+   end
+ 
+   def route_aliases(%{path_info: ["objects", id], query_string: query_string}) do
+-    ap_id = Router.Helpers.o_status_url(Pleroma.Web.Endpoint, :object, id)
++    ap_id = url(~p[/objects/#{id}])
+ 
+     with %Activity{} = activity <- Activity.get_by_object_ap_id_with_object(ap_id) do
+-      ["/notice/#{activity.id}", "/notice/#{activity.id}?#{query_string}"]
++      [~p"/notice/#{activity.id}", "/notice/#{activity.id}?#{query_string}"]
+     else
+       _ -> []
+     end
+diff --git a/lib/pleroma/web/static_fe/static_fe_controller.ex b/lib/pleroma/web/static_fe/static_fe_controller.ex
+index 56ee4e41e..f0d45293e 100644
+--- a/lib/pleroma/web/static_fe/static_fe_controller.ex
++++ b/lib/pleroma/web/static_fe/static_fe_controller.ex
+@@ -11,7 +11,6 @@ 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(:assign_id)
+@@ -111,11 +110,11 @@ def show(%{assigns: %{username_or_id: username_or_id, tab: tab}} = conn, params)
+   end
+ 
+   def show(%{assigns: %{object_id: _}} = conn, _params) do
+-    url = Helpers.url(conn) <> conn.request_path
++    url = unverified_url(conn, conn.request_path)
+ 
+     case Activity.get_create_by_object_ap_id_with_object(url) do
+       %Activity{} = activity ->
+-        to = Helpers.o_status_path(Pleroma.Web.Endpoint, :notice, activity)
++        to = ~p[/notice/#{activity}]
+         redirect(conn, to: to)
+ 
+       _ ->
+@@ -124,11 +123,11 @@ def show(%{assigns: %{object_id: _}} = conn, _params) do
+   end
+ 
+   def show(%{assigns: %{activity_id: _}} = conn, _params) do
+-    url = Helpers.url(conn) <> conn.request_path
++    url = unverified_url(conn, conn.request_path)
+ 
+     case Activity.get_by_ap_id(url) do
+       %Activity{} = activity ->
+-        to = Helpers.o_status_path(Pleroma.Web.Endpoint, :notice, activity)
++        to = ~p[/notice/#{activity}]
+         redirect(conn, to: to)
+ 
+       _ ->
+@@ -167,7 +166,7 @@ defp represent(%Activity{object: %Object{data: data}} = activity, selected) do
+ 
+     link =
+       case user.local do
+-        true -> Helpers.o_status_url(Pleroma.Web.Endpoint, :notice, activity)
++        true -> ~p[/notice/#{activity}]
+         _ -> data["url"] || data["external_url"] || data["id"]
+       end
+ 
+diff --git a/lib/pleroma/web/static_fe/static_fe_view.ex b/lib/pleroma/web/static_fe/static_fe_view.ex
+index f0c9ddd22..c1d83c5a0 100644
+--- a/lib/pleroma/web/static_fe/static_fe_view.ex
++++ b/lib/pleroma/web/static_fe/static_fe_view.ex
+@@ -11,7 +11,6 @@ defmodule Pleroma.Web.StaticFE.StaticFEView do
+   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/templates/akkoma_api/frontend_switcher/switch.html.eex b/lib/pleroma/web/templates/akkoma_api/frontend_switcher/switch.html.eex
+index a0b0a2361..010a7fbad 100644
+--- a/lib/pleroma/web/templates/akkoma_api/frontend_switcher/switch.html.eex
++++ b/lib/pleroma/web/templates/akkoma_api/frontend_switcher/switch.html.eex
+@@ -2,7 +2,7 @@
+ 
+ <h3>After you submit, you will need to refresh manually to get your new frontend!</h3>
+ 
+-<%= form_for @conn, Routes.frontend_switcher_path(@conn, :do_switch), fn f -> %>
++<%= form_for @conn, ~p"/akkoma/frontend", fn f -> %>
+   <%= select(f, :frontend, @choices) %>
+ 
+   <%= submit do: "submit" %>
+diff --git a/lib/pleroma/web/templates/feed/feed/tag.atom.eex b/lib/pleroma/web/templates/feed/feed/tag.atom.eex
+index 6d497e84c..e85c08b2b 100644
+--- a/lib/pleroma/web/templates/feed/feed/tag.atom.eex
++++ b/lib/pleroma/web/templates/feed/feed/tag.atom.eex
+@@ -9,13 +9,13 @@
+       xmlns:ostatus="http://ostatus.org/schema/1.0"
+       xmlns:statusnet="http://status.net/schema/api/1/">
+ 
+-    <id><%= '#{Routes.tag_feed_url(@conn, :feed, @tag)}.rss' %></id>
++    <id><%= '#{url(~p"/tags/#{@tag}")}.rss' %></id>
+     <title>#<%= @tag %></title>
+ 
+     <subtitle><%= Gettext.dpgettext("static_pages", "tag feed description", "These are public toots tagged with #%{tag}. You can interact with them if you have an account anywhere in the fediverse.", tag: @tag) %></subtitle>
+     <logo><%= feed_logo() %></logo>
+     <updated><%= most_recent_update(@activities) %></updated>
+-    <link rel="self" href="<%= '#{Routes.tag_feed_url(@conn, :feed, @tag)}.atom'  %>" type="application/atom+xml"/>
++    <link rel="self" href="<%= '#{url(~p"/tags/#{@tag}")}.atom'  %>" type="application/atom+xml"/>
+     <%= for activity <- @activities do %>
+     <%= render @view_module, "_tag_activity.atom", Map.merge(assigns, prepare_activity(activity, actor: true)) %>
+     <% end %>
+diff --git a/lib/pleroma/web/templates/feed/feed/tag.rss.eex b/lib/pleroma/web/templates/feed/feed/tag.rss.eex
+index edcc3e436..7ee3ba5a3 100644
+--- a/lib/pleroma/web/templates/feed/feed/tag.rss.eex
++++ b/lib/pleroma/web/templates/feed/feed/tag.rss.eex
+@@ -5,7 +5,7 @@
+ 
+     <title>#<%= @tag %></title>
+     <description><%= Gettext.dpgettext("static_pages", "tag feed description", "These are public toots tagged with #%{tag}. You can interact with them if you have an account anywhere in the fediverse.", tag: @tag) %></description>
+-    <link><%= '#{Routes.tag_feed_url(@conn, :feed, @tag)}.rss' %></link>
++    <link><%= '#{url(~p"/tags/#{@tag}")}.rss' %></link>
+     <webfeeds:logo><%= feed_logo() %></webfeeds:logo>
+     <webfeeds:accentColor>2b90d9</webfeeds:accentColor>
+     <%= for activity <- @activities do %>
+diff --git a/lib/pleroma/web/templates/feed/feed/user.atom.eex b/lib/pleroma/web/templates/feed/feed/user.atom.eex
+index 5c1f0ecbc..03585a9d5 100644
+--- a/lib/pleroma/web/templates/feed/feed/user.atom.eex
++++ b/lib/pleroma/web/templates/feed/feed/user.atom.eex
+@@ -6,16 +6,16 @@
+   xmlns:poco="http://portablecontacts.net/spec/1.0"
+   xmlns:ostatus="http://ostatus.org/schema/1.0">
+ 
+-  <id><%= Routes.user_feed_url(@conn, :feed, @user.nickname) <> ".atom" %></id>
++  <id><%= url(~p"/users/#{@user.nickname}/feed") <> ".atom" %></id>
+   <title><%= @user.nickname <> "'s timeline" %></title>
+   <updated><%= most_recent_update(@activities, @user) %></updated>
+   <logo><%= logo(@user) %></logo>
+-  <link rel="self" href="<%= '#{Routes.user_feed_url(@conn, :feed, @user.nickname)}.atom' %>" type="application/atom+xml"/>
++  <link rel="self" href="<%= '#{url(~p"/users/#{@user.nickname}/feed")}.atom' %>" type="application/atom+xml"/>
+ 
+   <%= render @view_module, "_author.atom", assigns %>
+ 
+   <%= if last_activity(@activities) do %>
+-    <link rel="next" href="<%= '#{Routes.user_feed_url(@conn, :feed, @user.nickname)}.atom?max_id=#{last_activity(@activities).id}' %>" type="application/atom+xml"/>
++    <link rel="next" href="<%= '#{url(~p"/users/#{@user.nickname}/feed")}.atom?max_id=#{last_activity(@activities).id}' %>" type="application/atom+xml"/>
+   <% end %>
+ 
+   <%= for activity <- @activities do %>
+diff --git a/lib/pleroma/web/templates/feed/feed/user.rss.eex b/lib/pleroma/web/templates/feed/feed/user.rss.eex
+index 6b842a085..f2eb7337e 100644
+--- a/lib/pleroma/web/templates/feed/feed/user.rss.eex
++++ b/lib/pleroma/web/templates/feed/feed/user.rss.eex
+@@ -1,16 +1,16 @@
+ <?xml version="1.0" encoding="UTF-8" ?>
+ <rss version="2.0">
+   <channel>
+-    <guid><%= Routes.user_feed_url(@conn, :feed, @user.nickname) <> ".rss" %></guid>
++    <guid><%= url(~p"/users/#{@user.nickname}/feed") <> ".rss" %></guid>
+     <title><%= @user.nickname <> "'s timeline" %></title>
+     <updated><%= most_recent_update(@activities, @user) %></updated>
+     <image><%= logo(@user) %></image>
+-    <link><%= '#{Routes.user_feed_url(@conn, :feed, @user.nickname)}.rss' %></link>
++    <link><%= '#{url(~p"/users/#{@user.nickname}/feed")}.rss' %></link>
+ 
+     <%= render @view_module, "_author.rss", assigns %>
+ 
+     <%= if last_activity(@activities) do %>
+-      <link rel="next"><%= '#{Routes.user_feed_url(@conn, :feed, @user.nickname)}.rss?max_id=#{last_activity(@activities).id}' %></link>
++      <link rel="next"><%= '#{url(~p"/users/#{@user.nickname}/feed")}.rss?max_id=#{last_activity(@activities).id}' %></link>
+     <% end %>
+ 
+     <%= for activity <- @activities do %>
+diff --git a/lib/pleroma/web/templates/masto_fe/fedibird.html.heex b/lib/pleroma/web/templates/masto_fe/fedibird.html.heex
+new file mode 100644
+index 000000000..7070bd8d8
+--- /dev/null
++++ b/lib/pleroma/web/templates/masto_fe/fedibird.html.heex
+@@ -0,0 +1,58 @@
++<!DOCTYPE html>
++<!-- FEDIBIRD -->
++<html lang="en">
++  <head>
++    <meta charset="utf-8" />
++    <meta content="width=device-width, initial-scale=1" name="viewport" />
++    <title>
++      <%= Config.get([:instance, :name]) %>
++    </title>
++    <link rel="icon" type="image/png" href="/favicon.png" />
++    <link rel="manifest" type="applicaton/manifest+json" {%{href: ~p"/web/manifest.json"}} />
++
++    <meta name="theme-color" {%{content: Config.get([:manifest, :theme_color])}} />
++
++    <script id="initial-state" type="application/json">
++      <%= initial_state(@token, @user, @custom_emojis) %>
++    </script>
++
++    <script crossorigin="anonymous" src="/packs/js/common.js">
++    </script>
++    <script crossorigin="anonymous" src="/packs/js/locale_en.js">
++    </script>
++
++    <link
++      rel="preload"
++      as="script"
++      crossorigin="anonymous"
++      href="/packs/js/features/getting_started.js"
++    />
++    <link rel="preload" as="script" crossorigin="anonymous" href="/packs/js/features/compose.js" />
++    <link
++      rel="preload"
++      as="script"
++      crossorigin="anonymous"
++      href="/packs/js/features/home_timeline.js"
++    />
++    <link
++      rel="preload"
++      as="script"
++      crossorigin="anonymous"
++      href="/packs/js/features/public_timeline.js"
++    />
++    <link
++      rel="preload"
++      as="script"
++      crossorigin="anonymous"
++      href="/packs/js/features/notifications.js"
++    />
++    <script crossorigin="anonymous" src="/packs/js/application.js">
++    </script>
++
++    <link rel="stylesheet" media="all" href="/packs/css/common.css" />
++    <link rel="stylesheet" media="all" href="/packs/css/default.css" />
++  </head>
++  <body class="app-body no-reduce-motion system-font">
++    <div class="app-holder" data-props="{&quot;locale&quot;:&quot;en&quot;}" id="mastodon"></div>
++  </body>
++</html>
+diff --git a/lib/pleroma/web/templates/masto_fe/fedibird.index.html.eex b/lib/pleroma/web/templates/masto_fe/fedibird.index.html.eex
+deleted file mode 100644
+index 6730c0ecc..000000000
+--- a/lib/pleroma/web/templates/masto_fe/fedibird.index.html.eex
++++ /dev/null
+@@ -1,35 +0,0 @@
+-<!DOCTYPE html>
+-<html lang='en'>
+-<head>
+-<meta charset='utf-8'>
+-<meta content='width=device-width, initial-scale=1' name='viewport'>
+-<title>
+-<%= Config.get([:instance, :name]) %>
+-</title>
+-<link rel="icon" type="image/png" href="/favicon.png"/>
+-<link rel="manifest" type="applicaton/manifest+json" href="<%= Routes.masto_fe_path(Pleroma.Web.Endpoint, :manifest) %>" />
+-
+-<meta name="theme-color" content="<%= Config.get([:manifest, :theme_color]) %>" />
+-
+-<script id='initial-state' type='application/json'><%= initial_state(@token, @user, @custom_emojis) %></script>
+-
+-<script crossorigin='anonymous' src="/packs/js/common.js"></script>
+-<script crossorigin='anonymous' src="/packs/js/locale_en.js"></script>
+-
+-<link rel='preload' as='script' crossorigin='anonymous' href='/packs/js/features/getting_started.js'>
+-<link rel='preload' as='script' crossorigin='anonymous' href='/packs/js/features/compose.js'>
+-<link rel='preload' as='script' crossorigin='anonymous' href='/packs/js/features/home_timeline.js'>
+-<link rel='preload' as='script' crossorigin='anonymous' href='/packs/js/features/public_timeline.js'>
+-<link rel='preload' as='script' crossorigin='anonymous' href='/packs/js/features/notifications.js'>
+-<script crossorigin='anonymous' src="/packs/js/application.js"></script>
+-
+-
+-<link rel="stylesheet" media="all" href="/packs/css/common.css" />
+-<link rel="stylesheet" media="all" href="/packs/css/default.css" />
+-
+-</head>
+-<body class='app-body no-reduce-motion system-font'>
+-  <div class='app-holder' data-props='{&quot;locale&quot;:&quot;en&quot;}' id='mastodon'>
+-  </div>
+-</body>
+-</html>
+diff --git a/lib/pleroma/web/templates/masto_fe/glitchsoc.html.heex b/lib/pleroma/web/templates/masto_fe/glitchsoc.html.heex
+new file mode 100644
+index 000000000..469c201a5
+--- /dev/null
++++ b/lib/pleroma/web/templates/masto_fe/glitchsoc.html.heex
+@@ -0,0 +1,57 @@
++<!DOCTYPE html>
++<!-- GLITCHSOC -->
++<html lang="en">
++  <head>
++    <meta charset="utf-8" />
++    <meta content="width=device-width, initial-scale=1" name="viewport" />
++    <title>
++      <%= Config.get([:instance, :name]) %>
++    </title>
++    <link rel="icon" type="image/png" href="/favicon.png" />
++    <link rel="manifest" type="applicaton/manifest+json" {%{href: ~p"/web/manifest.json"}} />
++
++    <meta name="theme-color" {%{content: Config.get([:manifest, :theme_color])}} />
++
++    <script crossorigin="anonymous" src="/packs/js/locales.js">
++    </script>
++    <script crossorigin="anonymous" src="/packs/js/locales/glitch/en.js">
++    </script>
++
++    <link
++      rel="preload"
++      as="script"
++      crossorigin="anonymous"
++      href="/packs/js/features/getting_started.js"
++    />
++    <link rel="preload" as="script" crossorigin="anonymous" href="/packs/js/features/compose.js" />
++    <link
++      rel="preload"
++      as="script"
++      crossorigin="anonymous"
++      href="/packs/js/features/home_timeline.js"
++    />
++    <link
++      rel="preload"
++      as="script"
++      crossorigin="anonymous"
++      href="/packs/js/features/notifications.js"
++    />
++    <script id="initial-state" type="application/json">
++      <%= initial_state(@token, @user, @custom_emojis) %>
++    </script>
++
++    <script src="/packs/js/core/common.js">
++    </script>
++    <link rel="stylesheet" media="all" href="/packs/css/core/common.css" />
++
++    <script src="/packs/js/flavours/glitch/common.js">
++    </script>
++    <link rel="stylesheet" media="all" href="/packs/css/flavours/glitch/common.css" />
++
++    <script src="/packs/js/flavours/glitch/home.js">
++    </script>
++  </head>
++  <body class="app-body no-reduce-motion system-font">
++    <div class="app-holder" data-props="{&quot;locale&quot;:&quot;en&quot;}" id="mastodon"></div>
++  </body>
++</html>
+diff --git a/lib/pleroma/web/templates/masto_fe/glitchsoc.index.html.eex b/lib/pleroma/web/templates/masto_fe/glitchsoc.index.html.eex
+deleted file mode 100644
+index dadf8f413..000000000
+--- a/lib/pleroma/web/templates/masto_fe/glitchsoc.index.html.eex
++++ /dev/null
+@@ -1,35 +0,0 @@
+-<!DOCTYPE html>
+-<html lang='en'>
+-<head>
+-<meta charset='utf-8'>
+-<meta content='width=device-width, initial-scale=1' name='viewport'>
+-<title>
+-<%= Config.get([:instance, :name]) %>
+-</title>
+-<link rel="icon" type="image/png" href="/favicon.png"/>
+-<link rel="manifest" type="applicaton/manifest+json" href="<%= Routes.masto_fe_path(Pleroma.Web.Endpoint, :manifest) %>" />
+-
+-<meta name="theme-color" content="<%= Config.get([:manifest, :theme_color]) %>" />
+-
+-<script crossorigin='anonymous' src="/packs/js/locales.js"></script>
+-<script crossorigin='anonymous' src="/packs/js/locales/glitch/en.js"></script>
+-
+-<link rel='preload' as='script' crossorigin='anonymous' href='/packs/js/features/getting_started.js'>
+-<link rel='preload' as='script' crossorigin='anonymous' href='/packs/js/features/compose.js'>
+-<link rel='preload' as='script' crossorigin='anonymous' href='/packs/js/features/home_timeline.js'>
+-<link rel='preload' as='script' crossorigin='anonymous' href='/packs/js/features/notifications.js'>
+-<script id='initial-state' type='application/json'><%= initial_state(@token, @user, @custom_emojis) %></script>
+-
+-<script src="/packs/js/core/common.js"></script>
+-<link rel="stylesheet" media="all" href="/packs/css/core/common.css" />
+-
+-<script src="/packs/js/flavours/glitch/common.js"></script>
+-<link rel="stylesheet" media="all" href="/packs/css/flavours/glitch/common.css" />
+-
+-<script src="/packs/js/flavours/glitch/home.js"></script>
+-</head>
+-<body class='app-body no-reduce-motion system-font'>
+-  <div class='app-holder' data-props='{&quot;locale&quot;:&quot;en&quot;}' id='mastodon'>
+-  </div>
+-</body>
+-</html>
+diff --git a/lib/pleroma/web/templates/o_auth/mfa/recovery.html.eex b/lib/pleroma/web/templates/o_auth/mfa/recovery.html.eex
+index ee40cf277..b9b08c45d 100644
+--- a/lib/pleroma/web/templates/o_auth/mfa/recovery.html.eex
++++ b/lib/pleroma/web/templates/o_auth/mfa/recovery.html.eex
+@@ -1,15 +1,15 @@
+ <div>
+-  <%= if get_flash(@conn, :info) do %>
+-  <p class="alert alert-info" role="alert"><%= get_flash(@conn, :info) %></p>
++  <%= if Flash.get(@flash, :info) do %>
++  <p class="alert alert-info" role="alert"><%= Flash.get(@flash, :info) %></p>
+   <% end %>
+-  <%= if get_flash(@conn, :error) do %>
+-  <p class="alert alert-danger" role="alert"><%= get_flash(@conn, :error) %></p>
++  <%= if Flash.get(@flash, :error) do %>
++  <p class="alert alert-danger" role="alert"><%= Flash.get(@flash, :error) %></p>
+   <% end %>
+   <div class="panel-heading">
+       <%= Gettext.dpgettext("static_pages", "mfa recover page title", "Two-factor recovery") %>
+   </div>
+   <div class="panel-content">
+-      <%= form_for @conn, Routes.mfa_verify_path(@conn, :verify), [as: "mfa"], fn f -> %>
++      <%= form_for @conn, ~p"/oauth/mfa/verify", [as: "mfa"], fn f -> %>
+       <div class="input">
+         <%= label f, :code, Gettext.dpgettext("static_pages", "mfa recover recovery code prompt", "Recovery code") %>
+         <%= text_input f, :code, [autocomplete: false, autocorrect: "off", autocapitalize: "off", autofocus: true, spellcheck: false] %>
+@@ -21,7 +21,7 @@
+ 
+       <%= submit Gettext.dpgettext("static_pages", "mfa recover verify recovery code button", "Verify") %>
+       <% end %>
+-      <a href="<%= Routes.mfa_path(@conn, :show, %{challenge_type: "totp", mfa_token: @mfa_token, state: @state, redirect_uri: @redirect_uri}) %>">
++      <a href="<%= ~p"/oauth/mfa?#{[challenge_type: "totp", mfa_token: @mfa_token, state: @state, redirect_uri: @redirect_uri]}" %>">
+         <%= Gettext.dpgettext("static_pages", "mfa recover use 2fa code link", "Enter a two-factor code") %>
+       </a>
+ 
+diff --git a/lib/pleroma/web/templates/o_auth/mfa/totp.html.eex b/lib/pleroma/web/templates/o_auth/mfa/totp.html.eex
+index 734e62112..59827780b 100644
+--- a/lib/pleroma/web/templates/o_auth/mfa/totp.html.eex
++++ b/lib/pleroma/web/templates/o_auth/mfa/totp.html.eex
+@@ -1,15 +1,15 @@
+ <div>
+-  <%= if get_flash(@conn, :info) do %>
+-  <p class="alert alert-info" role="alert"><%= get_flash(@conn, :info) %></p>
++  <%= if Flash.get(@flash, :info) do %>
++  <p class="alert alert-info" role="alert"><%= Flash.get(@flash, :info) %></p>
+   <% end %>
+-  <%= if get_flash(@conn, :error) do %>
+-  <p class="alert alert-danger" role="alert"><%= get_flash(@conn, :error) %></p>
++  <%= if Flash.get(@flash, :error) do %>
++  <p class="alert alert-danger" role="alert"><%= Flash.get(@flash, :error) %></p>
+   <% end %>
+   <div class="panel-heading">
+       <%= Gettext.dpgettext("static_pages", "mfa auth page title", "Two-factor authentication") %>
+   </div>
+   <div class="panel-content">
+-      <%= form_for @conn, Routes.mfa_verify_path(@conn, :verify), [as: "mfa"], fn f -> %>
++      <%= form_for @conn, ~p"/oauth/mfa/verify", [as: "mfa"], fn f -> %>
+       <div class="input">
+         <%= label f, :code, Gettext.dpgettext("static_pages", "mfa auth code prompt", "Authentication code") %>
+         <%= text_input f, :code, [autocomplete: "one-time-code", autocorrect: "off", autocapitalize: "off", autofocus: true, pattern: "[0-9]*", spellcheck: false] %>
+@@ -21,7 +21,7 @@
+ 
+       <%= submit Gettext.dpgettext("static_pages", "mfa auth verify code button", "Verify") %>
+       <% end %>
+-      <a href="<%= Routes.mfa_path(@conn, :show, %{challenge_type: "recovery", mfa_token: @mfa_token, state: @state, redirect_uri: @redirect_uri}) %>">
++      <a href="<%= ~p"/oauth/mfa?#{[challenge_type: "recovery", mfa_token: @mfa_token, state: @state, redirect_uri: @redirect_uri]}" %>">
+         <%= Gettext.dpgettext("static_pages", "mfa auth page use recovery code link", "Enter a two-factor recovery code") %>
+       </a>
+   </div>
+diff --git a/lib/pleroma/web/templates/o_auth/o_auth/consumer.html.eex b/lib/pleroma/web/templates/o_auth/o_auth/consumer.html.eex
+index 8b894cd58..97f2b770f 100644
+--- a/lib/pleroma/web/templates/o_auth/o_auth/consumer.html.eex
++++ b/lib/pleroma/web/templates/o_auth/o_auth/consumer.html.eex
+@@ -1,6 +1,6 @@
+ <h2><%= Gettext.dpgettext("static_pages", "oauth external provider page title", "Sign in with external provider") %></h2>
+ 
+-<%= form_for @conn, Routes.o_auth_path(@conn, :prepare_request), [as: "authorization", method: "get"], fn f -> %>
++<%= form_for @conn, ~p"/oauth/prepare_request", [as: "authorization", method: "get"], fn f -> %>
+   <div style="display: none">
+     <%= render @view_module, "_scopes.html", Map.merge(assigns, %{form: f}) %>
+   </div>
+diff --git a/lib/pleroma/web/templates/o_auth/o_auth/register.html.eex b/lib/pleroma/web/templates/o_auth/o_auth/register.html.eex
+index 1f661efb2..601b16b98 100644
+--- a/lib/pleroma/web/templates/o_auth/o_auth/register.html.eex
++++ b/lib/pleroma/web/templates/o_auth/o_auth/register.html.eex
+@@ -1,14 +1,14 @@
+-<%= if get_flash(@conn, :info) do %>
+-  <p class="alert alert-info" role="alert"><%= get_flash(@conn, :info) %></p>
++<%= if Flash.get(@flash, :info) do %>
++  <p class="alert alert-info" role="alert"><%= Flash.get(@flash, :info) %></p>
+ <% end %>
+-<%= if get_flash(@conn, :error) do %>
+-  <p class="alert alert-danger" role="alert"><%= get_flash(@conn, :error) %></p>
++<%= if Flash.get(@flash, :error) do %>
++  <p class="alert alert-danger" role="alert"><%= Flash.get(@flash, :error) %></p>
+ <% end %>
+ 
+ <h2><%= Gettext.dpgettext("static_pages", "oauth register page title", "Registration Details") %></h2>
+ 
+ <p><%= Gettext.dpgettext("static_pages", "oauth register page fill form prompt", "If you'd like to register a new account, please provide the details below.") %></p>
+-<%= form_for @conn, Routes.o_auth_path(@conn, :register), [as: "authorization"], fn f -> %>
++<%= form_for @conn, ~p"/oauth/register", [as: "authorization"], fn f -> %>
+ 
+ <div class="input">
+   <%= label f, :nickname, Gettext.dpgettext("static_pages", "oauth register page nickname prompt", "Nickname") %>
+diff --git a/lib/pleroma/web/templates/o_auth/o_auth/show.html.eex b/lib/pleroma/web/templates/o_auth/o_auth/show.html.eex
+index 986e6ffce..420a17562 100644
+--- a/lib/pleroma/web/templates/o_auth/o_auth/show.html.eex
++++ b/lib/pleroma/web/templates/o_auth/o_auth/show.html.eex
+@@ -1,11 +1,11 @@
+-<%= if get_flash(@conn, :info) do %>
+-<p class="alert alert-info" role="alert"><%= get_flash(@conn, :info) %></p>
++<%= if Flash.get(@flash, :info) do %>
++<p class="alert alert-info" role="alert"><%= Flash.get(@flash, :info) %></p>
+ <% end %>
+-<%= if get_flash(@conn, :error) do %>
+-<p class="alert alert-danger" role="alert"><%= get_flash(@conn, :error) %></p>
++<%= if Flash.get(@flash, :error) do %>
++<p class="alert alert-danger" role="alert"><%= Flash.get(@flash, :error) %></p>
+ <% end %>
+ 
+-<%= form_for @conn, Routes.o_auth_path(@conn, :authorize), [as: "authorization"], fn f -> %>
++<%= form_for @conn, ~p"/oauth/authorize", [as: "authorization"], fn f -> %>
+ 
+ <%= if @user do %>
+   <div class="account-header">
+diff --git a/lib/pleroma/web/templates/static_fe/static_fe/profile.html.eex b/lib/pleroma/web/templates/static_fe/static_fe/profile.html.eex
+index 3d1cf77e5..0bc44738b 100644
+--- a/lib/pleroma/web/templates/static_fe/static_fe/profile.html.eex
++++ b/lib/pleroma/web/templates/static_fe/static_fe/profile.html.eex
+@@ -36,7 +36,7 @@
+         </div>
+       </div>
+       <div class="remote-follow">
+-        <form method="POST" action="<%= Helpers.util_path(@conn, :remote_subscribe) %>">
++        <form method="POST" action="<%= ~p"/main/ostatus" %>">
+           <input type="hidden" name="nickname" value="<%= @user.nickname %>">
+           <input type="hidden" name="profile" value="">
+           <button type="submit" class="button-default"><%= Gettext.dpgettext("static_pages", "static fe profile page remote follow button", "Remote follow") %></button>
+diff --git a/lib/pleroma/web/templates/twitter_api/password/reset.html.eex b/lib/pleroma/web/templates/twitter_api/password/reset.html.eex
+index 6a544af51..390a8371f 100644
+--- a/lib/pleroma/web/templates/twitter_api/password/reset.html.eex
++++ b/lib/pleroma/web/templates/twitter_api/password/reset.html.eex
+@@ -1,5 +1,5 @@
+ <h2>Password Reset for <%= @user.nickname %></h2>
+-<%= form_for @conn, Routes.reset_password_path(@conn, :do_reset), [as: "data"], fn f -> %>
++<%= form_for @conn, ~p"/api/v1/pleroma/password_reset", [as: "data"], fn f -> %>
+   <div class="form-row">
+     <%= label f, :password, Gettext.dpgettext("static_pages", "password reset form password prompt", "Password") %>
+     <%= password_input f, :password %>
+diff --git a/lib/pleroma/web/templates/twitter_api/remote_follow/follow.html.eex b/lib/pleroma/web/templates/twitter_api/remote_follow/follow.html.eex
+index e2d251fac..894b5c6ee 100644
+--- a/lib/pleroma/web/templates/twitter_api/remote_follow/follow.html.eex
++++ b/lib/pleroma/web/templates/twitter_api/remote_follow/follow.html.eex
+@@ -4,7 +4,7 @@
+     <h2><%= Gettext.dpgettext("static_pages", "remote follow header", "Remote follow") %></h2>
+     <img height="128" width="128" src="<%= avatar_url(@followee) %>">
+     <p><%= @followee.nickname %></p>
+-    <%= form_for @conn, Routes.remote_follow_path(@conn, :do_follow), [as: "user"], fn f -> %>
++    <%= form_for @conn, ~p"/ostatus_subscribe", [as: "user"], fn f -> %>
+     <%= hidden_input f, :id, value: @followee.id %>
+     <%= submit Gettext.dpgettext("static_pages", "remote follow authorization button", "Authorize") %>
+     <% end %>
+diff --git a/lib/pleroma/web/templates/twitter_api/remote_follow/follow_login.html.eex b/lib/pleroma/web/templates/twitter_api/remote_follow/follow_login.html.eex
+index 26340a906..b0084aac4 100644
+--- a/lib/pleroma/web/templates/twitter_api/remote_follow/follow_login.html.eex
++++ b/lib/pleroma/web/templates/twitter_api/remote_follow/follow_login.html.eex
+@@ -4,7 +4,7 @@
+ <h2><%= Gettext.dpgettext("static_pages", "remote follow header, need login", "Log in to follow") %></h2>
+ <p><%= @followee.nickname %></p>
+ <img height="128" width="128" src="<%= avatar_url(@followee) %>">
+-<%= form_for @conn, Routes.remote_follow_path(@conn, :do_follow), [as: "authorization"], fn f -> %>
++<%= form_for @conn, ~p"/ostatus_subscribe", [as: "authorization"], fn f -> %>
+ <%= text_input f, :name, placeholder: Gettext.dpgettext("static_pages", "placeholder text for username entry", "Username"), required: true, autocomplete: "username" %>
+ <br>
+ <%= password_input f, :password, placeholder: Gettext.dpgettext("static_pages", "placeholder text for password entry", "Password"), required: true, autocomplete: "password" %>
+diff --git a/lib/pleroma/web/templates/twitter_api/remote_follow/follow_mfa.html.eex b/lib/pleroma/web/templates/twitter_api/remote_follow/follow_mfa.html.eex
+index 638212c1e..f34eba165 100644
+--- a/lib/pleroma/web/templates/twitter_api/remote_follow/follow_mfa.html.eex
++++ b/lib/pleroma/web/templates/twitter_api/remote_follow/follow_mfa.html.eex
+@@ -4,7 +4,7 @@
+ <h2><%= Gettext.dpgettext("static_pages", "remote follow mfa header", "Two-factor authentication") %></h2>
+ <p><%= @followee.nickname %></p>
+ <img height="128" width="128" src="<%= avatar_url(@followee) %>">
+-<%= form_for @conn, Routes.remote_follow_path(@conn, :do_follow), [as: "mfa"], fn f -> %>
++<%= form_for @conn, ~p"/ostatus_subscribe", [as: "mfa"], fn f -> %>
+ <%= text_input f, :code, placeholder: Gettext.dpgettext("static_pages", "placeholder text for auth code entry", "Authentication code"), required: true %>
+ <br>
+ <%= hidden_input f, :id, value: @followee.id %>
+diff --git a/lib/pleroma/web/templates/twitter_api/util/status_interact.html.eex b/lib/pleroma/web/templates/twitter_api/util/status_interact.html.eex
+index d77174967..3105772e2 100644
+--- a/lib/pleroma/web/templates/twitter_api/util/status_interact.html.eex
++++ b/lib/pleroma/web/templates/twitter_api/util/status_interact.html.eex
+@@ -2,7 +2,7 @@
+   <h2><%= Gettext.dpgettext("static_pages", "status interact error", "Error: %{error}", error: @error) %></h2>
+ <% else %>
+   <h2><%= raw Gettext.dpgettext("static_pages", "status interact header", "Interacting with %{nickname}'s %{status_link}", nickname: safe_to_string(html_escape(@nickname)), status_link: safe_to_string(link(Gettext.dpgettext("static_pages", "status interact header - status link text", "status"), to: @status_link))) %></h2>
+-  <%= form_for @conn, Routes.util_path(@conn, :remote_subscribe), [as: "status"], fn f -> %>
++  <%= form_for @conn, ~p"/main/ostatus", [as: "status"], fn f -> %>
+   <%= hidden_input f, :status_id, value: @status_id %>
+   <%= text_input f, :profile, placeholder: Gettext.dpgettext("static_pages", "placeholder text for account id", "Your account ID, e.g. lain@quitter.se") %>
+   <%= submit Gettext.dpgettext("static_pages", "status interact authorization button", "Interact") %>
+diff --git a/lib/pleroma/web/templates/twitter_api/util/subscribe.html.eex b/lib/pleroma/web/templates/twitter_api/util/subscribe.html.eex
+index 848660f26..47b73f676 100644
+--- a/lib/pleroma/web/templates/twitter_api/util/subscribe.html.eex
++++ b/lib/pleroma/web/templates/twitter_api/util/subscribe.html.eex
+@@ -2,7 +2,7 @@
+   <h2><%= Gettext.dpgettext("static_pages", "remote follow error", "Error: %{error}", error: @error) %></h2>
+ <% else %>
+   <h2><%= Gettext.dpgettext("static_pages", "remote follow header", "Remotely follow %{nickname}", nickname: @nickname) %></h2>
+-  <%= form_for @conn, Routes.util_path(@conn, :remote_subscribe), [as: "user"], fn f -> %>
++  <%= form_for @conn, ~p"/main/ostatus", [as: "user"], fn f -> %>
+   <%= hidden_input f, :nickname, value: @nickname %>
+   <%= text_input f, :profile, placeholder: Gettext.dpgettext("static_pages", "placeholder text for account id", "Your account ID, e.g. lain@quitter.se") %>
+   <%= submit Gettext.dpgettext("static_pages", "remote follow authorization button for following with a remote account", "Follow") %>
+diff --git a/lib/pleroma/web/twitter_api/controllers/remote_follow_controller.ex b/lib/pleroma/web/twitter_api/controllers/remote_follow_controller.ex
+index 42d7601ed..1927d2021 100644
+--- a/lib/pleroma/web/twitter_api/controllers/remote_follow_controller.ex
++++ b/lib/pleroma/web/twitter_api/controllers/remote_follow_controller.ex
+@@ -38,7 +38,7 @@ def follow(%{assigns: %{user: user}} = conn, %{"acct" => acct}) do
+   defp follow_status(conn, _user, acct) do
+     with {:ok, object} <- Fetcher.fetch_object_from_id(acct),
+          %Activity{id: activity_id} <- Activity.get_create_by_object_ap_id(object.data["id"]) do
+-      redirect(conn, to: Routes.o_status_path(conn, :notice, activity_id))
++      redirect(conn, to: ~p"/notice/#{activity_id}")
+     else
+       error ->
+         handle_follow_error(conn, error)
+diff --git a/lib/pleroma/web/views/embed_view.ex b/lib/pleroma/web/views/embed_view.ex
+index 913d717be..fe1009e2f 100644
+--- a/lib/pleroma/web/views/embed_view.ex
++++ b/lib/pleroma/web/views/embed_view.ex
+@@ -13,7 +13,6 @@ defmodule Pleroma.Web.EmbedView do
+   alias Pleroma.Web.Gettext
+   alias Pleroma.Web.MediaProxy
+   alias Pleroma.Web.Metadata.Utils
+-  alias Pleroma.Web.Router.Helpers
+ 
+   import Phoenix.HTML
+ 
+@@ -48,7 +47,7 @@ defp activity_content(%Activity{object: %Object{data: %{"content" => content}}})
+   defp activity_content(_), do: nil
+ 
+   defp activity_url(%User{local: true}, activity) do
+-    Helpers.o_status_url(Pleroma.Web.Endpoint, :notice, activity)
++    ~p[/notice/#{activity}]
+   end
+ 
+   defp activity_url(%User{local: false}, %Activity{object: %Object{data: data}}) do
+diff --git a/lib/pleroma/web/views/masto_fe_view.ex b/lib/pleroma/web/views/masto_fe_view.ex
+index 305368c9d..dfb235976 100644
+--- a/lib/pleroma/web/views/masto_fe_view.ex
++++ b/lib/pleroma/web/views/masto_fe_view.ex
+@@ -85,7 +85,7 @@ def render("manifest.json", _params) do
+       background_color: Config.get([:manifest, :background_color]),
+       display: "standalone",
+       scope: Pleroma.Web.Endpoint.url(),
+-      start_url: Routes.masto_fe_path(Pleroma.Web.Endpoint, :index, ["getting-started"]),
++      start_url: ~p"/web/getting-started",
+       categories: [
+         "social"
+       ],
+diff --git a/mix.exs b/mix.exs
+index 235a3d65d..01a6e8a82 100644
+--- a/mix.exs
++++ b/mix.exs
+@@ -7,7 +7,7 @@ def project do
+       version: version("3.10.4"),
+       elixir: "~> 1.14",
+       elixirc_paths: elixirc_paths(Mix.env()),
+-      compilers: [:phoenix] ++ Mix.compilers(),
++      compilers: Mix.compilers(),
+       elixirc_options: [warnings_as_errors: warnings_as_errors()],
+       xref: [exclude: [:eldap]],
+       start_permanent: Mix.env() == :prod,
+@@ -114,7 +114,9 @@ defp oauth_deps do
+   # Type `mix help deps` for examples and options.
+   defp deps do
+     [
+-      {:phoenix, "~> 1.6.15"},
++      {:phoenix, "~> 1.7.0"},
++      {:phoenix_view, "~> 2.0"},
++      {:phoenix_live_dashboard, "~> 0.7.2"},
+       {:tzdata, "~> 1.1.1"},
+       {:plug_cowboy, "~> 2.6"},
+       {:phoenix_pubsub, "~> 2.1"},
+@@ -184,7 +186,6 @@ defp deps do
+        git: "https://github.com/FloatingGhost/pleroma-contrib-search-parser.git",
+        ref: "08971a81e68686f9ac465cfb6661d51c5e4e1e7f"},
+       {:nimble_parsec, "~> 1.3", override: true},
+-      {:phoenix_live_dashboard, "~> 0.7.2"},
+       {:ecto_psql_extras, "~> 0.7"},
+       {:elasticsearch,
+        git: "https://akkoma.dev/AkkomaGang/elasticsearch-elixir.git", ref: "main"},
+diff --git a/mix.lock b/mix.lock
+index 19b954d81..3817ec210 100644
+--- a/mix.lock
++++ b/mix.lock
+@@ -38,7 +38,7 @@
+   "ex_aws": {:hex, :ex_aws, "2.4.4", "d7886eaca7e10f7bd3d9e9d2d5414cb336737b3ab2fddd4fa30358b725293fe0", [:mix], [{:configparser_ex, "~> 4.0", [hex: :configparser_ex, repo: "hexpm", optional: true]}, {:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: true]}, {:jsx, "~> 2.8 or ~> 3.0", [hex: :jsx, repo: "hexpm", optional: true]}, {:mime, "~> 1.2 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:sweet_xml, "~> 0.7", [hex: :sweet_xml, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a7d63e485ca2b16fb804f3f20097827aa69885eea6e69fa75c98f353c9c91dc7"},
+   "ex_aws_s3": {:hex, :ex_aws_s3, "2.4.0", "ce8decb6b523381812798396bc0e3aaa62282e1b40520125d1f4eff4abdff0f4", [:mix], [{:ex_aws, "~> 2.0", [hex: :ex_aws, repo: "hexpm", optional: false]}, {:sweet_xml, ">= 0.0.0", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm", "85dda6e27754d94582869d39cba3241d9ea60b6aa4167f9c88e309dc687e56bb"},
+   "ex_const": {:hex, :ex_const, "0.2.4", "d06e540c9d834865b012a17407761455efa71d0ce91e5831e86881b9c9d82448", [:mix], [], "hexpm", "96fd346610cc992b8f896ed26a98be82ac4efb065a0578f334a32d60a3ba9767"},
+-  "ex_doc": {:hex, :ex_doc, "0.30.3", "bfca4d340e3b95f2eb26e72e4890da83e2b3a5c5b0e52607333bf5017284b063", [:mix], [{:earmark_parser, "~> 1.4.31", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "fbc8702046c1d25edf79de376297e608ac78cdc3a29f075484773ad1718918b6"},
++  "ex_doc": {:hex, :ex_doc, "0.30.5", "aa6da96a5c23389d7dc7c381eba862710e108cee9cfdc629b7ec021313900e9e", [:mix], [{:earmark_parser, "~> 1.4.31", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "88a1e115dcb91cefeef7e22df4a6ebbe4634fbf98b38adcbc25c9607d6d9d8e6"},
+   "ex_machina": {:hex, :ex_machina, "2.7.0", "b792cc3127fd0680fecdb6299235b4727a4944a09ff0fa904cc639272cd92dc7", [:mix], [{:ecto, "~> 2.2 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_sql, "~> 3.0", [hex: :ecto_sql, repo: "hexpm", optional: true]}], "hexpm", "419aa7a39bde11894c87a615c4ecaa52d8f107bbdd81d810465186f783245bf8"},
+   "ex_syslogger": {:hex, :ex_syslogger, "2.0.0", "de6de5c5472a9c4fdafb28fa6610e381ae79ebc17da6490b81d785d68bd124c9", [:mix], [{:jason, "~> 1.2", [hex: :jason, repo: "hexpm", optional: true]}, {:syslog, "~> 1.1.0", [hex: :syslog, repo: "hexpm", optional: false]}], "hexpm", "a52b2fe71764e9e6ecd149ab66635812f68e39279cbeee27c52c0e35e8b8019e"},
+   "excoveralls": {:hex, :excoveralls, "0.16.1", "0bd42ed05c7d2f4d180331a20113ec537be509da31fed5c8f7047ce59ee5a7c5", [:mix], [{:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "dae763468e2008cf7075a64cb1249c97cb4bc71e236c5c2b5e5cdf1cfa2bf138"},
+@@ -63,7 +63,7 @@
+   "jose": {:hex, :jose, "1.11.6", "613fda82552128aa6fb804682e3a616f4bc15565a048dabd05b1ebd5827ed965", [:mix, :rebar3], [], "hexpm", "6275cb75504f9c1e60eeacb771adfeee4905a9e182103aa59b53fed651ff9738"},
+   "jumper": {:hex, :jumper, "1.0.1", "3c00542ef1a83532b72269fab9f0f0c82bf23a35e27d278bfd9ed0865cecabff", [:mix], [], "hexpm", "318c59078ac220e966d27af3646026db9b5a5e6703cb2aa3e26bcfaba65b7433"},
+   "linkify": {:git, "https://akkoma.dev/AkkomaGang/linkify.git", "2567e2c1073fa371fd26fd66dfa5bc77b6919c16", []},
+-  "mail": {:hex, :mail, "0.3.0", "f353ef5f41d9f2e483ba7c5df92cdfe27b990b2d8c8c41d84c7b2b40ec33989f", [:mix], [], "hexpm", "523d700b2231d887dc4ee41d3bb13f48358f6f118e55a67cef6d630d7907116c"},
++  "mail": {:hex, :mail, "0.3.1", "cb0a14e4ed8904e4e5a08214e686ccf6f9099346885db17d8c309381f865cc5c", [:mix], [], "hexpm", "1db701e89865c1d5fa296b2b57b1cd587587cca8d8a1a22892b35ef5a8e352a6"},
+   "majic": {:hex, :majic, "1.0.0", "37e50648db5f5c2ff0c9fb46454d034d11596c03683807b9fb3850676ffdaab3", [:make, :mix], [{:elixir_make, "~> 0.6.1", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "7905858f76650d49695f14ea55cd9aaaee0c6654fa391671d4cf305c275a0a9e"},
+   "makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"},
+   "makeup_elixir": {:hex, :makeup_elixir, "0.16.1", "cc9e3ca312f1cfeccc572b37a09980287e243648108384b97ff2b76e505c3555", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "e127a341ad1b209bd80f7bd1620a15693a9908ed780c3b763bccf7d200c767c6"},
+@@ -83,9 +83,9 @@
+   "oban": {:hex, :oban, "2.15.2", "8f934a49db39163633965139c8846d8e24c2beb4180f34a005c2c7c3f69a6aa2", [:mix], [{:ecto_sql, "~> 3.6", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:ecto_sqlite3, "~> 0.9", [hex: :ecto_sqlite3, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.16", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "0f4a579ea48fc7489e0d84facf8b01566e142bdc6542d7dabce32c10e664f1e9"},
+   "open_api_spex": {:hex, :open_api_spex, "3.17.3", "ada8e352eb786050dd639db2439d3316e92f3798eb2abd051f55bb9af825b37e", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:poison, "~> 3.0 or ~> 4.0 or ~> 5.0", [hex: :poison, repo: "hexpm", optional: true]}, {:ymlr, "~> 2.0 or ~> 3.0", [hex: :ymlr, repo: "hexpm", optional: true]}], "hexpm", "165db21a85ca83cffc8e7c8890f35b354eddda8255de7404a2848ed652b9f0fe"},
+   "parse_trans": {:hex, :parse_trans, "3.3.1", "16328ab840cc09919bd10dab29e431da3af9e9e7e7e6f0089dd5a2d2820011d8", [:rebar3], [], "hexpm", "07cd9577885f56362d414e8c4c4e6bdf10d43a8767abb92d24cbe8b24c54888b"},
+-  "phoenix": {:hex, :phoenix, "1.6.16", "e5bdd18c7a06da5852a25c7befb72246de4ddc289182285f8685a40b7b5f5451", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 1.0 or ~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: false]}, {:plug, "~> 1.10", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.2", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "e15989ff34f670a96b95ef6d1d25bad0d9c50df5df40b671d8f4a669e050ac39"},
++  "phoenix": {:hex, :phoenix, "1.7.7", "4cc501d4d823015007ba3cdd9c41ecaaf2ffb619d6fb283199fa8ddba89191e0", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5.3", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "8966e15c395e5e37591b6ed0bd2ae7f48e961f0f60ac4c733f9566b519453085"},
+   "phoenix_ecto": {:hex, :phoenix_ecto, "4.4.2", "b21bd01fdeffcfe2fab49e4942aa938b6d3e89e93a480d4aee58085560a0bc0d", [:mix], [{:ecto, "~> 3.5", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "70242edd4601d50b69273b057ecf7b684644c19ee750989fd555625ae4ce8f5d"},
+-  "phoenix_html": {:hex, :phoenix_html, "3.3.1", "4788757e804a30baac6b3fc9695bf5562465dd3f1da8eb8460ad5b404d9a2178", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "bed1906edd4906a15fd7b412b85b05e521e1f67c9a85418c55999277e553d0d3"},
++  "phoenix_html": {:hex, :phoenix_html, "3.3.2", "d6ce982c6d8247d2fc0defe625255c721fb8d5f1942c5ac051f6177bffa5973f", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "44adaf8e667c1c20fb9d284b6b0fa8dc7946ce29e81ce621860aa7e96de9a11d"},
+   "phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.7.2", "97cc4ff2dba1ebe504db72cb45098cb8e91f11160528b980bd282cc45c73b29c", [:mix], [{:ecto, "~> 3.6.2 or ~> 3.7", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_mysql_extras, "~> 0.5", [hex: :ecto_mysql_extras, repo: "hexpm", optional: true]}, {:ecto_psql_extras, "~> 0.7", [hex: :ecto_psql_extras, repo: "hexpm", optional: true]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.18.3", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6 or ~> 1.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "0e5fdf063c7a3b620c566a30fcf68b7ee02e5e46fe48ee46a6ec3ba382dc05b7"},
+   "phoenix_live_view": {:hex, :phoenix_live_view, "0.18.18", "1f38fbd7c363723f19aad1a04b5490ff3a178e37daaf6999594d5f34796c47fc", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.15 or ~> 1.7.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.3", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a5810d0472f3189ede6d2a95bda7f31c6113156b91784a3426cb0ab6a6d85214"},
+   "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.3", "3168d78ba41835aecad272d5e8cd51aa87a7ac9eb836eabc42f6e57538e3731d", [:mix], [], "hexpm", "bba06bc1dcfd8cb086759f0edc94a8ba2bc8896d5331a1e2c2902bf8e36ee502"},
+@@ -108,7 +108,7 @@
+   "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"},
+   "statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"},
+   "sweet_xml": {:hex, :sweet_xml, "0.7.3", "debb256781c75ff6a8c5cbf7981146312b66f044a2898f453709a53e5031b45b", [:mix], [], "hexpm", "e110c867a1b3fe74bfc7dd9893aa851f0eed5518d0d7cad76d7baafd30e4f5ba"},
+-  "swoosh": {:hex, :swoosh, "1.11.4", "9b353f998cba3c5e101a0669559c2fb2757b5d9eb7db058bf08687d82e93e416", [:mix], [{:cowboy, "~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:ex_aws, "~> 2.1", [hex: :ex_aws, repo: "hexpm", optional: true]}, {:finch, "~> 0.6", [hex: :finch, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13 or ~> 1.0", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "d3390914022a456ae1604bfcb3431bd12509b2afe8c70296bae6c9dca4903d0f"},
++  "swoosh": {:hex, :swoosh, "1.11.5", "429dccde78e2f60c6339e96917efecebca9d1f254d2878a150f580d2f782260b", [:mix], [{:cowboy, "~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:ex_aws, "~> 2.1", [hex: :ex_aws, repo: "hexpm", optional: true]}, {:finch, "~> 0.6", [hex: :finch, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13 or ~> 1.0", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "21ee57dcd68d2f56d3bbe11e76d56d142b221bb12b6018c551cc68442b800040"},
+   "syslog": {:hex, :syslog, "1.1.0", "6419a232bea84f07b56dc575225007ffe34d9fdc91abe6f1b2f254fd71d8efc2", [:rebar3], [], "hexpm", "4c6a41373c7e20587be33ef841d3de6f3beba08519809329ecc4d27b15b659e1"},
+   "table_rex": {:hex, :table_rex, "3.1.1", "0c67164d1714b5e806d5067c1e96ff098ba7ae79413cc075973e17c38a587caa", [:mix], [], "hexpm", "678a23aba4d670419c23c17790f9dcd635a4a89022040df7d5d772cb21012490"},
+   "telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"},
+@@ -126,5 +126,7 @@
+   "unsafe": {:hex, :unsafe, "1.0.1", "a27e1874f72ee49312e0a9ec2e0b27924214a05e3ddac90e91727bc76f8613d8", [:mix], [], "hexpm", "6c7729a2d214806450d29766abc2afaa7a2cbecf415be64f36a6691afebb50e5"},
+   "vex": {:hex, :vex, "0.9.0", "613ea5eb3055662e7178b83e25b2df0975f68c3d8bb67c1645f0573e1a78d606", [:mix], [], "hexpm", "c69fff44d5c8aa3f1faee71bba1dcab05dd36364c5a629df8bb11751240c857f"},
+   "web_push_encryption": {:hex, :web_push_encryption, "0.3.1", "76d0e7375142dfee67391e7690e89f92578889cbcf2879377900b5620ee4708d", [:mix], [{:httpoison, "~> 1.0", [hex: :httpoison, repo: "hexpm", optional: false]}, {:jose, "~> 1.11.1", [hex: :jose, repo: "hexpm", optional: false]}], "hexpm", "4f82b2e57622fb9337559058e8797cb0df7e7c9790793bdc4e40bc895f70e2a2"},
++  "websock": {:hex, :websock, "0.5.2", "b3c08511d8d79ed2c2f589ff430bd1fe799bb389686dafce86d28801783d8351", [:mix], [], "hexpm", "925f5de22fca6813dfa980fb62fd542ec43a2d1a1f83d2caec907483fe66ff05"},
++  "websock_adapter": {:hex, :websock_adapter, "0.5.3", "4908718e42e4a548fc20e00e70848620a92f11f7a6add8cf0886c4232267498d", [:mix], [{:bandit, ">= 0.6.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "cbe5b814c1f86b6ea002b52dd99f345aeecf1a1a6964e209d208fb404d930d3d"},
+   "websockex": {:hex, :websockex, "0.4.3", "92b7905769c79c6480c02daacaca2ddd49de936d912976a4d3c923723b647bf0", [:mix], [], "hexpm", "95f2e7072b85a3a4cc385602d42115b73ce0b74a9121d0d6dbbf557645ac53e4"},
+ }
+diff --git a/test/pleroma/emails/admin_email_test.exs b/test/pleroma/emails/admin_email_test.exs
+index e65752c23..6b0acf817 100644
+--- a/test/pleroma/emails/admin_email_test.exs
++++ b/test/pleroma/emails/admin_email_test.exs
+@@ -7,7 +7,6 @@ defmodule Pleroma.Emails.AdminEmailTest do
+   import Pleroma.Factory
+ 
+   alias Pleroma.Emails.AdminEmail
+-  alias Pleroma.Web.Router.Helpers
+ 
+   test "build report email" do
+     config = Pleroma.Config.get(:instance)
+@@ -18,7 +17,7 @@ test "build report email" do
+     res =
+       AdminEmail.report(to_user, reporter, account, [%{name: "Test", id: "12"}], "Test comment")
+ 
+-    status_url = Helpers.o_status_url(Pleroma.Web.Endpoint, :notice, "12")
++    status_url = url(~p[/notice/12])
+     reporter_url = reporter.ap_id
+     account_url = account.ap_id
+ 
+diff --git a/test/pleroma/emails/user_email_test.exs b/test/pleroma/emails/user_email_test.exs
+index 564552004..259efbb65 100644
+--- a/test/pleroma/emails/user_email_test.exs
++++ b/test/pleroma/emails/user_email_test.exs
+@@ -6,8 +6,6 @@ defmodule Pleroma.Emails.UserEmailTest do
+   use Pleroma.DataCase, async: true
+ 
+   alias Pleroma.Emails.UserEmail
+-  alias Pleroma.Web.Endpoint
+-  alias Pleroma.Web.Router
+ 
+   import Pleroma.Factory
+ 
+@@ -18,7 +16,7 @@ test "build password reset email" do
+     assert email.from == {config[:name], config[:notify_email]}
+     assert email.to == [{user.name, user.email}]
+     assert email.subject == "Password reset"
+-    assert email.html_body =~ Router.Helpers.reset_password_url(Endpoint, :reset, "test_token")
++    assert email.html_body =~ ~p"/api/v1/pleroma/password_reset/test_token"
+   end
+ 
+   test "build user invitation email" do
+@@ -30,8 +28,7 @@ test "build user invitation email" do
+     assert email.subject == "Invitation to Akkoma"
+     assert email.to == [{"Jonh", "test@test.com"}]
+ 
+-    assert email.html_body =~
+-             Router.Helpers.redirect_url(Endpoint, :registration_page, token.token)
++    assert email.html_body =~ ~p[/registration/#{token.token}]
+   end
+ 
+   test "build account confirmation email" do
+@@ -42,8 +39,7 @@ test "build account confirmation email" do
+     assert email.to == [{user.name, user.email}]
+     assert email.subject == "#{config[:name]} account confirmation"
+ 
+-    assert email.html_body =~
+-             Router.Helpers.confirm_email_url(Endpoint, :confirm_email, user.id, "conf-token")
++    assert email.html_body =~ ~p[/account/confirm_email/#{user.id}/conf-token]
+   end
+ 
+   test "build approval pending email" do
+diff --git a/test/pleroma/integration/federation_test.exs b/test/pleroma/integration/federation_test.exs
+index da433e2c0..e4e37aa9f 100644
+--- a/test/pleroma/integration/federation_test.exs
++++ b/test/pleroma/integration/federation_test.exs
+@@ -31,10 +31,9 @@ test "within/2 captures local bindings and executes block on remote node" do
+     test "runs webserver on customized port" do
+       {nickname, url, url_404} =
+         within @federated1 do
+-          import Pleroma.Web.Router.Helpers
+           user = Pleroma.Factory.insert(:user)
+-          user_url = account_url(Pleroma.Web.Endpoint, :show, user)
+-          url_404 = account_url(Pleroma.Web.Endpoint, :show, "not-exists")
++          user_url = ~p[/api/v1/accounts/#{user}]
++          url_404 = ~p"/api/v1/accounts/not-exists"
+ 
+           {user.nickname, user_url, url_404}
+         end
+diff --git a/test/pleroma/user_test.exs b/test/pleroma/user_test.exs
+index c33528a67..d067d6a92 100644
+--- a/test/pleroma/user_test.exs
++++ b/test/pleroma/user_test.exs
+@@ -1004,23 +1004,13 @@ test "it doesn't fail on invalid alsoKnownAs entries" do
+   test "returns an ap_id for a user" do
+     user = insert(:user)
+ 
+-    assert User.ap_id(user) ==
+-             Pleroma.Web.Router.Helpers.user_feed_url(
+-               Pleroma.Web.Endpoint,
+-               :feed_redirect,
+-               user.nickname
+-             )
++    assert User.ap_id(user) == url(@endpoint, ~p[/users/#{user.nickname}])
+   end
+ 
+   test "returns an ap_followers link for a user" do
+     user = insert(:user)
+ 
+-    assert User.ap_followers(user) ==
+-             Pleroma.Web.Router.Helpers.user_feed_url(
+-               Pleroma.Web.Endpoint,
+-               :feed_redirect,
+-               user.nickname
+-             ) <> "/followers"
++    assert User.ap_followers(user) == url(@endpoint, ~p[/users/#{user.nickname}/followers])
+   end
+ 
+   describe "remote user changeset" do
+diff --git a/test/pleroma/web/activity_pub/activity_pub_controller_test.exs b/test/pleroma/web/activity_pub/activity_pub_controller_test.exs
+index 4ef0c9302..dcb5f143c 100644
+--- a/test/pleroma/web/activity_pub/activity_pub_controller_test.exs
++++ b/test/pleroma/web/activity_pub/activity_pub_controller_test.exs
+@@ -39,7 +39,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
+     test "with the relay active, it returns the relay user", %{conn: conn} do
+       res =
+         conn
+-        |> get(activity_pub_path(conn, :relay))
++        |> get(~p"/relay")
+         |> json_response(200)
+ 
+       assert res["id"] =~ "/relay"
+@@ -49,7 +49,7 @@ test "with the relay disabled, it returns 404", %{conn: conn} do
+       clear_config([:instance, :allow_relay], false)
+ 
+       conn
+-      |> get(activity_pub_path(conn, :relay))
++      |> get(~p"/relay")
+       |> json_response(404)
+     end
+ 
+@@ -59,7 +59,7 @@ test "on non-federating instance, it returns 404", %{conn: conn} do
+ 
+       conn
+       |> assign(:user, user)
+-      |> get(activity_pub_path(conn, :relay))
++      |> get(~p"/relay")
+       |> json_response(404)
+     end
+   end
+@@ -68,7 +68,7 @@ test "on non-federating instance, it returns 404", %{conn: conn} do
+     test "it returns the internal fetch user", %{conn: conn} do
+       res =
+         conn
+-        |> get(activity_pub_path(conn, :internal_fetch))
++        |> get(~p"/internal/fetch")
+         |> json_response(200)
+ 
+       assert res["id"] =~ "/fetch"
+@@ -80,7 +80,7 @@ test "on non-federating instance, it returns 404", %{conn: conn} do
+ 
+       conn
+       |> assign(:user, user)
+-      |> get(activity_pub_path(conn, :internal_fetch))
++      |> get(~p"/internal/fetch")
+       |> json_response(404)
+     end
+   end
+diff --git a/test/pleroma/web/activity_pub/activity_pub_test.exs b/test/pleroma/web/activity_pub/activity_pub_test.exs
+index d4d1c2aac..5d5388cf5 100644
+--- a/test/pleroma/web/activity_pub/activity_pub_test.exs
++++ b/test/pleroma/web/activity_pub/activity_pub_test.exs
+@@ -2629,6 +2629,14 @@ test "allow fetching of accounts with an empty string name field" do
+     assert user.name == " "
+   end
+ 
++  test "pin_data_from_featured_collection will ignore unsupported values" do
++    assert %{} ==
++             ActivityPub.pin_data_from_featured_collection(%{
++               "type" => "CollectionThatIsNotRealAndCannotHurtMe",
++               "first" => "https://social.example/users/alice/collections/featured?page=true"
++             })
++  end
++
+   describe "persist/1" do
+     test "should not persist remote delete activities" do
+       poster = insert(:user, local: false)
+diff --git a/test/pleroma/web/admin_api/controllers/admin_api_controller_test.exs b/test/pleroma/web/admin_api/controllers/admin_api_controller_test.exs
+index e0a2cb9de..68d77ae5a 100644
+--- a/test/pleroma/web/admin_api/controllers/admin_api_controller_test.exs
++++ b/test/pleroma/web/admin_api/controllers/admin_api_controller_test.exs
+@@ -783,7 +783,8 @@ test "it confirms emails of two users", %{conn: conn, admin: admin} do
+ 
+   describe "PATCH /resend_confirmation_email" do
+     test "it resend emails for two users", %{conn: conn, admin: admin} do
+-      [first_user, second_user] = insert_pair(:user, is_confirmed: false)
++      [first_user, second_user] =
++        insert_pair(:user, is_confirmed: false, confirmation_token: "something")
+ 
+       ret_conn =
+         patch(conn, "/api/v1/pleroma/admin/users/resend_confirmation_email", %{
+diff --git a/test/pleroma/web/admin_api/controllers/report_controller_test.exs b/test/pleroma/web/admin_api/controllers/report_controller_test.exs
+index 2ec9ca969..ace71785b 100644
+--- a/test/pleroma/web/admin_api/controllers/report_controller_test.exs
++++ b/test/pleroma/web/admin_api/controllers/report_controller_test.exs
+@@ -212,7 +212,7 @@ test "updates state of multiple reports", %{
+     test "returns empty response when no reports created", %{conn: conn} do
+       response =
+         conn
+-        |> get(report_path(conn, :index))
++        |> get(~p"/api/v1/pleroma/admin/reports")
+         |> json_response_and_validate_schema(:ok)
+ 
+       assert Enum.empty?(response["reports"])
+@@ -232,7 +232,7 @@ test "returns reports", %{conn: conn} do
+ 
+       response =
+         conn
+-        |> get(report_path(conn, :index))
++        |> get(~p"/api/v1/pleroma/admin/reports")
+         |> json_response_and_validate_schema(:ok)
+ 
+       [report] = response["reports"]
+@@ -264,7 +264,7 @@ test "returns reports with specified state", %{conn: conn} do
+ 
+       response =
+         conn
+-        |> get(report_path(conn, :index, %{state: "open"}))
++        |> get(~p[/api/v1/pleroma/admin/reports?#{[state: "open"]}])
+         |> json_response_and_validate_schema(:ok)
+ 
+       assert [open_report] = response["reports"]
+@@ -276,7 +276,7 @@ test "returns reports with specified state", %{conn: conn} do
+ 
+       response =
+         conn
+-        |> get(report_path(conn, :index, %{state: "closed"}))
++        |> get(~p[/api/v1/pleroma/admin/reports?#{[state: "closed"]}])
+         |> json_response_and_validate_schema(:ok)
+ 
+       assert [closed_report] = response["reports"]
+@@ -288,7 +288,7 @@ test "returns reports with specified state", %{conn: conn} do
+ 
+       assert %{"total" => 0, "reports" => []} ==
+                conn
+-               |> get(report_path(conn, :index, %{state: "resolved"}))
++               |> get(~p[/api/v1/pleroma/admin/reports?#{[state: "resolved"]}])
+                |> json_response_and_validate_schema(:ok)
+     end
+ 
+diff --git a/test/pleroma/web/admin_api/controllers/user_controller_test.exs b/test/pleroma/web/admin_api/controllers/user_controller_test.exs
+index ba6465630..6dcef1a39 100644
+--- a/test/pleroma/web/admin_api/controllers/user_controller_test.exs
++++ b/test/pleroma/web/admin_api/controllers/user_controller_test.exs
+@@ -685,7 +685,7 @@ test "load users with actor_type is Person", %{admin: admin, conn: conn} do
+ 
+       response =
+         conn
+-        |> get(user_path(conn, :index), %{actor_types: ["Person"]})
++        |> get(~p"/api/v1/pleroma/admin/users", %{actor_types: ["Person"]})
+         |> json_response_and_validate_schema(200)
+ 
+       users = [
+@@ -706,7 +706,7 @@ test "load users with actor_type is Person and Service", %{admin: admin, conn: c
+ 
+       response =
+         conn
+-        |> get(user_path(conn, :index), %{actor_types: ["Person", "Service"]})
++        |> get(~p"/api/v1/pleroma/admin/users", %{actor_types: ["Person", "Service"]})
+         |> json_response_and_validate_schema(200)
+ 
+       users = [
+@@ -727,7 +727,7 @@ test "load users with actor_type is Service", %{conn: conn} do
+ 
+       response =
+         conn
+-        |> get(user_path(conn, :index), %{actor_types: ["Service"]})
++        |> get(~p"/api/v1/pleroma/admin/users", %{actor_types: ["Service"]})
+         |> json_response_and_validate_schema(200)
+ 
+       users = [user_response(user_service, %{"actor_type" => "Service"})]
+diff --git a/test/pleroma/web/feed/tag_controller_test.exs b/test/pleroma/web/feed/tag_controller_test.exs
+index fe7940f41..f67d5485d 100644
+--- a/test/pleroma/web/feed/tag_controller_test.exs
++++ b/test/pleroma/web/feed/tag_controller_test.exs
+@@ -50,7 +50,7 @@ test "gets a feed (ATOM)", %{conn: conn} do
+     response =
+       conn
+       |> put_req_header("accept", "application/atom+xml")
+-      |> get(tag_feed_path(conn, :feed, "pleromaart.atom"))
++      |> get(~p"/tags/pleromaart.atom")
+       |> response(200)
+ 
+     xml = parse(response)
+@@ -117,7 +117,7 @@ test "gets a feed (RSS)", %{conn: conn} do
+     response =
+       conn
+       |> put_req_header("accept", "application/rss+xml")
+-      |> get(tag_feed_path(conn, :feed, "pleromaart.rss"))
++      |> get(~p"/tags/pleromaart.rss")
+       |> response(200)
+ 
+     xml = parse(response)
+@@ -157,7 +157,7 @@ test "gets a feed (RSS)", %{conn: conn} do
+     response =
+       conn
+       |> put_req_header("accept", "application/rss+xml")
+-      |> get(tag_feed_path(conn, :feed, "pleromaart.rss"))
++      |> get(~p"/tags/pleromaart.rss")
+       |> response(200)
+ 
+     xml = parse(response)
+@@ -188,7 +188,7 @@ test "gets a feed (RSS)", %{conn: conn} do
+     test "returns 404 for tags feed", %{conn: conn} do
+       conn
+       |> put_req_header("accept", "application/rss+xml")
+-      |> get(tag_feed_path(conn, :feed, "pleromaart.rss"))
++      |> get(~p"/tags/pleromaart.rss")
+       |> response(404)
+     end
+   end
+diff --git a/test/pleroma/web/feed/user_controller_test.exs b/test/pleroma/web/feed/user_controller_test.exs
+index 245ffcf0a..d5d9faf06 100644
+--- a/test/pleroma/web/feed/user_controller_test.exs
++++ b/test/pleroma/web/feed/user_controller_test.exs
+@@ -66,7 +66,7 @@ test "gets an atom feed", %{conn: conn, user: user, object: object, max_id: max_
+       resp =
+         conn
+         |> put_req_header("accept", "application/atom+xml")
+-        |> get(user_feed_path(conn, :feed, user.nickname))
++        |> get(~p[/users/#{user.nickname}/feed])
+         |> response(200)
+ 
+       activity_titles =
+@@ -128,7 +128,7 @@ test "returns 404 for a missing feed", %{conn: conn} do
+       conn =
+         conn
+         |> put_req_header("accept", "application/atom+xml")
+-        |> get(user_feed_path(conn, :feed, "nonexisting"))
++        |> get(~p"/users/nonexisting/feed")
+ 
+       assert response(conn, 404)
+     end
+@@ -144,7 +144,7 @@ test "returns feed with public and unlisted activities", %{conn: conn} do
+       resp =
+         conn
+         |> put_req_header("accept", "application/atom+xml")
+-        |> get(user_feed_path(conn, :feed, user.nickname))
++        |> get(~p[/users/#{user.nickname}/feed])
+         |> response(200)
+ 
+       activity_titles =
+@@ -163,7 +163,7 @@ test "returns 404 when the user is remote", %{conn: conn} do
+ 
+       assert conn
+              |> put_req_header("accept", "application/atom+xml")
+-             |> get(user_feed_path(conn, :feed, user.nickname))
++             |> get(~p[/users/#{user.nickname}/feed])
+              |> response(404)
+     end
+ 
+@@ -240,7 +240,7 @@ test "with non-html / non-json format, it returns error when user is not found",
+       response =
+         conn
+         |> put_req_header("accept", "application/xml")
+-        |> get(user_feed_path(conn, :feed, "jimm"))
++        |> get(~p"/users/jimm/feed")
+         |> response(404)
+ 
+       assert response == ~S({"error":"Not found"})
+@@ -258,7 +258,7 @@ test "returns 404 for user feed", %{conn: conn} do
+ 
+       assert conn
+              |> put_req_header("accept", "application/atom+xml")
+-             |> get(user_feed_path(conn, :feed, user.nickname))
++             |> get(~p[/users/#{user.nickname}/feed])
+              |> response(404)
+     end
+   end
+diff --git a/test/pleroma/web/mastodon_api/update_credentials_test.exs b/test/pleroma/web/mastodon_api/update_credentials_test.exs
+index a347c7987..ebd536b0b 100644
+--- a/test/pleroma/web/mastodon_api/update_credentials_test.exs
++++ b/test/pleroma/web/mastodon_api/update_credentials_test.exs
+@@ -529,7 +529,7 @@ test "update fields with a link to content with rel=me, with frontend path", %{
+       user: user,
+       conn: conn
+     } do
+-      fe_url = "#{Pleroma.Web.Endpoint.url()}/#{user.nickname}"
++      fe_url = url(~p[/#{user.nickname}])
+ 
+       Tesla.Mock.mock(fn
+         %{url: "http://example.com/rel_me/fe_path"} ->
+diff --git a/test/pleroma/web/mastodon_api/views/status_view_test.exs b/test/pleroma/web/mastodon_api/views/status_view_test.exs
+index 682c633f4..22f65a0d1 100644
+--- a/test/pleroma/web/mastodon_api/views/status_view_test.exs
++++ b/test/pleroma/web/mastodon_api/views/status_view_test.exs
+@@ -258,7 +258,7 @@ test "a note activity" do
+     expected = %{
+       id: to_string(note.id),
+       uri: object_data["id"],
+-      url: Pleroma.Web.Router.Helpers.o_status_url(Pleroma.Web.Endpoint, :notice, note),
++      url: url(~p[/notice/#{note}]),
+       account: AccountView.render("show.json", %{user: user, skip_visibility_check: true}),
+       in_reply_to_id: nil,
+       in_reply_to_account_id: nil,
+diff --git a/test/pleroma/web/metadata/providers/twitter_card_test.exs b/test/pleroma/web/metadata/providers/twitter_card_test.exs
+index 731447094..cd3f5eced 100644
+--- a/test/pleroma/web/metadata/providers/twitter_card_test.exs
++++ b/test/pleroma/web/metadata/providers/twitter_card_test.exs
+@@ -12,7 +12,6 @@ defmodule Pleroma.Web.Metadata.Providers.TwitterCardTest do
+   alias Pleroma.Web.MediaProxy
+   alias Pleroma.Web.Metadata.Providers.TwitterCard
+   alias Pleroma.Web.Metadata.Utils
+-  alias Pleroma.Web.Router
+ 
+   setup do: clear_config([Pleroma.Web.Metadata, :unfurl_nsfw])
+ 
+@@ -189,7 +188,7 @@ test "it renders supported types of attachments and skips unknown types" do
+              {:meta,
+               [
+                 name: "twitter:player",
+-                content: Router.Helpers.o_status_url(Endpoint, :notice_player, activity.id)
++                content: url(Endpoint, ~p[/notice/#{activity.id}/embed_player])
+               ], []},
+              {:meta, [name: "twitter:player:width", content: "800"], []},
+              {:meta, [name: "twitter:player:height", content: "600"], []},
+diff --git a/test/pleroma/web/mongoose_im_controller_test.exs b/test/pleroma/web/mongoose_im_controller_test.exs
+index bda7c613f..cf7db6d93 100644
+--- a/test/pleroma/web/mongoose_im_controller_test.exs
++++ b/test/pleroma/web/mongoose_im_controller_test.exs
+@@ -13,28 +13,28 @@ test "/user_exists", %{conn: conn} do
+ 
+     res =
+       conn
+-      |> get(mongoose_im_path(conn, :user_exists), user: "lain")
++      |> get(~p"/user_exists", user: "lain")
+       |> json_response(200)
+ 
+     assert res == true
+ 
+     res =
+       conn
+-      |> get(mongoose_im_path(conn, :user_exists), user: "alice")
++      |> get(~p"/user_exists", user: "alice")
+       |> json_response(404)
+ 
+     assert res == false
+ 
+     res =
+       conn
+-      |> get(mongoose_im_path(conn, :user_exists), user: "bob")
++      |> get(~p"/user_exists", user: "bob")
+       |> json_response(404)
+ 
+     assert res == false
+ 
+     res =
+       conn
+-      |> get(mongoose_im_path(conn, :user_exists), user: "konata")
++      |> get(~p"/user_exists", user: "konata")
+       |> json_response(404)
+ 
+     assert res == false
+@@ -52,28 +52,28 @@ test "/check_password", %{conn: conn} do
+ 
+     res =
+       conn
+-      |> get(mongoose_im_path(conn, :check_password), user: user.nickname, pass: "cool")
++      |> get(~p"/check_password", user: user.nickname, pass: "cool")
+       |> json_response(200)
+ 
+     assert res == true
+ 
+     res =
+       conn
+-      |> get(mongoose_im_path(conn, :check_password), user: user.nickname, pass: "uncool")
++      |> get(~p"/check_password", user: user.nickname, pass: "uncool")
+       |> json_response(403)
+ 
+     assert res == false
+ 
+     res =
+       conn
+-      |> get(mongoose_im_path(conn, :check_password), user: "konata", pass: "cool")
++      |> get(~p"/check_password", user: "konata", pass: "cool")
+       |> json_response(404)
+ 
+     assert res == false
+ 
+     res =
+       conn
+-      |> get(mongoose_im_path(conn, :check_password), user: "nobody", pass: "cool")
++      |> get(~p"/check_password", user: "nobody", pass: "cool")
+       |> json_response(404)
+ 
+     assert res == false
+diff --git a/test/pleroma/web/o_auth/o_auth_controller_test.exs b/test/pleroma/web/o_auth/o_auth_controller_test.exs
+index 3748940fd..e0ba339db 100644
+--- a/test/pleroma/web/o_auth/o_auth_controller_test.exs
++++ b/test/pleroma/web/o_auth/o_auth_controller_test.exs
+@@ -57,7 +57,7 @@ test "GET /oauth/authorize renders auth forms, including OAuth consumer form", %
+ 
+       assert response = html_response(conn, 200)
+       assert response =~ "Sign in with Twitter"
+-      assert response =~ o_auth_path(conn, :prepare_request)
++      assert response =~ ~p"/prepare_request"
+     end
+ 
+     test "GET /oauth/prepare_request encodes parameters as `state` and redirects", %{
+@@ -186,7 +186,9 @@ test "on authentication error, GET /oauth/<provider>/callback redirects to `redi
+ 
+       assert html_response(conn, 302)
+       assert redirected_to(conn) == app.redirect_uris
+-      assert get_flash(conn, :error) == "Failed to authenticate: (error description)."
++
++      assert Phoenix.Flash.get(conn.assigns.flash, :error) ==
++               "Failed to authenticate: (error description)."
+     end
+ 
+     test "GET /oauth/registration_details renders registration details form", %{
+@@ -307,7 +309,9 @@ test "with invalid params, POST /oauth/register?op=register renders registration
+           |> post("/oauth/register", bad_params)
+ 
+         assert html_response(conn, 403) =~ ~r/name="op" type="submit" value="register"/
+-        assert get_flash(conn, :error) == "Error: #{bad_param} has already been taken."
++
++        assert Phoenix.Flash.get(conn.assigns.flash, :error) ==
++                 "Error: #{bad_param} has already been taken."
+       end
+     end
+ 
+@@ -398,7 +402,7 @@ test "with invalid params, POST /oauth/register?op=connect renders registration_
+         |> post("/oauth/register", params)
+ 
+       assert html_response(conn, 401) =~ ~r/name="op" type="submit" value="connect"/
+-      assert get_flash(conn, :error) == "Invalid Username/Password"
++      assert Phoenix.Flash.get(conn.assigns.flash, :error) == "Invalid Username/Password"
+     end
+   end
+ 
+diff --git a/test/pleroma/web/twitter_api/remote_follow_controller_test.exs b/test/pleroma/web/twitter_api/remote_follow_controller_test.exs
+index dc300296c..d2bc7840f 100644
+--- a/test/pleroma/web/twitter_api/remote_follow_controller_test.exs
++++ b/test/pleroma/web/twitter_api/remote_follow_controller_test.exs
+@@ -48,9 +48,7 @@ test "adds status to pleroma instance if the `acct` is a status", %{conn: conn}
+ 
+       assert conn
+              |> get(
+-               remote_follow_path(conn, :follow, %{
+-                 acct: "https://mastodon.social/users/emelie/statuses/101849165031453009"
+-               })
++               ~p[/ostatus_subscribe?#{[acct: "https://mastodon.social/users/emelie/statuses/101849165031453009"]}]
+              )
+              |> redirected_to() =~ "/notice/"
+     end
+@@ -77,7 +75,7 @@ test "show follow account page if the `acct` is a account link", %{conn: conn} d
+ 
+       response =
+         conn
+-        |> get(remote_follow_path(conn, :follow, %{acct: "https://mastodon.social/users/emelie"}))
++        |> get(~p[/ostatus_subscribe?#{[acct: "https://mastodon.social/users/emelie"]}])
+         |> html_response(200)
+ 
+       assert response =~ "Log in to follow"
+@@ -108,7 +106,7 @@ test "show follow page if the `acct` is a account link", %{conn: conn} do
+       response =
+         conn
+         |> assign(:user, user)
+-        |> get(remote_follow_path(conn, :follow, %{acct: "https://mastodon.social/users/emelie"}))
++        |> get(~p[/ostatus_subscribe?#{[acct: "https://mastodon.social/users/emelie"]}])
+         |> html_response(200)
+ 
+       assert response =~ "Remote follow"
+@@ -129,9 +127,7 @@ test "show follow page with error when user can not be fetched by `acct` link",
+                  conn
+                  |> assign(:user, user)
+                  |> get(
+-                   remote_follow_path(conn, :follow, %{
+-                     acct: "https://mastodon.social/users/not_found"
+-                   })
++                   ~p[/ostatus_subscribe?#{[acct: "https://mastodon.social/users/not_found"]}]
+                  )
+                  |> html_response(200)
+ 
+@@ -151,7 +147,7 @@ test "required `follow | write:follows` scope", %{conn: conn} do
+                  conn
+                  |> assign(:user, user)
+                  |> assign(:token, read_token)
+-                 |> post(remote_follow_path(conn, :do_follow), %{"user" => %{"id" => user2.id}})
++                 |> post(~p"/ostatus_subscribe", %{"user" => %{"id" => user2.id}})
+                  |> response(200)
+ 
+                assert response =~ "Error following account"
+@@ -166,7 +162,7 @@ test "follows user", %{conn: conn} do
+         conn
+         |> assign(:user, user)
+         |> assign(:token, insert(:oauth_token, user: user, scopes: ["write:follows"]))
+-        |> post(remote_follow_path(conn, :do_follow), %{"user" => %{"id" => user2.id}})
++        |> post(~p"/ostatus_subscribe", %{"user" => %{"id" => user2.id}})
+ 
+       assert redirected_to(conn) == "/users/#{user2.id}"
+     end
+@@ -178,7 +174,7 @@ test "returns error when user is deactivated", %{conn: conn} do
+       response =
+         conn
+         |> assign(:user, user)
+-        |> post(remote_follow_path(conn, :do_follow), %{"user" => %{"id" => user2.id}})
++        |> post(~p"/ostatus_subscribe", %{"user" => %{"id" => user2.id}})
+         |> response(200)
+ 
+       assert response =~ "Error following account"
+@@ -194,7 +190,7 @@ test "returns error when user is blocked", %{conn: conn} do
+       response =
+         conn
+         |> assign(:user, user)
+-        |> post(remote_follow_path(conn, :do_follow), %{"user" => %{"id" => user2.id}})
++        |> post(~p"/ostatus_subscribe", %{"user" => %{"id" => user2.id}})
+         |> response(200)
+ 
+       assert response =~ "Error following account"
+@@ -206,7 +202,7 @@ test "returns error when followee not found", %{conn: conn} do
+       response =
+         conn
+         |> assign(:user, user)
+-        |> post(remote_follow_path(conn, :do_follow), %{"user" => %{"id" => "jimm"}})
++        |> post(~p"/ostatus_subscribe", %{"user" => %{"id" => "jimm"}})
+         |> response(200)
+ 
+       assert response =~ "Error following account"
+@@ -221,7 +217,7 @@ test "returns success result when user already in followers", %{conn: conn} do
+         conn
+         |> assign(:user, refresh_record(user))
+         |> assign(:token, insert(:oauth_token, user: user, scopes: ["write:follows"]))
+-        |> post(remote_follow_path(conn, :do_follow), %{"user" => %{"id" => user2.id}})
++        |> post(~p"/ostatus_subscribe", %{"user" => %{"id" => user2.id}})
+ 
+       assert redirected_to(conn) == "/users/#{user2.id}"
+     end
+@@ -243,7 +239,7 @@ test "render the MFA login form", %{conn: conn} do
+ 
+       response =
+         conn
+-        |> post(remote_follow_path(conn, :do_follow), %{
++        |> post(~p"/ostatus_subscribe", %{
+           "authorization" => %{"name" => user.nickname, "password" => "test", "id" => user2.id}
+         })
+         |> response(200)
+@@ -271,7 +267,7 @@ test "returns error when password is incorrect", %{conn: conn} do
+ 
+       response =
+         conn
+-        |> post(remote_follow_path(conn, :do_follow), %{
++        |> post(~p"/ostatus_subscribe", %{
+           "authorization" => %{"name" => user.nickname, "password" => "test1", "id" => user2.id}
+         })
+         |> response(200)
+@@ -299,7 +295,7 @@ test "follows", %{conn: conn} do
+       conn =
+         conn
+         |> post(
+-          remote_follow_path(conn, :do_follow),
++          ~p"/ostatus_subscribe",
+           %{
+             "mfa" => %{"code" => otp_token, "token" => token, "id" => user2.id}
+           }
+@@ -328,7 +324,7 @@ test "returns error when auth code is incorrect", %{conn: conn} do
+       response =
+         conn
+         |> post(
+-          remote_follow_path(conn, :do_follow),
++          ~p"/ostatus_subscribe",
+           %{
+             "mfa" => %{"code" => otp_token, "token" => token, "id" => user2.id}
+           }
+@@ -347,7 +343,7 @@ test "follows", %{conn: conn} do
+ 
+       conn =
+         conn
+-        |> post(remote_follow_path(conn, :do_follow), %{
++        |> post(~p"/ostatus_subscribe", %{
+           "authorization" => %{"name" => user.nickname, "password" => "test", "id" => user2.id}
+         })
+ 
+@@ -360,7 +356,7 @@ test "returns error when followee not found", %{conn: conn} do
+ 
+       response =
+         conn
+-        |> post(remote_follow_path(conn, :do_follow), %{
++        |> post(~p"/ostatus_subscribe", %{
+           "authorization" => %{"name" => user.nickname, "password" => "test", "id" => "jimm"}
+         })
+         |> response(200)
+@@ -373,7 +369,7 @@ test "returns error when login invalid", %{conn: conn} do
+ 
+       response =
+         conn
+-        |> post(remote_follow_path(conn, :do_follow), %{
++        |> post(~p"/ostatus_subscribe", %{
+           "authorization" => %{"name" => "jimm", "password" => "test", "id" => user.id}
+         })
+         |> response(200)
+@@ -387,7 +383,7 @@ test "returns error when password invalid", %{conn: conn} do
+ 
+       response =
+         conn
+-        |> post(remote_follow_path(conn, :do_follow), %{
++        |> post(~p"/ostatus_subscribe", %{
+           "authorization" => %{"name" => user.nickname, "password" => "42", "id" => user2.id}
+         })
+         |> response(200)
+@@ -403,7 +399,7 @@ test "returns error when user is blocked", %{conn: conn} do
+ 
+       response =
+         conn
+-        |> post(remote_follow_path(conn, :do_follow), %{
++        |> post(~p"/ostatus_subscribe", %{
+           "authorization" => %{"name" => user.nickname, "password" => "test", "id" => user2.id}
+         })
+         |> response(200)
+diff --git a/test/pleroma/web/uploader_controller_test.exs b/test/pleroma/web/uploader_controller_test.exs
+index 032895e71..e11a7367f 100644
+--- a/test/pleroma/web/uploader_controller_test.exs
++++ b/test/pleroma/web/uploader_controller_test.exs
+@@ -10,7 +10,7 @@ defmodule Pleroma.Web.UploaderControllerTest do
+     test "it returns 400 response when process callback isn't alive", %{conn: conn} do
+       res =
+         conn
+-        |> post(uploader_path(conn, :callback, "test-path"))
++        |> post(~p"/api/v1/pleroma/uploader_callback/test-path")
+ 
+       assert res.status == 400
+       assert res.resp_body == "{\"error\":\"bad request\"}"
+@@ -34,7 +34,7 @@ test "it returns success result", %{conn: conn} do
+ 
+       res =
+         conn
+-        |> post(uploader_path(conn, :callback, "test-path"))
++        |> post(~p"/api/v1/pleroma/uploader_callback/test-path")
+         |> json_response(200)
+ 
+       assert res == %{"upload_path" => "test-path"}
+diff --git a/test/support/conn_case.ex b/test/support/conn_case.ex
+index eab469833..21f031bba 100644
+--- a/test/support/conn_case.ex
++++ b/test/support/conn_case.ex
+@@ -27,12 +27,12 @@ defmodule Pleroma.Web.ConnCase do
+       import Plug.Conn
+       import Phoenix.ConnTest
+       use Pleroma.Tests.Helpers
+-      import Pleroma.Web.Router.Helpers
+ 
+       alias Pleroma.Config
+ 
+       # The default endpoint for testing
+       @endpoint Pleroma.Web.Endpoint
++      use Pleroma.Web, :verified_routes
+ 
+       # Sets up OAuth access with specified scopes
+       defp oauth_access(scopes, opts \\ []) do
+diff --git a/test/support/data_case.ex b/test/support/data_case.ex
+index 0ee2aa4a2..5eacc148d 100644
+--- a/test/support/data_case.ex
++++ b/test/support/data_case.ex
+@@ -23,12 +23,14 @@ defmodule Pleroma.DataCase do
+   using do
+     quote do
+       alias Pleroma.Repo
++      @endpoint Pleroma.Web.Endpoint
+ 
+       import Ecto
+       import Ecto.Changeset
+       import Ecto.Query
+       import Pleroma.DataCase
+       use Pleroma.Tests.Helpers
++      use Pleroma.Web, :verified_routes
+ 
+       # Sets up OAuth access with specified scopes
+       defp oauth_access(scopes, opts \\ []) do
+-- 
+2.43.0
+