Introduce group posts

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
Thomas Citharel
2020-07-09 17:24:28 +02:00
parent bec1c69d4b
commit 9c9f1385fb
249 changed files with 11886 additions and 5023 deletions

View File

@@ -3,8 +3,8 @@ defmodule Mobilizon.GraphQL.API.Comments do
API for Comments.
"""
alias Mobilizon.Conversations.Comment
alias Mobilizon.Actors.Actor
alias Mobilizon.Discussions.Comment
alias Mobilizon.Federation.ActivityPub
alias Mobilizon.Federation.ActivityPub.Activity
@@ -19,7 +19,7 @@ defmodule Mobilizon.GraphQL.API.Comments do
end
def update_comment(%Comment{} = comment, args) do
ActivityPub.update(:comment, comment, args, true)
ActivityPub.update(comment, args, true)
end
@doc """
@@ -27,8 +27,8 @@ defmodule Mobilizon.GraphQL.API.Comments do
Deletes a comment from an actor
"""
@spec delete_comment(Comment.t()) :: {:ok, Activity.t(), Comment.t()} | any
def delete_comment(%Comment{} = comment) do
ActivityPub.delete(comment, true)
@spec delete_comment(Comment.t(), Actor.t()) :: {:ok, Activity.t(), Comment.t()} | any
def delete_comment(%Comment{} = comment, %Actor{} = actor) do
ActivityPub.delete(comment, actor, true)
end
end

View File

@@ -34,7 +34,7 @@ defmodule Mobilizon.GraphQL.API.Events do
Map.update(args, :picture, nil, fn picture ->
process_picture(picture, organizer_actor)
end) do
ActivityPub.update(:event, event, args, Map.get(args, :draft, false) == false)
ActivityPub.update(event, args, Map.get(args, :draft, false) == false)
end
end
@@ -43,8 +43,8 @@ defmodule Mobilizon.GraphQL.API.Events do
If the event is deleted by
"""
def delete_event(%Event{} = event, federate \\ true) do
ActivityPub.delete(event, federate)
def delete_event(%Event{} = event, %Actor{} = actor, federate \\ true) do
ActivityPub.delete(event, actor, federate)
end
defp process_picture(nil, _), do: nil

View File

@@ -19,8 +19,25 @@ defmodule Mobilizon.GraphQL.API.Groups do
args |> Map.get(:preferred_username) |> HTML.strip_tags() |> String.trim(),
{:existing_group, nil} <-
{:existing_group, Actors.get_local_group_by_title(preferred_username)},
args <- args |> Map.put(:type, :Group),
{:ok, %Activity{} = activity, %Actor{} = group} <-
ActivityPub.create(:group, args, true, %{"actor" => args.creator_actor.url}) do
ActivityPub.create(:actor, args, true, %{"actor" => args.creator_actor.url}) do
{:ok, activity, group}
else
{:existing_group, _} ->
{:error, "A group with this name already exists"}
{:is_owned, nil} ->
{:error, "Actor id is not owned by authenticated user"}
end
end
@spec create_group(map) :: {:ok, Activity.t(), Actor.t()} | any
def update_group(%{id: id} = args) do
with {:existing_group, {:ok, %Actor{type: :Group} = group}} <-
{:existing_group, Actors.get_group_by_actor_id(id)},
{:ok, %Activity{} = activity, %Actor{} = group} <-
ActivityPub.update(group, args, true, %{"actor" => args.updater_actor.url}) do
{:ok, activity, group}
else
{:existing_group, _} ->

View File

@@ -9,7 +9,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Admin do
alias Mobilizon.Actors.Actor
alias Mobilizon.Admin.{ActionLog, Setting}
alias Mobilizon.Config
alias Mobilizon.Conversations.Comment
alias Mobilizon.Discussions.Comment
alias Mobilizon.Events.Event
alias Mobilizon.Federation.ActivityPub
alias Mobilizon.Federation.ActivityPub.Relay
@@ -297,7 +297,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Admin do
with {:changes, true} <- {:changes, args != %{}},
%Actor{} = instance_actor <- Relay.get_actor(),
{:ok, _activity, _actor} <- ActivityPub.update(:actor, instance_actor, args, true) do
{:ok, _activity, _actor} <- ActivityPub.update(instance_actor, args, true) do
:ok
else
{:changes, false} ->

View File

@@ -3,9 +3,9 @@ defmodule Mobilizon.GraphQL.Resolvers.Comment do
Handles the comment-related GraphQL calls.
"""
alias Mobilizon.{Actors, Admin, Conversations}
alias Mobilizon.{Actors, Admin, Discussions}
alias Mobilizon.Actors.Actor
alias Mobilizon.Conversations.Comment, as: CommentModel
alias Mobilizon.Discussions.Comment, as: CommentModel
alias Mobilizon.Users
alias Mobilizon.Users.User
@@ -14,7 +14,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Comment do
require Logger
def get_thread(_parent, %{id: thread_id}, _context) do
{:ok, Conversations.get_thread_replies(thread_id)}
{:ok, Discussions.get_thread_replies(thread_id)}
end
def create_comment(
@@ -51,7 +51,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Comment do
) do
with {:actor, %Actor{id: actor_id} = _actor} <- {:actor, Users.get_actor_for_user(user)},
%CommentModel{actor_id: comment_actor_id} = comment <-
Mobilizon.Conversations.get_comment(comment_id),
Mobilizon.Discussions.get_comment(comment_id),
true <- actor_id === comment_actor_id,
{:ok, _, %CommentModel{} = comment} <- Comments.update_comment(comment, %{text: text}) do
{:ok, comment}
@@ -72,15 +72,15 @@ defmodule Mobilizon.GraphQL.Resolvers.Comment do
}
) do
with {actor_id, ""} <- Integer.parse(actor_id),
{:is_owned, %Actor{} = _organizer_actor} <- User.owns_actor(user, actor_id),
{:is_owned, %Actor{} = actor} <- User.owns_actor(user, actor_id),
%CommentModel{deleted_at: nil} = comment <-
Conversations.get_comment_with_preload(comment_id) do
Discussions.get_comment_with_preload(comment_id) do
cond do
{:comment_can_be_managed, true} == CommentModel.can_be_managed_by(comment, actor_id) ->
do_delete_comment(comment)
do_delete_comment(comment, actor)
role in [:moderator, :administrator] ->
with {:ok, res} <- do_delete_comment(comment),
with {:ok, res} <- do_delete_comment(comment, actor),
%Actor{} = actor <- Actors.get_actor(actor_id) do
Admin.log_action(actor, "delete", comment)
@@ -103,9 +103,9 @@ defmodule Mobilizon.GraphQL.Resolvers.Comment do
{:error, "You are not allowed to delete a comment if not connected"}
end
defp do_delete_comment(%CommentModel{} = comment) do
defp do_delete_comment(%CommentModel{} = comment, %Actor{} = actor) do
with {:ok, _, %CommentModel{} = comment} <-
Comments.delete_comment(comment) do
Comments.delete_comment(comment, actor) do
{:ok, comment}
end
end

View File

@@ -1,110 +0,0 @@
defmodule Mobilizon.GraphQL.Resolvers.Conversation do
@moduledoc """
Handles the group-related GraphQL calls.
"""
alias Mobilizon.{Actors, Conversations, Users}
alias Mobilizon.Actors.Actor
alias Mobilizon.Conversations.Conversation, as: ConversationModel
alias Mobilizon.Storage.Page
alias Mobilizon.Users.User
def find_conversations_for_actor(
%Actor{id: group_id},
_args,
%{
context: %{
current_user: %User{} = user
}
}
) do
with {:actor, %Actor{id: actor_id} = _actor} <- {:actor, Users.get_actor_for_user(user)},
{:member, true} <- {:member, Actors.is_member?(actor_id, group_id)} do
{:ok, Conversations.find_conversations_for_actor(group_id)}
else
{:member, false} ->
{:ok, %Page{total: 0, elements: []}}
end
end
def find_conversations_for_actor(%Actor{}, _args, _resolution) do
{:ok, %Page{total: 0, elements: []}}
end
def get_conversation(_parent, %{id: id}, _resolution) do
{:ok, Conversations.get_conversation(id)}
end
def get_comments_for_conversation(
%ConversationModel{id: conversation_id},
%{page: page, limit: limit},
_resolution
) do
{:ok, Conversations.get_comments_for_conversation(conversation_id, page, limit)}
end
def create_conversation(
_parent,
%{title: title, text: text, actor_id: actor_id, creator_id: creator_id},
_resolution
) do
with {:ok, %ConversationModel{} = conversation} <-
Conversations.create_conversation(%{
title: title,
text: text,
actor_id: actor_id,
creator_id: creator_id
}) do
{:ok, conversation}
end
end
def reply_to_conversation(
_parent,
%{text: text, conversation_id: conversation_id},
%{
context: %{
current_user: %User{} = user
}
}
) do
with {:actor, %Actor{id: actor_id} = _actor} <- {:actor, Users.get_actor_for_user(user)},
{:no_conversation, %ConversationModel{} = conversation} <-
{:no_conversation, Conversations.get_conversation(conversation_id)},
{:ok, %ConversationModel{} = conversation} <-
Conversations.reply_to_conversation(
conversation,
%{
text: text,
actor_id: actor_id
}
) do
{:ok, conversation}
end
end
@spec update_conversation(map(), map(), map()) :: {:ok, ConversationModel.t()}
def update_conversation(
_parent,
%{title: title, conversation_id: conversation_id},
%{
context: %{
current_user: %User{} = user
}
}
) do
with {:actor, %Actor{id: actor_id} = _actor} <- {:actor, Users.get_actor_for_user(user)},
{:no_conversation, %ConversationModel{creator_id: creator_id} = conversation} <-
{:no_conversation, Conversations.get_conversation(conversation_id)},
{:check_access, true} <- {:check_access, actor_id == creator_id},
{:ok, %ConversationModel{} = conversation} <-
Conversations.update_conversation(
conversation,
%{
title: title
}
) do
{:ok, conversation}
end
end
end

View File

@@ -0,0 +1,179 @@
defmodule Mobilizon.GraphQL.Resolvers.Discussion do
@moduledoc """
Handles the group-related GraphQL calls.
"""
alias Mobilizon.{Actors, Discussions, Users}
alias Mobilizon.Actors.Actor
alias Mobilizon.Discussions.{Comment, Discussion}
alias Mobilizon.Federation.ActivityPub
alias Mobilizon.Storage.Page
alias Mobilizon.Users.User
def find_discussions_for_actor(
%Actor{id: group_id},
_args,
%{
context: %{
current_user: %User{} = user
}
}
) do
with {:actor, %Actor{id: actor_id} = _actor} <- {:actor, Users.get_actor_for_user(user)},
{:member, true} <- {:member, Actors.is_member?(actor_id, group_id)} do
{:ok, Discussions.find_discussions_for_actor(group_id)}
else
{:member, false} ->
{:ok, %Page{total: 0, elements: []}}
end
end
def find_discussions_for_actor(%Actor{}, _args, _resolution) do
{:ok, %Page{total: 0, elements: []}}
end
def get_discussion(_parent, %{id: id}, %{
context: %{
current_user: %User{} = user
}
}) do
with {:actor, %Actor{id: creator_id} = _actor} <- {:actor, Users.get_actor_for_user(user)},
%Discussion{actor_id: actor_id} = discussion <-
Discussions.get_discussion(id),
{:member, true} <- {:member, Actors.is_member?(creator_id, actor_id)} do
{:ok, discussion}
end
end
def get_discussion(_parent, %{slug: slug}, %{
context: %{
current_user: %User{} = user
}
}) do
with {:actor, %Actor{id: creator_id} = _actor} <- {:actor, Users.get_actor_for_user(user)},
%Discussion{actor_id: actor_id} = discussion <-
Discussions.get_discussion_by_slug(slug),
{:member, true} <- {:member, Actors.is_member?(creator_id, actor_id)} do
{:ok, discussion}
else
nil -> {:error, "No such discussion"}
end
end
def get_discussion(_parent, _args, _resolution),
do: {:error, "You need to be logged-in to access discussions"}
def get_comments_for_discussion(
%Discussion{id: discussion_id},
%{page: page, limit: limit},
_resolution
) do
{:ok, Discussions.get_comments_for_discussion(discussion_id, page, limit)}
end
def create_discussion(
_parent,
%{title: title, text: text, actor_id: actor_id},
%{
context: %{
current_user: %User{} = user
}
}
) do
with {:actor, %Actor{id: creator_id} = _actor} <- {:actor, Users.get_actor_for_user(user)},
{:member, true} <- {:member, Actors.is_member?(creator_id, actor_id)},
{:ok, _activity, %Discussion{} = discussion} <-
ActivityPub.create(
:discussion,
%{
title: title,
text: text,
actor_id: actor_id,
creator_id: creator_id,
attributed_to_id: actor_id
},
true
) do
{:ok, discussion}
end
end
def reply_to_discussion(
_parent,
%{text: text, discussion_id: discussion_id},
%{
context: %{
current_user: %User{} = user
}
}
) do
with {:actor, %Actor{id: creator_id} = _actor} <- {:actor, Users.get_actor_for_user(user)},
{:no_discussion,
%Discussion{
actor_id: actor_id,
last_comment: %Comment{
id: last_comment_id,
origin_comment_id: origin_comment_id,
in_reply_to_comment_id: previous_in_reply_to_comment_id
}
} = _discussion} <-
{:no_discussion, Discussions.get_discussion(discussion_id)},
{:member, true} <- {:member, Actors.is_member?(creator_id, actor_id)},
{:ok, _activity, %Discussion{} = discussion} <-
ActivityPub.create(
:discussion,
%{
text: text,
discussion_id: discussion_id,
actor_id: creator_id,
attributed_to_id: actor_id,
in_reply_to_comment_id: last_comment_id,
origin_comment_id:
origin_comment_id || previous_in_reply_to_comment_id || last_comment_id
},
true
) do
{:ok, discussion}
end
end
@spec update_discussion(map(), map(), map()) :: {:ok, Discussion.t()}
def update_discussion(
_parent,
%{title: title, discussion_id: discussion_id},
%{
context: %{
current_user: %User{} = user
}
}
) do
with {:actor, %Actor{id: creator_id} = _actor} <- {:actor, Users.get_actor_for_user(user)},
{:no_discussion, %Discussion{actor_id: actor_id} = discussion} <-
{:no_discussion, Discussions.get_discussion(discussion_id)},
{:member, true} <- {:member, Actors.is_member?(creator_id, actor_id)},
{:ok, _activity, %Discussion{} = discussion} <-
ActivityPub.update(
discussion,
%{
title: title
}
) do
{:ok, discussion}
end
end
def delete_discussion(_parent, %{discussion_id: discussion_id}, %{
context: %{
current_user: %User{} = user
}
}) do
with {:actor, %Actor{id: creator_id} = actor} <- {:actor, Users.get_actor_for_user(user)},
{:no_discussion, %Discussion{actor_id: actor_id} = discussion} <-
{:no_discussion, Discussions.get_discussion(discussion_id)},
{:member, true} <- {:member, Actors.is_member?(creator_id, actor_id)},
{:ok, _activity, %Discussion{} = discussion} <-
ActivityPub.delete(discussion, actor) do
{:ok, discussion}
end
end
end

View File

@@ -255,13 +255,13 @@ defmodule Mobilizon.GraphQL.Resolvers.Event do
) do
with {:ok, %Event{local: is_local} = event} <- Events.get_event_with_preload(event_id),
{actor_id, ""} <- Integer.parse(actor_id),
{:is_owned, %Actor{}} <- User.owns_actor(user, actor_id) do
{:is_owned, %Actor{} = actor} <- User.owns_actor(user, actor_id) do
cond do
{:event_can_be_managed, true} == Event.can_be_managed_by(event, actor_id) ->
do_delete_event(event)
do_delete_event(event, actor)
role in [:moderator, :administrator] ->
with {:ok, res} <- do_delete_event(event, !is_local),
with {:ok, res} <- do_delete_event(event, actor, !is_local),
%Actor{} = actor <- Actors.get_actor(actor_id) do
Admin.log_action(actor, "delete", event)
@@ -284,8 +284,9 @@ defmodule Mobilizon.GraphQL.Resolvers.Event do
{:error, "You need to be logged-in to delete an event"}
end
defp do_delete_event(event, federate \\ true) when is_boolean(federate) do
with {:ok, _activity, event} <- API.Events.delete_event(event) do
defp do_delete_event(%Event{} = event, %Actor{} = actor, federate \\ true)
when is_boolean(federate) do
with {:ok, _activity, event} <- API.Events.delete_event(event, actor) do
{:ok, %{id: event.id}}
end
end

View File

@@ -80,7 +80,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Group do
API.Groups.create_group(args) do
{:ok, group}
else
{:error, err} when is_bitstring(err) ->
{:error, err} when is_binary(err) ->
{:error, err}
{:is_owned, nil} ->
@@ -92,6 +92,36 @@ defmodule Mobilizon.GraphQL.Resolvers.Group do
{:error, "You need to be logged-in to create a group"}
end
@doc """
Create a new group. The creator is automatically added as admin
"""
def update_group(
_parent,
args,
%{
context: %{
current_user: %User{} = user
}
}
) do
with %Actor{} = updater_actor <- Users.get_actor_for_user(user),
args <- Map.put(args, :updater_actor, updater_actor),
{:ok, _activity, %Actor{type: :Group} = group} <-
API.Groups.update_group(args) do
{:ok, group}
else
{:error, err} when is_binary(err) ->
{:error, err}
{:is_owned, nil} ->
{:error, "Creator actor id is not owned by the current user"}
end
end
def update_group(_parent, _args, _resolution) do
{:error, "You need to be logged-in to update a group"}
end
@doc """
Delete an existing group
"""

View File

@@ -16,14 +16,26 @@ defmodule Mobilizon.GraphQL.Resolvers.Member do
"""
def find_members_for_group(
%Actor{id: group_id} = group,
_args,
%{page: page, limit: limit, roles: roles},
%{
context: %{current_user: %User{} = user}
} = _resolution
) do
with %Actor{id: actor_id} <- Users.get_actor_for_user(user),
{:member, true} <- {:member, Actors.is_member?(actor_id, group_id)},
%Page{} = page <- Actors.list_members_for_group(group) do
{:member, true} <- {:member, Actors.is_member?(actor_id, group_id)} do
roles =
case roles do
"" ->
[]
roles ->
roles
|> String.split(",")
|> Enum.map(&String.downcase/1)
|> Enum.map(&String.to_existing_atom/1)
end
%Page{} = page = Actors.list_members_for_group(group, roles, page, limit)
{:ok, page}
else
{:member, false} ->

View File

@@ -129,7 +129,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Person do
{:find_actor, Actors.get_actor(id)},
{:is_owned, %Actor{}} <- User.owns_actor(user, actor.id),
args <- save_attached_pictures(args),
{:ok, _activity, %Actor{} = actor} <- ActivityPub.update(:actor, actor, args, true) do
{:ok, _activity, %Actor{} = actor} <- ActivityPub.update(actor, args, true) do
{:ok, actor}
else
{:find_actor, nil} ->

View File

@@ -0,0 +1,198 @@
defmodule Mobilizon.GraphQL.Resolvers.Post do
@moduledoc """
Handles the posts-related GraphQL calls
"""
alias Mobilizon.{Actors, Posts, Users}
alias Mobilizon.Actors.Actor
alias Mobilizon.Federation.ActivityPub
alias Mobilizon.Posts.Post
alias Mobilizon.Storage.Page
alias Mobilizon.Users.User
require Logger
@public_accessible_visibilities [:public, :unlisted]
@doc """
Find posts for group.
Returns only if actor requesting is a member of the group
"""
def find_posts_for_group(
%Actor{id: group_id} = group,
%{page: page, limit: limit} = args,
%{
context: %{
current_user: %User{} = user
}
} = _resolution
) do
with %Actor{id: actor_id} <- Users.get_actor_for_user(user),
{:member, true} <- {:member, Actors.is_member?(actor_id, group_id)},
%Page{} = page <- Posts.get_posts_for_group(group, page, limit) do
{:ok, page}
else
{:member, _} ->
find_posts_for_group(group, args, nil)
end
end
def find_posts_for_group(
%Actor{} = group,
%{page: page, limit: limit},
_resolution
) do
with %Page{} = page <- Posts.get_public_posts_for_group(group, page, limit) do
{:ok, page}
end
end
def find_posts_for_group(
_group,
_args,
_resolution
) do
{:ok, %Page{total: 0, elements: []}}
end
def get_post(
parent,
%{slug: slug},
%{
context: %{
current_user: %User{} = user
}
} = _resolution
) do
with {:current_actor, %Actor{id: actor_id}} <-
{:current_actor, Users.get_actor_for_user(user)},
{:post, %Post{attributed_to: %Actor{id: group_id}} = post} <-
{:post, Posts.get_post_by_slug_with_preloads(slug)},
{:member, true} <- {:member, Actors.is_member?(actor_id, group_id)} do
{:ok, post}
else
{:member, false} -> get_post(parent, %{slug: slug}, nil)
{:post, _} -> {:error, "No such post"}
end
end
def get_post(
_parent,
%{slug: slug},
_resolution
) do
case {:post, Posts.get_post_by_slug_with_preloads(slug)} do
{:post, %Post{visibility: visibility, draft: false} = post}
when visibility in @public_accessible_visibilities ->
{:ok, post}
{:post, _} ->
{:error, "No such post"}
end
end
def get_post(_parent, _args, _resolution) do
{:error, "No such post"}
end
def create_post(
_parent,
%{attributed_to_id: group_id} = args,
%{
context: %{
current_user: %User{} = user
}
} = _resolution
) do
with %Actor{id: actor_id} <- Users.get_actor_for_user(user),
{:member, true} <- {:member, Actors.is_member?(actor_id, group_id)},
{:ok, _, %Post{} = post} <-
ActivityPub.create(
:post,
args
|> Map.put(:author_id, actor_id)
|> Map.put(:attributed_to_id, group_id),
true,
%{}
) do
{:ok, post}
else
{:own_check, _} ->
{:error, "Parent post doesn't match this group"}
{:member, _} ->
{:error, "Actor id is not member of group"}
end
end
def create_post(_parent, _args, _resolution) do
{:error, "You need to be logged-in to create posts"}
end
def update_post(
_parent,
%{id: id} = args,
%{
context: %{
current_user: %User{} = user
}
} = _resolution
) do
with {:uuid, {:ok, _uuid}} <- {:uuid, Ecto.UUID.cast(id)},
%Actor{id: actor_id} <- Users.get_actor_for_user(user),
{:post, %Post{attributed_to: %Actor{id: group_id}} = post} <-
{:post, Posts.get_post_with_preloads(id)},
{:member, true} <- {:member, Actors.is_member?(actor_id, group_id)},
{:ok, _, %Post{} = post} <-
ActivityPub.update(post, args, true, %{}) do
{:ok, post}
else
{:uuid, :error} ->
{:error, "Post ID is not a valid ID"}
{:post, _} ->
{:error, "Post doesn't exist"}
{:member, _} ->
{:error, "Actor id is not member of group"}
end
end
def update_post(_parent, _args, _resolution) do
{:error, "You need to be logged-in to update posts"}
end
def delete_post(
_parent,
%{id: post_id},
%{
context: %{
current_user: %User{} = user
}
} = _resolution
) do
with {:uuid, {:ok, _uuid}} <- {:uuid, Ecto.UUID.cast(post_id)},
%Actor{id: actor_id} = actor <- Users.get_actor_for_user(user),
{:post, %Post{attributed_to: %Actor{id: group_id}} = post} <-
{:post, Posts.get_post_with_preloads(post_id)},
{:member, true} <- {:member, Actors.is_member?(actor_id, group_id)},
{:ok, _, %Post{} = post} <-
ActivityPub.delete(post, actor) do
{:ok, post}
else
{:uuid, :error} ->
{:error, "Post ID is not a valid ID"}
{:post, _} ->
{:error, "Post doesn't exist"}
{:member, _} ->
{:error, "Actor id is not member of group"}
end
end
def delete_post(_parent, _args, _resolution) do
{:error, "You need to be logged-in to delete posts"}
end
end

View File

@@ -141,7 +141,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Resource do
{:resource, Resources.get_resource_with_preloads(resource_id)},
{:member, true} <- {:member, Actors.is_member?(actor_id, group_id)},
{:ok, _, %Resource{} = resource} <-
ActivityPub.update(:resource, resource, args, true, %{}) do
ActivityPub.update(resource, args, true, %{}) do
{:ok, resource}
else
{:resource, _} ->
@@ -165,12 +165,12 @@ defmodule Mobilizon.GraphQL.Resolvers.Resource do
}
} = _resolution
) do
with %Actor{id: actor_id} <- Users.get_actor_for_user(user),
with %Actor{id: actor_id} = actor <- Users.get_actor_for_user(user),
{:resource, %Resource{parent_id: _parent_id, actor_id: group_id} = resource} <-
{:resource, Resources.get_resource_with_preloads(resource_id)},
{:member, true} <- {:member, Actors.is_member?(actor_id, group_id)},
{:ok, _, %Resource{} = resource} <-
ActivityPub.delete(resource) do
ActivityPub.delete(resource, actor) do
{:ok, resource}
else
{:resource, _} ->

View File

@@ -3,8 +3,9 @@ defmodule Mobilizon.GraphQL.Resolvers.Tag do
Handles the tag-related GraphQL calls
"""
alias Mobilizon.Events
alias Mobilizon.{Events, Posts}
alias Mobilizon.Events.{Event, Tag}
alias Mobilizon.Posts.Post
def list_tags(_parent, %{page: page, limit: limit}, _resolution) do
tags = Mobilizon.Events.list_tags(page, limit)
@@ -16,7 +17,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Tag do
Retrieve the list of tags for an event
"""
def list_tags_for_event(%Event{id: id}, _args, _resolution) do
{:ok, Mobilizon.Events.list_tags_for_event(id)}
{:ok, Events.list_tags_for_event(id)}
end
@doc """
@@ -24,10 +25,17 @@ defmodule Mobilizon.GraphQL.Resolvers.Tag do
"""
def list_tags_for_event(%{url: url}, _args, _resolution) do
with %Event{id: event_id} <- Events.get_event_by_url(url) do
{:ok, Mobilizon.Events.list_tags_for_event(event_id)}
{:ok, Events.list_tags_for_event(event_id)}
end
end
@doc """
Retrieve the list of tags for a post
"""
def list_tags_for_post(%Post{id: id}, _args, _resolution) do
{:ok, Posts.list_tags_for_post(id)}
end
# @doc """
# Retrieve the list of related tags for a given tag ID
# """
@@ -42,7 +50,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Tag do
Retrieve the list of related tags for a parent tag
"""
def get_related_tags(%Tag{} = tag, _args, _resolution) do
with tags <- Mobilizon.Events.list_tag_neighbors(tag) do
with tags <- Events.list_tag_neighbors(tag) do
{:ok, tags}
end
end

View File

@@ -211,7 +211,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Todos do
{:todo_list, Todos.get_todo_list(todo_list_id)},
{:member, true} <- {:member, Actors.is_member?(actor_id, group_id)},
{:ok, _, %Todo{} = todo} <-
ActivityPub.update(:todo, todo, args, true, %{}) do
ActivityPub.update(todo, args, true, %{}) do
{:ok, todo}
else
{:todo_list, _} ->

View File

@@ -9,6 +9,7 @@ defmodule Mobilizon.GraphQL.Resolvers.User do
alias Mobilizon.Actors.Actor
alias Mobilizon.Crypto
alias Mobilizon.Federation.ActivityPub
alias Mobilizon.Federation.ActivityPub.Relay
alias Mobilizon.Service.Auth.Authenticator
alias Mobilizon.Storage.{Page, Repo}
alias Mobilizon.Users.{Setting, User}
@@ -417,7 +418,8 @@ defmodule Mobilizon.GraphQL.Resolvers.User do
with {:moderator_actor, %Actor{} = moderator_actor} <-
{:moderator_actor, Users.get_actor_for_user(moderator_user)},
%User{disabled: false} = user <- Users.get_user(user_id),
{:ok, %User{}} <- do_delete_account(%User{} = user) do
{:ok, %User{}} <-
do_delete_account(%User{} = user, Relay.get_actor()) do
Admin.log_action(moderator_actor, "delete", user)
else
{:moderator_actor, nil} ->
@@ -432,7 +434,7 @@ defmodule Mobilizon.GraphQL.Resolvers.User do
{:error, "You need to be logged-in to delete your account"}
end
defp do_delete_account(%User{} = user) do
defp do_delete_account(%User{} = user, actor_performing \\ nil) do
with actors <- Users.get_actors_for_user(user),
activated <- not is_nil(user.confirmed_at),
# Detach actors from user
@@ -444,7 +446,8 @@ defmodule Mobilizon.GraphQL.Resolvers.User do
# Launch a background job to delete actors
:ok <-
Enum.each(actors, fn actor ->
ActivityPub.delete(actor, true)
actor_performing = actor_performing || actor
ActivityPub.delete(actor, actor_performing, true)
end),
# Delete user
{:ok, user} <- Users.delete_user(user, reserve_email: activated) do

View File

@@ -8,7 +8,7 @@ defmodule Mobilizon.GraphQL.Schema do
alias Mobilizon.{
Actors,
Addresses,
Conversations,
Discussions,
Events,
Media,
Reports,
@@ -18,7 +18,7 @@ defmodule Mobilizon.GraphQL.Schema do
}
alias Mobilizon.Actors.{Actor, Follower, Member}
alias Mobilizon.Conversations.Comment
alias Mobilizon.Discussions.Comment
alias Mobilizon.Events.{Event, Participant}
alias Mobilizon.GraphQL.Schema
alias Mobilizon.Storage.Repo
@@ -34,10 +34,11 @@ defmodule Mobilizon.GraphQL.Schema do
import_types(Schema.Actors.PersonType)
import_types(Schema.Actors.GroupType)
import_types(Schema.Actors.ApplicationType)
import_types(Schema.Conversations.CommentType)
import_types(Schema.Conversations.ConversationType)
import_types(Schema.Discussions.CommentType)
import_types(Schema.Discussions.DiscussionType)
import_types(Schema.SearchType)
import_types(Schema.ResourceType)
import_types(Schema.PostType)
import_types(Schema.Todos.TodoListType)
import_types(Schema.Todos.TodoType)
import_types(Schema.ConfigType)
@@ -116,7 +117,7 @@ defmodule Mobilizon.GraphQL.Schema do
|> Dataloader.add_source(Actors, default_source)
|> Dataloader.add_source(Users, default_source)
|> Dataloader.add_source(Events, default_source)
|> Dataloader.add_source(Conversations, Conversations.data())
|> Dataloader.add_source(Discussions, Discussions.data())
|> Dataloader.add_source(Addresses, default_source)
|> Dataloader.add_source(Media, default_source)
|> Dataloader.add_source(Reports, default_source)
@@ -148,8 +149,9 @@ defmodule Mobilizon.GraphQL.Schema do
import_fields(:admin_queries)
import_fields(:todo_list_queries)
import_fields(:todo_queries)
import_fields(:conversation_queries)
import_fields(:discussion_queries)
import_fields(:resource_queries)
import_fields(:post_queries)
import_fields(:statistics_queries)
end
@@ -170,8 +172,9 @@ defmodule Mobilizon.GraphQL.Schema do
import_fields(:admin_mutations)
import_fields(:todo_list_mutations)
import_fields(:todo_mutations)
import_fields(:conversation_mutations)
import_fields(:discussion_mutations)
import_fields(:resource_mutations)
import_fields(:post_mutations)
end
@desc """
@@ -179,5 +182,6 @@ defmodule Mobilizon.GraphQL.Schema do
"""
subscription do
import_fields(:person_subscriptions)
import_fields(:discussion_subscriptions)
end
end

View File

@@ -5,7 +5,7 @@ defmodule Mobilizon.GraphQL.Schema.Actors.GroupType do
use Absinthe.Schema.Notation
alias Mobilizon.GraphQL.Resolvers.{Conversation, Group, Member, Resource, Todos}
alias Mobilizon.GraphQL.Resolvers.{Discussion, Group, Member, Post, Resource, Todos}
alias Mobilizon.GraphQL.Schema
import_types(Schema.Actors.MemberType)
@@ -46,9 +46,9 @@ defmodule Mobilizon.GraphQL.Schema.Actors.GroupType do
description("A list of the events this actor has organized")
end
field :conversations, :paginated_conversation_list do
resolve(&Conversation.find_conversations_for_actor/3)
description("A list of the conversations for this group")
field :discussions, :paginated_discussion_list do
resolve(&Discussion.find_discussions_for_actor/3)
description("A list of the discussions for this group")
end
field(:types, :group_type, description: "The type of group : Group, Community,…")
@@ -58,8 +58,11 @@ defmodule Mobilizon.GraphQL.Schema.Actors.GroupType do
)
field :members, :paginated_member_list do
arg(:page, :integer, default_value: 1)
arg(:limit, :integer, default_value: 10)
arg(:roles, :string, default_value: "")
resolve(&Member.find_members_for_group/3)
description("List of group members")
description("A paginated list of group members")
end
field :resources, :paginated_resource_list do
@@ -69,6 +72,13 @@ defmodule Mobilizon.GraphQL.Schema.Actors.GroupType do
description("A paginated list of the resources this group has")
end
field :posts, :paginated_post_list do
arg(:page, :integer, default_value: 1)
arg(:limit, :integer, default_value: 10)
resolve(&Post.find_posts_for_group/3)
description("A paginated list of the posts this group has")
end
field :todo_lists, :paginated_todo_list_list do
resolve(&Todos.find_todo_lists_for_group/3)
description("A paginated list of the todo lists this group has")
@@ -99,6 +109,12 @@ defmodule Mobilizon.GraphQL.Schema.Actors.GroupType do
field(:total, :integer, description: "The total number of elements in the list")
end
@desc "The list of visibility options for a group"
enum :group_visibility do
value(:public, description: "Publicly listed and federated")
value(:unlisted, description: "Visible only to people with the link - or invited")
end
object :group_queries do
@desc "Get all groups"
field :groups, :paginated_group_list do
@@ -124,6 +140,11 @@ defmodule Mobilizon.GraphQL.Schema.Actors.GroupType do
arg(:name, :string, description: "The displayed name for the group")
arg(:summary, :string, description: "The summary for the group", default_value: "")
arg(:visibility, :group_visibility,
description: "The visibility for the group",
default_value: :public
)
arg(:avatar, :picture_input,
description:
"The avatar for the group, either as an object or directly the ID of an existing Picture"
@@ -137,6 +158,26 @@ defmodule Mobilizon.GraphQL.Schema.Actors.GroupType do
resolve(&Group.create_group/3)
end
@desc "Update a group"
field :update_group, :group do
arg(:id, non_null(:id), description: "The group ID")
arg(:name, :string, description: "The displayed name for the group")
arg(:summary, :string, description: "The summary for the group", default_value: "")
arg(:avatar, :picture_input,
description:
"The avatar for the group, either as an object or directly the ID of an existing Picture"
)
arg(:banner, :picture_input,
description:
"The banner for the group, either as an object or directly the ID of an existing Picture"
)
resolve(&Group.update_group/3)
end
@desc "Delete a group"
field :delete_group, :deleted_object do
arg(:group_id, non_null(:id))

View File

@@ -15,6 +15,7 @@ defmodule Mobilizon.GraphQL.Schema.Actors.MemberType do
field(:actor, :person, description: "Which profile is member of")
field(:role, :member_role_enum, description: "The role of this membership")
field(:invited_by, :person, description: "Who invited this member")
field(:inserted_at, :naive_datetime, description: "When was this member created")
end
enum :member_role_enum do

View File

@@ -6,7 +6,7 @@ defmodule Mobilizon.GraphQL.Schema.AdminType do
use Absinthe.Schema.Notation
alias Mobilizon.Actors.Actor
alias Mobilizon.Conversations.Comment
alias Mobilizon.Discussions.Comment
alias Mobilizon.Events.Event
alias Mobilizon.Reports.{Note, Report}
alias Mobilizon.Users.User

View File

@@ -1,74 +0,0 @@
defmodule Mobilizon.GraphQL.Schema.Conversations.ConversationType do
@moduledoc """
Schema representation for Conversation
"""
use Absinthe.Schema.Notation
import Absinthe.Resolution.Helpers, only: [dataloader: 1]
alias Mobilizon.Actors
alias Mobilizon.GraphQL.Resolvers.Conversation
@desc "A conversation"
object :conversation do
field(:id, :id, description: "Internal ID for this conversation")
field(:title, :string)
field(:slug, :string)
field(:last_comment, :comment)
field :comments, :paginated_comment_list do
arg(:page, :integer, default_value: 1)
arg(:limit, :integer, default_value: 10)
resolve(&Conversation.get_comments_for_conversation/3)
description("The comments for the conversation")
end
field(:creator, :person, resolve: dataloader(Actors))
field(:actor, :actor, resolve: dataloader(Actors))
field(:inserted_at, :datetime)
field(:updated_at, :datetime)
end
object :paginated_conversation_list do
field(:elements, list_of(:conversation), description: "A list of conversation")
field(:total, :integer, description: "The total number of comments in the list")
end
object :conversation_queries do
@desc "Get a conversation"
field :conversation, type: :conversation do
arg(:id, non_null(:id))
resolve(&Conversation.get_conversation/3)
end
end
object :conversation_mutations do
@desc "Create a conversation"
field :create_conversation, type: :conversation do
arg(:title, non_null(:string))
arg(:text, non_null(:string))
arg(:actor_id, non_null(:id))
arg(:creator_id, non_null(:id))
resolve(&Conversation.create_conversation/3)
end
field :reply_to_conversation, type: :conversation do
arg(:conversation_id, non_null(:id))
arg(:text, non_null(:string))
resolve(&Conversation.reply_to_conversation/3)
end
field :update_conversation, type: :conversation do
arg(:title, non_null(:string))
arg(:conversation_id, non_null(:id))
resolve(&Conversation.update_conversation/3)
end
field :delete_conversation, type: :conversation do
arg(:conversation_id, non_null(:id))
# resolve(&Conversation.delete_conversation/3)
end
end
end

View File

@@ -1,4 +1,4 @@
defmodule Mobilizon.GraphQL.Schema.Conversations.CommentType do
defmodule Mobilizon.GraphQL.Schema.Discussions.CommentType do
@moduledoc """
Schema representation for Comment
"""
@@ -6,7 +6,7 @@ defmodule Mobilizon.GraphQL.Schema.Conversations.CommentType do
import Absinthe.Resolution.Helpers, only: [dataloader: 1]
alias Mobilizon.{Actors, Conversations}
alias Mobilizon.{Actors, Discussions}
alias Mobilizon.GraphQL.Resolvers.Comment
@desc "A comment"
@@ -21,13 +21,13 @@ defmodule Mobilizon.GraphQL.Schema.Conversations.CommentType do
field(:primaryLanguage, :string)
field(:replies, list_of(:comment)) do
resolve(dataloader(Conversations))
resolve(dataloader(Discussions))
end
field(:total_replies, :integer)
field(:in_reply_to_comment, :comment, resolve: dataloader(Conversations))
field(:in_reply_to_comment, :comment, resolve: dataloader(Discussions))
field(:event, :event, resolve: dataloader(Events))
field(:origin_comment, :comment, resolve: dataloader(Conversations))
field(:origin_comment, :comment, resolve: dataloader(Discussions))
field(:threadLanguages, non_null(list_of(:string)))
field(:actor, :person, resolve: dataloader(Actors))
field(:inserted_at, :datetime)

View File

@@ -0,0 +1,85 @@
defmodule Mobilizon.GraphQL.Schema.Discussions.DiscussionType do
@moduledoc """
Schema representation for discussion
"""
use Absinthe.Schema.Notation
import Absinthe.Resolution.Helpers, only: [dataloader: 1]
alias Mobilizon.Actors
alias Mobilizon.GraphQL.Resolvers.Discussion
@desc "A discussion"
object :discussion do
field(:id, :id, description: "Internal ID for this discussion")
field(:title, :string)
field(:slug, :string)
field(:last_comment, :comment)
field :comments, :paginated_comment_list do
arg(:page, :integer, default_value: 1)
arg(:limit, :integer, default_value: 10)
resolve(&Discussion.get_comments_for_discussion/3)
description("The comments for the discussion")
end
field(:creator, :person, resolve: dataloader(Actors))
field(:actor, :actor, resolve: dataloader(Actors))
field(:inserted_at, :datetime)
field(:updated_at, :datetime)
end
object :paginated_discussion_list do
field(:elements, list_of(:discussion), description: "A list of discussion")
field(:total, :integer, description: "The total number of comments in the list")
end
object :discussion_queries do
@desc "Get a discussion"
field :discussion, type: :discussion do
arg(:id, :id)
arg(:slug, :string)
resolve(&Discussion.get_discussion/3)
end
end
object :discussion_mutations do
@desc "Create a discussion"
field :create_discussion, type: :discussion do
arg(:title, non_null(:string))
arg(:text, non_null(:string))
arg(:actor_id, non_null(:id))
arg(:creator_id, non_null(:id))
resolve(&Discussion.create_discussion/3)
end
field :reply_to_discussion, type: :discussion do
arg(:discussion_id, non_null(:id))
arg(:text, non_null(:string))
resolve(&Discussion.reply_to_discussion/3)
end
field :update_discussion, type: :discussion do
arg(:title, non_null(:string))
arg(:discussion_id, non_null(:id))
resolve(&Discussion.update_discussion/3)
end
field :delete_discussion, type: :discussion do
arg(:discussion_id, non_null(:id))
resolve(&Discussion.delete_discussion/3)
end
end
object :discussion_subscriptions do
field :discussion_comment_changed, :discussion do
arg(:slug, non_null(:string))
config(fn args, _ ->
{:ok, topic: args.slug}
end)
end
end
end

View File

@@ -8,7 +8,7 @@ defmodule Mobilizon.GraphQL.Schema.EventType do
import Absinthe.Resolution.Helpers, only: [dataloader: 1]
import Mobilizon.GraphQL.Helpers.Error
alias Mobilizon.{Actors, Addresses, Conversations}
alias Mobilizon.{Actors, Addresses, Discussions}
alias Mobilizon.GraphQL.Resolvers.{Event, Picture, Tag}
alias Mobilizon.GraphQL.Schema
@@ -82,7 +82,7 @@ defmodule Mobilizon.GraphQL.Schema.EventType do
)
field(:comments, list_of(:comment), description: "The comments in reply to the event") do
resolve(dataloader(Conversations))
resolve(dataloader(Discussions))
end
# field(:tracks, list_of(:track))

View File

@@ -0,0 +1,91 @@
defmodule Mobilizon.GraphQL.Schema.PostType do
@moduledoc """
Schema representation for Posts
"""
use Absinthe.Schema.Notation
alias Mobilizon.GraphQL.Resolvers.{Post, Tag}
@desc "A post"
object :post do
field(:id, :id, description: "The post's ID")
field(:title, :string, description: "The post's title")
field(:slug, :string, description: "The post's slug")
field(:body, :string, description: "The post's body, as HTML")
field(:url, :string, description: "The post's URL")
field(:draft, :boolean, description: "Whether the post is a draft")
field(:author, :actor, description: "The post's author")
field(:attributed_to, :actor, description: "The post's group")
field(:visibility, :post_visibility, description: "The post's visibility")
field(:publish_at, :datetime, description: "When the post was published")
field(:inserted_at, :naive_datetime, description: "The post's creation date")
field(:updated_at, :naive_datetime, description: "The post's last update date")
field(:tags, list_of(:tag),
resolve: &Tag.list_tags_for_post/3,
description: "The post's tags"
)
end
object :paginated_post_list do
field(:elements, list_of(:post), description: "A list of posts")
field(:total, :integer, description: "The total number of posts in the list")
end
@desc "The list of visibility options for a post"
enum :post_visibility do
value(:public, description: "Publicly listed and federated. Can be shared.")
value(:unlisted, description: "Visible only to people with the link")
# value(:restricted, description: "Visible only after a moderator accepted")
value(:private,
description: "Visible only to people members of the group or followers of the person"
)
end
object :post_queries do
@desc "Get a post"
field :post, :post do
arg(:slug, non_null(:string))
resolve(&Post.get_post/3)
end
end
object :post_mutations do
@desc "Create a post"
field :create_post, :post do
arg(:attributed_to_id, non_null(:id))
arg(:title, non_null(:string))
arg(:body, :string)
arg(:draft, :boolean, default_value: false)
arg(:visibility, :post_visibility)
arg(:publish_at, :datetime)
arg(:tags, list_of(:string),
default_value: [],
description: "The list of tags associated to the post"
)
resolve(&Post.create_post/3)
end
@desc "Update a post"
field :update_post, :post do
arg(:id, non_null(:id))
arg(:title, :string)
arg(:body, :string)
arg(:attributed_to_id, :id)
arg(:draft, :boolean)
arg(:visibility, :post_visibility)
arg(:publish_at, :datetime)
arg(:tags, list_of(:string), description: "The list of tags associated to the post")
resolve(&Post.update_post/3)
end
@desc "Delete a post"
field :delete_post, :deleted_object do
arg(:id, non_null(:id))
resolve(&Post.delete_post/3)
end
end
end