logo

pleroma

My custom branche(s) on git.pleroma.social/pleroma/pleroma git clone https://anongit.hacktivis.me/git/pleroma.git/

meilisearch.ex (4936B)


  1. defmodule Pleroma.Search.Meilisearch do
  2. require Logger
  3. require Pleroma.Constants
  4. alias Pleroma.Activity
  5. alias Pleroma.Config.Getting, as: Config
  6. import Pleroma.Search.DatabaseSearch
  7. import Ecto.Query
  8. @behaviour Pleroma.Search.SearchBackend
  9. @impl true
  10. def create_index, do: :ok
  11. @impl true
  12. def drop_index, do: :ok
  13. defp meili_headers do
  14. private_key = Config.get([Pleroma.Search.Meilisearch, :private_key])
  15. [{"Content-Type", "application/json"}] ++
  16. if is_nil(private_key), do: [], else: [{"Authorization", "Bearer #{private_key}"}]
  17. end
  18. def meili_get(path) do
  19. endpoint = Config.get([Pleroma.Search.Meilisearch, :url])
  20. result =
  21. Pleroma.HTTP.get(
  22. Path.join(endpoint, path),
  23. meili_headers()
  24. )
  25. with {:ok, res} <- result do
  26. {:ok, Jason.decode!(res.body)}
  27. end
  28. end
  29. def meili_post(path, params) do
  30. endpoint = Config.get([Pleroma.Search.Meilisearch, :url])
  31. result =
  32. Pleroma.HTTP.post(
  33. Path.join(endpoint, path),
  34. Jason.encode!(params),
  35. meili_headers()
  36. )
  37. with {:ok, res} <- result do
  38. {:ok, Jason.decode!(res.body)}
  39. end
  40. end
  41. def meili_put(path, params) do
  42. endpoint = Config.get([Pleroma.Search.Meilisearch, :url])
  43. result =
  44. Pleroma.HTTP.request(
  45. :put,
  46. Path.join(endpoint, path),
  47. Jason.encode!(params),
  48. meili_headers(),
  49. []
  50. )
  51. with {:ok, res} <- result do
  52. {:ok, Jason.decode!(res.body)}
  53. end
  54. end
  55. def meili_delete(path) do
  56. endpoint = Config.get([Pleroma.Search.Meilisearch, :url])
  57. with {:ok, _} <-
  58. Pleroma.HTTP.request(
  59. :delete,
  60. Path.join(endpoint, path),
  61. "",
  62. meili_headers(),
  63. []
  64. ) do
  65. :ok
  66. else
  67. _ -> {:error, "Could not remove from index"}
  68. end
  69. end
  70. @impl true
  71. def search(user, query, options \\ []) do
  72. limit = Enum.min([Keyword.get(options, :limit), 40])
  73. offset = Keyword.get(options, :offset, 0)
  74. author = Keyword.get(options, :author)
  75. res =
  76. meili_post(
  77. "/indexes/objects/search",
  78. %{q: query, offset: offset, limit: limit}
  79. )
  80. with {:ok, result} <- res do
  81. hits = result["hits"] |> Enum.map(& &1["ap"])
  82. try do
  83. hits
  84. |> Activity.create_by_object_ap_id()
  85. |> Activity.with_preloaded_object()
  86. |> Activity.restrict_deactivated_users()
  87. |> maybe_restrict_local(user)
  88. |> maybe_restrict_author(author)
  89. |> maybe_restrict_blocked(user)
  90. |> maybe_fetch(user, query)
  91. |> order_by([object: obj], desc: obj.data["published"])
  92. |> Pleroma.Repo.all()
  93. rescue
  94. _ -> maybe_fetch([], user, query)
  95. end
  96. end
  97. end
  98. def object_to_search_data(object) do
  99. # Only index public or unlisted Notes
  100. if not is_nil(object) and object.data["type"] == "Note" and
  101. not is_nil(object.data["content"]) and
  102. not is_nil(object.data["published"]) and
  103. (Pleroma.Constants.as_public() in object.data["to"] or
  104. Pleroma.Constants.as_public() in object.data["cc"]) and
  105. object.data["content"] not in ["", "."] do
  106. data = object.data
  107. content_str =
  108. case data["content"] do
  109. [nil | rest] -> to_string(rest)
  110. str -> str
  111. end
  112. content =
  113. with {:ok, scrubbed} <-
  114. FastSanitize.Sanitizer.scrub(content_str, Pleroma.HTML.Scrubber.SearchIndexing),
  115. trimmed <- String.trim(scrubbed) do
  116. trimmed
  117. end
  118. # Make sure we have a non-empty string
  119. if content != "" do
  120. {:ok, published, _} = DateTime.from_iso8601(data["published"])
  121. %{
  122. id: object.id,
  123. content: content,
  124. ap: data["id"],
  125. published: published |> DateTime.to_unix()
  126. }
  127. end
  128. end
  129. end
  130. @impl true
  131. def add_to_index(activity) do
  132. maybe_search_data = object_to_search_data(activity.object)
  133. if activity.data["type"] == "Create" and maybe_search_data do
  134. result =
  135. meili_put(
  136. "/indexes/objects/documents",
  137. [maybe_search_data]
  138. )
  139. with {:ok, %{"status" => "enqueued"}} <- result do
  140. # Added successfully
  141. :ok
  142. else
  143. _ ->
  144. # There was an error, report it
  145. Logger.error("Failed to add activity #{activity.id} to index: #{inspect(result)}")
  146. {:error, result}
  147. end
  148. else
  149. # The post isn't something we can search, that's ok
  150. :ok
  151. end
  152. end
  153. @impl true
  154. def remove_from_index(object) do
  155. meili_delete("/indexes/objects/documents/#{object.id}")
  156. end
  157. @impl true
  158. def healthcheck_endpoints do
  159. endpoint =
  160. Config.get([Pleroma.Search.Meilisearch, :url])
  161. |> URI.parse()
  162. |> Map.put(:path, "/health")
  163. |> URI.to_string()
  164. [endpoint]
  165. end
  166. end