Merge branch 'develop' into feature/new-registrations-digest
This commit is contained in:
commit
5c842e3b68
536 changed files with 1963 additions and 659 deletions
|
|
@ -312,9 +312,7 @@ defmodule Pleroma.Activity do
|
|||
from(u in User.Query.build(deactivated: true), select: u.ap_id)
|
||||
|> Repo.all()
|
||||
|
||||
from(activity in query,
|
||||
where: activity.actor not in ^deactivated_users
|
||||
)
|
||||
Activity.Queries.exclude_authors(query, deactivated_users)
|
||||
end
|
||||
|
||||
defdelegate search(user, query, options \\ []), to: Pleroma.Activity.Search
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ defmodule Pleroma.Activity.Queries do
|
|||
@type query :: Ecto.Queryable.t() | Activity.t()
|
||||
|
||||
alias Pleroma.Activity
|
||||
alias Pleroma.User
|
||||
|
||||
@spec by_ap_id(query, String.t()) :: query
|
||||
def by_ap_id(query \\ Activity, ap_id) do
|
||||
|
|
@ -29,6 +30,11 @@ defmodule Pleroma.Activity.Queries do
|
|||
)
|
||||
end
|
||||
|
||||
@spec by_author(query, String.t()) :: query
|
||||
def by_author(query \\ Activity, %User{ap_id: ap_id}) do
|
||||
from(a in query, where: a.actor == ^ap_id)
|
||||
end
|
||||
|
||||
@spec by_object_id(query, String.t() | [String.t()]) :: query
|
||||
def by_object_id(query \\ Activity, object_id)
|
||||
|
||||
|
|
@ -72,4 +78,8 @@ defmodule Pleroma.Activity.Queries do
|
|||
where: fragment("(?)->>'type' != ?", activity.data, ^activity_type)
|
||||
)
|
||||
end
|
||||
|
||||
def exclude_authors(query \\ Activity, actors) do
|
||||
from(activity in query, where: activity.actor not in ^actors)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -26,18 +26,23 @@ defmodule Pleroma.Activity.Search do
|
|||
|> query_with(index_type, search_query)
|
||||
|> maybe_restrict_local(user)
|
||||
|> maybe_restrict_author(author)
|
||||
|> maybe_restrict_blocked(user)
|
||||
|> Pagination.fetch_paginated(%{"offset" => offset, "limit" => limit}, :offset)
|
||||
|> maybe_fetch(user, search_query)
|
||||
end
|
||||
|
||||
def maybe_restrict_author(query, %User{} = author) do
|
||||
from([a, o] in query,
|
||||
where: a.actor == ^author.ap_id
|
||||
)
|
||||
Activity.Queries.by_author(query, author)
|
||||
end
|
||||
|
||||
def maybe_restrict_author(query, _), do: query
|
||||
|
||||
def maybe_restrict_blocked(query, %User{} = user) do
|
||||
Activity.Queries.exclude_authors(query, User.blocked_users_ap_ids(user))
|
||||
end
|
||||
|
||||
def maybe_restrict_blocked(query, _), do: query
|
||||
|
||||
defp restrict_public(q) do
|
||||
from([a, o] in q,
|
||||
where: fragment("?->>'type' = 'Create'", a.data),
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ defmodule Pleroma.Application do
|
|||
def start(_type, _args) do
|
||||
Pleroma.HTML.compile_scrubbers()
|
||||
Pleroma.Config.DeprecationWarnings.warn()
|
||||
Pleroma.Repo.check_migrations_applied!()
|
||||
setup_instrumenters()
|
||||
load_custom_modules()
|
||||
|
||||
|
|
|
|||
|
|
@ -19,6 +19,8 @@ defmodule Pleroma.Object do
|
|||
|
||||
@type t() :: %__MODULE__{}
|
||||
|
||||
@derive {Jason.Encoder, only: [:data]}
|
||||
|
||||
schema "objects" do
|
||||
field(:data, :map)
|
||||
|
||||
|
|
@ -180,85 +182,17 @@ defmodule Pleroma.Object do
|
|||
|
||||
def delete(%Object{data: %{"id" => id}} = object) do
|
||||
with {:ok, _obj} = swap_object_with_tombstone(object),
|
||||
:ok <- delete_attachments(object),
|
||||
deleted_activity = Activity.delete_all_by_object_ap_id(id),
|
||||
{:ok, true} <- Cachex.del(:object_cache, "object:#{id}"),
|
||||
{:ok, _} <- Cachex.del(:web_resp_cache, URI.parse(id).path) do
|
||||
{:ok, _} <- Cachex.del(:web_resp_cache, URI.parse(id).path),
|
||||
{:ok, _} <-
|
||||
Pleroma.Workers.AttachmentsCleanupWorker.enqueue("cleanup_attachments", %{
|
||||
"object" => object
|
||||
}) do
|
||||
{:ok, object, deleted_activity}
|
||||
end
|
||||
end
|
||||
|
||||
defp delete_attachments(%{data: %{"attachment" => [_ | _] = attachments, "actor" => actor}}) do
|
||||
hrefs =
|
||||
Enum.flat_map(attachments, fn attachment ->
|
||||
Enum.map(attachment["url"], & &1["href"])
|
||||
end)
|
||||
|
||||
names = Enum.map(attachments, & &1["name"])
|
||||
|
||||
uploader = Pleroma.Config.get([Pleroma.Upload, :uploader])
|
||||
|
||||
# find all objects for copies of the attachments, name and actor doesn't matter here
|
||||
delete_ids =
|
||||
from(o in Object,
|
||||
where:
|
||||
fragment(
|
||||
"to_jsonb(array(select jsonb_array_elements((?)#>'{url}') ->> 'href'))::jsonb \\?| (?)",
|
||||
o.data,
|
||||
^hrefs
|
||||
)
|
||||
)
|
||||
|> Repo.all()
|
||||
# we should delete 1 object for any given attachment, but don't delete files if
|
||||
# there are more than 1 object for it
|
||||
|> Enum.reduce(%{}, fn %{
|
||||
id: id,
|
||||
data: %{
|
||||
"url" => [%{"href" => href}],
|
||||
"actor" => obj_actor,
|
||||
"name" => name
|
||||
}
|
||||
},
|
||||
acc ->
|
||||
Map.update(acc, href, %{id: id, count: 1}, fn val ->
|
||||
case obj_actor == actor and name in names do
|
||||
true ->
|
||||
# set id of the actor's object that will be deleted
|
||||
%{val | id: id, count: val.count + 1}
|
||||
|
||||
false ->
|
||||
# another actor's object, just increase count to not delete file
|
||||
%{val | count: val.count + 1}
|
||||
end
|
||||
end)
|
||||
end)
|
||||
|> Enum.map(fn {href, %{id: id, count: count}} ->
|
||||
# only delete files that have single instance
|
||||
with 1 <- count do
|
||||
prefix =
|
||||
case Pleroma.Config.get([Pleroma.Upload, :base_url]) do
|
||||
nil -> "media"
|
||||
_ -> ""
|
||||
end
|
||||
|
||||
base_url = Pleroma.Config.get([__MODULE__, :base_url], Pleroma.Web.base_url())
|
||||
|
||||
file_path = String.trim_leading(href, "#{base_url}/#{prefix}")
|
||||
|
||||
uploader.delete_file(file_path)
|
||||
end
|
||||
|
||||
id
|
||||
end)
|
||||
|
||||
from(o in Object, where: o.id in ^delete_ids)
|
||||
|> Repo.delete_all()
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
defp delete_attachments(%{data: _data}), do: :ok
|
||||
|
||||
def prune(%Object{data: %{"id" => id}} = object) do
|
||||
with {:ok, object} <- Repo.delete(object),
|
||||
{:ok, true} <- Cachex.del(:object_cache, "object:#{id}"),
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@ defmodule Pleroma.Repo do
|
|||
adapter: Ecto.Adapters.Postgres,
|
||||
migration_timestamps: [type: :naive_datetime_usec]
|
||||
|
||||
require Logger
|
||||
|
||||
defmodule Instrumenter do
|
||||
use Prometheus.EctoInstrumenter
|
||||
end
|
||||
|
|
@ -47,4 +49,37 @@ defmodule Pleroma.Repo do
|
|||
_ -> {:error, :not_found}
|
||||
end
|
||||
end
|
||||
|
||||
def check_migrations_applied!() do
|
||||
unless Pleroma.Config.get(
|
||||
[:i_am_aware_this_may_cause_data_loss, :disable_migration_check],
|
||||
false
|
||||
) do
|
||||
Ecto.Migrator.with_repo(__MODULE__, fn repo ->
|
||||
down_migrations =
|
||||
Ecto.Migrator.migrations(repo)
|
||||
|> Enum.reject(fn
|
||||
{:up, _, _} -> true
|
||||
{:down, _, _} -> false
|
||||
end)
|
||||
|
||||
if length(down_migrations) > 0 do
|
||||
down_migrations_text =
|
||||
Enum.map(down_migrations, fn {:down, id, name} -> "- #{name} (#{id})\n" end)
|
||||
|
||||
Logger.error(
|
||||
"The following migrations were not applied:\n#{down_migrations_text}If you want to start Pleroma anyway, set\nconfig :pleroma, :i_am_aware_this_may_cause_data_loss, disable_migration_check: true"
|
||||
)
|
||||
|
||||
raise Pleroma.Repo.UnappliedMigrationsError
|
||||
end
|
||||
end)
|
||||
else
|
||||
:ok
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Pleroma.Repo.UnappliedMigrationsError do
|
||||
defexception message: "Unapplied Migrations detected"
|
||||
end
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.VocabularyPolicy do
|
|||
with accepted_vocabulary <- Pleroma.Config.get([:mrf_vocabulary, :accept]),
|
||||
rejected_vocabulary <- Pleroma.Config.get([:mrf_vocabulary, :reject]),
|
||||
true <-
|
||||
length(accepted_vocabulary) == 0 || Enum.member?(accepted_vocabulary, message_type),
|
||||
Enum.empty?(accepted_vocabulary) || Enum.member?(accepted_vocabulary, message_type),
|
||||
false <-
|
||||
length(rejected_vocabulary) > 0 && Enum.member?(rejected_vocabulary, message_type),
|
||||
{:ok, _} <- filter(message["object"]) do
|
||||
|
|
|
|||
|
|
@ -658,24 +658,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
|||
with %User{ap_id: ^actor_id} = actor <- User.get_cached_by_ap_id(object["id"]) do
|
||||
{:ok, new_user_data} = ActivityPub.user_data_from_user_object(object)
|
||||
|
||||
locked = new_user_data[:locked] || false
|
||||
attachment = get_in(new_user_data, [:source_data, "attachment"]) || []
|
||||
invisible = new_user_data[:invisible] || false
|
||||
|
||||
fields =
|
||||
attachment
|
||||
|> Enum.filter(fn %{"type" => t} -> t == "PropertyValue" end)
|
||||
|> Enum.map(fn fields -> Map.take(fields, ["name", "value"]) end)
|
||||
|
||||
update_data =
|
||||
new_user_data
|
||||
|> Map.take([:avatar, :banner, :bio, :name, :also_known_as])
|
||||
|> Map.put(:fields, fields)
|
||||
|> Map.put(:locked, locked)
|
||||
|> Map.put(:invisible, invisible)
|
||||
|
||||
actor
|
||||
|> User.upgrade_changeset(update_data, true)
|
||||
|> User.upgrade_changeset(new_user_data, true)
|
||||
|> User.update_and_set_cache()
|
||||
|
||||
ActivityPub.update(%{
|
||||
|
|
|
|||
|
|
@ -312,19 +312,12 @@ defmodule Pleroma.Web.ActivityPub.Utils do
|
|||
|> Map.put("content", emoji)
|
||||
end
|
||||
|
||||
@spec update_element_in_object(String.t(), list(any), Object.t()) ::
|
||||
@spec update_element_in_object(String.t(), list(any), Object.t(), integer() | nil) ::
|
||||
{:ok, Object.t()} | {:error, Ecto.Changeset.t()}
|
||||
def update_element_in_object(property, element, object) do
|
||||
def update_element_in_object(property, element, object, count \\ nil) do
|
||||
length =
|
||||
if is_map(element) do
|
||||
element
|
||||
|> Map.values()
|
||||
|> List.flatten()
|
||||
|> length()
|
||||
else
|
||||
element
|
||||
|> length()
|
||||
end
|
||||
count ||
|
||||
length(element)
|
||||
|
||||
data =
|
||||
Map.merge(
|
||||
|
|
@ -344,29 +337,52 @@ defmodule Pleroma.Web.ActivityPub.Utils do
|
|||
%Activity{data: %{"content" => emoji, "actor" => actor}},
|
||||
object
|
||||
) do
|
||||
reactions = object.data["reactions"] || %{}
|
||||
emoji_actors = reactions[emoji] || []
|
||||
new_emoji_actors = [actor | emoji_actors] |> Enum.uniq()
|
||||
new_reactions = Map.put(reactions, emoji, new_emoji_actors)
|
||||
update_element_in_object("reaction", new_reactions, object)
|
||||
reactions = object.data["reactions"] || []
|
||||
|
||||
new_reactions =
|
||||
case Enum.find_index(reactions, fn [candidate, _] -> emoji == candidate end) do
|
||||
nil ->
|
||||
reactions ++ [[emoji, [actor]]]
|
||||
|
||||
index ->
|
||||
List.update_at(
|
||||
reactions,
|
||||
index,
|
||||
fn [emoji, users] -> [emoji, Enum.uniq([actor | users])] end
|
||||
)
|
||||
end
|
||||
|
||||
count = emoji_count(new_reactions)
|
||||
|
||||
update_element_in_object("reaction", new_reactions, object, count)
|
||||
end
|
||||
|
||||
def emoji_count(reactions_list) do
|
||||
Enum.reduce(reactions_list, 0, fn [_, users], acc -> acc + length(users) end)
|
||||
end
|
||||
|
||||
def remove_emoji_reaction_from_object(
|
||||
%Activity{data: %{"content" => emoji, "actor" => actor}},
|
||||
object
|
||||
) do
|
||||
reactions = object.data["reactions"] || %{}
|
||||
emoji_actors = reactions[emoji] || []
|
||||
new_emoji_actors = List.delete(emoji_actors, actor)
|
||||
reactions = object.data["reactions"] || []
|
||||
|
||||
new_reactions =
|
||||
if new_emoji_actors == [] do
|
||||
Map.delete(reactions, emoji)
|
||||
else
|
||||
Map.put(reactions, emoji, new_emoji_actors)
|
||||
case Enum.find_index(reactions, fn [candidate, _] -> emoji == candidate end) do
|
||||
nil ->
|
||||
reactions
|
||||
|
||||
index ->
|
||||
List.update_at(
|
||||
reactions,
|
||||
index,
|
||||
fn [emoji, users] -> [emoji, List.delete(users, actor)] end
|
||||
)
|
||||
|> Enum.reject(fn [_, users] -> Enum.empty?(users) end)
|
||||
end
|
||||
|
||||
update_element_in_object("reaction", new_reactions, object)
|
||||
count = emoji_count(new_reactions)
|
||||
update_element_in_object("reaction", new_reactions, object, count)
|
||||
end
|
||||
|
||||
@spec add_like_to_object(Activity.t(), Object.t()) ::
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
|
|||
plug(
|
||||
OAuthScopesPlug,
|
||||
%{scopes: ["write:reports"], admin: true}
|
||||
when action in [:report_update_state, :report_respond]
|
||||
when action in [:reports_update]
|
||||
)
|
||||
|
||||
plug(
|
||||
|
|
@ -639,7 +639,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
|
|||
def force_password_reset(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
|
||||
users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
|
||||
|
||||
Enum.map(users, &User.force_password_reset_async/1)
|
||||
Enum.each(users, &User.force_password_reset_async/1)
|
||||
|
||||
ModerationLog.insert_log(%{
|
||||
actor: admin,
|
||||
|
|
|
|||
|
|
@ -85,9 +85,13 @@ defmodule Pleroma.Web.CommonAPI do
|
|||
def repeat(id_or_ap_id, user, params \\ %{}) do
|
||||
with %Activity{} = activity <- get_by_id_or_ap_id(id_or_ap_id),
|
||||
object <- Object.normalize(activity),
|
||||
nil <- Utils.get_existing_announce(user.ap_id, object),
|
||||
announce_activity <- Utils.get_existing_announce(user.ap_id, object),
|
||||
public <- public_announce?(object, params) do
|
||||
ActivityPub.announce(user, object, nil, true, public)
|
||||
if announce_activity do
|
||||
{:ok, announce_activity, object}
|
||||
else
|
||||
ActivityPub.announce(user, object, nil, true, public)
|
||||
end
|
||||
else
|
||||
_ -> {:error, dgettext("errors", "Could not repeat")}
|
||||
end
|
||||
|
|
@ -105,8 +109,12 @@ defmodule Pleroma.Web.CommonAPI do
|
|||
def favorite(id_or_ap_id, user) do
|
||||
with %Activity{} = activity <- get_by_id_or_ap_id(id_or_ap_id),
|
||||
object <- Object.normalize(activity),
|
||||
nil <- Utils.get_existing_like(user.ap_id, object) do
|
||||
ActivityPub.like(user, object)
|
||||
like_activity <- Utils.get_existing_like(user.ap_id, object) do
|
||||
if like_activity do
|
||||
{:ok, like_activity, object}
|
||||
else
|
||||
ActivityPub.like(user, object)
|
||||
end
|
||||
else
|
||||
_ -> {:error, dgettext("errors", "Could not favorite")}
|
||||
end
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ defmodule Pleroma.Web.MastodonAPI.SearchController do
|
|||
result =
|
||||
default_values
|
||||
|> Enum.map(fn {resource, default_value} ->
|
||||
if params["type"] == nil or params["type"] == resource do
|
||||
if params["type"] in [nil, resource] do
|
||||
{resource, fn -> resource_search(version, resource, query, options) end}
|
||||
else
|
||||
{resource, fn -> default_value end}
|
||||
|
|
|
|||
|
|
@ -6,9 +6,9 @@ defmodule Pleroma.Web.MastodonAPI.SubscriptionController do
|
|||
@moduledoc "The module represents functions to manage user subscriptions."
|
||||
use Pleroma.Web, :controller
|
||||
|
||||
alias Pleroma.Web.MastodonAPI.PushSubscriptionView, as: View
|
||||
alias Pleroma.Web.Push
|
||||
alias Pleroma.Web.Push.Subscription
|
||||
alias Pleroma.Web.MastodonAPI.PushSubscriptionView, as: View
|
||||
|
||||
action_fallback(:errors)
|
||||
|
||||
|
|
|
|||
|
|
@ -77,10 +77,7 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do
|
|||
|> render("index.json", activities: activities, for: user, as: :activity)
|
||||
end
|
||||
|
||||
# GET /api/v1/timelines/tag/:tag
|
||||
def hashtag(%{assigns: %{user: user}} = conn, params) do
|
||||
local_only = truthy_param?(params["local"])
|
||||
|
||||
def hashtag_fetching(params, user, local_only) do
|
||||
tags =
|
||||
[params["tag"], params["any"]]
|
||||
|> List.flatten()
|
||||
|
|
@ -98,7 +95,7 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do
|
|||
|> Map.get("none", [])
|
||||
|> Enum.map(&String.downcase(&1))
|
||||
|
||||
activities =
|
||||
_activities =
|
||||
params
|
||||
|> Map.put("type", "Create")
|
||||
|> Map.put("local_only", local_only)
|
||||
|
|
@ -109,6 +106,13 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do
|
|||
|> Map.put("tag_all", tag_all)
|
||||
|> Map.put("tag_reject", tag_reject)
|
||||
|> ActivityPub.fetch_public_activities()
|
||||
end
|
||||
|
||||
# GET /api/v1/timelines/tag/:tag
|
||||
def hashtag(%{assigns: %{user: user}} = conn, params) do
|
||||
local_only = truthy_param?(params["local"])
|
||||
|
||||
activities = hashtag_fetching(params, user, local_only)
|
||||
|
||||
conn
|
||||
|> add_link_headers(activities, %{"local" => local_only})
|
||||
|
|
|
|||
|
|
@ -253,6 +253,15 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
|
|||
nil
|
||||
end
|
||||
|
||||
emoji_reactions =
|
||||
with %{data: %{"reactions" => emoji_reactions}} <- object do
|
||||
Enum.map(emoji_reactions, fn [emoji, users] ->
|
||||
[emoji, length(users)]
|
||||
end)
|
||||
else
|
||||
_ -> []
|
||||
end
|
||||
|
||||
%{
|
||||
id: to_string(activity.id),
|
||||
uri: object.data["id"],
|
||||
|
|
@ -293,7 +302,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
|
|||
spoiler_text: %{"text/plain" => summary_plaintext},
|
||||
expires_at: expires_at,
|
||||
direct_conversation_id: direct_conversation_id,
|
||||
thread_muted: thread_muted?
|
||||
thread_muted: thread_muted?,
|
||||
emoji_reactions: emoji_reactions
|
||||
}
|
||||
}
|
||||
end
|
||||
|
|
|
|||
|
|
@ -14,10 +14,10 @@ defmodule Pleroma.Web.OAuth.OAuthController do
|
|||
alias Pleroma.Web.ControllerHelper
|
||||
alias Pleroma.Web.OAuth.App
|
||||
alias Pleroma.Web.OAuth.Authorization
|
||||
alias Pleroma.Web.OAuth.Scopes
|
||||
alias Pleroma.Web.OAuth.Token
|
||||
alias Pleroma.Web.OAuth.Token.Strategy.RefreshToken
|
||||
alias Pleroma.Web.OAuth.Token.Strategy.Revoke, as: RevokeToken
|
||||
alias Pleroma.Web.OAuth.Scopes
|
||||
|
||||
require Logger
|
||||
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ defmodule Pleroma.Web.PleromaAPI.PleromaAPIController do
|
|||
plug(
|
||||
OAuthScopesPlug,
|
||||
%{scopes: ["read:statuses"]}
|
||||
when action in [:conversation, :conversation_statuses, :emoji_reactions_by]
|
||||
when action in [:conversation, :conversation_statuses]
|
||||
)
|
||||
|
||||
plug(
|
||||
|
|
@ -43,21 +43,21 @@ defmodule Pleroma.Web.PleromaAPI.PleromaAPIController do
|
|||
|
||||
def emoji_reactions_by(%{assigns: %{user: user}} = conn, %{"id" => activity_id}) do
|
||||
with %Activity{} = activity <- Activity.get_by_id_with_object(activity_id),
|
||||
%Object{data: %{"reactions" => emoji_reactions}} <- Object.normalize(activity) do
|
||||
%Object{data: %{"reactions" => emoji_reactions}} when is_list(emoji_reactions) <-
|
||||
Object.normalize(activity) do
|
||||
reactions =
|
||||
emoji_reactions
|
||||
|> Enum.map(fn {emoji, users} ->
|
||||
|> Enum.map(fn [emoji, users] ->
|
||||
users = Enum.map(users, &User.get_cached_by_ap_id/1)
|
||||
{emoji, AccountView.render("index.json", %{users: users, for: user, as: :user})}
|
||||
end)
|
||||
|> Enum.into(%{})
|
||||
|
||||
conn
|
||||
|> json(reactions)
|
||||
else
|
||||
_e ->
|
||||
conn
|
||||
|> json(%{})
|
||||
|> json([])
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
88
lib/pleroma/workers/attachments_cleanup_worker.ex
Normal file
88
lib/pleroma/workers/attachments_cleanup_worker.ex
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Workers.AttachmentsCleanupWorker do
|
||||
import Ecto.Query
|
||||
|
||||
alias Pleroma.Object
|
||||
alias Pleroma.Repo
|
||||
|
||||
use Pleroma.Workers.WorkerHelper, queue: "attachments_cleanup"
|
||||
|
||||
@impl Oban.Worker
|
||||
def perform(
|
||||
%{"object" => %{"data" => %{"attachment" => [_ | _] = attachments, "actor" => actor}}},
|
||||
_job
|
||||
) do
|
||||
hrefs =
|
||||
Enum.flat_map(attachments, fn attachment ->
|
||||
Enum.map(attachment["url"], & &1["href"])
|
||||
end)
|
||||
|
||||
names = Enum.map(attachments, & &1["name"])
|
||||
|
||||
uploader = Pleroma.Config.get([Pleroma.Upload, :uploader])
|
||||
|
||||
# find all objects for copies of the attachments, name and actor doesn't matter here
|
||||
delete_ids =
|
||||
from(o in Object,
|
||||
where:
|
||||
fragment(
|
||||
"to_jsonb(array(select jsonb_array_elements((?)#>'{url}') ->> 'href' where jsonb_typeof((?)#>'{url}') = 'array'))::jsonb \\?| (?)",
|
||||
o.data,
|
||||
o.data,
|
||||
^hrefs
|
||||
)
|
||||
)
|
||||
# The query above can be time consumptive on large instances until we
|
||||
# refactor how uploads are stored
|
||||
|> Repo.all(timout: :infinity)
|
||||
# we should delete 1 object for any given attachment, but don't delete
|
||||
# files if there are more than 1 object for it
|
||||
|> Enum.reduce(%{}, fn %{
|
||||
id: id,
|
||||
data: %{
|
||||
"url" => [%{"href" => href}],
|
||||
"actor" => obj_actor,
|
||||
"name" => name
|
||||
}
|
||||
},
|
||||
acc ->
|
||||
Map.update(acc, href, %{id: id, count: 1}, fn val ->
|
||||
case obj_actor == actor and name in names do
|
||||
true ->
|
||||
# set id of the actor's object that will be deleted
|
||||
%{val | id: id, count: val.count + 1}
|
||||
|
||||
false ->
|
||||
# another actor's object, just increase count to not delete file
|
||||
%{val | count: val.count + 1}
|
||||
end
|
||||
end)
|
||||
end)
|
||||
|> Enum.map(fn {href, %{id: id, count: count}} ->
|
||||
# only delete files that have single instance
|
||||
with 1 <- count do
|
||||
prefix =
|
||||
case Pleroma.Config.get([Pleroma.Upload, :base_url]) do
|
||||
nil -> "media"
|
||||
_ -> ""
|
||||
end
|
||||
|
||||
base_url = Pleroma.Config.get([__MODULE__, :base_url], Pleroma.Web.base_url())
|
||||
|
||||
file_path = String.trim_leading(href, "#{base_url}/#{prefix}")
|
||||
|
||||
uploader.delete_file(file_path)
|
||||
end
|
||||
|
||||
id
|
||||
end)
|
||||
|
||||
from(o in Object, where: o.id in ^delete_ids)
|
||||
|> Repo.delete_all()
|
||||
end
|
||||
|
||||
def perform(%{"object" => _object}, _job), do: :ok
|
||||
end
|
||||
Loading…
Add table
Add a link
Reference in a new issue