lib/glific_web/schema.ex

defmodule GlificWeb.Schema do
  @moduledoc """
  This is the container for the top level Absinthe GraphQL schema which encapsulates the entire Glific Public API.
  This file is primarily a container and pulls in the relevant information for data type specific files.
  """

  use Absinthe.Schema
  import GlificWeb.Gettext

  alias Glific.Repo
  alias GlificWeb.Schema.Middleware

  import_types(Absinthe.Type.Custom)

  # Important: Needed to use the `:upload` type
  import_types(Absinthe.Plug.Types)

  import_types(__MODULE__.OrganizationTypes)
  import_types(__MODULE__.ProfileTypes)
  import_types(__MODULE__.ContactTypes)
  import_types(__MODULE__.ContactTagTypes)
  import_types(__MODULE__.EnumTypes)
  import_types(__MODULE__.GenericTypes)
  import_types(__MODULE__.LanguageTypes)
  import_types(__MODULE__.MessageTypes)
  import_types(__MODULE__.MessageMediaTypes)
  import_types(__MODULE__.MessageTagTypes)
  import_types(__MODULE__.CredentialTypes)
  import_types(__MODULE__.ProviderTypes)
  import_types(__MODULE__.SessionTemplateTypes)
  import_types(__MODULE__.TemplateTagTypes)
  import_types(__MODULE__.TagTypes)
  import_types(__MODULE__.UserTypes)
  import_types(__MODULE__.GroupTypes)
  import_types(__MODULE__.ContactGroupTypes)
  import_types(__MODULE__.UserGroupTypes)
  import_types(__MODULE__.SearchTypes)
  import_types(__MODULE__.FlowTypes)
  import_types(__MODULE__.TriggerTypes)
  import_types(__MODULE__.WebhookLogTypes)
  import_types(__MODULE__.NotificationTypes)
  import_types(__MODULE__.LocationTypes)
  import_types(__MODULE__.BillingTypes)
  import_types(__MODULE__.MediaTypes)
  import_types(__MODULE__.ConsultingHourTypes)
  import_types(__MODULE__.ContactsFieldTypes)
  import_types(__MODULE__.ExtensionTypes)
  import_types(__MODULE__.InteractiveTemplateTypes)
  import_types(__MODULE__.FlowLabelTypes)
  import_types(__MODULE__.RoleTypes)
  import_types(__MODULE__.SheetTypes)

  query do
    import_fields(:profile_queries)

    import_fields(:contact_queries)

    import_fields(:language_queries)

    import_fields(:message_queries)

    import_fields(:message_media_queries)

    import_fields(:organization_queries)

    import_fields(:credential_queries)

    import_fields(:provider_queries)

    import_fields(:session_template_queries)

    import_fields(:tag_queries)

    import_fields(:user_queries)

    import_fields(:group_queries)

    import_fields(:search_queries)

    import_fields(:flow_queries)

    import_fields(:trigger_queries)

    import_fields(:webhook_log_queries)

    import_fields(:notification_queries)

    import_fields(:location_queries)

    import_fields(:billing_queries)

    import_fields(:consulting_hours_queries)

    import_fields(:contacts_field_queries)

    import_fields(:extensions_queries)

    import_fields(:interactive_template_queries)

    import_fields(:flow_label_queries)

    import_fields(:access_role_queries)

    import_fields(:contact_group_queries)

    import_fields(:sheet_queries)
  end

  mutation do
    import_fields(:profile_mutations)

    import_fields(:contact_mutations)

    import_fields(:contact_tag_mutations)

    import_fields(:language_mutations)

    import_fields(:message_mutations)

    import_fields(:message_media_mutations)

    import_fields(:message_tag_mutations)

    import_fields(:organization_mutations)

    import_fields(:credential_mutations)

    import_fields(:provider_mutations)

    import_fields(:session_template_mutations)

    import_fields(:template_tag_mutations)

    import_fields(:tag_mutations)

    import_fields(:user_mutations)

    import_fields(:group_mutations)

    import_fields(:contact_group_mutations)

    import_fields(:user_group_mutations)

    import_fields(:search_mutations)

    import_fields(:flow_mutations)

    import_fields(:trigger_mutations)

    import_fields(:billing_mutations)

    import_fields(:media_mutations)

    import_fields(:consulting_hours_mutations)

    import_fields(:notification_mutations)

    import_fields(:contacts_field_mutations)

    import_fields(:extensions_mutations)

    import_fields(:interactive_template_mutations)

    import_fields(:access_role_mutations)

    import_fields(:sheet_mutations)
  end

  subscription do
    import_fields(:message_subscriptions)

    import_fields(:message_tag_subscriptions)

    import_fields(:organization_subscriptions)
  end

  @doc """
  Used to apply middleware on all or a group of fields based on pattern matching.

  It is passed the existing middleware for a field, the field itself, and the object that the field is a part of.
  """

  @spec middleware(
          [Absinthe.Middleware.spec(), ...],
          Absinthe.Type.Field.t(),
          Absinthe.Type.Object.t()
        ) :: [Absinthe.Middleware.spec(), ...]
  def middleware(middleware, _field, %{identifier: :mutation}),
    do: [Middleware.AddOrganization | middleware] ++ [Middleware.ChangesetErrors]

  def middleware(middleware, _field, %{identifier: :query}),
    do: [Middleware.AddOrganization | middleware] ++ [Middleware.QueryErrors]

  def middleware(middleware, _field, _object),
    do: middleware

  @spec repo_opts(map()) :: Keyword.t()
  defp repo_opts(%{current_user: user}) when user != nil,
    do: [organization_id: user.organization_id]

  defp repo_opts(_params), do: []

  @doc """
  Used to set some values in the context that we may need in order to run.
  We store the organization id and the current user in the context once the user has been
  authenticated
  """
  @spec context(map()) :: map()
  def context(ctx) do
    loader =
      Dataloader.new()
      |> Dataloader.add_source(
        Repo,
        Dataloader.Ecto.new(Repo, repo_opts: repo_opts(ctx))
      )

    Map.put(ctx, :loader, loader)
  end

  @doc """
  Used to define the list of plugins to run before and after resolution.
  """
  @spec plugins() :: [Absinthe.Plugin.t()]
  def plugins do
    [Absinthe.Middleware.Dataloader | Absinthe.Plugin.defaults()]
  end

  @doc """
  Validation function for all subscriptions received by the system
  """
  @spec config_fun(map(), map()) :: {:ok, Keyword.t()} | {:error, String.t()}
  def config_fun(args, %{context: %{current_user: user}}) do
    organization_id = args.organization_id

    if organization_id == Integer.to_string(user.organization_id) do
      {:ok, [topic: organization_id]}
    else
      {:error, dgettext("errors", "Auth Credentials mismatch")}
    end
  end
end