Merge branch 'develop' into tests/mastodon_api_controller.ex
[akkoma] / lib / mix / tasks / pleroma / database.ex
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
4
5 defmodule Mix.Tasks.Pleroma.Database do
6 alias Pleroma.Conversation
7 alias Pleroma.Object
8 alias Pleroma.Repo
9 alias Pleroma.User
10 require Logger
11 require Pleroma.Constants
12 import Mix.Pleroma
13 use Mix.Task
14
15 @shortdoc "A collection of database related tasks"
16 @moduledoc """
17 A collection of database related tasks
18
19 ## Replace embedded objects with their references
20
21 Replaces embedded objects with references to them in the `objects` table. Only needs to be ran once. The reason why this is not a migration is because it could significantly increase the database size after being ran, however after this `VACUUM FULL` will be able to reclaim about 20% (really depends on what is in the database, your mileage may vary) of the db size before the migration.
22
23 mix pleroma.database remove_embedded_objects
24
25 Options:
26 - `--vacuum` - run `VACUUM FULL` after the embedded objects are replaced with their references
27
28 ## Prune old objects from the database
29
30 mix pleroma.database prune_objects
31
32 ## Create a conversation for all existing DMs. Can be safely re-run.
33
34 mix pleroma.database bump_all_conversations
35
36 ## Remove duplicated items from following and update followers count for all users
37
38 mix pleroma.database update_users_following_followers_counts
39
40 ## Fix the pre-existing "likes" collections for all objects
41
42 mix pleroma.database fix_likes_collections
43 """
44 def run(["remove_embedded_objects" | args]) do
45 {options, [], []} =
46 OptionParser.parse(
47 args,
48 strict: [
49 vacuum: :boolean
50 ]
51 )
52
53 start_pleroma()
54 Logger.info("Removing embedded objects")
55
56 Repo.query!(
57 "update activities set data = jsonb_set(data, '{object}'::text[], data->'object'->'id') where data->'object'->>'id' is not null;",
58 [],
59 timeout: :infinity
60 )
61
62 if Keyword.get(options, :vacuum) do
63 Logger.info("Runnning VACUUM FULL")
64
65 Repo.query!(
66 "vacuum full;",
67 [],
68 timeout: :infinity
69 )
70 end
71 end
72
73 def run(["bump_all_conversations"]) do
74 start_pleroma()
75 Conversation.bump_for_all_activities()
76 end
77
78 def run(["update_users_following_followers_counts"]) do
79 start_pleroma()
80
81 users = Repo.all(User)
82 Enum.each(users, &User.remove_duplicated_following/1)
83 Enum.each(users, &User.update_follower_count/1)
84 end
85
86 def run(["prune_objects" | args]) do
87 import Ecto.Query
88
89 {options, [], []} =
90 OptionParser.parse(
91 args,
92 strict: [
93 vacuum: :boolean
94 ]
95 )
96
97 start_pleroma()
98
99 deadline = Pleroma.Config.get([:instance, :remote_post_retention_days])
100
101 Logger.info("Pruning objects older than #{deadline} days")
102
103 time_deadline =
104 NaiveDateTime.utc_now()
105 |> NaiveDateTime.add(-(deadline * 86_400))
106
107 from(o in Object,
108 where:
109 fragment(
110 "?->'to' \\? ? OR ?->'cc' \\? ?",
111 o.data,
112 ^Pleroma.Constants.as_public(),
113 o.data,
114 ^Pleroma.Constants.as_public()
115 ),
116 where: o.inserted_at < ^time_deadline,
117 where:
118 fragment("split_part(?->>'actor', '/', 3) != ?", o.data, ^Pleroma.Web.Endpoint.host())
119 )
120 |> Repo.delete_all(timeout: :infinity)
121
122 if Keyword.get(options, :vacuum) do
123 Logger.info("Runnning VACUUM FULL")
124
125 Repo.query!(
126 "vacuum full;",
127 [],
128 timeout: :infinity
129 )
130 end
131 end
132
133 def run(["fix_likes_collections"]) do
134 import Ecto.Query
135
136 start_pleroma()
137
138 from(object in Object,
139 where: fragment("(?)->>'likes' is not null", object.data),
140 select: %{id: object.id, likes: fragment("(?)->>'likes'", object.data)}
141 )
142 |> Pleroma.RepoStreamer.chunk_stream(100)
143 |> Stream.each(fn objects ->
144 ids =
145 objects
146 |> Enum.filter(fn object -> object.likes |> Jason.decode!() |> is_map() end)
147 |> Enum.map(& &1.id)
148
149 Object
150 |> where([object], object.id in ^ids)
151 |> update([object],
152 set: [
153 data:
154 fragment(
155 "jsonb_set(?, '{likes}', '[]'::jsonb, true)",
156 object.data
157 )
158 ]
159 )
160 |> Repo.update_all([], timeout: :infinity)
161 end)
162 |> Stream.run()
163 end
164 end