forked from AkkomaGang/akkoma
Merge branch 'refactor/preload-bookmarks-with-activities' into 'develop'
Optimize bookmarks by preloading them with activities Closes #861 See merge request pleroma/pleroma!1121
This commit is contained in:
commit
14deed7f7d
8 changed files with 106 additions and 33 deletions
|
@ -6,9 +6,11 @@ defmodule Pleroma.Activity do
|
||||||
use Ecto.Schema
|
use Ecto.Schema
|
||||||
|
|
||||||
alias Pleroma.Activity
|
alias Pleroma.Activity
|
||||||
|
alias Pleroma.Bookmark
|
||||||
alias Pleroma.Notification
|
alias Pleroma.Notification
|
||||||
alias Pleroma.Object
|
alias Pleroma.Object
|
||||||
alias Pleroma.Repo
|
alias Pleroma.Repo
|
||||||
|
alias Pleroma.User
|
||||||
|
|
||||||
import Ecto.Changeset
|
import Ecto.Changeset
|
||||||
import Ecto.Query
|
import Ecto.Query
|
||||||
|
@ -35,6 +37,8 @@ defmodule Pleroma.Activity do
|
||||||
field(:local, :boolean, default: true)
|
field(:local, :boolean, default: true)
|
||||||
field(:actor, :string)
|
field(:actor, :string)
|
||||||
field(:recipients, {:array, :string}, default: [])
|
field(:recipients, {:array, :string}, default: [])
|
||||||
|
# This is a fake relation, do not use outside of with_preloaded_bookmark/get_bookmark
|
||||||
|
has_one(:bookmark, Bookmark)
|
||||||
has_many(:notifications, Notification, on_delete: :delete_all)
|
has_many(:notifications, Notification, on_delete: :delete_all)
|
||||||
|
|
||||||
# Attention: this is a fake relation, don't try to preload it blindly and expect it to work!
|
# Attention: this is a fake relation, don't try to preload it blindly and expect it to work!
|
||||||
|
@ -73,6 +77,16 @@ def with_preloaded_object(query) do
|
||||||
|> preload([activity, object], object: object)
|
|> preload([activity, object], object: object)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def with_preloaded_bookmark(query, %User{} = user) do
|
||||||
|
from([a] in query,
|
||||||
|
left_join: b in Bookmark,
|
||||||
|
on: b.user_id == ^user.id and b.activity_id == a.id,
|
||||||
|
preload: [bookmark: b]
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def with_preloaded_bookmark(query, _), do: query
|
||||||
|
|
||||||
def get_by_ap_id(ap_id) do
|
def get_by_ap_id(ap_id) do
|
||||||
Repo.one(
|
Repo.one(
|
||||||
from(
|
from(
|
||||||
|
@ -82,6 +96,16 @@ def get_by_ap_id(ap_id) do
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def get_bookmark(%Activity{} = activity, %User{} = user) do
|
||||||
|
if Ecto.assoc_loaded?(activity.bookmark) do
|
||||||
|
activity.bookmark
|
||||||
|
else
|
||||||
|
Bookmark.get(user.id, activity.id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_bookmark(_, _), do: nil
|
||||||
|
|
||||||
def change(struct, params \\ %{}) do
|
def change(struct, params \\ %{}) do
|
||||||
struct
|
struct
|
||||||
|> cast(params, [:data])
|
|> cast(params, [:data])
|
||||||
|
|
|
@ -10,7 +10,6 @@ defmodule Pleroma.User do
|
||||||
|
|
||||||
alias Comeonin.Pbkdf2
|
alias Comeonin.Pbkdf2
|
||||||
alias Pleroma.Activity
|
alias Pleroma.Activity
|
||||||
alias Pleroma.Bookmark
|
|
||||||
alias Pleroma.Notification
|
alias Pleroma.Notification
|
||||||
alias Pleroma.Object
|
alias Pleroma.Object
|
||||||
alias Pleroma.Registration
|
alias Pleroma.Registration
|
||||||
|
@ -54,7 +53,6 @@ defmodule Pleroma.User do
|
||||||
field(:search_type, :integer, virtual: true)
|
field(:search_type, :integer, virtual: true)
|
||||||
field(:tags, {:array, :string}, default: [])
|
field(:tags, {:array, :string}, default: [])
|
||||||
field(:last_refreshed_at, :naive_datetime_usec)
|
field(:last_refreshed_at, :naive_datetime_usec)
|
||||||
has_many(:bookmarks, Bookmark)
|
|
||||||
has_many(:notifications, Notification)
|
has_many(:notifications, Notification)
|
||||||
has_many(:registrations, Registration)
|
has_many(:registrations, Registration)
|
||||||
embeds_one(:info, Pleroma.User.Info)
|
embeds_one(:info, Pleroma.User.Info)
|
||||||
|
|
|
@ -815,11 +815,32 @@ defp maybe_preload_objects(query, _) do
|
||||||
|> Activity.with_preloaded_object()
|
|> Activity.with_preloaded_object()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp maybe_preload_bookmarks(query, %{"skip_preload" => true}), do: query
|
||||||
|
|
||||||
|
defp maybe_preload_bookmarks(query, opts) do
|
||||||
|
query
|
||||||
|
|> Activity.with_preloaded_bookmark(opts["user"])
|
||||||
|
end
|
||||||
|
|
||||||
|
defp maybe_order(query, %{order: :desc}) do
|
||||||
|
query
|
||||||
|
|> order_by(desc: :id)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp maybe_order(query, %{order: :asc}) do
|
||||||
|
query
|
||||||
|
|> order_by(asc: :id)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp maybe_order(query, _), do: query
|
||||||
|
|
||||||
def fetch_activities_query(recipients, opts \\ %{}) do
|
def fetch_activities_query(recipients, opts \\ %{}) do
|
||||||
base_query = from(activity in Activity)
|
base_query = from(activity in Activity)
|
||||||
|
|
||||||
base_query
|
base_query
|
||||||
|> maybe_preload_objects(opts)
|
|> maybe_preload_objects(opts)
|
||||||
|
|> maybe_preload_bookmarks(opts)
|
||||||
|
|> maybe_order(opts)
|
||||||
|> restrict_recipients(recipients, opts["user"])
|
|> restrict_recipients(recipients, opts["user"])
|
||||||
|> restrict_tag(opts)
|
|> restrict_tag(opts)
|
||||||
|> restrict_tag_reject(opts)
|
|> restrict_tag_reject(opts)
|
||||||
|
|
|
@ -295,8 +295,6 @@ def home_timeline(%{assigns: %{user: user}} = conn, params) do
|
||||||
|> ActivityPub.contain_timeline(user)
|
|> ActivityPub.contain_timeline(user)
|
||||||
|> Enum.reverse()
|
|> Enum.reverse()
|
||||||
|
|
||||||
user = Repo.preload(user, bookmarks: :activity)
|
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|> add_link_headers(:home_timeline, activities)
|
|> add_link_headers(:home_timeline, activities)
|
||||||
|> put_view(StatusView)
|
|> put_view(StatusView)
|
||||||
|
@ -315,8 +313,6 @@ def public_timeline(%{assigns: %{user: user}} = conn, params) do
|
||||||
|> ActivityPub.fetch_public_activities()
|
|> ActivityPub.fetch_public_activities()
|
||||||
|> Enum.reverse()
|
|> Enum.reverse()
|
||||||
|
|
||||||
user = Repo.preload(user, bookmarks: :activity)
|
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|> add_link_headers(:public_timeline, activities, false, %{"local" => local_only})
|
|> add_link_headers(:public_timeline, activities, false, %{"local" => local_only})
|
||||||
|> put_view(StatusView)
|
|> put_view(StatusView)
|
||||||
|
@ -324,8 +320,7 @@ def public_timeline(%{assigns: %{user: user}} = conn, params) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def user_statuses(%{assigns: %{user: reading_user}} = conn, params) do
|
def user_statuses(%{assigns: %{user: reading_user}} = conn, params) do
|
||||||
with %User{} = user <- User.get_cached_by_id(params["id"]),
|
with %User{} = user <- User.get_cached_by_id(params["id"]) do
|
||||||
reading_user <- Repo.preload(reading_user, :bookmarks) do
|
|
||||||
activities = ActivityPub.fetch_user_activities(user, reading_user, params)
|
activities = ActivityPub.fetch_user_activities(user, reading_user, params)
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|
@ -352,8 +347,6 @@ def dm_timeline(%{assigns: %{user: user}} = conn, params) do
|
||||||
|> ActivityPub.fetch_activities_query(params)
|
|> ActivityPub.fetch_activities_query(params)
|
||||||
|> Pagination.fetch_paginated(params)
|
|> Pagination.fetch_paginated(params)
|
||||||
|
|
||||||
user = Repo.preload(user, bookmarks: :activity)
|
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|> add_link_headers(:dm_timeline, activities)
|
|> add_link_headers(:dm_timeline, activities)
|
||||||
|> put_view(StatusView)
|
|> put_view(StatusView)
|
||||||
|
@ -363,8 +356,6 @@ def dm_timeline(%{assigns: %{user: user}} = conn, params) do
|
||||||
def get_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
def get_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
||||||
with %Activity{} = activity <- Activity.get_by_id_with_object(id),
|
with %Activity{} = activity <- Activity.get_by_id_with_object(id),
|
||||||
true <- Visibility.visible_for_user?(activity, user) do
|
true <- Visibility.visible_for_user?(activity, user) do
|
||||||
user = Repo.preload(user, bookmarks: :activity)
|
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|> put_view(StatusView)
|
|> put_view(StatusView)
|
||||||
|> try_render("status.json", %{activity: activity, for: user})
|
|> try_render("status.json", %{activity: activity, for: user})
|
||||||
|
@ -514,8 +505,6 @@ def delete_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
||||||
def reblog_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do
|
def reblog_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do
|
||||||
with {:ok, announce, _activity} <- CommonAPI.repeat(ap_id_or_id, user),
|
with {:ok, announce, _activity} <- CommonAPI.repeat(ap_id_or_id, user),
|
||||||
%Activity{} = announce <- Activity.normalize(announce.data) do
|
%Activity{} = announce <- Activity.normalize(announce.data) do
|
||||||
user = Repo.preload(user, bookmarks: :activity)
|
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|> put_view(StatusView)
|
|> put_view(StatusView)
|
||||||
|> try_render("status.json", %{activity: announce, for: user, as: :activity})
|
|> try_render("status.json", %{activity: announce, for: user, as: :activity})
|
||||||
|
@ -525,8 +514,6 @@ def reblog_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do
|
||||||
def unreblog_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do
|
def unreblog_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do
|
||||||
with {:ok, _unannounce, %{data: %{"id" => id}}} <- CommonAPI.unrepeat(ap_id_or_id, user),
|
with {:ok, _unannounce, %{data: %{"id" => id}}} <- CommonAPI.unrepeat(ap_id_or_id, user),
|
||||||
%Activity{} = activity <- Activity.get_create_by_object_ap_id_with_object(id) do
|
%Activity{} = activity <- Activity.get_create_by_object_ap_id_with_object(id) do
|
||||||
user = Repo.preload(user, bookmarks: :activity)
|
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|> put_view(StatusView)
|
|> put_view(StatusView)
|
||||||
|> try_render("status.json", %{activity: activity, for: user, as: :activity})
|
|> try_render("status.json", %{activity: activity, for: user, as: :activity})
|
||||||
|
@ -577,8 +564,6 @@ def bookmark_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
||||||
%User{} = user <- User.get_cached_by_nickname(user.nickname),
|
%User{} = user <- User.get_cached_by_nickname(user.nickname),
|
||||||
true <- Visibility.visible_for_user?(activity, user),
|
true <- Visibility.visible_for_user?(activity, user),
|
||||||
{:ok, _bookmark} <- Bookmark.create(user.id, activity.id) do
|
{:ok, _bookmark} <- Bookmark.create(user.id, activity.id) do
|
||||||
user = Repo.preload(user, bookmarks: :activity)
|
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|> put_view(StatusView)
|
|> put_view(StatusView)
|
||||||
|> try_render("status.json", %{activity: activity, for: user, as: :activity})
|
|> try_render("status.json", %{activity: activity, for: user, as: :activity})
|
||||||
|
@ -590,8 +575,6 @@ def unbookmark_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
||||||
%User{} = user <- User.get_cached_by_nickname(user.nickname),
|
%User{} = user <- User.get_cached_by_nickname(user.nickname),
|
||||||
true <- Visibility.visible_for_user?(activity, user),
|
true <- Visibility.visible_for_user?(activity, user),
|
||||||
{:ok, _bookmark} <- Bookmark.destroy(user.id, activity.id) do
|
{:ok, _bookmark} <- Bookmark.destroy(user.id, activity.id) do
|
||||||
user = Repo.preload(user, bookmarks: :activity)
|
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|> put_view(StatusView)
|
|> put_view(StatusView)
|
||||||
|> try_render("status.json", %{activity: activity, for: user, as: :activity})
|
|> try_render("status.json", %{activity: activity, for: user, as: :activity})
|
||||||
|
@ -1112,8 +1095,6 @@ def favourites(%{assigns: %{user: user}} = conn, params) do
|
||||||
ActivityPub.fetch_activities([], params)
|
ActivityPub.fetch_activities([], params)
|
||||||
|> Enum.reverse()
|
|> Enum.reverse()
|
||||||
|
|
||||||
user = Repo.preload(user, bookmarks: :activity)
|
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|> add_link_headers(:favourites, activities)
|
|> add_link_headers(:favourites, activities)
|
||||||
|> put_view(StatusView)
|
|> put_view(StatusView)
|
||||||
|
@ -1159,7 +1140,6 @@ def user_favourites(%{assigns: %{user: for_user}} = conn, %{"id" => id} = params
|
||||||
|
|
||||||
def bookmarks(%{assigns: %{user: user}} = conn, params) do
|
def bookmarks(%{assigns: %{user: user}} = conn, params) do
|
||||||
user = User.get_cached_by_id(user.id)
|
user = User.get_cached_by_id(user.id)
|
||||||
user = Repo.preload(user, bookmarks: :activity)
|
|
||||||
|
|
||||||
bookmarks =
|
bookmarks =
|
||||||
Bookmark.for_user_query(user.id)
|
Bookmark.for_user_query(user.id)
|
||||||
|
@ -1167,7 +1147,7 @@ def bookmarks(%{assigns: %{user: user}} = conn, params) do
|
||||||
|
|
||||||
activities =
|
activities =
|
||||||
bookmarks
|
bookmarks
|
||||||
|> Enum.map(fn b -> b.activity end)
|
|> Enum.map(fn b -> Map.put(b.activity, :bookmark, Map.delete(b, :activity)) end)
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|> add_link_headers(:bookmarks, bookmarks)
|
|> add_link_headers(:bookmarks, bookmarks)
|
||||||
|
@ -1276,8 +1256,6 @@ def list_timeline(%{assigns: %{user: user}} = conn, %{"list_id" => id} = params)
|
||||||
|> ActivityPub.fetch_activities_bounded(following, params)
|
|> ActivityPub.fetch_activities_bounded(following, params)
|
||||||
|> Enum.reverse()
|
|> Enum.reverse()
|
||||||
|
|
||||||
user = Repo.preload(user, bookmarks: :activity)
|
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|> put_view(StatusView)
|
|> put_view(StatusView)
|
||||||
|> render("index.json", %{activities: activities, for: user, as: :activity})
|
|> render("index.json", %{activities: activities, for: user, as: :activity})
|
||||||
|
|
|
@ -75,18 +75,22 @@ def render("index.json", opts) do
|
||||||
|
|
||||||
def render(
|
def render(
|
||||||
"status.json",
|
"status.json",
|
||||||
%{activity: %{data: %{"type" => "Announce", "object" => object}} = activity} = opts
|
%{activity: %{data: %{"type" => "Announce", "object" => _object}} = activity} = opts
|
||||||
) do
|
) do
|
||||||
user = get_user(activity.data["actor"])
|
user = get_user(activity.data["actor"])
|
||||||
created_at = Utils.to_masto_date(activity.data["published"])
|
created_at = Utils.to_masto_date(activity.data["published"])
|
||||||
|
activity_object = Object.normalize(activity)
|
||||||
|
|
||||||
|
reblogged_activity =
|
||||||
|
Activity.create_by_object_ap_id(activity_object.data["id"])
|
||||||
|
|> Activity.with_preloaded_bookmark(opts[:for])
|
||||||
|
|> Repo.one()
|
||||||
|
|
||||||
reblogged_activity = Activity.get_create_by_object_ap_id(object)
|
|
||||||
reblogged = render("status.json", Map.put(opts, :activity, reblogged_activity))
|
reblogged = render("status.json", Map.put(opts, :activity, reblogged_activity))
|
||||||
|
|
||||||
activity_object = Object.normalize(activity)
|
|
||||||
favorited = opts[:for] && opts[:for].ap_id in (activity_object.data["likes"] || [])
|
favorited = opts[:for] && opts[:for].ap_id in (activity_object.data["likes"] || [])
|
||||||
|
|
||||||
bookmarked = opts[:for] && CommonAPI.bookmarked?(opts[:for], reblogged_activity)
|
bookmarked = Activity.get_bookmark(reblogged_activity, opts[:for]) != nil
|
||||||
|
|
||||||
mentions =
|
mentions =
|
||||||
activity.recipients
|
activity.recipients
|
||||||
|
@ -96,8 +100,8 @@ def render(
|
||||||
|
|
||||||
%{
|
%{
|
||||||
id: to_string(activity.id),
|
id: to_string(activity.id),
|
||||||
uri: object,
|
uri: activity_object.data["id"],
|
||||||
url: object,
|
url: activity_object.data["id"],
|
||||||
account: AccountView.render("account.json", %{user: user}),
|
account: AccountView.render("account.json", %{user: user}),
|
||||||
in_reply_to_id: nil,
|
in_reply_to_id: nil,
|
||||||
in_reply_to_account_id: nil,
|
in_reply_to_account_id: nil,
|
||||||
|
@ -149,7 +153,7 @@ def render("status.json", %{activity: %{data: %{"object" => _object}} = activity
|
||||||
|
|
||||||
favorited = opts[:for] && opts[:for].ap_id in (object.data["likes"] || [])
|
favorited = opts[:for] && opts[:for].ap_id in (object.data["likes"] || [])
|
||||||
|
|
||||||
bookmarked = opts[:for] && CommonAPI.bookmarked?(opts[:for], activity)
|
bookmarked = Activity.get_bookmark(activity, opts[:for]) != nil
|
||||||
|
|
||||||
attachment_data = object.data["attachment"] || []
|
attachment_data = object.data["attachment"] || []
|
||||||
attachments = render_many(attachment_data, StatusView, "attachment.json", as: :attachment)
|
attachments = render_many(attachment_data, StatusView, "attachment.json", as: :attachment)
|
||||||
|
|
|
@ -182,6 +182,7 @@ def dm_timeline(%{assigns: %{user: user}} = conn, params) do
|
||||||
|> Map.put("blocking_user", user)
|
|> Map.put("blocking_user", user)
|
||||||
|> Map.put("user", user)
|
|> Map.put("user", user)
|
||||||
|> Map.put(:visibility, "direct")
|
|> Map.put(:visibility, "direct")
|
||||||
|
|> Map.put(:order, :desc)
|
||||||
|
|
||||||
activities =
|
activities =
|
||||||
ActivityPub.fetch_activities_query([user.ap_id], params)
|
ActivityPub.fetch_activities_query([user.ap_id], params)
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
defmodule Pleroma.ActivityTest do
|
defmodule Pleroma.ActivityTest do
|
||||||
use Pleroma.DataCase
|
use Pleroma.DataCase
|
||||||
alias Pleroma.Activity
|
alias Pleroma.Activity
|
||||||
|
alias Pleroma.Bookmark
|
||||||
import Pleroma.Factory
|
import Pleroma.Factory
|
||||||
|
|
||||||
test "returns an activity by it's AP id" do
|
test "returns an activity by it's AP id" do
|
||||||
|
@ -28,4 +29,48 @@ test "returns the activity that created an object" do
|
||||||
|
|
||||||
assert activity == found_activity
|
assert activity == found_activity
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "preloading a bookmark" do
|
||||||
|
user = insert(:user)
|
||||||
|
user2 = insert(:user)
|
||||||
|
user3 = insert(:user)
|
||||||
|
activity = insert(:note_activity)
|
||||||
|
{:ok, _bookmark} = Bookmark.create(user.id, activity.id)
|
||||||
|
{:ok, _bookmark2} = Bookmark.create(user2.id, activity.id)
|
||||||
|
{:ok, bookmark3} = Bookmark.create(user3.id, activity.id)
|
||||||
|
|
||||||
|
queried_activity =
|
||||||
|
Ecto.Query.from(Pleroma.Activity)
|
||||||
|
|> Activity.with_preloaded_bookmark(user3)
|
||||||
|
|> Repo.one()
|
||||||
|
|
||||||
|
assert queried_activity.bookmark == bookmark3
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "getting a bookmark" do
|
||||||
|
test "when association is loaded" do
|
||||||
|
user = insert(:user)
|
||||||
|
activity = insert(:note_activity)
|
||||||
|
{:ok, bookmark} = Bookmark.create(user.id, activity.id)
|
||||||
|
|
||||||
|
queried_activity =
|
||||||
|
Ecto.Query.from(Pleroma.Activity)
|
||||||
|
|> Activity.with_preloaded_bookmark(user)
|
||||||
|
|> Repo.one()
|
||||||
|
|
||||||
|
assert Activity.get_bookmark(queried_activity, user) == bookmark
|
||||||
|
end
|
||||||
|
|
||||||
|
test "when association is not loaded" do
|
||||||
|
user = insert(:user)
|
||||||
|
activity = insert(:note_activity)
|
||||||
|
{:ok, bookmark} = Bookmark.create(user.id, activity.id)
|
||||||
|
|
||||||
|
queried_activity =
|
||||||
|
Ecto.Query.from(Pleroma.Activity)
|
||||||
|
|> Repo.one()
|
||||||
|
|
||||||
|
assert Activity.get_bookmark(queried_activity, user) == bookmark
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -168,6 +168,8 @@ test "tells if the status is bookmarked" do
|
||||||
|
|
||||||
{:ok, _bookmark} = Bookmark.create(user.id, activity.id)
|
{:ok, _bookmark} = Bookmark.create(user.id, activity.id)
|
||||||
|
|
||||||
|
activity = Activity.get_by_id_with_object(activity.id)
|
||||||
|
|
||||||
status = StatusView.render("status.json", %{activity: activity, for: user})
|
status = StatusView.render("status.json", %{activity: activity, for: user})
|
||||||
|
|
||||||
assert status.bookmarked == true
|
assert status.bookmarked == true
|
||||||
|
|
Loading…
Reference in a new issue