Implement announcement read relationships
This commit is contained in:
parent
c867d23250
commit
5169ad8f14
|
@ -7,6 +7,7 @@ defmodule Pleroma.Announcement do
|
|||
|
||||
import Ecto.Changeset, only: [cast: 3, validate_required: 2]
|
||||
|
||||
alias Pleroma.AnnouncementReadRelationship
|
||||
alias Pleroma.Repo
|
||||
|
||||
@type t :: %__MODULE__{}
|
||||
|
@ -49,15 +50,20 @@ def delete_by_id(id) do
|
|||
end
|
||||
end
|
||||
|
||||
def read_by?(_announcement, _user) do
|
||||
false
|
||||
def read_by?(announcement, user) do
|
||||
AnnouncementReadRelationship.exists?(user, announcement)
|
||||
end
|
||||
|
||||
def mark_read_by(announcement, user) do
|
||||
AnnouncementReadRelationship.mark_read(user, announcement)
|
||||
end
|
||||
|
||||
def render_json(announcement, opts \\ []) do
|
||||
extra_params =
|
||||
case Keyword.fetch(opts, :for) do
|
||||
{:ok, user} ->
|
||||
{:ok, user} when not is_nil(user) ->
|
||||
%{read: read_by?(announcement, user)}
|
||||
|
||||
_ ->
|
||||
%{}
|
||||
end
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.AnnouncementReadRelationship do
|
||||
use Ecto.Schema
|
||||
|
||||
import Ecto.Changeset
|
||||
|
||||
alias FlakeId.Ecto.CompatType
|
||||
alias Pleroma.Announcement
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.User
|
||||
|
||||
@type t :: %__MODULE__{}
|
||||
|
||||
schema "announcement_read_relationships" do
|
||||
belongs_to(:user, User, type: CompatType)
|
||||
belongs_to(:announcement, Announcement, type: CompatType)
|
||||
|
||||
timestamps(updated_at: false)
|
||||
end
|
||||
|
||||
def mark_read(user, announcement) do
|
||||
%__MODULE__{}
|
||||
|> cast(%{user_id: user.id, announcement_id: announcement.id}, [:user_id, :announcement_id])
|
||||
|> validate_required([:user_id, :announcement_id])
|
||||
|> foreign_key_constraint(:user_id)
|
||||
|> foreign_key_constraint(:announcement_id)
|
||||
|> unique_constraint([:user_id, :announcement_id])
|
||||
|> Repo.insert()
|
||||
end
|
||||
|
||||
def mark_unread(user, announcement) do
|
||||
with relationship <- get(user, announcement),
|
||||
{:exists, true} <- {:exists, not is_nil(relationship)},
|
||||
{:ok, _} <- Repo.delete(relationship) do
|
||||
:ok
|
||||
else
|
||||
{:exists, false} ->
|
||||
:ok
|
||||
|
||||
_ ->
|
||||
:error
|
||||
end
|
||||
end
|
||||
|
||||
def get(user, announcement) do
|
||||
Repo.get_by(__MODULE__, user_id: user.id, announcement_id: announcement.id)
|
||||
end
|
||||
|
||||
def exists?(user, announcement) do
|
||||
not is_nil(get(user, announcement))
|
||||
end
|
||||
end
|
|
@ -0,0 +1,77 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.ApiSpec.AnnouncementOperation do
|
||||
alias OpenApiSpex.Operation
|
||||
alias OpenApiSpex.Schema
|
||||
alias Pleroma.Web.ApiSpec.Schemas.Announcement
|
||||
alias Pleroma.Web.ApiSpec.Schemas.ApiError
|
||||
|
||||
def open_api_operation(action) do
|
||||
operation = String.to_existing_atom("#{action}_operation")
|
||||
apply(__MODULE__, operation, [])
|
||||
end
|
||||
|
||||
def index_operation do
|
||||
%Operation{
|
||||
tags: ["Announcement"],
|
||||
summary: "Retrieve a list of announcements",
|
||||
operationId: "MastodonAPI.AnnouncementController.index",
|
||||
responses: %{
|
||||
200 => Operation.response("Response", "application/json", list_of_announcements()),
|
||||
403 => Operation.response("Forbidden", "application/json", ApiError)
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def show_operation do
|
||||
%Operation{
|
||||
tags: ["Announcement"],
|
||||
summary: "Display one announcement",
|
||||
operationId: "MastodonAPI.AnnouncementController.show",
|
||||
parameters: [
|
||||
Operation.parameter(
|
||||
:id,
|
||||
:path,
|
||||
:string,
|
||||
"announcement id"
|
||||
)
|
||||
],
|
||||
responses: %{
|
||||
200 => Operation.response("Response", "application/json", Announcement),
|
||||
403 => Operation.response("Forbidden", "application/json", ApiError),
|
||||
404 => Operation.response("Not Found", "application/json", ApiError)
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def mark_read_operation do
|
||||
%Operation{
|
||||
tags: ["Announcement"],
|
||||
summary: "Mark one announcement as read",
|
||||
operationId: "MastodonAPI.AnnouncementController.mark_read",
|
||||
security: [%{"oAuth" => ["write:accounts"]}],
|
||||
parameters: [
|
||||
Operation.parameter(
|
||||
:id,
|
||||
:path,
|
||||
:string,
|
||||
"announcement id"
|
||||
)
|
||||
],
|
||||
responses: %{
|
||||
200 => Operation.response("Response", "application/json", Announcement),
|
||||
403 => Operation.response("Forbidden", "application/json", ApiError),
|
||||
404 => Operation.response("Not Found", "application/json", ApiError)
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def list_of_announcements do
|
||||
%Schema{
|
||||
type: :array,
|
||||
items: Announcement
|
||||
}
|
||||
end
|
||||
end
|
|
@ -0,0 +1,61 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.MastodonAPI.AnnouncementController do
|
||||
use Pleroma.Web, :controller
|
||||
|
||||
# import Pleroma.Web.ControllerHelper,
|
||||
# only: [
|
||||
# json_response: 3
|
||||
# ]
|
||||
|
||||
alias Pleroma.Announcement
|
||||
alias Pleroma.Web.Plugs.OAuthScopesPlug
|
||||
|
||||
plug(Pleroma.Web.ApiSpec.CastAndValidate)
|
||||
|
||||
# MastodonAPI specs do not have oauth requirements for showing
|
||||
# announcements, but we have "private instance" options. When that
|
||||
# is set, require read:accounts scope, symmetric to write:accounts
|
||||
# for `mark_read`.
|
||||
plug(
|
||||
OAuthScopesPlug,
|
||||
%{fallback: :proceed_unauthenticated, scopes: ["read:accounts"]}
|
||||
when action in [:show, :index]
|
||||
)
|
||||
|
||||
# Same as in MastodonAPI specs
|
||||
plug(
|
||||
OAuthScopesPlug,
|
||||
%{fallback: :proceed_unauthenticated, scopes: ["write:accounts"]}
|
||||
when action in [:mark_read]
|
||||
)
|
||||
|
||||
action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
|
||||
|
||||
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.AnnouncementOperation
|
||||
|
||||
@doc "GET /api/v1/announcements"
|
||||
def index(%{assigns: %{user: user}} = conn, _params) do
|
||||
render(conn, "index.json", announcements: all_visible(), user: user)
|
||||
end
|
||||
|
||||
def index(conn, _params) do
|
||||
render(conn, "index.json", announcements: all_visible(), user: nil)
|
||||
end
|
||||
|
||||
defp all_visible do
|
||||
Announcement.list_all()
|
||||
end
|
||||
|
||||
@doc "POST /api/v1/announcements/:id/dismiss"
|
||||
def mark_read(_conn, _params) do
|
||||
{:error, :not_found}
|
||||
end
|
||||
|
||||
@doc "POST /api/v1/announcements/:id"
|
||||
def show(_conn, _params) do
|
||||
{:error, :not_found}
|
||||
end
|
||||
end
|
|
@ -0,0 +1,15 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.MastodonAPI.AnnouncementView do
|
||||
use Pleroma.Web, :view
|
||||
|
||||
def render("index.json", %{announcements: announcements, user: user}) do
|
||||
render_many(announcements, __MODULE__, "show.json", user: user)
|
||||
end
|
||||
|
||||
def render("show.json", %{announcement: announcement, user: user}) do
|
||||
Pleroma.Announcement.render_json(announcement, for: user)
|
||||
end
|
||||
end
|
|
@ -580,6 +580,8 @@ defmodule Pleroma.Web.Router do
|
|||
get("/timelines/home", TimelineController, :home)
|
||||
get("/timelines/direct", TimelineController, :direct)
|
||||
get("/timelines/list/:list_id", TimelineController, :list)
|
||||
|
||||
post("/announcements/:id/dismiss", AnnouncementController, :mark_read)
|
||||
end
|
||||
|
||||
scope "/api/v1", Pleroma.Web.MastodonAPI do
|
||||
|
@ -624,6 +626,9 @@ defmodule Pleroma.Web.Router do
|
|||
get("/polls/:id", PollController, :show)
|
||||
|
||||
get("/directory", DirectoryController, :index)
|
||||
|
||||
get("/announcements", AnnouncementController, :index)
|
||||
get("/announcements/:id", AnnouncementController, :show)
|
||||
end
|
||||
|
||||
scope "/api/v2", Pleroma.Web.MastodonAPI do
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.AnnouncementReadRelationshipTest do
|
||||
alias Pleroma.AnnouncementReadRelationship
|
||||
|
||||
use Pleroma.DataCase, async: true
|
||||
|
||||
import Pleroma.Factory
|
||||
|
||||
setup do
|
||||
{:ok, user: insert(:user), announcement: insert(:announcement)}
|
||||
end
|
||||
|
||||
describe "mark_read/2" do
|
||||
test "should insert relationship", %{user: user, announcement: announcement} do
|
||||
{:ok, _} = AnnouncementReadRelationship.mark_read(user, announcement)
|
||||
|
||||
assert AnnouncementReadRelationship.exists?(user, announcement)
|
||||
end
|
||||
end
|
||||
|
||||
describe "mark_unread/2" do
|
||||
test "should delete relationship", %{user: user, announcement: announcement} do
|
||||
{:ok, _} = AnnouncementReadRelationship.mark_read(user, announcement)
|
||||
|
||||
assert :ok = AnnouncementReadRelationship.mark_unread(user, announcement)
|
||||
refute AnnouncementReadRelationship.exists?(user, announcement)
|
||||
end
|
||||
|
||||
test "should not fail if relationship does not exist", %{
|
||||
user: user,
|
||||
announcement: announcement
|
||||
} do
|
||||
assert :ok = AnnouncementReadRelationship.mark_unread(user, announcement)
|
||||
refute AnnouncementReadRelationship.exists?(user, announcement)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,54 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
|
||||
use Pleroma.Web.ConnCase
|
||||
|
||||
import Pleroma.Factory
|
||||
|
||||
alias Pleroma.AnnouncementReadRelationship
|
||||
|
||||
describe "GET /api/v1/announcements" do
|
||||
test "it lists all announcements" do
|
||||
%{id: id} = insert(:announcement)
|
||||
|
||||
response =
|
||||
build_conn()
|
||||
|> get("/api/v1/announcements")
|
||||
|> json_response_and_validate_schema(:ok)
|
||||
|
||||
assert [%{"id" => ^id}] = response
|
||||
refute Map.has_key?(Enum.at(response, 0), "read")
|
||||
end
|
||||
|
||||
test "when authenticated, also expose read property" do
|
||||
%{id: id} = insert(:announcement)
|
||||
|
||||
%{conn: conn} = oauth_access(["read:accounts"])
|
||||
|
||||
response =
|
||||
conn
|
||||
|> get("/api/v1/announcements")
|
||||
|> json_response_and_validate_schema(:ok)
|
||||
|
||||
assert [%{"id" => ^id, "read" => false}] = response
|
||||
end
|
||||
|
||||
test "when authenticated and announcement is read by user" do
|
||||
%{id: id} = announcement = insert(:announcement)
|
||||
user = insert(:user)
|
||||
|
||||
AnnouncementReadRelationship.mark_read(user, announcement)
|
||||
|
||||
%{conn: conn} = oauth_access(["read:accounts"], user: user)
|
||||
|
||||
response =
|
||||
conn
|
||||
|> get("/api/v1/announcements")
|
||||
|> json_response_and_validate_schema(:ok)
|
||||
|
||||
assert [%{"id" => ^id, "read" => true}] = response
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue