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/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/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 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/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/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/docs/administration/backup.md b/docs/administration/backup.md index 93325e702..326f095ec 100644 --- a/docs/administration/backup.md +++ b/docs/administration/backup.md @@ -2,28 +2,60 @@ ## Backup -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) +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. `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: +``` +# 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. @@ -32,10 +64,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` -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! +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!** 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: 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 diff --git a/lib/pleroma/hashtag.ex b/lib/pleroma/hashtag.ex index 3682f0c14..91c30c6e7 100644 --- a/lib/pleroma/hashtag.ex +++ b/lib/pleroma/hashtag.ex @@ -130,4 +130,66 @@ 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) + + search_terms = + query + |> String.downcase() + |> String.trim() + |> String.split(~r/\s+/) + |> Enum.filter(&(&1 != "")) + |> Enum.map(&String.trim_leading(&1, "#")) + |> 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}%") + + # 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 + ) + |> Repo.all() + |> Enum.map(& &1.name) + end + end end 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/lib/pleroma/web/activity_pub/activity_pub_controller.ex b/lib/pleroma/web/activity_pub/activity_pub_controller.ex index 2ee72c49a..daf0d38e6 100644 --- a/lib/pleroma/web/activity_pub/activity_pub_controller.ex +++ b/lib/pleroma/web/activity_pub/activity_pub_controller.ex @@ -273,13 +273,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/lib/pleroma/web/mastodon_api/controllers/search_controller.ex b/lib/pleroma/web/mastodon_api/controllers/search_controller.ex index d9a1ba41e..53f1216fd 100644 --- a/lib/pleroma/web/mastodon_api/controllers/search_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/search_controller.ex @@ -5,6 +5,7 @@ defmodule Pleroma.Web.MastodonAPI.SearchController do use Pleroma.Web, :controller + alias Pleroma.Hashtag alias Pleroma.Repo alias Pleroma.User alias Pleroma.Web.ControllerHelper @@ -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/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..27b8e86bf 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"}, @@ -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"}, @@ -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"}, @@ -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"}, @@ -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"}, diff --git a/test/pleroma/hashtag_test.exs b/test/pleroma/hashtag_test.exs index 8531b1879..0e16b8155 100644 --- a/test/pleroma/hashtag_test.exs +++ b/test/pleroma/hashtag_test.exs @@ -14,4 +14,133 @@ 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 "searches hashtags by multiple words in 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("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") + {: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 + + 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") + + 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 + + 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 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 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 d9be82e64..2bdd58dba 100644 --- a/test/pleroma/web/activity_pub/activity_pub_controller_test.exs +++ b/test/pleroma/web/activity_pub/activity_pub_controller_test.exs @@ -923,23 +923,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) @@ -1305,6 +1288,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 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..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) @@ -130,84 +125,97 @@ 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"} - ] + 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: "john doe JOHN DOE"})}") + |> get("/api/v2/search?#{URI.encode_query(%{q: "new computer"})}") |> 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"} - ] + 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: "accident-prone"})}") + |> get("/api/v2/search?#{URI.encode_query(%{q: "computer laptop"})}") |> 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" - } - ] + 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) + + {: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 +322,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) 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