# Akkoma: A lightweight social networking server # Copyright © 2022-2022 Akkoma Authors # SPDX-License-Identifier: AGPL-3.0-only # NOTE: Elasticsearch has never been and will never be well supported. # If you are experiencing strange behaviour such as posts being shuffled or # missing, or the limit option not being respected, this module is the cause. # If this bothers you, I recommend switching to database search or Meilisearch. # Using Elasticsearch with non-humongous web services might be overkill anyway. defmodule Pleroma.Search.Elasticsearch do @behaviour Pleroma.Search.SearchBackend alias Pleroma.Activity alias Pleroma.Object.Fetcher alias Pleroma.Web.ActivityPub.Visibility alias Pleroma.Search.Elasticsearch.Parsers alias Pleroma.User def es_query(:activity, query, offset, limit) do must = Parsers.Activity.parse(query) %{ size: limit, from: offset, terminate_after: 50, timeout: "5s", sort: [ "_score", %{"_timestamp" => %{order: "desc", format: "basic_date_time"}} ], query: %{ bool: %{ must: must } } } end defp maybe_fetch(:activity, search_query) do with true <- Regex.match?(~r/https?:/, search_query), {:ok, object} <- Fetcher.fetch_object_from_id(search_query), %Activity{} = activity <- Activity.get_create_by_object_ap_id(object.data["id"]) do activity else _ -> nil end end def search(user, query, options) do can_search_following = Pleroma.Config.get([Pleroma.Search, :extensions, :search_option_following], true) limit = Enum.min([Keyword.get(options, :limit), 40]) offset = Keyword.get(options, :offset, 0) following = can_search_following && Keyword.get(options, :following, false) only_media = Keyword.get(options, :only_media, false) only_local = Keyword.get(options, :local, false) parsed_query = query |> String.trim() |> SearchParser.parse!() activity_fetch_task = Task.async(fn -> maybe_fetch(:activity, String.trim(query)) end) activity_task = Task.async(fn -> q = es_query(:activity, parsed_query, offset, limit) :activities |> Pleroma.Search.Elasticsearch.Store.search(q) |> Enum.filter(fn x -> Enum.all?([ x.data["type"] == "Create", x.object.data["type"] == "Note", Visibility.visible_for_user?(x, user), if only_media do length(x.object.data["attachment"]) > 0 else true end, if following do Enum.member?(User.following_ap_ids(user), x.object.data["actor"]) else true end, if only_local do x.local == true else true end ]) end) end) activity_results = Task.await(activity_task) direct_activity = Task.await(activity_fetch_task) activity_results = if direct_activity == nil do activity_results else [direct_activity | activity_results] end activity_results end @impl true def add_to_index(activity) do Elasticsearch.put_document(Pleroma.Search.Elasticsearch.Cluster, activity, "activities") end @impl true def remove_from_index(object) do Elasticsearch.delete_document(Pleroma.Search.Elasticsearch.Cluster, object, "activities") end end