aboutsummaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/pleroma/conversation.ex71
-rw-r--r--lib/pleroma/conversation/participation.ex81
-rw-r--r--lib/pleroma/web/activity_pub/activity_pub.ex55
-rw-r--r--lib/pleroma/web/mastodon_api/mastodon_api_controller.ex27
-rw-r--r--lib/pleroma/web/mastodon_api/views/conversation_view.ex38
-rw-r--r--lib/pleroma/web/router.ex3
6 files changed, 253 insertions, 22 deletions
diff --git a/lib/pleroma/conversation.ex b/lib/pleroma/conversation.ex
new file mode 100644
index 000000000..d9c84cb1b
--- /dev/null
+++ b/lib/pleroma/conversation.ex
@@ -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.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),
+ "Create" <- activity.data["type"],
+ "Note" <- activity.data["object"]["type"],
+ ap_id when is_binary(ap_id) <- activity.data["object"]["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)
+
+ %{
+ conversation
+ | participations: participations
+ }
+ end
+ end
+end
diff --git a/lib/pleroma/conversation/participation.ex b/lib/pleroma/conversation/participation.ex
new file mode 100644
index 000000000..61021fb18
--- /dev/null
+++ b/lib/pleroma/conversation/participation.ex
@@ -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
diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex
index 483a2153f..28754e864 100644
--- a/lib/pleroma/web/activity_pub/activity_pub.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub.ex
@@ -4,6 +4,7 @@
defmodule Pleroma.Web.ActivityPub.ActivityPub do
alias Pleroma.Activity
+ alias Pleroma.Conversation
alias Pleroma.Instances
alias Pleroma.Notification
alias Pleroma.Object
@@ -141,6 +142,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
end)
Notification.create_notifications(activity)
+ Conversation.create_or_bump_for(activity)
stream_out(activity)
{:ok, activity}
else
@@ -457,35 +459,44 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
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"]
recipients =
if opts["user"], do: [opts["user"].ap_id | opts["user"].following] ++ public, else: public
- query = from(activity in Activity)
-
- query =
- query
- |> restrict_blocked(opts)
- |> restrict_recipients(recipients, opts["user"])
-
- query =
- from(
- activity in query,
- where:
- fragment(
- "?->>'type' = ? and ?->>'context' = ?",
- activity.data,
- "Create",
- activity.data,
- ^context
- ),
- order_by: [desc: :id]
+ from(activity in Activity)
+ |> restrict_blocked(opts)
+ |> restrict_recipients(recipients, opts["user"])
+ |> where(
+ [activity],
+ fragment(
+ "?->>'type' = ? and ?->>'context' = ?",
+ activity.data,
+ "Create",
+ activity.data,
+ ^context
)
- |> Activity.with_preloaded_object()
+ )
+ |> order_by([activity], desc: activity.id)
+ end
+
+ @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
- Repo.all(query)
+ @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
def fetch_public_activities(opts \\ %{}) do
diff --git a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
index ed585098a..aa3f46482 100644
--- a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
+++ b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
@@ -8,6 +8,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
alias Pleroma.Activity
alias Pleroma.Bookmark
alias Pleroma.Config
+ alias Pleroma.Conversation.Participation
alias Pleroma.Filter
alias Pleroma.Notification
alias Pleroma.Object
@@ -23,6 +24,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
alias Pleroma.Web.CommonAPI
alias Pleroma.Web.MastodonAPI.AccountView
alias Pleroma.Web.MastodonAPI.AppView
+ alias Pleroma.Web.MastodonAPI.ConversationView
alias Pleroma.Web.MastodonAPI.FilterView
alias Pleroma.Web.MastodonAPI.ListView
alias Pleroma.Web.MastodonAPI.MastodonAPI
@@ -1705,6 +1707,31 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
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)
when is_binary(target) do
res = render(conn, target, params)
diff --git a/lib/pleroma/web/mastodon_api/views/conversation_view.ex b/lib/pleroma/web/mastodon_api/views/conversation_view.ex
new file mode 100644
index 000000000..8e8f7cf31
--- /dev/null
+++ b/lib/pleroma/web/mastodon_api/views/conversation_view.ex
@@ -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
diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex
index ff4f08af5..6d9c77c1a 100644
--- a/lib/pleroma/web/router.ex
+++ b/lib/pleroma/web/router.ex
@@ -276,6 +276,9 @@ defmodule Pleroma.Web.Router do
get("/suggestions", MastodonAPIController, :suggestions)
+ get("/conversations", MastodonAPIController, :conversations)
+ post("/conversations/:id/read", MastodonAPIController, :conversation_read)
+
get("/endorsements", MastodonAPIController, :empty_array)
get("/pleroma/flavour", MastodonAPIController, :get_flavour)