Split GraphQL as separate context
This commit is contained in:
25
lib/graphql/helpers/error.ex
Normal file
25
lib/graphql/helpers/error.ex
Normal file
@@ -0,0 +1,25 @@
|
||||
defmodule Mobilizon.GraphQL.Helpers.Error do
|
||||
@moduledoc """
|
||||
Helper functions for Mobilizon.GraphQL
|
||||
"""
|
||||
|
||||
def handle_errors(fun) do
|
||||
fn source, args, info ->
|
||||
case Absinthe.Resolution.call(fun, source, args, info) do
|
||||
{:error, %Ecto.Changeset{} = changeset} -> format_changeset(changeset)
|
||||
val -> val
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def format_changeset(changeset) do
|
||||
# {:error, [email: {"has already been taken", []}]}
|
||||
errors =
|
||||
changeset.errors
|
||||
|> Enum.map(fn {key, {value, _context}} ->
|
||||
[message: "#{value}", details: key]
|
||||
end)
|
||||
|
||||
{:error, errors}
|
||||
end
|
||||
end
|
||||
46
lib/graphql/resolvers/address.ex
Normal file
46
lib/graphql/resolvers/address.ex
Normal file
@@ -0,0 +1,46 @@
|
||||
defmodule Mobilizon.GraphQL.Resolvers.Address do
|
||||
@moduledoc """
|
||||
Handles the comment-related GraphQL calls
|
||||
"""
|
||||
|
||||
alias Mobilizon.Addresses.Address
|
||||
alias Mobilizon.Service.Geospatial
|
||||
|
||||
require Logger
|
||||
|
||||
@doc """
|
||||
Search an address
|
||||
"""
|
||||
@spec search(map, map, map) :: {:ok, [Address.t()]}
|
||||
def search(
|
||||
_parent,
|
||||
%{query: query, locale: locale, page: _page, limit: _limit},
|
||||
%{context: %{ip: ip}}
|
||||
) do
|
||||
geolix = Geolix.lookup(ip)
|
||||
|
||||
country_code =
|
||||
case geolix do
|
||||
%{country: %{iso_code: country_code}} -> String.downcase(country_code)
|
||||
_ -> nil
|
||||
end
|
||||
|
||||
addresses = Geospatial.service().search(query, lang: locale, country_code: country_code)
|
||||
|
||||
{:ok, addresses}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Reverse geocode some coordinates
|
||||
"""
|
||||
@spec reverse_geocode(map, map, map) :: {:ok, [Address.t()]}
|
||||
def reverse_geocode(
|
||||
_parent,
|
||||
%{longitude: longitude, latitude: latitude, zoom: zoom, locale: locale},
|
||||
_context
|
||||
) do
|
||||
addresses = Geospatial.service().geocode(longitude, latitude, lang: locale, zoom: zoom)
|
||||
|
||||
{:ok, addresses}
|
||||
end
|
||||
end
|
||||
214
lib/graphql/resolvers/admin.ex
Normal file
214
lib/graphql/resolvers/admin.ex
Normal file
@@ -0,0 +1,214 @@
|
||||
defmodule Mobilizon.GraphQL.Resolvers.Admin do
|
||||
@moduledoc """
|
||||
Handles the report-related GraphQL calls.
|
||||
"""
|
||||
|
||||
import Mobilizon.Users.Guards
|
||||
|
||||
alias Mobilizon.Actors
|
||||
alias Mobilizon.Actors.Actor
|
||||
alias Mobilizon.Admin.ActionLog
|
||||
alias Mobilizon.Events
|
||||
alias Mobilizon.Events.{Event, Comment}
|
||||
alias Mobilizon.Reports.{Note, Report}
|
||||
alias Mobilizon.Users.User
|
||||
alias Mobilizon.Service.Statistics
|
||||
alias Mobilizon.Storage.Page
|
||||
|
||||
alias Mobilizon.Federation.ActivityPub.Relay
|
||||
|
||||
def list_action_logs(
|
||||
_parent,
|
||||
%{page: page, limit: limit},
|
||||
%{context: %{current_user: %User{role: role}}}
|
||||
)
|
||||
when is_moderator(role) do
|
||||
with action_logs <- Mobilizon.Admin.list_action_logs(page, limit) do
|
||||
action_logs =
|
||||
action_logs
|
||||
|> Enum.map(fn %ActionLog{
|
||||
target_type: target_type,
|
||||
action: action,
|
||||
actor: actor,
|
||||
id: id,
|
||||
inserted_at: inserted_at
|
||||
} = action_log ->
|
||||
with data when is_map(data) <-
|
||||
transform_action_log(String.to_existing_atom(target_type), action, action_log) do
|
||||
Map.merge(data, %{actor: actor, id: id, inserted_at: inserted_at})
|
||||
end
|
||||
end)
|
||||
|> Enum.filter(& &1)
|
||||
|
||||
{:ok, action_logs}
|
||||
end
|
||||
end
|
||||
|
||||
def list_action_logs(_parent, _args, _resolution) do
|
||||
{:error, "You need to be logged-in and a moderator to list action logs"}
|
||||
end
|
||||
|
||||
defp transform_action_log(
|
||||
Report,
|
||||
:update,
|
||||
%ActionLog{} = action_log
|
||||
) do
|
||||
with %Report{} = report <- Mobilizon.Reports.get_report(action_log.target_id) do
|
||||
action =
|
||||
case action_log do
|
||||
%ActionLog{changes: %{"status" => "closed"}} -> :report_update_closed
|
||||
%ActionLog{changes: %{"status" => "open"}} -> :report_update_opened
|
||||
%ActionLog{changes: %{"status" => "resolved"}} -> :report_update_resolved
|
||||
end
|
||||
|
||||
%{
|
||||
action: action,
|
||||
object: report
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
defp transform_action_log(Note, :create, %ActionLog{changes: changes}) do
|
||||
%{
|
||||
action: :note_creation,
|
||||
object: convert_changes_to_struct(Note, changes)
|
||||
}
|
||||
end
|
||||
|
||||
defp transform_action_log(Note, :delete, %ActionLog{changes: changes}) do
|
||||
%{
|
||||
action: :note_deletion,
|
||||
object: convert_changes_to_struct(Note, changes)
|
||||
}
|
||||
end
|
||||
|
||||
defp transform_action_log(Event, :delete, %ActionLog{changes: changes}) do
|
||||
%{
|
||||
action: :event_deletion,
|
||||
object: convert_changes_to_struct(Event, changes)
|
||||
}
|
||||
end
|
||||
|
||||
defp transform_action_log(Comment, :delete, %ActionLog{changes: changes}) do
|
||||
%{
|
||||
action: :comment_deletion,
|
||||
object: convert_changes_to_struct(Comment, changes)
|
||||
}
|
||||
end
|
||||
|
||||
# Changes are stored as %{"key" => "value"} so we need to convert them back as struct
|
||||
defp convert_changes_to_struct(struct, %{"report_id" => _report_id} = changes) do
|
||||
with data <- for({key, val} <- changes, into: %{}, do: {String.to_atom(key), val}),
|
||||
data <- Map.put(data, :report, Mobilizon.Reports.get_report(data.report_id)) do
|
||||
struct(struct, data)
|
||||
end
|
||||
end
|
||||
|
||||
defp convert_changes_to_struct(struct, changes) do
|
||||
with data <- for({key, val} <- changes, into: %{}, do: {String.to_atom(key), val}) do
|
||||
struct(struct, data)
|
||||
end
|
||||
end
|
||||
|
||||
def get_dashboard(_parent, _args, %{context: %{current_user: %User{role: role}}})
|
||||
when is_admin(role) do
|
||||
last_public_event_published =
|
||||
case Events.list_events(1, 1, :inserted_at, :desc) do
|
||||
[event | _] -> event
|
||||
_ -> nil
|
||||
end
|
||||
|
||||
{:ok,
|
||||
%{
|
||||
number_of_users: Statistics.get_cached_value(:local_users),
|
||||
number_of_events: Statistics.get_cached_value(:local_events),
|
||||
number_of_comments: Statistics.get_cached_value(:local_comments),
|
||||
number_of_reports: Mobilizon.Reports.count_opened_reports(),
|
||||
last_public_event_published: last_public_event_published
|
||||
}}
|
||||
end
|
||||
|
||||
def get_dashboard(_parent, _args, _resolution) do
|
||||
{:error, "You need to be logged-in and an administrator to access dashboard statistics"}
|
||||
end
|
||||
|
||||
def list_relay_followers(
|
||||
_parent,
|
||||
%{page: page, limit: limit},
|
||||
%{context: %{current_user: %User{role: role}}}
|
||||
)
|
||||
when is_admin(role) do
|
||||
with %Actor{} = relay_actor <- Relay.get_actor() do
|
||||
%Page{} =
|
||||
page = Actors.list_external_followers_for_actor_paginated(relay_actor, page, limit)
|
||||
|
||||
{:ok, page}
|
||||
end
|
||||
end
|
||||
|
||||
def list_relay_followings(
|
||||
_parent,
|
||||
%{page: page, limit: limit},
|
||||
%{context: %{current_user: %User{role: role}}}
|
||||
)
|
||||
when is_admin(role) do
|
||||
with %Actor{} = relay_actor <- Relay.get_actor() do
|
||||
%Page{} =
|
||||
page = Actors.list_external_followings_for_actor_paginated(relay_actor, page, limit)
|
||||
|
||||
{:ok, page}
|
||||
end
|
||||
end
|
||||
|
||||
def create_relay(_parent, %{address: address}, %{context: %{current_user: %User{role: role}}})
|
||||
when is_admin(role) do
|
||||
case Relay.follow(address) do
|
||||
{:ok, _activity, follow} ->
|
||||
{:ok, follow}
|
||||
|
||||
{:error, {:error, err}} when is_bitstring(err) ->
|
||||
{:error, err}
|
||||
end
|
||||
end
|
||||
|
||||
def remove_relay(_parent, %{address: address}, %{context: %{current_user: %User{role: role}}})
|
||||
when is_admin(role) do
|
||||
case Relay.unfollow(address) do
|
||||
{:ok, _activity, follow} ->
|
||||
{:ok, follow}
|
||||
|
||||
{:error, {:error, err}} when is_bitstring(err) ->
|
||||
{:error, err}
|
||||
end
|
||||
end
|
||||
|
||||
def accept_subscription(
|
||||
_parent,
|
||||
%{address: address},
|
||||
%{context: %{current_user: %User{role: role}}}
|
||||
)
|
||||
when is_admin(role) do
|
||||
case Relay.accept(address) do
|
||||
{:ok, _activity, follow} ->
|
||||
{:ok, follow}
|
||||
|
||||
{:error, {:error, err}} when is_bitstring(err) ->
|
||||
{:error, err}
|
||||
end
|
||||
end
|
||||
|
||||
def reject_subscription(
|
||||
_parent,
|
||||
%{address: address},
|
||||
%{context: %{current_user: %User{role: role}}}
|
||||
)
|
||||
when is_admin(role) do
|
||||
case Relay.reject(address) do
|
||||
{:ok, _activity, follow} ->
|
||||
{:ok, follow}
|
||||
|
||||
{:error, {:error, err}} when is_bitstring(err) ->
|
||||
{:error, err}
|
||||
end
|
||||
end
|
||||
end
|
||||
78
lib/graphql/resolvers/comment.ex
Normal file
78
lib/graphql/resolvers/comment.ex
Normal file
@@ -0,0 +1,78 @@
|
||||
defmodule Mobilizon.GraphQL.Resolvers.Comment do
|
||||
@moduledoc """
|
||||
Handles the comment-related GraphQL calls.
|
||||
"""
|
||||
|
||||
alias Mobilizon.{Actors, Admin, Events}
|
||||
alias Mobilizon.Actors.Actor
|
||||
alias Mobilizon.Events
|
||||
alias Mobilizon.Events.Comment, as: CommentModel
|
||||
alias Mobilizon.Users.User
|
||||
|
||||
alias MobilizonWeb.API.Comments
|
||||
|
||||
require Logger
|
||||
|
||||
def get_thread(_parent, %{id: thread_id}, _context) do
|
||||
{:ok, Events.get_thread_replies(thread_id)}
|
||||
end
|
||||
|
||||
def create_comment(
|
||||
_parent,
|
||||
%{actor_id: actor_id} = args,
|
||||
%{context: %{current_user: %User{} = user}}
|
||||
) do
|
||||
with {:is_owned, %Actor{} = _organizer_actor} <- User.owns_actor(user, actor_id),
|
||||
{:ok, _, %CommentModel{} = comment} <-
|
||||
Comments.create_comment(args) do
|
||||
{:ok, comment}
|
||||
else
|
||||
{:is_owned, nil} ->
|
||||
{:error, "Actor id is not owned by authenticated user"}
|
||||
end
|
||||
end
|
||||
|
||||
def create_comment(_parent, _args, _context) do
|
||||
{:error, "You are not allowed to create 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}}
|
||||
) 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
|
||||
cond do
|
||||
{:comment_can_be_managed, true} == CommentModel.can_be_managed_by(comment, actor_id) ->
|
||||
do_delete_comment(comment)
|
||||
|
||||
role in [:moderator, :administrator] ->
|
||||
with {:ok, res} <- do_delete_comment(comment),
|
||||
%Actor{} = actor <- Actors.get_actor(actor_id) do
|
||||
Admin.log_action(actor, "delete", comment)
|
||||
|
||||
{:ok, res}
|
||||
end
|
||||
|
||||
true ->
|
||||
{:error, "You cannot delete this comment"}
|
||||
end
|
||||
else
|
||||
{:is_owned, nil} ->
|
||||
{:error, "Actor id is not owned by authenticated user"}
|
||||
end
|
||||
end
|
||||
|
||||
def delete_comment(_parent, _args, %{}) do
|
||||
{:error, "You are not allowed to delete a comment if not connected"}
|
||||
end
|
||||
|
||||
defp do_delete_comment(%CommentModel{} = comment) do
|
||||
with {:ok, _, %CommentModel{} = comment} <-
|
||||
Comments.delete_comment(comment) do
|
||||
{:ok, comment}
|
||||
end
|
||||
end
|
||||
end
|
||||
49
lib/graphql/resolvers/config.ex
Normal file
49
lib/graphql/resolvers/config.ex
Normal file
@@ -0,0 +1,49 @@
|
||||
defmodule Mobilizon.GraphQL.Resolvers.Config do
|
||||
@moduledoc """
|
||||
Handles the config-related GraphQL calls.
|
||||
"""
|
||||
|
||||
alias Geolix.Adapter.MMDB2.Record.{Country, Location}
|
||||
|
||||
alias Mobilizon.Config
|
||||
|
||||
@doc """
|
||||
Gets config.
|
||||
"""
|
||||
def get_config(_parent, _params, %{context: %{ip: ip}}) do
|
||||
geolix = Geolix.lookup(ip)
|
||||
|
||||
country_code =
|
||||
case Map.get(geolix, :city) do
|
||||
%{country: %Country{iso_code: country_code}} -> String.downcase(country_code)
|
||||
_ -> nil
|
||||
end
|
||||
|
||||
location =
|
||||
case Map.get(geolix, :city) do
|
||||
%{location: %Location{} = location} -> Map.from_struct(location)
|
||||
_ -> nil
|
||||
end
|
||||
|
||||
{:ok,
|
||||
%{
|
||||
name: Config.instance_name(),
|
||||
registrations_open: Config.instance_registrations_open?(),
|
||||
registrations_whitelist: Config.instance_registrations_whitelist?(),
|
||||
demo_mode: Config.instance_demo_mode?(),
|
||||
description: Config.instance_description(),
|
||||
location: location,
|
||||
country_code: country_code,
|
||||
geocoding: %{
|
||||
provider: Config.instance_geocoding_provider(),
|
||||
autocomplete: Config.instance_geocoding_autocomplete()
|
||||
},
|
||||
maps: %{
|
||||
tiles: %{
|
||||
endpoint: Config.instance_maps_tiles_endpoint(),
|
||||
attribution: Config.instance_maps_tiles_attribution()
|
||||
}
|
||||
}
|
||||
}}
|
||||
end
|
||||
end
|
||||
367
lib/graphql/resolvers/event.ex
Normal file
367
lib/graphql/resolvers/event.ex
Normal file
@@ -0,0 +1,367 @@
|
||||
defmodule Mobilizon.GraphQL.Resolvers.Event do
|
||||
@moduledoc """
|
||||
Handles the event-related GraphQL calls.
|
||||
"""
|
||||
|
||||
alias Mobilizon.{Actors, Admin, Events}
|
||||
alias Mobilizon.Actors.Actor
|
||||
alias Mobilizon.Events.{Event, Participant, EventParticipantStats}
|
||||
alias Mobilizon.Users.User
|
||||
|
||||
alias Mobilizon.GraphQL.Resolvers.Person
|
||||
|
||||
alias Mobilizon.Federation.ActivityPub.Activity
|
||||
|
||||
alias MobilizonWeb.API
|
||||
|
||||
# We limit the max number of events that can be retrieved
|
||||
@event_max_limit 100
|
||||
@number_of_related_events 3
|
||||
|
||||
def list_events(_parent, %{page: page, limit: limit}, _resolution)
|
||||
when limit < @event_max_limit do
|
||||
{:ok, Mobilizon.Events.list_events(page, limit)}
|
||||
end
|
||||
|
||||
def list_events(_parent, %{page: _page, limit: _limit}, _resolution) do
|
||||
{:error, :events_max_limit_reached}
|
||||
end
|
||||
|
||||
defp find_private_event(
|
||||
_parent,
|
||||
%{uuid: uuid},
|
||||
%{context: %{current_user: %User{id: user_id}}} = _resolution
|
||||
) do
|
||||
case {:has_event, Mobilizon.Events.get_own_event_by_uuid_with_preload(uuid, user_id)} do
|
||||
{:has_event, %Event{} = event} ->
|
||||
{:ok, Map.put(event, :organizer_actor, Person.proxify_pictures(event.organizer_actor))}
|
||||
|
||||
{:has_event, _} ->
|
||||
{:error, "Event with UUID #{uuid} not found"}
|
||||
end
|
||||
end
|
||||
|
||||
defp find_private_event(_parent, %{uuid: uuid}, _resolution) do
|
||||
{:error, "Event with UUID #{uuid} not found"}
|
||||
end
|
||||
|
||||
def find_event(parent, %{uuid: uuid} = args, resolution) do
|
||||
case {:has_event, Mobilizon.Events.get_public_event_by_uuid_with_preload(uuid)} do
|
||||
{:has_event, %Event{} = event} ->
|
||||
{:ok, Map.put(event, :organizer_actor, Person.proxify_pictures(event.organizer_actor))}
|
||||
|
||||
{:has_event, _} ->
|
||||
find_private_event(parent, args, resolution)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
List participants for event (through an event request)
|
||||
"""
|
||||
def list_participants_for_event(
|
||||
%Event{id: event_id},
|
||||
%{page: page, limit: limit, roles: roles, actor_id: actor_id},
|
||||
%{context: %{current_user: %User{} = user}} = _resolution
|
||||
) do
|
||||
with {:is_owned, %Actor{} = _actor} <- User.owns_actor(user, actor_id),
|
||||
# Check that moderator has right
|
||||
{:actor_approve_permission, true} <-
|
||||
{:actor_approve_permission, Mobilizon.Events.moderator_for_event?(event_id, actor_id)} do
|
||||
roles =
|
||||
case roles do
|
||||
"" ->
|
||||
[]
|
||||
|
||||
roles ->
|
||||
roles
|
||||
|> String.split(",")
|
||||
|> Enum.map(&String.downcase/1)
|
||||
|> Enum.map(&String.to_existing_atom/1)
|
||||
end
|
||||
|
||||
{:ok, Mobilizon.Events.list_participants_for_event(event_id, roles, page, limit)}
|
||||
else
|
||||
{:is_owned, nil} ->
|
||||
{:error, "Moderator Actor ID is not owned by authenticated user"}
|
||||
|
||||
{:actor_approve_permission, _} ->
|
||||
{:error, "Provided moderator actor ID doesn't have permission on this event"}
|
||||
end
|
||||
end
|
||||
|
||||
def list_participants_for_event(_, _args, _resolution) do
|
||||
{:ok, []}
|
||||
end
|
||||
|
||||
def stats_participants_going(%EventParticipantStats{} = stats, _args, _resolution) do
|
||||
{:ok, stats.participant + stats.moderator + stats.administrator + stats.creator}
|
||||
end
|
||||
|
||||
@doc """
|
||||
List related events
|
||||
"""
|
||||
def list_related_events(
|
||||
%Event{tags: tags, organizer_actor: organizer_actor, uuid: uuid},
|
||||
_args,
|
||||
_resolution
|
||||
) do
|
||||
# We get the organizer's next public event
|
||||
events =
|
||||
[Events.get_upcoming_public_event_for_actor(organizer_actor, uuid)]
|
||||
|> Enum.filter(&is_map/1)
|
||||
|
||||
# We find similar events with the same tags
|
||||
# uniq_by : It's possible event_from_same_actor is inside events_from_tags
|
||||
events =
|
||||
events
|
||||
|> Enum.concat(Events.list_events_by_tags(tags, @number_of_related_events))
|
||||
|> uniq_events()
|
||||
|
||||
# TODO: We should use tag_relations to find more appropriate events
|
||||
|
||||
# We've considered all recommended events, so we fetch the latest events
|
||||
events =
|
||||
if @number_of_related_events - length(events) > 0 do
|
||||
events
|
||||
|> Enum.concat(
|
||||
Events.list_events(1, @number_of_related_events, :begins_on, :asc, true, true)
|
||||
)
|
||||
|> uniq_events()
|
||||
else
|
||||
events
|
||||
end
|
||||
|
||||
events =
|
||||
events
|
||||
# We remove the same event from the results
|
||||
|> Enum.filter(fn event -> event.uuid != uuid end)
|
||||
# We return only @number_of_related_events right now
|
||||
|> Enum.take(@number_of_related_events)
|
||||
|
||||
{:ok, events}
|
||||
end
|
||||
|
||||
defp uniq_events(events), do: Enum.uniq_by(events, fn event -> event.uuid end)
|
||||
|
||||
@doc """
|
||||
Join an event for an actor
|
||||
"""
|
||||
def actor_join_event(
|
||||
_parent,
|
||||
%{actor_id: actor_id, event_id: event_id},
|
||||
%{context: %{current_user: user}}
|
||||
) do
|
||||
with {:is_owned, %Actor{} = actor} <- User.owns_actor(user, actor_id),
|
||||
{:has_event, {:ok, %Event{} = event}} <-
|
||||
{:has_event, Mobilizon.Events.get_event_with_preload(event_id)},
|
||||
{:error, :participant_not_found} <- Mobilizon.Events.get_participant(event_id, actor_id),
|
||||
{:ok, _activity, participant} <- API.Participations.join(event, actor),
|
||||
participant <-
|
||||
participant
|
||||
|> Map.put(:event, event)
|
||||
|> Map.put(:actor, Person.proxify_pictures(actor)) do
|
||||
{:ok, participant}
|
||||
else
|
||||
{:maximum_attendee_capacity, _} ->
|
||||
{:error, "The event has already reached its maximum capacity"}
|
||||
|
||||
{:has_event, _} ->
|
||||
{:error, "Event with this ID #{inspect(event_id)} doesn't exist"}
|
||||
|
||||
{:is_owned, nil} ->
|
||||
{:error, "Actor id is not owned by authenticated user"}
|
||||
|
||||
{:error, :event_not_found} ->
|
||||
{:error, "Event id not found"}
|
||||
|
||||
{:ok, %Participant{}} ->
|
||||
{:error, "You are already a participant of this event"}
|
||||
end
|
||||
end
|
||||
|
||||
def actor_join_event(_parent, _args, _resolution) do
|
||||
{:error, "You need to be logged-in to join an event"}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Leave an event for an actor
|
||||
"""
|
||||
def actor_leave_event(
|
||||
_parent,
|
||||
%{actor_id: actor_id, event_id: event_id},
|
||||
%{context: %{current_user: user}}
|
||||
) do
|
||||
with {:is_owned, %Actor{} = actor} <- User.owns_actor(user, actor_id),
|
||||
{:has_event, {:ok, %Event{} = event}} <-
|
||||
{:has_event, Mobilizon.Events.get_event_with_preload(event_id)},
|
||||
{:ok, _activity, _participant} <- API.Participations.leave(event, actor) do
|
||||
{:ok, %{event: %{id: event_id}, actor: %{id: actor_id}}}
|
||||
else
|
||||
{:has_event, _} ->
|
||||
{:error, "Event with this ID #{inspect(event_id)} doesn't exist"}
|
||||
|
||||
{:is_owned, nil} ->
|
||||
{:error, "Actor id is not owned by authenticated user"}
|
||||
|
||||
{:only_organizer, true} ->
|
||||
{:error, "You can't leave event because you're the only event creator participant"}
|
||||
|
||||
{:error, :participant_not_found} ->
|
||||
{:error, "Participant not found"}
|
||||
end
|
||||
end
|
||||
|
||||
def actor_leave_event(_parent, _args, _resolution) do
|
||||
{:error, "You need to be logged-in to leave an event"}
|
||||
end
|
||||
|
||||
def update_participation(
|
||||
_parent,
|
||||
%{id: participation_id, moderator_actor_id: moderator_actor_id, role: new_role},
|
||||
%{context: %{current_user: user}}
|
||||
) do
|
||||
# Check that moderator provided is rightly authenticated
|
||||
with {:is_owned, moderator_actor} <- User.owns_actor(user, moderator_actor_id),
|
||||
# Check that participation already exists
|
||||
{:has_participation, %Participant{role: old_role} = participation} <-
|
||||
{:has_participation, Mobilizon.Events.get_participant(participation_id)},
|
||||
{:same_role, false} <- {:same_role, new_role == old_role},
|
||||
# Check that moderator has right
|
||||
{:actor_approve_permission, true} <-
|
||||
{:actor_approve_permission,
|
||||
Mobilizon.Events.moderator_for_event?(participation.event.id, moderator_actor_id)},
|
||||
{:ok, _activity, participation} <-
|
||||
MobilizonWeb.API.Participations.update(participation, moderator_actor, new_role) do
|
||||
{:ok, participation}
|
||||
else
|
||||
{:is_owned, nil} ->
|
||||
{:error, "Moderator Actor ID is not owned by authenticated user"}
|
||||
|
||||
{:has_participation, %Participant{role: role, id: id}} ->
|
||||
{:error,
|
||||
"Participant #{id} can't be approved since it's already a participant (with role #{role})"}
|
||||
|
||||
{:has_participation, nil} ->
|
||||
{:error, "Participant not found"}
|
||||
|
||||
{:actor_approve_permission, _} ->
|
||||
{:error, "Provided moderator actor ID doesn't have permission on this event"}
|
||||
|
||||
{:same_role, true} ->
|
||||
{:error, "Participant already has role #{new_role}"}
|
||||
|
||||
{:error, :participant_not_found} ->
|
||||
{:error, "Participant not found"}
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Create an event
|
||||
"""
|
||||
def create_event(
|
||||
_parent,
|
||||
%{organizer_actor_id: organizer_actor_id} = args,
|
||||
%{context: %{current_user: user}} = _resolution
|
||||
) do
|
||||
# See https://github.com/absinthe-graphql/absinthe/issues/490
|
||||
with args <- Map.put(args, :options, args[:options] || %{}),
|
||||
{:is_owned, %Actor{} = organizer_actor} <- User.owns_actor(user, organizer_actor_id),
|
||||
args_with_organizer <- Map.put(args, :organizer_actor, organizer_actor),
|
||||
{:ok, %Activity{data: %{"object" => %{"type" => "Event"}}}, %Event{} = event} <-
|
||||
MobilizonWeb.API.Events.create_event(args_with_organizer) do
|
||||
{:ok, event}
|
||||
else
|
||||
{:is_owned, nil} ->
|
||||
{:error, "Organizer actor id is not owned by the user"}
|
||||
|
||||
{:error, _, %Ecto.Changeset{} = error, _} ->
|
||||
{:error, error}
|
||||
|
||||
{:error, %Ecto.Changeset{} = error} ->
|
||||
{:error, error}
|
||||
end
|
||||
end
|
||||
|
||||
def create_event(_parent, _args, _resolution) do
|
||||
{:error, "You need to be logged-in to create events"}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Update an event
|
||||
"""
|
||||
def update_event(
|
||||
_parent,
|
||||
%{event_id: event_id} = args,
|
||||
%{context: %{current_user: user}} = _resolution
|
||||
) do
|
||||
# See https://github.com/absinthe-graphql/absinthe/issues/490
|
||||
with args <- Map.put(args, :options, args[:options] || %{}),
|
||||
{:ok, %Event{} = event} <- Events.get_event_with_preload(event_id),
|
||||
organizer_actor_id <- args |> Map.get(:organizer_actor_id, event.organizer_actor_id),
|
||||
{:is_owned, %Actor{} = organizer_actor} <-
|
||||
User.owns_actor(user, organizer_actor_id),
|
||||
args <- Map.put(args, :organizer_actor, organizer_actor),
|
||||
{:ok, %Activity{data: %{"object" => %{"type" => "Event"}}}, %Event{} = event} <-
|
||||
MobilizonWeb.API.Events.update_event(args, event) do
|
||||
{:ok, event}
|
||||
else
|
||||
{:error, :event_not_found} ->
|
||||
{:error, "Event not found"}
|
||||
|
||||
{:is_owned, nil} ->
|
||||
{:error, "User doesn't own actor"}
|
||||
|
||||
{:error, _, %Ecto.Changeset{} = error, _} ->
|
||||
{:error, error}
|
||||
end
|
||||
end
|
||||
|
||||
def update_event(_parent, _args, _resolution) do
|
||||
{:error, "You need to be logged-in to update an event"}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Delete an event
|
||||
"""
|
||||
def delete_event(
|
||||
_parent,
|
||||
%{event_id: event_id, actor_id: actor_id},
|
||||
%{context: %{current_user: %User{role: role} = user}}
|
||||
) 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
|
||||
cond do
|
||||
{:event_can_be_managed, true} == Event.can_be_managed_by(event, actor_id) ->
|
||||
do_delete_event(event)
|
||||
|
||||
role in [:moderator, :administrator] ->
|
||||
with {:ok, res} <- do_delete_event(event, !is_local),
|
||||
%Actor{} = actor <- Actors.get_actor(actor_id) do
|
||||
Admin.log_action(actor, "delete", event)
|
||||
|
||||
{:ok, res}
|
||||
end
|
||||
|
||||
true ->
|
||||
{:error, "You cannot delete this event"}
|
||||
end
|
||||
else
|
||||
{:error, :event_not_found} ->
|
||||
{:error, "Event not found"}
|
||||
|
||||
{:is_owned, nil} ->
|
||||
{:error, "Actor id is not owned by authenticated user"}
|
||||
end
|
||||
end
|
||||
|
||||
def delete_event(_parent, _args, _resolution) 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} <- MobilizonWeb.API.Events.delete_event(event) do
|
||||
{:ok, %{id: event.id}}
|
||||
end
|
||||
end
|
||||
end
|
||||
82
lib/graphql/resolvers/feed_token.ex
Normal file
82
lib/graphql/resolvers/feed_token.ex
Normal file
@@ -0,0 +1,82 @@
|
||||
defmodule Mobilizon.GraphQL.Resolvers.FeedToken do
|
||||
@moduledoc """
|
||||
Handles the feed tokens-related GraphQL calls.
|
||||
"""
|
||||
|
||||
alias Mobilizon.Actors.Actor
|
||||
alias Mobilizon.Events
|
||||
alias Mobilizon.Events.FeedToken
|
||||
alias Mobilizon.Users.User
|
||||
|
||||
require Logger
|
||||
|
||||
@doc """
|
||||
Create an feed token for an user and a defined actor
|
||||
"""
|
||||
@spec create_feed_token(any, map, map) :: {:ok, FeedToken.t()} | {:error, String.t()}
|
||||
def create_feed_token(
|
||||
_parent,
|
||||
%{actor_id: actor_id},
|
||||
%{context: %{current_user: %User{id: id} = user}}
|
||||
) do
|
||||
with {:is_owned, %Actor{}} <- User.owns_actor(user, actor_id),
|
||||
{:ok, feed_token} <- Events.create_feed_token(%{"user_id" => id, "actor_id" => actor_id}) do
|
||||
{:ok, feed_token}
|
||||
else
|
||||
{:is_owned, nil} ->
|
||||
{:error, "Actor id is not owned by authenticated user"}
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Create an feed token for an user
|
||||
"""
|
||||
@spec create_feed_token(any, map, map) :: {:ok, FeedToken.t()}
|
||||
def create_feed_token(_parent, %{}, %{context: %{current_user: %User{id: id}}}) do
|
||||
with {:ok, feed_token} <- Events.create_feed_token(%{"user_id" => id}) do
|
||||
{:ok, feed_token}
|
||||
end
|
||||
end
|
||||
|
||||
@spec create_feed_token(any, map, map) :: {:error, String.t()}
|
||||
def create_feed_token(_parent, _args, %{}) do
|
||||
{:error, "You are not allowed to create a feed token if not connected"}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Delete a feed token
|
||||
"""
|
||||
@spec delete_feed_token(any, map, map) :: {:ok, map} | {:error, String.t()}
|
||||
def delete_feed_token(
|
||||
_parent,
|
||||
%{token: token},
|
||||
%{context: %{current_user: %User{id: id} = _user}}
|
||||
) do
|
||||
with {:ok, token} <- Ecto.UUID.cast(token),
|
||||
{:no_token, %FeedToken{actor: actor, user: %User{} = user} = feed_token} <-
|
||||
{:no_token, Events.get_feed_token(token)},
|
||||
{:token_from_user, true} <- {:token_from_user, id == user.id},
|
||||
{:ok, _} <- Events.delete_feed_token(feed_token) do
|
||||
res = %{user: %{id: id}}
|
||||
res = if is_nil(actor), do: res, else: Map.put(res, :actor, %{id: actor.id})
|
||||
{:ok, res}
|
||||
else
|
||||
{:error, nil} ->
|
||||
{:error, "No such feed token"}
|
||||
|
||||
:error ->
|
||||
{:error, "Token is not a valid UUID"}
|
||||
|
||||
{:no_token, _} ->
|
||||
{:error, "Token does not exist"}
|
||||
|
||||
{:token_from_user, false} ->
|
||||
{:error, "You don't have permission to delete this token"}
|
||||
end
|
||||
end
|
||||
|
||||
@spec delete_feed_token(any, map, map) :: {:error, String.t()}
|
||||
def delete_feed_token(_parent, _args, %{}) do
|
||||
{:error, "You are not allowed to delete a feed token if not connected"}
|
||||
end
|
||||
end
|
||||
190
lib/graphql/resolvers/group.ex
Normal file
190
lib/graphql/resolvers/group.ex
Normal file
@@ -0,0 +1,190 @@
|
||||
defmodule Mobilizon.GraphQL.Resolvers.Group do
|
||||
@moduledoc """
|
||||
Handles the group-related GraphQL calls.
|
||||
"""
|
||||
|
||||
alias Mobilizon.Actors
|
||||
alias Mobilizon.Actors.{Actor, Member}
|
||||
alias Mobilizon.Users.User
|
||||
|
||||
alias Mobilizon.GraphQL.Resolvers.Person
|
||||
|
||||
alias Mobilizon.Federation.ActivityPub
|
||||
|
||||
alias MobilizonWeb.API
|
||||
|
||||
require Logger
|
||||
|
||||
@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
|
||||
{:ok, actor}
|
||||
else
|
||||
_ ->
|
||||
{:error, "Group with name #{name} not found"}
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
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)
|
||||
}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Create a new group. The creator is automatically added as admin
|
||||
"""
|
||||
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),
|
||||
{:ok, _activity, %Actor{type: :Group} = group} <-
|
||||
API.Groups.create_group(args) do
|
||||
{:ok, group}
|
||||
else
|
||||
{:error, err} when is_bitstring(err) ->
|
||||
{:error, err}
|
||||
|
||||
{:is_owned, nil} ->
|
||||
{:error, "Creator actor id is not owned by the current user"}
|
||||
end
|
||||
end
|
||||
|
||||
def create_group(_parent, _args, _resolution) do
|
||||
{:error, "You need to be logged-in to create a group"}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Delete an existing group
|
||||
"""
|
||||
def delete_group(
|
||||
_parent,
|
||||
%{group_id: group_id, actor_id: actor_id},
|
||||
%{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),
|
||||
group <- Actors.delete_group!(group) do
|
||||
{:ok, %{id: group.id}}
|
||||
else
|
||||
{:error, :group_not_found} ->
|
||||
{:error, "Group not found"}
|
||||
|
||||
{:is_owned, nil} ->
|
||||
{:error, "Actor id is not owned by authenticated user"}
|
||||
|
||||
{:error, :member_not_found} ->
|
||||
{:error, "Actor id is not a member of this group"}
|
||||
|
||||
{:is_admin, false} ->
|
||||
{:error, "Actor id is not an administrator of the selected group"}
|
||||
end
|
||||
end
|
||||
|
||||
def delete_group(_parent, _args, _resolution) do
|
||||
{:error, "You need to be logged-in to delete a group"}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Join an existing group
|
||||
"""
|
||||
def join_group(
|
||||
_parent,
|
||||
%{group_id: group_id, actor_id: actor_id},
|
||||
%{context: %{current_user: user}}
|
||||
) do
|
||||
with {actor_id, ""} <- Integer.parse(actor_id),
|
||||
{group_id, ""} <- Integer.parse(group_id),
|
||||
{:is_owned, %Actor{} = actor} <- User.owns_actor(user, actor_id),
|
||||
{:ok, %Actor{} = group} <- Actors.get_group_by_actor_id(group_id),
|
||||
{:error, :member_not_found} <- Actors.get_member(actor.id, group.id),
|
||||
{:is_able_to_join, true} <- {:is_able_to_join, Member.can_be_joined(group)},
|
||||
role <- Member.get_default_member_role(group),
|
||||
{:ok, _} <- Actors.create_member(%{parent_id: group.id, actor_id: actor.id, role: role}) do
|
||||
{
|
||||
:ok,
|
||||
%{
|
||||
parent: Person.proxify_pictures(group),
|
||||
actor: Person.proxify_pictures(actor),
|
||||
role: role
|
||||
}
|
||||
}
|
||||
else
|
||||
{:is_owned, nil} ->
|
||||
{:error, "Actor id is not owned by authenticated user"}
|
||||
|
||||
{:error, :group_not_found} ->
|
||||
{:error, "Group id not found"}
|
||||
|
||||
{:is_able_to_join, false} ->
|
||||
{:error, "You cannot join this group"}
|
||||
|
||||
{:ok, %Member{}} ->
|
||||
{:error, "You are already a member of this group"}
|
||||
end
|
||||
end
|
||||
|
||||
def join_group(_parent, _args, _resolution) do
|
||||
{:error, "You need to be logged-in to join a group"}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Leave a existing group
|
||||
"""
|
||||
def leave_group(
|
||||
_parent,
|
||||
%{group_id: group_id, actor_id: actor_id},
|
||||
%{context: %{current_user: user}}
|
||||
) do
|
||||
with {actor_id, ""} <- Integer.parse(actor_id),
|
||||
{group_id, ""} <- Integer.parse(group_id),
|
||||
{:is_owned, %Actor{} = actor} <- User.owns_actor(user, actor_id),
|
||||
{:ok, %Member{} = member} <- Actors.get_member(actor.id, group_id),
|
||||
{:only_administrator, false} <-
|
||||
{: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}}}
|
||||
else
|
||||
{:is_owned, nil} ->
|
||||
{:error, "Actor id is not owned by authenticated user"}
|
||||
|
||||
{:error, :member_not_found} ->
|
||||
{:error, "Member not found"}
|
||||
|
||||
{:only_administrator, true} ->
|
||||
{:error, "You can't leave this group because you are the only administrator"}
|
||||
end
|
||||
end
|
||||
|
||||
def leave_group(_parent, _args, _resolution) do
|
||||
{:error, "You need to be logged-in to leave a group"}
|
||||
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}}] ->
|
||||
actor_id == member_actor_id
|
||||
|
||||
_ ->
|
||||
false
|
||||
end
|
||||
end
|
||||
end
|
||||
15
lib/graphql/resolvers/member.ex
Normal file
15
lib/graphql/resolvers/member.ex
Normal file
@@ -0,0 +1,15 @@
|
||||
defmodule Mobilizon.GraphQL.Resolvers.Member do
|
||||
@moduledoc """
|
||||
Handles the member-related GraphQL calls
|
||||
"""
|
||||
alias Mobilizon.Actors
|
||||
alias Mobilizon.Actors.{Actor}
|
||||
|
||||
@doc """
|
||||
Find members for group
|
||||
"""
|
||||
def find_members_for_group(%Actor{} = actor, _args, _resolution) do
|
||||
members = Actors.list_members_for_group(actor)
|
||||
{:ok, members}
|
||||
end
|
||||
end
|
||||
252
lib/graphql/resolvers/person.ex
Normal file
252
lib/graphql/resolvers/person.ex
Normal file
@@ -0,0 +1,252 @@
|
||||
defmodule Mobilizon.GraphQL.Resolvers.Person do
|
||||
@moduledoc """
|
||||
Handles the person-related GraphQL calls
|
||||
"""
|
||||
|
||||
alias Mobilizon.Actors
|
||||
alias Mobilizon.Actors.Actor
|
||||
alias Mobilizon.Events
|
||||
alias Mobilizon.Events.Participant
|
||||
alias Mobilizon.Users
|
||||
alias Mobilizon.Users.User
|
||||
|
||||
alias Mobilizon.Federation.ActivityPub
|
||||
|
||||
@doc """
|
||||
Get a person
|
||||
"""
|
||||
def get_person(_parent, %{id: id}, _resolution) do
|
||||
with %Actor{} = actor <- Actors.get_actor_with_preload(id),
|
||||
actor <- proxify_pictures(actor) do
|
||||
{:ok, actor}
|
||||
else
|
||||
_ ->
|
||||
{:error, "Person with ID #{id} not found"}
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Find a person
|
||||
"""
|
||||
def fetch_person(_parent, %{preferred_username: preferred_username}, _resolution) do
|
||||
with {:ok, %Actor{} = actor} <-
|
||||
ActivityPub.find_or_make_actor_from_nickname(preferred_username),
|
||||
actor <- proxify_pictures(actor) do
|
||||
{:ok, actor}
|
||||
else
|
||||
_ ->
|
||||
{:error, "Person with username #{preferred_username} not found"}
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the current actor for the currently logged-in user
|
||||
"""
|
||||
def get_current_person(_parent, _args, %{context: %{current_user: user}}) do
|
||||
{:ok, Users.get_actor_for_user(user)}
|
||||
end
|
||||
|
||||
def get_current_person(_parent, _args, _resolution) do
|
||||
{:error, "You need to be logged-in to view current person"}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the list of identities for the logged-in user
|
||||
"""
|
||||
def identities(_parent, _args, %{context: %{current_user: user}}) do
|
||||
{:ok, Users.get_actors_for_user(user)}
|
||||
end
|
||||
|
||||
def identities(_parent, _args, _resolution) do
|
||||
{:error, "You need to be logged-in to view your list of identities"}
|
||||
end
|
||||
|
||||
@doc """
|
||||
This function is used to create more identities from an existing user
|
||||
"""
|
||||
def create_person(
|
||||
_parent,
|
||||
%{preferred_username: _preferred_username} = args,
|
||||
%{context: %{current_user: user}} = _resolution
|
||||
) do
|
||||
args = Map.put(args, :user_id, user.id)
|
||||
|
||||
with args <- save_attached_pictures(args),
|
||||
{:ok, %Actor{} = new_person} <- Actors.new_person(args) do
|
||||
{:ok, new_person}
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
This function is used to create more identities from an existing user
|
||||
"""
|
||||
def create_person(_parent, _args, _resolution) do
|
||||
{:error, "You need to be logged-in to create a new identity"}
|
||||
end
|
||||
|
||||
@doc """
|
||||
This function is used to update an existing identity
|
||||
"""
|
||||
def update_person(
|
||||
_parent,
|
||||
%{id: id} = args,
|
||||
%{context: %{current_user: user}} = _resolution
|
||||
) do
|
||||
args = Map.put(args, :user_id, user.id)
|
||||
|
||||
with {:find_actor, %Actor{} = actor} <-
|
||||
{: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, actor}
|
||||
else
|
||||
{:find_actor, nil} ->
|
||||
{:error, "Actor not found"}
|
||||
|
||||
{:is_owned, nil} ->
|
||||
{:error, "Actor is not owned by authenticated user"}
|
||||
end
|
||||
end
|
||||
|
||||
def update_person(_parent, _args, _resolution) do
|
||||
{:error, "You need to be logged-in to update an identity"}
|
||||
end
|
||||
|
||||
@doc """
|
||||
This function is used to delete an existing identity
|
||||
"""
|
||||
def delete_person(
|
||||
_parent,
|
||||
%{id: id} = _args,
|
||||
%{context: %{current_user: user}} = _resolution
|
||||
) do
|
||||
with {:find_actor, %Actor{} = actor} <-
|
||||
{:find_actor, Actors.get_actor(id)},
|
||||
{:is_owned, %Actor{}} <- User.owns_actor(user, actor.id),
|
||||
{:last_identity, false} <- {:last_identity, last_identity?(user)},
|
||||
{:last_admin, false} <- {:last_admin, last_admin_of_a_group?(actor.id)},
|
||||
{:ok, actor} <- Actors.delete_actor(actor) do
|
||||
{:ok, actor}
|
||||
else
|
||||
{:find_actor, nil} ->
|
||||
{:error, "Actor not found"}
|
||||
|
||||
{:last_identity, true} ->
|
||||
{:error, "Cannot remove the last identity of a user"}
|
||||
|
||||
{:last_admin, true} ->
|
||||
{:error, "Cannot remove the last administrator of a group"}
|
||||
|
||||
{:is_owned, nil} ->
|
||||
{:error, "Actor is not owned by authenticated user"}
|
||||
end
|
||||
end
|
||||
|
||||
def delete_person(_parent, _args, _resolution) do
|
||||
{:error, "You need to be logged-in to delete an identity"}
|
||||
end
|
||||
|
||||
defp last_identity?(user) do
|
||||
length(Users.get_actors_for_user(user)) <= 1
|
||||
end
|
||||
|
||||
defp save_attached_pictures(args) do
|
||||
Enum.reduce([:avatar, :banner], args, fn key, args ->
|
||||
if Map.has_key?(args, key) && !is_nil(args[key][:picture]) do
|
||||
pic = args[key][:picture]
|
||||
|
||||
with {:ok, %{name: name, url: url, content_type: content_type, size: _size}} <-
|
||||
MobilizonWeb.Upload.store(pic.file, type: key, description: pic.alt) do
|
||||
Map.put(args, key, %{"name" => name, "url" => url, "mediaType" => content_type})
|
||||
end
|
||||
else
|
||||
args
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
@doc """
|
||||
This function is used to register a person afterwards the user has been created (but not activated)
|
||||
"""
|
||||
def register_person(_parent, args, _resolution) do
|
||||
with {:ok, %User{} = user} <- Users.get_user_by_email(args.email),
|
||||
{:no_actor, nil} <- {:no_actor, Users.get_actor_for_user(user)},
|
||||
args <- Map.put(args, :user_id, user.id),
|
||||
args <- save_attached_pictures(args),
|
||||
{:ok, %Actor{} = new_person} <- Actors.new_person(args) do
|
||||
{:ok, new_person}
|
||||
else
|
||||
{:error, :user_not_found} ->
|
||||
{:error, "No user with this email was found"}
|
||||
|
||||
{:no_actor, _} ->
|
||||
{:error, "You already have a profile for this user"}
|
||||
|
||||
{:error, %Ecto.Changeset{} = e} ->
|
||||
{:error, e}
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the participation for a specific event
|
||||
"""
|
||||
def person_participations(
|
||||
%Actor{id: actor_id},
|
||||
%{event_id: event_id},
|
||||
%{context: %{current_user: user}}
|
||||
) do
|
||||
with {:is_owned, %Actor{} = _actor} <- User.owns_actor(user, actor_id),
|
||||
{:no_participant, {:ok, %Participant{} = participant}} <-
|
||||
{:no_participant, Events.get_participant(event_id, actor_id)} do
|
||||
{:ok, [participant]}
|
||||
else
|
||||
{:is_owned, nil} ->
|
||||
{:error, "Actor id is not owned by authenticated user"}
|
||||
|
||||
{:no_participant, _} ->
|
||||
{:ok, []}
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the list of events this person is going to
|
||||
"""
|
||||
def person_participations(%Actor{id: actor_id}, _args, %{context: %{current_user: user}}) do
|
||||
with {:is_owned, %Actor{} = actor} <- User.owns_actor(user, actor_id),
|
||||
participations <- Events.list_event_participations_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
|
||||
|> proxify_banner
|
||||
end
|
||||
|
||||
# We check that the actor is not the last administrator/creator of a group
|
||||
@spec last_admin_of_a_group?(integer()) :: boolean()
|
||||
defp last_admin_of_a_group?(actor_id) do
|
||||
length(Actors.list_group_ids_where_last_administrator(actor_id)) > 0
|
||||
end
|
||||
|
||||
@spec proxify_avatar(Actor.t()) :: Actor.t()
|
||||
defp proxify_avatar(%Actor{avatar: %{url: avatar_url} = avatar} = actor) do
|
||||
actor |> Map.put(:avatar, avatar |> Map.put(:url, MobilizonWeb.MediaProxy.url(avatar_url)))
|
||||
end
|
||||
|
||||
@spec proxify_avatar(Actor.t()) :: Actor.t()
|
||||
defp proxify_avatar(%Actor{} = actor), do: actor
|
||||
|
||||
@spec proxify_banner(Actor.t()) :: Actor.t()
|
||||
defp proxify_banner(%Actor{banner: %{url: banner_url} = banner} = actor) do
|
||||
actor |> Map.put(:banner, banner |> Map.put(:url, MobilizonWeb.MediaProxy.url(banner_url)))
|
||||
end
|
||||
|
||||
@spec proxify_banner(Actor.t()) :: Actor.t()
|
||||
defp proxify_banner(%Actor{} = actor), do: actor
|
||||
end
|
||||
83
lib/graphql/resolvers/picture.ex
Normal file
83
lib/graphql/resolvers/picture.ex
Normal file
@@ -0,0 +1,83 @@
|
||||
defmodule Mobilizon.GraphQL.Resolvers.Picture do
|
||||
@moduledoc """
|
||||
Handles the picture-related GraphQL calls
|
||||
"""
|
||||
alias Mobilizon.Actors.Actor
|
||||
alias Mobilizon.Media
|
||||
alias Mobilizon.Media.Picture
|
||||
alias Mobilizon.Users.User
|
||||
|
||||
@doc """
|
||||
Get picture for an event's pic
|
||||
"""
|
||||
def picture(%{picture_id: picture_id} = _parent, _args, _resolution) do
|
||||
with {:ok, picture} <- do_fetch_picture(picture_id), do: {:ok, picture}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Get picture for an event that has an attached
|
||||
|
||||
See MobilizonWeb.Resolvers.Event.create_event/3
|
||||
"""
|
||||
def picture(%{picture: picture} = _parent, _args, _resolution), do: {:ok, picture}
|
||||
def picture(_parent, %{id: picture_id}, _resolution), do: do_fetch_picture(picture_id)
|
||||
def picture(_parent, _args, _resolution), do: {:ok, nil}
|
||||
|
||||
@spec do_fetch_picture(nil) :: {:error, nil}
|
||||
defp do_fetch_picture(nil), do: {:error, nil}
|
||||
|
||||
@spec do_fetch_picture(String.t()) :: {:ok, Picture.t()} | {:error, :not_found}
|
||||
defp do_fetch_picture(picture_id) do
|
||||
case Media.get_picture(picture_id) do
|
||||
%Picture{id: id, file: file} ->
|
||||
{:ok,
|
||||
%{
|
||||
name: file.name,
|
||||
url: file.url,
|
||||
id: id,
|
||||
content_type: file.content_type,
|
||||
size: file.size
|
||||
}}
|
||||
|
||||
_error ->
|
||||
{:error, "Picture with ID #{picture_id} was not found"}
|
||||
end
|
||||
end
|
||||
|
||||
@spec upload_picture(map, map, map) :: {:ok, Picture.t()} | {:error, any}
|
||||
def upload_picture(
|
||||
_parent,
|
||||
%{file: %Plug.Upload{} = file, actor_id: actor_id} = args,
|
||||
%{context: %{current_user: user}}
|
||||
) do
|
||||
with {:is_owned, %Actor{}} <- User.owns_actor(user, actor_id),
|
||||
{:ok, %{name: _name, url: url, content_type: content_type, size: size}} <-
|
||||
MobilizonWeb.Upload.store(file),
|
||||
args <-
|
||||
args
|
||||
|> Map.put(:url, url)
|
||||
|> Map.put(:size, size)
|
||||
|> Map.put(:content_type, content_type),
|
||||
{:ok, picture = %Picture{}} <-
|
||||
Media.create_picture(%{"file" => args, "actor_id" => actor_id}) do
|
||||
{:ok,
|
||||
%{
|
||||
name: picture.file.name,
|
||||
url: picture.file.url,
|
||||
id: picture.id,
|
||||
content_type: picture.file.content_type,
|
||||
size: picture.file.size
|
||||
}}
|
||||
else
|
||||
{:is_owned, nil} ->
|
||||
{:error, "Actor id is not owned by authenticated user"}
|
||||
|
||||
error ->
|
||||
{:error, error}
|
||||
end
|
||||
end
|
||||
|
||||
def upload_picture(_parent, _args, _resolution) do
|
||||
{:error, "You need to login to upload a picture"}
|
||||
end
|
||||
end
|
||||
124
lib/graphql/resolvers/report.ex
Normal file
124
lib/graphql/resolvers/report.ex
Normal file
@@ -0,0 +1,124 @@
|
||||
defmodule Mobilizon.GraphQL.Resolvers.Report do
|
||||
@moduledoc """
|
||||
Handles the report-related GraphQL calls.
|
||||
"""
|
||||
|
||||
import Mobilizon.Users.Guards
|
||||
|
||||
alias Mobilizon.Actors
|
||||
alias Mobilizon.Actors.Actor
|
||||
alias Mobilizon.Reports
|
||||
alias Mobilizon.Reports.{Note, Report}
|
||||
alias Mobilizon.Users.User
|
||||
|
||||
alias MobilizonWeb.API.Reports, as: ReportsAPI
|
||||
|
||||
def list_reports(
|
||||
_parent,
|
||||
%{page: page, limit: limit, status: status},
|
||||
%{context: %{current_user: %User{role: role}}}
|
||||
)
|
||||
when is_moderator(role) do
|
||||
{:ok, Mobilizon.Reports.list_reports(page, limit, :updated_at, :desc, status)}
|
||||
end
|
||||
|
||||
def list_reports(_parent, _args, _resolution) do
|
||||
{:error, "You need to be logged-in and a moderator to list reports"}
|
||||
end
|
||||
|
||||
def get_report(_parent, %{id: id}, %{context: %{current_user: %User{role: role}}})
|
||||
when is_moderator(role) do
|
||||
case Mobilizon.Reports.get_report(id) do
|
||||
%Report{} = report ->
|
||||
{:ok, report}
|
||||
|
||||
nil ->
|
||||
{:error, "Report not found"}
|
||||
end
|
||||
end
|
||||
|
||||
def get_report(_parent, _args, _resolution) do
|
||||
{:error, "You need to be logged-in and a moderator to view a report"}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Create a report
|
||||
"""
|
||||
def create_report(
|
||||
_parent,
|
||||
%{reporter_id: reporter_id} = args,
|
||||
%{context: %{current_user: user}} = _resolution
|
||||
) do
|
||||
with {:is_owned, %Actor{}} <- User.owns_actor(user, reporter_id),
|
||||
{:ok, _, %Report{} = report} <- ReportsAPI.report(args) do
|
||||
{:ok, report}
|
||||
else
|
||||
{:is_owned, nil} ->
|
||||
{:error, "Reporter actor id is not owned by authenticated user"}
|
||||
|
||||
_error ->
|
||||
{:error, "Error while saving report"}
|
||||
end
|
||||
end
|
||||
|
||||
def create_report(_parent, _args, _resolution) do
|
||||
{:error, "You need to be logged-in to create reports"}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Update a report's status
|
||||
"""
|
||||
def update_report(
|
||||
_parent,
|
||||
%{report_id: report_id, moderator_id: moderator_id, status: status},
|
||||
%{context: %{current_user: %User{role: role} = user}}
|
||||
)
|
||||
when is_moderator(role) do
|
||||
with {:is_owned, %Actor{} = actor} <- User.owns_actor(user, moderator_id),
|
||||
%Report{} = report <- Mobilizon.Reports.get_report(report_id),
|
||||
{:ok, %Report{} = report} <-
|
||||
MobilizonWeb.API.Reports.update_report_status(actor, report, status) do
|
||||
{:ok, report}
|
||||
else
|
||||
{:is_owned, nil} ->
|
||||
{:error, "Actor id is not owned by authenticated user"}
|
||||
|
||||
_error ->
|
||||
{:error, "Error while updating report"}
|
||||
end
|
||||
end
|
||||
|
||||
def update_report(_parent, _args, _resolution) do
|
||||
{:error, "You need to be logged-in and a moderator to update a report"}
|
||||
end
|
||||
|
||||
def create_report_note(
|
||||
_parent,
|
||||
%{report_id: report_id, moderator_id: moderator_id, content: content},
|
||||
%{context: %{current_user: %User{role: role} = user}}
|
||||
)
|
||||
when is_moderator(role) do
|
||||
with {:is_owned, %Actor{}} <- User.owns_actor(user, moderator_id),
|
||||
%Report{} = report <- Reports.get_report(report_id),
|
||||
%Actor{} = moderator <- Actors.get_local_actor_with_preload(moderator_id),
|
||||
{:ok, %Note{} = note} <-
|
||||
MobilizonWeb.API.Reports.create_report_note(report, moderator, content) do
|
||||
{:ok, note}
|
||||
end
|
||||
end
|
||||
|
||||
def delete_report_note(
|
||||
_parent,
|
||||
%{note_id: note_id, moderator_id: moderator_id},
|
||||
%{context: %{current_user: %User{role: role} = user}}
|
||||
)
|
||||
when is_moderator(role) do
|
||||
with {:is_owned, %Actor{}} <- User.owns_actor(user, moderator_id),
|
||||
%Note{} = note <- Reports.get_note(note_id),
|
||||
%Actor{} = moderator <- Actors.get_local_actor_with_preload(moderator_id),
|
||||
{:ok, %Note{} = note} <-
|
||||
MobilizonWeb.API.Reports.delete_report_note(note, moderator) do
|
||||
{:ok, %{id: note.id}}
|
||||
end
|
||||
end
|
||||
end
|
||||
27
lib/graphql/resolvers/search.ex
Normal file
27
lib/graphql/resolvers/search.ex
Normal file
@@ -0,0 +1,27 @@
|
||||
defmodule Mobilizon.GraphQL.Resolvers.Search do
|
||||
@moduledoc """
|
||||
Handles the event-related GraphQL calls
|
||||
"""
|
||||
alias MobilizonWeb.API.Search
|
||||
|
||||
@doc """
|
||||
Search persons
|
||||
"""
|
||||
def search_persons(_parent, %{search: search, page: page, limit: limit}, _resolution) do
|
||||
Search.search_actors(search, page, limit, :Person)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Search groups
|
||||
"""
|
||||
def search_groups(_parent, %{search: search, page: page, limit: limit}, _resolution) do
|
||||
Search.search_actors(search, page, limit, :Group)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Search events
|
||||
"""
|
||||
def search_events(_parent, %{search: search, page: page, limit: limit}, _resolution) do
|
||||
Search.search_events(search, page, limit)
|
||||
end
|
||||
end
|
||||
48
lib/graphql/resolvers/tag.ex
Normal file
48
lib/graphql/resolvers/tag.ex
Normal file
@@ -0,0 +1,48 @@
|
||||
defmodule Mobilizon.GraphQL.Resolvers.Tag do
|
||||
@moduledoc """
|
||||
Handles the tag-related GraphQL calls
|
||||
"""
|
||||
alias Mobilizon.Events
|
||||
alias Mobilizon.Events.{Event, Tag}
|
||||
|
||||
def list_tags(_parent, %{page: page, limit: limit}, _resolution) do
|
||||
tags = Mobilizon.Events.list_tags(page, limit)
|
||||
|
||||
{:ok, tags}
|
||||
end
|
||||
|
||||
@doc """
|
||||
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)}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Retrieve the list of tags for an event
|
||||
"""
|
||||
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)}
|
||||
end
|
||||
end
|
||||
|
||||
# @doc """
|
||||
# Retrieve the list of related tags for a given tag ID
|
||||
# """
|
||||
# def get_related_tags(_parent, %{tag_id: tag_id}, _resolution) do
|
||||
# with %Tag{} = tag <- Mobilizon.Events.get_tag!(tag_id),
|
||||
# tags <- Mobilizon.Events.list_tag_neighbors(tag) do
|
||||
# {:ok, tags}
|
||||
# end
|
||||
# end
|
||||
|
||||
@doc """
|
||||
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
|
||||
{:ok, tags}
|
||||
end
|
||||
end
|
||||
end
|
||||
301
lib/graphql/resolvers/user.ex
Normal file
301
lib/graphql/resolvers/user.ex
Normal file
@@ -0,0 +1,301 @@
|
||||
defmodule Mobilizon.GraphQL.Resolvers.User do
|
||||
@moduledoc """
|
||||
Handles the user-related GraphQL calls.
|
||||
"""
|
||||
|
||||
import Mobilizon.Users.Guards
|
||||
|
||||
alias Mobilizon.{Actors, Config, Users, Events}
|
||||
alias Mobilizon.Actors.Actor
|
||||
alias Mobilizon.Storage.Repo
|
||||
alias Mobilizon.Users.User
|
||||
|
||||
alias MobilizonWeb.{Auth, Email}
|
||||
|
||||
require Logger
|
||||
|
||||
@doc """
|
||||
Find an user by its ID
|
||||
"""
|
||||
def find_user(_parent, %{id: id}, _resolution) do
|
||||
Users.get_user_with_actors(id)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Return current logged-in user
|
||||
"""
|
||||
def get_current_user(_parent, _args, %{context: %{current_user: user}}) do
|
||||
{:ok, user}
|
||||
end
|
||||
|
||||
def get_current_user(_parent, _args, _resolution) do
|
||||
{:error, "You need to be logged-in to view current user"}
|
||||
end
|
||||
|
||||
@doc """
|
||||
List instance users
|
||||
"""
|
||||
def list_and_count_users(
|
||||
_parent,
|
||||
%{page: page, limit: limit, sort: sort, direction: direction},
|
||||
%{context: %{current_user: %User{role: role}}}
|
||||
)
|
||||
when is_moderator(role) do
|
||||
total = Task.async(&Users.count_users/0)
|
||||
elements = Task.async(fn -> Users.list_users(page, limit, sort, direction) end)
|
||||
|
||||
{:ok, %{total: Task.await(total), elements: Task.await(elements)}}
|
||||
end
|
||||
|
||||
def list_and_count_users(_parent, _args, _resolution) do
|
||||
{:error, "You need to have admin access to list users"}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Login an user. Returns a token and the user
|
||||
"""
|
||||
def login_user(_parent, %{email: email, password: password}, _resolution) do
|
||||
with {:ok, %User{confirmed_at: %DateTime{}} = user} <- Users.get_user_by_email(email),
|
||||
{:ok, %{access_token: access_token, refresh_token: refresh_token}} <-
|
||||
Users.authenticate(%{user: user, password: password}) do
|
||||
{:ok, %{access_token: access_token, refresh_token: refresh_token, user: user}}
|
||||
else
|
||||
{:ok, %User{confirmed_at: nil} = _user} ->
|
||||
{:error, "User account not confirmed"}
|
||||
|
||||
{:error, :user_not_found} ->
|
||||
{:error, "No user with this email was found"}
|
||||
|
||||
{:error, :unauthorized} ->
|
||||
{:error, "Impossible to authenticate, either your email or password are invalid."}
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Refresh a token
|
||||
"""
|
||||
def refresh_token(_parent, %{refresh_token: refresh_token}, _context) do
|
||||
with {:ok, user, _claims} <- Auth.Guardian.resource_from_token(refresh_token),
|
||||
{:ok, _old, {exchanged_token, _claims}} <-
|
||||
Auth.Guardian.exchange(refresh_token, ["access", "refresh"], "access"),
|
||||
{:ok, refresh_token} <- Users.generate_refresh_token(user) do
|
||||
{:ok, %{access_token: exchanged_token, refresh_token: refresh_token}}
|
||||
else
|
||||
{:error, message} ->
|
||||
Logger.debug("Cannot refresh user token: #{inspect(message)}")
|
||||
{:error, "Cannot refresh the token"}
|
||||
end
|
||||
end
|
||||
|
||||
def refresh_token(_parent, _params, _context) do
|
||||
{:error, "You need to have an existing token to get a refresh token"}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Register an user:
|
||||
- check registrations are enabled
|
||||
- create the user
|
||||
- send a validation email to the user
|
||||
"""
|
||||
@spec create_user(any, map, any) :: tuple
|
||||
def create_user(_parent, args, _resolution) do
|
||||
with :registration_ok <- check_registration_config(args),
|
||||
{:ok, %User{} = user} <- Users.register(args) do
|
||||
Email.User.send_confirmation_email(user, Map.get(args, :locale, "en"))
|
||||
{:ok, user}
|
||||
else
|
||||
:registration_closed ->
|
||||
{:error, "Registrations are not enabled"}
|
||||
|
||||
:not_whitelisted ->
|
||||
{:error, "Your email is not on the whitelist"}
|
||||
|
||||
error ->
|
||||
error
|
||||
end
|
||||
end
|
||||
|
||||
@spec check_registration_config(map) :: atom
|
||||
defp check_registration_config(%{email: email}) do
|
||||
cond do
|
||||
Config.instance_registrations_open?() ->
|
||||
:registration_ok
|
||||
|
||||
Config.instance_registrations_whitelist?() ->
|
||||
check_white_listed_email?(email)
|
||||
|
||||
true ->
|
||||
:registration_closed
|
||||
end
|
||||
end
|
||||
|
||||
@spec check_white_listed_email?(String.t()) :: :registration_ok | :not_whitelisted
|
||||
defp check_white_listed_email?(email) do
|
||||
[_, domain] = String.split(email, "@", parts: 2, trim: true)
|
||||
|
||||
if domain in Config.instance_registrations_whitelist() or
|
||||
email in Config.instance_registrations_whitelist(),
|
||||
do: :registration_ok,
|
||||
else: :not_whitelisted
|
||||
end
|
||||
|
||||
@doc """
|
||||
Validate an user, get its actor and a token
|
||||
"""
|
||||
def validate_user(_parent, %{token: token}, _resolution) do
|
||||
with {:check_confirmation_token, {:ok, %User{} = user}} <-
|
||||
{:check_confirmation_token, Email.User.check_confirmation_token(token)},
|
||||
{:get_actor, actor} <- {:get_actor, Users.get_actor_for_user(user)},
|
||||
{:ok, %{access_token: access_token, refresh_token: refresh_token}} <-
|
||||
Users.generate_tokens(user) do
|
||||
{:ok,
|
||||
%{
|
||||
access_token: access_token,
|
||||
refresh_token: refresh_token,
|
||||
user: Map.put(user, :default_actor, actor)
|
||||
}}
|
||||
else
|
||||
error ->
|
||||
Logger.info("Unable to validate user with token #{token}")
|
||||
Logger.debug(inspect(error))
|
||||
|
||||
{:error, "Unable to validate user"}
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Send the confirmation email again.
|
||||
We only do this to accounts unconfirmed
|
||||
"""
|
||||
def resend_confirmation_email(_parent, args, _resolution) do
|
||||
with {:ok, %User{locale: locale} = user} <-
|
||||
Users.get_user_by_email(Map.get(args, :email), false),
|
||||
{:ok, email} <-
|
||||
Email.User.resend_confirmation_email(user, Map.get(args, :locale, locale)) do
|
||||
{:ok, email}
|
||||
else
|
||||
{:error, :user_not_found} ->
|
||||
{:error, "No user to validate with this email was found"}
|
||||
|
||||
{:error, :email_too_soon} ->
|
||||
{:error, "You requested again a confirmation email too soon"}
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Send an email to reset the password from an user
|
||||
"""
|
||||
def send_reset_password(_parent, args, _resolution) do
|
||||
with email <- Map.get(args, :email),
|
||||
{:ok, %User{locale: locale} = user} <- Users.get_user_by_email(email, true),
|
||||
{:ok, %Bamboo.Email{} = _email_html} <-
|
||||
Email.User.send_password_reset_email(user, Map.get(args, :locale, locale)) do
|
||||
{:ok, email}
|
||||
else
|
||||
{:error, :user_not_found} ->
|
||||
# TODO : implement rate limits for this endpoint
|
||||
{:error, "No user with this email was found"}
|
||||
|
||||
{:error, :email_too_soon} ->
|
||||
{:error, "You requested again a confirmation email too soon"}
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Reset the password from an user
|
||||
"""
|
||||
def reset_password(_parent, %{password: password, token: token}, _resolution) do
|
||||
with {:ok, %User{} = user} <-
|
||||
Email.User.check_reset_password_token(password, token),
|
||||
{:ok, %{access_token: access_token, refresh_token: refresh_token}} <-
|
||||
Users.authenticate(%{user: user, password: password}) do
|
||||
{:ok, %{access_token: access_token, refresh_token: refresh_token, user: user}}
|
||||
end
|
||||
end
|
||||
|
||||
@doc "Change an user default actor"
|
||||
def change_default_actor(
|
||||
_parent,
|
||||
%{preferred_username: username},
|
||||
%{context: %{current_user: user}}
|
||||
) do
|
||||
with %Actor{id: actor_id} <- Actors.get_local_actor_by_name(username),
|
||||
{:user_actor, true} <-
|
||||
{:user_actor, actor_id in Enum.map(Users.get_actors_for_user(user), & &1.id)},
|
||||
%User{} = user <- Users.update_user_default_actor(user.id, actor_id) do
|
||||
{:ok, user}
|
||||
else
|
||||
{:user_actor, _} ->
|
||||
{:error, :actor_not_from_user}
|
||||
|
||||
_error ->
|
||||
{:error, :unable_to_change_default_actor}
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the list of events for all of this user's identities are going to
|
||||
"""
|
||||
def user_participations(
|
||||
%User{id: user_id},
|
||||
args,
|
||||
%{context: %{current_user: %User{id: logged_user_id}}}
|
||||
) do
|
||||
with true <- user_id == logged_user_id,
|
||||
participations <-
|
||||
Events.list_participations_for_user(
|
||||
user_id,
|
||||
Map.get(args, :after_datetime),
|
||||
Map.get(args, :before_datetime),
|
||||
Map.get(args, :page),
|
||||
Map.get(args, :limit)
|
||||
) do
|
||||
{:ok, participations}
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the list of draft events for the current user
|
||||
"""
|
||||
def user_drafted_events(
|
||||
%User{id: user_id},
|
||||
args,
|
||||
%{context: %{current_user: %User{id: logged_user_id}}}
|
||||
) do
|
||||
with {:same_user, true} <- {:same_user, user_id == logged_user_id},
|
||||
events <-
|
||||
Events.list_drafts_for_user(user_id, Map.get(args, :page), Map.get(args, :limit)) do
|
||||
{:ok, events}
|
||||
end
|
||||
end
|
||||
|
||||
def change_password(
|
||||
_parent,
|
||||
%{old_password: old_password, new_password: new_password},
|
||||
%{context: %{current_user: %User{password_hash: old_password_hash} = user}}
|
||||
) do
|
||||
with {:current_password, true} <-
|
||||
{:current_password, Argon2.verify_pass(old_password, old_password_hash)},
|
||||
{:same_password, false} <- {:same_password, old_password == new_password},
|
||||
{:ok, %User{} = user} <-
|
||||
user
|
||||
|> User.password_change_changeset(%{"password" => new_password})
|
||||
|> Repo.update() do
|
||||
{:ok, user}
|
||||
else
|
||||
{:current_password, false} ->
|
||||
{:error, "The current password is invalid"}
|
||||
|
||||
{:same_password, true} ->
|
||||
{:error, "The new password must be different"}
|
||||
|
||||
{:error, %Ecto.Changeset{errors: [password: {"registration.error.password_too_short", _}]}} ->
|
||||
{:error,
|
||||
"The password you have chosen is too short. Please make sure your password contains at least 6 characters."}
|
||||
end
|
||||
end
|
||||
|
||||
def change_password(_parent, _args, _resolution) do
|
||||
{:error, "You need to be logged-in to change your password"}
|
||||
end
|
||||
end
|
||||
154
lib/graphql/schema.ex
Normal file
154
lib/graphql/schema.ex
Normal file
@@ -0,0 +1,154 @@
|
||||
defmodule Mobilizon.GraphQL.Schema do
|
||||
@moduledoc """
|
||||
GraphQL schema representation.
|
||||
"""
|
||||
|
||||
use Absinthe.Schema
|
||||
|
||||
alias Mobilizon.{Actors, Addresses, Events, Media, Reports, Users}
|
||||
alias Mobilizon.Actors.{Actor, Follower, Member}
|
||||
alias Mobilizon.Events.{Comment, Event, Participant}
|
||||
alias Mobilizon.GraphQL.Schema
|
||||
alias Mobilizon.Storage.Repo
|
||||
|
||||
import_types(Absinthe.Type.Custom)
|
||||
import_types(Absinthe.Plug.Types)
|
||||
import_types(Schema.Custom.UUID)
|
||||
import_types(Schema.Custom.Point)
|
||||
|
||||
import_types(Schema.UserType)
|
||||
import_types(Schema.PictureType)
|
||||
import_types(Schema.ActorInterface)
|
||||
import_types(Schema.Actors.PersonType)
|
||||
import_types(Schema.Actors.GroupType)
|
||||
import_types(Schema.Actors.ApplicationType)
|
||||
import_types(Schema.CommentType)
|
||||
import_types(Schema.SearchType)
|
||||
import_types(Schema.ConfigType)
|
||||
import_types(Schema.ReportType)
|
||||
import_types(Schema.AdminType)
|
||||
|
||||
@desc "A struct containing the id of the deleted object"
|
||||
object :deleted_object do
|
||||
field(:id, :id)
|
||||
end
|
||||
|
||||
@desc "A JWT and the associated user ID"
|
||||
object :login do
|
||||
field(:access_token, non_null(:string), description: "A JWT Token for this session")
|
||||
|
||||
field(:refresh_token, non_null(:string),
|
||||
description: "A JWT Token to refresh the access token"
|
||||
)
|
||||
|
||||
field(:user, non_null(:user), description: "The user associated to this session")
|
||||
end
|
||||
|
||||
@desc """
|
||||
Represents a notification for an user
|
||||
"""
|
||||
object :notification do
|
||||
field(:id, :id, description: "The notification ID")
|
||||
field(:user, :user, description: "The user to transmit the notification to")
|
||||
field(:actor, :actor, description: "The notification target profile")
|
||||
|
||||
field(:activity_type, :integer,
|
||||
description:
|
||||
"Whether the notification is about a follow, group join, event change or comment"
|
||||
)
|
||||
|
||||
field(:target_object, :object, description: "The object responsible for the notification")
|
||||
field(:summary, :string, description: "Text inside the notification")
|
||||
field(:seen, :boolean, description: "Whether or not the notification was seen by the user")
|
||||
field(:published, :datetime, description: "Datetime when the notification was published")
|
||||
end
|
||||
|
||||
union :object do
|
||||
types([:event, :person, :group, :comment, :follower, :member, :participant])
|
||||
|
||||
resolve_type(fn
|
||||
%Actor{type: :Person}, _ ->
|
||||
:person
|
||||
|
||||
%Actor{type: :Group}, _ ->
|
||||
:group
|
||||
|
||||
%Event{}, _ ->
|
||||
:event
|
||||
|
||||
%Comment{}, _ ->
|
||||
:comment
|
||||
|
||||
%Follower{}, _ ->
|
||||
:follower
|
||||
|
||||
%Member{}, _ ->
|
||||
:member
|
||||
|
||||
%Participant{}, _ ->
|
||||
:participant
|
||||
end)
|
||||
end
|
||||
|
||||
def context(ctx) do
|
||||
default_query = fn queryable, _params -> queryable end
|
||||
default_source = Dataloader.Ecto.new(Repo, query: default_query)
|
||||
|
||||
loader =
|
||||
Dataloader.new()
|
||||
|> Dataloader.add_source(Actors, default_source)
|
||||
|> Dataloader.add_source(Users, default_source)
|
||||
|> Dataloader.add_source(Events, Events.data())
|
||||
|> Dataloader.add_source(Addresses, default_source)
|
||||
|> Dataloader.add_source(Media, default_source)
|
||||
|> Dataloader.add_source(Reports, default_source)
|
||||
|
||||
Map.put(ctx, :loader, loader)
|
||||
end
|
||||
|
||||
def plugins do
|
||||
[Absinthe.Middleware.Dataloader] ++ Absinthe.Plugin.defaults()
|
||||
end
|
||||
|
||||
@desc """
|
||||
Root Query
|
||||
"""
|
||||
query do
|
||||
import_fields(:search_queries)
|
||||
import_fields(:user_queries)
|
||||
import_fields(:person_queries)
|
||||
import_fields(:group_queries)
|
||||
import_fields(:event_queries)
|
||||
import_fields(:comment_queries)
|
||||
import_fields(:tag_queries)
|
||||
import_fields(:address_queries)
|
||||
import_fields(:config_queries)
|
||||
import_fields(:picture_queries)
|
||||
import_fields(:report_queries)
|
||||
import_fields(:admin_queries)
|
||||
end
|
||||
|
||||
@desc """
|
||||
Root Mutation
|
||||
"""
|
||||
mutation do
|
||||
import_fields(:user_mutations)
|
||||
import_fields(:person_mutations)
|
||||
import_fields(:group_mutations)
|
||||
import_fields(:event_mutations)
|
||||
import_fields(:comment_mutations)
|
||||
import_fields(:participant_mutations)
|
||||
import_fields(:member_mutations)
|
||||
import_fields(:feed_token_mutations)
|
||||
import_fields(:picture_mutations)
|
||||
import_fields(:report_mutations)
|
||||
import_fields(:admin_mutations)
|
||||
end
|
||||
|
||||
@desc """
|
||||
Root subscription
|
||||
"""
|
||||
subscription do
|
||||
import_fields(:person_subscriptions)
|
||||
end
|
||||
end
|
||||
62
lib/graphql/schema/actor.ex
Normal file
62
lib/graphql/schema/actor.ex
Normal file
@@ -0,0 +1,62 @@
|
||||
defmodule Mobilizon.GraphQL.Schema.ActorInterface do
|
||||
@moduledoc """
|
||||
Schema representation for Actor
|
||||
"""
|
||||
use Absinthe.Schema.Notation
|
||||
|
||||
alias Mobilizon.Actors.Actor
|
||||
alias Mobilizon.GraphQL.Schema
|
||||
|
||||
import_types(Schema.Actors.FollowerType)
|
||||
import_types(Schema.EventType)
|
||||
|
||||
@desc "An ActivityPub actor"
|
||||
interface :actor do
|
||||
field(:id, :id, description: "Internal ID for this actor")
|
||||
field(:url, :string, description: "The ActivityPub actor's URL")
|
||||
field(:type, :actor_type, description: "The type of Actor (Person, Group,…)")
|
||||
field(:name, :string, description: "The actor's displayed name")
|
||||
field(:domain, :string, description: "The actor's domain if (null if it's this instance)")
|
||||
field(:local, :boolean, description: "If the actor is from this instance")
|
||||
field(:summary, :string, description: "The actor's summary")
|
||||
field(:preferred_username, :string, description: "The actor's preferred username")
|
||||
|
||||
field(:manually_approves_followers, :boolean,
|
||||
description: "Whether the actors manually approves followers"
|
||||
)
|
||||
|
||||
field(:suspended, :boolean, description: "If the actor is suspended")
|
||||
|
||||
field(:avatar, :picture, description: "The actor's avatar picture")
|
||||
field(:banner, :picture, description: "The actor's banner picture")
|
||||
|
||||
# These one should have a privacy setting
|
||||
field(:following, list_of(:follower), description: "List of followings")
|
||||
field(:followers, list_of(:follower), description: "List of followers")
|
||||
field(:followersCount, :integer, description: "Number of followers for this actor")
|
||||
field(:followingCount, :integer, description: "Number of actors following this actor")
|
||||
|
||||
resolve_type(fn
|
||||
%Actor{type: :Person}, _ ->
|
||||
:person
|
||||
|
||||
%Actor{type: :Group}, _ ->
|
||||
:group
|
||||
|
||||
%Actor{type: :Application}, _ ->
|
||||
:application
|
||||
|
||||
_, _ ->
|
||||
nil
|
||||
end)
|
||||
end
|
||||
|
||||
@desc "The list of types an actor can be"
|
||||
enum :actor_type do
|
||||
value(:Person, description: "An ActivityPub Person")
|
||||
value(:Application, description: "An ActivityPub Application")
|
||||
value(:Group, description: "An ActivityPub Group")
|
||||
value(:Organization, description: "An ActivityPub Organization")
|
||||
value(:Service, description: "An ActivityPub Service")
|
||||
end
|
||||
end
|
||||
38
lib/graphql/schema/actors/application.ex
Normal file
38
lib/graphql/schema/actors/application.ex
Normal file
@@ -0,0 +1,38 @@
|
||||
defmodule Mobilizon.GraphQL.Schema.Actors.ApplicationType do
|
||||
@moduledoc """
|
||||
Schema representation for Group.
|
||||
"""
|
||||
|
||||
use Absinthe.Schema.Notation
|
||||
|
||||
@desc """
|
||||
Represents an application
|
||||
"""
|
||||
object :application do
|
||||
interfaces([:actor])
|
||||
|
||||
field(:id, :id, description: "Internal ID for this application")
|
||||
field(:url, :string, description: "The ActivityPub actor's URL")
|
||||
field(:type, :actor_type, description: "The type of Actor (Person, Group,…)")
|
||||
field(:name, :string, description: "The actor's displayed name")
|
||||
field(:domain, :string, description: "The actor's domain if (null if it's this instance)")
|
||||
field(:local, :boolean, description: "If the actor is from this instance")
|
||||
field(:summary, :string, description: "The actor's summary")
|
||||
field(:preferred_username, :string, description: "The actor's preferred username")
|
||||
|
||||
field(:manually_approves_followers, :boolean,
|
||||
description: "Whether the actors manually approves followers"
|
||||
)
|
||||
|
||||
field(:suspended, :boolean, description: "If the actor is suspended")
|
||||
|
||||
field(:avatar, :picture, description: "The actor's avatar picture")
|
||||
field(:banner, :picture, description: "The actor's banner picture")
|
||||
|
||||
# These one should have a privacy setting
|
||||
field(:following, list_of(:follower), description: "List of followings")
|
||||
field(:followers, list_of(:follower), description: "List of followers")
|
||||
field(:followersCount, :integer, description: "Number of followers for this actor")
|
||||
field(:followingCount, :integer, description: "Number of actors following this actor")
|
||||
end
|
||||
end
|
||||
26
lib/graphql/schema/actors/follower.ex
Normal file
26
lib/graphql/schema/actors/follower.ex
Normal file
@@ -0,0 +1,26 @@
|
||||
defmodule Mobilizon.GraphQL.Schema.Actors.FollowerType do
|
||||
@moduledoc """
|
||||
Schema representation for Follower
|
||||
"""
|
||||
use Absinthe.Schema.Notation
|
||||
|
||||
@desc """
|
||||
Represents an actor's follower
|
||||
"""
|
||||
object :follower do
|
||||
field(:target_actor, :actor, description: "What or who the profile follows")
|
||||
field(:actor, :actor, description: "Which profile follows")
|
||||
|
||||
field(:approved, :boolean,
|
||||
description: "Whether the follow has been approved by the target actor"
|
||||
)
|
||||
|
||||
field(:inserted_at, :datetime, description: "When the follow was created")
|
||||
field(:updated_at, :datetime, description: "When the follow was updated")
|
||||
end
|
||||
|
||||
object :paginated_follower_list do
|
||||
field(:elements, list_of(:follower), description: "A list of followers")
|
||||
field(:total, :integer, description: "The total number of elements in the list")
|
||||
end
|
||||
end
|
||||
129
lib/graphql/schema/actors/group.ex
Normal file
129
lib/graphql/schema/actors/group.ex
Normal file
@@ -0,0 +1,129 @@
|
||||
defmodule Mobilizon.GraphQL.Schema.Actors.GroupType do
|
||||
@moduledoc """
|
||||
Schema representation for Group.
|
||||
"""
|
||||
|
||||
use Absinthe.Schema.Notation
|
||||
|
||||
import Absinthe.Resolution.Helpers, only: [dataloader: 1]
|
||||
|
||||
alias Mobilizon.Events
|
||||
alias Mobilizon.GraphQL.Resolvers.{Group, Member}
|
||||
alias Mobilizon.GraphQL.Schema
|
||||
|
||||
import_types(Schema.Actors.MemberType)
|
||||
|
||||
@desc """
|
||||
Represents a group of actors
|
||||
"""
|
||||
object :group do
|
||||
interfaces([:actor])
|
||||
|
||||
field(:id, :id, description: "Internal ID for this group")
|
||||
field(:url, :string, description: "The ActivityPub actor's URL")
|
||||
field(:type, :actor_type, description: "The type of Actor (Person, Group,…)")
|
||||
field(:name, :string, description: "The actor's displayed name")
|
||||
field(:domain, :string, description: "The actor's domain if (null if it's this instance)")
|
||||
field(:local, :boolean, description: "If the actor is from this instance")
|
||||
field(:summary, :string, description: "The actor's summary")
|
||||
field(:preferred_username, :string, description: "The actor's preferred username")
|
||||
|
||||
field(:manually_approves_followers, :boolean,
|
||||
description: "Whether the actors manually approves followers"
|
||||
)
|
||||
|
||||
field(:suspended, :boolean, description: "If the actor is suspended")
|
||||
|
||||
field(:avatar, :picture, description: "The actor's avatar picture")
|
||||
field(:banner, :picture, description: "The actor's banner picture")
|
||||
|
||||
# These one should have a privacy setting
|
||||
field(:following, list_of(:follower), description: "List of followings")
|
||||
field(:followers, list_of(:follower), description: "List of followers")
|
||||
field(:followersCount, :integer, description: "Number of followers for this actor")
|
||||
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(:types, :group_type, description: "The type of group : Group, Community,…")
|
||||
|
||||
field(:openness, :openness,
|
||||
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"
|
||||
)
|
||||
end
|
||||
|
||||
@desc """
|
||||
The types of Group that exist
|
||||
"""
|
||||
enum :group_type do
|
||||
value(:group, description: "A private group of persons")
|
||||
value(:community, description: "A public group of many actors")
|
||||
end
|
||||
|
||||
@desc """
|
||||
Describes how an actor is opened to follows
|
||||
"""
|
||||
enum :openness do
|
||||
value(:invite_only, description: "The actor can only be followed by invitation")
|
||||
|
||||
value(:moderated, description: "The actor needs to accept the following before it's effective")
|
||||
|
||||
value(:open, description: "The actor is open to followings")
|
||||
end
|
||||
|
||||
object :group_queries do
|
||||
@desc "Get all groups"
|
||||
field :groups, list_of(:group) do
|
||||
arg(:page, :integer, default_value: 1)
|
||||
arg(:limit, :integer, default_value: 10)
|
||||
resolve(&Group.list_groups/3)
|
||||
end
|
||||
|
||||
@desc "Get a group by its preferred username"
|
||||
field :group, :group do
|
||||
arg(:preferred_username, non_null(:string))
|
||||
resolve(&Group.find_group/3)
|
||||
end
|
||||
end
|
||||
|
||||
object :group_mutations do
|
||||
@desc "Create a group"
|
||||
field :create_group, :group do
|
||||
arg(:preferred_username, non_null(:string), description: "The name for the group")
|
||||
|
||||
arg(:creator_actor_id, non_null(:id), description: "The identity that creates the group")
|
||||
|
||||
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.create_group/3)
|
||||
end
|
||||
|
||||
@desc "Delete a group"
|
||||
field :delete_group, :deleted_object do
|
||||
arg(:group_id, non_null(:id))
|
||||
arg(:actor_id, non_null(:id))
|
||||
|
||||
resolve(&Group.delete_group/3)
|
||||
end
|
||||
end
|
||||
end
|
||||
41
lib/graphql/schema/actors/member.ex
Normal file
41
lib/graphql/schema/actors/member.ex
Normal file
@@ -0,0 +1,41 @@
|
||||
defmodule Mobilizon.GraphQL.Schema.Actors.MemberType do
|
||||
@moduledoc """
|
||||
Schema representation for Member
|
||||
"""
|
||||
use Absinthe.Schema.Notation
|
||||
|
||||
alias Mobilizon.GraphQL.Resolvers.Group
|
||||
|
||||
@desc """
|
||||
Represents a member of a group
|
||||
"""
|
||||
object :member do
|
||||
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")
|
||||
end
|
||||
|
||||
@desc "Represents a deleted member"
|
||||
object :deleted_member do
|
||||
field(:parent, :deleted_object)
|
||||
field(:actor, :deleted_object)
|
||||
end
|
||||
|
||||
object :member_mutations do
|
||||
@desc "Join a group"
|
||||
field :join_group, :member do
|
||||
arg(:group_id, non_null(:id))
|
||||
arg(:actor_id, non_null(:id))
|
||||
|
||||
resolve(&Group.join_group/3)
|
||||
end
|
||||
|
||||
@desc "Leave an event"
|
||||
field :leave_group, :deleted_member do
|
||||
arg(:group_id, non_null(:id))
|
||||
arg(:actor_id, non_null(:id))
|
||||
|
||||
resolve(&Group.leave_group/3)
|
||||
end
|
||||
end
|
||||
end
|
||||
175
lib/graphql/schema/actors/person.ex
Normal file
175
lib/graphql/schema/actors/person.ex
Normal file
@@ -0,0 +1,175 @@
|
||||
defmodule Mobilizon.GraphQL.Schema.Actors.PersonType do
|
||||
@moduledoc """
|
||||
Schema representation for Person
|
||||
"""
|
||||
use Absinthe.Schema.Notation
|
||||
|
||||
import Absinthe.Resolution.Helpers, only: [dataloader: 1]
|
||||
import Mobilizon.GraphQL.Helpers.Error
|
||||
|
||||
alias Mobilizon.Events
|
||||
alias Mobilizon.GraphQL.Resolvers.Person
|
||||
alias Mobilizon.GraphQL.Schema
|
||||
|
||||
import_types(Schema.Events.FeedTokenType)
|
||||
|
||||
@desc """
|
||||
Represents a person identity
|
||||
"""
|
||||
object :person do
|
||||
interfaces([:actor])
|
||||
field(:id, :id, description: "Internal ID for this person")
|
||||
field(:user, :user, description: "The user this actor is associated to")
|
||||
|
||||
field(:member_of, list_of(:member), description: "The list of groups this person is member of")
|
||||
|
||||
field(:url, :string, description: "The ActivityPub actor's URL")
|
||||
field(:type, :actor_type, description: "The type of Actor (Person, Group,…)")
|
||||
field(:name, :string, description: "The actor's displayed name")
|
||||
field(:domain, :string, description: "The actor's domain if (null if it's this instance)")
|
||||
field(:local, :boolean, description: "If the actor is from this instance")
|
||||
field(:summary, :string, description: "The actor's summary")
|
||||
field(:preferred_username, :string, description: "The actor's preferred username")
|
||||
|
||||
field(:manually_approves_followers, :boolean,
|
||||
description: "Whether the actors manually approves followers"
|
||||
)
|
||||
|
||||
field(:suspended, :boolean, description: "If the actor is suspended")
|
||||
|
||||
field(:avatar, :picture, description: "The actor's avatar picture")
|
||||
field(:banner, :picture, description: "The actor's banner picture")
|
||||
|
||||
# These one should have a privacy setting
|
||||
field(:following, list_of(:follower), description: "List of followings")
|
||||
field(:followers, list_of(:follower), description: "List of followers")
|
||||
field(:followersCount, :integer, description: "Number of followers for this actor")
|
||||
field(:followingCount, :integer, description: "Number of actors following this actor")
|
||||
|
||||
field(:feed_tokens, list_of(:feed_token),
|
||||
resolve: dataloader(Events),
|
||||
description: "A list of the feed tokens for this person"
|
||||
)
|
||||
|
||||
# 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"
|
||||
)
|
||||
|
||||
@desc "The list of events this person goes to"
|
||||
field(:participations, list_of(:participant),
|
||||
description: "The list of events this person goes to"
|
||||
) do
|
||||
arg(:event_id, :id)
|
||||
resolve(&Person.person_participations/3)
|
||||
end
|
||||
end
|
||||
|
||||
object :person_queries do
|
||||
@desc "Get the current actor for the logged-in user"
|
||||
field :logged_person, :person do
|
||||
resolve(&Person.get_current_person/3)
|
||||
end
|
||||
|
||||
@desc "Get a person by its (federated) username"
|
||||
field :fetch_person, :person do
|
||||
arg(:preferred_username, non_null(:string))
|
||||
resolve(&Person.fetch_person/3)
|
||||
end
|
||||
|
||||
@desc "Get a person by its ID"
|
||||
field :person, :person do
|
||||
arg(:id, non_null(:id))
|
||||
resolve(&Person.get_person/3)
|
||||
end
|
||||
|
||||
@desc "Get the persons for an user"
|
||||
field :identities, list_of(:person) do
|
||||
resolve(&Person.identities/3)
|
||||
end
|
||||
end
|
||||
|
||||
object :person_mutations do
|
||||
@desc "Create a new person for user"
|
||||
field :create_person, :person do
|
||||
arg(:preferred_username, non_null(:string))
|
||||
|
||||
arg(:name, :string, description: "The displayed name for the new profile", default_value: "")
|
||||
|
||||
arg(:summary, :string, description: "The summary for the new profile", default_value: "")
|
||||
|
||||
arg(:avatar, :picture_input,
|
||||
description:
|
||||
"The avatar for the profile, either as an object or directly the ID of an existing Picture"
|
||||
)
|
||||
|
||||
arg(:banner, :picture_input,
|
||||
description:
|
||||
"The banner for the profile, either as an object or directly the ID of an existing Picture"
|
||||
)
|
||||
|
||||
resolve(handle_errors(&Person.create_person/3))
|
||||
end
|
||||
|
||||
@desc "Update an identity"
|
||||
field :update_person, :person do
|
||||
arg(:id, non_null(:id))
|
||||
|
||||
arg(:name, :string, description: "The displayed name for this profile")
|
||||
|
||||
arg(:summary, :string, description: "The summary for this profile")
|
||||
|
||||
arg(:avatar, :picture_input,
|
||||
description:
|
||||
"The avatar for the profile, either as an object or directly the ID of an existing Picture"
|
||||
)
|
||||
|
||||
arg(:banner, :picture_input,
|
||||
description:
|
||||
"The banner for the profile, either as an object or directly the ID of an existing Picture"
|
||||
)
|
||||
|
||||
resolve(handle_errors(&Person.update_person/3))
|
||||
end
|
||||
|
||||
@desc "Delete an identity"
|
||||
field :delete_person, :person do
|
||||
arg(:id, non_null(:id))
|
||||
|
||||
resolve(handle_errors(&Person.delete_person/3))
|
||||
end
|
||||
|
||||
@desc "Register a first profile on registration"
|
||||
field :register_person, :person do
|
||||
arg(:preferred_username, non_null(:string))
|
||||
|
||||
arg(:name, :string, description: "The displayed name for the new profile", default_value: "")
|
||||
|
||||
arg(:summary, :string, description: "The summary for the new profile", default_value: "")
|
||||
arg(:email, non_null(:string), description: "The email from the user previously created")
|
||||
|
||||
arg(:avatar, :picture_input,
|
||||
description:
|
||||
"The avatar for the profile, either as an object or directly the ID of an existing Picture"
|
||||
)
|
||||
|
||||
arg(:banner, :picture_input,
|
||||
description:
|
||||
"The banner for the profile, either as an object or directly the ID of an existing Picture"
|
||||
)
|
||||
|
||||
resolve(handle_errors(&Person.register_person/3))
|
||||
end
|
||||
end
|
||||
|
||||
object :person_subscriptions do
|
||||
field :event_person_participation_changed, :person do
|
||||
arg(:person_id, non_null(:id))
|
||||
|
||||
config(fn args, _ ->
|
||||
{:ok, topic: args.person_id}
|
||||
end)
|
||||
end
|
||||
end
|
||||
end
|
||||
69
lib/graphql/schema/address.ex
Normal file
69
lib/graphql/schema/address.ex
Normal file
@@ -0,0 +1,69 @@
|
||||
defmodule Mobilizon.GraphQL.Schema.AddressType do
|
||||
@moduledoc """
|
||||
Schema representation for Address
|
||||
"""
|
||||
use Absinthe.Schema.Notation
|
||||
|
||||
alias Mobilizon.GraphQL.Resolvers.Address
|
||||
|
||||
object :address do
|
||||
field(:geom, :point, description: "The geocoordinates for the point where this address is")
|
||||
field(:street, :string, description: "The address's street name (with number)")
|
||||
field(:locality, :string, description: "The address's locality")
|
||||
field(:postal_code, :string)
|
||||
field(:region, :string)
|
||||
field(:country, :string)
|
||||
field(:description, :string)
|
||||
field(:type, :string)
|
||||
field(:url, :string)
|
||||
field(:id, :id)
|
||||
field(:origin_id, :string)
|
||||
end
|
||||
|
||||
object :phone_address do
|
||||
field(:phone, :string)
|
||||
field(:info, :string)
|
||||
end
|
||||
|
||||
object :online_address do
|
||||
field(:url, :string)
|
||||
field(:info, :string)
|
||||
end
|
||||
|
||||
input_object :address_input do
|
||||
# Either a full picture object
|
||||
field(:geom, :point, description: "The geocoordinates for the point where this address is")
|
||||
field(:street, :string, description: "The address's street name (with number)")
|
||||
field(:locality, :string, description: "The address's locality")
|
||||
field(:postal_code, :string)
|
||||
field(:region, :string)
|
||||
field(:country, :string)
|
||||
field(:description, :string)
|
||||
field(:url, :string)
|
||||
field(:type, :string)
|
||||
field(:id, :id)
|
||||
field(:origin_id, :string)
|
||||
end
|
||||
|
||||
object :address_queries do
|
||||
@desc "Search for an address"
|
||||
field :search_address, type: list_of(:address) do
|
||||
arg(:query, non_null(:string))
|
||||
arg(:locale, :string, default_value: "en")
|
||||
arg(:page, :integer, default_value: 1)
|
||||
arg(:limit, :integer, default_value: 10)
|
||||
|
||||
resolve(&Address.search/3)
|
||||
end
|
||||
|
||||
@desc "Reverse geocode coordinates"
|
||||
field :reverse_geocode, type: list_of(:address) do
|
||||
arg(:longitude, non_null(:float))
|
||||
arg(:latitude, non_null(:float))
|
||||
arg(:zoom, :integer, default_value: 15)
|
||||
arg(:locale, :string, default_value: "en")
|
||||
|
||||
resolve(&Address.reverse_geocode/3)
|
||||
end
|
||||
end
|
||||
end
|
||||
119
lib/graphql/schema/admin.ex
Normal file
119
lib/graphql/schema/admin.ex
Normal file
@@ -0,0 +1,119 @@
|
||||
defmodule Mobilizon.GraphQL.Schema.AdminType do
|
||||
@moduledoc """
|
||||
Schema representation for ActionLog.
|
||||
"""
|
||||
|
||||
use Absinthe.Schema.Notation
|
||||
|
||||
alias Mobilizon.Events.{Event, Comment}
|
||||
alias Mobilizon.Reports.{Note, Report}
|
||||
|
||||
alias Mobilizon.GraphQL.Resolvers.Admin
|
||||
|
||||
@desc "An action log"
|
||||
object :action_log do
|
||||
field(:id, :id, description: "Internal ID for this comment")
|
||||
field(:actor, :actor, description: "The actor that acted")
|
||||
field(:object, :action_log_object, description: "The object that was acted upon")
|
||||
field(:action, :action_log_action, description: "The action that was done")
|
||||
field(:inserted_at, :datetime, description: "The time when the action was performed")
|
||||
end
|
||||
|
||||
enum :action_log_action do
|
||||
value(:report_update_closed)
|
||||
value(:report_update_opened)
|
||||
value(:report_update_resolved)
|
||||
value(:note_creation)
|
||||
value(:note_deletion)
|
||||
value(:event_deletion)
|
||||
value(:comment_deletion)
|
||||
value(:event_update)
|
||||
end
|
||||
|
||||
@desc "The objects that can be in an action log"
|
||||
interface :action_log_object do
|
||||
field(:id, :id, description: "Internal ID for this object")
|
||||
|
||||
resolve_type(fn
|
||||
%Report{}, _ ->
|
||||
:report
|
||||
|
||||
%Note{}, _ ->
|
||||
:report_note
|
||||
|
||||
%Event{}, _ ->
|
||||
:event
|
||||
|
||||
%Comment{}, _ ->
|
||||
:comment
|
||||
|
||||
_, _ ->
|
||||
nil
|
||||
end)
|
||||
end
|
||||
|
||||
object :dashboard do
|
||||
field(:last_public_event_published, :event, description: "Last public event publish")
|
||||
field(:number_of_users, :integer, description: "The number of local users")
|
||||
field(:number_of_events, :integer, description: "The number of local events")
|
||||
field(:number_of_comments, :integer, description: "The number of local comments")
|
||||
field(:number_of_reports, :integer, description: "The number of current opened reports")
|
||||
end
|
||||
|
||||
object :admin_queries do
|
||||
@desc "Get the list of action logs"
|
||||
field :action_logs, type: list_of(:action_log) do
|
||||
arg(:page, :integer, default_value: 1)
|
||||
arg(:limit, :integer, default_value: 10)
|
||||
resolve(&Admin.list_action_logs/3)
|
||||
end
|
||||
|
||||
field :dashboard, type: :dashboard do
|
||||
resolve(&Admin.get_dashboard/3)
|
||||
end
|
||||
|
||||
field :relay_followers, type: :paginated_follower_list do
|
||||
arg(:page, :integer, default_value: 1)
|
||||
arg(:limit, :integer, default_value: 10)
|
||||
resolve(&Admin.list_relay_followers/3)
|
||||
end
|
||||
|
||||
field :relay_followings, type: :paginated_follower_list do
|
||||
arg(:page, :integer, default_value: 1)
|
||||
arg(:limit, :integer, default_value: 10)
|
||||
arg(:order_by, :string, default_value: :updated_at)
|
||||
arg(:direction, :string, default_value: :desc)
|
||||
resolve(&Admin.list_relay_followings/3)
|
||||
end
|
||||
end
|
||||
|
||||
object :admin_mutations do
|
||||
@desc "Add a relay subscription"
|
||||
field :add_relay, type: :follower do
|
||||
arg(:address, non_null(:string))
|
||||
|
||||
resolve(&Admin.create_relay/3)
|
||||
end
|
||||
|
||||
@desc "Delete a relay subscription"
|
||||
field :remove_relay, type: :follower do
|
||||
arg(:address, non_null(:string))
|
||||
|
||||
resolve(&Admin.remove_relay/3)
|
||||
end
|
||||
|
||||
@desc "Accept a relay subscription"
|
||||
field :accept_relay, type: :follower do
|
||||
arg(:address, non_null(:string))
|
||||
|
||||
resolve(&Admin.accept_subscription/3)
|
||||
end
|
||||
|
||||
@desc "Reject a relay subscription"
|
||||
field :reject_relay, type: :follower do
|
||||
arg(:address, non_null(:string))
|
||||
|
||||
resolve(&Admin.reject_subscription/3)
|
||||
end
|
||||
end
|
||||
end
|
||||
77
lib/graphql/schema/comment.ex
Normal file
77
lib/graphql/schema/comment.ex
Normal file
@@ -0,0 +1,77 @@
|
||||
defmodule Mobilizon.GraphQL.Schema.CommentType do
|
||||
@moduledoc """
|
||||
Schema representation for Comment
|
||||
"""
|
||||
use Absinthe.Schema.Notation
|
||||
|
||||
import Absinthe.Resolution.Helpers, only: [dataloader: 1]
|
||||
|
||||
alias Mobilizon.{Actors, Events}
|
||||
alias Mobilizon.GraphQL.Resolvers.Comment
|
||||
|
||||
@desc "A comment"
|
||||
object :comment do
|
||||
interfaces([:action_log_object])
|
||||
field(:id, :id, description: "Internal ID for this comment")
|
||||
field(:uuid, :uuid)
|
||||
field(:url, :string)
|
||||
field(:local, :boolean)
|
||||
field(:visibility, :comment_visibility)
|
||||
field(:text, :string)
|
||||
field(:primaryLanguage, :string)
|
||||
|
||||
field(:replies, list_of(:comment)) do
|
||||
resolve(dataloader(Events))
|
||||
end
|
||||
|
||||
field(:total_replies, :integer)
|
||||
field(:in_reply_to_comment, :comment, resolve: dataloader(Events))
|
||||
field(:event, :event, resolve: dataloader(Events))
|
||||
field(:origin_comment, :comment, resolve: dataloader(Events))
|
||||
field(:threadLanguages, non_null(list_of(:string)))
|
||||
field(:actor, :person, resolve: dataloader(Actors))
|
||||
field(:inserted_at, :datetime)
|
||||
field(:updated_at, :datetime)
|
||||
field(:deleted_at, :datetime)
|
||||
end
|
||||
|
||||
@desc "The list of visibility options for a comment"
|
||||
enum :comment_visibility do
|
||||
value(:public, description: "Publicly listed and federated. Can be shared.")
|
||||
value(:unlisted, description: "Visible only to people with the link - or invited")
|
||||
|
||||
value(:private,
|
||||
description: "Visible only to people members of the group or followers of the person"
|
||||
)
|
||||
|
||||
value(:moderated, description: "Visible only after a moderator accepted")
|
||||
value(:invite, description: "visible only to people invited")
|
||||
end
|
||||
|
||||
object :comment_queries do
|
||||
@desc "Get replies for thread"
|
||||
field :thread, type: list_of(:comment) do
|
||||
arg(:id, :id)
|
||||
resolve(&Comment.get_thread/3)
|
||||
end
|
||||
end
|
||||
|
||||
object :comment_mutations do
|
||||
@desc "Create a comment"
|
||||
field :create_comment, type: :comment do
|
||||
arg(:text, non_null(:string))
|
||||
arg(:event_id, :id)
|
||||
arg(:in_reply_to_comment_id, :id)
|
||||
arg(:actor_id, non_null(:id))
|
||||
|
||||
resolve(&Comment.create_comment/3)
|
||||
end
|
||||
|
||||
field :delete_comment, type: :comment do
|
||||
arg(:comment_id, non_null(:id))
|
||||
arg(:actor_id, non_null(:id))
|
||||
|
||||
resolve(&Comment.delete_comment/3)
|
||||
end
|
||||
end
|
||||
end
|
||||
50
lib/graphql/schema/config.ex
Normal file
50
lib/graphql/schema/config.ex
Normal file
@@ -0,0 +1,50 @@
|
||||
defmodule Mobilizon.GraphQL.Schema.ConfigType do
|
||||
@moduledoc """
|
||||
Schema representation for User
|
||||
"""
|
||||
use Absinthe.Schema.Notation
|
||||
|
||||
alias Mobilizon.GraphQL.Resolvers.Config
|
||||
|
||||
@desc "A config object"
|
||||
object :config do
|
||||
# Instance name
|
||||
field(:name, :string)
|
||||
field(:description, :string)
|
||||
|
||||
field(:registrations_open, :boolean)
|
||||
field(:registrations_whitelist, :boolean)
|
||||
field(:demo_mode, :boolean)
|
||||
field(:country_code, :string)
|
||||
field(:location, :lonlat)
|
||||
field(:geocoding, :geocoding)
|
||||
field(:maps, :maps)
|
||||
end
|
||||
|
||||
object :lonlat do
|
||||
field(:longitude, :float)
|
||||
field(:latitude, :float)
|
||||
field(:accuracy_radius, :integer)
|
||||
end
|
||||
|
||||
object :geocoding do
|
||||
field(:autocomplete, :boolean)
|
||||
field(:provider, :string)
|
||||
end
|
||||
|
||||
object :maps do
|
||||
field(:tiles, :tiles)
|
||||
end
|
||||
|
||||
object :tiles do
|
||||
field(:endpoint, :string)
|
||||
field(:attribution, :string)
|
||||
end
|
||||
|
||||
object :config_queries do
|
||||
@desc "Get the instance config"
|
||||
field :config, :config do
|
||||
resolve(&Config.get_config/3)
|
||||
end
|
||||
end
|
||||
end
|
||||
38
lib/graphql/schema/custom/point.ex
Normal file
38
lib/graphql/schema/custom/point.ex
Normal file
@@ -0,0 +1,38 @@
|
||||
defmodule Mobilizon.GraphQL.Schema.Custom.Point do
|
||||
@moduledoc """
|
||||
The geom scalar type allows Geo.PostGIS.Geometry strings to be passed in and out.
|
||||
Requires `{:geo, "~> 3.0"},` package: https://github.com/elixir-ecto/ecto
|
||||
"""
|
||||
use Absinthe.Schema.Notation
|
||||
|
||||
scalar :point, name: "Point" do
|
||||
description("""
|
||||
The `Point` scalar type represents Point geographic information compliant string data,
|
||||
represented as floats separated by a semi-colon. The geodetic system is WGS 84
|
||||
""")
|
||||
|
||||
serialize(&encode/1)
|
||||
parse(&decode/1)
|
||||
end
|
||||
|
||||
@spec decode(Absinthe.Blueprint.Input.String.t()) :: {:ok, term} | :error
|
||||
@spec decode(Absinthe.Blueprint.Input.Null.t()) :: {:ok, nil}
|
||||
defp decode(%Absinthe.Blueprint.Input.String{value: value}) do
|
||||
with [_, _] = lonlat <- String.split(value, ";", trim: true),
|
||||
[{lon, ""}, {lat, ""}] <- Enum.map(lonlat, &Float.parse(&1)) do
|
||||
{:ok, %Geo.Point{coordinates: {lon, lat}, srid: 4326}}
|
||||
else
|
||||
_ -> :error
|
||||
end
|
||||
end
|
||||
|
||||
defp decode(%Absinthe.Blueprint.Input.Null{}) do
|
||||
{:ok, nil}
|
||||
end
|
||||
|
||||
defp decode(_) do
|
||||
:error
|
||||
end
|
||||
|
||||
defp encode(%Geo.Point{coordinates: {lon, lat}, srid: 4326}), do: "#{lon};#{lat}"
|
||||
end
|
||||
36
lib/graphql/schema/custom/uuid4.ex
Normal file
36
lib/graphql/schema/custom/uuid4.ex
Normal file
@@ -0,0 +1,36 @@
|
||||
defmodule Mobilizon.GraphQL.Schema.Custom.UUID do
|
||||
@moduledoc """
|
||||
The UUID4 scalar type allows UUID compliant strings to be passed in and out.
|
||||
Requires `{ :ecto, ">= 0.0.0" }` package: https://github.com/elixir-ecto/ecto
|
||||
"""
|
||||
use Absinthe.Schema.Notation
|
||||
|
||||
alias Ecto.UUID
|
||||
|
||||
scalar :uuid, name: "UUID" do
|
||||
description("""
|
||||
The `UUID` scalar type represents UUID4 compliant string data, represented as UTF-8
|
||||
character sequences. The UUID4 type is most often used to represent unique
|
||||
human-readable ID strings.
|
||||
""")
|
||||
|
||||
serialize(&encode/1)
|
||||
parse(&decode/1)
|
||||
end
|
||||
|
||||
@spec decode(Absinthe.Blueprint.Input.String.t()) :: {:ok, term} | :error
|
||||
@spec decode(Absinthe.Blueprint.Input.Null.t()) :: {:ok, nil}
|
||||
defp decode(%Absinthe.Blueprint.Input.String{value: value}) do
|
||||
UUID.cast(value)
|
||||
end
|
||||
|
||||
defp decode(%Absinthe.Blueprint.Input.Null{}) do
|
||||
{:ok, nil}
|
||||
end
|
||||
|
||||
defp decode(_) do
|
||||
:error
|
||||
end
|
||||
|
||||
defp encode(value), do: value
|
||||
end
|
||||
323
lib/graphql/schema/event.ex
Normal file
323
lib/graphql/schema/event.ex
Normal file
@@ -0,0 +1,323 @@
|
||||
defmodule Mobilizon.GraphQL.Schema.EventType do
|
||||
@moduledoc """
|
||||
Schema representation for Event.
|
||||
"""
|
||||
|
||||
use Absinthe.Schema.Notation
|
||||
|
||||
import Absinthe.Resolution.Helpers, only: [dataloader: 1]
|
||||
import Mobilizon.GraphQL.Helpers.Error
|
||||
|
||||
alias Mobilizon.{Actors, Addresses, Events}
|
||||
alias Mobilizon.GraphQL.Resolvers.{Event, Picture, Tag}
|
||||
alias Mobilizon.GraphQL.Schema
|
||||
|
||||
import_types(Schema.AddressType)
|
||||
import_types(Schema.Events.ParticipantType)
|
||||
import_types(Schema.TagType)
|
||||
|
||||
@desc "An event"
|
||||
object :event do
|
||||
interfaces([:action_log_object])
|
||||
field(:id, :id, description: "Internal ID for this event")
|
||||
field(:uuid, :uuid, description: "The Event UUID")
|
||||
field(:url, :string, description: "The ActivityPub Event URL")
|
||||
field(:local, :boolean, description: "Whether the event is local or not")
|
||||
field(:title, :string, description: "The event's title")
|
||||
field(:slug, :string, description: "The event's description's slug")
|
||||
field(:description, :string, description: "The event's description")
|
||||
field(:begins_on, :datetime, description: "Datetime for when the event begins")
|
||||
field(:ends_on, :datetime, description: "Datetime for when the event ends")
|
||||
field(:status, :event_status, description: "Status of the event")
|
||||
field(:visibility, :event_visibility, description: "The event's visibility")
|
||||
field(:join_options, :event_join_options, description: "The event's visibility")
|
||||
|
||||
field(:picture, :picture,
|
||||
description: "The event's picture",
|
||||
resolve: &Picture.picture/3
|
||||
)
|
||||
|
||||
field(:publish_at, :datetime, description: "When the event was published")
|
||||
|
||||
field(:physical_address, :address,
|
||||
resolve: dataloader(Addresses),
|
||||
description: "The type of the event's address"
|
||||
)
|
||||
|
||||
field(:online_address, :string, description: "Online address of the event")
|
||||
field(:phone_address, :string, description: "Phone address for the event")
|
||||
|
||||
field(:organizer_actor, :actor,
|
||||
resolve: dataloader(Actors),
|
||||
description: "The event's organizer (as a person)"
|
||||
)
|
||||
|
||||
field(:attributed_to, :actor, description: "Who the event is attributed to (often a group)")
|
||||
|
||||
field(:tags, list_of(:tag),
|
||||
resolve: &Tag.list_tags_for_event/3,
|
||||
description: "The event's tags"
|
||||
)
|
||||
|
||||
field(:category, :string, description: "The event's category")
|
||||
|
||||
field(:draft, :boolean, description: "Whether or not the event is a draft")
|
||||
|
||||
field(:participant_stats, :participant_stats)
|
||||
|
||||
field(:participants, list_of(:participant), description: "The event's participants") do
|
||||
arg(:page, :integer, default_value: 1)
|
||||
arg(:limit, :integer, default_value: 10)
|
||||
arg(:roles, :string, default_value: "")
|
||||
arg(:actor_id, :id)
|
||||
resolve(&Event.list_participants_for_event/3)
|
||||
end
|
||||
|
||||
field(:related_events, list_of(:event),
|
||||
resolve: &Event.list_related_events/3,
|
||||
description: "Events related to this one"
|
||||
)
|
||||
|
||||
field(:comments, list_of(:comment), description: "The comments in reply to the event") do
|
||||
resolve(dataloader(Events))
|
||||
end
|
||||
|
||||
# field(:tracks, list_of(:track))
|
||||
# field(:sessions, list_of(:session))
|
||||
|
||||
field(:updated_at, :datetime, description: "When the event was last updated")
|
||||
field(:created_at, :datetime, description: "When the event was created")
|
||||
field(:options, :event_options, description: "The event options")
|
||||
end
|
||||
|
||||
@desc "The list of visibility options for an event"
|
||||
enum :event_visibility do
|
||||
value(:public, description: "Publicly listed and federated. Can be shared.")
|
||||
value(:unlisted, description: "Visible only to people with the link - or invited")
|
||||
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
|
||||
|
||||
@desc "The list of join options for an event"
|
||||
enum :event_join_options do
|
||||
value(:free, description: "Anyone can join and is automatically accepted")
|
||||
value(:restricted, description: "Manual acceptation")
|
||||
value(:invite, description: "Participants must be invited")
|
||||
end
|
||||
|
||||
@desc "The list of possible options for the event's status"
|
||||
enum :event_status do
|
||||
value(:tentative, description: "The event is tentative")
|
||||
value(:confirmed, description: "The event is confirmed")
|
||||
value(:cancelled, description: "The event is cancelled")
|
||||
end
|
||||
|
||||
object :participant_stats do
|
||||
field(:going, :integer,
|
||||
description: "The number of approved participants",
|
||||
resolve: &Event.stats_participants_going/3
|
||||
)
|
||||
|
||||
field(:not_approved, :integer, description: "The number of not approved participants")
|
||||
field(:rejected, :integer, description: "The number of rejected participants")
|
||||
|
||||
field(:participant, :integer,
|
||||
description: "The number of simple participants (excluding creators)"
|
||||
)
|
||||
|
||||
field(:moderator, :integer, description: "The number of moderators")
|
||||
field(:administrator, :integer, description: "The number of administrators")
|
||||
field(:creator, :integer, description: "The number of creators")
|
||||
end
|
||||
|
||||
object :event_offer do
|
||||
field(:price, :float, description: "The price amount for this offer")
|
||||
field(:price_currency, :string, description: "The currency for this price offer")
|
||||
field(:url, :string, description: "The URL to access to this offer")
|
||||
end
|
||||
|
||||
object :event_participation_condition do
|
||||
field(:title, :string, description: "The title for this condition")
|
||||
field(:content, :string, description: "The content for this condition")
|
||||
field(:url, :string, description: "The URL to access this condition")
|
||||
end
|
||||
|
||||
input_object :event_offer_input do
|
||||
field(:price, :float, description: "The price amount for this offer")
|
||||
field(:price_currency, :string, description: "The currency for this price offer")
|
||||
field(:url, :string, description: "The URL to access to this offer")
|
||||
end
|
||||
|
||||
input_object :event_participation_condition_input do
|
||||
field(:title, :string, description: "The title for this condition")
|
||||
field(:content, :string, description: "The content for this condition")
|
||||
field(:url, :string, description: "The URL to access this condition")
|
||||
end
|
||||
|
||||
@desc "The list of possible options for the event's status"
|
||||
enum :event_comment_moderation do
|
||||
value(:allow_all, description: "Anyone can comment under the event")
|
||||
value(:moderated, description: "Every comment has to be moderated by the admin")
|
||||
value(:closed, description: "No one can comment except for the admin")
|
||||
end
|
||||
|
||||
object :event_options do
|
||||
field(:maximum_attendee_capacity, :integer,
|
||||
description: "The maximum attendee capacity for this event"
|
||||
)
|
||||
|
||||
field(:remaining_attendee_capacity, :integer,
|
||||
description: "The number of remaining seats for this event"
|
||||
)
|
||||
|
||||
field(:show_remaining_attendee_capacity, :boolean,
|
||||
description: "Whether or not to show the number of remaining seats for this event"
|
||||
)
|
||||
|
||||
field(:offers, list_of(:event_offer), description: "The list of offers to show for this event")
|
||||
|
||||
field(:participation_conditions, list_of(:event_participation_condition),
|
||||
description: "The list of participation conditions to accept to join this event"
|
||||
)
|
||||
|
||||
field(:attendees, list_of(:string), description: "The list of special attendees")
|
||||
field(:program, :string, description: "The list of the event")
|
||||
|
||||
field(:comment_moderation, :event_comment_moderation,
|
||||
description: "The policy on public comment moderation under the event"
|
||||
)
|
||||
|
||||
field(:show_participation_price, :boolean,
|
||||
description: "Whether or not to show the participation price"
|
||||
)
|
||||
|
||||
field(:show_start_time, :boolean, description: "Show event start time")
|
||||
field(:show_end_time, :boolean, description: "Show event end time")
|
||||
end
|
||||
|
||||
input_object :event_options_input do
|
||||
field(:maximum_attendee_capacity, :integer,
|
||||
description: "The maximum attendee capacity for this event"
|
||||
)
|
||||
|
||||
field(:remaining_attendee_capacity, :integer,
|
||||
description: "The number of remaining seats for this event"
|
||||
)
|
||||
|
||||
field(:show_remaining_attendee_capacity, :boolean,
|
||||
description: "Whether or not to show the number of remaining seats for this event"
|
||||
)
|
||||
|
||||
field(:offers, list_of(:event_offer_input),
|
||||
description: "The list of offers to show for this event"
|
||||
)
|
||||
|
||||
field(:participation_conditions, list_of(:event_participation_condition_input),
|
||||
description: "The list of participation conditions to accept to join this event"
|
||||
)
|
||||
|
||||
field(:attendees, list_of(:string), description: "The list of special attendees")
|
||||
field(:program, :string, description: "The list of the event")
|
||||
|
||||
field(:comment_moderation, :event_comment_moderation,
|
||||
description: "The policy on public comment moderation under the event"
|
||||
)
|
||||
|
||||
field(:show_participation_price, :boolean,
|
||||
description: "Whether or not to show the participation price"
|
||||
)
|
||||
|
||||
field(:show_start_time, :boolean, description: "Show event start time")
|
||||
field(:show_end_time, :boolean, description: "Show event end time")
|
||||
end
|
||||
|
||||
object :event_queries do
|
||||
@desc "Get all events"
|
||||
field :events, list_of(:event) do
|
||||
arg(:page, :integer, default_value: 1)
|
||||
arg(:limit, :integer, default_value: 10)
|
||||
resolve(&Event.list_events/3)
|
||||
end
|
||||
|
||||
@desc "Get an event by uuid"
|
||||
field :event, :event do
|
||||
arg(:uuid, non_null(:uuid))
|
||||
resolve(&Event.find_event/3)
|
||||
end
|
||||
end
|
||||
|
||||
object :event_mutations do
|
||||
@desc "Create an event"
|
||||
field :create_event, type: :event do
|
||||
arg(:title, non_null(:string))
|
||||
arg(:description, non_null(:string))
|
||||
arg(:begins_on, non_null(:datetime))
|
||||
arg(:ends_on, :datetime)
|
||||
arg(:status, :event_status)
|
||||
arg(:visibility, :event_visibility, default_value: :public)
|
||||
arg(:join_options, :event_join_options, default_value: :free)
|
||||
|
||||
arg(:tags, list_of(:string),
|
||||
default_value: [],
|
||||
description: "The list of tags associated to the event"
|
||||
)
|
||||
|
||||
arg(:picture, :picture_input,
|
||||
description:
|
||||
"The picture for the event, either as an object or directly the ID of an existing Picture"
|
||||
)
|
||||
|
||||
arg(:publish_at, :datetime)
|
||||
arg(:online_address, :string)
|
||||
arg(:phone_address, :string)
|
||||
arg(:organizer_actor_id, non_null(:id))
|
||||
arg(:category, :string, default_value: "meeting")
|
||||
arg(:physical_address, :address_input)
|
||||
arg(:options, :event_options_input)
|
||||
arg(:draft, :boolean, default_value: false)
|
||||
|
||||
resolve(handle_errors(&Event.create_event/3))
|
||||
end
|
||||
|
||||
@desc "Update an event"
|
||||
field :update_event, type: :event do
|
||||
arg(:event_id, non_null(:id))
|
||||
|
||||
arg(:title, :string)
|
||||
arg(:description, :string)
|
||||
arg(:begins_on, :datetime)
|
||||
arg(:ends_on, :datetime)
|
||||
arg(:status, :event_status)
|
||||
arg(:visibility, :event_visibility, default_value: :public)
|
||||
arg(:join_options, :event_join_options, default_value: :free)
|
||||
|
||||
arg(:tags, list_of(:string), description: "The list of tags associated to the event")
|
||||
|
||||
arg(:picture, :picture_input,
|
||||
description:
|
||||
"The picture for the event, either as an object or directly the ID of an existing Picture"
|
||||
)
|
||||
|
||||
arg(:online_address, :string)
|
||||
arg(:phone_address, :string)
|
||||
arg(:organizer_actor_id, :id)
|
||||
arg(:category, :string)
|
||||
arg(:physical_address, :address_input)
|
||||
arg(:options, :event_options_input)
|
||||
arg(:draft, :boolean)
|
||||
|
||||
resolve(handle_errors(&Event.update_event/3))
|
||||
end
|
||||
|
||||
@desc "Delete an event"
|
||||
field :delete_event, :deleted_object do
|
||||
arg(:event_id, non_null(:id))
|
||||
arg(:actor_id, non_null(:id))
|
||||
|
||||
resolve(&Event.delete_event/3)
|
||||
end
|
||||
end
|
||||
end
|
||||
53
lib/graphql/schema/events/feed_token.ex
Normal file
53
lib/graphql/schema/events/feed_token.ex
Normal file
@@ -0,0 +1,53 @@
|
||||
defmodule Mobilizon.GraphQL.Schema.Events.FeedTokenType do
|
||||
@moduledoc """
|
||||
Schema representation for Participant.
|
||||
"""
|
||||
|
||||
use Absinthe.Schema.Notation
|
||||
|
||||
import Absinthe.Resolution.Helpers, only: [dataloader: 1]
|
||||
|
||||
alias Mobilizon.{Actors, Users}
|
||||
alias Mobilizon.GraphQL.Resolvers.FeedToken
|
||||
|
||||
@desc "Represents a participant to an event"
|
||||
object :feed_token do
|
||||
field(
|
||||
:actor,
|
||||
:actor,
|
||||
resolve: dataloader(Actors),
|
||||
description: "The event which the actor participates in"
|
||||
)
|
||||
|
||||
field(
|
||||
:user,
|
||||
:user,
|
||||
resolve: dataloader(Users),
|
||||
description: "The actor that participates to the event"
|
||||
)
|
||||
|
||||
field(:token, :string, description: "The role of this actor at this event")
|
||||
end
|
||||
|
||||
@desc "Represents a deleted feed_token"
|
||||
object :deleted_feed_token do
|
||||
field(:user, :deleted_object)
|
||||
field(:actor, :deleted_object)
|
||||
end
|
||||
|
||||
object :feed_token_mutations do
|
||||
@desc "Create a Feed Token"
|
||||
field :create_feed_token, :feed_token do
|
||||
arg(:actor_id, :id)
|
||||
|
||||
resolve(&FeedToken.create_feed_token/3)
|
||||
end
|
||||
|
||||
@desc "Delete a feed token"
|
||||
field :delete_feed_token, :deleted_feed_token do
|
||||
arg(:token, non_null(:string))
|
||||
|
||||
resolve(&FeedToken.delete_feed_token/3)
|
||||
end
|
||||
end
|
||||
end
|
||||
76
lib/graphql/schema/events/participant.ex
Normal file
76
lib/graphql/schema/events/participant.ex
Normal file
@@ -0,0 +1,76 @@
|
||||
defmodule Mobilizon.GraphQL.Schema.Events.ParticipantType do
|
||||
@moduledoc """
|
||||
Schema representation for Participant.
|
||||
"""
|
||||
|
||||
use Absinthe.Schema.Notation
|
||||
|
||||
import Absinthe.Resolution.Helpers, only: [dataloader: 1]
|
||||
|
||||
alias Mobilizon.{Actors, Events}
|
||||
alias Mobilizon.GraphQL.Resolvers.Event
|
||||
|
||||
@desc "Represents a participant to an event"
|
||||
object :participant do
|
||||
field(:id, :id, description: "The participation ID")
|
||||
|
||||
field(
|
||||
:event,
|
||||
:event,
|
||||
resolve: dataloader(Events),
|
||||
description: "The event which the actor participates in"
|
||||
)
|
||||
|
||||
field(
|
||||
:actor,
|
||||
:actor,
|
||||
resolve: dataloader(Actors),
|
||||
description: "The actor that participates to the event"
|
||||
)
|
||||
|
||||
field(:role, :participant_role_enum, description: "The role of this actor at this event")
|
||||
end
|
||||
|
||||
enum :participant_role_enum do
|
||||
value(:not_approved)
|
||||
value(:participant)
|
||||
value(:moderator)
|
||||
value(:administrator)
|
||||
value(:creator)
|
||||
value(:rejected)
|
||||
end
|
||||
|
||||
@desc "Represents a deleted participant"
|
||||
object :deleted_participant do
|
||||
field(:id, :id)
|
||||
field(:event, :deleted_object)
|
||||
field(:actor, :deleted_object)
|
||||
end
|
||||
|
||||
object :participant_mutations do
|
||||
@desc "Join an event"
|
||||
field :join_event, :participant do
|
||||
arg(:event_id, non_null(:id))
|
||||
arg(:actor_id, non_null(:id))
|
||||
|
||||
resolve(&Event.actor_join_event/3)
|
||||
end
|
||||
|
||||
@desc "Leave an event"
|
||||
field :leave_event, :deleted_participant do
|
||||
arg(:event_id, non_null(:id))
|
||||
arg(:actor_id, non_null(:id))
|
||||
|
||||
resolve(&Event.actor_leave_event/3)
|
||||
end
|
||||
|
||||
@desc "Accept a participation"
|
||||
field :update_participation, :participant do
|
||||
arg(:id, non_null(:id))
|
||||
arg(:role, non_null(:participant_role_enum))
|
||||
arg(:moderator_actor_id, non_null(:id))
|
||||
|
||||
resolve(&Event.update_participation/3)
|
||||
end
|
||||
end
|
||||
end
|
||||
53
lib/graphql/schema/picture.ex
Normal file
53
lib/graphql/schema/picture.ex
Normal file
@@ -0,0 +1,53 @@
|
||||
defmodule Mobilizon.GraphQL.Schema.PictureType do
|
||||
@moduledoc """
|
||||
Schema representation for Pictures
|
||||
"""
|
||||
use Absinthe.Schema.Notation
|
||||
|
||||
alias Mobilizon.GraphQL.Resolvers.Picture
|
||||
|
||||
@desc "A picture"
|
||||
object :picture do
|
||||
field(:id, :id, description: "The picture's ID")
|
||||
field(:alt, :string, description: "The picture's alternative text")
|
||||
field(:name, :string, description: "The picture's name")
|
||||
field(:url, :string, description: "The picture's full URL")
|
||||
field(:content_type, :string, description: "The picture's detected content type")
|
||||
field(:size, :integer, description: "The picture's size")
|
||||
end
|
||||
|
||||
@desc "An attached picture or a link to a picture"
|
||||
input_object :picture_input do
|
||||
# Either a full picture object
|
||||
field(:picture, :picture_input_object)
|
||||
# Or directly the ID of an existing picture
|
||||
field(:picture_id, :id)
|
||||
end
|
||||
|
||||
@desc "An attached picture"
|
||||
input_object :picture_input_object do
|
||||
field(:name, non_null(:string))
|
||||
field(:alt, :string)
|
||||
field(:file, non_null(:upload))
|
||||
field(:actor_id, :id)
|
||||
end
|
||||
|
||||
object :picture_queries do
|
||||
@desc "Get a picture"
|
||||
field :picture, :picture do
|
||||
arg(:id, non_null(:string))
|
||||
resolve(&Picture.picture/3)
|
||||
end
|
||||
end
|
||||
|
||||
object :picture_mutations do
|
||||
@desc "Upload a picture"
|
||||
field :upload_picture, :picture do
|
||||
arg(:name, non_null(:string))
|
||||
arg(:alt, :string)
|
||||
arg(:file, non_null(:upload))
|
||||
arg(:actor_id, non_null(:id))
|
||||
resolve(&Picture.upload_picture/3)
|
||||
end
|
||||
end
|
||||
end
|
||||
105
lib/graphql/schema/report.ex
Normal file
105
lib/graphql/schema/report.ex
Normal file
@@ -0,0 +1,105 @@
|
||||
defmodule Mobilizon.GraphQL.Schema.ReportType do
|
||||
@moduledoc """
|
||||
Schema representation for User
|
||||
"""
|
||||
use Absinthe.Schema.Notation
|
||||
|
||||
import Absinthe.Resolution.Helpers, only: [dataloader: 1]
|
||||
|
||||
alias Mobilizon.Reports
|
||||
alias Mobilizon.GraphQL.Resolvers.Report
|
||||
|
||||
@desc "A report object"
|
||||
object :report do
|
||||
interfaces([:action_log_object])
|
||||
field(:id, :id, description: "The internal ID of the report")
|
||||
field(:content, :string, description: "The comment the reporter added about this report")
|
||||
field(:status, :report_status, description: "Whether the report is still active")
|
||||
field(:uri, :string, description: "The URI of the report")
|
||||
field(:reported, :actor, description: "The actor that is being reported")
|
||||
field(:reporter, :actor, description: "The actor that created the report")
|
||||
field(:event, :event, description: "The event that is being reported")
|
||||
field(:comments, list_of(:comment), description: "The comments that are reported")
|
||||
|
||||
field(:notes, list_of(:report_note),
|
||||
description: "The notes made on the event",
|
||||
resolve: dataloader(Reports)
|
||||
)
|
||||
|
||||
field(:inserted_at, :datetime, description: "When the report was created")
|
||||
field(:updated_at, :datetime, description: "When the report was updated")
|
||||
end
|
||||
|
||||
@desc "A report note object"
|
||||
object :report_note do
|
||||
interfaces([:action_log_object])
|
||||
field(:id, :id, description: "The internal ID of the report note")
|
||||
field(:content, :string, description: "The content of the note")
|
||||
|
||||
field(:moderator, :actor,
|
||||
description: "The moderator who added the note",
|
||||
resolve: dataloader(Reports)
|
||||
)
|
||||
|
||||
field(:report, :report, description: "The report on which this note is added")
|
||||
field(:inserted_at, :datetime, description: "When the report note was created")
|
||||
end
|
||||
|
||||
@desc "The list of possible statuses for a report object"
|
||||
enum :report_status do
|
||||
value(:open, description: "The report has been opened")
|
||||
value(:closed, description: "The report has been closed")
|
||||
value(:resolved, description: "The report has been marked as resolved")
|
||||
end
|
||||
|
||||
object :report_queries do
|
||||
@desc "Get all reports"
|
||||
field :reports, list_of(:report) do
|
||||
arg(:page, :integer, default_value: 1)
|
||||
arg(:limit, :integer, default_value: 10)
|
||||
arg(:status, :report_status, default_value: :open)
|
||||
resolve(&Report.list_reports/3)
|
||||
end
|
||||
|
||||
@desc "Get a report by id"
|
||||
field :report, :report do
|
||||
arg(:id, non_null(:id))
|
||||
resolve(&Report.get_report/3)
|
||||
end
|
||||
end
|
||||
|
||||
object :report_mutations do
|
||||
@desc "Create a report"
|
||||
field :create_report, type: :report do
|
||||
arg(:content, :string)
|
||||
arg(:reporter_id, non_null(:id))
|
||||
arg(:reported_id, non_null(:id))
|
||||
arg(:event_id, :id, default_value: nil)
|
||||
arg(:comments_ids, list_of(:id), default_value: [])
|
||||
arg(:forward, :boolean, default_value: false)
|
||||
resolve(&Report.create_report/3)
|
||||
end
|
||||
|
||||
@desc "Update a report"
|
||||
field :update_report_status, type: :report do
|
||||
arg(:report_id, non_null(:id))
|
||||
arg(:moderator_id, non_null(:id))
|
||||
arg(:status, non_null(:report_status))
|
||||
resolve(&Report.update_report/3)
|
||||
end
|
||||
|
||||
@desc "Create a note on a report"
|
||||
field :create_report_note, type: :report_note do
|
||||
arg(:content, :string)
|
||||
arg(:moderator_id, non_null(:id))
|
||||
arg(:report_id, non_null(:id))
|
||||
resolve(&Report.create_report_note/3)
|
||||
end
|
||||
|
||||
field :delete_report_note, type: :deleted_object do
|
||||
arg(:note_id, non_null(:id))
|
||||
arg(:moderator_id, non_null(:id))
|
||||
resolve(&Report.delete_report_note/3)
|
||||
end
|
||||
end
|
||||
end
|
||||
55
lib/graphql/schema/search.ex
Normal file
55
lib/graphql/schema/search.ex
Normal file
@@ -0,0 +1,55 @@
|
||||
defmodule Mobilizon.GraphQL.Schema.SearchType do
|
||||
@moduledoc """
|
||||
Schema representation for Search
|
||||
"""
|
||||
use Absinthe.Schema.Notation
|
||||
|
||||
alias Mobilizon.GraphQL.Resolvers.Search
|
||||
|
||||
@desc "Search persons result"
|
||||
object :persons do
|
||||
field(:total, non_null(:integer), description: "Total elements")
|
||||
field(:elements, non_null(list_of(:person)), description: "Person elements")
|
||||
end
|
||||
|
||||
@desc "Search groups result"
|
||||
object :groups do
|
||||
field(:total, non_null(:integer), description: "Total elements")
|
||||
field(:elements, non_null(list_of(:group)), description: "Group elements")
|
||||
end
|
||||
|
||||
@desc "Search events result"
|
||||
object :events do
|
||||
field(:total, non_null(:integer), description: "Total elements")
|
||||
field(:elements, non_null(list_of(:event)), description: "Event elements")
|
||||
end
|
||||
|
||||
object :search_queries do
|
||||
@desc "Search persons"
|
||||
field :search_persons, :persons do
|
||||
arg(:search, non_null(:string))
|
||||
arg(:page, :integer, default_value: 1)
|
||||
arg(:limit, :integer, default_value: 10)
|
||||
|
||||
resolve(&Search.search_persons/3)
|
||||
end
|
||||
|
||||
@desc "Search groups"
|
||||
field :search_groups, :groups do
|
||||
arg(:search, non_null(:string))
|
||||
arg(:page, :integer, default_value: 1)
|
||||
arg(:limit, :integer, default_value: 10)
|
||||
|
||||
resolve(&Search.search_groups/3)
|
||||
end
|
||||
|
||||
@desc "Search events"
|
||||
field :search_events, :events do
|
||||
arg(:search, non_null(:string))
|
||||
arg(:page, :integer, default_value: 1)
|
||||
arg(:limit, :integer, default_value: 10)
|
||||
|
||||
resolve(&Search.search_events/3)
|
||||
end
|
||||
end
|
||||
end
|
||||
12
lib/graphql/schema/sort.ex
Normal file
12
lib/graphql/schema/sort.ex
Normal file
@@ -0,0 +1,12 @@
|
||||
defmodule Mobilizon.GraphQL.Schema.SortType do
|
||||
@moduledoc """
|
||||
Allows sorting a collection of elements
|
||||
"""
|
||||
use Absinthe.Schema.Notation
|
||||
|
||||
@desc "Available sort directions"
|
||||
enum :sort_direction do
|
||||
value(:asc)
|
||||
value(:desc)
|
||||
end
|
||||
end
|
||||
31
lib/graphql/schema/tag.ex
Normal file
31
lib/graphql/schema/tag.ex
Normal file
@@ -0,0 +1,31 @@
|
||||
defmodule Mobilizon.GraphQL.Schema.TagType do
|
||||
@moduledoc """
|
||||
Schema representation for Tags
|
||||
"""
|
||||
use Absinthe.Schema.Notation
|
||||
|
||||
alias Mobilizon.GraphQL.Resolvers.Tag
|
||||
|
||||
@desc "A tag"
|
||||
object :tag do
|
||||
field(:id, :id, description: "The tag's ID")
|
||||
field(:slug, :string, description: "The tags's slug")
|
||||
field(:title, :string, description: "The tag's title")
|
||||
|
||||
field(
|
||||
:related,
|
||||
list_of(:tag),
|
||||
resolve: &Tag.get_related_tags/3,
|
||||
description: "Related tags to this tag"
|
||||
)
|
||||
end
|
||||
|
||||
object :tag_queries do
|
||||
@desc "Get the list of tags"
|
||||
field :tags, non_null(list_of(:tag)) do
|
||||
arg(:page, :integer, default_value: 1)
|
||||
arg(:limit, :integer, default_value: 10)
|
||||
resolve(&Tag.list_tags/3)
|
||||
end
|
||||
end
|
||||
end
|
||||
182
lib/graphql/schema/user.ex
Normal file
182
lib/graphql/schema/user.ex
Normal file
@@ -0,0 +1,182 @@
|
||||
defmodule Mobilizon.GraphQL.Schema.UserType do
|
||||
@moduledoc """
|
||||
Schema representation for User
|
||||
"""
|
||||
use Absinthe.Schema.Notation
|
||||
|
||||
import Absinthe.Resolution.Helpers, only: [dataloader: 1]
|
||||
import Mobilizon.GraphQL.Helpers.Error
|
||||
|
||||
alias Mobilizon.Events
|
||||
alias Mobilizon.GraphQL.Resolvers.User
|
||||
alias Mobilizon.GraphQL.Schema
|
||||
|
||||
import_types(Schema.SortType)
|
||||
|
||||
@desc "A local user of Mobilizon"
|
||||
object :user do
|
||||
field(:id, non_null(:id), description: "The user's ID")
|
||||
field(:email, non_null(:string), description: "The user's email")
|
||||
|
||||
field(:profiles, non_null(list_of(:person)),
|
||||
description: "The user's list of profiles (identities)"
|
||||
)
|
||||
|
||||
field(:default_actor, :person, description: "The user's default actor")
|
||||
|
||||
field(:confirmed_at, :datetime,
|
||||
description: "The datetime when the user was confirmed/activated"
|
||||
)
|
||||
|
||||
field(:confirmation_sent_at, :datetime,
|
||||
description: "The datetime the last activation/confirmation token was sent"
|
||||
)
|
||||
|
||||
field(:confirmation_token, :string, description: "The account activation/confirmation token")
|
||||
|
||||
field(:reset_password_sent_at, :datetime,
|
||||
description: "The datetime last reset password email was sent"
|
||||
)
|
||||
|
||||
field(:reset_password_token, :string,
|
||||
description: "The token sent when requesting password token"
|
||||
)
|
||||
|
||||
field(:feed_tokens, list_of(:feed_token),
|
||||
resolve: dataloader(Events),
|
||||
description: "A list of the feed tokens for this user"
|
||||
)
|
||||
|
||||
field(:role, :user_role, description: "The role for the user")
|
||||
|
||||
field(:locale, :string, description: "The user's locale")
|
||||
|
||||
field(:participations, list_of(:participant),
|
||||
description: "The list of participations this user has"
|
||||
) do
|
||||
arg(:after_datetime, :datetime)
|
||||
arg(:before_datetime, :datetime)
|
||||
arg(:page, :integer, default_value: 1)
|
||||
arg(:limit, :integer, default_value: 10)
|
||||
resolve(&User.user_participations/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
|
||||
end
|
||||
|
||||
enum :user_role do
|
||||
value(:administrator)
|
||||
value(:moderator)
|
||||
value(:user)
|
||||
end
|
||||
|
||||
@desc "Token"
|
||||
object :refreshed_token do
|
||||
field(:access_token, non_null(:string), description: "Generated access token")
|
||||
field(:refresh_token, non_null(:string), description: "Generated refreshed token")
|
||||
end
|
||||
|
||||
@desc "Users list"
|
||||
object :users do
|
||||
field(:total, non_null(:integer), description: "Total elements")
|
||||
field(:elements, non_null(list_of(:user)), description: "User elements")
|
||||
end
|
||||
|
||||
@desc "The list of possible options for the event's status"
|
||||
enum :sortable_user_field do
|
||||
value(:id)
|
||||
end
|
||||
|
||||
object :user_queries do
|
||||
@desc "Get an user"
|
||||
field :user, :user do
|
||||
arg(:id, non_null(:id))
|
||||
resolve(&User.find_user/3)
|
||||
end
|
||||
|
||||
@desc "Get the current user"
|
||||
field :logged_user, :user do
|
||||
resolve(&User.get_current_user/3)
|
||||
end
|
||||
|
||||
@desc "List instance users"
|
||||
field :users, :users do
|
||||
arg(:page, :integer, default_value: 1)
|
||||
arg(:limit, :integer, default_value: 10)
|
||||
|
||||
arg(:sort, :sortable_user_field, default_value: :id)
|
||||
arg(:direction, :sort_direction, default_value: :desc)
|
||||
|
||||
resolve(&User.list_and_count_users/3)
|
||||
end
|
||||
end
|
||||
|
||||
object :user_mutations do
|
||||
@desc "Create an user"
|
||||
field :create_user, type: :user do
|
||||
arg(:email, non_null(:string))
|
||||
arg(:password, non_null(:string))
|
||||
arg(:locale, :string)
|
||||
|
||||
resolve(handle_errors(&User.create_user/3))
|
||||
end
|
||||
|
||||
@desc "Validate an user after registration"
|
||||
field :validate_user, type: :login do
|
||||
arg(:token, non_null(:string))
|
||||
resolve(&User.validate_user/3)
|
||||
end
|
||||
|
||||
@desc "Resend registration confirmation token"
|
||||
field :resend_confirmation_email, type: :string do
|
||||
arg(:email, non_null(:string))
|
||||
arg(:locale, :string)
|
||||
resolve(&User.resend_confirmation_email/3)
|
||||
end
|
||||
|
||||
@desc "Send a link through email to reset user password"
|
||||
field :send_reset_password, type: :string do
|
||||
arg(:email, non_null(:string))
|
||||
arg(:locale, :string)
|
||||
resolve(&User.send_reset_password/3)
|
||||
end
|
||||
|
||||
@desc "Reset user password"
|
||||
field :reset_password, type: :login do
|
||||
arg(:token, non_null(:string))
|
||||
arg(:password, non_null(:string))
|
||||
arg(:locale, :string, default_value: "en")
|
||||
resolve(&User.reset_password/3)
|
||||
end
|
||||
|
||||
@desc "Login an user"
|
||||
field :login, type: :login do
|
||||
arg(:email, non_null(:string))
|
||||
arg(:password, non_null(:string))
|
||||
resolve(&User.login_user/3)
|
||||
end
|
||||
|
||||
@desc "Refresh a token"
|
||||
field :refresh_token, type: :refreshed_token do
|
||||
arg(:refresh_token, non_null(:string))
|
||||
resolve(&User.refresh_token/3)
|
||||
end
|
||||
|
||||
@desc "Change default actor for user"
|
||||
field :change_default_actor, :user do
|
||||
arg(:preferred_username, non_null(:string))
|
||||
resolve(&User.change_default_actor/3)
|
||||
end
|
||||
|
||||
@desc "Change an user password"
|
||||
field :change_password, :user do
|
||||
arg(:old_password, non_null(:string))
|
||||
arg(:new_password, non_null(:string))
|
||||
resolve(&User.change_password/3)
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user