Merge branch 'conversations_three' into 'develop'
Conversations once more. See merge request pleroma/pleroma!1119
This commit is contained in:
commit
238dd72fad
75
lib/pleroma/conversation.ex
Normal file
75
lib/pleroma/conversation.ex
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Conversation do
|
||||||
|
alias Pleroma.Conversation.Participation
|
||||||
|
alias Pleroma.Repo
|
||||||
|
alias Pleroma.User
|
||||||
|
use Ecto.Schema
|
||||||
|
import Ecto.Changeset
|
||||||
|
|
||||||
|
schema "conversations" do
|
||||||
|
# This is the context ap id.
|
||||||
|
field(:ap_id, :string)
|
||||||
|
has_many(:participations, Participation)
|
||||||
|
has_many(:users, through: [:participations, :user])
|
||||||
|
|
||||||
|
timestamps()
|
||||||
|
end
|
||||||
|
|
||||||
|
def creation_cng(struct, params) do
|
||||||
|
struct
|
||||||
|
|> cast(params, [:ap_id])
|
||||||
|
|> validate_required([:ap_id])
|
||||||
|
|> unique_constraint(:ap_id)
|
||||||
|
end
|
||||||
|
|
||||||
|
def create_for_ap_id(ap_id) do
|
||||||
|
%__MODULE__{}
|
||||||
|
|> creation_cng(%{ap_id: ap_id})
|
||||||
|
|> Repo.insert(
|
||||||
|
on_conflict: [set: [updated_at: NaiveDateTime.utc_now()]],
|
||||||
|
returning: true,
|
||||||
|
conflict_target: :ap_id
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_for_ap_id(ap_id) do
|
||||||
|
Repo.get_by(__MODULE__, ap_id: ap_id)
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
This will
|
||||||
|
1. Create a conversation if there isn't one already
|
||||||
|
2. Create a participation for all the people involved who don't have one already
|
||||||
|
3. Bump all relevant participations to 'unread'
|
||||||
|
"""
|
||||||
|
def create_or_bump_for(activity) do
|
||||||
|
with true <- Pleroma.Web.ActivityPub.Visibility.is_direct?(activity),
|
||||||
|
object <- Pleroma.Object.normalize(activity),
|
||||||
|
"Create" <- activity.data["type"],
|
||||||
|
"Note" <- object.data["type"],
|
||||||
|
ap_id when is_binary(ap_id) and byte_size(ap_id) > 0 <- object.data["context"] do
|
||||||
|
{:ok, conversation} = create_for_ap_id(ap_id)
|
||||||
|
|
||||||
|
users = User.get_users_from_set(activity.recipients, false)
|
||||||
|
|
||||||
|
participations =
|
||||||
|
Enum.map(users, fn user ->
|
||||||
|
{:ok, participation} =
|
||||||
|
Participation.create_for_user_and_conversation(user, conversation)
|
||||||
|
|
||||||
|
participation
|
||||||
|
end)
|
||||||
|
|
||||||
|
{:ok,
|
||||||
|
%{
|
||||||
|
conversation
|
||||||
|
| participations: participations
|
||||||
|
}}
|
||||||
|
else
|
||||||
|
e -> {:error, e}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
81
lib/pleroma/conversation/participation.ex
Normal file
81
lib/pleroma/conversation/participation.ex
Normal file
|
@ -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.Conversation.Participation do
|
||||||
|
use Ecto.Schema
|
||||||
|
alias Pleroma.Conversation
|
||||||
|
alias Pleroma.Repo
|
||||||
|
alias Pleroma.User
|
||||||
|
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||||
|
import Ecto.Changeset
|
||||||
|
import Ecto.Query
|
||||||
|
|
||||||
|
schema "conversation_participations" do
|
||||||
|
belongs_to(:user, User, type: Pleroma.FlakeId)
|
||||||
|
belongs_to(:conversation, Conversation)
|
||||||
|
field(:read, :boolean, default: false)
|
||||||
|
field(:last_activity_id, Pleroma.FlakeId, virtual: true)
|
||||||
|
|
||||||
|
timestamps()
|
||||||
|
end
|
||||||
|
|
||||||
|
def creation_cng(struct, params) do
|
||||||
|
struct
|
||||||
|
|> cast(params, [:user_id, :conversation_id])
|
||||||
|
|> validate_required([:user_id, :conversation_id])
|
||||||
|
end
|
||||||
|
|
||||||
|
def create_for_user_and_conversation(user, conversation) do
|
||||||
|
%__MODULE__{}
|
||||||
|
|> creation_cng(%{user_id: user.id, conversation_id: conversation.id})
|
||||||
|
|> Repo.insert(
|
||||||
|
on_conflict: [set: [read: false, updated_at: NaiveDateTime.utc_now()]],
|
||||||
|
returning: true,
|
||||||
|
conflict_target: [:user_id, :conversation_id]
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def read_cng(struct, params) do
|
||||||
|
struct
|
||||||
|
|> cast(params, [:read])
|
||||||
|
|> validate_required([:read])
|
||||||
|
end
|
||||||
|
|
||||||
|
def mark_as_read(participation) do
|
||||||
|
participation
|
||||||
|
|> read_cng(%{read: true})
|
||||||
|
|> Repo.update()
|
||||||
|
end
|
||||||
|
|
||||||
|
def mark_as_unread(participation) do
|
||||||
|
participation
|
||||||
|
|> read_cng(%{read: false})
|
||||||
|
|> Repo.update()
|
||||||
|
end
|
||||||
|
|
||||||
|
def for_user(user, params \\ %{}) do
|
||||||
|
from(p in __MODULE__,
|
||||||
|
where: p.user_id == ^user.id,
|
||||||
|
order_by: [desc: p.updated_at]
|
||||||
|
)
|
||||||
|
|> Pleroma.Pagination.fetch_paginated(params)
|
||||||
|
|> Repo.preload(conversation: [:users])
|
||||||
|
end
|
||||||
|
|
||||||
|
def for_user_with_last_activity_id(user, params \\ %{}) do
|
||||||
|
for_user(user, params)
|
||||||
|
|> Enum.map(fn participation ->
|
||||||
|
activity_id =
|
||||||
|
ActivityPub.fetch_latest_activity_id_for_context(participation.conversation.ap_id, %{
|
||||||
|
"user" => user,
|
||||||
|
"blocking_user" => user
|
||||||
|
})
|
||||||
|
|
||||||
|
%{
|
||||||
|
participation
|
||||||
|
| last_activity_id: activity_id
|
||||||
|
}
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
end
|
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
||||||
alias Pleroma.Activity
|
alias Pleroma.Activity
|
||||||
|
alias Pleroma.Conversation
|
||||||
alias Pleroma.Instances
|
alias Pleroma.Instances
|
||||||
alias Pleroma.Notification
|
alias Pleroma.Notification
|
||||||
alias Pleroma.Object
|
alias Pleroma.Object
|
||||||
|
@ -141,7 +142,14 @@ def insert(map, local \\ true, fake \\ false) when is_map(map) do
|
||||||
end)
|
end)
|
||||||
|
|
||||||
Notification.create_notifications(activity)
|
Notification.create_notifications(activity)
|
||||||
|
|
||||||
|
participations =
|
||||||
|
activity
|
||||||
|
|> Conversation.create_or_bump_for()
|
||||||
|
|> get_participations()
|
||||||
|
|
||||||
stream_out(activity)
|
stream_out(activity)
|
||||||
|
stream_out_participations(participations)
|
||||||
{:ok, activity}
|
{:ok, activity}
|
||||||
else
|
else
|
||||||
%Activity{} = activity ->
|
%Activity{} = activity ->
|
||||||
|
@ -164,6 +172,19 @@ def insert(map, local \\ true, fake \\ false) when is_map(map) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp get_participations({:ok, %{participations: participations}}), do: participations
|
||||||
|
defp get_participations(_), do: []
|
||||||
|
|
||||||
|
def stream_out_participations(participations) do
|
||||||
|
participations =
|
||||||
|
participations
|
||||||
|
|> Repo.preload(:user)
|
||||||
|
|
||||||
|
Enum.each(participations, fn participation ->
|
||||||
|
Pleroma.Web.Streamer.stream("participation", participation)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
def stream_out(activity) do
|
def stream_out(activity) do
|
||||||
public = "https://www.w3.org/ns/activitystreams#Public"
|
public = "https://www.w3.org/ns/activitystreams#Public"
|
||||||
|
|
||||||
|
@ -195,6 +216,7 @@ def stream_out(activity) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
|
# TODO: Write test, replace with visibility test
|
||||||
if !Enum.member?(activity.data["cc"] || [], public) &&
|
if !Enum.member?(activity.data["cc"] || [], public) &&
|
||||||
!Enum.member?(
|
!Enum.member?(
|
||||||
activity.data["to"],
|
activity.data["to"],
|
||||||
|
@ -457,35 +479,44 @@ def flag(
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def fetch_activities_for_context(context, opts \\ %{}) do
|
defp fetch_activities_for_context_query(context, opts) do
|
||||||
public = ["https://www.w3.org/ns/activitystreams#Public"]
|
public = ["https://www.w3.org/ns/activitystreams#Public"]
|
||||||
|
|
||||||
recipients =
|
recipients =
|
||||||
if opts["user"], do: [opts["user"].ap_id | opts["user"].following] ++ public, else: public
|
if opts["user"], do: [opts["user"].ap_id | opts["user"].following] ++ public, else: public
|
||||||
|
|
||||||
query = from(activity in Activity)
|
from(activity in Activity)
|
||||||
|
|
||||||
query =
|
|
||||||
query
|
|
||||||
|> restrict_blocked(opts)
|
|> restrict_blocked(opts)
|
||||||
|> restrict_recipients(recipients, opts["user"])
|
|> restrict_recipients(recipients, opts["user"])
|
||||||
|
|> where(
|
||||||
query =
|
[activity],
|
||||||
from(
|
|
||||||
activity in query,
|
|
||||||
where:
|
|
||||||
fragment(
|
fragment(
|
||||||
"?->>'type' = ? and ?->>'context' = ?",
|
"?->>'type' = ? and ?->>'context' = ?",
|
||||||
activity.data,
|
activity.data,
|
||||||
"Create",
|
"Create",
|
||||||
activity.data,
|
activity.data,
|
||||||
^context
|
^context
|
||||||
),
|
|
||||||
order_by: [desc: :id]
|
|
||||||
)
|
)
|
||||||
|> Activity.with_preloaded_object()
|
)
|
||||||
|
|> order_by([activity], desc: activity.id)
|
||||||
|
end
|
||||||
|
|
||||||
Repo.all(query)
|
@spec fetch_activities_for_context(String.t(), keyword() | map()) :: [Activity.t()]
|
||||||
|
def fetch_activities_for_context(context, opts \\ %{}) do
|
||||||
|
context
|
||||||
|
|> fetch_activities_for_context_query(opts)
|
||||||
|
|> Activity.with_preloaded_object()
|
||||||
|
|> Repo.all()
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec fetch_latest_activity_id_for_context(String.t(), keyword() | map()) ::
|
||||||
|
Pleroma.FlakeId.t() | nil
|
||||||
|
def fetch_latest_activity_id_for_context(context, opts \\ %{}) do
|
||||||
|
context
|
||||||
|
|> fetch_activities_for_context_query(opts)
|
||||||
|
|> limit(1)
|
||||||
|
|> select([a], a.id)
|
||||||
|
|> Repo.one()
|
||||||
end
|
end
|
||||||
|
|
||||||
def fetch_public_activities(opts \\ %{}) do
|
def fetch_public_activities(opts \\ %{}) do
|
||||||
|
|
|
@ -8,6 +8,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
||||||
alias Pleroma.Activity
|
alias Pleroma.Activity
|
||||||
alias Pleroma.Bookmark
|
alias Pleroma.Bookmark
|
||||||
alias Pleroma.Config
|
alias Pleroma.Config
|
||||||
|
alias Pleroma.Conversation.Participation
|
||||||
alias Pleroma.Filter
|
alias Pleroma.Filter
|
||||||
alias Pleroma.Formatter
|
alias Pleroma.Formatter
|
||||||
alias Pleroma.Notification
|
alias Pleroma.Notification
|
||||||
|
@ -24,6 +25,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
||||||
alias Pleroma.Web.CommonAPI
|
alias Pleroma.Web.CommonAPI
|
||||||
alias Pleroma.Web.MastodonAPI.AccountView
|
alias Pleroma.Web.MastodonAPI.AccountView
|
||||||
alias Pleroma.Web.MastodonAPI.AppView
|
alias Pleroma.Web.MastodonAPI.AppView
|
||||||
|
alias Pleroma.Web.MastodonAPI.ConversationView
|
||||||
alias Pleroma.Web.MastodonAPI.FilterView
|
alias Pleroma.Web.MastodonAPI.FilterView
|
||||||
alias Pleroma.Web.MastodonAPI.ListView
|
alias Pleroma.Web.MastodonAPI.ListView
|
||||||
alias Pleroma.Web.MastodonAPI.MastodonAPI
|
alias Pleroma.Web.MastodonAPI.MastodonAPI
|
||||||
|
@ -165,7 +167,7 @@ def user(%{assigns: %{user: for_user}} = conn, %{"id" => nickname_or_id}) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@mastodon_api_level "2.5.0"
|
@mastodon_api_level "2.6.5"
|
||||||
|
|
||||||
def masto_instance(conn, _params) do
|
def masto_instance(conn, _params) do
|
||||||
instance = Config.get(:instance)
|
instance = Config.get(:instance)
|
||||||
|
@ -1712,6 +1714,31 @@ def reports(%{assigns: %{user: user}} = conn, params) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def conversations(%{assigns: %{user: user}} = conn, params) do
|
||||||
|
participations = Participation.for_user_with_last_activity_id(user, params)
|
||||||
|
|
||||||
|
conversations =
|
||||||
|
Enum.map(participations, fn participation ->
|
||||||
|
ConversationView.render("participation.json", %{participation: participation, user: user})
|
||||||
|
end)
|
||||||
|
|
||||||
|
conn
|
||||||
|
|> add_link_headers(:conversations, participations)
|
||||||
|
|> json(conversations)
|
||||||
|
end
|
||||||
|
|
||||||
|
def conversation_read(%{assigns: %{user: user}} = conn, %{"id" => participation_id}) do
|
||||||
|
with %Participation{} = participation <-
|
||||||
|
Repo.get_by(Participation, id: participation_id, user_id: user.id),
|
||||||
|
{:ok, participation} <- Participation.mark_as_read(participation) do
|
||||||
|
participation_view =
|
||||||
|
ConversationView.render("participation.json", %{participation: participation, user: user})
|
||||||
|
|
||||||
|
conn
|
||||||
|
|> json(participation_view)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def try_render(conn, target, params)
|
def try_render(conn, target, params)
|
||||||
when is_binary(target) do
|
when is_binary(target) do
|
||||||
res = render(conn, target, params)
|
res = render(conn, target, params)
|
||||||
|
|
38
lib/pleroma/web/mastodon_api/views/conversation_view.ex
Normal file
38
lib/pleroma/web/mastodon_api/views/conversation_view.ex
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
defmodule Pleroma.Web.MastodonAPI.ConversationView do
|
||||||
|
use Pleroma.Web, :view
|
||||||
|
|
||||||
|
alias Pleroma.Activity
|
||||||
|
alias Pleroma.Repo
|
||||||
|
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||||
|
alias Pleroma.Web.MastodonAPI.AccountView
|
||||||
|
alias Pleroma.Web.MastodonAPI.StatusView
|
||||||
|
|
||||||
|
def render("participation.json", %{participation: participation, user: user}) do
|
||||||
|
participation = Repo.preload(participation, conversation: :users)
|
||||||
|
|
||||||
|
last_activity_id =
|
||||||
|
with nil <- participation.last_activity_id do
|
||||||
|
ActivityPub.fetch_latest_activity_id_for_context(participation.conversation.ap_id, %{
|
||||||
|
"user" => user,
|
||||||
|
"blocking_user" => user
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
activity = Activity.get_by_id_with_object(last_activity_id)
|
||||||
|
|
||||||
|
last_status = StatusView.render("status.json", %{activity: activity, for: user})
|
||||||
|
|
||||||
|
accounts =
|
||||||
|
AccountView.render("accounts.json", %{
|
||||||
|
users: participation.conversation.users,
|
||||||
|
as: :user
|
||||||
|
})
|
||||||
|
|
||||||
|
%{
|
||||||
|
id: participation.id |> to_string(),
|
||||||
|
accounts: accounts,
|
||||||
|
unread: !participation.read,
|
||||||
|
last_status: last_status
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
|
@ -276,6 +276,9 @@ defmodule Pleroma.Web.Router do
|
||||||
|
|
||||||
get("/suggestions", MastodonAPIController, :suggestions)
|
get("/suggestions", MastodonAPIController, :suggestions)
|
||||||
|
|
||||||
|
get("/conversations", MastodonAPIController, :conversations)
|
||||||
|
post("/conversations/:id/read", MastodonAPIController, :conversation_read)
|
||||||
|
|
||||||
get("/endorsements", MastodonAPIController, :empty_array)
|
get("/endorsements", MastodonAPIController, :empty_array)
|
||||||
|
|
||||||
get("/pleroma/flavour", MastodonAPIController, :get_flavour)
|
get("/pleroma/flavour", MastodonAPIController, :get_flavour)
|
||||||
|
|
|
@ -6,6 +6,7 @@ defmodule Pleroma.Web.Streamer do
|
||||||
use GenServer
|
use GenServer
|
||||||
require Logger
|
require Logger
|
||||||
alias Pleroma.Activity
|
alias Pleroma.Activity
|
||||||
|
alias Pleroma.Conversation.Participation
|
||||||
alias Pleroma.Notification
|
alias Pleroma.Notification
|
||||||
alias Pleroma.Object
|
alias Pleroma.Object
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
|
@ -71,6 +72,15 @@ def handle_cast(%{action: :stream, topic: "direct", item: item}, topics) do
|
||||||
{:noreply, topics}
|
{:noreply, topics}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def handle_cast(%{action: :stream, topic: "participation", item: participation}, topics) do
|
||||||
|
user_topic = "direct:#{participation.user_id}"
|
||||||
|
Logger.debug("Trying to push a conversation participation to #{user_topic}\n\n")
|
||||||
|
|
||||||
|
push_to_socket(topics, user_topic, participation)
|
||||||
|
|
||||||
|
{:noreply, topics}
|
||||||
|
end
|
||||||
|
|
||||||
def handle_cast(%{action: :stream, topic: "list", item: item}, topics) do
|
def handle_cast(%{action: :stream, topic: "list", item: item}, topics) do
|
||||||
# filter the recipient list if the activity is not public, see #270.
|
# filter the recipient list if the activity is not public, see #270.
|
||||||
recipient_lists =
|
recipient_lists =
|
||||||
|
@ -192,6 +202,19 @@ defp represent_update(%Activity{} = activity) do
|
||||||
|> Jason.encode!()
|
|> Jason.encode!()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def represent_conversation(%Participation{} = participation) do
|
||||||
|
%{
|
||||||
|
event: "conversation",
|
||||||
|
payload:
|
||||||
|
Pleroma.Web.MastodonAPI.ConversationView.render("participation.json", %{
|
||||||
|
participation: participation,
|
||||||
|
user: participation.user
|
||||||
|
})
|
||||||
|
|> Jason.encode!()
|
||||||
|
}
|
||||||
|
|> Jason.encode!()
|
||||||
|
end
|
||||||
|
|
||||||
def push_to_socket(topics, topic, %Activity{data: %{"type" => "Announce"}} = item) do
|
def push_to_socket(topics, topic, %Activity{data: %{"type" => "Announce"}} = item) do
|
||||||
Enum.each(topics[topic] || [], fn socket ->
|
Enum.each(topics[topic] || [], fn socket ->
|
||||||
# Get the current user so we have up-to-date blocks etc.
|
# Get the current user so we have up-to-date blocks etc.
|
||||||
|
@ -214,6 +237,12 @@ def push_to_socket(topics, topic, %Activity{data: %{"type" => "Announce"}} = ite
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def push_to_socket(topics, topic, %Participation{} = participation) do
|
||||||
|
Enum.each(topics[topic] || [], fn socket ->
|
||||||
|
send(socket.transport_pid, {:text, represent_conversation(participation)})
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
def push_to_socket(topics, topic, %Activity{
|
def push_to_socket(topics, topic, %Activity{
|
||||||
data: %{"type" => "Delete", "deleted_activity_id" => deleted_activity_id}
|
data: %{"type" => "Delete", "deleted_activity_id" => deleted_activity_id}
|
||||||
}) do
|
}) do
|
||||||
|
|
2
mix.exs
2
mix.exs
|
@ -87,7 +87,7 @@ defp deps do
|
||||||
{:bbcode, "~> 0.1"},
|
{:bbcode, "~> 0.1"},
|
||||||
{:ex_machina, "~> 2.3", only: :test},
|
{:ex_machina, "~> 2.3", only: :test},
|
||||||
{:credo, "~> 0.9.3", only: [:dev, :test]},
|
{:credo, "~> 0.9.3", only: [:dev, :test]},
|
||||||
{:mock, "~> 0.3.1", only: :test},
|
{:mock, "~> 0.3.3", only: :test},
|
||||||
{:crypt,
|
{:crypt,
|
||||||
git: "https://github.com/msantos/crypt", ref: "1f2b58927ab57e72910191a7ebaeff984382a1d3"},
|
git: "https://github.com/msantos/crypt", ref: "1f2b58927ab57e72910191a7ebaeff984382a1d3"},
|
||||||
{:cors_plug, "~> 1.5"},
|
{:cors_plug, "~> 1.5"},
|
||||||
|
|
2
mix.lock
2
mix.lock
|
@ -46,7 +46,7 @@
|
||||||
"mime": {:hex, :mime, "1.3.1", "30ce04ab3175b6ad0bdce0035cba77bba68b813d523d1aac73d9781b4d193cf8", [:mix], [], "hexpm"},
|
"mime": {:hex, :mime, "1.3.1", "30ce04ab3175b6ad0bdce0035cba77bba68b813d523d1aac73d9781b4d193cf8", [:mix], [], "hexpm"},
|
||||||
"mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm"},
|
"mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm"},
|
||||||
"mochiweb": {:hex, :mochiweb, "2.15.0", "e1daac474df07651e5d17cc1e642c4069c7850dc4508d3db7263a0651330aacc", [:rebar3], [], "hexpm"},
|
"mochiweb": {:hex, :mochiweb, "2.15.0", "e1daac474df07651e5d17cc1e642c4069c7850dc4508d3db7263a0651330aacc", [:rebar3], [], "hexpm"},
|
||||||
"mock": {:hex, :mock, "0.3.1", "994f00150f79a0ea50dc9d86134cd9ebd0d177ad60bd04d1e46336cdfdb98ff9", [:mix], [{:meck, "~> 0.8.8", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm"},
|
"mock": {:hex, :mock, "0.3.3", "42a433794b1291a9cf1525c6d26b38e039e0d3a360732b5e467bfc77ef26c914", [:mix], [{:meck, "~> 0.8.13", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm"},
|
||||||
"mogrify": {:hex, :mogrify, "0.6.1", "de1b527514f2d95a7bbe9642eb556061afb337e220cf97adbf3a4e6438ed70af", [:mix], [], "hexpm"},
|
"mogrify": {:hex, :mogrify, "0.6.1", "de1b527514f2d95a7bbe9642eb556061afb337e220cf97adbf3a4e6438ed70af", [:mix], [], "hexpm"},
|
||||||
"nimble_parsec": {:hex, :nimble_parsec, "0.5.0", "90e2eca3d0266e5c53f8fbe0079694740b9c91b6747f2b7e3c5d21966bba8300", [:mix], [], "hexpm"},
|
"nimble_parsec": {:hex, :nimble_parsec, "0.5.0", "90e2eca3d0266e5c53f8fbe0079694740b9c91b6747f2b7e3c5d21966bba8300", [:mix], [], "hexpm"},
|
||||||
"parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm"},
|
"parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm"},
|
||||||
|
|
26
priv/repo/migrations/20190408123347_create_conversations.exs
Normal file
26
priv/repo/migrations/20190408123347_create_conversations.exs
Normal file
|
@ -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.Repo.Migrations.CreateConversations do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
def change do
|
||||||
|
create table(:conversations) do
|
||||||
|
add(:ap_id, :string, null: false)
|
||||||
|
timestamps()
|
||||||
|
end
|
||||||
|
|
||||||
|
create table(:conversation_participations) do
|
||||||
|
add(:user_id, references(:users, type: :uuid, on_delete: :delete_all))
|
||||||
|
add(:conversation_id, references(:conversations, on_delete: :delete_all))
|
||||||
|
add(:read, :boolean, default: false)
|
||||||
|
|
||||||
|
timestamps()
|
||||||
|
end
|
||||||
|
|
||||||
|
create index(:conversation_participations, [:conversation_id])
|
||||||
|
create unique_index(:conversation_participations, [:user_id, :conversation_id])
|
||||||
|
create unique_index(:conversations, [:ap_id])
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,7 @@
|
||||||
|
defmodule Pleroma.Repo.Migrations.AddParticipationUpdatedAtIndex do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
def change do
|
||||||
|
create index(:conversation_participations, ["updated_at desc"])
|
||||||
|
end
|
||||||
|
end
|
89
test/conversation/participation_test.exs
Normal file
89
test/conversation/participation_test.exs
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Conversation.ParticipationTest do
|
||||||
|
use Pleroma.DataCase
|
||||||
|
import Pleroma.Factory
|
||||||
|
alias Pleroma.Conversation.Participation
|
||||||
|
alias Pleroma.Web.CommonAPI
|
||||||
|
|
||||||
|
test "it creates a participation for a conversation and a user" do
|
||||||
|
user = insert(:user)
|
||||||
|
conversation = insert(:conversation)
|
||||||
|
|
||||||
|
{:ok, %Participation{} = participation} =
|
||||||
|
Participation.create_for_user_and_conversation(user, conversation)
|
||||||
|
|
||||||
|
assert participation.user_id == user.id
|
||||||
|
assert participation.conversation_id == conversation.id
|
||||||
|
|
||||||
|
:timer.sleep(1000)
|
||||||
|
# Creating again returns the same participation
|
||||||
|
{:ok, %Participation{} = participation_two} =
|
||||||
|
Participation.create_for_user_and_conversation(user, conversation)
|
||||||
|
|
||||||
|
assert participation.id == participation_two.id
|
||||||
|
refute participation.updated_at == participation_two.updated_at
|
||||||
|
end
|
||||||
|
|
||||||
|
test "recreating an existing participations sets it to unread" do
|
||||||
|
participation = insert(:participation, %{read: true})
|
||||||
|
|
||||||
|
{:ok, participation} =
|
||||||
|
Participation.create_for_user_and_conversation(
|
||||||
|
participation.user,
|
||||||
|
participation.conversation
|
||||||
|
)
|
||||||
|
|
||||||
|
refute participation.read
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it marks a participation as read" do
|
||||||
|
participation = insert(:participation, %{read: false})
|
||||||
|
{:ok, participation} = Participation.mark_as_read(participation)
|
||||||
|
|
||||||
|
assert participation.read
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it marks a participation as unread" do
|
||||||
|
participation = insert(:participation, %{read: true})
|
||||||
|
{:ok, participation} = Participation.mark_as_unread(participation)
|
||||||
|
|
||||||
|
refute participation.read
|
||||||
|
end
|
||||||
|
|
||||||
|
test "gets all the participations for a user, ordered by updated at descending" do
|
||||||
|
user = insert(:user)
|
||||||
|
{:ok, activity_one} = CommonAPI.post(user, %{"status" => "x", "visibility" => "direct"})
|
||||||
|
:timer.sleep(1000)
|
||||||
|
{:ok, activity_two} = CommonAPI.post(user, %{"status" => "x", "visibility" => "direct"})
|
||||||
|
:timer.sleep(1000)
|
||||||
|
|
||||||
|
{:ok, activity_three} =
|
||||||
|
CommonAPI.post(user, %{
|
||||||
|
"status" => "x",
|
||||||
|
"visibility" => "direct",
|
||||||
|
"in_reply_to_status_id" => activity_one.id
|
||||||
|
})
|
||||||
|
|
||||||
|
assert [participation_one, participation_two] = Participation.for_user(user)
|
||||||
|
|
||||||
|
object2 = Pleroma.Object.normalize(activity_two)
|
||||||
|
object3 = Pleroma.Object.normalize(activity_three)
|
||||||
|
|
||||||
|
assert participation_one.conversation.ap_id == object3.data["context"]
|
||||||
|
assert participation_two.conversation.ap_id == object2.data["context"]
|
||||||
|
|
||||||
|
# Pagination
|
||||||
|
assert [participation_one] = Participation.for_user(user, %{"limit" => 1})
|
||||||
|
|
||||||
|
assert participation_one.conversation.ap_id == object3.data["context"]
|
||||||
|
|
||||||
|
# With last_activity_id
|
||||||
|
assert [participation_one] =
|
||||||
|
Participation.for_user_with_last_activity_id(user, %{"limit" => 1})
|
||||||
|
|
||||||
|
assert participation_one.last_activity_id == activity_three.id
|
||||||
|
end
|
||||||
|
end
|
137
test/conversation_test.exs
Normal file
137
test/conversation_test.exs
Normal file
|
@ -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.ConversationTest do
|
||||||
|
use Pleroma.DataCase
|
||||||
|
alias Pleroma.Conversation
|
||||||
|
alias Pleroma.Web.CommonAPI
|
||||||
|
|
||||||
|
import Pleroma.Factory
|
||||||
|
|
||||||
|
test "it creates a conversation for given ap_id" do
|
||||||
|
assert {:ok, %Conversation{} = conversation} =
|
||||||
|
Conversation.create_for_ap_id("https://some_ap_id")
|
||||||
|
|
||||||
|
# Inserting again returns the same
|
||||||
|
assert {:ok, conversation_two} = Conversation.create_for_ap_id("https://some_ap_id")
|
||||||
|
assert conversation_two.id == conversation.id
|
||||||
|
end
|
||||||
|
|
||||||
|
test "public posts don't create conversations" do
|
||||||
|
user = insert(:user)
|
||||||
|
{:ok, activity} = CommonAPI.post(user, %{"status" => "Hey"})
|
||||||
|
|
||||||
|
object = Pleroma.Object.normalize(activity)
|
||||||
|
context = object.data["context"]
|
||||||
|
|
||||||
|
conversation = Conversation.get_for_ap_id(context)
|
||||||
|
|
||||||
|
refute conversation
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it creates or updates a conversation and participations for a given DM" do
|
||||||
|
har = insert(:user)
|
||||||
|
jafnhar = insert(:user, local: false)
|
||||||
|
tridi = insert(:user)
|
||||||
|
|
||||||
|
{:ok, activity} =
|
||||||
|
CommonAPI.post(har, %{"status" => "Hey @#{jafnhar.nickname}", "visibility" => "direct"})
|
||||||
|
|
||||||
|
object = Pleroma.Object.normalize(activity)
|
||||||
|
context = object.data["context"]
|
||||||
|
|
||||||
|
conversation =
|
||||||
|
Conversation.get_for_ap_id(context)
|
||||||
|
|> Repo.preload(:participations)
|
||||||
|
|
||||||
|
assert conversation
|
||||||
|
|
||||||
|
assert Enum.find(conversation.participations, fn %{user_id: user_id} -> har.id == user_id end)
|
||||||
|
|
||||||
|
assert Enum.find(conversation.participations, fn %{user_id: user_id} ->
|
||||||
|
jafnhar.id == user_id
|
||||||
|
end)
|
||||||
|
|
||||||
|
{:ok, activity} =
|
||||||
|
CommonAPI.post(jafnhar, %{
|
||||||
|
"status" => "Hey @#{har.nickname}",
|
||||||
|
"visibility" => "direct",
|
||||||
|
"in_reply_to_status_id" => activity.id
|
||||||
|
})
|
||||||
|
|
||||||
|
object = Pleroma.Object.normalize(activity)
|
||||||
|
context = object.data["context"]
|
||||||
|
|
||||||
|
conversation_two =
|
||||||
|
Conversation.get_for_ap_id(context)
|
||||||
|
|> Repo.preload(:participations)
|
||||||
|
|
||||||
|
assert conversation_two.id == conversation.id
|
||||||
|
|
||||||
|
assert Enum.find(conversation_two.participations, fn %{user_id: user_id} ->
|
||||||
|
har.id == user_id
|
||||||
|
end)
|
||||||
|
|
||||||
|
assert Enum.find(conversation_two.participations, fn %{user_id: user_id} ->
|
||||||
|
jafnhar.id == user_id
|
||||||
|
end)
|
||||||
|
|
||||||
|
{:ok, activity} =
|
||||||
|
CommonAPI.post(tridi, %{
|
||||||
|
"status" => "Hey @#{har.nickname}",
|
||||||
|
"visibility" => "direct",
|
||||||
|
"in_reply_to_status_id" => activity.id
|
||||||
|
})
|
||||||
|
|
||||||
|
object = Pleroma.Object.normalize(activity)
|
||||||
|
context = object.data["context"]
|
||||||
|
|
||||||
|
conversation_three =
|
||||||
|
Conversation.get_for_ap_id(context)
|
||||||
|
|> Repo.preload([:participations, :users])
|
||||||
|
|
||||||
|
assert conversation_three.id == conversation.id
|
||||||
|
|
||||||
|
assert Enum.find(conversation_three.participations, fn %{user_id: user_id} ->
|
||||||
|
har.id == user_id
|
||||||
|
end)
|
||||||
|
|
||||||
|
assert Enum.find(conversation_three.participations, fn %{user_id: user_id} ->
|
||||||
|
jafnhar.id == user_id
|
||||||
|
end)
|
||||||
|
|
||||||
|
assert Enum.find(conversation_three.participations, fn %{user_id: user_id} ->
|
||||||
|
tridi.id == user_id
|
||||||
|
end)
|
||||||
|
|
||||||
|
assert Enum.find(conversation_three.users, fn %{id: user_id} ->
|
||||||
|
har.id == user_id
|
||||||
|
end)
|
||||||
|
|
||||||
|
assert Enum.find(conversation_three.users, fn %{id: user_id} ->
|
||||||
|
jafnhar.id == user_id
|
||||||
|
end)
|
||||||
|
|
||||||
|
assert Enum.find(conversation_three.users, fn %{id: user_id} ->
|
||||||
|
tridi.id == user_id
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "create_or_bump_for returns the conversation with participations" do
|
||||||
|
har = insert(:user)
|
||||||
|
jafnhar = insert(:user, local: false)
|
||||||
|
|
||||||
|
{:ok, activity} =
|
||||||
|
CommonAPI.post(har, %{"status" => "Hey @#{jafnhar.nickname}", "visibility" => "direct"})
|
||||||
|
|
||||||
|
{:ok, conversation} = Conversation.create_or_bump_for(activity)
|
||||||
|
|
||||||
|
assert length(conversation.participations) == 2
|
||||||
|
|
||||||
|
{:ok, activity} =
|
||||||
|
CommonAPI.post(har, %{"status" => "Hey @#{jafnhar.nickname}", "visibility" => "public"})
|
||||||
|
|
||||||
|
assert {:error, _} = Conversation.create_or_bump_for(activity)
|
||||||
|
end
|
||||||
|
end
|
|
@ -5,6 +5,23 @@
|
||||||
defmodule Pleroma.Factory do
|
defmodule Pleroma.Factory do
|
||||||
use ExMachina.Ecto, repo: Pleroma.Repo
|
use ExMachina.Ecto, repo: Pleroma.Repo
|
||||||
|
|
||||||
|
def participation_factory do
|
||||||
|
conversation = insert(:conversation)
|
||||||
|
user = insert(:user)
|
||||||
|
|
||||||
|
%Pleroma.Conversation.Participation{
|
||||||
|
conversation: conversation,
|
||||||
|
user: user,
|
||||||
|
read: false
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def conversation_factory do
|
||||||
|
%Pleroma.Conversation{
|
||||||
|
ap_id: sequence(:ap_id, &"https://some_conversation/#{&1}")
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
def user_factory do
|
def user_factory do
|
||||||
user = %Pleroma.User{
|
user = %Pleroma.User{
|
||||||
name: sequence(:name, &"Test テスト User #{&1}"),
|
name: sequence(:name, &"Test テスト User #{&1}"),
|
||||||
|
|
|
@ -22,6 +22,28 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
|
||||||
:ok
|
:ok
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "streaming out participations" do
|
||||||
|
test "it streams them out" do
|
||||||
|
user = insert(:user)
|
||||||
|
{:ok, activity} = CommonAPI.post(user, %{"status" => ".", "visibility" => "direct"})
|
||||||
|
|
||||||
|
{:ok, conversation} = Pleroma.Conversation.create_or_bump_for(activity)
|
||||||
|
|
||||||
|
participations =
|
||||||
|
conversation.participations
|
||||||
|
|> Repo.preload(:user)
|
||||||
|
|
||||||
|
with_mock Pleroma.Web.Streamer,
|
||||||
|
stream: fn _, _ -> nil end do
|
||||||
|
ActivityPub.stream_out_participations(conversation.participations)
|
||||||
|
|
||||||
|
Enum.each(participations, fn participation ->
|
||||||
|
assert called(Pleroma.Web.Streamer.stream("participation", participation))
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe "fetching restricted by visibility" do
|
describe "fetching restricted by visibility" do
|
||||||
test "it restricts by the appropriate visibility" do
|
test "it restricts by the appropriate visibility" do
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
|
@ -130,9 +152,15 @@ test "drops activities beyond a certain limit" do
|
||||||
end
|
end
|
||||||
|
|
||||||
test "doesn't drop activities with content being null" do
|
test "doesn't drop activities with content being null" do
|
||||||
|
user = insert(:user)
|
||||||
|
|
||||||
data = %{
|
data = %{
|
||||||
"ok" => true,
|
"actor" => user.ap_id,
|
||||||
|
"to" => [],
|
||||||
"object" => %{
|
"object" => %{
|
||||||
|
"actor" => user.ap_id,
|
||||||
|
"to" => [],
|
||||||
|
"type" => "Note",
|
||||||
"content" => nil
|
"content" => nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -148,8 +176,17 @@ test "returns the activity if one with the same id is already in" do
|
||||||
end
|
end
|
||||||
|
|
||||||
test "inserts a given map into the activity database, giving it an id if it has none." do
|
test "inserts a given map into the activity database, giving it an id if it has none." do
|
||||||
|
user = insert(:user)
|
||||||
|
|
||||||
data = %{
|
data = %{
|
||||||
"ok" => true
|
"actor" => user.ap_id,
|
||||||
|
"to" => [],
|
||||||
|
"object" => %{
|
||||||
|
"actor" => user.ap_id,
|
||||||
|
"to" => [],
|
||||||
|
"type" => "Note",
|
||||||
|
"content" => "hey"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
{:ok, %Activity{} = activity} = ActivityPub.insert(data)
|
{:ok, %Activity{} = activity} = ActivityPub.insert(data)
|
||||||
|
@ -159,9 +196,16 @@ test "inserts a given map into the activity database, giving it an id if it has
|
||||||
given_id = "bla"
|
given_id = "bla"
|
||||||
|
|
||||||
data = %{
|
data = %{
|
||||||
"ok" => true,
|
|
||||||
"id" => given_id,
|
"id" => given_id,
|
||||||
"context" => "blabla"
|
"actor" => user.ap_id,
|
||||||
|
"to" => [],
|
||||||
|
"context" => "blabla",
|
||||||
|
"object" => %{
|
||||||
|
"actor" => user.ap_id,
|
||||||
|
"to" => [],
|
||||||
|
"type" => "Note",
|
||||||
|
"content" => "hey"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
{:ok, %Activity{} = activity} = ActivityPub.insert(data)
|
{:ok, %Activity{} = activity} = ActivityPub.insert(data)
|
||||||
|
@ -172,26 +216,39 @@ test "inserts a given map into the activity database, giving it an id if it has
|
||||||
end
|
end
|
||||||
|
|
||||||
test "adds a context when none is there" do
|
test "adds a context when none is there" do
|
||||||
|
user = insert(:user)
|
||||||
|
|
||||||
data = %{
|
data = %{
|
||||||
"id" => "some_id",
|
"actor" => user.ap_id,
|
||||||
|
"to" => [],
|
||||||
"object" => %{
|
"object" => %{
|
||||||
"id" => "object_id"
|
"actor" => user.ap_id,
|
||||||
|
"to" => [],
|
||||||
|
"type" => "Note",
|
||||||
|
"content" => "hey"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
{:ok, %Activity{} = activity} = ActivityPub.insert(data)
|
{:ok, %Activity{} = activity} = ActivityPub.insert(data)
|
||||||
|
object = Pleroma.Object.normalize(activity)
|
||||||
|
|
||||||
assert is_binary(activity.data["context"])
|
assert is_binary(activity.data["context"])
|
||||||
assert is_binary(activity.data["object"]["context"])
|
assert is_binary(object.data["context"])
|
||||||
assert activity.data["context_id"]
|
assert activity.data["context_id"]
|
||||||
assert activity.data["object"]["context_id"]
|
assert object.data["context_id"]
|
||||||
end
|
end
|
||||||
|
|
||||||
test "adds an id to a given object if it lacks one and is a note and inserts it to the object database" do
|
test "adds an id to a given object if it lacks one and is a note and inserts it to the object database" do
|
||||||
|
user = insert(:user)
|
||||||
|
|
||||||
data = %{
|
data = %{
|
||||||
|
"actor" => user.ap_id,
|
||||||
|
"to" => [],
|
||||||
"object" => %{
|
"object" => %{
|
||||||
|
"actor" => user.ap_id,
|
||||||
|
"to" => [],
|
||||||
"type" => "Note",
|
"type" => "Note",
|
||||||
"ok" => true
|
"content" => "hey"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -300,6 +300,65 @@ test "direct timeline", %{conn: conn} do
|
||||||
assert status["url"] != direct.data["id"]
|
assert status["url"] != direct.data["id"]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "Conversations", %{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"
|
||||||
|
})
|
||||||
|
|
||||||
|
res_conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user_one)
|
||||||
|
|> get("/api/v1/conversations")
|
||||||
|
|
||||||
|
assert response = json_response(res_conn, 200)
|
||||||
|
|
||||||
|
assert [
|
||||||
|
%{
|
||||||
|
"id" => res_id,
|
||||||
|
"accounts" => res_accounts,
|
||||||
|
"last_status" => res_last_status,
|
||||||
|
"unread" => unread
|
||||||
|
}
|
||||||
|
] = response
|
||||||
|
|
||||||
|
assert length(res_accounts) == 2
|
||||||
|
assert is_binary(res_id)
|
||||||
|
assert unread == true
|
||||||
|
assert res_last_status["id"] == direct.id
|
||||||
|
|
||||||
|
# Apparently undocumented API endpoint
|
||||||
|
res_conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user_one)
|
||||||
|
|> post("/api/v1/conversations/#{res_id}/read")
|
||||||
|
|
||||||
|
assert response = json_response(res_conn, 200)
|
||||||
|
assert length(response["accounts"]) == 2
|
||||||
|
assert response["last_status"]["id"] == direct.id
|
||||||
|
assert response["unread"] == false
|
||||||
|
|
||||||
|
# (vanilla) Mastodon frontend behaviour
|
||||||
|
res_conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user_one)
|
||||||
|
|> get("/api/v1/statuses/#{res_last_status["id"]}/context")
|
||||||
|
|
||||||
|
assert %{"ancestors" => [], "descendants" => []} == json_response(res_conn, 200)
|
||||||
|
end
|
||||||
|
|
||||||
test "doesn't include DMs from blocked users", %{conn: conn} do
|
test "doesn't include DMs from blocked users", %{conn: conn} do
|
||||||
blocker = insert(:user)
|
blocker = insert(:user)
|
||||||
blocked = insert(:user)
|
blocked = insert(:user)
|
||||||
|
|
Loading…
Reference in a new issue