Commit 96712676 authored by Alex Castaño's avatar Alex Castaño

Inboxes for community, user and collection

parent b9bbbf1f
......@@ -68,13 +68,14 @@ defmodule ActivityPub.ApplyAction do
defp side_effect(activity), do: {:ok, activity}
# TODO
defp insert_into_inbox(activity) do
{people, collections} =
[activity.to, activity.bto, activity.cc, activity.bcc, activity.audience]
|> Enum.concat()
|> Enum.split_with(fn
dest when has_type(dest, "Person") -> true
dest when has_type(dest, "MoodleNet:Community") -> true
dest when has_type(dest, "MoodleNet:Collection") -> true
dest when has_type(dest, "Collection") -> false
dest -> raise "Invalid destination #{inspect(dest)}"
end)
......
......@@ -5,6 +5,9 @@ defmodule MoodleNet do
alias MoodleNet.Policy
require ActivityPub.Guards, as: APG
# FIXME many preload of aspects and assocs for `to` property.
# It's probably it can be optimized or paralalized in a better way
# User connections
defp joined_communities_query(actor) do
......@@ -41,6 +44,25 @@ defmodule MoodleNet do
|> Query.count()
end
# Community connections
defp inbox_query(actor) do
Query.new()
|> Query.with_type("Activity")
|> Query.belongs_to(:inbox, actor)
end
def community_inbox_list(community, opts) do
inbox_query(community)
|> Query.paginate(opts)
|> Query.all()
end
def community_inbox_count(community) do
inbox_query(community)
|> Query.count()
end
defp community_thread_query(community) do
Query.new()
|> Query.with_type("Note")
......@@ -59,8 +81,6 @@ defmodule MoodleNet do
|> Query.count()
end
# Community connections
def community_list(opts \\ %{}) do
Query.new()
|> Query.with_type("MoodleNet:Community")
......@@ -92,6 +112,17 @@ defmodule MoodleNet do
end
# Collection connections
#
def collection_inbox_list(collection, opts) do
inbox_query(collection)
|> Query.paginate(opts)
|> Query.all()
end
def collection_inbox_count(collection) do
inbox_query(collection)
|> Query.count()
end
def collection_list(opts \\ %{}) do
Query.new()
......@@ -259,6 +290,17 @@ defmodule MoodleNet do
)
end
def user_inbox_list(user, opts) do
inbox_query(user)
|> Query.paginate(opts)
|> Query.all()
end
def user_inbox_count(user) do
inbox_query(user)
|> Query.count()
end
defp user_comment_query(actor) do
Query.new()
|> Query.with_type("Note")
......@@ -317,8 +359,9 @@ defmodule MoodleNet do
end
def update_community(actor, community, changes) do
community = Query.preload_assoc(community, :icon)
|> Query.preload_aspect(:actor)
community =
Query.preload_assoc(community, :icon)
|> Query.preload_aspect(:actor)
icon = List.first(community.icon)
{icon_url, changes} = Map.pop(changes, :icon, :no_change)
......@@ -327,7 +370,7 @@ defmodule MoodleNet do
"type" => "Update",
"actor" => actor,
"object" => community,
"to" => [Query.preload(community.followers), Query.preload(actor.followers)],
"to" => [community, Query.preload(community.followers), Query.preload(actor.followers)],
"_changes" => changes
}
......@@ -403,10 +446,12 @@ defmodule MoodleNet do
|> Map.put(:type, "MoodleNet:Collection")
|> Map.put(:attributed_to, [community])
community = Query.preload_aspect(community, :actor)
activity = %{
"type" => "Create",
"actor" => actor,
"to" => Query.preload(actor.followers),
"to" => [community, Query.preload(community.followers), Query.preload(actor.followers)],
"_public" => true
}
......@@ -421,8 +466,10 @@ defmodule MoodleNet do
end
def update_collection(actor, collection, changes) do
collection = Query.preload_assoc(collection, [:icon, attributed_to: {[:actor], []}])
|> Query.preload_aspect(:actor)
collection =
Query.preload_assoc(collection, [:icon, attributed_to: {[:actor], [:followers]}])
|> Query.preload_aspect(:actor)
%{attributed_to: [community]} = collection
icon = List.first(collection.icon)
......@@ -432,7 +479,13 @@ defmodule MoodleNet do
"type" => "Update",
"actor" => actor,
"object" => collection,
"to" => [Query.preload(community.followers), Query.preload(collection.followers), Query.preload(actor.followers)],
"to" => [
community,
collection,
community.followers,
Query.preload(collection.followers),
Query.preload(actor.followers)
],
"_changes" => changes
}
......@@ -471,7 +524,13 @@ defmodule MoodleNet do
activity = %{
"type" => "Create",
"actor" => actor,
"to" => [community.followers, collection.followers, Query.preload(actor.followers)],
"to" => [
community,
collection,
community.followers,
collection.followers,
Query.preload(actor.followers)
],
"_public" => true
}
......@@ -485,8 +544,14 @@ defmodule MoodleNet do
end
def update_resource(actor, resource, changes) do
resource = Query.preload_assoc(resource, [:icon, attributed_to: {[:actor], []}])
resource =
Query.preload_assoc(resource, [
:icon,
attributed_to: {[:actor], [attributed_to: {[:actor], []}]}
])
%{attributed_to: [collection]} = resource
%{attributed_to: [community]} = collection
icon = List.first(resource.icon)
{icon_url, changes} = Map.pop(changes, :icon, :no_change)
......@@ -495,7 +560,13 @@ defmodule MoodleNet do
"type" => "Update",
"actor" => actor,
"object" => resource,
"to" => [Query.preload(collection.followers), Query.preload(actor.followers)],
"to" => [
community,
collection,
Query.preload(community.followers),
Query.preload(collection.followers),
Query.preload(actor.followers)
],
"_changes" => changes
}
......@@ -581,9 +652,9 @@ defmodule MoodleNet do
to =
if reply_to = attrs[:in_reply_to] do
[in_reply_to_author] = reply_to.attributed_to
[in_reply_to_author, context.followers, Query.preload(actor.followers)]
[in_reply_to_author, context, context.followers, Query.preload(actor.followers)]
else
[context.followers, Query.preload(actor.followers)]
[context, context.followers, Query.preload(actor.followers)]
end
activity = %{
......@@ -612,7 +683,14 @@ defmodule MoodleNet do
def join_community(actor, community)
when has_type(actor, "Person") and has_type(community, "MoodleNet:Community") do
params = %{type: "Follow", actor: actor, object: community}
community = Query.preload_aspect(community, :actor)
params = %{
type: "Follow",
actor: actor,
object: community,
to: [community, Query.preload(community.followers)]
}
with {:ok, activity} = ActivityPub.new(params),
{:ok, _activity} <- ActivityPub.apply(activity) do
......@@ -622,7 +700,15 @@ defmodule MoodleNet do
def follow_collection(actor, collection)
when has_type(actor, "Person") and has_type(collection, "MoodleNet:Collection") do
params = %{type: "Follow", actor: actor, object: collection}
collection = Query.preload_assoc(collection, [:followers, attributed_to: :followers])
[community] = collection.attributed_to
params = %{
type: "Follow",
actor: actor,
object: collection,
to: [collection, collection.followers, community.followers]
}
with {:ok, activity} = ActivityPub.new(params),
{:ok, _activity} <- ActivityPub.apply(activity) do
......@@ -634,17 +720,18 @@ defmodule MoodleNet do
when has_type(actor, "Person") and has_type(comment, "Note") do
comment =
comment
|> Query.preload_assoc([:attributed_to, context: :attributed_to])
|> Query.preload_assoc([:attributed_to, context: [:followers, :attributed_to]])
[attributed_to] = comment.attributed_to
actor = Query.preload_assoc(actor, :followers)
[context] = comment.context
attrs = %{
type: "Like",
_public: true,
actor: actor,
object: comment,
to: [actor.followers, attributed_to]
to: [actor.followers, attributed_to, context, context.followers]
}
with :ok <- Policy.like_comment?(actor, comment, attrs),
......@@ -657,7 +744,14 @@ defmodule MoodleNet do
def like_collection(actor, collection)
when has_type(actor, "Person") and has_type(collection, "MoodleNet:Collection") do
collection = preload_community(collection)
attrs = %{type: "Like", actor: actor, object: collection}
|> Query.preload_aspect(:actor)
attrs = %{
type: "Like",
actor: actor,
object: collection,
to: [collection, Query.preload(collection.followers)]
}
with :ok <- Policy.like_collection?(actor, collection, attrs),
{:ok, activity} = ActivityPub.new(attrs),
......@@ -669,7 +763,14 @@ defmodule MoodleNet do
def like_resource(actor, resource)
when has_type(actor, "Person") and has_type(resource, "MoodleNet:EducationalResource") do
resource = preload_community(resource)
attrs = %{type: "Like", actor: actor, object: resource}
[collection] = resource.attributed_to
attrs = %{
type: "Like",
actor: actor,
object: resource,
to: [collection, Query.preload(collection.followers)]
}
with :ok <- Policy.like_resource?(actor, resource, attrs),
{:ok, activity} = ActivityPub.new(attrs),
......@@ -742,10 +843,10 @@ defmodule MoodleNet do
do: Query.preload_assoc(collection, :attributed_to)
defp preload_community(resource) when has_type(resource, "MoodleNet:EducationalResource") do
Query.preload_assoc(resource, attributed_to: :attributed_to)
Query.preload_assoc(resource, attributed_to: {[:actor], [:attributed_to]})
end
defp preload_community(comment) when has_type(comment, "Note") do
Query.preload_assoc(comment, context: :attributed_to)
Query.preload_assoc(comment, context: {[:actor], [:attributed_to]})
end
end
......@@ -5,7 +5,7 @@ defmodule MoodleNetWeb.GraphQL.ActivitySchema do
alias ActivityPub.SQL.Query
alias MoodleNetWeb.GraphQL.MoodleNetSchema, as: Resolver
object :generic_activity do
object :activity do
field(:id, :string)
field(:local_id, :integer)
field(:published, :string)
......@@ -31,7 +31,7 @@ defmodule MoodleNetWeb.GraphQL.ActivitySchema do
object :generic_activity_page do
field(:page_info, non_null(:page_info))
field(:nodes, non_null(list_of(non_null(:generic_activity))))
field(:nodes, non_null(list_of(non_null(:activity))))
field(:total_count, non_null(:integer))
end
......@@ -63,15 +63,28 @@ defmodule MoodleNetWeb.GraphQL.ActivitySchema do
when APG.has_type(activity, "Create") and APG.has_type(object, "MoodleNet:Community"),
do: "CreateCommunity"
defp resolve_activity_type(activity, object)
when APG.has_type(activity, "Update") and APG.has_type(object, "MoodleNet:Community"),
do: "UpdateCommunity"
defp resolve_activity_type(activity, object)
when APG.has_type(activity, "Create") and APG.has_type(object, "MoodleNet:Collection"),
do: "CreateCollection"
defp resolve_activity_type(activity, object)
when APG.has_type(activity, "Update") and APG.has_type(object, "MoodleNet:Collection"),
do: "UpdateCollection"
defp resolve_activity_type(activity, object)
when APG.has_type(activity, "Create") and
APG.has_type(object, "MoodleNet:EducationalResource"),
do: "CreateResource"
defp resolve_activity_type(activity, object)
when APG.has_type(activity, "Update") and
APG.has_type(object, "MoodleNet:EducationalResource"),
do: "UpdateResource"
defp resolve_activity_type(activity, object)
when APG.has_type(activity, "Create") and APG.has_type(object, "Note"),
do: "CreateComment"
......
......@@ -113,6 +113,13 @@ defmodule MoodleNetWeb.GraphQL.CollectionSchema do
resolve(Resolver.with_connection(:collection_liker))
end
field :inbox, non_null(:collection_inbox_connection) do
arg(:limit, :integer)
arg(:before, :integer)
arg(:after, :integer)
resolve(Resolver.with_connection(:collection_inbox))
end
field(:published, :string)
field(:updated, :string)
......@@ -169,6 +176,17 @@ defmodule MoodleNetWeb.GraphQL.CollectionSchema do
field(:node, :user)
end
object :collection_inbox_connection do
field(:page_info, non_null(:page_info))
field(:edges, list_of(:collection_activities_edge))
field(:total_count, non_null(:integer))
end
object :collection_activities_edge do
field(:cursor, non_null(:integer))
field(:node, :activity)
end
input_object :collection_input do
field(:name, non_null(:string))
field(:content, non_null(:string))
......
......@@ -90,6 +90,13 @@ defmodule MoodleNetWeb.GraphQL.CommunitySchema do
resolve(Resolver.with_connection(:community_member))
end
field :inbox, non_null(:community_inbox_connection) do
arg(:limit, :integer)
arg(:before, :integer)
arg(:after, :integer)
resolve(Resolver.with_connection(:community_inbox))
end
field(:published, :string)
field(:updated, :string)
......@@ -135,6 +142,17 @@ defmodule MoodleNetWeb.GraphQL.CommunitySchema do
field(:node, :user)
end
object :community_inbox_connection do
field(:page_info, non_null(:page_info))
field(:edges, list_of(:community_activities_edge))
field(:total_count, non_null(:integer))
end
object :community_activities_edge do
field(:cursor, non_null(:integer))
field(:node, :activity)
end
input_object :community_input do
field(:name, non_null(:string))
field(:content, non_null(:string))
......
......@@ -110,6 +110,13 @@ defmodule MoodleNetWeb.GraphQL.UserSchema do
arg(:after, :integer)
resolve(with_connection(:user_comment))
end
field :inbox, non_null(:user_inbox_connection) do
arg(:limit, :integer)
arg(:before, :integer)
arg(:after, :integer)
resolve(with_connection(:user_inbox))
end
end
object :user_joined_communities_connection do
......@@ -140,6 +147,18 @@ defmodule MoodleNetWeb.GraphQL.UserSchema do
field(:total_count, non_null(:integer))
end
object :user_inbox_connection do
field(:page_info, non_null(:page_info))
field(:edges, list_of(:user_activities_edge))
field(:total_count, non_null(:integer))
end
object :user_activities_edge do
field(:cursor, non_null(:integer))
field(:node, :activity)
end
object :user_created_comments_edge do
field(:cursor, non_null(:integer))
field(:node, :comment)
......
......@@ -862,4 +862,113 @@ defmodule MoodleNetWeb.GraphQL.CollectionTest do
assert ret_collection == ret_collection_2
end
@tag :user
test "inbox connection", %{conn: conn, actor: actor} do
community = Factory.community(actor)
MoodleNet.update_community(actor, community, %{name: "Name"})
collection = Factory.collection(actor, community)
MoodleNet.update_collection(actor, collection, %{name: "Name"})
MoodleNet.like_collection(actor, collection)
resource = Factory.resource(actor, collection)
MoodleNet.update_resource(actor, resource, %{name: "Name"})
MoodleNet.like_resource(actor, resource)
comment = Factory.comment(actor, collection)
reply = Factory.reply(actor, comment)
MoodleNet.like_comment(actor, comment)
MoodleNet.like_comment(actor, reply)
local_id = local_id(collection)
query = """
{
collection(localId: #{local_id}) {
inbox {
pageInfo {
startCursor
endCursor
}
edges {
cursor
node {
id
activity_type
}
}
totalCount
}
}
}
"""
assert ret =
conn
|> post("/api/graphql", %{query: query})
|> json_response(200)
|> Map.fetch!("data")
|> Map.fetch!("collection")
|> Map.fetch!("inbox")
assert %{
"pageInfo" => %{"startCursor" => nil, "endCursor" => nil},
"edges" => edges,
"totalCount" => 10
} = ret
assert [
%{
"node" => %{
"activity_type" => "LikeComment"
}
},
%{
"node" => %{
"activity_type" => "LikeComment"
}
},
%{
"node" => %{
"activity_type" => "CreateComment"
}
},
%{
"node" => %{
"activity_type" => "CreateComment"
}
},
%{
"node" => %{
"activity_type" => "LikeResource"
}
},
%{
"node" => %{
"activity_type" => "UpdateResource"
}
},
%{
"node" => %{
"activity_type" => "CreateResource"
}
},
%{
"node" => %{
"activity_type" => "LikeCollection"
}
},
%{
"node" => %{
"activity_type" => "UpdateCollection"
}
},
%{
"node" => %{
"activity_type" => "FollowCollection"
}
}
] = edges
end
end
......@@ -573,4 +573,99 @@ defmodule MoodleNetWeb.GraphQL.CommunityTest do
assert ret_community == ret_community_2
end
@tag :user
test "inbox connection", %{conn: conn, actor: actor} do
community = Factory.community(actor)
MoodleNet.update_community(actor, community, %{name: "Name"})
collection = Factory.collection(actor, community)
MoodleNet.update_collection(actor, collection, %{name: "Name"})
resource = Factory.resource(actor, collection)
MoodleNet.update_resource(actor, resource, %{name: "Name"})
comment = Factory.comment(actor, community)
Factory.reply(actor, comment)
local_id = local_id(community)
query = """
{
community(localId: #{local_id}) {
inbox {
pageInfo {
startCursor
endCursor
}
edges {
cursor
node {
id
activity_type
}
}
totalCount
}
}
}
"""
assert ret =
conn
|> post("/api/graphql", %{query: query})
|> json_response(200)
|> Map.fetch!("data")
|> Map.fetch!("community")
|> Map.fetch!("inbox")
assert %{
"pageInfo" => %{"startCursor" => nil, "endCursor" => nil},
"edges" => edges,
"totalCount" => 8
} = ret
assert [
%{
"node" => %{
"activity_type" => "CreateComment"
}
},
%{
"node" => %{
"activity_type" => "CreateComment"
}
},
%{
"node" => %{
"activity_type" => "UpdateResource"
}
},
%{
"node" => %{
"activity_type" => "CreateResource"
}
},
%{
"node" => %{
"activity_type" => "UpdateCollection"
}
},
%{
"node" => %{
"activity_type" => "CreateCollection"
}
},
%{
"node" => %{
"activity_type" => "UpdateCommunity"
}
},
%{
"node" => %{
"activity_type" => "JoinCommunity"
}
}
] = edges
end
end
......@@ -562,4 +562,126 @@ defmodule MoodleNetWeb.GraphQL.UserSchemaTest do
assert user["location"] == "MoodleNet"
assert user["icon"] == "https://imag.es/alexcastano"
end
@tag :user
test "inbox connection", %{conn: conn, actor: actor} do
owner = Factory.actor()
community = Factory.community(owner)
MoodleNet.join_community(actor, community)
MoodleNet.update_community(owner, community, %{name: "Name"})
collection = Factory.collection(owner, community)
MoodleNet.update_collection(owner, collection, %{name: "Name"})
MoodleNet.like_collection(owner, collection)
resource = Factory.resource(owner, collection)
MoodleNet.update_resource(owner, resource, %{name: "Name"})
MoodleNet.like_resource(owner, resource)
comment = Factory.comment(owner, collection)
reply = Factory.reply(owner, comment)
MoodleNet.like_comment(owner, comment)
MoodleNet.like_comment(owner, reply)
comment = Factory.comment(owner, community)
reply = Factory.reply(owner, comment)
MoodleNet.like_comment(owner, comment)
MoodleNet.like_comment(owner, reply)
local_id = local_id(actor)
query = """
{
user(localId: #{local_id}) {
inbox {
pageInfo {
startCursor
endCursor
}
edges {
cursor
node {
id