Extract poll actions from MastodonAPIController to PollController

This commit is contained in:
Egor Kislitsyn 2019-10-01 11:44:34 +07:00
commit 36a34c36fe
No known key found for this signature in database
GPG key ID: 1B49CB15B71E7805
11 changed files with 454 additions and 419 deletions

View file

@ -75,4 +75,16 @@ defmodule Pleroma.Web.ControllerHelper do
nil -> Pleroma.Web.MastodonAPI.FallbackController.call(conn, {:error, :not_found}) |> halt()
end
end
def try_render(conn, target, params)
when is_binary(target) do
case render(conn, target, params) do
nil -> render_error(conn, :not_implemented, "Can't display this activity")
res -> res
end
end
def try_render(conn, _, _) do
render_error(conn, :not_implemented, "Can't display this activity")
end
end

View file

@ -7,7 +7,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2]
alias Pleroma.Activity
alias Pleroma.Bookmark
alias Pleroma.Config
alias Pleroma.HTTP
@ -19,7 +18,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
alias Pleroma.User
alias Pleroma.Web
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Visibility
alias Pleroma.Web.CommonAPI
alias Pleroma.Web.MastodonAPI.AccountView
alias Pleroma.Web.MastodonAPI.AppView
@ -117,56 +115,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
json(conn, mastodon_emoji)
end
def get_poll(%{assigns: %{user: user}} = conn, %{"id" => id}) do
with %Object{} = object <- Object.get_by_id_and_maybe_refetch(id, interval: 60),
%Activity{} = activity <- Activity.get_create_by_object_ap_id(object.data["id"]),
true <- Visibility.visible_for_user?(activity, user) do
conn
|> put_view(StatusView)
|> try_render("poll.json", %{object: object, for: user})
else
error when is_nil(error) or error == false ->
render_error(conn, :not_found, "Record not found")
end
end
defp get_cached_vote_or_vote(user, object, choices) do
idempotency_key = "polls:#{user.id}:#{object.data["id"]}"
{_, res} =
Cachex.fetch(:idempotency_cache, idempotency_key, fn _ ->
case CommonAPI.vote(user, object, choices) do
{:error, _message} = res -> {:ignore, res}
res -> {:commit, res}
end
end)
res
end
def poll_vote(%{assigns: %{user: user}} = conn, %{"id" => id, "choices" => choices}) do
with %Object{} = object <- Object.get_by_id(id),
true <- object.data["type"] == "Question",
%Activity{} = activity <- Activity.get_create_by_object_ap_id(object.data["id"]),
true <- Visibility.visible_for_user?(activity, user),
{:ok, _activities, object} <- get_cached_vote_or_vote(user, object, choices) do
conn
|> put_view(StatusView)
|> try_render("poll.json", %{object: object, for: user})
else
nil ->
render_error(conn, :not_found, "Record not found")
false ->
render_error(conn, :not_found, "Record not found")
{:error, message} ->
conn
|> put_status(:unprocessable_entity)
|> json(%{error: message})
end
end
def update_media(
%{assigns: %{user: user}} = conn,
%{"id" => id, "description" => description} = _
@ -511,18 +459,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
end
end
def try_render(conn, target, params)
when is_binary(target) do
case render(conn, target, params) do
nil -> render_error(conn, :not_implemented, "Can't display this activity")
res -> res
end
end
def try_render(conn, _, _) do
render_error(conn, :not_implemented, "Can't display this activity")
end
defp present?(nil), do: false
defp present?(false), do: false
defp present?(_), do: true

View file

@ -0,0 +1,53 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.MastodonAPI.PollController do
use Pleroma.Web, :controller
import Pleroma.Web.ControllerHelper, only: [try_render: 3, json_response: 3]
alias Pleroma.Activity
alias Pleroma.Object
alias Pleroma.Web.ActivityPub.Visibility
alias Pleroma.Web.CommonAPI
action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
@doc "GET /api/v1/polls/:id"
def show(%{assigns: %{user: user}} = conn, %{"id" => id}) do
with %Object{} = object <- Object.get_by_id_and_maybe_refetch(id, interval: 60),
%Activity{} = activity <- Activity.get_create_by_object_ap_id(object.data["id"]),
true <- Visibility.visible_for_user?(activity, user) do
try_render(conn, "show.json", %{object: object, for: user})
else
error when is_nil(error) or error == false ->
render_error(conn, :not_found, "Record not found")
end
end
@doc "POST /api/v1/polls/:id/votes"
def vote(%{assigns: %{user: user}} = conn, %{"id" => id, "choices" => choices}) do
with %Object{data: %{"type" => "Question"}} = object <- Object.get_by_id(id),
%Activity{} = activity <- Activity.get_create_by_object_ap_id(object.data["id"]),
true <- Visibility.visible_for_user?(activity, user),
{:ok, _activities, object} <- get_cached_vote_or_vote(user, object, choices) do
try_render(conn, "show.json", %{object: object, for: user})
else
nil -> render_error(conn, :not_found, "Record not found")
false -> render_error(conn, :not_found, "Record not found")
{:error, message} -> json_response(conn, :unprocessable_entity, %{error: message})
end
end
defp get_cached_vote_or_vote(user, object, choices) do
idempotency_key = "polls:#{user.id}:#{object.data["id"]}"
Cachex.fetch!(:idempotency_cache, idempotency_key, fn ->
case CommonAPI.vote(user, object, choices) do
{:error, _message} = res -> {:ignore, res}
res -> {:commit, res}
end
end)
end
end

View file

@ -5,7 +5,7 @@
defmodule Pleroma.Web.MastodonAPI.StatusController do
use Pleroma.Web, :controller
import Pleroma.Web.MastodonAPI.MastodonAPIController, only: [try_render: 3]
import Pleroma.Web.ControllerHelper, only: [try_render: 3]
require Ecto.Query

View file

@ -0,0 +1,74 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.MastodonAPI.PollView do
use Pleroma.Web, :view
alias Pleroma.HTML
alias Pleroma.Web.CommonAPI.Utils
def render("show.json", %{object: object, multiple: multiple, options: options} = params) do
{end_time, expired} = end_time_and_expired(object)
{options, votes_count} = options_and_votes_count(options)
%{
# Mastodon uses separate ids for polls, but an object can't have
# more than one poll embedded so object id is fine
id: to_string(object.id),
expires_at: end_time,
expired: expired,
multiple: multiple,
votes_count: votes_count,
options: options,
voted: voted?(params),
emojis: Pleroma.Web.MastodonAPI.StatusView.build_emojis(object.data["emoji"])
}
end
def render("show.json", %{object: object} = params) do
case object.data do
%{"anyOf" => options} when is_list(options) ->
render(__MODULE__, "show.json", Map.merge(params, %{multiple: true, options: options}))
%{"oneOf" => options} when is_list(options) ->
render(__MODULE__, "show.json", Map.merge(params, %{multiple: false, options: options}))
_ ->
nil
end
end
defp end_time_and_expired(object) do
case object.data["closed"] || object.data["endTime"] do
end_time when is_binary(end_time) ->
end_time = NaiveDateTime.from_iso8601!(end_time)
expired = NaiveDateTime.compare(end_time, NaiveDateTime.utc_now()) == :lt
{Utils.to_masto_date(end_time), expired}
_ ->
{nil, false}
end
end
defp options_and_votes_count(options) do
Enum.map_reduce(options, 0, fn %{"name" => name} = option, count ->
current_count = option["replies"]["totalItems"] || 0
{%{
title: HTML.strip_tags(name),
votes_count: current_count
}, current_count + count}
end)
end
defp voted?(%{object: object} = opts) do
if opts[:for] do
existing_votes = Pleroma.Web.ActivityPub.Utils.get_existing_votes(opts[:for].ap_id, object)
existing_votes != [] or opts[:for].ap_id == object.data["actor"]
else
false
end
end
end

View file

@ -18,6 +18,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
alias Pleroma.Web.CommonAPI
alias Pleroma.Web.CommonAPI.Utils
alias Pleroma.Web.MastodonAPI.AccountView
alias Pleroma.Web.MastodonAPI.PollView
alias Pleroma.Web.MastodonAPI.StatusView
alias Pleroma.Web.MediaProxy
@ -277,7 +278,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
spoiler_text: summary_html,
visibility: get_visibility(object),
media_attachments: attachments,
poll: render("poll.json", %{object: object, for: opts[:for]}),
poll: render(PollView, "show.json", object: object, for: opts[:for]),
mentions: mentions,
tags: build_tags(tags),
application: %{
@ -389,75 +390,6 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
safe_render_many(opts.activities, StatusView, "listen.json", opts)
end
def render("poll.json", %{object: object} = opts) do
{multiple, options} =
case object.data do
%{"anyOf" => options} when is_list(options) -> {true, options}
%{"oneOf" => options} when is_list(options) -> {false, options}
_ -> {nil, nil}
end
if options do
{end_time, expired} =
case object.data["closed"] || object.data["endTime"] do
end_time when is_binary(end_time) ->
end_time =
(object.data["closed"] || object.data["endTime"])
|> NaiveDateTime.from_iso8601!()
expired =
end_time
|> NaiveDateTime.compare(NaiveDateTime.utc_now())
|> case do
:lt -> true
_ -> false
end
end_time = Utils.to_masto_date(end_time)
{end_time, expired}
_ ->
{nil, false}
end
voted =
if opts[:for] do
existing_votes =
Pleroma.Web.ActivityPub.Utils.get_existing_votes(opts[:for].ap_id, object)
existing_votes != [] or opts[:for].ap_id == object.data["actor"]
else
false
end
{options, votes_count} =
Enum.map_reduce(options, 0, fn %{"name" => name} = option, count ->
current_count = option["replies"]["totalItems"] || 0
{%{
title: HTML.strip_tags(name),
votes_count: current_count
}, current_count + count}
end)
%{
# Mastodon uses separate ids for polls, but an object can't have
# more than one poll embedded so object id is fine
id: to_string(object.id),
expires_at: end_time,
expired: expired,
multiple: multiple,
votes_count: votes_count,
options: options,
voted: voted,
emojis: build_emojis(object.data["emoji"])
}
else
nil
end
end
def render("context.json", %{activity: activity, activities: activities, user: user}) do
%{ancestors: ancestors, descendants: descendants} =
activities

View file

@ -403,7 +403,7 @@ defmodule Pleroma.Web.Router do
put("/scheduled_statuses/:id", ScheduledActivityController, :update)
delete("/scheduled_statuses/:id", ScheduledActivityController, :delete)
post("/polls/:id/votes", MastodonAPIController, :poll_vote)
post("/polls/:id/votes", PollController, :vote)
post("/media", MastodonAPIController, :upload)
put("/media/:id", MastodonAPIController, :update_media)
@ -488,7 +488,7 @@ defmodule Pleroma.Web.Router do
get("/statuses/:id", StatusController, :show)
get("/statuses/:id/context", StatusController, :context)
get("/polls/:id", MastodonAPIController, :get_poll)
get("/polls/:id", PollController, :show)
get("/accounts/:id/statuses", AccountController, :statuses)
get("/accounts/:id/followers", AccountController, :followers)