logo

pleroma

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

meilisearch.ex (4588B)


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