Work-in-progress media management
Adds a way to list media files uploaded by the user and delete them. Ideally needs to at least check if the attachment is used somewhere before actually deleting it, still.
This commit is contained in:
parent
d2b89f5723
commit
e0dce797ad
8 changed files with 188 additions and 3 deletions
|
|
@ -34,9 +34,15 @@ defmodule Pleroma.Upload do
|
|||
|
||||
"""
|
||||
alias Ecto.UUID
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.Object
|
||||
alias Pleroma.Maps
|
||||
alias Pleroma.Pagination
|
||||
alias Pleroma.Utils.URIEncoding
|
||||
alias Pleroma.Web.ActivityPub.Utils
|
||||
|
||||
import Ecto.Query
|
||||
|
||||
require Logger
|
||||
|
||||
@type source ::
|
||||
|
|
@ -301,4 +307,38 @@ defmodule Pleroma.Upload do
|
|||
public_endpoint || upload_base_url
|
||||
end
|
||||
end
|
||||
|
||||
def list_duplicate_attachments(%{"id" => id, "url" => [%{"href" => href}]}) do
|
||||
clean_href = %URI{ URI.parse(href) | query: nil } |> URI.to_string()
|
||||
|
||||
same_attachment_count =
|
||||
from(
|
||||
o in Object,
|
||||
where: fragment("STARTS_WITH((?)->'url'->0->>'href', (?))", o.data, ^clean_href) and fragment("(?)->>'id' <> (?)", o.data, ^id),
|
||||
) |> Repo.all()
|
||||
end
|
||||
|
||||
def list_objects_with_attachment(href) do
|
||||
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
|
||||
# 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
|
||||
Repo.query!(
|
||||
"""
|
||||
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')
|
||||
WHERE STARTS_WITH(json_attachments->'url'->0->>'href', $1)
|
||||
""",
|
||||
[clean_href]
|
||||
).rows |> Enum.map(&List.first(&1))
|
||||
end
|
||||
|
||||
def list_uploaded_by(user, offset, limit) do
|
||||
from(obj in Object,
|
||||
where: 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)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ defmodule Pleroma.Web.ApiSpec.MediaOperation do
|
|||
alias Pleroma.Web.ApiSpec.Helpers
|
||||
alias Pleroma.Web.ApiSpec.Schemas.ApiError
|
||||
alias Pleroma.Web.ApiSpec.Schemas.Attachment
|
||||
alias Pleroma.Web.ApiSpec.Schemas.BooleanLike
|
||||
|
||||
def open_api_operation(action) do
|
||||
operation = String.to_existing_atom("#{action}_operation")
|
||||
|
|
@ -73,6 +74,20 @@ defmodule Pleroma.Web.ApiSpec.MediaOperation do
|
|||
}
|
||||
end
|
||||
|
||||
def delete_operation do
|
||||
%Operation{
|
||||
tags: ["Media attachments"],
|
||||
summary: "Delete attachment",
|
||||
description: "Deletes an attachment.",
|
||||
operationId: "MediaController.delete",
|
||||
security: [%{"oAuth" => ["write:media"]}],
|
||||
parameters: [id_param()],
|
||||
responses: %{
|
||||
200 => Operation.response("Media", "application/json", Attachment),
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
defp update_request do
|
||||
%Schema{
|
||||
title: "MediaUpdateRequest",
|
||||
|
|
@ -101,7 +116,15 @@ defmodule Pleroma.Web.ApiSpec.MediaOperation do
|
|||
tags: ["Media attachments"],
|
||||
summary: "Attachment",
|
||||
operationId: "MediaController.show",
|
||||
parameters: [id_param()],
|
||||
parameters: [
|
||||
id_param(),
|
||||
Operation.parameter(
|
||||
:attachment_usage,
|
||||
:query,
|
||||
%Schema{type: :boolean, default: false},
|
||||
"Whether to include known usages of attachment"
|
||||
)
|
||||
],
|
||||
security: [%{"oAuth" => ["read:media"]}],
|
||||
responses: %{
|
||||
200 => Operation.response("Media", "application/json", Attachment),
|
||||
|
|
|
|||
|
|
@ -393,4 +393,28 @@ defmodule Pleroma.Web.ApiSpec.PleromaUtilOperation do
|
|||
}
|
||||
}
|
||||
end
|
||||
|
||||
def uploaded_media_operation do
|
||||
%Operation{
|
||||
tags: ["Media"],
|
||||
summary: "List uploaded media",
|
||||
security: [%{"oAuth" => ["read:accounts"]}],
|
||||
operationId: "PleromaAPI.UtilController.uploaded_media",
|
||||
responses: %{
|
||||
200 =>
|
||||
Operation.response("Success", "application/json", %Schema{
|
||||
type: :object,
|
||||
properties: %{
|
||||
aliases: %Schema{
|
||||
type: :array,
|
||||
items: %Schema{type: :string},
|
||||
example: ["foo@example.org"]
|
||||
}
|
||||
}
|
||||
}),
|
||||
400 => Operation.response("Error", "application/json", ApiError),
|
||||
403 => Operation.response("Error", "application/json", ApiError)
|
||||
}
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ defmodule Pleroma.Web.MastodonAPI.MediaController do
|
|||
|
||||
alias Pleroma.Object
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||
alias Pleroma.Web.Plugs.OAuthScopesPlug
|
||||
|
||||
|
|
@ -78,13 +79,51 @@ defmodule Pleroma.Web.MastodonAPI.MediaController do
|
|||
end
|
||||
end
|
||||
|
||||
def delete(
|
||||
%{
|
||||
assigns: %{user: user},
|
||||
private: %{open_api_spex: %{params: %{id: id}}}
|
||||
} = conn,
|
||||
_
|
||||
) do
|
||||
with %Object{} = object <- Object.get_by_id(id),
|
||||
:ok <- Object.authorize_access(object, user),
|
||||
{:ok, _, _} <- Object.delete(object) do
|
||||
|
||||
conn
|
||||
|> put_resp_header("content-type", "text/plain")
|
||||
|> send_resp(200, "")
|
||||
end
|
||||
end
|
||||
|
||||
def update(conn, data), do: show(conn, data)
|
||||
|
||||
@doc "GET /api/v1/media/:id"
|
||||
def show(%{assigns: %{user: user}, private: %{open_api_spex: %{params: %{id: id}}}} = 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),
|
||||
:ok <- Object.authorize_access(object, user) do
|
||||
|
||||
attachment_data = Map.put(data, "id", object_id)
|
||||
attachment_data =
|
||||
if attachment_usage do
|
||||
href = data["url"] |> List.first |> Map.get("href")
|
||||
used_in_objects = Pleroma.Upload.list_objects_with_attachment(href)
|
||||
|
||||
attachment_duplicates =
|
||||
Pleroma.Upload.list_duplicate_attachments(object.data)
|
||||
|> Enum.group_by(& if &1.data["actor"] == user.ap_id, do: :own, else: :other)
|
||||
|> Map.new(fn {key, val} -> {key, length(val)} end)
|
||||
|
||||
put_in(
|
||||
attachment_data["usage"],
|
||||
%{
|
||||
objects: used_in_objects,
|
||||
duplicates: attachment_duplicates
|
||||
}
|
||||
)
|
||||
else
|
||||
attachment_data
|
||||
end
|
||||
|
||||
render(conn, "attachment.json", %{attachment: attachment_data})
|
||||
end
|
||||
|
|
|
|||
|
|
@ -658,6 +658,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
|
|||
pleroma =
|
||||
%{mime_type: media_type}
|
||||
|> Maps.put_if_present(:name, name)
|
||||
|> Maps.put_if_present(:usage, attachment["usage"])
|
||||
|
||||
%{
|
||||
id: attachment_id,
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ defmodule Pleroma.Web.PleromaAPI.UtilController do
|
|||
alias Pleroma.Emoji
|
||||
alias Pleroma.Healthcheck
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Pagination
|
||||
alias Pleroma.Utils.URIEncoding
|
||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||
alias Pleroma.Web.Auth.WrapperAuthenticator, as: Authenticator
|
||||
|
|
@ -38,7 +39,8 @@ defmodule Pleroma.Web.PleromaAPI.UtilController do
|
|||
OAuthScopesPlug,
|
||||
%{scopes: ["read:accounts"]}
|
||||
when action in [
|
||||
:list_aliases
|
||||
:list_aliases,
|
||||
:uploaded_media
|
||||
]
|
||||
)
|
||||
|
||||
|
|
@ -274,4 +276,19 @@ defmodule Pleroma.Web.PleromaAPI.UtilController do
|
|||
|> put_status(:service_unavailable)
|
||||
|> json(info)
|
||||
end
|
||||
|
||||
def uploaded_media(
|
||||
%{assigns: %{user: user}} = conn,
|
||||
_
|
||||
) do
|
||||
|
||||
result =
|
||||
Pleroma.Upload.list_uploaded_by(user, 0, 50)
|
||||
|> Enum.map(
|
||||
fn %Pleroma.Object{ id: id, data: %{ "url" => [ mediaUrl ], "name" => name }, inserted_at: uploaded } ->
|
||||
Map.merge(%{ id: id, uploaded: uploaded, name: name }, mediaUrl) |> Map.drop(["type"]) end
|
||||
)
|
||||
|
||||
json(conn, result)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -583,6 +583,8 @@ defmodule Pleroma.Web.Router do
|
|||
put("/aliases", UtilController, :add_alias)
|
||||
get("/aliases", UtilController, :list_aliases)
|
||||
delete("/aliases", UtilController, :delete_alias)
|
||||
|
||||
get("/uploaded_media", UtilController, :uploaded_media)
|
||||
end
|
||||
|
||||
scope "/api/pleroma", Pleroma.Web.PleromaAPI do
|
||||
|
|
@ -803,6 +805,7 @@ defmodule Pleroma.Web.Router do
|
|||
post("/media", MediaController, :create)
|
||||
get("/media/:id", MediaController, :show)
|
||||
put("/media/:id", MediaController, :update)
|
||||
delete("/media/:id", MediaController, :delete)
|
||||
|
||||
get("/notifications", NotificationController, :index)
|
||||
get("/notifications/:id", NotificationController, :show)
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ defmodule Pleroma.Workers.AttachmentsCleanupWorker do
|
|||
"object" => %{"data" => %{"attachment" => [_ | _] = attachments, "actor" => actor}}
|
||||
}
|
||||
}) do
|
||||
|
||||
if Pleroma.Config.get([:instance, :cleanup_attachments], false) do
|
||||
attachments
|
||||
|> Enum.flat_map(fn item -> Enum.map(item["url"], & &1["href"]) end)
|
||||
|
|
@ -29,6 +30,41 @@ defmodule Pleroma.Workers.AttachmentsCleanupWorker do
|
|||
{:ok, :success}
|
||||
end
|
||||
|
||||
def perform(%Job{
|
||||
args: %{
|
||||
"op" => "cleanup_attachments",
|
||||
"object" => %{"data" => %{ "type" => "Document", "url" => [%{"href" => href}]}} = object_data
|
||||
}
|
||||
}) do
|
||||
|
||||
if Pleroma.Config.get([:instance, :cleanup_attachments], false) do
|
||||
clean_href = %URI{ URI.parse(href) | query: nil } |> URI.to_string()
|
||||
|
||||
same_attachment_count = Pleroma.Upload.list_duplicate_attachments(object_data)
|
||||
|
||||
# No copies of this attachment in the database
|
||||
if same_attachment_count == 0 do
|
||||
attachment_post_ids = Pleroma.Upload.list_objects_with_attachment(clean_href)
|
||||
# No posts with this attachment, either
|
||||
if attachment_post_ids == [] do
|
||||
base_url =
|
||||
String.trim_trailing(
|
||||
Pleroma.Upload.base_url(),
|
||||
"/"
|
||||
)
|
||||
|
||||
uploader = Pleroma.Config.get([Pleroma.Upload, :uploader])
|
||||
|
||||
clean_href
|
||||
|> String.trim_leading("#{base_url}")
|
||||
|> uploader.delete_file()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
{:ok, :success}
|
||||
end
|
||||
|
||||
def perform(%Job{args: %{"op" => "cleanup_attachments", "object" => _object}}), do: {:ok, :skip}
|
||||
|
||||
@impl true
|
||||
|
|
@ -61,6 +97,8 @@ defmodule Pleroma.Workers.AttachmentsCleanupWorker do
|
|||
# we should delete 1 object for any given attachment, but don't delete
|
||||
# files if there are more than 1 object for it
|
||||
defp filter_objects(objects) do
|
||||
IO.inspect(objects)
|
||||
|
||||
Enum.reduce(objects, {[], []}, fn {href, %{id: id, count: count}}, {ids, hrefs} ->
|
||||
with 1 <- count do
|
||||
{ids ++ [id], hrefs ++ [href]}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue