This commit is contained in:
Ekaterina Vaartis 2026-11-04 18:24:27 +03:00
commit 456dbf9997
5 changed files with 64 additions and 45 deletions

View file

@ -309,35 +309,41 @@ defmodule Pleroma.Upload do
end end
def list_duplicate_attachments(%{"id" => id, "url" => [%{"href" => href}]}) do def list_duplicate_attachments(%{"id" => id, "url" => [%{"href" => href}]}) do
clean_href = %URI{ URI.parse(href) | query: nil } |> URI.to_string() clean_href = %URI{URI.parse(href) | query: nil} |> URI.to_string()
same_attachment_count = same_attachment_count =
from( from(
o in Object, o in Object,
where: fragment("STARTS_WITH((?)->'url'->0->>'href', (?))", o.data, ^clean_href) and fragment("(?)->>'id' <> (?)", o.data, ^id), where:
) |> Repo.all() fragment("STARTS_WITH((?)->'url'->0->>'href', (?))", o.data, ^clean_href) and
fragment("(?)->>'id' <> (?)", o.data, ^id)
)
|> Repo.all()
end end
def list_objects_with_attachment(href) do def list_objects_with_attachment(href) do
clean_href = %URI{ URI.parse(href) | query: nil } |> URI.to_string() clean_href = %URI{URI.parse(href) | query: nil} |> URI.to_string()
# Ugly as sin, but I could not figure out a better way to do this in ecto # Ugly as sin, but I could not figure out a better way to do this in ecto
# This tries to find any objects with this attachment # This tries to find any objects with this attachment
# This DOES NOT handle chat messages and SKIPS them, because they have attachments as an object instead of an array # This DOES NOT handle chat messages and SKIPS them, because they have attachments as an object instead of an array
Repo.query!( Repo.query!(
""" """
SELECT * FROM SELECT * FROM
(SELECT objects.data->>'id' AS id, jsonb_array_elements(objects.data->'attachment') as json_attachments FROM objects WHERE jsonb_typeof(objects.data->'attachment') = 'array') (SELECT objects.data->>'id' AS id, jsonb_array_elements(objects.data->'attachment') as json_attachments FROM objects WHERE jsonb_typeof(objects.data->'attachment') = 'array')
WHERE STARTS_WITH(json_attachments->'url'->0->>'href', $1) WHERE STARTS_WITH(json_attachments->'url'->0->>'href', $1)
""", """,
[clean_href] [clean_href]
).rows |> Enum.map(&List.first(&1)) ).rows
|> Enum.map(&List.first(&1))
end end
def list_uploaded_by(user, offset, limit) do def list_uploaded_by(user, offset, limit) do
from(obj in Object, from(obj in Object,
where: fragment("(?)->>'type' = ?", obj.data, "Document") and fragment("(?)->>'actor' = ?", obj.data, ^user.ap_id), where:
order_by: [desc: obj.id] fragment("(?)->>'type' = ?", obj.data, "Document") and
fragment("(?)->>'actor' = ?", obj.data, ^user.ap_id),
order_by: [desc: obj.id]
) )
|> Pagination.fetch_paginated(%{"offset" => offset, "limit" => limit}, :offset) |> Pagination.fetch_paginated(%{"offset" => offset, "limit" => limit}, :offset)
end end

View file

@ -417,7 +417,9 @@ defmodule Pleroma.Web.ApiSpec.PleromaUtilOperation do
type: :object, type: :object,
properties: %{ properties: %{
uploaded: %Schema{ uploaded: %Schema{
type: :string, description: "Date and time when the attachment was uploaded", format: :"date-time" type: :string,
description: "Date and time when the attachment was uploaded",
format: :"date-time"
}, },
attachment: Attachment attachment: Attachment
} }

View file

@ -89,7 +89,7 @@ defmodule Pleroma.Web.MastodonAPI.MediaController do
) do ) do
with %Object{} = object <- Object.get_by_id(id), with %Object{} = object <- Object.get_by_id(id),
:ok <- Object.authorize_access(object, user), :ok <- Object.authorize_access(object, user),
href <- object.data["url"] |> List.first |> Map.get("href"), href <- object.data["url"] |> List.first() |> Map.get("href"),
{:used, []} <- {:used, Pleroma.Upload.list_objects_with_attachment(href)}, {:used, []} <- {:used, Pleroma.Upload.list_objects_with_attachment(href)},
{:ok, _, _} <- Object.delete(object) do {:ok, _, _} <- Object.delete(object) do
conn conn
@ -99,26 +99,34 @@ defmodule Pleroma.Web.MastodonAPI.MediaController do
{:used, list} -> {:used, list} ->
conn conn
|> put_status(:bad_request) |> put_status(:bad_request)
|> json(%{error: "Refusing to delete attachment that is still used by #{Enum.count(list)} objects"}) |> json(%{
error: "Refusing to delete attachment that is still used by #{Enum.count(list)} objects"
})
end end
end end
def update(conn, data), do: show(conn, data) def update(conn, data), do: show(conn, data)
@doc "GET /api/v1/media/:id" @doc "GET /api/v1/media/:id"
def show(%{assigns: %{user: user}, private: %{open_api_spex: %{params: %{id: id, attachment_usage: attachment_usage}}}} = conn, _) do def show(
%{
assigns: %{user: user},
private: %{open_api_spex: %{params: %{id: id, attachment_usage: attachment_usage}}}
} = conn,
_
) do
with %Object{data: data, id: object_id} = object <- Object.get_by_id(id), with %Object{data: data, id: object_id} = object <- Object.get_by_id(id),
:ok <- Object.authorize_access(object, user) do :ok <- Object.authorize_access(object, user) do
attachment_data = Map.put(data, "id", object_id) attachment_data = Map.put(data, "id", object_id)
attachment_data = attachment_data =
if attachment_usage do if attachment_usage do
href = data["url"] |> List.first |> Map.get("href") href = data["url"] |> List.first() |> Map.get("href")
used_in_objects = Pleroma.Upload.list_objects_with_attachment(href) used_in_objects = Pleroma.Upload.list_objects_with_attachment(href)
attachment_duplicates = attachment_duplicates =
Pleroma.Upload.list_duplicate_attachments(object.data) Pleroma.Upload.list_duplicate_attachments(object.data)
|> Enum.group_by(& if &1.data["actor"] == user.ap_id, do: :own, else: :other) |> Enum.group_by(&if &1.data["actor"] == user.ap_id, do: :own, else: :other)
|> Map.new(fn {key, val} -> {key, length(val)} end) |> Map.new(fn {key, val} -> {key, length(val)} end)
put_in( put_in(

View file

@ -278,21 +278,27 @@ defmodule Pleroma.Web.PleromaAPI.UtilController do
end end
def uploaded_media( def uploaded_media(
%{assigns: %{user: user}, private: %{open_api_spex: %{params: %{ offset: offset }}}} = conn, %{assigns: %{user: user}, private: %{open_api_spex: %{params: %{offset: offset}}}} = conn,
_ _
) do ) do
result = result =
Pleroma.Upload.list_uploaded_by(user, offset, 50) Pleroma.Upload.list_uploaded_by(user, offset, 50)
|> Enum.map( |> Enum.map(fn %Pleroma.Object{
fn %Pleroma.Object{ id: id, data: %{ "url" => [ mediaUrl ], "name" => name }, inserted_at: uploaded } -> id: id,
%{ data: %{"url" => [mediaUrl], "name" => name},
uploaded: uploaded, attachment: %{ inserted_at: uploaded
description: name, id: to_string(id), url: mediaUrl["href"], pleroma: %{ mime_type: mediaUrl["mediaType"] }, type: "unknown" } ->
} %{
uploaded: uploaded,
attachment: %{
description: name,
id: to_string(id),
url: mediaUrl["href"],
pleroma: %{mime_type: mediaUrl["mediaType"]},
type: "unknown"
} }
end }
) end)
json(conn, result) json(conn, result)
end end

View file

@ -17,7 +17,6 @@ defmodule Pleroma.Workers.AttachmentsCleanupWorker do
"object" => %{"data" => %{"attachment" => [_ | _] = attachments, "actor" => actor}} "object" => %{"data" => %{"attachment" => [_ | _] = attachments, "actor" => actor}}
} }
}) do }) do
if Pleroma.Config.get([:instance, :cleanup_attachments], false) do if Pleroma.Config.get([:instance, :cleanup_attachments], false) do
attachments attachments
|> Enum.flat_map(fn item -> Enum.map(item["url"], & &1["href"]) end) |> Enum.flat_map(fn item -> Enum.map(item["url"], & &1["href"]) end)
@ -33,12 +32,12 @@ defmodule Pleroma.Workers.AttachmentsCleanupWorker do
def perform(%Job{ def perform(%Job{
args: %{ args: %{
"op" => "cleanup_attachments", "op" => "cleanup_attachments",
"object" => %{"data" => %{ "type" => "Document", "url" => [%{"href" => href}]}} = object_data "object" =>
%{"data" => %{"type" => "Document", "url" => [%{"href" => href}]}} = object_data
} }
}) do }) do
if Pleroma.Config.get([:instance, :cleanup_attachments], false) do if Pleroma.Config.get([:instance, :cleanup_attachments], false) do
clean_href = %URI{ URI.parse(href) | query: nil } |> URI.to_string() clean_href = %URI{URI.parse(href) | query: nil} |> URI.to_string()
same_attachment_count = Pleroma.Upload.list_duplicate_attachments(object_data) same_attachment_count = Pleroma.Upload.list_duplicate_attachments(object_data)
@ -48,17 +47,17 @@ defmodule Pleroma.Workers.AttachmentsCleanupWorker do
# No posts with this attachment, either # No posts with this attachment, either
if attachment_post_ids == [] do if attachment_post_ids == [] do
base_url = base_url =
String.trim_trailing( String.trim_trailing(
Pleroma.Upload.base_url(), Pleroma.Upload.base_url(),
"/" "/"
) )
uploader = Pleroma.Config.get([Pleroma.Upload, :uploader]) uploader = Pleroma.Config.get([Pleroma.Upload, :uploader])
clean_href clean_href
|> String.trim_leading("#{base_url}") |> String.trim_leading("#{base_url}")
|> uploader.delete_file() |> uploader.delete_file()
end end
end end
end end
@ -97,8 +96,6 @@ defmodule Pleroma.Workers.AttachmentsCleanupWorker do
# we should delete 1 object for any given attachment, but don't delete # we should delete 1 object for any given attachment, but don't delete
# files if there are more than 1 object for it # files if there are more than 1 object for it
defp filter_objects(objects) do defp filter_objects(objects) do
IO.inspect(objects)
Enum.reduce(objects, {[], []}, fn {href, %{id: id, count: count}}, {ids, hrefs} -> Enum.reduce(objects, {[], []}, fn {href, %{id: id, count: count}}, {ids, hrefs} ->
with 1 <- count do with 1 <- count do
{ids ++ [id], hrefs ++ [href]} {ids ++ [id], hrefs ++ [href]}