GraphQL Layer

This layer is a set of opinonated defaults applied to Absinthe. It receives JSON from the web layer containing GraphQL queries and transforms them into Service Context requests to the data layer.

architecture

Default Middleware

By default, Potionx adds the following middleware to schema.ex:

Potionx.Middleware.Mutation

This normalizes the results of mutations to return a map containing the result assigned to the node property:

For example, a user mutation would return:

{:ok, %{node: %SomeProject.Users.User{}}}

Potionx.Middleware.ServiceContext

This converts incoming variables to a Potionx.Context.Service struct.

For example:

%{
  changes: some_changes,
  filters: some_filters
}

gets converted to:

%Potionx.Context.Service{
  changes: some_changes,
  filters: some_filters
}

Potionx.Middleware.ScopeOrganization and Potionx.Middleware.ScopeUser

This middleware uses the data layer's repo.ex to set variables required for user/organization multitenancy based on the ideas from Ecto's Multi tenancy with foreign keys

Potionx.Middleware.UserRequired

Requires that a user be in the context unless the query is added as an exception.

Default Types

Potionx adds the following types by default which are used by the generators:

 enum :sort_order do
    value :asc
    value :desc
  end
  object :error do
    field :field, :string
    field :message, :string
  end
  scalar :global_id do
    parse fn
      %{value: v}, ctx ->
        case v do
          s when is_binary(s) ->
            Absinthe.Relay.Node.from_global_id(s, __MODULE__)
            |> case do
              {:ok, %{id: id}} -> {:ok, id}
              err -> err
            end
          default ->
            {:ok, default}
        end
      _, _ ->
        {:ok, nil}
    end

    serialize fn input ->
      input
    end
  end

File and Folder Naming Conventions

Potionx names GraphQL files for models as follows:

MODEL_mutations.ex # model mutation config
MODEL_queries.ex # queries for a single or many models
MODEL_types.ex # types related to the model

For example, the user files generated by default are:

user_mutations.ex
user_queries.ex
user_types.ex

Permissions

Potionx includes a Potionx.Middleware.RolesAuthorization module to handle authorization at the GraphQL level. It authorizes requests based on roles in the Potionx.Service.Context passed in. It is set to :admin for all mutations and queries by default.

For example:

defmodule SomeProjectGraphQl.Schema.UserQueries do
  use Absinthe.Schema.Notation
  use Absinthe.Relay.Schema.Notation, :modern

  object :user_queries do
    connection field :user_collection, node_type: :user do
      arg :filters, :user_filters
      arg :order, type: :sort_order, default_value: :asc
      middleware Potionx.Middleware.RolesAuthorization, [roles: [:admin]]
      resolve &SomeProjectGraphQl.Resolver.User.collection/2
    end

    field :user_single, type: :user do
      arg :filters, :user_filters_single
      middleware Potionx.Middleware.RolesAuthorization, [roles: [:admin]]
      resolve &SomeProjectGraphQl.Resolver.User.one/2
    end
  end
end

Collections and Relay

By default collection queries (requests several models) adhere to the Relay specification. Offset pagination is used at the moment but will be swapped to cursor pagination soon.