WIP: post editing #103

Closed
Ghost wants to merge 48 commits from (deleted):feat/editing into develop
7 changed files with 245 additions and 23 deletions
Showing only changes of commit bc54d06588 - Show all commits

View file

@ -425,4 +425,24 @@ def object_data_hashtags(%{"tag" => tags}) when is_list(tags) do
end end
def object_data_hashtags(_), do: [] def object_data_hashtags(_), do: []
def history_for(object) do
with history <- Map.get(object, "formerRepresentations"),
true <- is_map(history),
"OrderedCollection" <- Map.get(history, "type"),
true <- is_list(Map.get(history, "orderedItems")),
true <- is_integer(Map.get(history, "totalItems")) do
history
else
_ -> history_skeleton()
end
end
defp history_skeleton do
%{
"type" => "OrderedCollection",
"totalItems" => 0,
"orderedItems" => []
}
end
end end

View file

@ -415,26 +415,6 @@ defp handle_update_user(
{:ok, object, meta} {:ok, object, meta}
end end
defp history_for_object(object) do
with history <- Map.get(object, "formerRepresentations"),
true <- is_map(history),
"OrderedCollection" <- Map.get(history, "type"),
true <- is_list(Map.get(history, "orderedItems")),
true <- is_integer(Map.get(history, "totalItems")) do
history
else
_ -> history_skeleton()
end
end
defp history_skeleton do
%{
"type" => "OrderedCollection",
"totalItems" => 0,
"orderedItems" => []
}
end
@updatable_object_types ["Note", "Question"] @updatable_object_types ["Note", "Question"]
# We do not allow poll options to be changed, but the poll description can be. # We do not allow poll options to be changed, but the poll description can be.
@updatable_fields [ @updatable_fields [
@ -473,7 +453,7 @@ defp maybe_update_history(updated_object, orig_object_data, updated) do
else else
# Put edit history # Put edit history
# Note that we may have got the edit history by first fetching the object # Note that we may have got the edit history by first fetching the object
history = history_for_object(orig_object_data) history = Object.history_for(orig_object_data)
latest_history_item = latest_history_item =
orig_object_data orig_object_data

View file

@ -6,9 +6,13 @@ defmodule Pleroma.Web.ApiSpec.StatusOperation do
alias OpenApiSpex.Operation alias OpenApiSpex.Operation
alias OpenApiSpex.Schema alias OpenApiSpex.Schema
alias Pleroma.Web.ApiSpec.AccountOperation alias Pleroma.Web.ApiSpec.AccountOperation
alias Pleroma.Web.ApiSpec.Schemas.Account
alias Pleroma.Web.ApiSpec.Schemas.ApiError alias Pleroma.Web.ApiSpec.Schemas.ApiError
alias Pleroma.Web.ApiSpec.Schemas.Attachment
alias Pleroma.Web.ApiSpec.Schemas.BooleanLike alias Pleroma.Web.ApiSpec.Schemas.BooleanLike
alias Pleroma.Web.ApiSpec.Schemas.Emoji
alias Pleroma.Web.ApiSpec.Schemas.FlakeID alias Pleroma.Web.ApiSpec.Schemas.FlakeID
alias Pleroma.Web.ApiSpec.Schemas.Poll
alias Pleroma.Web.ApiSpec.Schemas.ScheduledStatus alias Pleroma.Web.ApiSpec.Schemas.ScheduledStatus
alias Pleroma.Web.ApiSpec.Schemas.Status alias Pleroma.Web.ApiSpec.Schemas.Status
alias Pleroma.Web.ApiSpec.Schemas.VisibilityScope alias Pleroma.Web.ApiSpec.Schemas.VisibilityScope
@ -406,6 +410,29 @@ def bookmarks_operation do
} }
end end
def show_history_operation do
%Operation{
tags: ["Retrieve status history"],
summary: "Status history",
description: "View history of a status",
operationId: "StatusController.show_history",
security: [%{"oAuth" => ["read:statuses"]}],
parameters: [
id_param()
],
responses: %{
200 => status_history_response(),
404 => Operation.response("Not Found", "application/json", ApiError)
}
}
end
def show_source_operation do
end
def update_operation do
end
def array_of_statuses do def array_of_statuses do
%Schema{type: :array, items: Status, example: [Status.schema().example]} %Schema{type: :array, items: Status, example: [Status.schema().example]}
end end
@ -556,6 +583,61 @@ defp status_response do
Operation.response("Status", "application/json", Status) Operation.response("Status", "application/json", Status)
end end
defp status_history_response do
Operation.response(
"Status History",
"application/json",
%Schema{
title: "Status history",
description: "Response schema for history of a status",
type: :array,
items: %Schema{
type: :object,
properties: %{
account: %Schema{
allOf: [Account],
description: "The account that authored this status"
},
content: %Schema{
type: :string,
format: :html,
description: "HTML-encoded status content"
},
sensitive: %Schema{
type: :boolean,
description: "Is this status marked as sensitive content?"
},
spoiler_text: %Schema{
type: :string,
description:
"Subject or summary line, below which status content is collapsed until expanded"
},
created_at: %Schema{
type: :string,
format: "date-time",
description: "The date when this status was created"
},
media_attachments: %Schema{
type: :array,
items: Attachment,
description: "Media that is attached to this status"
},
emojis: %Schema{
type: :array,
items: Emoji,
description: "Custom emoji to be used when rendering status content"
},
poll: %Schema{
allOf: [Poll],
nullable: true,
description: "The poll attached to the status"
}
}
}
}
)
end
defp context do defp context do
%Schema{ %Schema{
title: "StatusContext", title: "StatusContext",

View file

@ -37,7 +37,9 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
when action in [ when action in [
:index, :index,
:show, :show,
:context :context,
:show_history,
:show_source
] ]
) )
@ -48,7 +50,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
:create, :create,
:delete, :delete,
:reblog, :reblog,
:unreblog :unreblog,
:update
] ]
) )
@ -190,6 +193,29 @@ def create(%{assigns: %{user: _user}, body_params: %{media_ids: _} = params} = c
create(%Plug.Conn{conn | body_params: params}, %{}) create(%Plug.Conn{conn | body_params: params}, %{})
end end
@doc "GET /api/v1/statuses/:id/history"
def show_history(%{assigns: %{user: user}} = conn, %{id: id} = params) do
with %Activity{} = activity <- Activity.get_by_id_with_object(id),
true <- Visibility.visible_for_user?(activity, user) do
try_render(conn, "history.json",
activity: activity,
for: user,
with_direct_conversation_id: true,
with_muted: Map.get(params, :with_muted, false)
)
else
_ -> {:error, :not_found}
end
end
@doc "GET /api/v1/statuses/:id/source"
def show_source(%{assigns: %{user: _user}} = _conn, %{id: _id} = _params) do
end
@doc "PUT /api/v1/statuses/:id"
def update(%{assigns: %{user: _user}} = _conn, %{id: _id} = _params) do
end
@doc "GET /api/v1/statuses/:id" @doc "GET /api/v1/statuses/:id"
def show(%{assigns: %{user: user}} = conn, %{id: id} = params) do def show(%{assigns: %{user: user}} = conn, %{id: id} = params) do
with %Activity{} = activity <- Activity.get_by_id_with_object(id), with %Activity{} = activity <- Activity.get_by_id_with_object(id),

View file

@ -390,6 +390,71 @@ def render("show.json", _) do
nil nil
end end
def render("history.json", %{activity: %{data: %{"object" => _object}} = activity} = opts) do
object = Object.normalize(activity, fetch: false)
hashtags = Object.hashtags(object)
user = CommonAPI.get_user(activity.data["actor"])
past_history =
Object.history_for(object.data)
|> Map.get("orderedItems")
|> Enum.map(&Map.put(&1, "id", object.data["id"]))
|> Enum.map(&%Object{data: &1, id: object.id})
history = [object | past_history]
individual_opts =
opts
|> Map.put(:as, :object)
|> Map.put(:user, user)
|> Map.put(:hashtags, hashtags)
render_many(history, StatusView, "history_item.json", individual_opts)
end
def render(
"history_item.json",
%{activity: activity, user: user, object: object, hashtags: hashtags} = opts
) do
sensitive = object.data["sensitive"] || Enum.member?(hashtags, "nsfw")
attachment_data = object.data["attachment"] || []
attachments = render_many(attachment_data, StatusView, "attachment.json", as: :attachment)
created_at = Utils.to_masto_date(object.data["updated"] || object.data["published"])
content =
object
|> render_content()
content_html =
content
|> Activity.HTML.get_cached_scrubbed_html_for_activity(
User.html_filter_policy(opts[:for]),
activity,
"mastoapi:content"
)
summary = object.data["summary"] || ""
%{
account:
AccountView.render("show.json", %{
user: user,
for: opts[:for]
}),
content: content_html,
sensitive: sensitive,
spoiler_text: summary,
created_at: created_at,
media_attachments: attachments,
emojis: build_emojis(object.data["emoji"]),
poll: render(PollView, "show.json", object: object, for: opts[:for])
}
end
def render("card.json", %{rich_media: rich_media, page_url: page_url}) do def render("card.json", %{rich_media: rich_media, page_url: page_url}) do
page_url_data = URI.parse(page_url) page_url_data = URI.parse(page_url)

View file

@ -537,6 +537,9 @@ defmodule Pleroma.Web.Router do
get("/bookmarks", StatusController, :bookmarks) get("/bookmarks", StatusController, :bookmarks)
post("/statuses", StatusController, :create) post("/statuses", StatusController, :create)
get("/statuses/:id/history", StatusController, :show_history)
get("/statuses/:id/source", StatusController, :show_source)
put("/statuses/:id", StatusController, :update)
delete("/statuses/:id", StatusController, :delete) delete("/statuses/:id", StatusController, :delete)
post("/statuses/:id/reblog", StatusController, :reblog) post("/statuses/:id/reblog", StatusController, :reblog)
post("/statuses/:id/unreblog", StatusController, :unreblog) post("/statuses/:id/unreblog", StatusController, :unreblog)

View file

@ -2042,4 +2042,50 @@ test "posting a quote of a status that doesn't exist", %{conn: conn} do
|> json_response_and_validate_schema(422) |> json_response_and_validate_schema(422)
end end
end end
describe "get status history" do
setup do
oauth_access(["read:statuses"])
end
test "unedited post", %{conn: conn} do
activity = insert(:note_activity)
conn = get(conn, "/api/v1/statuses/#{activity.id}/history")
assert [_] = json_response_and_validate_schema(conn, 200)
end
test "edited post", %{conn: conn} do
note =
insert(
:note,
data: %{
"formerRepresentations" => %{
"type" => "OrderedCollection",
"orderedItems" => [
%{
"type" => "Note",
"content" => "mew mew 2",
"summary" => "title 2"
},
%{
"type" => "Note",
"content" => "mew mew 1",
"summary" => "title 1"
}
],
"totalItems" => 2
}
}
)
activity = insert(:note_activity, note: note)
conn = get(conn, "/api/v1/statuses/#{activity.id}/history")
assert [_, %{"spoiler_text" => "title 2"}, %{"spoiler_text" => "title 1"}] =
json_response_and_validate_schema(conn, 200)
end
end
end end