From ee37b2d8c64b8fee5f6f1634ad817da64e631b98 Mon Sep 17 00:00:00 2001 From: Ekaterina Vaartis Date: Tue, 17 Jun 2025 21:12:20 +0300 Subject: [PATCH 01/17] Return 404 when an activity is sent to a deactivated user's /inbox Also return 404 when the user who sent the activity is believed to be deactivated. It was already an error, now it just returns a better reason than "Invalid request". Also send proper errors when either user is not known at all. --- changelog.d/deactivated-404-inbox.change | 1 + .../activity_pub/activity_pub_controller.ex | 28 ++++++++- .../activity_pub_controller_test.exs | 61 +++++++++++++------ 3 files changed, 71 insertions(+), 19 deletions(-) create mode 100644 changelog.d/deactivated-404-inbox.change diff --git a/changelog.d/deactivated-404-inbox.change b/changelog.d/deactivated-404-inbox.change new file mode 100644 index 000000000..3912c53ef --- /dev/null +++ b/changelog.d/deactivated-404-inbox.change @@ -0,0 +1 @@ +Return 404 with a better error message instead of 400 when receiving an activity for a deactivated user \ No newline at end of file diff --git a/lib/pleroma/web/activity_pub/activity_pub_controller.ex b/lib/pleroma/web/activity_pub/activity_pub_controller.ex index 7ac0bbab4..71e0a24de 100644 --- a/lib/pleroma/web/activity_pub/activity_pub_controller.ex +++ b/lib/pleroma/web/activity_pub/activity_pub_controller.ex @@ -274,13 +274,37 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do end def inbox(%{assigns: %{valid_signature: true}} = conn, %{"nickname" => nickname} = params) do - with %User{is_active: true} = recipient <- User.get_cached_by_nickname(nickname), - {:ok, %User{is_active: true} = actor} <- User.get_or_fetch_by_ap_id(params["actor"]), + with {:recipient_exists, %User{} = recipient} <- + {:recipient_exists, User.get_cached_by_nickname(nickname)}, + {:sender_exists, {:ok, %User{} = actor}} <- + {:sender_exists, User.get_or_fetch_by_ap_id(params["actor"])}, + {:recipient_active, true} <- {:recipient_active, recipient.is_active}, + {:sender_active, true} <- {:sender_active, actor.is_active}, true <- Utils.recipient_in_message(recipient, actor, params), params <- Utils.maybe_splice_recipient(recipient.ap_id, params) do Federator.incoming_ap_doc(params) json(conn, "ok") else + {:recipient_exists, _} -> + conn + |> put_status(:not_found) + |> json("User does not exist") + + {:sender_exists, _} -> + conn + |> put_status(:not_found) + |> json("Sender does not exist") + + {:recipient_active, _} -> + conn + |> put_status(:not_found) + |> json("User deactivated") + + {:sender_active, _} -> + conn + |> put_status(:not_found) + |> json("Sender deactivated") + _ -> conn |> put_status(:bad_request) 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 46b3d5f0d..ded5d9d99 100644 --- a/test/pleroma/web/activity_pub/activity_pub_controller_test.exs +++ b/test/pleroma/web/activity_pub/activity_pub_controller_test.exs @@ -941,23 +941,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do assert Activity.get_by_ap_id(data["id"]) end - test "it rejects an invalid incoming activity", %{conn: conn, data: data} do - user = insert(:user, is_active: false) - - data = - data - |> Map.put("bcc", [user.ap_id]) - |> Kernel.put_in(["object", "bcc"], [user.ap_id]) - - conn = - conn - |> assign(:valid_signature, true) - |> put_req_header("content-type", "application/activity+json") - |> post("/users/#{user.nickname}/inbox", data) - - assert "Invalid request." == json_response(conn, 400) - end - test "it accepts messages with to as string instead of array", %{conn: conn, data: data} do user = insert(:user) @@ -1341,6 +1324,50 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do ObanHelpers.perform(all_enqueued(worker: ReceiverWorker)) assert Activity.get_by_ap_id(data["id"]) end + + test "it returns an error when receiving an activity sent to a deactivated user", %{ + conn: conn, + data: data + } do + user = insert(:user) + {:ok, _} = User.set_activation(user, false) + + data = + data + |> Map.put("bcc", [user.ap_id]) + |> Kernel.put_in(["object", "bcc"], [user.ap_id]) + + conn = + conn + |> assign(:valid_signature, true) + |> put_req_header("content-type", "application/activity+json") + |> post("/users/#{user.nickname}/inbox", data) + + assert "User deactivated" == json_response(conn, 404) + end + + test "it returns an error when receiving an activity sent from a deactivated user", %{ + conn: conn, + data: data + } do + sender = insert(:user) + user = insert(:user) + {:ok, _} = User.set_activation(sender, false) + + data = + data + |> Map.put("bcc", [user.ap_id]) + |> Map.put("actor", sender.ap_id) + |> Kernel.put_in(["object", "bcc"], [user.ap_id]) + + conn = + conn + |> assign(:valid_signature, true) + |> put_req_header("content-type", "application/activity+json") + |> post("/users/#{user.nickname}/inbox", data) + + assert "Sender deactivated" == json_response(conn, 404) + end end describe "GET /users/:nickname/outbox" do From 9eb3fc2d3b66bb7865f6c3699a39ca34ee327cf0 Mon Sep 17 00:00:00 2001 From: Phantasm Date: Mon, 30 Jun 2025 23:25:56 +0200 Subject: [PATCH 02/17] Docs: Avoid long DB restore times and update few things Mostly to avoid long restore times thanks to an index not being built before it's needed by restoring the DB schema first. https://blog.freespeechextremist.com/blog/activities-visibility-index-slowness.html Also updates backup command to compress DB backups, removes Pleroma users's home directory, replaces "role" with "user" in PostgreSQL contexts since they are the same now. --- changelog.d/db-restore-docs.change | 1 + docs/administration/backup.md | 64 +++++++++++++++++++++++------- 2 files changed, 51 insertions(+), 14 deletions(-) create mode 100644 changelog.d/db-restore-docs.change diff --git a/changelog.d/db-restore-docs.change b/changelog.d/db-restore-docs.change new file mode 100644 index 000000000..21e0f8e97 --- /dev/null +++ b/changelog.d/db-restore-docs.change @@ -0,0 +1 @@ +Docs: Restore DB schema before data to avoid long restore times diff --git a/docs/administration/backup.md b/docs/administration/backup.md index 93325e702..794e82b19 100644 --- a/docs/administration/backup.md +++ b/docs/administration/backup.md @@ -4,7 +4,10 @@ 1. Stop the Pleroma service. 2. Go to the working directory of Pleroma (default is `/opt/pleroma`) -3. Run `sudo -Hu postgres pg_dump -d --format=custom -f ` (make sure the postgres user has write access to the destination file) +3. Run (make sure the postgres user has write access to the destination file): +``` +# sudo -Hu postgres pg_dump -d -v --format=custom --compress=9 -f +``` 4. Copy `pleroma.pgdump`, `config/prod.secret.exs`, `config/setup_db.psql` (if still available) and the `uploads` folder to your backup destination. If you have other modifications, copy those changes too. 5. Restart the Pleroma service. @@ -14,16 +17,33 @@ 2. Stop the Pleroma service. 3. Go to the working directory of Pleroma (default is `/opt/pleroma`) 4. Copy the above mentioned files back to their original position. -5. Drop the existing database and user if restoring in-place. `sudo -Hu postgres psql -c 'DROP DATABASE ;';` `sudo -Hu postgres psql -c 'DROP USER ;'` -6. Restore the database schema and pleroma postgres role the with the original `setup_db.psql` if you have it: `sudo -Hu postgres psql -f config/setup_db.psql`. +5. Drop the existing database and user if restoring in-place. +``` +# sudo -Hu postgres dropdb +# sudo -Hu postgres dropuser +``` +6. Restore the database schema and pleroma database user the with the original `setup_db.psql` if you have it: +``` +# sudo -Hu postgres psql -f config/setup_db.psql +``` - Alternatively, run the `mix pleroma.instance gen` task again. You can ignore most of the questions, but make the database user, name, and password the same as found in your backup of `config/prod.secret.exs`. Then run the restoration of the pleroma role and schema with of the generated `config/setup_db.psql` as instructed above. You may delete the `config/generated_config.exs` file as it is not needed. + Alternatively, run the `mix pleroma.instance gen` task again. You can ignore most of the questions, but make the database user, name, and password the same as found in your backup of `config/prod.secret.exs`. Then run the restoration of the pleroma user and schema with the generated `config/setup_db.psql` as instructed above. You may delete the `config/generated_config.exs` file as it is not needed. -7. Now restore the Pleroma instance's data into the empty database schema: `sudo -Hu postgres pg_restore -d -v -1 ` -8. If you installed a newer Pleroma version, you should run `mix ecto.migrate`[^1]. This task performs database migrations, if there were any. -9. Restart the Pleroma service. -10. Run `sudo -Hu postgres vacuumdb --all --analyze-in-stages`. This will quickly generate the statistics so that postgres can properly plan queries. -11. If setting up on a new server configure Nginx by using the `installation/pleroma.nginx` config sample or reference the Pleroma installation guide for your OS which contains the Nginx configuration instructions. +7. Now restore the Pleroma instance's schema into the empty database schema: +``` +# sudo -Hu postgres pg_restore -d -v -s -1 +``` +8. Now restore the Pleroma instance's data into the database: +``` +# sudo -Hu postgres pg_restore -d -v -a -1 --disable-triggers +``` +9. If you installed a newer Pleroma version, you should run `mix ecto.migrate`[^1]. This task performs database migrations, if there were any. +10. Generate the statistics so that PostgreSQL can properly plan queries. +``` +# sudo -Hu postgres vacuumdb -v --all --analyze-in-stages +``` +11. Restart the Pleroma service. +12. If setting up on a new server, configure Nginx by using your original configuration or by using the `installation/pleroma.nginx` config sample or reference the Pleroma installation guide for your OS which contains the Nginx configuration instructions. [^1]: Prefix with `MIX_ENV=prod` to run it using the production config file. @@ -32,10 +52,26 @@ 1. Optionally you can remove the users of your instance. This will trigger delete requests for their accounts and posts. Note that this is 'best effort' and doesn't mean that all traces of your instance will be gone from the fediverse. * You can do this from the admin-FE where you can select all local users and delete the accounts using the *Moderate multiple users* dropdown. * You can also list local users and delete them individually using the CLI tasks for [Managing users](./CLI_tasks/user.md). -2. Stop the Pleroma service `systemctl stop pleroma` -3. Disable pleroma from systemd `systemctl disable pleroma` +2. Stop the Pleroma service: +``` +# systemctl stop pleroma +``` +3. Disable pleroma from systemd: +``` +# systemctl disable pleroma +``` 4. Remove the files and folders you created during installation (see installation guide). This includes the pleroma, nginx and systemd files and folders. -5. Reload nginx now that the configuration is removed `systemctl reload nginx` -6. Remove the database and database user `sudo -Hu postgres psql -c 'DROP DATABASE ;';` `sudo -Hu postgres psql -c 'DROP USER ;'` -7. Remove the system user `userdel pleroma` +5. Reload nginx now that the configuration is removed: +``` +# systemctl reload nginx +``` +6. Remove the database and database user: +``` +# sudo -Hu postgres dropdb +# sudo -Hu postgres dropuser +``` +7. Remove the system user: +``` +# userdel -r pleroma +``` 8. Remove the dependencies that you don't need anymore (see installation guide). Make sure you don't remove packages that are still needed for other software that you have running! From d736d313082c5939e3421921ee51ded2ec5aebce Mon Sep 17 00:00:00 2001 From: Phantasm Date: Tue, 15 Jul 2025 16:08:58 +0200 Subject: [PATCH 03/17] Docs: Add systemctl commands to DB backup/restore --- docs/administration/backup.md | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/docs/administration/backup.md b/docs/administration/backup.md index 794e82b19..326f095ec 100644 --- a/docs/administration/backup.md +++ b/docs/administration/backup.md @@ -2,22 +2,31 @@ ## Backup -1. Stop the Pleroma service. -2. Go to the working directory of Pleroma (default is `/opt/pleroma`) +1. Stop the Pleroma service: +``` +# sudo systemctl stop pleroma +``` +2. Go to the working directory of Pleroma (default is `/opt/pleroma`). 3. Run (make sure the postgres user has write access to the destination file): ``` # sudo -Hu postgres pg_dump -d -v --format=custom --compress=9 -f ``` 4. Copy `pleroma.pgdump`, `config/prod.secret.exs`, `config/setup_db.psql` (if still available) and the `uploads` folder to your backup destination. If you have other modifications, copy those changes too. -5. Restart the Pleroma service. +5. Restart the Pleroma service: +``` +# sudo systemctl start pleroma +``` ## Restore/Move 1. Optionally reinstall Pleroma (either on the same server or on another server if you want to move servers). -2. Stop the Pleroma service. -3. Go to the working directory of Pleroma (default is `/opt/pleroma`) +2. Stop the Pleroma service: +``` +# sudo systemctl stop pleroma +``` +3. Go to the working directory of Pleroma (default is `/opt/pleroma`). 4. Copy the above mentioned files back to their original position. -5. Drop the existing database and user if restoring in-place. +5. Drop the existing database and user if restoring in-place: ``` # sudo -Hu postgres dropdb # sudo -Hu postgres dropuser @@ -38,11 +47,14 @@ # sudo -Hu postgres pg_restore -d -v -a -1 --disable-triggers ``` 9. If you installed a newer Pleroma version, you should run `mix ecto.migrate`[^1]. This task performs database migrations, if there were any. -10. Generate the statistics so that PostgreSQL can properly plan queries. +10. Generate the statistics so that PostgreSQL can properly plan queries: ``` # sudo -Hu postgres vacuumdb -v --all --analyze-in-stages ``` -11. Restart the Pleroma service. +11. Restart the Pleroma service: +``` +# sudo systemctl start pleroma +``` 12. If setting up on a new server, configure Nginx by using your original configuration or by using the `installation/pleroma.nginx` config sample or reference the Pleroma installation guide for your OS which contains the Nginx configuration instructions. [^1]: Prefix with `MIX_ENV=prod` to run it using the production config file. @@ -74,4 +86,4 @@ ``` # userdel -r pleroma ``` -8. Remove the dependencies that you don't need anymore (see installation guide). Make sure you don't remove packages that are still needed for other software that you have running! +8. Remove the dependencies that you don't need anymore (see installation guide). **Make sure you don't remove packages that are still needed for other software that you have running!** From 26fe60494246121b59e40898e6b950e61853452c Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Thu, 31 Jul 2025 17:35:11 -0700 Subject: [PATCH 04/17] Hashtag searches now return real results from the database --- lib/pleroma/hashtag.ex | 19 +++ .../controllers/search_controller.ex | 60 +-------- test/pleroma/hashtag_test.exs | 37 ++++++ .../controllers/search_controller_test.exs | 118 ++++++++---------- 4 files changed, 109 insertions(+), 125 deletions(-) diff --git a/lib/pleroma/hashtag.ex b/lib/pleroma/hashtag.ex index 3682f0c14..fdb564fec 100644 --- a/lib/pleroma/hashtag.ex +++ b/lib/pleroma/hashtag.ex @@ -130,4 +130,23 @@ defmodule Pleroma.Hashtag do end def get_recipients_for_activity(_activity), do: [] + + def search(query, options \\ []) do + limit = Keyword.get(options, :limit, 20) + offset = Keyword.get(options, :offset, 0) + + query + |> String.downcase() + |> String.trim() + |> then(fn search_term -> + from(ht in Hashtag, + where: fragment("LOWER(?) LIKE ?", ht.name, ^"%#{search_term}%"), + order_by: [asc: ht.name], + limit: ^limit, + offset: ^offset + ) + |> Repo.all() + |> Enum.map(& &1.name) + end) + end end diff --git a/lib/pleroma/web/mastodon_api/controllers/search_controller.ex b/lib/pleroma/web/mastodon_api/controllers/search_controller.ex index d9a1ba41e..e524a36dd 100644 --- a/lib/pleroma/web/mastodon_api/controllers/search_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/search_controller.ex @@ -7,6 +7,7 @@ defmodule Pleroma.Web.MastodonAPI.SearchController do alias Pleroma.Repo alias Pleroma.User + alias Pleroma.Hashtag alias Pleroma.Web.ControllerHelper alias Pleroma.Web.Endpoint alias Pleroma.Web.MastodonAPI.AccountView @@ -120,69 +121,14 @@ defmodule Pleroma.Web.MastodonAPI.SearchController do defp resource_search(:v2, "hashtags", query, options) do tags_path = Endpoint.url() <> "/tag/" - query - |> prepare_tags(options) + Hashtag.search(query, options) |> Enum.map(fn tag -> %{name: tag, url: tags_path <> tag} end) end defp resource_search(:v1, "hashtags", query, options) do - prepare_tags(query, options) - end - - defp prepare_tags(query, options) do - tags = - query - |> preprocess_uri_query() - |> String.split(~r/[^#\w]+/u, trim: true) - |> Enum.uniq_by(&String.downcase/1) - - explicit_tags = Enum.filter(tags, fn tag -> String.starts_with?(tag, "#") end) - - tags = - if Enum.any?(explicit_tags) do - explicit_tags - else - tags - end - - tags = Enum.map(tags, fn tag -> String.trim_leading(tag, "#") end) - - tags = - if Enum.empty?(explicit_tags) && !options[:skip_joined_tag] do - add_joined_tag(tags) - else - tags - end - - Pleroma.Pagination.paginate_list(tags, options) - end - - defp add_joined_tag(tags) do - tags - |> Kernel.++([joined_tag(tags)]) - |> Enum.uniq_by(&String.downcase/1) - end - - # If `query` is a URI, returns last component of its path, otherwise returns `query` - defp preprocess_uri_query(query) do - if query =~ ~r/https?:\/\// do - query - |> String.trim_trailing("/") - |> URI.parse() - |> Map.get(:path) - |> String.split("/") - |> Enum.at(-1) - else - query - end - end - - defp joined_tag(tags) do - tags - |> Enum.map(fn tag -> String.capitalize(tag) end) - |> Enum.join() + Hashtag.search(query, options) end defp with_fallback(f, fallback \\ []) do diff --git a/test/pleroma/hashtag_test.exs b/test/pleroma/hashtag_test.exs index 8531b1879..a84effc5d 100644 --- a/test/pleroma/hashtag_test.exs +++ b/test/pleroma/hashtag_test.exs @@ -14,4 +14,41 @@ defmodule Pleroma.HashtagTest do assert {:name, {"can't be blank", [validation: :required]}} in changeset.errors end end + + describe "search_hashtags" do + test "searches hashtags by partial match" do + {:ok, _} = Hashtag.get_or_create_by_name("car") + {:ok, _} = Hashtag.get_or_create_by_name("racecar") + {:ok, _} = Hashtag.get_or_create_by_name("nascar") + {:ok, _} = Hashtag.get_or_create_by_name("bicycle") + + results = Hashtag.search("car") + assert "car" in results + assert "racecar" in results + assert "nascar" in results + refute "bicycle" in results + + results = Hashtag.search("race") + assert "racecar" in results + refute "car" in results + refute "nascar" in results + refute "bicycle" in results + + results = Hashtag.search("nonexistent") + assert results == [] + end + + test "supports pagination" do + {:ok, _} = Hashtag.get_or_create_by_name("alpha") + {:ok, _} = Hashtag.get_or_create_by_name("beta") + {:ok, _} = Hashtag.get_or_create_by_name("gamma") + {:ok, _} = Hashtag.get_or_create_by_name("delta") + + results = Hashtag.search("a", limit: 2) + assert length(results) == 2 + + results = Hashtag.search("a", limit: 2, offset: 1) + assert length(results) == 2 + end + end end diff --git a/test/pleroma/web/mastodon_api/controllers/search_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/search_controller_test.exs index d8263dfad..1fbc7c9c6 100644 --- a/test/pleroma/web/mastodon_api/controllers/search_controller_test.exs +++ b/test/pleroma/web/mastodon_api/controllers/search_controller_test.exs @@ -130,84 +130,66 @@ defmodule Pleroma.Web.MastodonAPI.SearchControllerTest do assert [] = results["statuses"] end - test "constructs hashtags from search query", %{conn: conn} do + test "returns empty results when no hashtags match", %{conn: conn} do results = conn - |> get("/api/v2/search?#{URI.encode_query(%{q: "some text with #explicit #hashtags"})}") + |> get("/api/v2/search?#{URI.encode_query(%{q: "nonexistent"})}") |> json_response_and_validate_schema(200) - assert results["hashtags"] == [ - %{"name" => "explicit", "url" => "#{Endpoint.url()}/tag/explicit"}, - %{"name" => "hashtags", "url" => "#{Endpoint.url()}/tag/hashtags"} - ] - - results = - conn - |> get("/api/v2/search?#{URI.encode_query(%{q: "john doe JOHN DOE"})}") - |> json_response_and_validate_schema(200) - - assert results["hashtags"] == [ - %{"name" => "john", "url" => "#{Endpoint.url()}/tag/john"}, - %{"name" => "doe", "url" => "#{Endpoint.url()}/tag/doe"}, - %{"name" => "JohnDoe", "url" => "#{Endpoint.url()}/tag/JohnDoe"} - ] - - results = - conn - |> get("/api/v2/search?#{URI.encode_query(%{q: "accident-prone"})}") - |> json_response_and_validate_schema(200) - - assert results["hashtags"] == [ - %{"name" => "accident", "url" => "#{Endpoint.url()}/tag/accident"}, - %{"name" => "prone", "url" => "#{Endpoint.url()}/tag/prone"}, - %{"name" => "AccidentProne", "url" => "#{Endpoint.url()}/tag/AccidentProne"} - ] - - results = - conn - |> get("/api/v2/search?#{URI.encode_query(%{q: "https://shpposter.club/users/shpuld"})}") - |> json_response_and_validate_schema(200) - - assert results["hashtags"] == [ - %{"name" => "shpuld", "url" => "#{Endpoint.url()}/tag/shpuld"} - ] - - results = - conn - |> get( - "/api/v2/search?#{URI.encode_query(%{q: "https://www.washingtonpost.com/sports/2020/06/10/" <> "nascar-ban-display-confederate-flag-all-events-properties/"})}" - ) - |> json_response_and_validate_schema(200) - - assert results["hashtags"] == [ - %{"name" => "nascar", "url" => "#{Endpoint.url()}/tag/nascar"}, - %{"name" => "ban", "url" => "#{Endpoint.url()}/tag/ban"}, - %{"name" => "display", "url" => "#{Endpoint.url()}/tag/display"}, - %{"name" => "confederate", "url" => "#{Endpoint.url()}/tag/confederate"}, - %{"name" => "flag", "url" => "#{Endpoint.url()}/tag/flag"}, - %{"name" => "all", "url" => "#{Endpoint.url()}/tag/all"}, - %{"name" => "events", "url" => "#{Endpoint.url()}/tag/events"}, - %{"name" => "properties", "url" => "#{Endpoint.url()}/tag/properties"}, - %{ - "name" => "NascarBanDisplayConfederateFlagAllEventsProperties", - "url" => - "#{Endpoint.url()}/tag/NascarBanDisplayConfederateFlagAllEventsProperties" - } - ] + assert results["hashtags"] == [] end test "supports pagination of hashtags search results", %{conn: conn} do + user = insert(:user) + + {:ok, _activity1} = CommonAPI.post(user, %{status: "First #alpha hashtag"}) + {:ok, _activity2} = CommonAPI.post(user, %{status: "Second #beta hashtag"}) + {:ok, _activity3} = CommonAPI.post(user, %{status: "Third #gamma hashtag"}) + {:ok, _activity4} = CommonAPI.post(user, %{status: "Fourth #delta hashtag"}) + results = conn - |> get( - "/api/v2/search?#{URI.encode_query(%{q: "#some #text #with #hashtags", limit: 2, offset: 1})}" - ) + |> get("/api/v2/search?#{URI.encode_query(%{q: "a", limit: 2, offset: 1})}") |> json_response_and_validate_schema(200) - assert results["hashtags"] == [ - %{"name" => "text", "url" => "#{Endpoint.url()}/tag/text"}, - %{"name" => "with", "url" => "#{Endpoint.url()}/tag/with"} - ] + hashtag_names = Enum.map(results["hashtags"], & &1["name"]) + + # Should return 2 hashtags (alpha, beta, gamma, delta all contain 'a') + # With offset 1, we skip the first one, so we get 2 of the remaining 3 + assert length(hashtag_names) == 2 + assert Enum.all?(hashtag_names, &String.contains?(&1, "a")) + end + + test "searches real hashtags from database", %{conn: conn} do + user = insert(:user) + + {:ok, _activity1} = CommonAPI.post(user, %{status: "Check out this #car"}) + {:ok, _activity2} = CommonAPI.post(user, %{status: "Fast #racecar on the track"}) + {:ok, _activity3} = CommonAPI.post(user, %{status: "NASCAR #nascar racing"}) + + results = + conn + |> get("/api/v2/search?#{URI.encode_query(%{q: "car"})}") + |> json_response_and_validate_schema(200) + + hashtag_names = Enum.map(results["hashtags"], & &1["name"]) + + # Should return car, racecar, and nascar since they all contain "car" + assert "car" in hashtag_names + assert "racecar" in hashtag_names + assert "nascar" in hashtag_names + + # Search for "race" - should return racecar + results = + conn + |> get("/api/v2/search?#{URI.encode_query(%{q: "race"})}") + |> json_response_and_validate_schema(200) + + hashtag_names = Enum.map(results["hashtags"], & &1["name"]) + + assert "racecar" in hashtag_names + refute "car" in hashtag_names + refute "nascar" in hashtag_names end test "excludes a blocked users from search results", %{conn: conn} do @@ -314,7 +296,7 @@ defmodule Pleroma.Web.MastodonAPI.SearchControllerTest do [account | _] = results["accounts"] assert account["id"] == to_string(user_three.id) - assert results["hashtags"] == ["2hu"] + assert results["hashtags"] == [] [status] = results["statuses"] assert status["id"] == to_string(activity.id) From 93c144e397d408d7ff1761640e12fb51e333b2ce Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Thu, 31 Jul 2025 17:46:32 -0700 Subject: [PATCH 05/17] Improve hashtag search with multi word queries --- changelog.d/hashtag-search.change | 1 + lib/pleroma/hashtag.ex | 22 +++++++--- test/pleroma/hashtag_test.exs | 44 +++++++++++++++++++ .../controllers/search_controller_test.exs | 31 +++++++++++++ 4 files changed, 92 insertions(+), 6 deletions(-) create mode 100644 changelog.d/hashtag-search.change diff --git a/changelog.d/hashtag-search.change b/changelog.d/hashtag-search.change new file mode 100644 index 000000000..f17e711ce --- /dev/null +++ b/changelog.d/hashtag-search.change @@ -0,0 +1 @@ +Hashtag searches return real results based on words in your query diff --git a/lib/pleroma/hashtag.ex b/lib/pleroma/hashtag.ex index fdb564fec..99e6eb39b 100644 --- a/lib/pleroma/hashtag.ex +++ b/lib/pleroma/hashtag.ex @@ -135,18 +135,28 @@ defmodule Pleroma.Hashtag do limit = Keyword.get(options, :limit, 20) offset = Keyword.get(options, :offset, 0) - query - |> String.downcase() - |> String.trim() - |> then(fn search_term -> + search_terms = + query + |> String.downcase() + |> String.trim() + |> String.split(~r/\s+/) + |> Enum.filter(&(&1 != "")) + + if Enum.empty?(search_terms) do + [] + else + # Use PostgreSQL's ANY operator with array for efficient multi-term search + # This is much more efficient than multiple OR clauses + search_patterns = Enum.map(search_terms, &"%#{&1}%") + from(ht in Hashtag, - where: fragment("LOWER(?) LIKE ?", ht.name, ^"%#{search_term}%"), + where: fragment("LOWER(?) LIKE ANY(?)", ht.name, ^search_patterns), order_by: [asc: ht.name], limit: ^limit, offset: ^offset ) |> Repo.all() |> Enum.map(& &1.name) - end) + end end end diff --git a/test/pleroma/hashtag_test.exs b/test/pleroma/hashtag_test.exs index a84effc5d..907c5ff40 100644 --- a/test/pleroma/hashtag_test.exs +++ b/test/pleroma/hashtag_test.exs @@ -38,6 +38,35 @@ defmodule Pleroma.HashtagTest do assert results == [] end + test "searches hashtags by multiple words in query" do + # Create some hashtags + {:ok, _} = Hashtag.get_or_create_by_name("computer") + {:ok, _} = Hashtag.get_or_create_by_name("laptop") + {:ok, _} = Hashtag.get_or_create_by_name("desktop") + {:ok, _} = Hashtag.get_or_create_by_name("phone") + + # Search for "new computer" - should return "computer" + results = Hashtag.search("new computer") + assert "computer" in results + refute "laptop" in results + refute "desktop" in results + refute "phone" in results + + # Search for "computer laptop" - should return both + results = Hashtag.search("computer laptop") + assert "computer" in results + assert "laptop" in results + refute "desktop" in results + refute "phone" in results + + # Search for "new phone" - should return "phone" + results = Hashtag.search("new phone") + assert "phone" in results + refute "computer" in results + refute "laptop" in results + refute "desktop" in results + end + test "supports pagination" do {:ok, _} = Hashtag.get_or_create_by_name("alpha") {:ok, _} = Hashtag.get_or_create_by_name("beta") @@ -50,5 +79,20 @@ defmodule Pleroma.HashtagTest do results = Hashtag.search("a", limit: 2, offset: 1) assert length(results) == 2 end + + test "handles many search terms efficiently" do + # Create hashtags + {:ok, _} = Hashtag.get_or_create_by_name("computer") + {:ok, _} = Hashtag.get_or_create_by_name("laptop") + {:ok, _} = Hashtag.get_or_create_by_name("phone") + {:ok, _} = Hashtag.get_or_create_by_name("tablet") + + # Search with many terms - should be efficient with PostgreSQL ANY operator + results = Hashtag.search("new fast computer laptop phone tablet device") + assert "computer" in results + assert "laptop" in results + assert "phone" in results + assert "tablet" in results + end end end diff --git a/test/pleroma/web/mastodon_api/controllers/search_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/search_controller_test.exs index 1fbc7c9c6..8b4c6add2 100644 --- a/test/pleroma/web/mastodon_api/controllers/search_controller_test.exs +++ b/test/pleroma/web/mastodon_api/controllers/search_controller_test.exs @@ -139,6 +139,37 @@ defmodule Pleroma.Web.MastodonAPI.SearchControllerTest do assert results["hashtags"] == [] end + test "searches hashtags by multiple words in query", %{conn: conn} do + user = insert(:user) + + {:ok, _activity1} = CommonAPI.post(user, %{status: "This is my new #computer"}) + {:ok, _activity2} = CommonAPI.post(user, %{status: "Check out this #laptop"}) + {:ok, _activity3} = CommonAPI.post(user, %{status: "My #desktop setup"}) + {:ok, _activity4} = CommonAPI.post(user, %{status: "New #phone arrived"}) + + results = + conn + |> get("/api/v2/search?#{URI.encode_query(%{q: "new computer"})}") + |> json_response_and_validate_schema(200) + + hashtag_names = Enum.map(results["hashtags"], & &1["name"]) + assert "computer" in hashtag_names + refute "laptop" in hashtag_names + refute "desktop" in hashtag_names + refute "phone" in hashtag_names + + results = + conn + |> get("/api/v2/search?#{URI.encode_query(%{q: "computer laptop"})}") + |> json_response_and_validate_schema(200) + + hashtag_names = Enum.map(results["hashtags"], & &1["name"]) + assert "computer" in hashtag_names + assert "laptop" in hashtag_names + refute "desktop" in hashtag_names + refute "phone" in hashtag_names + end + test "supports pagination of hashtags search results", %{conn: conn} do user = insert(:user) From b1acc9281a69602b71ba35166e787efd000efa50 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Thu, 31 Jul 2025 18:02:33 -0700 Subject: [PATCH 06/17] Use ranking to improve order of results --- lib/pleroma/hashtag.ex | 36 ++++++++++++++++++++++++++++++++--- test/pleroma/hashtag_test.exs | 36 +++++++++++++++++++++++++++++++---- 2 files changed, 65 insertions(+), 7 deletions(-) diff --git a/lib/pleroma/hashtag.ex b/lib/pleroma/hashtag.ex index 99e6eb39b..8cffe840f 100644 --- a/lib/pleroma/hashtag.ex +++ b/lib/pleroma/hashtag.ex @@ -149,9 +149,39 @@ defmodule Pleroma.Hashtag do # This is much more efficient than multiple OR clauses search_patterns = Enum.map(search_terms, &"%#{&1}%") - from(ht in Hashtag, - where: fragment("LOWER(?) LIKE ANY(?)", ht.name, ^search_patterns), - order_by: [asc: ht.name], + # Create ranking query that prioritizes exact matches and closer matches + # Use a subquery to properly handle computed columns in ORDER BY + base_query = + from(ht in Hashtag, + where: fragment("LOWER(?) LIKE ANY(?)", ht.name, ^search_patterns), + select: %{ + name: ht.name, + # Ranking: exact matches get highest priority (0), then prefix matches (1), then contains (2) + match_rank: + fragment( + """ + CASE + WHEN LOWER(?) = ANY(?) THEN 0 + WHEN LOWER(?) LIKE ANY(?) THEN 1 + ELSE 2 + END + """, + ht.name, + ^search_terms, + ht.name, + ^Enum.map(search_terms, &"#{&1}%") + ), + # Secondary sort by name length (shorter names first) + name_length: fragment("LENGTH(?)", ht.name) + } + ) + + from(result in subquery(base_query), + order_by: [ + asc: result.match_rank, + asc: result.name_length, + asc: result.name + ], limit: ^limit, offset: ^offset ) diff --git a/test/pleroma/hashtag_test.exs b/test/pleroma/hashtag_test.exs index 907c5ff40..d15c7d1d9 100644 --- a/test/pleroma/hashtag_test.exs +++ b/test/pleroma/hashtag_test.exs @@ -39,7 +39,6 @@ defmodule Pleroma.HashtagTest do end test "searches hashtags by multiple words in query" do - # Create some hashtags {:ok, _} = Hashtag.get_or_create_by_name("computer") {:ok, _} = Hashtag.get_or_create_by_name("laptop") {:ok, _} = Hashtag.get_or_create_by_name("desktop") @@ -80,19 +79,48 @@ defmodule Pleroma.HashtagTest do assert length(results) == 2 end - test "handles many search terms efficiently" do - # Create hashtags + test "handles matching many search terms" do {:ok, _} = Hashtag.get_or_create_by_name("computer") {:ok, _} = Hashtag.get_or_create_by_name("laptop") {:ok, _} = Hashtag.get_or_create_by_name("phone") {:ok, _} = Hashtag.get_or_create_by_name("tablet") - # Search with many terms - should be efficient with PostgreSQL ANY operator results = Hashtag.search("new fast computer laptop phone tablet device") assert "computer" in results assert "laptop" in results assert "phone" in results assert "tablet" in results end + + test "ranks results by match quality" do + {:ok, _} = Hashtag.get_or_create_by_name("my_computer") + {:ok, _} = Hashtag.get_or_create_by_name("computer_science") + {:ok, _} = Hashtag.get_or_create_by_name("computer") + + results = Hashtag.search("computer") + + # Exact match first + assert Enum.at(results, 0) == "computer" + + # Prefix match would be next + assert Enum.at(results, 1) == "computer_science" + + # worst match is last + assert Enum.at(results, 2) == "my_computer" + end + + test "prioritizes shorter names when ranking is equal" do + # Create hashtags with same ranking but different lengths + {:ok, _} = Hashtag.get_or_create_by_name("car") + {:ok, _} = Hashtag.get_or_create_by_name("racecar") + {:ok, _} = Hashtag.get_or_create_by_name("nascar") + + # Search for "car" - shorter names should come first + results = Hashtag.search("car") + # Shortest exact match first + assert Enum.at(results, 0) == "car" + assert "racecar" in results + assert "nascar" in results + end end end From 97e668f4aa5e4ae7b45158b9bc3ff1982baf4089 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Thu, 31 Jul 2025 18:07:05 -0700 Subject: [PATCH 07/17] Alpha sort the aliases --- lib/pleroma/web/mastodon_api/controllers/search_controller.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pleroma/web/mastodon_api/controllers/search_controller.ex b/lib/pleroma/web/mastodon_api/controllers/search_controller.ex index e524a36dd..53f1216fd 100644 --- a/lib/pleroma/web/mastodon_api/controllers/search_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/search_controller.ex @@ -5,9 +5,9 @@ defmodule Pleroma.Web.MastodonAPI.SearchController do use Pleroma.Web, :controller + alias Pleroma.Hashtag alias Pleroma.Repo alias Pleroma.User - alias Pleroma.Hashtag alias Pleroma.Web.ControllerHelper alias Pleroma.Web.Endpoint alias Pleroma.Web.MastodonAPI.AccountView From 19f32f7b0981bdfc0da30df011ce1ef3df44dbe5 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Thu, 31 Jul 2025 18:17:58 -0700 Subject: [PATCH 08/17] Strip hashtag prefixes Users may actually type in a literal hashtag into the search, so this will ensure it still returns results. --- lib/pleroma/hashtag.ex | 2 ++ test/pleroma/hashtag_test.exs | 20 ++++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/lib/pleroma/hashtag.ex b/lib/pleroma/hashtag.ex index 8cffe840f..507bc09bd 100644 --- a/lib/pleroma/hashtag.ex +++ b/lib/pleroma/hashtag.ex @@ -141,6 +141,8 @@ defmodule Pleroma.Hashtag do |> String.trim() |> String.split(~r/\s+/) |> Enum.filter(&(&1 != "")) + |> Enum.map(&String.trim_leading(&1, "#")) + |> Enum.filter(&(&1 != "")) if Enum.empty?(search_terms) do [] diff --git a/test/pleroma/hashtag_test.exs b/test/pleroma/hashtag_test.exs index d15c7d1d9..0e16b8155 100644 --- a/test/pleroma/hashtag_test.exs +++ b/test/pleroma/hashtag_test.exs @@ -122,5 +122,25 @@ defmodule Pleroma.HashtagTest do assert "racecar" in results assert "nascar" in results end + + test "handles hashtag symbols in search query" do + {:ok, _} = Hashtag.get_or_create_by_name("computer") + {:ok, _} = Hashtag.get_or_create_by_name("laptop") + {:ok, _} = Hashtag.get_or_create_by_name("phone") + + results_with_hash = Hashtag.search("#computer #laptop") + results_without_hash = Hashtag.search("computer laptop") + + assert results_with_hash == results_without_hash + + results_mixed = Hashtag.search("#computer laptop #phone") + assert "computer" in results_mixed + assert "laptop" in results_mixed + assert "phone" in results_mixed + + results_only_hash = Hashtag.search("#computer") + results_no_hash = Hashtag.search("computer") + assert results_only_hash == results_no_hash + end end end From eac8ef79513e9ce425cec30e8f12489eccdf3305 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Fri, 1 Aug 2025 10:41:53 -0700 Subject: [PATCH 09/17] Credo --- lib/pleroma/hashtag.ex | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/pleroma/hashtag.ex b/lib/pleroma/hashtag.ex index 507bc09bd..91c30c6e7 100644 --- a/lib/pleroma/hashtag.ex +++ b/lib/pleroma/hashtag.ex @@ -158,7 +158,8 @@ defmodule Pleroma.Hashtag do where: fragment("LOWER(?) LIKE ANY(?)", ht.name, ^search_patterns), select: %{ name: ht.name, - # Ranking: exact matches get highest priority (0), then prefix matches (1), then contains (2) + # Ranking: exact matches get highest priority (0) + # then prefix matches (1), then contains (2) match_rank: fragment( """ From 4b01c0f165d1f930291fa1d533dfda599e6e7aab Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Fri, 1 Aug 2025 11:41:36 -0700 Subject: [PATCH 10/17] Update Tesla to 1.15.3 --- changelog.d/tesla.change | 1 + mix.lock | 12 ++++++------ 2 files changed, 7 insertions(+), 6 deletions(-) create mode 100644 changelog.d/tesla.change diff --git a/changelog.d/tesla.change b/changelog.d/tesla.change new file mode 100644 index 000000000..bd0ec6e94 --- /dev/null +++ b/changelog.d/tesla.change @@ -0,0 +1 @@ +Updated Tesla to 1.15.3 diff --git a/mix.lock b/mix.lock index a708087c9..2f02ba6f1 100644 --- a/mix.lock +++ b/mix.lock @@ -11,7 +11,7 @@ "cachex": {:hex, :cachex, "3.6.0", "14a1bfbeee060dd9bec25a5b6f4e4691e3670ebda28c8ba2884b12fe30b36bf8", [:mix], [{:eternal, "~> 1.2", [hex: :eternal, repo: "hexpm", optional: false]}, {:jumper, "~> 1.0", [hex: :jumper, repo: "hexpm", optional: false]}, {:sleeplocks, "~> 1.1", [hex: :sleeplocks, repo: "hexpm", optional: false]}, {:unsafe, "~> 1.0", [hex: :unsafe, repo: "hexpm", optional: false]}], "hexpm", "ebf24e373883bc8e0c8d894a63bbe102ae13d918f790121f5cfe6e485cc8e2e2"}, "calendar": {:hex, :calendar, "1.0.0", "f52073a708528482ec33d0a171954ca610fe2bd28f1e871f247dc7f1565fa807", [:mix], [{:tzdata, "~> 0.5.20 or ~> 0.1.201603 or ~> 1.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "990e9581920c82912a5ee50e62ff5ef96da6b15949a2ee4734f935fdef0f0a6f"}, "captcha": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/elixir-captcha.git", "e7b7cc34cc16b383461b966484c297e4ec9aeef6", [ref: "e7b7cc34cc16b383461b966484c297e4ec9aeef6"]}, - "castore": {:hex, :castore, "1.0.8", "dedcf20ea746694647f883590b82d9e96014057aff1d44d03ec90f36a5c0dc6e", [:mix], [], "hexpm", "0b2b66d2ee742cb1d9cb8c8be3b43c3a70ee8651f37b75a8b982e036752983f1"}, + "castore": {:hex, :castore, "1.0.14", "4582dd7d630b48cf5e1ca8d3d42494db51e406b7ba704e81fbd401866366896a", [:mix], [], "hexpm", "7bc1b65249d31701393edaaac18ec8398d8974d52c647b7904d01b964137b9f4"}, "cc_precompiler": {:hex, :cc_precompiler, "0.1.9", "e8d3364f310da6ce6463c3dd20cf90ae7bbecbf6c5203b98bf9b48035592649b", [:mix], [{:elixir_make, "~> 0.7", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "9dcab3d0f3038621f1601f13539e7a9ee99843862e66ad62827b0c42b2f58a54"}, "certifi": {:hex, :certifi, "2.12.0", "2d1cca2ec95f59643862af91f001478c9863c2ac9cb6e2f89780bfd8de987329", [:rebar3], [], "hexpm", "ee68d85df22e554040cdb4be100f33873ac6051387baf6a8f6ce82272340ff1c"}, "combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm", "1b1dbc1790073076580d0d1d64e42eae2366583e7aecd455d1215b0d16f2451b"}, @@ -22,7 +22,7 @@ "covertool": {:hex, :covertool, "2.0.6", "4a291b4e3449025b0595d8f44c8d7635d4f48f033be2ce88d22a329f36f94a91", [:rebar3], [], "hexpm", "5db3fcd82180d8ea4ad857d4d1ab21a8d31b5aee0d60d2f6c0f9e25a411d1e21"}, "cowboy": {:hex, :cowboy, "2.12.0", "f276d521a1ff88b2b9b4c54d0e753da6c66dd7be6c9fca3d9418b561828a3731", [:make, :rebar3], [{:cowlib, "2.13.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "8a7abe6d183372ceb21caa2709bec928ab2b72e18a3911aa1771639bef82651e"}, "cowboy_telemetry": {:hex, :cowboy_telemetry, "0.4.0", "f239f68b588efa7707abce16a84d0d2acf3a0f50571f8bb7f56a15865aae820c", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7d98bac1ee4565d31b62d59f8823dfd8356a169e7fcbb83831b8a5397404c9de"}, - "cowlib": {:hex, :cowlib, "2.13.0", "db8f7505d8332d98ef50a3ef34b34c1afddec7506e4ee4dd4a3a266285d282ca", [:make, :rebar3], [], "hexpm", "e1e1284dc3fc030a64b1ad0d8382ae7e99da46c3246b815318a4b848873800a4"}, + "cowlib": {:hex, :cowlib, "2.15.0", "3c97a318a933962d1c12b96ab7c1d728267d2c523c25a5b57b0f93392b6e9e25", [:make, :rebar3], [], "hexpm", "4f00c879a64b4fe7c8fcb42a4281925e9ffdb928820b03c3ad325a617e857532"}, "credo": {:hex, :credo, "1.7.12", "9e3c20463de4b5f3f23721527fcaf16722ec815e70ff6c60b86412c695d426c1", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "8493d45c656c5427d9c729235b99d498bd133421f3e0a683e5c1b561471291e5"}, "crontab": {:hex, :crontab, "1.1.8", "2ce0e74777dfcadb28a1debbea707e58b879e6aa0ffbf9c9bb540887bce43617", [:mix], [{:ecto, "~> 1.0 or ~> 2.0 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm"}, "custom_base": {:hex, :custom_base, "0.2.1", "4a832a42ea0552299d81652aa0b1f775d462175293e99dfbe4d7dbaab785a706", [:mix], [], "hexpm", "8df019facc5ec9603e94f7270f1ac73ddf339f56ade76a721eaa57c1493ba463"}, @@ -80,8 +80,8 @@ "meck": {:hex, :meck, "0.9.2", "85ccbab053f1db86c7ca240e9fc718170ee5bda03810a6292b5306bf31bae5f5", [:rebar3], [], "hexpm", "81344f561357dc40a8344afa53767c32669153355b626ea9fcbc8da6b3045826"}, "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"}, "mime": {:hex, :mime, "1.6.0", "dabde576a497cef4bbdd60aceee8160e02a6c89250d6c0b29e56c0dfb00db3d2", [:mix], [], "hexpm", "31a1a8613f8321143dde1dafc36006a17d28d02bdfecb9e95a880fa7aabd19a7"}, - "mimerl": {:hex, :mimerl, "1.3.0", "d0cd9fc04b9061f82490f6581e0128379830e78535e017f7780f37fea7545726", [:rebar3], [], "hexpm", "a1e15a50d1887217de95f0b9b0793e32853f7c258a5cd227650889b38839fe9d"}, - "mint": {:hex, :mint, "1.6.1", "065e8a5bc9bbd46a41099dfea3e0656436c5cbcb6e741c80bd2bad5cd872446f", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "4fc518dcc191d02f433393a72a7ba3f6f94b101d094cb6bf532ea54c89423780"}, + "mimerl": {:hex, :mimerl, "1.4.0", "3882a5ca67fbbe7117ba8947f27643557adec38fa2307490c4c4207624cb213b", [:rebar3], [], "hexpm", "13af15f9f68c65884ecca3a3891d50a7b57d82152792f3e19d88650aa126b144"}, + "mint": {:hex, :mint, "1.7.1", "113fdb2b2f3b59e47c7955971854641c61f378549d73e829e1768de90fc1abf1", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0 or ~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "fceba0a4d0f24301ddee3024ae116df1c3f4bb7a563a731f45fdfeb9d39a231b"}, "mochiweb": {:hex, :mochiweb, "2.18.0", "eb55f1db3e6e960fac4e6db4e2db9ec3602cc9f30b86cd1481d56545c3145d2e", [:rebar3], [], "hexpm"}, "mock": {:hex, :mock, "0.3.8", "7046a306b71db2488ef54395eeb74df0a7f335a7caca4a3d3875d1fc81c884dd", [:mix], [{:meck, "~> 0.9.2", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm", "7fa82364c97617d79bb7d15571193fc0c4fe5afd0c932cef09426b3ee6fe2022"}, "mogrify": {:hex, :mogrify, "0.9.3", "238c782f00271dace01369ad35ae2e9dd020feee3443b9299ea5ea6bed559841", [:mix], [], "hexpm", "0189b1e1de27455f2b9ae8cf88239cefd23d38de9276eb5add7159aea51731e6"}, @@ -139,14 +139,14 @@ "telemetry_metrics": {:hex, :telemetry_metrics, "0.6.2", "2caabe9344ec17eafe5403304771c3539f3b6e2f7fb6a6f602558c825d0d0bfb", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "9b43db0dc33863930b9ef9d27137e78974756f5f198cae18409970ed6fa5b561"}, "telemetry_metrics_prometheus_core": {:hex, :telemetry_metrics_prometheus_core, "1.2.0", "b583c3f18508f5c5561b674d16cf5d9afd2ea3c04505b7d92baaeac93c1b8260", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "9cba950e1c4733468efbe3f821841f34ac05d28e7af7798622f88ecdbbe63ea3"}, "telemetry_poller": {:hex, :telemetry_poller, "1.0.0", "db91bb424e07f2bb6e73926fcafbfcbcb295f0193e0a00e825e589a0a47e8453", [:rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b3a24eafd66c3f42da30fc3ca7dda1e9d546c12250a2d60d7b81d264fbec4f6e"}, - "tesla": {:hex, :tesla, "1.11.0", "81b2b10213dddb27105ec6102d9eb0cc93d7097a918a0b1594f2dfd1a4601190", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:finch, "~> 0.13", [hex: :finch, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:gun, ">= 1.0.0", [hex: :gun, repo: "hexpm", optional: true]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "4.4.2", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.0", [hex: :mint, repo: "hexpm", optional: true]}, {:msgpax, "~> 2.3", [hex: :msgpax, repo: "hexpm", optional: true]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "b83ab5d4c2d202e1ea2b7e17a49f788d49a699513d7c4f08f2aef2c281be69db"}, + "tesla": {:hex, :tesla, "1.15.3", "3a2b5c37f09629b8dcf5d028fbafc9143c0099753559d7fe567eaabfbd9b8663", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:finch, "~> 0.13", [hex: :finch, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:gun, ">= 1.0.0", [hex: :gun, repo: "hexpm", optional: true]}, {:hackney, "~> 1.21", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "4.4.2", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.0", [hex: :mint, repo: "hexpm", optional: true]}, {:mox, "~> 1.0", [hex: :mox, repo: "hexpm", optional: true]}, {:msgpax, "~> 2.3", [hex: :msgpax, repo: "hexpm", optional: true]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "98bb3d4558abc67b92fb7be4cd31bb57ca8d80792de26870d362974b58caeda7"}, "thousand_island": {:hex, :thousand_island, "1.3.5", "6022b6338f1635b3d32406ff98d68b843ba73b3aa95cfc27154223244f3a6ca5", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "2be6954916fdfe4756af3239fb6b6d75d0b8063b5df03ba76fd8a4c87849e180"}, "timex": {:hex, :timex, "3.7.7", "3ed093cae596a410759104d878ad7b38e78b7c2151c6190340835515d4a46b8a", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 1.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "0ec4b09f25fe311321f9fc04144a7e3affe48eb29481d7a5583849b6c4dfa0a7"}, "toml": {:hex, :toml, "0.7.0", "fbcd773caa937d0c7a02c301a1feea25612720ac3fa1ccb8bfd9d30d822911de", [:mix], [], "hexpm", "0690246a2478c1defd100b0c9b89b4ea280a22be9a7b313a8a058a2408a2fa70"}, "trailing_format_plug": {:hex, :trailing_format_plug, "0.0.7", "64b877f912cf7273bed03379936df39894149e35137ac9509117e59866e10e45", [:mix], [{:plug, "> 0.12.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "bd4fde4c15f3e993a999e019d64347489b91b7a9096af68b2bdadd192afa693f"}, "tzdata": {:hex, :tzdata, "1.0.5", "69f1ee029a49afa04ad77801febaf69385f3d3e3d1e4b56b9469025677b89a28", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "55519aa2a99e5d2095c1e61cc74c9be69688f8ab75c27da724eb8279ff402a5a"}, "ueberauth": {:hex, :ueberauth, "0.10.7", "5a31cbe11e7ce5c7484d745dc9e1f11948e89662f8510d03c616de03df581ebd", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "0bccf73e2ffd6337971340832947ba232877aa8122dba4c95be9f729c8987377"}, - "unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"}, + "unicode_util_compat": {:hex, :unicode_util_compat, "0.7.1", "a48703a25c170eedadca83b11e88985af08d35f37c6f664d6dcfb106a97782fc", [:rebar3], [], "hexpm", "b3a917854ce3ae233619744ad1e0102e05673136776fb2fa76234f3e03b23642"}, "unsafe": {:hex, :unsafe, "1.0.2", "23c6be12f6c1605364801f4b47007c0c159497d0446ad378b5cf05f1855c0581", [:mix], [], "hexpm", "b485231683c3ab01a9cd44cb4a79f152c6f3bb87358439c6f68791b85c2df675"}, "vix": {:hex, :vix, "0.26.0", "027f10b6969b759318be84bd0bd8c88af877445e4e41cf96a0460392cea5399c", [:make, :mix], [{:castore, "~> 1.0 or ~> 0.1", [hex: :castore, repo: "hexpm", optional: false]}, {:cc_precompiler, "~> 0.2 or ~> 0.1.4", [hex: :cc_precompiler, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.8 or ~> 0.7.3", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:kino, "~> 0.7", [hex: :kino, repo: "hexpm", optional: true]}], "hexpm", "71b0a79ae7f199cacfc8e679b0e4ba25ee47dc02e182c5b9097efb29fbe14efd"}, "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"}, From 3c36bcfaa6d09436ba06699655934e4d01dba31b Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Fri, 1 Aug 2025 12:19:41 -0700 Subject: [PATCH 11/17] Remove deprecated "use Tesla" macro usage --- lib/pleroma/search/qdrant_search.ex | 55 +++++++++++++++++----- test/pleroma/search/qdrant_search_test.exs | 6 +-- 2 files changed, 45 insertions(+), 16 deletions(-) diff --git a/lib/pleroma/search/qdrant_search.ex b/lib/pleroma/search/qdrant_search.ex index b659bb682..5142a273f 100644 --- a/lib/pleroma/search/qdrant_search.ex +++ b/lib/pleroma/search/qdrant_search.ex @@ -157,26 +157,55 @@ defmodule Pleroma.Search.QdrantSearch do end defmodule Pleroma.Search.QdrantSearch.OpenAIClient do - use Tesla alias Pleroma.Config.Getting, as: Config - plug(Tesla.Middleware.BaseUrl, Config.get([Pleroma.Search.QdrantSearch, :openai_url])) - plug(Tesla.Middleware.JSON) + def post(path, body) do + Tesla.post(client(), path, body) + end - plug(Tesla.Middleware.Headers, [ - {"Authorization", - "Bearer #{Pleroma.Config.get([Pleroma.Search.QdrantSearch, :openai_api_key])}"} - ]) + defp client do + Tesla.client(middleware()) + end + + defp middleware do + [ + {Tesla.Middleware.BaseUrl, Config.get([Pleroma.Search.QdrantSearch, :openai_url])}, + Tesla.Middleware.JSON, + {Tesla.Middleware.Headers, + [ + {"Authorization", "Bearer #{Config.get([Pleroma.Search.QdrantSearch, :openai_api_key])}"} + ]} + ] + end end defmodule Pleroma.Search.QdrantSearch.QdrantClient do - use Tesla alias Pleroma.Config.Getting, as: Config - plug(Tesla.Middleware.BaseUrl, Config.get([Pleroma.Search.QdrantSearch, :qdrant_url])) - plug(Tesla.Middleware.JSON) + def delete(path) do + Tesla.delete(client(), path) + end - plug(Tesla.Middleware.Headers, [ - {"api-key", Pleroma.Config.get([Pleroma.Search.QdrantSearch, :qdrant_api_key])} - ]) + def post(path, body) do + Tesla.post(client(), path, body) + end + + def put(path, body) do + Tesla.put(client(), path, body) + end + + defp client do + Tesla.client(middleware()) + end + + defp middleware do + [ + {Tesla.Middleware.BaseUrl, Config.get([Pleroma.Search.QdrantSearch, :qdrant_url])}, + Tesla.Middleware.JSON, + {Tesla.Middleware.Headers, + [ + {"api-key", Pleroma.Config.get([Pleroma.Search.QdrantSearch, :qdrant_api_key])} + ]} + ] + end end diff --git a/test/pleroma/search/qdrant_search_test.exs b/test/pleroma/search/qdrant_search_test.exs index 47a77a391..daf8eeb69 100644 --- a/test/pleroma/search/qdrant_search_test.exs +++ b/test/pleroma/search/qdrant_search_test.exs @@ -51,7 +51,7 @@ defmodule Pleroma.Search.QdrantSearchTest do }) Config - |> expect(:get, 3, fn + |> expect(:get, 4, fn [Pleroma.Search, :module], nil -> QdrantSearch @@ -93,7 +93,7 @@ defmodule Pleroma.Search.QdrantSearchTest do }) Config - |> expect(:get, 3, fn + |> expect(:get, 4, fn [Pleroma.Search, :module], nil -> QdrantSearch @@ -158,7 +158,7 @@ defmodule Pleroma.Search.QdrantSearchTest do end) Config - |> expect(:get, 6, fn + |> expect(:get, 7, fn [Pleroma.Search, :module], nil -> QdrantSearch From 09eb7dbf8eb876b9847d7323fd2b93ee04c5be2a Mon Sep 17 00:00:00 2001 From: Ekaterina Vaartis Date: Fri, 1 Aug 2025 23:31:54 +0300 Subject: [PATCH 12/17] Change mailer example to use Mua --- changelog.d/smtp-docs.change | 1 + docs/configuration/cheatsheet.md | 8 +++----- 2 files changed, 4 insertions(+), 5 deletions(-) create mode 100644 changelog.d/smtp-docs.change diff --git a/changelog.d/smtp-docs.change b/changelog.d/smtp-docs.change new file mode 100644 index 000000000..fb9925e43 --- /dev/null +++ b/changelog.d/smtp-docs.change @@ -0,0 +1 @@ +Change SMTP example to use the Mua adapter that works with OTP>25 \ No newline at end of file diff --git a/docs/configuration/cheatsheet.md b/docs/configuration/cheatsheet.md index 6e2fddcb6..07414f69a 100644 --- a/docs/configuration/cheatsheet.md +++ b/docs/configuration/cheatsheet.md @@ -733,13 +733,11 @@ An example for SMTP adapter: ```elixir config :pleroma, Pleroma.Emails.Mailer, enabled: true, - adapter: Swoosh.Adapters.SMTP, + adapter: Swoosh.Adapters.Mua, relay: "smtp.gmail.com", - username: "YOUR_USERNAME@gmail.com", - password: "YOUR_SMTP_PASSWORD", + auth: [username: "YOUR_USERNAME@gmail.com", password: "YOUR_SMTP_PASSWORD"], port: 465, - ssl: true, - auth: :always + protocol: :ssl ``` An example for Mua adapter: From 44898845a69814aad9d09e8af82df0679a8d5f7f Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Fri, 1 Aug 2025 12:52:29 -0700 Subject: [PATCH 13/17] Update Plug/Cowboy/Gun --- mix.exs | 6 +++--- mix.lock | 14 +++++++------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/mix.exs b/mix.exs index a79aaca8f..a8b08cf67 100644 --- a/mix.exs +++ b/mix.exs @@ -135,7 +135,7 @@ defmodule Pleroma.Mixfile do {:telemetry_metrics, "~> 0.6"}, {:telemetry_poller, "~> 1.0"}, {:tzdata, "~> 1.0.3"}, - {:plug_cowboy, "~> 2.5"}, + {:plug_cowboy, "~> 2.7"}, {:oban, "~> 2.19.0"}, {:gettext, "~> 0.20"}, {:bcrypt_elixir, "~> 2.2"}, @@ -146,8 +146,8 @@ defmodule Pleroma.Mixfile do {:cachex, "~> 3.2"}, {:tesla, "~> 1.11"}, {:castore, "~> 1.0"}, - {:cowlib, "~> 2.9", override: true}, - {:gun, "~> 2.0.0-rc.1", override: true}, + {:cowlib, "~> 2.15"}, + {:gun, "~> 2.2"}, {:finch, "~> 0.15"}, {:jason, "~> 1.2"}, {:mogrify, "~> 0.9.0", override: "true"}, diff --git a/mix.lock b/mix.lock index a708087c9..93f41bba8 100644 --- a/mix.lock +++ b/mix.lock @@ -20,9 +20,9 @@ "connection": {:hex, :connection, "1.1.0", "ff2a49c4b75b6fb3e674bfc5536451607270aac754ffd1bdfe175abe4a6d7a68", [:mix], [], "hexpm", "722c1eb0a418fbe91ba7bd59a47e28008a189d47e37e0e7bb85585a016b2869c"}, "cors_plug": {:hex, :cors_plug, "2.0.3", "316f806d10316e6d10f09473f19052d20ba0a0ce2a1d910ddf57d663dac402ae", [:mix], [{:plug, "~> 1.8", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "ee4ae1418e6ce117fc42c2ba3e6cbdca4e95ecd2fe59a05ec6884ca16d469aea"}, "covertool": {:hex, :covertool, "2.0.6", "4a291b4e3449025b0595d8f44c8d7635d4f48f033be2ce88d22a329f36f94a91", [:rebar3], [], "hexpm", "5db3fcd82180d8ea4ad857d4d1ab21a8d31b5aee0d60d2f6c0f9e25a411d1e21"}, - "cowboy": {:hex, :cowboy, "2.12.0", "f276d521a1ff88b2b9b4c54d0e753da6c66dd7be6c9fca3d9418b561828a3731", [:make, :rebar3], [{:cowlib, "2.13.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "8a7abe6d183372ceb21caa2709bec928ab2b72e18a3911aa1771639bef82651e"}, + "cowboy": {:hex, :cowboy, "2.13.0", "09d770dd5f6a22cc60c071f432cd7cb87776164527f205c5a6b0f24ff6b38990", [:make, :rebar3], [{:cowlib, ">= 2.14.0 and < 3.0.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, ">= 1.8.0 and < 3.0.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "e724d3a70995025d654c1992c7b11dbfea95205c047d86ff9bf1cda92ddc5614"}, "cowboy_telemetry": {:hex, :cowboy_telemetry, "0.4.0", "f239f68b588efa7707abce16a84d0d2acf3a0f50571f8bb7f56a15865aae820c", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7d98bac1ee4565d31b62d59f8823dfd8356a169e7fcbb83831b8a5397404c9de"}, - "cowlib": {:hex, :cowlib, "2.13.0", "db8f7505d8332d98ef50a3ef34b34c1afddec7506e4ee4dd4a3a266285d282ca", [:make, :rebar3], [], "hexpm", "e1e1284dc3fc030a64b1ad0d8382ae7e99da46c3246b815318a4b848873800a4"}, + "cowlib": {:hex, :cowlib, "2.15.0", "3c97a318a933962d1c12b96ab7c1d728267d2c523c25a5b57b0f93392b6e9e25", [:make, :rebar3], [], "hexpm", "4f00c879a64b4fe7c8fcb42a4281925e9ffdb928820b03c3ad325a617e857532"}, "credo": {:hex, :credo, "1.7.12", "9e3c20463de4b5f3f23721527fcaf16722ec815e70ff6c60b86412c695d426c1", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "8493d45c656c5427d9c729235b99d498bd133421f3e0a683e5c1b561471291e5"}, "crontab": {:hex, :crontab, "1.1.8", "2ce0e74777dfcadb28a1debbea707e58b879e6aa0ffbf9c9bb540887bce43617", [:mix], [{:ecto, "~> 1.0 or ~> 2.0 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm"}, "custom_base": {:hex, :custom_base, "0.2.1", "4a832a42ea0552299d81652aa0b1f775d462175293e99dfbe4d7dbaab785a706", [:mix], [], "hexpm", "8df019facc5ec9603e94f7270f1ac73ddf339f56ade76a721eaa57c1493ba463"}, @@ -58,7 +58,7 @@ "floki": {:hex, :floki, "0.35.2", "87f8c75ed8654b9635b311774308b2760b47e9a579dabf2e4d5f1e1d42c39e0b", [:mix], [], "hexpm", "6b05289a8e9eac475f644f09c2e4ba7e19201fd002b89c28c1293e7bd16773d9"}, "gen_smtp": {:hex, :gen_smtp, "0.15.0", "9f51960c17769b26833b50df0b96123605a8024738b62db747fece14eb2fbfcc", [:rebar3], [], "hexpm", "29bd14a88030980849c7ed2447b8db6d6c9278a28b11a44cafe41b791205440f"}, "gettext": {:hex, :gettext, "0.24.0", "6f4d90ac5f3111673cbefc4ebee96fe5f37a114861ab8c7b7d5b30a1108ce6d8", [:mix], [{:expo, "~> 0.5.1", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "bdf75cdfcbe9e4622dd18e034b227d77dd17f0f133853a1c73b97b3d6c770e8b"}, - "gun": {:hex, :gun, "2.0.1", "160a9a5394800fcba41bc7e6d421295cf9a7894c2252c0678244948e3336ad73", [:make, :rebar3], [{:cowlib, "2.12.1", [hex: :cowlib, repo: "hexpm", optional: false]}], "hexpm", "a10bc8d6096b9502205022334f719cc9a08d9adcfbfc0dbee9ef31b56274a20b"}, + "gun": {:hex, :gun, "2.2.0", "b8f6b7d417e277d4c2b0dc3c07dfdf892447b087f1cc1caff9c0f556b884e33d", [:make, :rebar3], [{:cowlib, ">= 2.15.0 and < 3.0.0", [hex: :cowlib, repo: "hexpm", optional: false]}], "hexpm", "76022700c64287feb4df93a1795cff6741b83fb37415c40c34c38d2a4645261a"}, "hackney": {:hex, :hackney, "1.18.2", "d7ff544ddae5e1cb49e9cf7fa4e356d7f41b283989a1c304bfc47a8cc1cf966f", [:rebar3], [{:certifi, "~>2.12.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~>6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~>1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.4.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~>1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "af94d5c9f97857db257090a4a10e5426ecb6f4918aa5cc666798566ae14b65fd"}, "hpax": {:hex, :hpax, "0.2.0", "5a58219adcb75977b2edce5eb22051de9362f08236220c9e859a47111c194ff5", [:mix], [], "hexpm", "bea06558cdae85bed075e6c036993d43cd54d447f76d8190a8db0dc5893fa2f1"}, "html_entities": {:hex, :html_entities, "0.5.2", "9e47e70598da7de2a9ff6af8758399251db6dbb7eebe2b013f2bbd2515895c3c", [:mix], [], "hexpm", "c53ba390403485615623b9531e97696f076ed415e8d8058b1dbaa28181f4fdcc"}, @@ -108,9 +108,9 @@ "phoenix_swoosh": {:hex, :phoenix_swoosh, "1.2.1", "b74ccaa8046fbc388a62134360ee7d9742d5a8ae74063f34eb050279de7a99e1", [:mix], [{:finch, "~> 0.8", [hex: :finch, repo: "hexpm", optional: true]}, {:hackney, "~> 1.10", [hex: :hackney, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6", [hex: :phoenix, repo: "hexpm", optional: true]}, {:phoenix_html, "~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_view, "~> 1.0 or ~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: false]}, {:swoosh, "~> 1.5", [hex: :swoosh, repo: "hexpm", optional: false]}], "hexpm", "4000eeba3f9d7d1a6bf56d2bd56733d5cadf41a7f0d8ffe5bb67e7d667e204a2"}, "phoenix_template": {:hex, :phoenix_template, "1.0.4", "e2092c132f3b5e5b2d49c96695342eb36d0ed514c5b252a77048d5969330d639", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "2c0c81f0e5c6753faf5cca2f229c9709919aba34fab866d3bc05060c9c444206"}, "phoenix_view": {:hex, :phoenix_view, "2.0.4", "b45c9d9cf15b3a1af5fb555c674b525391b6a1fe975f040fb4d913397b31abf4", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}], "hexpm", "4e992022ce14f31fe57335db27a28154afcc94e9983266835bb3040243eb620b"}, - "plug": {:hex, :plug, "1.16.1", "40c74619c12f82736d2214557dedec2e9762029b2438d6d175c5074c933edc9d", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a13ff6b9006b03d7e33874945b2755253841b238c34071ed85b0e86057f8cddc"}, - "plug_cowboy": {:hex, :plug_cowboy, "2.7.1", "87677ffe3b765bc96a89be7960f81703223fe2e21efa42c125fcd0127dd9d6b2", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "02dbd5f9ab571b864ae39418db7811618506256f6d13b4a45037e5fe78dc5de3"}, - "plug_crypto": {:hex, :plug_crypto, "2.1.0", "f44309c2b06d249c27c8d3f65cfe08158ade08418cf540fd4f72d4d6863abb7b", [:mix], [], "hexpm", "131216a4b030b8f8ce0f26038bc4421ae60e4bb95c5cf5395e1421437824c4fa"}, + "plug": {:hex, :plug, "1.18.1", "5067f26f7745b7e31bc3368bc1a2b818b9779faa959b49c934c17730efc911cf", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "57a57db70df2b422b564437d2d33cf8d33cd16339c1edb190cd11b1a3a546cc2"}, + "plug_cowboy": {:hex, :plug_cowboy, "2.7.4", "729c752d17cf364e2b8da5bdb34fb5804f56251e88bb602aff48ae0bd8673d11", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "9b85632bd7012615bae0a5d70084deb1b25d2bcbb32cab82d1e9a1e023168aa3"}, + "plug_crypto": {:hex, :plug_crypto, "2.1.1", "19bda8184399cb24afa10be734f84a16ea0a2bc65054e23a62bb10f06bc89491", [:mix], [], "hexpm", "6470bce6ffe41c8bd497612ffde1a7e4af67f36a15eea5f921af71cf3e11247c"}, "plug_static_index_html": {:hex, :plug_static_index_html, "1.0.0", "840123d4d3975585133485ea86af73cb2600afd7f2a976f9f5fd8b3808e636a0", [:mix], [{:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "79fd4fcf34d110605c26560cbae8f23c603ec4158c08298bd4360fdea90bb5cf"}, "poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm", "fec8660eb7733ee4117b85f55799fd3833eb769a6df71ccf8903e8dc5447cfce"}, "poolboy": {:hex, :poolboy, "1.5.2", "392b007a1693a64540cead79830443abf5762f5d30cf50bc95cb2c1aaafa006b", [:rebar3], [], "hexpm", "dad79704ce5440f3d5a3681c8590b9dc25d1a561e8f5a9c995281012860901e3"}, @@ -124,7 +124,7 @@ "prometheus_phx": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/prometheus-phx.git", "9cd8f248c9381ffedc799905050abce194a97514", [branch: "no-logging"]}, "prometheus_plugs": {:hex, :prometheus_plugs, "1.1.5", "25933d48f8af3a5941dd7b621c889749894d8a1082a6ff7c67cc99dec26377c5", [:mix], [{:accept, "~> 0.1", [hex: :accept, repo: "hexpm", optional: false]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}, {:prometheus_ex, "~> 1.1 or ~> 2.0 or ~> 3.0", [hex: :prometheus_ex, repo: "hexpm", optional: false]}, {:prometheus_process_collector, "~> 1.1", [hex: :prometheus_process_collector, repo: "hexpm", optional: true]}], "hexpm", "0273a6483ccb936d79ca19b0ab629aef0dba958697c94782bb728b920dfc6a79"}, "quantile_estimator": {:hex, :quantile_estimator, "0.2.1", "ef50a361f11b5f26b5f16d0696e46a9e4661756492c981f7b2229ef42ff1cd15", [:rebar3], [], "hexpm", "282a8a323ca2a845c9e6f787d166348f776c1d4a41ede63046d72d422e3da946"}, - "ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"}, + "ranch": {:hex, :ranch, "2.2.0", "25528f82bc8d7c6152c57666ca99ec716510fe0925cb188172f41ce93117b1b0", [:make, :rebar3], [], "hexpm", "fa0b99a1780c80218a4197a59ea8d3bdae32fbff7e88527d7d8a4787eff4f8e7"}, "recon": {:hex, :recon, "2.5.4", "05dd52a119ee4059fa9daa1ab7ce81bc7a8161a2f12e9d42e9d551ffd2ba901c", [:mix, :rebar3], [], "hexpm", "e9ab01ac7fc8572e41eb59385efeb3fb0ff5bf02103816535bacaedf327d0263"}, "remote_ip": {:git, "https://git.pleroma.social/pleroma/remote_ip.git", "b647d0deecaa3acb140854fe4bda5b7e1dc6d1c8", [ref: "b647d0deecaa3acb140854fe4bda5b7e1dc6d1c8"]}, "rustler": {:hex, :rustler, "0.30.0", "cefc49922132b072853fa9b0ca4dc2ffcb452f68fb73b779042b02d545e097fb", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:toml, "~> 0.6", [hex: :toml, repo: "hexpm", optional: false]}], "hexpm", "9ef1abb6a7dda35c47cfc649e6a5a61663af6cf842a55814a554a84607dee389"}, From 7b8d6eca65c660ae3fb8e606649d927231cc1a53 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Fri, 1 Aug 2025 12:53:15 -0700 Subject: [PATCH 14/17] Remove deprecated "use Plug.Test" --- test/pleroma/web/plugs/cache_test.exs | 3 ++- test/pleroma/web/plugs/digest_plug_test.exs | 3 ++- test/pleroma/web/plugs/idempotency_plug_test.exs | 3 ++- test/pleroma/web/plugs/remote_ip_test.exs | 3 ++- test/pleroma/web/plugs/set_format_plug_test.exs | 3 ++- test/pleroma/web/plugs/set_locale_plug_test.exs | 2 +- 6 files changed, 11 insertions(+), 6 deletions(-) diff --git a/test/pleroma/web/plugs/cache_test.exs b/test/pleroma/web/plugs/cache_test.exs index 0c119528d..09065e94e 100644 --- a/test/pleroma/web/plugs/cache_test.exs +++ b/test/pleroma/web/plugs/cache_test.exs @@ -5,7 +5,8 @@ defmodule Pleroma.Web.Plugs.CacheTest do # Relies on Cachex, has to stay synchronous use Pleroma.DataCase - use Plug.Test + import Plug.Conn + import Plug.Test alias Pleroma.Web.Plugs.Cache diff --git a/test/pleroma/web/plugs/digest_plug_test.exs b/test/pleroma/web/plugs/digest_plug_test.exs index 19f8a6f49..21ccddd88 100644 --- a/test/pleroma/web/plugs/digest_plug_test.exs +++ b/test/pleroma/web/plugs/digest_plug_test.exs @@ -4,7 +4,8 @@ defmodule Pleroma.Web.Plugs.DigestPlugTest do use ExUnit.Case, async: true - use Plug.Test + import Plug.Conn + import Plug.Test test "digest algorithm is taken from digest header" do body = "{\"hello\": \"world\"}" diff --git a/test/pleroma/web/plugs/idempotency_plug_test.exs b/test/pleroma/web/plugs/idempotency_plug_test.exs index cc55d341f..3b0131596 100644 --- a/test/pleroma/web/plugs/idempotency_plug_test.exs +++ b/test/pleroma/web/plugs/idempotency_plug_test.exs @@ -5,7 +5,8 @@ defmodule Pleroma.Web.Plugs.IdempotencyPlugTest do # Relies on Cachex, has to stay synchronous use Pleroma.DataCase - use Plug.Test + import Plug.Conn + import Plug.Test alias Pleroma.Web.Plugs.IdempotencyPlug alias Plug.Conn diff --git a/test/pleroma/web/plugs/remote_ip_test.exs b/test/pleroma/web/plugs/remote_ip_test.exs index aea0940f4..37b751370 100644 --- a/test/pleroma/web/plugs/remote_ip_test.exs +++ b/test/pleroma/web/plugs/remote_ip_test.exs @@ -4,7 +4,8 @@ defmodule Pleroma.Web.Plugs.RemoteIpTest do use ExUnit.Case - use Plug.Test + import Plug.Conn + import Plug.Test alias Pleroma.Web.Plugs.RemoteIp diff --git a/test/pleroma/web/plugs/set_format_plug_test.exs b/test/pleroma/web/plugs/set_format_plug_test.exs index 4d64fdde6..08a2b02a2 100644 --- a/test/pleroma/web/plugs/set_format_plug_test.exs +++ b/test/pleroma/web/plugs/set_format_plug_test.exs @@ -4,7 +4,8 @@ defmodule Pleroma.Web.Plugs.SetFormatPlugTest do use ExUnit.Case, async: true - use Plug.Test + import Plug.Conn + import Plug.Test alias Pleroma.Web.Plugs.SetFormatPlug diff --git a/test/pleroma/web/plugs/set_locale_plug_test.exs b/test/pleroma/web/plugs/set_locale_plug_test.exs index 4f664f84e..a7691ea11 100644 --- a/test/pleroma/web/plugs/set_locale_plug_test.exs +++ b/test/pleroma/web/plugs/set_locale_plug_test.exs @@ -4,7 +4,7 @@ defmodule Pleroma.Web.Plugs.SetLocalePlugTest do use ExUnit.Case, async: true - use Plug.Test + import Plug.Test alias Pleroma.Web.Plugs.SetLocalePlug alias Plug.Conn From d67ab670b0707951f905076a823e3d2a4d31749a Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Fri, 1 Aug 2025 13:13:01 -0700 Subject: [PATCH 15/17] Fix Gopher server to use modern :ranch --- lib/pleroma/gopher/server.ex | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/lib/pleroma/gopher/server.ex b/lib/pleroma/gopher/server.ex index 54245c9fa..add3ba925 100644 --- a/lib/pleroma/gopher/server.ex +++ b/lib/pleroma/gopher/server.ex @@ -22,14 +22,18 @@ defmodule Pleroma.Gopher.Server do def init([ip, port]) do Logger.info("Starting gopher server on #{port}") - :ranch.start_listener( - :gopher, - 100, - :ranch_tcp, - [ip: ip, port: port], - __MODULE__.ProtocolHandler, - [] - ) + {:ok, _pid} = + :ranch.start_listener( + :gopher, + :ranch_tcp, + %{ + num_acceptors: 100, + max_connections: 100, + socket_opts: [ip: ip, port: port] + }, + __MODULE__.ProtocolHandler, + [] + ) {:ok, %{ip: ip, port: port}} end @@ -43,13 +47,13 @@ defmodule Pleroma.Gopher.Server.ProtocolHandler do alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.Visibility - def start_link(ref, socket, transport, opts) do - pid = spawn_link(__MODULE__, :init, [ref, socket, transport, opts]) + def start_link(ref, transport, opts) do + pid = spawn_link(__MODULE__, :init, [ref, transport, opts]) {:ok, pid} end - def init(ref, socket, transport, [] = _Opts) do - :ok = :ranch.accept_ack(ref) + def init(ref, transport, opts \\ []) do + {:ok, socket} = :ranch.handshake(ref, opts) loop(socket, transport) end From 9195cfb2bc5f3e5db0f2ff98bee0473ee00dbaca Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Fri, 1 Aug 2025 16:23:20 -0700 Subject: [PATCH 16/17] Document Gun, Cowboy, and Plug update --- changelog.d/gun.change | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/gun.change diff --git a/changelog.d/gun.change b/changelog.d/gun.change new file mode 100644 index 000000000..3d72b7701 --- /dev/null +++ b/changelog.d/gun.change @@ -0,0 +1 @@ +Update Cowboy, Gun, and Plug family of dependencies From c1836c98214896469381fe8ef11abb33669452b0 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Sat, 2 Aug 2025 09:53:56 -0700 Subject: [PATCH 17/17] Fix test that relied on previous fake hashtag behavior This test is normally skipped on MacOS due to weird unicode behavior --- .../mastodon_api/controllers/search_controller_test.exs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/test/pleroma/web/mastodon_api/controllers/search_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/search_controller_test.exs index 8b4c6add2..f0c9c1901 100644 --- a/test/pleroma/web/mastodon_api/controllers/search_controller_test.exs +++ b/test/pleroma/web/mastodon_api/controllers/search_controller_test.exs @@ -7,7 +7,6 @@ defmodule Pleroma.Web.MastodonAPI.SearchControllerTest do alias Pleroma.Object alias Pleroma.Web.CommonAPI - alias Pleroma.Web.Endpoint import Pleroma.Factory import ExUnit.CaptureLog import Tesla.Mock @@ -66,9 +65,7 @@ defmodule Pleroma.Web.MastodonAPI.SearchControllerTest do [account | _] = results["accounts"] assert account["id"] == to_string(user_three.id) - assert results["hashtags"] == [ - %{"name" => "private", "url" => "#{Endpoint.url()}/tag/private"} - ] + assert results["hashtags"] == [] [status] = results["statuses"] assert status["id"] == to_string(activity.id) @@ -77,9 +74,7 @@ defmodule Pleroma.Web.MastodonAPI.SearchControllerTest do get(conn, "/api/v2/search?q=天子") |> json_response_and_validate_schema(200) - assert results["hashtags"] == [ - %{"name" => "天子", "url" => "#{Endpoint.url()}/tag/天子"} - ] + assert results["hashtags"] == [] [status] = results["statuses"] assert status["id"] == to_string(activity.id)