Browse Source

Move conversation actions to PleromaAPI.ConversationController

chore/expose-invalidation-to-adminfe
Egor Kislitsyn 4 years ago
parent
commit
f3fc8b22b1
No known key found for this signature in database GPG Key ID: 1B49CB15B71E7805
7 changed files with 341 additions and 324 deletions
  1. +106
    -0
      lib/pleroma/web/api_spec/operations/pleroma_conversation_operation.ex
  2. +0
    -93
      lib/pleroma/web/api_spec/operations/pleroma_operation.ex
  3. +95
    -0
      lib/pleroma/web/pleroma_api/controllers/conversation_controller.ex
  4. +0
    -99
      lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex
  5. +4
    -8
      lib/pleroma/web/router.ex
  6. +136
    -0
      test/web/pleroma_api/controllers/conversation_controller_test.exs
  7. +0
    -124
      test/web/pleroma_api/controllers/pleroma_api_controller_test.exs

+ 106
- 0
lib/pleroma/web/api_spec/operations/pleroma_conversation_operation.ex View File

@@ -0,0 +1,106 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only

defmodule Pleroma.Web.ApiSpec.PleromaConversationOperation do
alias OpenApiSpex.Operation
alias OpenApiSpex.Schema
alias Pleroma.Web.ApiSpec.Schemas.Conversation
alias Pleroma.Web.ApiSpec.Schemas.FlakeID
alias Pleroma.Web.ApiSpec.StatusOperation

import Pleroma.Web.ApiSpec.Helpers

def open_api_operation(action) do
operation = String.to_existing_atom("#{action}_operation")
apply(__MODULE__, operation, [])
end

def show_operation do
%Operation{
tags: ["Conversations"],
summary: "The conversation with the given ID",
parameters: [
Operation.parameter(:id, :path, :string, "Conversation ID",
example: "123",
required: true
)
],
security: [%{"oAuth" => ["read:statuses"]}],
operationId: "PleromaAPI.ConversationController.show",
responses: %{
200 => Operation.response("Conversation", "application/json", Conversation)
}
}
end

def statuses_operation do
%Operation{
tags: ["Conversations"],
summary: "Timeline for a given conversation",
parameters: [
Operation.parameter(:id, :path, :string, "Conversation ID",
example: "123",
required: true
)
| pagination_params()
],
security: [%{"oAuth" => ["read:statuses"]}],
operationId: "PleromaAPI.ConversationController.statuses",
responses: %{
200 =>
Operation.response(
"Array of Statuses",
"application/json",
StatusOperation.array_of_statuses()
)
}
}
end

def update_operation do
%Operation{
tags: ["Conversations"],
summary: "Update a conversation. Used to change the set of recipients.",
parameters: [
Operation.parameter(:id, :path, :string, "Conversation ID",
example: "123",
required: true
),
Operation.parameter(
:recipients,
:query,
%Schema{type: :array, items: FlakeID},
"A list of ids of users that should receive posts to this conversation. This will replace the current list of recipients, so submit the full list. The owner of owner of the conversation will always be part of the set of recipients, though.",
required: true
)
],
security: [%{"oAuth" => ["write:conversations"]}],
operationId: "PleromaAPI.ConversationController.update",
responses: %{
200 => Operation.response("Conversation", "application/json", Conversation)
}
}
end

def mark_as_read_operation do
%Operation{
tags: ["Conversations"],
summary: "Marks all user's conversations as read",
security: [%{"oAuth" => ["write:conversations"]}],
operationId: "PleromaAPI.ConversationController.mark_as_read",
responses: %{
200 =>
Operation.response(
"Array of Conversations that were marked as read",
"application/json",
%Schema{
type: :array,
items: Conversation,
example: [Conversation.schema().example]
}
)
}
}
end
end

+ 0
- 93
lib/pleroma/web/api_spec/operations/pleroma_operation.ex View File

@@ -7,105 +7,12 @@ defmodule Pleroma.Web.ApiSpec.PleromaOperation do
alias OpenApiSpex.Schema
alias Pleroma.Web.ApiSpec.NotificationOperation
alias Pleroma.Web.ApiSpec.Schemas.ApiError
alias Pleroma.Web.ApiSpec.Schemas.Conversation
alias Pleroma.Web.ApiSpec.Schemas.FlakeID
alias Pleroma.Web.ApiSpec.StatusOperation

import Pleroma.Web.ApiSpec.Helpers

def open_api_operation(action) do
operation = String.to_existing_atom("#{action}_operation")
apply(__MODULE__, operation, [])
end

def conversation_operation do
%Operation{
tags: ["Conversations"],
summary: "The conversation with the given ID",
parameters: [
Operation.parameter(:id, :path, :string, "Conversation ID",
example: "123",
required: true
)
],
security: [%{"oAuth" => ["read:statuses"]}],
operationId: "PleromaController.conversation",
responses: %{
200 => Operation.response("Conversation", "application/json", Conversation)
}
}
end

def conversation_statuses_operation do
%Operation{
tags: ["Conversations"],
summary: "Timeline for a given conversation",
parameters: [
Operation.parameter(:id, :path, :string, "Conversation ID",
example: "123",
required: true
)
| pagination_params()
],
security: [%{"oAuth" => ["read:statuses"]}],
operationId: "PleromaController.conversation_statuses",
responses: %{
200 =>
Operation.response(
"Array of Statuses",
"application/json",
StatusOperation.array_of_statuses()
)
}
}
end

def update_conversation_operation do
%Operation{
tags: ["Conversations"],
summary: "Update a conversation. Used to change the set of recipients.",
parameters: [
Operation.parameter(:id, :path, :string, "Conversation ID",
example: "123",
required: true
),
Operation.parameter(
:recipients,
:query,
%Schema{type: :array, items: FlakeID},
"A list of ids of users that should receive posts to this conversation. This will replace the current list of recipients, so submit the full list. The owner of owner of the conversation will always be part of the set of recipients, though.",
required: true
)
],
security: [%{"oAuth" => ["write:conversations"]}],
operationId: "PleromaController.update_conversation",
responses: %{
200 => Operation.response("Conversation", "application/json", Conversation)
}
}
end

def mark_conversations_as_read_operation do
%Operation{
tags: ["Conversations"],
summary: "Marks all user's conversations as read",
security: [%{"oAuth" => ["write:conversations"]}],
operationId: "PleromaController.mark_conversations_as_read",
responses: %{
200 =>
Operation.response(
"Array of Conversations that were marked as read",
"application/json",
%Schema{
type: :array,
items: Conversation,
example: [Conversation.schema().example]
}
)
}
}
end

def mark_notifications_as_read_operation do
%Operation{
tags: ["Notifications"],


+ 95
- 0
lib/pleroma/web/pleroma_api/controllers/conversation_controller.ex View File

@@ -0,0 +1,95 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only

defmodule Pleroma.Web.PleromaAPI.ConversationController do
use Pleroma.Web, :controller

import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2]

alias Pleroma.Conversation.Participation
alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.MastodonAPI.StatusView

plug(Pleroma.Web.ApiSpec.CastAndValidate)
plug(:put_view, Pleroma.Web.MastodonAPI.ConversationView)
plug(OAuthScopesPlug, %{scopes: ["read:statuses"]} when action in [:show, :statuses])

plug(
OAuthScopesPlug,
%{scopes: ["write:conversations"]} when action in [:update, :mark_as_read]
)

defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.PleromaConversationOperation

def show(%{assigns: %{user: %{id: user_id} = user}} = conn, %{id: participation_id}) do
with %Participation{user_id: ^user_id} = participation <- Participation.get(participation_id) do
render(conn, "participation.json", participation: participation, for: user)
else
_error ->
conn
|> put_status(:not_found)
|> json(%{"error" => "Unknown conversation id"})
end
end

def statuses(
%{assigns: %{user: %{id: user_id} = user}} = conn,
%{id: participation_id} = params
) do
with %Participation{user_id: ^user_id} = participation <-
Participation.get(participation_id, preload: [:conversation]) do
params =
params
|> Map.new(fn {key, value} -> {to_string(key), value} end)
|> Map.put("blocking_user", user)
|> Map.put("muting_user", user)
|> Map.put("user", user)

activities =
participation.conversation.ap_id
|> ActivityPub.fetch_activities_for_context_query(params)
|> Pleroma.Pagination.fetch_paginated(Map.put(params, "total", false))
|> Enum.reverse()

conn
|> add_link_headers(activities)
|> put_view(StatusView)
|> render("index.json", activities: activities, for: user, as: :activity)
else
_error ->
conn
|> put_status(:not_found)
|> json(%{"error" => "Unknown conversation id"})
end
end

def update(
%{assigns: %{user: %{id: user_id} = user}} = conn,
%{id: participation_id, recipients: recipients}
) do
with %Participation{user_id: ^user_id} = participation <- Participation.get(participation_id),
{:ok, participation} <- Participation.set_recipients(participation, recipients) do
render(conn, "participation.json", participation: participation, for: user)
else
{:error, message} ->
conn
|> put_status(:bad_request)
|> json(%{"error" => message})

_error ->
conn
|> put_status(:not_found)
|> json(%{"error" => "Unknown conversation id"})
end
end

def mark_as_read(%{assigns: %{user: user}} = conn, _params) do
with {:ok, _, participations} <- Participation.mark_all_as_read(user) do
conn
|> add_link_headers(participations)
|> render("participations.json", participations: participations, for: user)
end
end
end

+ 0
- 99
lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex View File

@@ -5,118 +5,19 @@
defmodule Pleroma.Web.PleromaAPI.PleromaAPIController do
use Pleroma.Web, :controller

import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2]

alias Pleroma.Conversation.Participation
alias Pleroma.Notification
alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.MastodonAPI.ConversationView
alias Pleroma.Web.MastodonAPI.NotificationView
alias Pleroma.Web.MastodonAPI.StatusView

plug(Pleroma.Web.ApiSpec.CastAndValidate)

plug(
OAuthScopesPlug,
%{scopes: ["read:statuses"]}
when action in [:conversation, :conversation_statuses]
)

plug(
OAuthScopesPlug,
%{scopes: ["write:conversations"]}
when action in [:update_conversation, :mark_conversations_as_read]
)

plug(
OAuthScopesPlug,
%{scopes: ["write:notifications"]} when action == :mark_notifications_as_read
)

defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.PleromaOperation

def conversation(%{assigns: %{user: user}} = conn, %{id: participation_id}) do
with %Participation{} = participation <- Participation.get(participation_id),
true <- user.id == participation.user_id do
conn
|> put_view(ConversationView)
|> render("participation.json", %{participation: participation, for: user})
else
_error ->
conn
|> put_status(404)
|> json(%{"error" => "Unknown conversation id"})
end
end

def conversation_statuses(
%{assigns: %{user: %{id: user_id} = user}} = conn,
%{id: participation_id} = params
) do
with %Participation{user_id: ^user_id} = participation <-
Participation.get(participation_id, preload: [:conversation]) do
params =
params
|> Map.new(fn {key, value} -> {to_string(key), value} end)
|> Map.put("blocking_user", user)
|> Map.put("muting_user", user)
|> Map.put("user", user)

activities =
participation.conversation.ap_id
|> ActivityPub.fetch_activities_for_context_query(params)
|> Pleroma.Pagination.fetch_paginated(Map.put(params, "total", false))
|> Enum.reverse()

conn
|> add_link_headers(activities)
|> put_view(StatusView)
|> render("index.json",
activities: activities,
for: user,
as: :activity
)
else
_error ->
conn
|> put_status(404)
|> json(%{"error" => "Unknown conversation id"})
end
end

def update_conversation(
%{assigns: %{user: user}} = conn,
%{id: participation_id, recipients: recipients}
) do
with %Participation{} = participation <- Participation.get(participation_id),
true <- user.id == participation.user_id,
{:ok, participation} <- Participation.set_recipients(participation, recipients) do
conn
|> put_view(ConversationView)
|> render("participation.json", %{participation: participation, for: user})
else
{:error, message} ->
conn
|> put_status(:bad_request)
|> json(%{"error" => message})

_error ->
conn
|> put_status(404)
|> json(%{"error" => "Unknown conversation id"})
end
end

def mark_conversations_as_read(%{assigns: %{user: user}} = conn, _params) do
with {:ok, _, participations} <- Participation.mark_all_as_read(user) do
conn
|> add_link_headers(participations)
|> put_view(ConversationView)
|> render("participations.json", participations: participations, for: user)
end
end

def mark_notifications_as_read(%{assigns: %{user: user}} = conn, %{id: notification_id}) do
with {:ok, notification} <- Notification.read_one(user, notification_id) do
conn


+ 4
- 8
lib/pleroma/web/router.ex View File

@@ -305,15 +305,11 @@ defmodule Pleroma.Web.Router do
scope [] do
pipe_through(:authenticated_api)

get("/conversations/:id/statuses", PleromaAPIController, :conversation_statuses)
get("/conversations/:id", PleromaAPIController, :conversation)
post("/conversations/read", PleromaAPIController, :mark_conversations_as_read)
end

scope [] do
pipe_through(:authenticated_api)
get("/conversations/:id/statuses", ConversationController, :statuses)
get("/conversations/:id", ConversationController, :show)
post("/conversations/read", ConversationController, :mark_as_read)
patch("/conversations/:id", ConversationController, :update)

patch("/conversations/:id", PleromaAPIController, :update_conversation)
put("/statuses/:id/reactions/:emoji", EmojiReactionController, :create)
delete("/statuses/:id/reactions/:emoji", EmojiReactionController, :delete)
post("/notifications/read", PleromaAPIController, :mark_notifications_as_read)


+ 136
- 0
test/web/pleroma_api/controllers/conversation_controller_test.exs View File

@@ -0,0 +1,136 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only

defmodule Pleroma.Web.PleromaAPI.ConversationControllerTest do
use Pleroma.Web.ConnCase

alias Pleroma.Conversation.Participation
alias Pleroma.Repo
alias Pleroma.User
alias Pleroma.Web.CommonAPI

import Pleroma.Factory

test "/api/v1/pleroma/conversations/:id" do
user = insert(:user)
%{user: other_user, conn: conn} = oauth_access(["read:statuses"])

{:ok, _activity} =
CommonAPI.post(user, %{status: "Hi @#{other_user.nickname}!", visibility: "direct"})

[participation] = Participation.for_user(other_user)

result =
conn
|> get("/api/v1/pleroma/conversations/#{participation.id}")
|> json_response_and_validate_schema(200)

assert result["id"] == participation.id |> to_string()
end

test "/api/v1/pleroma/conversations/:id/statuses" do
user = insert(:user)
%{user: other_user, conn: conn} = oauth_access(["read:statuses"])
third_user = insert(:user)

{:ok, _activity} =
CommonAPI.post(user, %{status: "Hi @#{third_user.nickname}!", visibility: "direct"})

{:ok, activity} =
CommonAPI.post(user, %{status: "Hi @#{other_user.nickname}!", visibility: "direct"})

[participation] = Participation.for_user(other_user)

{:ok, activity_two} =
CommonAPI.post(other_user, %{
status: "Hi!",
in_reply_to_status_id: activity.id,
in_reply_to_conversation_id: participation.id
})

result =
conn
|> get("/api/v1/pleroma/conversations/#{participation.id}/statuses")
|> json_response_and_validate_schema(200)

assert length(result) == 2

id_one = activity.id
id_two = activity_two.id
assert [%{"id" => ^id_one}, %{"id" => ^id_two}] = result

{:ok, %{id: id_three}} =
CommonAPI.post(other_user, %{
status: "Bye!",
in_reply_to_status_id: activity.id,
in_reply_to_conversation_id: participation.id
})

assert [%{"id" => ^id_two}, %{"id" => ^id_three}] =
conn
|> get("/api/v1/pleroma/conversations/#{participation.id}/statuses?limit=2")
|> json_response_and_validate_schema(:ok)

assert [%{"id" => ^id_three}] =
conn
|> get("/api/v1/pleroma/conversations/#{participation.id}/statuses?min_id=#{id_two}")
|> json_response_and_validate_schema(:ok)
end

test "PATCH /api/v1/pleroma/conversations/:id" do
%{user: user, conn: conn} = oauth_access(["write:conversations"])
other_user = insert(:user)

{:ok, _activity} = CommonAPI.post(user, %{status: "Hi", visibility: "direct"})

[participation] = Participation.for_user(user)

participation = Repo.preload(participation, :recipients)

user = User.get_cached_by_id(user.id)
assert [user] == participation.recipients
assert other_user not in participation.recipients

query = "recipients[]=#{user.id}&recipients[]=#{other_user.id}"

result =
conn
|> patch("/api/v1/pleroma/conversations/#{participation.id}?#{query}")
|> json_response_and_validate_schema(200)

assert result["id"] == participation.id |> to_string

[participation] = Participation.for_user(user)
participation = Repo.preload(participation, :recipients)

assert user in participation.recipients
assert other_user in participation.recipients
end

test "POST /api/v1/pleroma/conversations/read" do
user = insert(:user)
%{user: other_user, conn: conn} = oauth_access(["write:conversations"])

{:ok, _activity} =
CommonAPI.post(user, %{status: "Hi @#{other_user.nickname}", visibility: "direct"})

{:ok, _activity} =
CommonAPI.post(user, %{status: "Hi @#{other_user.nickname}", visibility: "direct"})

[participation2, participation1] = Participation.for_user(other_user)
assert Participation.get(participation2.id).read == false
assert Participation.get(participation1.id).read == false
assert User.get_cached_by_id(other_user.id).unread_conversation_count == 2

[%{"unread" => false}, %{"unread" => false}] =
conn
|> post("/api/v1/pleroma/conversations/read", %{})
|> json_response_and_validate_schema(200)

[participation2, participation1] = Participation.for_user(other_user)
assert Participation.get(participation2.id).read == true
assert Participation.get(participation1.id).read == true
assert User.get_cached_by_id(other_user.id).unread_conversation_count == 0
end
end

+ 0
- 124
test/web/pleroma_api/controllers/pleroma_api_controller_test.exs View File

@@ -5,136 +5,12 @@
defmodule Pleroma.Web.PleromaAPI.PleromaAPIControllerTest do
use Pleroma.Web.ConnCase

alias Pleroma.Conversation.Participation
alias Pleroma.Notification
alias Pleroma.Repo
alias Pleroma.User
alias Pleroma.Web.CommonAPI

import Pleroma.Factory

test "/api/v1/pleroma/conversations/:id" do
user = insert(:user)
%{user: other_user, conn: conn} = oauth_access(["read:statuses"])

{:ok, _activity} =
CommonAPI.post(user, %{status: "Hi @#{other_user.nickname}!", visibility: "direct"})

[participation] = Participation.for_user(other_user)

result =
conn
|> get("/api/v1/pleroma/conversations/#{participation.id}")
|> json_response_and_validate_schema(200)

assert result["id"] == participation.id |> to_string()
end

test "/api/v1/pleroma/conversations/:id/statuses" do
user = insert(:user)
%{user: other_user, conn: conn} = oauth_access(["read:statuses"])
third_user = insert(:user)

{:ok, _activity} =
CommonAPI.post(user, %{status: "Hi @#{third_user.nickname}!", visibility: "direct"})

{:ok, activity} =
CommonAPI.post(user, %{status: "Hi @#{other_user.nickname}!", visibility: "direct"})

[participation] = Participation.for_user(other_user)

{:ok, activity_two} =
CommonAPI.post(other_user, %{
status: "Hi!",
in_reply_to_status_id: activity.id,
in_reply_to_conversation_id: participation.id
})

result =
conn
|> get("/api/v1/pleroma/conversations/#{participation.id}/statuses")
|> json_response_and_validate_schema(200)

assert length(result) == 2

id_one = activity.id
id_two = activity_two.id
assert [%{"id" => ^id_one}, %{"id" => ^id_two}] = result

{:ok, %{id: id_three}} =
CommonAPI.post(other_user, %{
status: "Bye!",
in_reply_to_status_id: activity.id,
in_reply_to_conversation_id: participation.id
})

assert [%{"id" => ^id_two}, %{"id" => ^id_three}] =
conn
|> get("/api/v1/pleroma/conversations/#{participation.id}/statuses?limit=2")
|> json_response_and_validate_schema(:ok)

assert [%{"id" => ^id_three}] =
conn
|> get("/api/v1/pleroma/conversations/#{participation.id}/statuses?min_id=#{id_two}")
|> json_response_and_validate_schema(:ok)
end

test "PATCH /api/v1/pleroma/conversations/:id" do
%{user: user, conn: conn} = oauth_access(["write:conversations"])
other_user = insert(:user)

{:ok, _activity} = CommonAPI.post(user, %{status: "Hi", visibility: "direct"})

[participation] = Participation.for_user(user)

participation = Repo.preload(participation, :recipients)

user = User.get_cached_by_id(user.id)
assert [user] == participation.recipients
assert other_user not in participation.recipients

query = "recipients[]=#{user.id}&recipients[]=#{other_user.id}"

result =
conn
|> patch("/api/v1/pleroma/conversations/#{participation.id}?#{query}")
|> json_response_and_validate_schema(200)

assert result["id"] == participation.id |> to_string

[participation] = Participation.for_user(user)
participation = Repo.preload(participation, :recipients)

assert user in participation.recipients
assert other_user in participation.recipients
end

test "POST /api/v1/pleroma/conversations/read" do
user = insert(:user)
%{user: other_user, conn: conn} = oauth_access(["write:conversations"])

{:ok, _activity} =
CommonAPI.post(user, %{status: "Hi @#{other_user.nickname}", visibility: "direct"})

{:ok, _activity} =
CommonAPI.post(user, %{status: "Hi @#{other_user.nickname}", visibility: "direct"})

[participation2, participation1] = Participation.for_user(other_user)
assert Participation.get(participation2.id).read == false
assert Participation.get(participation1.id).read == false
assert User.get_cached_by_id(other_user.id).unread_conversation_count == 2

[%{"unread" => false}, %{"unread" => false}] =
conn
|> post("/api/v1/pleroma/conversations/read", %{})
|> json_response_and_validate_schema(200)

[participation2, participation1] = Participation.for_user(other_user)
assert Participation.get(participation2.id).read == true
assert Participation.get(participation1.id).read == true
assert User.get_cached_by_id(other_user.id).unread_conversation_count == 0
end

describe "POST /api/v1/pleroma/notifications/read" do
setup do: oauth_access(["write:notifications"])



Loading…
Cancel
Save