From 193c17cea519f5b12f13122fc6d612e1d9f30e62 Mon Sep 17 00:00:00 2001 From: Roman Chvanikov Date: Mon, 7 Dec 2020 22:37:56 +0300 Subject: [PATCH] wip --- lib/pleroma/media.ex | 88 ++++++++++++++++++++++ lib/pleroma/web/activity_pub/activity_pub.ex | 18 ++++- lib/pleroma/web/common_api/utils.ex | 10 ++- .../mastodon_api/controllers/media_controller.ex | 42 ++++++++++- lib/pleroma/web/mastodon_api/views/status_view.ex | 31 +++++++- lib/pleroma/web/plugs/uploaded_media.ex | 2 + .../migrations/20201125170429_create_media.exs | 19 +++++ 7 files changed, 197 insertions(+), 13 deletions(-) create mode 100644 lib/pleroma/media.ex create mode 100644 priv/repo/migrations/20201125170429_create_media.exs diff --git a/lib/pleroma/media.ex b/lib/pleroma/media.ex new file mode 100644 index 000000000..ed9ed6ed0 --- /dev/null +++ b/lib/pleroma/media.ex @@ -0,0 +1,88 @@ +defmodule Pleroma.Media do + use Ecto.Schema + + import Ecto.Changeset + + alias Pleroma.Media + alias Pleroma.Repo + alias Pleroma.User + + @derive {Jason.Encoder, + only: [:href, :type, :media_type, :name, :blurhash, :meta, :object_id, :user_id]} + + @type t() :: %__MODULE__{} + + schema "media" do + field(:href, :string) + field(:type, :string) + field(:media_type, :string) + field(:name, :string) + field(:blurhash, :string) + field(:meta, :map) + + belongs_to(:object, Pleroma.Object) + belongs_to(:user, Pleroma.User, type: FlakeId.Ecto.CompatType) + + timestamps() + end + + def create_from_object_data(%{"url" => [url]} = data, %{user: user} = _opts) do + %Media{} + |> changeset(%{ + href: url["href"], + type: url["type"], + media_type: url["mediaType"], + name: data["name"], + blurhash: nil, + meta: %{}, + user_id: user.id + }) + |> Repo.insert() + end + + def get_by_id(nil), do: nil + def get_by_id(id), do: Repo.get(Media, id) + + @spec authorize_access(Media.t(), User.t()) :: :ok | {:error, :forbidden} + def authorize_access(%Media{user_id: user_id}, %User{id: user_id}), do: :ok + def authorize_access(%Media{user_id: user_id}, %User{id: user_id}), do: {:error, :forbidden} + + def update(%Media{} = media, attrs \\ %{}) do + media + |> changeset(attrs) + |> Repo.update() + end + + def from_object(%Pleroma.Object{data: data}, %{user: user}) do + %Media{href: data["href"], user_id: user.id} + end + + def insert(%Media{} = media) do + media + |> changeset() + |> Repo.insert() + end + + def changeset(struct, params \\ %{}) do + struct + |> cast(params, [:href, :type, :media_type, :name, :blurhash, :meta, :user_id, :object_id]) + |> validate_required([:href, :type, :media_type]) + end + + def to_object_form(%Media{} = media) do + %{ + "id" => media.id, + "url" => [ + %{ + "href" => media.href, + "type" => media.type, + "mediaType" => media.media_type + } + ], + "name" => media.name, + "type" => "Document", + "blurhash" => media.blurhash, + "mediaType" => media.media_type + } + end +end diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index 1c91bc074..3a8b17baa 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -119,6 +119,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do {:fake, false, map, recipients} <- {:fake, fake, map, recipients}, {:containment, :ok} <- {:containment, Containment.contain_child(map)}, {:ok, map, object} <- insert_full_object(map), + :ok <- maybe_update_media(object), {:ok, activity} <- insert_activity_with_expiration(map, local, recipients) do # Splice in the child object if we have one. activity = Maps.put_if_present(activity, :object, object) @@ -161,6 +162,16 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do end end + defp maybe_update_media(%Object{data: %{"attachment" => []}}), do: :ok + + defp maybe_update_media(%Object{id: id, data: %{"attachment" => attachments}}) do + Enum.each(attachments, fn %{"id" => media_id} -> + media_id + |> Pleroma.Media.get_by_id() + |> Pleroma.Media.update(%{object_id: id}) + end) + end + defp insert_activity_with_expiration(data, local, recipients) do struct = %Activity{ data: data, @@ -1190,10 +1201,9 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do @spec upload(Upload.source(), keyword()) :: {:ok, Object.t()} | {:error, any()} def upload(file, opts \\ []) do - with {:ok, data} <- Upload.store(file, opts) do - obj_data = Maps.put_if_present(data, "actor", opts[:actor]) - - Repo.insert(%Object{data: obj_data}) + with {:ok, data} <- Upload.store(file, opts), + %User{} <- opts[:user] do + Pleroma.Media.create_from_object_data(data, %{user: opts[:user]}) end end diff --git a/lib/pleroma/web/common_api/utils.ex b/lib/pleroma/web/common_api/utils.ex index 1c74ea787..7b814826a 100644 --- a/lib/pleroma/web/common_api/utils.ex +++ b/lib/pleroma/web/common_api/utils.ex @@ -11,6 +11,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do alias Pleroma.Config alias Pleroma.Conversation.Participation alias Pleroma.Formatter + alias Pleroma.Media alias Pleroma.Object alias Pleroma.Repo alias Pleroma.User @@ -37,8 +38,8 @@ defmodule Pleroma.Web.CommonAPI.Utils do def attachments_from_ids_no_descs(ids) do Enum.map(ids, fn media_id -> - case Repo.get(Object, media_id) do - %Object{data: data} -> data + case Repo.get(Media, media_id) do + %Media{} = media -> Media.to_object_form(media) _ -> nil end end) @@ -51,8 +52,9 @@ defmodule Pleroma.Web.CommonAPI.Utils do {_, descs} = Jason.decode(descs_str) Enum.map(ids, fn media_id -> - with %Object{data: data} <- Repo.get(Object, media_id) do - Map.put(data, "name", descs[media_id]) + with %Media{} = media <- Repo.get(Media, media_id) do + %Media{media | name: descs[media_id]} + |> Media.to_object_form() end end) |> Enum.reject(&is_nil/1) diff --git a/lib/pleroma/web/mastodon_api/controllers/media_controller.ex b/lib/pleroma/web/mastodon_api/controllers/media_controller.ex index 161193134..b5283c2a3 100644 --- a/lib/pleroma/web/mastodon_api/controllers/media_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/media_controller.ex @@ -5,6 +5,7 @@ defmodule Pleroma.Web.MastodonAPI.MediaController do use Pleroma.Web, :controller + alias Pleroma.Media alias Pleroma.Object alias Pleroma.User alias Pleroma.Web.ActivityPub.ActivityPub @@ -22,6 +23,20 @@ defmodule Pleroma.Web.MastodonAPI.MediaController do @doc "POST /api/v1/media" def create(%{assigns: %{user: user}, body_params: %{file: file} = data} = conn, _) do + with {:ok, media} <- + ActivityPub.upload( + file, + user: user, + actor: User.ap_id(user), + description: Map.get(data, :description) + ) do + render(conn, "media.json", %{media: media}) + end + end + + def create(_conn, _data), do: {:error, :bad_request} + + def _create(%{assigns: %{user: user}, body_params: %{file: file} = data} = conn, _) do with {:ok, object} <- ActivityPub.upload( file, @@ -34,7 +49,7 @@ defmodule Pleroma.Web.MastodonAPI.MediaController do end end - def create(_conn, _data), do: {:error, :bad_request} + def _create(_conn, _data), do: {:error, :bad_request} @doc "POST /api/v2/media" def create2(%{assigns: %{user: user}, body_params: %{file: file} = data} = conn, _) do @@ -56,6 +71,18 @@ defmodule Pleroma.Web.MastodonAPI.MediaController do @doc "PUT /api/v1/media/:id" def update(%{assigns: %{user: user}, body_params: %{description: description}} = conn, %{id: id}) do + with %Media{} = media <- Media.get_by_id(id), + :ok <- Media.authorize_access(media, user), + {:ok, %Media{} = media} <- Media.update(media, %{"name" => description}) do + render(conn, "media.json", %{media: media}) + end + end + + def update(conn, data), do: show(conn, data) + + def _update(%{assigns: %{user: user}, body_params: %{description: description}} = conn, %{ + id: id + }) do with %Object{} = object <- Object.get_by_id(id), :ok <- Object.authorize_access(object, user), {:ok, %Object{data: data}} <- Object.update_data(object, %{"name" => description}) do @@ -65,10 +92,19 @@ defmodule Pleroma.Web.MastodonAPI.MediaController do end end - def update(conn, data), do: show(conn, data) + def _update(conn, data), do: show(conn, data) @doc "GET /api/v1/media/:id" def show(%{assigns: %{user: user}} = conn, %{id: id}) do + with %Pleroma.Media{} = media <- Pleroma.Media.get_by_id(id), + :ok <- Pleroma.Media.authorize_access(media, user) do + render(conn, "media.json", %{media: media}) + end + end + + def show(_conn, _data), do: {:error, :bad_request} + + def _show(%{assigns: %{user: user}} = conn, %{id: id}) 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) @@ -77,5 +113,5 @@ defmodule Pleroma.Web.MastodonAPI.MediaController do end end - def show(_conn, _data), do: {:error, :bad_request} + def _show(_conn, _data), do: {:error, :bad_request} end diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex index 2301e21cf..d1022f5a1 100644 --- a/lib/pleroma/web/mastodon_api/views/status_view.ex +++ b/lib/pleroma/web/mastodon_api/views/status_view.ex @@ -242,8 +242,9 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do true -> CommonAPI.thread_muted?(opts[:for], activity) end - attachment_data = object.data["attachment"] || [] - attachments = render_many(attachment_data, StatusView, "attachment.json", as: :attachment) + attachment_data = object.data["attachments"] || [] + # attachments = render_many(attachment_data, StatusView, "attachment.json", as: :attachment) + attachments = render_many(attachment_data, StatusView, "media.json", as: :media) created_at = Utils.to_masto_date(object.data["published"]) @@ -436,6 +437,32 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do } end + def render("media.json", %{media: media}) do + media_type = media.media_type || media.mime_type || "image" + href = MediaProxy.url(media.href) + href_preview = MediaProxy.preview_url(media.href) + + type = + cond do + String.contains?(media_type, "image") -> "image" + String.contains?(media_type, "video") -> "video" + String.contains?(media_type, "audio") -> "audio" + true -> "unknown" + end + + %{ + id: to_string(media.id), + url: href, + remote_url: href, + preview_url: href_preview, + text_url: href, + type: type, + description: media.name, + pleroma: %{mime_type: media_type}, + blurhash: media.blurhash + } + end + def render("context.json", %{activity: activity, activities: activities, user: user}) do %{ancestors: ancestors, descendants: descendants} = activities diff --git a/lib/pleroma/web/plugs/uploaded_media.ex b/lib/pleroma/web/plugs/uploaded_media.ex index 402a8bb34..e545b6b18 100644 --- a/lib/pleroma/web/plugs/uploaded_media.ex +++ b/lib/pleroma/web/plugs/uploaded_media.ex @@ -46,6 +46,8 @@ defmodule Pleroma.Web.Plugs.UploadedMedia do config = Pleroma.Config.get(Pleroma.Upload) + # https://pleroma.local/media/cf61935ec407b4df8fd3dcf58352948eb6231bdfe12fcbf5270e653c20da9860.jpeg + with uploader <- Keyword.fetch!(config, :uploader), proxy_remote = Keyword.get(config, :proxy_remote, false), {:ok, get_method} <- uploader.get_file(file), diff --git a/priv/repo/migrations/20201125170429_create_media.exs b/priv/repo/migrations/20201125170429_create_media.exs new file mode 100644 index 000000000..a1bd125c3 --- /dev/null +++ b/priv/repo/migrations/20201125170429_create_media.exs @@ -0,0 +1,19 @@ +defmodule Pleroma.Repo.Migrations.CreateMedia do + use Ecto.Migration + + def change do + create_if_not_exists table(:media) do + add(:href, :string, null: false) + add(:type, :string, null: false) + add(:media_type, :string, null: false) + add(:name, :string) + add(:blurhash, :string) + add(:meta, :map) + # TODO discuss delete_all option + add(:object_id, references(:objects, on_delete: :nothing), null: true) + add(:user_id, references(:users, type: :uuid, on_delete: :nothing), null: false) + + timestamps() + end + end +end