Merge branch 'feature/masto_api_markers' into 'develop'
[#1275] Markers /api/v1/markers See merge request pleroma/pleroma!1852
This commit is contained in:
commit
ce750dbab5
10 changed files with 353 additions and 0 deletions
|
@ -51,6 +51,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
- Admin API: `POST/DELETE /api/pleroma/admin/users/:nickname/permission_group/:permission_group` are deprecated in favor of: `POST/DELETE /api/pleroma/admin/users/permission_group/:permission_group` (both accept `nicknames` array), `DELETE /api/pleroma/admin/users` (`nickname` query param or `nickname` sent in JSON body) is deprecated in favor of: `DELETE /api/pleroma/admin/users` (`nicknames` query array param or `nicknames` sent in JSON body).
|
- Admin API: `POST/DELETE /api/pleroma/admin/users/:nickname/permission_group/:permission_group` are deprecated in favor of: `POST/DELETE /api/pleroma/admin/users/permission_group/:permission_group` (both accept `nicknames` array), `DELETE /api/pleroma/admin/users` (`nickname` query param or `nickname` sent in JSON body) is deprecated in favor of: `DELETE /api/pleroma/admin/users` (`nicknames` query array param or `nicknames` sent in JSON body).
|
||||||
- Admin API: Add `GET /api/pleroma/admin/relay` endpoint - lists all followed relays
|
- Admin API: Add `GET /api/pleroma/admin/relay` endpoint - lists all followed relays
|
||||||
- Pleroma API: `POST /api/v1/pleroma/conversations/read` to mark all conversations as read
|
- Pleroma API: `POST /api/v1/pleroma/conversations/read` to mark all conversations as read
|
||||||
|
- Mastodon API: Add `/api/v1/markers` for managing timeline read markers
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
- **Breaking:** Elixir >=1.8 is now required (was >= 1.7)
|
- **Breaking:** Elixir >=1.8 is now required (was >= 1.7)
|
||||||
|
|
74
lib/pleroma/marker.ex
Normal file
74
lib/pleroma/marker.ex
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
# 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.Repo
|
||||||
|
alias Pleroma.User
|
||||||
|
|
||||||
|
@timelines ["notifications"]
|
||||||
|
|
||||||
|
schema "markers" do
|
||||||
|
field(:last_read_id, :string, default: "")
|
||||||
|
field(:timeline, :string, default: "")
|
||||||
|
field(:lock_version, :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))
|
||||||
|
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]},
|
||||||
|
conflict_target: [:user_id, :timeline]
|
||||||
|
)
|
||||||
|
end)
|
||||||
|
|> Repo.transaction()
|
||||||
|
end
|
||||||
|
|
||||||
|
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])
|
||||||
|
|> 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
|
||||||
|
end
|
|
@ -0,0 +1,32 @@
|
||||||
|
# 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"])
|
||||||
|
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
|
17
lib/pleroma/web/mastodon_api/views/marker_view.ex
Normal file
17
lib/pleroma/web/mastodon_api/views/marker_view.ex
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
# 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.MarkerView do
|
||||||
|
use Pleroma.Web, :view
|
||||||
|
|
||||||
|
def render("markers.json", %{markers: markers}) do
|
||||||
|
Enum.reduce(markers, %{}, fn m, acc ->
|
||||||
|
Map.put_new(acc, m.timeline, %{
|
||||||
|
last_read_id: m.last_read_id,
|
||||||
|
version: m.lock_version,
|
||||||
|
updated_at: NaiveDateTime.to_iso8601(m.updated_at)
|
||||||
|
})
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
end
|
|
@ -405,6 +405,9 @@ defmodule Pleroma.Web.Router do
|
||||||
get("/push/subscription", SubscriptionController, :get)
|
get("/push/subscription", SubscriptionController, :get)
|
||||||
put("/push/subscription", SubscriptionController, :update)
|
put("/push/subscription", SubscriptionController, :update)
|
||||||
delete("/push/subscription", SubscriptionController, :delete)
|
delete("/push/subscription", SubscriptionController, :delete)
|
||||||
|
|
||||||
|
get("/markers", MarkerController, :index)
|
||||||
|
post("/markers", MarkerController, :upsert)
|
||||||
end
|
end
|
||||||
|
|
||||||
scope "/api/web", Pleroma.Web do
|
scope "/api/web", Pleroma.Web do
|
||||||
|
|
15
priv/repo/migrations/20191014181019_create_markers.exs
Normal file
15
priv/repo/migrations/20191014181019_create_markers.exs
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
defmodule Pleroma.Repo.Migrations.CreateMarkers do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
def change do
|
||||||
|
create_if_not_exists table(:markers) do
|
||||||
|
add(:user_id, references(:users, type: :uuid, on_delete: :delete_all))
|
||||||
|
add(:timeline, :string, default: "", null: false)
|
||||||
|
add(:last_read_id, :string, default: "", null: false)
|
||||||
|
add(:lock_version, :integer, default: 0, null: false)
|
||||||
|
timestamps()
|
||||||
|
end
|
||||||
|
|
||||||
|
create_if_not_exists(unique_index(:markers, [:user_id, :timeline]))
|
||||||
|
end
|
||||||
|
end
|
51
test/marker_test.exs
Normal file
51
test/marker_test.exs
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
# 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 "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
|
||||||
|
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
|
|
@ -377,4 +377,13 @@ defmodule Pleroma.Factory do
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def marker_factory do
|
||||||
|
%Pleroma.Marker{
|
||||||
|
user: build(:user),
|
||||||
|
timeline: "notifications",
|
||||||
|
lock_version: 0,
|
||||||
|
last_read_id: "1"
|
||||||
|
}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
124
test/web/mastodon_api/controllers/marker_controller_test.exs
Normal file
124
test/web/mastodon_api/controllers/marker_controller_test.exs
Normal file
|
@ -0,0 +1,124 @@
|
||||||
|
# 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"])
|
||||||
|
|
||||||
|
{:ok, %{"notifications" => marker}} =
|
||||||
|
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(200)
|
||||||
|
|
||||||
|
assert response == %{
|
||||||
|
"notifications" => %{
|
||||||
|
"last_read_id" => "69420",
|
||||||
|
"updated_at" => NaiveDateTime.to_iso8601(marker.updated_at),
|
||||||
|
"version" => 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
||||||
|
} = 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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
|
27
test/web/mastodon_api/views/marker_view_test.exs
Normal file
27
test/web/mastodon_api/views/marker_view_test.exs
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
# 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.MarkerViewTest do
|
||||||
|
use Pleroma.DataCase
|
||||||
|
alias Pleroma.Web.MastodonAPI.MarkerView
|
||||||
|
import Pleroma.Factory
|
||||||
|
|
||||||
|
test "returns markers" do
|
||||||
|
marker1 = insert(:marker, timeline: "notifications", last_read_id: "17")
|
||||||
|
marker2 = insert(:marker, timeline: "home", last_read_id: "42")
|
||||||
|
|
||||||
|
assert MarkerView.render("markers.json", %{markers: [marker1, marker2]}) == %{
|
||||||
|
"home" => %{
|
||||||
|
last_read_id: "42",
|
||||||
|
updated_at: NaiveDateTime.to_iso8601(marker2.updated_at),
|
||||||
|
version: 0
|
||||||
|
},
|
||||||
|
"notifications" => %{
|
||||||
|
last_read_id: "17",
|
||||||
|
updated_at: NaiveDateTime.to_iso8601(marker1.updated_at),
|
||||||
|
version: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in a new issue