commit
1053319cd6
@ -0,0 +1,54 @@
|
|||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Plugs.RemoteIp do
|
||||||
|
@moduledoc """
|
||||||
|
This is a shim to call [`RemoteIp`](https://git.pleroma.social/pleroma/remote_ip) but with runtime configuration.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@behaviour Plug
|
||||||
|
|
||||||
|
@headers ~w[
|
||||||
|
forwarded
|
||||||
|
x-forwarded-for
|
||||||
|
x-client-ip
|
||||||
|
x-real-ip
|
||||||
|
]
|
||||||
|
|
||||||
|
# https://en.wikipedia.org/wiki/Localhost
|
||||||
|
# https://en.wikipedia.org/wiki/Private_network
|
||||||
|
@reserved ~w[
|
||||||
|
127.0.0.0/8
|
||||||
|
::1/128
|
||||||
|
fc00::/7
|
||||||
|
10.0.0.0/8
|
||||||
|
172.16.0.0/12
|
||||||
|
192.168.0.0/16
|
||||||
|
]
|
||||||
|
|
||||||
|
def init(_), do: nil
|
||||||
|
|
||||||
|
def call(conn, _) do
|
||||||
|
config = Pleroma.Config.get(__MODULE__, [])
|
||||||
|
|
||||||
|
if Keyword.get(config, :enabled, false) do
|
||||||
|
RemoteIp.call(conn, remote_ip_opts(config))
|
||||||
|
else
|
||||||
|
conn
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp remote_ip_opts(config) do
|
||||||
|
headers = config |> Keyword.get(:headers, @headers) |> MapSet.new()
|
||||||
|
reserved = Keyword.get(config, :reserved, @reserved)
|
||||||
|
|
||||||
|
proxies =
|
||||||
|
config
|
||||||
|
|> Keyword.get(:proxies, [])
|
||||||
|
|> Enum.concat(reserved)
|
||||||
|
|> Enum.map(&InetCidr.parse/1)
|
||||||
|
|
||||||
|
{headers, proxies}
|
||||||
|
end
|
||||||
|
end
|
@ -0,0 +1,260 @@
|
|||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.SubscriptionNotification do
|
||||||
|
use Ecto.Schema
|
||||||
|
|
||||||
|
alias Pleroma.Activity
|
||||||
|
alias Pleroma.Object
|
||||||
|
alias Pleroma.Pagination
|
||||||
|
alias Pleroma.Repo
|
||||||
|
alias Pleroma.SubscriptionNotification
|
||||||
|
alias Pleroma.User
|
||||||
|
alias Pleroma.Web.CommonAPI.Utils
|
||||||
|
alias Pleroma.Web.Push
|
||||||
|
alias Pleroma.Web.Streamer
|
||||||
|
|
||||||
|
import Ecto.Query
|
||||||
|
import Ecto.Changeset
|
||||||
|
|
||||||
|
@type t :: %__MODULE__{}
|
||||||
|
|
||||||
|
schema "subscription_notifications" do
|
||||||
|
belongs_to(:user, User, type: FlakeId.Ecto.CompatType)
|
||||||
|
belongs_to(:activity, Activity, type: FlakeId.Ecto.CompatType)
|
||||||
|
|
||||||
|
timestamps()
|
||||||
|
end
|
||||||
|
|
||||||
|
def changeset(%SubscriptionNotification{} = notification, attrs) do
|
||||||
|
cast(notification, attrs, [])
|
||||||
|
end
|
||||||
|
|
||||||
|
def for_user_query(user, opts \\ []) do
|
||||||
|
query =
|
||||||
|
SubscriptionNotification
|
||||||
|
|> where(user_id: ^user.id)
|
||||||
|
|> where(
|
||||||
|
[n, a],
|
||||||
|
fragment(
|
||||||
|
"? not in (SELECT ap_id FROM users WHERE info->'deactivated' @> 'true')",
|
||||||
|
a.actor
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|> join(:inner, [n], activity in assoc(n, :activity))
|
||||||
|
|> join(:left, [n, a], object in Object,
|
||||||
|
on:
|
||||||
|
fragment(
|
||||||
|
"(?->>'id') = COALESCE((? -> 'object'::text) ->> 'id'::text)",
|
||||||
|
object.data,
|
||||||
|
a.data
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|> preload([n, a, o], activity: {a, object: o})
|
||||||
|
|
||||||
|
if opts[:with_muted] do
|
||||||
|
query
|
||||||
|
else
|
||||||
|
query
|
||||||
|
|> where([n, a], a.actor not in ^user.info.muted_notifications)
|
||||||
|
|> where([n, a], a.actor not in ^user.info.blocks)
|
||||||
|
|> where(
|
||||||
|
[n, a],
|
||||||
|
fragment("substring(? from '.*://([^/]*)')", a.actor) not in ^user.info.domain_blocks
|
||||||
|
)
|
||||||
|
|> join(:left, [n, a], tm in Pleroma.ThreadMute,
|
||||||
|
on: tm.user_id == ^user.id and tm.context == fragment("?->>'context'", a.data)
|
||||||
|
)
|
||||||
|
|> where([n, a, o, tm], is_nil(tm.user_id))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def for_user(user, opts \\ %{}) do
|
||||||
|
user
|
||||||
|
|> for_user_query(opts)
|
||||||
|
|> Pagination.fetch_paginated(opts)
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Returns notifications for user received since given date.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
iex> Pleroma.SubscriptionNotification.for_user_since(%Pleroma.User{}, ~N[2019-04-13 11:22:33])
|
||||||
|
[%Pleroma.SubscriptionNotification{}, %Pleroma.SubscriptionNotification{}]
|
||||||
|
|
||||||
|
iex> Pleroma.SubscriptionNotification.for_user_since(%Pleroma.User{}, ~N[2019-04-15 11:22:33])
|
||||||
|
[]
|
||||||
|
"""
|
||||||
|
@spec for_user_since(Pleroma.User.t(), NaiveDateTime.t()) :: [t()]
|
||||||
|
def for_user_since(user, date) do
|
||||||
|
user
|
||||||
|
|> for_user_query()
|
||||||
|
|> where([n], n.updated_at > ^date)
|
||||||
|
|> Repo.all()
|
||||||
|
end
|
||||||
|
|
||||||
|
def clear_up_to(%{id: user_id} = _user, id) do
|
||||||
|
from(
|
||||||
|
n in SubscriptionNotification,
|
||||||
|
where: n.user_id == ^user_id,
|
||||||
|
where: n.id <= ^id
|
||||||
|
)
|
||||||
|
|> Repo.delete_all([])
|
||||||
|
end
|
||||||
|
|
||||||
|
def get(%{id: user_id} = _user, id) do
|
||||||
|
query =
|
||||||
|
from(
|
||||||
|
n in SubscriptionNotification,
|
||||||
|
where: n.id == ^id,
|
||||||
|
join: activity in assoc(n, :activity),
|
||||||
|
preload: [activity: activity]
|
||||||
|
)
|
||||||
|
|
||||||
|
case Repo.one(query) do
|
||||||
|
%{user_id: ^user_id} = notification ->
|
||||||
|
{:ok, notification}
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
{:error, "Cannot get notification"}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def clear(user) do
|
||||||
|
from(n in SubscriptionNotification, where: n.user_id == ^user.id)
|
||||||
|
|> Repo.delete_all()
|
||||||
|
end
|
||||||
|
|
||||||
|
def destroy_multiple(%{id: user_id} = _user, ids) do
|
||||||
|
from(n in SubscriptionNotification,
|
||||||
|
where: n.id in ^ids,
|
||||||
|
where: n.user_id == ^user_id
|
||||||
|
)
|
||||||
|
|> Repo.delete_all()
|
||||||
|
end
|
||||||
|
|
||||||
|
def dismiss(%{id: user_id} = _user, id) do
|
||||||
|
case Repo.get(SubscriptionNotification, id) do
|
||||||
|
%{user_id: ^user_id} = notification ->
|
||||||
|
Repo.delete(notification)
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
{:error, "Cannot dismiss notification"}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def create_notifications(%Activity{data: %{"to" => _, "type" => "Create"}} = activity) do
|
||||||
|
case Object.normalize(activity) do
|
||||||
|
%{data: %{"type" => "Answer"}} ->
|
||||||
|
{:ok, []}
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
users = get_notified_from_activity(activity)
|
||||||
|
notifications = Enum.map(users, fn user -> create_notification(activity, user) end)
|
||||||
|
{:ok, notifications}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def create_notifications(%Activity{data: %{"to" => _, "type" => type}} = activity)
|
||||||
|
when type in ["Like", "Announce", "Follow"] do
|
||||||
|
notifications =
|
||||||
|
activity
|
||||||
|
|> get_notified_from_activity()
|
||||||
|
|> Enum.map(&create_notification(activity, &1))
|
||||||
|
|
||||||
|
{:ok, notifications}
|
||||||
|
end
|
||||||
|
|
||||||
|
def create_notifications(_), do: {:ok, []}
|
||||||
|
|
||||||
|
# TODO move to sql, too.
|
||||||
|
def create_notification(%Activity{} = activity, %User{} = user) do
|
||||||
|
unless skip?(activity, user) do
|
||||||
|
notification = %SubscriptionNotification{user_id: user.id, activity: activity}
|
||||||
|
{:ok, notification} = Repo.insert(notification)
|
||||||
|
Streamer.stream("user", notification)
|
||||||
|
Streamer.stream("user:subscription_notification", notification)
|
||||||
|
Push.send(notification)
|
||||||
|
notification
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_notified_from_activity(activity, local_only \\ true)
|
||||||
|
|
||||||
|
def get_notified_from_activity(
|
||||||
|
%Activity{data: %{"to" => _, "type" => type} = _data} = activity,
|
||||||
|
local_only
|
||||||
|
)
|
||||||
|
when type in ["Create", "Like", "Announce", "Follow"] do
|
||||||
|
[]
|
||||||
|
|> Utils.maybe_notify_subscribers(activity)
|
||||||
|
|> Enum.uniq()
|
||||||
|
|> User.get_users_from_set(local_only)
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_notified_from_activity(_, _local_only), do: []
|
||||||
|
|
||||||
|
@spec skip?(Activity.t(), User.t()) :: boolean()
|
||||||
|
def skip?(activity, user) do
|
||||||
|
[
|
||||||
|
:self,
|
||||||
|
:followers,
|
||||||
|
:follows,
|
||||||
|
:non_followers,
|
||||||
|
:non_follows,
|
||||||
|
:recently_followed
|
||||||
|
]
|
||||||
|
|> Enum.any?(&skip?(&1, activity, user))
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec skip?(atom(), Activity.t(), User.t()) :: boolean()
|
||||||
|
def skip?(:self, activity, user) do
|
||||||
|
activity.data["actor"] == user.ap_id
|
||||||
|
end
|
||||||
|
|
||||||
|
def skip?(
|
||||||
|
:followers,
|
||||||
|
%{data: %{"actor" => actor}},
|
||||||
|
%{info: %{notification_settings: %{"followers" => false}}} = user
|
||||||
|
) do
|
||||||
|
actor
|
||||||
|
|> User.get_cached_by_ap_id()
|
||||||
|
|> User.following?(user)
|
||||||
|
end
|
||||||
|
|
||||||
|
def skip?(
|
||||||
|
:non_followers,
|
||||||
|
activity,
|
||||||
|
%{info: %{notification_settings: %{"non_followers" => false}}} = user
|
||||||
|
) do
|
||||||
|
actor = activity.data["actor"]
|
||||||
|
follower = User.get_cached_by_ap_id(actor)
|
||||||
|
!User.following?(follower, user)
|
||||||
|
end
|
||||||
|
|
||||||
|
def skip?(:follows, activity, %{info: %{notification_settings: %{"follows" => false}}} = user) do
|
||||||
|
actor = activity.data["actor"]
|
||||||
|
followed = User.get_cached_by_ap_id(actor)
|
||||||
|
User.following?(user, followed)
|
||||||
|
end
|
||||||
|
|
||||||
|
def skip?(
|
||||||
|
:non_follows,
|
||||||
|
activity,
|
||||||
|
%{info: %{notification_settings: %{"non_follows" => false}}} = user
|
||||||
|
) do
|
||||||
|
actor = activity.data["actor"]
|
||||||
|
followed = User.get_cached_by_ap_id(actor)
|
||||||
|
!User.following?(user, followed)
|
||||||
|
end
|
||||||
|
|
||||||
|
def skip?(:recently_followed, %{data: %{"type" => "Follow", "actor" => actor}}, user) do
|
||||||
|
user
|
||||||
|
|> SubscriptionNotification.for_user()
|
||||||
|
|> Enum.any?(&match?(%{activity: %{data: %{"type" => "Follow", "actor" => ^actor}}}, &1))
|
||||||
|
end
|
||||||
|
|
||||||
|
def skip?(_, _, _), do: false
|
||||||
|
end
|
@ -0,0 +1,219 @@
|
|||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.CommonAPI.ActivityDraft do
|
||||||
|
alias Pleroma.Activity
|
||||||
|
alias Pleroma.Conversation.Participation
|
||||||
|
alias Pleroma.Web.CommonAPI
|
||||||
|
alias Pleroma.Web.CommonAPI.Utils
|
||||||
|
|
||||||
|
import Pleroma.Web.Gettext
|
||||||
|
|
||||||
|
defstruct valid?: true,
|
||||||
|
errors: [],
|
||||||
|
user: nil,
|
||||||
|
params: %{},
|
||||||
|
status: nil,
|
||||||
|
summary: nil,
|
||||||
|
full_payload: nil,
|
||||||
|
attachments: [],
|
||||||
|
in_reply_to: nil,
|
||||||
|
in_reply_to_conversation: nil,
|
||||||
|
visibility: nil,
|
||||||
|
expires_at: nil,
|
||||||
|
poll: nil,
|
||||||
|
emoji: %{},
|
||||||
|
content_html: nil,
|
||||||
|
mentions: [],
|
||||||
|
tags: [],
|
||||||
|
to: [],
|
||||||
|
cc: [],
|
||||||
|
context: nil,
|
||||||
|
sensitive: false,
|
||||||
|
object: nil,
|
||||||
|
preview?: false,
|
||||||
|
changes: %{}
|
||||||
|
|
||||||
|
def create(user, params) do
|
||||||
|
%__MODULE__{user: user}
|
||||||
|
|> put_params(params)
|
||||||
|
|> status()
|
||||||
|
|> summary()
|
||||||
|
|> with_valid(&attachments/1)
|
||||||
|
|> full_payload()
|
||||||
|
|> expires_at()
|
||||||
|
|> poll()
|
||||||
|
|> with_valid(&in_reply_to/1)
|
||||||
|
|> with_valid(&in_reply_to_conversation/1)
|
||||||
|
|> with_valid(&visibility/1)
|
||||||
|
|> content()
|
||||||
|
|> with_valid(&to_and_cc/1)
|
||||||
|
|> with_valid(&context/1)
|
||||||
|
|> sensitive()
|
||||||
|
|> with_valid(&object/1)
|
||||||
|
|> preview?()
|
||||||
|
|> with_valid(&changes/1)
|
||||||
|
|> validate()
|
||||||
|
end
|
||||||
|
|
||||||
|
defp put_params(draft, params) do
|
||||||
|
params = Map.put_new(params, "in_reply_to_status_id", params["in_reply_to_id"])
|
||||||
|
%__MODULE__{draft | params: params}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp status(%{params: %{"status" => status}} = draft) do
|
||||||
|
%__MODULE__{draft | status: String.trim(status)}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp summary(%{params: params} = draft) do
|
||||||
|
%__MODULE__{draft | summary: Map.get(params, "spoiler_text", "")}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp full_payload(%{status: status, summary: summary} = draft) do
|
||||||
|
full_payload = String.trim(status <> summary)
|
||||||
|
|
||||||
|
case Utils.validate_character_limit(full_payload, draft.attachments) do
|
||||||
|
:ok -> %__MODULE__{draft | full_payload: full_payload}
|
||||||
|
{:error, message} -> add_error(draft, message)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp attachments(%{params: params} = draft) do
|
||||||
|
attachments = Utils.attachments_from_ids(params)
|
||||||
|
%__MODULE__{draft | attachments: attachments}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp in_reply_to(draft) do
|
||||||
|
case Map.get(draft.params, "in_reply_to_status_id") do
|
||||||
|
"" -> draft
|
||||||
|
nil -> draft
|
||||||
|
id -> %__MODULE__{draft | in_reply_to: Activity.get_by_id(id)}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp in_reply_to_conversation(draft) do
|
||||||
|
in_reply_to_conversation = Participation.get(draft.params["in_reply_to_conversation_id"])
|
||||||
|
%__MODULE__{draft | in_reply_to_conversation: in_reply_to_conversation}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp visibility(%{params: params} = draft) do
|
||||||
|
case CommonAPI.get_visibility(params, draft.in_reply_to, draft.in_reply_to_conversation) do
|
||||||
|
{visibility, "direct"} when visibility != "direct" ->
|
||||||
|
add_error(draft, dgettext("errors", "The message visibility must be direct"))
|
||||||
|
|
||||||
|
{visibility, _} ->
|
||||||
|
%__MODULE__{draft | visibility: visibility}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp expires_at(draft) do
|
||||||
|
case CommonAPI.check_expiry_date(draft.params["expires_in"]) do
|
||||||
|
{:ok, expires_at} -> %__MODULE__{draft | expires_at: expires_at}
|
||||||
|
{:error, message} -> add_error(draft, message)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp poll(draft) do
|
||||||
|
case Utils.make_poll_data(draft.params) do
|
||||||
|
{:ok, {poll, poll_emoji}} ->
|
||||||
|
%__MODULE__{draft | poll: poll, emoji: Map.merge(draft.emoji, poll_emoji)}
|
||||||
|
|
||||||
|
{:error, message} ->
|
||||||
|
add_error(draft, message)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp content(draft) do
|
||||||
|
{content_html, mentions, tags} =
|
||||||
|
Utils.make_content_html(
|
||||||
|
draft.status,
|
||||||
|
draft.attachments,
|
||||||
|
draft.params,
|
||||||
|
draft.visibility
|
||||||
|
)
|
||||||
|
|
||||||
|
%__MODULE__{draft | content_html: content_html, mentions: mentions, tags: tags}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp to_and_cc(draft) do
|
||||||
|
addressed_users =
|
||||||
|
draft.mentions
|
||||||
|
|> Enum.map(fn {_, mentioned_user} -> mentioned_user.ap_id end)
|
||||||
|
|> Utils.get_addressed_users(draft.params["to"])
|
||||||
|
|
||||||
|
{to, cc} =
|
||||||
|
Utils.get_to_and_cc(
|
||||||
|
draft.user,
|
||||||
|
addressed_users,
|
||||||
|
draft.in_reply_to,
|
||||||
|
draft.visibility,
|
||||||
|
draft.in_reply_to_conversation
|
||||||
|
)
|
||||||
|
|
||||||
|
%__MODULE__{draft | to: to, cc: cc}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp context(draft) do
|
||||||
|
context = Utils.make_context(draft.in_reply_to, draft.in_reply_to_conversation)
|
||||||
|
%__MODULE__{draft | context: context}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp sensitive(draft) do
|
||||||
|
sensitive = draft.params["sensitive"] || Enum.member?(draft.tags, {"#nsfw", "nsfw"})
|
||||||
|
%__MODULE__{draft | sensitive: sensitive}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp object(draft) do
|
||||||
|
emoji = Map.merge(Pleroma.Emoji.Formatter.get_emoji_map(draft.full_payload), draft.emoji)
|
||||||
|
|
||||||
|
object =
|
||||||
|
Utils.make_note_data(
|
||||||
|
draft.user.ap_id,
|
||||||
|
draft.to,
|
||||||
|
draft.context,
|
||||||
|
draft.content_html,
|
||||||
|
draft.attachments,
|
||||||
|
draft.in_reply_to,
|
||||||
|
draft.tags,
|
||||||
|
draft.summary,
|
||||||
|
draft.cc,
|
||||||
|
draft.sensitive,
|
||||||
|
draft.poll
|
||||||
|
)
|
||||||
|
|> Map.put("emoji", emoji)
|
||||||
|
|
||||||
|
%__MODULE__{draft | object: object}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp preview?(draft) do
|
||||||
|
preview? = Pleroma.Web.ControllerHelper.truthy_param?(draft.params["preview"]) || false
|
||||||
|
%__MODULE__{draft | preview?: preview?}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp changes(draft) do
|
||||||
|
direct? = draft.visibility == "direct"
|
||||||
|
|
||||||
|
changes =
|
||||||
|
%{
|
||||||
|
to: draft.to,
|
||||||
|
actor: draft.user,
|
||||||
|
context: draft.context,
|
||||||
|
object: draft.object,
|
||||||
|
additional: %{"cc" => draft.cc, "directMessage" => direct?}
|
||||||
|
}
|
||||||
|
|> Utils.maybe_add_list_data(draft.user, draft.visibility)
|
||||||
|
|
||||||
|
%__MODULE__{draft | changes: changes}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp with_valid(%{valid?: true} = draft, func), do: func.(draft)
|
||||||
|
defp with_valid(draft, _func), do: draft
|
||||||
|
|
||||||
|
defp add_error(draft, message) do
|
||||||
|
%__MODULE__{draft | valid?: false, errors: [message | draft.errors]}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp validate(%{valid?: true} = draft), do: {:ok, draft}
|
||||||
|
defp validate(%{errors: [message | _]}), do: {:error, message}
|
||||||
|
end
|
@ -0,0 +1,26 @@
|
|||||||
|
# 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.DomainBlockController do
|
||||||
|
use Pleroma.Web, :controller
|
||||||
|
|
||||||
|
alias Pleroma.User
|
||||||
|
|
||||||
|
@doc "GET /api/v1/domain_blocks"
|
||||||
|
def index(%{assigns: %{user: %{info: info}}} = conn, _) do
|
||||||
|
json(conn, Map.get(info, :domain_blocks, []))
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc "POST /api/v1/domain_blocks"
|
||||||
|
def create(%{assigns: %{user: blocker}} = conn, %{"domain" => domain}) do
|
||||||
|
User.block_domain(blocker, domain)
|
||||||
|
json(conn, %{})
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc "DELETE /api/v1/domain_blocks"
|
||||||
|
def delete(%{assigns: %{user: blocker}} = conn, %{"domain" => domain}) do
|
||||||
|
User.unblock_domain(blocker, domain)
|
||||||
|
json(conn, %{})
|
||||||
|
end
|
||||||
|
end
|
@ -0,0 +1,72 @@
|
|||||||
|
# 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.FilterController do
|
||||||
|
use Pleroma.Web, :controller
|
||||||
|
|
||||||
|
alias Pleroma.Filter
|
||||||
|
|
||||||
|
@doc "GET /api/v1/filters"
|
||||||
|
def index(%{assigns: %{user: user}} = conn, _) do
|
||||||
|
filters = Filter.get_filters(user)
|
||||||
|
|
||||||
|
render(conn, "filters.json", filters: filters)
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc "POST /api/v1/filters"
|
||||||
|
def create(
|
||||||
|
%{assigns: %{user: user}} = conn,
|
||||||
|
%{"phrase" => phrase, "context" => context} = params
|
||||||
|
) do
|
||||||
|
query = %Filter{
|
||||||
|
user_id: user.id,
|
||||||
|
phrase: phrase,
|
||||||
|
context: context,
|
||||||
|
hide: Map.get(params, "irreversible", false),
|
||||||
|
whole_word: Map.get(params, "boolean", true)
|
||||||
|
# expires_at
|
||||||
|
}
|
||||||
|
|
||||||
|
{:ok, response} = Filter.create(query)
|
||||||
|
|
||||||
|
render(conn, "filter.json", filter: response)
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc "GET /api/v1/filters/:id"
|
||||||
|
def show(%{assigns: %{user: user}} = conn, %{"id" => filter_id}) do
|
||||||
|
filter = Filter.get(filter_id, user)
|
||||||
|
|
||||||
|
render(conn, "filter.json", filter: filter)
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc "PUT /api/v1/filters/:id"
|
||||||
|
def update(
|
||||||
|
%{assigns: %{user: user}} = conn,
|
||||||
|
%{"phrase" => phrase, "context" => context, "id" => filter_id} = params
|
||||||
|
) do
|
||||||
|
query = %Filter{
|
||||||
|
user_id: user.id,
|
||||||
|
filter_id: filter_id,
|
||||||
|
phrase: phrase,
|
||||||
|
context: context,
|
||||||
|
hide: Map.get(params, "irreversible", nil),
|
||||||
|
whole_word: Map.get(params, "boolean", true)
|
||||||
|
# expires_at
|
||||||
|
}
|
||||||
|
|
||||||
|
{:ok, response} = Filter.update(query)
|
||||||
|
render(conn, "filter.json", filter: response)
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc "DELETE /api/v1/filters/:id"
|
||||||
|
def delete(%{assigns: %{user: user}} = conn, %{"id" => filter_id}) do
|
||||||
|
query = %Filter{
|
||||||
|
user_id: user.id,
|
||||||
|
filter_id: filter_id
|
||||||
|
}
|
||||||
|
|
||||||
|
{:ok, _} = Filter.delete(query)
|
||||||
|
json(conn, %{})
|
||||||
|
end
|
||||||
|
end
|
@ -0,0 +1,49 @@
|
|||||||
|
# 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.FollowRequestController do
|
||||||
|
use Pleroma.Web, :controller
|
||||||
|
|
||||||
|
alias Pleroma.User
|
||||||
|
alias Pleroma.Web.CommonAPI
|
||||||
|
|
||||||
|
plug(:put_view, Pleroma.Web.MastodonAPI.AccountView)
|
||||||
|
plug(:assign_follower when action != :index)
|
||||||
|
|
||||||
|
action_fallback(:errors)
|
||||||
|
|
||||||
|
@doc "GET /api/v1/follow_requests"
|
||||||
|
def index(%{assigns: %{user: followed}} = conn, _params) do
|
||||||
|
follow_requests = User.get_follow_requests(followed)
|
||||||
|
|
||||||
|
render(conn, "accounts.json", for: followed, users: follow_requests, as: :user)
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc "POST /api/v1/follow_requests/:id/authorize"
|
||||||
|
def authorize(%{assigns: %{user: followed, follower: follower}} = conn, _params) do
|
||||||
|
with {:ok, follower} <- CommonAPI.accept_follow_request(follower, followed) do
|
||||||
|
render(conn, "relationship.json", user: followed, target: follower)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc "POST /api/v1/follow_requests/:id/reject"
|
||||||
|
def reject(%{assigns: %{user: followed, follower: follower}} = conn, _params) do
|
||||||
|
with {:ok, follower} <- CommonAPI.reject_follow_request(follower, followed) do
|
||||||
|
render(conn, "relationship.json", user: followed, target: follower)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp assign_follower(%{params: %{"id" => id}} = conn, _) do
|
||||||
|
case User.get_cached_by_id(id) do
|
||||||
|
%User{} = follower -> assign(conn, :follower, follower)
|
||||||
|
nil -> Pleroma.Web.MastodonAPI.FallbackController.call(conn, {:error, :not_found}) |> halt()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp errors(conn, {:error, message}) do
|
||||||
|
conn
|
||||||
|
|> put_status(:forbidden)
|
||||||
|
|> json(%{error: message})
|
||||||
|
end
|
||||||
|
end
|
@ -0,0 +1,51 @@
|
|||||||
|
# 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.ScheduledActivityController do
|
||||||
|
use Pleroma.Web, :controller
|
||||||
|
|
||||||
|
import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2]
|
||||||
|
|
||||||
|
alias Pleroma.ScheduledActivity
|
||||||
|
alias Pleroma.Web.MastodonAPI.MastodonAPI
|
||||||
|
|
||||||
|
plug(:assign_scheduled_activity when action != :index)
|
||||||
|
|
||||||
|
action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
|
||||||
|
|
||||||
|
@doc "GET /api/v1/scheduled_statuses"
|
||||||
|
def index(%{assigns: %{user: user}} = conn, params) do
|
||||||
|
with scheduled_activities <- MastodonAPI.get_scheduled_activities(user, params) do
|
||||||
|
conn
|
||||||
|
|> add_link_headers(scheduled_activities)
|
||||||
|
|> render("index.json", scheduled_activities: scheduled_activities)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc "GET /api/v1/scheduled_statuses/:id"
|
||||||
|
def show(%{assigns: %{scheduled_activity: scheduled_activity}} = conn, _params) do
|
||||||
|
render(conn, "show.json", scheduled_activity: scheduled_activity)
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc "PUT /api/v1/scheduled_statuses/:id"
|
||||||
|
def update(%{assigns: %{scheduled_activity: scheduled_activity}} = conn, params) do
|
||||||
|
with {:ok, scheduled_activity} <- ScheduledActivity.update(scheduled_activity, params) do
|
||||||
|
render(conn, "show.json", scheduled_activity: scheduled_activity)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc "DELETE /api/v1/scheduled_statuses/:id"
|
||||||
|
def delete(%{assigns: %{scheduled_activity: scheduled_activity}} = conn, _params) do
|
||||||
|
with {:ok, scheduled_activity} <- ScheduledActivity.delete(scheduled_activity) do
|
||||||
|
render(conn, "show.json", scheduled_activity: scheduled_activity)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp assign_scheduled_activity(%{assigns: %{user: user}, params: %{"id" => id}} = conn, _) do
|
||||||
|
case ScheduledActivity.get(user, id) do
|
||||||
|
%ScheduledActivity{} = activity -> assign(conn, :scheduled_activity, activity)
|
||||||
|
nil -> Pleroma.Web.MastodonAPI.FallbackController.call(conn, {:error, :not_found}) |> halt()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
@ -0,0 +1,274 @@
|
|||||||
|
# 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.StatusController do
|
||||||
|
use Pleroma.Web, :controller
|
||||||
|
|
||||||
|
import Pleroma.Web.MastodonAPI.MastodonAPIController, only: [try_render: 3]
|
||||||
|
|
||||||
|
require Ecto.Query
|
||||||
|
|
||||||
|
alias Pleroma.Activity
|
||||||
|
alias Pleroma.Bookmark
|
||||||
|
alias Pleroma.Object
|
||||||
|
alias Pleroma.Plugs.RateLimiter
|
||||||
|
alias Pleroma.Repo
|
||||||
|
alias Pleroma.ScheduledActivity
|
||||||
|
alias Pleroma.User
|
||||||
|
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||||
|
alias Pleroma.Web.ActivityPub.Visibility
|
||||||
|
alias Pleroma.Web.CommonAPI
|
||||||
|
alias Pleroma.Web.MastodonAPI.AccountView
|
||||||
|
alias Pleroma.Web.MastodonAPI.ScheduledActivityView
|
||||||
|
|
||||||
|
@rate_limited_status_actions ~w(reblog unreblog favourite unfavourite create delete)a
|
||||||
|
|
||||||
|
plug(
|
||||||
|
RateLimiter,
|
||||||
|
{:status_id_action, bucket_name: "status_id_action:reblog_unreblog", params: ["id"]}
|
||||||
|
when action in ~w(reblog unreblog)a
|
||||||
|
)
|
||||||
|
|
||||||
|
plug(
|
||||||
|
RateLimiter,
|
||||||
|
{:status_id_action, bucket_name: "status_id_action:fav_unfav", params: ["id"]}
|
||||||
|
when action in ~w(favourite unfavourite)a
|
||||||
|
)
|
||||||
|
|
||||||
|
plug(RateLimiter, :statuses_actions when action in @rate_limited_status_actions)
|
||||||
|
|
||||||
|
action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
GET `/api/v1/statuses?ids[]=1&ids[]=2`
|
||||||
|
|
||||||
|
`ids` query param is required
|
||||||
|
"""
|
||||||
|
def index(%{assigns: %{user: user}} = conn, %{"ids" => ids}) do
|
||||||
|
limit = 100
|
||||||
|
|
||||||
|
activities =
|
||||||
|
ids
|
||||||
|
|> Enum.take(limit)
|
||||||
|
|> Activity.all_by_ids_with_object()
|
||||||
|
|> Enum.filter(&Visibility.visible_for_user?(&1, user))
|
||||||
|
|
||||||
|
render(conn, "index.json", activities: activities, for: user, as: :activity)
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
POST /api/v1/statuses
|
||||||
|
|
||||||
|
Creates a scheduled status when `scheduled_at` param is present and it's far enough
|
||||||
|
"""
|
||||||
|
def create(
|
||||||
|
%{assigns: %{user: user}} = conn,
|
||||||
|
%{"status" => _, "scheduled_at" => scheduled_at} = params
|
||||||
|
) do
|
||||||
|
params = Map.put(params, "in_reply_to_status_id", params["in_reply_to_id"])
|
||||||
|
|
||||||
|
if ScheduledActivity.far_enough?(scheduled_at) do
|
||||||
|
with {:ok, scheduled_activity} <-
|
||||||
|
ScheduledActivity.create(user, %{"params" => params, "scheduled_at" => scheduled_at}) do
|
||||||
|
conn
|
||||||
|
|> put_view(ScheduledActivityView)
|
||||||
|
|> render("show.json", scheduled_activity: scheduled_activity)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
create(conn, Map.drop(params, ["scheduled_at"]))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
POST /api/v1/statuses
|
||||||
|
|
||||||
|
Creates a regular status
|
||||||
|
"""
|
||||||
|
def create(%{assigns: %{user: user}} = conn, %{"status" => _} = params) do
|
||||||
|
params = Map.put(params, "in_reply_to_status_id", params["in_reply_to_id"])
|
||||||
|
|
||||||
|
with {:ok, activity} <- CommonAPI.post(user, params) do
|
||||||
|
try_render(conn, "show.json",
|
||||||
|
activity: activity,
|
||||||
|
for: user,
|
||||||
|
as: :activity,
|
||||||
|
with_direct_conversation_id: true
|
||||||
|
)
|
||||||
|
else
|
||||||
|
{:error, message} ->
|
||||||
|
conn
|
||||||
|
|> put_status(:unprocessable_entity)
|
||||||
|
|> json(%{error: message})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def create(%{assigns: %{user: _user}} = conn, %{"media_ids" => _} = params) do
|
||||||
|
create(conn, Map.put(params, "status", ""))
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc "GET /api/v1/statuses/:id"
|
||||||
|
def show(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
||||||
|
with %Activity{} = activity <- Activity.get_by_id_with_object(id),
|
||||||
|
true <- Visibility.visible_for_user?(activity, user) do
|
||||||
|
try_render(conn, "show.json", activity: activity, for: user)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc "DELETE /api/v1/statuses/:id"
|
||||||
|
def delete(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
||||||
|
with {:ok, %Activity{}} <- CommonAPI.delete(id, user) do
|
||||||
|
json(conn, %{})
|
||||||
|
else
|
||||||
|
_e -> render_error(conn, :forbidden, "Can't delete this post")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc "POST /api/v1/statuses/:id/reblog"
|
||||||
|
def reblog(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do
|
||||||
|
with {:ok, announce, _activity} <- CommonAPI.repeat(ap_id_or_id, user),
|
||||||
|
%Activity{} = announce <- Activity.normalize(announce.data) do
|
||||||
|
try_render(conn, "show.json", %{activity: announce, for: user, as: :activity})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc "POST /api/v1/statuses/:id/unreblog"
|
||||||
|
def unreblog(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do
|
||||||
|
with {:ok, _unannounce, %{data: %{"id" => id}}} <- CommonAPI.unrepeat(ap_id_or_id, user),
|
||||||
|
%Activity{} = activity <- Activity.get_create_by_object_ap_id_with_object(id) do
|
||||||
|
try_render(conn, "show.json", %{activity: activity, for: user, as: :activity})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc "POST /api/v1/statuses/:id/favourite"
|
||||||
|
def favourite(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do
|
||||||
|
with {:ok, _fav, %{data: %{"id" => id}}} <- CommonAPI.favorite(ap_id_or_id, user),
|
||||||
|
%Activity{} = activity <- Activity.get_create_by_object_ap_id(id) do
|
||||||
|
try_render(conn, "show.json", activity: activity, for: user, as: :activity)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc "POST /api/v1/statuses/:id/unfavourite"
|
||||||
|
def unfavourite(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do
|
||||||
|
with {:ok, _, _, %{data: %{"id" => id}}} <- CommonAPI.unfavorite(ap_id_or_id, user),
|
||||||
|
%Activity{} = activity <- Activity.get_create_by_object_ap_id(id) do
|
||||||
|
try_render(conn, "show.json", activity: activity, for: user, as: :activity)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc "POST /api/v1/statuses/:id/pin"
|
||||||
|
def pin(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do
|
||||||
|
with {:ok, activity} <- CommonAPI.pin(ap_id_or_id, user) do
|
||||||
|
try_render(conn, "show.json", activity: activity, for: user, as: :activity)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc "POST /api/v1/statuses/:id/unpin"
|
||||||
|
def unpin(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do
|
||||||
|
with {:ok, activity} <- CommonAPI.unpin(ap_id_or_id, user) do
|
||||||
|
try_render(conn, "show.json", activity: activity, for: user, as: :activity)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc "POST /api/v1/statuses/:id/bookmark"
|
||||||
|
def bookmark(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
||||||
|
with %Activity{} = activity <- Activity.get_by_id_with_object(id),
|
||||||
|
%User{} = user <- User.get_cached_by_nickname(user.nickname),
|
||||||
|
true <- Visibility.visible_for_user?(activity, user),
|
||||||
|
{:ok, _bookmark} <- Bookmark.create(user.id, activity.id) do
|
||||||
|
try_render(conn, "show.json", activity: activity, for: user, as: :activity)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc "POST /api/v1/statuses/:id/unbookmark"
|
||||||
|
def unbookmark(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
||||||
|
with %Activity{} = activity <- Activity.get_by_id_with_object(id),
|
||||||
|
%User{} = user <- User.get_cached_by_nickname(user.nickname),
|
||||||
|
true <- Visibility.visible_for_user?(activity, user),
|
||||||
|
{:ok, _bookmark} <- Bookmark.destroy(user.id, activity.id) do
|
||||||
|
try_render(conn, "show.json", activity: activity, for: user, as: :activity)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc "POST /api/v1/statuses/:id/mute"
|
||||||
|
def mute_conversation(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
||||||
|
with %Activity{} = activity <- Activity.get_by_id(id),
|
||||||
|
{:ok, activity} <- CommonAPI.add_mute(user, activity) do
|
||||||
|
try_render(conn, "show.json", activity: activity, for: user, as: :activity)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc "POST /api/v1/statuses/:id/unmute"
|
||||||
|
def unmute_conversation(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
||||||
|
with %Activity{} = activity <- Activity.get_by_id(id),
|
||||||
|
{:ok, activity} <- CommonAPI.remove_mute(user, activity) do
|
||||||
|
try_render(conn, "show.json", activity: activity, for: user, as: :activity)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc "GET /api/v1/statuses/:id/card"
|
||||||
|
@deprecated "https://github.com/tootsuite/mastodon/pull/11213"
|
||||||
|
def card(%{assigns: %{user: user}} = conn, %{"id" => status_id}) do
|
||||||
|
with %Activity{} = activity <- Activity.get_by_id(status_id),
|
||||||
|
true <- Visibility.visible_for_user?(activity, user) do
|
||||||
|
data = Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity)
|
||||||
|
render(conn, "card.json", data)
|
||||||
|
else
|
||||||
|
_ -> render_error(conn, :not_found, "Record not found")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc "GET /api/v1/statuses/:id/favourited_by"
|
||||||
|
def favourited_by(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
||||||
|
with %Activity{} = activity <- Activity.get_by_id_with_object(id),
|
||||||
|
{:visible, true} <- {:visible, Visibility.visible_for_user?(activity, user)},
|
||||||
|
%Object{data: %{"likes" => likes}} <- Object.normalize(activity) do
|
||||||
|
users =
|
||||||
|
User
|
||||||
|
|> Ecto.Query.where([u], u.ap_id in ^likes)
|
||||||
|
|> Repo.all()
|
||||||
|
|> Enum.filter(&(not User.blocks?(user, &1)))
|
||||||
|
|
||||||
|
conn
|
||||||
|
|> put_view(AccountView)
|
||||||
|
|> render("accounts.json", for: user, users: users, as: :user)
|
||||||
|
else
|
||||||
|
{:visible, false} -> {:error, :not_found}
|
||||||
|
_ -> json(conn, [])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc "GET /api/v1/statuses/:id/reblogged_by"
|
||||||
|
def reblogged_by(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
||||||
|
with %Activity{} = activity <- Activity.get_by_id_with_object(id),
|
||||||
|
{:visible, true} <- {:visible, Visibility.visible_for_user?(activity, user)},
|
||||||
|
%Object{data: %{"announcements" => announces}} <- Object.normalize(activity) do
|
||||||
|
users =
|
||||||
|
User
|
||||||
|
|> Ecto.Query.where([u], u.ap_id in ^announces)
|
||||||
|
|> Repo.all()
|
||||||
|
|> Enum.filter(&(not User.blocks?(user, &1)))
|
||||||
|
|
||||||
|
conn
|
||||||
|
|> put_view(AccountView)
|
||||||
|
|> render("accounts.json", for: user, users: users, as: :user)
|
||||||
|
else
|
||||||
|
{:visible, false} -> {:error, :not_found}
|
||||||
|
_ -> json(conn, [])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc "GET /api/v1/statuses/:id/context"
|
||||||
|
def context(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
||||||
|
with %Activity{} = activity <- Activity.get_by_id(id) do
|
||||||
|
activities =
|
||||||
|
ActivityPub.fetch_activities_for_context(activity.data["context"], %{
|
||||||
|
"blocking_user" => user,
|
||||||
|
"user" => user,
|
||||||
|
"exclude_id" => activity.id
|
||||||
|
})
|
||||||
|
|
||||||
|
render(conn, "context.json", activity: activity, activities: activities, user: user)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
@ -0,0 +1,136 @@
|
|||||||
|
# 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.TimelineController do
|
||||||
|
use Pleroma.Web, :controller
|
||||||
|
|
||||||
|
import Pleroma.Web.ControllerHelper,
|
||||||
|
only: [add_link_headers: 2, add_link_headers: 3, truthy_param?: 1]
|
||||||
|
|
||||||
|
alias Pleroma.Pagination
|
||||||
|
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||||
|
|
||||||
|
plug(:put_view, Pleroma.Web.MastodonAPI.StatusView)
|
||||||
|
|
||||||
|
# GET /api/v1/timelines/home
|
||||||
|
def home(%{assigns: %{user: user}} = conn, params) do
|
||||||
|
params =
|
||||||
|
params
|
||||||
|
|> Map.put("type", ["Create", "Announce"])
|
||||||
|
|> Map.put("blocking_user", user)
|
||||||
|
|> Map.put("muting_user", user)
|
||||||
|
|> Map.put("user", user)
|
||||||
|
|
||||||
|
recipients = [user.ap_id | user.following]
|
||||||
|
|
||||||
|
activities =
|
||||||
|
recipients
|
||||||
|
|> ActivityPub.fetch_activities(params)
|
||||||
|
|> Enum.reverse()
|
||||||
|
|
||||||
|
conn
|
||||||
|
|> add_link_headers(activities)
|
||||||
|
|> render("index.json", activities: activities, for: user, as: :activity)
|
||||||
|
end
|
||||||
|
|
||||||
|
# GET /api/v1/timelines/direct
|
||||||
|
def direct(%{assigns: %{user: user}} = conn, params) do
|
||||||
|
params =
|
||||||
|
params
|
||||||
|
|> Map.put("type", "Create")
|
||||||
|
|> Map.put("blocking_user", user)
|
||||||
|
|> Map.put("user", user)
|
||||||
|
|> Map.put(:visibility, "direct")
|
||||||
|
|
||||||
|
activities =
|
||||||
|
[user.ap_id]
|
||||||
|
|> ActivityPub.fetch_activities_query(params)
|
||||||
|
|> Pagination.fetch_paginated(params)
|
||||||
|
|
||||||
|
conn
|
||||||
|
|> add_link_headers(activities)
|
||||||
|
|> render("index.json", activities: activities, for: user, as: :activity)
|
||||||
|
end
|
||||||
|
|
||||||
|
# GET /api/v1/timelines/public
|
||||||
|
def public(%{assigns: %{user: user}} = conn, params) do
|
||||||
|
local_only = truthy_param?(params["local"])
|
||||||
|
|
||||||
|
activities =
|
||||||
|
params
|
||||||
|
|> Map.put("type", ["Create", "Announce"])
|
||||||
|
|> Map.put("local_only", local_only)
|
||||||
|
|> Map.put("blocking_user", user)
|
||||||
|
|> Map.put("muting_user", user)
|
||||||
|
|> ActivityPub.fetch_public_activities()
|
||||||
|
|> Enum.reverse()
|
||||||
|
|
||||||
|
conn
|
||||||
|
|> add_link_headers(activities, %{"local" => local_only})
|
||||||
|
|> render("index.json", activities: activities, for: user, as: :activity)
|
||||||
|
end
|
||||||
|
|
||||||
|
# GET /api/v1/timelines/tag/:tag
|
||||||
|
def hashtag(%{assigns: %{user: user}} = conn, params) do
|
||||||
|
local_only = truthy_param?(params["local"])
|
||||||
|
|
||||||
|
tags =
|
||||||
|
[params["tag"], params["any"]]
|
||||||
|
|> List.flatten()
|
||||||
|
|> Enum.uniq()
|
||||||
|
|> Enum.filter(& &1)
|
||||||
|
|> Enum.map(&String.downcase(&1))
|
||||||
|
|
||||||
|
tag_all =
|
||||||
|
params
|
||||||
|
|> Map.get("all", [])
|
||||||
|
|> Enum.map(&String.downcase(&1))
|
||||||
|
|
||||||
|
tag_reject =
|
||||||
|
params
|
||||||
|
|> Map.get("none", [])
|
||||||
|
|> Enum.map(&String.downcase(&1))
|
||||||
|
|
||||||
|
activities =
|
||||||
|
params
|
||||||
|
|> Map.put("type", "Create")
|
||||||
|
|> Map.put("local_only", local_only)
|
||||||
|
|> Map.put("blocking_user", user)
|
||||||
|
|> Map.put("muting_user", user)
|
||||||
|
|> Map.put("user", user)
|
||||||
|
|> Map.put("tag", tags)
|
||||||
|
|> Map.put("tag_all", tag_all)
|
||||||
|
|> Map.put("tag_reject", tag_reject)
|
||||||
|
|> ActivityPub.fetch_public_activities()
|
||||||
|
|> Enum.reverse()
|
||||||
|
|
||||||
|
conn
|
||||||
|
|> add_link_headers(activities, %{"local" => local_only})
|
||||||
|
|> render("index.json", activities: activities, for: user, as: :activity)
|
||||||
|
end
|
||||||
|
|
||||||
|
# GET /api/v1/timelines/list/:list_id
|
||||||
|
def list(%{assigns: %{user: user}} = conn, %{"list_id" => id} = params) do
|
||||||
|
with %Pleroma.List{title: _title, following: following} <- Pleroma.List.get(id, user) do
|
||||||
|
params =
|
||||||
|
params
|
||||||
|
|> Map.put("type", "Create")
|
||||||
|
|> Map.put("blocking_user", user)
|
||||||
|
|> Map.put("user", user)
|
||||||
|
|> Map.put("muting_user", user)
|
||||||
|
|
||||||
|
# we must filter the following list for the user to avoid leaking statuses the user
|
||||||
|
# does not actually have permission to see (for more info, peruse security issue #270).
|
||||||
|
activities =
|
||||||
|
following
|
||||||
|
|> Enum.filter(fn x -> x in user.following end)
|
||||||
|
|> ActivityPub.fetch_activities_bounded(following, params)
|
||||||
|
|> Enum.reverse()
|
||||||
|
|
||||||
|
render(conn, "index.json", activities: activities, for: user, as: :activity)
|
||||||
|
else
|
||||||
|
_e -> render_error(conn, :forbidden, "Error.")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
@ -0,0 +1,71 @@
|
|||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.PleromaAPI.SubscriptionNotificationController do
|
||||||
|
use Pleroma.Web, :controller
|
||||||
|
|
||||||
|
import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2]
|
||||||
|
|
||||||
|
alias Pleroma.Activity
|
||||||
|
alias Pleroma.SubscriptionNotification
|
||||||
|
alias Pleroma.User
|
||||||
|
alias Pleroma.Web.PleromaAPI.PleromaAPI
|
||||||
|
|
||||||
|
def index(%{assigns: %{user: user}} = conn, params) do
|
||||||
|
notifications =
|
||||||
|
user
|
||||||
|
|> PleromaAPI.get_subscription_notifications(params)
|
||||||
|
|> Enum.map(&build_notification_data/1)
|
||||||
|
|
||||||
|
conn
|
||||||
|
|> add_link_headers(notifications)
|
||||||
|
|> render("index.json", %{notifications: notifications, for: user})
|
||||||
|
end
|
||||||
|
|
||||||
|
def show(%{assigns: %{user: user}} = conn, %{"id" => id} = _params) do
|
||||||
|
with {:ok, notification} <- SubscriptionNotification.get(user, id) do
|
||||||
|
render(conn, "show.json", %{
|
||||||
|
subscription_notification: build_notification_data(notification),
|
||||||
|
for: user
|
||||||
|
})
|
||||||
|
else
|
||||||
|
{:error, reason} ->
|
||||||
|
conn
|
||||||
|
|> put_status(:forbidden)
|
||||||
|
|> json(%{"error" => reason})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def clear(%{assigns: %{user: user}} = conn, _params) do
|
||||||
|
SubscriptionNotification.clear(user)
|
||||||
|
json(conn, %{})
|
||||||
|
end
|
||||||
|
|
||||||
|
def dismiss(%{assigns: %{user: user}} = conn, %{"id" => id} = _params) do
|
||||||
|
with {:ok, _notif} <- SubscriptionNotification.dismiss(user, id) do
|
||||||
|
json(conn, %{})
|
||||||
|
else
|
||||||
|
{:error, reason} ->
|
||||||
|
conn
|
||||||
|
|> put_status(:forbidden)
|
||||||
|
|> json(%{"error" => reason})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def destroy_multiple(
|
||||||
|
%{assigns: %{user: user}} = conn,
|
||||||
|
%{"ids" => ids} = _params
|
||||||
|
) do
|
||||||
|
SubscriptionNotification.destroy_multiple(user, ids)
|
||||||
|
json(conn, %{})
|
||||||
|
end
|
||||||
|
|
||||||
|
defp build_notification_data(%{activity: %{data: data}} = notification) do
|
||||||
|
%{
|
||||||
|
notification: notification,
|
||||||
|
actor: User.get_cached_by_ap_id(data["actor"]),
|
||||||
|
parent_activity: Activity.get_create_by_object_ap_id(data["object"])
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
@ -0,0 +1,40 @@
|
|||||||
|
defmodule Pleroma.Web.PleromaAPI.PleromaAPI do
|
||||||
|
import Ecto.Query
|
||||||
|
import Ecto.Changeset
|
||||||
|
|
||||||
|
alias Pleroma.Activity
|
||||||
|
alias Pleroma.Pagination
|
||||||
|
alias Pleroma.SubscriptionNotification
|
||||||
|
|
||||||
|
def get_subscription_notifications(user, params \\ %{}) do
|
||||||
|
options = cast_params(params)
|
||||||
|
|
||||||
|
user
|
||||||
|
|> SubscriptionNotification.for_user_query(options)
|
||||||
|
|> restrict(:exclude_types, options)
|
||||||
|
|> Pagination.fetch_paginated(params)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp cast_params(params) do
|
||||||
|
param_types = %{
|
||||||
|
exclude_types: {:array, :string},
|
||||||
|
reblogs: :boolean,
|
||||||
|
with_muted: :boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
changeset = cast({%{}, param_types}, params, Map.keys(param_types))
|
||||||
|
changeset.changes
|
||||||
|
end
|
||||||
|
|
||||||
|
defp restrict(query, :exclude_types, %{exclude_types: mastodon_types = [_ | _]}) do
|
||||||
|
ap_types =
|
||||||
|
mastodon_types
|
||||||
|
|> Enum.map(&Activity.from_mastodon_notification_type/1)
|
||||||
|
|> Enum.filter(& &1)
|
||||||
|
|
||||||
|
query
|
||||||
|
|> where([q, a], not fragment("? @> ARRAY[?->>'type']::varchar[]", ^ap_types, a.data))
|
||||||
|
end
|
||||||
|
|
||||||
|
defp restrict(query, _, _), do: query
|
||||||
|
end
|
@ -0,0 +1,61 @@
|
|||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.PleromaAPI.SubscriptionNotificationView do
|
||||||
|
use Pleroma.Web, :view
|
||||||
|
|
||||||
|
alias Pleroma.Activity
|
||||||
|
alias Pleroma.Web.CommonAPI
|
||||||
|
alias Pleroma.Web.MastodonAPI.AccountView
|
||||||
|
alias Pleroma.Web.MastodonAPI.StatusView
|
||||||
|
alias Pleroma.Web.PleromaAPI.SubscriptionNotificationView
|
||||||
|
|
||||||
|
def render("index.json", %{notifications: notifications, for: user}) do
|
||||||
|
safe_render_many(notifications, SubscriptionNotificationView, "show.json", %{for: user})
|
||||||
|
end
|
||||||
|
|
||||||
|
def render("show.json", %{
|
||||||
|
subscription_notification: %{
|
||||||
|
notification: %{activity: activity} = notification,
|
||||||
|
actor: actor,
|
||||||
|
parent_activity: parent_activity
|
||||||
|
},
|
||||||
|
for: user
|
||||||
|
}) do
|
||||||
|
mastodon_type = Activity.mastodon_notification_type(activity)
|
||||||
|
|
||||||
|
response = %{
|
||||||
|
id: to_string(notification.id),
|
||||||
|
type: mastodon_type,
|
||||||
|
created_at: CommonAPI.Utils.to_masto_date(notification.inserted_at),
|
||||||
|
account: AccountView.render("account.json", %{user: actor, for: user})
|
||||||
|
}
|
||||||
|
|
||||||
|
case mastodon_type do
|
||||||
|
"mention" ->
|
||||||
|
response
|
||||||
|
|> Map.merge(%{
|
||||||
|
status: StatusView.render("show.json", %{activity: activity, for: user})
|
||||||
|
})
|
||||||
|
|
||||||
|
"favourite" ->
|
||||||
|
response
|
||||||
|
|> Map.merge(%{
|
||||||
|
status: StatusView.render("show.json", %{activity: parent_activity, for: user})
|
||||||
|
})
|
||||||
|
|
||||||
|
"reblog" ->
|
||||||
|
response
|
||||||
|
|> Map.merge(%{
|
||||||
|
status: StatusView.render("show.json", %{activity: parent_activity, for: user})
|
||||||
|
})
|
||||||
|
|
||||||
|
"follow" ->
|
||||||
|
response
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
@ -0,0 +1,15 @@
|
|||||||
|
defmodule Pleroma.Repo.Migrations.CreateSubscriptionNotifications do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
def change do
|
||||||
|
create_if_not_exists table(:subscription_notifications) do
|
||||||
|
add(:user_id, references(:users, type: :uuid, on_delete: :delete_all))
|
||||||
|
add(:activity_id, references(:activities, type: :uuid, on_delete: :delete_all))
|
||||||
|
|
||||||
|
timestamps()
|
||||||
|
end
|
||||||
|
|
||||||
|
create_if_not_exists(index(:subscription_notifications, [:user_id]))
|
||||||
|
create_if_not_exists(index(:subscription_notifications, ["id desc nulls last"]))
|
||||||
|
end
|
||||||
|
end
|
@ -0,0 +1,72 @@
|
|||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Plugs.RemoteIpTest do
|
||||||
|
use ExUnit.Case, async: true
|
||||||
|
use Plug.Test
|
||||||
|
|
||||||
|
alias Pleroma.Plugs.RemoteIp
|
||||||
|
|
||||||
|
test "disabled" do
|
||||||
|
Pleroma.Config.put(RemoteIp, enabled: false)
|
||||||
|
|
||||||
|
%{remote_ip: remote_ip} = conn(:get, "/")
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn(:get, "/")
|
||||||
|
|> put_req_header("x-forwarded-for", "1.1.1.1")
|
||||||
|
|> RemoteIp.call(nil)
|
||||||
|
|
||||||
|
assert conn.remote_ip == remote_ip
|
||||||
|
end
|
||||||
|
|
||||||
|
test "enabled" do
|
||||||
|
Pleroma.Config.put(RemoteIp, enabled: true)
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn(:get, "/")
|
||||||
|
|> put_req_header("x-forwarded-for", "1.1.1.1")
|
||||||
|
|> RemoteIp.call(nil)
|
||||||
|
|
||||||
|
assert conn.remote_ip == {1, 1, 1, 1}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "custom headers" do
|
||||||
|
Pleroma.Config.put(RemoteIp, enabled: true, headers: ["cf-connecting-ip"])
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn(:get, "/")
|
||||||
|
|> put_req_header("x-forwarded-for", "1.1.1.1")
|
||||||
|
|> RemoteIp.call(nil)
|
||||||
|
|
||||||
|
refute conn.remote_ip == {1, 1, 1, 1}
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn(:get, "/")
|
||||||
|
|> put_req_header("cf-connecting-ip", "1.1.1.1")
|
||||||
|
|> RemoteIp.call(nil)
|
||||||
|
|
||||||
|
assert conn.remote_ip == {1, 1, 1, 1}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "custom proxies" do
|
||||||
|
Pleroma.Config.put(RemoteIp, enabled: true)
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn(:get, "/")
|
||||||
|
|> put_req_header("x-forwarded-for", "173.245.48.1, 1.1.1.1, 173.245.48.2")
|
||||||
|
|> RemoteIp.call(nil)
|
||||||
|
|
||||||
|
refute conn.remote_ip == {1, 1, 1, 1}
|
||||||
|
|
||||||
|
Pleroma.Config.put([RemoteIp, :proxies], ["173.245.48.0/20"])
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn(:get, "/")
|
||||||
|
|> put_req_header("x-forwarded-for", "173.245.48.1, 1.1.1.1, 173.245.48.2")
|
||||||
|
|> RemoteIp.call(nil)
|
||||||
|
|
||||||
|
assert conn.remote_ip == {1, 1, 1, 1}
|
||||||
|
end
|
||||||
|
end
|
@ -0,0 +1,51 @@
|
|||||||
|
# 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.DomainBlockControllerTest do
|
||||||
|
use Pleroma.Web.ConnCase, async: true
|
||||||
|
|
||||||
|
alias Pleroma.User
|
||||||
|
|
||||||
|
import Pleroma.Factory
|
||||||
|
|
||||||
|
test "blocking / unblocking a domain", %{conn: conn} do
|
||||||
|
user = insert(:user)
|
||||||
|
other_user = insert(:user, %{ap_id: "https://dogwhistle.zone/@pundit"})
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> post("/api/v1/domain_blocks", %{"domain" => "dogwhistle.zone"})
|
||||||
|
|
||||||
|
assert %{} = json_response(conn, 200)
|
||||||
|
user = User.get_cached_by_ap_id(user.ap_id)
|
||||||
|
assert User.blocks?(user, other_user)
|
||||||
|
|
||||||
|
conn =
|
||||||
|
build_conn()
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> delete("/api/v1/domain_blocks", %{"domain" => "dogwhistle.zone"})
|
||||||
|
|
||||||
|
assert %{} = json_response(conn, 200)
|
||||||
|
user = User.get_cached_by_ap_id(user.ap_id)
|
||||||
|
refute User.blocks?(user, other_user)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "getting a list of domain blocks", %{conn: conn} do
|
||||||
|
user = insert(:user)
|
||||||
|
|
||||||
|
{:ok, user} = User.block_domain(user, "bad.site")
|
||||||
|
{:ok, user} = User.block_domain(user, "even.worse.site")
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> get("/api/v1/domain_blocks")
|
||||||
|
|
||||||
|
domain_blocks = json_response(conn, 200)
|
||||||
|
|
||||||
|
assert "bad.site" in domain_blocks
|
||||||
|
assert "even.worse.site" in domain_blocks
|
||||||
|
end
|
||||||
|
end
|
@ -0,0 +1,137 @@
|
|||||||
|
# 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.FilterControllerTest do
|
||||||
|
use Pleroma.Web.ConnCase, async: true
|
||||||
|
|
||||||
|
alias Pleroma.Web.MastodonAPI.FilterView
|
||||||
|
|
||||||
|
import Pleroma.Factory
|
||||||
|
|
||||||
|
test "creating a filter", %{conn: conn} do
|
||||||
|
user = insert(:user)
|
||||||
|
|
||||||
|
filter = %Pleroma.Filter{
|
||||||
|
phrase: "knights",
|
||||||
|
context: ["home"]
|
||||||
|
}
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> post("/api/v1/filters", %{"phrase" => filter.phrase, context: filter.context})
|
||||||
|
|
||||||
|
assert response = json_response(conn, 200)
|
||||||
|
assert response["phrase"] == filter.phrase
|
||||||
|
assert response["context"] == filter.context
|
||||||
|
assert response["irreversible"] == false
|
||||||
|
assert response["id"] != nil
|
||||||
|
assert response["id"] != ""
|
||||||
|
end
|
||||||
|
|
||||||
|
test "fetching a list of filters", %{conn: conn} do
|
||||||
|
user = insert(:user)
|
||||||
|
|
||||||
|
query_one = %Pleroma.Filter{
|
||||||
|
user_id: user.id,
|
||||||
|
filter_id: 1,
|
||||||
|
phrase: "knights",
|
||||||
|
context: ["home"]
|
||||||
|
}
|
||||||
|
|
||||||
|
query_two = %Pleroma.Filter{
|
||||||
|
user_id: user.id,
|
||||||
|
filter_id: 2,
|
||||||
|
phrase: "who",
|
||||||
|
context: ["home"]
|
||||||
|
}
|
||||||
|
|
||||||
|
{:ok, filter_one} = Pleroma.Filter.create(query_one)
|
||||||
|
{:ok, filter_two} = Pleroma.Filter.create(query_two)
|
||||||
|
|
||||||
|
response =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> get("/api/v1/filters")
|
||||||
|
|> json_response(200)
|
||||||
|
|
||||||
|
assert response ==
|
||||||
|
render_json(
|
||||||
|
FilterView,
|
||||||
|
"filters.json",
|
||||||
|
filters: [filter_two, filter_one]
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "get a filter", %{conn: conn} do
|
||||||
|
user = insert(:user)
|
||||||
|
|
||||||
|
query = %Pleroma.Filter{
|
||||||
|
user_id: user.id,
|
||||||
|
filter_id: 2,
|
||||||
|
phrase: "knight",
|
||||||
|
context: ["home"]
|
||||||
|
}
|
||||||
|
|
||||||
|
{:ok, filter} = Pleroma.Filter.create(query)
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> get("/api/v1/filters/#{filter.filter_id}")
|
||||||
|
|
||||||
|
assert _response = json_response(conn, 200)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "update a filter", %{conn: conn} do
|
||||||
|
user = insert(:user)
|
||||||
|
|
||||||
|
query = %Pleroma.Filter{
|
||||||
|
user_id: user.id,
|
||||||
|
filter_id: 2,
|
||||||
|
phrase: "knight",
|
||||||
|
context: ["home"]
|
||||||
|
}
|
||||||
|
|
||||||
|
{:ok, _filter} = Pleroma.Filter.create(query)
|
||||||
|
|
||||||
|
new = %Pleroma.Filter{
|
||||||
|
phrase: "nii",
|
||||||
|
context: ["home"]
|
||||||
|
}
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> put("/api/v1/filters/#{query.filter_id}", %{
|
||||||
|
phrase: new.phrase,
|
||||||
|
context: new.context
|
||||||
|
})
|
||||||
|
|
||||||
|
assert response = json_response(conn, 200)
|
||||||
|
assert response["phrase"] == new.phrase
|
||||||
|
assert response["context"] == new.context
|
||||||
|
end
|
||||||
|
|
||||||
|
test "delete a filter", %{conn: conn} do
|
||||||
|
user = insert(:user)
|
||||||
|
|
||||||
|
query = %Pleroma.Filter{
|
||||||
|
user_id: user.id,
|
||||||
|
filter_id: 2,
|
||||||
|
phrase: "knight",
|
||||||
|
context: ["home"]
|
||||||
|
}
|
||||||
|
|
||||||
|
{:ok, filter} = Pleroma.Filter.create(query)
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> delete("/api/v1/filters/#{filter.filter_id}")
|
||||||
|
|
||||||
|
assert response = json_response(conn, 200)
|
||||||
|
assert response == %{}
|
||||||
|
end
|
||||||
|
end
|
@ -0,0 +1,81 @@
|
|||||||
|
# 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.FollowRequestControllerTest do
|
||||||
|
use Pleroma.Web.ConnCase
|
||||||
|
|
||||||
|
alias Pleroma.User
|
||||||
|
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||||
|
|
||||||
|
import Pleroma.Factory
|
||||||
|
|
||||||
|
describe "locked accounts" do
|
||||||
|
test "/api/v1/follow_requests works" do
|
||||||
|
user = insert(:user, %{info: %User.Info{locked: true}})
|
||||||
|
other_user = insert(:user)
|
||||||
|
|
||||||
|
{:ok, _activity} = ActivityPub.follow(other_user, user)
|
||||||
|
|
||||||
|
user = User.get_cached_by_id(user.id)
|
||||||
|
other_user = User.get_cached_by_id(other_user.id)
|
||||||
|
|
||||||
|
assert User.following?(other_user, user) == false
|
||||||
|
|
||||||
|
conn =
|
||||||
|
build_conn()
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> get("/api/v1/follow_requests")
|
||||||
|
|
||||||
|
assert [relationship] = json_response(conn, 200)
|
||||||
|
assert to_string(other_user.id) == relationship["id"]
|
||||||
|
end
|
||||||
|
|
||||||
|
test "/api/v1/follow_requests/:id/authorize works" do
|
||||||
|
user = insert(:user, %{info: %User.Info{locked: true}})
|
||||||
|
other_user = insert(:user)
|
||||||
|
|
||||||
|
{:ok, _activity} = ActivityPub.follow(other_user, user)
|
||||||
|
|
||||||
|
user = User.get_cached_by_id(user.id)
|
||||||
|
other_user = User.get_cached_by_id(other_user.id)
|
||||||
|
|
||||||
|
assert User.following?(other_user, user) == false
|
||||||
|
|
||||||
|
conn =
|
||||||
|
build_conn()
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> post("/api/v1/follow_requests/#{other_user.id}/authorize")
|
||||||
|
|
||||||
|
assert relationship = json_response(conn, 200)
|
||||||
|
assert to_string(other_user.id) == relationship["id"]
|
||||||
|
|
||||||
|
user = User.get_cached_by_id(user.id)
|
||||||
|
other_user = User.get_cached_by_id(other_user.id)
|
||||||
|
|
||||||
|
assert User.following?(other_user, user) == true
|
||||||
|
end
|
||||||
|
|
||||||
|
test "/api/v1/follow_requests/:id/reject works" do
|
||||||
|
user = insert(:user, %{info: %User.Info{locked: true}})
|
||||||
|
other_user = insert(:user)
|
||||||
|
|
||||||
|
{:ok, _activity} = ActivityPub.follow(other_user, user)
|
||||||
|
|
||||||
|
user = User.get_cached_by_id(user.id)
|
||||||
|
|
||||||
|
conn =
|
||||||
|
build_conn()
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> post("/api/v1/follow_requests/#{other_user.id}/reject")
|
||||||
|
|
||||||
|
assert relationship = json_response(conn, 200)
|
||||||
|
assert to_string(other_user.id) == relationship["id"]
|
||||||
|
|
||||||
|
user = User.get_cached_by_id(user.id)
|
||||||
|
other_user = User.get_cached_by_id(other_user.id)
|
||||||
|
|
||||||
|
assert User.following?(other_user, user) == false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
@ -0,0 +1,113 @@
|
|||||||
|
# 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.ScheduledActivityControllerTest do
|
||||||
|
use Pleroma.Web.ConnCase, async: true
|
||||||
|
|
||||||
|
alias Pleroma.Repo
|
||||||
|
alias Pleroma.ScheduledActivity
|
||||||
|
|
||||||
|
import Pleroma.Factory
|
||||||
|
|
||||||
|
test "shows scheduled activities", %{conn: conn} do
|
||||||
|
user = insert(:user)
|
||||||
|
scheduled_activity_id1 = insert(:scheduled_activity, user: user).id |> to_string()
|
||||||
|
scheduled_activity_id2 = insert(:scheduled_activity, user: user).id |> to_string()
|
||||||
|
scheduled_activity_id3 = insert(:scheduled_activity, user: user).id |> to_string()
|
||||||
|
scheduled_activity_id4 = insert(:scheduled_activity, user: user).id |> to_string()
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user)
|
||||||
|
|
||||||
|
# min_id
|
||||||
|
conn_res =
|
||||||
|
conn
|
||||||
|
|> get("/api/v1/scheduled_statuses?limit=2&min_id=#{scheduled_activity_id1}")
|
||||||
|
|
||||||
|
result = json_response(conn_res, 200)
|
||||||
|
assert [%{"id" => ^scheduled_activity_id3}, %{"id" => ^scheduled_activity_id2}] = result
|
||||||
|
|
||||||
|
# since_id
|
||||||
|
conn_res =
|
||||||
|
conn
|
||||||
|
|> get("/api/v1/scheduled_statuses?limit=2&since_id=#{scheduled_activity_id1}")
|
||||||
|
|
||||||
|
result = json_response(conn_res, 200)
|
||||||
|
assert [%{"id" => ^scheduled_activity_id4}, %{"id" => ^scheduled_activity_id3}] = result
|
||||||
|
|
||||||
|
# max_id
|
||||||
|
conn_res =
|
||||||
|
conn
|
||||||
|
|> get("/api/v1/scheduled_statuses?limit=2&max_id=#{scheduled_activity_id4}")
|
||||||
|
|
||||||
|
result = json_response(conn_res, 200)
|
||||||
|
assert [%{"id" => ^scheduled_activity_id3}, %{"id" => ^scheduled_activity_id2}] = result
|
||||||
|
end
|
||||||
|
|
||||||
|
test "shows a scheduled activity", %{conn: conn} do
|
||||||
|
user = insert(:user)
|
||||||
|
scheduled_activity = insert(:scheduled_activity, user: user)
|
||||||
|
|
||||||
|
res_conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> get("/api/v1/scheduled_statuses/#{scheduled_activity.id}")
|
||||||
|
|
||||||
|
assert %{"id" => scheduled_activity_id} = json_response(res_conn, 200)
|
||||||
|
assert scheduled_activity_id == scheduled_activity.id |> to_string()
|
||||||
|
|
||||||
|
res_conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> get("/api/v1/scheduled_statuses/404")
|
||||||
|
|
||||||
|
assert %{"error" => "Record not found"} = json_response(res_conn, 404)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "updates a scheduled activity", %{conn: conn} do
|
||||||
|
user = insert(:user)
|
||||||
|
scheduled_activity = insert(:scheduled_activity, user: user)
|
||||||
|
|
||||||
|
new_scheduled_at =
|
||||||
|
NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(120), :millisecond)
|
||||||
|
|
||||||
|
res_conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> put("/api/v1/scheduled_statuses/#{scheduled_activity.id}", %{
|
||||||
|
scheduled_at: new_scheduled_at
|
||||||
|
})
|
||||||
|
|
||||||
|
assert %{"scheduled_at" => expected_scheduled_at} = json_response(res_conn, 200)
|
||||||
|
assert expected_scheduled_at == Pleroma.Web.CommonAPI.Utils.to_masto_date(new_scheduled_at)
|
||||||
|
|
||||||
|
res_conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> put("/api/v1/scheduled_statuses/404", %{scheduled_at: new_scheduled_at})
|
||||||
|
|
||||||
|
assert %{"error" => "Record not found"} = json_response(res_conn, 404)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "deletes a scheduled activity", %{conn: conn} do
|
||||||
|
user = insert(:user)
|
||||||
|
scheduled_activity = insert(:scheduled_activity, user: user)
|
||||||
|
|
||||||
|
res_conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> delete("/api/v1/scheduled_statuses/#{scheduled_activity.id}")
|
||||||
|
|
||||||
|
assert %{} = json_response(res_conn, 200)
|
||||||
|
assert nil == Repo.get(ScheduledActivity, scheduled_activity.id)
|
||||||
|
|
||||||
|
res_conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> delete("/api/v1/scheduled_statuses/#{scheduled_activity.id}")
|
||||||
|
|
||||||
|
assert %{"error" => "Record not found"} = json_response(res_conn, 404)
|
||||||
|
end
|
||||||
|
end
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,291 @@
|
|||||||
|
# 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.TimelineControllerTest do
|
||||||
|
use Pleroma.Web.ConnCase
|
||||||
|
|
||||||
|
import Pleroma.Factory
|
||||||
|
import Tesla.Mock
|
||||||
|
|
||||||
|
alias Pleroma.Config
|
||||||
|
alias Pleroma.User
|
||||||
|
alias Pleroma.Web.CommonAPI
|
||||||
|
alias Pleroma.Web.OStatus
|
||||||
|
|
||||||
|
clear_config([:instance, :public])
|
||||||
|
|
||||||
|
setup do
|
||||||
|
mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
|
||||||
|
:ok
|
||||||
|
end
|
||||||
|
|
||||||
|
test "the home timeline", %{conn: conn} do
|
||||||
|
user = insert(:user)
|
||||||
|
following = insert(:user)
|
||||||
|
|
||||||
|
{:ok, _activity} = CommonAPI.post(following, %{"status" => "test"})
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> get("/api/v1/timelines/home")
|
||||||
|
|
||||||
|
assert Enum.empty?(json_response(conn, :ok))
|
||||||
|
|
||||||
|
{:ok, user} = User.follow(user, following)
|
||||||
|
|
||||||
|
conn =
|
||||||
|
build_conn()
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> get("/api/v1/timelines/home")
|
||||||
|
|
||||||
|
assert [%{"content" => "test"}] = json_response(conn, :ok)
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "public" do
|
||||||
|
@tag capture_log: true
|
||||||
|
test "the public timeline", %{conn: conn} do
|
||||||
|
following = insert(:user)
|
||||||
|
|
||||||
|
{:ok, _activity} = CommonAPI.post(following, %{"status" => "test"})
|
||||||
|
|
||||||
|
{:ok, [_activity]} =
|
||||||
|
OStatus.fetch_activity_from_url("https://shitposter.club/notice/2827873")
|
||||||
|
|
||||||
|
conn = get(conn, "/api/v1/timelines/public", %{"local" => "False"})
|
||||||
|
|
||||||
|
assert length(json_response(conn, :ok)) == 2
|
||||||
|
|
||||||
|
conn = get(build_conn(), "/api/v1/timelines/public", %{"local" => "True"})
|
||||||
|
|
||||||
|
assert [%{"content" => "test"}] = json_response(conn, :ok)
|
||||||
|
|
||||||
|
conn = get(build_conn(), "/api/v1/timelines/public", %{"local" => "1"})
|
||||||
|
|
||||||
|
assert [%{"content" => "test"}] = json_response(conn, :ok)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "the public timeline when public is set to false", %{conn: conn} do
|
||||||
|
Config.put([:instance, :public], false)
|
||||||
|
|
||||||
|
assert %{"error" => "This resource requires authentication."} ==
|
||||||
|
conn
|
||||||
|
|> get("/api/v1/timelines/public", %{"local" => "False"})
|
||||||
|
|> json_response(:forbidden)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "the public timeline includes only public statuses for an authenticated user" do
|
||||||
|
user = insert(:user)
|
||||||
|
|
||||||
|
conn =
|
||||||
|
build_conn()
|
||||||
|
|> assign(:user, user)
|
||||||
|
|
||||||
|
{:ok, _activity} = CommonAPI.post(user, %{"status" => "test"})
|
||||||
|
{:ok, _activity} = CommonAPI.post(user, %{"status" => "test", "visibility" => "private"})
|
||||||
|
{:ok, _activity} = CommonAPI.post(user, %{"status" => "test", "visibility" => "unlisted"})
|
||||||
|
{:ok, _activity} = CommonAPI.post(user, %{"status" => "test", "visibility" => "direct"})
|
||||||
|
|
||||||
|
res_conn = get(conn, "/api/v1/timelines/public")
|
||||||
|
assert length(json_response(res_conn, 200)) == 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "direct" do
|
||||||
|
test "direct timeline", %{conn: conn} do
|
||||||
|
user_one = insert(:user)
|
||||||
|
user_two = insert(:user)
|
||||||
|
|
||||||
|
{:ok, user_two} = User.follow(user_two, user_one)
|
||||||
|
|
||||||
|
{:ok, direct} =
|
||||||
|
CommonAPI.post(user_one, %{
|
||||||
|
"status" => "Hi @#{user_two.nickname}!",
|
||||||
|
"visibility" => "direct"
|
||||||
|
})
|
||||||
|
|
||||||
|
{:ok, _follower_only} =
|
||||||
|
CommonAPI.post(user_one, %{
|
||||||
|
"status" => "Hi @#{user_two.nickname}!",
|
||||||
|
"visibility" => "private"
|
||||||
|
})
|
||||||
|
|
||||||
|
# Only direct should be visible here
|
||||||
|
res_conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user_two)
|
||||||
|
|> get("api/v1/timelines/direct")
|
||||||
|
|
||||||
|
[status] = json_response(res_conn, :ok)
|
||||||
|
|
||||||
|
assert %{"visibility" => "direct"} = status
|
||||||
|
assert status["url"] != direct.data["id"]
|
||||||
|
|
||||||
|
# User should be able to see their own direct message
|
||||||
|
res_conn =
|
||||||
|
build_conn()
|
||||||
|
|> assign(:user, user_one)
|
||||||
|
|> get("api/v1/timelines/direct")
|
||||||
|
|
||||||
|
[status] = json_response(res_conn, :ok)
|
||||||
|
|
||||||
|
assert %{"visibility" => "direct"} = status
|
||||||
|
|
||||||
|
# Both should be visible here
|
||||||
|
res_conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user_two)
|
||||||
|
|> get("api/v1/timelines/home")
|
||||||
|
|
||||||
|
[_s1, _s2] = json_response(res_conn, :ok)
|
||||||
|
|
||||||
|
# Test pagination
|
||||||
|
Enum.each(1..20, fn _ ->
|
||||||
|
{:ok, _} =
|
||||||
|
CommonAPI.post(user_one, %{
|
||||||
|
"status" => "Hi @#{user_two.nickname}!",
|
||||||
|
"visibility" => "direct"
|
||||||
|
})
|
||||||
|
end)
|
||||||
|
|
||||||
|
res_conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user_two)
|
||||||
|
|> get("api/v1/timelines/direct")
|
||||||
|
|
||||||
|
statuses = json_response(res_conn, :ok)
|
||||||
|
assert length(statuses) == 20
|
||||||
|
|
||||||
|
res_conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user_two)
|
||||||
|
|> get("api/v1/timelines/direct", %{max_id: List.last(statuses)["id"]})
|
||||||
|
|
||||||
|
[status] = json_response(res_conn, :ok)
|
||||||
|
|
||||||
|
assert status["url"] != direct.data["id"]
|
||||||
|
end
|
||||||
|
|
||||||
|
test "doesn't include DMs from blocked users", %{conn: conn} do
|
||||||
|
blocker = insert(:user)
|
||||||
|
blocked = insert(:user)
|
||||||
|
user = insert(:user)
|
||||||
|
{:ok, blocker} = User.block(blocker, blocked)
|
||||||
|
|
||||||
|
{:ok, _blocked_direct} =
|
||||||
|
CommonAPI.post(blocked, %{
|
||||||
|
"status" => "Hi @#{blocker.nickname}!",
|
||||||
|
"visibility" => "direct"
|
||||||
|
})
|
||||||
|
|
||||||
|
{:ok, direct} =
|
||||||
|
CommonAPI.post(user, %{
|
||||||
|
"status" => "Hi @#{blocker.nickname}!",
|
||||||
|
"visibility" => "direct"
|
||||||
|
})
|
||||||
|
|
||||||
|
res_conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> get("api/v1/timelines/direct")
|
||||||
|
|
||||||
|
[status] = json_response(res_conn, :ok)
|
||||||
|
assert status["id"] == direct.id
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "list" do
|
||||||
|
test "list timeline", %{conn: conn} do
|
||||||
|
user = insert(:user)
|
||||||
|
other_user = insert(:user)
|
||||||
|
{:ok, _activity_one} = CommonAPI.post(user, %{"status" => "Marisa is cute."})
|
||||||
|
{:ok, activity_two} = CommonAPI.post(other_user, %{"status" => "Marisa is cute."})
|
||||||
|
{:ok, list} = Pleroma.List.create("name", user)
|
||||||
|
{:ok, list} = Pleroma.List.follow(list, other_user)
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> get("/api/v1/timelines/list/#{list.id}")
|
||||||
|
|
||||||
|
assert [%{"id" => id}] = json_response(conn, :ok)
|
||||||
|
|
||||||
|
assert id == to_string(activity_two.id)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "list timeline does not leak non-public statuses for unfollowed users", %{conn: conn} do
|
||||||
|
user = insert(:user)
|
||||||
|
other_user = insert(:user)
|
||||||
|
{:ok, activity_one} = CommonAPI.post(other_user, %{"status" => "Marisa is cute."})
|
||||||
|
|
||||||
|
{:ok, _activity_two} =
|
||||||
|
CommonAPI.post(other_user, %{
|
||||||
|
"status" => "Marisa is cute.",
|
||||||
|
"visibility" => "private"
|
||||||
|
})
|
||||||
|
|
||||||
|
{:ok, list} = Pleroma.List.create("name", user)
|
||||||
|
{:ok, list} = Pleroma.List.follow(list, other_user)
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> get("/api/v1/timelines/list/#{list.id}")
|
||||||
|
|
||||||
|
assert [%{"id" => id}] = json_response(conn, :ok)
|
||||||
|
|
||||||
|
assert id == to_string(activity_one.id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "hashtag" do
|
||||||
|
@tag capture_log: true
|
||||||
|
test "hashtag timeline", %{conn: conn} do
|
||||||
|
following = insert(:user)
|
||||||
|
|
||||||
|
{:ok, activity} = CommonAPI.post(following, %{"status" => "test #2hu"})
|
||||||
|
|
||||||
|
{:ok, [_activity]} =
|
||||||
|
OStatus.fetch_activity_from_url("https://shitposter.club/notice/2827873")
|
||||||
|
|
||||||
|
nconn = get(conn, "/api/v1/timelines/tag/2hu")
|
||||||
|
|
||||||
|
assert [%{"id" => id}] = json_response(nconn, :ok)
|
||||||
|
|
||||||
|
assert id == to_string(activity.id)
|
||||||
|
|
||||||
|
# works for different capitalization too
|
||||||
|
nconn = get(conn, "/api/v1/timelines/tag/2HU")
|
||||||
|
|
||||||
|
assert [%{"id" => id}] = json_response(nconn, :ok)
|
||||||
|
|
||||||
|
assert id == to_string(activity.id)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "multi-hashtag timeline", %{conn: conn} do
|
||||||
|
user = insert(:user)
|
||||||
|
|
||||||
|
{:ok, activity_test} = CommonAPI.post(user, %{"status" => "#test"})
|
||||||
|
{:ok, activity_test1} = CommonAPI.post(user, %{"status" => "#test #test1"})
|
||||||
|
{:ok, activity_none} = CommonAPI.post(user, %{"status" => "#test #none"})
|
||||||
|
|
||||||
|
any_test = get(conn, "/api/v1/timelines/tag/test", %{"any" => ["test1"]})
|
||||||
|
|
||||||
|
[status_none, status_test1, status_test] = json_response(any_test, :ok)
|
||||||
|
|
||||||
|
assert to_string(activity_test.id) == status_test["id"]
|
||||||
|
assert to_string(activity_test1.id) == status_test1["id"]
|
||||||
|
assert to_string(activity_none.id) == status_none["id"]
|
||||||
|
|
||||||
|
restricted_test =
|
||||||
|
get(conn, "/api/v1/timelines/tag/test", %{"all" => ["test1"], "none" => ["none"]})
|
||||||
|
|
||||||
|
assert [status_test1] == json_response(restricted_test, :ok)
|
||||||
|
|
||||||
|
all_test = get(conn, "/api/v1/timelines/tag/test", %{"all" => ["none"]})
|
||||||
|
|
||||||
|
assert [status_none] == json_response(all_test, :ok)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,234 @@
|
|||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.PleromaAPI.SubscriptionNotificationControllerTest do
|
||||||
|
use Pleroma.Web.ConnCase
|
||||||
|
|
||||||
|
alias Pleroma.Repo
|
||||||
|
alias Pleroma.SubscriptionNotification
|
||||||
|
alias Pleroma.User
|
||||||
|
alias Pleroma.Web.CommonAPI
|
||||||
|
import Pleroma.Factory
|
||||||
|
import Tesla.Mock
|
||||||
|
|
||||||
|
setup do
|
||||||
|
mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
|
||||||
|
:ok
|
||||||
|
end
|
||||||
|
|
||||||
|
clear_config([:instance, :public])
|
||||||
|
clear_config([:rich_media, :enabled])
|
||||||
|
|
||||||
|
describe "subscription_notifications" do
|
||||||
|
setup do
|
||||||
|
user = insert(:user)
|
||||||
|
subscriber = insert(:user)
|
||||||
|
|
||||||
|
User.subscribe(subscriber, user)
|
||||||
|
|
||||||
|
{:ok, %{user: user, subscriber: subscriber}}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "list of notifications", %{conn: conn, user: user, subscriber: subscriber} do
|
||||||
|
status_text = "Hello"
|
||||||
|
{:ok, _activity} = CommonAPI.post(user, %{"status" => status_text})
|
||||||
|
path = subscription_notification_path(conn, :index)
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, subscriber)
|
||||||
|
|> get(path)
|
||||||
|
|
||||||
|
assert [%{"status" => %{"content" => response}} | _rest] = json_response(conn, 200)
|
||||||
|
assert response == status_text
|
||||||
|
end
|
||||||
|
|
||||||
|
test "getting a single notification", %{conn: conn, user: user, subscriber: subscriber} do
|
||||||
|
status_text = "Hello"
|
||||||
|
|
||||||
|
{:ok, _activity} = CommonAPI.post(user, %{"status" => status_text})
|
||||||
|
[notification] = Repo.all(SubscriptionNotification)
|
||||||
|
|
||||||
|
path = subscription_notification_path(conn, :show, notification)
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, subscriber)
|
||||||
|
|> get(path)
|
||||||
|
|
||||||
|
assert %{"status" => %{"content" => response}} = json_response(conn, 200)
|
||||||
|
assert response == status_text
|
||||||
|
end
|
||||||
|
|
||||||
|
test "dismissing a single notification also deletes it", %{
|
||||||
|
conn: conn,
|
||||||
|
user: user,
|
||||||
|
subscriber: subscriber
|
||||||
|
} do
|
||||||
|
status_text = "Hello"
|
||||||
|
{:ok, _activity} = CommonAPI.post(user, %{"status" => status_text})
|
||||||
|
|
||||||
|
[notification] = Repo.all(SubscriptionNotification)
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, subscriber)
|
||||||
|
|> post(subscription_notification_path(conn, :dismiss), %{"id" => notification.id})
|
||||||
|
|
||||||
|
assert %{} = json_response(conn, 200)
|
||||||
|
|
||||||
|
assert Repo.all(SubscriptionNotification) == []
|
||||||
|
end
|
||||||
|
|
||||||
|
test "clearing all notifications also deletes them", %{
|
||||||
|
conn: conn,
|
||||||
|
user: user,
|
||||||
|
subscriber: subscriber
|
||||||
|
} do
|
||||||
|
status_text1 = "Hello"
|
||||||
|
status_text2 = "Hello again"
|
||||||
|
{:ok, _activity1} = CommonAPI.post(user, %{"status" => status_text1})
|
||||||
|
{:ok, _activity2} = CommonAPI.post(user, %{"status" => status_text2})
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, subscriber)
|
||||||
|
|> post(subscription_notification_path(conn, :clear))
|
||||||
|
|
||||||
|
assert %{} = json_response(conn, 200)
|
||||||
|
|
||||||
|
conn =
|
||||||
|
build_conn()
|
||||||
|
|> assign(:user, subscriber)
|
||||||
|
|> get(subscription_notification_path(conn, :index))
|
||||||
|
|
||||||
|
assert json_response(conn, 200) == []
|
||||||
|
|
||||||
|
assert Repo.all(SubscriptionNotification) == []
|
||||||
|
end
|
||||||
|
|
||||||
|
test "paginates notifications using min_id, since_id, max_id, and limit", %{
|
||||||
|
conn: conn,
|
||||||
|
user: user,
|
||||||
|
subscriber: subscriber
|
||||||
|
} do
|
||||||
|
{:ok, activity1} = CommonAPI.post(user, %{"status" => "Hello 1"})
|
||||||
|
{:ok, activity2} = CommonAPI.post(user, %{"status" => "Hello 2"})
|
||||||
|
{:ok, activity3} = CommonAPI.post(user, %{"status" => "Hello 3"})
|
||||||
|
{:ok, activity4} = CommonAPI.post(user, %{"status" => "Hello 4"})
|
||||||
|
|
||||||
|
notification1_id =
|
||||||
|
Repo.get_by(SubscriptionNotification, activity_id: activity1.id).id |> to_string()
|
||||||
|
|
||||||
|
notification2_id =
|
||||||
|
Repo.get_by(SubscriptionNotification, activity_id: activity2.id).id |> to_string()
|
||||||
|
|
||||||
|
notification3_id =
|
||||||
|
Repo.get_by(SubscriptionNotification, activity_id: activity3.id).id |> to_string()
|
||||||
|
|
||||||
|
notification4_id =
|
||||||
|
Repo.get_by(SubscriptionNotification, activity_id: activity4.id).id |> to_string()
|
||||||
|
|
||||||
|
conn = assign(conn, :user, subscriber)
|
||||||
|
|
||||||
|
# min_id
|
||||||
|
conn_res =
|
||||||
|
get(
|
||||||
|
conn,
|
||||||
|
subscription_notification_path(conn, :index, %{
|
||||||
|
"limit" => 2,
|
||||||
|
"min_id" => notification1_id
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
result = json_response(conn_res, 200)
|
||||||
|
assert [%{"id" => ^notification3_id}, %{"id" => ^notification2_id}] = result
|
||||||
|
|
||||||
|
# since_id
|
||||||
|
conn_res =
|
||||||
|
get(
|
||||||
|
conn,
|
||||||
|
subscription_notification_path(conn, :index, %{
|
||||||
|
"limit" => 2,
|
||||||
|
"since_id" => notification1_id
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
result = json_response(conn_res, 200)
|
||||||
|
assert [%{"id" => ^notification4_id}, %{"id" => ^notification3_id}] = result
|
||||||
|
|
||||||
|
# max_id
|
||||||
|
conn_res =
|
||||||
|
get(
|
||||||
|
conn,
|
||||||
|
subscription_notification_path(conn, :index, %{
|
||||||
|
"limit" => 2,
|
||||||
|
"max_id" => notification4_id
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
result = json_response(conn_res, 200)
|
||||||
|
assert [%{"id" => ^notification3_id}, %{"id" => ^notification2_id}] = result
|
||||||
|
end
|
||||||
|
|
||||||
|
test "destroy multiple", %{conn: conn, user: user1, subscriber: user2} do
|
||||||
|
# mutual subscription
|
||||||
|
User.subscribe(user1, user2)
|
||||||
|
|
||||||
|
{:ok, activity1} = CommonAPI.post(user1, %{"status" => "Hello 1"})
|
||||||
|
{:ok, activity2} = CommonAPI.post(user1, %{"status" => "World 1"})
|
||||||
|
{:ok, activity3} = CommonAPI.post(user2, %{"status" => "Hello 2"})
|
||||||
|
{:ok, activity4} = CommonAPI.post(user2, %{"status" => "World 2"})
|
||||||
|
|
||||||
|
notification1_id =
|
||||||
|
Repo.get_by(SubscriptionNotification, activity_id: activity1.id).id |> to_string()
|
||||||
|
|
||||||
|
notification2_id =
|
||||||
|
Repo.get_by(SubscriptionNotification, activity_id: activity2.id).id |> to_string()
|
||||||
|
|
||||||
|
notification3_id =
|
||||||
|
Repo.get_by(SubscriptionNotification, activity_id: activity3.id).id |> to_string()
|
||||||
|
|
||||||
|
notification4_id =
|
||||||
|
Repo.get_by(SubscriptionNotification, activity_id: activity4.id).id |> to_string()
|
||||||
|
|
||||||
|
conn = assign(conn, :user, user1)
|
||||||
|
|
||||||
|
conn_res = get(conn, subscription_notification_path(conn, :index))
|
||||||
|
|
||||||
|
result = json_response(conn_res, 200)
|
||||||
|
|
||||||
|
Enum.each(result, fn %{"id" => id} ->
|
||||||
|
assert id in [notification3_id, notification4_id]
|
||||||
|
end)
|
||||||
|
|
||||||
|
conn2 = assign(conn, :user, user2)
|
||||||
|
|
||||||
|
conn_res = get(conn2, subscription_notification_path(conn, :index))
|
||||||
|
|
||||||
|
result = json_response(conn_res, 200)
|
||||||
|
|
||||||
|
Enum.each(result, fn %{"id" => id} ->
|
||||||
|
assert id in [notification1_id, notification2_id]
|
||||||
|
end)
|
||||||
|
|
||||||
|
conn_destroy =
|
||||||
|
delete(conn, subscription_notification_path(conn, :destroy_multiple), %{
|
||||||
|
"ids" => [notification3_id, notification4_id]
|
||||||
|
})
|
||||||
|
|
||||||
|
assert json_response(conn_destroy, 200) == %{}
|
||||||
|
|
||||||
|
conn_res = get(conn2, subscription_notification_path(conn, :index))
|
||||||
|
|
||||||
|
result = json_response(conn_res, 200)
|
||||||
|
|
||||||
|
Enum.each(result, fn %{"id" => id} ->
|
||||||
|
assert id in [notification1_id, notification2_id]
|
||||||
|
end)
|
||||||
|
|
||||||
|
assert length(Repo.all(SubscriptionNotification)) == 2
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in new issue