### Changed
- **Breaking:** Configuration: A setting to explicitly disable the mailer was added, defaulting to true, if you are using a mailer add `config :pleroma, Pleroma.Emails.Mailer, enabled: true` to your config
- **Breaking:** Configuration: `/media/` is now removed when `base_url` is configured, append `/media/` to your `base_url` config to keep the old behaviour if desired
+- **Breaking:** `/api/pleroma/notifications/read` is moved to `/api/v1/pleroma/notifications/read` and now supports `max_id` and responds with Mastodon API entities.
- Configuration: OpenGraph and TwitterCard providers enabled by default
- Configuration: Filter.AnonymizeFilename added ability to retain file extension with custom text
- Federation: Return 403 errors when trying to request pages from a user's follower/following collections if they have `hide_followers`/`hide_follows` set
## `/api/pleroma/admin/`…
See [Admin-API](Admin-API.md)
-## `/api/pleroma/notifications/read`
-### Mark a single notification as read
+## `/api/pleroma/v1/notifications/read`
+### Mark notifications as read
* Method `POST`
* Authentication: required
-* Params:
- * `id`: notification's id
-* Response: JSON. Returns `{"status": "success"}` if the reading was successful, otherwise returns `{"error": "error_msg"}`
+* Params (mutually exclusive):
+ * `id`: a single notification id to read
+ * `max_id`: read all notifications up to this id
+* Response: Notification entity/Array of Notification entities. In case of `max_id`, only the first 80 notifications will be returned.
## `/api/v1/pleroma/accounts/:id/subscribe`
### Subscribe to receive notifications for all statuses posted by a user
n in Notification,
where: n.user_id == ^user_id,
where: n.id <= ^id,
+ where: n.seen == false,
update: [
set: [
seen: true,
updated_at: ^NaiveDateTime.utc_now()
]
- ]
+ ],
+ # Ideally we would preload object and activities here
+ # but Ecto does not support preloads in update_all
+ select: n.id
)
- Repo.update_all(query, [])
+ {_, notification_ids} = Repo.update_all(query, [])
+
+ from(n in Notification, where: n.id in ^notification_ids)
+ |> join(:inner, [n], activity in assoc(n, :activity))
+ |> join(:left, [n, a], object in Object,
+ on:
+ fragment(
+ "(?->>'id') = COALESCE((? -> 'object'::text) ->> 'id'::text)",
+ object.data,
+ a.data
+ )
+ )
+ |> preload([n, a, o], activity: {a, object: o})
+ |> Repo.all()
end
def read_one(%User{} = user, notification_id) do
import Pleroma.Web.ControllerHelper, only: [add_link_headers: 7]
alias Pleroma.Conversation.Participation
+ alias Pleroma.Notification
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.MastodonAPI.ConversationView
+ alias Pleroma.Web.MastodonAPI.NotificationView
alias Pleroma.Web.MastodonAPI.StatusView
def conversation(%{assigns: %{user: user}} = conn, %{"id" => participation_id}) do
|> render("participation.json", %{participation: participation, for: user})
end
end
+
+ def read_notification(%{assigns: %{user: user}} = conn, %{"id" => notification_id}) do
+ with {:ok, notification} <- Notification.read_one(user, notification_id) do
+ conn
+ |> put_view(NotificationView)
+ |> render("show.json", %{notification: notification, for: user})
+ else
+ {:error, message} ->
+ conn
+ |> put_status(:bad_request)
+ |> json(%{"error" => message})
+ end
+ end
+
+ def read_notification(%{assigns: %{user: user}} = conn, %{"max_id" => max_id}) do
+ with notifications <- Notification.set_read_up_to(user, max_id) do
+ notifications = Enum.take(notifications, 80)
+
+ conn
+ |> put_view(NotificationView)
+ |> render("index.json", %{notifications: notifications, for: user})
+ end
+ end
end
post("/blocks_import", UtilController, :blocks_import)
post("/follow_import", UtilController, :follow_import)
end
-
- scope [] do
- pipe_through(:oauth_read)
-
- post("/notifications/read", UtilController, :notifications_read)
- end
end
scope "/oauth", Pleroma.Web.OAuth do
scope [] do
pipe_through(:oauth_write)
patch("/conversations/:id", PleromaAPIController, :update_conversation)
+ post("/notifications/read", PleromaAPIController, :read_notification)
end
end
use Pleroma.Web.ConnCase
alias Pleroma.Conversation.Participation
+ alias Pleroma.Notification
alias Pleroma.Repo
alias Pleroma.Web.CommonAPI
assert user in participation.recipients
assert other_user in participation.recipients
end
+
+ describe "POST /api/v1/pleroma/notifications/read" do
+ test "it marks a single notification as read", %{conn: conn} do
+ user1 = insert(:user)
+ user2 = insert(:user)
+ {:ok, activity1} = CommonAPI.post(user2, %{"status" => "hi @#{user1.nickname}"})
+ {:ok, activity2} = CommonAPI.post(user2, %{"status" => "hi @#{user1.nickname}"})
+ {:ok, [notification1]} = Notification.create_notifications(activity1)
+ {:ok, [notification2]} = Notification.create_notifications(activity2)
+
+ response =
+ conn
+ |> assign(:user, user1)
+ |> post("/api/v1/pleroma/notifications/read", %{"id" => "#{notification1.id}"})
+ |> json_response(:ok)
+
+ assert %{"pleroma" => %{"is_seen" => true}} = response
+ assert Repo.get(Notification, notification1.id).seen
+ refute Repo.get(Notification, notification2.id).seen
+ end
+
+ test "it marks multiple notifications as read", %{conn: conn} do
+ user1 = insert(:user)
+ user2 = insert(:user)
+ {:ok, _activity1} = CommonAPI.post(user2, %{"status" => "hi @#{user1.nickname}"})
+ {:ok, _activity2} = CommonAPI.post(user2, %{"status" => "hi @#{user1.nickname}"})
+ {:ok, _activity3} = CommonAPI.post(user2, %{"status" => "HIE @#{user1.nickname}"})
+
+ [notification3, notification2, notification1] = Notification.for_user(user1, %{limit: 3})
+
+ [response1, response2] =
+ conn
+ |> assign(:user, user1)
+ |> post("/api/v1/pleroma/notifications/read", %{"max_id" => "#{notification2.id}"})
+ |> json_response(:ok)
+
+ assert %{"pleroma" => %{"is_seen" => true}} = response1
+ assert %{"pleroma" => %{"is_seen" => true}} = response2
+ assert Repo.get(Notification, notification1.id).seen
+ assert Repo.get(Notification, notification2.id).seen
+ refute Repo.get(Notification, notification3.id).seen
+ end
+
+ test "it returns error when notification not found", %{conn: conn} do
+ user1 = insert(:user)
+
+ response =
+ conn
+ |> assign(:user, user1)
+ |> post("/api/v1/pleroma/notifications/read", %{"id" => "22222222222222"})
+ |> json_response(:bad_request)
+
+ assert response == %{"error" => "Cannot get notification"}
+ end
+ end
end
defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do
use Pleroma.Web.ConnCase
- alias Pleroma.Notification
alias Pleroma.Repo
alias Pleroma.User
alias Pleroma.Web.CommonAPI
end
end
- describe "POST /api/pleroma/notifications/read" do
- test "it marks a single notification as read", %{conn: conn} do
- user1 = insert(:user)
- user2 = insert(:user)
- {:ok, activity1} = CommonAPI.post(user2, %{"status" => "hi @#{user1.nickname}"})
- {:ok, activity2} = CommonAPI.post(user2, %{"status" => "hi @#{user1.nickname}"})
- {:ok, [notification1]} = Notification.create_notifications(activity1)
- {:ok, [notification2]} = Notification.create_notifications(activity2)
-
- conn
- |> assign(:user, user1)
- |> post("/api/pleroma/notifications/read", %{"id" => "#{notification1.id}"})
- |> json_response(:ok)
-
- assert Repo.get(Notification, notification1.id).seen
- refute Repo.get(Notification, notification2.id).seen
- end
-
- test "it returns error when notification not found", %{conn: conn} do
- user1 = insert(:user)
-
- response =
- conn
- |> assign(:user, user1)
- |> post("/api/pleroma/notifications/read", %{"id" => "22222222222222"})
- |> json_response(403)
-
- assert response == %{"error" => "Cannot get notification"}
- end
- end
-
describe "PUT /api/pleroma/notification_settings" do
test "it updates notification settings", %{conn: conn} do
user = insert(:user)