Page MenuHomePhorge

No OneTemporary

Size
12 KB
Referenced Files
None
Subscribers
None
diff --git a/lib/pleroma/marker.ex b/lib/pleroma/marker.ex
index a32546094..2d217a0b7 100644
--- a/lib/pleroma/marker.ex
+++ b/lib/pleroma/marker.ex
@@ -1,102 +1,127 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Marker do
use Ecto.Schema
import Ecto.Changeset
import Ecto.Query
alias Ecto.Multi
alias Pleroma.Notification
alias Pleroma.Repo
alias Pleroma.User
alias __MODULE__
@timelines ["notifications"]
+ @type t :: %__MODULE__{}
schema "markers" do
field(:last_read_id, :string, default: "")
field(:timeline, :string, default: "")
field(:lock_version, :integer, default: 0)
field(:unread_count, :integer, default: 0)
belongs_to(:user, User, type: FlakeId.Ecto.CompatType)
timestamps()
end
- def get_markers(user, timelines \\ []) do
- Repo.all(get_query(user, timelines))
+ @doc """
+ Gets markers by user and timeline.
+
+ opts:
+ `recount_unread` - run force recount unread notifications for `true` value
+ """
+ @spec get_markers(User.t(), list(String), map()) :: list(t())
+ def get_markers(user, timelines \\ [], opts \\ %{}) do
+ user
+ |> get_query(timelines)
+ |> recount_unread_notifications(opts[:recount_unread])
+ |> Repo.all()
end
def upsert(%User{} = user, attrs) do
attrs
|> Map.take(@timelines)
|> Enum.reduce(Multi.new(), fn {timeline, timeline_attrs}, multi ->
marker =
user
|> get_marker(timeline)
|> changeset(timeline_attrs)
Multi.insert(multi, timeline, marker,
returning: true,
on_conflict: {:replace, [:last_read_id, :unread_count]},
conflict_target: [:user_id, :timeline]
)
end)
|> Repo.transaction()
end
@spec multi_set_unread_count(Multi.t(), User.t(), String.t()) :: Multi.t()
def multi_set_unread_count(multi, %User{} = user, "notifications") do
multi
|> Multi.run(:counters, fn _repo, _changes ->
{:ok,
%{
unread_count: Repo.aggregate(Notification.unread_count_query(user), :count, :id),
last_read_id: Repo.one(Notification.last_read_query(user))
}}
end)
|> Multi.insert(
:marker,
fn %{counters: attrs} ->
%Marker{timeline: "notifications", user_id: user.id}
|> struct(attrs)
|> Ecto.Changeset.change()
end,
returning: true,
on_conflict: {:replace, [:last_read_id, :unread_count]},
conflict_target: [:user_id, :timeline]
)
end
def multi_set_unread_count(multi, _, _), do: multi
defp get_marker(user, timeline) do
case Repo.find_resource(get_query(user, timeline)) do
{:ok, marker} -> %__MODULE__{marker | user: user}
_ -> %__MODULE__{timeline: timeline, user_id: user.id}
end
end
@doc false
defp changeset(marker, attrs) do
marker
|> cast(attrs, [:last_read_id, :unread_count])
|> validate_required([:user_id, :timeline, :last_read_id])
|> validate_inclusion(:timeline, @timelines)
end
defp by_timeline(query, timeline) do
from(m in query, where: m.timeline in ^List.wrap(timeline))
end
defp by_user_id(query, id), do: from(m in query, where: m.user_id == ^id)
defp get_query(user, timelines) do
__MODULE__
|> by_user_id(user.id)
|> by_timeline(timelines)
end
+
+ defp recount_unread_notifications(query, true) do
+ from(
+ q in query,
+ left_join: n in "notifications",
+ on: n.user_id == q.user_id and n.seen == false,
+ group_by: [:id],
+ select_merge: %{
+ unread_count: fragment("count(?)", n.id)
+ }
+ )
+ end
+
+ defp recount_unread_notifications(query, _), do: query
end
diff --git a/lib/pleroma/web/mastodon_api/controllers/marker_controller.ex b/lib/pleroma/web/mastodon_api/controllers/marker_controller.ex
index ce025624d..6649ffbda 100644
--- a/lib/pleroma/web/mastodon_api/controllers/marker_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/marker_controller.ex
@@ -1,32 +1,38 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.MastodonAPI.MarkerController do
use Pleroma.Web, :controller
alias Pleroma.Plugs.OAuthScopesPlug
plug(
OAuthScopesPlug,
%{scopes: ["read:statuses"]}
when action == :index
)
plug(OAuthScopesPlug, %{scopes: ["write:statuses"]} when action == :upsert)
plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug)
action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
# GET /api/v1/markers
def index(%{assigns: %{user: user}} = conn, params) do
- markers = Pleroma.Marker.get_markers(user, params["timeline"])
+ markers =
+ Pleroma.Marker.get_markers(
+ user,
+ params["timeline"],
+ %{recount_unread: true}
+ )
+
render(conn, "markers.json", %{markers: markers})
end
# POST /api/v1/markers
def upsert(%{assigns: %{user: user}} = conn, params) do
with {:ok, result} <- Pleroma.Marker.upsert(user, params),
markers <- Map.values(result) do
render(conn, "markers.json", %{markers: markers})
end
end
end
diff --git a/test/marker_test.exs b/test/marker_test.exs
index 5d03db48e..7b1d2218a 100644
--- a/test/marker_test.exs
+++ b/test/marker_test.exs
@@ -1,72 +1,86 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.MarkerTest do
use Pleroma.DataCase
alias Pleroma.Marker
import Pleroma.Factory
describe "multi_set_unread_count/3" do
test "returns multi" do
user = insert(:user)
assert %Ecto.Multi{
operations: [marker: {:run, _}, counters: {:run, _}]
} =
Marker.multi_set_unread_count(
Ecto.Multi.new(),
user,
"notifications"
)
end
test "return empty multi" do
user = insert(:user)
multi = Ecto.Multi.new()
assert Marker.multi_set_unread_count(multi, user, "home") == multi
end
end
describe "get_markers/2" do
test "returns user markers" do
user = insert(:user)
marker = insert(:marker, user: user)
insert(:marker, timeline: "home", user: user)
assert Marker.get_markers(user, ["notifications"]) == [refresh_record(marker)]
end
+
+ test "returns user markers with recount unread notifications" do
+ user = insert(:user)
+ marker = insert(:marker, user: user)
+ insert(:notification, user: user)
+ insert(:notification, user: user)
+ insert(:marker, timeline: "home", user: user)
+
+ assert Marker.get_markers(
+ user,
+ ["notifications"],
+ %{recount_unread: true}
+ ) == [%Marker{refresh_record(marker) | unread_count: 2}]
+ end
end
describe "upsert/2" do
test "creates a marker" do
user = insert(:user)
{:ok, %{"notifications" => %Marker{} = marker}} =
Marker.upsert(
user,
%{"notifications" => %{"last_read_id" => "34"}}
)
assert marker.timeline == "notifications"
assert marker.last_read_id == "34"
assert marker.lock_version == 0
end
test "updates exist marker" do
user = insert(:user)
marker = insert(:marker, user: user, last_read_id: "8909")
{:ok, %{"notifications" => %Marker{}}} =
Marker.upsert(
user,
%{"notifications" => %{"last_read_id" => "9909"}}
)
marker = refresh_record(marker)
assert marker.timeline == "notifications"
assert marker.last_read_id == "9909"
assert marker.lock_version == 0
end
end
end
diff --git a/test/web/mastodon_api/controllers/marker_controller_test.exs b/test/web/mastodon_api/controllers/marker_controller_test.exs
index 8bcfcb7e1..64bf79bb1 100644
--- a/test/web/mastodon_api/controllers/marker_controller_test.exs
+++ b/test/web/mastodon_api/controllers/marker_controller_test.exs
@@ -1,127 +1,128 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.MastodonAPI.MarkerControllerTest do
use Pleroma.Web.ConnCase
import Pleroma.Factory
describe "GET /api/v1/markers" do
test "gets markers with correct scopes", %{conn: conn} do
user = insert(:user)
token = insert(:oauth_token, user: user, scopes: ["read:statuses"])
+ insert_list(7, :notification, user: user)
{:ok, %{"notifications" => marker}} =
Pleroma.Marker.upsert(
user,
- %{"notifications" => %{"last_read_id" => "69420", "unread_count" => 7}}
+ %{"notifications" => %{"last_read_id" => "69420"}}
)
response =
conn
|> assign(:user, user)
|> assign(:token, token)
|> get("/api/v1/markers", %{timeline: ["notifications"]})
|> json_response(200)
assert response == %{
"notifications" => %{
"last_read_id" => "69420",
"updated_at" => NaiveDateTime.to_iso8601(marker.updated_at),
"version" => 0,
"pleroma" => %{"unread_count" => 7}
}
}
end
test "gets markers with missed scopes", %{conn: conn} do
user = insert(:user)
token = insert(:oauth_token, user: user, scopes: [])
Pleroma.Marker.upsert(user, %{"notifications" => %{"last_read_id" => "69420"}})
response =
conn
|> assign(:user, user)
|> assign(:token, token)
|> get("/api/v1/markers", %{timeline: ["notifications"]})
|> json_response(403)
assert response == %{"error" => "Insufficient permissions: read:statuses."}
end
end
describe "POST /api/v1/markers" do
test "creates a marker with correct scopes", %{conn: conn} do
user = insert(:user)
token = insert(:oauth_token, user: user, scopes: ["write:statuses"])
response =
conn
|> assign(:user, user)
|> assign(:token, token)
|> post("/api/v1/markers", %{
home: %{last_read_id: "777"},
notifications: %{"last_read_id" => "69420"}
})
|> json_response(200)
assert %{
"notifications" => %{
"last_read_id" => "69420",
"updated_at" => _,
"version" => 0,
"pleroma" => %{"unread_count" => 0}
}
} = response
end
test "updates exist marker", %{conn: conn} do
user = insert(:user)
token = insert(:oauth_token, user: user, scopes: ["write:statuses"])
{:ok, %{"notifications" => marker}} =
Pleroma.Marker.upsert(
user,
%{"notifications" => %{"last_read_id" => "69477"}}
)
response =
conn
|> assign(:user, user)
|> assign(:token, token)
|> post("/api/v1/markers", %{
home: %{last_read_id: "777"},
notifications: %{"last_read_id" => "69888"}
})
|> json_response(200)
assert response == %{
"notifications" => %{
"last_read_id" => "69888",
"updated_at" => NaiveDateTime.to_iso8601(marker.updated_at),
"version" => 0,
"pleroma" => %{"unread_count" => 0}
}
}
end
test "creates a marker with missed scopes", %{conn: conn} do
user = insert(:user)
token = insert(:oauth_token, user: user, scopes: [])
response =
conn
|> assign(:user, user)
|> assign(:token, token)
|> post("/api/v1/markers", %{
home: %{last_read_id: "777"},
notifications: %{"last_read_id" => "69420"}
})
|> json_response(403)
assert response == %{"error" => "Insufficient permissions: write:statuses."}
end
end
end

File Metadata

Mime Type
text/x-diff
Expires
Sat, Aug 8, 12:18 PM (1 d, 10 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1723171
Default Alt Text
(12 KB)

Event Timeline