Implement mastodon api for showing edit history
This commit is contained in:
parent
8acfe95f3e
commit
c004eb0fa2
|
@ -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
|
||||||
|
|
|
@ -410,26 +410,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 [
|
||||||
|
@ -468,7 +448,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
|
||||||
|
|
|
@ -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
|
||||||
|
@ -434,6 +438,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
|
||||||
|
@ -579,6 +606,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",
|
||||||
|
|
|
@ -38,7 +38,9 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
|
||||||
:index,
|
:index,
|
||||||
:show,
|
:show,
|
||||||
:card,
|
:card,
|
||||||
:context
|
:context,
|
||||||
|
:show_history,
|
||||||
|
:show_source
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -49,7 +51,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
|
||||||
:create,
|
:create,
|
||||||
:delete,
|
:delete,
|
||||||
:reblog,
|
:reblog,
|
||||||
:unreblog
|
:unreblog,
|
||||||
|
:update
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -191,6 +194,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),
|
||||||
|
|
|
@ -384,6 +384,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)
|
||||||
|
|
||||||
|
|
|
@ -552,6 +552,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)
|
||||||
|
|
|
@ -1990,4 +1990,50 @@ test "show" do
|
||||||
} = result
|
} = result
|
||||||
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
|
||||||
|
|
Loading…
Reference in New Issue