Introduce group basic federation, event new page and notifications

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
Thomas Citharel
2020-02-18 08:57:00 +01:00
parent 300ef8f245
commit 4144e9ffd0
416 changed files with 32220 additions and 16750 deletions

View File

@@ -3,7 +3,7 @@ defmodule Mobilizon.GraphQL.API.Comments do
API for Comments.
"""
alias Mobilizon.Events.Comment
alias Mobilizon.Conversations.Comment
alias Mobilizon.Federation.ActivityPub
alias Mobilizon.Federation.ActivityPub.Activity
@@ -18,6 +18,10 @@ defmodule Mobilizon.GraphQL.API.Comments do
ActivityPub.create(:comment, args, true)
end
def update_comment(%Comment{} = comment, args) do
ActivityPub.update(:comment, comment, args, true)
end
@doc """
Deletes a comment

View File

@@ -9,8 +9,8 @@ defmodule Mobilizon.GraphQL.Resolvers.Admin do
alias Mobilizon.Actors.Actor
alias Mobilizon.Admin.{ActionLog, Setting}
alias Mobilizon.Config
alias Mobilizon.Events.{Comment, Event}
alias Mobilizon.Events.{Comment, Event}
alias Mobilizon.Conversations.Comment
alias Mobilizon.Events.Event
alias Mobilizon.Federation.ActivityPub.Relay
alias Mobilizon.Reports.{Note, Report}
alias Mobilizon.Service.Statistics

View File

@@ -3,10 +3,10 @@ defmodule Mobilizon.GraphQL.Resolvers.Comment do
Handles the comment-related GraphQL calls.
"""
alias Mobilizon.{Actors, Admin, Events}
alias Mobilizon.{Actors, Admin, Conversations}
alias Mobilizon.Actors.Actor
alias Mobilizon.Events
alias Mobilizon.Events.Comment, as: CommentModel
alias Mobilizon.Conversations.Comment, as: CommentModel
alias Mobilizon.Users
alias Mobilizon.Users.User
alias Mobilizon.GraphQL.API.Comments
@@ -14,13 +14,17 @@ defmodule Mobilizon.GraphQL.Resolvers.Comment do
require Logger
def get_thread(_parent, %{id: thread_id}, _context) do
{:ok, Events.get_thread_replies(thread_id)}
{:ok, Conversations.get_thread_replies(thread_id)}
end
def create_comment(
_parent,
%{actor_id: actor_id} = args,
%{context: %{current_user: %User{} = user}}
%{
context: %{
current_user: %User{} = user
}
}
) do
with {:is_owned, %Actor{} = _organizer_actor} <- User.owns_actor(user, actor_id),
{:ok, _, %CommentModel{} = comment} <-
@@ -36,14 +40,40 @@ defmodule Mobilizon.GraphQL.Resolvers.Comment do
{:error, "You are not allowed to create a comment if not connected"}
end
def update_comment(
_parent,
%{text: text, comment_id: comment_id},
%{
context: %{
current_user: %User{} = user
}
}
) 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),
true <- actor_id === comment_actor_id,
{:ok, _, %CommentModel{} = comment} <- Comments.update_comment(comment, %{text: text}) do
{:ok, comment}
end
end
def edit_comment(_parent, _args, _context) do
{:error, "You are not allowed to update a comment if not connected"}
end
def delete_comment(
_parent,
%{actor_id: actor_id, comment_id: comment_id},
%{context: %{current_user: %User{role: role} = user}}
%{
context: %{
current_user: %User{role: role} = user
}
}
) do
with {actor_id, ""} <- Integer.parse(actor_id),
{:is_owned, %Actor{} = _organizer_actor} <- User.owns_actor(user, actor_id),
%CommentModel{} = comment <- Events.get_comment_with_preload(comment_id) do
%CommentModel{} = comment <- Conversations.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)

View File

@@ -100,7 +100,9 @@ defmodule Mobilizon.GraphQL.Resolvers.Config do
endpoint: Config.instance_maps_tiles_endpoint(),
attribution: Config.instance_maps_tiles_attribution()
}
}
},
resource_providers: Config.instance_resource_providers(),
timezones: Tzdata.zone_list()
}
end
end

View File

@@ -0,0 +1,110 @@
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

@@ -3,23 +3,50 @@ defmodule Mobilizon.GraphQL.Resolvers.Group do
Handles the group-related GraphQL calls.
"""
alias Mobilizon.Actors
alias Mobilizon.{Actors, Events, Users}
alias Mobilizon.Actors.{Actor, Member}
alias Mobilizon.Users.User
alias Mobilizon.Federation.ActivityPub
alias Mobilizon.GraphQL.API
alias Mobilizon.GraphQL.Resolvers.Person
alias Mobilizon.Federation.ActivityPub
alias Mobilizon.Storage.Page
alias Mobilizon.Users.User
require Logger
@doc """
Find a group
"""
def find_group(
parent,
%{preferred_username: name} = args,
%{
context: %{
current_user: %User{} = user
}
}
) do
with {:ok, %Actor{id: group_id} = group} <-
ActivityPub.find_or_make_group_from_nickname(name),
{:actor, %Actor{id: actor_id} = _actor} <- {:actor, Users.get_actor_for_user(user)},
{:member, true} <- {:member, Actors.is_member?(actor_id, group_id)},
group <- Person.proxify_pictures(group) do
{:ok, group}
else
{:member, false} ->
find_group(parent, args, nil)
_ ->
{:error, "Group with name #{name} not found"}
end
end
@doc """
Find a group
"""
def find_group(_parent, %{preferred_username: name}, _resolution) do
with {:ok, actor} <- ActivityPub.find_or_make_group_from_nickname(name),
actor <- Person.proxify_pictures(actor) do
%Actor{} = actor <- Person.proxify_pictures(actor),
%Actor{} = actor <- restrict_fields_for_non_member_request(actor) do
{:ok, actor}
else
_ ->
@@ -31,18 +58,21 @@ defmodule Mobilizon.GraphQL.Resolvers.Group do
Lists all groups
"""
def list_groups(_parent, %{page: page, limit: limit}, _resolution) do
{
:ok,
page
|> Actors.list_groups(limit)
|> Enum.map(fn actor -> Person.proxify_pictures(actor) end)
}
{:ok, Actors.list_groups(page, limit)}
end
@doc """
Create a new group. The creator is automatically added as admin
"""
def create_group(_parent, args, %{context: %{current_user: user}}) do
def create_group(
_parent,
args,
%{
context: %{
current_user: user
}
}
) do
with creator_actor_id <- Map.get(args, :creator_actor_id),
{:is_owned, %Actor{} = creator_actor} <- User.owns_actor(user, creator_actor_id),
args <- Map.put(args, :creator_actor, creator_actor),
@@ -68,14 +98,18 @@ defmodule Mobilizon.GraphQL.Resolvers.Group do
def delete_group(
_parent,
%{group_id: group_id, actor_id: actor_id},
%{context: %{current_user: user}}
%{
context: %{
current_user: user
}
}
) do
with {actor_id, ""} <- Integer.parse(actor_id),
{group_id, ""} <- Integer.parse(group_id),
{:ok, %Actor{} = group} <- Actors.get_group_by_actor_id(group_id),
{:is_owned, %Actor{}} <- User.owns_actor(user, actor_id),
{:ok, %Member{} = member} <- Actors.get_member(actor_id, group.id),
{:is_admin, true} <- Member.is_administrator(member),
{:is_admin, true} <- {:is_admin, Member.is_administrator(member)},
group <- Actors.delete_group!(group) do
{:ok, %{id: group.id}}
else
@@ -103,7 +137,11 @@ defmodule Mobilizon.GraphQL.Resolvers.Group do
def join_group(
_parent,
%{group_id: group_id, actor_id: actor_id},
%{context: %{current_user: user}}
%{
context: %{
current_user: user
}
}
) do
with {actor_id, ""} <- Integer.parse(actor_id),
{group_id, ""} <- Integer.parse(group_id),
@@ -146,7 +184,11 @@ defmodule Mobilizon.GraphQL.Resolvers.Group do
def leave_group(
_parent,
%{group_id: group_id, actor_id: actor_id},
%{context: %{current_user: user}}
%{
context: %{
current_user: user
}
}
) do
with {actor_id, ""} <- Integer.parse(actor_id),
{group_id, ""} <- Integer.parse(group_id),
@@ -156,7 +198,17 @@ defmodule Mobilizon.GraphQL.Resolvers.Group do
{:only_administrator, check_that_member_is_not_last_administrator(group_id, actor_id)},
{:ok, _} <-
Mobilizon.Actors.delete_member(member) do
{:ok, %{parent: %{id: group_id}, actor: %{id: actor_id}}}
{
:ok,
%{
parent: %{
id: group_id
},
actor: %{
id: actor_id
}
}
}
else
{:is_owned, nil} ->
{:error, "Actor id is not owned by authenticated user"}
@@ -173,17 +225,65 @@ defmodule Mobilizon.GraphQL.Resolvers.Group do
{:error, "You need to be logged-in to leave a group"}
end
def find_events_for_group(
%Actor{id: group_id} = group,
_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
# TODO : Handle public / restricted to group members events
{:ok, Events.list_organized_events_for_group(group)}
else
{:member, false} ->
{:ok, %Page{total: 0, elements: []}}
end
end
def find_events_for_group(_parent, _args, _resolution) do
{:ok, %Page{total: 0, elements: []}}
end
# We check that the actor asking to leave the group is not it's only administrator
# We start by fetching the list of administrator or creators and if there's only one of them
# and that it's the actor requesting leaving the group we return true
@spec check_that_member_is_not_last_administrator(integer, integer) :: boolean
defp check_that_member_is_not_last_administrator(group_id, actor_id) do
case Actors.list_administrator_members_for_group(group_id) do
[%Member{actor: %Actor{id: member_actor_id}}] ->
%Page{total: total} when total > 1 ->
true
%Page{
total: 1,
elements: [
%Member{
actor: %Actor{
id: member_actor_id
}
}
]
} ->
actor_id == member_actor_id
_ ->
false
end
end
defp restrict_fields_for_non_member_request(%Actor{} = group) do
Map.merge(
group,
%{
followers: [],
followings: [],
organized_events: [],
comments: [],
feed_tokens: []
}
)
end
end

View File

@@ -3,14 +3,92 @@ defmodule Mobilizon.GraphQL.Resolvers.Member do
Handles the member-related GraphQL calls
"""
alias Mobilizon.Actors
alias Mobilizon.Actors.{Actor}
alias Mobilizon.{Actors, Users}
alias Mobilizon.Actors.{Actor, Member}
alias Mobilizon.Federation.ActivityPub
alias Mobilizon.Storage.Page
alias Mobilizon.Users.User
@doc """
Find members for group
Find members for group.
If actor requesting is not part of the group, we only return the number of members, not members
"""
def find_members_for_group(%Actor{} = actor, _args, _resolution) do
members = Actors.list_members_for_group(actor)
{:ok, members}
def find_members_for_group(
%Actor{id: group_id} = group,
_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 <- Actors.list_members_for_group(group) do
{:ok, page}
else
{:member, false} ->
# Actor is not member of group, fallback to public
with %Page{} = page <- Actors.list_members_for_group(group) do
{:ok, %Page{page | elements: []}}
end
end
end
def find_members_for_group(%Actor{} = group, _args, _resolution) do
with %Page{} = page <- Actors.list_members_for_group(group) do
{:ok, %Page{page | elements: []}}
end
end
def invite_member(
_parent,
%{group_id: group_id, target_actor_username: target_actor_username},
%{context: %{current_user: %User{} = user}}
) do
with %Actor{id: actor_id} = actor <- Users.get_actor_for_user(user),
{:ok, %Actor{} = group} <- Actors.get_group_by_actor_id(group_id),
{:has_rights_to_invite, {:ok, %Member{role: role}}}
when role in [:moderator, :administrator, :creator] <-
{:has_rights_to_invite, Actors.get_member(actor_id, group_id)},
{:target_actor_username, {:ok, %Actor{id: target_actor_id} = target_actor}} <-
{:target_actor_username,
ActivityPub.find_or_make_actor_from_nickname(target_actor_username)},
{:error, :member_not_found} <- Actors.get_member(target_actor_id, group.id),
{:ok, _activity, %Member{} = member} <- ActivityPub.invite(group, actor, target_actor) do
{:ok, member}
else
{:is_owned, nil} ->
{:error, "Actor id is not owned by authenticated user"}
{:error, :group_not_found} ->
{:error, "Group id not found"}
{:target_actor_username, _} ->
{:error, "Actor invited doesn't exist"}
{:has_rights_to_invite, {:error, :member_not_found}} ->
{:error, "You are not a member of this group"}
{:has_rights_to_invite, _} ->
{:error, "You cannot invite to this group"}
{:ok, %Member{}} ->
{:error, "Actor is already a member of this group"}
end
end
def accept_invitation(_parent, %{id: member_id}, %{context: %{current_user: %User{} = user}}) do
with %Actor{id: actor_id} <- Users.get_actor_for_user(user),
%Member{actor: %Actor{id: member_actor_id}} = member <- Actors.get_member(member_id),
{:is_same_actor, true} <- {:is_same_actor, member_actor_id === actor_id},
{:ok, _activity, %Member{} = member} <-
ActivityPub.accept(
:invite,
member,
true
) do
# Launch an async task to refresh the group profile, fetch resources, discussions, members
{:ok, member}
end
end
end

View File

@@ -105,7 +105,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Participant do
@spec do_actor_join_event(Actor.t(), integer | String.t(), map()) ::
{:ok, Participant.t()} | {:error, String.t()}
defp do_actor_join_event(actor, event_id, args \\ %{}) do
defp do_actor_join_event(actor, event_id, args) do
with {:has_event, {:ok, %Event{} = event}} <-
{:has_event, Events.get_event_with_preload(event_id)},
{:ok, _activity, participant} <- Participations.join(event, actor, args),

View File

@@ -224,6 +224,19 @@ defmodule Mobilizon.GraphQL.Resolvers.Person do
end
end
@doc """
Returns the list of events this person is going to
"""
def person_memberships(%Actor{id: actor_id}, _args, %{context: %{current_user: user}}) do
with {:is_owned, %Actor{} = actor} <- User.owns_actor(user, actor_id),
participations <- Actors.list_members_for_actor(actor) do
{:ok, participations}
else
{:is_owned, nil} ->
{:error, "Actor id is not owned by authenticated user"}
end
end
def proxify_pictures(%Actor{} = actor) do
actor
|> proxify_avatar

View File

@@ -0,0 +1,230 @@
defmodule Mobilizon.GraphQL.Resolvers.Resource do
@moduledoc """
Handles the resources-related GraphQL calls
"""
alias Mobilizon.{Actors, Resources, Users}
alias Mobilizon.Actors.Actor
alias Mobilizon.Federation.ActivityPub
alias Mobilizon.Resources.Resource
alias Mobilizon.Resources.Resource.Metadata
alias Mobilizon.Service.RichMedia.Parser
alias Mobilizon.Storage.Page
alias Mobilizon.Users.User
require Logger
@doc """
Find resources for group.
Returns only if actor requesting is a member of the group
"""
def find_resources_for_group(
%Actor{id: group_id} = group,
%{page: page, limit: limit},
%{
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 <- Resources.get_resources_for_group(group, page, limit) do
{:ok, page}
else
{:member, _} ->
find_resources_for_group(nil, nil, nil)
end
end
def find_resources_for_group(
_group,
_args,
_resolution
) do
{:ok, %Page{total: 0, elements: []}}
end
def find_resources_for_parent(
%Resource{actor_id: group_id} = parent,
_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 <- Resources.get_resources_for_folder(parent) do
{:ok, page}
end
end
def find_resources_for_parent(_parent, _args, _resolution),
do: {:ok, %Page{total: 0, elements: []}}
def get_resource(
_parent,
%{path: path, username: username},
%{
context: %{
current_user: %User{} = user
}
} = _resolution
) do
with {:current_actor, %Actor{id: actor_id}} <-
{:current_actor, Users.get_actor_for_user(user)},
{:group, %Actor{id: group_id}} <- {:group, Actors.get_actor_by_name(username, :Group)},
{:member, true} <- {:member, Actors.is_member?(actor_id, group_id)},
{:resource, %Resource{} = resource} <-
{:resource, Resources.get_resource_by_group_and_path_with_preloads(group_id, path)} do
{:ok, resource}
else
{:member, false} -> {:error, "Actor is not member of group"}
{:resource, _} -> {:error, "No such resource"}
end
end
def get_resource(_parent, _args, _resolution) do
{:error, "You need to be logged-in to access resources"}
end
def create_resource(
_parent,
%{actor_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)},
parent <- get_eventual_parent(args),
{:own_check, true} <- {:own_check, check_resource_owned_by_group(parent, group_id)},
{:ok, _, %Resource{} = resource} <-
ActivityPub.create(
:resource,
args
|> Map.put(:actor_id, group_id)
|> Map.put(:creator_id, actor_id),
true,
%{}
) do
{:ok, resource}
else
{:own_check, _} ->
{:error, "Parent resource doesn't match this group"}
{:member, _} ->
{:error, "Actor id is not member of group"}
end
end
def create_resource(_parent, _args, _resolution) do
{:error, "You need to be logged-in to create resources"}
end
def update_resource(
_parent,
%{id: resource_id} = args,
%{
context: %{
current_user: %User{} = user
}
} = _resolution
) do
with %Actor{id: actor_id} <- Users.get_actor_for_user(user),
{:resource, %Resource{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.update(:resource, resource, args, true, %{}) do
{:ok, resource}
else
{:resource, _} ->
{:error, "Resource doesn't exist"}
{:member, _} ->
{:error, "Actor id is not member of group"}
end
end
def update_resource(_parent, _args, _resolution) do
{:error, "You need to be logged-in to update resources"}
end
def delete_resource(
_parent,
%{id: resource_id},
%{
context: %{
current_user: %User{} = user
}
} = _resolution
) do
with %Actor{id: actor_id} <- 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
{:ok, resource}
else
{:resource, _} ->
{:error, "Resource doesn't exist"}
{:member, _} ->
{:error, "Actor id is not member of group"}
end
end
def delete_resource(_parent, _args, _resolution) do
{:error, "You need to be logged-in to delete resources"}
end
def preview_resource_link(
_parent,
%{resource_url: resource_url},
%{
context: %{
current_user: %User{} = _user
}
} = _resolution
) do
with {:ok, data} when is_map(data) <- Parser.parse(resource_url) do
{:ok, struct(Metadata, data)}
end
end
def preview_resource_link(_parent, _args, _resolution) do
{:error, "You need to be logged-in to view a resource preview"}
end
@spec get_eventual_parent(map()) :: Resource.t() | nil
defp get_eventual_parent(args) do
parent = args |> Map.get(:parent_id) |> get_parent_resource()
case parent do
%Resource{} -> parent
_ -> nil
end
end
@spec get_parent_resource(integer | nil) :: nil | Resource.t()
defp get_parent_resource(nil), do: nil
defp get_parent_resource(parent_id), do: Resources.get_resource(parent_id)
@spec check_resource_owned_by_group(Resource.t() | nil, integer) :: boolean
defp check_resource_owned_by_group(nil, _group_id), do: true
defp check_resource_owned_by_group(%Resource{actor_id: actor_id}, group_id)
when is_binary(group_id),
do: actor_id == String.to_integer(group_id)
defp check_resource_owned_by_group(%Resource{actor_id: actor_id}, group_id)
when is_number(group_id),
do: actor_id == group_id
end

View File

@@ -0,0 +1,255 @@
defmodule Mobilizon.GraphQL.Resolvers.Todos do
@moduledoc """
Handles the todos related GraphQL calls
"""
alias Mobilizon.{Actors, Todos, Users}
alias Mobilizon.Actors.Actor
alias Mobilizon.Federation.ActivityPub
alias Mobilizon.Storage.Page
alias Mobilizon.Todos.{Todo, TodoList}
alias Mobilizon.Users.User
require Logger
@doc """
Find todo lists for group.
Returns only if actor requesting is a member of the group
"""
def find_todo_lists_for_group(
%Actor{id: group_id} = group,
_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 <- Todos.get_todo_lists_for_group(group) do
{:ok, page}
else
{:member, _} ->
with %Page{} = page <- Todos.get_todo_lists_for_group(group) do
{:ok, %Page{page | elements: []}}
end
end
end
def find_todo_lists_for_group(_parent, _args, _resolution) do
{:ok, %Page{total: 0, elements: []}}
end
def find_todos_for_todo_list(
%TodoList{actor_id: group_id} = todo_list,
_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 <- Todos.get_todos_for_todo_list(todo_list) do
{:ok, page}
else
{:is_owned, nil} ->
{:error, "Actor id is not owned by authenticated user"}
{:member, _} ->
{:error, "Actor id is not member of group"}
end
end
def get_todo_list(
_parent,
%{id: todo_list_id},
%{
context: %{current_user: %User{} = user}
} = _resolution
) do
with {:actor, %Actor{id: actor_id}} <- {:actor, Users.get_actor_for_user(user)},
{:todo, %TodoList{actor_id: group_id} = todo} <-
{:todo, Todos.get_todo_list(todo_list_id)},
{:member, true} <- {:member, Actors.is_member?(actor_id, group_id)} do
{:ok, todo}
else
{:todo, nil} ->
{:error, "Todo list doesn't exist"}
{:actor, nil} ->
{:error, "No actor found for user"}
{:member, _} ->
{:error, "Actor id is not member of group"}
end
end
def create_todo_list(
_parent,
%{group_id: group_id} = args,
%{
context: %{current_user: %User{} = user}
} = _resolution
) 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)},
{:ok, _, %TodoList{} = todo_list} <-
ActivityPub.create(:todo_list, Map.put(args, :actor_id, group_id), true, %{}) do
{:ok, todo_list}
else
{:member, _} ->
{:error, "Actor id is not member of group"}
end
end
# def update_todo_list(
# _parent,
# %{id: todo_list_id, actor_id: actor_id},
# %{
# context: %{current_user: %User{} = user}
# } = _resolution
# ) do
# with {:is_owned, %Actor{} = actor} <- User.owns_actor(user, actor_id),
# {:todo_list, %TodoList{actor_id: group_id} = todo_list} <-
# {:todo_list, Todos.get_todo_list(todo_list_id)},
# {:member, true} <- {:member, Actors.is_member?(actor_id, group_id)},
# {:ok, _, %TodoList{} = todo} <-
# ActivityPub.update_todo_list(todo_list, actor, true, %{}) do
# {:ok, todo}
# else
# {:todo_list, _} ->
# {:error, "TodoList doesn't exist"}
# {:member, _} ->
# {:error, "Actor id is not member of group"}
# end
# end
# def delete_todo_list(
# _parent,
# %{id: todo_list_id, actor_id: actor_id},
# %{
# context: %{current_user: %User{} = user}
# } = _resolution
# ) do
# with {:is_owned, %Actor{} = actor} <- User.owns_actor(user, actor_id),
# {:todo_list, %TodoList{actor_id: group_id} = todo_list} <-
# {:todo_list, Todos.get_todo_list(todo_list_id)},
# {:member, true} <- {:member, Actors.is_member?(actor_id, group_id)},
# {:ok, _, %TodoList{} = todo} <-
# ActivityPub.delete_todo_list(todo_list, actor, true, %{}) do
# {:ok, todo}
# else
# {:todo_list, _} ->
# {:error, "TodoList doesn't exist"}
# {:member, _} ->
# {:error, "Actor id is not member of group"}
# end
# end
def get_todo(
_parent,
%{id: todo_id},
%{
context: %{current_user: %User{} = user}
} = _resolution
) do
with {:actor, %Actor{id: actor_id}} <- {:actor, Users.get_actor_for_user(user)},
{:todo, %Todo{todo_list_id: todo_list_id} = todo} <-
{:todo, Todos.get_todo(todo_id)},
{:todo_list, %TodoList{actor_id: group_id}} <-
{:todo_list, Todos.get_todo_list(todo_list_id)},
{:member, true} <- {:member, Actors.is_member?(actor_id, group_id)} do
{:ok, todo}
else
{:todo, nil} ->
{:error, "Todo doesn't exist"}
{:actor, nil} ->
{:error, "No actor found for user"}
{:member, _} ->
{:error, "Actor id is not member of group"}
end
end
def create_todo(
_parent,
%{todo_list_id: todo_list_id} = args,
%{
context: %{current_user: %User{} = user}
} = _resolution
) do
with {:actor, %Actor{id: actor_id} = _actor} <- {:actor, Users.get_actor_for_user(user)},
{:todo_list, %TodoList{actor_id: group_id} = _todo_list} <-
{:todo_list, Todos.get_todo_list(todo_list_id)},
{:member, true} <- {:member, Actors.is_member?(actor_id, group_id)},
{:ok, _, %Todo{} = todo} <-
ActivityPub.create(:todo, Map.put(args, :creator_id, actor_id), true, %{}) do
{:ok, todo}
else
{:todo_list, _} ->
{:error, "TodoList doesn't exist"}
{:member, _} ->
{:error, "Actor id is not member of group"}
end
end
def update_todo(
_parent,
%{id: todo_id} = args,
%{
context: %{current_user: %User{} = user}
} = _resolution
) do
with {:actor, %Actor{id: actor_id} = _actor} <- {:actor, Users.get_actor_for_user(user)},
{:todo, %Todo{todo_list_id: todo_list_id} = todo} <-
{:todo, Todos.get_todo(todo_id)},
{:todo_list, %TodoList{actor_id: group_id}} <-
{: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
{:ok, todo}
else
{:todo_list, _} ->
{:error, "TodoList doesn't exist"}
{:todo, _} ->
{:error, "Todo doesn't exist"}
{:member, _} ->
{:error, "Actor id is not member of group"}
end
end
# def delete_todo(
# _parent,
# %{id: todo_id, actor_id: actor_id},
# %{
# context: %{current_user: %User{} = user}
# } = _resolution
# ) do
# with {:is_owned, %Actor{} = actor} <- User.owns_actor(user, actor_id),
# {:todo, %Todo{todo_list_id: todo_list_id} = todo} <-
# {:todo, Todos.get_todo(todo_id)},
# {:todo_list, %TodoList{actor_id: group_id}} <-
# {:todo_list, Todos.get_todo_list(todo_list_id)},
# {:member, true} <- {:member, Actors.is_member?(actor_id, group_id)},
# {:ok, _, %Todo{} = todo} <-
# ActivityPub.delete_todo(todo, actor, true, %{}) do
# {:ok, todo}
# else
# {:todo_list, _} ->
# {:error, "TodoList doesn't exist"}
# {:todo, _} ->
# {:error, "Todo doesn't exist"}
# {:member, _} ->
# {:error, "Actor id is not member of group"}
# end
# end
end

View File

@@ -8,8 +8,8 @@ defmodule Mobilizon.GraphQL.Resolvers.User do
alias Mobilizon.{Actors, Config, Events, Users}
alias Mobilizon.Actors.Actor
alias Mobilizon.Crypto
alias Mobilizon.Storage.Repo
alias Mobilizon.Users.User
alias Mobilizon.Storage.{Page, Repo}
alias Mobilizon.Users.{Setting, User}
alias Mobilizon.Web.{Auth, Email}
@@ -245,7 +245,7 @@ defmodule Mobilizon.GraphQL.Resolvers.User do
%{context: %{current_user: %User{id: logged_user_id}}}
) do
with true <- user_id == logged_user_id,
participations <-
%Page{} = page <-
Events.list_participations_for_user(
user_id,
Map.get(args, :after_datetime),
@@ -253,7 +253,26 @@ defmodule Mobilizon.GraphQL.Resolvers.User do
Map.get(args, :page),
Map.get(args, :limit)
) do
{:ok, participations}
{:ok, page}
end
end
@doc """
Returns the list of groups this user is a member is a member of
"""
def user_memberships(
%User{id: user_id},
%{page: page, limit: limit} = _args,
%{context: %{current_user: %User{id: logged_user_id}}}
) do
with true <- user_id == logged_user_id,
memberships <-
Actors.list_memberships_for_user(
user_id,
page,
limit
) do
{:ok, memberships}
end
end
@@ -379,4 +398,42 @@ defmodule Mobilizon.GraphQL.Resolvers.User do
def delete_account(_parent, _args, _resolution) do
{:error, "You need to be logged-in to delete your account"}
end
@spec user_settings(User.t(), map(), map()) :: {:ok, list(Setting.t())} | {:error, String.t()}
def user_settings(%User{id: user_id} = user, _args, %{
context: %{current_user: %User{id: logged_user_id}}
}) do
with {:same_user, true} <- {:same_user, user_id == logged_user_id},
{:setting, settings} <- {:setting, Users.get_setting(user)} do
{:ok, settings}
else
{:same_user, _} ->
{:error, "User requested is not logged-in"}
end
end
@spec set_user_setting(map(), map(), map()) :: {:ok, Setting.t()} | {:error, any()}
def set_user_setting(_parent, attrs, %{
context: %{current_user: %User{id: logged_user_id}}
}) do
attrs = Map.put(attrs, :user_id, logged_user_id)
res =
case Users.get_setting(logged_user_id) do
nil ->
Users.create_setting(attrs)
%Setting{} = setting ->
Users.update_setting(setting, attrs)
end
case res do
{:ok, %Setting{} = setting} ->
{:ok, setting}
{:error, changeset} ->
Logger.debug(inspect(changeset))
{:error, "Error while saving user setting"}
end
end
end

View File

@@ -5,9 +5,10 @@ defmodule Mobilizon.GraphQL.Schema do
use Absinthe.Schema
alias Mobilizon.{Actors, Addresses, Events, Media, Reports, Users}
alias Mobilizon.{Actors, Addresses, Conversations, Events, Media, Reports, Todos, Users}
alias Mobilizon.Actors.{Actor, Follower, Member}
alias Mobilizon.Events.{Comment, Event, Participant}
alias Mobilizon.Conversations.Comment
alias Mobilizon.Events.{Event, Participant}
alias Mobilizon.GraphQL.Schema
alias Mobilizon.Storage.Repo
@@ -22,8 +23,12 @@ defmodule Mobilizon.GraphQL.Schema do
import_types(Schema.Actors.PersonType)
import_types(Schema.Actors.GroupType)
import_types(Schema.Actors.ApplicationType)
import_types(Schema.CommentType)
import_types(Schema.Conversations.CommentType)
import_types(Schema.Conversations.ConversationType)
import_types(Schema.SearchType)
import_types(Schema.ResourceType)
import_types(Schema.Todos.TodoListType)
import_types(Schema.Todos.TodoType)
import_types(Schema.ConfigType)
import_types(Schema.ReportType)
import_types(Schema.AdminType)
@@ -98,10 +103,12 @@ defmodule Mobilizon.GraphQL.Schema do
Dataloader.new()
|> Dataloader.add_source(Actors, default_source)
|> Dataloader.add_source(Users, default_source)
|> Dataloader.add_source(Events, Events.data())
|> Dataloader.add_source(Events, default_source)
|> Dataloader.add_source(Conversations, Conversations.data())
|> Dataloader.add_source(Addresses, default_source)
|> Dataloader.add_source(Media, default_source)
|> Dataloader.add_source(Reports, default_source)
|> Dataloader.add_source(Todos, default_source)
Map.put(ctx, :loader, loader)
end
@@ -126,6 +133,10 @@ defmodule Mobilizon.GraphQL.Schema do
import_fields(:picture_queries)
import_fields(:report_queries)
import_fields(:admin_queries)
import_fields(:todo_list_queries)
import_fields(:todo_queries)
import_fields(:conversation_queries)
import_fields(:resource_queries)
end
@desc """
@@ -143,6 +154,10 @@ defmodule Mobilizon.GraphQL.Schema do
import_fields(:picture_mutations)
import_fields(:report_mutations)
import_fields(:admin_mutations)
import_fields(:todo_list_mutations)
import_fields(:todo_mutations)
import_fields(:conversation_mutations)
import_fields(:resource_mutations)
end
@desc """

View File

@@ -5,10 +5,7 @@ defmodule Mobilizon.GraphQL.Schema.Actors.GroupType do
use Absinthe.Schema.Notation
import Absinthe.Resolution.Helpers, only: [dataloader: 1]
alias Mobilizon.Events
alias Mobilizon.GraphQL.Resolvers.{Group, Member}
alias Mobilizon.GraphQL.Resolvers.{Conversation, Group, Member, Resource, Todos}
alias Mobilizon.GraphQL.Schema
import_types(Schema.Actors.MemberType)
@@ -44,10 +41,15 @@ defmodule Mobilizon.GraphQL.Schema.Actors.GroupType do
field(:followingCount, :integer, description: "Number of actors following this actor")
# This one should have a privacy setting
field(:organized_events, list_of(:event),
resolve: dataloader(Events),
description: "A list of the events this actor has organized"
)
field :organized_events, :paginated_event_list do
resolve(&Group.find_events_for_group/3)
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")
end
field(:types, :group_type, description: "The type of group : Group, Community,…")
@@ -55,10 +57,22 @@ defmodule Mobilizon.GraphQL.Schema.Actors.GroupType do
description: "Whether the group is opened to all or has restricted access"
)
field(:members, non_null(list_of(:member)),
resolve: &Member.find_members_for_group/3,
description: "List of group members"
)
field :members, :paginated_member_list do
resolve(&Member.find_members_for_group/3)
description("List of group members")
end
field :resources, :paginated_resource_list do
arg(:page, :integer, default_value: 1)
arg(:limit, :integer, default_value: 10)
resolve(&Resource.find_resources_for_group/3)
description("A paginated list of the resources 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")
end
end
@desc """
@@ -80,9 +94,14 @@ defmodule Mobilizon.GraphQL.Schema.Actors.GroupType do
value(:open, description: "The actor is open to followings")
end
object :paginated_group_list do
field(:elements, list_of(:group), description: "A list of groups")
field(:total, :integer, description: "The total number of elements in the list")
end
object :group_queries do
@desc "Get all groups"
field :groups, list_of(:group) do
field :groups, :paginated_group_list do
arg(:page, :integer, default_value: 1)
arg(:limit, :integer, default_value: 10)
resolve(&Group.list_groups/3)

View File

@@ -4,15 +4,27 @@ defmodule Mobilizon.GraphQL.Schema.Actors.MemberType do
"""
use Absinthe.Schema.Notation
alias Mobilizon.GraphQL.Resolvers.Group
alias Mobilizon.GraphQL.Resolvers.{Group, Member}
@desc """
Represents a member of a group
"""
object :member do
field(:id, :id, description: "The member's ID")
field(:parent, :group, description: "Of which the profile is member")
field(:actor, :person, description: "Which profile is member of")
field(:role, :integer, description: "The role of this membership")
field(:role, :member_role_enum, description: "The role of this membership")
field(:invited_by, :person, description: "Who invited this member")
end
enum :member_role_enum do
value(:not_approved)
value(:invited)
value(:member)
value(:moderator)
value(:administrator)
value(:creator)
value(:rejected)
end
@desc "Represents a deleted member"
@@ -21,6 +33,11 @@ defmodule Mobilizon.GraphQL.Schema.Actors.MemberType do
field(:actor, :deleted_object)
end
object :paginated_member_list do
field(:elements, list_of(:member), description: "A list of members")
field(:total, :integer, description: "The total number of elements in the list")
end
object :member_mutations do
@desc "Join a group"
field :join_group, :member do
@@ -30,12 +47,27 @@ defmodule Mobilizon.GraphQL.Schema.Actors.MemberType do
resolve(&Group.join_group/3)
end
@desc "Leave an event"
@desc "Leave a group"
field :leave_group, :deleted_member do
arg(:group_id, non_null(:id))
arg(:actor_id, non_null(:id))
resolve(&Group.leave_group/3)
end
@desc "Invite an actor to join the group"
field :invite_member, :member do
arg(:group_id, non_null(:id))
arg(:target_actor_username, non_null(:string))
resolve(&Member.invite_member/3)
end
@desc "Accept an invitation to a group"
field :accept_invitation, :member do
arg(:id, non_null(:id))
resolve(&Member.accept_invitation/3)
end
end
end

View File

@@ -64,6 +64,13 @@ defmodule Mobilizon.GraphQL.Schema.Actors.PersonType do
arg(:event_id, :id)
resolve(&Person.person_participations/3)
end
@desc "The list of group this person is member of"
field(:memberships, :paginated_member_list,
description: "The list of group this person is member of"
) do
resolve(&Person.person_memberships/3)
end
end
object :person_queries do

View File

@@ -5,7 +5,8 @@ defmodule Mobilizon.GraphQL.Schema.AdminType do
use Absinthe.Schema.Notation
alias Mobilizon.Events.{Comment, Event}
alias Mobilizon.Conversations.Comment
alias Mobilizon.Events.Event
alias Mobilizon.Reports.{Note, Report}
alias Mobilizon.GraphQL.Resolvers.Admin

View File

@@ -20,6 +20,8 @@ defmodule Mobilizon.GraphQL.Schema.ConfigType do
field(:geocoding, :geocoding)
field(:maps, :maps)
field(:anonymous, :anonymous)
field(:resource_providers, list_of(:resource_provider))
field(:timezones, list_of(:string))
field(:terms, :terms, description: "The instance's terms") do
arg(:locale, :string, default_value: "en")
@@ -97,6 +99,12 @@ defmodule Mobilizon.GraphQL.Schema.ConfigType do
field(:enabled, :boolean)
end
object :resource_provider do
field(:type, :string)
field(:endpoint, :string)
field(:software, :string)
end
object :config_queries do
@desc "Get the instance config"
field :config, :config do

View File

@@ -1,4 +1,4 @@
defmodule Mobilizon.GraphQL.Schema.CommentType do
defmodule Mobilizon.GraphQL.Schema.Conversations.CommentType do
@moduledoc """
Schema representation for Comment
"""
@@ -48,6 +48,11 @@ defmodule Mobilizon.GraphQL.Schema.CommentType do
value(:invite, description: "visible only to people invited")
end
object :paginated_comment_list do
field(:elements, list_of(:comment), description: "A list of comments")
field(:total, :integer, description: "The total number of comments in the list")
end
object :comment_queries do
@desc "Get replies for thread"
field :thread, type: list_of(:comment) do
@@ -67,6 +72,14 @@ defmodule Mobilizon.GraphQL.Schema.CommentType do
resolve(&Comment.create_comment/3)
end
@desc "Update a comment"
field :update_comment, type: :comment do
arg(:text, non_null(:string))
arg(:comment_id, non_null(:id))
resolve(&Comment.update_comment/3)
end
field :delete_comment, type: :comment do
arg(:comment_id, non_null(:id))
arg(:actor_id, non_null(:id))

View File

@@ -0,0 +1,74 @@
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

@@ -115,6 +115,11 @@ defmodule Mobilizon.GraphQL.Schema.EventType do
value(:cancelled, description: "The event is cancelled")
end
object :paginated_event_list do
field(:elements, list_of(:event), description: "A list of events")
field(:total, :integer, description: "The total number of events in the list")
end
object :participant_stats do
field(:going, :integer,
description: "The number of approved participants",
@@ -201,6 +206,11 @@ defmodule Mobilizon.GraphQL.Schema.EventType do
field(:show_start_time, :boolean, description: "Show event start time")
field(:show_end_time, :boolean, description: "Show event end time")
field(:hide_organizer_when_group_event, :boolean,
description:
"Whether to show or hide the person organizer when event is organized by a group"
)
end
input_object :event_options_input do
@@ -242,6 +252,11 @@ defmodule Mobilizon.GraphQL.Schema.EventType do
field(:show_start_time, :boolean, description: "Show event start time")
field(:show_end_time, :boolean, description: "Show event end time")
field(:hide_organizer_when_group_event, :boolean,
description:
"Whether to show or hide the person organizer when event is organized by a group"
)
end
object :event_queries do
@@ -284,6 +299,7 @@ defmodule Mobilizon.GraphQL.Schema.EventType do
arg(:online_address, :string)
arg(:phone_address, :string)
arg(:organizer_actor_id, non_null(:id))
arg(:attributed_to_id, :id)
arg(:category, :string, default_value: "meeting")
arg(:physical_address, :address_input)
arg(:options, :event_options_input)
@@ -314,6 +330,7 @@ defmodule Mobilizon.GraphQL.Schema.EventType do
arg(:online_address, :string)
arg(:phone_address, :string)
arg(:organizer_actor_id, :id)
arg(:attributed_to_id, :id)
arg(:category, :string)
arg(:physical_address, :address_input)
arg(:options, :event_options_input)

View File

@@ -0,0 +1,95 @@
defmodule Mobilizon.GraphQL.Schema.ResourceType do
@moduledoc """
Schema representation for Resources
"""
use Absinthe.Schema.Notation
alias Mobilizon.GraphQL.Resolvers.Resource
@desc "A resource"
object :resource do
field(:id, :id, description: "The resource's ID")
field(:title, :string, description: "The resource's title")
field(:summary, :string, description: "The resource's summary")
field(:url, :string, description: "The resource's URL")
field(:resource_url, :string, description: "The resource's URL")
field(:metadata, :resource_metadata, description: "The resource's metadata")
field(:creator, :actor, description: "The resource's creator")
field(:actor, :actor, description: "The resource's owner")
field(:inserted_at, :naive_datetime, description: "The resource's creation date")
field(:updated_at, :naive_datetime, description: "The resource's last update date")
field(:type, :string, description: "The resource's type (if it's a folder)")
field(:path, :string, description: "The resource's path")
field(:parent, :resource, description: "The resource's parent")
field :children, :paginated_resource_list do
description("Children resources in folder")
resolve(&Resource.find_resources_for_parent/3)
end
end
object :paginated_resource_list do
field(:elements, list_of(:resource), description: "A list of resources")
field(:total, :integer, description: "The total number of resources in the list")
end
object :resource_metadata do
field(:type, :string, description: "The type of the resource")
field(:title, :string, description: "The resource's metadata title")
field(:description, :string, description: "The resource's metadata description")
field(:image_remote_url, :string, description: "The resource's metadata image")
field(:width, :integer)
field(:height, :integer)
field(:author_name, :string)
field(:author_url, :string)
field(:provider_name, :string)
field(:provider_url, :string)
field(:html, :string)
field(:favicon_url, :string)
end
object :resource_queries do
@desc "Get a resource"
field :resource, :resource do
arg(:path, non_null(:string))
arg(:username, non_null(:string))
resolve(&Resource.get_resource/3)
end
end
object :resource_mutations do
@desc "Create a resource"
field :create_resource, :resource do
arg(:parent_id, :id)
arg(:actor_id, non_null(:id))
arg(:title, non_null(:string))
arg(:summary, :string)
arg(:resource_url, :string)
arg(:type, :string, default_value: "link")
resolve(&Resource.create_resource/3)
end
@desc "Update a resource"
field :update_resource, :resource do
arg(:id, non_null(:id))
arg(:title, :string)
arg(:summary, :string)
arg(:parent_id, :id)
arg(:resource_url, :string)
resolve(&Resource.update_resource/3)
end
@desc "Delete a resource"
field :delete_resource, :deleted_object do
arg(:id, non_null(:id))
resolve(&Resource.delete_resource/3)
end
@desc "Get a preview for a resource link"
field :preview_resource_link, :resource_metadata do
arg(:resource_url, non_null(:string))
resolve(&Resource.preview_resource_link/3)
end
end
end

View File

@@ -0,0 +1,72 @@
defmodule Mobilizon.GraphQL.Schema.Todos.TodoType do
@moduledoc """
Schema representation for Todos
"""
use Absinthe.Schema.Notation
import Absinthe.Resolution.Helpers, only: [dataloader: 1]
alias Mobilizon.GraphQL.Resolvers.Todos, as: TodoResolver
alias Mobilizon.{Actors, Todos}
@desc "A todo"
object :todo do
field(:id, :id, description: "The todo's ID")
field(:title, :string, description: "The todo's title")
field(:status, :boolean, description: "The todo's status")
field(:due_date, :datetime, description: "The todo's due date")
field(:creator, :actor, resolve: dataloader(Actors), description: "The todo's creator")
field(:todo_list, :todo_list,
resolve: dataloader(Todos),
description: "The todo list this todo is attached to"
)
field(:assigned_to, :actor,
resolve: dataloader(Actors),
description: "The todos's assigned person"
)
end
object :paginated_todo_list do
field(:elements, list_of(:todo), description: "A list of todos")
field(:total, :integer, description: "The total number of todos in the list")
end
object :todo_queries do
@desc "Get a todo"
field :todo, :todo do
arg(:id, non_null(:id))
resolve(&TodoResolver.get_todo/3)
end
end
object :todo_mutations do
@desc "Create a todo"
field :create_todo, :todo do
arg(:todo_list_id, non_null(:id))
arg(:title, non_null(:string))
arg(:status, :boolean)
arg(:due_date, :datetime)
arg(:assigned_to_id, :id)
resolve(&TodoResolver.create_todo/3)
end
@desc "Update a todo"
field :update_todo, :todo do
arg(:id, non_null(:id))
arg(:todo_list_id, :id)
arg(:title, :string)
arg(:status, :boolean)
arg(:due_date, :datetime)
arg(:assigned_to_id, :id)
resolve(&TodoResolver.update_todo/3)
end
# @desc "Delete a todo"
# field :delete_todo, :deleted_object do
# arg(:id, non_null(:id))
# resolve(&TodoResolver.delete_todo/3)
# end
end
end

View File

@@ -0,0 +1,47 @@
defmodule Mobilizon.GraphQL.Schema.Todos.TodoListType do
@moduledoc """
Schema representation for Todo Lists
"""
use Absinthe.Schema.Notation
import Absinthe.Resolution.Helpers, only: [dataloader: 1]
alias Mobilizon.Actors
alias Mobilizon.GraphQL.Resolvers.Todos
@desc "A todo list"
object :todo_list do
field(:id, :id, description: "The todo list's ID")
field(:title, :string, description: "The todo list's title")
field(:actor, :actor,
resolve: dataloader(Actors),
description: "The actor that owns this todo list"
)
field(:todos, :paginated_todo_list,
resolve: &Todos.find_todos_for_todo_list/3,
description: "The todo-list's todos"
)
end
object :paginated_todo_list_list do
field(:elements, list_of(:todo_list), description: "A list of todo lists")
field(:total, :integer, description: "The total number of todo lists in the list")
end
object :todo_list_queries do
@desc "Get a todo list"
field :todo_list, :todo_list do
arg(:id, non_null(:id))
resolve(&Todos.get_todo_list/3)
end
end
object :todo_list_mutations do
@desc "Create a todo list"
field :create_todo_list, :todo_list do
arg(:title, non_null(:string))
arg(:group_id, non_null(:id))
resolve(&Todos.create_todo_list/3)
end
end
end

View File

@@ -51,7 +51,7 @@ defmodule Mobilizon.GraphQL.Schema.UserType do
field(:locale, :string, description: "The user's locale")
field(:participations, list_of(:participant),
field(:participations, :paginated_participant_list,
description: "The list of participations this user has"
) do
arg(:after_datetime, :datetime)
@@ -61,11 +61,23 @@ defmodule Mobilizon.GraphQL.Schema.UserType do
resolve(&User.user_participations/3)
end
field(:memberships, :paginated_member_list,
description: "The list of memberships for this user"
) do
arg(:page, :integer, default_value: 1)
arg(:limit, :integer, default_value: 10)
resolve(&User.user_memberships/3)
end
field(:drafts, list_of(:event), description: "The list of draft events this user has created") do
arg(:page, :integer, default_value: 1)
arg(:limit, :integer, default_value: 10)
resolve(&User.user_drafted_events/3)
end
field(:settings, :user_settings, description: "The list of settings for this user") do
resolve(&User.user_settings/3)
end
end
enum :user_role do
@@ -91,6 +103,22 @@ defmodule Mobilizon.GraphQL.Schema.UserType do
value(:id)
end
object :user_settings do
field(:timezone, :string, description: "The timezone for this user")
field(:notification_on_day, :boolean,
description: "Whether this user will receive an email at the start of the day of an event."
)
field(:notification_each_week, :boolean,
description: "Whether this user will receive an weekly event recap"
)
field(:notification_before_event, :boolean,
description: "Whether this user will receive a notification right before event"
)
end
object :user_queries do
@desc "Get an user"
field :user, :user do
@@ -179,20 +207,31 @@ defmodule Mobilizon.GraphQL.Schema.UserType do
resolve(&User.change_password/3)
end
@desc "Change an user email"
field :change_email, :user do
arg(:email, non_null(:string))
arg(:password, non_null(:string))
resolve(&User.change_email/3)
end
@desc "Validate an user email"
field :validate_email, :user do
arg(:token, non_null(:string))
resolve(&User.validate_email/3)
end
@desc "Delete an account"
field :delete_account, :deleted_object do
arg(:password, non_null(:string))
resolve(&User.delete_account/3)
end
field :set_user_settings, :user_settings do
arg(:timezone, :string)
arg(:notification_on_day, :boolean)
arg(:notification_each_week, :boolean)
arg(:notification_before_event, :boolean)
resolve(&User.set_user_setting/3)
end
end
end