Page MenuHomePhorge

No OneTemporary

Size
14 KB
Referenced Files
None
Subscribers
None
diff --git a/lib/pleroma/web/controller_helper.ex b/lib/pleroma/web/controller_helper.ex
index eecd1774f..0c85e79f9 100644
--- a/lib/pleroma/web/controller_helper.ex
+++ b/lib/pleroma/web/controller_helper.ex
@@ -1,151 +1,155 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ControllerHelper do
use Pleroma.Web, :controller
alias Pleroma.Pagination
alias Pleroma.Web.Utils.Params
def json_response(conn, status, _) when status in [204, :no_content] do
conn
|> put_resp_header("content-type", "application/json")
|> send_resp(status, "")
end
def json_response(conn, status, json) do
conn
|> put_status(status)
|> json(json)
end
@spec fetch_integer_param(map(), String.t() | atom(), integer() | nil) :: integer() | nil
def fetch_integer_param(params, name, default \\ nil) do
params
|> Map.get(name, default)
|> param_to_integer(default)
end
defp param_to_integer(val, _) when is_integer(val), do: val
defp param_to_integer(val, default) when is_binary(val) do
case Integer.parse(val) do
{res, _} -> res
_ -> default
end
end
defp param_to_integer(_, default), do: default
def add_link_headers(conn, entries, extra_params \\ %{})
def add_link_headers(%{assigns: %{skip_link_headers: true}} = conn, _entries, _extra_params),
do: conn
def add_link_headers(conn, entries, extra_params) do
case get_pagination_fields(conn, entries, extra_params) do
%{"next" => next_url, "prev" => prev_url} ->
put_resp_header(conn, "link", "<#{next_url}>; rel=\"next\", <#{prev_url}>; rel=\"prev\"")
_ ->
conn
end
end
# TODO: Only fetch the params from open_api_spex when everything is converted
@id_keys Pagination.page_keys() -- ["limit", "order"]
@id_key_atoms Enum.map(@id_keys, &String.to_atom/1)
+ @drop_id_params_key :drop_id_params
defp build_pagination_fields(conn, min_id, max_id, extra_params, order) do
+ drop_id_params? = Map.get(extra_params, @drop_id_params_key, false)
+ extra_params = Map.delete(extra_params, @drop_id_params_key)
+
path_param_keys =
conn.path_params
|> Map.keys()
|> Enum.flat_map(&[&1, String.to_existing_atom(&1)])
params =
if Map.has_key?(conn.private, :open_api_spex) do
get_in(conn, [Access.key(:private), Access.key(:open_api_spex), Access.key(:params)])
else
conn.params
end
|> Map.drop(path_param_keys)
|> Map.merge(extra_params)
- # OpenApiSpex casts params to atoms while uncast conn params are string-keyed. Drop both so
- # generated links replace pagination cursors instead of preserving stale request cursors.
- |> Map.drop(@id_keys ++ @id_key_atoms)
+ # Some OpenApiSpex routes cast cursor params to atoms. Keep the historical default for
+ # existing endpoints, but allow grouped notifications to opt out of stale request cursors.
+ |> Map.drop(@id_keys ++ if(drop_id_params?, do: @id_key_atoms, else: []))
{{next_id, nid}, {prev_id, pid}} =
if order == :desc,
do: {{:max_id, max_id}, {:min_id, min_id}},
else: {{:min_id, min_id}, {:max_id, max_id}}
id = Phoenix.Controller.current_url(conn)
base_id = %{URI.parse(id) | query: nil} |> URI.to_string()
%{
"next" => current_url(conn, Map.put(params, next_id, nid)),
"prev" => current_url(conn, Map.put(params, prev_id, pid)),
"id" => id,
"partOf" => base_id
}
end
defp get_first_last_pagination_id(entries) do
case List.last(entries) do
%{pagination_id: last_id} when not is_nil(last_id) ->
%{pagination_id: first_id} = List.first(entries)
{first_id, last_id}
%{id: last_id} ->
%{id: first_id} = List.first(entries)
{first_id, last_id}
_ ->
nil
end
end
def get_pagination_fields(conn, entries, extra_params \\ %{}, order \\ :desc)
def get_pagination_fields(conn, entries, extra_params, :desc) do
case get_first_last_pagination_id(entries) do
nil -> %{}
{min_id, max_id} -> build_pagination_fields(conn, min_id, max_id, extra_params, :desc)
end
end
def get_pagination_fields(conn, entries, extra_params, :asc) do
case get_first_last_pagination_id(entries) do
nil -> %{}
{max_id, min_id} -> build_pagination_fields(conn, min_id, max_id, extra_params, :asc)
end
end
def assign_account_by_id(%{private: %{open_api_spex: %{params: %{id: id}}}} = conn, _) do
case Pleroma.User.get_cached_by_id(id) do
%Pleroma.User{} = account -> assign(conn, :account, account)
nil -> Pleroma.Web.MastodonAPI.FallbackController.call(conn, {:error, :not_found}) |> halt()
end
end
def try_render(conn, target, params) when is_binary(target) do
render(conn, target, params)
end
def try_render(conn, _, _) do
render_error(conn, :not_implemented, "Can't display this activity")
end
@doc """
Returns true if request specifies to include embedded relationships in account objects.
May only be used in selected account-related endpoints; has no effect for status- or
notification-related endpoints.
"""
# Intended for PleromaFE: https://git.pleroma.social/pleroma/pleroma-fe/-/issues/838
def embed_relationships?(params) do
# To do once OpenAPI transition mess is over: just `truthy_param?(params[:with_relationships])`
params
|> Map.get(:with_relationships, params["with_relationships"])
|> Params.truthy_param?()
end
end
diff --git a/lib/pleroma/web/mastodon_api/controllers/notification_controller.ex b/lib/pleroma/web/mastodon_api/controllers/notification_controller.ex
index 2cc31db0c..8a5f8e7b6 100644
--- a/lib/pleroma/web/mastodon_api/controllers/notification_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/notification_controller.ex
@@ -1,259 +1,259 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.MastodonAPI.NotificationController do
use Pleroma.Web, :controller
- import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2]
+ import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2, add_link_headers: 3]
alias Pleroma.Notification
alias Pleroma.User
alias Pleroma.Web.MastodonAPI.AccountView
alias Pleroma.Web.MastodonAPI.MastodonAPI
alias Pleroma.Web.Plugs.OAuthScopesPlug
# Mastodon's docs currently list write:notifications for group accounts, but the endpoint is
# read-only and Mastodon's implementation accepts read:notifications. Prefer least privilege.
@oauth_read_actions [:show, :index, :grouped_index, :show_group, :group_accounts, :unread_count]
plug(Pleroma.Web.ApiSpec.CastAndValidate, replace_params: false)
plug(
OAuthScopesPlug,
%{scopes: ["read:notifications"]} when action in @oauth_read_actions
)
plug(OAuthScopesPlug, %{scopes: ["write:notifications"]} when action not in @oauth_read_actions)
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.NotificationOperation
@default_notification_types ~w{
mention
follow
follow_request
reblog
favourite
move
pleroma:emoji_reaction
poll
update
status
}
# GET /api/v1/notifications
def index(%{private: %{open_api_spex: %{params: %{account_id: account_id} = params}}} = conn, _) do
case User.get_cached_by_id(account_id) do
%{ap_id: account_ap_id} ->
params =
params
|> Map.delete(:account_id)
|> Map.put(:account_ap_id, account_ap_id)
do_get_notifications(conn, params)
_ ->
conn
|> put_status(:not_found)
|> json(%{"error" => "Account is not found"})
end
end
def index(%{private: %{open_api_spex: %{params: params}}} = conn, _) do
do_get_notifications(conn, params)
end
# GET /api/v2/notifications
def grouped_index(
%{private: %{open_api_spex: %{params: %{account_id: account_id} = params}}} = conn,
_
) do
case User.get_cached_by_id(account_id) do
%{ap_id: account_ap_id} ->
params =
params
|> Map.delete(:account_id)
|> Map.put(:account_ap_id, account_ap_id)
do_get_grouped_notifications(conn, params)
_ ->
conn
|> put_status(:not_found)
|> json(%{"error" => "Account is not found"})
end
end
def grouped_index(%{private: %{open_api_spex: %{params: params}}} = conn, _) do
do_get_grouped_notifications(conn, params)
end
# GET /api/v2/notifications/:group_key
def show_group(
%{assigns: %{user: user}, private: %{open_api_spex: %{params: %{group_key: group_key}}}} =
conn,
_
) do
{notifications, notification_group_counts, notification_group_bounds} =
MastodonAPI.get_notification_group_result(user, group_key, %{})
if Enum.empty?(notifications) do
conn
|> put_status(:not_found)
|> json(%{"error" => "Notification group is not found"})
else
grouped_types = if String.starts_with?(group_key, "ungrouped-"), do: [], else: nil
render(conn, "grouped_index.json",
notification_groups: [notifications],
notification_group_counts: notification_group_counts,
notification_group_bounds: notification_group_bounds,
for: user,
grouped_types: grouped_types,
include_page_metadata: false
)
end
end
# GET /api/v2/notifications/:group_key/accounts
def group_accounts(
%{assigns: %{user: user}, private: %{open_api_spex: %{params: %{group_key: group_key}}}} =
conn,
_
) do
# Mastodon paginates this endpoint in code, but the public docs say it returns accounts of all
# notifications in the group and do not document cursor params here. Follow the documented API.
users = MastodonAPI.get_notification_group_accounts(user, group_key)
json(conn, AccountView.render("index.json", %{users: users, for: user}))
end
# GET /api/v2/notifications/unread_count
def unread_count(
%{private: %{open_api_spex: %{params: %{account_id: account_id} = params}}} = conn,
_
) do
case User.get_cached_by_id(account_id) do
%{ap_id: account_ap_id} ->
params =
params
|> Map.delete(:account_id)
|> Map.put(:account_ap_id, account_ap_id)
do_get_unread_group_count(conn, params)
_ ->
conn
|> put_status(:not_found)
|> json(%{"error" => "Account is not found"})
end
end
def unread_count(%{private: %{open_api_spex: %{params: params}}} = conn, _) do
do_get_unread_group_count(conn, params)
end
# POST /api/v2/notifications/:group_key/dismiss
def dismiss_group(
%{assigns: %{user: user}, private: %{open_api_spex: %{params: %{group_key: group_key}}}} =
conn,
_
) do
MastodonAPI.dismiss_notification_group(user, group_key)
json(conn, %{})
end
defp do_get_notifications(%{assigns: %{user: user}} = conn, params) do
params = normalize_notification_params(params)
notifications = MastodonAPI.get_notifications(user, params)
conn
|> add_link_headers(notifications)
|> render("index.json",
notifications: notifications,
for: user
)
end
defp do_get_grouped_notifications(%{assigns: %{user: user}} = conn, params) do
params = normalize_notification_params(params)
{notification_groups, page_notifications, notification_group_counts,
notification_group_bounds} =
MastodonAPI.get_grouped_notification_page(user, params)
conn
- |> add_link_headers(page_notifications)
+ |> add_link_headers(page_notifications, %{drop_id_params: true})
|> render("grouped_index.json",
notification_groups: notification_groups,
notification_group_counts: notification_group_counts,
notification_group_bounds: notification_group_bounds,
for: user,
grouped_types: params["grouped_types"]
)
end
defp do_get_unread_group_count(%{assigns: %{user: user}} = conn, params) do
params = normalize_notification_params(params)
json(conn, %{count: MastodonAPI.unread_notification_group_count(user, params)})
end
defp normalize_notification_params(params) do
params
|> Map.new(fn {k, v} -> {to_string(k), v} end)
|> Map.put_new("types", Map.get(params, :include_types, @default_notification_types))
end
# GET /api/v1/notifications/:id
def show(%{assigns: %{user: user}, private: %{open_api_spex: %{params: %{id: id}}}} = conn, _) do
with {:ok, notification} <- Notification.get(user, id) do
render(conn, "show.json", notification: notification, for: user)
else
{:error, reason} ->
conn
|> put_status(:forbidden)
|> json(%{"error" => reason})
end
end
# POST /api/v1/notifications/clear
def clear(%{assigns: %{user: user}} = conn, _params) do
Notification.clear(user)
json(conn, %{})
end
# POST /api/v1/notifications/:id/dismiss
def dismiss(%{private: %{open_api_spex: %{params: %{id: id}}}} = conn, _) do
do_dismiss(conn, id)
end
# POST /api/v1/notifications/dismiss (deprecated)
def dismiss_via_body(
%{private: %{open_api_spex: %{body_params: %{id: id}}}} = conn,
_
) do
do_dismiss(conn, id)
end
defp do_dismiss(%{assigns: %{user: user}} = conn, notification_id) do
with {:ok, _notif} <- Notification.dismiss(user, notification_id) do
json(conn, %{})
else
{:error, reason} ->
conn
|> put_status(:forbidden)
|> json(%{"error" => reason})
end
end
# DELETE /api/v1/notifications/destroy_multiple
def destroy_multiple(
%{assigns: %{user: user}, private: %{open_api_spex: %{params: %{ids: ids}}}} = conn,
_
) do
Notification.destroy_multiple(user, ids)
json(conn, %{})
end
end

File Metadata

Mime Type
text/x-diff
Expires
Sat, Aug 29, 9:41 AM (1 d, 21 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1736983
Default Alt Text
(14 KB)

Event Timeline