Add visibility to actors
Also use url helpers to generate urls properly Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
parent
7cd4df0ce9
commit
12116ba6fa
|
@ -8,11 +8,10 @@ config :mobilizon, :instance,
|
||||||
# you can enable the server option below.
|
# you can enable the server option below.
|
||||||
config :mobilizon, MobilizonWeb.Endpoint,
|
config :mobilizon, MobilizonWeb.Endpoint,
|
||||||
http: [
|
http: [
|
||||||
port: System.get_env("MOBILIZON_INSTANCE_PORT") || 4002
|
port: System.get_env("MOBILIZON_INSTANCE_PORT") || 80
|
||||||
],
|
],
|
||||||
url: [
|
url: [
|
||||||
host: System.get_env("MOBILIZON_INSTANCE_HOST") || "mobilizon.test",
|
host: System.get_env("MOBILIZON_INSTANCE_HOST") || "mobilizon.test"
|
||||||
port: System.get_env("MOBILIZON_INSTANCE_PORT") || 4002
|
|
||||||
],
|
],
|
||||||
server: false
|
server: false
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,14 @@ defenum(Mobilizon.Actors.ActorOpennessEnum, :actor_openness, [
|
||||||
:open
|
:open
|
||||||
])
|
])
|
||||||
|
|
||||||
|
defenum(Mobilizon.Actors.ActorVisibilityEnum, :actor_visibility_type, [
|
||||||
|
:public,
|
||||||
|
:unlisted,
|
||||||
|
# Probably unused
|
||||||
|
:restricted,
|
||||||
|
:private
|
||||||
|
])
|
||||||
|
|
||||||
defmodule Mobilizon.Actors.Actor do
|
defmodule Mobilizon.Actors.Actor do
|
||||||
@moduledoc """
|
@moduledoc """
|
||||||
Represents an actor (local and remote actors)
|
Represents an actor (local and remote actors)
|
||||||
|
@ -26,6 +34,9 @@ defmodule Mobilizon.Actors.Actor do
|
||||||
alias Mobilizon.Actors.{Actor, Follower, Member}
|
alias Mobilizon.Actors.{Actor, Follower, Member}
|
||||||
alias Mobilizon.Events.{Event, FeedToken}
|
alias Mobilizon.Events.{Event, FeedToken}
|
||||||
|
|
||||||
|
alias MobilizonWeb.Router.Helpers, as: Routes
|
||||||
|
alias MobilizonWeb.Endpoint
|
||||||
|
|
||||||
import Ecto.Query
|
import Ecto.Query
|
||||||
import Mobilizon.Ecto
|
import Mobilizon.Ecto
|
||||||
alias Mobilizon.Repo
|
alias Mobilizon.Repo
|
||||||
|
@ -49,6 +60,7 @@ defmodule Mobilizon.Actors.Actor do
|
||||||
field(:keys, :string)
|
field(:keys, :string)
|
||||||
field(:manually_approves_followers, :boolean, default: false)
|
field(:manually_approves_followers, :boolean, default: false)
|
||||||
field(:openness, Mobilizon.Actors.ActorOpennessEnum, default: :moderated)
|
field(:openness, Mobilizon.Actors.ActorOpennessEnum, default: :moderated)
|
||||||
|
field(:visibility, Mobilizon.Actors.ActorVisibilityEnum, default: :private)
|
||||||
field(:suspended, :boolean, default: false)
|
field(:suspended, :boolean, default: false)
|
||||||
field(:avatar_url, :string)
|
field(:avatar_url, :string)
|
||||||
field(:banner_url, :string)
|
field(:banner_url, :string)
|
||||||
|
@ -217,24 +229,43 @@ defmodule Mobilizon.Actors.Actor do
|
||||||
@spec build_urls(Ecto.Changeset.t(), atom()) :: Ecto.Changeset.t()
|
@spec build_urls(Ecto.Changeset.t(), atom()) :: Ecto.Changeset.t()
|
||||||
defp build_urls(changeset, type \\ :Person)
|
defp build_urls(changeset, type \\ :Person)
|
||||||
|
|
||||||
defp build_urls(%Ecto.Changeset{changes: %{preferred_username: username}} = changeset, type) do
|
defp build_urls(%Ecto.Changeset{changes: %{preferred_username: username}} = changeset, _type) do
|
||||||
symbol = if type == :Group, do: "~", else: "@"
|
|
||||||
|
|
||||||
changeset
|
changeset
|
||||||
|> put_change(
|
|> put_change(
|
||||||
:outbox_url,
|
:outbox_url,
|
||||||
"#{MobilizonWeb.Endpoint.url()}/#{symbol}#{username}/outbox"
|
build_url(username, :outbox)
|
||||||
)
|
)
|
||||||
|> put_change(
|
|> put_change(
|
||||||
:inbox_url,
|
:inbox_url,
|
||||||
"#{MobilizonWeb.Endpoint.url()}/#{symbol}#{username}/inbox"
|
build_url(username, :inbox)
|
||||||
)
|
)
|
||||||
|> put_change(:shared_inbox_url, "#{MobilizonWeb.Endpoint.url()}/inbox")
|
|> put_change(:shared_inbox_url, "#{MobilizonWeb.Endpoint.url()}/inbox")
|
||||||
|> put_change(:url, "#{MobilizonWeb.Endpoint.url()}/#{symbol}#{username}")
|
|> put_change(:url, build_url(username, :page))
|
||||||
end
|
end
|
||||||
|
|
||||||
defp build_urls(%Ecto.Changeset{} = changeset, _type), do: changeset
|
defp build_urls(%Ecto.Changeset{} = changeset, _type), do: changeset
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Build an AP URL for an actor
|
||||||
|
"""
|
||||||
|
@spec build_url(String.t(), atom()) :: String.t()
|
||||||
|
def build_url(preferred_username, endpoint, args \\ [])
|
||||||
|
|
||||||
|
def build_url(preferred_username, :page, args) do
|
||||||
|
Endpoint
|
||||||
|
|> Routes.page_url(:actor, preferred_username, args)
|
||||||
|
|> URI.decode()
|
||||||
|
end
|
||||||
|
|
||||||
|
def build_url(username, :inbox, _args), do: "#{build_url(username, :page)}/inbox"
|
||||||
|
|
||||||
|
def build_url(preferred_username, endpoint, args)
|
||||||
|
when endpoint in [:outbox, :following, :followers] do
|
||||||
|
Endpoint
|
||||||
|
|> Routes.activity_pub_url(endpoint, preferred_username, args)
|
||||||
|
|> URI.decode()
|
||||||
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Get a public key for a given ActivityPub actor ID (url)
|
Get a public key for a given ActivityPub actor ID (url)
|
||||||
"""
|
"""
|
||||||
|
@ -272,8 +303,24 @@ defmodule Mobilizon.Actors.Actor do
|
||||||
|
|
||||||
If actor A and C both follow actor B, actor B's followers are A and C
|
If actor A and C both follow actor B, actor B's followers are A and C
|
||||||
"""
|
"""
|
||||||
@spec get_followers(struct(), number(), number()) :: list()
|
@spec get_followers(struct(), number(), number()) :: map()
|
||||||
def get_followers(%Actor{id: actor_id} = _actor, page \\ nil, limit \\ nil) do
|
def get_followers(%Actor{id: actor_id} = _actor, page \\ nil, limit \\ nil) do
|
||||||
|
query =
|
||||||
|
from(
|
||||||
|
a in Actor,
|
||||||
|
join: f in Follower,
|
||||||
|
on: a.id == f.actor_id,
|
||||||
|
where: f.target_actor_id == ^actor_id
|
||||||
|
)
|
||||||
|
|
||||||
|
total = Task.async(fn -> Repo.aggregate(query, :count, :id) end)
|
||||||
|
elements = Task.async(fn -> Repo.all(paginate(query, page, limit)) end)
|
||||||
|
|
||||||
|
%{total: Task.await(total), elements: Task.await(elements)}
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec get_full_followers(struct()) :: list()
|
||||||
|
def get_full_followers(%Actor{id: actor_id} = _actor) do
|
||||||
Repo.all(
|
Repo.all(
|
||||||
from(
|
from(
|
||||||
a in Actor,
|
a in Actor,
|
||||||
|
@ -281,7 +328,6 @@ defmodule Mobilizon.Actors.Actor do
|
||||||
on: a.id == f.actor_id,
|
on: a.id == f.actor_id,
|
||||||
where: f.target_actor_id == ^actor_id
|
where: f.target_actor_id == ^actor_id
|
||||||
)
|
)
|
||||||
|> paginate(page, limit)
|
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -292,6 +338,22 @@ defmodule Mobilizon.Actors.Actor do
|
||||||
"""
|
"""
|
||||||
@spec get_followings(struct(), number(), number()) :: list()
|
@spec get_followings(struct(), number(), number()) :: list()
|
||||||
def get_followings(%Actor{id: actor_id} = _actor, page \\ nil, limit \\ nil) do
|
def get_followings(%Actor{id: actor_id} = _actor, page \\ nil, limit \\ nil) do
|
||||||
|
query =
|
||||||
|
from(
|
||||||
|
a in Actor,
|
||||||
|
join: f in Follower,
|
||||||
|
on: a.id == f.target_actor_id,
|
||||||
|
where: f.actor_id == ^actor_id
|
||||||
|
)
|
||||||
|
|
||||||
|
total = Task.async(fn -> Repo.aggregate(query, :count, :id) end)
|
||||||
|
elements = Task.async(fn -> Repo.all(paginate(query, page, limit)) end)
|
||||||
|
|
||||||
|
%{total: Task.await(total), elements: Task.await(elements)}
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec get_full_followings(struct()) :: list()
|
||||||
|
def get_full_followings(%Actor{id: actor_id} = _actor) do
|
||||||
Repo.all(
|
Repo.all(
|
||||||
from(
|
from(
|
||||||
a in Actor,
|
a in Actor,
|
||||||
|
@ -299,7 +361,6 @@ defmodule Mobilizon.Actors.Actor do
|
||||||
on: a.id == f.target_actor_id,
|
on: a.id == f.target_actor_id,
|
||||||
where: f.actor_id == ^actor_id
|
where: f.actor_id == ^actor_id
|
||||||
)
|
)
|
||||||
|> paginate(page, limit)
|
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -390,6 +451,9 @@ defmodule Mobilizon.Actors.Actor do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@spec public_visibility?(struct()) :: boolean()
|
||||||
|
def public_visibility?(%Actor{visibility: visibility}), do: visibility in [:public, :unlisted]
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Return the preferred_username with the eventual @domain suffix if it's a distant actor
|
Return the preferred_username with the eventual @domain suffix if it's a distant actor
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -158,7 +158,8 @@ defmodule Mobilizon.Actors do
|
||||||
Repo.all(
|
Repo.all(
|
||||||
from(
|
from(
|
||||||
a in Actor,
|
a in Actor,
|
||||||
where: a.type == ^:Group
|
where: a.type == ^:Group,
|
||||||
|
where: a.visibility in [^:public, ^:unlisted]
|
||||||
)
|
)
|
||||||
|> paginate(page, limit)
|
|> paginate(page, limit)
|
||||||
)
|
)
|
||||||
|
|
|
@ -19,6 +19,8 @@ defmodule Mobilizon.Events.Comment do
|
||||||
alias Mobilizon.Events.Event
|
alias Mobilizon.Events.Event
|
||||||
alias Mobilizon.Actors.Actor
|
alias Mobilizon.Actors.Actor
|
||||||
alias Mobilizon.Events.Comment
|
alias Mobilizon.Events.Comment
|
||||||
|
alias MobilizonWeb.Router.Helpers, as: Routes
|
||||||
|
alias MobilizonWeb.Endpoint
|
||||||
|
|
||||||
schema "comments" do
|
schema "comments" do
|
||||||
field(:text, :string)
|
field(:text, :string)
|
||||||
|
@ -46,7 +48,7 @@ defmodule Mobilizon.Events.Comment do
|
||||||
url =
|
url =
|
||||||
if Map.has_key?(attrs, "url"),
|
if Map.has_key?(attrs, "url"),
|
||||||
do: attrs["url"],
|
do: attrs["url"],
|
||||||
else: "#{MobilizonWeb.Endpoint.url()}/comments/#{uuid}"
|
else: Routes.page_url(Endpoint, :comment, uuid)
|
||||||
|
|
||||||
comment
|
comment
|
||||||
|> Ecto.Changeset.cast(attrs, [
|
|> Ecto.Changeset.cast(attrs, [
|
||||||
|
|
|
@ -111,8 +111,12 @@ defmodule MobilizonWeb.ActivityPubController do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def outbox(conn, %{"name" => username}) do
|
def outbox(conn, %{"name" => name}) do
|
||||||
outbox(conn, %{"name" => username, "page" => "0"})
|
with %Actor{} = actor <- Actors.get_local_actor_by_name(name) do
|
||||||
|
conn
|
||||||
|
|> put_resp_header("content-type", "application/activity+json")
|
||||||
|
|> json(ActorView.render("outbox.json", %{actor: actor}))
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# TODO: Ensure that this inbox is a recipient of the message
|
# TODO: Ensure that this inbox is a recipient of the message
|
||||||
|
|
|
@ -1,23 +1,23 @@
|
||||||
defmodule MobilizonWeb.ActivityPub.ActorView do
|
defmodule MobilizonWeb.ActivityPub.ActorView do
|
||||||
use MobilizonWeb, :view
|
use MobilizonWeb, :view
|
||||||
|
|
||||||
alias MobilizonWeb.ActivityPub.ActorView
|
|
||||||
alias MobilizonWeb.ActivityPub.ObjectView
|
|
||||||
alias Mobilizon.Actors.Actor
|
alias Mobilizon.Actors.Actor
|
||||||
alias Mobilizon.Service.ActivityPub
|
alias Mobilizon.Service.ActivityPub
|
||||||
alias Mobilizon.Service.ActivityPub.Utils
|
alias Mobilizon.Service.ActivityPub.Utils
|
||||||
alias Mobilizon.Activity
|
alias Mobilizon.Activity
|
||||||
|
|
||||||
|
@private_visibility_empty_collection %{elements: [], total: 0}
|
||||||
|
|
||||||
def render("actor.json", %{actor: actor}) do
|
def render("actor.json", %{actor: actor}) do
|
||||||
public_key = Mobilizon.Service.ActivityPub.Utils.pem_to_public_key_pem(actor.keys)
|
public_key = Mobilizon.Service.ActivityPub.Utils.pem_to_public_key_pem(actor.keys)
|
||||||
|
|
||||||
%{
|
%{
|
||||||
"id" => actor.url,
|
"id" => Actor.build_url(actor.preferred_username, :page),
|
||||||
"type" => "Person",
|
"type" => "Person",
|
||||||
"following" => actor.following_url,
|
"following" => Actor.build_url(actor.preferred_username, :following),
|
||||||
"followers" => actor.followers_url,
|
"followers" => Actor.build_url(actor.preferred_username, :followers),
|
||||||
"inbox" => actor.inbox_url,
|
"inbox" => Actor.build_url(actor.preferred_username, :inbox),
|
||||||
"outbox" => actor.outbox_url,
|
"outbox" => Actor.build_url(actor.preferred_username, :outbox),
|
||||||
"preferredUsername" => actor.preferred_username,
|
"preferredUsername" => actor.preferred_username,
|
||||||
"name" => actor.name,
|
"name" => actor.name,
|
||||||
"summary" => actor.summary,
|
"summary" => actor.summary,
|
||||||
|
@ -46,125 +46,102 @@ defmodule MobilizonWeb.ActivityPub.ActorView do
|
||||||
end
|
end
|
||||||
|
|
||||||
def render("following.json", %{actor: actor, page: page}) do
|
def render("following.json", %{actor: actor, page: page}) do
|
||||||
actor
|
%{total: total, elements: following} =
|
||||||
|> Actor.get_followings(page)
|
if Actor.public_visibility?(actor),
|
||||||
|> collection(actor.following_url, page)
|
do: Actor.get_followings(actor, page),
|
||||||
|
else: @private_visibility_empty_collection
|
||||||
|
|
||||||
|
following
|
||||||
|
|> collection(actor.preferred_username, :following, page, total)
|
||||||
|> Map.merge(Utils.make_json_ld_header())
|
|> Map.merge(Utils.make_json_ld_header())
|
||||||
end
|
end
|
||||||
|
|
||||||
def render("following.json", %{actor: actor}) do
|
def render("following.json", %{actor: actor}) do
|
||||||
following = Actor.get_followings(actor)
|
%{total: total, elements: following} =
|
||||||
|
if Actor.public_visibility?(actor),
|
||||||
|
do: Actor.get_followings(actor),
|
||||||
|
else: @private_visibility_empty_collection
|
||||||
|
|
||||||
%{
|
%{
|
||||||
"id" => actor.following_url,
|
"id" => Actor.build_url(actor.preferred_username, :following),
|
||||||
"type" => "OrderedCollection",
|
"type" => "OrderedCollection",
|
||||||
"totalItems" => length(following),
|
"totalItems" => total,
|
||||||
"first" => collection(following, actor.following_url, 1)
|
"first" => collection(following, actor.preferred_username, :following, 1, total)
|
||||||
}
|
}
|
||||||
|> Map.merge(Utils.make_json_ld_header())
|
|> Map.merge(Utils.make_json_ld_header())
|
||||||
end
|
end
|
||||||
|
|
||||||
def render("followers.json", %{actor: actor, page: page}) do
|
def render("followers.json", %{actor: actor, page: page}) do
|
||||||
actor
|
%{total: total, elements: followers} =
|
||||||
|> Actor.get_followers(page)
|
if Actor.public_visibility?(actor),
|
||||||
|> collection(actor.followers_url, page)
|
do: Actor.get_followers(actor, page),
|
||||||
|
else: @private_visibility_empty_collection
|
||||||
|
|
||||||
|
followers
|
||||||
|
|> collection(actor.preferred_username, :followers, page, total)
|
||||||
|> Map.merge(Utils.make_json_ld_header())
|
|> Map.merge(Utils.make_json_ld_header())
|
||||||
end
|
end
|
||||||
|
|
||||||
def render("followers.json", %{actor: actor}) do
|
def render("followers.json", %{actor: actor}) do
|
||||||
followers = Actor.get_followers(actor)
|
%{total: total, elements: followers} =
|
||||||
|
if Actor.public_visibility?(actor),
|
||||||
|
do: Actor.get_followers(actor),
|
||||||
|
else: @private_visibility_empty_collection
|
||||||
|
|
||||||
%{
|
%{
|
||||||
"id" => actor.followers_url,
|
"id" => Actor.build_url(actor.preferred_username, :followers),
|
||||||
"type" => "OrderedCollection",
|
"type" => "OrderedCollection",
|
||||||
# TODO put me back
|
"totalItems" => total,
|
||||||
# "totalItems" => length(followers),
|
"first" => collection(followers, actor.preferred_username, :followers, 1, total)
|
||||||
"first" => collection(followers, actor.followers_url, 1)
|
|
||||||
}
|
}
|
||||||
|> Map.merge(Utils.make_json_ld_header())
|
|> Map.merge(Utils.make_json_ld_header())
|
||||||
end
|
end
|
||||||
|
|
||||||
def render("outbox.json", %{actor: actor, page: page}) do
|
def render("outbox.json", %{actor: actor, page: page}) do
|
||||||
{page, no_page} =
|
%{total: total, elements: followers} =
|
||||||
if page == 0 do
|
if Actor.public_visibility?(actor),
|
||||||
{1, true}
|
do: ActivityPub.fetch_public_activities_for_actor(actor, page),
|
||||||
else
|
else: @private_visibility_empty_collection
|
||||||
{page, false}
|
|
||||||
|
followers
|
||||||
|
|> collection(actor.preferred_username, :outbox, page, total)
|
||||||
|
|> Map.merge(Utils.make_json_ld_header())
|
||||||
end
|
end
|
||||||
|
|
||||||
{activities, total} = ActivityPub.fetch_public_activities_for_actor(actor, page)
|
def render("outbox.json", %{actor: actor}) do
|
||||||
|
%{total: total, elements: followers} =
|
||||||
|
if Actor.public_visibility?(actor),
|
||||||
|
do: ActivityPub.fetch_public_activities_for_actor(actor),
|
||||||
|
else: @private_visibility_empty_collection
|
||||||
|
|
||||||
# collection =
|
|
||||||
# Enum.map(activities, fn act ->
|
|
||||||
# {:ok, data} = Transmogrifier.prepare_outgoing(act.data)
|
|
||||||
# data
|
|
||||||
# end)
|
|
||||||
|
|
||||||
iri = "#{actor.url}/outbox"
|
|
||||||
|
|
||||||
page = %{
|
|
||||||
"id" => "#{iri}?page=#{page}",
|
|
||||||
"type" => "OrderedCollectionPage",
|
|
||||||
"partOf" => iri,
|
|
||||||
"totalItems" => total,
|
|
||||||
"orderedItems" => render_many(activities, ActorView, "activity.json", as: :activity),
|
|
||||||
"next" => "#{iri}?page=#{page + 1}"
|
|
||||||
}
|
|
||||||
|
|
||||||
if no_page do
|
|
||||||
%{
|
%{
|
||||||
"id" => iri,
|
"id" => Actor.build_url(actor.preferred_username, :outbox),
|
||||||
"type" => "OrderedCollection",
|
"type" => "OrderedCollection",
|
||||||
"totalItems" => total,
|
"totalItems" => total,
|
||||||
"first" => page
|
"first" => collection(followers, actor.preferred_username, :outbox, 1, total)
|
||||||
}
|
|
||||||
|> Map.merge(Utils.make_json_ld_header())
|
|
||||||
else
|
|
||||||
page |> Map.merge(Utils.make_json_ld_header())
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def render("activity.json", %{activity: %Activity{local: local, data: data} = activity}) do
|
|
||||||
%{
|
|
||||||
"id" => data["id"],
|
|
||||||
"type" =>
|
|
||||||
if local do
|
|
||||||
"Create"
|
|
||||||
else
|
|
||||||
"Announce"
|
|
||||||
end,
|
|
||||||
"actor" => activity.actor,
|
|
||||||
# Not sure if needed since this is used into outbox
|
|
||||||
"published" => Timex.now(),
|
|
||||||
"to" => activity.recipients,
|
|
||||||
"object" =>
|
|
||||||
case data["type"] do
|
|
||||||
"Event" ->
|
|
||||||
render_one(data, ObjectView, "event.json", as: :event)
|
|
||||||
|
|
||||||
"Note" ->
|
|
||||||
render_one(data, ObjectView, "comment.json", as: :comment)
|
|
||||||
end
|
|
||||||
}
|
}
|
||||||
|> Map.merge(Utils.make_json_ld_header())
|
|> Map.merge(Utils.make_json_ld_header())
|
||||||
end
|
end
|
||||||
|
|
||||||
def collection(collection, iri, page, _total \\ nil) do
|
@spec collection(list(), String.t(), atom(), integer(), integer()) :: map()
|
||||||
items = Enum.map(collection, fn account -> account.url end)
|
defp collection(collection, preferred_username, endpoint, page, total)
|
||||||
|
when endpoint in [:followers, :following, :outbox] do
|
||||||
|
offset = (page - 1) * 10
|
||||||
|
|
||||||
# TODO : Add me back
|
map = %{
|
||||||
# total = total || length(collection)
|
"id" => Actor.build_url(preferred_username, endpoint, page: page),
|
||||||
|
|
||||||
%{
|
|
||||||
"id" => "#{iri}?page=#{page}",
|
|
||||||
"type" => "OrderedCollectionPage",
|
"type" => "OrderedCollectionPage",
|
||||||
"partOf" => iri,
|
"partOf" => Actor.build_url(preferred_username, endpoint),
|
||||||
# "totalItems" => total,
|
"orderedItems" => Enum.map(collection, &item/1)
|
||||||
"orderedItems" => items
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# if offset < total do
|
if offset < total do
|
||||||
# Map.put(map, "next", "#{iri}?page=#{page + 1}")
|
Map.put(map, "next", Actor.build_url(preferred_username, endpoint, page: page + 1))
|
||||||
# end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
map
|
||||||
|
end
|
||||||
|
|
||||||
|
def item(%Activity{data: %{"id" => id}}), do: id
|
||||||
|
def item(%Actor{url: url}), do: url
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
defmodule MobilizonWeb.ActivityPub.ObjectView do
|
defmodule MobilizonWeb.ActivityPub.ObjectView do
|
||||||
use MobilizonWeb, :view
|
use MobilizonWeb, :view
|
||||||
alias Mobilizon.Service.ActivityPub.Utils
|
alias Mobilizon.Service.ActivityPub.Utils
|
||||||
|
alias Mobilizon.Activity
|
||||||
|
|
||||||
def render("event.json", %{event: event}) do
|
def render("event.json", %{event: event}) do
|
||||||
{:ok, html, []} = Earmark.as_html(event["summary"])
|
{:ok, html, []} = Earmark.as_html(event["summary"])
|
||||||
|
@ -40,4 +41,29 @@ defmodule MobilizonWeb.ActivityPub.ObjectView do
|
||||||
|
|
||||||
Map.merge(comment, Utils.make_json_ld_header())
|
Map.merge(comment, Utils.make_json_ld_header())
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def render("activity.json", %{activity: %Activity{local: local, data: data} = activity}) do
|
||||||
|
%{
|
||||||
|
"id" => data["id"],
|
||||||
|
"type" =>
|
||||||
|
if local do
|
||||||
|
"Create"
|
||||||
|
else
|
||||||
|
"Announce"
|
||||||
|
end,
|
||||||
|
"actor" => activity.actor,
|
||||||
|
# Not sure if needed since this is used into outbox
|
||||||
|
"published" => Timex.now(),
|
||||||
|
"to" => activity.recipients,
|
||||||
|
"object" =>
|
||||||
|
case data["type"] do
|
||||||
|
"Event" ->
|
||||||
|
render_one(data, ObjectView, "event.json", as: :event)
|
||||||
|
|
||||||
|
"Note" ->
|
||||||
|
render_one(data, ObjectView, "comment.json", as: :comment)
|
||||||
|
end
|
||||||
|
}
|
||||||
|
|> Map.merge(Utils.make_json_ld_header())
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -383,7 +383,7 @@ defmodule Mobilizon.Service.ActivityPub do
|
||||||
|
|
||||||
followers =
|
followers =
|
||||||
if actor.followers_url in activity.recipients do
|
if actor.followers_url in activity.recipients do
|
||||||
Actor.get_followers(actor) |> Enum.filter(fn follower -> is_nil(follower.domain) end)
|
Actor.get_full_followers(actor) |> Enum.filter(fn follower -> is_nil(follower.domain) end)
|
||||||
else
|
else
|
||||||
[]
|
[]
|
||||||
end
|
end
|
||||||
|
@ -492,12 +492,8 @@ defmodule Mobilizon.Service.ActivityPub do
|
||||||
@doc """
|
@doc """
|
||||||
Return all public activities (events & comments) for an actor
|
Return all public activities (events & comments) for an actor
|
||||||
"""
|
"""
|
||||||
@spec fetch_public_activities_for_actor(Actor.t(), integer(), integer()) :: {list(), integer()}
|
@spec fetch_public_activities_for_actor(Actor.t(), integer(), integer()) :: map()
|
||||||
def fetch_public_activities_for_actor(actor, page \\ nil, limit \\ nil)
|
def fetch_public_activities_for_actor(%Actor{} = actor, page \\ 1, limit \\ 10) do
|
||||||
|
|
||||||
def fetch_public_activities_for_actor(%Actor{} = actor, page, limit) do
|
|
||||||
case actor.type do
|
|
||||||
:Person ->
|
|
||||||
{:ok, events, total_events} = Events.get_public_events_for_actor(actor, page, limit)
|
{:ok, events, total_events} = Events.get_public_events_for_actor(actor, page, limit)
|
||||||
{:ok, comments, total_comments} = Events.get_public_comments_for_actor(actor, page, limit)
|
{:ok, comments, total_comments} = Events.get_public_comments_for_actor(actor, page, limit)
|
||||||
|
|
||||||
|
@ -507,35 +503,7 @@ defmodule Mobilizon.Service.ActivityPub do
|
||||||
|
|
||||||
activities = event_activities ++ comment_activities
|
activities = event_activities ++ comment_activities
|
||||||
|
|
||||||
{activities, total_events + total_comments}
|
%{elements: activities, total: total_events + total_comments}
|
||||||
|
|
||||||
:Service ->
|
|
||||||
bot = Actors.get_bot_by_actor(actor)
|
|
||||||
|
|
||||||
case bot.type do
|
|
||||||
"ics" ->
|
|
||||||
{:ok, %HTTPoison.Response{body: body} = _resp} = HTTPoison.get(bot.source)
|
|
||||||
|
|
||||||
ical_events =
|
|
||||||
body
|
|
||||||
|> ExIcal.parse()
|
|
||||||
|> ExIcal.by_range(
|
|
||||||
DateTime.utc_now(),
|
|
||||||
DateTime.utc_now() |> DateTime.truncate(:second) |> Timex.shift(years: 1)
|
|
||||||
)
|
|
||||||
|
|
||||||
activities =
|
|
||||||
ical_events
|
|
||||||
|> Enum.chunk_every(limit)
|
|
||||||
|> Enum.at(page - 1)
|
|
||||||
|> Enum.map(fn event ->
|
|
||||||
{:ok, activity} = ical_event_to_activity(event, actor, bot.source)
|
|
||||||
activity
|
|
||||||
end)
|
|
||||||
|
|
||||||
{activities, length(ical_events)}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Create an activity from an event
|
# Create an activity from an event
|
||||||
|
@ -560,38 +528,6 @@ defmodule Mobilizon.Service.ActivityPub do
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
defp ical_event_to_activity(%ExIcal.Event{} = ical_event, %Actor{} = actor, _source) do
|
|
||||||
# Logger.debug(inspect ical_event)
|
|
||||||
# TODO : Use MobilizonWeb.API instead
|
|
||||||
# TODO : refactor me and move me somewhere else!
|
|
||||||
# TODO : also, there should be a form of cache that allows this to be more efficient
|
|
||||||
|
|
||||||
# ical_event.categories should be tags
|
|
||||||
|
|
||||||
{:ok, event} =
|
|
||||||
Events.create_event(%{
|
|
||||||
begins_on: ical_event.start,
|
|
||||||
ends_on: ical_event.end,
|
|
||||||
inserted_at: ical_event.stamp,
|
|
||||||
updated_at: ical_event.stamp,
|
|
||||||
description: ical_event.description |> sanitize_ical_event_strings,
|
|
||||||
title: ical_event.summary |> sanitize_ical_event_strings,
|
|
||||||
organizer_actor: actor
|
|
||||||
})
|
|
||||||
|
|
||||||
event_to_activity(event, false)
|
|
||||||
end
|
|
||||||
|
|
||||||
defp sanitize_ical_event_strings(string) when is_binary(string) do
|
|
||||||
string
|
|
||||||
|> String.replace(~s"\r\n", "")
|
|
||||||
|> String.replace(~s"\\,", ",")
|
|
||||||
end
|
|
||||||
|
|
||||||
defp sanitize_ical_event_strings(nil) do
|
|
||||||
nil
|
|
||||||
end
|
|
||||||
|
|
||||||
# # Whether the Public audience is in the activity's audience
|
# # Whether the Public audience is in the activity's audience
|
||||||
# defp is_public?(activity) do
|
# defp is_public?(activity) do
|
||||||
# "https://www.w3.org/ns/activitystreams#Public" in (activity.data["to"] ++
|
# "https://www.w3.org/ns/activitystreams#Public" in (activity.data["to"] ++
|
||||||
|
|
|
@ -20,6 +20,8 @@ defmodule Mobilizon.Service.ActivityPub.Utils do
|
||||||
alias Mobilizon.Service.ActivityPub
|
alias Mobilizon.Service.ActivityPub
|
||||||
alias Ecto.Changeset
|
alias Ecto.Changeset
|
||||||
require Logger
|
require Logger
|
||||||
|
alias MobilizonWeb.Router.Helpers, as: Routes
|
||||||
|
alias MobilizonWeb.Endpoint
|
||||||
|
|
||||||
# Some implementations send the actor URI as the actor field, others send the entire actor object,
|
# Some implementations send the actor URI as the actor field, others send the entire actor object,
|
||||||
# so figure out what the actor's URI is based on what we have.
|
# so figure out what the actor's URI is based on what we have.
|
||||||
|
@ -275,7 +277,7 @@ defmodule Mobilizon.Service.ActivityPub.Utils do
|
||||||
"begins_on" => metadata.begins_on,
|
"begins_on" => metadata.begins_on,
|
||||||
"category" => category,
|
"category" => category,
|
||||||
"actor" => actor,
|
"actor" => actor,
|
||||||
"id" => "#{MobilizonWeb.Endpoint.url()}/events/#{uuid}",
|
"id" => Routes.page_url(Endpoint, :event, uuid),
|
||||||
"uuid" => uuid,
|
"uuid" => uuid,
|
||||||
"tag" => tags |> Enum.map(fn {_, tag} -> tag end) |> Enum.uniq()
|
"tag" => tags |> Enum.map(fn {_, tag} -> tag end) |> Enum.uniq()
|
||||||
}
|
}
|
||||||
|
@ -296,7 +298,7 @@ defmodule Mobilizon.Service.ActivityPub.Utils do
|
||||||
"summary" => event.description,
|
"summary" => event.description,
|
||||||
"publish_at" => (event.publish_at || event.inserted_at) |> DateTime.to_iso8601(),
|
"publish_at" => (event.publish_at || event.inserted_at) |> DateTime.to_iso8601(),
|
||||||
"updated_at" => event.updated_at |> DateTime.to_iso8601(),
|
"updated_at" => event.updated_at |> DateTime.to_iso8601(),
|
||||||
"id" => "#{MobilizonWeb.Endpoint.url()}/events/#{event.uuid}"
|
"id" => Routes.page_url(Endpoint, :event, event.uuid)
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -320,7 +322,7 @@ defmodule Mobilizon.Service.ActivityPub.Utils do
|
||||||
"actor" => actor.url,
|
"actor" => actor.url,
|
||||||
"attributedTo" => actor.url,
|
"attributedTo" => actor.url,
|
||||||
"uuid" => uuid,
|
"uuid" => uuid,
|
||||||
"id" => "#{MobilizonWeb.Endpoint.url()}/comments/#{uuid}"
|
"id" => Routes.page_url(Endpoint, :comment, uuid)
|
||||||
}
|
}
|
||||||
|
|
||||||
if reply_to do
|
if reply_to do
|
||||||
|
@ -354,7 +356,7 @@ defmodule Mobilizon.Service.ActivityPub.Utils do
|
||||||
# "summary" => cw,
|
# "summary" => cw,
|
||||||
# "attachment" => attachments,
|
# "attachment" => attachments,
|
||||||
"actor" => actor,
|
"actor" => actor,
|
||||||
"id" => "#{MobilizonWeb.Endpoint.url()}/comments/#{uuid}",
|
"id" => Routes.page_url(Endpoint, :comment, uuid),
|
||||||
"uuid" => uuid,
|
"uuid" => uuid,
|
||||||
"tag" => tags |> Enum.map(fn {_, tag} -> tag end) |> Enum.uniq()
|
"tag" => tags |> Enum.map(fn {_, tag} -> tag end) |> Enum.uniq()
|
||||||
}
|
}
|
||||||
|
@ -386,7 +388,7 @@ defmodule Mobilizon.Service.ActivityPub.Utils do
|
||||||
"summary" => content_html,
|
"summary" => content_html,
|
||||||
"attributedTo" => actor,
|
"attributedTo" => actor,
|
||||||
"preferredUsername" => preferred_username,
|
"preferredUsername" => preferred_username,
|
||||||
"id" => "#{MobilizonWeb.Endpoint.url()}/~#{preferred_username}",
|
"id" => Actor.build_url(preferred_username, :page),
|
||||||
"uuid" => uuid,
|
"uuid" => uuid,
|
||||||
"tag" => tags |> Enum.map(fn {_, tag} -> tag end) |> Enum.uniq()
|
"tag" => tags |> Enum.map(fn {_, tag} -> tag end) |> Enum.uniq()
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,6 +41,7 @@ defmodule Mobilizon.Service.Export.Feed do
|
||||||
@spec fetch_actor_event_feed(String.t()) :: String.t()
|
@spec fetch_actor_event_feed(String.t()) :: String.t()
|
||||||
defp fetch_actor_event_feed(name) do
|
defp fetch_actor_event_feed(name) do
|
||||||
with %Actor{} = actor <- Actors.get_local_actor_by_name(name),
|
with %Actor{} = actor <- Actors.get_local_actor_by_name(name),
|
||||||
|
{:visibility, true} <- {:visibility, Actor.public_visibility?(actor)},
|
||||||
{:ok, events, _count} <- Events.get_public_events_for_actor(actor) do
|
{:ok, events, _count} <- Events.get_public_events_for_actor(actor) do
|
||||||
{:ok, build_actor_feed(actor, events)}
|
{:ok, build_actor_feed(actor, events)}
|
||||||
else
|
else
|
||||||
|
|
|
@ -40,12 +40,12 @@ defmodule Mobilizon.Service.Export.ICalendar do
|
||||||
@doc """
|
@doc """
|
||||||
Export a public actor's events to iCalendar format.
|
Export a public actor's events to iCalendar format.
|
||||||
|
|
||||||
The events must have a visibility of `:public` or `:unlisted`
|
The actor must have a visibility of `:public` or `:unlisted`, as well as the events
|
||||||
"""
|
"""
|
||||||
# TODO: The actor should also have visibility options
|
|
||||||
@spec export_public_actor(Actor.t()) :: String.t()
|
@spec export_public_actor(Actor.t()) :: String.t()
|
||||||
def export_public_actor(%Actor{} = actor) do
|
def export_public_actor(%Actor{} = actor) do
|
||||||
with {:ok, events, _} <- Events.get_public_events_for_actor(actor) do
|
with true <- Actor.public_visibility?(actor),
|
||||||
|
{:ok, events, _} <- Events.get_public_events_for_actor(actor) do
|
||||||
{:ok, %ICalendar{events: events |> Enum.map(&do_export_event/1)} |> ICalendar.to_ics()}
|
{:ok, %ICalendar{events: events |> Enum.map(&do_export_event/1)} |> ICalendar.to_ics()}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
defmodule Mobilizon.Repo.Migrations.AddVisibilityToActor do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
alias Mobilizon.Actors.ActorVisibilityEnum
|
||||||
|
|
||||||
|
def up do
|
||||||
|
ActorVisibilityEnum.create_type()
|
||||||
|
|
||||||
|
alter table(:actors) do
|
||||||
|
add(:visibility, ActorVisibilityEnum.type(), default: "private")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def down do
|
||||||
|
alter table(:actors) do
|
||||||
|
remove(:visibility)
|
||||||
|
end
|
||||||
|
|
||||||
|
ActorVisibilityEnum.drop_type()
|
||||||
|
end
|
||||||
|
end
|
|
@ -421,8 +421,8 @@ defmodule Mobilizon.ActorsTest do
|
||||||
assert follower.approved == true
|
assert follower.approved == true
|
||||||
assert follower.score == 42
|
assert follower.score == 42
|
||||||
|
|
||||||
assert [target_actor] = Actor.get_followings(actor)
|
assert %{total: 1, elements: [target_actor]} = Actor.get_followings(actor)
|
||||||
assert [actor] = Actor.get_followers(target_actor)
|
assert %{total: 1, elements: [actor]} = Actor.get_followers(target_actor)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "create_follower/1 with valid data but same actors fails to create a follower", %{
|
test "create_follower/1 with valid data but same actors fails to create a follower", %{
|
||||||
|
|
|
@ -3,6 +3,8 @@ defmodule Mobilizon.Service.ActivityPub.UtilsTest do
|
||||||
import Mobilizon.Factory
|
import Mobilizon.Factory
|
||||||
alias Mobilizon.Service.ActivityPub.Utils
|
alias Mobilizon.Service.ActivityPub.Utils
|
||||||
use ExVCR.Mock, adapter: ExVCR.Adapter.Hackney
|
use ExVCR.Mock, adapter: ExVCR.Adapter.Hackney
|
||||||
|
alias MobilizonWeb.Router.Helpers, as: Routes
|
||||||
|
alias MobilizonWeb.Endpoint
|
||||||
|
|
||||||
setup_all do
|
setup_all do
|
||||||
HTTPoison.start()
|
HTTPoison.start()
|
||||||
|
@ -19,7 +21,7 @@ defmodule Mobilizon.Service.ActivityPub.UtilsTest do
|
||||||
"content" => reply.text,
|
"content" => reply.text,
|
||||||
"actor" => reply.actor.url,
|
"actor" => reply.actor.url,
|
||||||
"uuid" => reply.uuid,
|
"uuid" => reply.uuid,
|
||||||
"id" => "#{MobilizonWeb.Endpoint.url()}/comments/#{reply.uuid}",
|
"id" => Routes.page_url(Endpoint, :comment, reply.uuid),
|
||||||
"inReplyTo" => comment.url,
|
"inReplyTo" => comment.url,
|
||||||
"attributedTo" => reply.actor.url
|
"attributedTo" => reply.actor.url
|
||||||
} == Utils.make_comment_data(reply)
|
} == Utils.make_comment_data(reply)
|
||||||
|
|
|
@ -12,6 +12,8 @@ defmodule MobilizonWeb.ActivityPubControllerTest do
|
||||||
alias Mobilizon.Service.ActivityPub
|
alias Mobilizon.Service.ActivityPub
|
||||||
alias Mobilizon.Service.ActivityPub.Utils
|
alias Mobilizon.Service.ActivityPub.Utils
|
||||||
use ExVCR.Mock, adapter: ExVCR.Adapter.Hackney
|
use ExVCR.Mock, adapter: ExVCR.Adapter.Hackney
|
||||||
|
alias MobilizonWeb.Router.Helpers, as: Routes
|
||||||
|
alias MobilizonWeb.Endpoint
|
||||||
|
|
||||||
setup do
|
setup do
|
||||||
conn = build_conn() |> put_req_header("accept", "application/activity+json")
|
conn = build_conn() |> put_req_header("accept", "application/activity+json")
|
||||||
|
@ -24,7 +26,7 @@ defmodule MobilizonWeb.ActivityPubControllerTest do
|
||||||
|
|
||||||
conn =
|
conn =
|
||||||
conn
|
conn
|
||||||
|> get("/@#{actor.preferred_username}")
|
|> get(Actor.build_url(actor.preferred_username, :page))
|
||||||
|
|
||||||
actor = Actors.get_actor!(actor.id)
|
actor = Actors.get_actor!(actor.id)
|
||||||
|
|
||||||
|
@ -38,7 +40,7 @@ defmodule MobilizonWeb.ActivityPubControllerTest do
|
||||||
|
|
||||||
conn =
|
conn =
|
||||||
conn
|
conn
|
||||||
|> get("/events/#{event.uuid}")
|
|> get(Routes.page_url(Endpoint, :event, event.uuid))
|
||||||
|
|
||||||
assert json_response(conn, 200) ==
|
assert json_response(conn, 200) ==
|
||||||
ObjectView.render("event.json", %{event: event |> Utils.make_event_data()})
|
ObjectView.render("event.json", %{event: event |> Utils.make_event_data()})
|
||||||
|
@ -49,7 +51,7 @@ defmodule MobilizonWeb.ActivityPubControllerTest do
|
||||||
|
|
||||||
conn =
|
conn =
|
||||||
conn
|
conn
|
||||||
|> get("/events/#{event.uuid}")
|
|> get(Routes.page_url(Endpoint, :event, event.uuid))
|
||||||
|
|
||||||
assert json_response(conn, 404)
|
assert json_response(conn, 404)
|
||||||
end
|
end
|
||||||
|
@ -61,7 +63,7 @@ defmodule MobilizonWeb.ActivityPubControllerTest do
|
||||||
|
|
||||||
conn =
|
conn =
|
||||||
conn
|
conn
|
||||||
|> get("/comments/#{comment.uuid}")
|
|> get(Routes.page_url(Endpoint, :comment, comment.uuid))
|
||||||
|
|
||||||
assert json_response(conn, 200) ==
|
assert json_response(conn, 200) ==
|
||||||
ObjectView.render("comment.json", %{comment: comment |> Utils.make_comment_data()})
|
ObjectView.render("comment.json", %{comment: comment |> Utils.make_comment_data()})
|
||||||
|
@ -88,7 +90,7 @@ defmodule MobilizonWeb.ActivityPubControllerTest do
|
||||||
conn =
|
conn =
|
||||||
conn
|
conn
|
||||||
|> assign(:valid_signature, true)
|
|> assign(:valid_signature, true)
|
||||||
|> post("/inbox", data)
|
|> post("#{MobilizonWeb.Endpoint.url()}/inbox", data)
|
||||||
|
|
||||||
assert "ok" == json_response(conn, 200)
|
assert "ok" == json_response(conn, 200)
|
||||||
:timer.sleep(500)
|
:timer.sleep(500)
|
||||||
|
@ -99,44 +101,106 @@ defmodule MobilizonWeb.ActivityPubControllerTest do
|
||||||
|
|
||||||
describe "/@:preferred_username/outbox" do
|
describe "/@:preferred_username/outbox" do
|
||||||
test "it returns a note activity in a collection", %{conn: conn} do
|
test "it returns a note activity in a collection", %{conn: conn} do
|
||||||
actor = insert(:actor)
|
actor = insert(:actor, visibility: :public)
|
||||||
comment = insert(:comment, actor: actor)
|
comment = insert(:comment, actor: actor)
|
||||||
|
|
||||||
conn =
|
conn =
|
||||||
conn
|
conn
|
||||||
|> get("/@#{actor.preferred_username}/outbox")
|
|> get(Actor.build_url(actor.preferred_username, :outbox))
|
||||||
|
|
||||||
assert response(conn, 200) =~ comment.text
|
assert json_response(conn, 200)["totalItems"] == 1
|
||||||
|
assert json_response(conn, 200)["first"]["orderedItems"] == [comment.url]
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it returns an event activity in a collection", %{conn: conn} do
|
test "it returns an event activity in a collection", %{conn: conn} do
|
||||||
actor = insert(:actor)
|
actor = insert(:actor, visibility: :public)
|
||||||
event = insert(:event, organizer_actor: actor)
|
event = insert(:event, organizer_actor: actor)
|
||||||
|
|
||||||
conn =
|
conn =
|
||||||
conn
|
conn
|
||||||
|> get("/@#{actor.preferred_username}/outbox")
|
|> get(Actor.build_url(actor.preferred_username, :outbox))
|
||||||
|
|
||||||
assert response(conn, 200) =~ event.title
|
assert json_response(conn, 200)["totalItems"] == 1
|
||||||
|
assert json_response(conn, 200)["first"]["orderedItems"] == [event.url]
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it works for more than 10 events", %{conn: conn} do
|
||||||
|
actor = insert(:actor, visibility: :public)
|
||||||
|
|
||||||
|
Enum.each(1..15, fn _ ->
|
||||||
|
insert(:event, organizer_actor: actor)
|
||||||
|
end)
|
||||||
|
|
||||||
|
result =
|
||||||
|
conn
|
||||||
|
|> get(Actor.build_url(actor.preferred_username, :outbox))
|
||||||
|
|> json_response(200)
|
||||||
|
|
||||||
|
assert length(result["first"]["orderedItems"]) == 10
|
||||||
|
assert result["totalItems"] == 15
|
||||||
|
|
||||||
|
result =
|
||||||
|
conn
|
||||||
|
|> get(Actor.build_url(actor.preferred_username, :outbox, page: 2))
|
||||||
|
|> json_response(200)
|
||||||
|
|
||||||
|
assert length(result["orderedItems"]) == 5
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it returns an empty collection if the actor has private visibility", %{conn: conn} do
|
||||||
|
actor = insert(:actor, visibility: :private)
|
||||||
|
insert(:event, organizer_actor: actor)
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> get(Actor.build_url(actor.preferred_username, :outbox))
|
||||||
|
|
||||||
|
assert json_response(conn, 200)["totalItems"] == 0
|
||||||
|
assert json_response(conn, 200)["first"]["orderedItems"] == []
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it doesn't returns an event activity in a collection if actor has private visibility",
|
||||||
|
%{conn: conn} do
|
||||||
|
actor = insert(:actor, visibility: :private)
|
||||||
|
insert(:event, organizer_actor: actor)
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> get(Actor.build_url(actor.preferred_username, :outbox))
|
||||||
|
|
||||||
|
assert json_response(conn, 200)["totalItems"] == 0
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "/@actor/followers" do
|
describe "/@actor/followers" do
|
||||||
test "it returns the followers in a collection", %{conn: conn} do
|
test "it returns the followers in a collection", %{conn: conn} do
|
||||||
actor = insert(:actor)
|
actor = insert(:actor, visibility: :public)
|
||||||
actor2 = insert(:actor)
|
actor2 = insert(:actor)
|
||||||
Actor.follow(actor, actor2)
|
Actor.follow(actor, actor2)
|
||||||
|
|
||||||
result =
|
result =
|
||||||
conn
|
conn
|
||||||
|> get("/@#{actor.preferred_username}/followers")
|
|> get(Actor.build_url(actor.preferred_username, :followers))
|
||||||
|> json_response(200)
|
|> json_response(200)
|
||||||
|
|
||||||
assert result["first"]["orderedItems"] == [actor2.url]
|
assert result["first"]["orderedItems"] == [actor2.url]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "it returns no followers for a private actor", %{conn: conn} do
|
||||||
|
actor = insert(:actor, visibility: :private)
|
||||||
|
actor2 = insert(:actor)
|
||||||
|
Actor.follow(actor, actor2)
|
||||||
|
|
||||||
|
result =
|
||||||
|
conn
|
||||||
|
|> get(Actor.build_url(actor.preferred_username, :followers))
|
||||||
|
|> json_response(200)
|
||||||
|
|
||||||
|
assert result["first"]["orderedItems"] == []
|
||||||
|
end
|
||||||
|
|
||||||
test "it works for more than 10 actors", %{conn: conn} do
|
test "it works for more than 10 actors", %{conn: conn} do
|
||||||
actor = insert(:actor)
|
actor = insert(:actor, visibility: :public)
|
||||||
|
|
||||||
Enum.each(1..15, fn _ ->
|
Enum.each(1..15, fn _ ->
|
||||||
other_actor = insert(:actor)
|
other_actor = insert(:actor)
|
||||||
|
@ -145,39 +209,50 @@ defmodule MobilizonWeb.ActivityPubControllerTest do
|
||||||
|
|
||||||
result =
|
result =
|
||||||
conn
|
conn
|
||||||
|> get("/@#{actor.preferred_username}/followers")
|
|> get(Actor.build_url(actor.preferred_username, :followers))
|
||||||
|> json_response(200)
|
|> json_response(200)
|
||||||
|
|
||||||
assert length(result["first"]["orderedItems"]) == 10
|
assert length(result["first"]["orderedItems"]) == 10
|
||||||
# assert result["first"]["totalItems"] == 15
|
assert result["totalItems"] == 15
|
||||||
# assert result["totalItems"] == 15
|
|
||||||
|
|
||||||
result =
|
result =
|
||||||
conn
|
conn
|
||||||
|> get("/@#{actor.preferred_username}/followers?page=2")
|
|> get(Actor.build_url(actor.preferred_username, :followers, page: 2))
|
||||||
|> json_response(200)
|
|> json_response(200)
|
||||||
|
|
||||||
assert length(result["orderedItems"]) == 5
|
assert length(result["orderedItems"]) == 5
|
||||||
# assert result["totalItems"] == 15
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "/@actor/following" do
|
describe "/@actor/following" do
|
||||||
test "it returns the followings in a collection", %{conn: conn} do
|
test "it returns the followings in a collection", %{conn: conn} do
|
||||||
actor = insert(:actor)
|
actor = insert(:actor)
|
||||||
actor2 = insert(:actor)
|
actor2 = insert(:actor, visibility: :public)
|
||||||
Actor.follow(actor, actor2)
|
Actor.follow(actor, actor2)
|
||||||
|
|
||||||
result =
|
result =
|
||||||
conn
|
conn
|
||||||
|> get("/@#{actor2.preferred_username}/following")
|
|> get(Actor.build_url(actor2.preferred_username, :following))
|
||||||
|> json_response(200)
|
|> json_response(200)
|
||||||
|
|
||||||
assert result["first"]["orderedItems"] == [actor.url]
|
assert result["first"]["orderedItems"] == [actor.url]
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it works for more than 10 actors", %{conn: conn} do
|
test "it returns no followings for a private actor", %{conn: conn} do
|
||||||
actor = insert(:actor)
|
actor = insert(:actor)
|
||||||
|
actor2 = insert(:actor, visibility: :private)
|
||||||
|
Actor.follow(actor, actor2)
|
||||||
|
|
||||||
|
result =
|
||||||
|
conn
|
||||||
|
|> get(Actor.build_url(actor2.preferred_username, :following))
|
||||||
|
|> json_response(200)
|
||||||
|
|
||||||
|
assert result["first"]["orderedItems"] == []
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it works for more than 10 actors", %{conn: conn} do
|
||||||
|
actor = insert(:actor, visibility: :public)
|
||||||
|
|
||||||
Enum.each(1..15, fn _ ->
|
Enum.each(1..15, fn _ ->
|
||||||
other_actor = insert(:actor)
|
other_actor = insert(:actor)
|
||||||
|
@ -186,7 +261,7 @@ defmodule MobilizonWeb.ActivityPubControllerTest do
|
||||||
|
|
||||||
result =
|
result =
|
||||||
conn
|
conn
|
||||||
|> get("/@#{actor.preferred_username}/following")
|
|> get(Actor.build_url(actor.preferred_username, :following))
|
||||||
|> json_response(200)
|
|> json_response(200)
|
||||||
|
|
||||||
assert length(result["first"]["orderedItems"]) == 10
|
assert length(result["first"]["orderedItems"]) == 10
|
||||||
|
@ -195,7 +270,7 @@ defmodule MobilizonWeb.ActivityPubControllerTest do
|
||||||
|
|
||||||
result =
|
result =
|
||||||
conn
|
conn
|
||||||
|> get("/@#{actor.preferred_username}/following?page=2")
|
|> get(Actor.build_url(actor.preferred_username, :following, page: 2))
|
||||||
|> json_response(200)
|
|> json_response(200)
|
||||||
|
|
||||||
assert length(result["orderedItems"]) == 5
|
assert length(result["orderedItems"]) == 5
|
||||||
|
|
|
@ -5,8 +5,9 @@ defmodule MobilizonWeb.FeedControllerTest do
|
||||||
alias MobilizonWeb.Endpoint
|
alias MobilizonWeb.Endpoint
|
||||||
|
|
||||||
describe "/@:preferred_username/feed/atom" do
|
describe "/@:preferred_username/feed/atom" do
|
||||||
test "it returns an RSS representation of the actor's public events", %{conn: conn} do
|
test "it returns an RSS representation of the actor's public events if the actor is publicly visible",
|
||||||
actor = insert(:actor)
|
%{conn: conn} do
|
||||||
|
actor = insert(:actor, visibility: :public)
|
||||||
tag1 = insert(:tag, title: "RSS", slug: "rss")
|
tag1 = insert(:tag, title: "RSS", slug: "rss")
|
||||||
tag2 = insert(:tag, title: "ATOM", slug: "atom")
|
tag2 = insert(:tag, title: "ATOM", slug: "atom")
|
||||||
event1 = insert(:event, organizer_actor: actor, tags: [tag1])
|
event1 = insert(:event, organizer_actor: actor, tags: [tag1])
|
||||||
|
@ -36,9 +37,27 @@ defmodule MobilizonWeb.FeedControllerTest do
|
||||||
assert entry2.categories == [tag1.slug]
|
assert entry2.categories == [tag1.slug]
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it returns an RSS representation of the actor's public events with the proper accept header",
|
test "it returns a 404 for the actor's public events Atom feed if the actor is not publicly visible",
|
||||||
%{conn: conn} do
|
%{conn: conn} do
|
||||||
actor = insert(:actor)
|
actor = insert(:actor)
|
||||||
|
tag1 = insert(:tag, title: "RSS", slug: "rss")
|
||||||
|
tag2 = insert(:tag, title: "ATOM", slug: "atom")
|
||||||
|
insert(:event, organizer_actor: actor, tags: [tag1])
|
||||||
|
insert(:event, organizer_actor: actor, tags: [tag1, tag2])
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> get(
|
||||||
|
Routes.feed_url(Endpoint, :actor, actor.preferred_username, "atom")
|
||||||
|
|> URI.decode()
|
||||||
|
)
|
||||||
|
|
||||||
|
assert response(conn, 404)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it returns an RSS representation of the actor's public events with the proper accept header",
|
||||||
|
%{conn: conn} do
|
||||||
|
actor = insert(:actor, visibility: :unlisted)
|
||||||
|
|
||||||
conn =
|
conn =
|
||||||
conn
|
conn
|
||||||
|
@ -63,8 +82,9 @@ defmodule MobilizonWeb.FeedControllerTest do
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "/@:preferred_username/feed/ics" do
|
describe "/@:preferred_username/feed/ics" do
|
||||||
test "it returns an iCalendar representation of the actor's public events", %{conn: conn} do
|
test "it returns an iCalendar representation of the actor's public events with an actor publicly visible",
|
||||||
actor = insert(:actor)
|
%{conn: conn} do
|
||||||
|
actor = insert(:actor, visibility: :public)
|
||||||
tag1 = insert(:tag, title: "iCalendar", slug: "icalendar")
|
tag1 = insert(:tag, title: "iCalendar", slug: "icalendar")
|
||||||
tag2 = insert(:tag, title: "Apple", slug: "apple")
|
tag2 = insert(:tag, title: "Apple", slug: "apple")
|
||||||
event1 = insert(:event, organizer_actor: actor, tags: [tag1])
|
event1 = insert(:event, organizer_actor: actor, tags: [tag1])
|
||||||
|
@ -90,9 +110,27 @@ defmodule MobilizonWeb.FeedControllerTest do
|
||||||
assert entry2.categories == [event2.category, tag1.slug, tag2.slug]
|
assert entry2.categories == [event2.category, tag1.slug, tag2.slug]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "it returns a 404 page for the actor's public events iCal feed with an actor not publicly visible",
|
||||||
|
%{conn: conn} do
|
||||||
|
actor = insert(:actor, visibility: :private)
|
||||||
|
tag1 = insert(:tag, title: "iCalendar", slug: "icalendar")
|
||||||
|
tag2 = insert(:tag, title: "Apple", slug: "apple")
|
||||||
|
insert(:event, organizer_actor: actor, tags: [tag1])
|
||||||
|
insert(:event, organizer_actor: actor, tags: [tag1, tag2])
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> get(
|
||||||
|
Routes.feed_url(Endpoint, :actor, actor.preferred_username, "ics")
|
||||||
|
|> URI.decode()
|
||||||
|
)
|
||||||
|
|
||||||
|
assert response(conn, 404)
|
||||||
|
end
|
||||||
|
|
||||||
test "it returns an iCalendar representation of the actor's public events with the proper accept header",
|
test "it returns an iCalendar representation of the actor's public events with the proper accept header",
|
||||||
%{conn: conn} do
|
%{conn: conn} do
|
||||||
actor = insert(:actor)
|
actor = insert(:actor, visibility: :unlisted)
|
||||||
|
|
||||||
conn =
|
conn =
|
||||||
conn
|
conn
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
defmodule MobilizonWeb.PageControllerTest do
|
defmodule MobilizonWeb.PageControllerTest do
|
||||||
use MobilizonWeb.ConnCase
|
use MobilizonWeb.ConnCase
|
||||||
import Mobilizon.Factory
|
import Mobilizon.Factory
|
||||||
|
alias Mobilizon.Actors.Actor
|
||||||
|
alias MobilizonWeb.Router.Helpers, as: Routes
|
||||||
|
alias MobilizonWeb.Endpoint
|
||||||
|
|
||||||
setup do
|
setup do
|
||||||
conn = build_conn() |> put_req_header("accept", "text/html")
|
conn = build_conn() |> put_req_header("accept", "text/html")
|
||||||
|
@ -14,29 +17,29 @@ defmodule MobilizonWeb.PageControllerTest do
|
||||||
|
|
||||||
test "GET /@actor with existing actor", %{conn: conn} do
|
test "GET /@actor with existing actor", %{conn: conn} do
|
||||||
actor = insert(:actor)
|
actor = insert(:actor)
|
||||||
conn = get(conn, "/@#{actor.preferred_username}")
|
conn = get(conn, Actor.build_url(actor.preferred_username, :page))
|
||||||
assert html_response(conn, 200)
|
assert html_response(conn, 200)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "GET /@actor with not existing actor", %{conn: conn} do
|
test "GET /@actor with not existing actor", %{conn: conn} do
|
||||||
conn = get(conn, "/@notexisting")
|
conn = get(conn, Actor.build_url("not_existing", :page))
|
||||||
assert html_response(conn, 404)
|
assert html_response(conn, 404)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "GET /events/:uuid", %{conn: conn} do
|
test "GET /events/:uuid", %{conn: conn} do
|
||||||
event = insert(:event)
|
event = insert(:event)
|
||||||
conn = get(conn, "/events/#{event.uuid}")
|
conn = get(conn, Routes.page_url(Endpoint, :event, event.uuid))
|
||||||
assert html_response(conn, 200)
|
assert html_response(conn, 200)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "GET /events/:uuid with not existing event", %{conn: conn} do
|
test "GET /events/:uuid with not existing event", %{conn: conn} do
|
||||||
conn = get(conn, "/events/not_existing_event")
|
conn = get(conn, Routes.page_url(Endpoint, :event, "not_existing_event"))
|
||||||
assert html_response(conn, 404)
|
assert html_response(conn, 404)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "GET /events/:uuid with event not public", %{conn: conn} do
|
test "GET /events/:uuid with event not public", %{conn: conn} do
|
||||||
event = insert(:event, visibility: :restricted)
|
event = insert(:event, visibility: :restricted)
|
||||||
conn = get(conn, "/events/#{event.uuid}")
|
conn = get(conn, Routes.page_url(Endpoint, :event, event.uuid))
|
||||||
assert html_response(conn, 404)
|
assert html_response(conn, 404)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -58,8 +58,9 @@ defmodule MobilizonWeb.Resolvers.GroupResolverTest do
|
||||||
assert hd(json_response(res, 200)["errors"])["message"] == "existing_group_name"
|
assert hd(json_response(res, 200)["errors"])["message"] == "existing_group_name"
|
||||||
end
|
end
|
||||||
|
|
||||||
test "list_groups/3 returns all groups", context do
|
test "list_groups/3 returns all public or unlisted groups", context do
|
||||||
group = insert(:group)
|
group = insert(:group, visibility: :unlisted)
|
||||||
|
insert(:group, visibility: :private)
|
||||||
|
|
||||||
query = """
|
query = """
|
||||||
{
|
{
|
||||||
|
@ -71,7 +72,9 @@ defmodule MobilizonWeb.Resolvers.GroupResolverTest do
|
||||||
|
|
||||||
res =
|
res =
|
||||||
context.conn
|
context.conn
|
||||||
|> get("/api", AbsintheHelpers.query_skeleton(query, "person"))
|
|> get("/api", AbsintheHelpers.query_skeleton(query, "groups"))
|
||||||
|
|
||||||
|
assert length(json_response(res, 200)["data"]["groups"]) == 1
|
||||||
|
|
||||||
assert hd(json_response(res, 200)["data"]["groups"])["preferredUsername"] ==
|
assert hd(json_response(res, 200)["data"]["groups"])["preferredUsername"] ==
|
||||||
group.preferred_username
|
group.preferred_username
|
||||||
|
|
|
@ -4,6 +4,9 @@ defmodule Mobilizon.Factory do
|
||||||
"""
|
"""
|
||||||
# with Ecto
|
# with Ecto
|
||||||
use ExMachina.Ecto, repo: Mobilizon.Repo
|
use ExMachina.Ecto, repo: Mobilizon.Repo
|
||||||
|
alias Mobilizon.Actors.Actor
|
||||||
|
alias MobilizonWeb.Router.Helpers, as: Routes
|
||||||
|
alias MobilizonWeb.Endpoint
|
||||||
|
|
||||||
def user_factory do
|
def user_factory do
|
||||||
%Mobilizon.Users.User{
|
%Mobilizon.Users.User{
|
||||||
|
@ -30,9 +33,10 @@ defmodule Mobilizon.Factory do
|
||||||
followings: [],
|
followings: [],
|
||||||
keys: pem,
|
keys: pem,
|
||||||
type: :Person,
|
type: :Person,
|
||||||
url: MobilizonWeb.Endpoint.url() <> "/@#{preferred_username}",
|
url: Actor.build_url(preferred_username, :page),
|
||||||
followers_url: MobilizonWeb.Endpoint.url() <> "/@#{preferred_username}/followers",
|
followers_url: Actor.build_url(preferred_username, :followers),
|
||||||
following_url: MobilizonWeb.Endpoint.url() <> "/@#{preferred_username}/following",
|
following_url: Actor.build_url(preferred_username, :following),
|
||||||
|
outbox_url: Actor.build_url(preferred_username, :outbox),
|
||||||
user: nil
|
user: nil
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
@ -89,7 +93,7 @@ defmodule Mobilizon.Factory do
|
||||||
event: build(:event),
|
event: build(:event),
|
||||||
uuid: uuid,
|
uuid: uuid,
|
||||||
in_reply_to_comment: nil,
|
in_reply_to_comment: nil,
|
||||||
url: "#{MobilizonWeb.Endpoint.url()}/comments/#{uuid}"
|
url: Routes.page_url(Endpoint, :comment, uuid)
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -109,7 +113,7 @@ defmodule Mobilizon.Factory do
|
||||||
physical_address: build(:address),
|
physical_address: build(:address),
|
||||||
visibility: :public,
|
visibility: :public,
|
||||||
tags: build_list(3, :tag),
|
tags: build_list(3, :tag),
|
||||||
url: "#{actor.url}/#{uuid}",
|
url: Routes.page_url(Endpoint, :event, uuid),
|
||||||
uuid: uuid
|
uuid: uuid
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in a new issue