Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F85805662
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Award Token
Flag For Later
Size
19 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/lib/pleroma/announcement.ex b/lib/pleroma/announcement.ex
index 8c15d5bdf..6c11eff7e 100644
--- a/lib/pleroma/announcement.ex
+++ b/lib/pleroma/announcement.ex
@@ -1,127 +1,134 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Announcement do
use Ecto.Schema
import Ecto.Changeset, only: [cast: 3, validate_required: 2]
import Ecto.Query
alias Pleroma.AnnouncementReadRelationship
alias Pleroma.Repo
@type t :: %__MODULE__{}
@primary_key {:id, FlakeId.Ecto.CompatType, autogenerate: true}
schema "announcements" do
field(:data, :map)
field(:starts_at, :naive_datetime)
field(:ends_at, :naive_datetime)
timestamps()
end
def change(struct, params \\ %{}) do
struct
|> cast(validate_params(struct, params), [:data, :starts_at, :ends_at])
|> validate_required([:data])
end
defp validate_params(struct, params) do
base_data =
%{
"content" => "",
"all_day" => false
}
|> Map.merge((struct && struct.data) || %{})
merged_data =
Map.merge(base_data, params.data)
|> Map.take(["content", "all_day"])
params
|> Map.merge(%{data: merged_data})
end
def add(params) do
changeset = change(%__MODULE__{}, params)
Repo.insert(changeset)
end
def update(announcement, params) do
changeset = change(announcement, params)
Repo.update(changeset)
end
def list_all do
__MODULE__
|> Repo.all()
end
+ def list_paginated(%{limit: limited_number, offset: offset_number}) do
+ __MODULE__
+ |> limit(^limited_number)
+ |> offset(^offset_number)
+ |> Repo.all()
+ end
+
def get_by_id(id) do
Repo.get_by(__MODULE__, id: id)
end
def delete_by_id(id) do
with announcement when not is_nil(announcement) <- get_by_id(id),
{:ok, _} <- Repo.delete(announcement) do
:ok
else
_ ->
:error
end
end
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} when not is_nil(user) ->
%{read: read_by?(announcement, user)}
_ ->
%{}
end
base = %{
id: announcement.id,
content: announcement.data["content"],
starts_at: announcement.starts_at,
ends_at: announcement.ends_at,
all_day: announcement.data["all_day"],
published_at: announcement.inserted_at,
updated_at: announcement.updated_at,
mentions: [],
statuses: [],
tags: [],
emojis: [],
reactions: []
}
base
|> Map.merge(extra_params)
end
# "visible" means:
# starts_at < time < ends_at
def list_all_visible_when(time) do
__MODULE__
|> where([a], is_nil(a.starts_at) or a.starts_at < ^time)
|> where([a], is_nil(a.ends_at) or a.ends_at > ^time)
|> Repo.all()
end
def list_all_visible do
list_all_visible_when(NaiveDateTime.utc_now())
end
end
diff --git a/lib/pleroma/web/admin_api/controllers/announcement_controller.ex b/lib/pleroma/web/admin_api/controllers/announcement_controller.ex
index 6195e582a..6ad5fc12c 100644
--- a/lib/pleroma/web/admin_api/controllers/announcement_controller.ex
+++ b/lib/pleroma/web/admin_api/controllers/announcement_controller.ex
@@ -1,78 +1,83 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.AdminAPI.AnnouncementController do
use Pleroma.Web, :controller
alias Pleroma.Announcement
alias Pleroma.Web.ControllerHelper
alias Pleroma.Web.Plugs.OAuthScopesPlug
plug(Pleroma.Web.ApiSpec.CastAndValidate)
plug(OAuthScopesPlug, %{scopes: ["admin:write"]} when action in [:create, :delete, :change])
plug(OAuthScopesPlug, %{scopes: ["admin:read"]} when action in [:index, :show])
action_fallback(Pleroma.Web.AdminAPI.FallbackController)
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.Admin.AnnouncementOperation
- def index(conn, _params) do
- announcements = Announcement.list_all()
+ defp default_limit, do: 20
+
+ def index(conn, params) do
+ limit = Map.get(params, :limit, default_limit())
+ offset = Map.get(params, :offset, 0)
+
+ announcements = Announcement.list_paginated(%{limit: limit, offset: offset})
render(conn, "index.json", announcements: announcements)
end
def show(conn, %{id: id} = _params) do
announcement = Announcement.get_by_id(id)
if is_nil(announcement) do
{:error, :not_found}
else
render(conn, "show.json", announcement: announcement)
end
end
def create(%{body_params: params} = conn, _params) do
with {:ok, announcement} <- Announcement.add(change_params(params)) do
render(conn, "show.json", announcement: announcement)
else
_ ->
{:error, 400}
end
end
def change_params(orig_params) do
data =
%{}
|> Pleroma.Maps.put_if_present("content", orig_params, &Map.fetch(&1, :content))
|> Pleroma.Maps.put_if_present("all_day", orig_params, &Map.fetch(&1, :all_day))
orig_params
|> Map.merge(%{data: data})
end
def change(%{body_params: params} = conn, %{id: id} = _params) do
with announcement <- Announcement.get_by_id(id),
{:exists, true} <- {:exists, not is_nil(announcement)},
{:ok, announcement} <- Announcement.update(announcement, change_params(params)) do
render(conn, "show.json", announcement: announcement)
else
{:exists, false} ->
{:error, :not_found}
_ ->
{:error, 400}
end
end
def delete(conn, %{id: id} = _params) do
case Announcement.delete_by_id(id) do
:ok ->
conn
|> ControllerHelper.json_response(:ok, %{})
_ ->
{:error, :not_found}
end
end
end
diff --git a/lib/pleroma/web/api_spec/operations/admin/announcement_operation.ex b/lib/pleroma/web/api_spec/operations/admin/announcement_operation.ex
index cdf04d357..58a039e72 100644
--- a/lib/pleroma/web/api_spec/operations/admin/announcement_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/admin/announcement_operation.ex
@@ -1,149 +1,165 @@
# 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.Admin.AnnouncementOperation do
alias OpenApiSpex.Operation
alias OpenApiSpex.Schema
alias Pleroma.Web.ApiSpec.Schemas.Announcement
alias Pleroma.Web.ApiSpec.Schemas.ApiError
import Pleroma.Web.ApiSpec.Helpers
def open_api_operation(action) do
operation = String.to_existing_atom("#{action}_operation")
apply(__MODULE__, operation, [])
end
def index_operation do
%Operation{
tags: ["Announcement managment"],
summary: "Retrieve a list of announcements",
operationId: "AdminAPI.AnnouncementController.index",
security: [%{"oAuth" => ["admin:read"]}],
+ parameters: [
+ Operation.parameter(
+ :limit,
+ :query,
+ %Schema{type: :integer, minimum: 1},
+ "the maximum number of announcements to return"
+ ),
+ Operation.parameter(
+ :offset,
+ :query,
+ %Schema{type: :integer, minimum: 0},
+ "the offset of the first announcement to return"
+ )
+ | admin_api_params()
+ ],
responses: %{
200 => Operation.response("Response", "application/json", list_of_announcements()),
+ 400 => Operation.response("Forbidden", "application/json", ApiError),
403 => Operation.response("Forbidden", "application/json", ApiError)
}
}
end
def show_operation do
%Operation{
tags: ["Announcement managment"],
summary: "Display one announcement",
operationId: "AdminAPI.AnnouncementController.show",
security: [%{"oAuth" => ["admin:read"]}],
parameters: [
Operation.parameter(
:id,
:path,
:string,
"announcement id"
)
| admin_api_params()
],
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 delete_operation do
%Operation{
tags: ["Announcement managment"],
summary: "Delete one announcement",
operationId: "AdminAPI.AnnouncementController.delete",
security: [%{"oAuth" => ["admin:write"]}],
parameters: [
Operation.parameter(
:id,
:path,
:string,
"announcement id"
)
| admin_api_params()
],
responses: %{
200 => Operation.response("Response", "application/json", %Schema{type: :object}),
403 => Operation.response("Forbidden", "application/json", ApiError),
404 => Operation.response("Not Found", "application/json", ApiError)
}
}
end
def create_operation do
%Operation{
tags: ["Announcement managment"],
summary: "Create one announcement",
operationId: "AdminAPI.AnnouncementController.create",
security: [%{"oAuth" => ["admin:write"]}],
requestBody: request_body("Parameters", create_request(), required: true),
responses: %{
200 => Operation.response("Response", "application/json", Announcement),
400 => Operation.response("Bad Request", "application/json", ApiError),
403 => Operation.response("Forbidden", "application/json", ApiError)
}
}
end
def change_operation do
%Operation{
tags: ["Announcement managment"],
summary: "Change one announcement",
operationId: "AdminAPI.AnnouncementController.change",
security: [%{"oAuth" => ["admin:write"]}],
parameters: [
Operation.parameter(
:id,
:path,
:string,
"announcement id"
)
| admin_api_params()
],
requestBody: request_body("Parameters", change_request(), required: true),
responses: %{
200 => Operation.response("Response", "application/json", Announcement),
400 => Operation.response("Bad Request", "application/json", ApiError),
403 => Operation.response("Forbidden", "application/json", ApiError),
404 => Operation.response("Not Found", "application/json", ApiError)
}
}
end
defp create_or_change_props do
%{
content: %Schema{type: :string},
starts_at: %Schema{type: :string, format: "date-time", nullable: true},
ends_at: %Schema{type: :string, format: "date-time", nullable: true},
all_day: %Schema{type: :boolean}
}
end
def create_request do
%Schema{
title: "AnnouncementCreateRequest",
type: :object,
required: [:content],
properties: create_or_change_props()
}
end
def change_request do
%Schema{
title: "AnnouncementChangeRequest",
type: :object,
properties: create_or_change_props()
}
end
def list_of_announcements do
%Schema{
type: :array,
items: Announcement
}
end
end
diff --git a/test/pleroma/web/admin_api/controllers/announcement_controller_test.exs b/test/pleroma/web/admin_api/controllers/announcement_controller_test.exs
index c6b2163d0..56da1c6aa 100644
--- a/test/pleroma/web/admin_api/controllers/announcement_controller_test.exs
+++ b/test/pleroma/web/admin_api/controllers/announcement_controller_test.exs
@@ -1,186 +1,234 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.AdminAPI.AnnouncementControllerTest do
use Pleroma.Web.ConnCase
import Pleroma.Factory
setup do
admin = insert(:user, is_admin: true)
token = insert(:oauth_admin_token, user: admin)
conn =
build_conn()
|> assign(:user, admin)
|> assign(:token, token)
{:ok, %{admin: admin, token: token, conn: conn}}
end
describe "GET /api/v1/pleroma/admin/announcements" do
test "it lists all announcements", %{conn: conn} do
%{id: id} = insert(:announcement)
response =
conn
|> get("/api/v1/pleroma/admin/announcements")
|> json_response_and_validate_schema(:ok)
assert [%{"id" => ^id}] = response
end
+
+ test "it paginates announcements", %{conn: conn} do
+ _announcements = Enum.map(0..20, fn _ -> insert(:announcement) end)
+
+ response =
+ conn
+ |> get("/api/v1/pleroma/admin/announcements")
+ |> json_response_and_validate_schema(:ok)
+
+ assert length(response) == 20
+ end
+
+ test "it paginates announcements with custom params", %{conn: conn} do
+ announcements = Enum.map(0..20, fn _ -> insert(:announcement) end)
+
+ response =
+ conn
+ |> get("/api/v1/pleroma/admin/announcements", limit: 5, offset: 7)
+ |> json_response_and_validate_schema(:ok)
+
+ assert length(response) == 5
+ assert Enum.at(response, 0)["id"] == Enum.at(announcements, 7).id
+ end
+
+ test "it returns empty list with out-of-bounds offset", %{conn: conn} do
+ _announcements = Enum.map(0..20, fn _ -> insert(:announcement) end)
+
+ response =
+ conn
+ |> get("/api/v1/pleroma/admin/announcements", offset: 21)
+ |> json_response_and_validate_schema(:ok)
+
+ assert [] = response
+ end
+
+ test "it rejects invalid pagination params", %{conn: conn} do
+ conn
+ |> get("/api/v1/pleroma/admin/announcements", limit: 0)
+ |> json_response_and_validate_schema(400)
+
+ conn
+ |> get("/api/v1/pleroma/admin/announcements", limit: -1)
+ |> json_response_and_validate_schema(400)
+
+ conn
+ |> get("/api/v1/pleroma/admin/announcements", offset: -1)
+ |> json_response_and_validate_schema(400)
+ end
end
describe "GET /api/v1/pleroma/admin/announcements/:id" do
test "it displays one announcement", %{conn: conn} do
%{id: id} = insert(:announcement)
response =
conn
|> get("/api/v1/pleroma/admin/announcements/#{id}")
|> json_response_and_validate_schema(:ok)
assert %{"id" => ^id} = response
end
test "it returns not found for non-existent id", %{conn: conn} do
%{id: id} = insert(:announcement)
_response =
conn
|> get("/api/v1/pleroma/admin/announcements/#{id}xxx")
|> json_response_and_validate_schema(:not_found)
end
end
describe "DELETE /api/v1/pleroma/admin/announcements/:id" do
test "it deletes specified announcement", %{conn: conn} do
%{id: id} = insert(:announcement)
_response =
conn
|> delete("/api/v1/pleroma/admin/announcements/#{id}")
|> json_response_and_validate_schema(:ok)
end
test "it returns not found for non-existent id", %{conn: conn} do
%{id: id} = insert(:announcement)
_response =
conn
|> delete("/api/v1/pleroma/admin/announcements/#{id}xxx")
|> json_response_and_validate_schema(:not_found)
assert %{id: ^id} = Pleroma.Announcement.get_by_id(id)
end
end
describe "PATCH /api/v1/pleroma/admin/announcements/:id" do
test "it returns not found for non-existent id", %{conn: conn} do
%{id: id} = insert(:announcement)
_response =
conn
|> put_req_header("content-type", "application/json")
|> patch("/api/v1/pleroma/admin/announcements/#{id}xxx", %{})
|> json_response_and_validate_schema(:not_found)
assert %{id: ^id} = Pleroma.Announcement.get_by_id(id)
end
test "it updates a field", %{conn: conn} do
%{id: id} = insert(:announcement)
now = NaiveDateTime.utc_now() |> NaiveDateTime.truncate(:second)
starts_at = NaiveDateTime.add(now, -10, :second)
_response =
conn
|> put_req_header("content-type", "application/json")
|> patch("/api/v1/pleroma/admin/announcements/#{id}", %{
starts_at: NaiveDateTime.to_iso8601(starts_at)
})
|> json_response_and_validate_schema(:ok)
new = Pleroma.Announcement.get_by_id(id)
assert NaiveDateTime.compare(new.starts_at, starts_at) == :eq
end
test "it updates a data field", %{conn: conn} do
%{id: id} = announcement = insert(:announcement, data: %{"all_day" => true})
assert announcement.data["all_day"] == true
new_content = "new content"
response =
conn
|> put_req_header("content-type", "application/json")
|> patch("/api/v1/pleroma/admin/announcements/#{id}", %{
content: new_content
})
|> json_response_and_validate_schema(:ok)
assert response["content"] == new_content
assert response["all_day"] == true
new = Pleroma.Announcement.get_by_id(id)
assert new.data["content"] == new_content
assert new.data["all_day"] == true
end
test "it nullifies a nullable field", %{conn: conn} do
now = NaiveDateTime.utc_now() |> NaiveDateTime.truncate(:second)
starts_at = NaiveDateTime.add(now, -10, :second)
%{id: id} = insert(:announcement, starts_at: starts_at)
response =
conn
|> put_req_header("content-type", "application/json")
|> patch("/api/v1/pleroma/admin/announcements/#{id}", %{
starts_at: nil
})
|> json_response_and_validate_schema(:ok)
assert response["starts_at"] == nil
new = Pleroma.Announcement.get_by_id(id)
assert new.starts_at == nil
end
end
describe "POST /api/v1/pleroma/admin/announcements" do
test "it creates an announcement", %{conn: conn} do
content = "test post announcement api"
now = NaiveDateTime.utc_now() |> NaiveDateTime.truncate(:second)
starts_at = NaiveDateTime.add(now, -10, :second)
ends_at = NaiveDateTime.add(now, 10, :second)
response =
conn
|> put_req_header("content-type", "application/json")
|> post("/api/v1/pleroma/admin/announcements", %{
"content" => content,
"starts_at" => NaiveDateTime.to_iso8601(starts_at),
"ends_at" => NaiveDateTime.to_iso8601(ends_at),
"all_day" => true
})
|> json_response_and_validate_schema(:ok)
assert %{"content" => ^content, "all_day" => true} = response
announcement = Pleroma.Announcement.get_by_id(response["id"])
assert not is_nil(announcement)
assert NaiveDateTime.compare(announcement.starts_at, starts_at) == :eq
assert NaiveDateTime.compare(announcement.ends_at, ends_at) == :eq
end
end
end
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Sun, Oct 11, 7:15 PM (1 d, 20 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1785933
Default Alt Text
(19 KB)
Attached To
Mode
rPUBE pleroma-upstream
Attached
Detach File
Event Timeline
Log In to Comment