Split GraphQL as separate context

This commit is contained in:
rustra
2020-01-26 20:34:25 +01:00
parent a7acf17a4a
commit ba3ad713c0
55 changed files with 292 additions and 269 deletions

View 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

View 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

View 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

View 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

View 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

View 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

View 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
View 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

View 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

View 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

View 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

View 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
View 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

View 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

View 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

View 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

View 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

View 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

View 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
View 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
View 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