Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F85831205
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Authored By
Unknown
Size
11 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/lib/pleroma/http/adapter_helper.ex b/lib/pleroma/http/adapter_helper.ex
index 32c1080f7..4f0e23f20 100644
--- a/lib/pleroma/http/adapter_helper.ex
+++ b/lib/pleroma/http/adapter_helper.ex
@@ -1,131 +1,149 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.HTTP.AdapterHelper do
@moduledoc """
Configure Tesla.Client with default and customized adapter options.
"""
@defaults [pool: :federation, connect_timeout: 5_000, recv_timeout: 5_000]
@type proxy_type() :: :socks4 | :socks5
@type host() :: charlist() | :inet.ip_address()
alias Pleroma.HTTP.AdapterHelper
require Logger
@type proxy ::
{host(), pos_integer()}
| {proxy_type(), host(), pos_integer()}
@callback options(keyword(), URI.t()) :: keyword()
@spec format_proxy(String.t() | tuple() | nil) :: proxy() | nil
def format_proxy(nil), do: nil
def format_proxy(proxy_url) do
case parse_proxy(proxy_url) do
{:ok, host, port} -> {host, port}
{:ok, type, host, port} -> {type, host, port}
_ -> nil
end
end
@spec maybe_add_proxy(keyword(), proxy() | nil) :: keyword()
def maybe_add_proxy(opts, nil), do: opts
def maybe_add_proxy(opts, proxy), do: Keyword.put_new(opts, :proxy, proxy)
@doc """
Merge default connection & adapter options with received ones.
"""
@spec options(URI.t(), keyword()) :: keyword()
def options(%URI{} = uri, opts \\ []) do
@defaults
|> Keyword.merge(opts)
|> adapter_helper().options(uri)
end
+ @doc """
+ The configured Tesla adapter with TLS options, for clients that do not go
+ through `Pleroma.HTTP`.
+ """
+ @spec tesla_adapter() :: module() | {module(), keyword()}
+ def tesla_adapter do
+ case adapter() do
+ Tesla.Adapter.Hackney ->
+ {Tesla.Adapter.Hackney, Pleroma.HTTP.TLS.put_hackney_ssl_options([])}
+
+ Tesla.Adapter.Gun ->
+ {Tesla.Adapter.Gun, certificates_verification: true}
+
+ adapter ->
+ adapter
+ end
+ end
+
defp adapter, do: Application.get_env(:tesla, :adapter)
defp adapter_helper do
case adapter() do
Tesla.Adapter.Gun -> AdapterHelper.Gun
Tesla.Adapter.Hackney -> AdapterHelper.Hackney
{Tesla.Adapter.Finch, _} -> AdapterHelper.Finch
_ -> AdapterHelper.Default
end
end
@spec parse_proxy(String.t() | tuple() | nil) ::
{:ok, host(), pos_integer()}
| {:ok, proxy_type(), host(), pos_integer()}
| {:error, atom()}
| nil
def parse_proxy(nil), do: nil
def parse_proxy(proxy) when is_binary(proxy) do
with [host, port] <- String.split(proxy, ":"),
{port, ""} <- Integer.parse(port) do
{:ok, parse_host(host), port}
else
{_, _} ->
Logger.warning("Parsing port failed #{inspect(proxy)}")
{:error, :invalid_proxy_port}
:error ->
Logger.warning("Parsing port failed #{inspect(proxy)}")
{:error, :invalid_proxy_port}
_ ->
Logger.warning("Parsing proxy failed #{inspect(proxy)}")
{:error, :invalid_proxy}
end
end
def parse_proxy(proxy) when is_tuple(proxy) do
with {type, host, port} <- proxy do
{:ok, type, parse_host(host), port}
else
_ ->
Logger.warning("Parsing proxy failed #{inspect(proxy)}")
{:error, :invalid_proxy}
end
end
@spec parse_host(String.t() | atom() | charlist()) :: charlist() | :inet.ip_address()
def parse_host(host) when is_list(host), do: host
def parse_host(host) when is_atom(host), do: to_charlist(host)
def parse_host(host) when is_binary(host) do
host = to_charlist(host)
case :inet.parse_address(host) do
{:error, :einval} -> host
{:ok, ip} -> ip
end
end
@spec format_host(String.t()) :: charlist()
def format_host(host) do
host_charlist = to_charlist(host)
case :inet.parse_address(host_charlist) do
{:error, :einval} ->
:idna.encode(host_charlist)
{:ok, _ip} ->
host_charlist
end
end
@spec can_stream? :: bool()
def can_stream? do
case Application.get_env(:tesla, :adapter) do
Tesla.Adapter.Gun -> true
{Tesla.Adapter.Finch, _} -> true
_ -> false
end
end
end
diff --git a/lib/pleroma/search/qdrant_search.ex b/lib/pleroma/search/qdrant_search.ex
index 4d57cfa88..a8c5a3eb9 100644
--- a/lib/pleroma/search/qdrant_search.ex
+++ b/lib/pleroma/search/qdrant_search.ex
@@ -1,207 +1,207 @@
defmodule Pleroma.Search.QdrantSearch do
@behaviour Pleroma.Search.SearchBackend
import Ecto.Query
alias Pleroma.Activity
alias Pleroma.Config.Getting, as: Config
alias Pleroma.Object
alias Pleroma.Search
alias __MODULE__.OpenAIClient
alias __MODULE__.QdrantClient
import Pleroma.Search.DatabaseSearch, only: [maybe_fetch: 3]
@impl true
def create_index do
payload = Config.get([Pleroma.Search.QdrantSearch, :qdrant_index_configuration])
with {:ok, %{status: 200}} <- QdrantClient.put("/collections/posts", payload) do
:ok
else
e -> {:error, e}
end
end
@impl true
def drop_index do
with {:ok, %{status: 200}} <- QdrantClient.delete("/collections/posts") do
:ok
else
e -> {:error, e}
end
end
def get_embedding(text) do
with {:ok, %{body: %{"data" => [%{"embedding" => embedding}]}}} <-
OpenAIClient.post("/v1/embeddings", %{
input: text,
model: Config.get([Pleroma.Search.QdrantSearch, :openai_model])
}) do
{:ok, embedding}
else
_ ->
{:error, "Failed to get embedding"}
end
end
defp actor_from_activity(%{data: %{"actor" => actor}}) do
actor
end
defp actor_from_activity(_), do: nil
defp build_index_payload(activity, embedding) do
actor = actor_from_activity(activity)
published_at = activity.data["published"]
%{
points: [
%{
id: activity.id |> FlakeId.from_string() |> Ecto.UUID.cast!(),
vector: embedding,
payload: %{actor: actor, published_at: published_at}
}
]
}
end
defp build_search_payload(embedding, options) do
base = %{
vector: embedding,
limit: options[:limit] || 20,
offset: options[:offset] || 0
}
if author = options[:author] do
Map.put(base, :filter, %{
must: [%{key: "actor", match: %{value: author.ap_id}}]
})
else
base
end
end
@impl true
def add_to_index(%Activity{object: %Object{} = object} = activity) do
search_data = Search.object_to_search_data(object)
with {:ok, embedding} <- get_embedding(search_data.content),
{:ok, %{status: 200}} <-
QdrantClient.put(
"/collections/posts/points",
build_index_payload(activity, embedding)
) do
:ok
else
e -> {:error, e}
end
end
@impl true
def remove_from_index(object) do
activity = Activity.get_by_object_ap_id_with_object(object.data["id"])
id = activity.id |> FlakeId.from_string() |> Ecto.UUID.cast!()
with {:ok, %{status: 200}} <-
QdrantClient.post("/collections/posts/points/delete", %{"points" => [id]}) do
:ok
else
e -> {:error, e}
end
end
@impl true
def search(user, original_query, options) do
query = "Represent this sentence for searching relevant passages: #{original_query}"
with {:ok, embedding} <- get_embedding(query),
{:ok, %{body: %{"result" => result}}} <-
QdrantClient.post(
"/collections/posts/points/search",
build_search_payload(embedding, options)
) do
ids =
Enum.map(result, fn %{"id" => id} ->
Ecto.UUID.dump!(id)
end)
from(a in Activity, where: a.id in ^ids)
|> Activity.with_preloaded_object()
|> Activity.restrict_deactivated_users()
|> Ecto.Query.order_by([a], fragment("array_position(?, ?)", ^ids, a.id))
|> Pleroma.Repo.all()
|> maybe_fetch(user, original_query)
else
_ ->
[]
end
end
@impl true
def healthcheck_endpoints do
qdrant_health =
Config.get([Pleroma.Search.QdrantSearch, :qdrant_url])
|> URI.parse()
|> Map.put(:path, "/healthz")
|> URI.to_string()
openai_health = Config.get([Pleroma.Search.QdrantSearch, :openai_healthcheck_url])
[qdrant_health, openai_health] |> Enum.filter(& &1)
end
end
defmodule Pleroma.Search.QdrantSearch.OpenAIClient do
alias Pleroma.Config.Getting, as: Config
def post(path, body) do
Tesla.post(client(), path, body)
end
defp client do
- Tesla.client(middleware())
+ Tesla.client(middleware(), Pleroma.HTTP.AdapterHelper.tesla_adapter())
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
alias Pleroma.Config.Getting, as: Config
def delete(path) do
Tesla.delete(client(), path)
end
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())
+ Tesla.client(middleware(), Pleroma.HTTP.AdapterHelper.tesla_adapter())
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/http/adapter_helper/tesla_adapter_test.exs b/test/pleroma/http/adapter_helper/tesla_adapter_test.exs
new file mode 100644
index 000000000..1ee2f13ba
--- /dev/null
+++ b/test/pleroma/http/adapter_helper/tesla_adapter_test.exs
@@ -0,0 +1,34 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.HTTP.AdapterHelper.TeslaAdapterTest do
+ use ExUnit.Case, async: false
+
+ alias Pleroma.HTTP.AdapterHelper
+ alias Pleroma.HTTP.TLS
+
+ setup do
+ adapter = Application.get_env(:tesla, :adapter)
+ on_exit(fn -> Application.put_env(:tesla, :adapter, adapter) end)
+ end
+
+ test "adds the TLS options to Hackney" do
+ Application.put_env(:tesla, :adapter, Tesla.Adapter.Hackney)
+
+ assert AdapterHelper.tesla_adapter() ==
+ {Tesla.Adapter.Hackney, ssl_options: TLS.ssl_options()}
+ end
+
+ test "turns on certificate checks for Gun" do
+ Application.put_env(:tesla, :adapter, Tesla.Adapter.Gun)
+
+ assert AdapterHelper.tesla_adapter() == {Tesla.Adapter.Gun, certificates_verification: true}
+ end
+
+ test "keeps other adapters" do
+ Application.put_env(:tesla, :adapter, {Tesla.Adapter.Finch, name: MyFinch})
+
+ assert AdapterHelper.tesla_adapter() == {Tesla.Adapter.Finch, name: MyFinch}
+ end
+end
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Sun, Nov 1, 11:36 AM (11 h, 45 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1802990
Default Alt Text
(11 KB)
Attached To
Mode
rPUBE pleroma-upstream
Attached
Detach File
Event Timeline