caeeb58d5f6ee39629b8a97ee55443ed5168512a
[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())) and
64 fragment("data->>'published' IS NOT NULL"),
65 order_by: [desc: fragment("data->'published'")]
66 )
67
68 count = query |> Pleroma.Repo.aggregate(:count, :data)
69 IO.puts("Entries to index: #{count}")
70
71 Pleroma.Repo.stream(
72 query,
73 timeout: :infinity
74 )
75 |> Stream.map(&Pleroma.Search.Meilisearch.object_to_search_data/1)
76 |> Stream.filter(fn o -> not is_nil(o) end)
77 |> Stream.chunk_every(chunk_size)
78 |> Stream.transform(0, fn objects, acc ->
79 new_acc = acc + Enum.count(objects)
80
81 # Reset to the beginning of the line and rewrite it
82 IO.write("\r")
83 IO.write("Indexed #{new_acc} entries")
84
85 {[objects], new_acc}
86 end)
87 |> Stream.each(fn objects ->
88 result =
89 meili_put(
90 "/indexes/objects/documents",
91 objects
92 )
93
94 with {:ok, res} <- result do
95 if not Map.has_key?(res, "indexUid") do
96 IO.puts("\nFailed to index: #{inspect(result)}")
97 end
98 else
99 e -> IO.puts("\nFailed to index due to network error: #{inspect(e)}")
100 end
101 end)
102 |> Stream.run()
103 end,
104 timeout: :infinity
105 )
106
107 IO.write("\n")
108 end
109
110 def run(["clear"]) do
111 start_pleroma()
112
113 meili_delete!("/indexes/objects/documents")
114 end
115
116 def run(["show-keys", master_key]) do
117 start_pleroma()
118
119 endpoint = Pleroma.Config.get([Pleroma.Search.Meilisearch, :url])
120
121 {:ok, result} =
122 Pleroma.HTTP.get(
123 Path.join(endpoint, "/keys"),
124 [{"Authorization", "Bearer #{master_key}"}]
125 )
126
127 decoded = Jason.decode!(result.body)
128
129 if decoded["results"] do
130 Enum.each(decoded["results"], fn %{"description" => desc, "key" => key} ->
131 IO.puts("#{desc}: #{key}")
132 end)
133 else
134 IO.puts("Error fetching the keys, check the master key is correct: #{inspect(decoded)}")
135 end
136 end
137
138 def run(["stats"]) do
139 start_pleroma()
140
141 {:ok, result} = meili_get("/indexes/objects/stats")
142 IO.puts("Number of entries: #{result["numberOfDocuments"]}")
143 IO.puts("Indexing? #{result["isIndexing"]}")
144 end
145 end