27a31afcf4a684f8d87dfb8daac2abe6a3c7a7e3
[akkoma] / lib / mix / tasks / pleroma / search / meilisearch.ex
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
4
5 defmodule Mix.Tasks.Pleroma.Search.Meilisearch do
6 require Pleroma.Constants
7
8 import Mix.Pleroma
9 import Ecto.Query
10
11 import Pleroma.Search.Meilisearch,
12 only: [meili_put: 2, meili_get: 1, meili_delete!: 1]
13
14 def run(["index"]) do
15 start_pleroma()
16
17 meili_version =
18 (
19 {:ok, result} = meili_get("/version")
20
21 result["pkgVersion"]
22 )
23
24 # The ranking rule syntax was changed but nothing about that is mentioned in the changelog
25 if not Version.match?(meili_version, ">= 0.25.0") do
26 raise "Meilisearch <0.24.0 not supported"
27 end
28
29 {:ok, _} =
30 meili_put(
31 "/indexes/objects/settings/ranking-rules",
32 [
33 "published:desc",
34 "words",
35 "exactness",
36 "proximity",
37 "typo",
38 "attribute",
39 "sort"
40 ]
41 )
42
43 {:ok, _} =
44 meili_put(
45 "/indexes/objects/settings/searchable-attributes",
46 [
47 "content"
48 ]
49 )
50
51 IO.puts("Created indices. Starting to insert posts.")
52
53 chunk_size = Pleroma.Config.get([Pleroma.Search.Meilisearch, :initial_indexing_chunk_size])
54
55 Pleroma.Repo.transaction(
56 fn ->
57 query =
58 from(Pleroma.Object,
59 # Only index public and unlisted posts which are notes and have some text
60 where:
61 fragment("data->>'type' = 'Note'") and
62 (fragment("data->'to' \\? ?", ^Pleroma.Constants.as_public()) or
63 fragment("data->'cc' \\? ?", ^Pleroma.Constants.as_public())),
64 order_by: [desc: fragment("data->'published'")]
65 )
66
67 count = query |> Pleroma.Repo.aggregate(:count, :data)
68 IO.puts("Entries to index: #{count}")
69
70 Pleroma.Repo.stream(
71 query,
72 timeout: :infinity
73 )
74 |> Stream.map(&Pleroma.Search.Meilisearch.object_to_search_data/1)
75 |> Stream.filter(fn o -> not is_nil(o) end)
76 |> Stream.chunk_every(chunk_size)
77 |> Stream.transform(0, fn objects, acc ->
78 new_acc = acc + Enum.count(objects)
79
80 # Reset to the beginning of the line and rewrite it
81 IO.write("\r")
82 IO.write("Indexed #{new_acc} entries")
83
84 {[objects], new_acc}
85 end)
86 |> Stream.each(fn objects ->
87 result =
88 meili_put(
89 "/indexes/objects/documents",
90 objects
91 )
92
93 with {:ok, res} <- result do
94 if not Map.has_key?(res, "indexUid") do
95 IO.puts("\nFailed to index: #{inspect(result)}")
96 end
97 else
98 e -> IO.puts("\nFailed to index due to network error: #{inspect(e)}")
99 end
100 end)
101 |> Stream.run()
102 end,
103 timeout: :infinity
104 )
105
106 IO.write("\n")
107 end
108
109 def run(["clear"]) do
110 start_pleroma()
111
112 meili_delete!("/indexes/objects/documents")
113 end
114
115 def run(["show-keys", master_key]) do
116 start_pleroma()
117
118 endpoint = Pleroma.Config.get([Pleroma.Search.Meilisearch, :url])
119
120 {:ok, result} =
121 Pleroma.HTTP.get(
122 Path.join(endpoint, "/keys"),
123 [{"Authorization", "Bearer #{master_key}"}]
124 )
125
126 decoded = Jason.decode!(result.body)
127
128 if decoded["results"] do
129 Enum.each(decoded["results"], fn %{"description" => desc, "key" => key} ->
130 IO.puts("#{desc}: #{key}")
131 end)
132 else
133 IO.puts("Error fetching the keys, check the master key is correct: #{inspect(decoded)}")
134 end
135 end
136
137 def run(["stats"]) do
138 start_pleroma()
139
140 {:ok, result} = meili_get("/indexes/objects/stats")
141 IO.puts("Number of entries: #{result["numberOfDocuments"]}")
142 IO.puts("Indexing? #{result["isIndexing"]}")
143 end
144 end